@namzu/sdk 5.2.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +237 -0
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +13 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/gateway/__tests__/completion-inbox.test.d.ts +2 -0
- package/dist/gateway/__tests__/completion-inbox.test.d.ts.map +1 -0
- package/dist/gateway/__tests__/completion-inbox.test.js +258 -0
- package/dist/gateway/__tests__/completion-inbox.test.js.map +1 -0
- package/dist/gateway/completion-inbox.d.ts +113 -0
- package/dist/gateway/completion-inbox.d.ts.map +1 -0
- package/dist/gateway/completion-inbox.js +211 -0
- package/dist/gateway/completion-inbox.js.map +1 -0
- package/dist/provider/__tests__/strict-schema.test.js +50 -2
- package/dist/provider/__tests__/strict-schema.test.js.map +1 -1
- package/dist/provider/__tests__/vendor-detail.test.d.ts +2 -0
- package/dist/provider/__tests__/vendor-detail.test.d.ts.map +1 -0
- package/dist/provider/__tests__/vendor-detail.test.js +89 -0
- package/dist/provider/__tests__/vendor-detail.test.js.map +1 -0
- package/dist/provider/errors.d.ts +38 -5
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +107 -5
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/strict-schema.d.ts.map +1 -1
- package/dist/provider/strict-schema.js +64 -8
- package/dist/provider/strict-schema.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +10 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/__tests__/dialect.test.d.ts +2 -0
- package/dist/registry/tool/__tests__/dialect.test.d.ts.map +1 -0
- package/dist/registry/tool/__tests__/dialect.test.js +146 -0
- package/dist/registry/tool/__tests__/dialect.test.js.map +1 -0
- package/dist/registry/tool/dialect.d.ts +50 -0
- package/dist/registry/tool/dialect.d.ts.map +1 -0
- package/dist/registry/tool/dialect.js +138 -0
- package/dist/registry/tool/dialect.js.map +1 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +34 -0
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/toolset/catalog.d.ts.map +1 -1
- package/dist/registry/toolset/catalog.js +10 -5
- package/dist/registry/toolset/catalog.js.map +1 -1
- package/dist/runtime/query/__tests__/completion-notification.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/completion-notification.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/completion-notification.test.js +168 -0
- package/dist/runtime/query/__tests__/completion-notification.test.js.map +1 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.js +171 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.js.map +1 -0
- package/dist/runtime/query/__tests__/stream-recovery.test.js +6 -0
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +14 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +20 -1
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +9 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +74 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +6 -0
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/coordinator/__tests__/completion-delivery.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js +255 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js +13 -7
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts +35 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +222 -16
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +20 -4
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +13 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/SupervisorAgent.ts +14 -0
- package/src/gateway/__tests__/completion-inbox.test.ts +320 -0
- package/src/gateway/completion-inbox.ts +227 -0
- package/src/provider/__tests__/strict-schema.test.ts +58 -2
- package/src/provider/__tests__/vendor-detail.test.ts +107 -0
- package/src/provider/errors.ts +106 -5
- package/src/provider/strict-schema.ts +65 -8
- package/src/public-runtime.ts +11 -0
- package/src/registry/tool/__tests__/dialect.test.ts +200 -0
- package/src/registry/tool/dialect.ts +143 -0
- package/src/registry/tool/execute.ts +35 -0
- package/src/registry/toolset/catalog.ts +10 -5
- package/src/runtime/query/__tests__/completion-notification.test.ts +196 -0
- package/src/runtime/query/__tests__/step-allow-list.test.ts +205 -0
- package/src/runtime/query/__tests__/stream-recovery.test.ts +6 -0
- package/src/runtime/query/executor.ts +21 -1
- package/src/runtime/query/index.ts +11 -0
- package/src/runtime/query/iteration/index.ts +77 -1
- package/src/runtime/query/iteration/phases/context.ts +10 -0
- package/src/runtime/query/result.ts +6 -0
- package/src/tools/coordinator/__tests__/completion-delivery.test.ts +347 -0
- package/src/tools/coordinator/__tests__/task-list.test.ts +13 -7
- package/src/tools/coordinator/index.ts +250 -20
- package/src/types/provider/error.ts +20 -4
- package/src/types/tool/index.ts +13 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
+
import type { CompletionInbox } from '../../gateway/completion-inbox.js'
|
|
2
3
|
import type { PlanManager } from '../../manager/plan/lifecycle.js'
|
|
3
4
|
import type { PendingAnswers, QuestionParkRecorder } from '../../runtime/query/question-park.js'
|
|
4
5
|
import type { AgentRuntimeContext } from '../../types/agent/base.js'
|
|
@@ -41,6 +42,17 @@ export interface CoordinatorToolsOptions {
|
|
|
41
42
|
|
|
42
43
|
onTaskLaunched?: TaskLaunchedCallback
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Where a completion goes when no call is left waiting for it.
|
|
47
|
+
*
|
|
48
|
+
* These tools claim a completion the moment they hand it to the model as a
|
|
49
|
+
* `tool_result`; anything unclaimed is delivered to the transcript as a
|
|
50
|
+
* notification instead. Without an inbox the tools still work and the
|
|
51
|
+
* blocking path is unchanged — only the abandoned and background
|
|
52
|
+
* completions go unheard, which is the behaviour before this existed.
|
|
53
|
+
*/
|
|
54
|
+
completionInbox?: CompletionInbox
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* HITL park channel for `ask_user_question`. The tool is registered
|
|
46
58
|
* only when BOTH `resumeHandler` and `runId` are present — without a
|
|
@@ -203,6 +215,40 @@ function delegateSchema(agentIds: readonly string[]): z.ZodType<string> {
|
|
|
203
215
|
return z.enum(agentIds as [string, ...string[]])
|
|
204
216
|
}
|
|
205
217
|
|
|
218
|
+
/**
|
|
219
|
+
* How much of a finished worker's output the listing inlines per task.
|
|
220
|
+
*
|
|
221
|
+
* A listing is consulted when several tasks are in flight, so the whole of
|
|
222
|
+
* every result would be a wall. Enough to be usable, with `wait_for_task`
|
|
223
|
+
* named as the way to get the rest.
|
|
224
|
+
*/
|
|
225
|
+
const LISTED_RESULT_LIMIT = 2_000
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* How long a coordinator tool may wait on a delegated agent.
|
|
229
|
+
*
|
|
230
|
+
* The executor's own default is two minutes, sized for a file read or a
|
|
231
|
+
* test run, and its docstring says outright that a tool which legitimately
|
|
232
|
+
* runs longer declares its own. This one runs an entire agent, and did not.
|
|
233
|
+
*
|
|
234
|
+
* Measured on real traffic: three delegated children took 4m21s, 5m58s and
|
|
235
|
+
* 8m04s; all three parents timed out at 120s. The children were never
|
|
236
|
+
* killed — only the parent's wait was — so the blocking path was not
|
|
237
|
+
* occasionally missed, it was structurally unreachable, and the model was
|
|
238
|
+
* left polling a listing because that was the only move left to it.
|
|
239
|
+
*
|
|
240
|
+
* An hour rather than "a bit more than eight minutes" because a generic
|
|
241
|
+
* stopwatch is the wrong instrument for a child that is making progress:
|
|
242
|
+
* a failure should come from what the child is doing, not from the clock
|
|
243
|
+
* the parent happens to be holding. Peer runtimes agree — the ones that
|
|
244
|
+
* bound a delegated child at all land on an hour, and several impose no
|
|
245
|
+
* wall-clock bound whatsoever, bounding turns or depth instead.
|
|
246
|
+
*
|
|
247
|
+
* A wedged child is still caught, an hour later, and the run budget and
|
|
248
|
+
* iteration ceiling both still apply above this.
|
|
249
|
+
*/
|
|
250
|
+
export const DELEGATION_TIMEOUT_MS = 60 * 60 * 1000
|
|
251
|
+
|
|
206
252
|
export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefinition[] {
|
|
207
253
|
const {
|
|
208
254
|
gateway,
|
|
@@ -213,6 +259,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
213
259
|
resumeHandler,
|
|
214
260
|
questionParks,
|
|
215
261
|
pendingAnswers,
|
|
262
|
+
completionInbox,
|
|
216
263
|
// `onTaskLaunched` was the entry point for the old
|
|
217
264
|
// non-blocking + envelope-injection flow. create_task is now
|
|
218
265
|
// blocking, so the callback is no longer wired here.
|
|
@@ -227,7 +274,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
227
274
|
|
|
228
275
|
const createTask = defineTool({
|
|
229
276
|
name: 'create_task',
|
|
230
|
-
description: `Launch a task on a specialized agent
|
|
277
|
+
description: `Launch a task on a specialized agent. By default this BLOCKS and returns the agent's final output as this call's tool_result; pass background: true to get a task_id back immediately and receive the result later as a task notification. Available agents: ${agentIds.join(', ')}. Prefer compact assignments; for large context, write/read shared workspace files and pass filenames or references. To launch multiple tasks in parallel, call this tool multiple times in a single assistant turn — the runtime executes every tool_use block from one response concurrently and delivers all tool_results together, so 'fan out 8 specialists' is one assistant message with 8 create_task blocks. Do not race: until a worker's result reaches you, you know nothing about it — never fabricate, summarise or predict what it will say, in any form.`,
|
|
231
278
|
inputSchema: z.object({
|
|
232
279
|
agent_id: agentIdEnum.describe('Which agent to run'),
|
|
233
280
|
prompt: z
|
|
@@ -242,13 +289,24 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
242
289
|
.describe(
|
|
243
290
|
'Existing planning task ID to link. If omitted, a planning task is auto-created.',
|
|
244
291
|
),
|
|
292
|
+
background: z
|
|
293
|
+
.boolean()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe(
|
|
296
|
+
'Return immediately with a task_id instead of waiting. The result arrives later as a task notification. Use this when you have other work to do meanwhile; leave it off when the next thing you do depends on this answer.',
|
|
297
|
+
),
|
|
245
298
|
}),
|
|
246
299
|
category: 'custom',
|
|
247
300
|
permissions: [],
|
|
248
301
|
readOnly: false,
|
|
249
302
|
destructive: false,
|
|
250
303
|
concurrencySafe: true,
|
|
251
|
-
|
|
304
|
+
// See DELEGATION_TIMEOUT_MS. Ten minutes was the first attempt at
|
|
305
|
+
// this and was still a guess dressed as a measurement — real
|
|
306
|
+
// children were observed at 8m04s, which it would have survived by
|
|
307
|
+
// under two minutes.
|
|
308
|
+
timeoutMs: DELEGATION_TIMEOUT_MS,
|
|
309
|
+
async execute({ agent_id, prompt, description, plan_task_id, background }, _context) {
|
|
252
310
|
let resolvedPlanTaskId = plan_task_id
|
|
253
311
|
|
|
254
312
|
if (taskStore) {
|
|
@@ -279,14 +337,58 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
279
337
|
...(_context.parentSpan ? { parentSpan: _context.parentSpan } : {}),
|
|
280
338
|
})
|
|
281
339
|
|
|
340
|
+
if (background) {
|
|
341
|
+
// Tell the inbox to hold the run open for this. Without it the
|
|
342
|
+
// supervisor could launch a worker, answer, and settle the run
|
|
343
|
+
// while the worker was still going — discarding the result the
|
|
344
|
+
// launch existed to produce.
|
|
345
|
+
completionInbox?.expect(handle.taskId)
|
|
346
|
+
// Launched to run alongside this turn. Nothing waits on it, so
|
|
347
|
+
// its completion reaches the supervisor as a notification in the
|
|
348
|
+
// transcript instead — see `CompletionInbox`. Returning the id
|
|
349
|
+
// here is what makes that notification correlatable, and what
|
|
350
|
+
// lets `wait_for_task` and `agent_task_list` reach the output.
|
|
351
|
+
return {
|
|
352
|
+
success: true,
|
|
353
|
+
output: `Launched ${agent_id} in the background as task ${handle.taskId}. You are not waiting on it: keep working, and its result will arrive as a task notification. To fetch it yourself, call wait_for_task with this task_id.`,
|
|
354
|
+
data: {
|
|
355
|
+
task_id: handle.taskId,
|
|
356
|
+
agent_id,
|
|
357
|
+
description,
|
|
358
|
+
state: handle.state,
|
|
359
|
+
plan_task_id: resolvedPlanTaskId,
|
|
360
|
+
background: true,
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
282
365
|
// The tool returns its real result as the `tool_result` for the
|
|
283
366
|
// dispatching `tool_use`. Parallel fan-out happens at the executor
|
|
284
367
|
// layer: when the supervisor emits N `create_task` blocks in one
|
|
285
368
|
// assistant turn, the runtime runs them together and delivers all
|
|
286
|
-
// N `tool_result`s at once. No
|
|
287
|
-
//
|
|
288
|
-
// reject a duplicated id outright.
|
|
369
|
+
// N `tool_result`s at once. No second `tool_result` for the
|
|
370
|
+
// same `tool_use_id` — providers reject a duplicated id outright.
|
|
289
371
|
const completed = await gateway.waitForTask(handle.taskId)
|
|
372
|
+
|
|
373
|
+
// Whether this call is still the live path decides who delivers the
|
|
374
|
+
// result. If the executor already gave up on us — its deadline
|
|
375
|
+
// passed and the model was told "timed out, it may still be
|
|
376
|
+
// running" — then NOT claiming is what routes this completion to the
|
|
377
|
+
// transcript as a notification. Claiming it would delete the output.
|
|
378
|
+
//
|
|
379
|
+
// The returned value below goes nowhere: the executor won its race
|
|
380
|
+
// and returned already, so this result is discarded. It is written
|
|
381
|
+
// out anyway because a bare `return` here would read as an oversight,
|
|
382
|
+
// and because a host reading tool outcomes off its own instrumentation
|
|
383
|
+
// should find the reason rather than an empty string.
|
|
384
|
+
if (_context.abortSignal?.aborted) {
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
output: `This wait was abandoned before ${agent_id} finished; its result will arrive separately as a task notification (task ${handle.taskId}).`,
|
|
388
|
+
data: { task_id: handle.taskId, agent_id, abandoned: true },
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
completionInbox?.claim(handle.taskId)
|
|
290
392
|
const success = completed.state === 'completed'
|
|
291
393
|
const resultText =
|
|
292
394
|
completed.result?.result ??
|
|
@@ -350,7 +452,10 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
350
452
|
readOnly: false,
|
|
351
453
|
destructive: false,
|
|
352
454
|
concurrencySafe: true,
|
|
353
|
-
|
|
455
|
+
// It waits on a child exactly as create_task does, so it inherits
|
|
456
|
+
// the same bound rather than the file-read default.
|
|
457
|
+
timeoutMs: DELEGATION_TIMEOUT_MS,
|
|
458
|
+
async execute({ task_id, message }, _context) {
|
|
354
459
|
await gateway.continueTask(task_id as TaskId, message)
|
|
355
460
|
// Mirror create_task's blocking pattern: await the new
|
|
356
461
|
// completion and return the agent's output inline. The
|
|
@@ -359,6 +464,17 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
359
464
|
// onTaskCompleted listener that the iteration loop
|
|
360
465
|
// no longer registers (envelope path is dead).
|
|
361
466
|
const completed = await gateway.waitForTask(task_id as TaskId)
|
|
467
|
+
// Same reasoning as create_task: the model already has a timeout
|
|
468
|
+
// for this call, so leaving the completion unclaimed is what sends
|
|
469
|
+
// it to the transcript as a notification.
|
|
470
|
+
if (_context.abortSignal?.aborted) {
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
output: `This wait was abandoned before task ${task_id} finished; its result will arrive separately as a task notification.`,
|
|
474
|
+
data: { task_id, abandoned: true },
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
completionInbox?.claim(task_id as TaskId)
|
|
362
478
|
const success = completed.state === 'completed'
|
|
363
479
|
const resultText =
|
|
364
480
|
completed.result?.result ??
|
|
@@ -372,6 +488,75 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
372
488
|
},
|
|
373
489
|
})
|
|
374
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Join a task already running, without sending it anything.
|
|
493
|
+
*
|
|
494
|
+
* `continue_task` blocks, but only as a side effect of sending a
|
|
495
|
+
* message — so a supervisor that merely wanted to wait had to invent
|
|
496
|
+
* something to say, and one that would not do that was left calling
|
|
497
|
+
* `agent_task_list` in a sleep loop. That polling was never the model
|
|
498
|
+
* misbehaving; it was the only move on the board.
|
|
499
|
+
*/
|
|
500
|
+
const waitForTaskTool = defineTool({
|
|
501
|
+
name: 'wait_for_task',
|
|
502
|
+
description:
|
|
503
|
+
'Block until an already-running task finishes and return its output. Use this instead of listing tasks in a loop: it costs one call and no waiting turns. Give it a task_id from a background create_task or from a task notification.',
|
|
504
|
+
inputSchema: z.object({
|
|
505
|
+
task_id: z.string().describe('Agent task ID to wait for'),
|
|
506
|
+
}),
|
|
507
|
+
category: 'custom',
|
|
508
|
+
permissions: [],
|
|
509
|
+
readOnly: true,
|
|
510
|
+
destructive: false,
|
|
511
|
+
concurrencySafe: true,
|
|
512
|
+
// A tool whose entire purpose is to wait must not be cut off for
|
|
513
|
+
// waiting. Same bound as the launch it is waiting on.
|
|
514
|
+
timeoutMs: DELEGATION_TIMEOUT_MS,
|
|
515
|
+
async execute({ task_id }, _context) {
|
|
516
|
+
const known = gateway.getTask(task_id as TaskId)
|
|
517
|
+
if (!known) {
|
|
518
|
+
return {
|
|
519
|
+
success: false,
|
|
520
|
+
output: `No task ${task_id}. Call agent_task_list to see which tasks exist.`,
|
|
521
|
+
data: { task_id },
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const completed = await gateway.waitForTask(task_id as TaskId)
|
|
526
|
+
if (_context.abortSignal?.aborted) {
|
|
527
|
+
return {
|
|
528
|
+
success: false,
|
|
529
|
+
output: `This wait was abandoned before task ${task_id} finished; its result will arrive separately as a task notification.`,
|
|
530
|
+
data: { task_id, abandoned: true },
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
completionInbox?.claim(task_id as TaskId)
|
|
534
|
+
|
|
535
|
+
const success = completed.state === 'completed'
|
|
536
|
+
const resultText =
|
|
537
|
+
completed.result?.result ??
|
|
538
|
+
completed.result?.lastError ??
|
|
539
|
+
`Task finished with state: ${completed.state}`
|
|
540
|
+
return {
|
|
541
|
+
success,
|
|
542
|
+
output: wrapUntrusted(
|
|
543
|
+
{
|
|
544
|
+
kind: 'agent-result',
|
|
545
|
+
attributes: { agent: completed.agentId, task: completed.taskId },
|
|
546
|
+
provenance: `This is the output of the delegated agent "${completed.agentId}", not this agent's own work.`,
|
|
547
|
+
},
|
|
548
|
+
resultText,
|
|
549
|
+
),
|
|
550
|
+
data: {
|
|
551
|
+
task_id,
|
|
552
|
+
agent_id: completed.agentId,
|
|
553
|
+
state: completed.state,
|
|
554
|
+
result: resultText,
|
|
555
|
+
},
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
})
|
|
559
|
+
|
|
375
560
|
const cancelTask = defineTool({
|
|
376
561
|
name: 'cancel_task',
|
|
377
562
|
description:
|
|
@@ -386,6 +571,12 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
386
571
|
concurrencySafe: true,
|
|
387
572
|
async execute({ task_id }) {
|
|
388
573
|
gateway.cancelTask(task_id as TaskId)
|
|
574
|
+
// Stop holding the run open for it. `expect` put this task on the
|
|
575
|
+
// inbox's outstanding list at launch and only a completion takes it
|
|
576
|
+
// off — so without this a cancelled worker kept `hasPendingWork`
|
|
577
|
+
// true and every attempt to settle paid the full grace period
|
|
578
|
+
// waiting for a result that had just been called off.
|
|
579
|
+
completionInbox?.forget(task_id as TaskId)
|
|
389
580
|
return {
|
|
390
581
|
success: true,
|
|
391
582
|
output: `Task ${task_id} cancelled`,
|
|
@@ -397,7 +588,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
397
588
|
const agentTaskList = defineTool({
|
|
398
589
|
name: 'agent_task_list',
|
|
399
590
|
description:
|
|
400
|
-
"Inspect the live state of every agent task launched on this gateway via create_task: returns each task's id, agent, state (pending/running/completed/failed/canceled), and timing. Distinct from the plan-task store's `task_list` (which lists planning tasks): this tool lists running/completed worker invocations.
|
|
591
|
+
"Inspect the live state of every agent task launched on this gateway via create_task: returns each task's id, agent, state (pending/running/completed/failed/canceled), and timing. Distinct from the plan-task store's `task_list` (which lists planning tasks): this tool lists running/completed worker invocations. Do NOT call this to find out whether work finished: a blocking create_task has already returned each worker's output, and a backgrounded one arrives as a task notification. Use it when you need to see what is still running, or to re-read the output of a task whose launch you stopped waiting for.",
|
|
401
592
|
inputSchema: z.object({
|
|
402
593
|
state: z
|
|
403
594
|
.enum(['pending', 'running', 'completed', 'failed', 'canceled'])
|
|
@@ -415,6 +606,14 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
415
606
|
const items = filtered.map((h) => {
|
|
416
607
|
const runStatus = h.result?.status
|
|
417
608
|
const lastError = h.result?.lastError ?? undefined
|
|
609
|
+
// The worker's actual output, which this listing used to drop. It
|
|
610
|
+
// read `h.result` for the status and the error and stopped one
|
|
611
|
+
// property short of the thing the task was launched to produce —
|
|
612
|
+
// so a supervisor that knew a task_id and knew it had completed
|
|
613
|
+
// still had no way to read what it said. That is the state a run
|
|
614
|
+
// lands in whenever the launching call was abandoned, which is
|
|
615
|
+
// exactly when this listing gets consulted.
|
|
616
|
+
const output = h.result?.result ?? undefined
|
|
418
617
|
return {
|
|
419
618
|
task_id: h.taskId,
|
|
420
619
|
agent_id: h.agentId,
|
|
@@ -424,6 +623,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
424
623
|
completed_at: h.completedAt ? new Date(h.completedAt).toISOString() : null,
|
|
425
624
|
duration_ms: h.completedAt ? h.completedAt - h.createdAt : null,
|
|
426
625
|
last_error: lastError,
|
|
626
|
+
...(output !== undefined ? { result: output } : {}),
|
|
427
627
|
}
|
|
428
628
|
})
|
|
429
629
|
const summary = {
|
|
@@ -434,12 +634,26 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
434
634
|
canceled: handles.filter((h) => h.state === 'canceled').length,
|
|
435
635
|
}
|
|
436
636
|
const lines = items.length
|
|
437
|
-
? items.map(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
637
|
+
? items.map((i) => {
|
|
638
|
+
const head = `- ${i.task_id} → ${i.agent_id} [${i.state}${i.run_status && i.run_status !== i.state ? ` / ${i.run_status}` : ''}]${
|
|
639
|
+
i.duration_ms !== null ? ` (${Math.round(i.duration_ms / 1000)}s)` : ''
|
|
640
|
+
}${i.last_error ? ` — error: ${i.last_error.slice(0, 200)}` : ''}`
|
|
641
|
+
// The output goes in the rendered TEXT, not only in `data`.
|
|
642
|
+
// Only `output` becomes the tool_result the model reads —
|
|
643
|
+
// the executor never looks at `data` — so a result added
|
|
644
|
+
// to the projection alone would have been added to a field the
|
|
645
|
+
// model cannot see, which is how this listing came to prove a
|
|
646
|
+
// task had finished while withholding what it said.
|
|
647
|
+
if (i.result === undefined) return head
|
|
648
|
+
const body =
|
|
649
|
+
i.result.length > LISTED_RESULT_LIMIT
|
|
650
|
+
? `${i.result.slice(0, LISTED_RESULT_LIMIT)}\n … truncated; call wait_for_task with "${i.task_id}" for the whole thing.`
|
|
651
|
+
: i.result
|
|
652
|
+
return `${head}\n${body
|
|
653
|
+
.split('\n')
|
|
654
|
+
.map((line) => ` ${line}`)
|
|
655
|
+
.join('\n')}`
|
|
656
|
+
})
|
|
443
657
|
: ['(no tasks launched yet)']
|
|
444
658
|
const header = `Tasks: ${summary.total} total — ${summary.running} running, ${summary.completed} completed, ${summary.failed} failed, ${summary.canceled} canceled`
|
|
445
659
|
return {
|
|
@@ -462,12 +676,20 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
462
676
|
// non-default gateway (one that keeps the worker process alive
|
|
463
677
|
// for follow-ups) wants to re-register it.
|
|
464
678
|
void continueTask
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
679
|
+
// `cancel_task` is registered again, and the reasoning that dropped it is
|
|
680
|
+
// worth keeping because it was sound at the time and is not any more.
|
|
681
|
+
//
|
|
682
|
+
// It read: per-task cancellation belonged to the old non-blocking worker
|
|
683
|
+
// protocol; since `create_task` blocks and returns the output as its
|
|
684
|
+
// tool_result, every worker is terminal by the time a later turn learns its
|
|
685
|
+
// id, so the tool could only manufacture a "cancelled" for something
|
|
686
|
+
// already finished.
|
|
687
|
+
//
|
|
688
|
+
// That held for exactly as long as blocking was the only way to launch.
|
|
689
|
+
// `background: true` brings back a worker that is running with nothing
|
|
690
|
+
// waiting on it and whose id the supervisor holds while it is still alive —
|
|
691
|
+
// the precondition the old rationale said had disappeared. A launch the
|
|
692
|
+
// model cannot stop is a hole, and this is the tool that closes it.
|
|
471
693
|
// An empty roster withholds `create_task` rather than mounting an
|
|
472
694
|
// unsatisfiable one. Mounting it and refusing at parse time reaches the
|
|
473
695
|
// same verdict, but it reaches it the expensive way: the model is shown a
|
|
@@ -485,8 +707,16 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
485
707
|
// and a human channel" stays a supported configuration, which is why this
|
|
486
708
|
// omits rather than refusing to build: a caller asking this builder for
|
|
487
709
|
// `ask_user_question` with no roster is doing something legitimate.
|
|
710
|
+
// `wait_for_task` and `cancel_task` ride with `create_task` because they
|
|
711
|
+
// are only meaningful once something has been launched.
|
|
712
|
+
//
|
|
713
|
+
// Waiting had no tool at all. `continue_task` blocks, but only as a side
|
|
714
|
+
// effect of sending a message, so a supervisor that merely wanted to wait
|
|
715
|
+
// had to invent something to say — and one that would not do that was left
|
|
716
|
+
// calling `agent_task_list` in a sleep loop. That was never the model
|
|
717
|
+
// misbehaving; it was the only move available.
|
|
488
718
|
const tools: ToolDefinition[] =
|
|
489
|
-
agentIds.length > 0 ? [createTask, agentTaskList] : [agentTaskList]
|
|
719
|
+
agentIds.length > 0 ? [createTask, waitForTaskTool, cancelTask, agentTaskList] : [agentTaskList]
|
|
490
720
|
|
|
491
721
|
if (getPlanManager) {
|
|
492
722
|
const approvePlan = defineTool({
|
|
@@ -10,20 +10,36 @@ export type ProviderErrorKind =
|
|
|
10
10
|
/**
|
|
11
11
|
* Serializable provider-failure metadata carried by failed runs and events.
|
|
12
12
|
*
|
|
13
|
-
* No response body,
|
|
13
|
+
* No response body, URL, or `cause` belongs here. `detail` is the one thing
|
|
14
|
+
* the provider itself said, and it arrives scrubbed — see below.
|
|
14
15
|
*/
|
|
15
16
|
export interface ProviderErrorInfo {
|
|
16
17
|
readonly kind: ProviderErrorKind
|
|
17
18
|
readonly providerId: string
|
|
18
19
|
readonly status?: number
|
|
19
20
|
readonly retryAfterMs?: number
|
|
21
|
+
/**
|
|
22
|
+
* What the provider said was wrong, truncated and scrubbed of anything
|
|
23
|
+
* credential-shaped.
|
|
24
|
+
*
|
|
25
|
+
* Carried here and not only on the error's `message` for the same reason
|
|
26
|
+
* `kind` is: a host rendering a failure should not have to parse a
|
|
27
|
+
* sentence to show one. It is the field that names the offending
|
|
28
|
+
* parameter, which is usually the whole diagnosis.
|
|
29
|
+
*/
|
|
30
|
+
readonly detail?: string
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export interface ProviderRequestErrorInit extends ProviderErrorInfo {
|
|
23
34
|
/**
|
|
24
|
-
* Optional extra clause for the message.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
35
|
+
* Optional extra clause for the message.
|
|
36
|
+
*
|
|
37
|
+
* This used to be required to be text the codebase authored, never a
|
|
38
|
+
* fragment of a vendor error — and the constructor did not read the field
|
|
39
|
+
* at all, so nothing carried either kind of text. Providers may now pass
|
|
40
|
+
* their own complaint through `vendorDetail`, which truncates it and
|
|
41
|
+
* replaces anything credential-shaped. Text this codebase authored is
|
|
42
|
+
* still welcome; what is not welcome is a raw body passed straight in.
|
|
27
43
|
*/
|
|
28
44
|
readonly detail?: string
|
|
29
45
|
}
|
package/src/types/tool/index.ts
CHANGED
|
@@ -100,6 +100,19 @@ export interface ToolContext {
|
|
|
100
100
|
invocationState?: InvocationState
|
|
101
101
|
|
|
102
102
|
toolRegistry?: ToolRegistryRef
|
|
103
|
+
/**
|
|
104
|
+
* The names this turn may call, if the turn was narrowed.
|
|
105
|
+
*
|
|
106
|
+
* Enforced at dispatch, not only used to decide which schemas the model is
|
|
107
|
+
* shown. It was the latter alone for a while, which made the narrowing
|
|
108
|
+
* presentational: a step could withhold a tool from the request and the
|
|
109
|
+
* executor would still run it when the model named it anyway — from
|
|
110
|
+
* repeated context, from a gateway with its own tool memory, or from a
|
|
111
|
+
* replayed prefix.
|
|
112
|
+
*
|
|
113
|
+
* Absent means no narrowing, which is not the same as an empty list: an
|
|
114
|
+
* empty list is a turn that may call nothing.
|
|
115
|
+
*/
|
|
103
116
|
allowedTools?: readonly string[]
|
|
104
117
|
sandbox?: Sandbox
|
|
105
118
|
fileReadTracker?: FileReadTracker
|