@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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -181
  3. package/dist/launcher.js +32 -0
  4. package/package.json +15 -27
  5. package/dist/best-agent-native-bridge.d.ts +0 -389
  6. package/dist/best-agent-native-bridge.js +0 -4645
  7. package/dist/cli-storage.d.ts +0 -5
  8. package/dist/cli-storage.js +0 -7
  9. package/dist/dimsdk/ask-user.d.ts +0 -21
  10. package/dist/dimsdk/ask-user.js +0 -131
  11. package/dist/dimsdk/cli/version.d.ts +0 -6
  12. package/dist/dimsdk/cli/version.js +0 -36
  13. package/dist/dimsdk/compat/archships/dim-agent/runtime-host.d.ts +0 -49
  14. package/dist/dimsdk/compat/archships/dim-agent/runtime-host.js +0 -215
  15. package/dist/dimsdk/compat/runtime-host.d.ts +0 -2
  16. package/dist/dimsdk/compat/runtime-host.js +0 -2
  17. package/dist/dimsdk/core/debug-log.d.ts +0 -8
  18. package/dist/dimsdk/core/debug-log.js +0 -100
  19. package/dist/dimsdk/core/env.d.ts +0 -26
  20. package/dist/dimsdk/core/env.js +0 -130
  21. package/dist/dimsdk/core/markdown/sanitize.d.ts +0 -2
  22. package/dist/dimsdk/core/markdown/sanitize.js +0 -43
  23. package/dist/dimsdk/core/markdown/wrap.d.ts +0 -9
  24. package/dist/dimsdk/core/markdown/wrap.js +0 -139
  25. package/dist/dimsdk/core/markdown.d.ts +0 -18
  26. package/dist/dimsdk/core/markdown.js +0 -62
  27. package/dist/dimsdk/core/model.d.ts +0 -273
  28. package/dist/dimsdk/core/model.js +0 -16
  29. package/dist/dimsdk/core/observability/perf-log.d.ts +0 -18
  30. package/dist/dimsdk/core/observability/perf-log.js +0 -236
  31. package/dist/dimsdk/core/store/agent-event-mappers.d.ts +0 -26
  32. package/dist/dimsdk/core/store/agent-event-mappers.js +0 -109
  33. package/dist/dimsdk/core/store/message-mutations.d.ts +0 -105
  34. package/dist/dimsdk/core/store/message-mutations.js +0 -1528
  35. package/dist/dimsdk/core/store/semantic-readers.d.ts +0 -2
  36. package/dist/dimsdk/core/store/semantic-readers.js +0 -4
  37. package/dist/dimsdk/core/store/transcript-projector.d.ts +0 -164
  38. package/dist/dimsdk/core/store/transcript-projector.js +0 -580
  39. package/dist/dimsdk/core/tool-metadata.d.ts +0 -12
  40. package/dist/dimsdk/core/tool-metadata.js +0 -70
  41. package/dist/dimsdk/core/transcript-part-id.d.ts +0 -6
  42. package/dist/dimsdk/core/transcript-part-id.js +0 -22
  43. package/dist/dimsdk/i18n/index.d.ts +0 -18
  44. package/dist/dimsdk/i18n/index.js +0 -141
  45. package/dist/dimsdk/i18n/locales/en.d.ts +0 -988
  46. package/dist/dimsdk/i18n/locales/en.js +0 -987
  47. package/dist/dimsdk/i18n/locales/index.d.ts +0 -1976
  48. package/dist/dimsdk/i18n/locales/index.js +0 -8
  49. package/dist/dimsdk/i18n/locales/zh-CN.d.ts +0 -988
  50. package/dist/dimsdk/i18n/locales/zh-CN.js +0 -987
  51. package/dist/dimsdk/i18n/types.d.ts +0 -13
  52. package/dist/dimsdk/i18n/types.js +0 -2
  53. package/dist/dimsdk/shared/value-normalizers.d.ts +0 -7
  54. package/dist/dimsdk/shared/value-normalizers.js +0 -48
  55. package/dist/dimsdk-tui.bundle-size.json +0 -169
  56. package/dist/dimsdk-tui.bundle.js +0 -330756
  57. package/dist/goatchain-adapter.d.ts +0 -41
  58. package/dist/goatchain-adapter.js +0 -499
  59. package/dist/index.d.ts +0 -3
  60. package/dist/index.js +0 -1405
  61. package/dist/native-runtime-bridge.d.ts +0 -387
  62. package/dist/native-runtime-bridge.js +0 -4624
  63. package/dist/provider-config-env.d.ts +0 -7
  64. package/dist/provider-config-env.js +0 -286
  65. package/dist/provider-model-limit-index.generated.d.ts +0 -3
  66. package/dist/provider-model-limit-index.generated.js +0 -7818
  67. package/dist/provider-model-limits.d.ts +0 -8
  68. package/dist/provider-model-limits.js +0 -43
  69. package/dist/runtime.d.ts +0 -47
  70. 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