@namzu/sdk 10.0.0 → 12.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 +124 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +12 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/contracts/api.d.ts +1 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.d.ts +11 -1
- package/dist/manager/plan/lifecycle.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.js +20 -1
- package/dist/manager/plan/lifecycle.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +2 -0
- package/dist/run/reporter.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/__tests__/a-settled-plan-reaches-the-host.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/a-settled-plan-reaches-the-host.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/a-settled-plan-reaches-the-host.test.js +97 -0
- package/dist/runtime/query/__tests__/a-settled-plan-reaches-the-host.test.js.map +1 -0
- package/dist/runtime/query/events.d.ts.map +1 -1
- package/dist/runtime/query/events.js +20 -2
- package/dist/runtime/query/events.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +14 -1
- package/dist/runtime/query/index.js.map +1 -1
- 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/dist/types/plan/index.d.ts +13 -0
- package/dist/types/plan/index.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +33 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +2 -0
- package/src/bridge/sse/mapper.ts +14 -0
- package/src/contracts/api.ts +5 -0
- package/src/manager/plan/lifecycle.ts +20 -1
- package/src/run/reporter.ts +2 -0
- package/src/runtime/query/__tests__/a-plan-that-succeeded-says-so.test.ts +109 -0
- package/src/runtime/query/__tests__/a-settled-plan-reaches-the-host.test.ts +122 -0
- package/src/runtime/query/events.ts +20 -2
- package/src/runtime/query/index.ts +15 -2
- 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
- package/src/types/plan/index.ts +14 -0
- package/src/types/run/events.ts +24 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { PlanManager } from '../../../manager/plan/lifecycle.js'
|
|
4
|
+
import type { TaskGateway, TaskHandle } from '../../../types/agent/gateway.js'
|
|
5
|
+
import type { RunId, TaskId } from '../../../types/ids/index.js'
|
|
6
|
+
import type { ToolContext, ToolDefinition } from '../../../types/tool/index.js'
|
|
7
|
+
import { buildCoordinatorTools } from '../index.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A plan's steps had no relationship to the work that carried them out.
|
|
11
|
+
*
|
|
12
|
+
* `approve_plan` built steps, `create_task` launched workers, and nothing
|
|
13
|
+
* connected the two — so no step could ever be observed, `updateStepStatus` had
|
|
14
|
+
* no production caller, and a plan could reach `failed` (the error path calls
|
|
15
|
+
* `failPlan`) or sit at `executing` forever, but never `completed`.
|
|
16
|
+
*
|
|
17
|
+
* Two bindings close it, because there are two kinds of step. A DELEGATED step
|
|
18
|
+
* reports through the `create_task` that carries it out. An ORCHESTRATOR-OWNED
|
|
19
|
+
* step has no tool call to bind to at all, and reports through
|
|
20
|
+
* `update_plan_step` — without which a plan containing one could never settle
|
|
21
|
+
* however well it went.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const RUN = 'run_step_binding' as RunId
|
|
25
|
+
|
|
26
|
+
function gatewayReturning(outcome: 'ok' | 'failed'): TaskGateway {
|
|
27
|
+
const handle = (taskId: TaskId): TaskHandle => ({
|
|
28
|
+
taskId,
|
|
29
|
+
agentId: 'worker',
|
|
30
|
+
state: 'completed',
|
|
31
|
+
createdAt: 1_000,
|
|
32
|
+
completedAt: 2_000,
|
|
33
|
+
result: (outcome === 'ok'
|
|
34
|
+
? { status: 'completed', result: 'the work' }
|
|
35
|
+
: { status: 'failed', lastError: 'the worker died' }) as TaskHandle['result'],
|
|
36
|
+
})
|
|
37
|
+
return {
|
|
38
|
+
async createTask() {
|
|
39
|
+
return handle('tsk_1' as TaskId)
|
|
40
|
+
},
|
|
41
|
+
async waitForTask(id) {
|
|
42
|
+
return handle(id)
|
|
43
|
+
},
|
|
44
|
+
async continueTask() {},
|
|
45
|
+
cancelTask() {},
|
|
46
|
+
getTask(id) {
|
|
47
|
+
return handle(id)
|
|
48
|
+
},
|
|
49
|
+
listTasks() {
|
|
50
|
+
return []
|
|
51
|
+
},
|
|
52
|
+
onTaskCompleted() {
|
|
53
|
+
return () => {}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ctx(): ToolContext {
|
|
59
|
+
return {
|
|
60
|
+
runId: RUN,
|
|
61
|
+
workingDirectory: '/tmp/test',
|
|
62
|
+
abortSignal: new AbortController().signal,
|
|
63
|
+
env: {},
|
|
64
|
+
log: () => {},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** An approved two-step plan: one delegated, one the orchestrator's own. */
|
|
69
|
+
function approvedPlan(): PlanManager {
|
|
70
|
+
const pm = new PlanManager(RUN, async () => ({ approved: true }))
|
|
71
|
+
pm.startGenerating('do the work')
|
|
72
|
+
pm.addStep({
|
|
73
|
+
id: 'step_1',
|
|
74
|
+
description: 'delegated work',
|
|
75
|
+
agentId: 'worker',
|
|
76
|
+
dependsOn: [],
|
|
77
|
+
order: 1,
|
|
78
|
+
})
|
|
79
|
+
pm.addStep({ id: 'step_2', description: 'my own work', dependsOn: [], order: 2 })
|
|
80
|
+
pm.markReady()
|
|
81
|
+
pm.approve()
|
|
82
|
+
pm.startExecution()
|
|
83
|
+
return pm
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toolsOver(pm: PlanManager, gateway: TaskGateway): (name: string) => ToolDefinition {
|
|
87
|
+
const tools = buildCoordinatorTools({
|
|
88
|
+
gateway,
|
|
89
|
+
workingDirectory: '/tmp/test',
|
|
90
|
+
allowedAgentIds: ['worker'],
|
|
91
|
+
getPlanManager: () => pm,
|
|
92
|
+
})
|
|
93
|
+
return (name: string) => {
|
|
94
|
+
const t = tools.find((tool) => tool.name === name)
|
|
95
|
+
if (!t) throw new Error(`${name} missing from coordinator builder`)
|
|
96
|
+
return t
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const stepStatus = (pm: PlanManager, id: string) =>
|
|
101
|
+
pm.active?.steps.find((s) => s.id === id)?.status
|
|
102
|
+
|
|
103
|
+
describe('a delegated step reports through the launch that carries it out', () => {
|
|
104
|
+
it('completes the step when the worker succeeded', async () => {
|
|
105
|
+
const pm = approvedPlan()
|
|
106
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
107
|
+
|
|
108
|
+
await named('create_task').execute(
|
|
109
|
+
{ agent_id: 'worker', prompt: 'go', description: 'do it', plan_step_id: 'step_1' },
|
|
110
|
+
ctx(),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
expect(stepStatus(pm, 'step_1')).toBe('completed')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('fails the step when the worker failed, from both authorities', async () => {
|
|
117
|
+
// The handle is `state: 'completed'` with `result.status: 'failed'` —
|
|
118
|
+
// the split that made a failed worker read as an answer. The step must
|
|
119
|
+
// follow the run status, not the gateway state.
|
|
120
|
+
const pm = approvedPlan()
|
|
121
|
+
const named = toolsOver(pm, gatewayReturning('failed'))
|
|
122
|
+
|
|
123
|
+
await named('create_task').execute(
|
|
124
|
+
{ agent_id: 'worker', prompt: 'go', description: 'do it', plan_step_id: 'step_1' },
|
|
125
|
+
ctx(),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(stepStatus(pm, 'step_1')).toBe('failed')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('leaves the plan alone when the launch names no step', async () => {
|
|
132
|
+
// A launch outside the approved plan must not silently settle one.
|
|
133
|
+
const pm = approvedPlan()
|
|
134
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
135
|
+
|
|
136
|
+
await named('create_task').execute(
|
|
137
|
+
{ agent_id: 'worker', prompt: 'go', description: 'unrelated work' },
|
|
138
|
+
ctx(),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
expect(stepStatus(pm, 'step_1')).toBe('pending')
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('an orchestrator-owned step reports through update_plan_step', () => {
|
|
146
|
+
it('records the outcome and says what is still outstanding', async () => {
|
|
147
|
+
const pm = approvedPlan()
|
|
148
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
149
|
+
|
|
150
|
+
const result = await named('update_plan_step').execute(
|
|
151
|
+
{ step_id: 'step_2', status: 'completed' },
|
|
152
|
+
ctx(),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
expect(result.success).toBe(true)
|
|
156
|
+
expect(stepStatus(pm, 'step_2')).toBe('completed')
|
|
157
|
+
// step_1 has not reported, and saying so is the point — this is what
|
|
158
|
+
// tells the model the plan cannot settle yet.
|
|
159
|
+
expect(result.output).toContain('step_1')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('treats skipped as a real outcome, not a failure', async () => {
|
|
163
|
+
const pm = approvedPlan()
|
|
164
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
165
|
+
|
|
166
|
+
await named('update_plan_step').execute({ step_id: 'step_2', status: 'skipped' }, ctx())
|
|
167
|
+
|
|
168
|
+
expect(stepStatus(pm, 'step_2')).toBe('skipped')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('refuses an id the plan does not have, and names the ones it does', async () => {
|
|
172
|
+
const pm = approvedPlan()
|
|
173
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
174
|
+
|
|
175
|
+
const result = await named('update_plan_step').execute(
|
|
176
|
+
{ step_id: 'step_9', status: 'completed' },
|
|
177
|
+
ctx(),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
expect(result.success).toBe(false)
|
|
181
|
+
expect(result.error).toContain('step_1')
|
|
182
|
+
expect(result.error).toContain('step_2')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('the two bindings together let a plan settle', () => {
|
|
187
|
+
it('reaches completed once every step has reported', async () => {
|
|
188
|
+
const pm = approvedPlan()
|
|
189
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
190
|
+
|
|
191
|
+
await named('create_task').execute(
|
|
192
|
+
{ agent_id: 'worker', prompt: 'go', description: 'do it', plan_step_id: 'step_1' },
|
|
193
|
+
ctx(),
|
|
194
|
+
)
|
|
195
|
+
await named('update_plan_step').execute({ step_id: 'step_2', status: 'completed' }, ctx())
|
|
196
|
+
|
|
197
|
+
expect(pm.unreportedSteps).toHaveLength(0)
|
|
198
|
+
expect(pm.completePlan()?.status).toBe('completed')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('leaves the plan unsettled while a step is still silent', async () => {
|
|
202
|
+
// The state the kernel reads before deciding whether to settle. An
|
|
203
|
+
// unreported step means the caller and the plan disagree about whether
|
|
204
|
+
// the work is over, and the run must not resolve that by guessing.
|
|
205
|
+
const pm = approvedPlan()
|
|
206
|
+
const named = toolsOver(pm, gatewayReturning('ok'))
|
|
207
|
+
|
|
208
|
+
await named('create_task').execute(
|
|
209
|
+
{ agent_id: 'worker', prompt: 'go', description: 'do it', plan_step_id: 'step_1' },
|
|
210
|
+
ctx(),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
expect(pm.unreportedSteps.map((s) => s.id)).toEqual(['step_2'])
|
|
214
|
+
})
|
|
215
|
+
})
|
|
@@ -88,14 +88,30 @@ describe('coordinator approve_plan tool', () => {
|
|
|
88
88
|
])
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
-
it('
|
|
91
|
+
it('opens with the historical approval sentence, then names the steps', async () => {
|
|
92
|
+
// This assertion used to be `toBe` on the whole string, guarding the
|
|
93
|
+
// approve-with-edits change against disturbing the bare-approve path.
|
|
94
|
+
// It was never a promise that the output would carry nothing more, and
|
|
95
|
+
// it now carries the step roster — without which `plan_step_id` and
|
|
96
|
+
// `update_plan_step` name ids the model has never been told.
|
|
92
97
|
const result = await executeApprovePlan({ approved: true })
|
|
93
98
|
|
|
94
99
|
expect(result.success).toBe(true)
|
|
95
|
-
expect(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
expect(
|
|
101
|
+
result.output.startsWith(
|
|
102
|
+
'Plan approved by user. Proceed with execution — launch workers via create_task.',
|
|
103
|
+
),
|
|
104
|
+
).toBe(true)
|
|
105
|
+
expect(result.output).toContain('step_1 — Extract uploaded DOCX files')
|
|
106
|
+
expect(result.data).toMatchObject({ approved: true, feedback: undefined })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('carries the step ids in data, so a host does not have to parse prose', async () => {
|
|
110
|
+
const result = await executeApprovePlan({ approved: true })
|
|
111
|
+
|
|
112
|
+
expect((result.data as { steps: unknown }).steps).toEqual([
|
|
113
|
+
{ step_id: 'step_1', description: 'Extract uploaded DOCX files', agent_id: undefined },
|
|
114
|
+
])
|
|
99
115
|
})
|
|
100
116
|
|
|
101
117
|
it('embeds approve-with-edits feedback in the output and data', async () => {
|
|
@@ -105,12 +121,17 @@ describe('coordinator approve_plan tool', () => {
|
|
|
105
121
|
})
|
|
106
122
|
|
|
107
123
|
expect(result.success).toBe(true)
|
|
108
|
-
expect(
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
expect(
|
|
125
|
+
result.output.startsWith(
|
|
126
|
+
'Plan approved by user with required edits — apply them during execution:\n' +
|
|
127
|
+
'Skip step 2 and use the staging database instead.\n' +
|
|
128
|
+
'Proceed with execution — launch workers via create_task.',
|
|
129
|
+
),
|
|
130
|
+
).toBe(true)
|
|
131
|
+
// The edits stay ahead of the roster: what the user demanded is the
|
|
132
|
+
// first thing read, and the step list is reference material after it.
|
|
133
|
+
expect(result.output.indexOf('staging database')).toBeLessThan(result.output.indexOf('step_1'))
|
|
134
|
+
expect(result.data).toMatchObject({
|
|
114
135
|
approved: true,
|
|
115
136
|
feedback: 'Skip step 2 and use the staging database instead.',
|
|
116
137
|
})
|
|
@@ -298,6 +298,13 @@ describe('waiting explicitly beats listing in a loop', () => {
|
|
|
298
298
|
describe('the task listing carries the output it always had', () => {
|
|
299
299
|
async function listWith(result: string): Promise<string> {
|
|
300
300
|
const h = harness()
|
|
301
|
+
// Launch it first. The listing is scoped to what this run launched, so
|
|
302
|
+
// settling a task the tools never created describes a sibling run's
|
|
303
|
+
// work — which the listing now declines to show, correctly.
|
|
304
|
+
await toolNamed(h.tools, 'create_task').execute(
|
|
305
|
+
{ agent_id: 'reviewer', prompt: 'go', description: 'review', background: true },
|
|
306
|
+
{} as never,
|
|
307
|
+
)
|
|
301
308
|
h.settle({
|
|
302
309
|
taskId: 'tsk_1' as TaskId,
|
|
303
310
|
agentId: 'reviewer',
|
|
@@ -27,13 +27,27 @@ function makeContext(): ToolContext {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Hands back the seeded handles in order, one per `createTask`.
|
|
32
|
+
*
|
|
33
|
+
* The listing is scoped to what this tool set launched, so a fixture that
|
|
34
|
+
* only stuffed `listTasks()` would now list nothing — and a gateway holding
|
|
35
|
+
* tasks these tools never launched is precisely the sibling-run case the
|
|
36
|
+
* scope exists to refuse. So the tests launch through the front door and the
|
|
37
|
+
* fixture plays along.
|
|
38
|
+
*/
|
|
30
39
|
function gatewayWith(handles: TaskHandle[]): TaskGateway {
|
|
40
|
+
let nextLaunch = 0
|
|
31
41
|
return {
|
|
32
42
|
async createTask() {
|
|
33
|
-
|
|
43
|
+
const h = handles[nextLaunch++]
|
|
44
|
+
if (!h) throw new Error('fixture ran out of seeded handles')
|
|
45
|
+
return h
|
|
34
46
|
},
|
|
35
|
-
async waitForTask() {
|
|
36
|
-
|
|
47
|
+
async waitForTask(id) {
|
|
48
|
+
const h = handles.find((x) => x.taskId === id)
|
|
49
|
+
if (!h) throw new Error(`fixture has no handle ${id}`)
|
|
50
|
+
return h
|
|
37
51
|
},
|
|
38
52
|
async continueTask() {},
|
|
39
53
|
cancelTask() {},
|
|
@@ -79,12 +93,29 @@ function handle(input: {
|
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Build the coordinator surface, launch each seeded handle through
|
|
98
|
+
* `create_task`, and return `agent_task_list`.
|
|
99
|
+
*
|
|
100
|
+
* Launching is what puts the tasks in this run's scope. Reaching past it to
|
|
101
|
+
* seed the gateway directly would test a listing nobody can produce.
|
|
102
|
+
*/
|
|
103
|
+
async function agentTaskListOver(seeded: TaskHandle[]) {
|
|
83
104
|
const tools = buildCoordinatorTools({
|
|
84
|
-
gateway,
|
|
105
|
+
gateway: gatewayWith(seeded),
|
|
85
106
|
workingDirectory: '/tmp/test',
|
|
86
107
|
allowedAgentIds: ['solution-architecture', 'enterprise-architecture'],
|
|
87
108
|
})
|
|
109
|
+
|
|
110
|
+
const createTask = tools.find((tool) => tool.name === 'create_task')
|
|
111
|
+
if (!createTask) throw new Error('create_task tool missing from coordinator builder')
|
|
112
|
+
for (const h of seeded) {
|
|
113
|
+
await createTask.execute(
|
|
114
|
+
{ agent_id: h.agentId, prompt: 'work', description: `launch ${h.taskId}` },
|
|
115
|
+
makeContext(),
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
const t = tools.find((tool) => tool.name === 'agent_task_list')
|
|
89
120
|
if (!t) throw new Error('agent_task_list tool missing from coordinator builder')
|
|
90
121
|
return t
|
|
@@ -92,7 +123,7 @@ function findAgentTaskList(gateway: TaskGateway) {
|
|
|
92
123
|
|
|
93
124
|
describe('coordinator agent_task_list tool', () => {
|
|
94
125
|
it('lists every task with state, agent, and timing', async () => {
|
|
95
|
-
const
|
|
126
|
+
const seeded = [
|
|
96
127
|
handle({
|
|
97
128
|
id: 'task_a',
|
|
98
129
|
agentId: 'solution-architecture',
|
|
@@ -114,9 +145,8 @@ describe('coordinator agent_task_list tool', () => {
|
|
|
114
145
|
completedAt: 4000,
|
|
115
146
|
lastError: 'bash exit 1',
|
|
116
147
|
}),
|
|
117
|
-
]
|
|
118
|
-
|
|
119
|
-
const tool = findAgentTaskList(gateway)
|
|
148
|
+
]
|
|
149
|
+
const tool = await agentTaskListOver(seeded)
|
|
120
150
|
const result = await tool.execute({}, makeContext())
|
|
121
151
|
expect(result.success).toBe(true)
|
|
122
152
|
expect(result.output).toMatch(/Tasks: 3 total/)
|
|
@@ -131,7 +161,7 @@ describe('coordinator agent_task_list tool', () => {
|
|
|
131
161
|
})
|
|
132
162
|
|
|
133
163
|
it('filters by state', async () => {
|
|
134
|
-
const
|
|
164
|
+
const seeded = [
|
|
135
165
|
handle({
|
|
136
166
|
id: 'task_a',
|
|
137
167
|
agentId: 'solution-architecture',
|
|
@@ -145,9 +175,8 @@ describe('coordinator agent_task_list tool', () => {
|
|
|
145
175
|
state: 'running',
|
|
146
176
|
createdAt: 1000,
|
|
147
177
|
}),
|
|
148
|
-
]
|
|
149
|
-
|
|
150
|
-
const tool = findAgentTaskList(gateway)
|
|
178
|
+
]
|
|
179
|
+
const tool = await agentTaskListOver(seeded)
|
|
151
180
|
const result = await tool.execute({ state: 'running' }, makeContext())
|
|
152
181
|
expect(result.success).toBe(true)
|
|
153
182
|
const data = result.data as { items: Array<{ task_id: string }> }
|
|
@@ -157,7 +186,7 @@ describe('coordinator agent_task_list tool', () => {
|
|
|
157
186
|
})
|
|
158
187
|
|
|
159
188
|
it('handles an empty gateway', async () => {
|
|
160
|
-
const tool =
|
|
189
|
+
const tool = await agentTaskListOver([])
|
|
161
190
|
const result = await tool.execute({}, makeContext())
|
|
162
191
|
expect(result.success).toBe(true)
|
|
163
192
|
expect(result.output).toMatch(/Tasks: 0 total/)
|
|
@@ -224,7 +253,7 @@ describe('agent_task_list frames what a worker said', () => {
|
|
|
224
253
|
}
|
|
225
254
|
|
|
226
255
|
async function render(text: string): Promise<string> {
|
|
227
|
-
const tool =
|
|
256
|
+
const tool = await agentTaskListOver([withResult(text)])
|
|
228
257
|
const out = await tool.execute({}, makeContext())
|
|
229
258
|
return out.output
|
|
230
259
|
}
|
|
@@ -263,11 +292,9 @@ describe('agent_task_list frames what a worker said', () => {
|
|
|
263
292
|
})
|
|
264
293
|
|
|
265
294
|
it('says nothing extra for a task that produced no output', async () => {
|
|
266
|
-
const tool =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
]),
|
|
270
|
-
)
|
|
295
|
+
const tool = await agentTaskListOver([
|
|
296
|
+
handle({ id: 'task_none', agentId: 'reviewer', state: 'running', createdAt: 0 }),
|
|
297
|
+
])
|
|
271
298
|
|
|
272
299
|
const out = await tool.execute({}, makeContext())
|
|
273
300
|
|