Jelajahi Sumber

fix(subagent): preserve ordinary start requests

Dudu-0223 1 bulan lalu
induk
melakukan
264bc41a13

+ 1 - 1
docs/cordis-catalog/services.md

@@ -2005,7 +2005,7 @@ list(): string[]
  * @param request - child prompt, parent, signal, and optional capabilities.
  * @returns the ready holder-owned run.
  */
-async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
+async start(name: string, request: SubagentStartRequest & { readonly continuation?: never }): Promise<SubagentRun>
 ```
 
 Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [SessionId](../core-data-structures/core.md) · [SubagentFollowupOptions](../core-data-structures/subagent.md) · [SubagentFollowupResult](../core-data-structures/subagent.md) · [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)

+ 2 - 2
docs/core-data-structures/subagent.i18n.yaml

@@ -2,5 +2,5 @@
 # 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 docs/core-data-structures/subagent.md
-subagent.md: 2dc25dfb14b1506edf7f53f6ce0d8681fefa98c6
-subagent.zh.md: 00f2748ad92ae37b0a2fe2616d9e052f9c4b916f
+subagent.md: 8f24afec47a970711aae49cae6b3535b9f532e5f
+subagent.zh.md: 50c5cb887ef814c074a85fc4fee9cd2fe85d685c

+ 1 - 1
docs/core-data-structures/subagent.md

@@ -94,7 +94,7 @@ interface SubagentStartRequest {
 
 `signal` is the single cancellation channel before and after readiness. The [subagent composition-controls Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md) owns the persona, live global-tool filter, absolute-depth, and visibility-not-authority rationale.
 
-Providers receive a separate resolved shape. Raw `SubagentService.start()` clears continuation state, while `startContinuable()` alone supplies the service-allocated identity and descriptor.
+Providers receive a separate resolved shape. The `SubagentService.start()` parameter type excludes continuation state, while `startContinuable()` alone supplies the service-allocated identity and descriptor.
 
 ```ts type-equiv
 /**

+ 1 - 1
docs/core-data-structures/subagent.zh.md

@@ -94,7 +94,7 @@ interface SubagentStartRequest {
 
 `signal` 是就绪前后唯一的取消通道。[subagent 组合控制 Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)规定 persona、live 全局工具过滤、绝对深度以及「可见性而非权限」的设计理由。
 
-提供方会接收单独的已解析请求类型。直接调用 `SubagentService.start()` 会清除继续执行状态;只有 `startContinuable()` 才会提供由服务分配的标识和描述符。
+提供方会接收单独的已解析请求类型。`SubagentService.start()` 的参数类型不包含继续执行状态;只有 `startContinuable()` 才会提供由服务分配的标识和描述符。
 
 ```ts type-equiv
 /**

+ 1 - 1
packages/cordis/tool-cordis/src/api-catalog.ts

@@ -905,7 +905,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
         jsDoc: '/**\n * List registered provider names in insertion order.\n * @returns the registered names.\n */',
       },
       {
-        signature: 'async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>',
+        signature: 'async start(name: string, request: SubagentStartRequest & { readonly continuation?: never }): Promise<SubagentRun>',
         jsDoc: '/**\n * Establish a ready child on the named provider. Capability and semantic\n * checks run before delegation. Provider ownership lasts until its promise\n * fulfills; a rejection therefore has no run for the caller to dispose and\n * emits no run lifecycle events.\n * @param name - the provider to use.\n * @param request - child prompt, parent, signal, and optional capabilities.\n * @returns the ready holder-owned run.\n */',
       },
     ],

+ 2 - 4
packages/subagent/subagent/src/index.ts

@@ -298,10 +298,8 @@ export class SubagentService extends Service {
    * @param request - child prompt, parent, signal, and optional capabilities.
    * @returns the ready holder-owned run.
    */
-  async start(name: string, request: SubagentStartRequest): Promise<SubagentRun> {
-    // A provider request is structurally assignable to the caller shape. Clear
-    // its wider field so only startContinuable can supply service-owned state.
-    return this.startProvider(name, { ...request, continuation: undefined })
+  async start(name: string, request: SubagentStartRequest & { readonly continuation?: never }): Promise<SubagentRun> {
+    return this.startProvider(name, request)
   }
 
   /** Validate and dispatch one ordinary or service-resolved provider start. */

+ 7 - 13
packages/subagent/subagent/tests/service.spec.ts

@@ -1,4 +1,4 @@
-import { describe, expect, it, vi } from 'vitest'
+import { describe, expect, expectTypeOf, it, vi } from 'vitest'
 import { Context } from 'cordis'
 import { type Agent } from '@deepseek-ai/dsh-agent'
 
@@ -105,22 +105,16 @@ describe('SubagentService', () => {
       .rejects.toMatchObject({ code: 'NO_PROVIDER' })
   })
 
-  it('keeps provider continuation state out of raw start and exposes no raw resume operation', async () => {
+  it('borrows ordinary start requests and exposes no provider continuation operations', async () => {
     const { subagents } = await service()
     const provider = new StubProvider('one-shot')
     subagents.registerProvider(provider)
-    const descriptor = snapshotSubagentDescriptor({ provider: 'one-shot' })
-    const sessionId = SessionId('continuable-child')
-    const parent = fakeParent()
-    const signal = new AbortController().signal
-
-    const providerRequest: SubagentProviderStartRequest = {
-      ...baseRequest({ parent, signal }),
-      continuation: { sessionId, descriptor },
-    }
-    await subagents.start('one-shot', providerRequest)
+    const request = baseRequest()
+    await subagents.start('one-shot', request)
 
-    expect(provider.lastRequest?.continuation).toBeUndefined()
+    expect(provider.lastRequest).toBe(request)
+    expectTypeOf<SubagentProviderStartRequest>()
+      .not.toExtend<Parameters<SubagentService['start']>[1]>()
     expect('resume' in subagents).toBe(false)
   })