Merge branch 'feat/plugin-mgmt-3-settings' into feat/plugin-mgmt-4-web

This commit is contained in:
Yichen Jiang committed 2026-09-11 16:53:06 +08:00
commit 4264b1d9f2
6 files changed
+65 -14

No files matched your search

+9 -5
View File
@@ -1,3 +1,4 @@
import { stageStartupBundle } from './startup-bundle.ts'
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -411,7 +412,8 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
'',
].join('\n'))
try {
const result = await runBuiltBin(['--profile', 'sdk', '--patch', patch], {
stageStartupBundle(home, 'sdk', patch)
const result = await runBuiltBin(['--profile', 'sdk'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
DEEPSEEK_API_KEY: 'built-sdk-startup-failure-no-call',
@@ -757,18 +759,20 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}
}, SPAWN_TIMEOUT_MS + 30_000)
it('keeps serving when an optional patch-overlay plugin fails', async () => {
it('rejects an external bundle that corrupts a builtin provider', async () => {
const home = mkdtempSync(join(tmpdir(), 'dsh-invalid-patch-'))
try {
const result = await runBuiltBin(['--profile', 'web', '--patch', invalidProvider, '--port', '0', '--no-open'], {
stageStartupBundle(home, 'web', invalidProvider)
const result = await runBuiltBin(['--profile', 'web', '--port', '0', '--no-open'], {
DSH_HOME: home,
DSH_BROWSER_OPEN_TEST_EXIT_ON_READY: '1',
DEEPSEEK_API_KEY: 'keyless-invalid-config',
DSH_TELEMETRY_DISABLED: '1',
NODE_OPTIONS: `--import=${webReadyExitHook}`,
})
expect(result.code, result.stderr).toBe(0)
expect(result.stdout).toMatch(/^dsh web: http:\/\/127\.0\.0\.1:\d+\/\?token=[A-Za-z0-9_-]+$/u)
expect(result.code, result.stderr).toBe(1)
expect(result.stdout).not.toContain('dsh web: http://')
expect(result.stderr).toContain('required startup failure')
expect(result.stderr).toContain('llm-pi-ai')
} finally {
rmSync(home, { recursive: true, force: true })
+1 -1
View File
@@ -1,4 +1,4 @@
# Invalid optional provider override used to prove best-effort startup continues.
# Invalid override of a builtin provider: an external patch retains the target's required startup policy.
- id: llm-pi-ai
config:
@@ -17,6 +17,7 @@ import {
scanZstdFrames,
} from '@deepseek-ai/dsh-session-persistence-jsonl/src/zstd.ts'
import { describe, expect, it } from 'vitest'
import { stageStartupBundle } from '../../../startup-bundle.ts'
const goldensDir = fileURLToPath(new URL('./expected/', import.meta.url))
const goalScenarioDir = join(goldensDir, 'goal-tools')
@@ -277,10 +278,10 @@ describe('headless stream-json snapshots', () => {
tempDirPrefix: 'headless-snapshot-startup-error-',
binScript: dshBinScript,
configPath: startupFailureConfigPath,
prepare: (cwd) => { stageStartupBundle(join(cwd, '.dsh'), 'headless', startupFailureConfigPath) },
binArgs: [
'--profile', 'headless',
'--patch', headlessOverlayPath,
'--patch', startupFailureConfigPath,
'Complete the task despite the unrelated startup failure.',
],
tsconfigPath,
@@ -8,6 +8,7 @@ import type { Readable } from 'node:stream'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it } from 'vitest'
import { stageStartupBundle } from '../../../startup-bundle.ts'
const repoRoot = fileURLToPath(new URL('../../../../../../', import.meta.url))
const dshBin = join(repoRoot, 'apps/cli/lib/bin.js')
@@ -46,6 +47,7 @@ function createFixture(): Fixture {
'}',
'',
].join('\n'))
writeFileSync(join(root, 'schema-failure.mjs'), 'export const Config = { "~standard": { version: 1, vendor: "startup", validate() { return { issues: [{ message: "web schema failure" }] } } } }; export function apply() {}\n')
writeFileSync(join(root, 'sync-failure.mjs'), 'export function apply() { throw new Error("web sync apply failure") }\n')
writeFileSync(join(root, 'async-failure.mjs'), [
'export async function apply() {',
@@ -61,10 +63,9 @@ function createFixture(): Fixture {
].join('\n'))
const patch = join(root, 'failures.patch.yml')
writeFileSync(patch, [
'- id: tool-todo',
' disabled: false',
' config: {}',
'- insert:',
' - id: web-probe-schema-failure',
` name: ${pathToFileURL(join(root, 'schema-failure.mjs')).href}`,
' - id: web-probe-good',
` name: ${pathToFileURL(join(root, 'good.mjs')).href}`,
' config:',
@@ -134,10 +135,10 @@ async function waitForStartup(
describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', () => {
it('serves the full Web app while unrelated entries fail to start', async () => {
const fixture = createFixture()
stageStartupBundle(fixture.home, 'web', fixture.patch)
const child = execa(process.execPath, [
dshBin,
'--profile', 'web',
'--patch', fixture.patch,
'--no-open',
'--port', '0',
], {
@@ -169,7 +170,7 @@ describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', ()
expect(html).toContain('__DSH_BOOT__')
expect(readFileSync(fixture.events, 'utf8')).toBe('good apply\n')
expect(startup.stderr).toContain('web-probe-import-failure')
expect(startup.stderr).toContain('@deepseek-ai/dsh-tool-todo')
expect(startup.stderr).toContain('web schema failure')
expect(startup.stderr).toContain('web sync apply failure')
expect(startup.stderr).toContain('web async apply failure')
expect(startup.stderr).toContain('pending (waiting for service: webProbeMissingService)')
@@ -316,11 +317,11 @@ describe.skipIf(!builtArtifactsExist)('dsh Web profile best-effort startup', ()
` name: ${pathToFileURL(plugin).href}`,
'',
].join('\n'))
stageStartupBundle(fixture.home, 'web', fixture.patch)
try {
const result = await execa(process.execPath, [
dshBin,
'--profile', 'web',
'--patch', fixture.patch,
'--no-open',
'--port', '0',
], {
@@ -8,6 +8,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it } from 'vitest'
import { FiberState } from '@deepseek-ai/cordis'
import { stageStartupBundle } from '../../../startup-bundle.ts'
const repoRoot = fileURLToPath(new URL('../../../../../../', import.meta.url))
const bin = join(repoRoot, 'apps/cli/lib/bin.js')
@@ -172,6 +173,8 @@ describe.skipIf(!built)('Web process failure matrix', () => {
it.each(failures)(`${required ? 'required' : 'optional'} startup %s`, async (failure, diagnostic) => {
const f = fixture()
writeFileSync(f.patch, f.render(id, failure))
stageStartupBundle(f.home, 'web', f.patch, required ? 'boot' : 'runtime')
writeFileSync(f.patch, '[]\n')
const app = start(f)
try {
if (required) {
@@ -360,7 +363,11 @@ describe.skipIf(!built)('Web process failure matrix', () => {
const patch = JSON.stringify([{ insert: [f.observer, f.witness(0), {
id: 'matrix-optional-http', name: pathToFileURL(plugin).href, config: { port: address.port },
}] }]) + '\n'
if (phase === 'startup') writeFileSync(f.patch, patch)
if (phase === 'startup') {
writeFileSync(f.patch, patch)
stageStartupBundle(f.home, 'web', f.patch)
writeFileSync(f.patch, '[]\n')
}
app = start(f)
const running = app
await app.serves()
+38
View File
@@ -0,0 +1,38 @@
/** Installed bundle provenance for product-profile startup acceptance tests. */
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import {
initProfile, loadOverlayPatches, PROFILE_TEMPLATES, readProfileManifest, writeProfileManifest, type ProfileLayer,
} from '@deepseek-ai/dsh-app-boot'
/**
* Install a patch as a real profile dependency, preserving its module resolution anchors.
* @param home - the scenario's isolated Harness home.
* @param profile - the shipped profile under test.
* @param patchFile - the fixture patch whose relative module names resolve at its source.
* @param stage - whether this external bundle is required or optional at startup.
*/
export function stageStartupBundle(
home: string,
profile: 'web' | 'sdk' | 'headless',
patchFile: string,
stage: ProfileLayer['stage'] = 'runtime',
): void {
const dir = join(home, 'profiles', profile)
const template = PROFILE_TEMPLATES[profile]
if (template === undefined) throw new Error(`Missing shipped profile template: ${profile}`)
if (!existsSync(join(dir, 'package.json'))) initProfile(dir, template.bundles, template.patchReload)
const name = '@fixture/startup-policy'
const bundle = join(dir, 'node_modules', name)
mkdirSync(bundle, { recursive: true })
writeFileSync(join(bundle, 'package.json'), JSON.stringify({
name, version: '1.0.0', private: true, type: 'module', dsh: { bundle: { patch: './cordis.patch.json', stage } },
}))
writeFileSync(join(bundle, 'cordis.patch.json'), JSON.stringify(loadOverlayPatches('startup fixture', patchFile)))
const manifest = readProfileManifest('startup fixture', dir)
writeProfileManifest(dir, {
...manifest,
dependencies: { ...manifest.dependencies, [name]: '1.0.0' },
dsh: { ...manifest.dsh, profile: { ...manifest.dsh?.profile, bundles: [...manifest.dsh?.profile?.bundles ?? [], name] } },
})
}