@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
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* - tool calls: single-line trace with status glyph, name, arg preview,
|
|
11
11
|
* error result preview
|
|
12
12
|
* - synthetic rows (interrupt, compaction, notices) rendered as notices
|
|
13
|
+
*
|
|
14
|
+
* Horizontal geometry: every surface shares one left rail (see
|
|
15
|
+
* TRANSCRIPT_RAIL_COLUMNS). The rail carries per-surface chrome and the body
|
|
16
|
+
* always starts at the same column, so user text, tool entries, reasoning and
|
|
17
|
+
* the final answer line up no matter which projector drew the row.
|
|
13
18
|
*/
|
|
14
19
|
import chalk from "chalk";
|
|
15
20
|
import stringWidth from "string-width";
|
|
@@ -77,10 +82,21 @@ export function createTranscriptTheme(getTheme) {
|
|
|
77
82
|
};
|
|
78
83
|
}
|
|
79
84
|
export const defaultTranscriptTheme = createTranscriptTheme(() => darkTheme);
|
|
85
|
+
/**
|
|
86
|
+
* Width of the shared left rail. Column 0..2 belong to surface chrome
|
|
87
|
+
* (user `›`, hovered tool `│`, reasoning `┃`); the body starts at column 3.
|
|
88
|
+
* Changing this moves every transcript surface together.
|
|
89
|
+
*/
|
|
90
|
+
export const TRANSCRIPT_RAIL_COLUMNS = 3;
|
|
91
|
+
/**
|
|
92
|
+
* Cells left unpainted on the right. Painting the physical last column can
|
|
93
|
+
* trigger an automatic wrap in several terminals and leaves a broken bg patch.
|
|
94
|
+
*/
|
|
95
|
+
const TRANSCRIPT_RIGHT_MARGIN = 2;
|
|
80
96
|
const TRACE_BORDER_MIN_COLUMNS = 4;
|
|
81
97
|
const IMAGE_LABEL_SPACE_SENTINEL = "\uE000";
|
|
82
|
-
const TRACE_CONTENT_LEFT_PAD =
|
|
83
|
-
const TRACE_RESERVED_COLUMNS =
|
|
98
|
+
const TRACE_CONTENT_LEFT_PAD = TRANSCRIPT_RAIL_COLUMNS - 1; // cells between the hover border and the body
|
|
99
|
+
const TRACE_RESERVED_COLUMNS = TRANSCRIPT_RAIL_COLUMNS + 1; // rail + right border
|
|
84
100
|
// Keep a zero-width row in projection joins without painting a terminal cell.
|
|
85
101
|
// The row exists in layout at rest, so hover can add corners without reflow.
|
|
86
102
|
const TRACE_IDLE_VPAD = "\x1b[0m";
|
|
@@ -88,22 +104,43 @@ const TRACE_IDLE_VPAD = "\x1b[0m";
|
|
|
88
104
|
function truncateVisible(text, maxColumns) {
|
|
89
105
|
return truncateVisual(text, maxColumns);
|
|
90
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Compose one transcript row from rail chrome and body. `rail` may be styled
|
|
109
|
+
* and is padded to exactly TRANSCRIPT_RAIL_COLUMNS cells, so the body column
|
|
110
|
+
* is identical for every surface regardless of which glyph the rail carries.
|
|
111
|
+
*/
|
|
112
|
+
function railRow(rail, body) {
|
|
113
|
+
const pad = Math.max(0, TRANSCRIPT_RAIL_COLUMNS - stringWidth(rail));
|
|
114
|
+
return `${rail}${" ".repeat(pad)}${body}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Shared body-width computation. Below the same threshold the tool frame uses
|
|
118
|
+
* to drop its border, surfaces give up the rail rather than squeezing text
|
|
119
|
+
* into a one-cell column.
|
|
120
|
+
*/
|
|
121
|
+
function railGeometry(columns) {
|
|
122
|
+
const width = Math.max(1, Math.floor(columns));
|
|
123
|
+
const frameColumns = width > TRANSCRIPT_RIGHT_MARGIN ? width - TRANSCRIPT_RIGHT_MARGIN : width;
|
|
124
|
+
const rail = frameColumns >= TRACE_BORDER_MIN_COLUMNS;
|
|
125
|
+
return {
|
|
126
|
+
rail,
|
|
127
|
+
bodyColumns: rail ? Math.max(1, frameColumns - TRANSCRIPT_RAIL_COLUMNS) : frameColumns,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
91
130
|
export function renderUserCard(content, options, images = [], messageKey = "user") {
|
|
92
131
|
const theme = options.theme ?? defaultTranscriptTheme;
|
|
93
132
|
const terminalWidth = Math.max(1, Math.floor(options.columns));
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
const width = terminalWidth > 2 ? terminalWidth - 2 : terminalWidth;
|
|
99
|
-
// The normal card gutter is four cells (marker + trailing pad). On an
|
|
133
|
+
// At one or two columns there is no room to reserve the right margin, so
|
|
134
|
+
// use every available cell.
|
|
135
|
+
const width = terminalWidth > TRANSCRIPT_RIGHT_MARGIN ? terminalWidth - TRANSCRIPT_RIGHT_MARGIN : terminalWidth;
|
|
136
|
+
// The card gutter is the shared rail plus one trailing pad cell. On an
|
|
100
137
|
// extremely narrow terminal, degrade to a plain painted row; forcing the
|
|
101
138
|
// historical 20-column minimum is what made settled rows overflow after a
|
|
102
139
|
// 20x5 resize even though the live trace itself was width-safe.
|
|
103
|
-
if (width <
|
|
140
|
+
if (width < TRANSCRIPT_RAIL_COLUMNS + 2) {
|
|
104
141
|
return wrapPlain(content, width).map((line) => theme.userBg(truncateVisible(line, width)));
|
|
105
142
|
}
|
|
106
|
-
const textWidth = width -
|
|
143
|
+
const textWidth = width - TRANSCRIPT_RAIL_COLUMNS - 1; // rail + trailing pad
|
|
107
144
|
const split = images.length > 0 ? splitImageDisplayContent(content) : undefined;
|
|
108
145
|
const body = split?.bodyLines.join("\n") ?? content;
|
|
109
146
|
// Preserve the label's visible width while preventing the generic word
|
|
@@ -137,7 +174,7 @@ export function renderUserCard(content, options, images = [], messageKey = "user
|
|
|
137
174
|
renderedLine = renderedLine.replace(image.label, chip);
|
|
138
175
|
}
|
|
139
176
|
const padded = `${renderedLine}${" ".repeat(Math.max(0, textWidth - stringWidth(renderedLine)))}`;
|
|
140
|
-
const filled = (index === 0 ? theme.accent(" › ") : "
|
|
177
|
+
const filled = railRow(index === 0 ? theme.accent(" › ") : "", theme.userText(padded)) + " ";
|
|
141
178
|
rows.push(theme.userBg(filled));
|
|
142
179
|
});
|
|
143
180
|
// Keep the painted padding symmetric so the message body is vertically
|
|
@@ -177,62 +214,98 @@ function projectUserCard(message, options) {
|
|
|
177
214
|
return { rows, traceTargets: targets };
|
|
178
215
|
}
|
|
179
216
|
export function wrapPlain(text, columns) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
217
|
+
return Array.from(iteratePlainRows(text, columns));
|
|
218
|
+
}
|
|
219
|
+
/** Stream rows so collapsed reasoning need not retain its entire wrapped body. */
|
|
220
|
+
function* iteratePlainRows(text, columns) {
|
|
221
|
+
const widths = new Map();
|
|
222
|
+
const ascii = { width: 1, additive: true };
|
|
223
|
+
const measureChar = (char) => {
|
|
224
|
+
const code = char.charCodeAt(0);
|
|
225
|
+
if (code >= 0x20 && code <= 0x7e)
|
|
226
|
+
return ascii;
|
|
227
|
+
let cached = widths.get(char);
|
|
228
|
+
if (!cached) {
|
|
229
|
+
cached = {
|
|
230
|
+
width: stringWidth(char),
|
|
231
|
+
// These scalars cannot combine with each other. Other text (marks,
|
|
232
|
+
// emoji, joiners, ANSI, Jamo, etc.) still uses whole-chunk measurement.
|
|
233
|
+
additive: /^[\p{Unified_Ideograph}\p{Punctuation}]$/u.test(char),
|
|
234
|
+
};
|
|
235
|
+
widths.set(char, cached);
|
|
236
|
+
}
|
|
237
|
+
return cached;
|
|
238
|
+
};
|
|
239
|
+
function* splitToken(token) {
|
|
183
240
|
let chunk = "";
|
|
184
241
|
let width = 0;
|
|
242
|
+
let additive = true;
|
|
185
243
|
for (const char of token) {
|
|
186
|
-
const
|
|
187
|
-
if (chunk && width +
|
|
188
|
-
|
|
244
|
+
const metric = measureChar(char);
|
|
245
|
+
if (chunk && width + metric.width > columns) {
|
|
246
|
+
yield { text: chunk, width: additive ? width : stringWidth(chunk) };
|
|
189
247
|
chunk = "";
|
|
190
248
|
width = 0;
|
|
249
|
+
additive = true;
|
|
191
250
|
}
|
|
192
251
|
chunk += char;
|
|
193
|
-
width +=
|
|
252
|
+
width += metric.width;
|
|
253
|
+
additive &&= metric.additive;
|
|
194
254
|
}
|
|
195
255
|
if (chunk)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
for
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
256
|
+
yield { text: chunk, width: additive ? width : stringWidth(chunk) };
|
|
257
|
+
}
|
|
258
|
+
// Scan paragraphs/tokens lazily: a head preview can stop early, and a tail
|
|
259
|
+
// preview never allocates arrays for all paragraphs, tokens or wrapped rows.
|
|
260
|
+
let start = 0;
|
|
261
|
+
while (start <= text.length) {
|
|
262
|
+
const newline = text.indexOf("\n", start);
|
|
263
|
+
const end = newline === -1 ? text.length : newline;
|
|
264
|
+
const paragraph = text.slice(start, end);
|
|
265
|
+
if (paragraph === "")
|
|
266
|
+
yield "";
|
|
204
267
|
let line = "";
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
line
|
|
268
|
+
let lineWidth = 0;
|
|
269
|
+
for (const match of paragraph.matchAll(/\S+/g)) {
|
|
270
|
+
for (const chunk of splitToken(match[0])) {
|
|
271
|
+
const candidateWidth = line ? lineWidth + 1 + chunk.width : chunk.width;
|
|
272
|
+
if (line && candidateWidth > columns) {
|
|
273
|
+
yield line;
|
|
274
|
+
line = chunk.text;
|
|
275
|
+
lineWidth = chunk.width;
|
|
211
276
|
}
|
|
212
277
|
else {
|
|
213
|
-
line =
|
|
278
|
+
line = line ? `${line} ${chunk.text}` : chunk.text;
|
|
279
|
+
lineWidth = candidateWidth;
|
|
214
280
|
}
|
|
215
281
|
}
|
|
216
282
|
}
|
|
217
283
|
if (line)
|
|
218
|
-
|
|
284
|
+
yield line;
|
|
285
|
+
if (newline === -1)
|
|
286
|
+
break;
|
|
287
|
+
start = newline + 1;
|
|
219
288
|
}
|
|
220
|
-
return out;
|
|
221
289
|
}
|
|
222
290
|
/**
|
|
223
291
|
* Project assistant prose without transcript spacing. Live streaming and
|
|
224
292
|
* settled history both call this function, so completing a turn cannot change
|
|
225
|
-
* the answer gutter or wrapping width.
|
|
293
|
+
* the answer gutter or wrapping width. Prose carries an empty rail: its body
|
|
294
|
+
* sits in the same column as user text and tool entries.
|
|
226
295
|
*/
|
|
227
296
|
export function projectAssistantRows(content, options) {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
297
|
+
const geometry = railGeometry(options.columns);
|
|
298
|
+
const body = options.markdownRenderer
|
|
299
|
+
? options.markdownRenderer(content, geometry.bodyColumns)
|
|
300
|
+
: wrapPlain(content, geometry.bodyColumns);
|
|
301
|
+
return body.map((row) => {
|
|
302
|
+
const clipped = truncateVisible(row, geometry.bodyColumns);
|
|
303
|
+
// Paragraph breaks stay genuinely empty so projection joins still treat
|
|
304
|
+
// them as spacers instead of three painted cells.
|
|
305
|
+
if (stringWidth(clipped) === 0)
|
|
306
|
+
return "";
|
|
307
|
+
return geometry.rail ? railRow("", clipped) : clipped;
|
|
308
|
+
});
|
|
236
309
|
}
|
|
237
310
|
export function renderAssistant(content, options) {
|
|
238
311
|
return [...projectAssistantRows(content, options), ""];
|
|
@@ -262,35 +335,46 @@ export function joinTranscriptBlocks(blocks) {
|
|
|
262
335
|
/** Grok minimal mode keeps the same rolling reasoning surface live/settled. */
|
|
263
336
|
export const MINIMAL_REASONING_BODY_ROWS = 5;
|
|
264
337
|
/**
|
|
265
|
-
* Grok-style reasoning surface. Reasoning
|
|
266
|
-
*
|
|
267
|
-
* the live row pool and committed transcript.
|
|
338
|
+
* Grok-style reasoning surface. Reasoning paints a `┃` in the shared rail so
|
|
339
|
+
* its body lines up with prose and tool entries. The same projection is used
|
|
340
|
+
* by the live row pool and committed transcript.
|
|
268
341
|
*/
|
|
269
342
|
export function projectReasoningRows(content, options, projection = {}) {
|
|
270
343
|
const theme = options.theme ?? defaultTranscriptTheme;
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const body = content
|
|
274
|
-
.split("\n")
|
|
275
|
-
.filter((line) => line.trim() !== "")
|
|
276
|
-
.flatMap((line) => wrapPlain(line, bodyWidth));
|
|
277
|
-
if (body.length === 0)
|
|
278
|
-
return [];
|
|
279
|
-
const limit = Math.max(0, projection.maxBodyRows ?? (options.showReasoning ? body.length : MINIMAL_REASONING_BODY_ROWS));
|
|
344
|
+
const geometry = railGeometry(options.columns);
|
|
345
|
+
const limit = Math.max(0, Math.floor(projection.maxBodyRows ?? (options.showReasoning ? Infinity : MINIMAL_REASONING_BODY_ROWS)));
|
|
280
346
|
const fromEnd = projection.fromEnd ?? !options.showReasoning;
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
347
|
+
const body = [];
|
|
348
|
+
let count = 0;
|
|
349
|
+
for (const line of iteratePlainRows(content, geometry.bodyColumns)) {
|
|
350
|
+
if (!line)
|
|
351
|
+
continue;
|
|
352
|
+
count += 1;
|
|
353
|
+
if (limit === 0)
|
|
354
|
+
break;
|
|
355
|
+
if (limit === Infinity || !fromEnd)
|
|
356
|
+
body.push(line);
|
|
357
|
+
else
|
|
358
|
+
body[(count - 1) % limit] = line;
|
|
359
|
+
// One extra row suffices to detect hidden content in a head preview.
|
|
360
|
+
if (!fromEnd && count > limit)
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
if (count === 0)
|
|
364
|
+
return [];
|
|
365
|
+
const visible = limit === 0 ? []
|
|
366
|
+
: fromEnd && count > limit
|
|
367
|
+
? [...body.slice(count % limit), ...body.slice(0, count % limit)]
|
|
285
368
|
: body.slice(0, limit);
|
|
286
369
|
const style = (text) => theme.dim(chalk.italic(text));
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
370
|
+
const row = (text) => style(geometry.rail
|
|
371
|
+
? railRow("┃", truncateVisible(text, geometry.bodyColumns))
|
|
372
|
+
: truncateVisible(text, geometry.bodyColumns));
|
|
373
|
+
const rows = [row(`◆ Thinking${projection.running ? "…" : ""}`)];
|
|
374
|
+
for (const line of visible)
|
|
375
|
+
rows.push(row(line));
|
|
376
|
+
if (count > visible.length) {
|
|
377
|
+
rows.push(row(options.showReasoning ? "…" : "… (Ctrl+T to expand)"));
|
|
294
378
|
}
|
|
295
379
|
return rows;
|
|
296
380
|
}
|
|
@@ -358,7 +442,7 @@ export function projectToolTraceGroups(toolCalls, options, renderOptions = {}) {
|
|
|
358
442
|
function projectTraceGroup(group, options) {
|
|
359
443
|
const theme = options.theme ?? defaultTranscriptTheme;
|
|
360
444
|
const columns = Math.max(1, options.columns);
|
|
361
|
-
const frameColumns = columns >
|
|
445
|
+
const frameColumns = columns > TRANSCRIPT_RIGHT_MARGIN ? columns - TRANSCRIPT_RIGHT_MARGIN : columns;
|
|
362
446
|
const contentColumns = frameColumns >= TRACE_BORDER_MIN_COLUMNS
|
|
363
447
|
? Math.max(1, frameColumns - TRACE_RESERVED_COLUMNS)
|
|
364
448
|
: frameColumns;
|
|
@@ -545,7 +629,7 @@ function decorateTraceGroup(projection, target, groupKey, columns, theme, intera
|
|
|
545
629
|
: TRACE_IDLE_VPAD;
|
|
546
630
|
const rows = projection.rows.map((row) => {
|
|
547
631
|
if (!active)
|
|
548
|
-
return
|
|
632
|
+
return railRow("", row);
|
|
549
633
|
const interior = `${" ".repeat(TRACE_CONTENT_LEFT_PAD)}${row}`;
|
|
550
634
|
const padding = " ".repeat(Math.max(0, columns - 2 - stringWidth(interior)));
|
|
551
635
|
return `${border("│")}${background(`${interior}${padding}`)}${border("│")}`;
|
|
@@ -613,7 +697,7 @@ function appendReadPreview(rows, traceTargets, tool, target, columns, theme, ind
|
|
|
613
697
|
function projectCompactionSummary(message, options) {
|
|
614
698
|
const theme = options.theme ?? defaultTranscriptTheme;
|
|
615
699
|
const columns = Math.max(1, options.columns);
|
|
616
|
-
const frameColumns = columns >
|
|
700
|
+
const frameColumns = columns > TRANSCRIPT_RIGHT_MARGIN ? columns - TRANSCRIPT_RIGHT_MARGIN : columns;
|
|
617
701
|
const contentColumns = frameColumns >= TRACE_BORDER_MIN_COLUMNS
|
|
618
702
|
? Math.max(1, frameColumns - TRACE_RESERVED_COLUMNS)
|
|
619
703
|
: frameColumns;
|
|
@@ -760,9 +844,12 @@ export function projectTranscript(messages, options) {
|
|
|
760
844
|
// The transcript owns the sole separator before a live assistant surface.
|
|
761
845
|
// When that surface settles, the same row becomes the transcript's trailing
|
|
762
846
|
// separator, so user -> Thinking geometry cannot change at commit time.
|
|
847
|
+
// A transcript that already ends in a spacer (a tool group's bottom padding)
|
|
848
|
+
// must not gain a second one, or the next turn's live Thinking sits one row
|
|
849
|
+
// lower than it will after commit.
|
|
763
850
|
if (projection.rows.length === 0)
|
|
764
851
|
return projection;
|
|
765
|
-
if (options.trailingSpacer !== false) {
|
|
852
|
+
if (options.trailingSpacer !== false && !projection.trailingSpacer) {
|
|
766
853
|
projection.rows.push("");
|
|
767
854
|
projection.traceTargets.push(undefined);
|
|
768
855
|
projection.trailingSpacer = true;
|
|
@@ -16,21 +16,35 @@ export interface ComposerAutocompleteSources {
|
|
|
16
16
|
providerId?(): string;
|
|
17
17
|
themeMode?(): ThemeMode;
|
|
18
18
|
detectedTheme?(): ResolvedTheme;
|
|
19
|
+
/** True while the session is bound to the legacy Grok external runtime. */
|
|
20
|
+
grokRuntimeActive?(): boolean;
|
|
19
21
|
onModelSuggestionsChanged?(): void;
|
|
20
22
|
fdPath?: string | null;
|
|
21
23
|
}
|
|
22
24
|
type ModelCompletionSource = (argumentPrefix: string) => AutocompleteArgumentSuggestions | AutocompleteItem[] | null;
|
|
23
25
|
type ProviderCompletionSource = ModelCompletionSource;
|
|
24
26
|
type ThemeCompletionSource = ModelCompletionSource;
|
|
25
|
-
type
|
|
27
|
+
export type AuthCommandName = "login" | "logout";
|
|
28
|
+
type AuthCompletionSource = (command: AuthCommandName, argumentPrefix: string) => AutocompleteArgumentSuggestions | AutocompleteItem[] | null;
|
|
29
|
+
type ComposerPickerRegistry = ModelPickerRegistry & Pick<ProviderRegistry, "getConfigured" | "getDefault"> & Partial<Pick<ProviderRegistry, "getOAuthLoginKeys">>;
|
|
26
30
|
export declare function buildModelAutocompleteItems(groups: ModelProviderGroup[], argumentPrefix?: string, currentThinkingLevel?: ThinkingLevel): AutocompleteItem[];
|
|
27
31
|
export declare function buildProviderAutocompleteItems(registry: Pick<ProviderRegistry, "getConfigured" | "getModelConfig">, argumentPrefix?: string, currentProviderId?: string): AutocompleteItem[];
|
|
32
|
+
export interface AuthAccountState {
|
|
33
|
+
isSignedIn(providerId: string): boolean;
|
|
34
|
+
/** Session is bound to the legacy Grok external runtime. */
|
|
35
|
+
grokRuntimeActive?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Accounts for /login and /logout. Derived from the catalog so a new OAuth
|
|
39
|
+
* provider is offered without touching this list.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildAuthAutocompleteItems(command: AuthCommandName, argumentPrefix?: string, state?: AuthAccountState): AutocompleteItem[];
|
|
28
42
|
export declare function buildThemeAutocompleteItems(argumentPrefix?: string, detectedTheme?: ResolvedTheme): AutocompleteItem[];
|
|
29
43
|
/**
|
|
30
44
|
* Build the command surface in execution-priority order. The registry remains
|
|
31
45
|
* live so MCP prompts that connect after startup appear on the next keypress.
|
|
32
46
|
*/
|
|
33
|
-
export declare function buildComposerSlashCommands(commands: UnifiedCommand[], skills: SkillSummary[], uiMode?: TuiMode, modelCompletions?: ModelCompletionSource, providerCompletions?: ProviderCompletionSource, themeCompletions?: ThemeCompletionSource): TuiSlashCommand[];
|
|
47
|
+
export declare function buildComposerSlashCommands(commands: UnifiedCommand[], skills: SkillSummary[], uiMode?: TuiMode, modelCompletions?: ModelCompletionSource, providerCompletions?: ProviderCompletionSource, themeCompletions?: ThemeCompletionSource, authCompletions?: AuthCompletionSource): TuiSlashCommand[];
|
|
34
48
|
/** A live adapter around pi-tui's command/file completion implementation. */
|
|
35
49
|
export declare class ComposerAutocompleteProvider implements AutocompleteProvider {
|
|
36
50
|
private readonly sources;
|
|
@@ -48,6 +62,7 @@ export declare class ComposerAutocompleteProvider implements AutocompleteProvide
|
|
|
48
62
|
};
|
|
49
63
|
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
|
50
64
|
private delegate;
|
|
65
|
+
private getAuthCompletions;
|
|
51
66
|
private getThemeCompletions;
|
|
52
67
|
private getProviderCompletions;
|
|
53
68
|
private getModelCompletions;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { CombinedAutocompleteProvider, } from "@bubblebrain-ai/pi-tui";
|
|
2
2
|
import { BUILTIN_PROVIDERS, encodeModel, isUserVisibleProvider, } from "../provider-registry.js";
|
|
3
3
|
import { getAvailableThinkingLevels, isThinkingToggleModel, normalizeThinkingLevel, } from "../provider-transform.js";
|
|
4
|
+
import { GROK_SUBSCRIPTION_PROVIDER_ID, isGrokSubscriptionProviderId, } from "../external-runtime/grok-provider.js";
|
|
4
5
|
import { discoverModelProviderGroups, getVisibleModelProviders, localModelsForProvider, } from "./model-picker-data.js";
|
|
5
6
|
const MODEL_COMMAND_PREFIX = "/model ";
|
|
6
7
|
const REASONING_EFFORT_SEPARATOR = " --reasoning-effort ";
|
|
7
8
|
const PROVIDER_COMMAND_PREFIX = "/provider ";
|
|
8
9
|
const THEME_COMMAND_PREFIX = "/theme ";
|
|
10
|
+
const AUTH_COMMAND_PREFIX = {
|
|
11
|
+
login: "/login ",
|
|
12
|
+
logout: "/logout ",
|
|
13
|
+
};
|
|
9
14
|
const EFFORT_DESCRIPTIONS = {
|
|
10
15
|
off: "no reasoning effort",
|
|
11
16
|
minimal: "fastest reasoning",
|
|
@@ -160,6 +165,47 @@ export function buildProviderAutocompleteItems(registry, argumentPrefix = "", cu
|
|
|
160
165
|
}];
|
|
161
166
|
});
|
|
162
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* What picking a row will actually do. Provider-specific because the handlers
|
|
170
|
+
* differ: /login openai always runs a fresh OAuth flow, while /login grok
|
|
171
|
+
* reuses stored credentials and only opens the browser when they fail.
|
|
172
|
+
*/
|
|
173
|
+
function authActionHint(command, providerId, signedIn, grokRuntimeActive) {
|
|
174
|
+
const grok = isGrokSubscriptionProviderId(providerId);
|
|
175
|
+
if (command === "login") {
|
|
176
|
+
if (!signedIn)
|
|
177
|
+
return "opens the browser";
|
|
178
|
+
return grok ? "reuses the stored sign-in · /logout grok first to switch accounts" : "sign in again";
|
|
179
|
+
}
|
|
180
|
+
if (grok && grokRuntimeActive)
|
|
181
|
+
return "ends the active Grok session and starts a fresh one";
|
|
182
|
+
return signedIn ? "removes this device's credentials" : "nothing to remove";
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Accounts for /login and /logout. Derived from the catalog so a new OAuth
|
|
186
|
+
* provider is offered without touching this list.
|
|
187
|
+
*/
|
|
188
|
+
export function buildAuthAutocompleteItems(command, argumentPrefix = "", state = { isSignedIn: () => false }) {
|
|
189
|
+
const query = argumentPrefix.trim().toLowerCase();
|
|
190
|
+
return BUILTIN_PROVIDERS.flatMap((provider) => {
|
|
191
|
+
if (!provider.supportsOAuth || !isUserVisibleProvider(provider.id))
|
|
192
|
+
return [];
|
|
193
|
+
const grok = isGrokSubscriptionProviderId(provider.id);
|
|
194
|
+
const runtimeActive = grok && state.grokRuntimeActive === true;
|
|
195
|
+
const signedIn = state.isSignedIn(provider.id);
|
|
196
|
+
const status = signedIn ? "Signed in" : runtimeActive ? "Active session" : "Not signed in";
|
|
197
|
+
const hint = authActionHint(command, provider.id, signedIn, runtimeActive);
|
|
198
|
+
const description = `${provider.id} · ${status} · ${hint}`;
|
|
199
|
+
// Match the account only. The status/hint prose is shared by every row,
|
|
200
|
+
// so searching it would make "open" match Grok via "opens the browser".
|
|
201
|
+
// Include every id the handler accepts so a typed alias still matches.
|
|
202
|
+
const aliases = grok ? ` ${GROK_SUBSCRIPTION_PROVIDER_ID}` : "";
|
|
203
|
+
const searchable = `${provider.id} ${provider.name}${aliases}`.toLowerCase();
|
|
204
|
+
if (query && !searchable.includes(query))
|
|
205
|
+
return [];
|
|
206
|
+
return [{ value: provider.id, label: provider.name, description, submitOnSelect: true }];
|
|
207
|
+
});
|
|
208
|
+
}
|
|
163
209
|
export function buildThemeAutocompleteItems(argumentPrefix = "", detectedTheme = "dark") {
|
|
164
210
|
const query = argumentPrefix.trim().toLowerCase();
|
|
165
211
|
const candidates = [
|
|
@@ -178,7 +224,7 @@ export function buildThemeAutocompleteItems(argumentPrefix = "", detectedTheme =
|
|
|
178
224
|
* Build the command surface in execution-priority order. The registry remains
|
|
179
225
|
* live so MCP prompts that connect after startup appear on the next keypress.
|
|
180
226
|
*/
|
|
181
|
-
export function buildComposerSlashCommands(commands, skills, uiMode = "regular", modelCompletions, providerCompletions, themeCompletions) {
|
|
227
|
+
export function buildComposerSlashCommands(commands, skills, uiMode = "regular", modelCompletions, providerCompletions, themeCompletions, authCompletions) {
|
|
182
228
|
const result = new Map();
|
|
183
229
|
const add = (command) => {
|
|
184
230
|
if (!result.has(command.name))
|
|
@@ -237,6 +283,23 @@ export function buildComposerSlashCommands(commands, skills, uiMode = "regular",
|
|
|
237
283
|
getArgumentCompletions: themeCompletions,
|
|
238
284
|
});
|
|
239
285
|
}
|
|
286
|
+
else if ((command.name === "login" || command.name === "logout") && authCompletions) {
|
|
287
|
+
const authCommand = command.name;
|
|
288
|
+
add({
|
|
289
|
+
name: command.name,
|
|
290
|
+
description: command.description,
|
|
291
|
+
argumentHint: "<account>",
|
|
292
|
+
submitOnSelect: false,
|
|
293
|
+
argumentInputHint: {
|
|
294
|
+
prompt: "⌕ ",
|
|
295
|
+
placeholder: "Select account…",
|
|
296
|
+
valuePrefix: AUTH_COMMAND_PREFIX[authCommand],
|
|
297
|
+
},
|
|
298
|
+
// No keep-open-on-empty: an empty menu swallows Enter, and a typed id
|
|
299
|
+
// the list does not show must still reach the handler's own error.
|
|
300
|
+
getArgumentCompletions: (prefix) => authCompletions(authCommand, prefix),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
240
303
|
else {
|
|
241
304
|
add({ name: command.name, description: command.description });
|
|
242
305
|
}
|
|
@@ -274,7 +337,21 @@ export class ComposerAutocompleteProvider {
|
|
|
274
337
|
return this.delegate().shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
|
|
275
338
|
}
|
|
276
339
|
delegate() {
|
|
277
|
-
return new CombinedAutocompleteProvider(buildComposerSlashCommands(this.sources.commands(), this.sources.skills(), this.sources.uiMode?.() ?? "regular", this.sources.registry ? (prefix) => this.getModelCompletions(prefix) : undefined, this.sources.registry ? (prefix) => this.getProviderCompletions(prefix) : undefined, (prefix) => this.getThemeCompletions(prefix)), this.sources.cwd, this.sources.fdPath ?? null);
|
|
340
|
+
return new CombinedAutocompleteProvider(buildComposerSlashCommands(this.sources.commands(), this.sources.skills(), this.sources.uiMode?.() ?? "regular", this.sources.registry ? (prefix) => this.getModelCompletions(prefix) : undefined, this.sources.registry ? (prefix) => this.getProviderCompletions(prefix) : undefined, (prefix) => this.getThemeCompletions(prefix), (command, prefix) => this.getAuthCompletions(command, prefix)), this.sources.cwd, this.sources.fdPath ?? null);
|
|
341
|
+
}
|
|
342
|
+
getAuthCompletions(command, argumentPrefix) {
|
|
343
|
+
const registry = this.sources.registry;
|
|
344
|
+
return {
|
|
345
|
+
items: buildAuthAutocompleteItems(command, argumentPrefix, {
|
|
346
|
+
isSignedIn: (id) => (registry?.getOAuthLoginKeys?.(id).length ?? 0) > 0,
|
|
347
|
+
grokRuntimeActive: this.sources.grokRuntimeActive?.() ?? false,
|
|
348
|
+
}),
|
|
349
|
+
inputHint: {
|
|
350
|
+
prompt: "⌕ ",
|
|
351
|
+
placeholder: "Select account…",
|
|
352
|
+
valuePrefix: AUTH_COMMAND_PREFIX[command],
|
|
353
|
+
},
|
|
354
|
+
};
|
|
278
355
|
}
|
|
279
356
|
getThemeCompletions(argumentPrefix) {
|
|
280
357
|
const items = buildThemeAutocompleteItems(argumentPrefix, this.sources.detectedTheme?.() ?? "dark");
|
|
@@ -60,6 +60,7 @@ export declare class BubbleTuiController {
|
|
|
60
60
|
private readonly queue;
|
|
61
61
|
private readonly sessionTransition;
|
|
62
62
|
private readonly startedAtMs;
|
|
63
|
+
private turnStartedAt;
|
|
63
64
|
private readonly goalRuntime?;
|
|
64
65
|
private readonly taskRuntime?;
|
|
65
66
|
private readonly pendingTaskAnnouncements;
|
|
@@ -67,9 +68,6 @@ export declare class BubbleTuiController {
|
|
|
67
68
|
private transcript;
|
|
68
69
|
private runActive;
|
|
69
70
|
private runState;
|
|
70
|
-
/** Background subagent updates can outlive the provider turn that launched
|
|
71
|
-
* them. Keep their latest synthetic tool snapshot in the live trace. */
|
|
72
|
-
private readonly liveSubagentTools;
|
|
73
71
|
/** Provider-turn accumulators for true child-session inspection. */
|
|
74
72
|
private readonly childRuns;
|
|
75
73
|
private activeInputController;
|
|
@@ -81,7 +79,6 @@ export declare class BubbleTuiController {
|
|
|
81
79
|
private disposed;
|
|
82
80
|
constructor(deps: BubbleTuiControllerDeps);
|
|
83
81
|
private externalGeneration;
|
|
84
|
-
private liveSubagentVersion;
|
|
85
82
|
subscribe(listener: (version: number) => void): () => void;
|
|
86
83
|
getSnapshotVersion(): number;
|
|
87
84
|
getTranscript(): readonly DisplayMessage[];
|
|
@@ -89,6 +86,12 @@ export declare class BubbleTuiController {
|
|
|
89
86
|
getGoalIndicator(): string | undefined;
|
|
90
87
|
handleGoalCommand(input: string, cwd: string): void;
|
|
91
88
|
getSubagentGroups(): SubagentGroup[];
|
|
89
|
+
/**
|
|
90
|
+
* Start of the current (or, when idle, the latest) agent run. The tasks pane
|
|
91
|
+
* keeps finished work that was launched since then; wall clock on purpose,
|
|
92
|
+
* it is compared with subagent/task/workflow creation stamps.
|
|
93
|
+
*/
|
|
94
|
+
getTurnStartedAt(): number | undefined;
|
|
92
95
|
getWorkflows(): WorkflowRunSnapshot[];
|
|
93
96
|
getBackgroundTasks(): BackgroundTaskInfo[];
|
|
94
97
|
getChildTranscript(agentId: string): DisplayMessage[];
|
|
@@ -106,6 +109,8 @@ export declare class BubbleTuiController {
|
|
|
106
109
|
persistFinalTaskMarkers(): void;
|
|
107
110
|
private appendTaskMarker;
|
|
108
111
|
private announceTaskCompletion;
|
|
112
|
+
/** Pending completions whose launch row has settled since they arrived. */
|
|
113
|
+
private landPendingTaskAnnouncementsInLiveRound;
|
|
109
114
|
/** Move the newest live Bash Execute into the unified task manager. */
|
|
110
115
|
promoteActiveBash(): string | undefined;
|
|
111
116
|
/** Drop every transcript row (/clear). */
|