@bubblebrain-ai/bubble 0.0.23 → 0.0.25
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 +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +22 -6
- package/dist/goal/command.d.ts +20 -0
- package/dist/goal/command.js +71 -0
- package/dist/goal/engine.d.ts +33 -0
- package/dist/goal/engine.js +65 -0
- package/dist/goal/format.d.ts +18 -0
- package/dist/goal/format.js +112 -0
- package/dist/goal/prompts.d.ts +13 -0
- package/dist/goal/prompts.js +84 -0
- package/dist/goal/store.d.ts +64 -0
- package/dist/goal/store.js +174 -0
- package/dist/goal/tools.d.ts +10 -0
- package/dist/goal/tools.js +70 -0
- package/dist/goal/usage.d.ts +2 -0
- package/dist/goal/usage.js +3 -0
- package/dist/main.js +29 -42
- package/dist/model-catalog.js +11 -0
- package/dist/provider-transform.js +17 -0
- package/dist/provider.js +20 -5
- package/dist/session-types.d.ts +3 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +2 -0
- package/dist/tui/detect-theme.d.ts +1 -0
- package/dist/tui/detect-theme.js +23 -0
- package/dist/tui/image-display.d.ts +13 -0
- package/dist/tui/image-display.js +49 -0
- package/dist/tui/input-history.d.ts +37 -6
- package/dist/tui/input-history.js +194 -23
- package/dist/tui/model-switch.d.ts +42 -0
- package/dist/tui/model-switch.js +55 -0
- package/dist/tui-ink/app.d.ts +32 -2
- package/dist/tui-ink/app.js +1360 -522
- package/dist/tui-ink/approval/select.js +10 -0
- package/dist/tui-ink/detect-theme.d.ts +1 -2
- package/dist/tui-ink/detect-theme.js +1 -87
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +11 -0
- package/dist/tui-ink/feedback-dialog.js +10 -0
- package/dist/tui-ink/feishu-setup-picker.js +10 -0
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +8 -2
- package/dist/tui-ink/input-box.d.ts +70 -9
- package/dist/tui-ink/input-box.js +354 -120
- package/dist/tui-ink/input-history.d.ts +1 -16
- package/dist/tui-ink/input-history.js +1 -79
- package/dist/tui-ink/input-queue.d.ts +12 -0
- package/dist/tui-ink/input-queue.js +17 -0
- package/dist/tui-ink/key-events.d.ts +9 -0
- package/dist/tui-ink/key-events.js +8 -0
- package/dist/tui-ink/markdown.js +1 -1
- package/dist/tui-ink/message-list.d.ts +3 -1
- package/dist/tui-ink/message-list.js +42 -24
- package/dist/tui-ink/model-picker.d.ts +24 -2
- package/dist/tui-ink/model-picker.js +224 -20
- package/dist/tui-ink/plan-confirm.js +10 -0
- package/dist/tui-ink/question-dialog.js +10 -0
- package/dist/tui-ink/run.d.ts +11 -0
- package/dist/tui-ink/run.js +21 -28
- package/dist/tui-ink/session-picker.js +3 -0
- package/dist/tui-ink/submit-dedupe.d.ts +5 -0
- package/dist/tui-ink/submit-dedupe.js +25 -0
- package/dist/tui-ink/terminal-mouse.d.ts +13 -1
- package/dist/tui-ink/terminal-mouse.js +63 -21
- package/dist/tui-ink/theme.d.ts +6 -3
- package/dist/tui-ink/theme.js +10 -4
- package/dist/tui-ink/transcript-input.d.ts +8 -0
- package/dist/tui-ink/transcript-input.js +9 -0
- package/dist/tui-ink/transcript-viewport-math.d.ts +1 -2
- package/dist/tui-ink/transcript-viewport-math.js +1 -2
- package/dist/tui-ink/welcome.d.ts +1 -0
- package/dist/tui-ink/welcome.js +25 -28
- package/package.json +1 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/markdown-inline.d.ts +0 -22
- package/dist/tui/markdown-inline.js +0 -68
- package/dist/tui/markdown-theme-rules.d.ts +0 -23
- package/dist/tui/markdown-theme-rules.js +0 -164
- package/dist/tui/markdown-theme.d.ts +0 -5
- package/dist/tui/markdown-theme.js +0 -27
- package/dist/tui/opencode-spinner.d.ts +0 -22
- package/dist/tui/opencode-spinner.js +0 -216
- package/dist/tui/prompt-keybindings.d.ts +0 -42
- package/dist/tui/prompt-keybindings.js +0 -35
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -65
- package/dist/tui/run.js +0 -9934
- package/dist/tui/sidebar-mcp.d.ts +0 -31
- package/dist/tui/sidebar-mcp.js +0 -62
- package/dist/tui/sidebar-state.d.ts +0 -12
- package/dist/tui/sidebar-state.js +0 -69
- package/dist/tui/streaming-tool-args.d.ts +0 -15
- package/dist/tui/streaming-tool-args.js +0 -30
- package/dist/tui/tool-renderers/fallback.d.ts +0 -2
- package/dist/tui/tool-renderers/fallback.js +0 -75
- package/dist/tui/tool-renderers/registry.d.ts +0 -3
- package/dist/tui/tool-renderers/registry.js +0 -11
- package/dist/tui/tool-renderers/subagent.d.ts +0 -2
- package/dist/tui/tool-renderers/subagent.js +0 -135
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/types.js +0 -1
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -32
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- package/dist/tui-opentui/app.d.ts +0 -54
- package/dist/tui-opentui/app.js +0 -1371
- package/dist/tui-opentui/approval/approval-dialog.d.ts +0 -15
- package/dist/tui-opentui/approval/approval-dialog.js +0 -155
- package/dist/tui-opentui/approval/diff-view.d.ts +0 -9
- package/dist/tui-opentui/approval/diff-view.js +0 -43
- package/dist/tui-opentui/approval/select.d.ts +0 -37
- package/dist/tui-opentui/approval/select.js +0 -91
- package/dist/tui-opentui/detect-theme.d.ts +0 -2
- package/dist/tui-opentui/detect-theme.js +0 -87
- package/dist/tui-opentui/display-history.d.ts +0 -56
- package/dist/tui-opentui/display-history.js +0 -130
- package/dist/tui-opentui/edit-diff.d.ts +0 -11
- package/dist/tui-opentui/edit-diff.js +0 -57
- package/dist/tui-opentui/feedback-dialog.d.ts +0 -21
- package/dist/tui-opentui/feedback-dialog.js +0 -164
- package/dist/tui-opentui/feishu-setup-picker.d.ts +0 -7
- package/dist/tui-opentui/feishu-setup-picker.js +0 -272
- package/dist/tui-opentui/file-mentions.d.ts +0 -29
- package/dist/tui-opentui/file-mentions.js +0 -174
- package/dist/tui-opentui/footer.d.ts +0 -26
- package/dist/tui-opentui/footer.js +0 -40
- package/dist/tui-opentui/image-paste.d.ts +0 -54
- package/dist/tui-opentui/image-paste.js +0 -288
- package/dist/tui-opentui/input-box.d.ts +0 -32
- package/dist/tui-opentui/input-box.js +0 -462
- package/dist/tui-opentui/input-history.d.ts +0 -16
- package/dist/tui-opentui/input-history.js +0 -79
- package/dist/tui-opentui/markdown.d.ts +0 -66
- package/dist/tui-opentui/markdown.js +0 -127
- package/dist/tui-opentui/message-list.d.ts +0 -31
- package/dist/tui-opentui/message-list.js +0 -131
- package/dist/tui-opentui/model-picker.d.ts +0 -63
- package/dist/tui-opentui/model-picker.js +0 -450
- package/dist/tui-opentui/plan-confirm.d.ts +0 -9
- package/dist/tui-opentui/plan-confirm.js +0 -124
- package/dist/tui-opentui/question-dialog.d.ts +0 -10
- package/dist/tui-opentui/question-dialog.js +0 -110
- package/dist/tui-opentui/recent-activity.d.ts +0 -8
- package/dist/tui-opentui/recent-activity.js +0 -71
- package/dist/tui-opentui/run-session-picker.d.ts +0 -10
- package/dist/tui-opentui/run-session-picker.js +0 -28
- package/dist/tui-opentui/run.d.ts +0 -38
- package/dist/tui-opentui/run.js +0 -48
- package/dist/tui-opentui/session-picker.d.ts +0 -12
- package/dist/tui-opentui/session-picker.js +0 -120
- package/dist/tui-opentui/theme.d.ts +0 -89
- package/dist/tui-opentui/theme.js +0 -157
- package/dist/tui-opentui/todos.d.ts +0 -9
- package/dist/tui-opentui/todos.js +0 -45
- package/dist/tui-opentui/trace-groups.d.ts +0 -27
- package/dist/tui-opentui/trace-groups.js +0 -455
- package/dist/tui-opentui/use-terminal-size.d.ts +0 -4
- package/dist/tui-opentui/use-terminal-size.js +0 -5
- package/dist/tui-opentui/welcome.d.ts +0 -25
- package/dist/tui-opentui/welcome.js +0 -77
|
@@ -1,16 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare function loadHistorySync(filePath?: string): string[];
|
|
3
|
-
export declare function appendHistoryEntry(entry: string, filePath?: string): void;
|
|
4
|
-
export interface HistoryNavState {
|
|
5
|
-
history: string[];
|
|
6
|
-
index: number | null;
|
|
7
|
-
draft: string;
|
|
8
|
-
}
|
|
9
|
-
export interface HistoryNavResult {
|
|
10
|
-
text: string;
|
|
11
|
-
index: number | null;
|
|
12
|
-
draft: string;
|
|
13
|
-
changed: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function stepHistory(state: HistoryNavState, direction: "up" | "down", currentText: string): HistoryNavResult;
|
|
16
|
-
export declare function pushHistoryEntry(history: string[], entry: string): string[];
|
|
1
|
+
export * from "../tui/input-history.js";
|
|
@@ -1,79 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { getBubbleHome } from "../bubble-home.js";
|
|
4
|
-
const MAX_HISTORY_ENTRIES = 1000;
|
|
5
|
-
export function defaultHistoryFilePath() {
|
|
6
|
-
return join(getBubbleHome(), "input-history.jsonl");
|
|
7
|
-
}
|
|
8
|
-
// JSONL on disk: each line is a JSON-encoded string. JSON encoding handles
|
|
9
|
-
// embedded newlines and quotes so multi-line composer entries round-trip safely.
|
|
10
|
-
export function loadHistorySync(filePath = defaultHistoryFilePath()) {
|
|
11
|
-
try {
|
|
12
|
-
if (!existsSync(filePath))
|
|
13
|
-
return [];
|
|
14
|
-
const raw = readFileSync(filePath, "utf8");
|
|
15
|
-
const out = [];
|
|
16
|
-
for (const line of raw.split("\n")) {
|
|
17
|
-
if (!line)
|
|
18
|
-
continue;
|
|
19
|
-
try {
|
|
20
|
-
const parsed = JSON.parse(line);
|
|
21
|
-
if (typeof parsed === "string" && parsed.length > 0)
|
|
22
|
-
out.push(parsed);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// Malformed line - skip rather than fail the whole load.
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return out.length > MAX_HISTORY_ENTRIES ? out.slice(-MAX_HISTORY_ENTRIES) : out;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export function appendHistoryEntry(entry, filePath = defaultHistoryFilePath()) {
|
|
35
|
-
if (!entry || entry.trim().length === 0)
|
|
36
|
-
return;
|
|
37
|
-
try {
|
|
38
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
39
|
-
appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// Persistence is best-effort; never crash the composer over disk IO.
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// Pure transition for up/down navigation. `index === null` means the user is
|
|
46
|
-
// editing a fresh draft; otherwise it points at history[index]. When stepping
|
|
47
|
-
// from the draft into history we snapshot the current text so down past the
|
|
48
|
-
// newest entry can restore it.
|
|
49
|
-
export function stepHistory(state, direction, currentText) {
|
|
50
|
-
const { history, index, draft } = state;
|
|
51
|
-
const noChange = { text: currentText, index, draft, changed: false };
|
|
52
|
-
if (direction === "up") {
|
|
53
|
-
if (history.length === 0)
|
|
54
|
-
return noChange;
|
|
55
|
-
if (index === null) {
|
|
56
|
-
const newIdx = history.length - 1;
|
|
57
|
-
return { text: history[newIdx], index: newIdx, draft: currentText, changed: true };
|
|
58
|
-
}
|
|
59
|
-
if (index > 0) {
|
|
60
|
-
return { text: history[index - 1], index: index - 1, draft, changed: true };
|
|
61
|
-
}
|
|
62
|
-
return noChange;
|
|
63
|
-
}
|
|
64
|
-
if (index === null)
|
|
65
|
-
return noChange;
|
|
66
|
-
if (index < history.length - 1) {
|
|
67
|
-
return { text: history[index + 1], index: index + 1, draft, changed: true };
|
|
68
|
-
}
|
|
69
|
-
return { text: draft, index: null, draft: "", changed: true };
|
|
70
|
-
}
|
|
71
|
-
// Push to in-memory history with last-entry dedupe so repeated identical
|
|
72
|
-
// submissions don't spam the stack.
|
|
73
|
-
export function pushHistoryEntry(history, entry) {
|
|
74
|
-
if (!entry || entry.trim().length === 0)
|
|
75
|
-
return history;
|
|
76
|
-
if (history.length > 0 && history[history.length - 1] === entry)
|
|
77
|
-
return history;
|
|
78
|
-
return [...history, entry];
|
|
79
|
-
}
|
|
1
|
+
export * from "../tui/input-history.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SubmitPayload } from "./input-box.js";
|
|
2
|
+
export interface QueuedInput {
|
|
3
|
+
payload: SubmitPayload;
|
|
4
|
+
displayKey?: string;
|
|
5
|
+
sessionFile?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PendingSteerMeta {
|
|
8
|
+
displayKey: string;
|
|
9
|
+
sessionFile?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function isQueuedInputForCurrentSession(input: QueuedInput, currentSessionFile?: string): boolean;
|
|
12
|
+
export declare function queuedAndPendingDisplayKeys(queuedInputs: QueuedInput[], pendingSteers: Iterable<PendingSteerMeta>): Set<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function isQueuedInputForCurrentSession(input, currentSessionFile) {
|
|
2
|
+
if (!input.sessionFile || !currentSessionFile)
|
|
3
|
+
return true;
|
|
4
|
+
return input.sessionFile === currentSessionFile;
|
|
5
|
+
}
|
|
6
|
+
export function queuedAndPendingDisplayKeys(queuedInputs, pendingSteers) {
|
|
7
|
+
const keys = new Set();
|
|
8
|
+
for (const input of queuedInputs) {
|
|
9
|
+
if (input.displayKey)
|
|
10
|
+
keys.add(input.displayKey);
|
|
11
|
+
}
|
|
12
|
+
for (const steer of pendingSteers) {
|
|
13
|
+
if (steer.displayKey)
|
|
14
|
+
keys.add(steer.displayKey);
|
|
15
|
+
}
|
|
16
|
+
return keys;
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface InkKeyEvent {
|
|
2
|
+
eventType?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Kitty keyboard protocol can report key press/repeat/release separately.
|
|
6
|
+
* Release events still carry the printable text, so handling them like normal
|
|
7
|
+
* input inserts every typed character twice.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isKeyReleaseEvent(key: InkKeyEvent): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty keyboard protocol can report key press/repeat/release separately.
|
|
3
|
+
* Release events still carry the printable text, so handling them like normal
|
|
4
|
+
* input inserts every typed character twice.
|
|
5
|
+
*/
|
|
6
|
+
export function isKeyReleaseEvent(key) {
|
|
7
|
+
return key.eventType === "release";
|
|
8
|
+
}
|
package/dist/tui-ink/markdown.js
CHANGED
|
@@ -377,7 +377,7 @@ function TableBlock({ headers, rows, maxWidth, }) {
|
|
|
377
377
|
const { columns: termWidth } = useTerminalSize();
|
|
378
378
|
const colCount = headers.length;
|
|
379
379
|
// Reserve a buffer so the table fits even when wrapped inside an indented
|
|
380
|
-
// box (e.g. the timeline gutter contributes marginLeft + "
|
|
380
|
+
// box (e.g. the timeline gutter contributes marginLeft + "● " = 5 cells).
|
|
381
381
|
const budget = Math.max(20, (maxWidth ?? termWidth) - 8);
|
|
382
382
|
const maxWidths = headers.map((h, i) => {
|
|
383
383
|
let max = visualWidth(inlinePlainText(h));
|
|
@@ -17,6 +17,8 @@ interface MessageListProps {
|
|
|
17
17
|
streamingTools: DisplayToolCall[];
|
|
18
18
|
streamingParts: DisplayMessagePart[];
|
|
19
19
|
terminalColumns: number;
|
|
20
|
+
showThinking?: boolean;
|
|
21
|
+
expandedToolOutput?: boolean;
|
|
20
22
|
verboseTrace: boolean;
|
|
21
23
|
pendingApproval?: PendingApprovalHint | null;
|
|
22
24
|
/** Animation tick used to refresh in-progress elapsed counters. */
|
|
@@ -24,5 +26,5 @@ interface MessageListProps {
|
|
|
24
26
|
/** Optional banner rendered as the first item in the app-controlled transcript. */
|
|
25
27
|
welcomeBanner?: React.ReactNode;
|
|
26
28
|
}
|
|
27
|
-
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, pendingApproval, nowTick, welcomeBanner, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking, expandedToolOutput, verboseTrace, pendingApproval, nowTick, welcomeBanner, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
28
30
|
export {};
|
|
@@ -9,42 +9,48 @@ import { buildTraceGroups, executeCommandBlock, formatTracePath, shouldInlineExe
|
|
|
9
9
|
import { EDIT_COLLAPSED_DIFF_LINES, formatEditSuccessSummary, getEditDiffDetails } from "./edit-diff.js";
|
|
10
10
|
import { formatSubagentRoute } from "../agent/subagent-route-format.js";
|
|
11
11
|
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
12
|
+
import { splitImageDisplayContent } from "../tui/image-display.js";
|
|
12
13
|
const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
|
|
13
|
-
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, pendingApproval, nowTick, welcomeBanner, }) {
|
|
14
|
+
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, expandedToolOutput = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, }) {
|
|
15
|
+
const theme = useTheme();
|
|
14
16
|
const hasStreaming = !!(streamingContent ||
|
|
15
17
|
streamingReasoning ||
|
|
16
18
|
streamingTools.length > 0 ||
|
|
17
19
|
streamingParts.length > 0);
|
|
20
|
+
const regularMessages = messages.filter((message) => !message.inputStatus);
|
|
21
|
+
const pendingSteerMessages = messages.filter((message) => message.inputStatus === "pending_steer");
|
|
22
|
+
const queuedInputMessages = messages.filter((message) => message.inputStatus === "queued");
|
|
18
23
|
const staticItems = [];
|
|
19
24
|
if (welcomeBanner) {
|
|
20
25
|
staticItems.push({ kind: "welcome", key: "welcome" });
|
|
21
26
|
}
|
|
22
|
-
const lastMessageIndex =
|
|
23
|
-
for (let i = 0; i <
|
|
24
|
-
const msg =
|
|
27
|
+
const lastMessageIndex = regularMessages.length - 1;
|
|
28
|
+
for (let i = 0; i < regularMessages.length; i++) {
|
|
29
|
+
const msg = regularMessages[i];
|
|
25
30
|
staticItems.push({
|
|
26
31
|
kind: "message",
|
|
27
32
|
key: msg.key ?? `message-${i}`,
|
|
28
33
|
message: msg,
|
|
29
34
|
showExpandHint: !hasStreaming && i === lastMessageIndex,
|
|
35
|
+
separateFromPrevious: msg.role === "user" && regularMessages[i - 1]?.role === "user",
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
38
|
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [staticItems.map((item) => {
|
|
33
39
|
if (item.kind === "welcome") {
|
|
34
40
|
return _jsx(React.Fragment, { children: welcomeBanner }, item.key);
|
|
35
41
|
}
|
|
36
|
-
return (_jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, nowTick: item.showExpandHint ? nowTick : undefined }, item.key));
|
|
37
|
-
}), hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick }))] }));
|
|
42
|
+
return (_jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, separateFromPrevious: item.separateFromPrevious, nowTick: item.showExpandHint ? nowTick : undefined }, item.key));
|
|
43
|
+
}), hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick })), pendingSteerMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: pendingSteerMessages, terminalColumns: terminalColumns, title: "Messages to steer at next model call", hint: "applies before the next provider request", bulletColor: theme.warning })), queuedInputMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: queuedInputMessages, terminalColumns: terminalColumns, title: "Messages queued for next turn", hint: "runs after the current answer", bulletColor: theme.muted }))] }));
|
|
38
44
|
}
|
|
39
45
|
// Memoized: with no <Static> region, every transcript row re-renders on each
|
|
40
46
|
// state change unless its props are referentially stable. Message objects are
|
|
41
47
|
// append-only (compaction reuses already-compacted instances), keys are
|
|
42
48
|
// stable, and nowTick is only threaded to the last row, so memo hits for all
|
|
43
49
|
// settled history rows.
|
|
44
|
-
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, verboseTrace, showExpandHint, nowTick, }) {
|
|
50
|
+
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking, expandedToolOutput, verboseTrace, showExpandHint, separateFromPrevious, nowTick, }) {
|
|
45
51
|
const theme = useTheme();
|
|
46
52
|
if (message.role === "user") {
|
|
47
|
-
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus }));
|
|
53
|
+
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus, separateFromPrevious: separateFromPrevious }));
|
|
48
54
|
}
|
|
49
55
|
if (message.role === "error") {
|
|
50
56
|
return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Text, { color: theme.error, children: ["Error: ", message.content] }) }));
|
|
@@ -59,12 +65,12 @@ const MessageItem = React.memo(function MessageItem({ message, terminalColumns,
|
|
|
59
65
|
const hasVisibleAssistantContent = !!message.content ||
|
|
60
66
|
(message.toolCalls?.length ?? 0) > 0 ||
|
|
61
67
|
(message.parts?.length ?? 0) > 0 ||
|
|
62
|
-
(!!visibleReasoning && verboseTrace);
|
|
68
|
+
(!!visibleReasoning && (showThinking || verboseTrace));
|
|
63
69
|
if (!hasVisibleAssistantContent)
|
|
64
70
|
return null;
|
|
65
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && verboseTrace && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), message.content && _jsx(MarkdownContent, { content: message.content })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
71
|
+
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), message.content && _jsx(MarkdownContent, { content: message.content })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
66
72
|
});
|
|
67
|
-
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, verboseTrace, pendingApproval, nowTick, }) {
|
|
73
|
+
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, expandedToolOutput, verboseTrace, pendingApproval, nowTick, }) {
|
|
68
74
|
const deferredContent = React.useDeferredValue(content);
|
|
69
75
|
const deferredReasoning = React.useDeferredValue(reasoning);
|
|
70
76
|
const deferredParts = React.useDeferredValue(parts);
|
|
@@ -72,22 +78,22 @@ function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, v
|
|
|
72
78
|
const visibleParts = deferredParts.length > 0
|
|
73
79
|
? deferredParts
|
|
74
80
|
: fallbackStreamingParts(deferredContent, tools);
|
|
75
|
-
return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }) })), visibleParts.length > 0 && (
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }) })), visibleParts.length > 0 && (
|
|
76
82
|
// marginTop=1 matches the committed MessageItem layout exactly, so the
|
|
77
83
|
// gap under the user message is identical while streaming and after the
|
|
78
84
|
// turn commits — no spacing jump at finalize time. (The old marginTop=0
|
|
79
85
|
// was a flicker mitigation for the main-screen <Static> renderer; the
|
|
80
86
|
// alt-screen viewport repaints frames atomically, so it's obsolete.)
|
|
81
|
-
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
87
|
+
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
82
88
|
}
|
|
83
|
-
function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
89
|
+
function MessageParts({ parts, terminalColumns, expandedToolOutput, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
84
90
|
const lastToolsPartIndex = findLastToolsPartIndex(parts);
|
|
85
91
|
const lastTextPartIndex = findLastTextPartIndex(parts);
|
|
86
92
|
return (_jsx(Box, { flexDirection: "column", children: parts.map((part, idx) => {
|
|
87
93
|
if (part.type === "text") {
|
|
88
94
|
return (_jsx(TimelineText, { content: part.content, compactTop: idx === 0, terminalColumns: terminalColumns, streaming: streaming && idx === lastTextPartIndex }, `text-${idx}`));
|
|
89
95
|
}
|
|
90
|
-
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
96
|
+
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
91
97
|
}) }));
|
|
92
98
|
}
|
|
93
99
|
function findLastTextPartIndex(parts) {
|
|
@@ -101,23 +107,24 @@ function TimelineText({ content, compactTop, terminalColumns, streaming = false,
|
|
|
101
107
|
const theme = useTheme();
|
|
102
108
|
if (!content.trim())
|
|
103
109
|
return null;
|
|
104
|
-
// marginLeft (2) + "
|
|
110
|
+
// marginLeft (2) + "● " marker (3 visual cells) = 5 cells consumed by the
|
|
105
111
|
// timeline gutter; pass the remaining width so wide blocks like tables size
|
|
106
112
|
// themselves against the actual content area instead of the raw terminal.
|
|
107
113
|
const available = terminalColumns ? Math.max(20, terminalColumns - 5) : undefined;
|
|
108
114
|
const trimmed = content.trim();
|
|
109
|
-
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\
|
|
115
|
+
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available })) })] }));
|
|
110
116
|
}
|
|
111
|
-
function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
117
|
+
function ToolsPart({ toolCalls, terminalColumns, expandedToolOutput, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
112
118
|
if (toolCalls.length === 0)
|
|
113
119
|
return null;
|
|
114
|
-
|
|
120
|
+
const expandTools = verboseTrace || expandedToolOutput;
|
|
121
|
+
if (!expandTools) {
|
|
115
122
|
return (_jsx(TraceGroupList, { toolCalls: toolCalls, terminalColumns: terminalColumns, pendingApproval: pendingApproval, nowTick: nowTick, compactTop: compactTop, showActivity: showActivity }));
|
|
116
123
|
}
|
|
117
124
|
const lastIdx = toolCalls.length - 1;
|
|
118
125
|
return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
|
|
119
126
|
const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
|
|
120
|
-
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose:
|
|
127
|
+
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: expandTools, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
|
|
121
128
|
}) }));
|
|
122
129
|
}
|
|
123
130
|
function fallbackStreamingParts(content, tools) {
|
|
@@ -236,7 +243,7 @@ function CompactionSummaryBlock({ message }) {
|
|
|
236
243
|
const summary = message.compactionSummary?.trim();
|
|
237
244
|
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, paddingX: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.borderActive, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.success, bold: true, children: "\u2713 " }), _jsx(Text, { color: theme.accent, bold: true, children: "Compaction checkpoint" }), _jsxs(Text, { color: theme.muted, children: [" \u00B7 ", status] })] }), summary && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, dimColor: true, children: "Preserved context summary" }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsx(MarkdownContent, { content: summary }) })] }))] }));
|
|
238
245
|
}
|
|
239
|
-
function UserMessageBlock({ content, terminalColumns, inputStatus, }) {
|
|
246
|
+
function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromPrevious = false, }) {
|
|
240
247
|
const theme = useTheme();
|
|
241
248
|
const badge = userInputStatusBadgeLabel(inputStatus);
|
|
242
249
|
// Rail and its right gutter must share the bubble background; otherwise the
|
|
@@ -244,10 +251,21 @@ function UserMessageBlock({ content, terminalColumns, inputStatus, }) {
|
|
|
244
251
|
const railWidth = 2;
|
|
245
252
|
const horizontalRoom = Math.max(20, terminalColumns - 2);
|
|
246
253
|
const bubbleTextWidth = Math.max(1, horizontalRoom - railWidth - 2);
|
|
247
|
-
const
|
|
248
|
-
|
|
254
|
+
const { bodyLines, referenceLines } = splitImageDisplayContent(content);
|
|
255
|
+
const wrappedLines = bodyLines
|
|
249
256
|
.flatMap((line) => wrapByVisualWidth(line, bubbleTextWidth));
|
|
250
|
-
return (_jsxs(Box, { flexDirection: "column", children: [badge && (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: inputStatus === "pending_steer" ? theme.warning : theme.muted, children: ` ${badge} ` }), _jsx(Text, { color: theme.dim, children: inputStatus === "pending_steer" ? "applies at the next model call" : "runs after this turn" })] })), wrappedLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userRail, children: index === 0 ? "▌ " : " " }), _jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userMessageText, children: ` ${padVisual(line || " ", bubbleTextWidth)} ` })] }, index)))] }));
|
|
257
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: separateFromPrevious ? 1 : 0, children: [badge && (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: inputStatus === "pending_steer" ? theme.warning : theme.muted, children: ` ${badge} ` }), _jsx(Text, { color: theme.dim, children: inputStatus === "pending_steer" ? "applies at the next model call" : "runs after this turn" })] })), wrappedLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userRail, children: index === 0 ? "▌ " : " " }), _jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userMessageText, children: ` ${padVisual(line || " ", bubbleTextWidth)} ` })] }, index))), referenceLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: ` ${line}` }) }, `attachment-${index}`)))] }));
|
|
258
|
+
}
|
|
259
|
+
function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
|
|
260
|
+
const theme = useTheme();
|
|
261
|
+
const contentWidth = Math.max(20, terminalColumns - 5);
|
|
262
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsxs(Text, { bold: true, color: theme.inputText, children: [title, " "] }), _jsxs(Text, { color: theme.dim, children: ["(", hint, ")"] })] }), messages.flatMap((message, messageIndex) => {
|
|
263
|
+
const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
|
|
264
|
+
const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
|
|
265
|
+
const bodyRows = wrappedBody.map((line, lineIndex) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.dim, children: lineIndex === 0 ? "↳ " : " " }), _jsx(Text, { color: theme.inputText, children: line })] }, `body-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
266
|
+
const attachmentRows = referenceLines.map((line, lineIndex) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: [" ", line] }) }, `attachment-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
267
|
+
return [...bodyRows, ...attachmentRows];
|
|
268
|
+
})] }));
|
|
251
269
|
}
|
|
252
270
|
const TOOL_DISPLAY_NAMES = {
|
|
253
271
|
read: "Read",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ProviderRegistry } from "../provider-registry.js";
|
|
2
|
+
import type { ThinkingLevel } from "../types.js";
|
|
2
3
|
export { padVisual, truncateVisual } from "../text-display.js";
|
|
3
4
|
export interface ModelPickerOption {
|
|
4
5
|
id: string;
|
|
5
6
|
label: string;
|
|
6
7
|
group: string;
|
|
7
8
|
providerBadge: string;
|
|
9
|
+
reasoningLevels: ThinkingLevel[];
|
|
8
10
|
}
|
|
9
11
|
export type PickerKeyAction = "up" | "down" | "enter" | "escape" | "backspace" | "delete";
|
|
10
12
|
export declare function resolvePickerKeyAction(input: string, key: {
|
|
@@ -23,14 +25,34 @@ export declare function formatSkillPickerRow(skill: {
|
|
|
23
25
|
selected: boolean;
|
|
24
26
|
width: number;
|
|
25
27
|
}): string;
|
|
28
|
+
export declare const MODEL_PICKER_MAX_BODY_ROWS = 10;
|
|
29
|
+
export declare const MODEL_PICKER_CHROME_ROWS = 13;
|
|
30
|
+
export declare function modelPickerBodyRows(termHeight: number): number;
|
|
31
|
+
export declare function clampPickerIndex(index: number, length: number): number;
|
|
32
|
+
export declare function pickerWindowStart(selectedIndex: number, length: number, visibleRows: number): number;
|
|
33
|
+
export declare function padPickerRows(rows: string[], bodyRows: number, width: number): string[];
|
|
34
|
+
export declare function formatReasoningLevelsLabel(levels: readonly ThinkingLevel[]): string;
|
|
35
|
+
export declare function formatModelPickerRow(option: Pick<ModelPickerOption, "label" | "providerBadge" | "reasoningLevels">, options: {
|
|
36
|
+
selected: boolean;
|
|
37
|
+
current: boolean;
|
|
38
|
+
width: number;
|
|
39
|
+
}): string;
|
|
40
|
+
export declare function formatEffortPickerRow(level: ThinkingLevel, options: {
|
|
41
|
+
selected: boolean;
|
|
42
|
+
width: number;
|
|
43
|
+
}): string;
|
|
44
|
+
export declare function formatNoModelResultsRow(query: string, width: number): string;
|
|
45
|
+
export declare function preferredEffortIndex(option: Pick<ModelPickerOption, "reasoningLevels">, currentThinkingLevel: ThinkingLevel): number;
|
|
46
|
+
export declare function shouldOpenEffortPicker(option: Pick<ModelPickerOption, "reasoningLevels">): boolean;
|
|
26
47
|
export interface ModelPickerProps {
|
|
27
48
|
registry: ProviderRegistry;
|
|
28
49
|
current: string;
|
|
50
|
+
currentThinkingLevel: ThinkingLevel;
|
|
29
51
|
recent: string[];
|
|
30
|
-
onSelect: (model: string) => void;
|
|
52
|
+
onSelect: (model: string, thinkingLevel: ThinkingLevel) => void;
|
|
31
53
|
onCancel: () => void;
|
|
32
54
|
}
|
|
33
|
-
export declare function ModelPicker({ registry, current, recent, onSelect, onCancel }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
export declare function ModelPicker({ registry, current, currentThinkingLevel, recent, onSelect, onCancel }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
34
56
|
export declare function buildLocalModelOptions(registry: ProviderRegistry, current: string, recent: string[]): ModelPickerOption[];
|
|
35
57
|
export interface ProviderPickerProps {
|
|
36
58
|
providers: Array<{
|