diff --git a/apps/web/tests/sidebar-subagent-activity.e2e.ts b/apps/web/tests/sidebar-subagent-activity.e2e.ts index f2c8eb3434..c3cd1214b9 100644 --- a/apps/web/tests/sidebar-subagent-activity.e2e.ts +++ b/apps/web/tests/sidebar-subagent-activity.e2e.ts @@ -101,10 +101,8 @@ describe('web e2e: sidebar subagent activity', () => { provider: 'spawn', label: 'sidebar activity child', signal: new AbortController().signal, - request: { - prompt: [{ type: 'text', text: 'Hold this delegated task open.' }], - parent: parentHandle.agent, - }, + prompt: [{ type: 'text', text: 'Hold this delegated task open.' }], + parent: parentHandle.agent, }) childId = started.childId await waitForRunningChild(scaffold, adapter, childId) diff --git a/apps/web/tests/subagent-conversation.e2e.ts b/apps/web/tests/subagent-conversation.e2e.ts index ad5aabded3..b42353b76b 100644 --- a/apps/web/tests/subagent-conversation.e2e.ts +++ b/apps/web/tests/subagent-conversation.e2e.ts @@ -152,10 +152,8 @@ describe('web e2e: persisted subagent conversation and human continuation', () = provider: 'spawn', label: LABEL, signal: new AbortController().signal, - request: { - prompt: [{ type: 'text', text: INITIAL_PROMPT }], - parent, - }, + prompt: [{ type: 'text', text: INITIAL_PROMPT }], + parent, }) childId = started.childId await waitForAgentToSettle(scaffold, childId) diff --git a/apps/web/tests/subagent-fork-schedule.snapshot.ts b/apps/web/tests/subagent-fork-schedule.snapshot.ts index 62f42cb69a..7f155c8a35 100644 --- a/apps/web/tests/subagent-fork-schedule.snapshot.ts +++ b/apps/web/tests/subagent-fork-schedule.snapshot.ts @@ -48,7 +48,7 @@ it.skipIf(MODE === 'record')('replays inherited schedule tools and refusals in a const activation = await scaffold.ctx.subagents.startActivation({ provider: 'fork', label: 'Check child reminder refusal', delivery: 'caller', signal: new AbortController().signal, - request: { parent, prompt: [...childPrompt.data.content] }, + parent, prompt: [...childPrompt.data.content], }) try { await activation.result diff --git a/apps/web/tests/subagent-interrupt-ui.e2e.ts b/apps/web/tests/subagent-interrupt-ui.e2e.ts index 954491d56e..aec5df8ec4 100644 --- a/apps/web/tests/subagent-interrupt-ui.e2e.ts +++ b/apps/web/tests/subagent-interrupt-ui.e2e.ts @@ -153,7 +153,7 @@ describe.skipIf(MODE === 'record')('web e2e: composer interrupt for a running co provider: 'spawn', label: LABEL, signal: new AbortController().signal, - request: { prompt: [{ type: 'text', text: INITIAL }], parent }, + prompt: [{ type: 'text', text: INITIAL }], parent, }) childId = started.childId await waitFor(() => existsSync(readyFile), 'the held child turn to open') diff --git a/apps/web/tests/subagent-interrupt.e2e.ts b/apps/web/tests/subagent-interrupt.e2e.ts index 2de00f6685..92ca9cde62 100644 --- a/apps/web/tests/subagent-interrupt.e2e.ts +++ b/apps/web/tests/subagent-interrupt.e2e.ts @@ -113,7 +113,7 @@ describe.skipIf(MODE === 'record')('web e2e: subagents/interruptByUser over the provider: 'spawn', label: 'event-sourcing researcher', signal: new AbortController().signal, - request: { prompt: [{ type: 'text', text: INITIAL }], parent }, + prompt: [{ type: 'text', text: INITIAL }], parent, }) childId = started.childId // The hang entry writes readyFile after its prefix chunks, immediately diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 089970df47..e727ccfb8b 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -45,5 +45,5 @@ en: 85b38f78d87fc88f zh: 00d5eb625e7dfb61 /development-guide/contributor-reference/documenting-types-verbatim-ts-type-equiv: - en: 9d578b6797be921b - zh: 823cbfcfd9a75c17 + en: 6b9b948e0b80cf76 + zh: 2642f4127ed9b719 diff --git a/docs/development.md b/docs/development.md index 8b7c6a8e46..d27e95332c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -214,4 +214,4 @@ The [subsystems](subsystems/README.md) pages paste source-equivalent declaration { "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` -`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `"projection": "public-api"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact type definition. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change. +`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `"projection": "public-api"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact type definition. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. Upgrade guides in version directories older than the root package version are frozen; their examples are excluded from current-API compilation. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change. diff --git a/docs/development.zh.md b/docs/development.zh.md index fc445f0ab0..a87cf5b91f 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -221,4 +221,4 @@ Web 命令后面的参数会传给 `dsh web`,例如 `pnpm run dev:web --no-ope { "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` -`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码约定和确切类型定义。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例的计算之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。 +`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码约定和确切类型定义。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例的计算之外。版本目录早于根包版本的升级指南已冻结,其中的示例不参与当前 API 的编译检查。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。 diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index bf72076ec1..508178bfc2 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -6,8 +6,8 @@ en: 6af28b3c7e92cf9f zh: 210f2c490dc23d3c /subagents/capabilities-and-activation-requests: - en: 8c92b131f67997f6 - zh: c2dc606a0859420c + en: f09270fcd7bfa26a + zh: 082664920b3c65e2 /subagents/local-children-and-activations: en: 6f87231d18d82916 zh: 9f064b23058f8766 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index 7e1e037c84..153c74d60b 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -8,7 +8,7 @@ The unified activation API manages local conversations and external executions. Every backend uses startActivation. Capability flags validate request options; prepareContinuable selects local Agent creation, while start selects one external execution. The model-facing tool uses parent delivery. Workflow and code mode consumers can await caller results without injecting another parent message. Historical children with unavailable descriptors remain discoverable as `mode: 'unknown'` without granting continuation capabilities; the [package README](../../packages/subagent/subagent/README.md) defines catalog persistence. -Optional `request.cwd` selects the child's initial directory; relative paths resolve against the parent's current effective directory. Omitting it captures that directory at admission. Local child Sessions retain the parent's original project, and cold resume retains their logged directory state. +Optional `cwd` selects the child's initial directory; relative paths resolve against the parent's current effective directory. Omitting it captures that directory at admission. Local child Sessions retain the parent's original project, and cold resume retains their logged directory state. ```ts type-equiv /** @@ -28,15 +28,13 @@ interface SubagentCapabilities { ```ts type-equiv /** One managed child execution and its result delivery policy. */ -interface SubagentActivationSpec { +interface SubagentActivationSpec extends Omit { /** Registered backend to use. */ readonly provider: string /** Short task label retained in cataloged child membership. */ readonly label: string /** Optional reserved identity for a local child; external backends allocate their own ids. */ readonly childId?: SessionId - /** Task, parent, and backend-supported execution options. */ - readonly request: Omit /** Cancellation before publication; callers own later cancellation through dispose. */ readonly signal: AbortSignal /** Parent delivery notifies the model; caller delivery only returns the result. Local children always enter the parent catalog. */ @@ -63,9 +61,8 @@ interface SubagentActivation { ```ts type-equiv /** - * Task and optional capabilities supplied to a backend. startActivation carries - * these fields in request while owning its label, cancellation, and delivery - * policy separately. + * Task and optional capabilities supplied to a backend. The activation spec + * shares these task options and defines its own label and cancellation policy. */ interface SubagentStartRequest { /** Initial child directory; relative paths resolve against the parent's current directory. Omitted inherits that directory at start. */ diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index 5213088104..35fb760aad 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -8,7 +8,7 @@ 所有后端都使用 startActivation。能力标记校验请求选项;prepareContinuable 选择本地 Agent 创建,start 选择一次外部执行。面向模型的工具采用 parent 回传。Workflow 与 code mode 消费者可以等待 caller 结果,不额外向父代理注入消息。历史子会话的 descriptor 不可用时,以 `mode: 'unknown'` 保持可发现性,但不授予继续执行能力;[包 README](../../packages/subagent/subagent/README.zh.md) 定义目录持久化语义。 -可选的 `request.cwd` 指定子级的初始目录;相对路径以父级当前有效目录为基准解析。省略时,在准入阶段捕获该目录。本地子 Session 保留父级的原始项目,冷恢复保留其日志中的目录状态。 +可选的 `cwd` 指定子级的初始目录;相对路径以父级当前有效目录为基准解析。省略时,在准入阶段捕获该目录。本地子 Session 保留父级的原始项目,冷恢复保留其日志中的目录状态。 ```ts type-equiv /** @@ -28,15 +28,13 @@ interface SubagentCapabilities { ```ts type-equiv /** One managed child execution and its result delivery policy. */ -interface SubagentActivationSpec { +interface SubagentActivationSpec extends Omit { /** Registered backend to use. */ readonly provider: string /** Short task label retained in cataloged child membership. */ readonly label: string /** Optional reserved identity for a local child; external backends allocate their own ids. */ readonly childId?: SessionId - /** Task, parent, and backend-supported execution options. */ - readonly request: Omit /** Cancellation before publication; callers own later cancellation through dispose. */ readonly signal: AbortSignal /** Parent delivery notifies the model; caller delivery only returns the result. Local children always enter the parent catalog. */ @@ -63,9 +61,8 @@ interface SubagentActivation { ```ts type-equiv /** - * Task and optional capabilities supplied to a backend. startActivation carries - * these fields in request while owning its label, cancellation, and delivery - * policy separately. + * Task and optional capabilities supplied to a backend. The activation spec + * shares these task options and defines its own label and cancellation policy. */ interface SubagentStartRequest { /** Initial child directory; relative paths resolve against the parent's current directory. Omitted inherits that directory at start. */ diff --git a/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.i18n.yaml new file mode 100644 index 0000000000..12139cd087 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.md +/: + en: b087162d9a24f772 + zh: a32f167c3529fb1c +/subagent-activation-options: + en: 20a5b01ef11bff72 + zh: 919de21bc865ee9e +/subagent-activation-options/change: + en: 7cc2554992e98c53 + zh: 434540bcd037d1f5 +/subagent-activation-options/migration: + en: 2243d08661eb457c + zh: a928a8e7fb3515ff diff --git a/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.md b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.md new file mode 100644 index 0000000000..3f2a202b0c --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "SubagentActivationSpec accepts task options directly instead of a nested request object." +--- + +# Subagent activation options + +English | [中文](guide.zh.md) + +## Change + +Plugin code calling `ctx.subagents.startActivation()` must pass `prompt`, `parent`, and optional execution settings directly on `SubagentActivationSpec`. The nested `request` field is removed. Type names, provider requests, cancellation, and result delivery are unchanged. + +## Migration + +1. In each `startActivation()` call, move the fields from `request` to the outer object. For an existing task object, replace `request: task` with `...task`; keep the activation's `label` and `signal` after the spread so they take precedence. +2. Replace reads such as `spec.request.prompt` with `spec.prompt`. Keep backend `SubagentStartRequest` implementations unchanged. +3. Typecheck the plugin and run its delegation tests to verify the updated calls. diff --git a/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.zh.md new file mode 100644 index 0000000000..32c2785bcc --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "SubagentActivationSpec 直接接受任务选项,不再使用嵌套的 request 对象。" +--- + +# 子代理 activation 选项 + +[English](guide.md) | 中文 + +## 变更 + +调用 `ctx.subagents.startActivation()` 的插件代码必须将 `prompt`、`parent` 和可选执行设置直接放在 `SubagentActivationSpec` 上。嵌套的 `request` 字段已移除。类型名称、提供方请求、取消和结果投递保持不变。 + +## 迁移 + +1. 在每个 `startActivation()` 调用中,将 `request` 内的字段移到外层对象。已有任务对象时,将 `request: task` 替换为 `...task`;将 activation 的 `label` 和 `signal` 放在展开操作之后,以确保它们优先生效。 +2. 将 `spec.request.prompt` 等读取替换为 `spec.prompt`。保持后端的 `SubagentStartRequest` 实现不变。 +3. 对插件运行类型检查和委派测试,验证更新后的调用。 diff --git a/packages/acp/acp/tests/turns.spec.ts b/packages/acp/acp/tests/turns.spec.ts index fa482ded1e..39eb9f784e 100644 --- a/packages/acp/acp/tests/turns.spec.ts +++ b/packages/acp/acp/tests/turns.spec.ts @@ -50,7 +50,7 @@ async function installDelegationTool(ctx: BridgeHarness['ctx']): Promise<() => b if (exec.agent === undefined) throw new Error('delegation fixture requires an Agent') const child = await ctx.subagents.startActivation({ provider: 'local-test', label: 'later child', delivery: 'caller', signal: exec.signal, - request: { parent: exec.agent, prompt: [{ type: 'text', text: 'later child task' }] }, + parent: exec.agent, prompt: [{ type: 'text', text: 'later child task' }], }) const result = await child.result delegated = result.stopReason === 'completed' diff --git a/packages/api/session-controller/tests/subagent-access.host.spec.ts b/packages/api/session-controller/tests/subagent-access.host.spec.ts index 1ae549a427..ad077c695b 100644 --- a/packages/api/session-controller/tests/subagent-access.host.spec.ts +++ b/packages/api/session-controller/tests/subagent-access.host.spec.ts @@ -54,7 +54,7 @@ async function harness() { const parent = await ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' }) const run = await ctx.subagents.startActivation({ provider: 'test', label: 'access target', delivery: 'caller', - request: { parent, prompt: [{ type: 'text', text: 'hold' }] }, + parent, prompt: [{ type: 'text', text: 'hold' }], signal: new AbortController().signal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) diff --git a/packages/bundle/headless/tests/headless.spec.ts b/packages/bundle/headless/tests/headless.spec.ts index 066c58ac63..bb795d7dba 100644 --- a/packages/bundle/headless/tests/headless.spec.ts +++ b/packages/bundle/headless/tests/headless.spec.ts @@ -381,7 +381,7 @@ describe('headless runner', () => { if (turns === 1) { await test.ctx.subagents.startActivation({ provider: 'external', label: 'work', delivery: 'parent', signal: new AbortController().signal, - request: { parent, prompt: [] }, + parent, prompt: [], }) await cleaning.promise appendTurn(session, 1, message, 'waiting for child', true) diff --git a/packages/experimental/agent-team/tests/persistence.spec.ts b/packages/experimental/agent-team/tests/persistence.spec.ts index b9daf65464..a7ff070bf2 100644 --- a/packages/experimental/agent-team/tests/persistence.spec.ts +++ b/packages/experimental/agent-team/tests/persistence.spec.ts @@ -120,7 +120,7 @@ for (const backend of backends) { const started = await first.ctx.subagents.startActivation({ provider: 'spawn', label: 'restart worker', - request: { prompt: [{ type: 'text', text: 'finish before restart' }], parent: firstLead }, + prompt: [{ type: 'text', text: 'finish before restart' }], parent: firstLead, signal: SIGNAL, delivery: 'caller', }) diff --git a/packages/experimental/agent-team/tests/team.spec.ts b/packages/experimental/agent-team/tests/team.spec.ts index 5f8b0ac753..3b196f4eb4 100644 --- a/packages/experimental/agent-team/tests/team.spec.ts +++ b/packages/experimental/agent-team/tests/team.spec.ts @@ -77,7 +77,7 @@ async function spawn(ctx: Context, lead: Agent, description: string | undefined, const started = await ctx.subagents.startActivation({ provider, label: `${description ?? 'shared'} responsibility`, - request: { prompt: content(`${description ?? 'shared'} initial`), parent: lead }, + prompt: content(`${description ?? 'shared'} initial`), parent: lead, signal: SIGNAL, delivery: 'parent', }) @@ -151,7 +151,7 @@ describe('Tree-owned task boards', () => { const unnamed = await waitRunning(ctx, await spawn(ctx, lead, undefined)) const grandchildId = (await ctx.subagents.startActivation({ provider: 'spawn', label: 'nested', delivery: 'caller', signal: SIGNAL, - request: { prompt: content('nested'), parent: named }, + prompt: content('nested'), parent: named, })).childId const grandchild = await waitRunning(ctx, grandchildId) diff --git a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts index 33d381843e..b8ca8acf78 100644 --- a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts +++ b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts @@ -129,7 +129,7 @@ async function spawn(ctx: Context, lead: Agent, description: string | undefined, const started = await ctx.subagents.startActivation({ provider, label: `${description ?? 'shared'} task`, - request: { prompt: [{ type: 'text', text: prompt }], parent: lead }, + prompt: [{ type: 'text', text: prompt }], parent: lead, signal: SIGNAL, delivery: 'parent', }) diff --git a/packages/experimental/tool-subagent-oar/src/index.ts b/packages/experimental/tool-subagent-oar/src/index.ts index a5ca008a7f..c73af97901 100644 --- a/packages/experimental/tool-subagent-oar/src/index.ts +++ b/packages/experimental/tool-subagent-oar/src/index.ts @@ -88,11 +88,9 @@ export function apply(ctx: Context, config: Config): void { const activation = await ctx.subagents.startActivation({ provider: providerName, label: args.description, - request: { - parent, - prompt: [{ type: 'text', text: args.prompt }], - ...args.cwd === undefined ? {} : { cwd: args.cwd }, - }, + parent, + prompt: [{ type: 'text', text: args.prompt }], + ...args.cwd === undefined ? {} : { cwd: args.cwd }, signal: exec.signal, delivery: 'parent', }) diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index 50de87cd98..4155c0662a 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -8092,7 +8092,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SubagentActivationSpec', - declaration: 'export interface SubagentActivationSpec {\n readonly provider: string;\n readonly label: string;\n readonly childId?: SessionId;\n readonly request: Omit;\n readonly signal: AbortSignal;\n readonly delivery: \'parent\' | \'caller\';\n}', + declaration: 'export interface SubagentActivationSpec extends Omit {\n readonly provider: string;\n readonly label: string;\n readonly childId?: SessionId;\n readonly signal: AbortSignal;\n readonly delivery: \'parent\' | \'caller\';\n}', }, { name: 'SubagentCapabilities', diff --git a/packages/sdk/server/tests/built-scope-carrier.e2e.ts b/packages/sdk/server/tests/built-scope-carrier.e2e.ts index a9caf27ebf..3fc87176fd 100644 --- a/packages/sdk/server/tests/built-scope-carrier.e2e.ts +++ b/packages/sdk/server/tests/built-scope-carrier.e2e.ts @@ -72,7 +72,7 @@ try { childId: SessionId("built-child"), label: "built scope carrier", delivery: "caller", - request: { parent: parent.agent, prompt: [{ type: "text", text: "Use the unavailable test model." }] }, + parent: parent.agent, prompt: [{ type: "text", text: "Use the unavailable test model." }], signal: new AbortController().signal, }); await run.result; diff --git a/packages/sdk/server/tests/shutdown.spec.ts b/packages/sdk/server/tests/shutdown.spec.ts index 60810a29f6..29627e5ff3 100644 --- a/packages/sdk/server/tests/shutdown.spec.ts +++ b/packages/sdk/server/tests/shutdown.spec.ts @@ -51,7 +51,7 @@ describe('SDK descendant shutdown', () => { gates.set(id, gate) const receipt = await ctx.subagents.startActivation({ provider: 'local-test', childId: id, label: 'parked child', delivery: 'caller', signal: new AbortController().signal, - request: { parent, prompt: [{ type: 'text', text: 'work' }] }, + parent, prompt: [{ type: 'text', text: 'work' }], }) receipts.push(receipt) await gate.entered.promise diff --git a/packages/sdk/server/tests/wait.spec.ts b/packages/sdk/server/tests/wait.spec.ts index d78f4579a5..7f5e6adedf 100644 --- a/packages/sdk/server/tests/wait.spec.ts +++ b/packages/sdk/server/tests/wait.spec.ts @@ -66,7 +66,7 @@ describe('SDK session/wait with parked child input', () => { for (let level = 0; level < depth; level++) { const started = await ctx.subagents.startActivation({ provider: 'local-test', label: 'interruptible child', delivery: 'caller', signal: new AbortController().signal, - request: { parent: owner, prompt: [{ type: 'text', text: 'work' }] }, + parent: owner, prompt: [{ type: 'text', text: 'work' }], }) closeChildren ??= () => started.dispose() child = ctx.agents.get(started.childId)! @@ -118,7 +118,7 @@ describe('SDK session/wait failures', () => { const warnings = vi.spyOn(ctx.logger, 'warn') const child = await ctx.subagents.startActivation({ provider: 'local-test', label: 'child failure', delivery: 'caller', signal: new AbortController().signal, - request: { parent, prompt: [{ type: 'text', text: 'delegated task' }] }, + parent, prompt: [{ type: 'text', text: 'delegated task' }], }) await expect(child.result).resolves.toMatchObject({ stopReason: 'error' }) expect(observed).toEqual([failure]) diff --git a/packages/subagent/subagent-spawn-in-process/README.i18n.yaml b/packages/subagent/subagent-spawn-in-process/README.i18n.yaml index d444d26d6d..0ee0a2191a 100644 --- a/packages/subagent/subagent-spawn-in-process/README.i18n.yaml +++ b/packages/subagent/subagent-spawn-in-process/README.i18n.yaml @@ -9,8 +9,8 @@ en: c949befcfe1e376c zh: 13c78d0fbbd323ae /deepseek-ai-dsh-subagent-spawn-in-process/summary: - en: 84d0a0acfc4251fe - zh: 8480ddf68e377522 + en: 36ecbffd1d9ac412 + zh: 609a2d30499785fc /deepseek-ai-dsh-subagent-spawn-in-process/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb diff --git a/packages/subagent/subagent-spawn-in-process/README.md b/packages/subagent/subagent-spawn-in-process/README.md index c828c4f921..bd5a08b848 100644 --- a/packages/subagent/subagent-spawn-in-process/README.md +++ b/packages/subagent/subagent-spawn-in-process/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-subagent-spawn-in-process` is an in-process subagent backend: it runs each delegated task in a fresh child agent that shares this process and its agent factory, LLM, and tool services. The child starts with an empty conversation, so a task prompt must stand alone; it inherits the parent's working directory, session lineage, provider, model, reasoning effort, and output-token limit unless `request.agentOptions` overrides them. A delegation tool or API call reaches it under the `spawn` provider name. Choose it for the cheapest delegation transport; choose the fork backend when the child must build on the parent's completed conversation turns. +`dsh-subagent-spawn-in-process` is an in-process subagent backend: it runs each delegated task in a fresh child agent that shares this process and its agent factory, LLM, and tool services. The child starts with an empty conversation, so a task prompt must stand alone; it inherits the parent's working directory, session lineage, provider, model, reasoning effort, and output-token limit unless `agentOptions` overrides them. A delegation tool or API call reaches it under the `spawn` provider name. Choose it for the cheapest delegation transport; choose the fork backend when the child must build on the parent's completed conversation turns. ## Table of Contents diff --git a/packages/subagent/subagent-spawn-in-process/README.zh.md b/packages/subagent/subagent-spawn-in-process/README.zh.md index 19d94ed7a1..5133d18837 100644 --- a/packages/subagent/subagent-spawn-in-process/README.zh.md +++ b/packages/subagent/subagent-spawn-in-process/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -`dsh-subagent-spawn-in-process` 是一个进程内 subagent 后端:它在当前进程中运行每个委派任务,子 agent(智能体)是一个全新子 `Agent`,复用宿主的 agent 工厂及 LLM(大语言模型)/工具服务。子 agent 以空对话开始,因此任务提示词必须自足;除非 `request.agentOptions` 覆盖,否则它继承父 agent 的工作目录、会话谱系、提供方、模型、推理强度与输出 token 上限。委派工具或 API 调用以 `spawn` 提供方名称找到它。需要成本最低的委派传输时选择它;需要子 agent 建立在父级已完成对话轮次之上时,请选择 fork 后端。 +`dsh-subagent-spawn-in-process` 是一个进程内 subagent 后端:它在当前进程中运行每个委派任务,子 agent(智能体)是一个全新子 `Agent`,复用宿主的 agent 工厂及 LLM(大语言模型)/工具服务。子 agent 以空对话开始,因此任务提示词必须自足;除非 `agentOptions` 覆盖,否则它继承父 agent 的工作目录、会话谱系、提供方、模型、推理强度与输出 token 上限。委派工具或 API 调用以 `spawn` 提供方名称找到它。需要成本最低的委派传输时选择它;需要子 agent 建立在父级已完成对话轮次之上时,请选择 fork 后端。 ## 目录 diff --git a/packages/subagent/subagent/README.i18n.yaml b/packages/subagent/subagent/README.i18n.yaml index 3c57e421aa..0752cd7951 100644 --- a/packages/subagent/subagent/README.i18n.yaml +++ b/packages/subagent/subagent/README.i18n.yaml @@ -27,8 +27,8 @@ en: 4b40bd380bea81ce zh: 896a584190981168 /deepseek-ai-dsh-subagent/use-this-package/local-and-external-children: - en: 013644290e74493e - zh: 05014c3c3773edeb + en: d418617831da8105 + zh: 049116154b1c2080 /deepseek-ai-dsh-subagent/use-this-package/child-presets: en: a740e5e9bdd661d9 zh: 712a5458fc8b3615 diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index 773bd18709..9756acc0ee 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -58,7 +58,7 @@ At capacity, creation or cold resume rejects with `ACTIVATION_LIMIT_REACHED` (br ### Local and external children -`startActivation({ provider, label, request, signal, delivery })` returns `{ childId, messageId?, result, dispose }`. Supplying `childId` selects a local backend and guarantees `messageId`. Local children accept later messages and can cold-resume; external children expose one execution and reject continuation. `request.agentOptions` overrides supported child model settings. `delivery: 'parent'` supplies the model-facing completion notice; `delivery: 'caller'` lets workflows await the result without a parent notice or initial return guidance. Local parent delivery requires persistence; caller delivery can use an ephemeral local session. Cold resume requires persistence and Session query. +`startActivation({ provider, label, prompt, parent, signal, delivery })` returns `{ childId, messageId?, result, dispose }`. Supplying `childId` selects a local backend and guarantees `messageId`. Local children accept later messages and can cold-resume; external children expose one execution and reject continuation. `agentOptions` overrides supported child model settings. `delivery: 'parent'` supplies the model-facing completion notice; `delivery: 'caller'` lets workflows await the result without a parent notice or initial return guidance. Local parent delivery requires persistence; caller delivery can use an ephemeral local session. Cold resume requires persistence and Session query. ### Child presets diff --git a/packages/subagent/subagent/README.zh.md b/packages/subagent/subagent/README.zh.md index 280bf6d401..ca4c09d598 100644 --- a/packages/subagent/subagent/README.zh.md +++ b/packages/subagent/subagent/README.zh.md @@ -58,7 +58,7 @@ kind: "package-reference" ### 本地与外部子级 -`startActivation({ provider, label, request, signal, delivery })` 返回 `{ childId, messageId?, result, dispose }`。提供 `childId` 会选择本地后端,并保证返回 `messageId`。本地子级接受后续消息并支持冷恢复;外部子级只暴露一次执行,拒绝继续输入。`request.agentOptions` 覆盖后端支持的子级模型设置。`delivery: 'parent'` 提供面向模型的完成通知;`delivery: 'caller'` 让工作流等待结果,不发送父级通知,也不附加初始返回指导。本地父级投递需要持久化,调用方投递可以使用临时本地会话;冷恢复需要持久化与 Session 查询服务。 +`startActivation({ provider, label, prompt, parent, signal, delivery })` 返回 `{ childId, messageId?, result, dispose }`。提供 `childId` 会选择本地后端,并保证返回 `messageId`。本地子级接受后续消息并支持冷恢复;外部子级只暴露一次执行,拒绝继续输入。`agentOptions` 覆盖后端支持的子级模型设置。`delivery: 'parent'` 提供面向模型的完成通知;`delivery: 'caller'` 让工作流等待结果,不发送父级通知,也不附加初始返回指导。本地父级投递需要持久化,调用方投递可以使用临时本地会话;冷恢复需要持久化与 Session 查询服务。 ### 子级 preset diff --git a/packages/subagent/subagent/src/index.ts b/packages/subagent/subagent/src/index.ts index a2ed8c2771..d29d2da258 100644 --- a/packages/subagent/subagent/src/index.ts +++ b/packages/subagent/subagent/src/index.ts @@ -278,13 +278,13 @@ export class SubagentRuntime extends TypertRemoteService { if (spec.childId !== undefined && provider.prepareContinuable === undefined) { throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY') } - assertSubagentMaxDepth(spec.request.maxDepth) - this.assertCapabilities(provider, { ...spec.request, signal: spec.signal }) + assertSubagentMaxDepth(spec.maxDepth) + this.assertCapabilities(provider, spec) const manager = this.requireManager() - if (this.ctx.get('agents')?.get(spec.request.parent.id) !== spec.request.parent) { + if (this.ctx.get('agents')?.get(spec.parent.id) !== spec.parent) { throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED') } - if (spec.request.outputSchema !== undefined) assertObjectJsonSchema(spec.request.outputSchema) + if (spec.outputSchema !== undefined) assertObjectJsonSchema(spec.outputSchema) return provider.prepareContinuable === undefined ? manager.startExternal(spec) : manager.startLocal(spec) diff --git a/packages/subagent/subagent/src/manager.ts b/packages/subagent/subagent/src/manager.ts index c4d441b93d..f0cf7ff2ba 100644 --- a/packages/subagent/subagent/src/manager.ts +++ b/packages/subagent/subagent/src/manager.ts @@ -320,19 +320,18 @@ export class SubagentManager { * @returns the durable child id and accepted initial prompt message id. */ async startLocal(spec: SubagentActivationSpec): Promise { - const request = spec.request - const parent = request.parent + const parent = spec.parent this.assertAdmitting(parent) const ancestry = this.ancestryForChild(parent) return this.trackStartup(ancestry.root, spec.signal, async (signal) => { const persistence = spec.delivery === 'caller' ? this.ctx.get('sessionPersistence') : this.requirePersistence() - assertSubagentMaxDepth(request.maxDepth) + assertSubagentMaxDepth(spec.maxDepth) const childId = spec.childId ?? brandString(randomUUID()) this.assertChildIdAvailable(childId) - const childDepth = resolveChildDepth(parent, request.maxDepth) + const childDepth = resolveChildDepth(parent, spec.maxDepth) // Snapshot before any await: invalid descriptor JSON rejects the call // before a child exists, and the detached value is what reaches the log. - const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth) + const agentOptions = resolveChildAgentOptions(parent, spec.agentOptions, childDepth) const agentProvider = agentOptions.provider const agentModel = agentOptions.model const agentReasoningEffort = agentOptions.reasoningEffort @@ -348,7 +347,7 @@ export class SubagentManager { // parent's future, not to this child. const delegatedPolicies = captureDelegatedPolicyOverrides(parent) - const cwd = await this.resolveDirectory(request, signal) + const cwd = await this.resolveDirectory(spec, signal) const prepared = await this.host.prepareContinuable(spec.provider, { sessionId: childId, cwd, @@ -383,16 +382,16 @@ export class SubagentManager { parent, cwd, seed, - meta: childSessionMeta(parent, childDepth, prepared.seed !== undefined, request.agentPreset), + meta: childSessionMeta(parent, childDepth, prepared.seed !== undefined, spec.agentPreset), inheritedEventCount, delegatedPolicies, descriptor, }, agentOptions, - composition: { agentPreset: request.agentPreset }, + composition: { agentPreset: spec.agentPreset }, signal, delivery: spec.delivery, - outputSchema: spec.request.outputSchema, + outputSchema: spec.outputSchema, }) established = activation const child = requireLocalActivation(activation).handle.agent @@ -400,8 +399,8 @@ export class SubagentManager { return await this.submitMaterialized( activation, spec.delivery !== 'caller' && isAdjacentAgentSendMessageTool(this.ctx.get('tools')?.get('send_message', child)) - ? withContinuableReturnGuidance(parent.id, request.prompt) - : request.prompt, + ? withContinuableReturnGuidance(parent.id, spec.prompt) + : spec.prompt, { source: { kind: 'user' }, signal, delivery: 'queue', authorize: () => { this.assertAdmitting(parent) } }, ancestry.root, () => { @@ -420,28 +419,29 @@ export class SubagentManager { * @returns the accepted activation and its result/disposal capabilities. */ async startExternal(spec: SubagentActivationSpec): Promise { - const parent = spec.request.parent + const { provider, delivery, childId: _childId, ...request } = spec + const parent = request.parent this.assertAdmitting(parent) const pendingId = brandString(randomUUID()) const ancestry = this.ancestryForChild(parent) return this.trackStartup(ancestry.root, spec.signal, async (signal) => { let activation: Activation | undefined try { - const cwd = await this.resolveDirectory(spec.request, signal) + const cwd = await this.resolveDirectory(spec, signal) activation = await this.materialize({ kind: 'external', childId: pendingId, - provider: spec.provider, + provider, ...ancestry, signal, - delivery: spec.delivery, - start: signal => this.host.startExternal(spec.provider, { - ...spec.request, cwd, label: spec.label, signal, + delivery, + start: signal => this.host.startExternal(provider, { + ...request, cwd, signal, }), }) this.assertAdmitting(parent) signal.throwIfAborted() - if (spec.delivery === 'parent') { + if (delivery === 'parent') { establishExternalCatalogChild(parent.session, activation.childId, spec.label) } this.accept(activation) diff --git a/packages/subagent/subagent/src/types.ts b/packages/subagent/subagent/src/types.ts index 6845ec1fc0..5c4ab63e7b 100644 --- a/packages/subagent/subagent/src/types.ts +++ b/packages/subagent/subagent/src/types.ts @@ -26,15 +26,13 @@ export function SubagentRunId(id: string): SubagentRunId { } /** One managed child execution and its result delivery policy. */ -export interface SubagentActivationSpec { +export interface SubagentActivationSpec extends Omit { /** Registered backend to use. */ readonly provider: string /** Short task label retained in cataloged child membership. */ readonly label: string /** Optional reserved identity for a local child; external backends allocate their own ids. */ readonly childId?: SessionId - /** Task, parent, and backend-supported execution options. */ - readonly request: Omit /** Cancellation before publication; callers own later cancellation through dispose. */ readonly signal: AbortSignal /** Parent delivery notifies the model; caller delivery only returns the result. Local children always enter the parent catalog. */ @@ -121,9 +119,8 @@ export interface SubagentCapabilities { } /** - * Task and optional capabilities supplied to a backend. startActivation carries - * these fields in request while owning its label, cancellation, and delivery - * policy separately. + * Task and optional capabilities supplied to a backend. The activation spec + * shares these task options and defines its own label and cancellation policy. */ export interface SubagentStartRequest { /** Initial child directory; relative paths resolve against the parent's current directory. Omitted inherits that directory at start. */ diff --git a/packages/subagent/subagent/tests/continuation-inheritance.spec.ts b/packages/subagent/subagent/tests/continuation-inheritance.spec.ts index a118dc1573..b24cd4f037 100644 --- a/packages/subagent/subagent/tests/continuation-inheritance.spec.ts +++ b/packages/subagent/subagent/tests/continuation-inheritance.spec.ts @@ -63,7 +63,7 @@ function startSpec(parent: Agent, provider = 'spawn') { return { provider, label: 'child task', - request: { prompt: [{ type: 'text' as const, text: 'child task' }], parent }, + prompt: [{ type: 'text' as const, text: 'child task' }], parent, signal: new AbortController().signal, } } diff --git a/packages/subagent/subagent/tests/continuation-messages-adapter.spec.ts b/packages/subagent/subagent/tests/continuation-messages-adapter.spec.ts index 7d4a5a266c..0383149fca 100644 --- a/packages/subagent/subagent/tests/continuation-messages-adapter.spec.ts +++ b/packages/subagent/subagent/tests/continuation-messages-adapter.spec.ts @@ -61,7 +61,7 @@ it('continues the parent through default Messages after a reasoning-bearing cont delivery: 'parent', provider: 'spawn', label: 'child task', - request: { parent, prompt: [{ type: 'text', text: 'child task' }] }, + parent, prompt: [{ type: 'text', text: 'child task' }], signal: new AbortController().signal, }) await settled.promise diff --git a/packages/subagent/subagent/tests/continuation-presets.spec.ts b/packages/subagent/subagent/tests/continuation-presets.spec.ts index b1a0104198..4ed210bdcb 100644 --- a/packages/subagent/subagent/tests/continuation-presets.spec.ts +++ b/packages/subagent/subagent/tests/continuation-presets.spec.ts @@ -83,11 +83,9 @@ function startSpec(parent: Agent, agentPreset?: string, provider = 'separate') { provider, label: 'preset work', delivery: 'parent' as const, - request: { - parent, - prompt: [{ type: 'text' as const, text: 'work' }], - ...(agentPreset === undefined ? {} : { agentPreset }), - }, + parent, + prompt: [{ type: 'text' as const, text: 'work' }], + ...(agentPreset === undefined ? {} : { agentPreset }), signal: new AbortController().signal, } } diff --git a/packages/subagent/subagent/tests/continuation.spec.ts b/packages/subagent/subagent/tests/continuation.spec.ts index fc0fdfff52..9c934341ec 100644 --- a/packages/subagent/subagent/tests/continuation.spec.ts +++ b/packages/subagent/subagent/tests/continuation.spec.ts @@ -123,7 +123,7 @@ function startSpec(parent: Agent, provider = 'spawn', signal: AbortSignal = test return { provider, label: 'child task', - request: { prompt: [{ type: 'text' as const, text: 'child task' }], parent }, + prompt: [{ type: 'text' as const, text: 'child task' }], parent, signal, } } @@ -630,11 +630,9 @@ describe('SubagentRuntime.startActivation', () => { const started = await ctx.subagents.startActivation({ delivery: 'parent', ...startSpec(parent), - request: { - prompt: message('selected reasoning'), - parent, - agentOptions: { reasoningEffort: effort }, - }, + prompt: message('selected reasoning'), + parent, + agentOptions: { reasoningEffort: effort }, }) await waitNoActivation(ctx, started.childId) const loaded = await loadStoredSession(ctx.sessionPersistence, started.childId) @@ -766,7 +764,7 @@ describe('SubagentRuntime.startActivation', () => { await expect(ctx.subagents.startActivation({ delivery: 'parent', ...startSpec(parent), - request: { prompt: message('deep'), parent, maxDepth: 0 }, + prompt: message('deep'), parent, maxDepth: 0, })).rejects.toThrow(/exceeds maxDepth 0/) expect(ctx.agents.list().map(agent => agent.id)).toEqual([SessionId('parent')]) }) @@ -776,7 +774,7 @@ describe('SubagentRuntime.startActivation', () => { await expect(ctx.subagents.startActivation({ delivery: 'parent', ...startSpec(parent), - request: { prompt: message('deep'), parent, maxDepth: Number.NaN }, + prompt: message('deep'), parent, maxDepth: Number.NaN, })).rejects.toThrow(/non-negative safe integer/) expect(ctx.agents.list().map(agent => agent.id)).toEqual([SessionId('parent')]) }) @@ -3840,11 +3838,9 @@ describe('continuable errors', () => { const started = await ctx.subagents.startActivation({ delivery: 'parent', ...startSpec(parent), - request: { - prompt: message('routed work'), - parent, - agentOptions: { provider: 'mock', model: 'child-model', reasoningEffort: effort }, - }, + prompt: message('routed work'), + parent, + agentOptions: { provider: 'mock', model: 'child-model', reasoningEffort: effort }, }) await waitNoActivation(ctx, started.childId) const loaded = await loadStoredSession(ctx.sessionPersistence, started.childId) diff --git a/packages/subagent/subagent/tests/external-activation-helpers.ts b/packages/subagent/subagent/tests/external-activation-helpers.ts index 93e86ce580..d27d17caa2 100644 --- a/packages/subagent/subagent/tests/external-activation-helpers.ts +++ b/packages/subagent/subagent/tests/external-activation-helpers.ts @@ -72,7 +72,7 @@ export async function startExternalActivation( return ctx.subagents.startActivation({ provider, label: label ?? 'External test task', - request: { ...execution, parent }, + ...execution, parent, signal, delivery: 'caller', }) diff --git a/packages/subagent/subagent/tests/external-activation.spec.ts b/packages/subagent/subagent/tests/external-activation.spec.ts index ff50c5f4dd..33ea3b08e6 100644 --- a/packages/subagent/subagent/tests/external-activation.spec.ts +++ b/packages/subagent/subagent/tests/external-activation.spec.ts @@ -28,7 +28,7 @@ async function setup(start: (request: SubagentStartRequest) => Promise ctx.subagents.startActivation({ provider: 'external', label: 'External work', signal: controller.signal, delivery, - request: { parent, prompt: [{ type: 'text', text: 'Work independently' }] }, + parent, prompt: [{ type: 'text', text: 'Work independently' }], }), records: () => ctx.sessionProjections.snapshot(parent.session, ['subagentCatalog']).values.subagentCatalog, } @@ -104,7 +104,7 @@ describe('external subagent activations', () => { try { await expect(fixture.ctx.subagents.startActivation({ provider: 'external', label: 'Unpublished work', signal: fixture.controller.signal, delivery: 'parent', - request: { parent, prompt: [{ type: 'text', text: 'Must not publish' }] }, + parent, prompt: [{ type: 'text', text: 'Must not publish' }], })).rejects.toMatchObject({ code: change === 'drain' ? 'DRAINING' : 'UNAUTHORIZED' }) await drain expect(backend.dispose).toHaveBeenCalledTimes(1) @@ -367,7 +367,7 @@ it('refuses a reserved Session id for an external provider before dispatch', asy const fixture = await setup(start) await expect(fixture.ctx.subagents.startActivation({ provider: 'external', childId: SessionId('reserved'), label: 'External', delivery: 'parent', - signal: fixture.controller.signal, request: { parent: fixture.parent, prompt: [] }, + signal: fixture.controller.signal, parent: fixture.parent, prompt: [], })).rejects.toMatchObject({ code: 'UNSUPPORTED_CAPABILITY' }) expect(start).not.toHaveBeenCalled() }) diff --git a/packages/subagent/subagent/tests/external-lifecycle.spec.ts b/packages/subagent/subagent/tests/external-lifecycle.spec.ts index e646f8df2b..a4cb9cfba8 100644 --- a/packages/subagent/subagent/tests/external-lifecycle.spec.ts +++ b/packages/subagent/subagent/tests/external-lifecycle.spec.ts @@ -27,7 +27,7 @@ async function setup(start: (request: SubagentStartRequest) => Promise ctx.subagents.startActivation({ provider: 'external', label: 'External work', signal: new AbortController().signal, delivery, - request: { parent: owner, prompt: [{ type: 'text', text: 'work' }] }, + parent: owner, prompt: [{ type: 'text', text: 'work' }], }), } } diff --git a/packages/subagent/subagent/tests/list-children.spec.ts b/packages/subagent/subagent/tests/list-children.spec.ts index 2e64d9124f..b5e37e0ea6 100644 --- a/packages/subagent/subagent/tests/list-children.spec.ts +++ b/packages/subagent/subagent/tests/list-children.spec.ts @@ -76,7 +76,7 @@ async function startChild( const started = await ctx.subagents.startActivation({ delivery: 'parent', provider: 'spawn', label, - request: { prompt: [{ type: 'text', text: `task: ${label}` }], parent }, + prompt: [{ type: 'text', text: `task: ${label}` }], parent, signal: testSignal, }) await vi.waitFor(() => { diff --git a/packages/subagent/subagent/tests/local-activation.ts b/packages/subagent/subagent/tests/local-activation.ts index 02b9d594c5..4baaf348aa 100644 --- a/packages/subagent/subagent/tests/local-activation.ts +++ b/packages/subagent/subagent/tests/local-activation.ts @@ -36,7 +36,7 @@ export async function startTestActivation( request: Omit & { signal?: AbortSignal }, ) { const { signal = new AbortController().signal, label = provider, ...task } = request - const activation = await ctx.get('subagents')!.startActivation({ provider, label, request: task, signal, delivery: 'caller' }) + const activation = await ctx.get('subagents')!.startActivation({ provider, label, ...task, signal, delivery: 'caller' }) return { ...activation, id: activation.childId, localAgent: ctx.get('agents')!.get(activation.childId)! } } diff --git a/packages/subagent/subagent/tests/parent-result.spec.ts b/packages/subagent/subagent/tests/parent-result.spec.ts index a87afb502e..1d16f38cc2 100644 --- a/packages/subagent/subagent/tests/parent-result.spec.ts +++ b/packages/subagent/subagent/tests/parent-result.spec.ts @@ -26,9 +26,7 @@ it.each(['absent', 'unused'] as const)('returns the closing answer when send_mes const parent = await ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' }) const activation = await ctx.subagents.startActivation({ provider: 'spawn', label: 'Compute the answer', delivery: 'parent', signal: new AbortController().signal, - request: { - parent, prompt: [{ type: 'text', text: 'Find the answer.' }], - }, + parent, prompt: [{ type: 'text', text: 'Find the answer.' }], }) try { await ctx.subagents.waitForTreeWork(parent) @@ -60,10 +58,8 @@ it('delivers structured output to the parent when the child has send_message', a const parent = await ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' }) const activation = await ctx.subagents.startActivation({ provider: 'spawn', label: 'Compute the answer', delivery: 'parent', signal: new AbortController().signal, - request: { - parent, prompt: [{ type: 'text', text: 'Find the answer.' }], - outputSchema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] }, - }, + parent, prompt: [{ type: 'text', text: 'Find the answer.' }], + outputSchema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] }, }) try { await activation.result diff --git a/packages/subagent/subagent/tests/parked-wait.spec.ts b/packages/subagent/subagent/tests/parked-wait.spec.ts index 65a3c01e34..0e70577374 100644 --- a/packages/subagent/subagent/tests/parked-wait.spec.ts +++ b/packages/subagent/subagent/tests/parked-wait.spec.ts @@ -54,7 +54,7 @@ async function setup() { label: 'child task', delivery: 'caller', signal: new AbortController().signal, - request: { parent: owner, prompt: [{ type: 'text', text: 'child task' }] }, + parent: owner, prompt: [{ type: 'text', text: 'child task' }], }) starts.push(started) return started diff --git a/packages/subagent/subagent/tests/schedule-tools.spec.ts b/packages/subagent/subagent/tests/schedule-tools.spec.ts index 11c44b9707..135feb826f 100644 --- a/packages/subagent/subagent/tests/schedule-tools.spec.ts +++ b/packages/subagent/subagent/tests/schedule-tools.spec.ts @@ -98,7 +98,7 @@ describe('a delegated child under a preset that mounts the Schedule tools', () = label: 'child task', delivery: 'caller', signal: new AbortController().signal, - request: { parent, prompt: [{ type: 'text', text: 'child task' }] }, + parent, prompt: [{ type: 'text', text: 'child task' }], }) const child = ctx.agents.get(run.childId) diff --git a/packages/subagent/subagent/tests/service.spec.ts b/packages/subagent/subagent/tests/service.spec.ts index 93abc999bd..09c6cd581c 100644 --- a/packages/subagent/subagent/tests/service.spec.ts +++ b/packages/subagent/subagent/tests/service.spec.ts @@ -98,7 +98,7 @@ async function start(subagents: SubagentRuntime, provider: string, request: Suba ?? (await ctx.agents.create({ sessionId: request.parent.id })).agent const { label, signal, ...options } = request return subagents.startActivation({ - provider, label: label ?? provider, request: { ...options, parent }, signal, delivery, + provider, label: label ?? provider, ...options, parent, signal, delivery, }) } @@ -208,7 +208,7 @@ describe('SubagentRuntime', () => { delivery: 'parent', provider: 'unused', label: 'unused child', - request: baseRequest(), + ...baseRequest(), signal: new AbortController().signal, })).rejects.toMatchObject({ code: 'NO_PROVIDER' }) await expect(subagents.sendMessage( @@ -226,7 +226,7 @@ describe('SubagentRuntime', () => { await expect(subagents.startActivation({ provider: provider.name, label: 'Unavailable runtime', - request: baseRequest(), + ...baseRequest(), signal: new AbortController().signal, delivery: 'caller', })).rejects.toMatchObject({ code: 'CONTINUATION_UNAVAILABLE' }) @@ -255,7 +255,7 @@ describe('SubagentRuntime', () => { provider: provider.name, label: 'Reserved team child', childId: SessionId('reserved-child'), - request: { parent, prompt: [{ type: 'text', text: 'Work' }] }, + parent, prompt: [{ type: 'text', text: 'Work' }], signal: new AbortController().signal, delivery: 'caller', })).rejects.toMatchObject({ code: 'UNSUPPORTED_CAPABILITY' }) @@ -653,7 +653,7 @@ it('rejects a disposed parent before starting provider execution', async () => { await handle.dispose() await expect(subagents.startActivation({ provider: provider.name, label: 'work', delivery: 'caller', signal: new AbortController().signal, - request: { parent: handle.agent, prompt: [] }, + parent: handle.agent, prompt: [], })).rejects.toMatchObject({ code: 'UNAUTHORIZED' }) expect(provider.startCount).toBe(0) }) diff --git a/packages/subagent/subagent/tests/sibling-messages.spec.ts b/packages/subagent/subagent/tests/sibling-messages.spec.ts index 0f41bd07cc..cb93f2df90 100644 --- a/packages/subagent/subagent/tests/sibling-messages.spec.ts +++ b/packages/subagent/subagent/tests/sibling-messages.spec.ts @@ -84,7 +84,7 @@ async function start(ctx: Context, parent: Agent, marker: string, description = const started = await ctx.subagents.startActivation({ provider: 'spawn', label: description, - request: { prompt: [{ type: 'text', text: `TASK:${marker}` }], parent }, + prompt: [{ type: 'text', text: `TASK:${marker}` }], parent, signal, delivery: 'caller', }) diff --git a/packages/subagent/subagent/tests/structured.spec.ts b/packages/subagent/subagent/tests/structured.spec.ts index 88e4081a8f..26628d800c 100644 --- a/packages/subagent/subagent/tests/structured.spec.ts +++ b/packages/subagent/subagent/tests/structured.spec.ts @@ -388,10 +388,8 @@ describe('in-process structured output', () => { if (exec.agent === undefined) throw new Error('expected the structured parent') descendant = await ctx.subagents.startActivation({ provider: 'spawn', label: 'Nested work', signal: testToolSignal, delivery: 'parent', - request: { - parent: exec.agent, prompt: [{ type: 'text', text: 'Work until released' }], - agentOptions: { provider: 'descendant', model: 'mock' }, - }, + parent: exec.agent, prompt: [{ type: 'text', text: 'Work until released' }], + agentOptions: { provider: 'descendant', model: 'mock' }, }) return [{ type: 'text', text: 'Child accepted' }] }, @@ -433,7 +431,7 @@ describe('in-process structured output', () => { if (exec.agent === undefined) throw new Error('expected the structured parent') await ctx.subagents.startActivation({ provider: 'delayed-answer', label: 'Compute answer', signal: testToolSignal, delivery: 'parent', - request: { parent: exec.agent, prompt: [{ type: 'text', text: 'Compute the answer' }] }, + parent: exec.agent, prompt: [{ type: 'text', text: 'Compute the answer' }], }) return [{ type: 'text', text: 'Answer computation accepted' }] }, diff --git a/packages/subagent/subagent/tests/working-directory.spec.ts b/packages/subagent/subagent/tests/working-directory.spec.ts index bd8e680cd8..f3cb6aa47d 100644 --- a/packages/subagent/subagent/tests/working-directory.spec.ts +++ b/packages/subagent/subagent/tests/working-directory.spec.ts @@ -71,7 +71,7 @@ describe('subagent working directories', () => { const started = await ctx.subagents.startActivation({ delivery: 'caller', provider: 'spawn', label: 'directory child', - request: { parent, cwd: form === 'absolute' ? second : 'second', prompt: [{ type: 'text', text: 'child' }] }, + parent, cwd: form === 'absolute' ? second : 'second', prompt: [{ type: 'text', text: 'child' }], signal: new AbortController().signal, }) await vi.waitFor(() => { expect(ctx.agents.get(started.childId)).toBeUndefined() }) @@ -99,7 +99,7 @@ describe('subagent working directories', () => { await expect(ctx.subagents.startActivation({ delivery: 'caller', provider: 'spawn', label: 'unavailable directory service', childId, - request: { parent, cwd: second, prompt: [{ type: 'text', text: 'child' }] }, + parent, cwd: second, prompt: [{ type: 'text', text: 'child' }], signal: new AbortController().signal, })).rejects.toThrow(/subagents require the working-directory service/) diff --git a/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts b/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts index 82d21acddd..a4c1368025 100644 --- a/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts +++ b/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts @@ -113,7 +113,7 @@ describe('list_agents', () => { const { ctx, parent } = await setup([textResponse('private result')]) const started = await ctx.subagents.startActivation({ delivery: 'caller', provider: 'spawn', label: 'workflow child', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await started.result @@ -162,7 +162,7 @@ describe('list_agents', () => { const started = await ctx.subagents.startActivation({ delivery: 'parent', provider: 'spawn', label: 'real child', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) @@ -236,7 +236,7 @@ describe('list_agents', () => { const started = await ctx.subagents.startActivation({ delivery: 'parent', provider: 'spawn', label: 'summarize the doc', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) @@ -301,7 +301,7 @@ describe('list_agents', () => { const started = await ctx.subagents.startActivation({ delivery: 'parent', provider: 'spawn', label: 'waiting branch', - request: { prompt: [{ type: 'text', text: 'branch work' }], parent }, + prompt: [{ type: 'text', text: 'branch work' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -309,7 +309,7 @@ describe('list_agents', () => { const grandchild = await ctx.subagents.startActivation({ delivery: 'parent', provider: 'spawn', label: 'nested leaf', - request: { prompt: [{ type: 'text', text: 'leaf work' }], parent: child }, + prompt: [{ type: 'text', text: 'leaf work' }], parent: child, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(2) }) @@ -388,7 +388,7 @@ describe('list_agents', () => { const { ctx, parent } = await setupWith(adapter) const start = async (label: string) => (await ctx.subagents.startActivation({ delivery: 'caller', provider: 'spawn', label, - request: { prompt: [{ type: 'text', text: label }], parent }, signal: testToolSignal, + prompt: [{ type: 'text', text: label }], parent, signal: testToolSignal, })).childId const alpha = await start('alpha task') await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) diff --git a/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts b/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts index dc9f637486..5364a36cd2 100644 --- a/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts +++ b/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts @@ -141,7 +141,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'fork', label: 'fork child', - request: { prompt: [{ type: 'text', text: 'fork task' }], parent }, + prompt: [{ type: 'text', text: 'fork task' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(2) }) @@ -177,7 +177,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'spawn', label: 'encoded parent', - request: { prompt: [{ type: 'text', text: 'encoded task' }], parent }, + prompt: [{ type: 'text', text: 'encoded task' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) @@ -201,7 +201,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'spawn', label: 'child task', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -240,7 +240,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'spawn', label: 'child task', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) @@ -274,7 +274,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'spawn', label: 'long work', - request: { prompt: [{ type: 'text', text: 'long work' }], parent }, + prompt: [{ type: 'text', text: 'long work' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -316,7 +316,7 @@ describe('dsh-tool-subagent-control', () => { delivery: 'parent', provider: 'spawn', label: 'child task', - request: { prompt: [{ type: 'text', text: 'child task' }], parent }, + prompt: [{ type: 'text', text: 'child task' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) @@ -400,7 +400,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'long work', - request: { prompt: [{ type: 'text', text: 'long work' }], parent }, + prompt: [{ type: 'text', text: 'long work' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -457,7 +457,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'child', - request: { prompt: [{ type: 'text', text: 'child work' }], parent }, + prompt: [{ type: 'text', text: 'child work' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -466,7 +466,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'grandchild', - request: { prompt: [{ type: 'text', text: 'grandchild work' }], parent: child }, + prompt: [{ type: 'text', text: 'grandchild work' }], parent: child, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(2) }) @@ -495,7 +495,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'target', - request: { prompt: [{ type: 'text', text: 'a' }], parent }, + prompt: [{ type: 'text', text: 'a' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) }) @@ -503,7 +503,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'sibling', - request: { prompt: [{ type: 'text', text: 'b' }], parent }, + prompt: [{ type: 'text', text: 'b' }], parent, signal: testToolSignal, }) await vi.waitFor(() => { expect(adapter.requests).toHaveLength(2) }) @@ -535,7 +535,7 @@ describe('dsh-tool-subagent-control interrupt_agent', () => { delivery: 'parent', provider: 'spawn', label: 'settled child', - request: { prompt: [{ type: 'text', text: 'child work' }], parent }, + prompt: [{ type: 'text', text: 'child work' }], parent, signal: testToolSignal, }) await waitNoActivation(ctx, started.childId) diff --git a/packages/subagent/tool-subagent/src/index.ts b/packages/subagent/tool-subagent/src/index.ts index 6981de5b73..a5d581378c 100644 --- a/packages/subagent/tool-subagent/src/index.ts +++ b/packages/subagent/tool-subagent/src/index.ts @@ -325,7 +325,7 @@ export async function apply(ctx: Context, config: Config): Promise { const started = await runtimeCtx.subagents.startActivation({ provider: config.provider, label: args.description, - request, + ...request, signal: exec.signal, delivery: 'parent', }) diff --git a/packages/test-support/session-snapshot/tests/fixtures/subagent-structured-parent.ts b/packages/test-support/session-snapshot/tests/fixtures/subagent-structured-parent.ts index 5762a95c16..4d70e21cdf 100644 --- a/packages/test-support/session-snapshot/tests/fixtures/subagent-structured-parent.ts +++ b/packages/test-support/session-snapshot/tests/fixtures/subagent-structured-parent.ts @@ -30,10 +30,8 @@ export function apply(ctx: Context): void { try { const activation = await ctx.subagents.startActivation({ provider: 'spawn', label: 'Structured answer', delivery: 'parent', signal: exec.signal, - request: { - parent, prompt: [{ type: 'text', text: 'Submit answer 41 through structured_output.' }], - outputSchema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] }, - }, + parent, prompt: [{ type: 'text', text: 'Submit answer 41 through structured_output.' }], + outputSchema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] }, }) await activation.result return [{ type: 'text', text: `Child ${activation.childId} settled.` }] diff --git a/packages/workflow/workflow-ptc/src/host.ts b/packages/workflow/workflow-ptc/src/host.ts index 7a2266e778..eb67cf5fe5 100644 --- a/packages/workflow/workflow-ptc/src/host.ts +++ b/packages/workflow/workflow-ptc/src/host.ts @@ -205,16 +205,14 @@ export class PtcWorkflowRun implements WorkflowRun { label: `${this.meta.name} child ${callId}`, delivery: 'caller', signal: this.controller.signal, - request: { - cwd: await this.cwd, - prompt: [{ type: 'text', text: request.prompt }], - parent: this.parent, - ...request.schema === undefined ? {} : { outputSchema: request.schema }, - ...request.provider === undefined && request.model === undefined ? {} : { - agentOptions: { - ...request.provider === undefined ? {} : { provider: request.provider }, - ...request.model === undefined ? {} : { model: request.model }, - }, + cwd: await this.cwd, + prompt: [{ type: 'text', text: request.prompt }], + parent: this.parent, + ...request.schema === undefined ? {} : { outputSchema: request.schema }, + ...request.provider === undefined && request.model === undefined ? {} : { + agentOptions: { + ...request.provider === undefined ? {} : { provider: request.provider }, + ...request.model === undefined ? {} : { model: request.model }, }, }, }) diff --git a/packages/workflow/workflow-ptc/tests/host.spec.ts b/packages/workflow/workflow-ptc/tests/host.spec.ts index 5254a25ceb..9e65eccd8a 100644 --- a/packages/workflow/workflow-ptc/tests/host.spec.ts +++ b/packages/workflow/workflow-ptc/tests/host.spec.ts @@ -118,7 +118,7 @@ describe('workflow host callback validation', () => { expect(childStart).toHaveBeenCalledOnce() const selected = childStart.mock.calls[0]![0] expect(selected.provider).toBe('stub') - expect(selected.request.cwd).toBe(initial) + expect(selected.cwd).toBe(initial) expect(ensureDirectory).toHaveBeenCalledOnce() } finally { release.resolve(undefined) @@ -207,16 +207,13 @@ describe('workflow runtime outcomes', () => { await disposing.promise expect(settled).toBe(false) expect(activation).toHaveBeenCalledOnce() - const { signal, ...delegation } = activation.mock.calls[0]![0] + const { signal, parent: delegatedParent, ...delegation } = activation.mock.calls[0]![0] expect(signal).toBeInstanceOf(AbortSignal) - const { parent: delegatedParent, ...request } = delegation.request expect(delegatedParent).toBe(parent) - expect({ ...delegation, request }).toEqual({ + expect(delegation).toEqual({ provider: 'stub', label: 'host-test child 1', delivery: 'caller', - request: { - cwd: ctx.workingDirectory.get(parent.session), - prompt: [{ type: 'text', text: 'answer' }], outputSchema: schema, agentOptions: { model: 'selected' }, - }, + cwd: ctx.workingDirectory.get(parent.session), + prompt: [{ type: 'text', text: 'answer' }], outputSchema: schema, agentOptions: { model: 'selected' }, }) released.resolve(undefined) await expect(run.result).resolves.toMatchObject({ stopReason: 'completed' }) diff --git a/scripts/doc-typecheck-paths.spec.ts b/scripts/doc-typecheck-paths.spec.ts index a4e8839556..65ca2307af 100644 --- a/scripts/doc-typecheck-paths.spec.ts +++ b/scripts/doc-typecheck-paths.spec.ts @@ -1,5 +1,21 @@ import { describe, expect, it } from 'vitest' -import { builtDeclarationPath } from './doc-typecheck-paths.ts' +import { builtDeclarationPath, isFrozenUpgradeGuidePath } from './doc-typecheck-paths.ts' + +describe('frozen upgrade-guide examples', () => { + it('excludes earlier release guides in both languages from current API compilation', () => { + expect(isFrozenUpgradeGuidePath('docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.md', '0.2.1-rc.2')).toBe(true) + expect(isFrozenUpgradeGuidePath('docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.zh.md', '0.2.1-rc.2')).toBe(true) + }) + + it.each([ + 'docs/upgrade-guide/v0.2.1-rc.2/subagent-activations/guide.md', + 'docs/upgrade-guide/v0.2.1-rc.3/subagent-activations/guide.md', + 'docs/upgrade-guide/vinvalid/subagent-activations/guide.md', + 'docs/subsystems/subagent.md', + ])('keeps %s subject to current API compilation', (file) => { + expect(isFrozenUpgradeGuidePath(file, '0.2.1-rc.2')).toBe(false) + }) +}) describe('builtDeclarationPath', () => { it('maps package source directories and exact entry files to built declarations', () => { diff --git a/scripts/doc-typecheck-paths.ts b/scripts/doc-typecheck-paths.ts index ee09d7a27a..a436e5a47d 100644 --- a/scripts/doc-typecheck-paths.ts +++ b/scripts/doc-typecheck-paths.ts @@ -1,3 +1,16 @@ +import { lt, valid } from 'semver' + +/** + * Identify frozen upgrade guides whose examples target an earlier release. + * @param file - Repository-relative path normalized to forward slashes. + * @param currentVersion - Current root package version. + * @returns Whether the guide predates the current upgrade-guide directory. + */ +export function isFrozenUpgradeGuidePath(file: string, currentVersion: string): boolean { + const version = /^docs\/upgrade-guide\/v([^/]+)\/[^/]+\/guide(?:\.zh)?\.md$/.exec(file)?.[1] + return version !== undefined && valid(version) !== null && lt(version, currentVersion) +} + /** Map one workspace source alias target to its declaration-build target. */ export function builtDeclarationPath(candidate: string): string { // Two workspace path forms exist: whole-package entries end in /src, subpath diff --git a/scripts/doc-typecheck.ts b/scripts/doc-typecheck.ts index 4c02c6d64c..9a4e7b28b6 100644 --- a/scripts/doc-typecheck.ts +++ b/scripts/doc-typecheck.ts @@ -1,7 +1,8 @@ /** * Typecheck Markdown `ts` fences against the workspace API. `ignore-check` fences are reported as * opt-outs; generated catalog fragments and source-equivalence blocks are skipped here because their - * owning gates verify them. Byte-identical `.zh.md` copies reuse their unsuffixed sibling's check. A + * owning gates verify them. Frozen upgrade guides retain their release-specific examples. + * Byte-identical `.zh.md` copies reuse their unsuffixed sibling's check. A * build-coordinated mode consumes existing declarations without emit. */ @@ -9,7 +10,7 @@ import { execFileSync } from 'node:child_process' import { globSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { join, relative, resolve } from 'node:path' import ts from 'typescript' -import { builtDeclarationPath } from './doc-typecheck-paths.ts' +import { builtDeclarationPath, isFrozenUpgradeGuidePath } from './doc-typecheck-paths.ts' import { markdownFences } from './markdown.ts' import { partitionPairedMarkdownDerivatives } from './paired-markdown-derivatives.ts' import { isArchivedAgentNotePath } from './repo-files.ts' @@ -205,9 +206,11 @@ function remapBlockPaths(output: string, blocks: Block[]): string { const markdownGlobs = ['README.md', '.agents/notes/**/*.md', 'docs/**/*.md', 'packages/*/*.md', 'packages/*/*/*.md'] const files: string[] = [] +const { version } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { version: string } for (const pattern of markdownGlobs) { for (const match of globSync(pattern, { cwd: root })) { - if (!isArchivedAgentNotePath(match)) files.push(resolve(root, match)) + const file = match.replaceAll('\\', '/') + if (!isArchivedAgentNotePath(file) && !isFrozenUpgradeGuidePath(file, version)) files.push(resolve(root, file)) } } files.sort() diff --git a/scripts/python-snapshot-workflow-order.spec.ts b/scripts/python-snapshot-workflow-order.spec.ts index 6685f5a04f..c433f8d60b 100644 --- a/scripts/python-snapshot-workflow-order.spec.ts +++ b/scripts/python-snapshot-workflow-order.spec.ts @@ -79,7 +79,7 @@ describe('advanced Python snapshot workflow ordering', () => { const parent = await ctx.agentLoop.create(SessionId(config.parentSessionId), { provider: 'mock', model: 'mock' }) const run = await ctx.subagents.startActivation({ provider: 'spawn', label: 'workflow-child', delivery: 'caller', - request: { parent, prompt: [{ type: 'text', text: config.prompt }] }, signal: new AbortController().signal, + parent, prompt: [{ type: 'text', text: config.prompt }], signal: new AbortController().signal, }) cleanups.push(() => run.dispose()) const child = await entered.promise