@bubblebrain-ai/bubble 0.0.48 → 0.0.50
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 +17 -0
- package/dist/agent/abort-errors.d.ts +4 -0
- package/dist/agent/abort-errors.js +30 -0
- package/dist/agent/provider-fingerprint.d.ts +6 -0
- package/dist/agent/provider-fingerprint.js +89 -0
- package/dist/agent/resident-history.d.ts +7 -0
- package/dist/agent/resident-history.js +61 -0
- package/dist/agent/transcript-helpers.d.ts +20 -0
- package/dist/agent/transcript-helpers.js +62 -0
- package/dist/agent/update-queue.d.ts +13 -0
- package/dist/agent/update-queue.js +69 -0
- package/dist/agent.d.ts +33 -16
- package/dist/agent.js +90 -300
- package/dist/external-runtime/grok-runtime.js +1 -1
- package/dist/feishu/agent-host/run-driver.js +1 -1
- package/dist/lsp/service.js +2 -2
- package/dist/model-catalog.d.ts +13 -0
- package/dist/model-catalog.js +52 -8
- package/dist/permissions/settings.d.ts +0 -1
- package/dist/permissions/settings.js +0 -2
- package/dist/provider-grok.d.ts +16 -18
- package/dist/provider-grok.js +28 -20
- package/dist/provider-registry.d.ts +9 -0
- package/dist/provider-registry.js +109 -6
- package/dist/provider-transform.js +7 -8
- package/dist/provider.js +11 -0
- package/dist/session-log.js +1 -0
- package/dist/slash-commands/commands.js +13 -9
- package/dist/tools/grep.js +1 -1
- package/dist/tui-ink/app-helpers.d.ts +43 -0
- package/dist/tui-ink/app-helpers.js +150 -0
- package/dist/tui-ink/app.d.ts +4 -11
- package/dist/tui-ink/app.js +104 -635
- package/dist/tui-ink/command-palette.d.ts +32 -0
- package/dist/tui-ink/command-palette.js +158 -0
- package/dist/tui-ink/display-history.d.ts +2 -0
- package/dist/tui-ink/display-reconstruct.d.ts +3 -0
- package/dist/tui-ink/display-reconstruct.js +82 -0
- package/dist/tui-ink/footer.d.ts +14 -3
- package/dist/tui-ink/footer.js +19 -5
- package/dist/tui-ink/rewind-picker.d.ts +8 -0
- package/dist/tui-ink/rewind-picker.js +102 -0
- package/dist/tui-ink/session-picker.js +1 -1
- package/dist/tui-ink/stats-panel.d.ts +11 -0
- package/dist/tui-ink/stats-panel.js +65 -0
- package/dist/tui-ink/waiting-indicator.d.ts +12 -0
- package/dist/tui-ink/waiting-indicator.js +129 -0
- package/dist/tui-ink/wordmark.d.ts +8 -0
- package/dist/tui-ink/wordmark.js +103 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The animated spinner row shown while a run is in flight: tool-targeted or
|
|
4
|
+
* generic phrases, streaming token counter, and steer/queue hints.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
import { Box, Text } from "ink";
|
|
8
|
+
import { useTheme } from "./theme.js";
|
|
9
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10
|
+
const GENERIC_PHRASES = [
|
|
11
|
+
"mapping the workspace",
|
|
12
|
+
"reading the room",
|
|
13
|
+
"following the threads",
|
|
14
|
+
"connecting the pieces",
|
|
15
|
+
"sorting the context",
|
|
16
|
+
"scanning the structure",
|
|
17
|
+
"shaping the next step",
|
|
18
|
+
"gathering signal",
|
|
19
|
+
"checking the edges",
|
|
20
|
+
"lining up the answer",
|
|
21
|
+
"tracing the flow",
|
|
22
|
+
"building the picture",
|
|
23
|
+
"walking the graph",
|
|
24
|
+
"collecting the clues",
|
|
25
|
+
"framing the problem",
|
|
26
|
+
"locating the source",
|
|
27
|
+
"resolving the shape",
|
|
28
|
+
"untangling the state",
|
|
29
|
+
"comparing the paths",
|
|
30
|
+
"narrowing the target",
|
|
31
|
+
"tracking the changes",
|
|
32
|
+
"reading the patterns",
|
|
33
|
+
"weighing the options",
|
|
34
|
+
"assembling the context",
|
|
35
|
+
"following the signal",
|
|
36
|
+
"checking the assumptions",
|
|
37
|
+
"aligning the details",
|
|
38
|
+
"testing the shape",
|
|
39
|
+
"pulling the thread",
|
|
40
|
+
"cleaning the edges",
|
|
41
|
+
"refining the draft",
|
|
42
|
+
"verifying the route",
|
|
43
|
+
"making sense of it",
|
|
44
|
+
"looking for leverage",
|
|
45
|
+
"stitching the answer",
|
|
46
|
+
"holding the thread",
|
|
47
|
+
"distilling the noise",
|
|
48
|
+
"finding the seam",
|
|
49
|
+
"reading between the lines",
|
|
50
|
+
"preparing the response",
|
|
51
|
+
];
|
|
52
|
+
const TOOL_TARGET_PHRASES = {
|
|
53
|
+
read: "reading files",
|
|
54
|
+
write: "writing changes",
|
|
55
|
+
edit: "patching files",
|
|
56
|
+
grep: "searching the codebase",
|
|
57
|
+
glob: "scanning paths",
|
|
58
|
+
ls: "listing directories",
|
|
59
|
+
bash: "running command",
|
|
60
|
+
web_search: "searching the web",
|
|
61
|
+
web_fetch: "fetching a page",
|
|
62
|
+
task: "spawning subagent",
|
|
63
|
+
};
|
|
64
|
+
function formatTokensApprox(chars) {
|
|
65
|
+
const tokens = Math.max(0, Math.round(chars / 4));
|
|
66
|
+
if (tokens < 1000)
|
|
67
|
+
return `${tokens}`;
|
|
68
|
+
if (tokens < 10000)
|
|
69
|
+
return `${(tokens / 1000).toFixed(1)}k`;
|
|
70
|
+
return `${Math.round(tokens / 1000)}k`;
|
|
71
|
+
}
|
|
72
|
+
export function WaitingIndicator({ tools, hasStreamingText, hasStreamingReasoning, streamedChars, nowTick, pendingSteerCount = 0, queuedCount = 0, }) {
|
|
73
|
+
void nowTick;
|
|
74
|
+
const theme = useTheme();
|
|
75
|
+
const [frameIndex, setFrameIndex] = useState(0);
|
|
76
|
+
const [idlePhrase, setIdlePhrase] = useState(() => GENERIC_PHRASES[0]);
|
|
77
|
+
// Frame timer is independent of the agent state — keeps animation smooth.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const t = setInterval(() => {
|
|
80
|
+
setFrameIndex((i) => (i + 1) % SPINNER_FRAMES.length);
|
|
81
|
+
}, 100);
|
|
82
|
+
return () => clearInterval(t);
|
|
83
|
+
}, []);
|
|
84
|
+
// Determine state: active tool > streaming text > streaming reasoning > idle
|
|
85
|
+
const activeTool = [...tools].reverse().find((t) => !t.result);
|
|
86
|
+
const state = activeTool
|
|
87
|
+
? "tool"
|
|
88
|
+
: hasStreamingText
|
|
89
|
+
? "text"
|
|
90
|
+
: hasStreamingReasoning
|
|
91
|
+
? "reasoning"
|
|
92
|
+
: "idle";
|
|
93
|
+
// Rotate idle phrases on a slower cadence; only matters in the idle state.
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (state !== "idle")
|
|
96
|
+
return;
|
|
97
|
+
const t = setInterval(() => {
|
|
98
|
+
setIdlePhrase((current) => {
|
|
99
|
+
const candidates = GENERIC_PHRASES.filter((item) => item !== current);
|
|
100
|
+
return candidates[Math.floor(Math.random() * candidates.length)] || current;
|
|
101
|
+
});
|
|
102
|
+
}, 1500);
|
|
103
|
+
return () => clearInterval(t);
|
|
104
|
+
}, [state]);
|
|
105
|
+
let phrase;
|
|
106
|
+
if (state === "tool" && activeTool) {
|
|
107
|
+
phrase =
|
|
108
|
+
TOOL_TARGET_PHRASES[activeTool.name] || `running ${activeTool.name}`;
|
|
109
|
+
}
|
|
110
|
+
else if (state === "text") {
|
|
111
|
+
phrase = "writing the response";
|
|
112
|
+
}
|
|
113
|
+
else if (state === "reasoning") {
|
|
114
|
+
phrase = "working through the request";
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
phrase = idlePhrase;
|
|
118
|
+
}
|
|
119
|
+
const tokenText = streamedChars > 0 ? `↓${formatTokensApprox(streamedChars)} tok` : "";
|
|
120
|
+
const hintParts = [];
|
|
121
|
+
if (tokenText)
|
|
122
|
+
hintParts.push(tokenText);
|
|
123
|
+
if (pendingSteerCount > 0)
|
|
124
|
+
hintParts.push(`${pendingSteerCount} pending steer${pendingSteerCount === 1 ? "" : "s"}`);
|
|
125
|
+
if (queuedCount > 0)
|
|
126
|
+
hintParts.push(`${queuedCount} queued`);
|
|
127
|
+
hintParts.push("enter steer", "tab queue", "esc stop");
|
|
128
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: SPINNER_FRAMES[frameIndex] }), _jsxs(Text, { color: theme.muted, children: [" ", phrase, " "] }), _jsxs(Text, { color: theme.muted, dimColor: true, children: ["(", hintParts.join(" · "), ")"] })] }));
|
|
129
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
/**
|
|
4
|
+
* Retro terminal pixel-art wordmark: "BUBBLE CODE" rendered in a 5×7 block
|
|
5
|
+
* font with a light-blue front face and a dark-blue offset shadow (extrusion),
|
|
6
|
+
* plus a pale highlight along the top edge. Purely decorative; keeps the
|
|
7
|
+
* terminal background.
|
|
8
|
+
*/
|
|
9
|
+
const LETTER_W = 5;
|
|
10
|
+
const LETTER_H = 7;
|
|
11
|
+
const LETTER_GAP = 2;
|
|
12
|
+
const WORD_GAP = 4;
|
|
13
|
+
const SHADOW_DX = 1;
|
|
14
|
+
const SHADOW_DY = 1;
|
|
15
|
+
const FRONT = "#E0E6ED";
|
|
16
|
+
const SHADOW = "#3A4A5A";
|
|
17
|
+
const HIGHLIGHT = "#FFFFFF";
|
|
18
|
+
// 5×7 bold block pixel font ("1" = filled, "0" = empty).
|
|
19
|
+
const FONT = {
|
|
20
|
+
B: ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
|
|
21
|
+
U: ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
|
|
22
|
+
L: ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
|
|
23
|
+
E: ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
|
|
24
|
+
C: ["01111", "10000", "10000", "10000", "10000", "10000", "01111"],
|
|
25
|
+
O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
|
|
26
|
+
D: ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
|
|
27
|
+
" ": ["00000", "00000", "00000", "00000", "00000", "00000", "00000"],
|
|
28
|
+
};
|
|
29
|
+
const WORD = "BUBBLE CODE";
|
|
30
|
+
function letterOffsets() {
|
|
31
|
+
const offsets = [];
|
|
32
|
+
let x = 0;
|
|
33
|
+
for (const ch of WORD) {
|
|
34
|
+
offsets.push({ ch, x });
|
|
35
|
+
x += ch === " " ? WORD_GAP : LETTER_W + LETTER_GAP;
|
|
36
|
+
}
|
|
37
|
+
return offsets;
|
|
38
|
+
}
|
|
39
|
+
export function buildBubbleCodeGrid() {
|
|
40
|
+
const offsets = letterOffsets();
|
|
41
|
+
const last = offsets[offsets.length - 1];
|
|
42
|
+
const width = last.x + LETTER_W + SHADOW_DX;
|
|
43
|
+
const height = LETTER_H + SHADOW_DY;
|
|
44
|
+
const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => ({ char: " ", color: null })));
|
|
45
|
+
const set = (x, y, color) => {
|
|
46
|
+
if (x >= 0 && x < width && y >= 0 && y < height) {
|
|
47
|
+
grid[y][x] = { char: "█", color };
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const forEachPixel = (cb) => {
|
|
51
|
+
for (const { ch, x } of offsets) {
|
|
52
|
+
const glyph = FONT[ch] ?? FONT[" "];
|
|
53
|
+
for (let r = 0; r < LETTER_H; r++) {
|
|
54
|
+
for (let c = 0; c < LETTER_W; c++) {
|
|
55
|
+
if (glyph[r][c] === "1")
|
|
56
|
+
cb(x + c, r);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Extrusion (dark blue), offset one cell right + one down.
|
|
62
|
+
forEachPixel((x, y) => set(x + SHADOW_DX, y + SHADOW_DY, SHADOW));
|
|
63
|
+
// Front face (light blue).
|
|
64
|
+
forEachPixel((x, y) => set(x, y, FRONT));
|
|
65
|
+
// Top-edge highlight (pale blue) for a bit of shine.
|
|
66
|
+
for (const { ch, x } of offsets) {
|
|
67
|
+
const glyph = FONT[ch] ?? FONT[" "];
|
|
68
|
+
for (let c = 0; c < LETTER_W; c++) {
|
|
69
|
+
if (glyph[0][c] === "1")
|
|
70
|
+
set(x + c, 0, HIGHLIGHT);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return grid;
|
|
74
|
+
}
|
|
75
|
+
function mergeRow(row) {
|
|
76
|
+
const segments = [];
|
|
77
|
+
let current = null;
|
|
78
|
+
for (const cell of row) {
|
|
79
|
+
if (current && current.color === cell.color) {
|
|
80
|
+
current.text += cell.char;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
current = { text: cell.char, color: cell.color };
|
|
84
|
+
segments.push(current);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Drop trailing empty cells so the wordmark has no trailing whitespace.
|
|
88
|
+
while (segments.length > 0 && segments[segments.length - 1].color === null) {
|
|
89
|
+
segments.pop();
|
|
90
|
+
}
|
|
91
|
+
return segments;
|
|
92
|
+
}
|
|
93
|
+
export function BubbleCodeWordmark({ width }) {
|
|
94
|
+
const grid = buildBubbleCodeGrid();
|
|
95
|
+
const contentWidth = grid[0]?.length ?? 0;
|
|
96
|
+
const leftPad = width && width > contentWidth
|
|
97
|
+
? Math.floor((width - contentWidth) / 2)
|
|
98
|
+
: 0;
|
|
99
|
+
return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: leftPad, children: grid.map((row, rowIndex) => {
|
|
100
|
+
const segments = mergeRow(row);
|
|
101
|
+
return (_jsx(Box, { flexDirection: "row", flexShrink: 0, children: segments.map((segment, segmentIndex) => (_jsx(Text, { color: segment.color ?? undefined, children: segment.text }, segmentIndex))) }, rowIndex));
|
|
102
|
+
}) }));
|
|
103
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface AssistantMessage {
|
|
|
47
47
|
providerId?: string;
|
|
48
48
|
modelId?: string;
|
|
49
49
|
usage?: TokenUsage;
|
|
50
|
+
/** Backend configuration fingerprint reported by the model provider. */
|
|
51
|
+
systemFingerprint?: string;
|
|
50
52
|
error?: {
|
|
51
53
|
name: string;
|
|
52
54
|
message: string;
|
|
@@ -310,6 +312,9 @@ export type StreamChunk = {
|
|
|
310
312
|
} | {
|
|
311
313
|
type: "usage";
|
|
312
314
|
usage: TokenUsage;
|
|
315
|
+
} | {
|
|
316
|
+
type: "response_metadata";
|
|
317
|
+
systemFingerprint: string;
|
|
313
318
|
} | {
|
|
314
319
|
type: "done";
|
|
315
320
|
};
|
|
@@ -430,6 +435,7 @@ export type AgentEvent = {
|
|
|
430
435
|
type: "turn_end";
|
|
431
436
|
usage?: TokenUsage;
|
|
432
437
|
cost?: UsageCost;
|
|
438
|
+
systemFingerprint?: string;
|
|
433
439
|
willContinue?: boolean;
|
|
434
440
|
} | {
|
|
435
441
|
type: "context_recovered";
|