@bubblebrain-ai/bubble 0.0.20 → 0.0.22
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/dist/agent/abort-errors.d.ts +14 -0
- package/dist/agent/abort-errors.js +21 -0
- package/dist/agent/budget-ledger.d.ts +41 -0
- package/dist/agent/budget-ledger.js +64 -0
- package/dist/agent/child-runner.d.ts +55 -0
- package/dist/agent/child-runner.js +312 -0
- package/dist/agent/profiles.d.ts +8 -0
- package/dist/agent/profiles.js +27 -5
- package/dist/agent/result-integrator.d.ts +22 -0
- package/dist/agent/result-integrator.js +50 -0
- package/dist/agent/subagent-control.d.ts +31 -0
- package/dist/agent/subagent-control.js +27 -0
- package/dist/agent/subagent-lifecycle-reminder.js +11 -2
- package/dist/agent/subagent-scheduler.d.ts +95 -0
- package/dist/agent/subagent-scheduler.js +256 -0
- package/dist/agent/subagent-store.d.ts +41 -0
- package/dist/agent/subagent-store.js +149 -0
- package/dist/agent/subagent-summary.d.ts +30 -0
- package/dist/agent/subagent-summary.js +74 -0
- package/dist/agent/worktree.d.ts +29 -0
- package/dist/agent/worktree.js +73 -0
- package/dist/agent.d.ts +64 -5
- package/dist/agent.js +365 -288
- package/dist/approval/controller.js +9 -1
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +17 -1
- package/dist/checkpoints.d.ts +57 -0
- package/dist/checkpoints.js +0 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +17 -0
- package/dist/feishu/agent-host/approval-card.js +9 -0
- package/dist/feishu/agent-host/run-driver.js +2 -0
- package/dist/main.js +88 -13
- package/dist/network/errors.d.ts +28 -0
- package/dist/network/errors.js +24 -0
- package/dist/orchestrator/default-hooks.js +5 -1
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.d.ts +14 -0
- package/dist/prompt/delegation.js +64 -0
- package/dist/prompt/task-reminders.d.ts +5 -1
- package/dist/prompt/task-reminders.js +10 -2
- package/dist/provider-anthropic.js +23 -0
- package/dist/provider.js +23 -3
- package/dist/session.d.ts +31 -0
- package/dist/session.js +69 -0
- package/dist/slash-commands/commands.js +109 -2
- package/dist/slash-commands/types.d.ts +6 -0
- package/dist/tools/agent-lifecycle.d.ts +29 -3
- package/dist/tools/agent-lifecycle.js +394 -40
- package/dist/tools/bash.js +4 -0
- package/dist/tools/child-tools.d.ts +31 -0
- package/dist/tools/child-tools.js +106 -0
- package/dist/tools/edit.d.ts +2 -1
- package/dist/tools/edit.js +2 -1
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +3 -3
- package/dist/tools/write.d.ts +2 -1
- package/dist/tools/write.js +2 -1
- package/dist/tui/image-paste.d.ts +18 -0
- package/dist/tui/image-paste.js +60 -0
- package/dist/tui/run.d.ts +11 -1
- package/dist/tui/run.js +399 -71
- package/dist/tui/session-picker-data.d.ts +18 -0
- package/dist/tui/session-picker-data.js +21 -0
- package/dist/tui/trace-groups.d.ts +16 -0
- package/dist/tui/trace-groups.js +42 -1
- package/dist/tui/transcript-scroll.d.ts +25 -0
- package/dist/tui/transcript-scroll.js +20 -0
- package/dist/tui/wordmark.d.ts +2 -0
- package/dist/tui/wordmark.js +31 -4
- package/dist/tui-ink/app.d.ts +4 -1
- package/dist/tui-ink/app.js +301 -247
- package/dist/tui-ink/approval/approval-dialog.js +10 -0
- package/dist/tui-ink/display-history.d.ts +16 -1
- package/dist/tui-ink/display-history.js +50 -21
- package/dist/tui-ink/footer.d.ts +6 -12
- package/dist/tui-ink/footer.js +10 -29
- package/dist/tui-ink/image-paste.d.ts +59 -0
- package/dist/tui-ink/image-paste.js +277 -0
- package/dist/tui-ink/input-box.d.ts +26 -1
- package/dist/tui-ink/input-box.js +171 -41
- package/dist/tui-ink/message-list.d.ts +1 -1
- package/dist/tui-ink/message-list.js +46 -29
- package/dist/tui-ink/run.d.ts +7 -2
- package/dist/tui-ink/run.js +73 -23
- package/dist/tui-ink/terminal-mouse.d.ts +1 -0
- package/dist/tui-ink/terminal-mouse.js +4 -0
- package/dist/tui-ink/trace-groups.d.ts +16 -0
- package/dist/tui-ink/trace-groups.js +50 -2
- package/dist/tui-ink/transcript-viewport-math.d.ts +11 -0
- package/dist/tui-ink/transcript-viewport-math.js +17 -0
- package/dist/tui-ink/transcript-viewport.d.ts +24 -0
- package/dist/tui-ink/transcript-viewport.js +83 -0
- package/dist/tui-ink/welcome.d.ts +9 -7
- package/dist/tui-ink/welcome.js +7 -33
- package/dist/tui-opentui/approval/approval-dialog.js +10 -0
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SessionSummary } from "../session.js";
|
|
2
|
+
export interface SessionPickerEntry {
|
|
3
|
+
/** Session title (or first-message preview when untitled). */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Message count, e.g. "12 messages". */
|
|
6
|
+
detail: string;
|
|
7
|
+
/** Absolute path to the session .jsonl file. */
|
|
8
|
+
value: string;
|
|
9
|
+
/** "current" for the active session, otherwise a relative timestamp. */
|
|
10
|
+
footer: string;
|
|
11
|
+
/** "●" marks the active session. */
|
|
12
|
+
gutter?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildSessionPickerEntries(summaries: SessionSummary[], activeFile: string | undefined, now?: number): SessionPickerEntry[];
|
|
15
|
+
/** Default selection: the most recent session that is not the active one. */
|
|
16
|
+
export declare function preferredSessionPickerIndex(entries: Array<{
|
|
17
|
+
gutter?: string;
|
|
18
|
+
}>): number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { normalizeSingleLine, truncateVisual } from "../text-display.js";
|
|
2
|
+
import { formatRelativeTime } from "./recent-activity.js";
|
|
3
|
+
const SESSION_PICKER_LABEL_MAX_WIDTH = 72;
|
|
4
|
+
export function buildSessionPickerEntries(summaries, activeFile, now = Date.now()) {
|
|
5
|
+
return summaries.map((summary) => {
|
|
6
|
+
const isCurrent = summary.file === activeFile;
|
|
7
|
+
const label = truncateVisual(normalizeSingleLine(summary.title || summary.preview || summary.name), SESSION_PICKER_LABEL_MAX_WIDTH) || summary.name;
|
|
8
|
+
return {
|
|
9
|
+
label,
|
|
10
|
+
detail: `${summary.messageCount} message${summary.messageCount === 1 ? "" : "s"}`,
|
|
11
|
+
value: summary.file,
|
|
12
|
+
footer: isCurrent ? "current" : formatRelativeTime(summary.mtime, now),
|
|
13
|
+
gutter: isCurrent ? "●" : undefined,
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Default selection: the most recent session that is not the active one. */
|
|
18
|
+
export function preferredSessionPickerIndex(entries) {
|
|
19
|
+
const firstOther = entries.findIndex((entry) => entry.gutter !== "●");
|
|
20
|
+
return firstOther >= 0 ? firstOther : 0;
|
|
21
|
+
}
|
|
@@ -7,6 +7,10 @@ export interface TraceGroup {
|
|
|
7
7
|
count?: number;
|
|
8
8
|
noun?: string;
|
|
9
9
|
command?: string;
|
|
10
|
+
/** Model-provided one-line summary of what the command does (bash `description` arg). */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Original command split into lines, line breaks preserved (execute groups only). */
|
|
13
|
+
commandLines?: string[];
|
|
10
14
|
items: string[];
|
|
11
15
|
previewLines: string[];
|
|
12
16
|
errorLines: string[];
|
|
@@ -25,3 +29,15 @@ export declare function buildTraceGroups(toolCalls: DisplayToolCall[], options?:
|
|
|
25
29
|
export declare function formatTracePath(value: unknown, homeDir?: string): string;
|
|
26
30
|
export declare function formatElapsed(startedAt: number | undefined, now?: number): string | null;
|
|
27
31
|
export declare function traceGroupLabel(group: TraceGroup): string;
|
|
32
|
+
/**
|
|
33
|
+
* An execute command is shown inline in the header only when nothing is lost:
|
|
34
|
+
* no description competing for the slot, a single logical line, and it fits
|
|
35
|
+
* the width budget. Otherwise the full command renders as a wrapped block
|
|
36
|
+
* below the header — commands are never clipped mid-line.
|
|
37
|
+
*/
|
|
38
|
+
export declare function shouldInlineExecuteCommand(group: TraceGroup, widthBudget: number): boolean;
|
|
39
|
+
/** Visible command-block lines for compact rendering, capped at `maxLines`. */
|
|
40
|
+
export declare function executeCommandBlock(group: TraceGroup, maxLines: number): {
|
|
41
|
+
lines: string[];
|
|
42
|
+
omitted: number;
|
|
43
|
+
};
|
package/dist/tui/trace-groups.js
CHANGED
|
@@ -65,12 +65,36 @@ export function formatElapsed(startedAt, now = Date.now()) {
|
|
|
65
65
|
return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
|
|
66
66
|
}
|
|
67
67
|
export function traceGroupLabel(group) {
|
|
68
|
+
if (group.description)
|
|
69
|
+
return `${group.title} ${group.description}`;
|
|
68
70
|
if (group.command)
|
|
69
71
|
return `${group.title} ${group.command}`;
|
|
70
72
|
if (group.count !== undefined && group.noun)
|
|
71
73
|
return `${group.title} ${group.count} ${group.noun}`;
|
|
72
74
|
return group.title;
|
|
73
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* An execute command is shown inline in the header only when nothing is lost:
|
|
78
|
+
* no description competing for the slot, a single logical line, and it fits
|
|
79
|
+
* the width budget. Otherwise the full command renders as a wrapped block
|
|
80
|
+
* below the header — commands are never clipped mid-line.
|
|
81
|
+
*/
|
|
82
|
+
export function shouldInlineExecuteCommand(group, widthBudget) {
|
|
83
|
+
if (group.kind !== "execute" || !group.command)
|
|
84
|
+
return false;
|
|
85
|
+
if (group.description)
|
|
86
|
+
return false;
|
|
87
|
+
const lines = group.commandLines ?? [];
|
|
88
|
+
if (lines.length > 1)
|
|
89
|
+
return false;
|
|
90
|
+
return group.command.length <= widthBudget;
|
|
91
|
+
}
|
|
92
|
+
/** Visible command-block lines for compact rendering, capped at `maxLines`. */
|
|
93
|
+
export function executeCommandBlock(group, maxLines) {
|
|
94
|
+
const lines = group.commandLines ?? [];
|
|
95
|
+
const shown = lines.slice(0, maxLines);
|
|
96
|
+
return { lines: shown, omitted: Math.max(0, lines.length - shown.length) };
|
|
97
|
+
}
|
|
74
98
|
function classifyTool(toolCall) {
|
|
75
99
|
if (toolCall.metadata?.kind === "subagent") {
|
|
76
100
|
return { kind: "subagent", title: "Subagents", bucketKey: `subagent:${toolCall.id}`, groupable: false };
|
|
@@ -244,11 +268,15 @@ function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError
|
|
|
244
268
|
function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasError, errorCount) {
|
|
245
269
|
const lines = resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
|
|
246
270
|
const { shown, omitted } = take(lines, options.maxPreviewLines);
|
|
271
|
+
const rawCommand = String(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments) ?? "");
|
|
272
|
+
const description = String(tool.args.description ?? "").trim() || undefined;
|
|
247
273
|
return {
|
|
248
274
|
kind: "execute",
|
|
249
275
|
title: classifier.title,
|
|
250
276
|
raw: [tool],
|
|
251
|
-
command: normalizeCommand(
|
|
277
|
+
command: normalizeCommand(rawCommand),
|
|
278
|
+
description,
|
|
279
|
+
commandLines: commandLinesOf(rawCommand),
|
|
252
280
|
items: [],
|
|
253
281
|
previewLines: shown,
|
|
254
282
|
errorLines: [],
|
|
@@ -400,6 +428,19 @@ function normalizeCommand(value) {
|
|
|
400
428
|
const command = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
401
429
|
return command;
|
|
402
430
|
}
|
|
431
|
+
// Preserves the command's own line structure (heredocs, && chains the model
|
|
432
|
+
// formatted across lines); only trims trailing whitespace and outer blank lines.
|
|
433
|
+
function commandLinesOf(rawCommand) {
|
|
434
|
+
const lines = rawCommand
|
|
435
|
+
.replace(/\r\n/g, "\n")
|
|
436
|
+
.split("\n")
|
|
437
|
+
.map((line) => line.trimEnd());
|
|
438
|
+
while (lines.length > 0 && lines[0].trim() === "")
|
|
439
|
+
lines.shift();
|
|
440
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
441
|
+
lines.pop();
|
|
442
|
+
return lines;
|
|
443
|
+
}
|
|
403
444
|
function commandFromRawArguments(rawArguments) {
|
|
404
445
|
if (!rawArguments)
|
|
405
446
|
return "";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript scroll-follow policy.
|
|
3
|
+
*
|
|
4
|
+
* The transcript snaps to the bottom ("follows") while the user is at the
|
|
5
|
+
* bottom, and stays put while they read older history. Two events override a
|
|
6
|
+
* scrolled-up position and re-engage following:
|
|
7
|
+
* - the user sends a message (explicit intent to watch the newest turn)
|
|
8
|
+
* - an approval prompt appears (requires the user's attention)
|
|
9
|
+
*
|
|
10
|
+
* Those renders set `forcePending`, which must survive until the deferred
|
|
11
|
+
* scroll actually runs: streaming redraws in the interim recompute the follow
|
|
12
|
+
* flag from the (still unscrolled) position and would otherwise cancel the
|
|
13
|
+
* snap. A user mouse scroll clears the pending force — their latest gesture
|
|
14
|
+
* always wins.
|
|
15
|
+
*/
|
|
16
|
+
export interface TranscriptScrollState {
|
|
17
|
+
/** A forceFollow render is waiting for its deferred scroll to execute. */
|
|
18
|
+
forcePending: boolean;
|
|
19
|
+
/** The viewport was at the bottom when the update was scheduled. */
|
|
20
|
+
shouldFollow: boolean;
|
|
21
|
+
/** Live follow flag, recomputed from the viewport position on each render. */
|
|
22
|
+
following: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type TranscriptScrollAction = "scroll-bottom" | "sync-position";
|
|
25
|
+
export declare function resolveTranscriptScroll(state: TranscriptScrollState): TranscriptScrollAction;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript scroll-follow policy.
|
|
3
|
+
*
|
|
4
|
+
* The transcript snaps to the bottom ("follows") while the user is at the
|
|
5
|
+
* bottom, and stays put while they read older history. Two events override a
|
|
6
|
+
* scrolled-up position and re-engage following:
|
|
7
|
+
* - the user sends a message (explicit intent to watch the newest turn)
|
|
8
|
+
* - an approval prompt appears (requires the user's attention)
|
|
9
|
+
*
|
|
10
|
+
* Those renders set `forcePending`, which must survive until the deferred
|
|
11
|
+
* scroll actually runs: streaming redraws in the interim recompute the follow
|
|
12
|
+
* flag from the (still unscrolled) position and would otherwise cancel the
|
|
13
|
+
* snap. A user mouse scroll clears the pending force — their latest gesture
|
|
14
|
+
* always wins.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveTranscriptScroll(state) {
|
|
17
|
+
if (state.forcePending)
|
|
18
|
+
return "scroll-bottom";
|
|
19
|
+
return state.shouldFollow && state.following ? "scroll-bottom" : "sync-position";
|
|
20
|
+
}
|
package/dist/tui/wordmark.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface BubbleWordmarkLine {
|
|
|
8
8
|
tone?: BubbleWordmarkTone;
|
|
9
9
|
segments?: BubbleWordmarkSegment[];
|
|
10
10
|
}
|
|
11
|
+
export declare const BUBBLE_CAT: BubbleWordmarkLine[];
|
|
12
|
+
export declare const BUBBLE_CAT_LARGE: BubbleWordmarkLine[];
|
|
11
13
|
export declare const BUBBLE_WORDMARK: BubbleWordmarkLine[];
|
|
12
14
|
export declare const BUBBLE_WORDMARK_LARGE: BubbleWordmarkLine[];
|
|
13
15
|
export declare const BUBBLE_COMPACT_WORDMARK: BubbleWordmarkLine[];
|
package/dist/tui/wordmark.js
CHANGED
|
@@ -22,6 +22,27 @@ const LOWER_B = {
|
|
|
22
22
|
" ",
|
|
23
23
|
],
|
|
24
24
|
};
|
|
25
|
+
// Pixel cat mascot, drawn on the same half-block pixel grid as the letters:
|
|
26
|
+
// pointy ears, 2x2-pixel eyes, tiny mouth, round chin (10x14 pixels). It is
|
|
27
|
+
// stacked above the wordmark (icon-over-name lockup) rather than inlined, so
|
|
28
|
+
// its solid fill doesn't compete with the thin letter strokes.
|
|
29
|
+
const CAT_LINES = [
|
|
30
|
+
" █▄ ▄█ ",
|
|
31
|
+
" ███▄▄███ ",
|
|
32
|
+
"██████████",
|
|
33
|
+
"█ ████ █",
|
|
34
|
+
"████▀▀████",
|
|
35
|
+
"██████████",
|
|
36
|
+
" ▀██████▀ ",
|
|
37
|
+
];
|
|
38
|
+
export const BUBBLE_CAT = CAT_LINES.map((text) => ({
|
|
39
|
+
text,
|
|
40
|
+
tone: "brand",
|
|
41
|
+
}));
|
|
42
|
+
export const BUBBLE_CAT_LARGE = CAT_LINES.map((text) => ({
|
|
43
|
+
text: text.split("").map((ch) => ch + ch).join(""),
|
|
44
|
+
tone: "brand",
|
|
45
|
+
}));
|
|
25
46
|
const GLYPHS = {
|
|
26
47
|
u: {
|
|
27
48
|
tone: "ink",
|
|
@@ -172,10 +193,16 @@ export function bubbleWordmarkLineText(line) {
|
|
|
172
193
|
export function bubbleWordmarkMaxWidth(lines = BUBBLE_WORDMARK) {
|
|
173
194
|
return Math.max(...lines.map((line) => bubbleWordmarkLineText(line).length));
|
|
174
195
|
}
|
|
196
|
+
const LOGO_GAP = { text: "", tone: "caption" };
|
|
197
|
+
// Icon-over-name lockup: pixel cat centered above the wordmark. Both render
|
|
198
|
+
// sites center every line independently, which is what stacks the cat over
|
|
199
|
+
// the text without any per-line padding here.
|
|
175
200
|
export function bubbleWordmarkForWidth(width) {
|
|
176
|
-
if (width >= bubbleWordmarkMaxWidth(BUBBLE_WORDMARK_LARGE) + 4)
|
|
177
|
-
return BUBBLE_WORDMARK_LARGE;
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
if (width >= bubbleWordmarkMaxWidth(BUBBLE_WORDMARK_LARGE) + 4) {
|
|
202
|
+
return [...BUBBLE_CAT_LARGE, LOGO_GAP, ...BUBBLE_WORDMARK_LARGE];
|
|
203
|
+
}
|
|
204
|
+
if (width >= bubbleWordmarkMaxWidth() + 4) {
|
|
205
|
+
return [...BUBBLE_CAT, LOGO_GAP, ...BUBBLE_WORDMARK];
|
|
206
|
+
}
|
|
180
207
|
return BUBBLE_COMPACT_WORDMARK;
|
|
181
208
|
}
|
package/dist/tui-ink/app.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { McpManager } from "../mcp/manager.js";
|
|
|
12
12
|
import type { LspService } from "../lsp/index.js";
|
|
13
13
|
import type { QuestionController } from "../question/index.js";
|
|
14
14
|
import type { MemoryScope } from "../memory/index.js";
|
|
15
|
+
import type { ExternalHookController } from "../hooks/controller.js";
|
|
15
16
|
export interface PlanHandlerRef {
|
|
16
17
|
current?: (plan: string) => Promise<PlanDecision>;
|
|
17
18
|
}
|
|
@@ -42,11 +43,13 @@ interface AppProps {
|
|
|
42
43
|
runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
|
|
43
44
|
/** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
|
|
44
45
|
bypassEnabled?: boolean;
|
|
46
|
+
updateNotice?: string;
|
|
47
|
+
hookController?: ExternalHookController;
|
|
45
48
|
onExit?: (summary: ExitSummary) => void;
|
|
46
49
|
}
|
|
47
50
|
export interface ExitSummary {
|
|
48
51
|
/** Wall-clock duration of the session, in milliseconds. */
|
|
49
52
|
wallMs: number;
|
|
50
53
|
}
|
|
51
|
-
export declare function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export declare function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, updateNotice, hookController, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
52
55
|
export {};
|