@prestyj/cli 4.3.207 → 4.3.210
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/cli.js +18 -7
- package/dist/cli.js.map +1 -1
- package/dist/core/process-manager-dev-server-repro.test.js +37 -1
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/process-manager.d.ts +9 -0
- package/dist/core/process-manager.d.ts.map +1 -1
- package/dist/core/process-manager.js +20 -6
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/repomap.js +8 -1
- package/dist/core/repomap.js.map +1 -1
- package/dist/core/repomap.test.js +32 -0
- package/dist/core/repomap.test.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +2 -51
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/enter-plan.d.ts +3 -1
- package/dist/tools/enter-plan.d.ts.map +1 -1
- package/dist/tools/enter-plan.js +10 -1
- package/dist/tools/enter-plan.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +22 -5
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.d.ts +2 -0
- package/dist/tools/grep.test.d.ts.map +1 -0
- package/dist/tools/grep.test.js +20 -0
- package/dist/tools/grep.test.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/plan-mode.test.js +22 -0
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/safe-env.d.ts +2 -0
- package/dist/tools/safe-env.d.ts.map +1 -0
- package/dist/tools/safe-env.js +52 -0
- package/dist/tools/safe-env.js.map +1 -0
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +1 -2
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +5 -0
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +12 -1
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-fetch.test.js +9 -0
- package/dist/tools/web-fetch.test.js.map +1 -1
- package/dist/ui/App.d.ts +11 -3
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +224 -215
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-state-persistence.test.js +60 -4
- package/dist/ui/app-state-persistence.test.js.map +1 -1
- package/dist/ui/goal-events.test.js +19 -0
- package/dist/ui/goal-events.test.js.map +1 -1
- package/dist/ui/goal-overlay.test.js +30 -0
- package/dist/ui/goal-overlay.test.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +3 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +10 -2
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/render.d.ts +15 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +2 -0
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/scroll-stabilization.test.js +2 -2
- package/dist/ui/scroll-stabilization.test.js.map +1 -1
- package/dist/ui/slash-command-images.test.js +10 -0
- package/dist/ui/slash-command-images.test.js.map +1 -1
- package/package.json +6 -5
package/dist/ui/App.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useRef, useCallback, useEffect, useMemo } from "react";
|
|
3
|
-
import { Box, Text, Static
|
|
3
|
+
import { Box, Text, Static } from "ink";
|
|
4
4
|
import { useTerminalSize } from "./hooks/useTerminalSize.js";
|
|
5
5
|
import { useDoublePress } from "./hooks/useDoublePress.js";
|
|
6
6
|
import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
|
|
@@ -59,8 +59,9 @@ import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMes
|
|
|
59
59
|
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
60
60
|
import { getMCPServers } from "../core/mcp/index.js";
|
|
61
61
|
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
|
|
62
|
-
import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns,
|
|
62
|
+
import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, summarizeGoalCounts, summarizeGoalCountsFromRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
|
|
63
63
|
import { canCompleteGoalRun, decideGoalNextAction, shouldCreateVerifierFixTask, } from "../core/goal-controller.js";
|
|
64
|
+
import { runGoalVerifierCommand } from "../core/goal-verifier.js";
|
|
64
65
|
import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
|
|
65
66
|
import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
|
|
66
67
|
/** Where ezcoder bugs should be reported. Surfaced in the guidance line. */
|
|
@@ -189,9 +190,7 @@ function summarizeGoalCompletion(summary) {
|
|
|
189
190
|
return statusLine ?? changedLine ?? verificationLine ?? lines[0];
|
|
190
191
|
}
|
|
191
192
|
function formatGoalWorkerFinishedTitle(taskTitle, status) {
|
|
192
|
-
return status === "done"
|
|
193
|
-
? `Worker finished: ${taskTitle}. Reporting back.`
|
|
194
|
-
: `Worker failed: ${taskTitle}. Reporting back.`;
|
|
193
|
+
return status === "done" ? `Done: ${taskTitle}` : `Failed: ${taskTitle}`;
|
|
195
194
|
}
|
|
196
195
|
function countGoalTasksByStatus(tasks, status) {
|
|
197
196
|
return tasks.filter((task) => task.status === status).length;
|
|
@@ -298,12 +297,11 @@ export function formatGoalTerminalProgress(run) {
|
|
|
298
297
|
return null;
|
|
299
298
|
}
|
|
300
299
|
}
|
|
301
|
-
export function shouldHideHistoryForOverlayView(
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
return isOverlayView && !isAgentRunning;
|
|
300
|
+
export function shouldHideHistoryForOverlayView(_isOverlayView, _isAgentRunning) {
|
|
301
|
+
// Ink Static is append-only. Passing [] for overlay panes rewrites the Static
|
|
302
|
+
// accumulator and can destroy scrollback when the pane closes. Keep history
|
|
303
|
+
// mounted and let overlays render below it.
|
|
304
|
+
return false;
|
|
307
305
|
}
|
|
308
306
|
export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
|
|
309
307
|
return isAgentRunning && (overlayPane === "goal" || overlayPane === "plan");
|
|
@@ -321,8 +319,8 @@ export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, h
|
|
|
321
319
|
export function isTallLiveUserMessage(text, rows) {
|
|
322
320
|
return text.split("\n").length > Math.max(8, Math.floor(rows * 0.6));
|
|
323
321
|
}
|
|
324
|
-
export function getStaticHistoryKey({ resizeKey
|
|
325
|
-
return `${resizeKey}
|
|
322
|
+
export function getStaticHistoryKey({ resizeKey }) {
|
|
323
|
+
return `${resizeKey}`;
|
|
326
324
|
}
|
|
327
325
|
// flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
|
|
328
326
|
/** Check whether an item is still active (running spinner, pending result). */
|
|
@@ -481,11 +479,29 @@ function markTaskInProgress(cwd, taskId) {
|
|
|
481
479
|
// ignore
|
|
482
480
|
}
|
|
483
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Mark a task as done in the on-disk tasks.json. Used by run-all auto-advance
|
|
484
|
+
* so the in-flight task is recorded as completed even when the agent forgets
|
|
485
|
+
* to call the `tasks` tool. Errors are swallowed — if the file is missing or
|
|
486
|
+
* the id is stale, the next auto-advance step still proceeds correctly.
|
|
487
|
+
*/
|
|
488
|
+
function markTaskDone(cwd, taskId) {
|
|
489
|
+
try {
|
|
490
|
+
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
491
|
+
const filePath = join(homedir(), ".ezcoder-tasks", "projects", hash, "tasks.json");
|
|
492
|
+
const data = readFileSync(filePath, "utf-8");
|
|
493
|
+
const tasks = JSON.parse(data);
|
|
494
|
+
const updated = tasks.map((t) => (t.id === taskId ? { ...t, status: "done" } : t));
|
|
495
|
+
writeFileSync(filePath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// ignore
|
|
499
|
+
}
|
|
500
|
+
}
|
|
484
501
|
// ── App Component ──────────────────────────────────────────
|
|
485
502
|
export function App(props) {
|
|
486
503
|
const theme = useTheme();
|
|
487
504
|
const switchTheme = useSetTheme();
|
|
488
|
-
const { stdout } = useStdout();
|
|
489
505
|
const { columns, resizeKey } = useTerminalSize();
|
|
490
506
|
// Hoisted before terminal title hook so it can reference them
|
|
491
507
|
const [lastUserMessage, setLastUserMessage] = useState("");
|
|
@@ -537,6 +553,11 @@ export function App(props) {
|
|
|
537
553
|
// remount that startTask() triggers between tasks.
|
|
538
554
|
const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
|
|
539
555
|
const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
|
|
556
|
+
// Task id of the run-all task currently driving the agent. Tracked so the
|
|
557
|
+
// run-all `onDone` auto-advance can mark the just-finished task as done
|
|
558
|
+
// even when the agent forgets to call the tasks tool. Mirrored through
|
|
559
|
+
// sessionStore so the value survives the resetUI() remount between tasks.
|
|
560
|
+
const currentTaskIdRef = useRef(props.sessionStore?.currentTaskId ?? null);
|
|
540
561
|
const startTaskRef = useRef(() => { });
|
|
541
562
|
const agentRunningRef = useRef(false);
|
|
542
563
|
const runningGoalIdsRef = useRef(new Set());
|
|
@@ -547,7 +568,6 @@ export function App(props) {
|
|
|
547
568
|
const startPixelFixRef = useRef(() => { });
|
|
548
569
|
const cwdRef = useRef(props.cwd);
|
|
549
570
|
const [displayedCwd, setDisplayedCwd] = useState(props.cwd);
|
|
550
|
-
const [staticKey, setStaticKey] = useState(0);
|
|
551
571
|
const [doneStatus, setDoneStatus] = useState(props.sessionStore?.doneStatus ?? null);
|
|
552
572
|
// Suppress "done" status when a plan overlay is about to open
|
|
553
573
|
const planOverlayPendingRef = useRef(false);
|
|
@@ -906,14 +926,6 @@ export function App(props) {
|
|
|
906
926
|
// premature "done" status that fires when the agent loop finishes
|
|
907
927
|
planOverlayPendingRef.current = true;
|
|
908
928
|
setTimeout(() => {
|
|
909
|
-
// NOTE: this is the one open-overlay path that does NOT remount via
|
|
910
|
-
// resetUI. It runs while the agent is still mid-turn (after the
|
|
911
|
-
// exit_plan tool returned but before onDone fires), and unmounting
|
|
912
|
-
// here would kill the in-flight agent stream. Keep the bare ANSI
|
|
913
|
-
// clear; the drift bug is tolerable across just the agent's
|
|
914
|
-
// wrap-up turn, and onApprove/onReject both remount cleanly via
|
|
915
|
-
// resetUI when the user resolves the plan.
|
|
916
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
917
929
|
setPlanAutoExpand(true);
|
|
918
930
|
setOverlay("plan");
|
|
919
931
|
// Don't clear planOverlayPendingRef here — keep it true until
|
|
@@ -928,7 +940,7 @@ export function App(props) {
|
|
|
928
940
|
planPath);
|
|
929
941
|
};
|
|
930
942
|
}
|
|
931
|
-
}, [props.onExitPlanRef, replaceSystemPrompt
|
|
943
|
+
}, [props.onExitPlanRef, replaceSystemPrompt]);
|
|
932
944
|
const appendMessagesToSession = useCallback(async (sessionPath, messages, startIndex) => {
|
|
933
945
|
const sm = sessionManagerRef.current;
|
|
934
946
|
if (!sm)
|
|
@@ -1674,7 +1686,7 @@ export function App(props) {
|
|
|
1674
1686
|
return remaining;
|
|
1675
1687
|
});
|
|
1676
1688
|
}, []),
|
|
1677
|
-
onDone: useCallback((durationMs, toolsUsed) => {
|
|
1689
|
+
onDone: useCallback((durationMs, toolsUsed, errored) => {
|
|
1678
1690
|
log("INFO", "agent", `Agent done`, {
|
|
1679
1691
|
duration: `${durationMs}ms`,
|
|
1680
1692
|
toolsUsed: toolsUsed.join(",") || "none",
|
|
@@ -1701,17 +1713,43 @@ export function App(props) {
|
|
|
1701
1713
|
if (runAllTasksRef.current) {
|
|
1702
1714
|
setTimeout(() => {
|
|
1703
1715
|
const cwd = cwdRef.current;
|
|
1716
|
+
// Clean turn: mark the in-flight task done so the row flips to ✓.
|
|
1717
|
+
// Error path: leave it at `in-progress` so the failed task is
|
|
1718
|
+
// visibly distinct from completed ones. The chain still advances
|
|
1719
|
+
// either way — matching how errors were handled before this fix.
|
|
1720
|
+
const finishedId = currentTaskIdRef.current;
|
|
1721
|
+
if (finishedId) {
|
|
1722
|
+
if (!errored)
|
|
1723
|
+
markTaskDone(cwd, finishedId);
|
|
1724
|
+
currentTaskIdRef.current = null;
|
|
1725
|
+
if (props.sessionStore)
|
|
1726
|
+
props.sessionStore.currentTaskId = null;
|
|
1727
|
+
}
|
|
1704
1728
|
const next = getNextPendingTask(cwd);
|
|
1705
1729
|
if (next) {
|
|
1706
1730
|
markTaskInProgress(cwd, next.id);
|
|
1731
|
+
// startTaskRef.current() re-sets taskRunningRef = true before
|
|
1732
|
+
// the next agent run begins, so the brief clear-then-set is
|
|
1733
|
+
// safe — the agent isn't running in between.
|
|
1707
1734
|
startTaskRef.current(next.title, next.prompt, next.id);
|
|
1708
1735
|
}
|
|
1709
1736
|
else {
|
|
1710
1737
|
setRunAllTasks(false);
|
|
1738
|
+
if (props.taskRunningRef)
|
|
1739
|
+
props.taskRunningRef.current = false;
|
|
1711
1740
|
log("INFO", "tasks", "Run-all complete — no more pending tasks");
|
|
1712
1741
|
}
|
|
1713
1742
|
}, 500);
|
|
1714
1743
|
}
|
|
1744
|
+
else if (currentTaskIdRef.current) {
|
|
1745
|
+
// Single manual task done — clear the task-running flag so a
|
|
1746
|
+
// subsequent free-form prompt can use plan mode again.
|
|
1747
|
+
if (props.taskRunningRef)
|
|
1748
|
+
props.taskRunningRef.current = false;
|
|
1749
|
+
currentTaskIdRef.current = null;
|
|
1750
|
+
if (props.sessionStore)
|
|
1751
|
+
props.sessionStore.currentTaskId = null;
|
|
1752
|
+
}
|
|
1715
1753
|
// Goal loop: after the orchestrator handles a worker/verifier event,
|
|
1716
1754
|
// continue the same Goal automatically until it reaches a terminal state.
|
|
1717
1755
|
for (const runId of [...runningGoalIdsRef.current]) {
|
|
@@ -1758,6 +1796,16 @@ export function App(props) {
|
|
|
1758
1796
|
setRunAllTasks(false);
|
|
1759
1797
|
setRunAllPixel(false);
|
|
1760
1798
|
currentPixelFixRef.current = null;
|
|
1799
|
+
// Clear the task-running flag so a subsequent free-form prompt can
|
|
1800
|
+
// re-enable plan mode. Aborted runs never auto-advance.
|
|
1801
|
+
if (props.taskRunningRef)
|
|
1802
|
+
props.taskRunningRef.current = false;
|
|
1803
|
+
// Clear the run-all task id so a subsequent run-all does not
|
|
1804
|
+
// mistakenly mark this aborted task as done. The on-disk row stays
|
|
1805
|
+
// at `in-progress`, which is the correct truth for an aborted task.
|
|
1806
|
+
currentTaskIdRef.current = null;
|
|
1807
|
+
if (props.sessionStore)
|
|
1808
|
+
props.sessionStore.currentTaskId = null;
|
|
1761
1809
|
setDoneStatus(null);
|
|
1762
1810
|
setLiveItems((prev) => {
|
|
1763
1811
|
const next = prev.map((item) => {
|
|
@@ -2045,14 +2093,10 @@ export function App(props) {
|
|
|
2045
2093
|
process.exit(0);
|
|
2046
2094
|
}
|
|
2047
2095
|
// Handle /clear — tear down the entire Ink instance and rebuild fresh.
|
|
2048
|
-
//
|
|
2049
|
-
//
|
|
2050
|
-
//
|
|
2051
|
-
//
|
|
2052
|
-
// terminal-state assumptions that ANSI clearing breaks. The reliable
|
|
2053
|
-
// fix is unmount + render again. Runtime state (model, provider,
|
|
2054
|
-
// thinking) survives via renderApp's closure-held `runtimeState`,
|
|
2055
|
-
// mirrored from React state via the useEffects above.
|
|
2096
|
+
// Avoid direct ANSI terminal clears here; they can erase scrollback.
|
|
2097
|
+
// Runtime state (model, provider, thinking) survives via renderApp's
|
|
2098
|
+
// closure-held `runtimeState`, mirrored from React state via the
|
|
2099
|
+
// useEffects above.
|
|
2056
2100
|
if (trimmed === "/clear") {
|
|
2057
2101
|
if (props.resetUI) {
|
|
2058
2102
|
void (async () => {
|
|
@@ -2065,8 +2109,7 @@ export function App(props) {
|
|
|
2065
2109
|
return;
|
|
2066
2110
|
}
|
|
2067
2111
|
// Fallback path (resetUI not wired — e.g. tests). Best-effort: clear
|
|
2068
|
-
// React state in place
|
|
2069
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2112
|
+
// React state in place without touching terminal scrollback.
|
|
2070
2113
|
pendingFlushRef.current = [];
|
|
2071
2114
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
2072
2115
|
setLiveItems([]);
|
|
@@ -2082,7 +2125,6 @@ export function App(props) {
|
|
|
2082
2125
|
agentLoop.reset();
|
|
2083
2126
|
setSessionTitle(undefined);
|
|
2084
2127
|
sessionTitleGeneratedRef.current = false;
|
|
2085
|
-
setStaticKey((k) => k + 1);
|
|
2086
2128
|
setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
|
|
2087
2129
|
return;
|
|
2088
2130
|
}
|
|
@@ -2190,8 +2232,6 @@ export function App(props) {
|
|
|
2190
2232
|
props.resetUI();
|
|
2191
2233
|
}
|
|
2192
2234
|
else {
|
|
2193
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2194
|
-
setStaticKey((key) => key + 1);
|
|
2195
2235
|
if (props.sessionStore) {
|
|
2196
2236
|
props.sessionStore.overlay = "goal";
|
|
2197
2237
|
props.sessionStore.planAutoExpand = false;
|
|
@@ -2549,11 +2589,23 @@ export function App(props) {
|
|
|
2549
2589
|
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "▶ " }), _jsx(Text, { color: theme.textDim, children: "Goal: " }), _jsx(Text, { color: theme.success, children: item.title }), item.workerId ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] }) : null] }) }, item.id));
|
|
2550
2590
|
case "goal_progress": {
|
|
2551
2591
|
const isError = item.status === "failed" || item.status === "fail" || item.status === "blocked";
|
|
2552
|
-
const color =
|
|
2553
|
-
? theme.
|
|
2554
|
-
:
|
|
2555
|
-
? theme.
|
|
2556
|
-
:
|
|
2592
|
+
const color = isError
|
|
2593
|
+
? theme.error
|
|
2594
|
+
: item.phase === "worker_finished"
|
|
2595
|
+
? theme.success
|
|
2596
|
+
: item.phase === "verifier_finished"
|
|
2597
|
+
? theme.accent
|
|
2598
|
+
: item.phase === "orchestrator_reviewing" || item.phase === "orchestrator_working"
|
|
2599
|
+
? theme.secondary
|
|
2600
|
+
: item.phase === "continuing"
|
|
2601
|
+
? theme.warning
|
|
2602
|
+
: item.phase === "verifier_started"
|
|
2603
|
+
? theme.accent
|
|
2604
|
+
: item.phase === "worker_started"
|
|
2605
|
+
? theme.primary
|
|
2606
|
+
: item.phase === "terminal"
|
|
2607
|
+
? theme.success
|
|
2608
|
+
: theme.primary;
|
|
2557
2609
|
const glyph = item.phase === "worker_finished" || item.phase === "verifier_finished"
|
|
2558
2610
|
? "✓ "
|
|
2559
2611
|
: item.phase === "terminal"
|
|
@@ -2643,14 +2695,30 @@ export function App(props) {
|
|
|
2643
2695
|
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
2644
2696
|
`tasks({ action: "done", id: "${shortId}" })`;
|
|
2645
2697
|
const fullPrompt = prompt + completionHint;
|
|
2698
|
+
// Task-pane runs are unattended — there is no human to approve a
|
|
2699
|
+
// plan via the plan overlay. Force plan mode off and flag
|
|
2700
|
+
// `taskRunningRef` so `enter_plan` refuses if the agent tries to call
|
|
2701
|
+
// it. Both flags persist across the resetUI() unmount/remount below
|
|
2702
|
+
// because they live in cli.ts-owned refs, not React state.
|
|
2703
|
+
if (props.planModeRef)
|
|
2704
|
+
props.planModeRef.current = false;
|
|
2705
|
+
planModeStateRef.current = false;
|
|
2706
|
+
approvedPlanPathRef.current = undefined;
|
|
2707
|
+
setPlanMode(false);
|
|
2708
|
+
if (props.taskRunningRef)
|
|
2709
|
+
props.taskRunningRef.current = true;
|
|
2646
2710
|
if (props.resetUI && props.sessionStore) {
|
|
2647
|
-
// Preserve the current system prompt (may differ from the launch
|
|
2648
|
-
// config — e.g. plan mode toggled or skills changed).
|
|
2649
|
-
const sysMsg = messagesRef.current[0];
|
|
2650
|
-
const newMessages = sysMsg && sysMsg.role === "system" ? [sysMsg] : messagesRef.current.slice(0, 1);
|
|
2651
2711
|
const taskItem = { kind: "task", title, id: String(nextIdRef.current++) };
|
|
2652
2712
|
const sm = sessionManagerRef.current;
|
|
2653
2713
|
void (async () => {
|
|
2714
|
+
// Rebuild the system prompt without plan mode / approved plan so the
|
|
2715
|
+
// next mount doesn't inherit a stale plan-mode prefix from a prior
|
|
2716
|
+
// user-toggled state. Skills, languages, and cwd are preserved.
|
|
2717
|
+
const taskSysPrompt = await rebuildSystemPrompt({
|
|
2718
|
+
planMode: false,
|
|
2719
|
+
clearApprovedPlan: true,
|
|
2720
|
+
});
|
|
2721
|
+
const newMessages = [{ role: "system", content: taskSysPrompt }];
|
|
2654
2722
|
let newSessionPath;
|
|
2655
2723
|
if (sm) {
|
|
2656
2724
|
try {
|
|
@@ -2662,8 +2730,14 @@ export function App(props) {
|
|
|
2662
2730
|
// session creation is best-effort
|
|
2663
2731
|
}
|
|
2664
2732
|
}
|
|
2665
|
-
if (props.sessionStore)
|
|
2733
|
+
if (props.sessionStore) {
|
|
2666
2734
|
props.sessionStore.overlay = null;
|
|
2735
|
+
// Mirror through sessionStore so the post-resetUI remount can
|
|
2736
|
+
// re-seed the ref. markTaskDone() in the run-all onDone branch
|
|
2737
|
+
// reads currentTaskIdRef.current, which is seeded from this.
|
|
2738
|
+
props.sessionStore.currentTaskId = taskId;
|
|
2739
|
+
}
|
|
2740
|
+
currentTaskIdRef.current = taskId;
|
|
2667
2741
|
props.resetUI?.({
|
|
2668
2742
|
wipeSession: true,
|
|
2669
2743
|
messages: newMessages,
|
|
@@ -2675,7 +2749,9 @@ export function App(props) {
|
|
|
2675
2749
|
return;
|
|
2676
2750
|
}
|
|
2677
2751
|
// Fallback path (resetUI not wired — tests).
|
|
2678
|
-
|
|
2752
|
+
currentTaskIdRef.current = taskId;
|
|
2753
|
+
if (props.sessionStore)
|
|
2754
|
+
props.sessionStore.currentTaskId = taskId;
|
|
2679
2755
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
2680
2756
|
setLiveItems([]);
|
|
2681
2757
|
messagesRef.current = messagesRef.current.slice(0, 1);
|
|
@@ -2709,15 +2785,7 @@ export function App(props) {
|
|
|
2709
2785
|
setRunAllTasks(false);
|
|
2710
2786
|
}
|
|
2711
2787
|
})();
|
|
2712
|
-
}, [
|
|
2713
|
-
props.cwd,
|
|
2714
|
-
props.resetUI,
|
|
2715
|
-
props.sessionStore,
|
|
2716
|
-
stdout,
|
|
2717
|
-
agentLoop,
|
|
2718
|
-
currentProvider,
|
|
2719
|
-
currentModel,
|
|
2720
|
-
]);
|
|
2788
|
+
}, [props.cwd, props.resetUI, props.sessionStore, agentLoop, currentProvider, currentModel]);
|
|
2721
2789
|
const openOverlay = useCallback((kind) => {
|
|
2722
2790
|
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
2723
2791
|
props.sessionStore.overlay = kind;
|
|
@@ -2738,7 +2806,7 @@ export function App(props) {
|
|
|
2738
2806
|
setPlanAutoExpand(false);
|
|
2739
2807
|
setOverlay(kind);
|
|
2740
2808
|
}
|
|
2741
|
-
}, [agentLoop.isRunning, props
|
|
2809
|
+
}, [agentLoop.isRunning, props]);
|
|
2742
2810
|
const closeOverlay = useCallback(() => {
|
|
2743
2811
|
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
2744
2812
|
props.sessionStore.overlay = null;
|
|
@@ -2750,7 +2818,7 @@ export function App(props) {
|
|
|
2750
2818
|
}
|
|
2751
2819
|
setOverlay(null);
|
|
2752
2820
|
}
|
|
2753
|
-
}, [agentLoop.isRunning, overlay, props
|
|
2821
|
+
}, [agentLoop.isRunning, overlay, props]);
|
|
2754
2822
|
const runGoalSyntheticEvent = useCallback((eventText) => {
|
|
2755
2823
|
const eventInfo = parseGoalSyntheticEvent(eventText);
|
|
2756
2824
|
const detail = eventInfo?.kind === "worker"
|
|
@@ -2841,8 +2909,8 @@ export function App(props) {
|
|
|
2841
2909
|
appendGoalProgress({
|
|
2842
2910
|
kind: "goal_progress",
|
|
2843
2911
|
phase: "continuing",
|
|
2844
|
-
title: `
|
|
2845
|
-
detail: "
|
|
2912
|
+
title: `Choosing next Goal step: ${latestRun.title}`,
|
|
2913
|
+
detail: "Latest result is recorded; starting the next worker task or verifier automatically.",
|
|
2846
2914
|
status: latestRun.status,
|
|
2847
2915
|
});
|
|
2848
2916
|
upsertGoalStatusEntry({
|
|
@@ -3138,143 +3206,89 @@ export function App(props) {
|
|
|
3138
3206
|
detail: run.verifier.command,
|
|
3139
3207
|
goalNumber: goalNumberForRun(run.id),
|
|
3140
3208
|
});
|
|
3141
|
-
|
|
3142
|
-
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
3143
|
-
const { join } = await import("node:path");
|
|
3144
|
-
const logDir = join(projectDir(props.cwd), "verifiers");
|
|
3145
|
-
await mkdir(logDir, { recursive: true });
|
|
3146
|
-
const outputPath = join(logDir, `${run.id}-${startedAt}.log`);
|
|
3147
|
-
const child = spawn(run.verifier.command, {
|
|
3209
|
+
void runGoalVerifierCommand({
|
|
3148
3210
|
cwd: props.cwd,
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
output += chunk.toString("utf-8");
|
|
3156
|
-
if (output.length > 20_000)
|
|
3157
|
-
output = output.slice(output.length - 20_000);
|
|
3158
|
-
});
|
|
3159
|
-
child.stderr?.on("data", (chunk) => {
|
|
3160
|
-
output += chunk.toString("utf-8");
|
|
3161
|
-
if (output.length > 20_000)
|
|
3162
|
-
output = output.slice(output.length - 20_000);
|
|
3163
|
-
});
|
|
3164
|
-
let verifierSettled = false;
|
|
3165
|
-
let timedOut = false;
|
|
3166
|
-
const timeout = verifierTimeoutMs > 0
|
|
3167
|
-
? setTimeout(() => {
|
|
3168
|
-
timedOut = true;
|
|
3169
|
-
if (child.pid)
|
|
3170
|
-
child.kill("SIGTERM");
|
|
3171
|
-
const killTimer = setTimeout(() => {
|
|
3172
|
-
if (!verifierSettled && child.pid)
|
|
3173
|
-
child.kill("SIGKILL");
|
|
3174
|
-
}, 5000);
|
|
3175
|
-
killTimer.unref?.();
|
|
3176
|
-
finishVerifier(124, `Verifier timed out after ${verifierTimeoutMs}ms and was terminated.\n${output}`);
|
|
3177
|
-
}, verifierTimeoutMs)
|
|
3178
|
-
: undefined;
|
|
3179
|
-
timeout?.unref?.();
|
|
3180
|
-
const finishVerifier = (code, forcedOutput) => {
|
|
3181
|
-
if (verifierSettled)
|
|
3182
|
-
return;
|
|
3183
|
-
verifierSettled = true;
|
|
3184
|
-
if (timeout)
|
|
3185
|
-
clearTimeout(timeout);
|
|
3211
|
+
runId: run.id,
|
|
3212
|
+
command: run.verifier.command,
|
|
3213
|
+
timeoutMs: verifierTimeoutMs,
|
|
3214
|
+
now: () => startedAt,
|
|
3215
|
+
})
|
|
3216
|
+
.then(async ({ verification, failureClass, durationMs }) => {
|
|
3186
3217
|
activeVerifierRunIdsRef.current.delete(run.id);
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
label: `Verifier ${status}`,
|
|
3229
|
-
content: `${failureClass}: ${summary}`.slice(0, 4000),
|
|
3230
|
-
path: outputPath,
|
|
3231
|
-
});
|
|
3232
|
-
await appendGoalDecision(props.cwd, run.id, {
|
|
3233
|
-
kind: `verifier_${status}`,
|
|
3234
|
-
reason: `${failureClass}: verifier exited with code ${code ?? 1}.`,
|
|
3235
|
-
content: `outputPath=${outputPath}; durationMs=${Date.now() - startedAt}`,
|
|
3236
|
-
});
|
|
3237
|
-
if (status === "fail" && shouldCreateVerifierFixTask(latestRun)) {
|
|
3238
|
-
await updateGoalTask(props.cwd, run.id, `fix-${Date.now()}`, {
|
|
3239
|
-
title: "Fix verifier failure",
|
|
3240
|
-
prompt: `Goal verifier failed after ${Date.now() - startedAt}ms. Original goal: ${run.goal}\n\n` +
|
|
3241
|
-
`Verifier command: ${run.verifier?.command}\n\n` +
|
|
3242
|
-
`Failure output:\n${summary.slice(-6000)}\n\nFix the cause, record evidence with the goals tool, and rerun relevant verification.`,
|
|
3243
|
-
status: "pending",
|
|
3244
|
-
});
|
|
3245
|
-
const runWithPendingFix = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? latestRun;
|
|
3246
|
-
await upsertGoalRun(props.cwd, { ...runWithPendingFix, status: "ready" });
|
|
3247
|
-
}
|
|
3248
|
-
setGoalCount((await summarizeGoalCounts(props.cwd)).active);
|
|
3249
|
-
appendGoalProgress({
|
|
3250
|
-
kind: "goal_progress",
|
|
3251
|
-
phase: "verifier_finished",
|
|
3252
|
-
title: `Verifier ${status}: ${run.title}`,
|
|
3253
|
-
detail: summarizeGoalCompletion(summary),
|
|
3254
|
-
status,
|
|
3255
|
-
});
|
|
3256
|
-
upsertGoalStatusEntry({
|
|
3257
|
-
runId: run.id,
|
|
3258
|
-
label: run.title,
|
|
3259
|
-
phase: status === "pass" ? "reviewing" : "failed",
|
|
3260
|
-
startedAt: Date.now(),
|
|
3261
|
-
detail: status === "pass" ? "reviewing verifier evidence" : "verifier failed",
|
|
3262
|
-
goalNumber: goalNumberForRun(run.id),
|
|
3218
|
+
const status = verification.status;
|
|
3219
|
+
const summary = verification.summary;
|
|
3220
|
+
const outputPath = verification.outputPath;
|
|
3221
|
+
const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
|
|
3222
|
+
const runWithVerifier = {
|
|
3223
|
+
...latestRun,
|
|
3224
|
+
verifier: {
|
|
3225
|
+
...latestRun.verifier,
|
|
3226
|
+
description: latestRun.verifier?.description ?? "Goal verifier",
|
|
3227
|
+
command: run.verifier?.command,
|
|
3228
|
+
lastResult: verification,
|
|
3229
|
+
},
|
|
3230
|
+
};
|
|
3231
|
+
const completionCheck = canCompleteGoalRun(runWithVerifier);
|
|
3232
|
+
const verifiedRun = await upsertGoalRun(props.cwd, {
|
|
3233
|
+
...runWithVerifier,
|
|
3234
|
+
continueRequestedAt: undefined,
|
|
3235
|
+
status: status === "pass" && completionCheck.ok
|
|
3236
|
+
? "passed"
|
|
3237
|
+
: status === "pass"
|
|
3238
|
+
? "ready"
|
|
3239
|
+
: "failed",
|
|
3240
|
+
});
|
|
3241
|
+
await appendGoalEvidence(props.cwd, run.id, {
|
|
3242
|
+
kind: "command",
|
|
3243
|
+
label: `Verifier ${status}`,
|
|
3244
|
+
content: `${failureClass}: ${summary}`.slice(0, 4000),
|
|
3245
|
+
path: outputPath,
|
|
3246
|
+
});
|
|
3247
|
+
await appendGoalDecision(props.cwd, run.id, {
|
|
3248
|
+
kind: `verifier_${status}`,
|
|
3249
|
+
reason: `${failureClass}: verifier exited with code ${verification.exitCode ?? 1}.`,
|
|
3250
|
+
content: `outputPath=${outputPath ?? ""}; durationMs=${durationMs}`,
|
|
3251
|
+
});
|
|
3252
|
+
if (status === "fail" && shouldCreateVerifierFixTask(latestRun)) {
|
|
3253
|
+
await updateGoalTask(props.cwd, run.id, `fix-${Date.now()}`, {
|
|
3254
|
+
title: "Fix verifier failure",
|
|
3255
|
+
prompt: `Goal verifier failed after ${durationMs}ms. Original goal: ${run.goal}\n\n` +
|
|
3256
|
+
`Verifier command: ${run.verifier?.command}\n\n` +
|
|
3257
|
+
`Failure output:\n${summary.slice(-6000)}\n\nFix the cause, record evidence with the goals tool, and rerun relevant verification.`,
|
|
3258
|
+
status: "pending",
|
|
3263
3259
|
});
|
|
3264
|
-
const
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3260
|
+
const runWithPendingFix = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? latestRun;
|
|
3261
|
+
await upsertGoalRun(props.cwd, { ...runWithPendingFix, status: "ready" });
|
|
3262
|
+
}
|
|
3263
|
+
setGoalCount((await summarizeGoalCounts(props.cwd)).active);
|
|
3264
|
+
appendGoalProgress({
|
|
3265
|
+
kind: "goal_progress",
|
|
3266
|
+
phase: "verifier_finished",
|
|
3267
|
+
title: `Verifier ${status}: ${run.title}`,
|
|
3268
|
+
detail: summarizeGoalCompletion(summary),
|
|
3269
|
+
status,
|
|
3274
3270
|
});
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3271
|
+
upsertGoalStatusEntry({
|
|
3272
|
+
runId: run.id,
|
|
3273
|
+
label: run.title,
|
|
3274
|
+
phase: status === "pass" ? "reviewing" : "failed",
|
|
3275
|
+
startedAt: Date.now(),
|
|
3276
|
+
detail: status === "pass" ? "reviewing verifier evidence" : "verifier failed",
|
|
3277
|
+
goalNumber: goalNumberForRun(run.id),
|
|
3278
|
+
});
|
|
3279
|
+
const eventText = formatGoalVerifierCompletionEvent(verifiedRun, status === "pass" ? "pass" : "fail", run.verifier?.command ?? "", verification.exitCode ?? 1, summary);
|
|
3280
|
+
runGoalSyntheticEvent(eventText);
|
|
3281
|
+
const continuationRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id);
|
|
3282
|
+
if (continuationRun?.continueRequestedAt && status === "pass") {
|
|
3283
|
+
setTimeout(() => continueGoalRun(run.id), 500);
|
|
3284
|
+
}
|
|
3285
|
+
})
|
|
3286
|
+
.catch((err) => {
|
|
3287
|
+
activeVerifierRunIdsRef.current.delete(run.id);
|
|
3288
|
+
clearGoalStatusEntry(run.id);
|
|
3289
|
+
log("ERROR", "goal", err instanceof Error ? err.message : String(err));
|
|
3290
|
+
setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal verifier")]);
|
|
3291
|
+
});
|
|
3278
3292
|
}, [
|
|
3279
3293
|
props.cwd,
|
|
3280
3294
|
appendGoalProgress,
|
|
@@ -3308,8 +3322,12 @@ export function App(props) {
|
|
|
3308
3322
|
startGoalRunRef.current = startGoalRun;
|
|
3309
3323
|
useEffect(() => {
|
|
3310
3324
|
runAllTasksRef.current = runAllTasks;
|
|
3311
|
-
if (props.sessionStore)
|
|
3325
|
+
if (props.sessionStore) {
|
|
3312
3326
|
props.sessionStore.runAllTasks = runAllTasks;
|
|
3327
|
+
// Keep currentTaskId in sync with the ref so a sessionStore-seeded
|
|
3328
|
+
// remount picks up the same value the imperative sites wrote.
|
|
3329
|
+
props.sessionStore.currentTaskId = currentTaskIdRef.current;
|
|
3330
|
+
}
|
|
3313
3331
|
}, [runAllTasks, props.sessionStore]);
|
|
3314
3332
|
useEffect(() => {
|
|
3315
3333
|
agentRunningRef.current = agentLoop.isRunning;
|
|
@@ -3364,14 +3382,10 @@ export function App(props) {
|
|
|
3364
3382
|
activeLanguages: detectedForPixelFix,
|
|
3365
3383
|
tools: toolsForPixelFix,
|
|
3366
3384
|
});
|
|
3367
|
-
// Now that the cwd swap is committed, reset chat.
|
|
3368
|
-
//
|
|
3369
|
-
// staticKey would print a second banner with the new cwd — leaving
|
|
3370
|
-
// two banners stacked in the scrollback.
|
|
3371
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
3385
|
+
// Now that the cwd swap is committed, reset chat. Do not clear the
|
|
3386
|
+
// terminal here; terminal clear sequences can erase saved scrollback.
|
|
3372
3387
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
3373
3388
|
setLiveItems([]);
|
|
3374
|
-
setStaticKey((k) => k + 1);
|
|
3375
3389
|
messagesRef.current = messagesRef.current.slice(0, 1);
|
|
3376
3390
|
agentLoop.reset();
|
|
3377
3391
|
persistedIndexRef.current = messagesRef.current.length;
|
|
@@ -3403,7 +3417,7 @@ export function App(props) {
|
|
|
3403
3417
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
3404
3418
|
}
|
|
3405
3419
|
})();
|
|
3406
|
-
}, [props.cwd,
|
|
3420
|
+
}, [props.cwd, agentLoop, currentProvider, currentModel]);
|
|
3407
3421
|
startPixelFixRef.current = startPixelFix;
|
|
3408
3422
|
// Seed from sessionStore so "Fix All" chaining survives a deferred
|
|
3409
3423
|
// resetUI() if it fires between pixel fixes (e.g. user toggled a pane).
|
|
@@ -3431,12 +3445,13 @@ export function App(props) {
|
|
|
3431
3445
|
overlayPane: overlay,
|
|
3432
3446
|
isAgentRunning: agentLoop.isRunning,
|
|
3433
3447
|
});
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3448
|
+
const staticItems = shouldHideStaticItemsForOverlayView({
|
|
3449
|
+
shouldHideHistoryForOverlay,
|
|
3450
|
+
stabilizeOverlayPaneRerender,
|
|
3451
|
+
})
|
|
3452
|
+
? []
|
|
3453
|
+
: history;
|
|
3454
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: staticItems, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, getStaticHistoryKey({ resizeKey })), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
|
|
3440
3455
|
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
3441
3456
|
props.sessionStore.overlay = null;
|
|
3442
3457
|
props.resetUI();
|
|
@@ -3586,10 +3601,8 @@ export function App(props) {
|
|
|
3586
3601
|
approvedPlanPathRef.current = planPath;
|
|
3587
3602
|
planStepsRef.current = steps;
|
|
3588
3603
|
setPlanSteps(steps);
|
|
3589
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
3590
3604
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
3591
3605
|
setLiveItems([]);
|
|
3592
|
-
setStaticKey((k) => k + 1);
|
|
3593
3606
|
setPlanAutoExpand(false);
|
|
3594
3607
|
setOverlay(null);
|
|
3595
3608
|
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
@@ -3630,8 +3643,6 @@ export function App(props) {
|
|
|
3630
3643
|
});
|
|
3631
3644
|
return;
|
|
3632
3645
|
}
|
|
3633
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
3634
|
-
setStaticKey((k) => k + 1);
|
|
3635
3646
|
setPlanAutoExpand(false);
|
|
3636
3647
|
setOverlay(null);
|
|
3637
3648
|
setDoneStatus(null);
|
|
@@ -3646,8 +3657,6 @@ export function App(props) {
|
|
|
3646
3657
|
});
|
|
3647
3658
|
} })) : (_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, thinkingMs: agentLoop.thinkingMs, planMode: planMode })] }), agentLoop.isRunning && agentLoop.activityPhase !== "idle" ? (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: agentLoop.activityPhase === "thinking" ? THINKING_BORDER_COLORS[0] : "transparent", paddingLeft: 1, paddingRight: 1, width: columns, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: thinkingEnabled, 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, staticDisplay: true }) })) : agentLoop.stallError ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.warning, children: "⚠ API provider stream interrupted — retries exhausted." }), _jsx(Text, { color: theme.textDim, children: " Your conversation is preserved. Send a message to continue." })] })) : (doneStatus &&
|
|
3648
3659
|
!agentLoop.isRunning && (_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: () => {
|
|
3649
|
-
// While the agent is running, skip the screen-clear + staticKey
|
|
3650
|
-
// bump that would otherwise wipe the chat history from scrollback.
|
|
3651
3660
|
// Just flip the overlay state — Ink's log-update handles the
|
|
3652
3661
|
// live-area transition (chat input → TaskOverlay) natively, and
|
|
3653
3662
|
// the chat history above stays in scrollback. When the overlay
|