Unified · Split
| @@ -9,6 +9,36 @@ describe("orchestrator MCP tool guidance", () => { · 8 unmodified lines | |||
| 9 | assert.include(DelegateTaskTool.description ?? "", "cross-provider"); | 9 | assert.include(DelegateTaskTool.description ?? "", "cross-provider"); |
| 10 | assert.include(CreateThreadsTool.description ?? "", "not delegation"); | 10 | assert.include(CreateThreadsTool.description ?? "", "not delegation"); |
| 11 | assert.include(CreateThreadsTool.description ?? "", "call delegate_task"); | 11 | assert.include(CreateThreadsTool.description ?? "", "call delegate_task"); |
| 12 | assert.include(DelegateTaskTool.description ?? "", "waitTimedOut"); | ||
| 13 | assert.include(DelegateTaskTool.description ?? "", "does not cancel the child"); | ||
| 14 | assert.include(DelegateTaskTool.description ?? "", "keep that taskId"); | ||
| 15 | }); | ||
| 16 | |||
| 17 | it("documents wait timeout as a parent budget, not a child failure", () => { | ||
| 18 | const schema = Tool.getJsonSchema(DelegateTaskTool) as { | ||
| 19 | readonly properties?: Readonly< | ||
| 20 | Record< | ||
| 21 | string, | ||
| 22 | { | ||
| 23 | readonly description?: unknown; | ||
| 24 | readonly anyOf?: ReadonlyArray<{ readonly description?: unknown }>; | ||
| 25 | } | ||
| 26 | > | ||
| 27 | >; | ||
| 28 | }; | ||
| 29 | const mode = schema.properties?.mode; | ||
| 30 | const timeoutMs = schema.properties?.timeoutMs; | ||
| 31 | const modeText = [mode?.description, ...(mode?.anyOf ?? []).map((entry) => entry.description)] | ||
| 32 | .filter((value) => typeof value === "string") | ||
| 33 | .join(" "); | ||
| 34 | const timeoutText = [ | ||
| 35 | timeoutMs?.description, | ||
| 36 | ...(timeoutMs?.anyOf ?? []).map((entry) => entry.description), | ||
| 37 | ] | ||
| 38 | .filter((value) => typeof value === "string") | ||
| 39 | .join(" "); | ||
| 40 | assert.include(modeText, "Defaults to async"); | ||
| 41 | assert.include(timeoutText, "does not cancel the child"); | ||
| 12 | }); | 42 | }); |
| 13 | 43 | ||
| 14 | it("publishes an actionable schedule schema and compatibility string branch", () => { | 44 | it("publishes an actionable schedule schema and compatibility string branch", () => { |
| @@ -49,7 +49,7 @@ export const OrchestratorCapabilitiesTool = Tool.make("orchestrator_capabilities · 48 unmodified lines | |||
| 49 | 49 | ||
| 50 | export const DelegateTaskTool = Tool.make("delegate_task", { | 50 | export const DelegateTaskTool = Tool.make("delegate_task", { |
| 51 | description: | 51 | description: |
| 52 | "Delegate one task to a T3-owned child agent/subagent of THIS thread and run it with only the supplied task prompt, without copying parent conversation history. Use this whenever the user asks for an agent, subagent, worker, delegated task, or parallel help—including cross-provider work. The childThreadId is backing storage, not an ordinary top-level thread. Provider, model, model options (see orchestrator_capabilities), runtime mode, and interaction mode inherit unless target overrides them. Prefer mode='async' for long work; mode='wait' blocks until completion or timeout. An async child's completion wakes this thread with a continuation message naming the task (queued behind any turn in progress), so end the turn instead of polling or spawning watchers; use task_status only when the result is needed mid-turn.", | 52 | "Delegate one task to a T3-owned child agent/subagent of THIS thread and run it with only the supplied task prompt, without copying parent conversation history. Use this whenever the user asks for an agent, subagent, worker, delegated task, or parallel help, including cross-provider work. The childThreadId is backing storage, not an ordinary top-level thread. Provider, model, model options (see orchestrator_capabilities), runtime mode, and interaction mode inherit unless target overrides them. Prefer mode='async' for long work; mode='wait' blocks until completion or timeout. timeoutMs on mode=wait is only the parent's wait budget and does not cancel the child. waitTimedOut on that wait call means the timeout fired; keep that taskId and read status on later task_status. An async child's completion wakes this thread with a continuation message naming the task (queued behind any turn in progress), so end the turn instead of polling or spawning watchers; use task_status only when the result is needed mid-turn.", |
| 53 | parameters: OrchestratorMcpDelegateTaskInput, | 53 | parameters: OrchestratorMcpDelegateTaskInput, |
| 54 | success: OrchestratorMcpDelegateTaskResult, | 54 | success: OrchestratorMcpDelegateTaskResult, |
| 55 | failure: OrchestratorMcpFailure, | 55 | failure: OrchestratorMcpFailure, |
| @@ -162,8 +162,16 @@ export const OrchestratorMcpDelegateTaskInput = Schema.Struct({ · 161 unmodified lines | |||
| 162 | target: Schema.optional(OrchestratorMcpTarget), | 162 | target: Schema.optional(OrchestratorMcpTarget), |
| 163 | title: Schema.optional(OrchestratorMcpTitle), | 163 | title: Schema.optional(OrchestratorMcpTitle), |
| 164 | role: Schema.optional(OrchestratorMcpTaskRole), | 164 | role: Schema.optional(OrchestratorMcpTaskRole), |
| 165 | mode: Schema.optional(Schema.Literals(["async", "wait"])), | 165 | mode: Schema.optional( |
| 166 | timeoutMs: Schema.optional(Schema.Number), | 166 | Schema.Literals(["async", "wait"]).annotate({ |
| 167 | description: | ||
| 168 | "Defaults to async. Use wait only when this turn needs the child's result before you can continue.", | ||
| 169 | }), | ||
| 170 | ), | ||
| 171 | timeoutMs: Schema.optional(Schema.Number).annotate({ | ||
| 172 | description: | ||
| 173 | "Wait budget for mode=wait only. Default 10 minutes. Elapsing it returns waitTimedOut=true on that call and does not cancel the child.", | ||
| 174 | }), | ||
| 167 | clientRequestId: Schema.optional(OrchestratorMcpClientRequestId), | 175 | clientRequestId: Schema.optional(OrchestratorMcpClientRequestId), |
| 168 | runtimeMode: Schema.optional(OrchestratorMcpRuntimeMode), | 176 | runtimeMode: Schema.optional(OrchestratorMcpRuntimeMode), |
| 169 | interactionMode: Schema.optional(OrchestratorMcpInteractionMode), | 177 | interactionMode: Schema.optional(OrchestratorMcpInteractionMode), |
| @@ -180,7 +188,10 @@ export const OrchestratorMcpDelegateTaskResult = Schema.Struct({ · 10 unmodified lines | |||
| 180 | model: Schema.NullOr(Schema.String), | 188 | model: Schema.NullOr(Schema.String), |
| 181 | summary: Schema.NullOr(Schema.String), | 189 | summary: Schema.NullOr(Schema.String), |
| 182 | resultContextTransferId: Schema.NullOr(ContextTransferId), | 190 | resultContextTransferId: Schema.NullOr(ContextTransferId), |
| 183 | waitTimedOut: Schema.Boolean, | 191 | waitTimedOut: Schema.Boolean.annotate({ |
| 192 | description: | ||
| 193 | "True only on that mode=wait call when timeoutMs elapsed. The timeout does not cancel the child. Later task_status reads return false and use status for liveness.", | ||
| 194 | }), | ||
| 184 | }); | 195 | }); |
| 185 | export type OrchestratorMcpDelegateTaskResult = typeof OrchestratorMcpDelegateTaskResult.Type; | 196 | export type OrchestratorMcpDelegateTaskResult = typeof OrchestratorMcpDelegateTaskResult.Type; |
| 186 | 197 | ||