diff --git a/docs/config-catalog.md b/docs/config-catalog.md index da0bb41007..f4f0e6e691 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1353,7 +1353,7 @@ Requires: `subagents` ```ts config-catalog /** Config: how to spawn and drive the child SDK runtime process. */ export interface Config { - /** Provider name on `ctx.subagents` (default `sdk`). */ + /** Provider name on `ctx.subagents` (default `dsh-sdk`). */ providerName: string /** The executable to spawn for each run (the child runtime bin or packaged exe). */ command: string diff --git a/examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml b/examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml index a1e95fcddd..6511a0c3a4 100644 --- a/examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml +++ b/examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml @@ -12,10 +12,11 @@ - id: subagent name: '@deepseek-ai/dsh-subagent' +# providerName is omitted: the composition exercises the shipped default +# (`dsh-sdk`) through the real Loader. - id: subagent-sdk name: '@deepseek-ai/dsh-subagent-sdk' config: - providerName: sdk command: !!js process.env.DSH_TEST_CHILD_COMMAND args: !!js JSON.parse(process.env.DSH_TEST_CHILD_ARGS ?? '[]') provider: mock @@ -25,7 +26,7 @@ - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' config: - provider: sdk + provider: dsh-sdk toolName: subagent # The SDK backend advertises no depthLimit: the child harness owns its own # recursion budget, so the local numeric default cannot apply here. diff --git a/packages/subagent/subagent-sdk/README.i18n.yaml b/packages/subagent/subagent-sdk/README.i18n.yaml index b35d54db49..9e531c0406 100644 --- a/packages/subagent/subagent-sdk/README.i18n.yaml +++ b/packages/subagent/subagent-sdk/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: bc9b1ed7706d16d0a7463d0b26beb3ccf171ce5b -README.zh.md: 76eb4ffaf9854892e05ad891c7b951a838b89e8e +# pnpm run verify-translation-pairing --write packages/subagent/subagent-sdk/README.md +README.md: 92c31e4823c4f8a3b2526441ab01dc732fcecbdd +README.zh.md: 3610bfcf93ddd2f458640e6149d398f0f7f4b173 diff --git a/packages/subagent/subagent-sdk/README.md b/packages/subagent/subagent-sdk/README.md index bc9b1ed770..92c31e4823 100644 --- a/packages/subagent/subagent-sdk/README.md +++ b/packages/subagent/subagent-sdk/README.md @@ -26,7 +26,7 @@ The provider advertises no start-time capabilities (`outputSchema`/`depthLimit`/ | Key | Default | Meaning | |---|---|---| -| `providerName` | `sdk` | Registry name on `ctx.subagents`. | +| `providerName` | `dsh-sdk` | Registry name on `ctx.subagents`. | | `command` | required | Executable spawned per run (the child runtime bin or packaged exe). | | `args` | `[]` | Command arguments (typically the child's `cordis.yml` path). | | `cwd` | parent session cwd | Working-directory override; same validation as [`subagent-acp`](../subagent-acp/README.md). | @@ -41,14 +41,14 @@ The provider advertises no start-time capabilities (`outputSchema`/`depthLimit`/ - id: subagent-sdk name: '@deepseek-ai/dsh-subagent-sdk' config: - providerName: sdk + providerName: dsh-sdk command: node args: ['./packages/examples/jsonrpc-demo/lib/bin.js', './examples/jsonrpc-agent/cordis.yml'] env: DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' - config: { provider: sdk, toolName: subagent, maxDepth: 'provider-managed' } + config: { provider: dsh-sdk, toolName: subagent, maxDepth: 'provider-managed' } ``` ## Process boundary diff --git a/packages/subagent/subagent-sdk/README.zh.md b/packages/subagent/subagent-sdk/README.zh.md index 76eb4ffaf9..3610bfcf93 100644 --- a/packages/subagent/subagent-sdk/README.zh.md +++ b/packages/subagent/subagent-sdk/README.zh.md @@ -26,7 +26,7 @@ Provider 不宣告任何启动期能力(`outputSchema`/`depthLimit`/`toolFilte | 键 | 默认 | 含义 | |---|---|---| -| `providerName` | `sdk` | `ctx.subagents` 上的注册名。 | +| `providerName` | `dsh-sdk` | `ctx.subagents` 上的注册名。 | | `command` | 必填 | 每次 run 生成的可执行文件(子运行时 bin 或打包 exe)。 | | `args` | `[]` | 命令参数(通常是子进程的 `cordis.yml` 路径)。 | | `cwd` | 父会话 cwd | 工作目录覆盖;校验规则与 [`subagent-acp`](../subagent-acp/README.md) 相同。 | @@ -41,14 +41,14 @@ Provider 不宣告任何启动期能力(`outputSchema`/`depthLimit`/`toolFilte - id: subagent-sdk name: '@deepseek-ai/dsh-subagent-sdk' config: - providerName: sdk + providerName: dsh-sdk command: node args: ['./packages/examples/jsonrpc-demo/lib/bin.js', './examples/jsonrpc-agent/cordis.yml'] env: DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' - config: { provider: sdk, toolName: subagent, maxDepth: 'provider-managed' } + config: { provider: dsh-sdk, toolName: subagent, maxDepth: 'provider-managed' } ``` ## 进程边界 diff --git a/packages/subagent/subagent-sdk/src/index.ts b/packages/subagent/subagent-sdk/src/index.ts index 7165ad05ad..b4fdcabadc 100644 --- a/packages/subagent/subagent-sdk/src/index.ts +++ b/packages/subagent/subagent-sdk/src/index.ts @@ -27,7 +27,7 @@ export const inject = ['subagents'] /** Config: how to spawn and drive the child SDK runtime process. */ export interface Config { - /** Provider name on `ctx.subagents` (default `sdk`). */ + /** Provider name on `ctx.subagents` (default `dsh-sdk`). */ providerName: string /** The executable to spawn for each run (the child runtime bin or packaged exe). */ command: string @@ -67,7 +67,7 @@ export interface Config { } export const Config: z = z.object({ - providerName: z.string().default('sdk'), + providerName: z.string().default('dsh-sdk'), command: z.string().required(), args: z.array(z.string()).default([]), cwd: z.string(), diff --git a/packages/subagent/subagent-sdk/tests/loader-composition.e2e.ts b/packages/subagent/subagent-sdk/tests/loader-composition.e2e.ts index 4fe435b311..8e6a499d4e 100644 --- a/packages/subagent/subagent-sdk/tests/loader-composition.e2e.ts +++ b/packages/subagent/subagent-sdk/tests/loader-composition.e2e.ts @@ -15,7 +15,7 @@ import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import { type SessionEvent } from '@deepseek-ai/dsh-session' -import { LOADER_SMOKE_TEST_TIMEOUT_MS, resolveExampleLaunch, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' +import { resolveExampleLaunch, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' const fixtureDir = new URL('../../../../examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/', import.meta.url) const driver = fileURLToPath(new URL('driver.ts', fixtureDir)) @@ -60,6 +60,10 @@ describe('SDK subagent cwd inheritance through a real cordis.yml', () => { libBinScript: driver, configPath, tsconfigPath: repoTsconfig, + // Two complete harness runtimes boot in sequence (driver, then the SDK + // child); from-source tsx boots under load need more than the default + // 30s window. + processTimeoutMs: 120_000, env: { DSH_TEST_CHILD_COMMAND: childLaunch.command, DSH_TEST_CHILD_ARGS: JSON.stringify(childLaunch.args), @@ -97,5 +101,7 @@ describe('SDK subagent cwd inheritance through a real cordis.yml', () => { expect(childEvents.some(event => event.type === 'user/message')).toBe(true) const childAnswers = childEvents.filter(event => event.type === 'assistant/message') expect(childAnswers.length).toBeGreaterThan(0) - }, LOADER_SMOKE_TEST_TIMEOUT_MS) + // 15s of vitest headroom past the subprocess deadline, mirroring + // LOADER_SMOKE_TEST_TIMEOUT_MS's margin over the default window. + }, 135_000) }) diff --git a/packages/subagent/subagent-sdk/tests/subagent-sdk.spec.ts b/packages/subagent/subagent-sdk/tests/subagent-sdk.spec.ts index a2d49de343..872b375e27 100644 --- a/packages/subagent/subagent-sdk/tests/subagent-sdk.spec.ts +++ b/packages/subagent/subagent-sdk/tests/subagent-sdk.spec.ts @@ -37,8 +37,11 @@ function request(text = 'p', signal = new AbortController().signal) { async function setup(fakeEnv: Record = {}, config: Partial = {}) { const ctx = new Context() await ctx.plugin(SubagentService) + // The Config type models the post-validation shape, so the default registry + // name is stated here; the Loader-composition fixture omits providerName and + // exercises the schemastery default end to end. await ctx.plugin(sdk, { - providerName: 'sdk', + providerName: 'dsh-sdk', command: process.execPath, args: [fakeRuntime], provider: 'fake-provider', @@ -84,7 +87,7 @@ describe('sdkStopReason', () => { describe('dsh-subagent-sdk provider', () => { it('runs a child turn end to end with a parent-unique run id', async () => { const ctx = await setup({ FAKE_TEXT: 'hello from sdk child' }) - const run = await ctx.subagents.start('sdk', request('do X')) + const run = await ctx.subagents.start('dsh-sdk', request('do X')) expect(run.localAgent).toBeUndefined() const result = await run.result expect(result.stopReason).toBe('completed') @@ -94,7 +97,7 @@ describe('dsh-subagent-sdk provider', () => { expect(run.dispose()).toBe(disposal) await disposal - const nextRun = await ctx.subagents.start('sdk', request('again')) + const nextRun = await ctx.subagents.start('dsh-sdk', request('again')) expect(nextRun.id).not.toBe(run.id) await nextRun.result await nextRun.dispose() @@ -106,7 +109,7 @@ describe('dsh-subagent-sdk provider', () => { const recordFile = join(tmp, 'init.jsonl') try { const ctx = await setup({ FAKE_RECORD_INIT: recordFile }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) await run.result await run.dispose() const { readFileSync } = await import('node:fs') @@ -126,7 +129,7 @@ describe('dsh-subagent-sdk provider', () => { DEEPSEEK_API_KEY: 'explicit-child-key', FAKE_TEXT: 'done', }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) const result = await run.result const answer = text(result.output) expect(answer).toContain('DSH_TEST_AMBIENT_SECRET_KEY=\n') @@ -140,7 +143,7 @@ describe('dsh-subagent-sdk provider', () => { it('maps a max-tokens child turn end', async () => { const ctx = await setup({ FAKE_REASON_KIND: 'max-tokens', FAKE_STATUS: 'error' }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) expect((await run.result).stopReason).toBe('max-tokens') await run.dispose() await ctx.fiber.dispose() @@ -148,7 +151,7 @@ describe('dsh-subagent-sdk provider', () => { it('flattens a child turn error into stopReason error and keeps partial text', async () => { const ctx = await setup({ FAKE_REASON_KIND: 'error', FAKE_STATUS: 'error', FAKE_TEXT: 'partial answer' }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) const result = await run.result expect(result.stopReason).toBe('error') expect(text(result.output)).toBe('partial answer') @@ -158,7 +161,7 @@ describe('dsh-subagent-sdk provider', () => { it('reports a settled-without-turn child as an error', async () => { const ctx = await setup({ FAKE_REASON_KIND: 'none', FAKE_STATUS: 'error' }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) expect((await run.result).stopReason).toBe('error') await run.dispose() await ctx.fiber.dispose() @@ -167,7 +170,7 @@ describe('dsh-subagent-sdk provider', () => { it('aborting the required signal settles a hung child as aborted', async () => { const ctx = await setup({ FAKE_HANG_PROMPT: '1' }, { disposeEofGraceMs: 200, disposeGraceMs: 200 }) const controller = new AbortController() - const run = await ctx.subagents.start('sdk', request('p', controller.signal)) + const run = await ctx.subagents.start('dsh-sdk', request('p', controller.signal)) controller.abort('test') const result = await run.result expect(result.stopReason).toBe('aborted') @@ -215,7 +218,7 @@ describe('dsh-subagent-sdk provider', () => { // the failure settles, so the accumulated partial text (no complete // assistant/message ever arrived) must survive into the error result. const ctx = await setup({ FAKE_STREAM_THEN_MALFORMED: '1' }, { shutdownTimeoutMs: 100, disposeEofGraceMs: 200, disposeGraceMs: 200 }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) const result = await run.result expect(result.stopReason).toBe('error') expect(text(result.output)).toBe('streamed then cut short') @@ -225,7 +228,7 @@ describe('dsh-subagent-sdk provider', () => { it('dispose cancels a hung child locally and reaps it', async () => { const ctx = await setup({ FAKE_HANG_PROMPT: '1' }, { shutdownTimeoutMs: 100, disposeEofGraceMs: 200, disposeGraceMs: 200 }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) await run.dispose() expect((await run.result).stopReason).toBe('aborted') await ctx.fiber.dispose() @@ -260,7 +263,7 @@ describe('dsh-subagent-sdk provider', () => { it('rejects after reaping when the child dies before the handshake', async () => { const ctx = await setup({ FAKE_EXIT_BEFORE_INIT: '1', FAKE_STDERR: 'scripted boot failure' }) - const failure = await ctx.subagents.start('sdk', request()).then( + const failure = await ctx.subagents.start('dsh-sdk', request()).then( () => { throw new Error('start unexpectedly succeeded') }, (error: unknown) => error, ) @@ -318,10 +321,10 @@ describe('dsh-subagent-sdk provider', () => { const ctx = await setup({ FAKE_MALFORMED_PROMPT: '1' }) const warnings: string[] = [] ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof ctx.logger.warn - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) expect((await run.result).stopReason).toBe('error') expect(warnings).toHaveLength(1) - expect(warnings[0]).toContain('subagent-sdk "sdk": child run failed (error)') + expect(warnings[0]).toContain('subagent-sdk "dsh-sdk": child run failed (error)') await run.dispose() await ctx.fiber.dispose() }) @@ -379,7 +382,7 @@ describe('dsh-subagent-sdk provider', () => { const tmp = mkdtempSync(join(tmpdir(), 'subagent-sdk-cwd-')) try { const ctx = await setup({ FAKE_ECHO_CWD: '1', FAKE_TEXT: 'done' }, { cwd: tmp }) - const run = await ctx.subagents.start('sdk', request()) + const run = await ctx.subagents.start('dsh-sdk', request()) const result = await run.result const { realpathSync } = await import('node:fs') expect(text(result.output)).toContain(`cwd=${realpathSync(tmp)}`) @@ -393,7 +396,7 @@ describe('dsh-subagent-sdk provider', () => { it('fails loud when neither config cwd nor parent session cwd exists', async () => { const ctx = await setup() const parent = { id: 'parent', session: { header: {} } } as unknown as Agent - await expect(ctx.subagents.start('sdk', { prompt: [{ type: 'text' as const, text: 'p' }], parent, signal: new AbortController().signal })) + await expect(ctx.subagents.start('dsh-sdk', { prompt: [{ type: 'text' as const, text: 'p' }], parent, signal: new AbortController().signal })) .rejects.toThrow('no working directory for the child') await ctx.fiber.dispose() })