@syengup/friday-channel-next 0.1.36 → 0.1.38
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/dist/index.js +1 -1
- package/dist/src/agent/dispatch-bridge.d.ts +1 -1
- package/dist/src/agent/node-pairing-bridge.d.ts +11 -8
- package/dist/src/agent/node-pairing-bridge.js +6 -2
- package/dist/src/agent/operator-scope.d.ts +19 -0
- package/dist/src/agent/operator-scope.js +54 -0
- package/dist/src/agent/subagent-registry.js +0 -3
- package/dist/src/channel-actions.js +3 -1
- package/dist/src/channel.js +0 -2
- package/dist/src/collect-message-media-paths.js +10 -1
- package/dist/src/friday-session.js +34 -10
- package/dist/src/history/normalize-message.js +22 -8
- package/dist/src/http/handlers/agent-config.js +10 -4
- package/dist/src/http/handlers/cancel.js +4 -2
- package/dist/src/http/handlers/device-approve.js +3 -1
- package/dist/src/http/handlers/files-download.js +6 -8
- package/dist/src/http/handlers/files.js +1 -1
- package/dist/src/http/handlers/health.js +18 -4
- package/dist/src/http/handlers/history-messages.js +1 -1
- package/dist/src/http/handlers/history-sessions.js +5 -3
- package/dist/src/http/handlers/messages.js +34 -11
- package/dist/src/http/handlers/models-list.js +1 -1
- package/dist/src/http/handlers/nodes-approve.js +1 -6
- package/dist/src/http/handlers/plugin-info.js +1 -1
- package/dist/src/http/server.js +4 -2
- package/dist/src/link-preview/og-parse.js +3 -1
- package/dist/src/plugin-install-info.js +4 -1
- package/dist/src/session/session-manager.js +9 -3
- package/dist/src/session-usage-store.js +3 -1
- package/dist/src/skills-discovery.d.ts +5 -4
- package/dist/src/skills-discovery.js +27 -22
- package/dist/src/sse/offline-queue.js +4 -1
- package/dist/src/tool-catalog.js +2 -3
- package/dist/src/upgrade-runtime.d.ts +1 -1
- package/dist/src/version.js +3 -1
- package/index.ts +43 -35
- package/install.js +131 -43
- package/package.json +10 -1
- package/src/agent/abort-run.ts +2 -3
- package/src/agent/dispatch-bridge.ts +2 -1
- package/src/agent/media-bridge.ts +9 -2
- package/src/agent/node-pairing-bridge.ts +29 -15
- package/src/agent/operator-scope.test.ts +66 -0
- package/src/agent/operator-scope.ts +63 -0
- package/src/agent/run-usage-accumulator.ts +4 -2
- package/src/agent/subagent-registry.ts +0 -4
- package/src/agent-run-context-bridge.ts +3 -1
- package/src/channel-actions.test.ts +10 -4
- package/src/channel-actions.ts +3 -1
- package/src/channel.outbound.test.ts +18 -4
- package/src/channel.ts +121 -123
- package/src/collect-message-media-paths.ts +15 -6
- package/src/config.ts +1 -4
- package/src/e2e/agents-list.e2e.test.ts +9 -2
- package/src/e2e/attachments-inbound.e2e.test.ts +5 -1
- package/src/e2e/attachments-outbound.e2e.test.ts +7 -2
- package/src/e2e/auto-approve.integration.test.ts +13 -7
- package/src/e2e/cancel-reconnect-errors.e2e.test.ts +18 -3
- package/src/e2e/connect-and-connected.e2e.test.ts +5 -1
- package/src/e2e/offline-replay.e2e.test.ts +17 -3
- package/src/e2e/send-text.e2e.test.ts +11 -2
- package/src/e2e/slash-commands.e2e.test.ts +5 -1
- package/src/e2e/status-cors-auth.e2e.test.ts +11 -2
- package/src/e2e/subagent-smoke.e2e.test.ts +68 -28
- package/src/e2e/subagent.e2e.test.ts +136 -53
- package/src/e2e/tool-lifecycle.e2e.test.ts +5 -1
- package/src/friday-session.forward-agent.test.ts +44 -12
- package/src/friday-session.ts +44 -20
- package/src/history/normalize-message.test.ts +35 -8
- package/src/history/normalize-message.ts +24 -12
- package/src/history/read-transcript.ts +1 -4
- package/src/http/handlers/agent-config.test.ts +10 -3
- package/src/http/handlers/agent-config.ts +22 -8
- package/src/http/handlers/agents-list.test.ts +1 -5
- package/src/http/handlers/cancel.test.ts +12 -3
- package/src/http/handlers/cancel.ts +4 -2
- package/src/http/handlers/device-approve.test.ts +12 -3
- package/src/http/handlers/device-approve.ts +33 -21
- package/src/http/handlers/files-download.ts +17 -13
- package/src/http/handlers/files.test.ts +8 -2
- package/src/http/handlers/files.ts +21 -7
- package/src/http/handlers/health.test.ts +43 -11
- package/src/http/handlers/health.ts +22 -6
- package/src/http/handlers/history-messages.test.ts +51 -9
- package/src/http/handlers/history-messages.ts +4 -1
- package/src/http/handlers/history-sessions.test.ts +46 -9
- package/src/http/handlers/history-sessions.ts +5 -3
- package/src/http/handlers/history-set-title.test.ts +14 -5
- package/src/http/handlers/link-preview.test.ts +57 -16
- package/src/http/handlers/link-preview.ts +4 -1
- package/src/http/handlers/messages.test.ts +12 -8
- package/src/http/handlers/messages.ts +67 -19
- package/src/http/handlers/models-list.ts +14 -8
- package/src/http/handlers/nodes-approve.test.ts +15 -4
- package/src/http/handlers/nodes-approve.ts +38 -40
- package/src/http/handlers/plugin-info.ts +5 -6
- package/src/http/handlers/plugin-upgrade.ts +4 -1
- package/src/http/handlers/sse.ts +3 -1
- package/src/http/server.ts +9 -6
- package/src/link-preview/og-parse.test.ts +6 -2
- package/src/link-preview/og-parse.ts +10 -3
- package/src/link-preview/preview-service.ts +4 -1
- package/src/link-preview/ssrf-guard.test.ts +72 -15
- package/src/link-preview/ssrf-guard.ts +2 -1
- package/src/media-fetch.test.ts +7 -2
- package/src/media-fetch.ts +1 -2
- package/src/openclaw.d.ts +26 -9
- package/src/plugin-install-info.ts +20 -9
- package/src/run-metadata.ts +2 -1
- package/src/session/session-manager.ts +19 -11
- package/src/session-usage-snapshot.ts +3 -1
- package/src/session-usage-store.ts +3 -1
- package/src/skills-discovery.test.ts +14 -10
- package/src/skills-discovery.ts +43 -27
- package/src/sse/emitter.test.ts +1 -1
- package/src/sse/emitter.ts +9 -3
- package/src/sse/offline-queue.ts +17 -8
- package/src/test-support/app-simulator.ts +17 -3
- package/src/test-support/mock-dispatch.ts +17 -4
- package/src/thinking-levels.ts +3 -1
- package/src/tool-catalog.ts +16 -7
- package/src/upgrade-runtime.ts +4 -2
- package/src/version.ts +5 -1
- package/tsconfig.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { createAppSimulator } from "../test-support/app-simulator.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createTempHistoryDir,
|
|
5
|
+
removeTempHistoryDir,
|
|
6
|
+
setMockRuntime,
|
|
7
|
+
} from "../test-support/mock-runtime.js";
|
|
4
8
|
import { registerFridayNextHttpRoutes } from "../http/server.js";
|
|
5
9
|
|
|
6
10
|
describe("e2e status cors auth", () => {
|
|
@@ -22,7 +26,12 @@ describe("e2e status cors auth", () => {
|
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
it("CORS 预检", async () => {
|
|
25
|
-
setMockRuntime({
|
|
29
|
+
setMockRuntime({
|
|
30
|
+
historyDir,
|
|
31
|
+
authToken: "test-token",
|
|
32
|
+
corsEnabled: true,
|
|
33
|
+
allowOrigin: "https://app.example",
|
|
34
|
+
});
|
|
26
35
|
const app = createAppSimulator({ token: "test-token" });
|
|
27
36
|
const res = await app.options("/friday-next/events", "https://app.example");
|
|
28
37
|
expect(res.status).toBe(204);
|
|
@@ -12,10 +12,7 @@ import {
|
|
|
12
12
|
ensureSubagentFromSpawnTool,
|
|
13
13
|
resetForTest as resetSubagentRegistry,
|
|
14
14
|
} from "../agent/subagent-registry.js";
|
|
15
|
-
import {
|
|
16
|
-
forwardAgentEventRaw,
|
|
17
|
-
registerFridaySessionDeviceMapping,
|
|
18
|
-
} from "../friday-session.js";
|
|
15
|
+
import { forwardAgentEventRaw, registerFridaySessionDeviceMapping } from "../friday-session.js";
|
|
19
16
|
import { sseEmitter } from "../sse/emitter.js";
|
|
20
17
|
|
|
21
18
|
const deviceId = "IOS-DEVICE-AAAA-BBBB-CCCC-DDDD";
|
|
@@ -37,17 +34,22 @@ function describeFrame(frame: { event?: string; data?: Record<string, unknown> }
|
|
|
37
34
|
case "connected":
|
|
38
35
|
return `connected deviceId=${data.deviceId} lastSeq=${data.lastSeq}`;
|
|
39
36
|
case "subagent": {
|
|
40
|
-
const extra =
|
|
37
|
+
const extra =
|
|
38
|
+
data.phase === "ended" ? ` outcome=${data.outcome} error=${data.error ?? "-"}` : "";
|
|
41
39
|
return `subagent phase=${data.phase} runId=${data.runId ?? "(pending)"} label=${data.label ?? "-"} parentRunId=${data.parentRunId ?? "-"} depth=${data.depth}${extra}`;
|
|
42
40
|
}
|
|
43
41
|
case "agent": {
|
|
44
42
|
const sub = data.subagent as Record<string, unknown> | undefined;
|
|
45
|
-
const subTag = sub
|
|
43
|
+
const subTag = sub
|
|
44
|
+
? ` SUB="agent:${sub.label ?? "?"}" depth=${sub.depth} parent=${sub.parentRunId ?? "-"}`
|
|
45
|
+
: " (main)";
|
|
46
46
|
const inner = (data.data ?? {}) as Record<string, unknown>;
|
|
47
47
|
let detail = "";
|
|
48
|
-
if (data.stream === "thinking")
|
|
48
|
+
if (data.stream === "thinking")
|
|
49
|
+
detail = ` thinking: "${String(inner.text ?? "").slice(0, 50)}"`;
|
|
49
50
|
else if (data.stream === "tool") detail = ` tool=${inner.name ?? "?"} phase=${inner.phase}`;
|
|
50
|
-
else if (data.stream === "assistant")
|
|
51
|
+
else if (data.stream === "assistant")
|
|
52
|
+
detail = ` text="${String(inner.text ?? inner.phase ?? "")}"`;
|
|
51
53
|
else if (data.stream === "lifecycle") detail = ` phase=${inner.phase}`;
|
|
52
54
|
return `agent stream=${data.stream} runId=${String(data.runId).slice(0, 30)}…${subTag}${detail}`;
|
|
53
55
|
}
|
|
@@ -72,8 +74,11 @@ describe("subagent smoke", () => {
|
|
|
72
74
|
|
|
73
75
|
// ── Main run lifecycle start ──────────────────────────
|
|
74
76
|
forwardAgentEventRaw({
|
|
75
|
-
runId: mainRunId,
|
|
76
|
-
|
|
77
|
+
runId: mainRunId,
|
|
78
|
+
seq: 1,
|
|
79
|
+
stream: "lifecycle",
|
|
80
|
+
sessionKey: mainSessionKey,
|
|
81
|
+
data: { phase: "start" },
|
|
77
82
|
});
|
|
78
83
|
|
|
79
84
|
// ── Subagent A: tool.start (spawning) ─────────────────
|
|
@@ -82,10 +87,13 @@ describe("subagent smoke", () => {
|
|
|
82
87
|
const compoundA = `announce:v1:${childKeyA}:${bareA}`;
|
|
83
88
|
|
|
84
89
|
forwardAgentEventRaw({
|
|
85
|
-
runId: mainRunId,
|
|
90
|
+
runId: mainRunId,
|
|
91
|
+
seq: 2,
|
|
92
|
+
stream: "tool",
|
|
86
93
|
sessionKey: mainSessionKey,
|
|
87
94
|
data: {
|
|
88
|
-
phase: "start",
|
|
95
|
+
phase: "start",
|
|
96
|
+
name: "sessions_spawn",
|
|
89
97
|
toolCallId: "call_a",
|
|
90
98
|
args: { taskName: "code-reviewer" },
|
|
91
99
|
},
|
|
@@ -93,10 +101,14 @@ describe("subagent smoke", () => {
|
|
|
93
101
|
|
|
94
102
|
// ── Subagent A: tool.result (spawned) ─────────────────
|
|
95
103
|
forwardAgentEventRaw({
|
|
96
|
-
runId: mainRunId,
|
|
104
|
+
runId: mainRunId,
|
|
105
|
+
seq: 3,
|
|
106
|
+
stream: "tool",
|
|
97
107
|
sessionKey: mainSessionKey,
|
|
98
108
|
data: {
|
|
99
|
-
phase: "result",
|
|
109
|
+
phase: "result",
|
|
110
|
+
name: "sessions_spawn",
|
|
111
|
+
toolCallId: "call_1",
|
|
100
112
|
meta: "code-reviewer",
|
|
101
113
|
result: { details: spawnResult(childKeyA, bareA, "code-reviewer") },
|
|
102
114
|
},
|
|
@@ -105,26 +117,41 @@ describe("subagent smoke", () => {
|
|
|
105
117
|
|
|
106
118
|
// ── Subagent A: agent events (subagent's own sessionKey) ─
|
|
107
119
|
forwardAgentEventRaw({
|
|
108
|
-
runId: compoundA,
|
|
109
|
-
|
|
120
|
+
runId: compoundA,
|
|
121
|
+
seq: 1,
|
|
122
|
+
stream: "lifecycle",
|
|
123
|
+
data: { phase: "start" },
|
|
124
|
+
sessionKey: childKeyA,
|
|
110
125
|
});
|
|
111
126
|
forwardAgentEventRaw({
|
|
112
|
-
runId: compoundA,
|
|
113
|
-
|
|
127
|
+
runId: compoundA,
|
|
128
|
+
seq: 2,
|
|
129
|
+
stream: "thinking",
|
|
130
|
+
data: {
|
|
131
|
+
text: "Let me review the code for issues…",
|
|
132
|
+
delta: "Let me review the code for issues…",
|
|
133
|
+
reasoningPrefixChars: 0,
|
|
134
|
+
},
|
|
114
135
|
sessionKey: childKeyA,
|
|
115
136
|
});
|
|
116
137
|
forwardAgentEventRaw({
|
|
117
|
-
runId: compoundA,
|
|
138
|
+
runId: compoundA,
|
|
139
|
+
seq: 3,
|
|
140
|
+
stream: "tool",
|
|
118
141
|
data: { phase: "start", name: "read", args: { path: "src/app.ts" } },
|
|
119
142
|
sessionKey: childKeyA,
|
|
120
143
|
});
|
|
121
144
|
forwardAgentEventRaw({
|
|
122
|
-
runId: compoundA,
|
|
145
|
+
runId: compoundA,
|
|
146
|
+
seq: 4,
|
|
147
|
+
stream: "tool",
|
|
123
148
|
data: { phase: "result", name: "read", result: "file contents…" },
|
|
124
149
|
sessionKey: childKeyA,
|
|
125
150
|
});
|
|
126
151
|
forwardAgentEventRaw({
|
|
127
|
-
runId: compoundA,
|
|
152
|
+
runId: compoundA,
|
|
153
|
+
seq: 5,
|
|
154
|
+
stream: "assistant",
|
|
128
155
|
data: { phase: "delta", text: "Found 3 issues in the code." },
|
|
129
156
|
sessionKey: childKeyA,
|
|
130
157
|
});
|
|
@@ -135,20 +162,27 @@ describe("subagent smoke", () => {
|
|
|
135
162
|
const compoundB = `announce:v1:${childKeyB}:${bareB}`;
|
|
136
163
|
|
|
137
164
|
forwardAgentEventRaw({
|
|
138
|
-
runId: compoundA,
|
|
165
|
+
runId: compoundA,
|
|
166
|
+
seq: 7,
|
|
167
|
+
stream: "tool",
|
|
139
168
|
sessionKey: mainSessionKey,
|
|
140
169
|
data: {
|
|
141
|
-
phase: "start",
|
|
170
|
+
phase: "start",
|
|
171
|
+
name: "sessions_spawn",
|
|
142
172
|
toolCallId: "call_b",
|
|
143
173
|
args: { taskName: "lint" },
|
|
144
174
|
},
|
|
145
175
|
});
|
|
146
176
|
|
|
147
177
|
forwardAgentEventRaw({
|
|
148
|
-
runId: compoundA,
|
|
178
|
+
runId: compoundA,
|
|
179
|
+
seq: 8,
|
|
180
|
+
stream: "tool",
|
|
149
181
|
sessionKey: mainSessionKey,
|
|
150
182
|
data: {
|
|
151
|
-
phase: "result",
|
|
183
|
+
phase: "result",
|
|
184
|
+
name: "sessions_spawn",
|
|
185
|
+
toolCallId: "call_2",
|
|
152
186
|
meta: "lint",
|
|
153
187
|
result: { details: spawnResult(childKeyB, bareB, "lint") },
|
|
154
188
|
},
|
|
@@ -157,21 +191,27 @@ describe("subagent smoke", () => {
|
|
|
157
191
|
|
|
158
192
|
// ── Subagent B: agent event (subagent's own sessionKey) ─
|
|
159
193
|
forwardAgentEventRaw({
|
|
160
|
-
runId: compoundB,
|
|
194
|
+
runId: compoundB,
|
|
195
|
+
seq: 1,
|
|
196
|
+
stream: "assistant",
|
|
161
197
|
data: { phase: "delta", text: "No lint errors found." },
|
|
162
198
|
sessionKey: childKeyB,
|
|
163
199
|
});
|
|
164
200
|
|
|
165
201
|
// ── Subagent B ended (subagent's own sessionKey) ──────
|
|
166
202
|
forwardAgentEventRaw({
|
|
167
|
-
runId: compoundB,
|
|
203
|
+
runId: compoundB,
|
|
204
|
+
seq: 2,
|
|
205
|
+
stream: "lifecycle",
|
|
168
206
|
data: { phase: "end" },
|
|
169
207
|
sessionKey: childKeyB,
|
|
170
208
|
});
|
|
171
209
|
|
|
172
210
|
// ── Subagent A ended (subagent's own sessionKey) ──────
|
|
173
211
|
forwardAgentEventRaw({
|
|
174
|
-
runId: compoundA,
|
|
212
|
+
runId: compoundA,
|
|
213
|
+
seq: 7,
|
|
214
|
+
stream: "lifecycle",
|
|
175
215
|
data: { phase: "end" },
|
|
176
216
|
sessionKey: childKeyA,
|
|
177
217
|
});
|
|
@@ -203,15 +203,20 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
203
203
|
|
|
204
204
|
// Main run start
|
|
205
205
|
forwardAgentEventRaw({
|
|
206
|
-
runId: mainRunId,
|
|
207
|
-
|
|
206
|
+
runId: mainRunId,
|
|
207
|
+
seq: 1,
|
|
208
|
+
stream: "lifecycle",
|
|
209
|
+
sessionKey: mainSessionKey,
|
|
210
|
+
data: { phase: "start" },
|
|
208
211
|
});
|
|
209
212
|
|
|
210
213
|
const broadcastCalls = captureBroadcastCalls();
|
|
211
214
|
|
|
212
215
|
// Simulate sessions_spawn tool result
|
|
213
216
|
forwardAgentEventRaw({
|
|
214
|
-
runId: mainRunId,
|
|
217
|
+
runId: mainRunId,
|
|
218
|
+
seq: 2,
|
|
219
|
+
stream: "tool",
|
|
215
220
|
sessionKey: mainSessionKey,
|
|
216
221
|
data: {
|
|
217
222
|
phase: "result",
|
|
@@ -243,17 +248,30 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
243
248
|
|
|
244
249
|
// Main run
|
|
245
250
|
forwardAgentEventRaw({
|
|
246
|
-
runId: mainRunId,
|
|
247
|
-
|
|
251
|
+
runId: mainRunId,
|
|
252
|
+
seq: 1,
|
|
253
|
+
stream: "lifecycle",
|
|
254
|
+
sessionKey: mainSessionKey,
|
|
255
|
+
data: { phase: "start" },
|
|
248
256
|
});
|
|
249
257
|
|
|
250
258
|
// Spawn subagent
|
|
251
259
|
forwardAgentEventRaw({
|
|
252
|
-
runId: mainRunId,
|
|
260
|
+
runId: mainRunId,
|
|
261
|
+
seq: 2,
|
|
262
|
+
stream: "tool",
|
|
253
263
|
sessionKey: mainSessionKey,
|
|
254
264
|
data: {
|
|
255
|
-
phase: "result",
|
|
256
|
-
|
|
265
|
+
phase: "result",
|
|
266
|
+
name: "sessions_spawn",
|
|
267
|
+
toolCallId: "c1",
|
|
268
|
+
result: {
|
|
269
|
+
details: makeSpawnToolResult({
|
|
270
|
+
childSessionKey: childKey,
|
|
271
|
+
runId: bareRunId,
|
|
272
|
+
taskName: "cr",
|
|
273
|
+
}),
|
|
274
|
+
},
|
|
257
275
|
},
|
|
258
276
|
});
|
|
259
277
|
|
|
@@ -264,14 +282,14 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
264
282
|
|
|
265
283
|
// Agent event for subagent (announce runId, subagent's own sessionKey)
|
|
266
284
|
forwardAgentEventRaw({
|
|
267
|
-
runId: compoundRunId,
|
|
285
|
+
runId: compoundRunId,
|
|
286
|
+
seq: 1,
|
|
287
|
+
stream: "thinking",
|
|
268
288
|
data: { text: "reviewing..." },
|
|
269
289
|
sessionKey: childKey,
|
|
270
290
|
});
|
|
271
291
|
|
|
272
|
-
const agentCall = calls.find(
|
|
273
|
-
([, e]) => e.type === "agent" && e.data.stream === "thinking",
|
|
274
|
-
);
|
|
292
|
+
const agentCall = calls.find(([, e]) => e.type === "agent" && e.data.stream === "thinking");
|
|
275
293
|
expect(agentCall).toBeTruthy();
|
|
276
294
|
expect(agentCall![1].data.subagent).toEqual({
|
|
277
295
|
label: "cr",
|
|
@@ -282,21 +300,24 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
282
300
|
|
|
283
301
|
it("main agent events are NOT annotated", () => {
|
|
284
302
|
forwardAgentEventRaw({
|
|
285
|
-
runId: mainRunId,
|
|
286
|
-
|
|
303
|
+
runId: mainRunId,
|
|
304
|
+
seq: 1,
|
|
305
|
+
stream: "lifecycle",
|
|
306
|
+
sessionKey: mainSessionKey,
|
|
307
|
+
data: { phase: "start" },
|
|
287
308
|
});
|
|
288
309
|
|
|
289
310
|
const calls = captureBroadcastToRunCalls();
|
|
290
311
|
|
|
291
312
|
forwardAgentEventRaw({
|
|
292
|
-
runId: mainRunId,
|
|
313
|
+
runId: mainRunId,
|
|
314
|
+
seq: 2,
|
|
315
|
+
stream: "assistant",
|
|
293
316
|
sessionKey: mainSessionKey,
|
|
294
317
|
data: { text: "main reply" },
|
|
295
318
|
});
|
|
296
319
|
|
|
297
|
-
const agentCall = calls.find(
|
|
298
|
-
([, e]) => e.type === "agent" && e.data.stream === "assistant",
|
|
299
|
-
);
|
|
320
|
+
const agentCall = calls.find(([, e]) => e.type === "agent" && e.data.stream === "assistant");
|
|
300
321
|
expect(agentCall![1].data.subagent).toBeUndefined();
|
|
301
322
|
});
|
|
302
323
|
|
|
@@ -305,16 +326,29 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
305
326
|
const bareRunId = "bare-cr";
|
|
306
327
|
|
|
307
328
|
forwardAgentEventRaw({
|
|
308
|
-
runId: mainRunId,
|
|
309
|
-
|
|
329
|
+
runId: mainRunId,
|
|
330
|
+
seq: 1,
|
|
331
|
+
stream: "lifecycle",
|
|
332
|
+
sessionKey: mainSessionKey,
|
|
333
|
+
data: { phase: "start" },
|
|
310
334
|
});
|
|
311
335
|
|
|
312
336
|
forwardAgentEventRaw({
|
|
313
|
-
runId: mainRunId,
|
|
337
|
+
runId: mainRunId,
|
|
338
|
+
seq: 2,
|
|
339
|
+
stream: "tool",
|
|
314
340
|
sessionKey: mainSessionKey,
|
|
315
341
|
data: {
|
|
316
|
-
phase: "result",
|
|
317
|
-
|
|
342
|
+
phase: "result",
|
|
343
|
+
name: "sessions_spawn",
|
|
344
|
+
toolCallId: "c1",
|
|
345
|
+
result: {
|
|
346
|
+
details: makeSpawnToolResult({
|
|
347
|
+
childSessionKey: childKey,
|
|
348
|
+
runId: bareRunId,
|
|
349
|
+
taskName: "cr",
|
|
350
|
+
}),
|
|
351
|
+
},
|
|
318
352
|
},
|
|
319
353
|
});
|
|
320
354
|
|
|
@@ -325,7 +359,9 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
325
359
|
|
|
326
360
|
// Lifecycle end for subagent (subagent's own sessionKey)
|
|
327
361
|
forwardAgentEventRaw({
|
|
328
|
-
runId: compoundRunId,
|
|
362
|
+
runId: compoundRunId,
|
|
363
|
+
seq: 5,
|
|
364
|
+
stream: "lifecycle",
|
|
329
365
|
data: { phase: "end" },
|
|
330
366
|
sessionKey: childKey,
|
|
331
367
|
});
|
|
@@ -342,15 +378,22 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
342
378
|
const bareRunId = "bare-cr";
|
|
343
379
|
|
|
344
380
|
forwardAgentEventRaw({
|
|
345
|
-
runId: mainRunId,
|
|
346
|
-
|
|
381
|
+
runId: mainRunId,
|
|
382
|
+
seq: 1,
|
|
383
|
+
stream: "lifecycle",
|
|
384
|
+
sessionKey: mainSessionKey,
|
|
385
|
+
data: { phase: "start" },
|
|
347
386
|
});
|
|
348
387
|
|
|
349
388
|
forwardAgentEventRaw({
|
|
350
|
-
runId: mainRunId,
|
|
389
|
+
runId: mainRunId,
|
|
390
|
+
seq: 2,
|
|
391
|
+
stream: "tool",
|
|
351
392
|
sessionKey: mainSessionKey,
|
|
352
393
|
data: {
|
|
353
|
-
phase: "result",
|
|
394
|
+
phase: "result",
|
|
395
|
+
name: "sessions_spawn",
|
|
396
|
+
toolCallId: "c1",
|
|
354
397
|
result: { details: makeSpawnToolResult({ childSessionKey: childKey, runId: bareRunId }) },
|
|
355
398
|
},
|
|
356
399
|
});
|
|
@@ -361,7 +404,9 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
361
404
|
const broadcastCalls = captureBroadcastCalls();
|
|
362
405
|
|
|
363
406
|
forwardAgentEventRaw({
|
|
364
|
-
runId: compoundRunId,
|
|
407
|
+
runId: compoundRunId,
|
|
408
|
+
seq: 5,
|
|
409
|
+
stream: "lifecycle",
|
|
365
410
|
data: { phase: "error", error: "timeout" },
|
|
366
411
|
sessionKey: childKey,
|
|
367
412
|
});
|
|
@@ -379,15 +424,22 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
379
424
|
const bareRunId = "bare-cr";
|
|
380
425
|
|
|
381
426
|
forwardAgentEventRaw({
|
|
382
|
-
runId: mainRunId,
|
|
383
|
-
|
|
427
|
+
runId: mainRunId,
|
|
428
|
+
seq: 1,
|
|
429
|
+
stream: "lifecycle",
|
|
430
|
+
sessionKey: mainSessionKey,
|
|
431
|
+
data: { phase: "start" },
|
|
384
432
|
});
|
|
385
433
|
|
|
386
434
|
forwardAgentEventRaw({
|
|
387
|
-
runId: mainRunId,
|
|
435
|
+
runId: mainRunId,
|
|
436
|
+
seq: 2,
|
|
437
|
+
stream: "tool",
|
|
388
438
|
sessionKey: mainSessionKey,
|
|
389
439
|
data: {
|
|
390
|
-
phase: "result",
|
|
440
|
+
phase: "result",
|
|
441
|
+
name: "sessions_spawn",
|
|
442
|
+
toolCallId: "c1",
|
|
391
443
|
result: { details: makeSpawnToolResult({ childSessionKey: childKey, runId: bareRunId }) },
|
|
392
444
|
},
|
|
393
445
|
});
|
|
@@ -398,12 +450,16 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
398
450
|
const broadcastCalls = captureBroadcastCalls();
|
|
399
451
|
|
|
400
452
|
forwardAgentEventRaw({
|
|
401
|
-
runId: compoundRunId,
|
|
453
|
+
runId: compoundRunId,
|
|
454
|
+
seq: 5,
|
|
455
|
+
stream: "lifecycle",
|
|
402
456
|
data: { phase: "end" },
|
|
403
457
|
sessionKey: childKey,
|
|
404
458
|
});
|
|
405
459
|
forwardAgentEventRaw({
|
|
406
|
-
runId: compoundRunId,
|
|
460
|
+
runId: compoundRunId,
|
|
461
|
+
seq: 6,
|
|
462
|
+
stream: "lifecycle",
|
|
407
463
|
data: { phase: "end" },
|
|
408
464
|
sessionKey: childKey,
|
|
409
465
|
});
|
|
@@ -426,17 +482,30 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
426
482
|
|
|
427
483
|
// Main run start
|
|
428
484
|
forwardAgentEventRaw({
|
|
429
|
-
runId: mainRunId,
|
|
430
|
-
|
|
485
|
+
runId: mainRunId,
|
|
486
|
+
seq: 1,
|
|
487
|
+
stream: "lifecycle",
|
|
488
|
+
sessionKey: mainSessionKey,
|
|
489
|
+
data: { phase: "start" },
|
|
431
490
|
});
|
|
432
491
|
|
|
433
492
|
// sessions_spawn for A
|
|
434
493
|
forwardAgentEventRaw({
|
|
435
|
-
runId: mainRunId,
|
|
494
|
+
runId: mainRunId,
|
|
495
|
+
seq: 2,
|
|
496
|
+
stream: "tool",
|
|
436
497
|
sessionKey: mainSessionKey,
|
|
437
498
|
data: {
|
|
438
|
-
phase: "result",
|
|
439
|
-
|
|
499
|
+
phase: "result",
|
|
500
|
+
name: "sessions_spawn",
|
|
501
|
+
toolCallId: "c1",
|
|
502
|
+
result: {
|
|
503
|
+
details: makeSpawnToolResult({
|
|
504
|
+
childSessionKey: childKeyA,
|
|
505
|
+
runId: bareA,
|
|
506
|
+
taskName: "reviewer",
|
|
507
|
+
}),
|
|
508
|
+
},
|
|
440
509
|
},
|
|
441
510
|
});
|
|
442
511
|
sseEmitter.trackDeviceForRun(deviceId, compoundA);
|
|
@@ -444,13 +513,13 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
444
513
|
// Subagent A thinking (subagent's own sessionKey)
|
|
445
514
|
const calls = captureBroadcastToRunCalls();
|
|
446
515
|
forwardAgentEventRaw({
|
|
447
|
-
runId: compoundA,
|
|
516
|
+
runId: compoundA,
|
|
517
|
+
seq: 1,
|
|
518
|
+
stream: "thinking",
|
|
448
519
|
data: { text: "reviewing code..." },
|
|
449
520
|
sessionKey: childKeyA,
|
|
450
521
|
});
|
|
451
|
-
const thinkingA = calls.find(
|
|
452
|
-
([, e]) => e.type === "agent" && e.data.stream === "thinking",
|
|
453
|
-
);
|
|
522
|
+
const thinkingA = calls.find(([, e]) => e.type === "agent" && e.data.stream === "thinking");
|
|
454
523
|
expect(thinkingA![1].data.subagent).toEqual({
|
|
455
524
|
label: "reviewer",
|
|
456
525
|
parentRunId: mainRunId,
|
|
@@ -461,36 +530,50 @@ describe("subagent via sessions_spawn tool", () => {
|
|
|
461
530
|
// In reality, B is spawned from a tool call inside A's run, but here we simulate it from the main run
|
|
462
531
|
// The registry handles nesting via requesterSessionKey chain
|
|
463
532
|
forwardAgentEventRaw({
|
|
464
|
-
runId: compoundA,
|
|
533
|
+
runId: compoundA,
|
|
534
|
+
seq: 2,
|
|
535
|
+
stream: "tool",
|
|
465
536
|
sessionKey: mainSessionKey,
|
|
466
537
|
data: {
|
|
467
|
-
phase: "result",
|
|
468
|
-
|
|
538
|
+
phase: "result",
|
|
539
|
+
name: "sessions_spawn",
|
|
540
|
+
toolCallId: "c2",
|
|
541
|
+
result: {
|
|
542
|
+
details: makeSpawnToolResult({
|
|
543
|
+
childSessionKey: childKeyB,
|
|
544
|
+
runId: bareB,
|
|
545
|
+
taskName: "lint",
|
|
546
|
+
}),
|
|
547
|
+
},
|
|
469
548
|
},
|
|
470
549
|
});
|
|
471
550
|
sseEmitter.trackDeviceForRun(deviceId, compoundB);
|
|
472
551
|
|
|
473
552
|
// Subagent B thinking (subagent's own sessionKey)
|
|
474
553
|
forwardAgentEventRaw({
|
|
475
|
-
runId: compoundB,
|
|
554
|
+
runId: compoundB,
|
|
555
|
+
seq: 1,
|
|
556
|
+
stream: "assistant",
|
|
476
557
|
data: { text: "no lint errors" },
|
|
477
558
|
sessionKey: childKeyB,
|
|
478
559
|
});
|
|
479
|
-
const assistantB = calls.find(
|
|
480
|
-
([, e]) => e.type === "agent" && e.data.stream === "assistant",
|
|
481
|
-
);
|
|
560
|
+
const assistantB = calls.find(([, e]) => e.type === "agent" && e.data.stream === "assistant");
|
|
482
561
|
expect(assistantB![1].data.subagent).toBeDefined();
|
|
483
562
|
|
|
484
563
|
// B ends (subagent's own sessionKey)
|
|
485
564
|
forwardAgentEventRaw({
|
|
486
|
-
runId: compoundB,
|
|
565
|
+
runId: compoundB,
|
|
566
|
+
seq: 5,
|
|
567
|
+
stream: "lifecycle",
|
|
487
568
|
data: { phase: "end" },
|
|
488
569
|
sessionKey: childKeyB,
|
|
489
570
|
});
|
|
490
571
|
|
|
491
572
|
// A ends (subagent's own sessionKey)
|
|
492
573
|
forwardAgentEventRaw({
|
|
493
|
-
runId: compoundA,
|
|
574
|
+
runId: compoundA,
|
|
575
|
+
seq: 6,
|
|
576
|
+
stream: "lifecycle",
|
|
494
577
|
data: { phase: "end" },
|
|
495
578
|
sessionKey: childKeyA,
|
|
496
579
|
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { createAppSimulator } from "../test-support/app-simulator.js";
|
|
3
3
|
import { mockDispatchScript, resetMockDispatch } from "../test-support/mock-dispatch.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createTempHistoryDir,
|
|
6
|
+
removeTempHistoryDir,
|
|
7
|
+
setMockRuntime,
|
|
8
|
+
} from "../test-support/mock-runtime.js";
|
|
5
9
|
|
|
6
10
|
describe("e2e tool lifecycle", () => {
|
|
7
11
|
let historyDir = "";
|