@bubblebrain-ai/bubble 0.0.10 → 0.0.12
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.d.ts +1 -0
- package/dist/agent.js +6 -2
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +31 -3
- package/dist/feedback/collect.d.ts +7 -0
- package/dist/feedback/collect.js +119 -0
- package/dist/feedback/config.d.ts +14 -0
- package/dist/feedback/config.js +16 -0
- package/dist/feedback/redact.d.ts +1 -0
- package/dist/feedback/redact.js +25 -0
- package/dist/feedback/submit.d.ts +6 -0
- package/dist/feedback/submit.js +43 -0
- package/dist/feedback/types.d.ts +22 -0
- package/dist/feishu/agent-host/approval-card.d.ts +11 -0
- package/dist/feishu/agent-host/approval-card.js +46 -0
- package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
- package/dist/feishu/agent-host/approval-ui.js +214 -0
- package/dist/feishu/agent-host/run-driver.d.ts +51 -0
- package/dist/feishu/agent-host/run-driver.js +302 -0
- package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
- package/dist/feishu/agent-host/runtime-deps.js +8 -0
- package/dist/feishu/card/budget.d.ts +40 -0
- package/dist/feishu/card/budget.js +134 -0
- package/dist/feishu/card/renderer.d.ts +29 -0
- package/dist/feishu/card/renderer.js +245 -0
- package/dist/feishu/card/run-state-types.d.ts +49 -0
- package/dist/feishu/card/run-state-types.js +15 -0
- package/dist/feishu/card/run-state.d.ts +21 -0
- package/dist/feishu/card/run-state.js +217 -0
- package/dist/feishu/channel/channel.d.ts +52 -0
- package/dist/feishu/channel/channel.js +74 -0
- package/dist/feishu/config.d.ts +24 -0
- package/dist/feishu/config.js +97 -0
- package/dist/feishu/format.d.ts +6 -0
- package/dist/feishu/format.js +14 -0
- package/dist/feishu/index.d.ts +4 -0
- package/dist/feishu/index.js +4 -0
- package/dist/feishu/logger.d.ts +31 -0
- package/dist/feishu/logger.js +62 -0
- package/dist/feishu/paths.d.ts +12 -0
- package/dist/feishu/paths.js +38 -0
- package/dist/feishu/process-registry.d.ts +29 -0
- package/dist/feishu/process-registry.js +90 -0
- package/dist/feishu/router/commands.d.ts +38 -0
- package/dist/feishu/router/commands.js +286 -0
- package/dist/feishu/router/event-router.d.ts +40 -0
- package/dist/feishu/router/event-router.js +208 -0
- package/dist/feishu/router/whitelist.d.ts +23 -0
- package/dist/feishu/router/whitelist.js +20 -0
- package/dist/feishu/runtime/active-runs.d.ts +32 -0
- package/dist/feishu/runtime/active-runs.js +84 -0
- package/dist/feishu/runtime/pending-queue.d.ts +36 -0
- package/dist/feishu/runtime/pending-queue.js +98 -0
- package/dist/feishu/runtime/process-pool.d.ts +29 -0
- package/dist/feishu/runtime/process-pool.js +49 -0
- package/dist/feishu/schema.d.ts +17 -0
- package/dist/feishu/schema.js +252 -0
- package/dist/feishu/scope/scope-registry.d.ts +39 -0
- package/dist/feishu/scope/scope-registry.js +148 -0
- package/dist/feishu/scope/session-binder.d.ts +44 -0
- package/dist/feishu/scope/session-binder.js +100 -0
- package/dist/feishu/scope/session-store.d.ts +24 -0
- package/dist/feishu/scope/session-store.js +73 -0
- package/dist/feishu/secrets.d.ts +37 -0
- package/dist/feishu/secrets.js +129 -0
- package/dist/feishu/serve.d.ts +12 -0
- package/dist/feishu/serve.js +288 -0
- package/dist/feishu/types.d.ts +75 -0
- package/dist/feishu/types.js +23 -0
- package/dist/feishu/wizard.d.ts +24 -0
- package/dist/feishu/wizard.js +121 -0
- package/dist/main.js +98 -32
- package/dist/model-catalog.js +3 -0
- package/dist/prompt/compose.js +3 -3
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-openai-codex.d.ts +8 -1
- package/dist/provider-openai-codex.js +33 -9
- package/dist/provider.d.ts +2 -0
- package/dist/session-title.d.ts +16 -0
- package/dist/session-title.js +134 -0
- package/dist/session-types.d.ts +5 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.js +154 -2
- package/dist/skills/invocation.js +0 -18
- package/dist/skills/registry.d.ts +1 -0
- package/dist/skills/registry.js +2 -0
- package/dist/slash-commands/commands.js +15 -22
- package/dist/slash-commands/feishu.d.ts +17 -0
- package/dist/slash-commands/feishu.js +400 -0
- package/dist/slash-commands/registry.js +1 -1
- package/dist/slash-commands/types.d.ts +3 -1
- package/dist/text-display.d.ts +3 -0
- package/dist/text-display.js +25 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/skill-search.d.ts +10 -0
- package/dist/tools/skill-search.js +134 -0
- package/dist/tools/skill.js +1 -4
- package/dist/tui-ink/app.js +265 -118
- package/dist/tui-ink/code-highlight.js +2 -3
- package/dist/tui-ink/detect-theme.d.ts +1 -18
- package/dist/tui-ink/detect-theme.js +1 -37
- package/dist/tui-ink/display-history.d.ts +20 -3
- package/dist/tui-ink/display-history.js +26 -27
- package/dist/tui-ink/feedback-dialog.d.ts +19 -0
- package/dist/tui-ink/feedback-dialog.js +123 -0
- package/dist/tui-ink/feishu-setup-picker.d.ts +5 -0
- package/dist/tui-ink/feishu-setup-picker.js +261 -0
- package/dist/tui-ink/input-box.d.ts +25 -1
- package/dist/tui-ink/input-box.js +132 -11
- package/dist/tui-ink/input-history.js +3 -5
- package/dist/tui-ink/markdown.d.ts +32 -0
- package/dist/tui-ink/markdown.js +111 -4
- package/dist/tui-ink/message-list.d.ts +1 -6
- package/dist/tui-ink/message-list.js +86 -34
- package/dist/tui-ink/model-picker.d.ts +18 -0
- package/dist/tui-ink/model-picker.js +81 -27
- package/dist/tui-ink/run-session-picker.d.ts +10 -0
- package/dist/tui-ink/run-session-picker.js +22 -0
- package/dist/tui-ink/run.js +7 -2
- package/dist/tui-ink/session-picker.d.ts +10 -0
- package/dist/tui-ink/session-picker.js +110 -0
- package/dist/tui-ink/terminal-mouse.d.ts +4 -0
- package/dist/tui-ink/terminal-mouse.js +23 -0
- package/dist/tui-ink/theme.js +2 -2
- package/dist/tui-ink/trace-groups.js +25 -2
- package/dist/tui-ink/welcome.js +2 -4
- package/package.json +4 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/display-history.d.ts +0 -44
- package/dist/tui/display-history.js +0 -243
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/file-mentions.d.ts +0 -29
- package/dist/tui/file-mentions.js +0 -174
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/image-paste.d.ts +0 -95
- package/dist/tui/image-paste.js +0 -505
- 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 -21
- 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/recent-activity.d.ts +0 -8
- package/dist/tui/recent-activity.js +0 -71
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -38
- package/dist/tui/run.js +0 -6996
- 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 -114
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -30
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- /package/dist/{tui/tool-renderers → feedback}/types.js +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
4
|
+
import { useTheme } from "./theme.js";
|
|
5
|
+
import { formatRelativeTime } from "./recent-activity.js";
|
|
6
|
+
import { padVisual, truncateVisual } from "../text-display.js";
|
|
7
|
+
export function SessionPicker({ currentCwd, currentSessions, allSessions, onSelect, onCancel }) {
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
const { stdout } = useStdout();
|
|
10
|
+
const termHeight = stdout?.rows || 24;
|
|
11
|
+
const termWidth = stdout?.columns || 80;
|
|
12
|
+
const maxVisible = Math.max(6, termHeight - 10);
|
|
13
|
+
const [mode, setMode] = useState("current");
|
|
14
|
+
const [selectedSessionIdx, setSelectedSessionIdx] = useState(0);
|
|
15
|
+
const rows = useMemo(() => buildRows(mode, currentCwd, currentSessions, allSessions), [mode, currentCwd, currentSessions, allSessions]);
|
|
16
|
+
const sessionRowIndices = useMemo(() => rows.map((row, i) => (row.type === "session" ? i : -1)).filter((i) => i >= 0), [rows]);
|
|
17
|
+
const clampedIdx = sessionRowIndices.length === 0
|
|
18
|
+
? 0
|
|
19
|
+
: Math.min(selectedSessionIdx, sessionRowIndices.length - 1);
|
|
20
|
+
const selectedRowIndex = sessionRowIndices[clampedIdx] ?? -1;
|
|
21
|
+
useInput((input, key) => {
|
|
22
|
+
if (key.escape) {
|
|
23
|
+
onCancel();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (key.tab) {
|
|
27
|
+
setMode((m) => (m === "current" ? "all" : "current"));
|
|
28
|
+
setSelectedSessionIdx(0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (key.return) {
|
|
32
|
+
const row = rows[selectedRowIndex];
|
|
33
|
+
if (row?.type === "session" && row.session)
|
|
34
|
+
onSelect(row.session.file);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (key.upArrow) {
|
|
38
|
+
setSelectedSessionIdx((i) => Math.max(0, i - 1));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (key.downArrow) {
|
|
42
|
+
setSelectedSessionIdx((i) => Math.min(Math.max(0, sessionRowIndices.length - 1), i + 1));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// Window the visible rows around the selected session.
|
|
47
|
+
const start = clampWindowStart(rows, selectedRowIndex, maxVisible);
|
|
48
|
+
const visible = rows.slice(start, start + maxVisible);
|
|
49
|
+
const modeLabel = mode === "current" ? "Current dir" : "All directories";
|
|
50
|
+
const totalSessions = sessionRowIndices.length;
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: "Resume session" }), _jsxs(Text, { color: theme.muted, children: ["View: ", _jsx(Text, { color: theme.accent, children: modeLabel }), " · ", totalSessions, " session", totalSessions === 1 ? "" : "s"] }), _jsx(Text, { color: theme.muted, children: "\u2191/\u2193 navigate \u00B7 Enter resume \u00B7 Tab toggle scope \u00B7 Esc start fresh" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [totalSessions === 0 && (_jsx(Text, { color: theme.muted, children: mode === "current"
|
|
52
|
+
? "No previous sessions in this directory."
|
|
53
|
+
: "No previous sessions found." })), visible.map((row, i) => {
|
|
54
|
+
const actualIndex = start + i;
|
|
55
|
+
if (row.type === "header") {
|
|
56
|
+
return (_jsx(Box, { marginTop: i === 0 ? 0 : 1, children: _jsx(Text, { color: theme.muted, bold: true, children: row.label }) }, `h-${actualIndex}`));
|
|
57
|
+
}
|
|
58
|
+
const session = row.session;
|
|
59
|
+
const isSelected = actualIndex === selectedRowIndex;
|
|
60
|
+
const time = padVisual(formatRelativeTime(session.mtime), 9);
|
|
61
|
+
const titleWidth = Math.max(20, Math.min(80, termWidth - 30));
|
|
62
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? theme.accent : undefined, children: [isSelected ? "> " : " ", time, " ", padVisual(truncateVisual(session.title, titleWidth), titleWidth)] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u00B7 ", session.messageCount, " msg", session.messageCount === 1 ? "" : "s"] }) })] }, session.file));
|
|
63
|
+
})] })] }));
|
|
64
|
+
}
|
|
65
|
+
function buildRows(mode, currentCwd, currentSessions, allSessions) {
|
|
66
|
+
if (mode === "current") {
|
|
67
|
+
if (currentSessions.length === 0)
|
|
68
|
+
return [];
|
|
69
|
+
return [
|
|
70
|
+
{ type: "header", label: currentCwd },
|
|
71
|
+
...currentSessions.map((session) => ({ type: "session", session })),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
const grouped = new Map();
|
|
75
|
+
for (const session of allSessions) {
|
|
76
|
+
const key = session.cwdLabel;
|
|
77
|
+
const list = grouped.get(key);
|
|
78
|
+
if (list)
|
|
79
|
+
list.push(session);
|
|
80
|
+
else
|
|
81
|
+
grouped.set(key, [session]);
|
|
82
|
+
}
|
|
83
|
+
const sortedGroups = Array.from(grouped.entries()).sort((a, b) => {
|
|
84
|
+
if (a[0] === currentCwd)
|
|
85
|
+
return -1;
|
|
86
|
+
if (b[0] === currentCwd)
|
|
87
|
+
return 1;
|
|
88
|
+
const aLatest = a[1][0]?.mtime ?? 0;
|
|
89
|
+
const bLatest = b[1][0]?.mtime ?? 0;
|
|
90
|
+
return bLatest - aLatest;
|
|
91
|
+
});
|
|
92
|
+
const rows = [];
|
|
93
|
+
for (const [label, sessions] of sortedGroups) {
|
|
94
|
+
rows.push({ type: "header", label });
|
|
95
|
+
for (const session of sessions)
|
|
96
|
+
rows.push({ type: "session", session });
|
|
97
|
+
}
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
100
|
+
function clampWindowStart(rows, selectedRowIndex, maxVisible) {
|
|
101
|
+
if (rows.length <= maxVisible)
|
|
102
|
+
return 0;
|
|
103
|
+
if (selectedRowIndex < 0)
|
|
104
|
+
return 0;
|
|
105
|
+
const half = Math.floor(maxVisible / 2);
|
|
106
|
+
let start = Math.max(0, selectedRowIndex - half);
|
|
107
|
+
if (start + maxVisible > rows.length)
|
|
108
|
+
start = rows.length - maxVisible;
|
|
109
|
+
return Math.max(0, start);
|
|
110
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type MouseWheelDirection = "up" | "down";
|
|
2
|
+
export declare function stripTerminalMouseSequences(input: string): string;
|
|
3
|
+
export declare function hasTerminalMouseSequence(input: string): boolean;
|
|
4
|
+
export declare function parseTerminalMouseWheel(input: string): MouseWheelDirection[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const SGR_MOUSE_SEQUENCE_RE = /\x1b?\[?<\d+;\d+;\d+[mM]/g;
|
|
2
|
+
const SGR_MOUSE_WHEEL_RE = /\x1b?\[?<(\d+);\d+;\d+([mM])/g;
|
|
3
|
+
export function stripTerminalMouseSequences(input) {
|
|
4
|
+
return input.replace(SGR_MOUSE_SEQUENCE_RE, "");
|
|
5
|
+
}
|
|
6
|
+
export function hasTerminalMouseSequence(input) {
|
|
7
|
+
SGR_MOUSE_SEQUENCE_RE.lastIndex = 0;
|
|
8
|
+
return SGR_MOUSE_SEQUENCE_RE.test(input);
|
|
9
|
+
}
|
|
10
|
+
export function parseTerminalMouseWheel(input) {
|
|
11
|
+
const directions = [];
|
|
12
|
+
SGR_MOUSE_WHEEL_RE.lastIndex = 0;
|
|
13
|
+
for (const match of input.matchAll(SGR_MOUSE_WHEEL_RE)) {
|
|
14
|
+
if (match[2] !== "M")
|
|
15
|
+
continue;
|
|
16
|
+
const code = Number(match[1]);
|
|
17
|
+
if (code === 64)
|
|
18
|
+
directions.push("up");
|
|
19
|
+
if (code === 65)
|
|
20
|
+
directions.push("down");
|
|
21
|
+
}
|
|
22
|
+
return directions;
|
|
23
|
+
}
|
package/dist/tui-ink/theme.js
CHANGED
|
@@ -68,8 +68,8 @@ export const lightTheme = {
|
|
|
68
68
|
borderActive: "#0E5A85",
|
|
69
69
|
inputBorder: "#6B5FB8",
|
|
70
70
|
inputBorderDisabled: "#c5c3d0",
|
|
71
|
-
inputBg: "#
|
|
72
|
-
inputBgDisabled: "#
|
|
71
|
+
inputBg: "#eeeef6",
|
|
72
|
+
inputBgDisabled: "#e2e2ec",
|
|
73
73
|
inputText: "#1c1c24",
|
|
74
74
|
inputPlaceholder: "#7a7886",
|
|
75
75
|
muted: "gray",
|
|
@@ -212,7 +212,7 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
|
|
|
212
212
|
kind: "execute",
|
|
213
213
|
title: classifier.title,
|
|
214
214
|
raw: [tool],
|
|
215
|
-
command: normalizeCommand(tool.args.command ??
|
|
215
|
+
command: normalizeCommand(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments)),
|
|
216
216
|
items: [],
|
|
217
217
|
previewLines: shown,
|
|
218
218
|
errorLines: [],
|
|
@@ -362,7 +362,30 @@ function plural(count, singular, pluralValue) {
|
|
|
362
362
|
}
|
|
363
363
|
function normalizeCommand(value) {
|
|
364
364
|
const command = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
365
|
-
return command
|
|
365
|
+
return command;
|
|
366
|
+
}
|
|
367
|
+
function commandFromRawArguments(rawArguments) {
|
|
368
|
+
if (!rawArguments)
|
|
369
|
+
return "";
|
|
370
|
+
try {
|
|
371
|
+
const parsed = JSON.parse(rawArguments);
|
|
372
|
+
if (parsed && typeof parsed === "object") {
|
|
373
|
+
const command = parsed.command ?? parsed.cmd;
|
|
374
|
+
return typeof command === "string" ? command : "";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
const match = rawArguments.match(/"(?:command|cmd)"\s*:\s*"((?:\\.|[^"\\])*)/);
|
|
379
|
+
if (match?.[1]) {
|
|
380
|
+
try {
|
|
381
|
+
return JSON.parse(`"${match[1]}"`);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return match[1];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return "";
|
|
366
389
|
}
|
|
367
390
|
function displayToolName(name) {
|
|
368
391
|
if (!name)
|
package/dist/tui-ink/welcome.js
CHANGED
|
@@ -78,10 +78,8 @@ function logoColors(theme) {
|
|
|
78
78
|
const COMPACT_LOGO = ["B", "U", "B", "B", "L", "E"];
|
|
79
79
|
const WIDE_LOGO_MIN_WIDTH = 52;
|
|
80
80
|
export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// items list — when the items grow back, ink replays the banner a second
|
|
84
|
-
// time into scrollback. Keep visibility decided purely by initial history.
|
|
81
|
+
// Keep banner visibility tied to the initial history, not transient overlays,
|
|
82
|
+
// so opening and closing a picker does not move it in the transcript.
|
|
85
83
|
if (startedWithVisibleHistory)
|
|
86
84
|
return false;
|
|
87
85
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebrain-ai/bubble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "A terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@
|
|
28
|
-
"@opentui/solid": "^0.1.99",
|
|
27
|
+
"@larksuiteoapi/node-sdk": "^1.65.0",
|
|
29
28
|
"@types/better-sqlite3": "^7.6.13",
|
|
30
29
|
"@types/react": "^19.2.14",
|
|
31
30
|
"@vue/language-server": "^3.2.7",
|
|
@@ -35,11 +34,10 @@
|
|
|
35
34
|
"ink": "^7.0.3",
|
|
36
35
|
"js-tiktoken": "^1.0.21",
|
|
37
36
|
"openai": "^4.77.0",
|
|
38
|
-
"opentui-spinner": "^0.0.6",
|
|
39
37
|
"picomatch": "^4.0.4",
|
|
38
|
+
"qrcode-terminal": "^0.12.0",
|
|
40
39
|
"react": "^19.2.6",
|
|
41
40
|
"shiki": "^4.0.2",
|
|
42
|
-
"solid-js": "^1.9.11",
|
|
43
41
|
"string-width": "^8.2.1",
|
|
44
42
|
"typescript-language-server": "^5.1.3",
|
|
45
43
|
"vscode-jsonrpc": "^8.2.1",
|
|
@@ -49,6 +47,7 @@
|
|
|
49
47
|
"@types/diff": "^7.0.0",
|
|
50
48
|
"@types/node": "^22.0.0",
|
|
51
49
|
"@types/picomatch": "^4.0.3",
|
|
50
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
52
51
|
"@vitest/coverage-v8": "^4.1.4",
|
|
53
52
|
"typescript": "^5.7.0",
|
|
54
53
|
"vitest": "^4.1.4"
|
package/dist/tui/clipboard.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function copyTextToClipboard(text: string): Promise<void>;
|
package/dist/tui/clipboard.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
export async function copyTextToClipboard(text) {
|
|
3
|
-
if (process.platform === "darwin") {
|
|
4
|
-
await writeToProcess("pbcopy", [], text);
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
if (process.platform === "win32") {
|
|
8
|
-
await writeToProcess("powershell", [
|
|
9
|
-
"-NoProfile",
|
|
10
|
-
"-Command",
|
|
11
|
-
"Set-Clipboard -Value ([Console]::In.ReadToEnd())",
|
|
12
|
-
], text);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const candidates = [
|
|
16
|
-
["wl-copy", []],
|
|
17
|
-
["xclip", ["-selection", "clipboard"]],
|
|
18
|
-
["xsel", ["--clipboard", "--input"]],
|
|
19
|
-
];
|
|
20
|
-
let lastError;
|
|
21
|
-
for (const [command, args] of candidates) {
|
|
22
|
-
try {
|
|
23
|
-
await writeToProcess(command, args, text);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
lastError = error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
throw lastError instanceof Error ? lastError : new Error("No clipboard command available");
|
|
31
|
-
}
|
|
32
|
-
function writeToProcess(command, args, input) {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const child = spawn(command, args, {
|
|
35
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
36
|
-
windowsHide: true,
|
|
37
|
-
});
|
|
38
|
-
let stderr = "";
|
|
39
|
-
child.stderr.setEncoding("utf8");
|
|
40
|
-
child.stderr.on("data", (chunk) => {
|
|
41
|
-
stderr += chunk;
|
|
42
|
-
});
|
|
43
|
-
child.on("error", reject);
|
|
44
|
-
child.on("close", (code) => {
|
|
45
|
-
if (code === 0) {
|
|
46
|
-
resolve();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
reject(new Error(stderr.trim() || `${command} exited with code ${code}`));
|
|
50
|
-
});
|
|
51
|
-
child.stdin.end(input);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { ToolResultMetadata, TokenUsage } from "../types.js";
|
|
2
|
-
export interface CompactionMeta {
|
|
3
|
-
turns: number;
|
|
4
|
-
messages: number;
|
|
5
|
-
tokensSaved: number;
|
|
6
|
-
summarySections: Array<{
|
|
7
|
-
label: string;
|
|
8
|
-
content: string;
|
|
9
|
-
}>;
|
|
10
|
-
contextWindow?: number;
|
|
11
|
-
compactedAt: number;
|
|
12
|
-
}
|
|
13
|
-
export interface DisplayMessage {
|
|
14
|
-
role: "user" | "assistant" | "error";
|
|
15
|
-
content: string;
|
|
16
|
-
reasoning?: string;
|
|
17
|
-
toolCalls?: DisplayToolCall[];
|
|
18
|
-
status?: "thinking" | "responding";
|
|
19
|
-
streaming?: boolean;
|
|
20
|
-
syntheticKind?: "ui_compact_card";
|
|
21
|
-
hiddenCount?: number;
|
|
22
|
-
compactionMeta?: CompactionMeta;
|
|
23
|
-
turnStartedAt?: number;
|
|
24
|
-
turnCompletedAt?: number;
|
|
25
|
-
turnUsage?: TokenUsage;
|
|
26
|
-
}
|
|
27
|
-
export interface DisplayToolCall {
|
|
28
|
-
id: string;
|
|
29
|
-
name: string;
|
|
30
|
-
args: Record<string, any>;
|
|
31
|
-
rawArguments?: string;
|
|
32
|
-
streamingArgs?: boolean;
|
|
33
|
-
/** During streaming, an approximate line count derived from `\n` escapes in rawArguments. */
|
|
34
|
-
streamingNewlineCount?: number;
|
|
35
|
-
status?: "pending" | "running" | "completed" | "error";
|
|
36
|
-
result?: string;
|
|
37
|
-
isError?: boolean;
|
|
38
|
-
metadata?: ToolResultMetadata;
|
|
39
|
-
startedAt?: number;
|
|
40
|
-
completedAt?: number;
|
|
41
|
-
}
|
|
42
|
-
export declare function compactDisplayMessages(messages: DisplayMessage[]): DisplayMessage[];
|
|
43
|
-
export declare function truncateText(value: string, maxChars: number): string;
|
|
44
|
-
export declare function formatCompactNumber(n: number): string;
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
const MAX_VISIBLE_MESSAGES = 80;
|
|
2
|
-
const FULL_DETAIL_WINDOW = 24;
|
|
3
|
-
const MAX_OLD_CONTENT_CHARS = 1200;
|
|
4
|
-
const MAX_OLD_REASONING_CHARS = 600;
|
|
5
|
-
const MAX_OLD_TOOL_RESULT_CHARS = 800;
|
|
6
|
-
const COMPACTION_SUMMARY_ITEMS = 6;
|
|
7
|
-
const COMPACTION_FILE_LIMIT = 8;
|
|
8
|
-
const TOOL_PATH_KEYS = ["file", "path", "paths", "filePath"];
|
|
9
|
-
export function compactDisplayMessages(messages) {
|
|
10
|
-
if (messages.length === 0) {
|
|
11
|
-
return messages;
|
|
12
|
-
}
|
|
13
|
-
let hiddenCount = 0;
|
|
14
|
-
let accumulatedTurns = 0;
|
|
15
|
-
let accumulatedTokens = 0;
|
|
16
|
-
const summarySections = [];
|
|
17
|
-
const withoutSynthetic = messages.filter((message) => {
|
|
18
|
-
if (message.syntheticKind !== "ui_compact_card") {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
hiddenCount += message.hiddenCount ?? 0;
|
|
22
|
-
if (message.compactionMeta) {
|
|
23
|
-
accumulatedTurns += message.compactionMeta.turns;
|
|
24
|
-
accumulatedTokens += message.compactionMeta.tokensSaved;
|
|
25
|
-
for (const section of message.compactionMeta.summarySections) {
|
|
26
|
-
summarySections.push(section);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
});
|
|
31
|
-
const overflow = Math.max(0, withoutSynthetic.length - MAX_VISIBLE_MESSAGES);
|
|
32
|
-
hiddenCount += overflow;
|
|
33
|
-
const visible = overflow > 0 ? withoutSynthetic.slice(overflow) : withoutSynthetic;
|
|
34
|
-
const detailStart = Math.max(0, visible.length - FULL_DETAIL_WINDOW);
|
|
35
|
-
const compacted = visible.map((message, index) => {
|
|
36
|
-
if (message.syntheticKind === "ui_compact_card") {
|
|
37
|
-
return message;
|
|
38
|
-
}
|
|
39
|
-
return index < detailStart ? compactDisplayMessage(message) : message;
|
|
40
|
-
});
|
|
41
|
-
if (hiddenCount === 0) {
|
|
42
|
-
return compacted;
|
|
43
|
-
}
|
|
44
|
-
const truncatedMessages = visible.slice(0, Math.max(1, detailStart));
|
|
45
|
-
const extractedMeta = extractCompactionMeta(truncatedMessages, hiddenCount, accumulatedTurns, accumulatedTokens, summarySections);
|
|
46
|
-
return [buildCompactCard(extractedMeta), ...compacted];
|
|
47
|
-
}
|
|
48
|
-
function extractCompactionMeta(truncatedMessages, hiddenCount, previousTurns, previousTokens, previousSections) {
|
|
49
|
-
const turnsInBatch = countUserTurns(truncatedMessages);
|
|
50
|
-
const totalTurns = previousTurns + turnsInBatch;
|
|
51
|
-
const messagesInBatch = truncatedMessages.length;
|
|
52
|
-
const totalMessages = hiddenCount;
|
|
53
|
-
const estimatedTokens = estimateTokenSavings(truncatedMessages);
|
|
54
|
-
const totalTokens = previousTokens + estimatedTokens;
|
|
55
|
-
const sections = [
|
|
56
|
-
...previousSections,
|
|
57
|
-
...extractSummarySections(truncatedMessages),
|
|
58
|
-
];
|
|
59
|
-
return {
|
|
60
|
-
turns: totalTurns,
|
|
61
|
-
messages: totalMessages,
|
|
62
|
-
tokensSaved: totalTokens > 0 ? totalTokens : estimatedTokens,
|
|
63
|
-
summarySections: mergeSummarySections(sections, COMPACTION_SUMMARY_ITEMS),
|
|
64
|
-
compactedAt: Date.now(),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function countUserTurns(messages) {
|
|
68
|
-
return messages.filter((message) => message.role === "user").length;
|
|
69
|
-
}
|
|
70
|
-
function estimateTokenSavings(messages) {
|
|
71
|
-
let chars = 0;
|
|
72
|
-
for (const message of messages) {
|
|
73
|
-
chars += message.content.length;
|
|
74
|
-
chars += (message.reasoning?.length ?? 0);
|
|
75
|
-
for (const tool of message.toolCalls ?? []) {
|
|
76
|
-
chars += (tool.result?.length ?? 0);
|
|
77
|
-
chars += JSON.stringify(tool.args).length;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return Math.ceil(chars / 4);
|
|
81
|
-
}
|
|
82
|
-
function extractSummarySections(messages) {
|
|
83
|
-
const sections = [];
|
|
84
|
-
const userMessages = messages
|
|
85
|
-
.filter((m) => m.role === "user")
|
|
86
|
-
.map((m) => m.content);
|
|
87
|
-
if (userMessages.length > 0) {
|
|
88
|
-
sections.push({
|
|
89
|
-
label: "Progress",
|
|
90
|
-
content: userMessages.slice(0, 5).map((c) => `- ${shorten(c, 100)}`).join("\n"),
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
const assistantInsights = messages
|
|
94
|
-
.filter((m) => m.role === "assistant" && m.content.trim())
|
|
95
|
-
.map((m) => m.content.trim());
|
|
96
|
-
if (assistantInsights.length > 0) {
|
|
97
|
-
sections.push({
|
|
98
|
-
label: "Decisions",
|
|
99
|
-
content: assistantInsights.slice(0, 3).map((c) => `- ${shorten(c, 120)}`).join("\n"),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
const files = collectFiles(messages);
|
|
103
|
-
if (files.length > 0) {
|
|
104
|
-
sections.push({
|
|
105
|
-
label: "Files",
|
|
106
|
-
content: files.slice(0, COMPACTION_FILE_LIMIT).join(", "),
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
const toolFindings = collectToolFindings(messages);
|
|
110
|
-
if (toolFindings.length > 0) {
|
|
111
|
-
sections.push({
|
|
112
|
-
label: "Tools",
|
|
113
|
-
content: toolFindings.slice(0, 5).map((f) => `- ${f}`).join("\n"),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return sections;
|
|
117
|
-
}
|
|
118
|
-
function collectFiles(messages) {
|
|
119
|
-
const files = new Set();
|
|
120
|
-
for (const message of messages) {
|
|
121
|
-
for (const tool of message.toolCalls ?? []) {
|
|
122
|
-
for (const key of TOOL_PATH_KEYS) {
|
|
123
|
-
const value = tool.args[key];
|
|
124
|
-
if (typeof value === "string" && value) {
|
|
125
|
-
files.add(value);
|
|
126
|
-
}
|
|
127
|
-
if (Array.isArray(value)) {
|
|
128
|
-
for (const item of value) {
|
|
129
|
-
if (typeof item === "string" && item) {
|
|
130
|
-
files.add(item);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return [...files].slice(0, COMPACTION_FILE_LIMIT);
|
|
138
|
-
}
|
|
139
|
-
function collectToolFindings(messages) {
|
|
140
|
-
const findings = [];
|
|
141
|
-
for (const message of messages) {
|
|
142
|
-
for (const tool of message.toolCalls ?? []) {
|
|
143
|
-
if (tool.result && tool.result.length > 0) {
|
|
144
|
-
findings.push(`${tool.name}: ${shorten(tool.result, 80)}`);
|
|
145
|
-
if (findings.length >= 10)
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (findings.length >= 10)
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
return findings;
|
|
153
|
-
}
|
|
154
|
-
function mergeSummarySections(sections, maxItems) {
|
|
155
|
-
const merged = new Map();
|
|
156
|
-
for (const section of sections) {
|
|
157
|
-
const existing = merged.get(section.label);
|
|
158
|
-
if (existing) {
|
|
159
|
-
merged.set(section.label, `${existing}\n${section.content}`);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
merged.set(section.label, section.content);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return [...merged.entries()]
|
|
166
|
-
.map(([label, content]) => ({ label, content }))
|
|
167
|
-
.slice(0, maxItems);
|
|
168
|
-
}
|
|
169
|
-
function buildCompactCard(meta) {
|
|
170
|
-
const formatNum = (n) => {
|
|
171
|
-
if (n >= 1_000_000)
|
|
172
|
-
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
173
|
-
if (n >= 1_000)
|
|
174
|
-
return `${(n / 1_000).toFixed(1)}K`;
|
|
175
|
-
return String(n);
|
|
176
|
-
};
|
|
177
|
-
const parts = [];
|
|
178
|
-
if (meta.turns > 0) {
|
|
179
|
-
parts.push(`${meta.turns} turn${meta.turns === 1 ? "" : "s"}`);
|
|
180
|
-
}
|
|
181
|
-
if (meta.messages > 0) {
|
|
182
|
-
parts.push(`${meta.messages} message${meta.messages === 1 ? "" : "s"}`);
|
|
183
|
-
}
|
|
184
|
-
if (meta.tokensSaved > 0) {
|
|
185
|
-
parts.push(`~${formatNum(meta.tokensSaved)} tokens`);
|
|
186
|
-
}
|
|
187
|
-
const statsLine = parts.length > 0 ? `┃ ${parts.join(" · ")}` : "";
|
|
188
|
-
const sectionLines = [];
|
|
189
|
-
for (const section of meta.summarySections) {
|
|
190
|
-
sectionLines.push(`┃ ${section.label}: ${section.content.split("\n")[0]}`);
|
|
191
|
-
}
|
|
192
|
-
const content = [statsLine, ...sectionLines].filter(Boolean).join("\n");
|
|
193
|
-
return {
|
|
194
|
-
role: "assistant",
|
|
195
|
-
content,
|
|
196
|
-
syntheticKind: "ui_compact_card",
|
|
197
|
-
hiddenCount: meta.messages,
|
|
198
|
-
compactionMeta: meta,
|
|
199
|
-
status: "responding",
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
function compactDisplayMessage(message) {
|
|
203
|
-
if (message.syntheticKind === "ui_compact_card") {
|
|
204
|
-
return message;
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
...message,
|
|
208
|
-
content: truncateText(message.content, MAX_OLD_CONTENT_CHARS),
|
|
209
|
-
reasoning: message.reasoning
|
|
210
|
-
? truncateText(message.reasoning, MAX_OLD_REASONING_CHARS)
|
|
211
|
-
: message.reasoning,
|
|
212
|
-
toolCalls: message.toolCalls?.map((toolCall) => ({
|
|
213
|
-
...toolCall,
|
|
214
|
-
result: toolCall.result
|
|
215
|
-
? truncateText(toolCall.result, MAX_OLD_TOOL_RESULT_CHARS)
|
|
216
|
-
: toolCall.result,
|
|
217
|
-
})),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
export function truncateText(value, maxChars) {
|
|
221
|
-
if (value.length <= maxChars) {
|
|
222
|
-
return value;
|
|
223
|
-
}
|
|
224
|
-
const head = Math.max(1, Math.floor(maxChars * 0.7));
|
|
225
|
-
const tail = Math.max(1, maxChars - head - 32);
|
|
226
|
-
const omitted = value.length - head - tail;
|
|
227
|
-
const separator = "─".repeat(12);
|
|
228
|
-
return `${value.slice(0, head)}\n${separator} ✂ ${omitted} chars truncated ${separator}\n${value.slice(-tail)}`;
|
|
229
|
-
}
|
|
230
|
-
function shorten(text, maxChars) {
|
|
231
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
232
|
-
if (normalized.length <= maxChars) {
|
|
233
|
-
return normalized;
|
|
234
|
-
}
|
|
235
|
-
return `${normalized.slice(0, maxChars - 1)}…`;
|
|
236
|
-
}
|
|
237
|
-
export function formatCompactNumber(n) {
|
|
238
|
-
if (n >= 1_000_000)
|
|
239
|
-
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
240
|
-
if (n >= 1_000)
|
|
241
|
-
return `${(n / 1_000).toFixed(1)}K`;
|
|
242
|
-
return String(n);
|
|
243
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export type EscapeConfirmationDecision = {
|
|
2
|
-
action: "arm";
|
|
3
|
-
expiresAt: number;
|
|
4
|
-
} | {
|
|
5
|
-
action: "confirm";
|
|
6
|
-
};
|
|
7
|
-
export declare class EscapeConfirmationGate {
|
|
8
|
-
private readonly windowMs;
|
|
9
|
-
private armedRunId;
|
|
10
|
-
private deadline;
|
|
11
|
-
constructor(windowMs: number);
|
|
12
|
-
press(runId: number, now?: number): EscapeConfirmationDecision;
|
|
13
|
-
isArmed(runId: number, now?: number): boolean;
|
|
14
|
-
clear(): void;
|
|
15
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export class EscapeConfirmationGate {
|
|
2
|
-
windowMs;
|
|
3
|
-
armedRunId;
|
|
4
|
-
deadline = 0;
|
|
5
|
-
constructor(windowMs) {
|
|
6
|
-
this.windowMs = windowMs;
|
|
7
|
-
}
|
|
8
|
-
press(runId, now = Date.now()) {
|
|
9
|
-
if (this.armedRunId === runId && now <= this.deadline) {
|
|
10
|
-
this.clear();
|
|
11
|
-
return { action: "confirm" };
|
|
12
|
-
}
|
|
13
|
-
this.armedRunId = runId;
|
|
14
|
-
this.deadline = now + this.windowMs;
|
|
15
|
-
return { action: "arm", expiresAt: this.deadline };
|
|
16
|
-
}
|
|
17
|
-
isArmed(runId, now = Date.now()) {
|
|
18
|
-
if (this.armedRunId !== runId)
|
|
19
|
-
return false;
|
|
20
|
-
if (now > this.deadline) {
|
|
21
|
-
this.clear();
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
clear() {
|
|
27
|
-
this.armedRunId = undefined;
|
|
28
|
-
this.deadline = 0;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export interface AtContext {
|
|
2
|
-
start: number;
|
|
3
|
-
end: number;
|
|
4
|
-
query: string;
|
|
5
|
-
}
|
|
6
|
-
export interface FileSuggestion {
|
|
7
|
-
path: string;
|
|
8
|
-
score: number;
|
|
9
|
-
}
|
|
10
|
-
export interface ExpandedMention {
|
|
11
|
-
path: string;
|
|
12
|
-
bytes: number;
|
|
13
|
-
truncated: boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface ExpandResult {
|
|
16
|
-
text: string;
|
|
17
|
-
expanded: ExpandedMention[];
|
|
18
|
-
missing: string[];
|
|
19
|
-
skipped: Array<{
|
|
20
|
-
path: string;
|
|
21
|
-
reason: string;
|
|
22
|
-
bytes?: number;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
export declare function findAtContext(text: string, cursor: number): AtContext | null;
|
|
26
|
-
export declare function filterFileSuggestions(files: string[], query: string, limit?: number): FileSuggestion[];
|
|
27
|
-
export declare function listProjectFiles(cwd: string): Promise<string[]>;
|
|
28
|
-
export declare function invalidateFileListCache(cwd?: string): void;
|
|
29
|
-
export declare function expandAtMentions(text: string, cwd: string): Promise<ExpandResult>;
|