@bubblebrain-ai/bubble 0.0.58 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -6
- package/dist/agent/child-runner.js +5 -1
- package/dist/agent/internal-reminder-sanitizer.d.ts +1 -1
- package/dist/agent/internal-reminder-sanitizer.js +7 -1
- package/dist/agent/profiles.js +4 -4
- package/dist/agent/routing-catalog.d.ts +19 -1
- package/dist/agent/routing-catalog.js +32 -13
- package/dist/agent/subagent/runtime.d.ts +28 -0
- package/dist/agent/subagent/runtime.js +34 -30
- package/dist/agent/subagent-control.d.ts +7 -0
- package/dist/agent/subagent-control.js +2 -0
- package/dist/agent/subagent-lifecycle-reminder.js +2 -1
- package/dist/agent/subagent-wait-guidance.d.ts +2 -0
- package/dist/agent/subagent-wait-guidance.js +8 -0
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent.d.ts +20 -11
- package/dist/agent.js +200 -117
- package/dist/anthropic-thinking.d.ts +4 -0
- package/dist/anthropic-thinking.js +11 -0
- package/dist/approval/controller.d.ts +20 -6
- package/dist/approval/controller.js +67 -8
- package/dist/approval/protected-paths.d.ts +1 -0
- package/dist/approval/protected-paths.js +35 -0
- package/dist/approval/session-cache.d.ts +12 -26
- package/dist/approval/session-cache.js +20 -50
- package/dist/approval/types.d.ts +27 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +25 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/context/checkpoint.d.ts +18 -0
- package/dist/context/checkpoint.js +117 -0
- package/dist/context/compact-llm.js +7 -0
- package/dist/context/compact.d.ts +8 -0
- package/dist/context/compact.js +40 -7
- package/dist/context/llm-compactor.d.ts +36 -2
- package/dist/context/llm-compactor.js +227 -44
- package/dist/context/session-write-lock.d.ts +20 -0
- package/dist/context/session-write-lock.js +162 -0
- package/dist/context/tool-images.d.ts +3 -0
- package/dist/context/tool-images.js +35 -0
- package/dist/context/usage.js +22 -5
- package/dist/feishu/agent-host/run-driver.js +56 -9
- package/dist/lsp/service.d.ts +13 -1
- package/dist/lsp/service.js +163 -43
- package/dist/main.js +82 -16
- package/dist/mcp/config.d.ts +4 -0
- package/dist/mcp/config.js +17 -0
- package/dist/mcp/manager.d.ts +13 -1
- package/dist/mcp/manager.js +32 -1
- package/dist/mcp/transports.js +11 -0
- package/dist/memory/db.d.ts +1 -0
- package/dist/memory/db.js +20 -4
- package/dist/memory/phase1.js +122 -16
- package/dist/memory/prompts.js +3 -0
- package/dist/model-catalog.js +19 -3
- package/dist/model-config.d.ts +1 -0
- package/dist/model-config.js +5 -5
- package/dist/model-pricing.js +26 -9
- package/dist/network/provider-transport-log.d.ts +12 -0
- package/dist/network/provider-transport-log.js +32 -0
- package/dist/network/provider-transport.js +6 -1
- package/dist/network/retry.d.ts +1 -1
- package/dist/network/retry.js +1 -1
- package/dist/oauth/grok.d.ts +1 -0
- package/dist/oauth/grok.js +20 -16
- package/dist/oauth/openai-codex.d.ts +1 -0
- package/dist/oauth/openai-codex.js +29 -25
- package/dist/oauth/refresh-control.d.ts +12 -0
- package/dist/oauth/refresh-control.js +42 -0
- package/dist/oauth/storage.d.ts +55 -1
- package/dist/oauth/storage.js +281 -16
- package/dist/permissions/rule.d.ts +6 -3
- package/dist/permissions/rule.js +71 -12
- package/dist/permissions/settings.d.ts +28 -1
- package/dist/permissions/settings.js +93 -3
- package/dist/permissions/shell-command.d.ts +36 -0
- package/dist/permissions/shell-command.js +226 -0
- package/dist/permissions/trust-prompt.d.ts +10 -0
- package/dist/permissions/trust-prompt.js +34 -0
- package/dist/permissions/trust.d.ts +26 -0
- package/dist/permissions/trust.js +113 -0
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +24 -7
- package/dist/prompt/environment.js +2 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +6 -2
- package/dist/provider-anthropic.js +98 -18
- package/dist/provider-error-record.d.ts +9 -0
- package/dist/provider-error-record.js +42 -14
- package/dist/provider-grok.js +5 -1
- package/dist/provider-openai-codex.d.ts +8 -0
- package/dist/provider-openai-codex.js +107 -24
- package/dist/provider-registry.d.ts +45 -2
- package/dist/provider-registry.js +300 -88
- package/dist/provider-transform.js +8 -0
- package/dist/provider-zen-errors.d.ts +9 -0
- package/dist/provider-zen-errors.js +36 -0
- package/dist/provider.js +36 -5
- package/dist/sdk/index.d.ts +69 -8
- package/dist/sdk/index.js +252 -39
- package/dist/sdk/replay-event-log.d.ts +7 -0
- package/dist/sdk/replay-event-log.js +66 -16
- package/dist/session-context-fence.d.ts +16 -0
- package/dist/session-context-fence.js +26 -0
- package/dist/session-log.d.ts +11 -2
- package/dist/session-log.js +187 -39
- package/dist/session-types.d.ts +5 -1
- package/dist/session.d.ts +39 -7
- package/dist/session.js +341 -93
- package/dist/skills/format.d.ts +31 -1
- package/dist/skills/format.js +45 -24
- package/dist/slash-commands/commands.d.ts +3 -0
- package/dist/slash-commands/commands.js +29 -14
- package/dist/system-prompt.d.ts +3 -1
- package/dist/tasks/manager.d.ts +7 -0
- package/dist/tasks/manager.js +97 -18
- package/dist/tools/agent-lifecycle.js +11 -7
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +4 -4
- package/dist/tools/edit-apply.d.ts +0 -3
- package/dist/tools/edit-apply.js +45 -249
- package/dist/tools/edit.js +3 -3
- package/dist/tools/exit-plan-mode.d.ts +5 -0
- package/dist/tools/exit-plan-mode.js +3 -2
- package/dist/tools/file-state.d.ts +4 -0
- package/dist/tools/file-state.js +12 -3
- package/dist/tools/grep.js +20 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/ls.d.ts +3 -0
- package/dist/tools/ls.js +85 -0
- package/dist/tools/path-utils.d.ts +6 -0
- package/dist/tools/path-utils.js +38 -1
- package/dist/tools/sensitive-paths.d.ts +8 -0
- package/dist/tools/sensitive-paths.js +85 -3
- package/dist/tools/skill.d.ts +2 -0
- package/dist/tools/skill.js +34 -2
- package/dist/tools/task-tools.js +11 -7
- package/dist/tui/app.js +22 -12
- package/dist/tui/components/approval-dialog.d.ts +5 -5
- package/dist/tui/components/approval-dialog.js +16 -51
- package/dist/tui/components/skills-panel.d.ts +4 -0
- package/dist/tui/components/skills-panel.js +15 -1
- package/dist/tui/components/streaming-message.js +7 -2
- package/dist/tui/components/tasks-pane.d.ts +38 -0
- package/dist/tui/components/tasks-pane.js +182 -32
- package/dist/tui/components/transcript.d.ts +11 -4
- package/dist/tui/components/transcript.js +158 -71
- package/dist/tui/composer-autocomplete.d.ts +17 -2
- package/dist/tui/composer-autocomplete.js +79 -2
- package/dist/tui/controller/controller.d.ts +9 -4
- package/dist/tui/controller/controller.js +87 -95
- package/dist/tui/controller/goal-runtime-controller.js +1 -1
- package/dist/tui/controller/session-transition.js +1 -1
- package/dist/tui/model/subagent-view.d.ts +11 -19
- package/dist/tui/model/subagent-view.js +56 -85
- package/dist/tui/model/task-lifecycle.d.ts +57 -0
- package/dist/tui/model/task-lifecycle.js +124 -0
- package/dist/tui/model/trace-groups.js +10 -9
- package/dist/tui/trace-groups.js +10 -9
- package/dist/types.d.ts +20 -0
- package/dist/variant/variant-resolver.js +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +6 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +8 -4
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -1
- package/package.json +11 -3
- package/dist/prompt/skills.d.ts +0 -2
- package/dist/prompt/skills.js +0 -4
- package/dist/tools/glob.d.ts +0 -5
- package/dist/tools/glob.js +0 -199
|
@@ -7,8 +7,9 @@ import { AgentRunInputQueue } from "../../agent/input-controller.js";
|
|
|
7
7
|
import { OverlayRequestController } from "./overlay-controller.js";
|
|
8
8
|
import { SessionTransitionController } from "./session-transition.js";
|
|
9
9
|
import { moveStatusMessageToEnd, nextDisplayMessageKey, setUserInputStatus, snapshotDisplayParts, } from "../model/display-history.js";
|
|
10
|
-
import {
|
|
10
|
+
import { applySubagentUpdateToMessages, collectSubagentGroups, mergeSubagentSnapshotsIntoMessages, } from "../model/subagent-view.js";
|
|
11
11
|
import { reconstructDisplayMessages } from "../model/display-reconstruct.js";
|
|
12
|
+
import { applyTaskLifecycleToMessages, landTaskLifecycles, mergeTaskLifecycleIntoLiveTools, taskLifecycleDisplayMessage, UNMATCHED_LAUNCH, } from "../model/task-lifecycle.js";
|
|
12
13
|
import { buildImageContentParts, buildImageContentPartsFromDisplayText } from "../model/image-paste.js";
|
|
13
14
|
import { displayImagesFromPayload, formatImageUserDisplayText } from "../image-display.js";
|
|
14
15
|
import { GoalRuntimeController } from "./goal-runtime-controller.js";
|
|
@@ -29,6 +30,7 @@ export class BubbleTuiController {
|
|
|
29
30
|
queue = createInputQueueState();
|
|
30
31
|
sessionTransition;
|
|
31
32
|
startedAtMs;
|
|
33
|
+
turnStartedAt;
|
|
32
34
|
goalRuntime;
|
|
33
35
|
taskRuntime;
|
|
34
36
|
pendingTaskAnnouncements = new Map();
|
|
@@ -36,9 +38,6 @@ export class BubbleTuiController {
|
|
|
36
38
|
transcript = [];
|
|
37
39
|
runActive = false;
|
|
38
40
|
runState = null;
|
|
39
|
-
/** Background subagent updates can outlive the provider turn that launched
|
|
40
|
-
* them. Keep their latest synthetic tool snapshot in the live trace. */
|
|
41
|
-
liveSubagentTools = new Map();
|
|
42
41
|
/** Provider-turn accumulators for true child-session inspection. */
|
|
43
42
|
childRuns = new Map();
|
|
44
43
|
activeInputController = null;
|
|
@@ -51,10 +50,7 @@ export class BubbleTuiController {
|
|
|
51
50
|
constructor(deps) {
|
|
52
51
|
this.deps = deps;
|
|
53
52
|
this.sessionManager = deps.sessionManager;
|
|
54
|
-
this.transcript = [
|
|
55
|
-
...reconstructDisplayMessages([...deps.agent.messages]),
|
|
56
|
-
...restoredTaskLifecycleMessages(this.sessionManager),
|
|
57
|
-
];
|
|
53
|
+
this.transcript = landTaskLifecycles(reconstructDisplayMessages([...deps.agent.messages]), restoredTaskLifecycles(this.sessionManager));
|
|
58
54
|
this.overlays = new OverlayRequestController();
|
|
59
55
|
this.startedAtMs = Date.now();
|
|
60
56
|
this.sessionTransition = new SessionTransitionController({
|
|
@@ -70,17 +66,18 @@ export class BubbleTuiController {
|
|
|
70
66
|
this.externalGeneration += 1;
|
|
71
67
|
},
|
|
72
68
|
clearLiveSubagentTools: () => {
|
|
73
|
-
this.liveSubagentTools.clear();
|
|
74
69
|
this.childRuns.clear();
|
|
75
|
-
this.liveSubagentVersion += 1;
|
|
76
70
|
},
|
|
77
71
|
commit: (manager, transcript) => {
|
|
78
72
|
this.sessionManager = manager;
|
|
79
73
|
this.deps.ports.flush.cancelFlush();
|
|
80
|
-
this.transcript =
|
|
74
|
+
this.transcript = landTaskLifecycles(transcript, restoredTaskLifecycles(manager));
|
|
81
75
|
this.queue.queued.length = 0;
|
|
82
76
|
this.liveStreamVisible = false;
|
|
83
77
|
this.runState = null;
|
|
78
|
+
// Session-scoped: the restored session has not started a run, so none
|
|
79
|
+
// of its subagents or tasks belong to a "current turn".
|
|
80
|
+
this.turnStartedAt = undefined;
|
|
84
81
|
this.activeInputController = null;
|
|
85
82
|
this.activeAbortController = null;
|
|
86
83
|
this.commandActivity = null;
|
|
@@ -143,7 +140,6 @@ export class BubbleTuiController {
|
|
|
143
140
|
}));
|
|
144
141
|
}
|
|
145
142
|
externalGeneration = 0;
|
|
146
|
-
liveSubagentVersion = 0;
|
|
147
143
|
subscribe(listener) {
|
|
148
144
|
this.listeners.add(listener);
|
|
149
145
|
return () => this.listeners.delete(listener);
|
|
@@ -172,7 +168,11 @@ export class BubbleTuiController {
|
|
|
172
168
|
this.goalRuntime.handleCommand(input, cwd);
|
|
173
169
|
}
|
|
174
170
|
getSubagentGroups() {
|
|
175
|
-
|
|
171
|
+
// Launches from earlier rounds live in the transcript (kept current by
|
|
172
|
+
// applySubagentUpdateToMessages); this round's launches are still in the
|
|
173
|
+
// streaming accumulator until turn_end commits them.
|
|
174
|
+
const streamingTools = this.runActive && this.runState ? this.runState.accumulator.toolCalls : [];
|
|
175
|
+
const fromTrace = collectSubagentGroups(this.transcript, streamingTools);
|
|
176
176
|
const claimed = new Set(fromTrace.flatMap((group) => group.members.map((member) => member.subAgentId).filter(Boolean)));
|
|
177
177
|
const direct = this.deps.agent.listSubAgents()
|
|
178
178
|
.filter((snapshot) => !claimed.has(snapshot.agentId))
|
|
@@ -201,6 +201,14 @@ export class BubbleTuiController {
|
|
|
201
201
|
}));
|
|
202
202
|
return [...fromTrace, ...direct];
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Start of the current (or, when idle, the latest) agent run. The tasks pane
|
|
206
|
+
* keeps finished work that was launched since then; wall clock on purpose,
|
|
207
|
+
* it is compared with subagent/task/workflow creation stamps.
|
|
208
|
+
*/
|
|
209
|
+
getTurnStartedAt() {
|
|
210
|
+
return this.turnStartedAt;
|
|
211
|
+
}
|
|
204
212
|
getWorkflows() {
|
|
205
213
|
return this.deps.agent.listWorkflows();
|
|
206
214
|
}
|
|
@@ -262,16 +270,48 @@ export class BubbleTuiController {
|
|
|
262
270
|
// Switching back to the owner session reconstructs terminal task rows
|
|
263
271
|
// from its persisted marker before held completions are released. Treat
|
|
264
272
|
// that row as the same lifecycle event instead of appending a duplicate.
|
|
265
|
-
|
|
273
|
+
// Task ids repeat across processes, so the row must be this very run.
|
|
274
|
+
if (this.transcript.some((message) => message.toolCalls?.some((tool) => tool.metadata?.taskId === task.id
|
|
275
|
+
&& tool.metadata?.taskLifecycle !== undefined
|
|
276
|
+
&& tool.metadata?.endedAt === task.endedAt))) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const output = this.deps.processManager?.taskOutputTail(task.id, 12_000);
|
|
280
|
+
// Land the outcome on the launch row: this round's launches are still in
|
|
281
|
+
// the streaming accumulator, earlier rounds' have committed.
|
|
282
|
+
if (this.runActive && this.runState
|
|
283
|
+
&& mergeTaskLifecycleIntoLiveTools(this.runState.accumulator.toolCalls, task, output)) {
|
|
284
|
+
this.state.touch();
|
|
285
|
+
this.notify();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const landed = applyTaskLifecycleToMessages(this.transcript, task, output);
|
|
289
|
+
if (landed.merged) {
|
|
290
|
+
this.transcript = landed.messages;
|
|
291
|
+
this.state.touch();
|
|
292
|
+
this.notify();
|
|
266
293
|
return;
|
|
267
294
|
}
|
|
268
295
|
if (this.runActive) {
|
|
296
|
+
// Launch row not settled yet (or absent): retry on the next tool_end,
|
|
297
|
+
// and fall back to a detached row when the round commits.
|
|
269
298
|
this.pendingTaskAnnouncements.set(task.id, task);
|
|
270
299
|
this.state.touch();
|
|
271
300
|
this.notify();
|
|
272
301
|
return;
|
|
273
302
|
}
|
|
274
|
-
this.appendDisplayMessage(taskLifecycleDisplayMessage(task,
|
|
303
|
+
this.appendDisplayMessage(taskLifecycleDisplayMessage(task, output));
|
|
304
|
+
}
|
|
305
|
+
/** Pending completions whose launch row has settled since they arrived. */
|
|
306
|
+
landPendingTaskAnnouncementsInLiveRound() {
|
|
307
|
+
if (!this.runState || this.pendingTaskAnnouncements.size === 0)
|
|
308
|
+
return;
|
|
309
|
+
for (const [id, task] of this.pendingTaskAnnouncements) {
|
|
310
|
+
const output = this.deps.processManager?.taskOutputTail(task.id, 12_000);
|
|
311
|
+
if (mergeTaskLifecycleIntoLiveTools(this.runState.accumulator.toolCalls, task, output)) {
|
|
312
|
+
this.pendingTaskAnnouncements.delete(id);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
275
315
|
}
|
|
276
316
|
/** Move the newest live Bash Execute into the unified task manager. */
|
|
277
317
|
promoteActiveBash() {
|
|
@@ -288,7 +328,6 @@ export class BubbleTuiController {
|
|
|
288
328
|
/** Drop every transcript row (/clear). */
|
|
289
329
|
clearTranscript() {
|
|
290
330
|
this.transcript = [];
|
|
291
|
-
this.liveSubagentTools.clear();
|
|
292
331
|
this.childRuns.clear();
|
|
293
332
|
this.state.touch();
|
|
294
333
|
this.notify();
|
|
@@ -296,13 +335,8 @@ export class BubbleTuiController {
|
|
|
296
335
|
/** Re-project the visible transcript after a non-controller command rewrites Agent history. */
|
|
297
336
|
rebuildTranscriptFromAgent() {
|
|
298
337
|
this.state.withTransaction(() => {
|
|
299
|
-
this.transcript = [
|
|
300
|
-
...reconstructDisplayMessages([...this.deps.agent.messages]),
|
|
301
|
-
...restoredTaskLifecycleMessages(this.sessionManager),
|
|
302
|
-
];
|
|
303
|
-
this.liveSubagentTools.clear();
|
|
338
|
+
this.transcript = landTaskLifecycles(reconstructDisplayMessages([...this.deps.agent.messages]), restoredTaskLifecycles(this.sessionManager));
|
|
304
339
|
this.childRuns.clear();
|
|
305
|
-
this.liveSubagentVersion += 1;
|
|
306
340
|
this.state.touch();
|
|
307
341
|
});
|
|
308
342
|
this.notify();
|
|
@@ -320,13 +354,8 @@ export class BubbleTuiController {
|
|
|
320
354
|
this.state.withTransaction(() => {
|
|
321
355
|
purgeForSessionSwitch(this.queue);
|
|
322
356
|
this.queue.queued.length = 0;
|
|
323
|
-
this.transcript = [
|
|
324
|
-
...reconstructDisplayMessages([...this.deps.agent.messages]),
|
|
325
|
-
...restoredTaskLifecycleMessages(this.sessionManager),
|
|
326
|
-
];
|
|
327
|
-
this.liveSubagentTools.clear();
|
|
357
|
+
this.transcript = landTaskLifecycles(reconstructDisplayMessages([...this.deps.agent.messages]), restoredTaskLifecycles(this.sessionManager));
|
|
328
358
|
this.childRuns.clear();
|
|
329
|
-
this.liveSubagentVersion += 1;
|
|
330
359
|
this.liveStreamVisible = false;
|
|
331
360
|
this.runState = null;
|
|
332
361
|
this.state.touch();
|
|
@@ -493,20 +522,7 @@ export class BubbleTuiController {
|
|
|
493
522
|
if (!this.runActive || !this.runState || !this.liveStreamVisible)
|
|
494
523
|
return null;
|
|
495
524
|
const acc = this.runState.accumulator;
|
|
496
|
-
const currentIds = new Set(acc.toolCalls.map((tool) => tool.id));
|
|
497
|
-
const carriedTools = [...this.liveSubagentTools.values()].filter((tool) => !currentIds.has(tool.id));
|
|
498
|
-
const allTools = [...acc.toolCalls, ...carriedTools];
|
|
499
525
|
const parts = snapshotDisplayParts(acc.parts);
|
|
500
|
-
if (carriedTools.length > 0) {
|
|
501
|
-
parts.push({
|
|
502
|
-
type: "tools",
|
|
503
|
-
toolCalls: carriedTools.map((tool) => ({
|
|
504
|
-
...tool,
|
|
505
|
-
args: { ...tool.args },
|
|
506
|
-
metadata: tool.metadata ? { ...tool.metadata } : undefined,
|
|
507
|
-
})),
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
526
|
return {
|
|
511
527
|
content: acc.content,
|
|
512
528
|
// Ink kept the current provider turn's reasoning visible even after
|
|
@@ -514,7 +530,7 @@ export class BubbleTuiController {
|
|
|
514
530
|
reasoning: acc.reasoning,
|
|
515
531
|
// Return a render-safe snapshot: reducer events continue mutating the
|
|
516
532
|
// accumulator in place while the TUI may still hold the previous tail.
|
|
517
|
-
tools:
|
|
533
|
+
tools: acc.toolCalls.map((tool) => ({
|
|
518
534
|
...tool,
|
|
519
535
|
args: { ...tool.args },
|
|
520
536
|
metadata: tool.metadata ? { ...tool.metadata } : undefined,
|
|
@@ -524,7 +540,7 @@ export class BubbleTuiController {
|
|
|
524
540
|
parts,
|
|
525
541
|
// Phase is provider-turn local, matching Ink's clearAssistantStream().
|
|
526
542
|
// A tool in an earlier committed turn must not suppress fresh Thinking.
|
|
527
|
-
phase:
|
|
543
|
+
phase: acc.toolCalls.length > 0 ? "working" : "thinking",
|
|
528
544
|
};
|
|
529
545
|
}
|
|
530
546
|
pendingOverlayCount() {
|
|
@@ -558,6 +574,7 @@ export class BubbleTuiController {
|
|
|
558
574
|
const agentInput = isSubmitPayload(input) ? this.submitAgentInput(input) : input;
|
|
559
575
|
this.runActive = true;
|
|
560
576
|
this.liveStreamVisible = true;
|
|
577
|
+
this.turnStartedAt = Date.now();
|
|
561
578
|
this.runState = createRunState(Date.now());
|
|
562
579
|
const inputController = new AgentRunInputQueue(`run-${this.runState.accumulator.runId}`);
|
|
563
580
|
const abortController = new AbortController();
|
|
@@ -583,7 +600,6 @@ export class BubbleTuiController {
|
|
|
583
600
|
upstreamAbort();
|
|
584
601
|
else
|
|
585
602
|
options?.abortSignal?.addEventListener("abort", upstreamAbort, { once: true });
|
|
586
|
-
pruneSettledLiveSubagentTools(this.liveSubagentTools);
|
|
587
603
|
this.state.touch();
|
|
588
604
|
this.notify();
|
|
589
605
|
const ctx = {
|
|
@@ -611,22 +627,28 @@ export class BubbleTuiController {
|
|
|
611
627
|
}
|
|
612
628
|
const { state, effects } = reduceAgentEvent(this.runState, event, ctx);
|
|
613
629
|
this.runState = state;
|
|
630
|
+
if (event.type === "tool_end")
|
|
631
|
+
this.landPendingTaskAnnouncementsInLiveRound();
|
|
614
632
|
const subagentMetadata = event.type === "tool_update"
|
|
615
633
|
? event.update.metadata
|
|
616
634
|
: event.type === "tool_end"
|
|
617
635
|
? event.result.metadata
|
|
618
636
|
: undefined;
|
|
619
|
-
if (subagentMetadata?.kind === "subagent") {
|
|
620
|
-
this.transcript = mergeSubagentSnapshotsIntoMessages(this.transcript, subagentMetadata);
|
|
621
|
-
}
|
|
622
637
|
if (event.type === "tool_update"
|
|
638
|
+
&& subagentMetadata?.kind === "subagent"
|
|
623
639
|
&& effects.some((effect) => effect.kind === "live-subagent-changed")) {
|
|
624
|
-
|
|
640
|
+
// The launching call already committed: merge the update into its
|
|
641
|
+
// transcript entry so the settled row stays the single source of
|
|
642
|
+
// truth for live and settled rendering alike.
|
|
643
|
+
this.transcript = applySubagentUpdateToMessages(this.transcript, {
|
|
625
644
|
id: event.id,
|
|
626
645
|
name: event.name,
|
|
627
|
-
metadata:
|
|
646
|
+
metadata: subagentMetadata,
|
|
628
647
|
});
|
|
629
648
|
}
|
|
649
|
+
else if (subagentMetadata?.kind === "subagent") {
|
|
650
|
+
this.transcript = mergeSubagentSnapshotsIntoMessages(this.transcript, subagentMetadata);
|
|
651
|
+
}
|
|
630
652
|
if (event.type === "tool_update" && event.update.childEvent) {
|
|
631
653
|
this.reduceChildEvent(event.update.subAgentId, event.update.childEvent);
|
|
632
654
|
}
|
|
@@ -763,10 +785,10 @@ export class BubbleTuiController {
|
|
|
763
785
|
this.activeInputController = null;
|
|
764
786
|
this.activeAbortController = null;
|
|
765
787
|
if (this.pendingTaskAnnouncements.size > 0) {
|
|
766
|
-
this.transcript = [
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
788
|
+
this.transcript = landTaskLifecycles(this.transcript, [...this.pendingTaskAnnouncements.values()].map((task) => ({
|
|
789
|
+
task,
|
|
790
|
+
output: this.deps.processManager?.taskOutputTail(task.id, 12_000),
|
|
791
|
+
})));
|
|
770
792
|
this.pendingTaskAnnouncements.clear();
|
|
771
793
|
}
|
|
772
794
|
this.state.touch();
|
|
@@ -929,42 +951,7 @@ export class BubbleTuiController {
|
|
|
929
951
|
return { reason, wallMs: Date.now() - this.startedAtMs };
|
|
930
952
|
}
|
|
931
953
|
}
|
|
932
|
-
function
|
|
933
|
-
const status = task.status === "killed"
|
|
934
|
-
? "cancelled"
|
|
935
|
-
: task.status === "failed"
|
|
936
|
-
? "failed"
|
|
937
|
-
: "completed";
|
|
938
|
-
const tool = {
|
|
939
|
-
id: `task-lifecycle:${task.id}:${task.endedAt ?? Date.now()}`,
|
|
940
|
-
name: "bash",
|
|
941
|
-
args: {
|
|
942
|
-
command: task.command,
|
|
943
|
-
...(task.description ? { description: task.description } : {}),
|
|
944
|
-
},
|
|
945
|
-
status,
|
|
946
|
-
isError: task.status === "failed",
|
|
947
|
-
result: output?.trim() ? output : undefined,
|
|
948
|
-
startedAt: task.startedAt,
|
|
949
|
-
metadata: {
|
|
950
|
-
kind: "shell",
|
|
951
|
-
background: true,
|
|
952
|
-
taskId: task.id,
|
|
953
|
-
taskLifecycle: task.status,
|
|
954
|
-
exitCode: task.exitCode ?? null,
|
|
955
|
-
endedAt: task.endedAt,
|
|
956
|
-
outputLines: task.outputLines,
|
|
957
|
-
},
|
|
958
|
-
};
|
|
959
|
-
return {
|
|
960
|
-
key: nextDisplayMessageKey("task"),
|
|
961
|
-
role: "assistant",
|
|
962
|
-
content: "",
|
|
963
|
-
toolCalls: [tool],
|
|
964
|
-
parts: [{ type: "tools", toolCalls: [tool] }],
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
function restoredTaskLifecycleMessages(manager) {
|
|
954
|
+
function restoredTaskLifecycles(manager) {
|
|
968
955
|
let entries;
|
|
969
956
|
try {
|
|
970
957
|
entries = manager.getEntries();
|
|
@@ -981,7 +968,10 @@ function restoredTaskLifecycleMessages(manager) {
|
|
|
981
968
|
}
|
|
982
969
|
}
|
|
983
970
|
const starts = new Map();
|
|
984
|
-
|
|
971
|
+
// Ids restart at task_0001 per process: count launches per id so each
|
|
972
|
+
// terminal marker lands on the launch row it belongs to.
|
|
973
|
+
const launches = new Map();
|
|
974
|
+
const terminals = [];
|
|
985
975
|
for (let index = startIndex; index < entries.length; index += 1) {
|
|
986
976
|
const entry = entries[index];
|
|
987
977
|
if (entry?.type !== "marker" || !entry.value)
|
|
@@ -998,6 +988,7 @@ function restoredTaskLifecycleMessages(manager) {
|
|
|
998
988
|
continue;
|
|
999
989
|
if (entry.kind === "task_started") {
|
|
1000
990
|
starts.set(id, payload);
|
|
991
|
+
launches.set(id, (launches.get(id) ?? 0) + 1);
|
|
1001
992
|
continue;
|
|
1002
993
|
}
|
|
1003
994
|
if (entry.kind !== "task_finished" && entry.kind !== "task_killed")
|
|
@@ -1010,8 +1001,11 @@ function restoredTaskLifecycleMessages(manager) {
|
|
|
1010
1001
|
: "completed";
|
|
1011
1002
|
const startedAt = numeric(payload.startedAt) ?? numeric(start.startedAt) ?? entry.timestamp;
|
|
1012
1003
|
const endedAt = numeric(payload.endedAt) ?? entry.timestamp;
|
|
1013
|
-
|
|
1014
|
-
|
|
1004
|
+
const seen = launches.get(id) ?? 0;
|
|
1005
|
+
terminals.push({
|
|
1006
|
+
// A start persisted before conversation_clear is out of scope: keep its
|
|
1007
|
+
// terminal state detached rather than landing it on a later reuse.
|
|
1008
|
+
occurrence: seen > 0 ? seen - 1 : UNMATCHED_LAUNCH,
|
|
1015
1009
|
task: {
|
|
1016
1010
|
kind: "task",
|
|
1017
1011
|
id,
|
|
@@ -1030,9 +1024,7 @@ function restoredTaskLifecycleMessages(manager) {
|
|
|
1030
1024
|
output: stringValue(payload.output),
|
|
1031
1025
|
});
|
|
1032
1026
|
}
|
|
1033
|
-
return
|
|
1034
|
-
.sort((left, right) => left.order - right.order)
|
|
1035
|
-
.map(({ task, output }) => taskLifecycleDisplayMessage(task, output));
|
|
1027
|
+
return terminals;
|
|
1036
1028
|
}
|
|
1037
1029
|
function numeric(value) {
|
|
1038
1030
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
@@ -179,7 +179,7 @@ export class GoalRuntimeController {
|
|
|
179
179
|
persistGoal(goal) {
|
|
180
180
|
try {
|
|
181
181
|
const manager = this.deps.getSessionManager();
|
|
182
|
-
manager.
|
|
182
|
+
manager.mutateMetadata((current) => ({ ...current, goal: goal ?? undefined }));
|
|
183
183
|
}
|
|
184
184
|
catch {
|
|
185
185
|
// Persistence is best-effort; a filesystem failure must not break a run.
|
|
@@ -42,7 +42,7 @@ export class SessionTransitionController {
|
|
|
42
42
|
this.deps.agent.setSessionID(manager.getSessionFile());
|
|
43
43
|
// 7. External binding refresh comes from the new manager's metadata.
|
|
44
44
|
void manager.getMetadata().externalRuntime;
|
|
45
|
-
// 8. Drop
|
|
45
|
+
// 8. Drop child-run accumulators (ghost-group defense).
|
|
46
46
|
this.deps.clearLiveSubagentTools();
|
|
47
47
|
// 11. Settle every blocking request (behavior delta vs legacy).
|
|
48
48
|
this.deps.overlays.settleAll("session-switch");
|
|
@@ -41,6 +41,8 @@ export interface SubagentGroup {
|
|
|
41
41
|
label: string;
|
|
42
42
|
members: SubagentDisplay[];
|
|
43
43
|
}
|
|
44
|
+
/** Maps every tool call in the transcript; returns the same array when nothing changed. */
|
|
45
|
+
export declare function mapTranscriptTools(messages: DisplayMessage[], updateTool: (tool: DisplayToolCall) => DisplayToolCall): DisplayMessage[];
|
|
44
46
|
/**
|
|
45
47
|
* Projects a newer lifecycle snapshot back onto every existing trace that
|
|
46
48
|
* already references the same child. spawn_agent settles as soon as the child
|
|
@@ -58,29 +60,19 @@ export declare function synchronizeSubagentSnapshots(messages: DisplayMessage[])
|
|
|
58
60
|
*/
|
|
59
61
|
export declare function mergeToolMetadata(current: ToolResultMetadata | undefined, incoming: ToolResultMetadata | undefined): ToolResultMetadata | undefined;
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
62
|
-
* the
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* a workflow
|
|
66
|
-
*
|
|
63
|
+
* Applies a subagent tool_update whose launching call has already settled out
|
|
64
|
+
* of the streaming round. Children keep reporting against the launching
|
|
65
|
+
* spawn_agent / run_workflow call id across later rounds (e.g. while a
|
|
66
|
+
* wait_workflow blocks), so the update is merged into that entry by id — which
|
|
67
|
+
* absorbs members a workflow spawned after commit — and then projected onto
|
|
68
|
+
* every echo referencing the same members. The settled transcript is the only
|
|
69
|
+
* data source; the live tail never carries synthetic tool rows.
|
|
67
70
|
*/
|
|
68
|
-
export declare function
|
|
69
|
-
/**
|
|
70
|
-
* Accumulates a subagent tool_update whose originating tool call has already
|
|
71
|
-
* settled out of the current streaming round. The TUI clears its streaming
|
|
72
|
-
* toolCalls on every turn_start, but background children keep reporting
|
|
73
|
-
* against the launching run_workflow/spawn_agent call id across later rounds
|
|
74
|
-
* (e.g. while a wait_workflow blocks) — without this side-channel those
|
|
75
|
-
* updates are dropped and traces only appear after the whole team finishes.
|
|
76
|
-
* Entries act as synthetic tool calls feeding collectSubagentGroups.
|
|
77
|
-
* Returns true when the update was absorbed.
|
|
78
|
-
*/
|
|
79
|
-
export declare function accumulateLiveSubagentUpdate(map: Map<string, DisplayToolCall>, event: {
|
|
71
|
+
export declare function applySubagentUpdateToMessages(messages: DisplayMessage[], update: {
|
|
80
72
|
id: string;
|
|
81
73
|
name: string;
|
|
82
74
|
metadata?: ToolResultMetadata;
|
|
83
|
-
}):
|
|
75
|
+
}): DisplayMessage[];
|
|
84
76
|
/**
|
|
85
77
|
* Collects every spawned subagent from the live transcript + streaming tools,
|
|
86
78
|
* grouped by their originating tool call, for the inspector. Pure.
|
|
@@ -103,6 +103,39 @@ function freshestSubagentSnapshots(messages) {
|
|
|
103
103
|
}
|
|
104
104
|
return [...freshest.values()];
|
|
105
105
|
}
|
|
106
|
+
/** Maps every tool call in the transcript; returns the same array when nothing changed. */
|
|
107
|
+
export function mapTranscriptTools(messages, updateTool) {
|
|
108
|
+
let anyChanged = false;
|
|
109
|
+
const updated = messages.map((message) => {
|
|
110
|
+
let messageChanged = false;
|
|
111
|
+
const toolCalls = message.toolCalls?.map((tool) => {
|
|
112
|
+
const next = updateTool(tool);
|
|
113
|
+
if (next !== tool)
|
|
114
|
+
messageChanged = true;
|
|
115
|
+
return next;
|
|
116
|
+
});
|
|
117
|
+
const parts = message.parts?.map((part) => {
|
|
118
|
+
if (part.type !== "tools")
|
|
119
|
+
return part;
|
|
120
|
+
let partChanged = false;
|
|
121
|
+
const nextTools = part.toolCalls.map((tool) => {
|
|
122
|
+
const next = updateTool(tool);
|
|
123
|
+
if (next !== tool)
|
|
124
|
+
partChanged = true;
|
|
125
|
+
return next;
|
|
126
|
+
});
|
|
127
|
+
if (!partChanged)
|
|
128
|
+
return part;
|
|
129
|
+
messageChanged = true;
|
|
130
|
+
return { ...part, toolCalls: nextTools };
|
|
131
|
+
});
|
|
132
|
+
if (!messageChanged)
|
|
133
|
+
return message;
|
|
134
|
+
anyChanged = true;
|
|
135
|
+
return { ...message, toolCalls, parts };
|
|
136
|
+
});
|
|
137
|
+
return anyChanged ? updated : messages;
|
|
138
|
+
}
|
|
106
139
|
/**
|
|
107
140
|
* Projects a newer lifecycle snapshot back onto every existing trace that
|
|
108
141
|
* already references the same child. spawn_agent settles as soon as the child
|
|
@@ -140,36 +173,7 @@ export function mergeSubagentSnapshotsIntoMessages(messages, incoming) {
|
|
|
140
173
|
});
|
|
141
174
|
return changed ? { ...tool, metadata: { ...tool.metadata, subagents } } : tool;
|
|
142
175
|
};
|
|
143
|
-
|
|
144
|
-
const updated = messages.map((message) => {
|
|
145
|
-
let messageChanged = false;
|
|
146
|
-
const toolCalls = message.toolCalls?.map((tool) => {
|
|
147
|
-
const next = updateTool(tool);
|
|
148
|
-
if (next !== tool)
|
|
149
|
-
messageChanged = true;
|
|
150
|
-
return next;
|
|
151
|
-
});
|
|
152
|
-
const parts = message.parts?.map((part) => {
|
|
153
|
-
if (part.type !== "tools")
|
|
154
|
-
return part;
|
|
155
|
-
let partChanged = false;
|
|
156
|
-
const nextTools = part.toolCalls.map((tool) => {
|
|
157
|
-
const next = updateTool(tool);
|
|
158
|
-
if (next !== tool)
|
|
159
|
-
partChanged = true;
|
|
160
|
-
return next;
|
|
161
|
-
});
|
|
162
|
-
if (!partChanged)
|
|
163
|
-
return part;
|
|
164
|
-
messageChanged = true;
|
|
165
|
-
return { ...part, toolCalls: nextTools };
|
|
166
|
-
});
|
|
167
|
-
if (!messageChanged)
|
|
168
|
-
return message;
|
|
169
|
-
anyChanged = true;
|
|
170
|
-
return { ...message, toolCalls, parts };
|
|
171
|
-
});
|
|
172
|
-
return anyChanged ? updated : messages;
|
|
176
|
+
return mapTranscriptTools(messages, updateTool);
|
|
173
177
|
}
|
|
174
178
|
/** Reconciles persisted launch/wait/list echoes when rebuilding a session. */
|
|
175
179
|
export function synchronizeSubagentSnapshots(messages) {
|
|
@@ -210,64 +214,31 @@ export function mergeToolMetadata(current, incoming) {
|
|
|
210
214
|
subagents: [...byId.values()],
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
|
-
const FINAL_MEMBER_STATUSES = new Set(["completed", "failed", "blocked", "cancelled", "closed"]);
|
|
214
217
|
/**
|
|
215
|
-
*
|
|
216
|
-
* the
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* a workflow
|
|
220
|
-
*
|
|
218
|
+
* Applies a subagent tool_update whose launching call has already settled out
|
|
219
|
+
* of the streaming round. Children keep reporting against the launching
|
|
220
|
+
* spawn_agent / run_workflow call id across later rounds (e.g. while a
|
|
221
|
+
* wait_workflow blocks), so the update is merged into that entry by id — which
|
|
222
|
+
* absorbs members a workflow spawned after commit — and then projected onto
|
|
223
|
+
* every echo referencing the same members. The settled transcript is the only
|
|
224
|
+
* data source; the live tail never carries synthetic tool rows.
|
|
221
225
|
*/
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
* Accumulates a subagent tool_update whose originating tool call has already
|
|
237
|
-
* settled out of the current streaming round. The TUI clears its streaming
|
|
238
|
-
* toolCalls on every turn_start, but background children keep reporting
|
|
239
|
-
* against the launching run_workflow/spawn_agent call id across later rounds
|
|
240
|
-
* (e.g. while a wait_workflow blocks) — without this side-channel those
|
|
241
|
-
* updates are dropped and traces only appear after the whole team finishes.
|
|
242
|
-
* Entries act as synthetic tool calls feeding collectSubagentGroups.
|
|
243
|
-
* Returns true when the update was absorbed.
|
|
244
|
-
*/
|
|
245
|
-
export function accumulateLiveSubagentUpdate(map, event) {
|
|
246
|
-
if (event.metadata?.kind !== "subagent")
|
|
247
|
-
return false;
|
|
248
|
-
const prev = map.get(event.id);
|
|
249
|
-
const metadata = mergeToolMetadata(prev?.metadata, event.metadata);
|
|
250
|
-
if (!metadata)
|
|
251
|
-
return false;
|
|
252
|
-
// Per-child updates carry no mode; group them under the launching workflow
|
|
253
|
-
// call rather than falling back to one single-agent group per member.
|
|
254
|
-
if (event.name === "run_workflow" && metadata.mode === undefined)
|
|
255
|
-
metadata.mode = "workflow";
|
|
256
|
-
const members = Array.isArray(metadata.subagents)
|
|
257
|
-
? metadata.subagents.filter((member) => (typeof member === "object" && member !== null))
|
|
258
|
-
: [];
|
|
259
|
-
const statuses = members.map((member) => String(member.status ?? "running"));
|
|
260
|
-
const stillRunning = statuses.some((status) => status === "queued" || status === "running");
|
|
261
|
-
const failed = statuses.some((status) => status === "failed" || status === "blocked" || status === "cancelled");
|
|
262
|
-
map.set(event.id, {
|
|
263
|
-
id: event.id,
|
|
264
|
-
name: event.name,
|
|
265
|
-
args: prev?.args ?? {},
|
|
266
|
-
metadata,
|
|
267
|
-
status: stillRunning ? "running" : failed ? "failed" : "completed",
|
|
268
|
-
isError: failed,
|
|
226
|
+
export function applySubagentUpdateToMessages(messages, update) {
|
|
227
|
+
if (update.metadata?.kind !== "subagent")
|
|
228
|
+
return messages;
|
|
229
|
+
const byId = mapTranscriptTools(messages, (tool) => {
|
|
230
|
+
if (tool.id !== update.id)
|
|
231
|
+
return tool;
|
|
232
|
+
const metadata = mergeToolMetadata(tool.metadata, update.metadata);
|
|
233
|
+
if (!metadata || metadata === tool.metadata)
|
|
234
|
+
return tool;
|
|
235
|
+
// Per-child updates carry no mode; keep them grouped under the launching
|
|
236
|
+
// workflow call rather than falling back to one single-agent group each.
|
|
237
|
+
if (update.name === "run_workflow" && metadata.mode === undefined)
|
|
238
|
+
metadata.mode = "workflow";
|
|
239
|
+
return { ...tool, metadata };
|
|
269
240
|
});
|
|
270
|
-
return
|
|
241
|
+
return mergeSubagentSnapshotsIntoMessages(byId, update.metadata);
|
|
271
242
|
}
|
|
272
243
|
/**
|
|
273
244
|
* Collects every spawned subagent from the live transcript + streaming tools,
|