fix: address human review — default providerName dsh-sdk; raise composition-e2e deadline

- subagent-sdk: the default registry name becomes `dsh-sdk` (the bare
  `sdk` read ambiguously in configs); READMEs, config catalog, fixture,
  and suites follow. The Loader fixture now omits providerName to exercise
  the shipped default end to end.
- loader-composition.e2e: two full harness runtimes boot in sequence, so
  the default 30s loader-smoke window times out under host load; raise the
  subprocess deadline to 120s with matching vitest headroom (the
  real-model.e2e precedent).
This commit is contained in:
Tianyi Cui
2026-07-27 18:25:18 +08:00
parent cf2b9e211d
commit 6c8bf0a522
8 files changed
+42 -32

No files matched your search

+1 -1
View File
@@ -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
@@ -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.
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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' }
```
## 进程边界
+2 -2
View File
@@ -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<Config> = 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(),
@@ -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)
})
@@ -37,8 +37,11 @@ function request(text = 'p', signal = new AbortController().signal) {
async function setup(fakeEnv: Record<string, string> = {}, config: Partial<sdk.Config> = {}) {
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()
})