@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
|
@@ -116,7 +116,18 @@ function normalize(snapshot, showHistory) {
|
|
|
116
116
|
status: task.status,
|
|
117
117
|
task,
|
|
118
118
|
}));
|
|
119
|
-
|
|
119
|
+
// Which rows belong above the composer is derived, not remembered: whatever
|
|
120
|
+
// is still running, plus whatever this turn launched — so a sibling that
|
|
121
|
+
// finishes (or fails) first stays in view with its outcome until the next
|
|
122
|
+
// turn, whether its siblings ran in parallel or one after another. Work from
|
|
123
|
+
// earlier turns is history and shows only on request.
|
|
124
|
+
const launchedThisTurn = (item) => {
|
|
125
|
+
const launchedAt = itemLaunchedAt(item);
|
|
126
|
+
return snapshot.turnStartedAt !== undefined && launchedAt !== undefined && launchedAt >= snapshot.turnStartedAt;
|
|
127
|
+
};
|
|
128
|
+
const filter = (items) => (showHistory
|
|
129
|
+
? items
|
|
130
|
+
: items.filter((item) => isActive(item.status) || launchedThisTurn(item)))
|
|
120
131
|
.sort((left, right) => {
|
|
121
132
|
const activityOrder = Number(isActive(right.status)) - Number(isActive(left.status));
|
|
122
133
|
if (activityOrder !== 0)
|
|
@@ -129,6 +140,13 @@ function normalize(snapshot, showHistory) {
|
|
|
129
140
|
tasks: filter(taskItems),
|
|
130
141
|
};
|
|
131
142
|
}
|
|
143
|
+
function itemLaunchedAt(item) {
|
|
144
|
+
if (item.kind === "workflow")
|
|
145
|
+
return item.createdAt;
|
|
146
|
+
if (item.kind === "subagent")
|
|
147
|
+
return item.member.createdAt;
|
|
148
|
+
return item.task.startedAt;
|
|
149
|
+
}
|
|
132
150
|
function itemUpdatedAt(item) {
|
|
133
151
|
if (item.kind === "workflow")
|
|
134
152
|
return item.updatedAt ?? item.createdAt ?? 0;
|
|
@@ -144,10 +162,25 @@ export class TasksPaneComponent {
|
|
|
144
162
|
focused = false;
|
|
145
163
|
open = false;
|
|
146
164
|
manuallyClosed = false;
|
|
165
|
+
/**
|
|
166
|
+
* History is a view the user opts into for one look, never a mode: it is
|
|
167
|
+
* dropped whenever the pane closes and when a new round of activity starts.
|
|
168
|
+
* Otherwise one Ctrl+G after a finished turn left every earlier subagent
|
|
169
|
+
* listed above the composer for the rest of the session, next to a header
|
|
170
|
+
* that counts only the running ones.
|
|
171
|
+
*/
|
|
147
172
|
showHistory = false;
|
|
148
173
|
selectedId;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the user picked the selection (keys or click). The pane always
|
|
176
|
+
* needs one, so it defaults to the first row — but a default nobody chose
|
|
177
|
+
* must not steer the window: a task that was once the only row stayed
|
|
178
|
+
* "selected" and scrolled later subagents out of an untouched pane.
|
|
179
|
+
*/
|
|
180
|
+
selectionIsUsers = false;
|
|
149
181
|
hoveredId;
|
|
150
182
|
lastActiveCount = 0;
|
|
183
|
+
lastTurnStartedAt;
|
|
151
184
|
lastRows = [];
|
|
152
185
|
allRows = [];
|
|
153
186
|
frame = 0;
|
|
@@ -179,6 +212,32 @@ export class TasksPaneComponent {
|
|
|
179
212
|
const items = normalize(this.getSnapshot(), true);
|
|
180
213
|
return Object.values(items).flat().filter((item) => isActive(item.status)).length;
|
|
181
214
|
}
|
|
215
|
+
/** Rows the default view lists: running work plus what this turn launched. */
|
|
216
|
+
defaultViewCount() {
|
|
217
|
+
return Object.values(normalize(this.getSnapshot(), false)).flat().length;
|
|
218
|
+
}
|
|
219
|
+
visibleCount() {
|
|
220
|
+
return Object.values(normalize(this.getSnapshot(), this.showHistory)).flat().length;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Finished rows the pane lists next to the running ones, by outcome. Counted
|
|
224
|
+
* in the pane's current view, history included when it is showing, so the
|
|
225
|
+
* header never reports fewer rows than the list holds.
|
|
226
|
+
*/
|
|
227
|
+
settledCounts() {
|
|
228
|
+
const counts = { done: 0, failed: 0, stopped: 0 };
|
|
229
|
+
for (const item of Object.values(normalize(this.getSnapshot(), this.showHistory)).flat()) {
|
|
230
|
+
if (isActive(item.status))
|
|
231
|
+
continue;
|
|
232
|
+
if (item.status === "failed" || item.status === "blocked")
|
|
233
|
+
counts.failed += 1;
|
|
234
|
+
else if (item.status === "cancelled" || item.status === "killed")
|
|
235
|
+
counts.stopped += 1;
|
|
236
|
+
else
|
|
237
|
+
counts.done += 1;
|
|
238
|
+
}
|
|
239
|
+
return counts;
|
|
240
|
+
}
|
|
182
241
|
totalCount() {
|
|
183
242
|
const items = normalize(this.getSnapshot(), true);
|
|
184
243
|
return Object.values(items).flat().length;
|
|
@@ -193,6 +252,14 @@ export class TasksPaneComponent {
|
|
|
193
252
|
if (this.open && !forceFocus) {
|
|
194
253
|
this.open = false;
|
|
195
254
|
this.manuallyClosed = true;
|
|
255
|
+
this.showHistory = false;
|
|
256
|
+
// Closing through the status bar (a click) bypasses the app's Ctrl+G
|
|
257
|
+
// handler. A pane that still owned the keyboard would swallow input while
|
|
258
|
+
// invisible — and the bar itself can now be hidden — so hand focus back.
|
|
259
|
+
if (this.focused) {
|
|
260
|
+
this.focused = false;
|
|
261
|
+
this.callbacks.onEscape();
|
|
262
|
+
}
|
|
196
263
|
}
|
|
197
264
|
else {
|
|
198
265
|
this.open = true;
|
|
@@ -200,7 +267,9 @@ export class TasksPaneComponent {
|
|
|
200
267
|
// Once all activity has settled, Ctrl+G must remain a usable route back
|
|
201
268
|
// to the completed child transcript. Opening an empty active-only pane
|
|
202
269
|
// makes the work look lost even though the child session still exists.
|
|
203
|
-
|
|
270
|
+
// This turn's rows (what the bar counts) open first; older history only
|
|
271
|
+
// when there is nothing else to show, and behind `h` otherwise.
|
|
272
|
+
if (this.activeCount() === 0 && this.defaultViewCount() === 0 && this.totalCount() > 0)
|
|
204
273
|
this.showHistory = true;
|
|
205
274
|
}
|
|
206
275
|
this.callbacks.onRender();
|
|
@@ -208,30 +277,66 @@ export class TasksPaneComponent {
|
|
|
208
277
|
close() {
|
|
209
278
|
this.open = false;
|
|
210
279
|
this.manuallyClosed = true;
|
|
280
|
+
this.showHistory = false;
|
|
211
281
|
this.callbacks.onRender();
|
|
212
282
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Advance open/history state from the latest snapshot. Both the status bar
|
|
285
|
+
* and the pane call this before rendering: the bar is laid out first, so a
|
|
286
|
+
* transition made only inside the pane's render would leave the bar one
|
|
287
|
+
* frame behind — forever, when nothing is animating to trigger a redraw.
|
|
288
|
+
* Idempotent per snapshot (it compares against the last values it saw).
|
|
289
|
+
*/
|
|
290
|
+
syncState() {
|
|
291
|
+
if (this.getTerminalRows() < 12)
|
|
292
|
+
return;
|
|
220
293
|
const activeCount = this.activeCount();
|
|
221
|
-
|
|
294
|
+
// A new run, or activity starting from idle, ends the history view. The
|
|
295
|
+
// run boundary matters on its own: with a task still running the count
|
|
296
|
+
// never crosses zero, and new work would otherwise keep listing history.
|
|
297
|
+
// A user who is browsing history inside the pane keeps their rows; the
|
|
298
|
+
// view resets when they close it.
|
|
299
|
+
const turnStartedAt = this.getSnapshot().turnStartedAt;
|
|
300
|
+
const newTurn = turnStartedAt !== this.lastTurnStartedAt;
|
|
301
|
+
this.lastTurnStartedAt = turnStartedAt;
|
|
302
|
+
const activityFromIdle = activeCount > 0 && this.lastActiveCount === 0;
|
|
303
|
+
if ((newTurn || activityFromIdle) && !this.focused) {
|
|
304
|
+
this.showHistory = false;
|
|
305
|
+
// If that leaves nothing to list (the pane was opened while idle, then a
|
|
306
|
+
// turn launches nothing), close it rather than keep an empty pane under
|
|
307
|
+
// a "0 completed" header. Not a manual close: the next activity reopens it.
|
|
308
|
+
if (this.open && this.visibleCount() === 0) {
|
|
309
|
+
this.open = false;
|
|
310
|
+
this.manuallyClosed = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (activityFromIdle && !this.manuallyClosed)
|
|
222
314
|
this.open = true;
|
|
223
315
|
if (activeCount === 0 && this.lastActiveCount > 0) {
|
|
224
316
|
if (this.focused) {
|
|
225
317
|
// A user who is already inspecting the pane should see the final
|
|
226
318
|
// status land in place instead of watching the selected row vanish.
|
|
227
|
-
|
|
319
|
+
// This turn's rows stay on their own; only work launched in an earlier
|
|
320
|
+
// turn needs the history view to remain visible.
|
|
321
|
+
if (this.visibleCount() === 0)
|
|
322
|
+
this.showHistory = true;
|
|
228
323
|
}
|
|
229
324
|
else {
|
|
230
325
|
this.open = false;
|
|
231
326
|
this.manuallyClosed = false;
|
|
327
|
+
this.showHistory = false;
|
|
232
328
|
}
|
|
233
329
|
}
|
|
234
330
|
this.lastActiveCount = activeCount;
|
|
331
|
+
}
|
|
332
|
+
render(width) {
|
|
333
|
+
const terminalRows = this.getTerminalRows();
|
|
334
|
+
if (terminalRows < 12) {
|
|
335
|
+
this.lastRows = [];
|
|
336
|
+
this.allRows = [];
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
this.syncState();
|
|
235
340
|
if (!this.open) {
|
|
236
341
|
this.lastRows = [];
|
|
237
342
|
this.allRows = [];
|
|
@@ -253,28 +358,55 @@ export class TasksPaneComponent {
|
|
|
253
358
|
}
|
|
254
359
|
const itemRows = rows.filter((row) => row.kind === "item");
|
|
255
360
|
this.allRows = rows;
|
|
256
|
-
|
|
361
|
+
const selectionGone = !this.selectedId
|
|
362
|
+
|| !itemRows.some((row) => `${row.item.kind}:${row.item.id}` === this.selectedId);
|
|
363
|
+
if (selectionGone)
|
|
364
|
+
this.selectionIsUsers = false;
|
|
365
|
+
if (!this.selectionIsUsers) {
|
|
257
366
|
this.selectedId = itemRows[0] ? `${itemRows[0].item.kind}:${itemRows[0].item.id}` : undefined;
|
|
258
367
|
}
|
|
368
|
+
// When the rows do not fit, the last line says how many are out of view
|
|
369
|
+
// rather than clipping them silently. Only a focused pane scrolls to its
|
|
370
|
+
// selection; untouched, it shows the top, where running work sorts first.
|
|
259
371
|
const maxRows = Math.max(3, Math.min(8, Math.floor(terminalRows * 0.15)));
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
372
|
+
const selectedIndex = this.focused
|
|
373
|
+
? rows.findIndex((row) => row.kind === "item" && `${row.item.kind}:${row.item.id}` === this.selectedId)
|
|
374
|
+
: -1;
|
|
375
|
+
const layout = (windowRows) => {
|
|
376
|
+
const start = selectedIndex >= windowRows ? selectedIndex - windowRows + 1 : 0;
|
|
377
|
+
const items = (slice) => slice.filter((row) => row.kind === "item").length;
|
|
378
|
+
return { start, windowRows, above: items(rows.slice(0, start)), below: items(rows.slice(start + windowRows)) };
|
|
379
|
+
};
|
|
380
|
+
// Reserve the last line only when it has something to say: hidden rows can
|
|
381
|
+
// be nothing but collapsed section headers, and a blank reserved slot would
|
|
382
|
+
// cost one of as few as three lines.
|
|
383
|
+
let view = layout(maxRows);
|
|
384
|
+
if (rows.length > maxRows) {
|
|
385
|
+
const reserved = layout(maxRows - 1);
|
|
386
|
+
if (reserved.above + reserved.below > 0)
|
|
387
|
+
view = reserved;
|
|
388
|
+
}
|
|
389
|
+
const visibleRows = rows.slice(view.start, view.start + view.windowRows);
|
|
265
390
|
this.lastRows = visibleRows;
|
|
266
391
|
const theme = this.getTheme();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
392
|
+
const more = [
|
|
393
|
+
view.above > 0 ? `${view.above} more above` : "",
|
|
394
|
+
view.below > 0 ? `${view.below} more below` : "",
|
|
395
|
+
].filter(Boolean).join(" · ");
|
|
396
|
+
const moreLine = view.windowRows < maxRows && more
|
|
397
|
+
? [themeDim(theme.dim, pad(` … ${more}${this.focused ? "" : " · Ctrl+G"}`, width))]
|
|
398
|
+
: [];
|
|
399
|
+
return [...visibleRows.map((row) => {
|
|
400
|
+
if (row.kind === "header") {
|
|
401
|
+
const count = items[row.section].length;
|
|
402
|
+
const marker = this.collapsed.has(row.section) ? "▸" : "▾";
|
|
403
|
+
const label = sections.find(([section]) => section === row.section)?.[1] ?? row.section;
|
|
404
|
+
return themeDim(theme.dim, pad(` ${marker} ${label} ${count}`, width));
|
|
405
|
+
}
|
|
406
|
+
const key = `${row.item.kind}:${row.item.id}`;
|
|
407
|
+
const state = key === this.selectedId && this.focused ? "selected" : key === this.hoveredId ? "hover" : "idle";
|
|
408
|
+
return paint(this.renderItem(row.item, width), width, state, theme);
|
|
409
|
+
}), ...moreLine];
|
|
278
410
|
}
|
|
279
411
|
renderItem(item, width) {
|
|
280
412
|
const theme = this.getTheme();
|
|
@@ -314,15 +446,19 @@ export class TasksPaneComponent {
|
|
|
314
446
|
const index = Math.max(0, itemRows.findIndex((row) => `${row.item.kind}:${row.item.id}` === this.selectedId));
|
|
315
447
|
if (matchesKey(data, "up") || data === "k") {
|
|
316
448
|
const row = itemRows[Math.max(0, index - 1)];
|
|
317
|
-
if (row)
|
|
449
|
+
if (row) {
|
|
318
450
|
this.selectedId = `${row.item.kind}:${row.item.id}`;
|
|
451
|
+
this.selectionIsUsers = true;
|
|
452
|
+
}
|
|
319
453
|
this.callbacks.onRender();
|
|
320
454
|
return;
|
|
321
455
|
}
|
|
322
456
|
if (matchesKey(data, "down") || data === "j") {
|
|
323
457
|
const row = itemRows[Math.min(itemRows.length - 1, index + 1)];
|
|
324
|
-
if (row)
|
|
458
|
+
if (row) {
|
|
325
459
|
this.selectedId = `${row.item.kind}:${row.item.id}`;
|
|
460
|
+
this.selectionIsUsers = true;
|
|
461
|
+
}
|
|
326
462
|
this.callbacks.onRender();
|
|
327
463
|
return;
|
|
328
464
|
}
|
|
@@ -366,6 +502,7 @@ export class TasksPaneComponent {
|
|
|
366
502
|
return true;
|
|
367
503
|
}
|
|
368
504
|
this.selectedId = `${row.item.kind}:${row.item.id}`;
|
|
505
|
+
this.selectionIsUsers = true;
|
|
369
506
|
if (event.clickCount >= 2)
|
|
370
507
|
this.openItem(row.item);
|
|
371
508
|
return true;
|
|
@@ -395,14 +532,27 @@ export class TaskStatusBarComponent {
|
|
|
395
532
|
render(width) {
|
|
396
533
|
if (!this.pane.isAvailable())
|
|
397
534
|
return [];
|
|
535
|
+
this.pane.syncState();
|
|
398
536
|
const count = this.pane.activeCount();
|
|
399
|
-
|
|
537
|
+
// The bar describes what the pane lists: open, the rows on screen; closed,
|
|
538
|
+
// the default view Ctrl+G opens (running work plus this turn's). It used to
|
|
539
|
+
// count the whole session, a number that only ever grew. With nothing from
|
|
540
|
+
// this turn the bar is hidden; Ctrl+G still reaches the history.
|
|
541
|
+
const total = this.pane.isOpen() ? this.pane.visibleCount() : this.pane.defaultViewCount();
|
|
400
542
|
if (total === 0 && !this.pane.isOpen())
|
|
401
543
|
return [];
|
|
402
544
|
const marker = this.pane.isOpen() ? "▾" : "▸";
|
|
403
545
|
const theme = this.pane.theme();
|
|
546
|
+
// The list below also holds this turn's finished rows; say so, or the
|
|
547
|
+
// header counts fewer activities than the pane shows.
|
|
548
|
+
const settled = this.pane.settledCounts();
|
|
549
|
+
const outcome = [
|
|
550
|
+
settled.failed > 0 ? themeForeground(theme.error, `${settled.failed} failed`) : "",
|
|
551
|
+
settled.done > 0 ? `${settled.done} done` : "",
|
|
552
|
+
settled.stopped > 0 ? `${settled.stopped} stopped` : "",
|
|
553
|
+
].filter(Boolean).map((part) => ` · ${part}`).join("");
|
|
404
554
|
const text = count > 0
|
|
405
|
-
? `${marker} ${themeForeground(theme.accent, this.pane.activityGlyph())} ${count} background activit${count === 1 ? "y" : "ies"} · Ctrl+G`
|
|
555
|
+
? `${marker} ${themeForeground(theme.accent, this.pane.activityGlyph())} ${count} background activit${count === 1 ? "y" : "ies"}${outcome} · Ctrl+G`
|
|
406
556
|
: `${marker} ${themeForeground(theme.success, "✓")} ${total} completed activit${total === 1 ? "y" : "ies"} · Ctrl+G`;
|
|
407
557
|
return [themeDim(theme.dim, truncateToWidth(` ${text}`, Math.max(1, width), ""))];
|
|
408
558
|
}
|
|
@@ -45,12 +45,19 @@ export interface TranscriptTheme {
|
|
|
45
45
|
}
|
|
46
46
|
export declare function createTranscriptTheme(getTheme: () => Theme): TranscriptTheme;
|
|
47
47
|
export declare const defaultTranscriptTheme: TranscriptTheme;
|
|
48
|
+
/**
|
|
49
|
+
* Width of the shared left rail. Column 0..2 belong to surface chrome
|
|
50
|
+
* (user `›`, hovered tool `│`, reasoning `┃`); the body starts at column 3.
|
|
51
|
+
* Changing this moves every transcript surface together.
|
|
52
|
+
*/
|
|
53
|
+
export declare const TRANSCRIPT_RAIL_COLUMNS = 3;
|
|
48
54
|
export declare function renderUserCard(content: string, options: TranscriptRenderOptions, images?: readonly DisplayImageAttachment[], messageKey?: string): string[];
|
|
49
55
|
export declare function wrapPlain(text: string, columns: number): string[];
|
|
50
56
|
/**
|
|
51
57
|
* Project assistant prose without transcript spacing. Live streaming and
|
|
52
58
|
* settled history both call this function, so completing a turn cannot change
|
|
53
|
-
* the answer gutter or wrapping width.
|
|
59
|
+
* the answer gutter or wrapping width. Prose carries an empty rail: its body
|
|
60
|
+
* sits in the same column as user text and tool entries.
|
|
54
61
|
*/
|
|
55
62
|
export declare function projectAssistantRows(content: string, options: TranscriptRenderOptions): string[];
|
|
56
63
|
export declare function renderAssistant(content: string, options: TranscriptRenderOptions): string[];
|
|
@@ -71,9 +78,9 @@ export interface ReasoningProjectionOptions {
|
|
|
71
78
|
/** Grok minimal mode keeps the same rolling reasoning surface live/settled. */
|
|
72
79
|
export declare const MINIMAL_REASONING_BODY_ROWS = 5;
|
|
73
80
|
/**
|
|
74
|
-
* Grok-style reasoning surface. Reasoning
|
|
75
|
-
*
|
|
76
|
-
* the live row pool and committed transcript.
|
|
81
|
+
* Grok-style reasoning surface. Reasoning paints a `┃` in the shared rail so
|
|
82
|
+
* its body lines up with prose and tool entries. The same projection is used
|
|
83
|
+
* by the live row pool and committed transcript.
|
|
77
84
|
*/
|
|
78
85
|
export declare function projectReasoningRows(content: string, options: TranscriptRenderOptions, projection?: ReasoningProjectionOptions): string[];
|
|
79
86
|
export declare function renderReasoning(content: string, options: TranscriptRenderOptions): string[];
|