@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.
Files changed (124) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/src/agent/dispatch-bridge.d.ts +1 -1
  3. package/dist/src/agent/node-pairing-bridge.d.ts +11 -8
  4. package/dist/src/agent/node-pairing-bridge.js +6 -2
  5. package/dist/src/agent/operator-scope.d.ts +19 -0
  6. package/dist/src/agent/operator-scope.js +54 -0
  7. package/dist/src/agent/subagent-registry.js +0 -3
  8. package/dist/src/channel-actions.js +3 -1
  9. package/dist/src/channel.js +0 -2
  10. package/dist/src/collect-message-media-paths.js +10 -1
  11. package/dist/src/friday-session.js +34 -10
  12. package/dist/src/history/normalize-message.js +22 -8
  13. package/dist/src/http/handlers/agent-config.js +10 -4
  14. package/dist/src/http/handlers/cancel.js +4 -2
  15. package/dist/src/http/handlers/device-approve.js +3 -1
  16. package/dist/src/http/handlers/files-download.js +6 -8
  17. package/dist/src/http/handlers/files.js +1 -1
  18. package/dist/src/http/handlers/health.js +18 -4
  19. package/dist/src/http/handlers/history-messages.js +1 -1
  20. package/dist/src/http/handlers/history-sessions.js +5 -3
  21. package/dist/src/http/handlers/messages.js +34 -11
  22. package/dist/src/http/handlers/models-list.js +1 -1
  23. package/dist/src/http/handlers/nodes-approve.js +1 -6
  24. package/dist/src/http/handlers/plugin-info.js +1 -1
  25. package/dist/src/http/server.js +4 -2
  26. package/dist/src/link-preview/og-parse.js +3 -1
  27. package/dist/src/plugin-install-info.js +4 -1
  28. package/dist/src/session/session-manager.js +9 -3
  29. package/dist/src/session-usage-store.js +3 -1
  30. package/dist/src/skills-discovery.d.ts +5 -4
  31. package/dist/src/skills-discovery.js +27 -22
  32. package/dist/src/sse/offline-queue.js +4 -1
  33. package/dist/src/tool-catalog.js +2 -3
  34. package/dist/src/upgrade-runtime.d.ts +1 -1
  35. package/dist/src/version.js +3 -1
  36. package/index.ts +43 -35
  37. package/install.js +131 -43
  38. package/package.json +10 -1
  39. package/src/agent/abort-run.ts +2 -3
  40. package/src/agent/dispatch-bridge.ts +2 -1
  41. package/src/agent/media-bridge.ts +9 -2
  42. package/src/agent/node-pairing-bridge.ts +29 -15
  43. package/src/agent/operator-scope.test.ts +66 -0
  44. package/src/agent/operator-scope.ts +63 -0
  45. package/src/agent/run-usage-accumulator.ts +4 -2
  46. package/src/agent/subagent-registry.ts +0 -4
  47. package/src/agent-run-context-bridge.ts +3 -1
  48. package/src/channel-actions.test.ts +10 -4
  49. package/src/channel-actions.ts +3 -1
  50. package/src/channel.outbound.test.ts +18 -4
  51. package/src/channel.ts +121 -123
  52. package/src/collect-message-media-paths.ts +15 -6
  53. package/src/config.ts +1 -4
  54. package/src/e2e/agents-list.e2e.test.ts +9 -2
  55. package/src/e2e/attachments-inbound.e2e.test.ts +5 -1
  56. package/src/e2e/attachments-outbound.e2e.test.ts +7 -2
  57. package/src/e2e/auto-approve.integration.test.ts +13 -7
  58. package/src/e2e/cancel-reconnect-errors.e2e.test.ts +18 -3
  59. package/src/e2e/connect-and-connected.e2e.test.ts +5 -1
  60. package/src/e2e/offline-replay.e2e.test.ts +17 -3
  61. package/src/e2e/send-text.e2e.test.ts +11 -2
  62. package/src/e2e/slash-commands.e2e.test.ts +5 -1
  63. package/src/e2e/status-cors-auth.e2e.test.ts +11 -2
  64. package/src/e2e/subagent-smoke.e2e.test.ts +68 -28
  65. package/src/e2e/subagent.e2e.test.ts +136 -53
  66. package/src/e2e/tool-lifecycle.e2e.test.ts +5 -1
  67. package/src/friday-session.forward-agent.test.ts +44 -12
  68. package/src/friday-session.ts +44 -20
  69. package/src/history/normalize-message.test.ts +35 -8
  70. package/src/history/normalize-message.ts +24 -12
  71. package/src/history/read-transcript.ts +1 -4
  72. package/src/http/handlers/agent-config.test.ts +10 -3
  73. package/src/http/handlers/agent-config.ts +22 -8
  74. package/src/http/handlers/agents-list.test.ts +1 -5
  75. package/src/http/handlers/cancel.test.ts +12 -3
  76. package/src/http/handlers/cancel.ts +4 -2
  77. package/src/http/handlers/device-approve.test.ts +12 -3
  78. package/src/http/handlers/device-approve.ts +33 -21
  79. package/src/http/handlers/files-download.ts +17 -13
  80. package/src/http/handlers/files.test.ts +8 -2
  81. package/src/http/handlers/files.ts +21 -7
  82. package/src/http/handlers/health.test.ts +43 -11
  83. package/src/http/handlers/health.ts +22 -6
  84. package/src/http/handlers/history-messages.test.ts +51 -9
  85. package/src/http/handlers/history-messages.ts +4 -1
  86. package/src/http/handlers/history-sessions.test.ts +46 -9
  87. package/src/http/handlers/history-sessions.ts +5 -3
  88. package/src/http/handlers/history-set-title.test.ts +14 -5
  89. package/src/http/handlers/link-preview.test.ts +57 -16
  90. package/src/http/handlers/link-preview.ts +4 -1
  91. package/src/http/handlers/messages.test.ts +12 -8
  92. package/src/http/handlers/messages.ts +67 -19
  93. package/src/http/handlers/models-list.ts +14 -8
  94. package/src/http/handlers/nodes-approve.test.ts +15 -4
  95. package/src/http/handlers/nodes-approve.ts +38 -40
  96. package/src/http/handlers/plugin-info.ts +5 -6
  97. package/src/http/handlers/plugin-upgrade.ts +4 -1
  98. package/src/http/handlers/sse.ts +3 -1
  99. package/src/http/server.ts +9 -6
  100. package/src/link-preview/og-parse.test.ts +6 -2
  101. package/src/link-preview/og-parse.ts +10 -3
  102. package/src/link-preview/preview-service.ts +4 -1
  103. package/src/link-preview/ssrf-guard.test.ts +72 -15
  104. package/src/link-preview/ssrf-guard.ts +2 -1
  105. package/src/media-fetch.test.ts +7 -2
  106. package/src/media-fetch.ts +1 -2
  107. package/src/openclaw.d.ts +26 -9
  108. package/src/plugin-install-info.ts +20 -9
  109. package/src/run-metadata.ts +2 -1
  110. package/src/session/session-manager.ts +19 -11
  111. package/src/session-usage-snapshot.ts +3 -1
  112. package/src/session-usage-store.ts +3 -1
  113. package/src/skills-discovery.test.ts +14 -10
  114. package/src/skills-discovery.ts +43 -27
  115. package/src/sse/emitter.test.ts +1 -1
  116. package/src/sse/emitter.ts +9 -3
  117. package/src/sse/offline-queue.ts +17 -8
  118. package/src/test-support/app-simulator.ts +17 -3
  119. package/src/test-support/mock-dispatch.ts +17 -4
  120. package/src/thinking-levels.ts +3 -1
  121. package/src/tool-catalog.ts +16 -7
  122. package/src/upgrade-runtime.ts +4 -2
  123. package/src/version.ts +5 -1
  124. 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 { createTempHistoryDir, removeTempHistoryDir, setMockRuntime } from "../test-support/mock-runtime.js";
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({ historyDir, authToken: "test-token", corsEnabled: true, allowOrigin: "https://app.example" });
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 = data.phase === "ended" ? ` outcome=${data.outcome} error=${data.error ?? "-"}` : "";
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 ? ` SUB="agent:${sub.label ?? "?"}" depth=${sub.depth} parent=${sub.parentRunId ?? "-"}` : " (main)";
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") detail = ` thinking: "${String(inner.text ?? "").slice(0, 50)}"`;
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") detail = ` text="${String(inner.text ?? inner.phase ?? "")}"`;
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, seq: 1, stream: "lifecycle",
76
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
90
+ runId: mainRunId,
91
+ seq: 2,
92
+ stream: "tool",
86
93
  sessionKey: mainSessionKey,
87
94
  data: {
88
- phase: "start", name: "sessions_spawn",
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, seq: 3, stream: "tool",
104
+ runId: mainRunId,
105
+ seq: 3,
106
+ stream: "tool",
97
107
  sessionKey: mainSessionKey,
98
108
  data: {
99
- phase: "result", name: "sessions_spawn", toolCallId: "call_1",
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, seq: 1, stream: "lifecycle",
109
- data: { phase: "start" }, sessionKey: childKeyA,
120
+ runId: compoundA,
121
+ seq: 1,
122
+ stream: "lifecycle",
123
+ data: { phase: "start" },
124
+ sessionKey: childKeyA,
110
125
  });
111
126
  forwardAgentEventRaw({
112
- runId: compoundA, seq: 2, stream: "thinking",
113
- data: { text: "Let me review the code for issues…", delta: "Let me review the code for issues…", reasoningPrefixChars: 0 },
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, seq: 3, stream: "tool",
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, seq: 4, stream: "tool",
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, seq: 5, stream: "assistant",
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, seq: 7, stream: "tool",
165
+ runId: compoundA,
166
+ seq: 7,
167
+ stream: "tool",
139
168
  sessionKey: mainSessionKey,
140
169
  data: {
141
- phase: "start", name: "sessions_spawn",
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, seq: 8, stream: "tool",
178
+ runId: compoundA,
179
+ seq: 8,
180
+ stream: "tool",
149
181
  sessionKey: mainSessionKey,
150
182
  data: {
151
- phase: "result", name: "sessions_spawn", toolCallId: "call_2",
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, seq: 1, stream: "assistant",
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, seq: 2, stream: "lifecycle",
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, seq: 7, stream: "lifecycle",
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, seq: 1, stream: "lifecycle",
207
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
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, seq: 1, stream: "lifecycle",
247
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
260
+ runId: mainRunId,
261
+ seq: 2,
262
+ stream: "tool",
253
263
  sessionKey: mainSessionKey,
254
264
  data: {
255
- phase: "result", name: "sessions_spawn", toolCallId: "c1",
256
- result: { details: makeSpawnToolResult({ childSessionKey: childKey, runId: bareRunId, taskName: "cr" }) },
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, seq: 1, stream: "thinking",
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, seq: 1, stream: "lifecycle",
286
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "assistant",
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, seq: 1, stream: "lifecycle",
309
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
337
+ runId: mainRunId,
338
+ seq: 2,
339
+ stream: "tool",
314
340
  sessionKey: mainSessionKey,
315
341
  data: {
316
- phase: "result", name: "sessions_spawn", toolCallId: "c1",
317
- result: { details: makeSpawnToolResult({ childSessionKey: childKey, runId: bareRunId, taskName: "cr" }) },
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, seq: 5, stream: "lifecycle",
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, seq: 1, stream: "lifecycle",
346
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
389
+ runId: mainRunId,
390
+ seq: 2,
391
+ stream: "tool",
351
392
  sessionKey: mainSessionKey,
352
393
  data: {
353
- phase: "result", name: "sessions_spawn", toolCallId: "c1",
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, seq: 5, stream: "lifecycle",
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, seq: 1, stream: "lifecycle",
383
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
435
+ runId: mainRunId,
436
+ seq: 2,
437
+ stream: "tool",
388
438
  sessionKey: mainSessionKey,
389
439
  data: {
390
- phase: "result", name: "sessions_spawn", toolCallId: "c1",
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, seq: 5, stream: "lifecycle",
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, seq: 6, stream: "lifecycle",
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, seq: 1, stream: "lifecycle",
430
- sessionKey: mainSessionKey, data: { phase: "start" },
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, seq: 2, stream: "tool",
494
+ runId: mainRunId,
495
+ seq: 2,
496
+ stream: "tool",
436
497
  sessionKey: mainSessionKey,
437
498
  data: {
438
- phase: "result", name: "sessions_spawn", toolCallId: "c1",
439
- result: { details: makeSpawnToolResult({ childSessionKey: childKeyA, runId: bareA, taskName: "reviewer" }) },
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, seq: 1, stream: "thinking",
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, seq: 2, stream: "tool",
533
+ runId: compoundA,
534
+ seq: 2,
535
+ stream: "tool",
465
536
  sessionKey: mainSessionKey,
466
537
  data: {
467
- phase: "result", name: "sessions_spawn", toolCallId: "c2",
468
- result: { details: makeSpawnToolResult({ childSessionKey: childKeyB, runId: bareB, taskName: "lint" }) },
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, seq: 1, stream: "assistant",
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, seq: 5, stream: "lifecycle",
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, seq: 6, stream: "lifecycle",
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 { createTempHistoryDir, removeTempHistoryDir, setMockRuntime } from "../test-support/mock-runtime.js";
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 = "";