@syengup/friday-channel-next 0.1.30 → 0.1.37
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/README.md +8 -4
- package/dist/index.js +1 -1
- package/dist/src/agent/abort-run.d.ts +12 -1
- package/dist/src/agent/abort-run.js +24 -9
- package/dist/src/agent/dispatch-bridge.d.ts +1 -1
- package/dist/src/agent/media-bridge.d.ts +8 -1
- package/dist/src/agent/media-bridge.js +23 -2
- 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/subagent-registry.js +0 -3
- package/dist/src/agent-forward-runtime.d.ts +15 -0
- package/dist/src/agent-forward-runtime.js +2 -0
- package/dist/src/agent-id.d.ts +8 -0
- package/dist/src/agent-id.js +21 -0
- package/dist/src/channel-actions.js +48 -15
- package/dist/src/channel.js +22 -3
- 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.d.ts +27 -0
- package/dist/src/http/handlers/agent-config.js +188 -0
- package/dist/src/http/handlers/agent-files.d.ts +21 -0
- package/dist/src/http/handlers/agent-files.js +137 -0
- package/dist/src/http/handlers/agent-tools-catalog.d.ts +10 -0
- package/dist/src/http/handlers/agent-tools-catalog.js +33 -0
- package/dist/src/http/handlers/agents-list.js +1 -19
- package/dist/src/http/handlers/cancel.js +14 -6
- 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.d.ts +16 -0
- package/dist/src/http/handlers/files.js +81 -13
- 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 +33 -14
- package/dist/src/http/handlers/models-list.d.ts +5 -0
- package/dist/src/http/handlers/models-list.js +9 -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/handlers/sessions-settings.js +15 -10
- package/dist/src/http/server.js +27 -2
- package/dist/src/link-preview/og-parse.js +3 -1
- package/dist/src/link-preview/ssrf-guard.js +6 -2
- package/dist/src/media-fetch.js +4 -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 +59 -0
- package/dist/src/skills-discovery.js +252 -0
- package/dist/src/sse/offline-queue.js +4 -1
- package/dist/src/thinking-levels.d.ts +21 -0
- package/dist/src/thinking-levels.js +48 -0
- package/dist/src/tool-catalog.d.ts +53 -0
- package/dist/src/tool-catalog.js +191 -0
- package/dist/src/upgrade-runtime.d.ts +1 -1
- package/dist/src/version.js +4 -2
- package/index.ts +43 -35
- package/install.js +131 -43
- package/package.json +10 -1
- package/src/agent/abort-run.ts +23 -8
- package/src/agent/dispatch-bridge.ts +2 -1
- package/src/agent/media-bridge.test.ts +71 -0
- package/src/agent/media-bridge.ts +30 -1
- package/src/agent/node-pairing-bridge.ts +29 -15
- package/src/agent/run-usage-accumulator.ts +4 -2
- package/src/agent/subagent-registry.ts +0 -4
- package/src/agent-forward-runtime.ts +11 -0
- package/src/agent-id.ts +24 -0
- package/src/agent-run-context-bridge.ts +3 -1
- package/src/channel-actions.test.ts +57 -4
- package/src/channel-actions.ts +41 -15
- package/src/channel.lifecycle.test.ts +41 -0
- package/src/channel.outbound.test.ts +18 -4
- package/src/channel.ts +140 -120
- 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 +212 -0
- package/src/http/handlers/agent-config.ts +232 -0
- package/src/http/handlers/agent-files.test.ts +136 -0
- package/src/http/handlers/agent-files.ts +149 -0
- package/src/http/handlers/agent-tools-catalog.ts +42 -0
- package/src/http/handlers/agents-list.test.ts +1 -5
- package/src/http/handlers/agents-list.ts +1 -22
- package/src/http/handlers/cancel.test.ts +23 -4
- package/src/http/handlers/cancel.ts +14 -6
- 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 +120 -0
- package/src/http/handlers/files.ts +115 -17
- 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 +64 -21
- package/src/http/handlers/models-list.test.ts +114 -0
- package/src/http/handlers/models-list.ts +26 -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/sessions-settings.ts +16 -11
- package/src/http/handlers/sse.ts +3 -1
- package/src/http/server.ts +33 -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 +78 -16
- package/src/link-preview/ssrf-guard.ts +7 -2
- package/src/media-fetch.test.ts +8 -3
- package/src/media-fetch.ts +5 -3
- package/src/openclaw.d.ts +41 -10
- 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 +152 -0
- package/src/skills-discovery.ts +264 -0
- 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.test.ts +143 -0
- package/src/thinking-levels.ts +70 -0
- package/src/tool-catalog.ts +261 -0
- package/src/upgrade-runtime.ts +4 -2
- package/src/version.ts +6 -2
- package/tsconfig.json +1 -1
|
@@ -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 = "";
|
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
resetThinkingStreamAccumStateForTest,
|
|
11
11
|
} from "./friday-session.js";
|
|
12
12
|
import { resetRunMetadataForTest } from "./run-metadata.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
accumulateRunUsage,
|
|
15
|
+
resetRunUsageAccumulatorForTest,
|
|
16
|
+
} from "./agent/run-usage-accumulator.js";
|
|
14
17
|
import { sseEmitter } from "./sse/emitter.js";
|
|
15
18
|
import { toSessionStoreKey } from "./session/session-manager.js";
|
|
16
19
|
|
|
@@ -56,8 +59,10 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
56
59
|
});
|
|
57
60
|
|
|
58
61
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(2);
|
|
59
|
-
const first = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data
|
|
60
|
-
|
|
62
|
+
const first = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data
|
|
63
|
+
.data;
|
|
64
|
+
const second = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[1][1].data
|
|
65
|
+
.data;
|
|
61
66
|
|
|
62
67
|
expect(first.text).toBe(t1);
|
|
63
68
|
expect(first.delta).toBe(t1);
|
|
@@ -94,7 +99,8 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
94
99
|
});
|
|
95
100
|
|
|
96
101
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(3);
|
|
97
|
-
const third = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[2][1].data
|
|
102
|
+
const third = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[2][1].data
|
|
103
|
+
.data;
|
|
98
104
|
expect(third.delta).toBe(t1);
|
|
99
105
|
expect(third.reasoningPrefixChars).toBe(0);
|
|
100
106
|
});
|
|
@@ -123,7 +129,8 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
123
129
|
data: { text: t1, delta: t1 },
|
|
124
130
|
});
|
|
125
131
|
|
|
126
|
-
const third = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[2][1].data
|
|
132
|
+
const third = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[2][1].data
|
|
133
|
+
.data;
|
|
127
134
|
expect(third.reasoningPrefixChars).toBe(0);
|
|
128
135
|
expect(third.delta).toBe(t1);
|
|
129
136
|
});
|
|
@@ -179,7 +186,8 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
179
186
|
});
|
|
180
187
|
|
|
181
188
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(2);
|
|
182
|
-
const endPayload = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[1][1]
|
|
189
|
+
const endPayload = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[1][1]
|
|
190
|
+
.data;
|
|
183
191
|
expect(endPayload.stream).toBe("lifecycle");
|
|
184
192
|
expect((endPayload.data as { phase?: string }).phase).toBe("end");
|
|
185
193
|
expect(endPayload.sessionKey).toBe(sessionKey);
|
|
@@ -214,8 +222,18 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
214
222
|
},
|
|
215
223
|
} as never);
|
|
216
224
|
|
|
217
|
-
accumulateRunUsage(
|
|
218
|
-
|
|
225
|
+
accumulateRunUsage(
|
|
226
|
+
runId,
|
|
227
|
+
{ input: 100, output: 50, cacheRead: 10, total: 150 },
|
|
228
|
+
"my-model",
|
|
229
|
+
"openai",
|
|
230
|
+
);
|
|
231
|
+
accumulateRunUsage(
|
|
232
|
+
runId,
|
|
233
|
+
{ input: 30, output: 10, cacheRead: 0, total: 40 },
|
|
234
|
+
"my-model",
|
|
235
|
+
"openai",
|
|
236
|
+
);
|
|
219
237
|
|
|
220
238
|
forwardAgentEventRaw({
|
|
221
239
|
runId,
|
|
@@ -232,7 +250,10 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
232
250
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
|
|
233
251
|
const forwarded = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data;
|
|
234
252
|
expect(forwarded.stream).toBe("lifecycle");
|
|
235
|
-
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
253
|
+
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
254
|
+
string,
|
|
255
|
+
unknown
|
|
256
|
+
>;
|
|
236
257
|
expect(sessionUsage).toBeDefined();
|
|
237
258
|
// llm_output fallback — per-run totals.
|
|
238
259
|
expect(sessionUsage.modelId).toBe("my-model");
|
|
@@ -272,7 +293,12 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
272
293
|
} as never);
|
|
273
294
|
|
|
274
295
|
// llm_output has fresher model/provider but per-run (smaller) tokens.
|
|
275
|
-
accumulateRunUsage(
|
|
296
|
+
accumulateRunUsage(
|
|
297
|
+
runId,
|
|
298
|
+
{ input: 500, output: 100, cacheRead: 200, total: 800 },
|
|
299
|
+
"llm-model",
|
|
300
|
+
"llm-provider",
|
|
301
|
+
);
|
|
276
302
|
|
|
277
303
|
forwardAgentEventRaw({
|
|
278
304
|
runId,
|
|
@@ -287,7 +313,10 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
287
313
|
|
|
288
314
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
|
|
289
315
|
const forwarded = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data;
|
|
290
|
-
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
316
|
+
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
317
|
+
string,
|
|
318
|
+
unknown
|
|
319
|
+
>;
|
|
291
320
|
expect(sessionUsage).toBeDefined();
|
|
292
321
|
// Store cumulative totals win.
|
|
293
322
|
expect((sessionUsage.tokens as Record<string, unknown>).input).toBe(5000);
|
|
@@ -344,7 +373,10 @@ describe("forwardAgentEventRaw (thinking delta rewrite)", () => {
|
|
|
344
373
|
expect(sseEmitter.broadcastToRun).toHaveBeenCalledTimes(1);
|
|
345
374
|
const forwarded = (sseEmitter.broadcastToRun as ReturnType<typeof vi.fn>).mock.calls[0][1].data;
|
|
346
375
|
expect(forwarded.stream).toBe("lifecycle");
|
|
347
|
-
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
376
|
+
const sessionUsage = (forwarded.data as Record<string, unknown>).sessionUsage as Record<
|
|
377
|
+
string,
|
|
378
|
+
unknown
|
|
379
|
+
>;
|
|
348
380
|
expect(sessionUsage).toBeDefined();
|
|
349
381
|
expect(sessionUsage.modelId).toBe("store-model");
|
|
350
382
|
expect(sessionUsage.modelProvider).toBe("store-provider");
|