@best-agent/cli 0.0.2-beta.8 → 0.0.2-beta.9
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/LICENSE +21 -0
- package/README.md +44 -181
- package/dist/launcher.js +32 -0
- package/package.json +15 -27
- package/dist/best-agent-native-bridge.d.ts +0 -389
- package/dist/best-agent-native-bridge.js +0 -4645
- package/dist/cli-storage.d.ts +0 -5
- package/dist/cli-storage.js +0 -7
- package/dist/dimsdk/ask-user.d.ts +0 -21
- package/dist/dimsdk/ask-user.js +0 -131
- package/dist/dimsdk/cli/version.d.ts +0 -6
- package/dist/dimsdk/cli/version.js +0 -36
- package/dist/dimsdk/compat/archships/dim-agent/runtime-host.d.ts +0 -49
- package/dist/dimsdk/compat/archships/dim-agent/runtime-host.js +0 -215
- package/dist/dimsdk/compat/runtime-host.d.ts +0 -2
- package/dist/dimsdk/compat/runtime-host.js +0 -2
- package/dist/dimsdk/core/debug-log.d.ts +0 -8
- package/dist/dimsdk/core/debug-log.js +0 -100
- package/dist/dimsdk/core/env.d.ts +0 -26
- package/dist/dimsdk/core/env.js +0 -130
- package/dist/dimsdk/core/markdown/sanitize.d.ts +0 -2
- package/dist/dimsdk/core/markdown/sanitize.js +0 -43
- package/dist/dimsdk/core/markdown/wrap.d.ts +0 -9
- package/dist/dimsdk/core/markdown/wrap.js +0 -139
- package/dist/dimsdk/core/markdown.d.ts +0 -18
- package/dist/dimsdk/core/markdown.js +0 -62
- package/dist/dimsdk/core/model.d.ts +0 -273
- package/dist/dimsdk/core/model.js +0 -16
- package/dist/dimsdk/core/observability/perf-log.d.ts +0 -18
- package/dist/dimsdk/core/observability/perf-log.js +0 -236
- package/dist/dimsdk/core/store/agent-event-mappers.d.ts +0 -26
- package/dist/dimsdk/core/store/agent-event-mappers.js +0 -109
- package/dist/dimsdk/core/store/message-mutations.d.ts +0 -105
- package/dist/dimsdk/core/store/message-mutations.js +0 -1528
- package/dist/dimsdk/core/store/semantic-readers.d.ts +0 -2
- package/dist/dimsdk/core/store/semantic-readers.js +0 -4
- package/dist/dimsdk/core/store/transcript-projector.d.ts +0 -164
- package/dist/dimsdk/core/store/transcript-projector.js +0 -580
- package/dist/dimsdk/core/tool-metadata.d.ts +0 -12
- package/dist/dimsdk/core/tool-metadata.js +0 -70
- package/dist/dimsdk/core/transcript-part-id.d.ts +0 -6
- package/dist/dimsdk/core/transcript-part-id.js +0 -22
- package/dist/dimsdk/i18n/index.d.ts +0 -18
- package/dist/dimsdk/i18n/index.js +0 -141
- package/dist/dimsdk/i18n/locales/en.d.ts +0 -988
- package/dist/dimsdk/i18n/locales/en.js +0 -987
- package/dist/dimsdk/i18n/locales/index.d.ts +0 -1976
- package/dist/dimsdk/i18n/locales/index.js +0 -8
- package/dist/dimsdk/i18n/locales/zh-CN.d.ts +0 -988
- package/dist/dimsdk/i18n/locales/zh-CN.js +0 -987
- package/dist/dimsdk/i18n/types.d.ts +0 -13
- package/dist/dimsdk/i18n/types.js +0 -2
- package/dist/dimsdk/shared/value-normalizers.d.ts +0 -7
- package/dist/dimsdk/shared/value-normalizers.js +0 -48
- package/dist/dimsdk-tui.bundle-size.json +0 -169
- package/dist/dimsdk-tui.bundle.js +0 -330756
- package/dist/goatchain-adapter.d.ts +0 -41
- package/dist/goatchain-adapter.js +0 -499
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -1405
- package/dist/native-runtime-bridge.d.ts +0 -387
- package/dist/native-runtime-bridge.js +0 -4624
- package/dist/provider-config-env.d.ts +0 -7
- package/dist/provider-config-env.js +0 -286
- package/dist/provider-model-limit-index.generated.d.ts +0 -3
- package/dist/provider-model-limit-index.generated.js +0 -7818
- package/dist/provider-model-limits.d.ts +0 -8
- package/dist/provider-model-limits.js +0 -43
- package/dist/runtime.d.ts +0 -47
- package/dist/runtime.js +0 -516
|
@@ -1,4645 +0,0 @@
|
|
|
1
|
-
import { extractPromptText, normalizePromptContentParts } from "@best-agent/protocol";
|
|
2
|
-
import { t } from "./dimsdk/i18n/index.js";
|
|
3
|
-
import { normalizeAskUserQuestions } from "./dimsdk/ask-user.js";
|
|
4
|
-
import { createMessageMutations } from "./dimsdk/core/store/message-mutations.js";
|
|
5
|
-
import { withPerfSpan, withPerfSpanAsync } from "./dimsdk/core/observability/perf-log.js";
|
|
6
|
-
import { createTranscriptProjector } from "./dimsdk/core/store/transcript-projector.js";
|
|
7
|
-
import { createThinkingPartIdFromReasoningItem, createThinkingPartIdFromStream, } from "./dimsdk/core/transcript-part-id.js";
|
|
8
|
-
import { canonicalToolName, toolSemantic } from "./dimsdk/core/tool-metadata.js";
|
|
9
|
-
function isLlmDebugEnabled() {
|
|
10
|
-
const raw = String(process.env.BEST_AGENT_LLM_DEBUG ?? "")
|
|
11
|
-
.trim()
|
|
12
|
-
.toLowerCase();
|
|
13
|
-
return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
|
|
14
|
-
}
|
|
15
|
-
function logLlmDebug(eventName, payload) {
|
|
16
|
-
if (!isLlmDebugEnabled())
|
|
17
|
-
return;
|
|
18
|
-
try {
|
|
19
|
-
const timestamp = new Date().toISOString();
|
|
20
|
-
const suffix = payload === undefined ? "" : ` ${JSON.stringify(payload)}`;
|
|
21
|
-
console.warn(`[best-agent][llm][${timestamp}] ${eventName}${suffix}`);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
console.warn(`[best-agent][llm] ${eventName}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function createPendingTurnState(input) {
|
|
28
|
-
return {
|
|
29
|
-
...(input.action ? { action: input.action } : {}),
|
|
30
|
-
...(input.approval ? { approval: input.approval } : {}),
|
|
31
|
-
childToolInputsByCallId: new Map(),
|
|
32
|
-
inputDeltasByCallId: new Map(),
|
|
33
|
-
promise: input.promise ?? Promise.resolve(),
|
|
34
|
-
reasoningPartIdByStreamId: new Map(),
|
|
35
|
-
reasoningSummaries: [],
|
|
36
|
-
reasoningTextByStreamId: new Map(),
|
|
37
|
-
sawReasoningTokens: false,
|
|
38
|
-
sawVisibleReasoning: false,
|
|
39
|
-
startedSubagentIds: new Set(),
|
|
40
|
-
startedToolCallIds: new Set(),
|
|
41
|
-
status: input.status,
|
|
42
|
-
streamedAgentTurnIds: new Set(),
|
|
43
|
-
streamedReasoning: false,
|
|
44
|
-
streamedChildToolCallIds: new Set(),
|
|
45
|
-
streamedToolInputCallIds: new Set(),
|
|
46
|
-
pendingSteerPrompts: [],
|
|
47
|
-
subagentDescriptionsById: new Map(),
|
|
48
|
-
...(input.turnId ? { turnId: input.turnId } : {}),
|
|
49
|
-
toolInputsByCallId: new Map(),
|
|
50
|
-
toolNamesByCallId: new Map(),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function createAsyncQueue(opts) {
|
|
54
|
-
const state = {
|
|
55
|
-
closed: false,
|
|
56
|
-
pending: [],
|
|
57
|
-
waiters: [],
|
|
58
|
-
};
|
|
59
|
-
let closedNotified = false;
|
|
60
|
-
const notifyClosed = () => {
|
|
61
|
-
if (closedNotified)
|
|
62
|
-
return;
|
|
63
|
-
closedNotified = true;
|
|
64
|
-
opts?.onClose?.();
|
|
65
|
-
};
|
|
66
|
-
return {
|
|
67
|
-
close() {
|
|
68
|
-
if (state.closed)
|
|
69
|
-
return;
|
|
70
|
-
state.closed = true;
|
|
71
|
-
while (state.waiters.length > 0)
|
|
72
|
-
state.waiters.shift()?.({ done: true, value: undefined });
|
|
73
|
-
notifyClosed();
|
|
74
|
-
},
|
|
75
|
-
push(value) {
|
|
76
|
-
if (state.closed)
|
|
77
|
-
return;
|
|
78
|
-
const waiter = state.waiters.shift();
|
|
79
|
-
if (waiter) {
|
|
80
|
-
waiter({ done: false, value });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
state.pending.push(value);
|
|
84
|
-
},
|
|
85
|
-
[Symbol.asyncIterator]() {
|
|
86
|
-
return {
|
|
87
|
-
next() {
|
|
88
|
-
if (state.pending.length > 0) {
|
|
89
|
-
return Promise.resolve({
|
|
90
|
-
done: false,
|
|
91
|
-
value: state.pending.shift(),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
if (state.closed)
|
|
95
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
state.waiters.push(resolve);
|
|
98
|
-
});
|
|
99
|
-
},
|
|
100
|
-
return() {
|
|
101
|
-
if (!state.closed) {
|
|
102
|
-
state.closed = true;
|
|
103
|
-
while (state.waiters.length > 0)
|
|
104
|
-
state.waiters.shift()?.({ done: true, value: undefined });
|
|
105
|
-
}
|
|
106
|
-
notifyClosed();
|
|
107
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function normalizeText(value) {
|
|
114
|
-
return String(value ?? "").trim();
|
|
115
|
-
}
|
|
116
|
-
function manualCompactionFailureCode(error) {
|
|
117
|
-
const raw = error && typeof error === "object"
|
|
118
|
-
? normalizeText(error.errorCode ?? error.code)
|
|
119
|
-
: "";
|
|
120
|
-
if (raw === "REQUEST_BUDGET_EXCEEDED" || raw === "PROVIDER_CONTEXT_OVERFLOW") {
|
|
121
|
-
return "compact_token_limit";
|
|
122
|
-
}
|
|
123
|
-
return raw || undefined;
|
|
124
|
-
}
|
|
125
|
-
function runtimeErrorCode(error) {
|
|
126
|
-
if (!error || typeof error !== "object")
|
|
127
|
-
return "";
|
|
128
|
-
return normalizeText(error.errorCode ?? error.code);
|
|
129
|
-
}
|
|
130
|
-
function isApprovalNotFoundError(error) {
|
|
131
|
-
if (runtimeErrorCode(error) === "APPROVAL_NOT_FOUND")
|
|
132
|
-
return true;
|
|
133
|
-
return error instanceof Error && /\bApproval approval_[^\s]+ not found\b/.test(error.message);
|
|
134
|
-
}
|
|
135
|
-
function isHiddenRuntimeReasoningSummary(summary) {
|
|
136
|
-
return (/^The .+ profile selected tool [^:]+: .+/.test(summary) ||
|
|
137
|
-
/^The orchestrator profile started the .+ workflow\.$/.test(summary) ||
|
|
138
|
-
/^The orchestrator profile started the deep-research workflow with \d+ sub-questions\.$/.test(summary) ||
|
|
139
|
-
/^Deep research completed with \d+ evidence cards and \d+ sub-question failures\.$/.test(summary) ||
|
|
140
|
-
/^Workflow phase (explore|plan|build|verify) started under the .+ profile and is running in thread .+\.$/.test(summary) ||
|
|
141
|
-
/^Workflow phase (explore|plan|build|verify) result: [\s\S]+$/.test(summary) ||
|
|
142
|
-
/^Research sub-question .+ started in thread .+\.$/.test(summary) ||
|
|
143
|
-
/^Research sub-question .+ started under the .+ profile\.$/.test(summary) ||
|
|
144
|
-
/^Research sub-question .+ result: [\s\S]+$/.test(summary) ||
|
|
145
|
-
/^Research sub-question .+ failed: [\s\S]+$/.test(summary) ||
|
|
146
|
-
/^Coordinator worker .+ started under the .+ profile(?: and is running in thread .+)?\.$/.test(summary) ||
|
|
147
|
-
/^Coordinator worker .+ result: [\s\S]+$/.test(summary) ||
|
|
148
|
-
/^Coordinator worker .+ failed: [\s\S]+$/.test(summary) ||
|
|
149
|
-
/^Workflow child phase (explore|plan|build|verify) started under the .+ profile\.$/.test(summary));
|
|
150
|
-
}
|
|
151
|
-
function isDraftReasoningItem(item) {
|
|
152
|
-
return (item.status === "draft" ||
|
|
153
|
-
(normalizeText(item.status).length === 0 &&
|
|
154
|
-
normalizeText(item.content).length === 0 &&
|
|
155
|
-
isHiddenRuntimeReasoningSummary(item.summary)));
|
|
156
|
-
}
|
|
157
|
-
function hasVisibleReasoningText(value) {
|
|
158
|
-
return normalizeText(value).length > 0;
|
|
159
|
-
}
|
|
160
|
-
function normalizeReasoningDiagnosticState(value) {
|
|
161
|
-
const normalized = normalizeText(value);
|
|
162
|
-
if (normalized === "visible" || normalized === "tokens_only" || normalized === "none") {
|
|
163
|
-
return normalized;
|
|
164
|
-
}
|
|
165
|
-
return undefined;
|
|
166
|
-
}
|
|
167
|
-
function normalizeReasoningDiagnosticsByTurn(value) {
|
|
168
|
-
const record = asRecord(value);
|
|
169
|
-
const next = {};
|
|
170
|
-
for (const [turnId, state] of Object.entries(record)) {
|
|
171
|
-
const normalizedTurnId = normalizeText(turnId);
|
|
172
|
-
const normalizedState = normalizeReasoningDiagnosticState(state);
|
|
173
|
-
if (!normalizedTurnId || !normalizedState)
|
|
174
|
-
continue;
|
|
175
|
-
next[normalizedTurnId] = normalizedState;
|
|
176
|
-
}
|
|
177
|
-
return next;
|
|
178
|
-
}
|
|
179
|
-
function buildReasoningInfoPart(state) {
|
|
180
|
-
if (state === "visible") {
|
|
181
|
-
return {
|
|
182
|
-
_reasoning: state,
|
|
183
|
-
_tone: "success",
|
|
184
|
-
text: "",
|
|
185
|
-
type: "info",
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (state === "tokens_only") {
|
|
189
|
-
return {
|
|
190
|
-
_reasoning: state,
|
|
191
|
-
_tone: "warning",
|
|
192
|
-
text: "",
|
|
193
|
-
type: "info",
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
_reasoning: state,
|
|
198
|
-
text: "",
|
|
199
|
-
type: "info",
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
function isBenignAskUserCancelInterruptError(error) {
|
|
203
|
-
if (!error || typeof error !== "object")
|
|
204
|
-
return false;
|
|
205
|
-
const record = error;
|
|
206
|
-
const name = typeof record.name === "string" ? record.name : "";
|
|
207
|
-
const message = typeof record.message === "string" ? record.message : "";
|
|
208
|
-
const code = typeof record.code === "string" ? record.code : "";
|
|
209
|
-
return (name === "TurnTerminatedError" ||
|
|
210
|
-
message === "Turn terminated" ||
|
|
211
|
-
code === "TURN_NOT_ACTIVE" ||
|
|
212
|
-
code === "TURN_MISMATCH");
|
|
213
|
-
}
|
|
214
|
-
function asRecord(value) {
|
|
215
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
216
|
-
return {};
|
|
217
|
-
return value;
|
|
218
|
-
}
|
|
219
|
-
function bridgePartSubagentId(part) {
|
|
220
|
-
const record = asRecord(part);
|
|
221
|
-
const metaId = normalizeText(record._subagentId);
|
|
222
|
-
if (metaId)
|
|
223
|
-
return metaId;
|
|
224
|
-
if (part.type === "tool_call")
|
|
225
|
-
return normalizeText(part.call.subagentId);
|
|
226
|
-
if (part.type === "tool_output")
|
|
227
|
-
return normalizeText(part.subagentId);
|
|
228
|
-
if (part.type === "tool_result")
|
|
229
|
-
return normalizeText(part.result.subagentId);
|
|
230
|
-
if (part.type === "approve")
|
|
231
|
-
return normalizeText(part.request.subagentId);
|
|
232
|
-
return "";
|
|
233
|
-
}
|
|
234
|
-
function bridgeRelatedToolCallId(part) {
|
|
235
|
-
if (part.type === "tool_call")
|
|
236
|
-
return normalizeText(part.call.id);
|
|
237
|
-
if (part.type === "tool_output")
|
|
238
|
-
return normalizeText(part.toolCallId);
|
|
239
|
-
if (part.type === "tool_result")
|
|
240
|
-
return normalizeText(part.result.toolCallId);
|
|
241
|
-
if (part.type === "approve")
|
|
242
|
-
return normalizeText(part.request.toolCallId);
|
|
243
|
-
return "";
|
|
244
|
-
}
|
|
245
|
-
function isTerminalBridgeSubagentPart(part) {
|
|
246
|
-
if (part.type !== "info")
|
|
247
|
-
return false;
|
|
248
|
-
const record = asRecord(part);
|
|
249
|
-
const kind = normalizeText(record._subagent);
|
|
250
|
-
const state = normalizeText(record._subagentState);
|
|
251
|
-
return (kind === "finished" ||
|
|
252
|
-
state === "done" ||
|
|
253
|
-
state === "completed" ||
|
|
254
|
-
state === "failed" ||
|
|
255
|
-
state === "rejected");
|
|
256
|
-
}
|
|
257
|
-
function cleanupInterruptedBridgeAssistantMessage(messages, assistantId, unfinishedThinkingPartIds) {
|
|
258
|
-
const assistantIndex = messages.findIndex((message) => message.role === "assistant" && message.id === assistantId);
|
|
259
|
-
if (assistantIndex < 0)
|
|
260
|
-
return;
|
|
261
|
-
const assistant = messages[assistantIndex];
|
|
262
|
-
const thinkingIds = new Set(unfinishedThinkingPartIds.map(normalizeText).filter(Boolean));
|
|
263
|
-
const completedToolCallIds = new Set();
|
|
264
|
-
for (const part of assistant.parts) {
|
|
265
|
-
if (part.type === "tool_result") {
|
|
266
|
-
completedToolCallIds.add(normalizeText(part.result.toolCallId));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
for (const message of messages) {
|
|
270
|
-
if (message.role === "tool")
|
|
271
|
-
completedToolCallIds.add(normalizeText(message.result.toolCallId));
|
|
272
|
-
}
|
|
273
|
-
const unfinishedToolCallIds = new Set();
|
|
274
|
-
const subagentIds = new Set();
|
|
275
|
-
const finishedSubagentIds = new Set();
|
|
276
|
-
for (const part of assistant.parts) {
|
|
277
|
-
const subagentId = bridgePartSubagentId(part);
|
|
278
|
-
if (subagentId)
|
|
279
|
-
subagentIds.add(subagentId);
|
|
280
|
-
if (subagentId && isTerminalBridgeSubagentPart(part))
|
|
281
|
-
finishedSubagentIds.add(subagentId);
|
|
282
|
-
if (part.type === "tool_call") {
|
|
283
|
-
const callId = normalizeText(part.call.id);
|
|
284
|
-
if (callId && !completedToolCallIds.has(callId))
|
|
285
|
-
unfinishedToolCallIds.add(callId);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const unfinishedSubagentIds = new Set([...subagentIds].filter((subagentId) => !finishedSubagentIds.has(subagentId)));
|
|
289
|
-
messages[assistantIndex] = {
|
|
290
|
-
...assistant,
|
|
291
|
-
parts: assistant.parts.filter((part) => {
|
|
292
|
-
if (part.type === "thinking" && thinkingIds.has(normalizeText(part.partId)))
|
|
293
|
-
return false;
|
|
294
|
-
const subagentId = bridgePartSubagentId(part);
|
|
295
|
-
if (subagentId && unfinishedSubagentIds.has(subagentId))
|
|
296
|
-
return false;
|
|
297
|
-
const callId = bridgeRelatedToolCallId(part);
|
|
298
|
-
if (callId && unfinishedToolCallIds.has(callId))
|
|
299
|
-
return false;
|
|
300
|
-
return true;
|
|
301
|
-
}),
|
|
302
|
-
};
|
|
303
|
-
if (unfinishedToolCallIds.size === 0)
|
|
304
|
-
return;
|
|
305
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
306
|
-
const message = messages[index];
|
|
307
|
-
if (message?.role === "tool" &&
|
|
308
|
-
unfinishedToolCallIds.has(normalizeText(message.result.toolCallId))) {
|
|
309
|
-
messages.splice(index, 1);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function bridgeAssistantHasMarkdownOutput(messages, assistantId) {
|
|
314
|
-
const message = messages.find((candidate) => candidate.role === "assistant" && candidate.id === assistantId);
|
|
315
|
-
return Boolean(message?.role === "assistant" &&
|
|
316
|
-
message.parts.some((part) => part.type === "markdown" && normalizeText(part.markdown)));
|
|
317
|
-
}
|
|
318
|
-
function bridgeInterruptedMarkdown(appendToExisting) {
|
|
319
|
-
const line = t("chatStatus.interruptedMarkdown");
|
|
320
|
-
return appendToExisting ? `\n\n${line}` : line;
|
|
321
|
-
}
|
|
322
|
-
function cloneJson(value) {
|
|
323
|
-
return JSON.parse(JSON.stringify(value ?? null));
|
|
324
|
-
}
|
|
325
|
-
function normalizePositiveNumber(value) {
|
|
326
|
-
const numeric = Number(value);
|
|
327
|
-
if (!Number.isFinite(numeric) || numeric <= 0)
|
|
328
|
-
return null;
|
|
329
|
-
return Math.floor(numeric);
|
|
330
|
-
}
|
|
331
|
-
function normalizeNonNegativeNumber(value) {
|
|
332
|
-
const numeric = Number(value);
|
|
333
|
-
if (!Number.isFinite(numeric) || numeric < 0)
|
|
334
|
-
return null;
|
|
335
|
-
return Math.floor(numeric);
|
|
336
|
-
}
|
|
337
|
-
function normalizeLastRequestBudget(raw) {
|
|
338
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
339
|
-
return null;
|
|
340
|
-
const estimatedInputTokens = normalizeNonNegativeNumber(raw.estimatedInputTokens);
|
|
341
|
-
const autoBudgetOutcome = normalizeText(raw.autoBudgetOutcome) ||
|
|
342
|
-
(raw.usedAutoCompaction === true ? "compacted" : "not-needed");
|
|
343
|
-
const inputBudgetTokens = normalizePositiveNumber(raw.inputBudgetTokens);
|
|
344
|
-
const contextWindowTokens = normalizePositiveNumber(raw.contextWindowTokens);
|
|
345
|
-
const maxOutputTokens = normalizeNonNegativeNumber(raw.maxOutputTokens);
|
|
346
|
-
const protocolReserveTokens = normalizeNonNegativeNumber(raw.protocolReserveTokens);
|
|
347
|
-
const reservedOutputTokens = normalizeNonNegativeNumber(raw.reservedOutputTokens);
|
|
348
|
-
const thresholdTokens = normalizePositiveNumber(raw.thresholdTokens);
|
|
349
|
-
const maxInputTokens = normalizePositiveNumber(raw.maxInputTokens);
|
|
350
|
-
const triggerRatio = Number(raw.triggerRatio);
|
|
351
|
-
const updatedAtMs = normalizePositiveNumber(raw.updatedAtMs) ?? Date.now();
|
|
352
|
-
if (estimatedInputTokens == null ||
|
|
353
|
-
(autoBudgetOutcome !== "not-needed" &&
|
|
354
|
-
autoBudgetOutcome !== "compacted" &&
|
|
355
|
-
autoBudgetOutcome !== "failed-to-fit" &&
|
|
356
|
-
autoBudgetOutcome !== "budget-unknown")) {
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
runId: normalizeText(raw.runId) || undefined,
|
|
361
|
-
requestId: normalizeText(raw.requestId) || undefined,
|
|
362
|
-
autoBudgetOutcome,
|
|
363
|
-
autoBudgetReasonCode: normalizeText(raw.autoBudgetReasonCode) || undefined,
|
|
364
|
-
budgetSource: normalizeText(raw.budgetSource) || undefined,
|
|
365
|
-
...(contextWindowTokens != null ? { contextWindowTokens } : {}),
|
|
366
|
-
estimatedInputTokens,
|
|
367
|
-
...(inputBudgetTokens != null ? { inputBudgetTokens } : {}),
|
|
368
|
-
...(maxOutputTokens != null ? { maxOutputTokens } : {}),
|
|
369
|
-
...(protocolReserveTokens != null ? { protocolReserveTokens } : {}),
|
|
370
|
-
...(reservedOutputTokens != null ? { reservedOutputTokens } : {}),
|
|
371
|
-
...(thresholdTokens != null ? { thresholdTokens } : {}),
|
|
372
|
-
...(maxInputTokens != null ? { maxInputTokens } : {}),
|
|
373
|
-
...(Number.isFinite(triggerRatio) && triggerRatio > 0 ? { triggerRatio } : {}),
|
|
374
|
-
usedAutoCompaction: raw.usedAutoCompaction === true,
|
|
375
|
-
updatedAt: normalizeText(raw.updatedAt) || undefined,
|
|
376
|
-
updatedAtMs,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
function cliModeFromThreadMode(mode) {
|
|
380
|
-
return mode === "plan" ? "plan" : "agent";
|
|
381
|
-
}
|
|
382
|
-
function threadModeFromCliMode(modeId) {
|
|
383
|
-
return modeId === "plan" ? "plan" : "default";
|
|
384
|
-
}
|
|
385
|
-
function parseToolInput(input) {
|
|
386
|
-
const trimmed = input.trim();
|
|
387
|
-
if (!trimmed)
|
|
388
|
-
return {};
|
|
389
|
-
try {
|
|
390
|
-
const parsed = JSON.parse(trimmed);
|
|
391
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
392
|
-
return parsed;
|
|
393
|
-
}
|
|
394
|
-
catch { }
|
|
395
|
-
return { _raw: trimmed };
|
|
396
|
-
}
|
|
397
|
-
function readExplicitToolCallId(input) {
|
|
398
|
-
const callId = normalizeText(input.callId);
|
|
399
|
-
return callId || undefined;
|
|
400
|
-
}
|
|
401
|
-
function resolveToolItemCallId(input) {
|
|
402
|
-
return readExplicitToolCallId(input) ?? normalizeText(input.id);
|
|
403
|
-
}
|
|
404
|
-
function usageMetadataFromSessionUsage(usage) {
|
|
405
|
-
const promptTokens = Math.max(0, Math.floor(Number(usage.inputTokens ?? 0)));
|
|
406
|
-
const completionTokens = Math.max(0, Math.floor(Number(usage.outputTokens ?? 0)));
|
|
407
|
-
const reasoningTokens = Math.max(0, Math.floor(Number(usage.reasoningTokens ?? 0)));
|
|
408
|
-
const totalTokens = Math.max(0, Math.floor(Number(usage.totalTokens ?? 0)));
|
|
409
|
-
if (!Number.isFinite(totalTokens) || totalTokens <= 0)
|
|
410
|
-
return undefined;
|
|
411
|
-
return {
|
|
412
|
-
completionTokens,
|
|
413
|
-
promptTokens,
|
|
414
|
-
reasoningTokens,
|
|
415
|
-
totalTokens,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function hasReasoningTokens(usage) {
|
|
419
|
-
const reasoningTokens = Math.max(0, Math.floor(Number(usage?.reasoningTokens ?? 0)));
|
|
420
|
-
return reasoningTokens > 0;
|
|
421
|
-
}
|
|
422
|
-
function updatePendingReasoningTokens(pending, usage) {
|
|
423
|
-
if (hasReasoningTokens(usage))
|
|
424
|
-
pending.sawReasoningTokens = true;
|
|
425
|
-
}
|
|
426
|
-
function consumePendingSteerPrompt(pending, text) {
|
|
427
|
-
const normalizedText = normalizeText(text);
|
|
428
|
-
if (!normalizedText)
|
|
429
|
-
return false;
|
|
430
|
-
const index = pending.pendingSteerPrompts.findIndex((prompt) => normalizeText(prompt) === normalizedText);
|
|
431
|
-
if (index < 0)
|
|
432
|
-
return false;
|
|
433
|
-
pending.pendingSteerPrompts.splice(index, 1);
|
|
434
|
-
return true;
|
|
435
|
-
}
|
|
436
|
-
function resolvePendingReasoningDiagnostics(pending) {
|
|
437
|
-
if (pending.sawVisibleReasoning)
|
|
438
|
-
return { state: "visible" };
|
|
439
|
-
if (pending.sawReasoningTokens)
|
|
440
|
-
return { state: "tokens_only" };
|
|
441
|
-
return { state: "none" };
|
|
442
|
-
}
|
|
443
|
-
const AUTO_COMPACTION_TRIGGER_RATIO = 0.8;
|
|
444
|
-
function resolveAutoCompactionThresholdTokens(maxInputTokens) {
|
|
445
|
-
const normalizedMaxInputTokens = Math.max(0, Math.floor(Number(maxInputTokens ?? 0)));
|
|
446
|
-
if (normalizedMaxInputTokens <= 0)
|
|
447
|
-
return 0;
|
|
448
|
-
return Math.max(1, Math.floor(normalizedMaxInputTokens * AUTO_COMPACTION_TRIGGER_RATIO));
|
|
449
|
-
}
|
|
450
|
-
function usageMetadataFromThreadUiMetadata(metadata) {
|
|
451
|
-
const usage = asRecord(metadata.usage);
|
|
452
|
-
const promptTokens = Math.max(0, Math.floor(Number(usage.promptTokens ?? usage.prompt_tokens ?? 0)));
|
|
453
|
-
const completionTokens = Math.max(0, Math.floor(Number(usage.completionTokens ?? usage.completion_tokens ?? 0)));
|
|
454
|
-
const reasoningTokens = Math.max(0, Math.floor(Number(usage.reasoningTokens ?? usage.reasoning_tokens ?? 0)));
|
|
455
|
-
const totalTokens = Math.max(0, Math.floor(Number(usage.totalTokens ?? usage.total_tokens ?? 0)));
|
|
456
|
-
if (totalTokens > 0) {
|
|
457
|
-
return {
|
|
458
|
-
completionTokens,
|
|
459
|
-
promptTokens,
|
|
460
|
-
reasoningTokens,
|
|
461
|
-
totalTokens,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
if (promptTokens > 0 || completionTokens > 0) {
|
|
465
|
-
return {
|
|
466
|
-
completionTokens,
|
|
467
|
-
promptTokens,
|
|
468
|
-
reasoningTokens,
|
|
469
|
-
totalTokens: promptTokens + completionTokens,
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
474
|
-
function requestBudgetMetadataFromThreadUiMetadata(metadata) {
|
|
475
|
-
const budget = asRecord(metadata.lastRequestBudget);
|
|
476
|
-
const maxInputTokens = Math.max(0, Math.floor(Number(budget.inputBudgetTokens ?? budget.maxInputTokens ?? 0)));
|
|
477
|
-
return (normalizeLastRequestBudget({
|
|
478
|
-
autoBudgetOutcome: normalizeText(budget.autoBudgetOutcome) ||
|
|
479
|
-
(budget.usedAutoCompaction === true ? "compacted" : "not-needed"),
|
|
480
|
-
autoBudgetReasonCode: normalizeText(budget.autoBudgetReasonCode) || undefined,
|
|
481
|
-
budgetSource: normalizeText(budget.budgetSource) || undefined,
|
|
482
|
-
contextWindowTokens: budget.contextWindowTokens,
|
|
483
|
-
estimatedInputTokens: budget.estimatedInputTokens,
|
|
484
|
-
inputBudgetTokens: budget.inputBudgetTokens ?? maxInputTokens,
|
|
485
|
-
maxInputTokens,
|
|
486
|
-
maxOutputTokens: budget.maxOutputTokens,
|
|
487
|
-
protocolReserveTokens: budget.protocolReserveTokens,
|
|
488
|
-
reservedOutputTokens: budget.reservedOutputTokens,
|
|
489
|
-
thresholdTokens: budget.thresholdTokens ?? resolveAutoCompactionThresholdTokens(maxInputTokens),
|
|
490
|
-
triggerRatio: budget.triggerRatio ?? AUTO_COMPACTION_TRIGGER_RATIO,
|
|
491
|
-
updatedAt: normalizeText(budget.updatedAt) || undefined,
|
|
492
|
-
updatedAtMs: budget.updatedAtMs ?? Date.now(),
|
|
493
|
-
usedAutoCompaction: budget.usedAutoCompaction === true,
|
|
494
|
-
runId: normalizeText(budget.runId) || undefined,
|
|
495
|
-
requestId: normalizeText(budget.requestId) || undefined,
|
|
496
|
-
}) ?? undefined);
|
|
497
|
-
}
|
|
498
|
-
function restoreThreadUiMetricsIntoStore(store, thread) {
|
|
499
|
-
if (!store)
|
|
500
|
-
return;
|
|
501
|
-
const metadata = sessionUiMetadata(thread);
|
|
502
|
-
const usage = usageMetadataFromThreadUiMetadata(metadata);
|
|
503
|
-
const budget = requestBudgetMetadataFromThreadUiMetadata(metadata);
|
|
504
|
-
if (budget) {
|
|
505
|
-
if (typeof store.applyLastRequestBudget === "function")
|
|
506
|
-
store.applyLastRequestBudget(budget);
|
|
507
|
-
else if ("lastRequestBudget" in store.state)
|
|
508
|
-
store.state.lastRequestBudget = budget;
|
|
509
|
-
}
|
|
510
|
-
if (usage) {
|
|
511
|
-
if (typeof store.applyUsageFromSession === "function") {
|
|
512
|
-
store.applyUsageFromSession(usage);
|
|
513
|
-
}
|
|
514
|
-
else if (store.state.usage) {
|
|
515
|
-
store.state.usage.prompt_tokens = usage.promptTokens;
|
|
516
|
-
store.state.usage.completion_tokens = usage.completionTokens;
|
|
517
|
-
store.state.usage.total_tokens = usage.totalTokens;
|
|
518
|
-
}
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (!budget || !store.state.usage)
|
|
522
|
-
return;
|
|
523
|
-
const fallbackPromptTokens = Math.max(0, Math.floor(Number(budget.estimatedInputTokens ?? 0)));
|
|
524
|
-
if (fallbackPromptTokens <= 0)
|
|
525
|
-
return;
|
|
526
|
-
if (typeof store.applyUsageFromSession === "function") {
|
|
527
|
-
store.applyUsageFromSession({
|
|
528
|
-
completionTokens: 0,
|
|
529
|
-
promptTokens: fallbackPromptTokens,
|
|
530
|
-
totalTokens: fallbackPromptTokens,
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
store.state.usage.prompt_tokens = fallbackPromptTokens;
|
|
535
|
-
store.state.usage.completion_tokens = 0;
|
|
536
|
-
store.state.usage.total_tokens = fallbackPromptTokens;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
function requestBudgetMetadataFromCompactedEvent(event) {
|
|
540
|
-
if (event.lastRequestBudget)
|
|
541
|
-
return normalizeLastRequestBudget(event.lastRequestBudget) ?? undefined;
|
|
542
|
-
const maxInputTokens = Math.max(0, Math.floor(Number(event.diagnostics.compaction.lastBudgetTokens ?? 0)));
|
|
543
|
-
const thresholdTokens = resolveAutoCompactionThresholdTokens(maxInputTokens);
|
|
544
|
-
const estimatedInputTokens = Math.max(0, Math.floor(Number(event.estimatedTokensAfter ?? event.diagnostics.compaction.lastEstimatedTokens ?? 0)));
|
|
545
|
-
if (!maxInputTokens || estimatedInputTokens == null)
|
|
546
|
-
return undefined;
|
|
547
|
-
return (normalizeLastRequestBudget({
|
|
548
|
-
autoBudgetOutcome: event.reason === "manual-user" ? "not-needed" : "compacted",
|
|
549
|
-
estimatedInputTokens,
|
|
550
|
-
inputBudgetTokens: maxInputTokens,
|
|
551
|
-
maxInputTokens,
|
|
552
|
-
thresholdTokens,
|
|
553
|
-
triggerRatio: AUTO_COMPACTION_TRIGGER_RATIO,
|
|
554
|
-
updatedAtMs: Date.now(),
|
|
555
|
-
usedAutoCompaction: event.reason !== "manual-user",
|
|
556
|
-
}) ?? undefined);
|
|
557
|
-
}
|
|
558
|
-
function syncStoreUsageMetrics(store, event) {
|
|
559
|
-
if (!store || store.state.sessionId !== event.threadId)
|
|
560
|
-
return;
|
|
561
|
-
const usage = usageMetadataFromSessionUsage(event.inclusive);
|
|
562
|
-
if (usage)
|
|
563
|
-
store.applyUsageFromSession?.(usage);
|
|
564
|
-
if (event.lastRequestBudget)
|
|
565
|
-
store.applyLastRequestBudget?.(event.lastRequestBudget);
|
|
566
|
-
}
|
|
567
|
-
function syncStoreBudgetMetrics(store, event) {
|
|
568
|
-
if (!store || store.state.sessionId !== event.threadId)
|
|
569
|
-
return;
|
|
570
|
-
const budget = requestBudgetMetadataFromCompactedEvent(event);
|
|
571
|
-
if (budget)
|
|
572
|
-
store.applyLastRequestBudget?.(budget);
|
|
573
|
-
}
|
|
574
|
-
function relationScopeLabel(relation) {
|
|
575
|
-
if (relation.kind === "workflow-phase")
|
|
576
|
-
return `${relation.workflowName}/${relation.phaseName}`;
|
|
577
|
-
if (relation.kind === "coordinator-worker")
|
|
578
|
-
return `${relation.workflowName}/${relation.workerId}`;
|
|
579
|
-
return `research/${relation.subquestionId}`;
|
|
580
|
-
}
|
|
581
|
-
function childScopeLabel(event) {
|
|
582
|
-
return relationScopeLabel(event.relation);
|
|
583
|
-
}
|
|
584
|
-
function parseIsoTimeMs(value) {
|
|
585
|
-
const text = normalizeText(value);
|
|
586
|
-
if (!text)
|
|
587
|
-
return undefined;
|
|
588
|
-
const ms = Date.parse(text);
|
|
589
|
-
return Number.isFinite(ms) ? ms : undefined;
|
|
590
|
-
}
|
|
591
|
-
function previewTraceText(value, max = 96) {
|
|
592
|
-
const firstLine = String(value ?? "")
|
|
593
|
-
.trim()
|
|
594
|
-
.split(/\r?\n/u)[0];
|
|
595
|
-
const compact = firstLine?.replace(/\s+/gu, " ").trim() ?? "";
|
|
596
|
-
if (!compact)
|
|
597
|
-
return undefined;
|
|
598
|
-
return compact.length > max ? `${compact.slice(0, max - 1)}…` : compact;
|
|
599
|
-
}
|
|
600
|
-
function sessionTraceEventTurnId(event) {
|
|
601
|
-
switch (event.type) {
|
|
602
|
-
case "turn.started":
|
|
603
|
-
case "turn.completed":
|
|
604
|
-
case "turn.interrupted":
|
|
605
|
-
case "turn.blocked":
|
|
606
|
-
case "turn.failed":
|
|
607
|
-
case "turn.reconciled":
|
|
608
|
-
return normalizeText(event.turn.id) || undefined;
|
|
609
|
-
case "run.started":
|
|
610
|
-
case "run.updated":
|
|
611
|
-
case "run.completed":
|
|
612
|
-
case "run.cancelled":
|
|
613
|
-
case "run.failed":
|
|
614
|
-
return normalizeText(event.run.turnId) || undefined;
|
|
615
|
-
case "step.started":
|
|
616
|
-
case "step.updated":
|
|
617
|
-
case "step.completed":
|
|
618
|
-
return normalizeText(event.step.turnId) || undefined;
|
|
619
|
-
case "phase.started":
|
|
620
|
-
case "phase.completed":
|
|
621
|
-
case "phase.reopened":
|
|
622
|
-
case "phase.blocked":
|
|
623
|
-
case "phase.failed":
|
|
624
|
-
case "turn.item.added":
|
|
625
|
-
case "turn.steered":
|
|
626
|
-
case "action.required":
|
|
627
|
-
case "thread.transcript.delta":
|
|
628
|
-
case "turn.text.delta":
|
|
629
|
-
case "turn.text.stream.reset":
|
|
630
|
-
case "turn.reasoning.started":
|
|
631
|
-
case "turn.reasoning.delta":
|
|
632
|
-
case "turn.reasoning.completed":
|
|
633
|
-
case "turn.tool-call.started":
|
|
634
|
-
case "turn.tool-call.delta":
|
|
635
|
-
case "turn.tool-output.delta":
|
|
636
|
-
case "workflow.phase.started":
|
|
637
|
-
case "workflow.phase.completed":
|
|
638
|
-
case "workflow.phase.failed":
|
|
639
|
-
case "child-task.started":
|
|
640
|
-
case "child-task.completed":
|
|
641
|
-
case "child-task.failed":
|
|
642
|
-
case "child-task.joined":
|
|
643
|
-
case "child-task.rejected":
|
|
644
|
-
case "coordinator.plan":
|
|
645
|
-
case "coordinator.synthesize":
|
|
646
|
-
case "coordinator.complete":
|
|
647
|
-
case "workspace.file.updated":
|
|
648
|
-
case "workspace.file.invalidated":
|
|
649
|
-
case "workspace.file.stale-write-rejected":
|
|
650
|
-
case "research.started":
|
|
651
|
-
case "research.subquestion.started":
|
|
652
|
-
case "research.subquestion.completed":
|
|
653
|
-
case "research.subquestion.failed":
|
|
654
|
-
case "research.evidence.added":
|
|
655
|
-
case "evidence.added":
|
|
656
|
-
case "research.synthesis.started":
|
|
657
|
-
case "research.audit.completed":
|
|
658
|
-
case "research.completed":
|
|
659
|
-
case "research.failed":
|
|
660
|
-
case "turn.queued":
|
|
661
|
-
case "turn.dequeued":
|
|
662
|
-
case "thread.forked":
|
|
663
|
-
case "thread.compacted":
|
|
664
|
-
case "thread.compaction.started":
|
|
665
|
-
case "thread.usage.updated":
|
|
666
|
-
case "routing.selected":
|
|
667
|
-
case "plan.started":
|
|
668
|
-
case "plan.updated":
|
|
669
|
-
case "plan.todo.updated":
|
|
670
|
-
case "plan.review_ready":
|
|
671
|
-
case "plan.approval.requested":
|
|
672
|
-
return normalizeText(event.turnId) || undefined;
|
|
673
|
-
case "approval.requested":
|
|
674
|
-
return normalizeText(event.approval.turnId) || undefined;
|
|
675
|
-
case "child-thread.item.added":
|
|
676
|
-
return normalizeText(event.parentTurnId) || undefined;
|
|
677
|
-
case "turn.queue.drained":
|
|
678
|
-
case "thread.archived":
|
|
679
|
-
case "thread.unarchived":
|
|
680
|
-
case "thread.purged":
|
|
681
|
-
case "plan.approved":
|
|
682
|
-
case "plan.rejected":
|
|
683
|
-
case "plan.handoff.ready":
|
|
684
|
-
return undefined;
|
|
685
|
-
default:
|
|
686
|
-
return undefined;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
function sessionTraceEventSummary(event) {
|
|
690
|
-
switch (event.type) {
|
|
691
|
-
case "turn.started":
|
|
692
|
-
return "turn started";
|
|
693
|
-
case "turn.completed":
|
|
694
|
-
case "turn.interrupted":
|
|
695
|
-
case "turn.blocked":
|
|
696
|
-
case "turn.failed":
|
|
697
|
-
return `turn ${event.turn.status}`;
|
|
698
|
-
case "run.started":
|
|
699
|
-
return `run started${event.run.phaseName ? ` · ${event.run.phaseName}` : ""}`;
|
|
700
|
-
case "run.updated":
|
|
701
|
-
return `run updated${event.run.phaseName ? ` · ${event.run.phaseName}` : ""}`;
|
|
702
|
-
case "run.completed":
|
|
703
|
-
case "run.cancelled":
|
|
704
|
-
case "run.failed":
|
|
705
|
-
return `run ${event.run.status}`;
|
|
706
|
-
case "step.started":
|
|
707
|
-
case "step.updated":
|
|
708
|
-
case "step.completed":
|
|
709
|
-
return `${event.step.kind} ${event.type.split(".").at(-1)}`;
|
|
710
|
-
case "phase.started":
|
|
711
|
-
case "phase.completed":
|
|
712
|
-
case "phase.reopened":
|
|
713
|
-
case "phase.blocked":
|
|
714
|
-
case "phase.failed":
|
|
715
|
-
return `${event.phaseName} ${event.type.split(".").at(-1)}`;
|
|
716
|
-
case "turn.item.added":
|
|
717
|
-
switch (event.item.kind) {
|
|
718
|
-
case "message":
|
|
719
|
-
return `${event.item.role === "user" ? "user" : "assistant"} message${previewTraceText(event.item.text) ? ` · ${previewTraceText(event.item.text)}` : ""}`;
|
|
720
|
-
case "tool-call":
|
|
721
|
-
return `tool call · ${event.item.toolName}`;
|
|
722
|
-
case "tool-result":
|
|
723
|
-
return `tool result · ${event.item.toolName}`;
|
|
724
|
-
case "reasoning":
|
|
725
|
-
return `reasoning${previewTraceText(event.item.summary) ? ` · ${previewTraceText(event.item.summary)}` : ""}`;
|
|
726
|
-
case "error":
|
|
727
|
-
return `error${previewTraceText(event.item.message) ? ` · ${previewTraceText(event.item.message)}` : ""}`;
|
|
728
|
-
case "control":
|
|
729
|
-
return `control · ${previewTraceText(event.item.summary) ?? event.item.action}`;
|
|
730
|
-
}
|
|
731
|
-
case "approval.requested":
|
|
732
|
-
return `approval requested · ${event.approval.group}/${event.approval.action}`;
|
|
733
|
-
case "action.required":
|
|
734
|
-
return `ask user · ${event.toolName}`;
|
|
735
|
-
case "thread.compaction.started":
|
|
736
|
-
return `compaction started · ${event.reason}`;
|
|
737
|
-
case "thread.compacted": {
|
|
738
|
-
const before = Number(event.estimatedTokensBefore ?? 0);
|
|
739
|
-
const after = Number(event.estimatedTokensAfter ?? 0);
|
|
740
|
-
return before > 0 && after > 0
|
|
741
|
-
? `compacted · ${before} -> ${after}`
|
|
742
|
-
: `compacted · ${event.reason}`;
|
|
743
|
-
}
|
|
744
|
-
case "thread.usage.updated": {
|
|
745
|
-
const budget = event.lastRequestBudget;
|
|
746
|
-
const inputBudgetTokens = Math.floor(Number(budget?.inputBudgetTokens ?? budget?.maxInputTokens ?? 0));
|
|
747
|
-
return budget
|
|
748
|
-
? `usage updated · ${Math.floor(budget.estimatedInputTokens)}/${inputBudgetTokens || 0}`
|
|
749
|
-
: "usage updated";
|
|
750
|
-
}
|
|
751
|
-
case "turn.text.delta":
|
|
752
|
-
return "assistant delta";
|
|
753
|
-
case "turn.text.stream.reset":
|
|
754
|
-
return "assistant stream reset";
|
|
755
|
-
case "turn.reasoning.started":
|
|
756
|
-
return "reasoning started";
|
|
757
|
-
case "turn.reasoning.delta":
|
|
758
|
-
return "reasoning delta";
|
|
759
|
-
case "turn.reasoning.completed":
|
|
760
|
-
return "reasoning completed";
|
|
761
|
-
case "turn.tool-call.started":
|
|
762
|
-
return `tool started · ${event.toolName}`;
|
|
763
|
-
case "turn.tool-call.delta":
|
|
764
|
-
return `tool delta · ${event.toolName}`;
|
|
765
|
-
case "turn.tool-output.delta":
|
|
766
|
-
return `tool output · ${event.toolName}`;
|
|
767
|
-
case "child-thread.item.added":
|
|
768
|
-
return `child ${childScopeLabel(event)} · ${event.item.kind}`;
|
|
769
|
-
case "child-task.started":
|
|
770
|
-
case "child-task.completed":
|
|
771
|
-
case "child-task.failed":
|
|
772
|
-
case "child-task.joined":
|
|
773
|
-
case "child-task.rejected":
|
|
774
|
-
return `${event.type.replace("child-task.", "child task ")} · ${previewTraceText(event.record.objective, 72) ?? event.record.profileName}`;
|
|
775
|
-
case "workflow.phase.started":
|
|
776
|
-
case "workflow.phase.completed":
|
|
777
|
-
return `${event.workflowName}/${event.phaseName} ${event.type.split(".").at(-1)}`;
|
|
778
|
-
case "workflow.phase.failed":
|
|
779
|
-
return `${event.workflowName}/${event.phaseName} failed · ${previewTraceText(event.message, 72) ?? event.failureKind}`;
|
|
780
|
-
case "research.started":
|
|
781
|
-
return `research started · ${previewTraceText(event.run.objective, 72) ?? event.run.runId}`;
|
|
782
|
-
case "research.completed":
|
|
783
|
-
return `research completed · ${previewTraceText(event.report.executiveSummary, 72) ?? event.runId}`;
|
|
784
|
-
case "research.failed":
|
|
785
|
-
return `research failed · ${previewTraceText(event.message, 72) ?? event.runId}`;
|
|
786
|
-
case "routing.selected":
|
|
787
|
-
return `routing selected · ${event.record.routingRole}/${event.record.model}`;
|
|
788
|
-
default:
|
|
789
|
-
return event.type;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function createSessionTraceBucket(id, kind, sequence, turnId) {
|
|
793
|
-
return {
|
|
794
|
-
id,
|
|
795
|
-
kind,
|
|
796
|
-
turnId,
|
|
797
|
-
status: kind === "session" ? "session" : "unknown",
|
|
798
|
-
userMessageCount: 0,
|
|
799
|
-
assistantMessageCount: 0,
|
|
800
|
-
compactionCount: 0,
|
|
801
|
-
events: [],
|
|
802
|
-
firstSeenSequence: sequence,
|
|
803
|
-
runIds: new Set(),
|
|
804
|
-
toolCallIds: new Set(),
|
|
805
|
-
childTaskIds: new Set(),
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
function collectSessionTraceItem(bucket, item) {
|
|
809
|
-
const createdAtMs = parseIsoTimeMs(item.createdAt);
|
|
810
|
-
if (createdAtMs != null) {
|
|
811
|
-
bucket.startedAt =
|
|
812
|
-
bucket.startedAt == null ? createdAtMs : Math.min(bucket.startedAt, createdAtMs);
|
|
813
|
-
bucket.firstSeenAt =
|
|
814
|
-
bucket.firstSeenAt == null ? createdAtMs : Math.min(bucket.firstSeenAt, createdAtMs);
|
|
815
|
-
}
|
|
816
|
-
if (item.kind === "message") {
|
|
817
|
-
if (item.role === "user") {
|
|
818
|
-
bucket.userMessageCount += 1;
|
|
819
|
-
bucket.promptPreview ??= previewTraceText(item.text);
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
bucket.assistantMessageCount += 1;
|
|
823
|
-
bucket.assistantPreview ??= previewTraceText(item.text);
|
|
824
|
-
}
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
if (item.kind === "tool-call") {
|
|
828
|
-
bucket.toolCallIds.add(normalizeText(item.callId) || normalizeText(item.id));
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
async function loadThreadTraceItems(runtime, threadId) {
|
|
832
|
-
const pages = [];
|
|
833
|
-
let beforeItemId;
|
|
834
|
-
while (true) {
|
|
835
|
-
const response = await runtime.getThreadHistory({
|
|
836
|
-
threadId,
|
|
837
|
-
...(beforeItemId ? { beforeItemId: beforeItemId } : {}),
|
|
838
|
-
limit: 500,
|
|
839
|
-
});
|
|
840
|
-
pages.push([...response.items]);
|
|
841
|
-
if (!response.hasMore || !response.nextBeforeItemId)
|
|
842
|
-
break;
|
|
843
|
-
beforeItemId = response.nextBeforeItemId;
|
|
844
|
-
}
|
|
845
|
-
return pages.reverse().flat();
|
|
846
|
-
}
|
|
847
|
-
async function loadThreadTraceEvents(runtime, threadId) {
|
|
848
|
-
const events = [];
|
|
849
|
-
let afterSequence;
|
|
850
|
-
while (true) {
|
|
851
|
-
const response = await runtime.getThreadEvents({
|
|
852
|
-
threadId,
|
|
853
|
-
...(afterSequence != null ? { afterSequence } : {}),
|
|
854
|
-
limit: 500,
|
|
855
|
-
});
|
|
856
|
-
events.push(...response.events);
|
|
857
|
-
if (!response.hasMore || response.nextAfterSequence == null)
|
|
858
|
-
break;
|
|
859
|
-
afterSequence = response.nextAfterSequence;
|
|
860
|
-
}
|
|
861
|
-
return events;
|
|
862
|
-
}
|
|
863
|
-
function buildSessionTrace(threadId, items, envelopes, processRecords = []) {
|
|
864
|
-
const sessionBucketId = "__session__";
|
|
865
|
-
const buckets = new Map();
|
|
866
|
-
const ensureBucket = (bucketId, kind, sequence, turnId) => {
|
|
867
|
-
const existing = buckets.get(bucketId);
|
|
868
|
-
if (existing)
|
|
869
|
-
return existing;
|
|
870
|
-
const bucket = createSessionTraceBucket(bucketId, kind, sequence, turnId);
|
|
871
|
-
buckets.set(bucketId, bucket);
|
|
872
|
-
return bucket;
|
|
873
|
-
};
|
|
874
|
-
for (const item of items) {
|
|
875
|
-
const turnId = normalizeText(item.turnId);
|
|
876
|
-
if (!turnId)
|
|
877
|
-
continue;
|
|
878
|
-
collectSessionTraceItem(ensureBucket(turnId, "turn", Number.MAX_SAFE_INTEGER, turnId), item);
|
|
879
|
-
}
|
|
880
|
-
for (const envelope of envelopes) {
|
|
881
|
-
const turnId = sessionTraceEventTurnId(envelope.event);
|
|
882
|
-
const bucket = ensureBucket(turnId || sessionBucketId, turnId ? "turn" : "session", envelope.sequence, turnId || undefined);
|
|
883
|
-
const at = parseIsoTimeMs(envelope.createdAt) ?? 0;
|
|
884
|
-
if (at > 0) {
|
|
885
|
-
bucket.firstSeenAt = bucket.firstSeenAt == null ? at : Math.min(bucket.firstSeenAt, at);
|
|
886
|
-
}
|
|
887
|
-
bucket.events.push({
|
|
888
|
-
at,
|
|
889
|
-
sequence: envelope.sequence,
|
|
890
|
-
summary: sessionTraceEventSummary(envelope.event),
|
|
891
|
-
type: envelope.event.type,
|
|
892
|
-
});
|
|
893
|
-
switch (envelope.event.type) {
|
|
894
|
-
case "turn.started":
|
|
895
|
-
bucket.status = envelope.event.turn.status;
|
|
896
|
-
bucket.startedAt ??= parseIsoTimeMs(envelope.event.turn.createdAt) ?? at;
|
|
897
|
-
break;
|
|
898
|
-
case "turn.completed":
|
|
899
|
-
case "turn.interrupted":
|
|
900
|
-
case "turn.blocked":
|
|
901
|
-
case "turn.failed":
|
|
902
|
-
bucket.status = envelope.event.turn.status;
|
|
903
|
-
bucket.completedAt =
|
|
904
|
-
parseIsoTimeMs(envelope.event.turn.completedAt) ?? bucket.completedAt ?? at;
|
|
905
|
-
bucket.startedAt ??= parseIsoTimeMs(envelope.event.turn.createdAt) ?? at;
|
|
906
|
-
break;
|
|
907
|
-
case "run.started":
|
|
908
|
-
case "run.updated":
|
|
909
|
-
case "run.completed":
|
|
910
|
-
case "run.cancelled":
|
|
911
|
-
case "run.failed":
|
|
912
|
-
bucket.runIds.add(normalizeText(envelope.event.run.id));
|
|
913
|
-
bucket.startedAt ??= parseIsoTimeMs(envelope.event.run.startedAt) ?? at;
|
|
914
|
-
break;
|
|
915
|
-
case "turn.item.added":
|
|
916
|
-
collectSessionTraceItem(bucket, envelope.event.item);
|
|
917
|
-
break;
|
|
918
|
-
case "turn.tool-call.started":
|
|
919
|
-
case "turn.tool-call.delta":
|
|
920
|
-
case "turn.tool-output.delta":
|
|
921
|
-
bucket.toolCallIds.add(normalizeText(envelope.event.callId));
|
|
922
|
-
break;
|
|
923
|
-
case "child-thread.item.added":
|
|
924
|
-
bucket.childTaskIds.add(normalizeText(envelope.event.childThreadId) ||
|
|
925
|
-
normalizeText(envelope.event.relation.correlationId));
|
|
926
|
-
break;
|
|
927
|
-
case "child-task.started":
|
|
928
|
-
case "child-task.completed":
|
|
929
|
-
case "child-task.failed":
|
|
930
|
-
case "child-task.joined":
|
|
931
|
-
case "child-task.rejected":
|
|
932
|
-
bucket.childTaskIds.add(normalizeText(envelope.event.record.childTaskId) ||
|
|
933
|
-
normalizeText(envelope.event.record.childThreadId) ||
|
|
934
|
-
normalizeText(envelope.event.record.correlationId));
|
|
935
|
-
break;
|
|
936
|
-
case "thread.compacted":
|
|
937
|
-
bucket.compactionCount += 1;
|
|
938
|
-
break;
|
|
939
|
-
default:
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
const requests = [...buckets.values()]
|
|
944
|
-
.filter((bucket) => (bucket.kind === "session" ? bucket.events.length > 0 : true))
|
|
945
|
-
.sort((left, right) => {
|
|
946
|
-
const leftAt = left.startedAt ?? left.firstSeenAt ?? Number.MAX_SAFE_INTEGER;
|
|
947
|
-
const rightAt = right.startedAt ?? right.firstSeenAt ?? Number.MAX_SAFE_INTEGER;
|
|
948
|
-
if (leftAt !== rightAt)
|
|
949
|
-
return leftAt - rightAt;
|
|
950
|
-
return left.firstSeenSequence - right.firstSeenSequence;
|
|
951
|
-
})
|
|
952
|
-
.map((bucket, index) => ({
|
|
953
|
-
assistantMessageCount: bucket.assistantMessageCount,
|
|
954
|
-
assistantPreview: bucket.assistantPreview,
|
|
955
|
-
childTaskCount: bucket.childTaskIds.size,
|
|
956
|
-
compactionCount: bucket.compactionCount,
|
|
957
|
-
completedAt: bucket.completedAt,
|
|
958
|
-
eventCount: bucket.events.length,
|
|
959
|
-
events: bucket.events.map((event) => ({
|
|
960
|
-
at: event.at,
|
|
961
|
-
sequence: event.sequence,
|
|
962
|
-
summary: event.summary,
|
|
963
|
-
type: event.type,
|
|
964
|
-
})),
|
|
965
|
-
id: bucket.id,
|
|
966
|
-
index: index + 1,
|
|
967
|
-
kind: bucket.kind,
|
|
968
|
-
promptPreview: bucket.promptPreview,
|
|
969
|
-
runCount: bucket.runIds.size,
|
|
970
|
-
startedAt: bucket.startedAt,
|
|
971
|
-
status: bucket.status,
|
|
972
|
-
toolCallCount: bucket.toolCallIds.size,
|
|
973
|
-
turnId: bucket.turnId,
|
|
974
|
-
userMessageCount: bucket.userMessageCount,
|
|
975
|
-
}));
|
|
976
|
-
return {
|
|
977
|
-
eventCount: envelopes.length,
|
|
978
|
-
generatedAt: Date.now(),
|
|
979
|
-
processes: processRecords.map(toSessionTraceProcess),
|
|
980
|
-
requests,
|
|
981
|
-
threadId,
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
function toSessionTraceProcess(record) {
|
|
985
|
-
return {
|
|
986
|
-
command: record.command,
|
|
987
|
-
...(record.completedAt ? { completedAt: record.completedAt } : {}),
|
|
988
|
-
cwd: record.cwd,
|
|
989
|
-
descendantPids: [...record.descendantPids],
|
|
990
|
-
...(record.evictedAt ? { evictedAt: record.evictedAt } : {}),
|
|
991
|
-
exitCode: record.exitCode,
|
|
992
|
-
...(record.owner
|
|
993
|
-
? {
|
|
994
|
-
owner: {
|
|
995
|
-
threadId: record.owner.threadId,
|
|
996
|
-
...(record.owner.toolCallId ? { toolCallId: record.owner.toolCallId } : {}),
|
|
997
|
-
turnId: record.owner.turnId,
|
|
998
|
-
},
|
|
999
|
-
}
|
|
1000
|
-
: {}),
|
|
1001
|
-
...(record.pid != null ? { pid: record.pid } : {}),
|
|
1002
|
-
sessionId: record.sessionId,
|
|
1003
|
-
...(record.signalCode !== undefined ? { signalCode: record.signalCode } : {}),
|
|
1004
|
-
...(record.sigkillSentAt ? { sigkillSentAt: record.sigkillSentAt } : {}),
|
|
1005
|
-
...(record.sigtermSentAt ? { sigtermSentAt: record.sigtermSentAt } : {}),
|
|
1006
|
-
startedAt: record.startedAt,
|
|
1007
|
-
status: record.status,
|
|
1008
|
-
...(record.stopMethod ? { stopMethod: record.stopMethod } : {}),
|
|
1009
|
-
...(record.stopRequestedAt ? { stopRequestedAt: record.stopRequestedAt } : {}),
|
|
1010
|
-
...(record.stopVerifiedAt ? { stopVerifiedAt: record.stopVerifiedAt } : {}),
|
|
1011
|
-
...(record.survivingPids ? { survivingPids: [...record.survivingPids] } : {}),
|
|
1012
|
-
updatedAt: record.updatedAt,
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
function subagentLineForItem(item) {
|
|
1016
|
-
const at = parseIsoTimeMs(item.createdAt);
|
|
1017
|
-
switch (item.kind) {
|
|
1018
|
-
case "message":
|
|
1019
|
-
return {
|
|
1020
|
-
...(at != null ? { at } : {}),
|
|
1021
|
-
id: item.id,
|
|
1022
|
-
kind: "message",
|
|
1023
|
-
label: item.role === "user" ? "user" : "assistant",
|
|
1024
|
-
text: item.text,
|
|
1025
|
-
};
|
|
1026
|
-
case "reasoning":
|
|
1027
|
-
return {
|
|
1028
|
-
...(at != null ? { at } : {}),
|
|
1029
|
-
id: item.id,
|
|
1030
|
-
kind: "reasoning",
|
|
1031
|
-
label: "thinking",
|
|
1032
|
-
text: item.content || item.summary,
|
|
1033
|
-
};
|
|
1034
|
-
case "tool-call":
|
|
1035
|
-
return {
|
|
1036
|
-
...(at != null ? { at } : {}),
|
|
1037
|
-
id: item.id,
|
|
1038
|
-
kind: "tool-call",
|
|
1039
|
-
label: `tool ${canonicalToolName(item.toolName)}`,
|
|
1040
|
-
text: item.input,
|
|
1041
|
-
};
|
|
1042
|
-
case "tool-result":
|
|
1043
|
-
return {
|
|
1044
|
-
...(at != null ? { at } : {}),
|
|
1045
|
-
id: item.id,
|
|
1046
|
-
kind: "tool-result",
|
|
1047
|
-
label: `${item.outcome} ${canonicalToolName(item.toolName)}`,
|
|
1048
|
-
text: item.output,
|
|
1049
|
-
};
|
|
1050
|
-
case "error":
|
|
1051
|
-
return {
|
|
1052
|
-
...(at != null ? { at } : {}),
|
|
1053
|
-
id: item.id,
|
|
1054
|
-
kind: "error",
|
|
1055
|
-
label: "error",
|
|
1056
|
-
text: item.message,
|
|
1057
|
-
};
|
|
1058
|
-
case "control":
|
|
1059
|
-
return {
|
|
1060
|
-
...(at != null ? { at } : {}),
|
|
1061
|
-
id: item.id,
|
|
1062
|
-
kind: "control",
|
|
1063
|
-
label: item.action,
|
|
1064
|
-
text: item.prompt ? `${item.summary}\n${item.prompt}` : item.summary,
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
function findPendingSubagentBlock(items) {
|
|
1069
|
-
const pending = new Map();
|
|
1070
|
-
for (const item of items) {
|
|
1071
|
-
if (item.kind !== "control" || !item.requestId)
|
|
1072
|
-
continue;
|
|
1073
|
-
const requestId = String(item.requestId);
|
|
1074
|
-
if (item.action === "approval-requested" || item.action === "action-requested") {
|
|
1075
|
-
pending.set(requestId, item);
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
if (item.action === "approval-resolved" || item.action === "action-resolved") {
|
|
1079
|
-
pending.delete(requestId);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return [...pending.values()].at(-1);
|
|
1083
|
-
}
|
|
1084
|
-
function findPendingSubagentSteers(items) {
|
|
1085
|
-
const consumed = new Map();
|
|
1086
|
-
for (const item of items) {
|
|
1087
|
-
if (item.kind !== "message" || item.role !== "user")
|
|
1088
|
-
continue;
|
|
1089
|
-
const text = normalizeText(item.text);
|
|
1090
|
-
if (!text)
|
|
1091
|
-
continue;
|
|
1092
|
-
const key = `${item.turnId}\n${text}`;
|
|
1093
|
-
consumed.set(key, (consumed.get(key) ?? 0) + 1);
|
|
1094
|
-
}
|
|
1095
|
-
return items
|
|
1096
|
-
.filter((item) => item.kind === "control" && item.action === "steer" && Boolean(normalizeText(item.prompt)))
|
|
1097
|
-
.filter((item) => {
|
|
1098
|
-
const key = `${item.turnId}\n${normalizeText(item.prompt)}`;
|
|
1099
|
-
const count = consumed.get(key) ?? 0;
|
|
1100
|
-
if (count <= 0)
|
|
1101
|
-
return true;
|
|
1102
|
-
consumed.set(key, count - 1);
|
|
1103
|
-
return false;
|
|
1104
|
-
})
|
|
1105
|
-
.map((item) => {
|
|
1106
|
-
const createdAt = parseIsoTimeMs(item.createdAt);
|
|
1107
|
-
return {
|
|
1108
|
-
...(createdAt != null ? { createdAt } : {}),
|
|
1109
|
-
id: item.id,
|
|
1110
|
-
text: normalizeText(item.prompt),
|
|
1111
|
-
};
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
function normalizeSubagentStatus(value) {
|
|
1115
|
-
const text = normalizeText(value);
|
|
1116
|
-
if (text === "running" ||
|
|
1117
|
-
text === "paused" ||
|
|
1118
|
-
text === "completed" ||
|
|
1119
|
-
text === "failed" ||
|
|
1120
|
-
text === "interrupted" ||
|
|
1121
|
-
text === "blocked") {
|
|
1122
|
-
return text;
|
|
1123
|
-
}
|
|
1124
|
-
return "unknown";
|
|
1125
|
-
}
|
|
1126
|
-
function isTerminalSubagentStatus(status) {
|
|
1127
|
-
return (status === "completed" ||
|
|
1128
|
-
status === "failed" ||
|
|
1129
|
-
status === "interrupted" ||
|
|
1130
|
-
status === "blocked");
|
|
1131
|
-
}
|
|
1132
|
-
function filterSubagentChildHistory(thread, turns, items, envelopes) {
|
|
1133
|
-
const threadStartedAt = parseIsoTimeMs(thread.createdAt);
|
|
1134
|
-
if (threadStartedAt == null)
|
|
1135
|
-
return { envelopes, items, turns };
|
|
1136
|
-
const childTurns = turns.filter((turn) => {
|
|
1137
|
-
const startedAt = parseIsoTimeMs(turn.createdAt);
|
|
1138
|
-
return startedAt == null || startedAt >= threadStartedAt;
|
|
1139
|
-
});
|
|
1140
|
-
const childTurnIds = new Set(childTurns.map((turn) => turn.id));
|
|
1141
|
-
const childItems = items.filter((item) => {
|
|
1142
|
-
if (childTurnIds.size > 0 && !childTurnIds.has(item.turnId))
|
|
1143
|
-
return false;
|
|
1144
|
-
const createdAt = parseIsoTimeMs(item.createdAt);
|
|
1145
|
-
return createdAt == null || createdAt >= threadStartedAt;
|
|
1146
|
-
});
|
|
1147
|
-
const childEnvelopes = envelopes.filter((envelope) => {
|
|
1148
|
-
if (childTurnIds.size === 0)
|
|
1149
|
-
return true;
|
|
1150
|
-
const turnId = sessionTraceEventTurnId(envelope.event);
|
|
1151
|
-
return !turnId || childTurnIds.has(turnId);
|
|
1152
|
-
});
|
|
1153
|
-
return {
|
|
1154
|
-
envelopes: childEnvelopes,
|
|
1155
|
-
items: childItems,
|
|
1156
|
-
turns: childTurns,
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
function turnsFromThreadEvents(envelopes) {
|
|
1160
|
-
const byId = new Map();
|
|
1161
|
-
for (const envelope of envelopes) {
|
|
1162
|
-
const event = envelope.event;
|
|
1163
|
-
if (event.type === "turn.started" ||
|
|
1164
|
-
event.type === "turn.completed" ||
|
|
1165
|
-
event.type === "turn.failed" ||
|
|
1166
|
-
event.type === "turn.interrupted" ||
|
|
1167
|
-
event.type === "turn.blocked" ||
|
|
1168
|
-
event.type === "turn.reconciled") {
|
|
1169
|
-
byId.set(event.turn.id, event.turn);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return [...byId.values()].sort((a, b) => {
|
|
1173
|
-
const aMs = parseIsoTimeMs(a.createdAt) ?? 0;
|
|
1174
|
-
const bMs = parseIsoTimeMs(b.createdAt) ?? 0;
|
|
1175
|
-
return aMs - bMs;
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
function inferRunningSubagentTurnId(turns, items, envelopes) {
|
|
1179
|
-
const terminalTurnIds = new Set(turns
|
|
1180
|
-
.filter((turn) => isTerminalSubagentStatus(normalizeSubagentStatus(turn.status)))
|
|
1181
|
-
.map((turn) => turn.id));
|
|
1182
|
-
const unresolvedCallIds = new Set();
|
|
1183
|
-
for (const item of items) {
|
|
1184
|
-
if (item.kind === "tool-call") {
|
|
1185
|
-
unresolvedCallIds.add(normalizeText(item.callId) || normalizeText(item.id));
|
|
1186
|
-
}
|
|
1187
|
-
else if (item.kind === "tool-result") {
|
|
1188
|
-
unresolvedCallIds.delete(normalizeText(item.callId));
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
if (unresolvedCallIds.size > 0) {
|
|
1192
|
-
const turnIdsWithLaterAssistantText = new Set();
|
|
1193
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
1194
|
-
const item = items[index];
|
|
1195
|
-
if (item.kind === "message" && item.role !== "user" && normalizeText(item.text)) {
|
|
1196
|
-
turnIdsWithLaterAssistantText.add(item.turnId);
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
if (terminalTurnIds.has(item.turnId))
|
|
1200
|
-
continue;
|
|
1201
|
-
if (item.kind !== "tool-call")
|
|
1202
|
-
continue;
|
|
1203
|
-
const callId = normalizeText(item.callId) || normalizeText(item.id);
|
|
1204
|
-
if (unresolvedCallIds.has(callId) && !turnIdsWithLaterAssistantText.has(item.turnId)) {
|
|
1205
|
-
return item.turnId;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
for (let index = envelopes.length - 1; index >= 0; index -= 1) {
|
|
1210
|
-
const turnId = sessionTraceEventTurnId(envelopes[index].event);
|
|
1211
|
-
if (turnId && !terminalTurnIds.has(turnId))
|
|
1212
|
-
return turnId;
|
|
1213
|
-
}
|
|
1214
|
-
return undefined;
|
|
1215
|
-
}
|
|
1216
|
-
function buildSubagentDetail(thread, turns, items, envelopes, pending = {}) {
|
|
1217
|
-
const childHistory = filterSubagentChildHistory(thread, turns, items, envelopes);
|
|
1218
|
-
const activeTurn = [...childHistory.turns].reverse().find((turn) => turn.status === "running");
|
|
1219
|
-
const latestTurn = childHistory.turns.at(-1);
|
|
1220
|
-
const pendingBlock = findPendingSubagentBlock(childHistory.items);
|
|
1221
|
-
const pendingSteers = findPendingSubagentSteers(childHistory.items);
|
|
1222
|
-
const fallbackStatus = normalizeSubagentStatus(latestTurn?.status ?? thread.status);
|
|
1223
|
-
const inferredRunningTurnId = !pendingBlock && !activeTurn && fallbackStatus === "unknown"
|
|
1224
|
-
? inferRunningSubagentTurnId(childHistory.turns, childHistory.items, childHistory.envelopes)
|
|
1225
|
-
: undefined;
|
|
1226
|
-
const activeTurnId = activeTurn?.id ?? inferredRunningTurnId;
|
|
1227
|
-
const status = pendingBlock
|
|
1228
|
-
? "paused"
|
|
1229
|
-
: activeTurn
|
|
1230
|
-
? "running"
|
|
1231
|
-
: inferredRunningTurnId
|
|
1232
|
-
? "running"
|
|
1233
|
-
: fallbackStatus;
|
|
1234
|
-
const projection = hydrateThreadItems({
|
|
1235
|
-
events: childHistory.envelopes,
|
|
1236
|
-
hideInitialUserMessages: true,
|
|
1237
|
-
hideInterruptedStatus: true,
|
|
1238
|
-
includeDraftReasoning: true,
|
|
1239
|
-
items: childHistory.items,
|
|
1240
|
-
renderPendingSteerControls: false,
|
|
1241
|
-
...(pending.pendingAction ? { pendingAction: pending.pendingAction } : {}),
|
|
1242
|
-
...(pending.pendingApproval ? { pendingApproval: pending.pendingApproval } : {}),
|
|
1243
|
-
thread,
|
|
1244
|
-
turns: childHistory.turns,
|
|
1245
|
-
});
|
|
1246
|
-
return {
|
|
1247
|
-
...(activeTurnId && !pendingBlock ? { activeTurnId } : {}),
|
|
1248
|
-
...(pendingBlock?.requestId ? { requestId: String(pendingBlock.requestId) } : {}),
|
|
1249
|
-
...(pendingBlock ? { pausedReason: pendingBlock.summary } : {}),
|
|
1250
|
-
eventCount: childHistory.envelopes.length,
|
|
1251
|
-
generatedAt: Date.now(),
|
|
1252
|
-
itemCount: childHistory.items.length,
|
|
1253
|
-
lines: childHistory.items.map(subagentLineForItem),
|
|
1254
|
-
messages: projection.messages,
|
|
1255
|
-
messageCount: projection.messages.length,
|
|
1256
|
-
...(pendingSteers.length ? { pendingSteers } : {}),
|
|
1257
|
-
status,
|
|
1258
|
-
steerable: status === "running" && Boolean(activeTurnId) && !pendingBlock,
|
|
1259
|
-
threadId: thread.id,
|
|
1260
|
-
title: thread.title,
|
|
1261
|
-
toolCallCount: childHistory.items.filter((item) => item.kind === "tool-call").length,
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
function watchSubagentLiveEvents(runtime, threadId, input, onEvent) {
|
|
1265
|
-
const turnIds = new Set();
|
|
1266
|
-
const initialTurnId = normalizeText(input.turnId);
|
|
1267
|
-
if (initialTurnId)
|
|
1268
|
-
turnIds.add(initialTurnId);
|
|
1269
|
-
const pendingSteerPrompts = [];
|
|
1270
|
-
const reasoningPartIdByStreamId = new Map();
|
|
1271
|
-
const reasoningTextByStreamId = new Map();
|
|
1272
|
-
const turnsWithReasoningStreams = new Set();
|
|
1273
|
-
const toolNamesByCallId = new Map();
|
|
1274
|
-
let eventCount = 0;
|
|
1275
|
-
const isTrackedTurn = (turnId) => {
|
|
1276
|
-
const normalized = normalizeText(turnId);
|
|
1277
|
-
return Boolean(normalized && turnIds.has(normalized));
|
|
1278
|
-
};
|
|
1279
|
-
const trackTurnId = (turnId) => {
|
|
1280
|
-
const normalized = normalizeText(turnId);
|
|
1281
|
-
if (!normalized)
|
|
1282
|
-
return undefined;
|
|
1283
|
-
const trackedTurnId = normalized;
|
|
1284
|
-
turnIds.add(trackedTurnId);
|
|
1285
|
-
return trackedTurnId;
|
|
1286
|
-
};
|
|
1287
|
-
const isDirectThreadEvent = (event) => normalizeText(event.threadId) === threadId;
|
|
1288
|
-
const shouldTrackTurnEvent = (event) => {
|
|
1289
|
-
if (isTrackedTurn(event.turnId))
|
|
1290
|
-
return true;
|
|
1291
|
-
if (!isDirectThreadEvent(event))
|
|
1292
|
-
return false;
|
|
1293
|
-
trackTurnId(event.turnId);
|
|
1294
|
-
return true;
|
|
1295
|
-
};
|
|
1296
|
-
const isTrackedItemEvent = (event) => {
|
|
1297
|
-
if (isTrackedTurn(event.turnId))
|
|
1298
|
-
return true;
|
|
1299
|
-
if (normalizeText(event.item?.threadId) !== threadId)
|
|
1300
|
-
return false;
|
|
1301
|
-
trackTurnId(event.turnId);
|
|
1302
|
-
return true;
|
|
1303
|
-
};
|
|
1304
|
-
const emitStatus = (status, activeTurnId) => {
|
|
1305
|
-
onEvent({
|
|
1306
|
-
...(activeTurnId ? { activeTurnId } : {}),
|
|
1307
|
-
eventCount,
|
|
1308
|
-
status,
|
|
1309
|
-
steerable: status === "running" && Boolean(activeTurnId),
|
|
1310
|
-
type: "subagent.status",
|
|
1311
|
-
});
|
|
1312
|
-
};
|
|
1313
|
-
const consumeSteerPrompt = (text) => {
|
|
1314
|
-
const normalizedText = normalizeText(text);
|
|
1315
|
-
if (!normalizedText)
|
|
1316
|
-
return false;
|
|
1317
|
-
const index = pendingSteerPrompts.findIndex((prompt) => normalizeText(prompt) === normalizedText);
|
|
1318
|
-
if (index < 0)
|
|
1319
|
-
return false;
|
|
1320
|
-
pendingSteerPrompts.splice(index, 1);
|
|
1321
|
-
return true;
|
|
1322
|
-
};
|
|
1323
|
-
const unsubscribe = runtime.subscribe((envelope) => {
|
|
1324
|
-
const event = envelope.event;
|
|
1325
|
-
const createdAtMs = parseIsoTimeMs(envelope.createdAt) ?? undefined;
|
|
1326
|
-
if (event.type === "turn.started" && event.turn.threadId === threadId) {
|
|
1327
|
-
eventCount += 1;
|
|
1328
|
-
const activeTurnId = trackTurnId(event.turn.id);
|
|
1329
|
-
emitStatus("running", activeTurnId ?? event.turn.id);
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
if ((event.type === "turn.completed" ||
|
|
1333
|
-
event.type === "turn.failed" ||
|
|
1334
|
-
event.type === "turn.interrupted" ||
|
|
1335
|
-
event.type === "turn.blocked") &&
|
|
1336
|
-
(event.turn.threadId === threadId || isTrackedTurn(event.turn.id))) {
|
|
1337
|
-
eventCount += 1;
|
|
1338
|
-
trackTurnId(event.turn.id);
|
|
1339
|
-
emitStatus(normalizeSubagentStatus(event.turn.status), undefined);
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
if (event.type === "turn.steered") {
|
|
1343
|
-
if (!shouldTrackTurnEvent(event))
|
|
1344
|
-
return;
|
|
1345
|
-
const prompt = normalizeText(event.prompt);
|
|
1346
|
-
if (!prompt)
|
|
1347
|
-
return;
|
|
1348
|
-
eventCount += 1;
|
|
1349
|
-
pendingSteerPrompts.push(prompt);
|
|
1350
|
-
onEvent({
|
|
1351
|
-
content: prompt,
|
|
1352
|
-
createdAtMs,
|
|
1353
|
-
id: `${event.turnId}:${envelope.sequence}`,
|
|
1354
|
-
type: "steer_queued",
|
|
1355
|
-
});
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
if (event.type === "turn.reasoning.started") {
|
|
1359
|
-
if (!shouldTrackTurnEvent(event))
|
|
1360
|
-
return;
|
|
1361
|
-
eventCount += 1;
|
|
1362
|
-
turnsWithReasoningStreams.add(event.turnId);
|
|
1363
|
-
const partId = createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
1364
|
-
reasoningPartIdByStreamId.set(event.streamId, partId);
|
|
1365
|
-
reasoningTextByStreamId.set(event.streamId, "");
|
|
1366
|
-
onEvent({ partId, type: "thinking.started" });
|
|
1367
|
-
return;
|
|
1368
|
-
}
|
|
1369
|
-
if (event.type === "turn.reasoning.delta") {
|
|
1370
|
-
if (!shouldTrackTurnEvent(event))
|
|
1371
|
-
return;
|
|
1372
|
-
eventCount += 1;
|
|
1373
|
-
turnsWithReasoningStreams.add(event.turnId);
|
|
1374
|
-
const partId = reasoningPartIdByStreamId.get(event.streamId) ??
|
|
1375
|
-
createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
1376
|
-
reasoningPartIdByStreamId.set(event.streamId, partId);
|
|
1377
|
-
reasoningTextByStreamId.set(event.streamId, `${reasoningTextByStreamId.get(event.streamId) ?? ""}${event.delta}`);
|
|
1378
|
-
onEvent({ delta: event.delta, partId, type: "thinking.delta" });
|
|
1379
|
-
return;
|
|
1380
|
-
}
|
|
1381
|
-
if (event.type === "turn.reasoning.completed") {
|
|
1382
|
-
if (!shouldTrackTurnEvent(event))
|
|
1383
|
-
return;
|
|
1384
|
-
eventCount += 1;
|
|
1385
|
-
turnsWithReasoningStreams.add(event.turnId);
|
|
1386
|
-
const partId = reasoningPartIdByStreamId.get(event.streamId) ??
|
|
1387
|
-
createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
1388
|
-
const content = reasoningTextByStreamId.get(event.streamId);
|
|
1389
|
-
reasoningPartIdByStreamId.delete(event.streamId);
|
|
1390
|
-
reasoningTextByStreamId.delete(event.streamId);
|
|
1391
|
-
onEvent({ ...(content ? { content } : {}), partId, type: "thinking.finished" });
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
if (event.type === "turn.text.delta") {
|
|
1395
|
-
if (!shouldTrackTurnEvent(event))
|
|
1396
|
-
return;
|
|
1397
|
-
eventCount += 1;
|
|
1398
|
-
onEvent({ delta: event.delta, streamId: event.streamId, type: "assistant.delta" });
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
if (event.type === "turn.text.stream.reset") {
|
|
1402
|
-
if (!shouldTrackTurnEvent(event))
|
|
1403
|
-
return;
|
|
1404
|
-
eventCount += 1;
|
|
1405
|
-
onEvent({ streamId: event.streamId, type: "assistant.reset" });
|
|
1406
|
-
return;
|
|
1407
|
-
}
|
|
1408
|
-
if (event.type === "turn.tool-call.started") {
|
|
1409
|
-
if (!shouldTrackTurnEvent(event))
|
|
1410
|
-
return;
|
|
1411
|
-
eventCount += 1;
|
|
1412
|
-
const toolName = canonicalToolName(event.toolName);
|
|
1413
|
-
toolNamesByCallId.set(event.callId, toolName);
|
|
1414
|
-
onEvent({
|
|
1415
|
-
input: {},
|
|
1416
|
-
originalEventType: "tool_call",
|
|
1417
|
-
startedAtMs: createdAtMs,
|
|
1418
|
-
toolCallId: event.callId,
|
|
1419
|
-
toolName,
|
|
1420
|
-
type: "tool.started",
|
|
1421
|
-
});
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
if (event.type === "turn.tool-call.delta") {
|
|
1425
|
-
if (!shouldTrackTurnEvent(event))
|
|
1426
|
-
return;
|
|
1427
|
-
eventCount += 1;
|
|
1428
|
-
const toolName = canonicalToolName(event.toolName);
|
|
1429
|
-
toolNamesByCallId.set(event.callId, toolName);
|
|
1430
|
-
onEvent({
|
|
1431
|
-
delta: event.delta,
|
|
1432
|
-
kind: "input",
|
|
1433
|
-
toolCallId: event.callId,
|
|
1434
|
-
toolName,
|
|
1435
|
-
type: "tool.delta",
|
|
1436
|
-
});
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
if (event.type === "turn.tool-output.delta") {
|
|
1440
|
-
if (!shouldTrackTurnEvent(event))
|
|
1441
|
-
return;
|
|
1442
|
-
eventCount += 1;
|
|
1443
|
-
const toolName = canonicalToolName(event.toolName);
|
|
1444
|
-
toolNamesByCallId.set(event.callId, toolName);
|
|
1445
|
-
onEvent({
|
|
1446
|
-
delta: event.delta,
|
|
1447
|
-
kind: "output",
|
|
1448
|
-
toolCallId: event.callId,
|
|
1449
|
-
toolName,
|
|
1450
|
-
type: "tool.delta",
|
|
1451
|
-
});
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
if (event.type !== "turn.item.added" || !isTrackedItemEvent(event))
|
|
1455
|
-
return;
|
|
1456
|
-
eventCount += 1;
|
|
1457
|
-
const item = event.item;
|
|
1458
|
-
if (item.kind === "message" && item.role === "user") {
|
|
1459
|
-
if (consumeSteerPrompt(item.text)) {
|
|
1460
|
-
onEvent({
|
|
1461
|
-
content: item.text,
|
|
1462
|
-
createdAtMs: parseIsoTimeMs(item.createdAt) ?? createdAtMs,
|
|
1463
|
-
type: "steer_injected",
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
return;
|
|
1467
|
-
}
|
|
1468
|
-
if (item.kind === "message" && item.role === "agent") {
|
|
1469
|
-
onEvent({
|
|
1470
|
-
message: { content: item.text },
|
|
1471
|
-
originalEventType: "thread_item_message",
|
|
1472
|
-
type: "assistant.message",
|
|
1473
|
-
});
|
|
1474
|
-
return;
|
|
1475
|
-
}
|
|
1476
|
-
if (item.kind === "reasoning") {
|
|
1477
|
-
if (turnsWithReasoningStreams.has(item.turnId) || isDraftReasoningItem(item))
|
|
1478
|
-
return;
|
|
1479
|
-
const content = item.content ?? item.summary;
|
|
1480
|
-
if (!hasVisibleReasoningText(content))
|
|
1481
|
-
return;
|
|
1482
|
-
onEvent({
|
|
1483
|
-
content,
|
|
1484
|
-
partId: createThinkingPartIdFromReasoningItem(item.id),
|
|
1485
|
-
type: "thinking.finished",
|
|
1486
|
-
});
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
if (item.kind === "tool-call") {
|
|
1490
|
-
const toolCallId = resolveToolItemCallId(item);
|
|
1491
|
-
const toolName = canonicalToolName(item.toolName);
|
|
1492
|
-
toolNamesByCallId.set(toolCallId, toolName);
|
|
1493
|
-
onEvent({
|
|
1494
|
-
input: parseToolInput(item.input),
|
|
1495
|
-
originalEventType: "tool_call_end",
|
|
1496
|
-
startedAtMs: parseIsoTimeMs(item.createdAt) ?? createdAtMs,
|
|
1497
|
-
toolCallId,
|
|
1498
|
-
toolName,
|
|
1499
|
-
type: "tool.started",
|
|
1500
|
-
});
|
|
1501
|
-
return;
|
|
1502
|
-
}
|
|
1503
|
-
if (item.kind === "tool-result") {
|
|
1504
|
-
const toolCallId = readExplicitToolCallId(item) ?? normalizeText(item.id);
|
|
1505
|
-
const toolName = canonicalToolName(item.toolName) || toolNamesByCallId.get(toolCallId) || toolCallId;
|
|
1506
|
-
toolNamesByCallId.set(toolCallId, toolName);
|
|
1507
|
-
onEvent({
|
|
1508
|
-
...(item.errorCode ? { errorCode: item.errorCode } : {}),
|
|
1509
|
-
...(item.failureKind ? { failureKind: item.failureKind } : {}),
|
|
1510
|
-
isError: item.outcome === "error",
|
|
1511
|
-
output: {
|
|
1512
|
-
output: item.output,
|
|
1513
|
-
...(item.structuredContent !== undefined
|
|
1514
|
-
? { structuredContent: item.structuredContent }
|
|
1515
|
-
: {}),
|
|
1516
|
-
},
|
|
1517
|
-
toolCallId,
|
|
1518
|
-
toolName,
|
|
1519
|
-
type: "tool.finished",
|
|
1520
|
-
});
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
if (item.kind === "control" && item.action === "steer" && item.prompt) {
|
|
1524
|
-
const prompt = normalizeText(item.prompt);
|
|
1525
|
-
if (!prompt)
|
|
1526
|
-
return;
|
|
1527
|
-
pendingSteerPrompts.push(prompt);
|
|
1528
|
-
onEvent({
|
|
1529
|
-
content: prompt,
|
|
1530
|
-
createdAtMs: parseIsoTimeMs(item.createdAt) ?? createdAtMs,
|
|
1531
|
-
id: item.id,
|
|
1532
|
-
type: "steer_queued",
|
|
1533
|
-
});
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
if (item.kind === "error" && normalizeText(item.message)) {
|
|
1537
|
-
onEvent({ message: item.message, type: "error.recorded" });
|
|
1538
|
-
}
|
|
1539
|
-
});
|
|
1540
|
-
return unsubscribe;
|
|
1541
|
-
}
|
|
1542
|
-
function belongsToThread(event, threadId, turnId) {
|
|
1543
|
-
switch (event.type) {
|
|
1544
|
-
case "turn.started":
|
|
1545
|
-
return event.turn.threadId === threadId;
|
|
1546
|
-
case "turn.steered":
|
|
1547
|
-
case "turn.item.added":
|
|
1548
|
-
return turnId !== undefined && event.turnId === turnId;
|
|
1549
|
-
case "turn.text.delta":
|
|
1550
|
-
case "turn.text.stream.reset":
|
|
1551
|
-
case "turn.reasoning.started":
|
|
1552
|
-
case "turn.reasoning.delta":
|
|
1553
|
-
case "turn.reasoning.completed":
|
|
1554
|
-
case "turn.tool-call.started":
|
|
1555
|
-
case "turn.tool-call.delta":
|
|
1556
|
-
case "turn.tool-output.delta":
|
|
1557
|
-
return turnId !== undefined && (event.turnId === turnId || event.parentTurnId === turnId);
|
|
1558
|
-
case "approval.requested":
|
|
1559
|
-
return (event.approval.threadId === threadId &&
|
|
1560
|
-
(turnId === undefined || event.approval.turnId === turnId));
|
|
1561
|
-
case "action.required":
|
|
1562
|
-
return event.threadId === threadId && (turnId === undefined || event.turnId === turnId);
|
|
1563
|
-
case "child-thread.item.added":
|
|
1564
|
-
return turnId !== undefined && event.parentTurnId === turnId;
|
|
1565
|
-
case "child-task.started":
|
|
1566
|
-
case "child-task.completed":
|
|
1567
|
-
case "child-task.failed":
|
|
1568
|
-
case "child-task.joined":
|
|
1569
|
-
case "child-task.rejected":
|
|
1570
|
-
return turnId !== undefined && event.threadId === threadId && event.turnId === turnId;
|
|
1571
|
-
case "thread.compaction.started":
|
|
1572
|
-
case "thread.compacted":
|
|
1573
|
-
return event.threadId === threadId && (turnId === undefined || event.turnId === turnId);
|
|
1574
|
-
case "thread.usage.updated":
|
|
1575
|
-
return (event.threadId === threadId &&
|
|
1576
|
-
(turnId === undefined || event.turnId === undefined || event.turnId === turnId));
|
|
1577
|
-
case "turn.completed":
|
|
1578
|
-
case "turn.failed":
|
|
1579
|
-
case "turn.interrupted":
|
|
1580
|
-
case "turn.blocked":
|
|
1581
|
-
return turnId !== undefined && event.turn.id === turnId;
|
|
1582
|
-
default:
|
|
1583
|
-
return false;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
const VALID_SESSION_QUEUE_STATUSES = new Set([
|
|
1587
|
-
"idle",
|
|
1588
|
-
"running",
|
|
1589
|
-
"paused_confirm",
|
|
1590
|
-
"error",
|
|
1591
|
-
]);
|
|
1592
|
-
const VALID_SESSION_QUEUE_ERROR_CODES = new Set([
|
|
1593
|
-
"network_error",
|
|
1594
|
-
"timeout",
|
|
1595
|
-
"canceled_by_user",
|
|
1596
|
-
"rate_limited",
|
|
1597
|
-
"config_unavailable",
|
|
1598
|
-
"unknown",
|
|
1599
|
-
]);
|
|
1600
|
-
function defaultBridgeSessionUiMetadata() {
|
|
1601
|
-
return {
|
|
1602
|
-
lastErrorCode: undefined,
|
|
1603
|
-
lastUpdatedAt: 0,
|
|
1604
|
-
pinnedMessageIds: [],
|
|
1605
|
-
queue: [],
|
|
1606
|
-
queueLength: 0,
|
|
1607
|
-
reasoningDiagnosticsByTurn: {},
|
|
1608
|
-
status: "idle",
|
|
1609
|
-
titleSuggestedAt: undefined,
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
function normalizeSessionQueueItem(raw) {
|
|
1613
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1614
|
-
return null;
|
|
1615
|
-
const record = raw;
|
|
1616
|
-
const id = normalizeText(record.id);
|
|
1617
|
-
const text = normalizeText(record.text);
|
|
1618
|
-
if (!id || !text)
|
|
1619
|
-
return null;
|
|
1620
|
-
const createdAt = Number(record.createdAt);
|
|
1621
|
-
const steeredAt = Number(record.steeredAt);
|
|
1622
|
-
const configRecord = asRecord(record.config);
|
|
1623
|
-
const approvalsMode = configRecord.approvalsMode;
|
|
1624
|
-
const normalizedApprovalsMode = approvalsMode === "auto" || approvalsMode === "normal" || approvalsMode === "strict"
|
|
1625
|
-
? approvalsMode
|
|
1626
|
-
: undefined;
|
|
1627
|
-
const config = Object.keys(configRecord).length > 0
|
|
1628
|
-
? {
|
|
1629
|
-
model: normalizeText(configRecord.model) || undefined,
|
|
1630
|
-
temperature: Number.isFinite(Number(configRecord.temperature))
|
|
1631
|
-
? Number(configRecord.temperature)
|
|
1632
|
-
: undefined,
|
|
1633
|
-
interleavedThinking: typeof configRecord.interleavedThinking === "boolean"
|
|
1634
|
-
? configRecord.interleavedThinking
|
|
1635
|
-
: undefined,
|
|
1636
|
-
approvalsMode: normalizedApprovalsMode,
|
|
1637
|
-
providerId: normalizeText(configRecord.providerId) || undefined,
|
|
1638
|
-
}
|
|
1639
|
-
: undefined;
|
|
1640
|
-
return {
|
|
1641
|
-
id,
|
|
1642
|
-
text,
|
|
1643
|
-
createdAt: Number.isFinite(createdAt) ? createdAt : 0,
|
|
1644
|
-
kind: normalizeText(record.kind) === "steer" ? "steer" : "user",
|
|
1645
|
-
steeredAt: Number.isFinite(steeredAt) && steeredAt > 0 ? steeredAt : undefined,
|
|
1646
|
-
...(config ? { config } : {}),
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
function sessionUiMetadata(thread) {
|
|
1650
|
-
const metadata = asRecord(thread.metadata);
|
|
1651
|
-
const ui = asRecord(metadata.ui);
|
|
1652
|
-
return {
|
|
1653
|
-
...defaultBridgeSessionUiMetadata(),
|
|
1654
|
-
...cloneJson(ui),
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
function normalizeBridgeSessionUiMetadata(raw) {
|
|
1658
|
-
const base = defaultBridgeSessionUiMetadata();
|
|
1659
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1660
|
-
return base;
|
|
1661
|
-
const record = raw;
|
|
1662
|
-
const queue = Array.isArray(record.queue)
|
|
1663
|
-
? record.queue
|
|
1664
|
-
.map(normalizeSessionQueueItem)
|
|
1665
|
-
.filter((item) => item !== null)
|
|
1666
|
-
: [];
|
|
1667
|
-
return {
|
|
1668
|
-
lastErrorCode: VALID_SESSION_QUEUE_ERROR_CODES.has(record.lastErrorCode)
|
|
1669
|
-
? record.lastErrorCode
|
|
1670
|
-
: undefined,
|
|
1671
|
-
lastUpdatedAt: Number.isFinite(Number(record.lastUpdatedAt)) ? Number(record.lastUpdatedAt) : 0,
|
|
1672
|
-
pinnedMessageIds: Array.isArray(record.pinnedMessageIds)
|
|
1673
|
-
? record.pinnedMessageIds.map((id) => normalizeText(id)).filter(Boolean)
|
|
1674
|
-
: [],
|
|
1675
|
-
queue,
|
|
1676
|
-
queueLength: queue.length,
|
|
1677
|
-
reasoningDiagnosticsByTurn: normalizeReasoningDiagnosticsByTurn(record.reasoningDiagnosticsByTurn),
|
|
1678
|
-
status: VALID_SESSION_QUEUE_STATUSES.has(record.status)
|
|
1679
|
-
? record.status
|
|
1680
|
-
: base.status,
|
|
1681
|
-
titleSuggestedAt: Number.isFinite(Number(record.titleSuggestedAt)) && Number(record.titleSuggestedAt) > 0
|
|
1682
|
-
? Number(record.titleSuggestedAt)
|
|
1683
|
-
: undefined,
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
function sessionListMetadata(thread) {
|
|
1687
|
-
const metadata = normalizeBridgeSessionUiMetadata(sessionUiMetadata(thread));
|
|
1688
|
-
return {
|
|
1689
|
-
lastUpdatedAt: typeof metadata.lastUpdatedAt === "number" ? metadata.lastUpdatedAt : undefined,
|
|
1690
|
-
queue: Array.isArray(metadata.queue) ? metadata.queue : undefined,
|
|
1691
|
-
queueLength: typeof metadata.queueLength === "number" ? metadata.queueLength : undefined,
|
|
1692
|
-
reasoningDiagnosticsByTurn: Object.keys(metadata.reasoningDiagnosticsByTurn).length > 0
|
|
1693
|
-
? metadata.reasoningDiagnosticsByTurn
|
|
1694
|
-
: undefined,
|
|
1695
|
-
status: VALID_SESSION_QUEUE_STATUSES.has(metadata.status)
|
|
1696
|
-
? metadata.status
|
|
1697
|
-
: undefined,
|
|
1698
|
-
};
|
|
1699
|
-
}
|
|
1700
|
-
function sessionListUpdatedAt(thread, metadata) {
|
|
1701
|
-
const metadataUpdatedAt = Number(metadata.lastUpdatedAt ?? 0);
|
|
1702
|
-
if (Number.isFinite(metadataUpdatedAt) && metadataUpdatedAt > 0)
|
|
1703
|
-
return metadataUpdatedAt;
|
|
1704
|
-
const threadUpdatedAt = Date.parse(thread.updatedAt);
|
|
1705
|
-
return Number.isFinite(threadUpdatedAt) ? threadUpdatedAt : 0;
|
|
1706
|
-
}
|
|
1707
|
-
function wrapSessionUiMetadata(metadata) {
|
|
1708
|
-
const ui = asRecord(metadata.ui);
|
|
1709
|
-
if (Object.keys(ui).length > 0)
|
|
1710
|
-
return metadata;
|
|
1711
|
-
return { ui: metadata };
|
|
1712
|
-
}
|
|
1713
|
-
function latestSummary(messages) {
|
|
1714
|
-
const last = [...messages].reverse().find((message) => {
|
|
1715
|
-
if (message.role === "user")
|
|
1716
|
-
return message.content.trim().length > 0;
|
|
1717
|
-
if (message.role === "tool")
|
|
1718
|
-
return message.result.output.trim().length > 0;
|
|
1719
|
-
return message.parts.some((part) => {
|
|
1720
|
-
if (part.type === "markdown")
|
|
1721
|
-
return part.markdown.trim().length > 0;
|
|
1722
|
-
if (part.type === "tool_result")
|
|
1723
|
-
return part.result.output.trim().length > 0;
|
|
1724
|
-
return false;
|
|
1725
|
-
});
|
|
1726
|
-
});
|
|
1727
|
-
const lastMessage = last?.role === "user"
|
|
1728
|
-
? last.content
|
|
1729
|
-
: last?.role === "tool"
|
|
1730
|
-
? last.result.output
|
|
1731
|
-
: last?.parts
|
|
1732
|
-
.map((part) => {
|
|
1733
|
-
if (part.type === "markdown")
|
|
1734
|
-
return part.markdown;
|
|
1735
|
-
if (part.type === "tool_result")
|
|
1736
|
-
return part.result.output;
|
|
1737
|
-
return "";
|
|
1738
|
-
})
|
|
1739
|
-
.join("\n")
|
|
1740
|
-
.trim();
|
|
1741
|
-
return {
|
|
1742
|
-
lastMessage: lastMessage || undefined,
|
|
1743
|
-
messageCount: messages.length,
|
|
1744
|
-
summary: lastMessage?.split(/\r?\n/, 1)[0] ?? "(empty)",
|
|
1745
|
-
};
|
|
1746
|
-
}
|
|
1747
|
-
function formatSessionTitle(text) {
|
|
1748
|
-
const title = String(text ?? "")
|
|
1749
|
-
.replace(/\s+/g, " ")
|
|
1750
|
-
.trim();
|
|
1751
|
-
return title.length <= 60 ? title : `${title.slice(0, 59)}…`;
|
|
1752
|
-
}
|
|
1753
|
-
function activeSessionTitle(threadTitle, messages) {
|
|
1754
|
-
if (threadTitle !== "New Thread" || !messages?.length)
|
|
1755
|
-
return threadTitle;
|
|
1756
|
-
const firstUser = messages.find((message) => message.role === "user" && normalizeText(message.content).length > 0);
|
|
1757
|
-
const derived = firstUser ? formatSessionTitle(firstUser.content) : "";
|
|
1758
|
-
return derived || threadTitle;
|
|
1759
|
-
}
|
|
1760
|
-
function previewSummary(thread) {
|
|
1761
|
-
const preview = String(thread.preview ?? "").trim();
|
|
1762
|
-
return {
|
|
1763
|
-
lastMessage: preview || undefined,
|
|
1764
|
-
messageCount: Math.max(0, Number(thread.turnCount ?? 0) || 0),
|
|
1765
|
-
summary: preview.split(/\r?\n/, 1)[0] ?? "(empty)",
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
async function collectSubagentThreadIds(runtime, threads) {
|
|
1769
|
-
if (typeof runtime.getThreadDiagnostics !== "function")
|
|
1770
|
-
return new Set();
|
|
1771
|
-
const childCandidatesByParent = new Map();
|
|
1772
|
-
for (const thread of threads) {
|
|
1773
|
-
const parentThreadId = normalizeText(thread.forkedFromThreadId);
|
|
1774
|
-
if (!parentThreadId)
|
|
1775
|
-
continue;
|
|
1776
|
-
let candidates = childCandidatesByParent.get(parentThreadId);
|
|
1777
|
-
if (!candidates) {
|
|
1778
|
-
candidates = new Set();
|
|
1779
|
-
childCandidatesByParent.set(parentThreadId, candidates);
|
|
1780
|
-
}
|
|
1781
|
-
candidates.add(thread.id);
|
|
1782
|
-
}
|
|
1783
|
-
if (childCandidatesByParent.size === 0)
|
|
1784
|
-
return new Set();
|
|
1785
|
-
const subagentThreadIds = new Set();
|
|
1786
|
-
await Promise.all([...childCandidatesByParent.entries()].map(async ([parentThreadId, candidates]) => {
|
|
1787
|
-
try {
|
|
1788
|
-
const diagnostics = await runtime.getThreadDiagnostics(parentThreadId);
|
|
1789
|
-
for (const record of diagnostics.diagnostics.childTasks.records) {
|
|
1790
|
-
const childThreadId = normalizeText(record.childThreadId);
|
|
1791
|
-
if (childThreadId && candidates.has(childThreadId))
|
|
1792
|
-
subagentThreadIds.add(childThreadId);
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
catch { }
|
|
1796
|
-
const remaining = new Set([...candidates].filter((childThreadId) => !subagentThreadIds.has(childThreadId)));
|
|
1797
|
-
if (remaining.size === 0 || typeof runtime.getThreadHistory !== "function")
|
|
1798
|
-
return;
|
|
1799
|
-
try {
|
|
1800
|
-
const history = await runtime.getThreadHistory({
|
|
1801
|
-
threadId: parentThreadId,
|
|
1802
|
-
limit: 1000,
|
|
1803
|
-
});
|
|
1804
|
-
for (const item of history.items) {
|
|
1805
|
-
if (item.kind !== "control" || item.action !== "child-thread-started")
|
|
1806
|
-
continue;
|
|
1807
|
-
const childThreadId = normalizeText(item.childThreadId);
|
|
1808
|
-
if (childThreadId && remaining.has(childThreadId))
|
|
1809
|
-
subagentThreadIds.add(childThreadId);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
catch { }
|
|
1813
|
-
}));
|
|
1814
|
-
return subagentThreadIds;
|
|
1815
|
-
}
|
|
1816
|
-
async function filterSessionThreads(runtime, threads) {
|
|
1817
|
-
return await withPerfSpanAsync("filterSessionThreads", `threads=${threads.length}`, async () => {
|
|
1818
|
-
if (String(process.env.BEST_AGENT_DISABLE_SUBAGENT_SESSION_FILTER ?? "").trim() === "1")
|
|
1819
|
-
return [...threads];
|
|
1820
|
-
const timeoutMsRaw = Number(process.env.BEST_AGENT_SESSION_FILTER_TIMEOUT_MS ?? 150);
|
|
1821
|
-
const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0 ? Math.floor(timeoutMsRaw) : 150;
|
|
1822
|
-
let timeout;
|
|
1823
|
-
const subagentThreadIds = await Promise.race([
|
|
1824
|
-
collectSubagentThreadIds(runtime, threads),
|
|
1825
|
-
new Promise((resolve) => {
|
|
1826
|
-
timeout = setTimeout(() => resolve(new Set()), timeoutMs);
|
|
1827
|
-
}),
|
|
1828
|
-
]).finally(() => {
|
|
1829
|
-
if (timeout)
|
|
1830
|
-
clearTimeout(timeout);
|
|
1831
|
-
});
|
|
1832
|
-
if (subagentThreadIds.size === 0)
|
|
1833
|
-
return [...threads];
|
|
1834
|
-
return threads.filter((thread) => !subagentThreadIds.has(thread.id));
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
const FAST_THREAD_RESUME_TURN_LIMIT = 80;
|
|
1838
|
-
const HISTORY_PAGE_TURN_LIMIT = 80;
|
|
1839
|
-
function isHiddenSubagentUserMessage(message) {
|
|
1840
|
-
if (message.role !== "user")
|
|
1841
|
-
return false;
|
|
1842
|
-
const text = normalizeText(message.content);
|
|
1843
|
-
return (text.includes("Execution constraints:") ||
|
|
1844
|
-
text.includes("Persistence constraints:") ||
|
|
1845
|
-
text.includes("Return findings in the transcript/evidence only") ||
|
|
1846
|
-
text.includes("Do not write scratch or workspace files in this child thread") ||
|
|
1847
|
-
text.startsWith("Continue the same assigned child task from the current thread and workspace state.") ||
|
|
1848
|
-
text.includes("Runtime error (repairable provider):"));
|
|
1849
|
-
}
|
|
1850
|
-
function removeHiddenSubagentUserMessages(messages) {
|
|
1851
|
-
return messages.filter((message) => !isHiddenSubagentUserMessage(message));
|
|
1852
|
-
}
|
|
1853
|
-
function hydrateThreadItems(input) {
|
|
1854
|
-
const projectionState = {
|
|
1855
|
-
layoutDirtyMessageId: null,
|
|
1856
|
-
layoutDirtySeq: 0,
|
|
1857
|
-
messages: [],
|
|
1858
|
-
revertedMessages: [],
|
|
1859
|
-
userHistory: [],
|
|
1860
|
-
};
|
|
1861
|
-
const mutations = createMessageMutations(projectionState);
|
|
1862
|
-
const projector = createTranscriptProjector({
|
|
1863
|
-
...mutations,
|
|
1864
|
-
state: projectionState,
|
|
1865
|
-
});
|
|
1866
|
-
const assistantIdsByTurn = new Map();
|
|
1867
|
-
const assistantIdHistoryByTurn = new Map();
|
|
1868
|
-
const assistantSegmentCountByTurn = new Map();
|
|
1869
|
-
const hydratedUserTextByTurn = new Map();
|
|
1870
|
-
const hydratedSubagentIdsByTurn = new Map();
|
|
1871
|
-
const toolInputsByCallId = new Map();
|
|
1872
|
-
const toolNamesByCallId = new Map();
|
|
1873
|
-
const assistantIdByToolCallId = new Map();
|
|
1874
|
-
const assistantSplitBoundariesByTurn = new Map();
|
|
1875
|
-
const toolCallStartedAtMsByCallId = new Map();
|
|
1876
|
-
for (const item of input.items) {
|
|
1877
|
-
if (item.kind !== "tool-call")
|
|
1878
|
-
continue;
|
|
1879
|
-
const startedAtMs = parseIsoTimeMs(item.createdAt);
|
|
1880
|
-
if (startedAtMs == null)
|
|
1881
|
-
continue;
|
|
1882
|
-
toolCallStartedAtMsByCallId.set(resolveToolItemCallId(item), startedAtMs);
|
|
1883
|
-
}
|
|
1884
|
-
const reasoningDiagnosticsByTurn = input.thread
|
|
1885
|
-
? normalizeBridgeSessionUiMetadata(sessionUiMetadata(input.thread)).reasoningDiagnosticsByTurn
|
|
1886
|
-
: {};
|
|
1887
|
-
const turnsById = new Map((input.turns ?? []).map((turn) => [turn.id, turn]));
|
|
1888
|
-
const interruptedTurnIds = new Set(input.hideInterruptedStatus
|
|
1889
|
-
? []
|
|
1890
|
-
: (input.turns ?? []).filter((turn) => turn.status === "interrupted").map((turn) => turn.id));
|
|
1891
|
-
const startedReasoningStreamsByTurn = new Map();
|
|
1892
|
-
const completedReasoningStreamsByTurn = new Map();
|
|
1893
|
-
let approvalActive = null;
|
|
1894
|
-
let paused = null;
|
|
1895
|
-
for (const envelope of input.events ?? []) {
|
|
1896
|
-
const event = envelope.event;
|
|
1897
|
-
if (event.type === "turn.interrupted") {
|
|
1898
|
-
if (!input.hideInterruptedStatus)
|
|
1899
|
-
interruptedTurnIds.add(event.turn.id);
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
if (event.type !== "turn.reasoning.started" &&
|
|
1903
|
-
event.type !== "turn.reasoning.delta" &&
|
|
1904
|
-
event.type !== "turn.reasoning.completed") {
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
if ("parentTurnId" in event && event.parentTurnId)
|
|
1908
|
-
continue;
|
|
1909
|
-
const started = startedReasoningStreamsByTurn.get(event.turnId) ?? new Set();
|
|
1910
|
-
started.add(event.streamId);
|
|
1911
|
-
startedReasoningStreamsByTurn.set(event.turnId, started);
|
|
1912
|
-
if (event.type === "turn.reasoning.completed") {
|
|
1913
|
-
const completed = completedReasoningStreamsByTurn.get(event.turnId) ?? new Set();
|
|
1914
|
-
completed.add(event.streamId);
|
|
1915
|
-
completedReasoningStreamsByTurn.set(event.turnId, completed);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
const markHydratedSubagent = (turnId, subagentId) => {
|
|
1919
|
-
const set = hydratedSubagentIdsByTurn.get(turnId) ?? new Set();
|
|
1920
|
-
set.add(subagentId);
|
|
1921
|
-
hydratedSubagentIdsByTurn.set(turnId, set);
|
|
1922
|
-
};
|
|
1923
|
-
const hasHydratedSubagent = (turnId, subagentId) => hydratedSubagentIdsByTurn.get(turnId)?.has(subagentId) ?? false;
|
|
1924
|
-
const ensureAssistantId = (turnId) => {
|
|
1925
|
-
const existing = assistantIdsByTurn.get(turnId);
|
|
1926
|
-
if (existing)
|
|
1927
|
-
return existing;
|
|
1928
|
-
const segment = assistantSegmentCountByTurn.get(turnId) ?? 0;
|
|
1929
|
-
const assistantId = segment === 0 ? `assistant_${turnId}` : `assistant_${turnId}_${segment}`;
|
|
1930
|
-
mutations.pushMessage({
|
|
1931
|
-
id: assistantId,
|
|
1932
|
-
parts: [{ text: "", type: "status" }],
|
|
1933
|
-
role: "assistant",
|
|
1934
|
-
});
|
|
1935
|
-
assistantIdsByTurn.set(turnId, assistantId);
|
|
1936
|
-
const history = assistantIdHistoryByTurn.get(turnId) ?? [];
|
|
1937
|
-
history.push(assistantId);
|
|
1938
|
-
assistantIdHistoryByTurn.set(turnId, history);
|
|
1939
|
-
return assistantId;
|
|
1940
|
-
};
|
|
1941
|
-
const assistantHasRenderableContent = (assistantId) => {
|
|
1942
|
-
const assistant = projectionState.messages.find((message) => message.id === assistantId && message.role === "assistant");
|
|
1943
|
-
return Boolean(assistant?.parts.some((part) => part.type !== "status"));
|
|
1944
|
-
};
|
|
1945
|
-
const userTextBucketsForTurn = (turnId) => {
|
|
1946
|
-
const existing = hydratedUserTextByTurn.get(turnId);
|
|
1947
|
-
if (existing)
|
|
1948
|
-
return existing;
|
|
1949
|
-
const created = { real: new Set(), steer: new Set() };
|
|
1950
|
-
hydratedUserTextByTurn.set(turnId, created);
|
|
1951
|
-
return created;
|
|
1952
|
-
};
|
|
1953
|
-
const normalizedUserText = (text) => normalizeText(text);
|
|
1954
|
-
const hasHydratedUserMessageForTurn = (turnId, text, kind) => {
|
|
1955
|
-
const normalizedText = normalizedUserText(text);
|
|
1956
|
-
if (!normalizedText)
|
|
1957
|
-
return false;
|
|
1958
|
-
const buckets = hydratedUserTextByTurn.get(turnId);
|
|
1959
|
-
if (!buckets)
|
|
1960
|
-
return false;
|
|
1961
|
-
if (kind === "real")
|
|
1962
|
-
return buckets.real.has(normalizedText);
|
|
1963
|
-
if (kind === "steer")
|
|
1964
|
-
return buckets.steer.has(normalizedText);
|
|
1965
|
-
return buckets.real.has(normalizedText) || buckets.steer.has(normalizedText);
|
|
1966
|
-
};
|
|
1967
|
-
const hasAnyHydratedUserMessageForTurn = (turnId) => {
|
|
1968
|
-
const buckets = hydratedUserTextByTurn.get(turnId);
|
|
1969
|
-
return Boolean(buckets && (buckets.real.size > 0 || buckets.steer.size > 0));
|
|
1970
|
-
};
|
|
1971
|
-
const recordUserMessageForTurn = (turnId, text, kind) => {
|
|
1972
|
-
const normalizedText = normalizedUserText(text);
|
|
1973
|
-
if (!normalizedText)
|
|
1974
|
-
return;
|
|
1975
|
-
userTextBucketsForTurn(turnId)[kind].add(normalizedText);
|
|
1976
|
-
};
|
|
1977
|
-
const hasSourceUserMessageForTurn = (sourceItems, turnId, text) => {
|
|
1978
|
-
const normalizedText = normalizeText(text);
|
|
1979
|
-
if (!normalizedText)
|
|
1980
|
-
return false;
|
|
1981
|
-
for (const candidate of sourceItems) {
|
|
1982
|
-
if (candidate?.kind === "message" &&
|
|
1983
|
-
candidate.role === "user" &&
|
|
1984
|
-
candidate.turnId === turnId &&
|
|
1985
|
-
normalizeText(candidate.text) === normalizedText) {
|
|
1986
|
-
return true;
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
return false;
|
|
1990
|
-
};
|
|
1991
|
-
const hasSourceSteerControlForTurn = (sourceItems, turnId, text) => {
|
|
1992
|
-
const normalizedText = normalizeText(text);
|
|
1993
|
-
if (!normalizedText)
|
|
1994
|
-
return false;
|
|
1995
|
-
for (const candidate of sourceItems) {
|
|
1996
|
-
if (candidate?.kind === "control" &&
|
|
1997
|
-
candidate.action === "steer" &&
|
|
1998
|
-
candidate.turnId === turnId &&
|
|
1999
|
-
normalizeText(candidate.prompt) === normalizedText) {
|
|
2000
|
-
return true;
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
return false;
|
|
2004
|
-
};
|
|
2005
|
-
const pushUserMessageForTurn = (turnId, message, kind) => {
|
|
2006
|
-
const assistantId = assistantIdsByTurn.get(turnId);
|
|
2007
|
-
const assistantIndex = assistantId
|
|
2008
|
-
? projectionState.messages.findIndex((candidate) => candidate.id === assistantId)
|
|
2009
|
-
: -1;
|
|
2010
|
-
const shouldSplitAssistant = assistantId &&
|
|
2011
|
-
assistantIndex >= 0 &&
|
|
2012
|
-
assistantHasRenderableContent(assistantId) &&
|
|
2013
|
-
(kind === "steer" || hasAnyHydratedUserMessageForTurn(turnId));
|
|
2014
|
-
if (shouldSplitAssistant) {
|
|
2015
|
-
if (typeof message.createdAtMs === "number" && Number.isFinite(message.createdAtMs)) {
|
|
2016
|
-
const boundaries = assistantSplitBoundariesByTurn.get(turnId) ?? [];
|
|
2017
|
-
boundaries.push({ assistantId, atMs: message.createdAtMs });
|
|
2018
|
-
boundaries.sort((a, b) => a.atMs - b.atMs);
|
|
2019
|
-
assistantSplitBoundariesByTurn.set(turnId, boundaries);
|
|
2020
|
-
}
|
|
2021
|
-
mutations.pushMessage(message);
|
|
2022
|
-
const nextSegment = (assistantSegmentCountByTurn.get(turnId) ?? 0) + 1;
|
|
2023
|
-
assistantSegmentCountByTurn.set(turnId, nextSegment);
|
|
2024
|
-
assistantIdsByTurn.delete(turnId);
|
|
2025
|
-
}
|
|
2026
|
-
else if (assistantIndex >= 0) {
|
|
2027
|
-
projectionState.messages.splice(assistantIndex, 0, message);
|
|
2028
|
-
}
|
|
2029
|
-
else {
|
|
2030
|
-
mutations.pushMessage(message);
|
|
2031
|
-
}
|
|
2032
|
-
if (message.role === "user")
|
|
2033
|
-
recordUserMessageForTurn(turnId, message.content, kind);
|
|
2034
|
-
};
|
|
2035
|
-
const assistantIdForToolCall = (turnId, callId, startedAtMs) => {
|
|
2036
|
-
const existing = assistantIdByToolCallId.get(callId);
|
|
2037
|
-
if (existing)
|
|
2038
|
-
return existing;
|
|
2039
|
-
if (typeof startedAtMs === "number" && Number.isFinite(startedAtMs)) {
|
|
2040
|
-
const boundary = (assistantSplitBoundariesByTurn.get(turnId) ?? []).find((candidate) => startedAtMs <= candidate.atMs);
|
|
2041
|
-
if (boundary) {
|
|
2042
|
-
assistantIdByToolCallId.set(callId, boundary.assistantId);
|
|
2043
|
-
return boundary.assistantId;
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
const assistantId = ensureAssistantId(turnId);
|
|
2047
|
-
assistantIdByToolCallId.set(callId, assistantId);
|
|
2048
|
-
return assistantId;
|
|
2049
|
-
};
|
|
2050
|
-
const applyPendingApproval = (pendingApproval) => {
|
|
2051
|
-
approvalActive = pendingApproval;
|
|
2052
|
-
const assistantId = ensureAssistantId(pendingApproval.turnId);
|
|
2053
|
-
projector.apply(assistantId, {
|
|
2054
|
-
actionId: pendingApproval.id,
|
|
2055
|
-
args: toolInputsByCallId.get(pendingApproval.toolCallId) ?? {},
|
|
2056
|
-
availableScopes: pendingApproval.availableScopes,
|
|
2057
|
-
kind: "tool_approval",
|
|
2058
|
-
permission: approvalPermission(pendingApproval.action),
|
|
2059
|
-
reason: pendingApproval.reason,
|
|
2060
|
-
riskLevel: pendingApproval.riskClass ?? approvalRiskLevel(pendingApproval.action),
|
|
2061
|
-
toolCallId: pendingApproval.toolCallId,
|
|
2062
|
-
toolName: toolNamesByCallId.get(pendingApproval.toolCallId) ??
|
|
2063
|
-
canonicalToolName(pendingApproval.action),
|
|
2064
|
-
type: "action.required",
|
|
2065
|
-
});
|
|
2066
|
-
paused = {
|
|
2067
|
-
actionId: pendingApproval.id,
|
|
2068
|
-
kind: "tool_approval",
|
|
2069
|
-
toolCallId: pendingApproval.toolCallId,
|
|
2070
|
-
};
|
|
2071
|
-
};
|
|
2072
|
-
const applyPendingAction = (pendingAction) => {
|
|
2073
|
-
const assistantId = ensureAssistantId(pendingAction.turnId);
|
|
2074
|
-
projector.apply(assistantId, {
|
|
2075
|
-
actionId: pendingAction.actionId,
|
|
2076
|
-
kind: "ask_user",
|
|
2077
|
-
questions: pendingAction.questions,
|
|
2078
|
-
...(pendingAction.toolCallId ? { toolCallId: pendingAction.toolCallId } : {}),
|
|
2079
|
-
toolName: pendingAction.toolName,
|
|
2080
|
-
type: "action.required",
|
|
2081
|
-
});
|
|
2082
|
-
paused = {
|
|
2083
|
-
actionId: pendingAction.actionId,
|
|
2084
|
-
kind: "ask_user",
|
|
2085
|
-
...(pendingAction.toolCallId ? { toolCallId: pendingAction.toolCallId } : {}),
|
|
2086
|
-
questions: normalizeAskUserQuestions(pendingAction.questions),
|
|
2087
|
-
};
|
|
2088
|
-
};
|
|
2089
|
-
const eventHydratedItemIds = new Set();
|
|
2090
|
-
const eventHydratedActionIds = new Set();
|
|
2091
|
-
const eventHydratedApprovalIds = new Set();
|
|
2092
|
-
const turnsWithReasoningStreams = new Set();
|
|
2093
|
-
for (const envelope of input.events ?? []) {
|
|
2094
|
-
const event = envelope.event;
|
|
2095
|
-
if (event.type === "turn.reasoning.started" ||
|
|
2096
|
-
event.type === "turn.reasoning.delta" ||
|
|
2097
|
-
event.type === "turn.reasoning.completed") {
|
|
2098
|
-
turnsWithReasoningStreams.add(event.turnId);
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
const applyItem = (item, sourceItems) => {
|
|
2102
|
-
if (item.kind === "message" && item.role === "user") {
|
|
2103
|
-
const isConsumedSteer = hasSourceSteerControlForTurn(sourceItems, item.turnId, item.text);
|
|
2104
|
-
if (hasHydratedUserMessageForTurn(item.turnId, item.text, "steer") ||
|
|
2105
|
-
hasHydratedUserMessageForTurn(item.turnId, item.text, "real")) {
|
|
2106
|
-
return;
|
|
2107
|
-
}
|
|
2108
|
-
pushUserMessageForTurn(item.turnId, {
|
|
2109
|
-
content: item.text,
|
|
2110
|
-
...(parseIsoTimeMs(item.createdAt) != null
|
|
2111
|
-
? { createdAtMs: parseIsoTimeMs(item.createdAt) }
|
|
2112
|
-
: {}),
|
|
2113
|
-
id: item.id,
|
|
2114
|
-
...(isConsumedSteer
|
|
2115
|
-
? { metadata: { originalEventType: "steer_injected", steer: true } }
|
|
2116
|
-
: {}),
|
|
2117
|
-
role: "user",
|
|
2118
|
-
}, isConsumedSteer ? "steer" : "real");
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
if (item.kind === "message" && item.role === "agent") {
|
|
2122
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2123
|
-
message: { content: item.text },
|
|
2124
|
-
originalEventType: "thread_item_message",
|
|
2125
|
-
type: "assistant.message",
|
|
2126
|
-
});
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
if (item.kind === "reasoning") {
|
|
2130
|
-
if (isDraftReasoningItem(item) && !input.includeDraftReasoning)
|
|
2131
|
-
return;
|
|
2132
|
-
if (turnsWithReasoningStreams.has(item.turnId))
|
|
2133
|
-
return;
|
|
2134
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2135
|
-
content: item.content ?? item.summary,
|
|
2136
|
-
partId: createThinkingPartIdFromReasoningItem(item.id),
|
|
2137
|
-
type: "thinking.finished",
|
|
2138
|
-
});
|
|
2139
|
-
return;
|
|
2140
|
-
}
|
|
2141
|
-
if (item.kind === "tool-call") {
|
|
2142
|
-
const callId = resolveToolItemCallId(item);
|
|
2143
|
-
const parsedInput = parseToolInput(item.input);
|
|
2144
|
-
toolInputsByCallId.set(callId, parsedInput);
|
|
2145
|
-
toolNamesByCallId.set(callId, canonicalToolName(item.toolName));
|
|
2146
|
-
const toolAssistantId = assistantIdForToolCall(item.turnId, callId, parseIsoTimeMs(item.createdAt));
|
|
2147
|
-
projector.apply(toolAssistantId, {
|
|
2148
|
-
input: parsedInput,
|
|
2149
|
-
originalEventType: "tool_call_end",
|
|
2150
|
-
startedAtMs: parseIsoTimeMs(item.createdAt) ?? undefined,
|
|
2151
|
-
toolCallId: callId,
|
|
2152
|
-
toolName: canonicalToolName(item.toolName),
|
|
2153
|
-
type: "tool.started",
|
|
2154
|
-
});
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
if (item.kind === "tool-result") {
|
|
2158
|
-
const toolCallId = readExplicitToolCallId(item) ?? normalizeText(item.id);
|
|
2159
|
-
toolNamesByCallId.set(toolCallId, canonicalToolName(item.toolName));
|
|
2160
|
-
projector.apply(assistantIdForToolCall(item.turnId, toolCallId, toolCallStartedAtMsByCallId.get(toolCallId) ?? parseIsoTimeMs(item.createdAt)), {
|
|
2161
|
-
...(item.errorCode ? { errorCode: item.errorCode } : {}),
|
|
2162
|
-
...(item.failureKind ? { failureKind: item.failureKind } : {}),
|
|
2163
|
-
isError: item.outcome === "error",
|
|
2164
|
-
output: {
|
|
2165
|
-
output: item.output,
|
|
2166
|
-
...(item.structuredContent !== undefined
|
|
2167
|
-
? { structuredContent: item.structuredContent }
|
|
2168
|
-
: {}),
|
|
2169
|
-
},
|
|
2170
|
-
toolCallId,
|
|
2171
|
-
toolName: canonicalToolName(item.toolName),
|
|
2172
|
-
type: "tool.finished",
|
|
2173
|
-
});
|
|
2174
|
-
return;
|
|
2175
|
-
}
|
|
2176
|
-
if (item.kind === "control") {
|
|
2177
|
-
if (item.action === "steer" && item.prompt) {
|
|
2178
|
-
if (input.renderPendingSteerControls === true &&
|
|
2179
|
-
!hasHydratedUserMessageForTurn(item.turnId, item.prompt) &&
|
|
2180
|
-
!hasSourceUserMessageForTurn(sourceItems, item.turnId, item.prompt)) {
|
|
2181
|
-
pushUserMessageForTurn(item.turnId, {
|
|
2182
|
-
content: item.prompt,
|
|
2183
|
-
...(parseIsoTimeMs(item.createdAt) != null
|
|
2184
|
-
? { createdAtMs: parseIsoTimeMs(item.createdAt) }
|
|
2185
|
-
: {}),
|
|
2186
|
-
id: `${item.id}::steer`,
|
|
2187
|
-
metadata: {
|
|
2188
|
-
originalEventType: "steer_injected",
|
|
2189
|
-
pending: true,
|
|
2190
|
-
steer: true,
|
|
2191
|
-
},
|
|
2192
|
-
role: "user",
|
|
2193
|
-
}, "steer");
|
|
2194
|
-
}
|
|
2195
|
-
return;
|
|
2196
|
-
}
|
|
2197
|
-
if (item.action === "child-thread-started" && item.childThreadId) {
|
|
2198
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2199
|
-
subagentId: item.childThreadId,
|
|
2200
|
-
taskDescription: normalizeText(item.summary) || item.childThreadId,
|
|
2201
|
-
type: "subagent.started",
|
|
2202
|
-
});
|
|
2203
|
-
markHydratedSubagent(item.turnId, item.childThreadId);
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
|
-
if (item.action === "child-thread-paused" && item.childThreadId) {
|
|
2207
|
-
if (!hasHydratedSubagent(item.turnId, item.childThreadId)) {
|
|
2208
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2209
|
-
subagentId: item.childThreadId,
|
|
2210
|
-
taskDescription: item.childThreadId,
|
|
2211
|
-
type: "subagent.started",
|
|
2212
|
-
});
|
|
2213
|
-
markHydratedSubagent(item.turnId, item.childThreadId);
|
|
2214
|
-
}
|
|
2215
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2216
|
-
...(item.requestId ? { requestId: item.requestId } : {}),
|
|
2217
|
-
state: "paused",
|
|
2218
|
-
subagentId: item.childThreadId,
|
|
2219
|
-
type: "subagent.state",
|
|
2220
|
-
});
|
|
2221
|
-
paused = {
|
|
2222
|
-
actionId: item.requestId,
|
|
2223
|
-
childThreadId: item.childThreadId,
|
|
2224
|
-
kind: "subagent_wait",
|
|
2225
|
-
};
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
if (item.action === "child-thread-resumed" && item.childThreadId) {
|
|
2229
|
-
if (!hasHydratedSubagent(item.turnId, item.childThreadId)) {
|
|
2230
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2231
|
-
subagentId: item.childThreadId,
|
|
2232
|
-
taskDescription: item.childThreadId,
|
|
2233
|
-
type: "subagent.started",
|
|
2234
|
-
});
|
|
2235
|
-
markHydratedSubagent(item.turnId, item.childThreadId);
|
|
2236
|
-
}
|
|
2237
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2238
|
-
...(item.requestId ? { requestId: item.requestId } : {}),
|
|
2239
|
-
state: "running",
|
|
2240
|
-
subagentId: item.childThreadId,
|
|
2241
|
-
type: "subagent.state",
|
|
2242
|
-
});
|
|
2243
|
-
if (paused?.kind === "subagent_wait" &&
|
|
2244
|
-
paused.childThreadId === item.childThreadId &&
|
|
2245
|
-
(!item.requestId || paused.actionId === item.requestId)) {
|
|
2246
|
-
paused = null;
|
|
2247
|
-
}
|
|
2248
|
-
return;
|
|
2249
|
-
}
|
|
2250
|
-
if (item.action === "child-thread-completed" && item.childThreadId) {
|
|
2251
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2252
|
-
subagentId: item.childThreadId,
|
|
2253
|
-
taskDescription: normalizeText(item.summary) || item.childThreadId,
|
|
2254
|
-
type: "subagent.started",
|
|
2255
|
-
});
|
|
2256
|
-
markHydratedSubagent(item.turnId, item.childThreadId);
|
|
2257
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2258
|
-
state: "completed",
|
|
2259
|
-
subagentId: item.childThreadId,
|
|
2260
|
-
type: "subagent.finished",
|
|
2261
|
-
});
|
|
2262
|
-
if (paused?.kind === "subagent_wait" && paused.childThreadId === item.childThreadId) {
|
|
2263
|
-
paused = null;
|
|
2264
|
-
}
|
|
2265
|
-
return;
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
if (item.kind === "error") {
|
|
2269
|
-
if (item.scope === "child-thread" && item.childThreadId) {
|
|
2270
|
-
if (!hasHydratedSubagent(item.turnId, item.childThreadId)) {
|
|
2271
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2272
|
-
subagentId: item.childThreadId,
|
|
2273
|
-
taskDescription: item.childThreadId,
|
|
2274
|
-
type: "subagent.started",
|
|
2275
|
-
});
|
|
2276
|
-
markHydratedSubagent(item.turnId, item.childThreadId);
|
|
2277
|
-
}
|
|
2278
|
-
if (normalizeText(item.message)) {
|
|
2279
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2280
|
-
delta: item.message,
|
|
2281
|
-
stream: "error",
|
|
2282
|
-
subagentId: item.childThreadId,
|
|
2283
|
-
subagentTurnId: item.turnId,
|
|
2284
|
-
type: "subagent.output",
|
|
2285
|
-
});
|
|
2286
|
-
}
|
|
2287
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2288
|
-
state: "failed",
|
|
2289
|
-
subagentId: item.childThreadId,
|
|
2290
|
-
type: "subagent.finished",
|
|
2291
|
-
});
|
|
2292
|
-
if (paused?.kind === "subagent_wait" && paused.childThreadId === item.childThreadId) {
|
|
2293
|
-
paused = null;
|
|
2294
|
-
}
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
|
-
projector.apply(ensureAssistantId(item.turnId), {
|
|
2298
|
-
message: item.message,
|
|
2299
|
-
type: "error.recorded",
|
|
2300
|
-
});
|
|
2301
|
-
}
|
|
2302
|
-
};
|
|
2303
|
-
for (const envelope of input.events ?? []) {
|
|
2304
|
-
const event = envelope.event;
|
|
2305
|
-
if (event.type === "turn.reasoning.started" && !event.parentTurnId) {
|
|
2306
|
-
projector.apply(ensureAssistantId(event.turnId), {
|
|
2307
|
-
partId: createThinkingPartIdFromStream(event.turnId, event.streamId),
|
|
2308
|
-
type: "thinking.started",
|
|
2309
|
-
});
|
|
2310
|
-
continue;
|
|
2311
|
-
}
|
|
2312
|
-
if (event.type === "turn.reasoning.delta" && !event.parentTurnId) {
|
|
2313
|
-
projector.apply(ensureAssistantId(event.turnId), {
|
|
2314
|
-
delta: event.delta,
|
|
2315
|
-
partId: createThinkingPartIdFromStream(event.turnId, event.streamId),
|
|
2316
|
-
type: "thinking.delta",
|
|
2317
|
-
});
|
|
2318
|
-
continue;
|
|
2319
|
-
}
|
|
2320
|
-
if (event.type === "turn.reasoning.completed" && !event.parentTurnId) {
|
|
2321
|
-
projector.apply(ensureAssistantId(event.turnId), {
|
|
2322
|
-
partId: createThinkingPartIdFromStream(event.turnId, event.streamId),
|
|
2323
|
-
type: "thinking.finished",
|
|
2324
|
-
});
|
|
2325
|
-
continue;
|
|
2326
|
-
}
|
|
2327
|
-
if (event.type === "turn.tool-call.started" && !event.parentTurnId) {
|
|
2328
|
-
toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
2329
|
-
const toolAssistantId = assistantIdForToolCall(event.turnId, event.callId, toolCallStartedAtMsByCallId.get(event.callId) ?? parseIsoTimeMs(envelope.createdAt));
|
|
2330
|
-
projector.apply(toolAssistantId, {
|
|
2331
|
-
input: {},
|
|
2332
|
-
originalEventType: "tool_call",
|
|
2333
|
-
toolCallId: event.callId,
|
|
2334
|
-
toolName: canonicalToolName(event.toolName),
|
|
2335
|
-
type: "tool.started",
|
|
2336
|
-
});
|
|
2337
|
-
continue;
|
|
2338
|
-
}
|
|
2339
|
-
if (event.type === "turn.tool-call.delta" && !event.parentTurnId) {
|
|
2340
|
-
toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
2341
|
-
projector.apply(assistantIdForToolCall(event.turnId, event.callId, toolCallStartedAtMsByCallId.get(event.callId) ?? parseIsoTimeMs(envelope.createdAt)), {
|
|
2342
|
-
delta: event.delta,
|
|
2343
|
-
kind: "input",
|
|
2344
|
-
toolCallId: event.callId,
|
|
2345
|
-
toolName: canonicalToolName(event.toolName),
|
|
2346
|
-
type: "tool.delta",
|
|
2347
|
-
});
|
|
2348
|
-
continue;
|
|
2349
|
-
}
|
|
2350
|
-
if (event.type === "turn.text.delta" && !event.parentTurnId) {
|
|
2351
|
-
projector.apply(ensureAssistantId(event.turnId), {
|
|
2352
|
-
delta: event.delta,
|
|
2353
|
-
streamId: event.streamId,
|
|
2354
|
-
type: "assistant.delta",
|
|
2355
|
-
});
|
|
2356
|
-
continue;
|
|
2357
|
-
}
|
|
2358
|
-
if (event.type === "turn.text.stream.reset" && !event.parentTurnId) {
|
|
2359
|
-
projector.apply(ensureAssistantId(event.turnId), {
|
|
2360
|
-
streamId: event.streamId,
|
|
2361
|
-
type: "assistant.reset",
|
|
2362
|
-
});
|
|
2363
|
-
continue;
|
|
2364
|
-
}
|
|
2365
|
-
if (event.type === "turn.item.added") {
|
|
2366
|
-
eventHydratedItemIds.add(event.item.id);
|
|
2367
|
-
applyItem(event.item, input.items);
|
|
2368
|
-
continue;
|
|
2369
|
-
}
|
|
2370
|
-
if (event.type === "approval.requested") {
|
|
2371
|
-
if (input.pendingApproval?.id === event.approval.id) {
|
|
2372
|
-
eventHydratedApprovalIds.add(event.approval.id);
|
|
2373
|
-
applyPendingApproval(event.approval);
|
|
2374
|
-
}
|
|
2375
|
-
continue;
|
|
2376
|
-
}
|
|
2377
|
-
if (event.type === "action.required" && event.kind === "ask_user") {
|
|
2378
|
-
if (input.pendingAction?.actionId === event.actionId) {
|
|
2379
|
-
eventHydratedActionIds.add(event.actionId);
|
|
2380
|
-
applyPendingAction({
|
|
2381
|
-
actionId: event.actionId,
|
|
2382
|
-
questions: event.questions,
|
|
2383
|
-
toolCallId: normalizeText(event.toolCallId) || undefined,
|
|
2384
|
-
toolName: canonicalToolName(event.toolName),
|
|
2385
|
-
turnId: event.turnId,
|
|
2386
|
-
});
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
for (let itemIndex = 0; itemIndex < input.items.length; itemIndex++) {
|
|
2391
|
-
const item = input.items[itemIndex];
|
|
2392
|
-
if (eventHydratedItemIds.has(item.id))
|
|
2393
|
-
continue;
|
|
2394
|
-
applyItem(item, input.items);
|
|
2395
|
-
}
|
|
2396
|
-
if (input.pendingApproval && !eventHydratedApprovalIds.has(input.pendingApproval.id)) {
|
|
2397
|
-
applyPendingApproval(input.pendingApproval);
|
|
2398
|
-
}
|
|
2399
|
-
if (input.pendingAction && !eventHydratedActionIds.has(input.pendingAction.actionId)) {
|
|
2400
|
-
applyPendingAction(input.pendingAction);
|
|
2401
|
-
}
|
|
2402
|
-
const turnIdsToFinalize = new Set([
|
|
2403
|
-
...assistantIdHistoryByTurn.keys(),
|
|
2404
|
-
...assistantIdsByTurn.keys(),
|
|
2405
|
-
...interruptedTurnIds,
|
|
2406
|
-
]);
|
|
2407
|
-
for (const turnId of turnIdsToFinalize) {
|
|
2408
|
-
let interruptedAssistantId = assistantIdsByTurn.get(turnId);
|
|
2409
|
-
if (interruptedTurnIds.has(turnId) && !interruptedAssistantId) {
|
|
2410
|
-
interruptedAssistantId = ensureAssistantId(turnId);
|
|
2411
|
-
}
|
|
2412
|
-
if (interruptedTurnIds.has(turnId)) {
|
|
2413
|
-
const assistantId = interruptedAssistantId ?? ensureAssistantId(turnId);
|
|
2414
|
-
const startedReasoningStreams = startedReasoningStreamsByTurn.get(turnId) ?? new Set();
|
|
2415
|
-
const completedReasoningStreams = completedReasoningStreamsByTurn.get(turnId) ?? new Set();
|
|
2416
|
-
const unfinishedThinkingPartIds = [...startedReasoningStreams]
|
|
2417
|
-
.filter((streamId) => !completedReasoningStreams.has(streamId))
|
|
2418
|
-
.map((streamId) => createThinkingPartIdFromStream(turnId, streamId));
|
|
2419
|
-
cleanupInterruptedBridgeAssistantMessage(projectionState.messages, assistantId, unfinishedThinkingPartIds);
|
|
2420
|
-
const assistant = projectionState.messages.find((message) => message.id === assistantId && message.role === "assistant");
|
|
2421
|
-
const alreadyMarked = assistant?.parts.some((part) => part.type === "markdown" &&
|
|
2422
|
-
(String(part.markdown ?? "").includes(t("chatStatus.interruptedMarkdown")) ||
|
|
2423
|
-
String(part.markdown ?? "").includes("Interrupted") ||
|
|
2424
|
-
String(part.markdown ?? "").includes("已中断")));
|
|
2425
|
-
if (!alreadyMarked) {
|
|
2426
|
-
mutations.appendAssistantMarkdown(assistantId, bridgeInterruptedMarkdown(bridgeAssistantHasMarkdownOutput(projectionState.messages, assistantId)));
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
const assistantIds = assistantIdHistoryByTurn.get(turnId) ?? [];
|
|
2430
|
-
for (const assistantId of assistantIds) {
|
|
2431
|
-
const assistant = projectionState.messages.find((message) => message.id === assistantId && message.role === "assistant");
|
|
2432
|
-
if (!assistant)
|
|
2433
|
-
continue;
|
|
2434
|
-
const hasContent = assistant.parts.some((part) => part.type !== "status");
|
|
2435
|
-
if (!hasContent)
|
|
2436
|
-
continue;
|
|
2437
|
-
const hasVisibleReasoning = assistant.parts.some((part) => {
|
|
2438
|
-
if (part.type !== "thinking")
|
|
2439
|
-
return false;
|
|
2440
|
-
return hasVisibleReasoningText(part.content);
|
|
2441
|
-
});
|
|
2442
|
-
const turnStatus = turnsById.get(turnId)?.status;
|
|
2443
|
-
const reasoningState = hasVisibleReasoning
|
|
2444
|
-
? "visible"
|
|
2445
|
-
: reasoningDiagnosticsByTurn[turnId];
|
|
2446
|
-
if (reasoningState) {
|
|
2447
|
-
mutations.appendInfoPart(assistantId, "", buildReasoningInfoPart(reasoningState));
|
|
2448
|
-
}
|
|
2449
|
-
if (input.pendingApproval?.turnId === turnId) {
|
|
2450
|
-
mutations.setAssistantStatus(assistantId, t("chatStatus.awaitingApproval"));
|
|
2451
|
-
}
|
|
2452
|
-
else if (input.pendingAction?.turnId === turnId) {
|
|
2453
|
-
mutations.setAssistantStatus(assistantId, t("chatStatus.awaitingAnswers"));
|
|
2454
|
-
}
|
|
2455
|
-
else if (turnStatus === "completed") {
|
|
2456
|
-
mutations.setAssistantStatus(assistantId, t("chatStatus.done"));
|
|
2457
|
-
}
|
|
2458
|
-
else if (turnStatus === "interrupted") {
|
|
2459
|
-
mutations.setAssistantStatus(assistantId, t("chatStatus.interrupted"));
|
|
2460
|
-
}
|
|
2461
|
-
else if (turnStatus === "failed" || turnStatus === "blocked") {
|
|
2462
|
-
mutations.setAssistantStatus(assistantId, t("chatStatus.error"));
|
|
2463
|
-
}
|
|
2464
|
-
mutations.compactSettledSubagentTranscript(assistantId);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
const messages = input.hideInitialUserMessages
|
|
2468
|
-
? removeHiddenSubagentUserMessages(projectionState.messages)
|
|
2469
|
-
: projectionState.messages;
|
|
2470
|
-
return {
|
|
2471
|
-
approvalActive,
|
|
2472
|
-
messages,
|
|
2473
|
-
paused,
|
|
2474
|
-
userHistory: projectionState.userHistory,
|
|
2475
|
-
};
|
|
2476
|
-
}
|
|
2477
|
-
async function loadThreadProjection(runtime, threadId, options) {
|
|
2478
|
-
const mode = options?.mode ?? "full";
|
|
2479
|
-
return await withPerfSpanAsync(`loadThreadProjection.${mode}`, `threadId=${threadId}`, async () => {
|
|
2480
|
-
const resumed = await withPerfSpanAsync("runtime.resumeThread", `threadId=${threadId}`, () => mode === "fast"
|
|
2481
|
-
? runtime.resumeThread(threadId, { turnLimit: FAST_THREAD_RESUME_TURN_LIMIT })
|
|
2482
|
-
: runtime.resumeThread(threadId));
|
|
2483
|
-
const hydrated = { items: resumed.items, turns: resumed.turns };
|
|
2484
|
-
const events = mode === "fast" ? [] : await loadThreadEventsForHydration(runtime, threadId);
|
|
2485
|
-
const hydrateDetail = `threadId=${threadId},items=${hydrated.items.length}/${resumed.items.length}`;
|
|
2486
|
-
const projection = withPerfSpan("hydrateThreadItems", hydrateDetail, () => hydrateThreadItems({
|
|
2487
|
-
events,
|
|
2488
|
-
items: hydrated.items,
|
|
2489
|
-
...(resumed.pendingAction ? { pendingAction: resumed.pendingAction } : {}),
|
|
2490
|
-
...(resumed.pendingApproval ? { pendingApproval: resumed.pendingApproval } : {}),
|
|
2491
|
-
renderPendingSteerControls: true,
|
|
2492
|
-
thread: resumed.thread,
|
|
2493
|
-
turns: hydrated.turns,
|
|
2494
|
-
}));
|
|
2495
|
-
return {
|
|
2496
|
-
...projection,
|
|
2497
|
-
...(mode === "fast" && resumed.historyHasMore !== undefined
|
|
2498
|
-
? { historyHasMore: resumed.historyHasMore }
|
|
2499
|
-
: {}),
|
|
2500
|
-
...(mode === "fast" && resumed.historyNextBeforeItemId
|
|
2501
|
-
? { historyNextBeforeItemId: resumed.historyNextBeforeItemId }
|
|
2502
|
-
: {}),
|
|
2503
|
-
...(mode === "fast" && resumed.historyNextBeforeTurnId
|
|
2504
|
-
? { historyNextBeforeTurnId: resumed.historyNextBeforeTurnId }
|
|
2505
|
-
: {}),
|
|
2506
|
-
};
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2509
|
-
async function loadThreadEventsForHydration(runtime, threadId) {
|
|
2510
|
-
return await withPerfSpanAsync("loadThreadEventsForHydration", `threadId=${threadId}`, async () => {
|
|
2511
|
-
const getThreadEvents = runtime.getThreadEvents?.bind(runtime);
|
|
2512
|
-
if (!getThreadEvents)
|
|
2513
|
-
return [];
|
|
2514
|
-
const events = [];
|
|
2515
|
-
let afterSequence;
|
|
2516
|
-
for (let page = 0; page < 100; page += 1) {
|
|
2517
|
-
const response = await getThreadEvents({
|
|
2518
|
-
...(afterSequence !== undefined ? { afterSequence } : {}),
|
|
2519
|
-
limit: 500,
|
|
2520
|
-
threadId,
|
|
2521
|
-
});
|
|
2522
|
-
events.push(...response.events);
|
|
2523
|
-
if (!response.hasMore || response.nextAfterSequence === undefined)
|
|
2524
|
-
break;
|
|
2525
|
-
afterSequence = response.nextAfterSequence;
|
|
2526
|
-
}
|
|
2527
|
-
return events;
|
|
2528
|
-
});
|
|
2529
|
-
}
|
|
2530
|
-
function syncThreadMetadataIntoStore(store, thread, opts) {
|
|
2531
|
-
if (!store)
|
|
2532
|
-
return;
|
|
2533
|
-
const incoming = normalizeBridgeSessionUiMetadata(sessionUiMetadata(thread));
|
|
2534
|
-
let next = incoming;
|
|
2535
|
-
if (opts?.preserveLocalSessionMeta && store.state.sessionMeta) {
|
|
2536
|
-
const local = normalizeBridgeSessionUiMetadata(store.state.sessionMeta);
|
|
2537
|
-
const incomingUpdatedAt = Number(incoming.lastUpdatedAt ?? 0);
|
|
2538
|
-
const localUpdatedAt = Number(local.lastUpdatedAt ?? 0);
|
|
2539
|
-
const preserveLocal = localUpdatedAt > incomingUpdatedAt ||
|
|
2540
|
-
(local.queue.length > 0 &&
|
|
2541
|
-
incoming.queue.length === 0 &&
|
|
2542
|
-
localUpdatedAt >= incomingUpdatedAt);
|
|
2543
|
-
if (preserveLocal)
|
|
2544
|
-
next = local;
|
|
2545
|
-
}
|
|
2546
|
-
const target = store.state.sessionMeta;
|
|
2547
|
-
if (target) {
|
|
2548
|
-
target.queue = [...next.queue];
|
|
2549
|
-
target.queueLength = next.queueLength;
|
|
2550
|
-
target.status = next.status;
|
|
2551
|
-
target.lastUpdatedAt = next.lastUpdatedAt;
|
|
2552
|
-
target.lastErrorCode = next.lastErrorCode ?? undefined;
|
|
2553
|
-
target.pinnedMessageIds = [...next.pinnedMessageIds];
|
|
2554
|
-
target.titleSuggestedAt = next.titleSuggestedAt ?? undefined;
|
|
2555
|
-
target.reasoningDiagnosticsByTurn = {
|
|
2556
|
-
...next.reasoningDiagnosticsByTurn,
|
|
2557
|
-
};
|
|
2558
|
-
return;
|
|
2559
|
-
}
|
|
2560
|
-
store.state.sessionMeta = next;
|
|
2561
|
-
}
|
|
2562
|
-
function clearStorePendingApproval(store) {
|
|
2563
|
-
const approval = store.state.approval;
|
|
2564
|
-
if (!approval?.active)
|
|
2565
|
-
return false;
|
|
2566
|
-
const active = asRecord(approval.active);
|
|
2567
|
-
const activeId = normalizeText(active.id);
|
|
2568
|
-
const activeToolCallId = normalizeText(active.toolCallId);
|
|
2569
|
-
approval.active = null;
|
|
2570
|
-
approval.pendingAutoRun = null;
|
|
2571
|
-
if (Array.isArray(approval.queue))
|
|
2572
|
-
approval.queue = [];
|
|
2573
|
-
if (approval.decisions && typeof approval.decisions === "object")
|
|
2574
|
-
approval.decisions = {};
|
|
2575
|
-
if ("skipApproval" in approval)
|
|
2576
|
-
approval.skipApproval = false;
|
|
2577
|
-
const paused = asRecord(store.state.paused);
|
|
2578
|
-
if (!paused.kind ||
|
|
2579
|
-
paused.kind === "tool_approval" ||
|
|
2580
|
-
normalizeText(paused.actionId) === activeId ||
|
|
2581
|
-
normalizeText(paused.toolCallId) === activeToolCallId) {
|
|
2582
|
-
store.state.paused = null;
|
|
2583
|
-
}
|
|
2584
|
-
store.state.runningAgentRound = false;
|
|
2585
|
-
store.state.runningToolCallId = null;
|
|
2586
|
-
if (activeId || activeToolCallId) {
|
|
2587
|
-
store.state.messages = store.state.messages.map((message) => {
|
|
2588
|
-
if (message.role !== "assistant")
|
|
2589
|
-
return message;
|
|
2590
|
-
let changed = false;
|
|
2591
|
-
const parts = message.parts.map((part) => {
|
|
2592
|
-
if (part.type !== "approve")
|
|
2593
|
-
return part;
|
|
2594
|
-
const request = asRecord(part.request);
|
|
2595
|
-
const sameRequest = (activeId && normalizeText(request.id) === activeId) ||
|
|
2596
|
-
(activeToolCallId && normalizeText(request.toolCallId) === activeToolCallId);
|
|
2597
|
-
if (!sameRequest || request.status !== "pending")
|
|
2598
|
-
return part;
|
|
2599
|
-
changed = true;
|
|
2600
|
-
return {
|
|
2601
|
-
...part,
|
|
2602
|
-
request: {
|
|
2603
|
-
...part.request,
|
|
2604
|
-
status: "denied",
|
|
2605
|
-
},
|
|
2606
|
-
};
|
|
2607
|
-
});
|
|
2608
|
-
return changed ? { ...message, parts } : message;
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
return true;
|
|
2612
|
-
}
|
|
2613
|
-
function applyThreadProjectionToStore(store, thread, projection) {
|
|
2614
|
-
if (!store)
|
|
2615
|
-
return;
|
|
2616
|
-
if (typeof store.setCwd === "function") {
|
|
2617
|
-
store.setCwd(thread.cwd);
|
|
2618
|
-
}
|
|
2619
|
-
else {
|
|
2620
|
-
store.state.context = {
|
|
2621
|
-
...store.state.context,
|
|
2622
|
-
cwd: thread.cwd,
|
|
2623
|
-
};
|
|
2624
|
-
}
|
|
2625
|
-
if (typeof store.replaceMessages === "function") {
|
|
2626
|
-
store.replaceMessages(projection.messages);
|
|
2627
|
-
}
|
|
2628
|
-
else {
|
|
2629
|
-
store.state.messages = projection.messages;
|
|
2630
|
-
if (Array.isArray(store.state.userHistory)) {
|
|
2631
|
-
store.state.userHistory.splice(0, store.state.userHistory.length, ...projection.userHistory);
|
|
2632
|
-
}
|
|
2633
|
-
else {
|
|
2634
|
-
store.state.userHistory = [...projection.userHistory];
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
store.state.paused = projection.paused;
|
|
2638
|
-
store.state.runningAgentRound = false;
|
|
2639
|
-
store.state.runningToolCallId = null;
|
|
2640
|
-
store.state.sessionId = thread.id;
|
|
2641
|
-
if (store.state.usage) {
|
|
2642
|
-
store.state.usage.prompt_tokens = 0;
|
|
2643
|
-
store.state.usage.completion_tokens = 0;
|
|
2644
|
-
store.state.usage.total_tokens = 0;
|
|
2645
|
-
}
|
|
2646
|
-
if ("lastRequestBudget" in store.state)
|
|
2647
|
-
store.state.lastRequestBudget = null;
|
|
2648
|
-
restoreThreadUiMetricsIntoStore(store, thread);
|
|
2649
|
-
if ("chromeVersion" in store.state && typeof store.state.chromeVersion === "number")
|
|
2650
|
-
store.state.chromeVersion += 1;
|
|
2651
|
-
syncThreadMetadataIntoStore(store, thread);
|
|
2652
|
-
store.setMode?.(cliModeFromThreadMode(thread.mode));
|
|
2653
|
-
if (store.state.ui)
|
|
2654
|
-
store.state.ui.mode = cliModeFromThreadMode(thread.mode);
|
|
2655
|
-
if (store.state.revertedMessages)
|
|
2656
|
-
store.state.revertedMessages = [];
|
|
2657
|
-
if (store.state.approval) {
|
|
2658
|
-
store.state.approval.active = projection.approvalActive;
|
|
2659
|
-
store.state.approval.pendingAutoRun = null;
|
|
2660
|
-
if ("skipApproval" in store.state.approval)
|
|
2661
|
-
store.state.approval.skipApproval = false;
|
|
2662
|
-
}
|
|
2663
|
-
if (store.state.thinking) {
|
|
2664
|
-
store.state.thinking.active = false;
|
|
2665
|
-
store.state.thinking.endedAt = null;
|
|
2666
|
-
store.state.thinking.startedAt = null;
|
|
2667
|
-
}
|
|
2668
|
-
if (store.state.ui?.restore) {
|
|
2669
|
-
store.state.ui.restore = { status: "idle", sessionId: thread.id };
|
|
2670
|
-
}
|
|
2671
|
-
if (store.state.ui?.activeSubagentIds) {
|
|
2672
|
-
store.state.ui.activeSubagentIds = [];
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
function setThreadRestoreStatus(store, status, threadId) {
|
|
2676
|
-
if (!store?.state.ui?.restore)
|
|
2677
|
-
return;
|
|
2678
|
-
if (status !== "loading" && store.state.ui.restore.sessionId !== threadId)
|
|
2679
|
-
return;
|
|
2680
|
-
store.state.ui.restore = { status, sessionId: threadId };
|
|
2681
|
-
}
|
|
2682
|
-
function approvalPermission(toolName) {
|
|
2683
|
-
const semantic = toolSemantic(toolName);
|
|
2684
|
-
return semantic === "read_like" || semantic === "search_like" ? "pass" : "require_action";
|
|
2685
|
-
}
|
|
2686
|
-
function approvalRiskLevel(toolName) {
|
|
2687
|
-
switch (toolSemantic(toolName)) {
|
|
2688
|
-
case "read_like":
|
|
2689
|
-
case "search_like":
|
|
2690
|
-
return "safe";
|
|
2691
|
-
case "write_like":
|
|
2692
|
-
return "high";
|
|
2693
|
-
case "shell_like":
|
|
2694
|
-
return "critical";
|
|
2695
|
-
default:
|
|
2696
|
-
return "medium";
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
function buildChildThreadText(event) {
|
|
2700
|
-
const item = event.item;
|
|
2701
|
-
if (item.kind === "tool-call")
|
|
2702
|
-
return `${canonicalToolName(item.toolName)} ${item.input}`;
|
|
2703
|
-
if (item.kind === "tool-result")
|
|
2704
|
-
return item.output;
|
|
2705
|
-
if (item.kind === "message" && item.role === "agent")
|
|
2706
|
-
return item.text;
|
|
2707
|
-
if (item.kind === "error")
|
|
2708
|
-
return item.message;
|
|
2709
|
-
return null;
|
|
2710
|
-
}
|
|
2711
|
-
function childTaskSubagentId(event) {
|
|
2712
|
-
return (normalizeText(event.record.childThreadId) ||
|
|
2713
|
-
normalizeText(event.record.childTaskId) ||
|
|
2714
|
-
normalizeText(event.record.correlationId));
|
|
2715
|
-
}
|
|
2716
|
-
function childStreamSubagentId(event) {
|
|
2717
|
-
return normalizeText(event.threadId);
|
|
2718
|
-
}
|
|
2719
|
-
function childThreadSubagentId(event) {
|
|
2720
|
-
return normalizeText(event.childThreadId) || childScopeLabel(event);
|
|
2721
|
-
}
|
|
2722
|
-
function ensureSubagentStarted(queue, state, subagentId, taskDescription) {
|
|
2723
|
-
const normalizedSubagentId = normalizeText(subagentId);
|
|
2724
|
-
if (!normalizedSubagentId)
|
|
2725
|
-
return;
|
|
2726
|
-
const description = normalizeText(taskDescription);
|
|
2727
|
-
if (description)
|
|
2728
|
-
state.subagentDescriptionsById.set(normalizedSubagentId, description);
|
|
2729
|
-
if (state.startedSubagentIds.has(normalizedSubagentId))
|
|
2730
|
-
return;
|
|
2731
|
-
state.startedSubagentIds.add(normalizedSubagentId);
|
|
2732
|
-
queue.push({
|
|
2733
|
-
subagentId: normalizedSubagentId,
|
|
2734
|
-
taskDescription: description || state.subagentDescriptionsById.get(normalizedSubagentId) || undefined,
|
|
2735
|
-
type: "subagent.started",
|
|
2736
|
-
});
|
|
2737
|
-
}
|
|
2738
|
-
const SUBAGENT_TOOL_ACTIVITY_MIN_INTERVAL_MS = 500;
|
|
2739
|
-
function compactSubagentActivityText(value, max = 96) {
|
|
2740
|
-
const normalized = String(value ?? "")
|
|
2741
|
-
.replace(/\r/g, "")
|
|
2742
|
-
.replace(/\s+/g, " ")
|
|
2743
|
-
.trim();
|
|
2744
|
-
if (!normalized)
|
|
2745
|
-
return "";
|
|
2746
|
-
if (normalized.length <= max)
|
|
2747
|
-
return normalized;
|
|
2748
|
-
if (max <= 1)
|
|
2749
|
-
return normalized.slice(0, max);
|
|
2750
|
-
return `${normalized.slice(0, max - 1).trimEnd()}…`;
|
|
2751
|
-
}
|
|
2752
|
-
function subagentToolInputSummary(input) {
|
|
2753
|
-
const value = input.file_path ??
|
|
2754
|
-
input.path ??
|
|
2755
|
-
input.query ??
|
|
2756
|
-
input.pattern ??
|
|
2757
|
-
input.command ??
|
|
2758
|
-
input.file ??
|
|
2759
|
-
input.url;
|
|
2760
|
-
if (Array.isArray(value))
|
|
2761
|
-
return value.map((entry) => compactSubagentActivityText(entry, 40)).join(", ");
|
|
2762
|
-
return compactSubagentActivityText(value, 60);
|
|
2763
|
-
}
|
|
2764
|
-
function formatSubagentToolActivityLabel(toolName, input, rawInput) {
|
|
2765
|
-
const name = canonicalToolName(toolName) || normalizeText(toolName);
|
|
2766
|
-
if (!name)
|
|
2767
|
-
return "";
|
|
2768
|
-
const summaryFromObject = input ? subagentToolInputSummary(input) : "";
|
|
2769
|
-
let rawSummary = "";
|
|
2770
|
-
if (!summaryFromObject && typeof rawInput === "string") {
|
|
2771
|
-
const raw = rawInput.trim();
|
|
2772
|
-
if (raw && !raw.startsWith("{") && !raw.startsWith("[")) {
|
|
2773
|
-
rawSummary = compactSubagentActivityText(raw, 60);
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
const summary = summaryFromObject || rawSummary;
|
|
2777
|
-
return compactSubagentActivityText(summary ? `${name} ${summary}` : name, 96);
|
|
2778
|
-
}
|
|
2779
|
-
function shouldEmitSubagentToolActivity(entry, label, force) {
|
|
2780
|
-
if (!label)
|
|
2781
|
-
return false;
|
|
2782
|
-
const now = Date.now();
|
|
2783
|
-
if (!force && entry.lastActivityLabel === label)
|
|
2784
|
-
return false;
|
|
2785
|
-
if (!force &&
|
|
2786
|
-
typeof entry.lastActivityEmittedAtMs === "number" &&
|
|
2787
|
-
now - entry.lastActivityEmittedAtMs < SUBAGENT_TOOL_ACTIVITY_MIN_INTERVAL_MS) {
|
|
2788
|
-
return false;
|
|
2789
|
-
}
|
|
2790
|
-
entry.lastActivityLabel = label;
|
|
2791
|
-
entry.lastActivityEmittedAtMs = now;
|
|
2792
|
-
return true;
|
|
2793
|
-
}
|
|
2794
|
-
function emitSubagentToolActivity(queue, state, input) {
|
|
2795
|
-
const callId = normalizeText(input.callId);
|
|
2796
|
-
const subagentId = normalizeText(input.subagentId);
|
|
2797
|
-
const toolName = canonicalToolName(input.toolName);
|
|
2798
|
-
if (!callId || !subagentId || !toolName)
|
|
2799
|
-
return;
|
|
2800
|
-
const entry = state.childToolInputsByCallId.get(callId) ?? {
|
|
2801
|
-
deltas: [],
|
|
2802
|
-
subagentId,
|
|
2803
|
-
toolName,
|
|
2804
|
-
};
|
|
2805
|
-
entry.subagentId = subagentId;
|
|
2806
|
-
entry.subagentTurnId = normalizeText(input.subagentTurnId) || entry.subagentTurnId;
|
|
2807
|
-
entry.toolName = toolName;
|
|
2808
|
-
// Do not accumulate or join streamed child tool-call input here. Long-context
|
|
2809
|
-
// subagents can stream huge JSON arguments; joining the whole prefix on every
|
|
2810
|
-
// delta is O(n^2) and blocks the TUI before the projector can coalesce work.
|
|
2811
|
-
if (typeof input.delta === "string" && input.delta.length > 0)
|
|
2812
|
-
entry.deltas.length = 0;
|
|
2813
|
-
state.childToolInputsByCallId.set(callId, entry);
|
|
2814
|
-
state.streamedChildToolCallIds.add(callId);
|
|
2815
|
-
const force = typeof input.delta !== "string";
|
|
2816
|
-
const label = formatSubagentToolActivityLabel(toolName);
|
|
2817
|
-
if (!shouldEmitSubagentToolActivity(entry, label, force))
|
|
2818
|
-
return;
|
|
2819
|
-
queue.push({
|
|
2820
|
-
delta: label,
|
|
2821
|
-
stream: "tool",
|
|
2822
|
-
subagentId,
|
|
2823
|
-
...(entry.subagentTurnId ? { subagentTurnId: entry.subagentTurnId } : {}),
|
|
2824
|
-
type: "subagent.output",
|
|
2825
|
-
});
|
|
2826
|
-
}
|
|
2827
|
-
async function resumeBestAgentThread(runtime, threadId, requestedMode) {
|
|
2828
|
-
const thread = await resolveBestAgentThread(runtime, threadId, requestedMode);
|
|
2829
|
-
await runtime.resumeThread(thread.id);
|
|
2830
|
-
return (await runtime.getThread({ threadId: thread.id })).thread;
|
|
2831
|
-
}
|
|
2832
|
-
async function resolveBestAgentThread(runtime, threadId, requestedMode) {
|
|
2833
|
-
const detail = await runtime.getThread({ threadId });
|
|
2834
|
-
let thread = detail.thread;
|
|
2835
|
-
if (thread.status === "archived") {
|
|
2836
|
-
await runtime.unarchiveThread(threadId);
|
|
2837
|
-
thread = (await runtime.getThread({ threadId })).thread;
|
|
2838
|
-
}
|
|
2839
|
-
if (requestedMode && thread.mode !== requestedMode)
|
|
2840
|
-
return (await runtime.setThreadMode({ mode: requestedMode, threadId })).thread;
|
|
2841
|
-
return thread;
|
|
2842
|
-
}
|
|
2843
|
-
export async function resolveInitialBestAgentThread(runtime, options) {
|
|
2844
|
-
if (options.resumeThreadId)
|
|
2845
|
-
return resolveBestAgentThread(runtime, options.resumeThreadId, options.threadMode);
|
|
2846
|
-
if (options.latest) {
|
|
2847
|
-
const listed = await runtime.listThreads({ limit: 1 });
|
|
2848
|
-
if (listed.threads[0])
|
|
2849
|
-
return resolveBestAgentThread(runtime, listed.threads[0].id, options.threadMode);
|
|
2850
|
-
}
|
|
2851
|
-
const created = await runtime.startThread({ mode: options.threadMode });
|
|
2852
|
-
return created.thread;
|
|
2853
|
-
}
|
|
2854
|
-
export function createBestAgentNativeBridge(options) {
|
|
2855
|
-
const threadStates = new Map();
|
|
2856
|
-
let storeRef = null;
|
|
2857
|
-
let unsubscribeStoreRuntimeEvents = null;
|
|
2858
|
-
let currentMode = cliModeFromThreadMode(options.threadMode);
|
|
2859
|
-
let threadSwitchGeneration = 0;
|
|
2860
|
-
const getThreadState = (threadId) => {
|
|
2861
|
-
const existing = threadStates.get(threadId);
|
|
2862
|
-
if (existing)
|
|
2863
|
-
return existing;
|
|
2864
|
-
const created = {};
|
|
2865
|
-
threadStates.set(threadId, created);
|
|
2866
|
-
return created;
|
|
2867
|
-
};
|
|
2868
|
-
const refreshAttachedStoreThread = (threadId) => {
|
|
2869
|
-
const targetStore = storeRef;
|
|
2870
|
-
if (!targetStore || targetStore.state.sessionId !== threadId)
|
|
2871
|
-
return;
|
|
2872
|
-
void options.runtime
|
|
2873
|
-
.getThread({ threadId })
|
|
2874
|
-
.then(({ thread }) => {
|
|
2875
|
-
if (storeRef === targetStore && targetStore.state.sessionId === threadId) {
|
|
2876
|
-
return switchAttachedStoreThread(thread, { mode: "fast" });
|
|
2877
|
-
}
|
|
2878
|
-
})
|
|
2879
|
-
.catch(() => { });
|
|
2880
|
-
};
|
|
2881
|
-
const switchAttachedStoreThread = async (thread, opts) => {
|
|
2882
|
-
const generation = opts?.generation ?? ++threadSwitchGeneration;
|
|
2883
|
-
const targetStore = storeRef;
|
|
2884
|
-
if (!targetStore)
|
|
2885
|
-
return { applied: false, generation };
|
|
2886
|
-
const projection = await loadThreadProjection(options.runtime, thread.id, {
|
|
2887
|
-
mode: opts?.mode,
|
|
2888
|
-
});
|
|
2889
|
-
if (generation !== threadSwitchGeneration)
|
|
2890
|
-
return { applied: false, generation };
|
|
2891
|
-
if (storeRef !== targetStore)
|
|
2892
|
-
return { applied: false, generation };
|
|
2893
|
-
applyThreadProjectionToStore(targetStore, thread, projection);
|
|
2894
|
-
getThreadState(thread.id).history = {
|
|
2895
|
-
hasMore: Boolean(projection.historyHasMore),
|
|
2896
|
-
...(projection.historyNextBeforeItemId
|
|
2897
|
-
? { nextBeforeItemId: projection.historyNextBeforeItemId }
|
|
2898
|
-
: {}),
|
|
2899
|
-
...(projection.historyNextBeforeTurnId
|
|
2900
|
-
? { nextBeforeTurnId: projection.historyNextBeforeTurnId }
|
|
2901
|
-
: {}),
|
|
2902
|
-
};
|
|
2903
|
-
return { applied: true, generation };
|
|
2904
|
-
};
|
|
2905
|
-
const fastHistoryRestoreEnabled = () => String(process.env.BEST_AGENT_FAST_HISTORY_RESTORE ?? "").trim() !== "0";
|
|
2906
|
-
const waitForPendingTurnToSettle = async (threadId, pending, timeoutMs = 1500) => {
|
|
2907
|
-
const isSettled = () => {
|
|
2908
|
-
const current = getThreadState(threadId).pendingTurn;
|
|
2909
|
-
if (current !== pending)
|
|
2910
|
-
return true;
|
|
2911
|
-
return pending.status !== "running" && pending.status !== "paused" && !pending.resumeInFlight;
|
|
2912
|
-
};
|
|
2913
|
-
if (isSettled())
|
|
2914
|
-
return;
|
|
2915
|
-
await new Promise((resolve) => {
|
|
2916
|
-
let settled = false;
|
|
2917
|
-
let timeout;
|
|
2918
|
-
let unsubscribe;
|
|
2919
|
-
const finish = () => {
|
|
2920
|
-
if (settled)
|
|
2921
|
-
return;
|
|
2922
|
-
settled = true;
|
|
2923
|
-
if (timeout)
|
|
2924
|
-
clearTimeout(timeout);
|
|
2925
|
-
unsubscribe?.();
|
|
2926
|
-
resolve();
|
|
2927
|
-
};
|
|
2928
|
-
unsubscribe = options.runtime.subscribe(({ event }) => {
|
|
2929
|
-
if (!belongsToThread(event, threadId, pending.turnId)) {
|
|
2930
|
-
if (isSettled())
|
|
2931
|
-
finish();
|
|
2932
|
-
return;
|
|
2933
|
-
}
|
|
2934
|
-
if (event.type === "turn.completed" ||
|
|
2935
|
-
event.type === "turn.failed" ||
|
|
2936
|
-
event.type === "turn.interrupted" ||
|
|
2937
|
-
event.type === "turn.blocked") {
|
|
2938
|
-
finish();
|
|
2939
|
-
}
|
|
2940
|
-
});
|
|
2941
|
-
timeout = setTimeout(finish, timeoutMs);
|
|
2942
|
-
if (isSettled())
|
|
2943
|
-
finish();
|
|
2944
|
-
});
|
|
2945
|
-
};
|
|
2946
|
-
const hydrateThread = async (threadId) => (await loadThreadProjection(options.runtime, threadId)).messages;
|
|
2947
|
-
const activeThreadId = () => {
|
|
2948
|
-
const value = normalizeText(storeRef?.state.sessionId);
|
|
2949
|
-
return value ? value : undefined;
|
|
2950
|
-
};
|
|
2951
|
-
const prependThreadHistoryPage = (store, messages) => {
|
|
2952
|
-
const seen = new Set(store.state.messages.map((message) => String(message.id ?? "")));
|
|
2953
|
-
const older = messages.filter((message) => {
|
|
2954
|
-
const id = String(message.id ?? "");
|
|
2955
|
-
if (!id || seen.has(id))
|
|
2956
|
-
return false;
|
|
2957
|
-
seen.add(id);
|
|
2958
|
-
return true;
|
|
2959
|
-
});
|
|
2960
|
-
if (older.length === 0)
|
|
2961
|
-
return false;
|
|
2962
|
-
if (typeof store.replaceMessages === "function") {
|
|
2963
|
-
store.replaceMessages([...older, ...store.state.messages]);
|
|
2964
|
-
}
|
|
2965
|
-
else {
|
|
2966
|
-
store.state.messages.splice(0, 0, ...older);
|
|
2967
|
-
if (Array.isArray(store.state.userHistory)) {
|
|
2968
|
-
const userHistory = older
|
|
2969
|
-
.filter((message) => Boolean(message && message.role === "user"))
|
|
2970
|
-
.map((message) => message.content);
|
|
2971
|
-
store.state.userHistory.splice(0, 0, ...userHistory);
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
if ("chromeVersion" in store.state && typeof store.state.chromeVersion === "number") {
|
|
2975
|
-
store.state.chromeVersion += 1;
|
|
2976
|
-
}
|
|
2977
|
-
return true;
|
|
2978
|
-
};
|
|
2979
|
-
const loadMoreAttachedStoreHistory = async (threadId) => {
|
|
2980
|
-
const targetStore = storeRef;
|
|
2981
|
-
if (!targetStore || normalizeText(targetStore.state.sessionId) !== threadId)
|
|
2982
|
-
return false;
|
|
2983
|
-
const state = getThreadState(threadId);
|
|
2984
|
-
const history = state.history;
|
|
2985
|
-
if (!history?.hasMore || history.loading || !history.nextBeforeTurnId)
|
|
2986
|
-
return false;
|
|
2987
|
-
history.loading = true;
|
|
2988
|
-
try {
|
|
2989
|
-
const [response, detail] = await Promise.all([
|
|
2990
|
-
options.runtime.getThreadHistory({
|
|
2991
|
-
beforeTurnId: history.nextBeforeTurnId,
|
|
2992
|
-
threadId,
|
|
2993
|
-
turnLimit: HISTORY_PAGE_TURN_LIMIT,
|
|
2994
|
-
}),
|
|
2995
|
-
options.runtime.getThread({ threadId }),
|
|
2996
|
-
]);
|
|
2997
|
-
if (storeRef !== targetStore || normalizeText(targetStore.state.sessionId) !== threadId) {
|
|
2998
|
-
return false;
|
|
2999
|
-
}
|
|
3000
|
-
const projection = hydrateThreadItems({
|
|
3001
|
-
items: response.items,
|
|
3002
|
-
renderPendingSteerControls: true,
|
|
3003
|
-
thread: detail.thread,
|
|
3004
|
-
turns: response.turns ?? [],
|
|
3005
|
-
});
|
|
3006
|
-
state.history = {
|
|
3007
|
-
hasMore: response.hasMore,
|
|
3008
|
-
...(response.nextBeforeItemId ? { nextBeforeItemId: response.nextBeforeItemId } : {}),
|
|
3009
|
-
...(response.nextBeforeTurnId ? { nextBeforeTurnId: response.nextBeforeTurnId } : {}),
|
|
3010
|
-
};
|
|
3011
|
-
return prependThreadHistoryPage(targetStore, projection.messages);
|
|
3012
|
-
}
|
|
3013
|
-
finally {
|
|
3014
|
-
const latest = getThreadState(threadId).history;
|
|
3015
|
-
if (latest)
|
|
3016
|
-
latest.loading = false;
|
|
3017
|
-
}
|
|
3018
|
-
};
|
|
3019
|
-
const sessions = {
|
|
3020
|
-
async createNew() {
|
|
3021
|
-
const created = await options.runtime.startThread({
|
|
3022
|
-
mode: threadModeFromCliMode(currentMode),
|
|
3023
|
-
});
|
|
3024
|
-
const switched = await switchAttachedStoreThread(created.thread);
|
|
3025
|
-
if (switched.applied)
|
|
3026
|
-
currentMode = cliModeFromThreadMode(created.thread.mode);
|
|
3027
|
-
},
|
|
3028
|
-
async delete(threadId) {
|
|
3029
|
-
await options.runtime.purgeThread(threadId);
|
|
3030
|
-
threadStates.delete(threadId);
|
|
3031
|
-
if (!storeRef || storeRef.state.sessionId !== threadId)
|
|
3032
|
-
return;
|
|
3033
|
-
const listed = await options.runtime.listThreads({ limit: 1 });
|
|
3034
|
-
if (listed.threads[0]) {
|
|
3035
|
-
const switched = await switchAttachedStoreThread(listed.threads[0], { mode: "fast" });
|
|
3036
|
-
if (switched.applied)
|
|
3037
|
-
currentMode = cliModeFromThreadMode(listed.threads[0].mode);
|
|
3038
|
-
return;
|
|
3039
|
-
}
|
|
3040
|
-
const created = await options.runtime.startThread({
|
|
3041
|
-
mode: threadModeFromCliMode(currentMode),
|
|
3042
|
-
});
|
|
3043
|
-
const switched = await switchAttachedStoreThread(created.thread);
|
|
3044
|
-
if (switched.applied)
|
|
3045
|
-
currentMode = cliModeFromThreadMode(created.thread.mode);
|
|
3046
|
-
},
|
|
3047
|
-
async fork() {
|
|
3048
|
-
const currentThreadId = normalizeText(storeRef?.state.sessionId);
|
|
3049
|
-
if (!currentThreadId)
|
|
3050
|
-
return;
|
|
3051
|
-
const forked = await options.runtime.forkThread({
|
|
3052
|
-
threadId: currentThreadId,
|
|
3053
|
-
});
|
|
3054
|
-
const switched = await switchAttachedStoreThread(forked.thread, { mode: "fast" });
|
|
3055
|
-
if (switched.applied)
|
|
3056
|
-
currentMode = cliModeFromThreadMode(forked.thread.mode);
|
|
3057
|
-
},
|
|
3058
|
-
async get(threadId, getOptions) {
|
|
3059
|
-
try {
|
|
3060
|
-
const thread = (await options.runtime.getThread({ threadId: threadId })).thread;
|
|
3061
|
-
if (!getOptions?.includeMessages)
|
|
3062
|
-
return thread;
|
|
3063
|
-
const projection = await loadThreadProjection(options.runtime, threadId);
|
|
3064
|
-
return { ...thread, messages: projection.messages };
|
|
3065
|
-
}
|
|
3066
|
-
catch {
|
|
3067
|
-
return null;
|
|
3068
|
-
}
|
|
3069
|
-
},
|
|
3070
|
-
async trace(threadId) {
|
|
3071
|
-
try {
|
|
3072
|
-
const normalizedThreadId = threadId;
|
|
3073
|
-
const diagnosticsPromise = typeof options.runtime.getThreadDiagnostics === "function"
|
|
3074
|
-
? options.runtime.getThreadDiagnostics(normalizedThreadId).catch(() => undefined)
|
|
3075
|
-
: Promise.resolve(undefined);
|
|
3076
|
-
const [items, events, diagnostics] = await Promise.all([
|
|
3077
|
-
loadThreadTraceItems(options.runtime, normalizedThreadId),
|
|
3078
|
-
loadThreadTraceEvents(options.runtime, normalizedThreadId),
|
|
3079
|
-
diagnosticsPromise,
|
|
3080
|
-
]);
|
|
3081
|
-
return buildSessionTrace(normalizedThreadId, items, events, diagnostics?.diagnostics.processes?.records ?? []);
|
|
3082
|
-
}
|
|
3083
|
-
catch {
|
|
3084
|
-
return null;
|
|
3085
|
-
}
|
|
3086
|
-
},
|
|
3087
|
-
async subagent(threadId) {
|
|
3088
|
-
try {
|
|
3089
|
-
const normalizedThreadId = threadId;
|
|
3090
|
-
const [resumed, events] = await Promise.all([
|
|
3091
|
-
options.runtime.resumeThread(normalizedThreadId),
|
|
3092
|
-
loadThreadTraceEvents(options.runtime, normalizedThreadId),
|
|
3093
|
-
]);
|
|
3094
|
-
return buildSubagentDetail(resumed.thread, resumed.turns, resumed.items, events, {
|
|
3095
|
-
...(resumed.pendingAction ? { pendingAction: resumed.pendingAction } : {}),
|
|
3096
|
-
...(resumed.pendingApproval ? { pendingApproval: resumed.pendingApproval } : {}),
|
|
3097
|
-
});
|
|
3098
|
-
}
|
|
3099
|
-
catch {
|
|
3100
|
-
return null;
|
|
3101
|
-
}
|
|
3102
|
-
},
|
|
3103
|
-
watchSubagent(threadId, watchOptions, onEvent) {
|
|
3104
|
-
return watchSubagentLiveEvents(options.runtime, threadId, watchOptions, onEvent);
|
|
3105
|
-
},
|
|
3106
|
-
async steerSubagent(threadId, turnId, prompt) {
|
|
3107
|
-
const normalizedPrompt = normalizeText(prompt);
|
|
3108
|
-
if (!normalizedPrompt)
|
|
3109
|
-
return { message: "Missing steer prompt.", ok: false, status: "unknown" };
|
|
3110
|
-
const normalizedThreadId = threadId;
|
|
3111
|
-
const normalizedTurnId = turnId;
|
|
3112
|
-
const response = await options.runtime.steerTurn(normalizedThreadId, normalizedTurnId, normalizedPrompt);
|
|
3113
|
-
const status = normalizeSubagentStatus(response?.turn?.status);
|
|
3114
|
-
return { ok: true, status: status === "unknown" ? "running" : status };
|
|
3115
|
-
},
|
|
3116
|
-
async loadMoreHistory(threadId) {
|
|
3117
|
-
const normalizedThreadId = normalizeText(threadId);
|
|
3118
|
-
if (!normalizedThreadId)
|
|
3119
|
-
return false;
|
|
3120
|
-
return loadMoreAttachedStoreHistory(normalizedThreadId);
|
|
3121
|
-
},
|
|
3122
|
-
async list() {
|
|
3123
|
-
return await withPerfSpanAsync("native.sessions.list", undefined, async () => {
|
|
3124
|
-
const listed = await options.runtime.listThreads({ limit: 50 });
|
|
3125
|
-
const threads = await filterSessionThreads(options.runtime, listed.threads);
|
|
3126
|
-
const activeId = activeThreadId();
|
|
3127
|
-
return Promise.all(threads.map(async (thread) => {
|
|
3128
|
-
const activeMessages = activeId && thread.id === activeId && storeRef?.state.messages.length
|
|
3129
|
-
? storeRef.state.messages
|
|
3130
|
-
: null;
|
|
3131
|
-
const summary = activeMessages ? latestSummary(activeMessages) : previewSummary(thread);
|
|
3132
|
-
const metadata = sessionListMetadata(thread);
|
|
3133
|
-
return {
|
|
3134
|
-
cwd: thread.cwd,
|
|
3135
|
-
id: thread.id,
|
|
3136
|
-
lastUpdatedAt: metadata.lastUpdatedAt,
|
|
3137
|
-
lastMessage: summary.lastMessage,
|
|
3138
|
-
messageCount: summary.messageCount,
|
|
3139
|
-
queue: metadata.queue,
|
|
3140
|
-
queueLength: metadata.queueLength,
|
|
3141
|
-
reasoningDiagnosticsByTurn: metadata.reasoningDiagnosticsByTurn,
|
|
3142
|
-
summary: summary.summary,
|
|
3143
|
-
status: metadata.status ?? thread.status,
|
|
3144
|
-
title: activeSessionTitle(thread.title, activeMessages),
|
|
3145
|
-
updatedAt: sessionListUpdatedAt(thread, metadata),
|
|
3146
|
-
};
|
|
3147
|
-
}));
|
|
3148
|
-
});
|
|
3149
|
-
},
|
|
3150
|
-
async rename(threadId, title) {
|
|
3151
|
-
const renamed = await options.runtime.renameThread(threadId, title);
|
|
3152
|
-
if (!storeRef || storeRef.state.sessionId !== threadId)
|
|
3153
|
-
return;
|
|
3154
|
-
const switched = await switchAttachedStoreThread(renamed.thread, { mode: "fast" });
|
|
3155
|
-
if (switched.applied)
|
|
3156
|
-
currentMode = cliModeFromThreadMode(renamed.thread.mode);
|
|
3157
|
-
},
|
|
3158
|
-
async reload(threadId) {
|
|
3159
|
-
const normalizedThreadId = threadId;
|
|
3160
|
-
const thread = await resolveBestAgentThread(options.runtime, normalizedThreadId);
|
|
3161
|
-
if (!storeRef || storeRef.state.sessionId !== threadId)
|
|
3162
|
-
return false;
|
|
3163
|
-
const switched = await switchAttachedStoreThread(thread, { mode: "fast" });
|
|
3164
|
-
if (switched.applied)
|
|
3165
|
-
currentMode = cliModeFromThreadMode(thread.mode);
|
|
3166
|
-
return switched.applied;
|
|
3167
|
-
},
|
|
3168
|
-
async updateMetadata(threadId, metadata) {
|
|
3169
|
-
const updated = await options.runtime.updateThreadMetadata(threadId, wrapSessionUiMetadata(metadata));
|
|
3170
|
-
if (!storeRef || storeRef.state.sessionId !== threadId)
|
|
3171
|
-
return;
|
|
3172
|
-
syncThreadMetadataIntoStore(storeRef, updated.thread, {
|
|
3173
|
-
preserveLocalSessionMeta: true,
|
|
3174
|
-
});
|
|
3175
|
-
},
|
|
3176
|
-
async use(threadId) {
|
|
3177
|
-
await withPerfSpanAsync("sessions.use", `threadId=${normalizeText(threadId)}`, async () => {
|
|
3178
|
-
const normalized = normalizeText(threadId);
|
|
3179
|
-
if (!normalized)
|
|
3180
|
-
return;
|
|
3181
|
-
if (storeRef &&
|
|
3182
|
-
normalizeText(storeRef.state.sessionId) === normalized &&
|
|
3183
|
-
storeRef.state.messages.length > 0) {
|
|
3184
|
-
return;
|
|
3185
|
-
}
|
|
3186
|
-
const restoreStore = storeRef;
|
|
3187
|
-
setThreadRestoreStatus(restoreStore, "loading", normalized);
|
|
3188
|
-
try {
|
|
3189
|
-
const thread = await resolveBestAgentThread(options.runtime, normalized);
|
|
3190
|
-
const useFastProjection = Boolean(storeRef) && fastHistoryRestoreEnabled();
|
|
3191
|
-
const generation = ++threadSwitchGeneration;
|
|
3192
|
-
const switched = await switchAttachedStoreThread(thread, {
|
|
3193
|
-
generation,
|
|
3194
|
-
mode: useFastProjection ? "fast" : "full",
|
|
3195
|
-
});
|
|
3196
|
-
if (!switched.applied) {
|
|
3197
|
-
setThreadRestoreStatus(restoreStore, "idle", normalized);
|
|
3198
|
-
return;
|
|
3199
|
-
}
|
|
3200
|
-
currentMode = cliModeFromThreadMode(thread.mode);
|
|
3201
|
-
}
|
|
3202
|
-
catch (error) {
|
|
3203
|
-
setThreadRestoreStatus(restoreStore, "idle", normalized);
|
|
3204
|
-
throw error;
|
|
3205
|
-
}
|
|
3206
|
-
});
|
|
3207
|
-
},
|
|
3208
|
-
};
|
|
3209
|
-
const createSession = (threadId) => {
|
|
3210
|
-
const clearBackgroundTracking = (pending) => {
|
|
3211
|
-
pending.backgroundUnsubscribe?.();
|
|
3212
|
-
pending.backgroundUnsubscribe = undefined;
|
|
3213
|
-
};
|
|
3214
|
-
const clearPendingTurnIfTerminal = (pending) => {
|
|
3215
|
-
if (pending.status === "completed" ||
|
|
3216
|
-
pending.status === "failed" ||
|
|
3217
|
-
pending.status === "interrupted") {
|
|
3218
|
-
const state = getThreadState(threadId);
|
|
3219
|
-
if (state.pendingTurn === pending)
|
|
3220
|
-
state.pendingTurn = undefined;
|
|
3221
|
-
}
|
|
3222
|
-
};
|
|
3223
|
-
const persistTurnReasoningDiagnostics = async (pending, diagnostics) => {
|
|
3224
|
-
if (!pending.turnId)
|
|
3225
|
-
return;
|
|
3226
|
-
try {
|
|
3227
|
-
const updated = await options.runtime.updateThreadMetadata(threadId, {
|
|
3228
|
-
ui: {
|
|
3229
|
-
reasoningDiagnosticsByTurn: {
|
|
3230
|
-
[pending.turnId]: diagnostics.state,
|
|
3231
|
-
},
|
|
3232
|
-
},
|
|
3233
|
-
});
|
|
3234
|
-
if (!storeRef || storeRef.state.sessionId !== threadId)
|
|
3235
|
-
return;
|
|
3236
|
-
syncThreadMetadataIntoStore(storeRef, updated.thread, {
|
|
3237
|
-
preserveLocalSessionMeta: true,
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
catch { }
|
|
3241
|
-
};
|
|
3242
|
-
const startDetachedTracking = (pending) => {
|
|
3243
|
-
clearBackgroundTracking(pending);
|
|
3244
|
-
if (pending.status !== "running")
|
|
3245
|
-
return;
|
|
3246
|
-
if (pending.turnId) {
|
|
3247
|
-
const detach = options.runtime.markTurnDetached?.(threadId, pending.turnId);
|
|
3248
|
-
void detach?.catch(() => { });
|
|
3249
|
-
}
|
|
3250
|
-
pending.backgroundUnsubscribe = options.runtime.subscribe(({ event }) => {
|
|
3251
|
-
if (!belongsToThread(event, threadId, pending.turnId))
|
|
3252
|
-
return;
|
|
3253
|
-
if (event.type === "turn.started") {
|
|
3254
|
-
pending.turnId = event.turn.id;
|
|
3255
|
-
const detach = options.runtime.markTurnDetached?.(threadId, event.turn.id);
|
|
3256
|
-
void detach?.catch(() => { });
|
|
3257
|
-
return;
|
|
3258
|
-
}
|
|
3259
|
-
if (event.type === "turn.item.added" && event.item.kind === "error") {
|
|
3260
|
-
pending.error = event.item;
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
|
-
if (event.type === "turn.item.added" &&
|
|
3264
|
-
event.item.kind === "reasoning" &&
|
|
3265
|
-
!isDraftReasoningItem(event.item) &&
|
|
3266
|
-
hasVisibleReasoningText(event.item.content ?? event.item.summary)) {
|
|
3267
|
-
pending.sawVisibleReasoning = true;
|
|
3268
|
-
return;
|
|
3269
|
-
}
|
|
3270
|
-
if ((event.type === "turn.reasoning.delta" || event.type === "turn.reasoning.completed") &&
|
|
3271
|
-
!event.parentTurnId) {
|
|
3272
|
-
const currentReasoning = event.type === "turn.reasoning.delta"
|
|
3273
|
-
? event.delta
|
|
3274
|
-
: pending.reasoningTextByStreamId.get(event.streamId);
|
|
3275
|
-
if (hasVisibleReasoningText(currentReasoning))
|
|
3276
|
-
pending.sawVisibleReasoning = true;
|
|
3277
|
-
return;
|
|
3278
|
-
}
|
|
3279
|
-
if (event.type === "thread.usage.updated") {
|
|
3280
|
-
pending.latestUsage = usageMetadataFromSessionUsage(event.inclusive);
|
|
3281
|
-
updatePendingReasoningTokens(pending, event.last);
|
|
3282
|
-
return;
|
|
3283
|
-
}
|
|
3284
|
-
if (event.type === "approval.requested") {
|
|
3285
|
-
pending.approval = event.approval;
|
|
3286
|
-
pending.status = "paused";
|
|
3287
|
-
clearBackgroundTracking(pending);
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
|
-
if (event.type === "action.required" && event.kind === "ask_user") {
|
|
3291
|
-
pending.action = {
|
|
3292
|
-
actionId: event.actionId,
|
|
3293
|
-
questions: event.questions,
|
|
3294
|
-
toolCallId: normalizeText(event.toolCallId) || undefined,
|
|
3295
|
-
toolName: canonicalToolName(event.toolName),
|
|
3296
|
-
};
|
|
3297
|
-
pending.status = "paused";
|
|
3298
|
-
clearBackgroundTracking(pending);
|
|
3299
|
-
return;
|
|
3300
|
-
}
|
|
3301
|
-
if (event.type === "turn.completed") {
|
|
3302
|
-
pending.action = undefined;
|
|
3303
|
-
pending.approval = undefined;
|
|
3304
|
-
pending.resumeInFlight = false;
|
|
3305
|
-
pending.status = "completed";
|
|
3306
|
-
void persistTurnReasoningDiagnostics(pending, resolvePendingReasoningDiagnostics(pending));
|
|
3307
|
-
clearBackgroundTracking(pending);
|
|
3308
|
-
clearPendingTurnIfTerminal(pending);
|
|
3309
|
-
return;
|
|
3310
|
-
}
|
|
3311
|
-
if (event.type === "turn.interrupted") {
|
|
3312
|
-
pending.action = undefined;
|
|
3313
|
-
pending.approval = undefined;
|
|
3314
|
-
pending.resumeInFlight = false;
|
|
3315
|
-
pending.status = "interrupted";
|
|
3316
|
-
void persistTurnReasoningDiagnostics(pending, resolvePendingReasoningDiagnostics(pending));
|
|
3317
|
-
clearBackgroundTracking(pending);
|
|
3318
|
-
clearPendingTurnIfTerminal(pending);
|
|
3319
|
-
return;
|
|
3320
|
-
}
|
|
3321
|
-
if (event.type === "turn.failed" || event.type === "turn.blocked") {
|
|
3322
|
-
pending.action = undefined;
|
|
3323
|
-
pending.approval = undefined;
|
|
3324
|
-
pending.resumeInFlight = false;
|
|
3325
|
-
pending.status = "failed";
|
|
3326
|
-
void persistTurnReasoningDiagnostics(pending, resolvePendingReasoningDiagnostics(pending));
|
|
3327
|
-
clearBackgroundTracking(pending);
|
|
3328
|
-
clearPendingTurnIfTerminal(pending);
|
|
3329
|
-
}
|
|
3330
|
-
});
|
|
3331
|
-
};
|
|
3332
|
-
const streamPendingTurn = async function* (pending, input) {
|
|
3333
|
-
let finish = null;
|
|
3334
|
-
const queue = createAsyncQueue({
|
|
3335
|
-
onClose: () => {
|
|
3336
|
-
finish?.({ detach: true });
|
|
3337
|
-
},
|
|
3338
|
-
});
|
|
3339
|
-
clearBackgroundTracking(pending);
|
|
3340
|
-
let abortRequested = false;
|
|
3341
|
-
let catchUpComplete = input.attachAfterSequence == null;
|
|
3342
|
-
const bufferedEnvelopes = [];
|
|
3343
|
-
let settled = false;
|
|
3344
|
-
let approvalResumeWatchdog;
|
|
3345
|
-
finish = (opts2) => {
|
|
3346
|
-
if (settled)
|
|
3347
|
-
return;
|
|
3348
|
-
settled = true;
|
|
3349
|
-
if (approvalResumeWatchdog)
|
|
3350
|
-
clearTimeout(approvalResumeWatchdog);
|
|
3351
|
-
unsubscribe();
|
|
3352
|
-
input.signal?.removeEventListener("abort", onAbort);
|
|
3353
|
-
if (opts2?.detach && pending.status === "running" && !pending.resumeInFlight) {
|
|
3354
|
-
startDetachedTracking(pending);
|
|
3355
|
-
}
|
|
3356
|
-
queue.close();
|
|
3357
|
-
};
|
|
3358
|
-
const onAbort = () => {
|
|
3359
|
-
abortRequested = true;
|
|
3360
|
-
finish?.({ detach: true });
|
|
3361
|
-
};
|
|
3362
|
-
const flushPendingReasoningSummaries = () => {
|
|
3363
|
-
if (pending.streamedReasoning || pending.reasoningSummaries.length === 0)
|
|
3364
|
-
return;
|
|
3365
|
-
const summaries = pending.reasoningSummaries.map(normalizeText).filter(Boolean);
|
|
3366
|
-
pending.reasoningSummaries = [];
|
|
3367
|
-
logLlmDebug("bridge.reasoning.summary.flush", {
|
|
3368
|
-
summaries,
|
|
3369
|
-
threadId,
|
|
3370
|
-
turnId: pending.turnId,
|
|
3371
|
-
});
|
|
3372
|
-
for (const summary of summaries) {
|
|
3373
|
-
queue.push({
|
|
3374
|
-
message: {
|
|
3375
|
-
content: "",
|
|
3376
|
-
thinking: summary,
|
|
3377
|
-
},
|
|
3378
|
-
type: "assistant.message",
|
|
3379
|
-
});
|
|
3380
|
-
}
|
|
3381
|
-
};
|
|
3382
|
-
const shouldFlushPendingReasoningSummaries = (event) => {
|
|
3383
|
-
switch (event.type) {
|
|
3384
|
-
case "turn.text.delta":
|
|
3385
|
-
case "turn.text.stream.reset":
|
|
3386
|
-
case "turn.tool-call.started":
|
|
3387
|
-
case "turn.tool-call.delta":
|
|
3388
|
-
case "turn.tool-output.delta":
|
|
3389
|
-
case "child-task.started":
|
|
3390
|
-
case "child-task.completed":
|
|
3391
|
-
case "child-task.joined":
|
|
3392
|
-
case "child-task.failed":
|
|
3393
|
-
case "child-task.rejected":
|
|
3394
|
-
case "child-thread.item.added":
|
|
3395
|
-
case "approval.requested":
|
|
3396
|
-
case "turn.completed":
|
|
3397
|
-
case "turn.interrupted":
|
|
3398
|
-
case "turn.blocked":
|
|
3399
|
-
case "turn.failed":
|
|
3400
|
-
return true;
|
|
3401
|
-
case "action.required":
|
|
3402
|
-
return true;
|
|
3403
|
-
case "turn.item.added":
|
|
3404
|
-
return event.item.kind !== "reasoning";
|
|
3405
|
-
default:
|
|
3406
|
-
return false;
|
|
3407
|
-
}
|
|
3408
|
-
};
|
|
3409
|
-
const processEnvelope = (envelope, opts2) => {
|
|
3410
|
-
const { event, sequence } = envelope;
|
|
3411
|
-
const eventCreatedAtMs = parseIsoTimeMs(envelope.createdAt) ?? undefined;
|
|
3412
|
-
if (event.type === "turn.reasoning.started" ||
|
|
3413
|
-
event.type === "turn.reasoning.delta" ||
|
|
3414
|
-
event.type === "turn.reasoning.completed" ||
|
|
3415
|
-
event.type === "turn.text.delta" ||
|
|
3416
|
-
event.type === "turn.text.stream.reset" ||
|
|
3417
|
-
event.type === "turn.item.added") {
|
|
3418
|
-
const debugEvent = event;
|
|
3419
|
-
const isChildEvent = typeof debugEvent.parentTurnId === "string" && debugEvent.parentTurnId.length > 0;
|
|
3420
|
-
logLlmDebug("bridge.event", {
|
|
3421
|
-
event: event.type === "turn.item.added"
|
|
3422
|
-
? {
|
|
3423
|
-
itemKind: event.item.kind,
|
|
3424
|
-
role: "role" in event.item ? event.item.role : undefined,
|
|
3425
|
-
summaryChars: "summary" in event.item && typeof event.item.summary === "string"
|
|
3426
|
-
? event.item.summary.length
|
|
3427
|
-
: undefined,
|
|
3428
|
-
textChars: "text" in event.item && typeof event.item.text === "string"
|
|
3429
|
-
? event.item.text.length
|
|
3430
|
-
: undefined,
|
|
3431
|
-
type: event.type,
|
|
3432
|
-
}
|
|
3433
|
-
: isChildEvent
|
|
3434
|
-
? {
|
|
3435
|
-
callId: typeof debugEvent.callId === "string" ? debugEvent.callId : undefined,
|
|
3436
|
-
deltaChars: typeof debugEvent.delta === "string" ? debugEvent.delta.length : undefined,
|
|
3437
|
-
parentTurnId: debugEvent.parentTurnId,
|
|
3438
|
-
relation: typeof debugEvent.relation === "string" ? debugEvent.relation : undefined,
|
|
3439
|
-
streamId: typeof debugEvent.streamId === "string" ? debugEvent.streamId : undefined,
|
|
3440
|
-
turnId: typeof debugEvent.turnId === "string" ? debugEvent.turnId : undefined,
|
|
3441
|
-
type: event.type,
|
|
3442
|
-
}
|
|
3443
|
-
: event,
|
|
3444
|
-
threadId,
|
|
3445
|
-
turnId: pending.turnId,
|
|
3446
|
-
});
|
|
3447
|
-
}
|
|
3448
|
-
if (!belongsToThread(event, threadId, pending.turnId))
|
|
3449
|
-
return;
|
|
3450
|
-
if (opts2?.trackSequence !== false)
|
|
3451
|
-
pending.lastDeliveredSequence = sequence;
|
|
3452
|
-
if (event.type === "turn.started") {
|
|
3453
|
-
pending.turnId = event.turn.id;
|
|
3454
|
-
return;
|
|
3455
|
-
}
|
|
3456
|
-
if (event.type === "turn.steered") {
|
|
3457
|
-
const prompt = normalizeText(event.prompt);
|
|
3458
|
-
if (prompt)
|
|
3459
|
-
pending.pendingSteerPrompts.push(prompt);
|
|
3460
|
-
return;
|
|
3461
|
-
}
|
|
3462
|
-
if (event.type === "turn.reasoning.started") {
|
|
3463
|
-
if (!event.parentTurnId) {
|
|
3464
|
-
pending.streamedReasoning = true;
|
|
3465
|
-
pending.reasoningSummaries = [];
|
|
3466
|
-
const partId = createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
3467
|
-
pending.reasoningPartIdByStreamId.set(event.streamId, partId);
|
|
3468
|
-
pending.reasoningTextByStreamId.set(event.streamId, "");
|
|
3469
|
-
queue.push({ type: "thinking.started", partId });
|
|
3470
|
-
}
|
|
3471
|
-
else if (event.relation) {
|
|
3472
|
-
const subagentId = childStreamSubagentId(event);
|
|
3473
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3474
|
-
}
|
|
3475
|
-
return;
|
|
3476
|
-
}
|
|
3477
|
-
if (event.type === "turn.reasoning.delta") {
|
|
3478
|
-
if (!event.parentTurnId) {
|
|
3479
|
-
pending.streamedReasoning = true;
|
|
3480
|
-
pending.reasoningSummaries = [];
|
|
3481
|
-
if (hasVisibleReasoningText(event.delta))
|
|
3482
|
-
pending.sawVisibleReasoning = true;
|
|
3483
|
-
const partId = pending.reasoningPartIdByStreamId.get(event.streamId) ??
|
|
3484
|
-
createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
3485
|
-
pending.reasoningPartIdByStreamId.set(event.streamId, partId);
|
|
3486
|
-
const nextText = `${pending.reasoningTextByStreamId.get(event.streamId) ?? ""}${event.delta}`;
|
|
3487
|
-
pending.reasoningTextByStreamId.set(event.streamId, nextText);
|
|
3488
|
-
queue.push({ partId, delta: event.delta, type: "thinking.delta" });
|
|
3489
|
-
}
|
|
3490
|
-
else if (event.relation) {
|
|
3491
|
-
const subagentId = childStreamSubagentId(event);
|
|
3492
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3493
|
-
}
|
|
3494
|
-
return;
|
|
3495
|
-
}
|
|
3496
|
-
if (event.type === "turn.reasoning.completed") {
|
|
3497
|
-
if (!event.parentTurnId) {
|
|
3498
|
-
pending.streamedReasoning = true;
|
|
3499
|
-
pending.reasoningSummaries = [];
|
|
3500
|
-
const partId = pending.reasoningPartIdByStreamId.get(event.streamId) ??
|
|
3501
|
-
createThinkingPartIdFromStream(event.turnId, event.streamId);
|
|
3502
|
-
const finalReasoning = pending.reasoningTextByStreamId.get(event.streamId);
|
|
3503
|
-
if (hasVisibleReasoningText(finalReasoning))
|
|
3504
|
-
pending.sawVisibleReasoning = true;
|
|
3505
|
-
queue.push({
|
|
3506
|
-
partId,
|
|
3507
|
-
content: finalReasoning || undefined,
|
|
3508
|
-
type: "thinking.finished",
|
|
3509
|
-
});
|
|
3510
|
-
pending.reasoningPartIdByStreamId.delete(event.streamId);
|
|
3511
|
-
pending.reasoningTextByStreamId.delete(event.streamId);
|
|
3512
|
-
}
|
|
3513
|
-
else if (event.relation) {
|
|
3514
|
-
const subagentId = childStreamSubagentId(event);
|
|
3515
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3516
|
-
}
|
|
3517
|
-
return;
|
|
3518
|
-
}
|
|
3519
|
-
if (shouldFlushPendingReasoningSummaries(event))
|
|
3520
|
-
flushPendingReasoningSummaries();
|
|
3521
|
-
if (event.type === "turn.text.delta") {
|
|
3522
|
-
pending.streamedAgentTurnIds.add(event.turnId);
|
|
3523
|
-
if (event.parentTurnId && event.relation) {
|
|
3524
|
-
const subagentId = childStreamSubagentId(event);
|
|
3525
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3526
|
-
queue.push({
|
|
3527
|
-
delta: event.delta,
|
|
3528
|
-
stream: "text",
|
|
3529
|
-
subagentId,
|
|
3530
|
-
subagentTurnId: event.turnId,
|
|
3531
|
-
type: "subagent.output",
|
|
3532
|
-
});
|
|
3533
|
-
return;
|
|
3534
|
-
}
|
|
3535
|
-
const delta = event.delta;
|
|
3536
|
-
queue.push({ delta, streamId: event.streamId, type: "assistant.delta" });
|
|
3537
|
-
return;
|
|
3538
|
-
}
|
|
3539
|
-
if (event.type === "turn.text.stream.reset") {
|
|
3540
|
-
if (event.parentTurnId && event.relation) {
|
|
3541
|
-
return;
|
|
3542
|
-
}
|
|
3543
|
-
pending.streamedAgentTurnIds.delete(event.turnId);
|
|
3544
|
-
queue.push({ streamId: event.streamId, type: "assistant.reset" });
|
|
3545
|
-
return;
|
|
3546
|
-
}
|
|
3547
|
-
if (event.type === "turn.tool-call.started") {
|
|
3548
|
-
if (event.parentTurnId && event.relation) {
|
|
3549
|
-
const subagentId = childStreamSubagentId(event);
|
|
3550
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3551
|
-
pending.childToolInputsByCallId.set(event.callId, {
|
|
3552
|
-
deltas: [],
|
|
3553
|
-
subagentId,
|
|
3554
|
-
subagentTurnId: event.turnId,
|
|
3555
|
-
toolName: canonicalToolName(event.toolName),
|
|
3556
|
-
});
|
|
3557
|
-
pending.toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
3558
|
-
queue.push({
|
|
3559
|
-
input: {},
|
|
3560
|
-
originalEventType: "tool_call",
|
|
3561
|
-
startedAtMs: eventCreatedAtMs,
|
|
3562
|
-
subagentId,
|
|
3563
|
-
subagentTurnId: event.turnId,
|
|
3564
|
-
toolCallId: event.callId,
|
|
3565
|
-
toolName: canonicalToolName(event.toolName),
|
|
3566
|
-
type: "tool.started",
|
|
3567
|
-
});
|
|
3568
|
-
emitSubagentToolActivity(queue, pending, {
|
|
3569
|
-
callId: event.callId,
|
|
3570
|
-
subagentId,
|
|
3571
|
-
subagentTurnId: event.turnId,
|
|
3572
|
-
toolName: event.toolName,
|
|
3573
|
-
});
|
|
3574
|
-
return;
|
|
3575
|
-
}
|
|
3576
|
-
pending.toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
3577
|
-
pending.startedToolCallIds.add(event.callId);
|
|
3578
|
-
pending.inputDeltasByCallId.set(event.callId, {
|
|
3579
|
-
deltas: [],
|
|
3580
|
-
toolName: canonicalToolName(event.toolName),
|
|
3581
|
-
});
|
|
3582
|
-
queue.push({
|
|
3583
|
-
input: {},
|
|
3584
|
-
originalEventType: "tool_call",
|
|
3585
|
-
startedAtMs: eventCreatedAtMs,
|
|
3586
|
-
toolCallId: event.callId,
|
|
3587
|
-
toolName: canonicalToolName(event.toolName),
|
|
3588
|
-
type: "tool.started",
|
|
3589
|
-
});
|
|
3590
|
-
return;
|
|
3591
|
-
}
|
|
3592
|
-
if (event.type === "turn.tool-call.delta") {
|
|
3593
|
-
if (event.parentTurnId && event.relation) {
|
|
3594
|
-
const subagentId = childStreamSubagentId(event);
|
|
3595
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3596
|
-
pending.toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
3597
|
-
queue.push({
|
|
3598
|
-
delta: event.delta,
|
|
3599
|
-
kind: "input",
|
|
3600
|
-
subagentId,
|
|
3601
|
-
subagentTurnId: event.turnId,
|
|
3602
|
-
toolCallId: event.callId,
|
|
3603
|
-
toolName: canonicalToolName(event.toolName),
|
|
3604
|
-
type: "tool.delta",
|
|
3605
|
-
});
|
|
3606
|
-
if (!pending.childToolInputsByCallId.has(event.callId)) {
|
|
3607
|
-
emitSubagentToolActivity(queue, pending, {
|
|
3608
|
-
callId: event.callId,
|
|
3609
|
-
subagentId,
|
|
3610
|
-
subagentTurnId: event.turnId,
|
|
3611
|
-
toolName: event.toolName,
|
|
3612
|
-
});
|
|
3613
|
-
}
|
|
3614
|
-
else {
|
|
3615
|
-
pending.streamedChildToolCallIds.add(event.callId);
|
|
3616
|
-
}
|
|
3617
|
-
return;
|
|
3618
|
-
}
|
|
3619
|
-
const callId = event.callId;
|
|
3620
|
-
const existing = pending.inputDeltasByCallId.get(callId) ?? {
|
|
3621
|
-
deltas: [],
|
|
3622
|
-
toolName: canonicalToolName(event.toolName),
|
|
3623
|
-
};
|
|
3624
|
-
existing.toolName = canonicalToolName(event.toolName);
|
|
3625
|
-
existing.deltas.push(event.delta);
|
|
3626
|
-
pending.toolNamesByCallId.set(callId, existing.toolName);
|
|
3627
|
-
pending.inputDeltasByCallId.set(callId, existing);
|
|
3628
|
-
pending.streamedToolInputCallIds.add(callId);
|
|
3629
|
-
queue.push({
|
|
3630
|
-
delta: event.delta,
|
|
3631
|
-
kind: "input",
|
|
3632
|
-
toolCallId: callId,
|
|
3633
|
-
toolName: existing.toolName,
|
|
3634
|
-
type: "tool.delta",
|
|
3635
|
-
});
|
|
3636
|
-
return;
|
|
3637
|
-
}
|
|
3638
|
-
if (event.type === "turn.tool-output.delta") {
|
|
3639
|
-
if (event.parentTurnId && event.relation) {
|
|
3640
|
-
const subagentId = childStreamSubagentId(event);
|
|
3641
|
-
ensureSubagentStarted(queue, pending, subagentId, relationScopeLabel(event.relation));
|
|
3642
|
-
pending.toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
3643
|
-
queue.push({
|
|
3644
|
-
delta: event.delta,
|
|
3645
|
-
kind: "output",
|
|
3646
|
-
subagentId,
|
|
3647
|
-
subagentTurnId: event.turnId,
|
|
3648
|
-
toolCallId: event.callId,
|
|
3649
|
-
toolName: canonicalToolName(event.toolName),
|
|
3650
|
-
type: "tool.delta",
|
|
3651
|
-
});
|
|
3652
|
-
return;
|
|
3653
|
-
}
|
|
3654
|
-
pending.toolNamesByCallId.set(event.callId, canonicalToolName(event.toolName));
|
|
3655
|
-
queue.push({
|
|
3656
|
-
delta: event.delta,
|
|
3657
|
-
kind: "output",
|
|
3658
|
-
toolCallId: event.callId,
|
|
3659
|
-
toolName: canonicalToolName(event.toolName),
|
|
3660
|
-
type: "tool.delta",
|
|
3661
|
-
});
|
|
3662
|
-
return;
|
|
3663
|
-
}
|
|
3664
|
-
if (event.type === "child-task.started") {
|
|
3665
|
-
ensureSubagentStarted(queue, pending, childTaskSubagentId(event), event.record.objective);
|
|
3666
|
-
return;
|
|
3667
|
-
}
|
|
3668
|
-
if (event.type === "child-task.completed" || event.type === "child-task.joined") {
|
|
3669
|
-
const subagentId = childTaskSubagentId(event);
|
|
3670
|
-
ensureSubagentStarted(queue, pending, subagentId, event.record.objective);
|
|
3671
|
-
queue.push({ state: "completed", subagentId, type: "subagent.finished" });
|
|
3672
|
-
return;
|
|
3673
|
-
}
|
|
3674
|
-
if (event.type === "child-task.failed" || event.type === "child-task.rejected") {
|
|
3675
|
-
const subagentId = childTaskSubagentId(event);
|
|
3676
|
-
ensureSubagentStarted(queue, pending, subagentId, event.record.objective);
|
|
3677
|
-
queue.push({
|
|
3678
|
-
state: event.type === "child-task.failed" ? "failed" : "rejected",
|
|
3679
|
-
subagentId,
|
|
3680
|
-
type: "subagent.finished",
|
|
3681
|
-
});
|
|
3682
|
-
return;
|
|
3683
|
-
}
|
|
3684
|
-
if (event.type === "turn.item.added") {
|
|
3685
|
-
const item = event.item;
|
|
3686
|
-
if (item.kind === "reasoning") {
|
|
3687
|
-
logLlmDebug("bridge.reasoning.item.added", {
|
|
3688
|
-
content: item.content,
|
|
3689
|
-
draft: item.status === "draft",
|
|
3690
|
-
pendingStreamedReasoning: pending.streamedReasoning,
|
|
3691
|
-
summary: item.summary,
|
|
3692
|
-
threadId,
|
|
3693
|
-
turnId: pending.turnId,
|
|
3694
|
-
});
|
|
3695
|
-
if (!isDraftReasoningItem(item) &&
|
|
3696
|
-
hasVisibleReasoningText(item.content ?? item.summary))
|
|
3697
|
-
pending.sawVisibleReasoning = true;
|
|
3698
|
-
if (!pending.streamedReasoning && !isDraftReasoningItem(item))
|
|
3699
|
-
pending.reasoningSummaries.push(item.content ?? item.summary);
|
|
3700
|
-
return;
|
|
3701
|
-
}
|
|
3702
|
-
if (item.kind === "message" && item.role === "user") {
|
|
3703
|
-
if (consumePendingSteerPrompt(pending, item.text)) {
|
|
3704
|
-
queue.push({
|
|
3705
|
-
content: item.text,
|
|
3706
|
-
createdAtMs: parseIsoTimeMs(item.createdAt) ?? eventCreatedAtMs,
|
|
3707
|
-
type: "steer_injected",
|
|
3708
|
-
});
|
|
3709
|
-
}
|
|
3710
|
-
return;
|
|
3711
|
-
}
|
|
3712
|
-
if (item.kind === "message" && item.role === "agent") {
|
|
3713
|
-
queue.push({
|
|
3714
|
-
message: { content: item.text },
|
|
3715
|
-
originalEventType: "thread_item_message",
|
|
3716
|
-
type: "assistant.message",
|
|
3717
|
-
});
|
|
3718
|
-
return;
|
|
3719
|
-
}
|
|
3720
|
-
if (item.kind === "tool-call") {
|
|
3721
|
-
const toolCallId = resolveToolItemCallId(item);
|
|
3722
|
-
const toolName = canonicalToolName(item.toolName);
|
|
3723
|
-
pending.toolNamesByCallId.set(toolCallId, toolName);
|
|
3724
|
-
const input = parseToolInput(item.input);
|
|
3725
|
-
pending.toolInputsByCallId.set(toolCallId, input);
|
|
3726
|
-
queue.push({
|
|
3727
|
-
input,
|
|
3728
|
-
originalEventType: pending.startedToolCallIds.has(toolCallId)
|
|
3729
|
-
? "tool_call_end"
|
|
3730
|
-
: "tool_call",
|
|
3731
|
-
startedAtMs: parseIsoTimeMs(item.createdAt) ?? eventCreatedAtMs,
|
|
3732
|
-
toolCallId,
|
|
3733
|
-
toolName,
|
|
3734
|
-
type: "tool.started",
|
|
3735
|
-
});
|
|
3736
|
-
pending.startedToolCallIds.delete(toolCallId);
|
|
3737
|
-
const pendingInput = pending.inputDeltasByCallId.get(toolCallId);
|
|
3738
|
-
if (pendingInput && !pending.streamedToolInputCallIds.has(toolCallId)) {
|
|
3739
|
-
for (const delta of pendingInput.deltas) {
|
|
3740
|
-
queue.push({
|
|
3741
|
-
delta,
|
|
3742
|
-
kind: "input",
|
|
3743
|
-
toolCallId,
|
|
3744
|
-
toolName,
|
|
3745
|
-
type: "tool.delta",
|
|
3746
|
-
});
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
pending.inputDeltasByCallId.delete(toolCallId);
|
|
3750
|
-
pending.streamedToolInputCallIds.delete(toolCallId);
|
|
3751
|
-
return;
|
|
3752
|
-
}
|
|
3753
|
-
if (item.kind === "tool-result") {
|
|
3754
|
-
const toolCallId = readExplicitToolCallId(item) ?? normalizeText(item.id);
|
|
3755
|
-
const toolName = canonicalToolName(item.toolName);
|
|
3756
|
-
pending.toolNamesByCallId.set(toolCallId, toolName);
|
|
3757
|
-
queue.push({
|
|
3758
|
-
...(item.errorCode ? { errorCode: item.errorCode } : {}),
|
|
3759
|
-
...(item.failureKind ? { failureKind: item.failureKind } : {}),
|
|
3760
|
-
isError: item.outcome === "error",
|
|
3761
|
-
output: {
|
|
3762
|
-
output: item.output,
|
|
3763
|
-
...(item.structuredContent !== undefined
|
|
3764
|
-
? { structuredContent: item.structuredContent }
|
|
3765
|
-
: {}),
|
|
3766
|
-
},
|
|
3767
|
-
toolCallId,
|
|
3768
|
-
toolName,
|
|
3769
|
-
type: "tool.finished",
|
|
3770
|
-
});
|
|
3771
|
-
return;
|
|
3772
|
-
}
|
|
3773
|
-
if (item.kind === "control" &&
|
|
3774
|
-
item.action === "child-thread-paused" &&
|
|
3775
|
-
item.childThreadId) {
|
|
3776
|
-
ensureSubagentStarted(queue, pending, item.childThreadId, pending.subagentDescriptionsById.get(item.childThreadId) ?? item.childThreadId);
|
|
3777
|
-
queue.push({
|
|
3778
|
-
...(item.requestId ? { requestId: item.requestId } : {}),
|
|
3779
|
-
state: "paused",
|
|
3780
|
-
subagentId: item.childThreadId,
|
|
3781
|
-
type: "subagent.state",
|
|
3782
|
-
});
|
|
3783
|
-
return;
|
|
3784
|
-
}
|
|
3785
|
-
if (item.kind === "control" &&
|
|
3786
|
-
item.action === "child-thread-resumed" &&
|
|
3787
|
-
item.childThreadId) {
|
|
3788
|
-
ensureSubagentStarted(queue, pending, item.childThreadId, pending.subagentDescriptionsById.get(item.childThreadId) ?? item.childThreadId);
|
|
3789
|
-
queue.push({
|
|
3790
|
-
...(item.requestId ? { requestId: item.requestId } : {}),
|
|
3791
|
-
state: "running",
|
|
3792
|
-
subagentId: item.childThreadId,
|
|
3793
|
-
type: "subagent.state",
|
|
3794
|
-
});
|
|
3795
|
-
return;
|
|
3796
|
-
}
|
|
3797
|
-
if (item.kind === "control" &&
|
|
3798
|
-
item.action === "child-thread-completed" &&
|
|
3799
|
-
item.childThreadId) {
|
|
3800
|
-
const subagentId = item.childThreadId;
|
|
3801
|
-
const compactSummary = normalizeText(item.summary) ||
|
|
3802
|
-
pending.subagentDescriptionsById.get(subagentId) ||
|
|
3803
|
-
subagentId;
|
|
3804
|
-
ensureSubagentStarted(queue, pending, subagentId, compactSummary);
|
|
3805
|
-
queue.push({
|
|
3806
|
-
subagentId,
|
|
3807
|
-
taskDescription: compactSummary,
|
|
3808
|
-
type: "subagent.started",
|
|
3809
|
-
});
|
|
3810
|
-
queue.push({ state: "completed", subagentId, type: "subagent.finished" });
|
|
3811
|
-
return;
|
|
3812
|
-
}
|
|
3813
|
-
if (item.kind === "error") {
|
|
3814
|
-
if (item.scope === "child-thread" && item.childThreadId) {
|
|
3815
|
-
pending.error = item;
|
|
3816
|
-
ensureSubagentStarted(queue, pending, item.childThreadId, pending.subagentDescriptionsById.get(item.childThreadId) ?? item.childThreadId);
|
|
3817
|
-
if (normalizeText(item.message)) {
|
|
3818
|
-
queue.push({
|
|
3819
|
-
delta: item.message,
|
|
3820
|
-
stream: "error",
|
|
3821
|
-
subagentId: item.childThreadId,
|
|
3822
|
-
subagentTurnId: event.turnId,
|
|
3823
|
-
type: "subagent.output",
|
|
3824
|
-
});
|
|
3825
|
-
}
|
|
3826
|
-
queue.push({
|
|
3827
|
-
state: "failed",
|
|
3828
|
-
subagentId: item.childThreadId,
|
|
3829
|
-
type: "subagent.finished",
|
|
3830
|
-
});
|
|
3831
|
-
return;
|
|
3832
|
-
}
|
|
3833
|
-
pending.error = item;
|
|
3834
|
-
}
|
|
3835
|
-
return;
|
|
3836
|
-
}
|
|
3837
|
-
if (event.type === "child-thread.item.added") {
|
|
3838
|
-
const subagentId = childThreadSubagentId(event);
|
|
3839
|
-
ensureSubagentStarted(queue, pending, subagentId, pending.subagentDescriptionsById.get(subagentId) ?? childScopeLabel(event));
|
|
3840
|
-
if (event.item.kind === "tool-call") {
|
|
3841
|
-
const callId = resolveToolItemCallId(event.item);
|
|
3842
|
-
const toolName = canonicalToolName(event.item.toolName);
|
|
3843
|
-
const input = parseToolInput(event.item.input);
|
|
3844
|
-
pending.toolNamesByCallId.set(callId, toolName);
|
|
3845
|
-
pending.toolInputsByCallId.set(callId, input);
|
|
3846
|
-
queue.push({
|
|
3847
|
-
input,
|
|
3848
|
-
originalEventType: pending.streamedChildToolCallIds.has(callId)
|
|
3849
|
-
? "tool_call_end"
|
|
3850
|
-
: "tool_call",
|
|
3851
|
-
startedAtMs: parseIsoTimeMs(event.item.createdAt) ?? eventCreatedAtMs,
|
|
3852
|
-
subagentId,
|
|
3853
|
-
subagentTurnId: event.childTurnId,
|
|
3854
|
-
toolCallId: callId,
|
|
3855
|
-
toolName,
|
|
3856
|
-
type: "tool.started",
|
|
3857
|
-
});
|
|
3858
|
-
queue.push({
|
|
3859
|
-
delta: buildChildThreadText(event) ?? formatSubagentToolActivityLabel(toolName, input),
|
|
3860
|
-
stream: "tool",
|
|
3861
|
-
subagentId,
|
|
3862
|
-
subagentTurnId: event.childTurnId,
|
|
3863
|
-
type: "subagent.output",
|
|
3864
|
-
});
|
|
3865
|
-
return;
|
|
3866
|
-
}
|
|
3867
|
-
if (event.item.kind === "message" && event.item.role === "agent") {
|
|
3868
|
-
const text = buildChildThreadText(event);
|
|
3869
|
-
if (text) {
|
|
3870
|
-
queue.push({
|
|
3871
|
-
delta: text,
|
|
3872
|
-
stream: "text",
|
|
3873
|
-
subagentId,
|
|
3874
|
-
subagentTurnId: event.childTurnId,
|
|
3875
|
-
type: "subagent.output",
|
|
3876
|
-
});
|
|
3877
|
-
}
|
|
3878
|
-
return;
|
|
3879
|
-
}
|
|
3880
|
-
if (event.item.kind === "tool-result") {
|
|
3881
|
-
const toolCallId = readExplicitToolCallId(event.item) ?? normalizeText(event.item.id);
|
|
3882
|
-
const toolName = canonicalToolName(event.item.toolName) ||
|
|
3883
|
-
pending.toolNamesByCallId.get(toolCallId) ||
|
|
3884
|
-
toolCallId;
|
|
3885
|
-
pending.toolNamesByCallId.set(toolCallId, toolName);
|
|
3886
|
-
queue.push({
|
|
3887
|
-
...(event.item.errorCode ? { errorCode: event.item.errorCode } : {}),
|
|
3888
|
-
...(event.item.failureKind ? { failureKind: event.item.failureKind } : {}),
|
|
3889
|
-
isError: event.item.outcome === "error",
|
|
3890
|
-
output: {
|
|
3891
|
-
output: event.item.output,
|
|
3892
|
-
...(event.item.structuredContent !== undefined
|
|
3893
|
-
? { structuredContent: event.item.structuredContent }
|
|
3894
|
-
: {}),
|
|
3895
|
-
},
|
|
3896
|
-
subagentId,
|
|
3897
|
-
subagentTurnId: event.childTurnId,
|
|
3898
|
-
toolCallId,
|
|
3899
|
-
toolName,
|
|
3900
|
-
type: "tool.finished",
|
|
3901
|
-
});
|
|
3902
|
-
if (event.item.outcome === "error") {
|
|
3903
|
-
queue.push({
|
|
3904
|
-
delta: `${toolName} error`,
|
|
3905
|
-
stream: "error",
|
|
3906
|
-
subagentId,
|
|
3907
|
-
subagentTurnId: event.childTurnId,
|
|
3908
|
-
type: "subagent.output",
|
|
3909
|
-
});
|
|
3910
|
-
}
|
|
3911
|
-
return;
|
|
3912
|
-
}
|
|
3913
|
-
if (event.item.kind === "error") {
|
|
3914
|
-
const text = buildChildThreadText(event);
|
|
3915
|
-
if (!text)
|
|
3916
|
-
return;
|
|
3917
|
-
queue.push({
|
|
3918
|
-
delta: text,
|
|
3919
|
-
stream: "error",
|
|
3920
|
-
subagentId,
|
|
3921
|
-
subagentTurnId: event.childTurnId,
|
|
3922
|
-
type: "subagent.output",
|
|
3923
|
-
});
|
|
3924
|
-
}
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
if (event.type === "approval.requested") {
|
|
3928
|
-
pending.approval = event.approval;
|
|
3929
|
-
pending.status = "paused";
|
|
3930
|
-
const childTool = pending.childToolInputsByCallId.get(event.approval.toolCallId);
|
|
3931
|
-
const toolCallId = normalizeText(event.approval.toolCallId);
|
|
3932
|
-
const toolName = pending.toolNamesByCallId.get(event.approval.toolCallId) ??
|
|
3933
|
-
childTool?.toolName ??
|
|
3934
|
-
canonicalToolName(event.approval.action);
|
|
3935
|
-
if (toolCallId && !pending.toolNamesByCallId.has(toolCallId)) {
|
|
3936
|
-
queue.push({
|
|
3937
|
-
input: pending.toolInputsByCallId.get(toolCallId) ?? {},
|
|
3938
|
-
originalEventType: "tool_call",
|
|
3939
|
-
...(childTool?.subagentId ? { subagentId: childTool.subagentId } : {}),
|
|
3940
|
-
toolCallId,
|
|
3941
|
-
toolName,
|
|
3942
|
-
type: "tool.started",
|
|
3943
|
-
});
|
|
3944
|
-
}
|
|
3945
|
-
queue.push({
|
|
3946
|
-
actionId: event.approval.id,
|
|
3947
|
-
args: pending.toolInputsByCallId.get(event.approval.toolCallId),
|
|
3948
|
-
availableScopes: event.approval.availableScopes,
|
|
3949
|
-
kind: "tool_approval",
|
|
3950
|
-
permission: approvalPermission(toolName),
|
|
3951
|
-
reason: event.approval.reason,
|
|
3952
|
-
riskLevel: event.approval.riskClass ?? approvalRiskLevel(toolName),
|
|
3953
|
-
...(childTool?.subagentId ? { subagentId: childTool.subagentId } : {}),
|
|
3954
|
-
toolCallId: event.approval.toolCallId,
|
|
3955
|
-
toolName,
|
|
3956
|
-
type: "action.required",
|
|
3957
|
-
});
|
|
3958
|
-
queue.close();
|
|
3959
|
-
return;
|
|
3960
|
-
}
|
|
3961
|
-
if (event.type === "action.required" && event.kind === "ask_user") {
|
|
3962
|
-
const toolCallId = normalizeText(event.toolCallId) || undefined;
|
|
3963
|
-
const toolName = canonicalToolName(event.toolName);
|
|
3964
|
-
if (toolCallId && !pending.toolNamesByCallId.has(toolCallId)) {
|
|
3965
|
-
pending.toolNamesByCallId.set(toolCallId, toolName);
|
|
3966
|
-
pending.toolInputsByCallId.set(toolCallId, {});
|
|
3967
|
-
queue.push({
|
|
3968
|
-
input: {},
|
|
3969
|
-
originalEventType: "tool_call",
|
|
3970
|
-
toolCallId,
|
|
3971
|
-
toolName,
|
|
3972
|
-
type: "tool.started",
|
|
3973
|
-
});
|
|
3974
|
-
}
|
|
3975
|
-
pending.action = {
|
|
3976
|
-
actionId: event.actionId,
|
|
3977
|
-
questions: event.questions,
|
|
3978
|
-
toolCallId,
|
|
3979
|
-
toolName,
|
|
3980
|
-
};
|
|
3981
|
-
pending.status = "paused";
|
|
3982
|
-
queue.push({
|
|
3983
|
-
actionId: event.actionId,
|
|
3984
|
-
kind: "ask_user",
|
|
3985
|
-
questions: event.questions,
|
|
3986
|
-
toolCallId,
|
|
3987
|
-
toolName,
|
|
3988
|
-
type: "action.required",
|
|
3989
|
-
});
|
|
3990
|
-
queue.close();
|
|
3991
|
-
return;
|
|
3992
|
-
}
|
|
3993
|
-
if (event.type === "thread.usage.updated") {
|
|
3994
|
-
pending.latestUsage = usageMetadataFromSessionUsage(event.inclusive);
|
|
3995
|
-
updatePendingReasoningTokens(pending, event.last);
|
|
3996
|
-
queue.push({
|
|
3997
|
-
lastRequestBudget: event.lastRequestBudget
|
|
3998
|
-
? cloneJson(event.lastRequestBudget)
|
|
3999
|
-
: undefined,
|
|
4000
|
-
type: "usage.updated",
|
|
4001
|
-
usage: pending.latestUsage,
|
|
4002
|
-
});
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
if (event.type === "thread.compaction.started") {
|
|
4006
|
-
queue.push({
|
|
4007
|
-
state: "started",
|
|
4008
|
-
tokensBefore: event.estimatedTokensBefore,
|
|
4009
|
-
trigger: event.reason === "manual-user" ? "manual" : "auto",
|
|
4010
|
-
type: "context.compression",
|
|
4011
|
-
});
|
|
4012
|
-
return;
|
|
4013
|
-
}
|
|
4014
|
-
if (event.type === "thread.compacted") {
|
|
4015
|
-
const manualCompaction = event.reason === "manual-user";
|
|
4016
|
-
const maxInputTokens = Math.max(0, Math.floor(Number(event.diagnostics.compaction.lastBudgetTokens ?? 0)));
|
|
4017
|
-
queue.push({
|
|
4018
|
-
diagnostics: {
|
|
4019
|
-
autoCompactAttempt: {
|
|
4020
|
-
compactedMessageCount: event.summary.compactedMessageCount,
|
|
4021
|
-
estimatedInputTokens: event.estimatedTokensBefore,
|
|
4022
|
-
estimatedInputTokensAfter: event.estimatedTokensAfter,
|
|
4023
|
-
estimatedSavedTokens: event.estimatedTokensAfter !== undefined &&
|
|
4024
|
-
event.estimatedTokensBefore !== undefined
|
|
4025
|
-
? Math.max(0, event.estimatedTokensBefore - event.estimatedTokensAfter)
|
|
4026
|
-
: undefined,
|
|
4027
|
-
maxInputTokens,
|
|
4028
|
-
thresholdTokens: resolveAutoCompactionThresholdTokens(maxInputTokens),
|
|
4029
|
-
status: "compacted",
|
|
4030
|
-
trigger: manualCompaction ? "manual" : "threshold",
|
|
4031
|
-
},
|
|
4032
|
-
},
|
|
4033
|
-
state: "completed",
|
|
4034
|
-
tokensAfter: event.estimatedTokensAfter,
|
|
4035
|
-
tokensBefore: event.estimatedTokensBefore,
|
|
4036
|
-
trigger: manualCompaction ? "manual" : "auto",
|
|
4037
|
-
type: "context.compression",
|
|
4038
|
-
});
|
|
4039
|
-
return;
|
|
4040
|
-
}
|
|
4041
|
-
if (event.type === "turn.completed") {
|
|
4042
|
-
pending.action = undefined;
|
|
4043
|
-
pending.approval = undefined;
|
|
4044
|
-
pending.interruptRequested = false;
|
|
4045
|
-
pending.status = "completed";
|
|
4046
|
-
const reasoningDiagnostics = resolvePendingReasoningDiagnostics(pending);
|
|
4047
|
-
void persistTurnReasoningDiagnostics(pending, reasoningDiagnostics);
|
|
4048
|
-
queue.push({
|
|
4049
|
-
reasoningDiagnostics,
|
|
4050
|
-
status: "completed",
|
|
4051
|
-
type: "run.finished",
|
|
4052
|
-
usage: pending.latestUsage,
|
|
4053
|
-
});
|
|
4054
|
-
finish?.();
|
|
4055
|
-
return;
|
|
4056
|
-
}
|
|
4057
|
-
if (event.type === "turn.interrupted") {
|
|
4058
|
-
pending.action = undefined;
|
|
4059
|
-
pending.approval = undefined;
|
|
4060
|
-
pending.interruptRequested = false;
|
|
4061
|
-
pending.status = "interrupted";
|
|
4062
|
-
const reasoningDiagnostics = resolvePendingReasoningDiagnostics(pending);
|
|
4063
|
-
void persistTurnReasoningDiagnostics(pending, reasoningDiagnostics);
|
|
4064
|
-
queue.push({
|
|
4065
|
-
reasoningDiagnostics,
|
|
4066
|
-
status: "interrupted",
|
|
4067
|
-
type: "run.finished",
|
|
4068
|
-
usage: pending.latestUsage,
|
|
4069
|
-
});
|
|
4070
|
-
finish?.();
|
|
4071
|
-
return;
|
|
4072
|
-
}
|
|
4073
|
-
if (event.type === "turn.failed" || event.type === "turn.blocked") {
|
|
4074
|
-
pending.action = undefined;
|
|
4075
|
-
pending.approval = undefined;
|
|
4076
|
-
pending.interruptRequested = false;
|
|
4077
|
-
pending.status = "failed";
|
|
4078
|
-
const reasoningDiagnostics = resolvePendingReasoningDiagnostics(pending);
|
|
4079
|
-
void persistTurnReasoningDiagnostics(pending, reasoningDiagnostics);
|
|
4080
|
-
queue.push({
|
|
4081
|
-
error: pending.error ??
|
|
4082
|
-
new Error(event.type === "turn.blocked" ? "Turn blocked." : "Turn failed."),
|
|
4083
|
-
reasoningDiagnostics,
|
|
4084
|
-
type: "run.error",
|
|
4085
|
-
usage: pending.latestUsage,
|
|
4086
|
-
});
|
|
4087
|
-
finish?.();
|
|
4088
|
-
}
|
|
4089
|
-
};
|
|
4090
|
-
const unsubscribe = options.runtime.subscribe((envelope) => {
|
|
4091
|
-
if (!catchUpComplete) {
|
|
4092
|
-
bufferedEnvelopes.push(envelope);
|
|
4093
|
-
return;
|
|
4094
|
-
}
|
|
4095
|
-
processEnvelope(envelope);
|
|
4096
|
-
});
|
|
4097
|
-
input.signal?.addEventListener("abort", onAbort);
|
|
4098
|
-
if (input.attachAfterSequence != null && pending.turnId) {
|
|
4099
|
-
const attach = options.runtime.markTurnAttached?.(threadId, pending.turnId);
|
|
4100
|
-
void attach?.catch(() => { });
|
|
4101
|
-
}
|
|
4102
|
-
if (abortRequested) {
|
|
4103
|
-
finish({ detach: true });
|
|
4104
|
-
}
|
|
4105
|
-
else if (input.attachAfterSequence != null) {
|
|
4106
|
-
try {
|
|
4107
|
-
const response = await options.runtime.getThreadEvents({
|
|
4108
|
-
afterSequence: input.attachAfterSequence,
|
|
4109
|
-
threadId,
|
|
4110
|
-
});
|
|
4111
|
-
const envelopes = [...response.events, ...bufferedEnvelopes].sort((a, b) => a.sequence - b.sequence);
|
|
4112
|
-
const seen = new Set();
|
|
4113
|
-
for (const envelope of envelopes) {
|
|
4114
|
-
if (seen.has(envelope.sequence))
|
|
4115
|
-
continue;
|
|
4116
|
-
seen.add(envelope.sequence);
|
|
4117
|
-
processEnvelope(envelope);
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
finally {
|
|
4121
|
-
catchUpComplete = true;
|
|
4122
|
-
bufferedEnvelopes.length = 0;
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
if (input.steerPrompt) {
|
|
4126
|
-
if (!pending.turnId) {
|
|
4127
|
-
queue.push({
|
|
4128
|
-
error: new Error("Turn is not steerable yet."),
|
|
4129
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4130
|
-
type: "run.error",
|
|
4131
|
-
usage: pending.latestUsage,
|
|
4132
|
-
});
|
|
4133
|
-
finish?.();
|
|
4134
|
-
}
|
|
4135
|
-
else {
|
|
4136
|
-
void options.runtime
|
|
4137
|
-
.steerTurn(threadId, pending.turnId, input.steerPrompt)
|
|
4138
|
-
.catch((error) => {
|
|
4139
|
-
pending.error = error;
|
|
4140
|
-
pending.status = "failed";
|
|
4141
|
-
queue.push({
|
|
4142
|
-
error,
|
|
4143
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4144
|
-
type: "run.error",
|
|
4145
|
-
usage: pending.latestUsage,
|
|
4146
|
-
});
|
|
4147
|
-
finish?.();
|
|
4148
|
-
});
|
|
4149
|
-
}
|
|
4150
|
-
}
|
|
4151
|
-
if (pending.approval && typeof input.approved === "boolean") {
|
|
4152
|
-
const approvalId = pending.approval.id;
|
|
4153
|
-
const approvalScope = input.approved && input.alwaysAllowForConversation === true
|
|
4154
|
-
? pending.approval.availableScopes.includes("thread")
|
|
4155
|
-
? "thread"
|
|
4156
|
-
: pending.approval.availableScopes.includes("session")
|
|
4157
|
-
? "session"
|
|
4158
|
-
: "once"
|
|
4159
|
-
: "once";
|
|
4160
|
-
const approvalSequence = pending.lastDeliveredSequence ?? 0;
|
|
4161
|
-
const reportApprovalResumeError = (error) => {
|
|
4162
|
-
if (isApprovalNotFoundError(error)) {
|
|
4163
|
-
pending.error = error;
|
|
4164
|
-
pending.resumeInFlight = false;
|
|
4165
|
-
pending.status = "failed";
|
|
4166
|
-
refreshAttachedStoreThread(threadId);
|
|
4167
|
-
finish?.();
|
|
4168
|
-
return;
|
|
4169
|
-
}
|
|
4170
|
-
pending.error = error;
|
|
4171
|
-
pending.status = "failed";
|
|
4172
|
-
queue.push({
|
|
4173
|
-
error,
|
|
4174
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4175
|
-
type: "run.error",
|
|
4176
|
-
usage: pending.latestUsage,
|
|
4177
|
-
});
|
|
4178
|
-
finish?.();
|
|
4179
|
-
};
|
|
4180
|
-
pending.approval = undefined;
|
|
4181
|
-
pending.status = "running";
|
|
4182
|
-
void options.runtime
|
|
4183
|
-
.respondToApprovalWithScope(approvalId, input.approved ? "allow" : "reject", approvalScope)
|
|
4184
|
-
.then(() => {
|
|
4185
|
-
if (!input.approved)
|
|
4186
|
-
return;
|
|
4187
|
-
approvalResumeWatchdog = setTimeout(() => {
|
|
4188
|
-
if (settled || pending.status !== "running")
|
|
4189
|
-
return;
|
|
4190
|
-
if ((pending.lastDeliveredSequence ?? 0) > approvalSequence)
|
|
4191
|
-
return;
|
|
4192
|
-
void options.runtime
|
|
4193
|
-
.respondToApprovalWithScope(approvalId, "allow", approvalScope)
|
|
4194
|
-
.catch(reportApprovalResumeError);
|
|
4195
|
-
}, 750);
|
|
4196
|
-
approvalResumeWatchdog.unref?.();
|
|
4197
|
-
})
|
|
4198
|
-
.catch(reportApprovalResumeError);
|
|
4199
|
-
}
|
|
4200
|
-
if (pending.action && input.rejected) {
|
|
4201
|
-
pending.action = undefined;
|
|
4202
|
-
pending.interruptRequested = true;
|
|
4203
|
-
pending.status = "running";
|
|
4204
|
-
if (!pending.turnId) {
|
|
4205
|
-
pending.interruptRequested = false;
|
|
4206
|
-
pending.error = new Error("Turn is not interruptible yet.");
|
|
4207
|
-
pending.status = "failed";
|
|
4208
|
-
queue.push({
|
|
4209
|
-
error: pending.error,
|
|
4210
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4211
|
-
type: "run.error",
|
|
4212
|
-
usage: pending.latestUsage,
|
|
4213
|
-
});
|
|
4214
|
-
finish?.();
|
|
4215
|
-
}
|
|
4216
|
-
else {
|
|
4217
|
-
void options.runtime
|
|
4218
|
-
.interruptTurn(threadId, pending.turnId)
|
|
4219
|
-
.catch(async (error) => {
|
|
4220
|
-
if (isBenignAskUserCancelInterruptError(error)) {
|
|
4221
|
-
pending.interruptRequested = false;
|
|
4222
|
-
pending.status = "interrupted";
|
|
4223
|
-
const reasoningDiagnostics = resolvePendingReasoningDiagnostics(pending);
|
|
4224
|
-
void persistTurnReasoningDiagnostics(pending, reasoningDiagnostics);
|
|
4225
|
-
queue.push({
|
|
4226
|
-
reasoningDiagnostics,
|
|
4227
|
-
status: "interrupted",
|
|
4228
|
-
type: "run.finished",
|
|
4229
|
-
usage: pending.latestUsage,
|
|
4230
|
-
});
|
|
4231
|
-
finish?.();
|
|
4232
|
-
return;
|
|
4233
|
-
}
|
|
4234
|
-
pending.interruptRequested = false;
|
|
4235
|
-
pending.error = error;
|
|
4236
|
-
pending.status = "failed";
|
|
4237
|
-
queue.push({
|
|
4238
|
-
error,
|
|
4239
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4240
|
-
type: "run.error",
|
|
4241
|
-
usage: pending.latestUsage,
|
|
4242
|
-
});
|
|
4243
|
-
finish?.();
|
|
4244
|
-
});
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
if (pending.action && input.answers) {
|
|
4248
|
-
const actionId = pending.action.actionId;
|
|
4249
|
-
pending.action = undefined;
|
|
4250
|
-
pending.resumeInFlight = true;
|
|
4251
|
-
void options.runtime
|
|
4252
|
-
.respondToAction(actionId, input.answers)
|
|
4253
|
-
.catch((error) => {
|
|
4254
|
-
pending.error = error;
|
|
4255
|
-
pending.status = "failed";
|
|
4256
|
-
queue.push({
|
|
4257
|
-
error,
|
|
4258
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4259
|
-
type: "run.error",
|
|
4260
|
-
usage: pending.latestUsage,
|
|
4261
|
-
});
|
|
4262
|
-
finish?.();
|
|
4263
|
-
})
|
|
4264
|
-
.finally(() => {
|
|
4265
|
-
pending.resumeInFlight = false;
|
|
4266
|
-
});
|
|
4267
|
-
}
|
|
4268
|
-
void pending.promise.catch((error) => {
|
|
4269
|
-
pending.error = error;
|
|
4270
|
-
if (pending.status === "paused" ||
|
|
4271
|
-
pending.interruptRequested ||
|
|
4272
|
-
pending.status === "interrupted") {
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
pending.status = "failed";
|
|
4276
|
-
clearBackgroundTracking(pending);
|
|
4277
|
-
clearPendingTurnIfTerminal(pending);
|
|
4278
|
-
if (settled)
|
|
4279
|
-
return;
|
|
4280
|
-
queue.push({
|
|
4281
|
-
error,
|
|
4282
|
-
reasoningDiagnostics: resolvePendingReasoningDiagnostics(pending),
|
|
4283
|
-
type: "run.error",
|
|
4284
|
-
usage: pending.latestUsage,
|
|
4285
|
-
});
|
|
4286
|
-
finish?.();
|
|
4287
|
-
});
|
|
4288
|
-
for await (const message of queue)
|
|
4289
|
-
yield message;
|
|
4290
|
-
clearPendingTurnIfTerminal(pending);
|
|
4291
|
-
};
|
|
4292
|
-
const recoverPausedTurnForResume = async (input) => {
|
|
4293
|
-
const resumed = await options.runtime.resumeThread(threadId);
|
|
4294
|
-
if (input.kind === "ask_user") {
|
|
4295
|
-
const action = resumed.pendingAction;
|
|
4296
|
-
if (!action || action.actionId !== input.actionId)
|
|
4297
|
-
return null;
|
|
4298
|
-
return createPendingTurnState({
|
|
4299
|
-
action: {
|
|
4300
|
-
actionId: action.actionId,
|
|
4301
|
-
questions: action.questions,
|
|
4302
|
-
...(action.toolCallId ? { toolCallId: action.toolCallId } : {}),
|
|
4303
|
-
toolName: action.toolName,
|
|
4304
|
-
},
|
|
4305
|
-
status: "paused",
|
|
4306
|
-
turnId: action.turnId,
|
|
4307
|
-
});
|
|
4308
|
-
}
|
|
4309
|
-
const approval = resumed.pendingApproval;
|
|
4310
|
-
if (!approval || approval.id !== input.actionId)
|
|
4311
|
-
return null;
|
|
4312
|
-
return createPendingTurnState({
|
|
4313
|
-
approval,
|
|
4314
|
-
status: "paused",
|
|
4315
|
-
turnId: approval.turnId,
|
|
4316
|
-
});
|
|
4317
|
-
};
|
|
4318
|
-
return {
|
|
4319
|
-
id: threadId,
|
|
4320
|
-
async *run(input, runOptions) {
|
|
4321
|
-
const threadState = getThreadState(threadId);
|
|
4322
|
-
const metadata = input.metadata;
|
|
4323
|
-
const attach = Boolean(metadata?.attach || metadata?.resume);
|
|
4324
|
-
const steer = Boolean(metadata?.steer);
|
|
4325
|
-
if (steer &&
|
|
4326
|
-
(threadState.pendingTurn?.status === "running" || threadState.pendingTurn?.resumeInFlight)) {
|
|
4327
|
-
const prompt = extractPromptText(input.content);
|
|
4328
|
-
if (!prompt) {
|
|
4329
|
-
yield { error: new Error("Missing steer prompt."), type: "run.error" };
|
|
4330
|
-
return;
|
|
4331
|
-
}
|
|
4332
|
-
yield* streamPendingTurn(threadState.pendingTurn, {
|
|
4333
|
-
signal: runOptions?.signal,
|
|
4334
|
-
steerPrompt: prompt,
|
|
4335
|
-
});
|
|
4336
|
-
return;
|
|
4337
|
-
}
|
|
4338
|
-
if (attach) {
|
|
4339
|
-
const pending = threadState.pendingTurn;
|
|
4340
|
-
if (!pending || (pending.status !== "running" && !pending.resumeInFlight)) {
|
|
4341
|
-
yield {
|
|
4342
|
-
error: new Error("Thread does not have a running turn to attach."),
|
|
4343
|
-
type: "run.error",
|
|
4344
|
-
};
|
|
4345
|
-
return;
|
|
4346
|
-
}
|
|
4347
|
-
yield* streamPendingTurn(pending, {
|
|
4348
|
-
attachAfterSequence: pending.lastDeliveredSequence,
|
|
4349
|
-
signal: runOptions?.signal,
|
|
4350
|
-
});
|
|
4351
|
-
return;
|
|
4352
|
-
}
|
|
4353
|
-
if (threadState.pendingTurn?.status === "running" ||
|
|
4354
|
-
threadState.pendingTurn?.resumeInFlight) {
|
|
4355
|
-
const stuckPending = threadState.pendingTurn;
|
|
4356
|
-
// If the running turn was detached after an abort (e.g. user pressed Ctrl+C after
|
|
4357
|
-
// submitting ask_user answers), wait for it to settle before blocking the new message.
|
|
4358
|
-
if (stuckPending.backgroundUnsubscribe && !stuckPending.resumeInFlight) {
|
|
4359
|
-
await waitForPendingTurnToSettle(threadId, stuckPending);
|
|
4360
|
-
}
|
|
4361
|
-
if (threadState.pendingTurn === stuckPending &&
|
|
4362
|
-
(stuckPending.status === "running" || stuckPending.resumeInFlight)) {
|
|
4363
|
-
yield { error: new Error("Thread already has a running turn."), type: "run.error" };
|
|
4364
|
-
return;
|
|
4365
|
-
}
|
|
4366
|
-
if (threadState.pendingTurn) {
|
|
4367
|
-
yield { error: new Error("Thread already has a running turn."), type: "run.error" };
|
|
4368
|
-
return;
|
|
4369
|
-
}
|
|
4370
|
-
// pendingTurn was cleared — fall through to start the new turn
|
|
4371
|
-
}
|
|
4372
|
-
const prompt = extractPromptText(input.content);
|
|
4373
|
-
const promptContent = normalizePromptContentParts(input.content);
|
|
4374
|
-
if (!prompt && !promptContent?.length) {
|
|
4375
|
-
yield { error: new Error("Missing user prompt."), type: "run.error" };
|
|
4376
|
-
return;
|
|
4377
|
-
}
|
|
4378
|
-
const promise = options.runtime.runTurn({
|
|
4379
|
-
agentProfile: options.agentProfile,
|
|
4380
|
-
prompt,
|
|
4381
|
-
...(promptContent?.length ? { promptContent } : {}),
|
|
4382
|
-
threadId,
|
|
4383
|
-
});
|
|
4384
|
-
void promise.catch(() => { });
|
|
4385
|
-
const pending = createPendingTurnState({
|
|
4386
|
-
promise,
|
|
4387
|
-
status: "running",
|
|
4388
|
-
});
|
|
4389
|
-
threadState.pendingTurn = pending;
|
|
4390
|
-
yield* streamPendingTurn(pending, { signal: runOptions?.signal });
|
|
4391
|
-
},
|
|
4392
|
-
async *resume(input, resumeOptions) {
|
|
4393
|
-
const threadState = getThreadState(threadId);
|
|
4394
|
-
let pending = threadState.pendingTurn;
|
|
4395
|
-
if (!pending || pending.status !== "paused") {
|
|
4396
|
-
const recovered = await recoverPausedTurnForResume(input).catch(() => null);
|
|
4397
|
-
if (!recovered) {
|
|
4398
|
-
yield { error: new Error("Session is not paused."), type: "run.error" };
|
|
4399
|
-
return;
|
|
4400
|
-
}
|
|
4401
|
-
pending = recovered;
|
|
4402
|
-
threadState.pendingTurn = recovered;
|
|
4403
|
-
}
|
|
4404
|
-
if (input.kind === "tool_approval") {
|
|
4405
|
-
yield* streamPendingTurn(pending, {
|
|
4406
|
-
alwaysAllowForConversation: input.alwaysAllowForConversation,
|
|
4407
|
-
approved: input.approved,
|
|
4408
|
-
reason: input.reason,
|
|
4409
|
-
signal: resumeOptions?.signal,
|
|
4410
|
-
});
|
|
4411
|
-
return;
|
|
4412
|
-
}
|
|
4413
|
-
yield* streamPendingTurn(pending, {
|
|
4414
|
-
answers: input.answers ?? {},
|
|
4415
|
-
rejected: input.rejected,
|
|
4416
|
-
signal: resumeOptions?.signal,
|
|
4417
|
-
});
|
|
4418
|
-
},
|
|
4419
|
-
async setMode(modeId) {
|
|
4420
|
-
await options.runtime.setThreadMode({
|
|
4421
|
-
mode: threadModeFromCliMode(modeId),
|
|
4422
|
-
threadId,
|
|
4423
|
-
});
|
|
4424
|
-
},
|
|
4425
|
-
async steer(input) {
|
|
4426
|
-
const pending = getThreadState(threadId).pendingTurn;
|
|
4427
|
-
const prompt = normalizeText(input);
|
|
4428
|
-
if (!pending?.turnId || pending.status !== "running" || !prompt)
|
|
4429
|
-
return false;
|
|
4430
|
-
try {
|
|
4431
|
-
await options.runtime.steerTurn(threadId, pending.turnId, prompt);
|
|
4432
|
-
return true;
|
|
4433
|
-
}
|
|
4434
|
-
catch {
|
|
4435
|
-
return false;
|
|
4436
|
-
}
|
|
4437
|
-
},
|
|
4438
|
-
async triggerManualCompaction() {
|
|
4439
|
-
try {
|
|
4440
|
-
const compacted = await options.runtime.compactThread({ threadId });
|
|
4441
|
-
const maxInputTokens = Math.max(0, Math.floor(Number(compacted.diagnostics.compaction.lastBudgetTokens ?? 0)));
|
|
4442
|
-
return {
|
|
4443
|
-
event: {
|
|
4444
|
-
diagnostics: {
|
|
4445
|
-
notification: {
|
|
4446
|
-
maxInputTokens,
|
|
4447
|
-
thresholdTokens: resolveAutoCompactionThresholdTokens(maxInputTokens),
|
|
4448
|
-
},
|
|
4449
|
-
},
|
|
4450
|
-
tokensAfter: compacted.diagnostics.compaction.lastEstimatedTokens,
|
|
4451
|
-
},
|
|
4452
|
-
messageCount: compacted.summary.compactedMessageCount,
|
|
4453
|
-
success: true,
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
catch (error) {
|
|
4457
|
-
const errorCode = manualCompactionFailureCode(error);
|
|
4458
|
-
return {
|
|
4459
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4460
|
-
...(errorCode ? { errorCode } : {}),
|
|
4461
|
-
success: false,
|
|
4462
|
-
};
|
|
4463
|
-
}
|
|
4464
|
-
},
|
|
4465
|
-
async updateConfig(input) {
|
|
4466
|
-
const nextMode = normalizeText(input.modeId);
|
|
4467
|
-
if (nextMode === "agent" || nextMode === "plan") {
|
|
4468
|
-
await options.runtime.setThreadMode({
|
|
4469
|
-
mode: threadModeFromCliMode(nextMode),
|
|
4470
|
-
threadId,
|
|
4471
|
-
});
|
|
4472
|
-
}
|
|
4473
|
-
const metadata = asRecord(input.metadata);
|
|
4474
|
-
if (Object.keys(metadata).length > 0)
|
|
4475
|
-
await options.runtime.updateThreadMetadata(threadId, metadata);
|
|
4476
|
-
},
|
|
4477
|
-
};
|
|
4478
|
-
};
|
|
4479
|
-
const agentSessions = {
|
|
4480
|
-
async create() {
|
|
4481
|
-
return createSession((await options.runtime.startThread({ mode: threadModeFromCliMode(currentMode) })).thread.id);
|
|
4482
|
-
},
|
|
4483
|
-
async destroy(sessionId) {
|
|
4484
|
-
await options.runtime.purgeThread(sessionId);
|
|
4485
|
-
},
|
|
4486
|
-
async get(sessionId) {
|
|
4487
|
-
try {
|
|
4488
|
-
return (await options.runtime.getThread({ threadId: sessionId })).thread;
|
|
4489
|
-
}
|
|
4490
|
-
catch {
|
|
4491
|
-
return null;
|
|
4492
|
-
}
|
|
4493
|
-
},
|
|
4494
|
-
async interrupt(sessionId) {
|
|
4495
|
-
const pending = getThreadState(sessionId).pendingTurn;
|
|
4496
|
-
if (!pending?.turnId)
|
|
4497
|
-
return false;
|
|
4498
|
-
await options.runtime.interruptTurn(sessionId, pending.turnId);
|
|
4499
|
-
await waitForPendingTurnToSettle(sessionId, pending);
|
|
4500
|
-
return true;
|
|
4501
|
-
},
|
|
4502
|
-
async list() {
|
|
4503
|
-
const listed = await options.runtime.listThreads({ limit: 50 });
|
|
4504
|
-
return await filterSessionThreads(options.runtime, listed.threads);
|
|
4505
|
-
},
|
|
4506
|
-
async rename(sessionId, title) {
|
|
4507
|
-
await options.runtime.renameThread(sessionId, title);
|
|
4508
|
-
},
|
|
4509
|
-
async updateMetadata(sessionId, metadata) {
|
|
4510
|
-
await options.runtime.updateThreadMetadata(sessionId, wrapSessionUiMetadata(metadata));
|
|
4511
|
-
},
|
|
4512
|
-
};
|
|
4513
|
-
const agent = {
|
|
4514
|
-
ensureSession: async (input) => {
|
|
4515
|
-
const requestedSessionId = normalizeText(input?.sessionId);
|
|
4516
|
-
if (requestedSessionId) {
|
|
4517
|
-
try {
|
|
4518
|
-
const thread = await resumeBestAgentThread(options.runtime, requestedSessionId, threadModeFromCliMode(input?.modeId ?? currentMode));
|
|
4519
|
-
if (input?.metadata && typeof input.metadata === "object")
|
|
4520
|
-
await options.runtime.updateThreadMetadata(thread.id, input.metadata);
|
|
4521
|
-
return createSession(thread.id);
|
|
4522
|
-
}
|
|
4523
|
-
catch { }
|
|
4524
|
-
}
|
|
4525
|
-
const created = await options.runtime.startThread({
|
|
4526
|
-
mode: threadModeFromCliMode(input?.modeId ?? currentMode),
|
|
4527
|
-
});
|
|
4528
|
-
if (input?.metadata && typeof input.metadata === "object")
|
|
4529
|
-
await options.runtime.updateThreadMetadata(created.thread.id, input.metadata);
|
|
4530
|
-
return createSession(created.thread.id);
|
|
4531
|
-
},
|
|
4532
|
-
getMode() {
|
|
4533
|
-
return currentMode;
|
|
4534
|
-
},
|
|
4535
|
-
invalidateSession(sessionId) {
|
|
4536
|
-
threadStates.delete(sessionId);
|
|
4537
|
-
if (storeRef?.state.sessionId !== sessionId)
|
|
4538
|
-
return;
|
|
4539
|
-
storeRef.state.paused = null;
|
|
4540
|
-
storeRef.state.runningAgentRound = false;
|
|
4541
|
-
storeRef.state.runningToolCallId = null;
|
|
4542
|
-
},
|
|
4543
|
-
openSession: async (sessionId) => {
|
|
4544
|
-
const normalizedSessionId = normalizeText(sessionId);
|
|
4545
|
-
if (!normalizedSessionId)
|
|
4546
|
-
return null;
|
|
4547
|
-
try {
|
|
4548
|
-
await options.runtime.getThread({ threadId: normalizedSessionId });
|
|
4549
|
-
return createSession(normalizedSessionId);
|
|
4550
|
-
}
|
|
4551
|
-
catch {
|
|
4552
|
-
return null;
|
|
4553
|
-
}
|
|
4554
|
-
},
|
|
4555
|
-
sessions: agentSessions,
|
|
4556
|
-
setMode(modeId) {
|
|
4557
|
-
if (currentMode === modeId)
|
|
4558
|
-
return;
|
|
4559
|
-
currentMode = modeId;
|
|
4560
|
-
const threadId = activeThreadId();
|
|
4561
|
-
if (!threadId)
|
|
4562
|
-
return;
|
|
4563
|
-
void options.runtime
|
|
4564
|
-
.setThreadMode({
|
|
4565
|
-
mode: threadModeFromCliMode(modeId),
|
|
4566
|
-
threadId,
|
|
4567
|
-
})
|
|
4568
|
-
.then(async ({ thread }) => {
|
|
4569
|
-
if (!storeRef || storeRef.state.sessionId !== thread.id)
|
|
4570
|
-
return;
|
|
4571
|
-
await switchAttachedStoreThread(thread, { mode: "fast" });
|
|
4572
|
-
})
|
|
4573
|
-
.catch(() => { });
|
|
4574
|
-
},
|
|
4575
|
-
async steerSession(sessionId, input) {
|
|
4576
|
-
const pendingTurn = getThreadState(sessionId).pendingTurn;
|
|
4577
|
-
const prompt = input.trim();
|
|
4578
|
-
if (!pendingTurn?.turnId || pendingTurn.status !== "running" || prompt.length === 0)
|
|
4579
|
-
return false;
|
|
4580
|
-
try {
|
|
4581
|
-
await options.runtime.steerTurn(sessionId, pendingTurn.turnId, prompt);
|
|
4582
|
-
return true;
|
|
4583
|
-
}
|
|
4584
|
-
catch {
|
|
4585
|
-
return false;
|
|
4586
|
-
}
|
|
4587
|
-
},
|
|
4588
|
-
async triggerManualCompression(sessionId) {
|
|
4589
|
-
const threadId = (normalizeText(sessionId) || activeThreadId());
|
|
4590
|
-
if (!threadId) {
|
|
4591
|
-
return {
|
|
4592
|
-
error: "No active session.",
|
|
4593
|
-
errorCode: "THREAD_NOT_FOUND",
|
|
4594
|
-
success: false,
|
|
4595
|
-
};
|
|
4596
|
-
}
|
|
4597
|
-
return await createSession(threadId).triggerManualCompaction();
|
|
4598
|
-
},
|
|
4599
|
-
};
|
|
4600
|
-
return {
|
|
4601
|
-
agent,
|
|
4602
|
-
attachStore(store) {
|
|
4603
|
-
unsubscribeStoreRuntimeEvents?.();
|
|
4604
|
-
storeRef = store;
|
|
4605
|
-
currentMode = cliModeFromThreadMode(store.state.ui?.mode === "plan" ? "plan" : options.threadMode);
|
|
4606
|
-
unsubscribeStoreRuntimeEvents = options.runtime.subscribe(({ event }) => {
|
|
4607
|
-
if (event.type === "thread.usage.updated") {
|
|
4608
|
-
syncStoreUsageMetrics(storeRef, event);
|
|
4609
|
-
return;
|
|
4610
|
-
}
|
|
4611
|
-
if (event.type === "thread.compacted")
|
|
4612
|
-
syncStoreBudgetMetrics(storeRef, event);
|
|
4613
|
-
if (event.type === "turn.completed" ||
|
|
4614
|
-
event.type === "turn.interrupted" ||
|
|
4615
|
-
event.type === "turn.failed" ||
|
|
4616
|
-
event.type === "turn.blocked") {
|
|
4617
|
-
const threadId = event.turn.threadId;
|
|
4618
|
-
if (storeRef?.state.sessionId !== threadId)
|
|
4619
|
-
return;
|
|
4620
|
-
const pending = getThreadState(threadId).pendingTurn;
|
|
4621
|
-
if (pending?.turnId === event.turn.id) {
|
|
4622
|
-
pending.action = undefined;
|
|
4623
|
-
pending.approval = undefined;
|
|
4624
|
-
pending.resumeInFlight = false;
|
|
4625
|
-
pending.status =
|
|
4626
|
-
event.type === "turn.completed"
|
|
4627
|
-
? "completed"
|
|
4628
|
-
: event.type === "turn.interrupted"
|
|
4629
|
-
? "interrupted"
|
|
4630
|
-
: "failed";
|
|
4631
|
-
getThreadState(threadId).pendingTurn = undefined;
|
|
4632
|
-
}
|
|
4633
|
-
if (!clearStorePendingApproval(storeRef))
|
|
4634
|
-
return;
|
|
4635
|
-
refreshAttachedStoreThread(threadId);
|
|
4636
|
-
}
|
|
4637
|
-
});
|
|
4638
|
-
},
|
|
4639
|
-
hydrateThread,
|
|
4640
|
-
sessions,
|
|
4641
|
-
};
|
|
4642
|
-
}
|
|
4643
|
-
export const createGoatChainBridge = createBestAgentNativeBridge;
|
|
4644
|
-
export const resolveInitialGoatChainThread = resolveInitialBestAgentThread;
|
|
4645
|
-
//# sourceMappingURL=best-agent-native-bridge.js.map
|