@namzu/sdk 12.0.0 → 12.0.1
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 +32 -0
- package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.d.ts +2 -0
- package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.d.ts.map +1 -0
- package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.js +137 -0
- package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.js.map +1 -0
- package/dist/gateway/local.d.ts.map +1 -1
- package/dist/gateway/local.js +25 -1
- package/dist/gateway/local.js.map +1 -1
- package/package.json +1 -1
- package/src/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.ts +175 -0
- package/src/gateway/local.ts +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 12.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5fe072b: A concurrent fan-out no longer kills most of its own launches.
|
|
8
|
+
|
|
9
|
+
`LocalTaskGateway.createTask` passes a progress tee into `sendMessage`, and that
|
|
10
|
+
callback read `task.taskId` — the `const` that the very same `await` assigns. So
|
|
11
|
+
a child that emitted any run event before `sendMessage` resolved reached that
|
|
12
|
+
line inside the temporal dead zone and threw `Cannot access 'task' before
|
|
13
|
+
initialization`, taking the whole launch down with it.
|
|
14
|
+
|
|
15
|
+
Two things kept it hidden. A single sequential launch usually resolves before
|
|
16
|
+
the child says anything, so the ordering rarely bit. And the throw only happens
|
|
17
|
+
when a progress listener is actually attached — with an empty subscriber set the
|
|
18
|
+
loop body never runs and the dead zone is never entered, which is why no unit
|
|
19
|
+
test caught it. `create_task` attaches one for its idle bound on every blocking
|
|
20
|
+
launch, so production always had one.
|
|
21
|
+
|
|
22
|
+
Under a concurrent fan-out both conditions hold. Observed on the published
|
|
23
|
+
package: four `create_task` calls from one assistant turn — the shape that
|
|
24
|
+
tool's own description tells the model to use — and three of the four died with
|
|
25
|
+
`create_task failed: Cannot access 'task' before initialization`.
|
|
26
|
+
|
|
27
|
+
The tee now reads an id filled in after the spawn resolves, and reports nothing
|
|
28
|
+
before then. That window is not a loss: with no id the caller does not yet hold
|
|
29
|
+
the handle, so no idle bound is running against the task and there is no
|
|
30
|
+
progress to attribute to anyone.
|
|
31
|
+
|
|
32
|
+
Introduced in #130 with the idle-bound work. Found by running a live concurrent
|
|
33
|
+
fan-out against the published package rather than by any test.
|
|
34
|
+
|
|
3
35
|
## 12.0.0
|
|
4
36
|
|
|
5
37
|
### Major Changes
|
package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-progress-tee-cannot-name-a-task-that-does-not-exist.test.d.ts","sourceRoot":"","sources":["../../../src/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { LocalTaskGateway } from '../local.js';
|
|
3
|
+
/**
|
|
4
|
+
* A child that spoke before its own spawn resolved killed the launch.
|
|
5
|
+
*
|
|
6
|
+
* The progress tee handed to `sendMessage` read `task.taskId` — the `const`
|
|
7
|
+
* that the very same `await` assigns. So any run event emitted by the child
|
|
8
|
+
* before `sendMessage` returned reached that line inside the temporal dead
|
|
9
|
+
* zone and threw `Cannot access 'task' before initialization`, taking the whole
|
|
10
|
+
* `create_task` down with it.
|
|
11
|
+
*
|
|
12
|
+
* It survived review and a full unit suite because a single sequential launch
|
|
13
|
+
* usually resolves before the child says anything. A CONCURRENT fan-out does
|
|
14
|
+
* not — and that is the shape `create_task`'s own description tells the model
|
|
15
|
+
* to use. Observed live on the published package: four launches from one turn,
|
|
16
|
+
* three dead.
|
|
17
|
+
*/
|
|
18
|
+
const RUN = 'run_tee';
|
|
19
|
+
/** Emits a run event DURING the spawn, before it resolves. */
|
|
20
|
+
class TalksDuringSpawn {
|
|
21
|
+
emitsBeforeResolve;
|
|
22
|
+
/** Kept so a test can make the child speak AFTER the spawn resolved too. */
|
|
23
|
+
lastListener;
|
|
24
|
+
constructor(emitsBeforeResolve) {
|
|
25
|
+
this.emitsBeforeResolve = emitsBeforeResolve;
|
|
26
|
+
}
|
|
27
|
+
/** The child says something once the caller holds its handle. */
|
|
28
|
+
speakNow() {
|
|
29
|
+
this.lastListener?.({ type: 'iteration_started', runId: RUN, iteration: 99 });
|
|
30
|
+
}
|
|
31
|
+
async sendMessage(options, _context, listener) {
|
|
32
|
+
this.lastListener = listener;
|
|
33
|
+
// The child is alive and streaming before the caller holds its handle.
|
|
34
|
+
for (let i = 0; i < this.emitsBeforeResolve; i += 1) {
|
|
35
|
+
listener?.({ type: 'iteration_started', runId: RUN, iteration: i });
|
|
36
|
+
}
|
|
37
|
+
await Promise.resolve();
|
|
38
|
+
return {
|
|
39
|
+
taskId: 'task_spoke_early',
|
|
40
|
+
agentId: options.agentId,
|
|
41
|
+
agent: {},
|
|
42
|
+
childAbortController: new AbortController(),
|
|
43
|
+
context: {},
|
|
44
|
+
state: 'completed',
|
|
45
|
+
pendingMessages: [],
|
|
46
|
+
createdAt: 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
cancel() { }
|
|
50
|
+
cancelAll() { }
|
|
51
|
+
async continueTask() { }
|
|
52
|
+
queueMessage() { }
|
|
53
|
+
drainMessages() {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
async waitForCompletion() { }
|
|
57
|
+
getInstance() {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
listByParent() {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
listActive() {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
getState() {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
on() { }
|
|
70
|
+
off() { }
|
|
71
|
+
cleanup() { }
|
|
72
|
+
dispose() { }
|
|
73
|
+
}
|
|
74
|
+
function context() {
|
|
75
|
+
return {
|
|
76
|
+
parentRunId: RUN,
|
|
77
|
+
parentAgentId: 'supervisor',
|
|
78
|
+
parentAbortController: new AbortController(),
|
|
79
|
+
depth: 0,
|
|
80
|
+
budgetTracker: { total: 100_000, remaining: 100_000 },
|
|
81
|
+
tenantId: 'tnt_t',
|
|
82
|
+
threadId: 'thd_t',
|
|
83
|
+
sessionId: 'ses_t',
|
|
84
|
+
projectId: 'prj_t',
|
|
85
|
+
parentActor: { kind: 'agent', agentId: 'supervisor', tenantId: 'tnt_t' },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
describe('a launch survives a child that speaks before the spawn resolves', () => {
|
|
89
|
+
it('does not throw when an event arrives mid-spawn', async () => {
|
|
90
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(3), context());
|
|
91
|
+
// The listener is what makes this reproduce, and its absence is what
|
|
92
|
+
// made the bug invisible for so long: with no progress subscriber the
|
|
93
|
+
// loop body never runs, so `task.taskId` is never evaluated and the
|
|
94
|
+
// dead zone is never entered. `create_task` attaches one for the idle
|
|
95
|
+
// bound on every blocking launch, which is why it bit in production and
|
|
96
|
+
// not in any test.
|
|
97
|
+
gateway.onTaskProgress?.(() => { });
|
|
98
|
+
await expect(gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' })).resolves.toMatchObject({ taskId: 'task_spoke_early' });
|
|
99
|
+
});
|
|
100
|
+
it('still forwards those events to the host listener', async () => {
|
|
101
|
+
// The tee is what broke, not the forwarding. A host watching the child
|
|
102
|
+
// must not lose its early events to this fix.
|
|
103
|
+
const seen = [];
|
|
104
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(3), context(), (e) => {
|
|
105
|
+
seen.push(e.type);
|
|
106
|
+
});
|
|
107
|
+
await gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' });
|
|
108
|
+
expect(seen).toEqual(['iteration_started', 'iteration_started', 'iteration_started']);
|
|
109
|
+
});
|
|
110
|
+
it('reports progress once the task has an id to report it against', async () => {
|
|
111
|
+
// Before the id exists nothing is waiting on the task — the caller does
|
|
112
|
+
// not hold the handle yet — so silence there is correct. What must work
|
|
113
|
+
// is everything after.
|
|
114
|
+
const manager = new TalksDuringSpawn(1);
|
|
115
|
+
const gateway = new LocalTaskGateway(manager, context());
|
|
116
|
+
const progressed = [];
|
|
117
|
+
gateway.onTaskProgress?.((id) => progressed.push(id));
|
|
118
|
+
await gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' });
|
|
119
|
+
// The one emitted mid-spawn is not attributed — there was no id yet.
|
|
120
|
+
expect(progressed).toEqual([]);
|
|
121
|
+
// Now the child speaks with the handle already in the caller's hands,
|
|
122
|
+
// which is the case an idle bound is actually measuring.
|
|
123
|
+
manager.speakNow();
|
|
124
|
+
expect(progressed).toEqual(['task_spoke_early']);
|
|
125
|
+
});
|
|
126
|
+
it('survives a concurrent fan-out, which is how this was found', async () => {
|
|
127
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(2), context());
|
|
128
|
+
// Same reason as above: the live failure came through the idle bound's
|
|
129
|
+
// subscriber, so a fan-out test without one would not reproduce the
|
|
130
|
+
// thing it is named after.
|
|
131
|
+
gateway.onTaskProgress?.(() => { });
|
|
132
|
+
const launched = await Promise.all([1, 2, 3, 4].map(() => gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' })));
|
|
133
|
+
expect(launched).toHaveLength(4);
|
|
134
|
+
expect(launched.every((h) => h.taskId === 'task_spoke_early')).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=a-progress-tee-cannot-name-a-task-that-does-not-exist.test.js.map
|
package/dist/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-progress-tee-cannot-name-a-task-that-does-not-exist.test.js","sourceRoot":"","sources":["../../../src/gateway/__tests__/a-progress-tee-cannot-name-a-task-that-does-not-exist.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAI9D,qBAAa,gBAAiB,YAAW,WAAW;IACnD,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,cAAc,CAAyB;IAE/C,OAAO,CAAC,WAAW,CAAC,CAA2E;IAE/F,OAAO,CAAC,mBAAmB,CAA+C;IAE1E;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc,CAAqC;IAE3D,OAAO,CAAC,oBAAoB,CAAmC;IAC/D,kCAAkC;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;gBAGvE,YAAY,EAAE,oBAAoB,EAClC,WAAW,EAAE,gBAAgB,EAC7B,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,sBAAsB,GAAG,gBAAgB,CAAC,EACvF,OAAO,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;KAAE;IAWpD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAI9D,qBAAa,gBAAiB,YAAW,WAAW;IACnD,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,cAAc,CAAyB;IAE/C,OAAO,CAAC,WAAW,CAAC,CAA2E;IAE/F,OAAO,CAAC,mBAAmB,CAA+C;IAE1E;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc,CAAqC;IAE3D,OAAO,CAAC,oBAAoB,CAAmC;IAC/D,kCAAkC;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;gBAGvE,YAAY,EAAE,oBAAoB,EAClC,WAAW,EAAE,gBAAgB,EAC7B,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,sBAAsB,GAAG,gBAAgB,CAAC,EACvF,OAAO,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;KAAE;IAWpD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;IA6GjE;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,kBAAkB;IA0BpB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAShD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAK/C;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKrC,SAAS,IAAI,UAAU,EAAE;IAgBzB;;;;;;;OAOG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAO9D,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;CAMnE"}
|
package/dist/gateway/local.js
CHANGED
|
@@ -36,6 +36,11 @@ export class LocalTaskGateway {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
async createTask(options) {
|
|
39
|
+
// Filled once the spawn resolves. A box rather than a bare binding
|
|
40
|
+
// because the assignment happens AFTER the `await` that the reader is
|
|
41
|
+
// passed into — see the progress tee below for why it cannot simply
|
|
42
|
+
// read the `task` const it is declared beside.
|
|
43
|
+
const launched = {};
|
|
39
44
|
const task = await this.agentManager.sendMessage({
|
|
40
45
|
agentId: options.agentId,
|
|
41
46
|
input: {
|
|
@@ -84,11 +89,30 @@ export class LocalTaskGateway {
|
|
|
84
89
|
// idle bound measures. The event itself is not forwarded — a
|
|
85
90
|
// progress signal that carried the child's output would be a
|
|
86
91
|
// second, undocumented way to read a worker's work.
|
|
92
|
+
//
|
|
93
|
+
// The id comes from `launched.id`, NOT from the `task` const
|
|
94
|
+
// below. This callback is handed to the very `await` that assigns
|
|
95
|
+
// `task`, so a child that emits anything before `sendMessage`
|
|
96
|
+
// resolves reached it inside the temporal dead zone and threw
|
|
97
|
+
// `Cannot access 'task' before initialization` — killing the launch
|
|
98
|
+
// outright.
|
|
99
|
+
//
|
|
100
|
+
// It survived because a single sequential launch usually resolves
|
|
101
|
+
// before the child says anything. A concurrent fan-out does not:
|
|
102
|
+
// with four `create_task` calls from one turn — the shape this
|
|
103
|
+
// tool's own description tells the model to use — the event loop
|
|
104
|
+
// interleaves and three of the four died. Found by running one.
|
|
87
105
|
(event) => {
|
|
88
106
|
this.listener?.(event);
|
|
107
|
+
// No id yet means nothing is waiting on this task: the caller
|
|
108
|
+
// does not hold the handle, so an idle bound cannot be running
|
|
109
|
+
// against it. There is no progress to report to anyone.
|
|
110
|
+
if (launched.id === undefined)
|
|
111
|
+
return;
|
|
89
112
|
for (const notify of this.progressListeners)
|
|
90
|
-
notify(
|
|
113
|
+
notify(launched.id);
|
|
91
114
|
});
|
|
115
|
+
launched.id = task.taskId;
|
|
92
116
|
this.trackedTaskIds.add(task.taskId);
|
|
93
117
|
this.agentManager
|
|
94
118
|
.waitForCompletion(task.taskId)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { Agent } from '../../types/agent/core.js'
|
|
4
|
+
import type { AgentManagerContract } from '../../types/agent/manager.js'
|
|
5
|
+
import type {
|
|
6
|
+
AgentTask,
|
|
7
|
+
AgentTaskContext,
|
|
8
|
+
AgentTaskState,
|
|
9
|
+
SendMessageOptions,
|
|
10
|
+
} from '../../types/agent/task.js'
|
|
11
|
+
import type { AgentId, RunId, SessionId, TaskId, TenantId } from '../../types/ids/index.js'
|
|
12
|
+
import type { RunEventListener } from '../../types/run/events.js'
|
|
13
|
+
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
14
|
+
import { LocalTaskGateway } from '../local.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A child that spoke before its own spawn resolved killed the launch.
|
|
18
|
+
*
|
|
19
|
+
* The progress tee handed to `sendMessage` read `task.taskId` — the `const`
|
|
20
|
+
* that the very same `await` assigns. So any run event emitted by the child
|
|
21
|
+
* before `sendMessage` returned reached that line inside the temporal dead
|
|
22
|
+
* zone and threw `Cannot access 'task' before initialization`, taking the whole
|
|
23
|
+
* `create_task` down with it.
|
|
24
|
+
*
|
|
25
|
+
* It survived review and a full unit suite because a single sequential launch
|
|
26
|
+
* usually resolves before the child says anything. A CONCURRENT fan-out does
|
|
27
|
+
* not — and that is the shape `create_task`'s own description tells the model
|
|
28
|
+
* to use. Observed live on the published package: four launches from one turn,
|
|
29
|
+
* three dead.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const RUN = 'run_tee' as RunId
|
|
33
|
+
|
|
34
|
+
/** Emits a run event DURING the spawn, before it resolves. */
|
|
35
|
+
class TalksDuringSpawn implements AgentManagerContract {
|
|
36
|
+
/** Kept so a test can make the child speak AFTER the spawn resolved too. */
|
|
37
|
+
private lastListener?: RunEventListener
|
|
38
|
+
|
|
39
|
+
constructor(private readonly emitsBeforeResolve: number) {}
|
|
40
|
+
|
|
41
|
+
/** The child says something once the caller holds its handle. */
|
|
42
|
+
speakNow(): void {
|
|
43
|
+
this.lastListener?.({ type: 'iteration_started', runId: RUN, iteration: 99 } as never)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async sendMessage(
|
|
47
|
+
options: SendMessageOptions,
|
|
48
|
+
_context: AgentTaskContext,
|
|
49
|
+
listener?: RunEventListener,
|
|
50
|
+
): Promise<AgentTask> {
|
|
51
|
+
this.lastListener = listener
|
|
52
|
+
// The child is alive and streaming before the caller holds its handle.
|
|
53
|
+
for (let i = 0; i < this.emitsBeforeResolve; i += 1) {
|
|
54
|
+
listener?.({ type: 'iteration_started', runId: RUN, iteration: i } as never)
|
|
55
|
+
}
|
|
56
|
+
await Promise.resolve()
|
|
57
|
+
return {
|
|
58
|
+
taskId: 'task_spoke_early' as TaskId,
|
|
59
|
+
agentId: options.agentId,
|
|
60
|
+
agent: {} as Agent<never, never>,
|
|
61
|
+
childAbortController: new AbortController(),
|
|
62
|
+
context: {} as AgentTaskContext,
|
|
63
|
+
state: 'completed' as AgentTaskState,
|
|
64
|
+
pendingMessages: [],
|
|
65
|
+
createdAt: 1,
|
|
66
|
+
} as AgentTask
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cancel(): void {}
|
|
70
|
+
cancelAll(): void {}
|
|
71
|
+
async continueTask(): Promise<void> {}
|
|
72
|
+
queueMessage(): void {}
|
|
73
|
+
drainMessages() {
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
async waitForCompletion(): Promise<void> {}
|
|
77
|
+
getInstance(): AgentTask | undefined {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
listByParent(): AgentTask[] {
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
listActive(): AgentTask[] {
|
|
84
|
+
return []
|
|
85
|
+
}
|
|
86
|
+
getState(): AgentTaskState | undefined {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
on(): void {}
|
|
90
|
+
off(): void {}
|
|
91
|
+
cleanup(): void {}
|
|
92
|
+
dispose(): void {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function context(): AgentTaskContext {
|
|
96
|
+
return {
|
|
97
|
+
parentRunId: RUN,
|
|
98
|
+
parentAgentId: 'supervisor',
|
|
99
|
+
parentAbortController: new AbortController(),
|
|
100
|
+
depth: 0,
|
|
101
|
+
budgetTracker: { total: 100_000, remaining: 100_000 },
|
|
102
|
+
tenantId: 'tnt_t' as TenantId,
|
|
103
|
+
threadId: 'thd_t' as ThreadId,
|
|
104
|
+
sessionId: 'ses_t' as SessionId,
|
|
105
|
+
projectId: 'prj_t' as ProjectId,
|
|
106
|
+
parentActor: { kind: 'agent', agentId: 'supervisor' as AgentId, tenantId: 'tnt_t' as TenantId },
|
|
107
|
+
} as AgentTaskContext
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe('a launch survives a child that speaks before the spawn resolves', () => {
|
|
111
|
+
it('does not throw when an event arrives mid-spawn', async () => {
|
|
112
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(3), context())
|
|
113
|
+
// The listener is what makes this reproduce, and its absence is what
|
|
114
|
+
// made the bug invisible for so long: with no progress subscriber the
|
|
115
|
+
// loop body never runs, so `task.taskId` is never evaluated and the
|
|
116
|
+
// dead zone is never entered. `create_task` attaches one for the idle
|
|
117
|
+
// bound on every blocking launch, which is why it bit in production and
|
|
118
|
+
// not in any test.
|
|
119
|
+
gateway.onTaskProgress?.(() => {})
|
|
120
|
+
|
|
121
|
+
await expect(
|
|
122
|
+
gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' }),
|
|
123
|
+
).resolves.toMatchObject({ taskId: 'task_spoke_early' })
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('still forwards those events to the host listener', async () => {
|
|
127
|
+
// The tee is what broke, not the forwarding. A host watching the child
|
|
128
|
+
// must not lose its early events to this fix.
|
|
129
|
+
const seen: string[] = []
|
|
130
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(3), context(), (e) => {
|
|
131
|
+
seen.push(e.type)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' })
|
|
135
|
+
|
|
136
|
+
expect(seen).toEqual(['iteration_started', 'iteration_started', 'iteration_started'])
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('reports progress once the task has an id to report it against', async () => {
|
|
140
|
+
// Before the id exists nothing is waiting on the task — the caller does
|
|
141
|
+
// not hold the handle yet — so silence there is correct. What must work
|
|
142
|
+
// is everything after.
|
|
143
|
+
const manager = new TalksDuringSpawn(1)
|
|
144
|
+
const gateway = new LocalTaskGateway(manager, context())
|
|
145
|
+
const progressed: TaskId[] = []
|
|
146
|
+
gateway.onTaskProgress?.((id) => progressed.push(id))
|
|
147
|
+
|
|
148
|
+
await gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' })
|
|
149
|
+
// The one emitted mid-spawn is not attributed — there was no id yet.
|
|
150
|
+
expect(progressed).toEqual([])
|
|
151
|
+
|
|
152
|
+
// Now the child speaks with the handle already in the caller's hands,
|
|
153
|
+
// which is the case an idle bound is actually measuring.
|
|
154
|
+
manager.speakNow()
|
|
155
|
+
|
|
156
|
+
expect(progressed).toEqual(['task_spoke_early' as TaskId])
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('survives a concurrent fan-out, which is how this was found', async () => {
|
|
160
|
+
const gateway = new LocalTaskGateway(new TalksDuringSpawn(2), context())
|
|
161
|
+
// Same reason as above: the live failure came through the idle bound's
|
|
162
|
+
// subscriber, so a fan-out test without one would not reproduce the
|
|
163
|
+
// thing it is named after.
|
|
164
|
+
gateway.onTaskProgress?.(() => {})
|
|
165
|
+
|
|
166
|
+
const launched = await Promise.all(
|
|
167
|
+
[1, 2, 3, 4].map(() =>
|
|
168
|
+
gateway.createTask({ agentId: 'worker', prompt: 'go', workingDirectory: '/tmp' }),
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
expect(launched).toHaveLength(4)
|
|
173
|
+
expect(launched.every((h) => h.taskId === 'task_spoke_early')).toBe(true)
|
|
174
|
+
})
|
|
175
|
+
})
|
package/src/gateway/local.ts
CHANGED
|
@@ -60,6 +60,12 @@ export class LocalTaskGateway implements TaskGateway {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
async createTask(options: CreateTaskOptions): Promise<TaskHandle> {
|
|
63
|
+
// Filled once the spawn resolves. A box rather than a bare binding
|
|
64
|
+
// because the assignment happens AFTER the `await` that the reader is
|
|
65
|
+
// passed into — see the progress tee below for why it cannot simply
|
|
66
|
+
// read the `task` const it is declared beside.
|
|
67
|
+
const launched: { id?: TaskId } = {}
|
|
68
|
+
|
|
63
69
|
const task = await this.agentManager.sendMessage(
|
|
64
70
|
{
|
|
65
71
|
agentId: options.agentId,
|
|
@@ -109,12 +115,30 @@ export class LocalTaskGateway implements TaskGateway {
|
|
|
109
115
|
// idle bound measures. The event itself is not forwarded — a
|
|
110
116
|
// progress signal that carried the child's output would be a
|
|
111
117
|
// second, undocumented way to read a worker's work.
|
|
118
|
+
//
|
|
119
|
+
// The id comes from `launched.id`, NOT from the `task` const
|
|
120
|
+
// below. This callback is handed to the very `await` that assigns
|
|
121
|
+
// `task`, so a child that emits anything before `sendMessage`
|
|
122
|
+
// resolves reached it inside the temporal dead zone and threw
|
|
123
|
+
// `Cannot access 'task' before initialization` — killing the launch
|
|
124
|
+
// outright.
|
|
125
|
+
//
|
|
126
|
+
// It survived because a single sequential launch usually resolves
|
|
127
|
+
// before the child says anything. A concurrent fan-out does not:
|
|
128
|
+
// with four `create_task` calls from one turn — the shape this
|
|
129
|
+
// tool's own description tells the model to use — the event loop
|
|
130
|
+
// interleaves and three of the four died. Found by running one.
|
|
112
131
|
(event) => {
|
|
113
132
|
this.listener?.(event)
|
|
114
|
-
|
|
133
|
+
// No id yet means nothing is waiting on this task: the caller
|
|
134
|
+
// does not hold the handle, so an idle bound cannot be running
|
|
135
|
+
// against it. There is no progress to report to anyone.
|
|
136
|
+
if (launched.id === undefined) return
|
|
137
|
+
for (const notify of this.progressListeners) notify(launched.id)
|
|
115
138
|
},
|
|
116
139
|
)
|
|
117
140
|
|
|
141
|
+
launched.id = task.taskId
|
|
118
142
|
this.trackedTaskIds.add(task.taskId)
|
|
119
143
|
|
|
120
144
|
this.agentManager
|