@prestyj/cli 4.2.56 → 4.2.75
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/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -5
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +11 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +3 -1
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +28 -5
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +86 -10
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +1 -1
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/model-registry.js +8 -8
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +2 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +18 -4
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/telegram.d.ts.map +1 -1
- package/dist/core/telegram.js +3 -0
- package/dist/core/telegram.js.map +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +1 -0
- package/dist/interactive.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +21 -3
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +4 -7
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +4 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/overflow.d.ts +6 -0
- package/dist/tools/overflow.d.ts.map +1 -0
- package/dist/tools/overflow.js +13 -0
- package/dist/tools/overflow.js.map +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +6 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/truncate-utils.test.js +11 -11
- package/dist/tools/truncate-utils.test.js.map +1 -1
- package/dist/tools/truncate.d.ts +6 -4
- package/dist/tools/truncate.d.ts.map +1 -1
- package/dist/tools/truncate.js +12 -10
- package/dist/tools/truncate.js.map +1 -1
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +261 -62
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/buddy/Buddy.d.ts +15 -0
- package/dist/ui/buddy/Buddy.d.ts.map +1 -0
- package/dist/ui/buddy/Buddy.js +118 -0
- package/dist/ui/buddy/Buddy.js.map +1 -0
- package/dist/ui/buddy/gacha.d.ts +8 -0
- package/dist/ui/buddy/gacha.d.ts.map +1 -0
- package/dist/ui/buddy/gacha.js +94 -0
- package/dist/ui/buddy/gacha.js.map +1 -0
- package/dist/ui/buddy/species.d.ts +52 -0
- package/dist/ui/buddy/species.d.ts.map +1 -0
- package/dist/ui/buddy/species.js +257 -0
- package/dist/ui/buddy/species.js.map +1 -0
- package/dist/ui/components/ActivityIndicator.d.ts +10 -2
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +79 -22
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AnimationContext.d.ts +14 -2
- package/dist/ui/components/AnimationContext.d.ts.map +1 -1
- package/dist/ui/components/AnimationContext.js +54 -8
- package/dist/ui/components/AnimationContext.js.map +1 -1
- package/dist/ui/components/AssistantMessage.d.ts +2 -1
- package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
- package/dist/ui/components/AssistantMessage.js +5 -4
- package/dist/ui/components/AssistantMessage.js.map +1 -1
- package/dist/ui/components/BackgroundTasksBar.js +2 -2
- package/dist/ui/components/BackgroundTasksBar.js.map +1 -1
- package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
- package/dist/ui/components/CompactionNotice.js +2 -1
- package/dist/ui/components/CompactionNotice.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +3 -1
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +23 -48
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +2 -1
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/Markdown.d.ts +11 -6
- package/dist/ui/components/Markdown.d.ts.map +1 -1
- package/dist/ui/components/Markdown.js +170 -18
- package/dist/ui/components/Markdown.js.map +1 -1
- package/dist/ui/components/MessageResponse.d.ts +25 -0
- package/dist/ui/components/MessageResponse.d.ts.map +1 -0
- package/dist/ui/components/MessageResponse.js +42 -0
- package/dist/ui/components/MessageResponse.js.map +1 -0
- package/dist/ui/components/NoSelect.d.ts +20 -0
- package/dist/ui/components/NoSelect.d.ts.map +1 -0
- package/dist/ui/components/NoSelect.js +14 -0
- package/dist/ui/components/NoSelect.js.map +1 -0
- package/dist/ui/components/PlanProgress.d.ts +7 -0
- package/dist/ui/components/PlanProgress.d.ts.map +1 -0
- package/dist/ui/components/PlanProgress.js +30 -0
- package/dist/ui/components/PlanProgress.js.map +1 -0
- package/dist/ui/components/Ratchet.d.ts +20 -0
- package/dist/ui/components/Ratchet.d.ts.map +1 -0
- package/dist/ui/components/Ratchet.js +30 -0
- package/dist/ui/components/Ratchet.js.map +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ServerToolExecution.js +5 -6
- package/dist/ui/components/ServerToolExecution.js.map +1 -1
- package/dist/ui/components/Spinner.d.ts +8 -2
- package/dist/ui/components/Spinner.d.ts.map +1 -1
- package/dist/ui/components/Spinner.js +52 -5
- package/dist/ui/components/Spinner.js.map +1 -1
- package/dist/ui/components/StreamingArea.d.ts +2 -1
- package/dist/ui/components/StreamingArea.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.js +9 -44
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +6 -4
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/components/ThinkingBlock.d.ts +2 -1
- package/dist/ui/components/ThinkingBlock.d.ts.map +1 -1
- package/dist/ui/components/ThinkingBlock.js +2 -2
- package/dist/ui/components/ThinkingBlock.js.map +1 -1
- package/dist/ui/components/ThinkingIndicator.d.ts.map +1 -1
- package/dist/ui/components/ThinkingIndicator.js +2 -1
- package/dist/ui/components/ThinkingIndicator.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts +1 -0
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +44 -15
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolGroupExecution.js +21 -17
- package/dist/ui/components/ToolGroupExecution.js.map +1 -1
- package/dist/ui/components/ToolUseLoader.d.ts +19 -0
- package/dist/ui/components/ToolUseLoader.d.ts.map +1 -0
- package/dist/ui/components/ToolUseLoader.js +42 -0
- package/dist/ui/components/ToolUseLoader.js.map +1 -0
- package/dist/ui/components/index.d.ts +1 -1
- package/dist/ui/components/index.d.ts.map +1 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/constants/figures.d.ts +30 -0
- package/dist/ui/constants/figures.d.ts.map +1 -0
- package/dist/ui/constants/figures.js +41 -0
- package/dist/ui/constants/figures.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts +16 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +49 -141
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useBlink.d.ts +11 -0
- package/dist/ui/hooks/useBlink.d.ts.map +1 -0
- package/dist/ui/hooks/useBlink.js +19 -0
- package/dist/ui/hooks/useBlink.js.map +1 -0
- package/dist/ui/hooks/useMinDisplayTime.d.ts +11 -0
- package/dist/ui/hooks/useMinDisplayTime.d.ts.map +1 -0
- package/dist/ui/hooks/useMinDisplayTime.js +34 -0
- package/dist/ui/hooks/useMinDisplayTime.js.map +1 -0
- package/dist/ui/hooks/useTerminalProgress.d.ts +2 -0
- package/dist/ui/hooks/useTerminalProgress.d.ts.map +1 -0
- package/dist/ui/hooks/useTerminalProgress.js +98 -0
- package/dist/ui/hooks/useTerminalProgress.js.map +1 -0
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +2 -1
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/spinner-frames.d.ts +1 -0
- package/dist/ui/spinner-frames.d.ts.map +1 -1
- package/dist/ui/spinner-frames.js +15 -5
- package/dist/ui/spinner-frames.js.map +1 -1
- package/dist/ui/theme/dark.json +2 -1
- package/dist/ui/theme/light.json +2 -1
- package/dist/ui/theme/theme.d.ts +1 -0
- package/dist/ui/theme/theme.d.ts.map +1 -1
- package/dist/ui/utils/markdown-cache.d.ts +12 -0
- package/dist/ui/utils/markdown-cache.d.ts.map +1 -0
- package/dist/ui/utils/markdown-cache.js +43 -0
- package/dist/ui/utils/markdown-cache.js.map +1 -0
- package/dist/ui/utils/word-diff.d.ts +11 -0
- package/dist/ui/utils/word-diff.d.ts.map +1 -0
- package/dist/ui/utils/word-diff.js +23 -0
- package/dist/ui/utils/word-diff.js.map +1 -0
- package/dist/utils/plan-steps.d.ts +41 -0
- package/dist/utils/plan-steps.d.ts.map +1 -0
- package/dist/utils/plan-steps.js +91 -0
- package/dist/utils/plan-steps.js.map +1 -0
- package/package.json +3 -3
package/dist/ui/App.js
CHANGED
|
@@ -27,8 +27,9 @@ import { TaskOverlay } from "./components/TaskOverlay.js";
|
|
|
27
27
|
import { SkillsOverlay } from "./components/SkillsOverlay.js";
|
|
28
28
|
import { BackgroundTasksBar } from "./components/BackgroundTasksBar.js";
|
|
29
29
|
import { useTheme } from "./theme/theme.js";
|
|
30
|
-
import { useAnimationTick, deriveFrame } from "./components/AnimationContext.js";
|
|
30
|
+
import { useAnimationTick, useAnimationActive, deriveFrame, } from "./components/AnimationContext.js";
|
|
31
31
|
import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
|
|
32
|
+
import { useTerminalProgress } from "./hooks/useTerminalProgress.js";
|
|
32
33
|
import { getGitBranch } from "../utils/git.js";
|
|
33
34
|
import { getModel, getContextWindow } from "../core/model-registry.js";
|
|
34
35
|
import { SessionManager } from "../core/session-manager.js";
|
|
@@ -39,8 +40,10 @@ import { estimateConversationTokens } from "../core/compaction/token-estimator.j
|
|
|
39
40
|
import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
|
|
40
41
|
import { loadCustomCommands } from "../core/custom-commands.js";
|
|
41
42
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
43
|
+
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
42
44
|
import { getMCPServers } from "../core/mcp/index.js";
|
|
43
45
|
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd } from "./live-item-flush.js";
|
|
46
|
+
import { Buddy } from "./buddy/Buddy.js";
|
|
44
47
|
// ── Provider Error Hints ──────────────────────────────────
|
|
45
48
|
/** Detect provider-side errors and return a user-facing hint. */
|
|
46
49
|
function getProviderErrorHint(message) {
|
|
@@ -102,6 +105,41 @@ function compactHistory(items) {
|
|
|
102
105
|
return compacted;
|
|
103
106
|
}
|
|
104
107
|
// flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
|
|
108
|
+
/** Check whether an item is still active (running spinner, pending result). */
|
|
109
|
+
function isActiveItem(item) {
|
|
110
|
+
switch (item.kind) {
|
|
111
|
+
case "tool_start":
|
|
112
|
+
case "server_tool_start":
|
|
113
|
+
case "compacting":
|
|
114
|
+
return true;
|
|
115
|
+
case "tool_group":
|
|
116
|
+
return item.tools.some((t) => t.status === "running");
|
|
117
|
+
case "subagent_group":
|
|
118
|
+
return item.agents.some((a) => a.status === "running");
|
|
119
|
+
default:
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Partition live items into completed (flushable to Static) and still-active.
|
|
125
|
+
* Completed items precede active ones — we flush the longest contiguous prefix
|
|
126
|
+
* of completed items to keep ordering stable.
|
|
127
|
+
*/
|
|
128
|
+
function partitionCompleted(items) {
|
|
129
|
+
// Find the first active item — everything before it is safe to flush
|
|
130
|
+
const firstActiveIdx = items.findIndex(isActiveItem);
|
|
131
|
+
if (firstActiveIdx === -1) {
|
|
132
|
+
// All items are completed
|
|
133
|
+
return { flushed: items, remaining: [] };
|
|
134
|
+
}
|
|
135
|
+
if (firstActiveIdx === 0) {
|
|
136
|
+
return { flushed: [], remaining: items };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
flushed: items.slice(0, firstActiveIdx),
|
|
140
|
+
remaining: items.slice(firstActiveIdx),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
105
143
|
// ── Duration summary ─────────────────────────────────────
|
|
106
144
|
function formatDuration(ms) {
|
|
107
145
|
const totalSec = Math.round(ms / 1000);
|
|
@@ -276,10 +314,16 @@ export function App(props) {
|
|
|
276
314
|
const messagesRef = useRef(props.messages);
|
|
277
315
|
const [planAutoExpand, setPlanAutoExpand] = useState(false);
|
|
278
316
|
const approvedPlanPathRef = useRef(undefined);
|
|
317
|
+
const planStepsRef = useRef([]);
|
|
318
|
+
const [planSteps, setPlanSteps] = useState([]);
|
|
279
319
|
const nextIdRef = useRef(0);
|
|
280
320
|
const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
|
|
281
321
|
const sessionPathRef = useRef(props.sessionPath);
|
|
282
322
|
const persistedIndexRef = useRef(messagesRef.current.length);
|
|
323
|
+
/** Last actual API-reported input token count (from turn_end). */
|
|
324
|
+
const lastActualTokensRef = useRef(0);
|
|
325
|
+
/** Timestamp of last compaction — used for time-based cooldown. */
|
|
326
|
+
const lastCompactionTimeRef = useRef(0);
|
|
283
327
|
const getId = () => String(nextIdRef.current++);
|
|
284
328
|
// Two-phase flush: items waiting to be moved to Static history after the
|
|
285
329
|
// live area has been cleared and Ink has committed the smaller output.
|
|
@@ -379,13 +423,15 @@ export function App(props) {
|
|
|
379
423
|
persistedIndexRef.current = allMsgs.length;
|
|
380
424
|
}, []);
|
|
381
425
|
// ── Compaction ─────────────────────────────────────────
|
|
382
|
-
// Load settings for auto-compaction
|
|
426
|
+
// Load settings for auto-compaction + buddy
|
|
383
427
|
const settingsRef = useRef(null);
|
|
428
|
+
const [buddyEnabled, setBuddyEnabled] = useState(false);
|
|
384
429
|
useEffect(() => {
|
|
385
430
|
if (props.settingsFile) {
|
|
386
431
|
const sm = new SettingsManager(props.settingsFile);
|
|
387
432
|
sm.load().then(() => {
|
|
388
433
|
settingsRef.current = sm;
|
|
434
|
+
setBuddyEnabled(sm.get("buddyEnabled") ?? false);
|
|
389
435
|
});
|
|
390
436
|
}
|
|
391
437
|
}, [props.settingsFile]);
|
|
@@ -442,6 +488,11 @@ export function App(props) {
|
|
|
442
488
|
* transformContext callback for the agent loop.
|
|
443
489
|
* Called before each LLM call and on context overflow.
|
|
444
490
|
* Checks if auto-compaction is needed and runs it.
|
|
491
|
+
*
|
|
492
|
+
* Uses actual API-reported token counts (from previous turn_end) when
|
|
493
|
+
* available, falling back to the character-based estimate. A 30-second
|
|
494
|
+
* cooldown prevents repeated compaction — matching the pattern used by
|
|
495
|
+
* Mysti, openclaw, and other real-world agent frameworks.
|
|
445
496
|
*/
|
|
446
497
|
const transformContext = useCallback(async (messages, options) => {
|
|
447
498
|
const settings = settingsRef.current;
|
|
@@ -449,13 +500,26 @@ export function App(props) {
|
|
|
449
500
|
const threshold = settings?.get("compactThreshold") ?? 0.8;
|
|
450
501
|
// Force-compact on context overflow regardless of settings
|
|
451
502
|
if (options?.force) {
|
|
452
|
-
|
|
503
|
+
const result = await compactConversation(messages);
|
|
504
|
+
lastCompactionTimeRef.current = Date.now();
|
|
505
|
+
lastActualTokensRef.current = 0; // Reset stale pre-compaction count
|
|
506
|
+
return result;
|
|
453
507
|
}
|
|
454
508
|
if (!autoCompact)
|
|
455
509
|
return messages;
|
|
510
|
+
// Time-based cooldown: skip if compaction ran within the last 30 seconds
|
|
511
|
+
if (Date.now() - lastCompactionTimeRef.current < 30_000) {
|
|
512
|
+
log("INFO", "compaction", `Skipping compaction — cooldown active`);
|
|
513
|
+
return messages;
|
|
514
|
+
}
|
|
456
515
|
const contextWindow = getContextWindow(currentModel);
|
|
457
|
-
|
|
458
|
-
|
|
516
|
+
// Prefer actual API-reported tokens over char-based estimate
|
|
517
|
+
const actualTokens = lastActualTokensRef.current > 0 ? lastActualTokensRef.current : undefined;
|
|
518
|
+
if (shouldCompact(messages, contextWindow, threshold, actualTokens)) {
|
|
519
|
+
const result = await compactConversation(messages);
|
|
520
|
+
lastCompactionTimeRef.current = Date.now();
|
|
521
|
+
lastActualTokensRef.current = 0; // Reset stale pre-compaction count
|
|
522
|
+
return result;
|
|
459
523
|
}
|
|
460
524
|
return messages;
|
|
461
525
|
}, [currentModel, compactConversation]);
|
|
@@ -535,8 +599,33 @@ export function App(props) {
|
|
|
535
599
|
}, {
|
|
536
600
|
onComplete: useCallback(() => {
|
|
537
601
|
persistNewMessages();
|
|
538
|
-
|
|
602
|
+
// Auto-clear plan progress and approved plan when all steps are completed
|
|
603
|
+
const steps = planStepsRef.current;
|
|
604
|
+
if (steps.length > 0 && steps.every((s) => s.completed)) {
|
|
605
|
+
planStepsRef.current = [];
|
|
606
|
+
setPlanSteps([]);
|
|
607
|
+
approvedPlanPathRef.current = undefined;
|
|
608
|
+
// Rebuild system prompt to remove the completed plan from context
|
|
609
|
+
void (async () => {
|
|
610
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, undefined);
|
|
611
|
+
if (messagesRef.current[0]?.role === "system") {
|
|
612
|
+
messagesRef.current[0] = { role: "system", content: newPrompt };
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
615
|
+
}
|
|
616
|
+
}, [persistNewMessages, planMode, props.cwd, props.skills]),
|
|
539
617
|
onTurnText: useCallback((text, thinking, thinkingMs) => {
|
|
618
|
+
// Track [DONE:n] markers for plan step progress
|
|
619
|
+
if (planStepsRef.current.length > 0) {
|
|
620
|
+
const completed = findCompletedMarkers(text);
|
|
621
|
+
if (completed.size > 0) {
|
|
622
|
+
const updated = markStepsCompleted(planStepsRef.current, completed);
|
|
623
|
+
if (updated !== planStepsRef.current) {
|
|
624
|
+
planStepsRef.current = updated;
|
|
625
|
+
setPlanSteps(updated);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
540
629
|
// Flush all completed items from the previous turn to Static history.
|
|
541
630
|
// This keeps liveItems bounded per-turn, preventing Ink's live area from
|
|
542
631
|
// growing unbounded, which makes Ink's live-area re-renders expensive.
|
|
@@ -545,11 +634,22 @@ export function App(props) {
|
|
|
545
634
|
if (flushed.length > 0) {
|
|
546
635
|
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
547
636
|
}
|
|
548
|
-
|
|
637
|
+
const displayText = planStepsRef.current.length > 0 ? stripDoneMarkers(text) : text;
|
|
638
|
+
return [{ kind: "assistant", text: displayText, thinking, thinkingMs, id: getId() }];
|
|
549
639
|
});
|
|
550
640
|
}, []),
|
|
551
641
|
onToolStart: useCallback((toolCallId, name, args) => {
|
|
552
642
|
log("INFO", "tool", `Tool call started: ${name}`, { id: toolCallId });
|
|
643
|
+
// Flush completed items (assistant text, finished tools) to Static
|
|
644
|
+
// before adding tool UI. Keeping both in the live area makes it tall
|
|
645
|
+
// and causes Ink's cursor math to clip the top.
|
|
646
|
+
setLiveItems((prev) => {
|
|
647
|
+
const { flushed, remaining } = partitionCompleted(prev);
|
|
648
|
+
if (flushed.length > 0) {
|
|
649
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
650
|
+
}
|
|
651
|
+
return remaining;
|
|
652
|
+
});
|
|
553
653
|
if (name === "subagent") {
|
|
554
654
|
// Create or update the sub-agent group item
|
|
555
655
|
const newAgent = {
|
|
@@ -656,7 +756,12 @@ export function App(props) {
|
|
|
656
756
|
};
|
|
657
757
|
const next = [...prev];
|
|
658
758
|
next[groupIdx] = { ...group, agents: updatedAgents };
|
|
659
|
-
|
|
759
|
+
// Flush completed items to Static to keep the live area small
|
|
760
|
+
const { flushed, remaining } = partitionCompleted(next);
|
|
761
|
+
if (flushed.length > 0) {
|
|
762
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
763
|
+
}
|
|
764
|
+
return remaining;
|
|
660
765
|
});
|
|
661
766
|
}
|
|
662
767
|
else {
|
|
@@ -664,59 +769,87 @@ export function App(props) {
|
|
|
664
769
|
// Check if this tool is in a tool_group
|
|
665
770
|
const groupIdx = prev.findIndex((item) => item.kind === "tool_group" &&
|
|
666
771
|
item.tools.some((t) => t.toolCallId === toolCallId));
|
|
772
|
+
let updated;
|
|
667
773
|
if (groupIdx !== -1) {
|
|
668
774
|
const group = prev[groupIdx];
|
|
669
|
-
|
|
670
|
-
|
|
775
|
+
updated = [...prev];
|
|
776
|
+
updated[groupIdx] = {
|
|
671
777
|
...group,
|
|
672
778
|
tools: group.tools.map((t) => t.toolCallId === toolCallId
|
|
673
779
|
? { ...t, status: "done", result, isError }
|
|
674
780
|
: t),
|
|
675
781
|
};
|
|
676
|
-
return next;
|
|
677
782
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
783
|
+
else {
|
|
784
|
+
// Find the matching tool_start and replace it with tool_done
|
|
785
|
+
const startIdx = prev.findIndex((item) => item.kind === "tool_start" && item.toolCallId === toolCallId);
|
|
786
|
+
if (startIdx !== -1) {
|
|
787
|
+
const startItem = prev[startIdx];
|
|
788
|
+
const doneItem = {
|
|
789
|
+
kind: "tool_done",
|
|
790
|
+
name,
|
|
791
|
+
args: startItem.args,
|
|
792
|
+
result,
|
|
793
|
+
isError,
|
|
794
|
+
durationMs,
|
|
795
|
+
details,
|
|
796
|
+
id: startItem.id,
|
|
797
|
+
};
|
|
798
|
+
updated = [...prev];
|
|
799
|
+
updated[startIdx] = doneItem;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
// Fallback: just append
|
|
803
|
+
updated = [
|
|
804
|
+
...prev,
|
|
805
|
+
{
|
|
806
|
+
kind: "tool_done",
|
|
807
|
+
name,
|
|
808
|
+
args: {},
|
|
809
|
+
result,
|
|
810
|
+
isError,
|
|
811
|
+
durationMs,
|
|
812
|
+
details,
|
|
813
|
+
id: getId(),
|
|
814
|
+
},
|
|
815
|
+
];
|
|
816
|
+
}
|
|
694
817
|
}
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
818
|
+
// Flush completed items to Static to keep the live area small
|
|
819
|
+
const { flushed, remaining } = partitionCompleted(updated);
|
|
820
|
+
if (flushed.length > 0) {
|
|
821
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
822
|
+
}
|
|
823
|
+
return remaining;
|
|
700
824
|
});
|
|
701
825
|
}
|
|
702
826
|
}, []),
|
|
703
827
|
onServerToolCall: useCallback((id, name, input) => {
|
|
704
828
|
log("INFO", "server_tool", `Server tool call: ${name}`, { id });
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
829
|
+
// Flush completed items (including assistant text) to Static before
|
|
830
|
+
// adding server tool UI — same rationale as onToolStart.
|
|
831
|
+
setLiveItems((prev) => {
|
|
832
|
+
const { flushed, remaining } = partitionCompleted(prev);
|
|
833
|
+
if (flushed.length > 0) {
|
|
834
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
835
|
+
}
|
|
836
|
+
return [
|
|
837
|
+
...remaining,
|
|
838
|
+
{
|
|
839
|
+
kind: "server_tool_start",
|
|
840
|
+
serverToolCallId: id,
|
|
841
|
+
name,
|
|
842
|
+
input,
|
|
843
|
+
startedAt: Date.now(),
|
|
844
|
+
id: getId(),
|
|
845
|
+
},
|
|
846
|
+
];
|
|
847
|
+
});
|
|
716
848
|
}, []),
|
|
717
849
|
onServerToolResult: useCallback((toolUseId, resultType, data) => {
|
|
718
850
|
log("INFO", "server_tool", `Server tool result`, { toolUseId, resultType });
|
|
719
851
|
setLiveItems((prev) => {
|
|
852
|
+
let updated;
|
|
720
853
|
const startIdx = prev.findIndex((item) => item.kind === "server_tool_start" && item.serverToolCallId === toolUseId);
|
|
721
854
|
if (startIdx !== -1) {
|
|
722
855
|
const startItem = prev[startIdx];
|
|
@@ -729,22 +862,29 @@ export function App(props) {
|
|
|
729
862
|
durationMs: Date.now() - startItem.startedAt,
|
|
730
863
|
id: startItem.id,
|
|
731
864
|
};
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
return next;
|
|
865
|
+
updated = [...prev];
|
|
866
|
+
updated[startIdx] = doneItem;
|
|
735
867
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
868
|
+
else {
|
|
869
|
+
updated = [
|
|
870
|
+
...prev,
|
|
871
|
+
{
|
|
872
|
+
kind: "server_tool_done",
|
|
873
|
+
name: "unknown",
|
|
874
|
+
input: {},
|
|
875
|
+
resultType,
|
|
876
|
+
data,
|
|
877
|
+
durationMs: 0,
|
|
878
|
+
id: getId(),
|
|
879
|
+
},
|
|
880
|
+
];
|
|
881
|
+
}
|
|
882
|
+
// Flush completed items to Static
|
|
883
|
+
const { flushed, remaining } = partitionCompleted(updated);
|
|
884
|
+
if (flushed.length > 0) {
|
|
885
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
886
|
+
}
|
|
887
|
+
return remaining;
|
|
748
888
|
});
|
|
749
889
|
}, []),
|
|
750
890
|
onTurnEnd: useCallback((turn, stopReason, usage) => {
|
|
@@ -755,6 +895,9 @@ export function App(props) {
|
|
|
755
895
|
...(usage.cacheRead != null && { cacheRead: String(usage.cacheRead) }),
|
|
756
896
|
...(usage.cacheWrite != null && { cacheWrite: String(usage.cacheWrite) }),
|
|
757
897
|
});
|
|
898
|
+
// Track actual token count for compaction decisions
|
|
899
|
+
lastActualTokensRef.current =
|
|
900
|
+
usage.inputTokens + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
758
901
|
// For tool-only turns (no text), flush completed items to Static so
|
|
759
902
|
// liveItems doesn't grow unbounded across consecutive tool-only turns.
|
|
760
903
|
setLiveItems((prev) => {
|
|
@@ -901,7 +1044,10 @@ export function App(props) {
|
|
|
901
1044
|
setTitleRunning(agentLoop.isRunning);
|
|
902
1045
|
setTitleToolNames(agentLoop.activeToolCalls.map((tc) => tc.name));
|
|
903
1046
|
}, [agentLoop.activityPhase, agentLoop.isRunning, activeToolNamesKey]);
|
|
1047
|
+
// Terminal progress bar (OSC 9;4) — pulsing bar in supported terminals
|
|
1048
|
+
useTerminalProgress(agentLoop.isRunning, agentLoop.activeToolCalls.length > 0);
|
|
904
1049
|
// Animated thinking border — derived from global animation tick
|
|
1050
|
+
useAnimationActive();
|
|
905
1051
|
const animTick = useAnimationTick();
|
|
906
1052
|
const thinkingBorderFrame = agentLoop.activityPhase === "thinking"
|
|
907
1053
|
? deriveFrame(animTick, 1000, THINKING_BORDER_COLORS.length)
|
|
@@ -941,7 +1087,14 @@ export function App(props) {
|
|
|
941
1087
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
942
1088
|
setLiveItems([]);
|
|
943
1089
|
setDoneStatus(null);
|
|
944
|
-
|
|
1090
|
+
approvedPlanPathRef.current = undefined;
|
|
1091
|
+
planStepsRef.current = [];
|
|
1092
|
+
setPlanSteps([]);
|
|
1093
|
+
// Rebuild system prompt without the approved plan
|
|
1094
|
+
void (async () => {
|
|
1095
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, undefined);
|
|
1096
|
+
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
1097
|
+
})();
|
|
945
1098
|
agentLoop.reset();
|
|
946
1099
|
setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
|
|
947
1100
|
return;
|
|
@@ -968,6 +1121,40 @@ export function App(props) {
|
|
|
968
1121
|
]);
|
|
969
1122
|
return;
|
|
970
1123
|
}
|
|
1124
|
+
// Handle /clearplan — dismiss the approved plan
|
|
1125
|
+
if (trimmed === "/clearplan") {
|
|
1126
|
+
approvedPlanPathRef.current = undefined;
|
|
1127
|
+
planStepsRef.current = [];
|
|
1128
|
+
setPlanSteps([]);
|
|
1129
|
+
// Rebuild system prompt without the plan
|
|
1130
|
+
void (async () => {
|
|
1131
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, undefined);
|
|
1132
|
+
if (messagesRef.current[0]?.role === "system") {
|
|
1133
|
+
messagesRef.current[0] = { role: "system", content: newPrompt };
|
|
1134
|
+
}
|
|
1135
|
+
})();
|
|
1136
|
+
setLiveItems([{ kind: "info", text: "Approved plan dismissed.", id: getId() }]);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// Handle /buddy — toggle companion
|
|
1140
|
+
if (trimmed === "/buddy") {
|
|
1141
|
+
const next = !buddyEnabled;
|
|
1142
|
+
setBuddyEnabled(next);
|
|
1143
|
+
if (settingsRef.current) {
|
|
1144
|
+
settingsRef.current.set("buddyEnabled", next);
|
|
1145
|
+
}
|
|
1146
|
+
setLiveItems((items) => [
|
|
1147
|
+
...items,
|
|
1148
|
+
{
|
|
1149
|
+
kind: "info",
|
|
1150
|
+
text: next
|
|
1151
|
+
? "Buddy enabled! Your companion will appear near the prompt."
|
|
1152
|
+
: "Buddy disabled.",
|
|
1153
|
+
id: getId(),
|
|
1154
|
+
},
|
|
1155
|
+
]);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
971
1158
|
// Handle /plans — open plan pane
|
|
972
1159
|
if (trimmed === "/plans") {
|
|
973
1160
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
@@ -1113,6 +1300,12 @@ export function App(props) {
|
|
|
1113
1300
|
};
|
|
1114
1301
|
setLastUserMessage(input);
|
|
1115
1302
|
setDoneStatus(null);
|
|
1303
|
+
// Clear stale plan progress if there's no active approved plan
|
|
1304
|
+
// (avoids lingering progress from a completed or abandoned plan run)
|
|
1305
|
+
if (planStepsRef.current.length > 0 && !approvedPlanPathRef.current) {
|
|
1306
|
+
planStepsRef.current = [];
|
|
1307
|
+
setPlanSteps([]);
|
|
1308
|
+
}
|
|
1116
1309
|
setLiveItems([userItem]);
|
|
1117
1310
|
// Run agent
|
|
1118
1311
|
try {
|
|
@@ -1249,7 +1442,7 @@ export function App(props) {
|
|
|
1249
1442
|
case "tool_start":
|
|
1250
1443
|
return _jsx(ToolExecution, { status: "running", name: item.name, args: item.args }, item.id);
|
|
1251
1444
|
case "tool_done":
|
|
1252
|
-
return (_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError }, item.id));
|
|
1445
|
+
return (_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError, details: item.details }, item.id));
|
|
1253
1446
|
case "tool_group":
|
|
1254
1447
|
return _jsx(ToolGroupExecution, { tools: item.tools }, item.id);
|
|
1255
1448
|
case "server_tool_start":
|
|
@@ -1361,6 +1554,12 @@ export function App(props) {
|
|
|
1361
1554
|
}, onApprove: (planPath) => {
|
|
1362
1555
|
// Store approved plan path — will be injected into the new system prompt
|
|
1363
1556
|
approvedPlanPathRef.current = planPath;
|
|
1557
|
+
// Extract plan steps for progress tracking
|
|
1558
|
+
void import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8").then((content) => {
|
|
1559
|
+
const steps = extractPlanSteps(content);
|
|
1560
|
+
planStepsRef.current = steps;
|
|
1561
|
+
setPlanSteps(steps);
|
|
1562
|
+
}));
|
|
1364
1563
|
// Clear session for a fresh context focused on the plan
|
|
1365
1564
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1366
1565
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
@@ -1407,7 +1606,7 @@ export function App(props) {
|
|
|
1407
1606
|
void agentLoop.run(msg);
|
|
1408
1607
|
} })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 1, children: [liveItems.map((item) => renderItem(item)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: agentLoop.streamingText, streamingThinking: agentLoop.streamingThinking, showThinking: props.showThinking, thinkingMs: agentLoop.thinkingMs, planMode: planMode })] }), agentLoop.isRunning && agentLoop.activityPhase !== "idle" ? (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: agentLoop.activityPhase === "thinking"
|
|
1409
1608
|
? THINKING_BORDER_COLORS[thinkingBorderFrame]
|
|
1410
|
-
: "transparent", paddingLeft: 1, paddingRight: 1, width: columns, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), planMode: planMode }) })) : (doneStatus && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), agentLoop.queuedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.accent, children: ["⏳ ", agentLoop.queuedCount, " message", agentLoop.queuedCount > 1 ? "s" : "", " queued"] }) })), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
|
|
1609
|
+
: "transparent", paddingLeft: 1, paddingRight: 1, width: columns, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), planMode: planMode, retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length }) })) : (doneStatus && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), agentLoop.queuedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.accent, children: ["⏳ ", agentLoop.queuedCount, " message", agentLoop.queuedCount > 1 ? "s" : "", " queued"] }) })), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
|
|
1411
1610
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1412
1611
|
setOverlay("tasks");
|
|
1413
1612
|
}, onToggleSkills: () => {
|
|
@@ -1426,6 +1625,6 @@ export function App(props) {
|
|
|
1426
1625
|
id: getId(),
|
|
1427
1626
|
},
|
|
1428
1627
|
]);
|
|
1429
|
-
}, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: props.cwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled, planMode: planMode })), bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate }))] }))] }));
|
|
1628
|
+
}, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, linesAdded: agentLoop.linesChanged.added, linesRemoved: agentLoop.linesChanged.removed, cwd: props.cwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled, planMode: planMode })), buddyEnabled && _jsx(Buddy, { phase: agentLoop.activityPhase }), bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate }))] }))] }));
|
|
1430
1629
|
}
|
|
1431
1630
|
//# sourceMappingURL=App.js.map
|