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:
8 files changed
+42
-32
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
```
|
||||
|
||||
## 进程边界
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user