@bubblebrain-ai/bubble 0.0.28 → 0.0.30
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 +23 -3
- package/dist/agent/categories.d.ts +2 -0
- package/dist/agent/categories.js +4 -0
- package/dist/agent/child-runner.d.ts +5 -1
- package/dist/agent/child-runner.js +35 -2
- package/dist/agent/profiles.js +3 -0
- package/dist/agent/structured-output.d.ts +37 -0
- package/dist/agent/structured-output.js +193 -0
- package/dist/agent/subagent-control.d.ts +3 -0
- package/dist/agent/subagent-scheduler.d.ts +10 -0
- package/dist/agent/subagent-scheduler.js +31 -0
- package/dist/agent/workflow/control.d.ts +37 -0
- package/dist/agent/workflow/control.js +20 -0
- package/dist/agent/workflow/errors.d.ts +16 -0
- package/dist/agent/workflow/errors.js +24 -0
- package/dist/agent/workflow/runtime.d.ts +75 -0
- package/dist/agent/workflow/runtime.js +237 -0
- package/dist/agent.d.ts +105 -0
- package/dist/agent.js +425 -17
- package/dist/context/compact-llm.d.ts +10 -1
- package/dist/context/compact-llm.js +13 -5
- package/dist/context/compact.d.ts +30 -0
- package/dist/context/compact.js +34 -17
- package/dist/goal/format.d.ts +1 -1
- package/dist/goal/format.js +1 -1
- package/dist/network/provider-transport.d.ts +9 -0
- package/dist/network/provider-transport.js +19 -1
- package/dist/provider.d.ts +14 -0
- package/dist/provider.js +24 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.js +33 -1
- package/dist/slash-commands/commands.js +41 -113
- package/dist/slash-commands/types.d.ts +14 -9
- package/dist/tools/agent-lifecycle.d.ts +6 -0
- package/dist/tools/agent-lifecycle.js +285 -0
- package/dist/tools/child-tools.d.ts +10 -0
- package/dist/tools/child-tools.js +12 -0
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.js +9 -0
- package/dist/tui/image-display.d.ts +6 -0
- package/dist/tui/image-display.js +26 -1
- package/dist/tui-ink/app.d.ts +0 -18
- package/dist/tui-ink/app.js +168 -230
- package/dist/tui-ink/compaction-progress.d.ts +19 -0
- package/dist/tui-ink/compaction-progress.js +74 -0
- package/dist/tui-ink/input-box.d.ts +10 -1
- package/dist/tui-ink/input-box.js +56 -16
- package/dist/tui-ink/markdown.d.ts +18 -0
- package/dist/tui-ink/markdown.js +172 -16
- package/dist/tui-ink/message-list.d.ts +1 -2
- package/dist/tui-ink/message-list.js +50 -107
- package/dist/tui-ink/run.js +5 -0
- package/dist/tui-ink/subagent-inspector.d.ts +17 -0
- package/dist/tui-ink/subagent-inspector.js +189 -0
- package/dist/tui-ink/subagent-view.d.ts +47 -0
- package/dist/tui-ink/subagent-view.js +163 -0
- package/dist/tui-ink/terminal-env.d.ts +15 -0
- package/dist/tui-ink/terminal-env.js +22 -0
- package/dist/tui-ink/use-terminal-size.js +33 -6
- package/dist/tui-ink/width.d.ts +18 -0
- package/dist/tui-ink/width.js +130 -0
- package/dist/types.d.ts +35 -0
- package/package.json +2 -1
package/dist/tui-ink/app.js
CHANGED
|
@@ -6,8 +6,9 @@ import { isHiddenToolMetadata } from "../agent/discovery-barrier.js";
|
|
|
6
6
|
import { SessionManager } from "../session.js";
|
|
7
7
|
import { registry as slashRegistry } from "../slash-commands/index.js";
|
|
8
8
|
import { UserConfig, maskKey } from "../config.js";
|
|
9
|
-
import { InputBox, isCtrlCInput, } from "./input-box.js";
|
|
9
|
+
import { InputBox, isCtrlCInput, isCtrlLetterInput, } from "./input-box.js";
|
|
10
10
|
import { MessageList } from "./message-list.js";
|
|
11
|
+
import { isMultiplexedTerminal } from "./terminal-env.js";
|
|
11
12
|
import { appendTextPart, appendToolPart, compactDisplayMessages, contentFromParts, latestCompactionSummary, moveStatusMessageToEnd, nextDisplayMessageKey, setUserInputStatus, snapshotDisplayParts, stripInterruptedAssistantMarker, toolCallsFromParts, } from "./display-history.js";
|
|
12
13
|
import { AgentRunInputQueue } from "../agent/input-controller.js";
|
|
13
14
|
import { paletteFor, ThemeProvider, useTheme } from "./theme.js";
|
|
@@ -23,6 +24,7 @@ import { useTerminalSize } from "./use-terminal-size.js";
|
|
|
23
24
|
import { WelcomeBanner, shouldShowWelcomeBanner } from "./welcome.js";
|
|
24
25
|
import { expandAtMentions } from "./file-mentions.js";
|
|
25
26
|
import { TodosPanel } from "./todos.js";
|
|
27
|
+
import { CompactionProgressCard } from "./compaction-progress.js";
|
|
26
28
|
import { PlanConfirm } from "./plan-confirm.js";
|
|
27
29
|
import { ApprovalDialog } from "./approval/approval-dialog.js";
|
|
28
30
|
import { getNextPermissionMode } from "../permission/mode.js";
|
|
@@ -35,6 +37,8 @@ import { formatImageUserDisplayText, nextImageDisplayLabelStart } from "../tui/i
|
|
|
35
37
|
import { decideStartingSubmitFingerprint, submitPayloadFingerprint } from "./submit-dedupe.js";
|
|
36
38
|
import { isQueuedInputForCurrentSession, queuedAndPendingDisplayKeys, } from "./input-queue.js";
|
|
37
39
|
import { SessionPicker } from "./session-picker.js";
|
|
40
|
+
import { SubagentInspector } from "./subagent-inspector.js";
|
|
41
|
+
import { collectSubagentGroups, subagentSummary } from "./subagent-view.js";
|
|
38
42
|
import { sessionDisplayName } from "../tui/session-display.js";
|
|
39
43
|
import { parseGoalCommand } from "../goal/command.js";
|
|
40
44
|
import { continuationPrompt, initialPrompt } from "../goal/prompts.js";
|
|
@@ -223,34 +227,10 @@ function withMessageKey(message) {
|
|
|
223
227
|
// 40ms keeps perceived latency invisible while capping layout work at 25fps.
|
|
224
228
|
const STREAMING_FLUSH_INTERVAL_MS = 40;
|
|
225
229
|
export const INK_LOCAL_SLASH_COMMANDS = [
|
|
226
|
-
{
|
|
227
|
-
name: "thinking",
|
|
228
|
-
description: "Toggle thinking block visibility",
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
name: "toggle-thinking",
|
|
232
|
-
description: "Toggle thinking block visibility",
|
|
233
|
-
},
|
|
234
230
|
{
|
|
235
231
|
name: "goal",
|
|
236
232
|
description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)",
|
|
237
233
|
},
|
|
238
|
-
{
|
|
239
|
-
name: "trace",
|
|
240
|
-
description: "Toggle verbose trace output",
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
name: "verbose",
|
|
244
|
-
description: "Toggle verbose trace output",
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: "debug",
|
|
248
|
-
description: "Toggle verbose trace output",
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: "write-previews",
|
|
252
|
-
description: "Toggle write preview expansion",
|
|
253
|
-
},
|
|
254
234
|
];
|
|
255
235
|
export function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, onExit }) {
|
|
256
236
|
const [sessionManager, setSessionManager] = useState(initialSessionManager);
|
|
@@ -277,7 +257,31 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
277
257
|
const [streamingReasoning, setStreamingReasoning] = useState("");
|
|
278
258
|
const [streamingTools, setStreamingTools] = useState([]);
|
|
279
259
|
const [streamingParts, setStreamingParts] = useState([]);
|
|
280
|
-
|
|
260
|
+
// Live subagent groups for the inspector opened from the subagent entry line;
|
|
261
|
+
// recomputed each render so it reflects members as their events stream into the transcript.
|
|
262
|
+
const subagentGroups = useMemo(() => collectSubagentGroups(messages, streamingTools), [messages, streamingTools]);
|
|
263
|
+
const subagentMembers = useMemo(() => subagentGroups.flatMap((g) => g.members), [subagentGroups]);
|
|
264
|
+
// Down-arrow from the composer focuses the subagent entry line; Enter then
|
|
265
|
+
// opens the inspector, Esc/Up returns to the composer (Claude Code parity).
|
|
266
|
+
const [subagentEntryFocused, setSubagentEntryFocused] = useState(false);
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
if (subagentMembers.length === 0 && subagentEntryFocused)
|
|
269
|
+
setSubagentEntryFocused(false);
|
|
270
|
+
}, [subagentMembers.length, subagentEntryFocused]);
|
|
271
|
+
// Live progress for a manual `/compact` run (null when not compacting).
|
|
272
|
+
const [compaction, setCompaction] = useState(null);
|
|
273
|
+
// Normalize agent.thinking against the current model's supported levels so the
|
|
274
|
+
// banner displays the *effective* level, not a stale user-config value like
|
|
275
|
+
// "xhigh" when switching to a model that only supports ["high","max","off"].
|
|
276
|
+
const [thinkingLevel, setThinkingLevel] = useState(() => {
|
|
277
|
+
const modelParts = agent.model.includes(":")
|
|
278
|
+
? agent.model.split(":")
|
|
279
|
+
: [agent.providerId || safeRegistry.getDefault()?.id || "openai", agent.model];
|
|
280
|
+
const providerId = modelParts[0];
|
|
281
|
+
const modelId = modelParts.slice(1).join(":");
|
|
282
|
+
const availableLevels = getAvailableThinkingLevels(providerId, modelId);
|
|
283
|
+
return normalizeThinkingLevel(agent.thinking, availableLevels);
|
|
284
|
+
});
|
|
281
285
|
const [permissionMode, setPermissionMode] = useState(agent.mode);
|
|
282
286
|
const [todos, setTodos] = useState(() => agent.getTodos());
|
|
283
287
|
const [goalLine, setGoalLine] = useState("");
|
|
@@ -292,9 +296,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
292
296
|
const [composerDraft, setComposerDraft] = useState(null);
|
|
293
297
|
const [keyProviderId, setKeyProviderId] = useState(null);
|
|
294
298
|
const [showThinking, setShowThinking] = useState(false);
|
|
295
|
-
const [expandedToolOutput, setExpandedToolOutput] = useState(false);
|
|
296
299
|
const [verboseTrace, setVerboseTrace] = useState(false);
|
|
297
|
-
const [sidebarMode, setSidebarMode] = useState("collapsed");
|
|
298
300
|
const startedWithVisibleHistoryRef = useRef(messages.some((message) => message.syntheticKind !== "ui_summary"));
|
|
299
301
|
const { columns: terminalColumns, rows: terminalRows } = useTerminalSize();
|
|
300
302
|
const showWelcome = shouldShowWelcomeBanner({
|
|
@@ -310,6 +312,12 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
310
312
|
// MessageList so Ink discards its already-printed rows and re-prints the
|
|
311
313
|
// rebuilt list onto a freshly-cleared screen instead of appending duplicates.
|
|
312
314
|
const [staticGeneration, setStaticGeneration] = useState(0);
|
|
315
|
+
const reprintTranscript = useCallback(() => {
|
|
316
|
+
if (process.stdout.isTTY) {
|
|
317
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
318
|
+
}
|
|
319
|
+
setStaticGeneration((generation) => generation + 1);
|
|
320
|
+
}, []);
|
|
313
321
|
// Steer/queue while the agent runs:
|
|
314
322
|
// Enter steers the current run via the agent's input controller; Tab (or an
|
|
315
323
|
// ineligible input) queues for the next turn. Both render placeholder user
|
|
@@ -521,7 +529,25 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
521
529
|
// also frees the arrow keys entirely for composer history.
|
|
522
530
|
if (pendingPlan || pendingApproval || pendingQuestion || pendingFeedback || statsPanel)
|
|
523
531
|
return;
|
|
524
|
-
|
|
532
|
+
// Subagent entry is focused (the composer is disabled): Enter opens the
|
|
533
|
+
// inspector, Up/Esc returns to the composer. Other keys just return focus.
|
|
534
|
+
if (subagentEntryFocused && !pickerMode) {
|
|
535
|
+
if (key.return) {
|
|
536
|
+
setSubagentEntryFocused(false);
|
|
537
|
+
setStatsPanel(null);
|
|
538
|
+
setPickerMode("agents");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (key.escape || key.upArrow) {
|
|
542
|
+
setSubagentEntryFocused(false);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (key.downArrow)
|
|
546
|
+
return; // stay focused
|
|
547
|
+
setSubagentEntryFocused(false);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (isCtrlLetterInput(input, key, "p") && !pickerMode && !activeAbortRef.current) {
|
|
525
551
|
setStatsPanel(null);
|
|
526
552
|
setPickerMode("slash");
|
|
527
553
|
return;
|
|
@@ -536,7 +562,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
536
562
|
}
|
|
537
563
|
return;
|
|
538
564
|
}
|
|
539
|
-
if (
|
|
565
|
+
if (isCtrlLetterInput(input, key, "t") && !pickerMode) {
|
|
540
566
|
setShowThinking((current) => {
|
|
541
567
|
const next = !current;
|
|
542
568
|
addMessage("assistant", next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
@@ -544,12 +570,13 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
544
570
|
});
|
|
545
571
|
return;
|
|
546
572
|
}
|
|
547
|
-
if (
|
|
573
|
+
if (isCtrlLetterInput(input, key, "o") && !pickerMode) {
|
|
548
574
|
setVerboseTrace((v) => !v);
|
|
575
|
+
reprintTranscript();
|
|
549
576
|
return;
|
|
550
577
|
}
|
|
551
578
|
// Ctrl+R: cycle thinking level (formerly Shift+Tab)
|
|
552
|
-
if (
|
|
579
|
+
if (isCtrlLetterInput(input, key, "r") && !pickerMode) {
|
|
553
580
|
const modelParts = agent.model.includes(":")
|
|
554
581
|
? agent.model.split(":")
|
|
555
582
|
: [agent.providerId || safeRegistry.getDefault()?.id || "openai", agent.model];
|
|
@@ -593,12 +620,9 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
593
620
|
// un-printed, so we wipe the screen + scrollback and bump the Static key:
|
|
594
621
|
// Ink then re-prints the rebuilt list fresh instead of appending duplicates.
|
|
595
622
|
const resetTranscript = useCallback((updater) => {
|
|
596
|
-
|
|
597
|
-
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
598
|
-
}
|
|
599
|
-
setStaticGeneration((generation) => generation + 1);
|
|
623
|
+
reprintTranscript();
|
|
600
624
|
updateDisplayMessages(updater);
|
|
601
|
-
}, [updateDisplayMessages]);
|
|
625
|
+
}, [reprintTranscript, updateDisplayMessages]);
|
|
602
626
|
const addMessage = useCallback((role, content) => {
|
|
603
627
|
updateDisplayMessages((prev) => [...prev, withMessageKey({ role, content })]);
|
|
604
628
|
}, [updateDisplayMessages]);
|
|
@@ -642,7 +666,11 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
642
666
|
// <Static>), so clearing React state is not enough — resetTranscript wipes
|
|
643
667
|
// the screen + scrollback and re-prints the (now empty) transcript.
|
|
644
668
|
resetTranscript(() => []);
|
|
645
|
-
|
|
669
|
+
// The todos panel renders off React state, not the transcript, so wiping
|
|
670
|
+
// messages alone leaves a stale To-Do list on screen. /clear already reset
|
|
671
|
+
// the agent's todos; mirror that into the UI (same as session switch).
|
|
672
|
+
setTodos(agent.getTodos());
|
|
673
|
+
}, [resetTranscript, agent]);
|
|
646
674
|
// Render a placeholder user row for input waiting to enter the run.
|
|
647
675
|
const addStatusUserMessage = useCallback((content, status) => {
|
|
648
676
|
const key = nextDisplayMessageKey("user");
|
|
@@ -709,21 +737,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
709
737
|
const { description: _drop, ...rest } = base;
|
|
710
738
|
setPendingFeedback({ base: rest, initialDescription });
|
|
711
739
|
}, [agent]);
|
|
712
|
-
const sidebarFits = terminalColumns > 120;
|
|
713
|
-
const sidebarVisible = sidebarMode === "expanded" ? sidebarFits : sidebarMode === "auto" && sidebarFits;
|
|
714
|
-
const currentSidebarCommandState = useCallback((mode = sidebarMode) => {
|
|
715
|
-
const visible = mode === "expanded" ? sidebarFits : mode === "auto" && sidebarFits;
|
|
716
|
-
return { mode, visible, active: visible };
|
|
717
|
-
}, [sidebarFits, sidebarMode]);
|
|
718
|
-
const toggleSidebar = useCallback(() => {
|
|
719
|
-
const next = sidebarVisible ? "collapsed" : "expanded";
|
|
720
|
-
setSidebarMode(next);
|
|
721
|
-
return currentSidebarCommandState(next);
|
|
722
|
-
}, [currentSidebarCommandState, sidebarVisible]);
|
|
723
|
-
const applySidebarMode = useCallback((mode) => {
|
|
724
|
-
setSidebarMode(mode);
|
|
725
|
-
return currentSidebarCommandState(mode);
|
|
726
|
-
}, [currentSidebarCommandState]);
|
|
727
740
|
const openSessionPicker = useCallback(() => {
|
|
728
741
|
if (activeAbortRef.current) {
|
|
729
742
|
addMessage("error", "Stop the current run before switching sessions.");
|
|
@@ -1238,7 +1251,14 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1238
1251
|
if (steer) {
|
|
1239
1252
|
pendingSteersRef.current.delete(event.id);
|
|
1240
1253
|
setPendingSteerCount(pendingSteersRef.current.size);
|
|
1241
|
-
|
|
1254
|
+
// Moving the steer placeholder out of the live region into
|
|
1255
|
+
// <Static> is a pure append (it was never in the settled list,
|
|
1256
|
+
// only the dynamic block). Off a multiplexer Ink erases the
|
|
1257
|
+
// vacated live rows in place, so a plain append avoids the
|
|
1258
|
+
// full-screen reprint flash. Under tmux/screen the in-place
|
|
1259
|
+
// erase can't reach scrolled rows, so keep the clean reprint.
|
|
1260
|
+
const commit = isMultiplexedTerminal() ? resetTranscript : updateDisplayMessages;
|
|
1261
|
+
commit((prev) => moveStatusMessageToEnd(prev, steer.displayKey));
|
|
1242
1262
|
}
|
|
1243
1263
|
break;
|
|
1244
1264
|
}
|
|
@@ -1287,7 +1307,24 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1287
1307
|
commitAssistantMessage();
|
|
1288
1308
|
if (err instanceof AgentAbortError || err?.name === "AbortError") {
|
|
1289
1309
|
runCancelled = true;
|
|
1290
|
-
|
|
1310
|
+
// commitAssistantMessage already appended the partial answer; the
|
|
1311
|
+
// interrupt is otherwise a pure append (the partial + a "Interrupted"
|
|
1312
|
+
// row). Off a multiplexer, append just the interrupt row so settled
|
|
1313
|
+
// history is never reprinted — no flash. Under tmux/screen, fall back
|
|
1314
|
+
// to the full reprint that rebuilds from the canonical agent.messages.
|
|
1315
|
+
if (isMultiplexedTerminal()) {
|
|
1316
|
+
resetTranscript(() => reconstructDisplayMessages(agent.messages));
|
|
1317
|
+
}
|
|
1318
|
+
else {
|
|
1319
|
+
updateDisplayMessages((prev) => [
|
|
1320
|
+
...prev,
|
|
1321
|
+
withMessageKey({
|
|
1322
|
+
role: "assistant",
|
|
1323
|
+
content: "Interrupted by user",
|
|
1324
|
+
syntheticKind: "ui_interrupt",
|
|
1325
|
+
}),
|
|
1326
|
+
]);
|
|
1327
|
+
}
|
|
1291
1328
|
}
|
|
1292
1329
|
else {
|
|
1293
1330
|
runErrored = true;
|
|
@@ -1479,30 +1516,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1479
1516
|
requestExit();
|
|
1480
1517
|
return;
|
|
1481
1518
|
}
|
|
1482
|
-
if (/^\/(?:thinking|toggle-thinking)(?:\s|$)/.test(input.trim())) {
|
|
1483
|
-
setShowThinking((current) => {
|
|
1484
|
-
const next = !current;
|
|
1485
|
-
addMessage("assistant", next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
1486
|
-
return next;
|
|
1487
|
-
});
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
if (/^\/(?:trace|verbose|debug)(?:\s|$)/.test(input.trim())) {
|
|
1491
|
-
setVerboseTrace((current) => {
|
|
1492
|
-
const next = !current;
|
|
1493
|
-
addMessage("assistant", next ? "Verbose trace visible" : "Compact trace visible");
|
|
1494
|
-
return next;
|
|
1495
|
-
});
|
|
1496
|
-
return;
|
|
1497
|
-
}
|
|
1498
|
-
if (/^\/write-previews(?:\s|$)/.test(input.trim())) {
|
|
1499
|
-
setExpandedToolOutput((current) => {
|
|
1500
|
-
const next = !current;
|
|
1501
|
-
addMessage("assistant", next ? "Write previews expanded" : "Write previews collapsed");
|
|
1502
|
-
return next;
|
|
1503
|
-
});
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
1519
|
if (/^\/goal(?:\s|$)/.test(input.trim())) {
|
|
1507
1520
|
await handleGoalCommand(input);
|
|
1508
1521
|
return;
|
|
@@ -1541,9 +1554,8 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1541
1554
|
getThemeMode: () => themeMode,
|
|
1542
1555
|
getResolvedTheme: () => themeResolved,
|
|
1543
1556
|
setThemeMode: applyThemeMode,
|
|
1544
|
-
toggleSidebar,
|
|
1545
|
-
setSidebarMode: applySidebarMode,
|
|
1546
1557
|
openStats: openStatsPanel,
|
|
1558
|
+
compactionProgress: setCompaction,
|
|
1547
1559
|
});
|
|
1548
1560
|
if (handled) {
|
|
1549
1561
|
if (agent.mode !== permissionMode) {
|
|
@@ -1607,7 +1619,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1607
1619
|
setStartingSubmit(null);
|
|
1608
1620
|
}
|
|
1609
1621
|
}
|
|
1610
|
-
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, fillComposer, prepareSubmitDisplay, safeRegistry, safeSkillRegistry, updateDisplayMessages, queueInput, submitSteer, requestExit,
|
|
1622
|
+
}, [addMessage, agent, args.cwd, openPicker, openSessionPicker, openRewindPicker, openStatsPanel, createProvider, currentSessionFile, fillComposer, prepareSubmitDisplay, safeRegistry, safeSkillRegistry, updateDisplayMessages, queueInput, submitSteer, requestExit, setStartingSubmit]);
|
|
1611
1623
|
// Drain the queue once the run ends and no modal needs the user first.
|
|
1612
1624
|
// The placeholder row is removed right before resubmitting — handleSubmit
|
|
1613
1625
|
// renders the message again as a regular user row.
|
|
@@ -1672,78 +1684,80 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1672
1684
|
// tail, pickers, composer, footer) occupies the live region. Letting it size
|
|
1673
1685
|
// to its content keeps the composer pinned just below the latest output the
|
|
1674
1686
|
// way ordinary shell programs do.
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1687
|
+
const mainWidth = Math.max(40, terminalColumns);
|
|
1688
|
+
return (_jsx(ThemeProvider, { value: palette, children: _jsxs(Box, { flexDirection: "column", width: mainWidth, backgroundColor: palette.background, children: [_jsx(MessageList, { messages: messages, streamingContent: streamingContent, streamingReasoning: streamingReasoning, streamingTools: streamingTools, streamingParts: streamingParts, terminalColumns: mainWidth, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: approvalHint, nowTick: nowTick, welcomeBanner: welcomeBannerNode, staticGeneration: staticGeneration, paddingX: 1, maxStreamRows: Math.max(6, terminalRows - 10) }), pickerMode === "model" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ModelPicker, { registry: safeRegistry, current: agent.model, currentThinkingLevel: thinkingLevel, recent: userConfig.getRecentModels(), onSelect: handleModelSelect, onCancel: closePicker }) })), pickerMode === "provider" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1689
|
+
.filter((p) => isUserVisibleProvider(p.id))
|
|
1690
|
+
.map((p) => {
|
|
1691
|
+
const configured = safeRegistry.getConfigured().find((item) => item.id === p.id);
|
|
1692
|
+
const configuredLabel = configured?.apiKey ? "configured" : "needs key";
|
|
1693
|
+
return {
|
|
1694
|
+
id: p.id,
|
|
1695
|
+
name: `${p.name} [${configuredLabel}]`,
|
|
1696
|
+
enabled: true,
|
|
1697
|
+
};
|
|
1698
|
+
}), current: currentProviderId, onSelect: handleProviderSelect, onCancel: closePicker }) })), pickerMode === "provider-add" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1699
|
+
.filter((p) => isUserVisibleProvider(p.id))
|
|
1700
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleProviderAddSelect, onCancel: closePicker, title: "Add Provider" }) })), pickerMode === "login" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: BUILTIN_PROVIDERS
|
|
1701
|
+
.filter((p) => isUserVisibleProvider(p.id) && safeRegistry.supportsOAuth(p.id))
|
|
1702
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLoginProviderSelect, onCancel: closePicker, title: "Select Login Provider" }) })), pickerMode === "logout" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ProviderPicker, { providers: safeRegistry.getConfigured()
|
|
1703
|
+
.filter((p) => safeRegistry.getAuthStorage().has(p.id))
|
|
1704
|
+
.map((p) => ({ id: p.id, name: p.name, enabled: true })), current: currentProviderId, onSelect: handleLogoutProviderSelect, onCancel: closePicker, title: "Select Logout Provider" }) })), pickerMode === "key" && keyTarget && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(KeyPicker, { providerName: keyTarget.name, onSubmit: handleKeySubmit, onCancel: () => {
|
|
1705
|
+
closePicker();
|
|
1706
|
+
setKeyProviderId(null);
|
|
1707
|
+
} }) })), pickerMode === "skill" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SkillPicker, { skills: safeSkillRegistry.summaries(), onSelect: (name) => {
|
|
1708
|
+
fillComposer(`/${name} `);
|
|
1709
|
+
closePicker();
|
|
1710
|
+
}, onCancel: closePicker }) })), pickerMode === "slash" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(CommandPalette, { items: commandPaletteItems, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (item) => {
|
|
1711
|
+
closePicker();
|
|
1712
|
+
if (item.action === "insert-skill") {
|
|
1713
|
+
fillComposer(`/${item.value} `);
|
|
1714
|
+
}
|
|
1715
|
+
else {
|
|
1716
|
+
void handleSubmit(item.command);
|
|
1717
|
+
}
|
|
1718
|
+
}, onCancel: closePicker }) })), pickerMode === "mcp-reconnect" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(McpReconnectPicker, { items: mcpReconnectItems, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (item) => {
|
|
1719
|
+
closePicker();
|
|
1720
|
+
void handleSubmit(item.command);
|
|
1721
|
+
}, onCancel: closePicker }) })), pickerMode === "session" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SessionPicker, { currentCwd: args.cwd, currentSessions: SessionManager.summarizeSessionsForCwd(args.cwd), allSessions: SessionManager.listAllSessions(), onSelect: handleSessionSelect, onCancel: closePicker }) })), pickerMode === "agents" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(SubagentInspector, { groups: subagentGroups, onCancel: closePicker }) })), pickerMode === "rewind" && sessionManager && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(RewindPicker, { sessionManager: sessionManager, terminalColumns: mainWidth, terminalRows: terminalRows, onSelect: (command) => {
|
|
1722
|
+
closePicker();
|
|
1723
|
+
void handleSubmit(command);
|
|
1724
|
+
}, onCancel: closePicker }) })), pickerMode === "feishu-setup" && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(FeishuSetupPicker, { onComplete: (summary) => {
|
|
1725
|
+
closePicker();
|
|
1726
|
+
addMessage("assistant", summary);
|
|
1727
|
+
}, onCancel: () => {
|
|
1728
|
+
closePicker();
|
|
1729
|
+
addMessage("assistant", "已取消 Feishu setup。");
|
|
1730
|
+
} }) })), statsPanel && !pickerMode && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(StatsPanel, { panel: statsPanel, terminalColumns: mainWidth, terminalRows: terminalRows, onRangeChange: (range) => setStatsPanel((current) => current ? { ...current, range } : current), onCancel: closeStatsPanel }) })), todos.length > 0 && !pickerMode && !statsPanel && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(TodosPanel, { todos: todos, terminalColumns: terminalColumns }) })), pendingPlan && !pickerMode && !statsPanel && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(PlanConfirm, { initialPlan: pendingPlan.plan, onApprove: (finalPlan) => {
|
|
1731
|
+
const resolve = pendingPlan.resolve;
|
|
1732
|
+
setPendingPlan(null);
|
|
1733
|
+
resolve({ action: "approve", plan: finalPlan });
|
|
1734
|
+
}, onReject: (reason) => {
|
|
1735
|
+
const resolve = pendingPlan.resolve;
|
|
1736
|
+
setPendingPlan(null);
|
|
1737
|
+
resolve({ action: "reject", reason });
|
|
1738
|
+
} }) })), pendingApproval && !pickerMode && !statsPanel && !pendingPlan && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(ApprovalDialog, { request: pendingApproval.request, onDecision: (decision) => {
|
|
1739
|
+
const resolve = pendingApproval.resolve;
|
|
1740
|
+
setPendingApproval(null);
|
|
1741
|
+
resolve(decision);
|
|
1742
|
+
}, onAllowBashPrefix: (prefix) => {
|
|
1743
|
+
bashAllowlist?.add(prefix);
|
|
1744
|
+
} }) })), pendingQuestion && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingFeedback && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(QuestionDialog, { request: pendingQuestion, onSubmit: (answers) => {
|
|
1745
|
+
questionController?.reply(pendingQuestion.id, answers);
|
|
1746
|
+
setPendingQuestion(null);
|
|
1747
|
+
}, onCancel: () => {
|
|
1748
|
+
questionController?.reject(pendingQuestion.id);
|
|
1749
|
+
setPendingQuestion(null);
|
|
1750
|
+
} }) })), pendingFeedback && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(FeedbackDialog, { base: pendingFeedback.base, initialDescription: pendingFeedback.initialDescription, onDismiss: () => setPendingFeedback(null), onResult: (result) => {
|
|
1751
|
+
if (result.kind === "success") {
|
|
1752
|
+
addMessage("assistant", `Feedback submitted: ${result.url}`);
|
|
1753
|
+
}
|
|
1754
|
+
else if (result.kind === "error") {
|
|
1755
|
+
addMessage("error", `Feedback failed: ${result.message}`);
|
|
1756
|
+
}
|
|
1757
|
+
} }) })), !isExiting && compaction && (_jsx(Box, { flexShrink: 0, backgroundColor: palette.background, children: _jsx(CompactionProgressCard, { progress: compaction }) })), !isExiting && isRunning && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && (_jsx(Box, { paddingX: 1, paddingBottom: 1, flexShrink: 0, backgroundColor: palette.background, children: _jsx(WaitingIndicator, { tools: streamingTools, hasStreamingText: streamingContent.length > 0, hasStreamingReasoning: streamingReasoning.length > 0, streamedChars: streamingContent.length + streamingReasoning.length, nowTick: nowTick, pendingSteerCount: pendingSteerCount, queuedCount: queuedCount }) })), !isExiting && !pickerMode && !statsPanel && (_jsx(Box, { paddingBottom: 1, flexShrink: 0, backgroundColor: palette.background, children: _jsx(InputBox, { onSubmit: handleSubmit, onQueue: isRunning ? queueInput : undefined, onArrowDownAtBottom: () => {
|
|
1758
|
+
if (subagentMembers.length > 0 && !pickerMode)
|
|
1759
|
+
setSubagentEntryFocused(true);
|
|
1760
|
+
}, disabled: !!pendingPlan || !!pendingApproval || !!pendingQuestion || !!pendingFeedback || !!statsPanel || subagentEntryFocused, cursorResetEpoch: cursorResetEpoch, draftText: composerDraft?.text, draftEpoch: composerDraft?.epoch, onDraftApplied: clearComposerDraft, skillRegistry: safeSkillRegistry, localSlashCommands: [...INK_LOCAL_SLASH_COMMANDS], terminalColumns: mainWidth, cwd: args.cwd, sessionFile: currentSessionFile(), nextImageLabelStart: nextImageDisplayLabelStartRef.current }) })), !isExiting && !pickerMode && !statsPanel && !pendingPlan && !pendingApproval && !pendingQuestion && !pendingFeedback && subagentMembers.length > 0 && (_jsxs(Box, { paddingX: 1, flexShrink: 0, backgroundColor: palette.background, children: [_jsx(Text, { bold: subagentEntryFocused, color: subagentEntryFocused ? palette.accent : palette.toolName, children: subagentEntryFocused ? "> ↳ " : " ↳ " }), _jsxs(Text, { color: subagentEntryFocused ? palette.accent : palette.muted, children: [subagentMembers.length, " subagent", subagentMembers.length === 1 ? "" : "s", " \u00B7 ", subagentSummary(subagentMembers), " \u00B7 "] }), _jsx(Text, { color: palette.accent, children: subagentEntryFocused ? "Enter open · Esc back" : "↓ to inspect traces" })] })), !isExiting && (_jsx(Box, { flexShrink: 0, children: _jsx(FooterBar, { data: buildFooterData({ mode: permissionMode, goalLine }) }) }))] }) }));
|
|
1747
1761
|
}
|
|
1748
1762
|
function buildCommandPaletteItems(skillRegistry) {
|
|
1749
1763
|
const items = new Map();
|
|
@@ -2031,82 +2045,6 @@ function StatsPanel({ panel, terminalColumns, terminalRows, onRangeChange, onCan
|
|
|
2031
2045
|
return (_jsx(Text, { color: heading ? theme.accent : undefined, bold: heading, children: line || " " }, key));
|
|
2032
2046
|
}) }), maxScroll > 0 && (_jsxs(Text, { color: theme.muted, children: [scroll + 1, "-", Math.min(lines.length, scroll + maxVisible), " of ", lines.length] }))] }));
|
|
2033
2047
|
}
|
|
2034
|
-
function summarizeMcpStates(states) {
|
|
2035
|
-
const summary = { connected: 0, starting: 0, failed: 0, disabled: 0, tools: 0 };
|
|
2036
|
-
for (const state of states) {
|
|
2037
|
-
if (state.status.kind === "connected") {
|
|
2038
|
-
summary.connected += 1;
|
|
2039
|
-
summary.tools += state.status.tools.length;
|
|
2040
|
-
}
|
|
2041
|
-
else if (state.status.kind === "failed") {
|
|
2042
|
-
summary.failed += 1;
|
|
2043
|
-
}
|
|
2044
|
-
else {
|
|
2045
|
-
summary.disabled += 1;
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
return summary;
|
|
2049
|
-
}
|
|
2050
|
-
function summarizeLspStatuses(statuses) {
|
|
2051
|
-
const summary = { connected: 0, starting: 0, failed: 0, disabled: 0 };
|
|
2052
|
-
for (const status of statuses) {
|
|
2053
|
-
if (status.status === "connected")
|
|
2054
|
-
summary.connected += 1;
|
|
2055
|
-
else if (status.status === "starting")
|
|
2056
|
-
summary.starting += 1;
|
|
2057
|
-
else
|
|
2058
|
-
summary.failed += 1;
|
|
2059
|
-
}
|
|
2060
|
-
return summary;
|
|
2061
|
-
}
|
|
2062
|
-
function formatStatusCount(summary) {
|
|
2063
|
-
const parts = [];
|
|
2064
|
-
if (summary.connected > 0)
|
|
2065
|
-
parts.push(`${summary.connected} up`);
|
|
2066
|
-
if (summary.starting > 0)
|
|
2067
|
-
parts.push(`${summary.starting} starting`);
|
|
2068
|
-
if (summary.failed > 0)
|
|
2069
|
-
parts.push(`${summary.failed} failed`);
|
|
2070
|
-
if (summary.disabled > 0)
|
|
2071
|
-
parts.push(`${summary.disabled} disabled`);
|
|
2072
|
-
return parts.join(" · ") || "none";
|
|
2073
|
-
}
|
|
2074
|
-
function SidebarSection({ title, children }) {
|
|
2075
|
-
const theme = useTheme();
|
|
2076
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: title }), children] }));
|
|
2077
|
-
}
|
|
2078
|
-
function SidebarRow({ label, value, color, }) {
|
|
2079
|
-
const theme = useTheme();
|
|
2080
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.muted, children: [label, ": "] }), _jsx(Text, { color: color ?? theme.userMessageText, children: value })] }));
|
|
2081
|
-
}
|
|
2082
|
-
function InkSidebar({ width, agent, sessionManager, cwd, mode, goalLine, todos, mcpManager, lspService, }) {
|
|
2083
|
-
const theme = useTheme();
|
|
2084
|
-
const innerWidth = Math.max(12, width - 4);
|
|
2085
|
-
const todoCounts = todos.reduce((acc, todo) => {
|
|
2086
|
-
acc[todo.status] = (acc[todo.status] ?? 0) + 1;
|
|
2087
|
-
return acc;
|
|
2088
|
-
}, {});
|
|
2089
|
-
const todoSummary = todos.length === 0
|
|
2090
|
-
? "none"
|
|
2091
|
-
: [
|
|
2092
|
-
todoCounts.in_progress ? `${todoCounts.in_progress} active` : "",
|
|
2093
|
-
todoCounts.pending ? `${todoCounts.pending} pending` : "",
|
|
2094
|
-
todoCounts.completed ? `${todoCounts.completed} done` : "",
|
|
2095
|
-
].filter(Boolean).join(" · ");
|
|
2096
|
-
const mcpStates = mcpManager?.getStates() ?? [];
|
|
2097
|
-
const mcpSummary = summarizeMcpStates(mcpStates);
|
|
2098
|
-
const lspSummary = lspService?.isDisabled()
|
|
2099
|
-
? { connected: 0, starting: 0, failed: 0, disabled: 1 }
|
|
2100
|
-
: summarizeLspStatuses(lspService?.status() ?? []);
|
|
2101
|
-
const latestMcpFailure = mcpStates.find((state) => state.status.kind === "failed");
|
|
2102
|
-
const latestLspFailure = lspService?.status().find((status) => status.status === "error");
|
|
2103
|
-
const sessionTitle = truncate(sessionDisplayName(sessionManager), innerWidth);
|
|
2104
|
-
const modelLabel = agent.model ? displayModel(agent.model) : "not selected";
|
|
2105
|
-
const route = agent.providerId
|
|
2106
|
-
? `${agent.providerId}/${modelLabel}`
|
|
2107
|
-
: modelLabel;
|
|
2108
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: "100%", borderStyle: "single", borderColor: theme.border, paddingX: 1, paddingY: 1, flexShrink: 0, children: [_jsx(Text, { color: theme.borderActive, bold: true, children: "Session" }), _jsx(Text, { color: theme.userMessageText, children: sessionTitle }), _jsx(Text, { color: theme.muted, children: truncate(friendlyCwd(cwd), innerWidth) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(SidebarSection, { title: "Runtime", children: [_jsx(SidebarRow, { label: "model", value: truncate(route, innerWidth - 7) }), _jsx(SidebarRow, { label: "mode", value: mode, color: mode === "bypassPermissions" ? theme.warning : theme.userMessageText }), _jsx(SidebarRow, { label: "thinking", value: agent.thinking || "off" })] }), goalLine && (_jsx(SidebarSection, { title: "Goal", children: _jsx(Text, { color: theme.userMessageText, children: truncate(goalLine, innerWidth) }) })), _jsx(SidebarSection, { title: "Todos", children: _jsx(Text, { color: todos.length > 0 ? theme.userMessageText : theme.muted, children: truncate(todoSummary, innerWidth) }) }), _jsxs(SidebarSection, { title: "MCP", children: [_jsx(Text, { color: mcpSummary.failed > 0 ? theme.warning : theme.userMessageText, children: truncate(`${formatStatusCount(mcpSummary)}${mcpSummary.tools > 0 ? ` · ${mcpSummary.tools} tools` : ""}`, innerWidth) }), latestMcpFailure?.status.kind === "failed" && (_jsx(Text, { color: theme.muted, children: truncate(latestMcpFailure.status.error, innerWidth) }))] }), _jsxs(SidebarSection, { title: "LSP", children: [_jsx(Text, { color: lspSummary.failed > 0 ? theme.warning : theme.userMessageText, children: truncate(formatStatusCount(lspSummary), innerWidth) }), latestLspFailure?.message && (_jsx(Text, { color: theme.muted, children: truncate(latestLspFailure.message, innerWidth) }))] })] })] }));
|
|
2109
|
-
}
|
|
2110
2048
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2111
2049
|
const GENERIC_PHRASES = [
|
|
2112
2050
|
"mapping the workspace",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CompactionProgress } from "../slash-commands/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map a compaction phase + streamed length onto a 0..1 bar fill. There is no
|
|
4
|
+
* true denominator for a single LLM call, so the curve is honest by design:
|
|
5
|
+
* it ramps but never reaches 1.0 (or even 0.9) until the work is actually done.
|
|
6
|
+
*/
|
|
7
|
+
export declare function compactionFraction(progress: CompactionProgress): number;
|
|
8
|
+
export declare function renderBar(fraction: number, width?: number): {
|
|
9
|
+
filled: string;
|
|
10
|
+
empty: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Bottom-stack progress card for a manual `/compact` run. Mount it only while a
|
|
14
|
+
* compaction is in flight (i.e. render conditionally on a non-null progress) so
|
|
15
|
+
* its elapsed-time clock resets per run.
|
|
16
|
+
*/
|
|
17
|
+
export declare function CompactionProgressCard({ progress }: {
|
|
18
|
+
progress: CompactionProgress | null;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|