@namzu/sdk 10.0.0 → 11.0.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 +81 -0
- package/dist/manager/plan/lifecycle.d.ts +10 -0
- package/dist/manager/plan/lifecycle.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.js +14 -0
- package/dist/manager/plan/lifecycle.js.map +1 -1
- package/dist/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.js +90 -0
- package/dist/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.js.map +1 -0
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +17 -1
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/coordinator/__tests__/a-listing-is-not-a-back-door.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/a-listing-is-not-a-back-door.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/a-listing-is-not-a-back-door.test.js +139 -0
- package/dist/tools/coordinator/__tests__/a-listing-is-not-a-back-door.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/a-plan-step-reports-its-own-outcome.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/a-plan-step-reports-its-own-outcome.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/a-plan-step-reports-its-own-outcome.test.js +160 -0
- package/dist/tools/coordinator/__tests__/a-plan-step-reports-its-own-outcome.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js +21 -6
- package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js +4 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/task-list.test.js +44 -15
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +159 -8
- package/dist/tools/coordinator/index.js.map +1 -1
- package/package.json +1 -1
- package/src/manager/plan/lifecycle.ts +14 -0
- package/src/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.ts +109 -0
- package/src/runtime/query/result.ts +18 -1
- package/src/tools/coordinator/__tests__/a-listing-is-not-a-back-door.test.ts +171 -0
- package/src/tools/coordinator/__tests__/a-plan-step-reports-its-own-outcome.test.ts +215 -0
- package/src/tools/coordinator/__tests__/approve-plan.test.ts +32 -11
- package/src/tools/coordinator/__tests__/completion-delivery.test.ts +7 -0
- package/src/tools/coordinator/__tests__/task-list.test.ts +47 -20
- package/src/tools/coordinator/index.ts +177 -8
|
@@ -308,6 +308,33 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
308
308
|
// compatibility (Agent tool consumes it from its own path).
|
|
309
309
|
} = opts
|
|
310
310
|
const cwd = opts.workingDirectory
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The tasks THIS surface launched — the scope of everything it will read back.
|
|
314
|
+
*
|
|
315
|
+
* A `TaskGateway` is shared on purpose: `SupervisorAgentConfig.gateway`
|
|
316
|
+
* exists so a host can hand the same one to several runs. `listTasks()` is
|
|
317
|
+
* therefore gateway-wide by design, and `agent_task_list` used to hand that
|
|
318
|
+
* straight to the model — so a supervisor could read a sibling run's worker
|
|
319
|
+
* output, including the `result` field, by listing. `wait_for_task` had the
|
|
320
|
+
* same reach through `getTask`.
|
|
321
|
+
*
|
|
322
|
+
* That is the leak `CompletionInbox` closed on the push side, through a
|
|
323
|
+
* different door: the inbox refuses a completion for a task it was not told
|
|
324
|
+
* about, precisely because `onTaskCompleted` is a broadcast. The pull side
|
|
325
|
+
* kept no such record and asked the gateway directly.
|
|
326
|
+
*
|
|
327
|
+
* The scope lives here rather than in `listTasks()` because the two answer
|
|
328
|
+
* different questions. A host calling `listTasks()` is the operator and may
|
|
329
|
+
* legitimately want everything on its gateway; a model calling
|
|
330
|
+
* `agent_task_list` is one run asking about its own work. Narrowing the
|
|
331
|
+
* gateway method would take the operator's view away to fix the model's.
|
|
332
|
+
*
|
|
333
|
+
* Consequence worth stating: a task launched through a DIFFERENT surface on
|
|
334
|
+
* the same gateway — `buildAgentTool`, or the host itself — is not listed
|
|
335
|
+
* here. That is the same rule, not an exception to it.
|
|
336
|
+
*/
|
|
337
|
+
const launchedHere = new Set<TaskId>()
|
|
311
338
|
void opts.onTaskLaunched
|
|
312
339
|
|
|
313
340
|
const agentIdEnum = delegateSchema(agentIds)
|
|
@@ -386,6 +413,12 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
386
413
|
.describe(
|
|
387
414
|
'Existing planning task ID to link. If omitted, a planning task is auto-created.',
|
|
388
415
|
),
|
|
416
|
+
plan_step_id: z
|
|
417
|
+
.string()
|
|
418
|
+
.optional()
|
|
419
|
+
.describe(
|
|
420
|
+
'The approve_plan step this launch carries out (e.g. "step_2"). Pass it and the step reports its own outcome — running on launch, completed or failed when the worker settles — so the plan can say how it went. Omit it only when this launch is not part of the approved plan.',
|
|
421
|
+
),
|
|
389
422
|
...(canLaunchInBackground
|
|
390
423
|
? {
|
|
391
424
|
background: z
|
|
@@ -407,9 +440,24 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
407
440
|
// children were observed at 8m04s, which it would have survived by
|
|
408
441
|
// under two minutes.
|
|
409
442
|
timeoutMs: DELEGATION_TIMEOUT_MS,
|
|
410
|
-
async execute(
|
|
443
|
+
async execute(
|
|
444
|
+
{ agent_id, prompt, description, plan_task_id, plan_step_id, background },
|
|
445
|
+
_context,
|
|
446
|
+
) {
|
|
411
447
|
let resolvedPlanTaskId = plan_task_id
|
|
412
448
|
|
|
449
|
+
// The binding between a plan step and the work that carries it out.
|
|
450
|
+
// Without it a plan's steps had no relationship to any tool call, so
|
|
451
|
+
// nothing could ever observe how a step went — which is why a plan
|
|
452
|
+
// could report `failed` or stay `executing` forever but never
|
|
453
|
+
// `completed`.
|
|
454
|
+
const planStepId = plan_step_id
|
|
455
|
+
const reportStep = (status: 'running' | 'completed' | 'failed', error?: string): void => {
|
|
456
|
+
if (!planStepId) return
|
|
457
|
+
getPlanManager?.()?.updateStepStatus(planStepId, status, error)
|
|
458
|
+
}
|
|
459
|
+
reportStep('running')
|
|
460
|
+
|
|
413
461
|
if (taskStore) {
|
|
414
462
|
if (resolvedPlanTaskId) {
|
|
415
463
|
await taskStore.update(resolvedPlanTaskId as `task_${string}`, {
|
|
@@ -446,6 +494,9 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
446
494
|
// is exactly the blocking launch whose wait was abandoned.
|
|
447
495
|
completionInbox?.launched(handle.taskId)
|
|
448
496
|
|
|
497
|
+
// ...and whose it is for the READ side too. See `launchedHere`.
|
|
498
|
+
launchedHere.add(handle.taskId)
|
|
499
|
+
|
|
449
500
|
// A background launch asked for with nowhere to deliver it is
|
|
450
501
|
// REFUSED, not quietly turned into a blocking one.
|
|
451
502
|
//
|
|
@@ -469,6 +520,10 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
469
520
|
description: 'Failed: the launch was refused before any worker started',
|
|
470
521
|
})
|
|
471
522
|
}
|
|
523
|
+
// Same reason the plan task is closed here: nothing is running,
|
|
524
|
+
// so a step left `running` would show work underway with no
|
|
525
|
+
// worker behind it, and the plan could never settle.
|
|
526
|
+
reportStep('failed', 'the launch was refused before any worker started')
|
|
472
527
|
return {
|
|
473
528
|
success: false,
|
|
474
529
|
output: '',
|
|
@@ -569,6 +624,11 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
569
624
|
})
|
|
570
625
|
}
|
|
571
626
|
|
|
627
|
+
// The step reports the same outcome, from the same two authorities.
|
|
628
|
+
// This is the only point in a delegated launch where the answer is
|
|
629
|
+
// actually known.
|
|
630
|
+
reportStep(success ? 'completed' : 'failed', success ? undefined : resultText.slice(0, 200))
|
|
631
|
+
|
|
572
632
|
return {
|
|
573
633
|
success,
|
|
574
634
|
// Framed, because a delegated worker is the component MOST
|
|
@@ -631,6 +691,21 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
631
691
|
// waiting. Same bound as the launch it is waiting on.
|
|
632
692
|
timeoutMs: DELEGATION_TIMEOUT_MS,
|
|
633
693
|
async execute({ task_id }, _context) {
|
|
694
|
+
// Same scope as the listing — see `launchedHere`. Asked FIRST, so a
|
|
695
|
+
// task belonging to a sibling run on a shared gateway is refused
|
|
696
|
+
// here rather than waited on and then read.
|
|
697
|
+
if (!launchedHere.has(task_id as TaskId)) {
|
|
698
|
+
// Deliberately does not distinguish "never existed" from
|
|
699
|
+
// "belongs to someone else". The second answer is itself the
|
|
700
|
+
// leak in miniature: it confirms a task id a run was not
|
|
701
|
+
// supposed to know about.
|
|
702
|
+
return {
|
|
703
|
+
success: false,
|
|
704
|
+
output: `No task ${task_id} was launched by this run. Call agent_task_list to see the tasks you can wait on.`,
|
|
705
|
+
data: { task_id },
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
634
709
|
const known = gateway.getTask(task_id as TaskId)
|
|
635
710
|
if (!known) {
|
|
636
711
|
return {
|
|
@@ -716,7 +791,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
716
791
|
|
|
717
792
|
const agentTaskList = defineTool({
|
|
718
793
|
name: 'agent_task_list',
|
|
719
|
-
description: `Inspect the live state of
|
|
794
|
+
description: `Inspect the live state of the agent tasks YOU launched with create_task: returns each task's id, agent, state (pending/running/completed/failed/canceled), and timing. Tasks launched by another run are not listed, even when it shares this gateway. Distinct from the plan-task store's \`task_list\` (which lists planning tasks): this tool lists running/completed worker invocations. ${listingAdvice}`,
|
|
720
795
|
inputSchema: z.object({
|
|
721
796
|
state: z
|
|
722
797
|
.enum(['pending', 'running', 'completed', 'failed', 'canceled'])
|
|
@@ -729,7 +804,10 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
729
804
|
destructive: false,
|
|
730
805
|
concurrencySafe: true,
|
|
731
806
|
async execute({ state }) {
|
|
732
|
-
|
|
807
|
+
// Scoped to this run's own launches — see `launchedHere`. The
|
|
808
|
+
// gateway may hold a sibling run's tasks, and this listing is not
|
|
809
|
+
// the door to them.
|
|
810
|
+
const handles = gateway.listTasks().filter((h) => launchedHere.has(h.taskId))
|
|
733
811
|
const filtered = state ? handles.filter((h) => h.state === state) : handles
|
|
734
812
|
const items = filtered.map((h) => {
|
|
735
813
|
const runStatus = h.result?.status
|
|
@@ -965,17 +1043,38 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
965
1043
|
|
|
966
1044
|
if (response.approved) {
|
|
967
1045
|
pm.startExecution()
|
|
1046
|
+
|
|
1047
|
+
// The step ids, because nothing else tells the model what
|
|
1048
|
+
// they are. `plan_step_id` on create_task and `step_id` on
|
|
1049
|
+
// update_plan_step are both unusable without them — a
|
|
1050
|
+
// binding the caller cannot name is a binding that does not
|
|
1051
|
+
// exist. Rendered from the plan as approved, so a plan the
|
|
1052
|
+
// user edited lists the steps they actually approved.
|
|
1053
|
+
const roster = (pm.active?.steps ?? [])
|
|
1054
|
+
.map((s) => ` ${s.id} — ${s.description}${s.agentId ? ` (${s.agentId})` : ''}`)
|
|
1055
|
+
.join('\n')
|
|
1056
|
+
const howToReport = roster
|
|
1057
|
+
? `\nSteps, and how each reports its outcome:\n${roster}\nPass plan_step_id to create_task for a delegated step; call update_plan_step for one you do yourself. The plan cannot report success until every step has reported.`
|
|
1058
|
+
: ''
|
|
1059
|
+
|
|
968
1060
|
// Approve-with-edits: when the user attached feedback to an
|
|
969
1061
|
// approval, embed it in the model-visible output so the
|
|
970
|
-
// supervisor applies the edits during execution.
|
|
971
|
-
// approve keeps the historical output byte-identical.
|
|
1062
|
+
// supervisor applies the edits during execution.
|
|
972
1063
|
const output = response.feedback
|
|
973
|
-
? `Plan approved by user with required edits — apply them during execution:\n${response.feedback}\nProceed with execution — launch workers via create_task
|
|
974
|
-
:
|
|
1064
|
+
? `Plan approved by user with required edits — apply them during execution:\n${response.feedback}\nProceed with execution — launch workers via create_task.${howToReport}`
|
|
1065
|
+
: `Plan approved by user. Proceed with execution — launch workers via create_task.${howToReport}`
|
|
975
1066
|
return {
|
|
976
1067
|
success: true,
|
|
977
1068
|
output,
|
|
978
|
-
data: {
|
|
1069
|
+
data: {
|
|
1070
|
+
approved: true,
|
|
1071
|
+
feedback: response.feedback,
|
|
1072
|
+
steps: (pm.active?.steps ?? []).map((s) => ({
|
|
1073
|
+
step_id: s.id,
|
|
1074
|
+
description: s.description,
|
|
1075
|
+
agent_id: s.agentId,
|
|
1076
|
+
})),
|
|
1077
|
+
},
|
|
979
1078
|
}
|
|
980
1079
|
}
|
|
981
1080
|
|
|
@@ -991,6 +1090,76 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
991
1090
|
},
|
|
992
1091
|
})
|
|
993
1092
|
tools.push(approvePlan)
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* How a step the ORCHESTRATOR did reports its own outcome.
|
|
1096
|
+
*
|
|
1097
|
+
* `create_task` reports the steps it carries out, which covers every
|
|
1098
|
+
* delegated step. A step with no `agent_id` is the orchestrator's own
|
|
1099
|
+
* work and has no tool call to bind to — so without this there is no
|
|
1100
|
+
* way for it to ever report, and a plan containing one could never
|
|
1101
|
+
* settle no matter how well it went.
|
|
1102
|
+
*
|
|
1103
|
+
* `skipped` is a first-class outcome and not a euphemism for failure:
|
|
1104
|
+
* a plan that turned out not to need a step went right, and forcing
|
|
1105
|
+
* that into `completed` or `failed` would make the plan lie in one
|
|
1106
|
+
* direction or the other.
|
|
1107
|
+
*/
|
|
1108
|
+
const updatePlanStep = defineTool({
|
|
1109
|
+
name: 'update_plan_step',
|
|
1110
|
+
description:
|
|
1111
|
+
'Report how a step of the approved plan went. Use it for steps YOU carried out — steps delegated with create_task report themselves when you pass plan_step_id. Call it as each step settles, not in a batch at the end: the plan cannot say it succeeded until every step has reported, and an unreported step is not scored as a failure, it simply leaves the plan unsettled. Use "skipped" for a step that turned out not to be needed; that is a successful outcome, not a failure.',
|
|
1112
|
+
inputSchema: z.object({
|
|
1113
|
+
step_id: z.string().describe('The plan step id, e.g. "step_2".'),
|
|
1114
|
+
status: z
|
|
1115
|
+
.enum(['completed', 'skipped', 'failed'])
|
|
1116
|
+
.describe(
|
|
1117
|
+
'How it went. "skipped" means the step was not needed and the plan is still on track.',
|
|
1118
|
+
),
|
|
1119
|
+
error: z
|
|
1120
|
+
.string()
|
|
1121
|
+
.optional()
|
|
1122
|
+
.describe('What went wrong. Only meaningful with status "failed".'),
|
|
1123
|
+
}),
|
|
1124
|
+
category: 'custom',
|
|
1125
|
+
permissions: [],
|
|
1126
|
+
readOnly: false,
|
|
1127
|
+
destructive: false,
|
|
1128
|
+
concurrencySafe: true,
|
|
1129
|
+
async execute({ step_id, status, error }) {
|
|
1130
|
+
const pm = getPlanManager?.()
|
|
1131
|
+
if (!pm?.active) {
|
|
1132
|
+
return {
|
|
1133
|
+
success: false,
|
|
1134
|
+
output: '',
|
|
1135
|
+
error: 'There is no active plan to report against. Call approve_plan first.',
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const step = pm.updateStepStatus(step_id, status, error)
|
|
1140
|
+
if (!step) {
|
|
1141
|
+
const known = pm.active.steps.map((s) => s.id).join(', ')
|
|
1142
|
+
return {
|
|
1143
|
+
success: false,
|
|
1144
|
+
output: '',
|
|
1145
|
+
error: `No plan step "${step_id}". This plan's steps are: ${known || '(none)'}.`,
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const outstanding = pm.unreportedSteps
|
|
1150
|
+
return {
|
|
1151
|
+
success: true,
|
|
1152
|
+
output:
|
|
1153
|
+
outstanding.length === 0
|
|
1154
|
+
? `Step ${step_id} reported as ${status}. Every step has now reported.`
|
|
1155
|
+
: `Step ${step_id} reported as ${status}. Still unreported: ${outstanding
|
|
1156
|
+
.map((s) => s.id)
|
|
1157
|
+
.join(', ')}.`,
|
|
1158
|
+
data: { step_id, status, unreported: outstanding.map((s) => s.id) },
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
})
|
|
1162
|
+
tools.push(updatePlanStep)
|
|
994
1163
|
}
|
|
995
1164
|
|
|
996
1165
|
if (resumeHandler && runId) {
|