@prestyj/cli 4.3.95 → 4.3.159
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 +104 -47
- package/dist/cli.js +17 -13
- package/dist/cli.js.map +1 -1
- package/dist/core/auth/index.d.ts +13 -0
- package/dist/core/auth/index.d.ts.map +1 -0
- package/dist/core/auth/index.js +12 -0
- package/dist/core/auth/index.js.map +1 -0
- package/dist/core/auth-storage.d.ts +6 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +14 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.js +2 -2
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/auto-update.test.js +1 -1
- package/dist/core/auto-update.test.js.map +1 -1
- package/dist/core/compaction/compactor.js +2 -2
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +0 -2
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +26 -2
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/defaults.js +1 -1
- package/dist/core/mcp/defaults.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +3 -23
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/pixel-fix.test.js +2 -2
- package/dist/core/pixel-fix.test.js.map +1 -1
- package/dist/core/prompt-commands.js +5 -5
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/modes/agent-home-mode.js +3 -3
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/session.d.ts +0 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +3 -15
- package/dist/session.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +10 -4
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/prompt-hints.js +2 -2
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/ui/App.d.ts +64 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +349 -96
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +14 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +8 -5
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AssistantMessage.d.ts +1 -1
- package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
- package/dist/ui/components/AssistantMessage.js +9 -1
- package/dist/ui/components/AssistantMessage.js.map +1 -1
- package/dist/ui/components/EyesOverlay.js +2 -2
- package/dist/ui/components/EyesOverlay.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +18 -1
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +34 -7
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +24 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +19 -4
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/Markdown.d.ts +4 -4
- package/dist/ui/components/Markdown.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.d.ts +1 -1
- package/dist/ui/components/StreamingArea.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.js +7 -3
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/ThinkingBlock.d.ts +1 -1
- package/dist/ui/components/ThinkingBlock.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts +25 -0
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +46 -12
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/index.d.ts +11 -1
- package/dist/ui/components/index.d.ts.map +1 -1
- package/dist/ui/components/index.js +9 -0
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +1 -1
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.d.ts +13 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +21 -9
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/pixel.js +1 -1
- package/dist/ui/pixel.js.map +1 -1
- package/dist/ui/pixel.test.js +1 -1
- package/dist/ui/render.d.ts +57 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +130 -25
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/utils/highlight.d.ts.map +1 -1
- package/dist/ui/utils/highlight.js +13 -1
- package/dist/ui/utils/highlight.js.map +1 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +13 -1
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/open-browser.d.ts +14 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +31 -0
- package/dist/utils/open-browser.js.map +1 -0
- package/dist/utils/plan-steps.d.ts +22 -0
- package/dist/utils/plan-steps.d.ts.map +1 -1
- package/dist/utils/plan-steps.js +32 -0
- package/dist/utils/plan-steps.js.map +1 -1
- package/package.json +17 -9
- package/dist/ui/hooks/useTerminalProgress.d.ts +0 -2
- package/dist/ui/hooks/useTerminalProgress.d.ts.map +0 -1
- package/dist/ui/hooks/useTerminalProgress.js +0 -101
- package/dist/ui/hooks/useTerminalProgress.js.map +0 -1
- package/dist/utils/markdown.d.ts +0 -6
- package/dist/utils/markdown.d.ts.map +0 -1
- package/dist/utils/markdown.js +0 -25
- package/dist/utils/markdown.js.map +0 -1
package/dist/ui/App.js
CHANGED
|
@@ -11,7 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import { playNotificationSound } from "../utils/sound.js";
|
|
12
12
|
import { extractImagePaths } from "../utils/image.js";
|
|
13
13
|
import { useAgentLoop } from "./hooks/useAgentLoop.js";
|
|
14
|
-
import { isEyesActive, journalCount } from "@prestyj/
|
|
14
|
+
import { isEyesActive, journalCount } from "@prestyj/eyes";
|
|
15
15
|
import { UserMessage } from "./components/UserMessage.js";
|
|
16
16
|
import { AssistantMessage } from "./components/AssistantMessage.js";
|
|
17
17
|
import { ToolExecution } from "./components/ToolExecution.js";
|
|
@@ -36,7 +36,6 @@ import { BackgroundTasksBar } from "./components/BackgroundTasksBar.js";
|
|
|
36
36
|
import { useTheme, useSetTheme } from "./theme/theme.js";
|
|
37
37
|
import { useAnimationTick, useAnimationActive, deriveFrame, } from "./components/AnimationContext.js";
|
|
38
38
|
import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
|
|
39
|
-
import { useTerminalProgress } from "./hooks/useTerminalProgress.js";
|
|
40
39
|
import { getGitBranch } from "../utils/git.js";
|
|
41
40
|
import { getModel, getContextWindow } from "../core/model-registry.js";
|
|
42
41
|
import { SessionManager } from "../core/session-manager.js";
|
|
@@ -49,7 +48,7 @@ import { estimateConversationTokens } from "../core/compaction/token-estimator.j
|
|
|
49
48
|
import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
|
|
50
49
|
import { loadCustomCommands } from "../core/custom-commands.js";
|
|
51
50
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
52
|
-
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
51
|
+
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
53
52
|
import { getMCPServers } from "../core/mcp/index.js";
|
|
54
53
|
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
|
|
55
54
|
import { Buddy } from "./buddy/Buddy.js";
|
|
@@ -98,7 +97,7 @@ const AGGREGATABLE_TOOLS = new Set(["read", "grep", "find", "ls"]);
|
|
|
98
97
|
* we can swap out heavy objects for lightweight `{ kind: "tombstone", id }`
|
|
99
98
|
* entries so GC can reclaim the original data.
|
|
100
99
|
*/
|
|
101
|
-
const MAX_LIVE_HISTORY =
|
|
100
|
+
const MAX_LIVE_HISTORY = 200;
|
|
102
101
|
function compactHistory(items) {
|
|
103
102
|
if (items.length <= MAX_LIVE_HISTORY)
|
|
104
103
|
return items;
|
|
@@ -231,7 +230,7 @@ const THINKING_BORDER_COLORS = ["#60a5fa", "#818cf8", "#a78bfa", "#818cf8", "#60
|
|
|
231
230
|
function getTaskCount(cwd) {
|
|
232
231
|
try {
|
|
233
232
|
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
234
|
-
const data = readFileSync(join(homedir(), ".
|
|
233
|
+
const data = readFileSync(join(homedir(), ".gg-tasks", "projects", hash, "tasks.json"), "utf-8");
|
|
235
234
|
const tasks = JSON.parse(data);
|
|
236
235
|
return tasks.filter((t) => t.status !== "done").length;
|
|
237
236
|
}
|
|
@@ -242,7 +241,7 @@ function getTaskCount(cwd) {
|
|
|
242
241
|
function getNextPendingTask(cwd) {
|
|
243
242
|
try {
|
|
244
243
|
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
245
|
-
const data = readFileSync(join(homedir(), ".
|
|
244
|
+
const data = readFileSync(join(homedir(), ".gg-tasks", "projects", hash, "tasks.json"), "utf-8");
|
|
246
245
|
const tasks = JSON.parse(data);
|
|
247
246
|
const pending = tasks.find((t) => t.status === "pending");
|
|
248
247
|
if (!pending)
|
|
@@ -260,7 +259,7 @@ function getNextPendingTask(cwd) {
|
|
|
260
259
|
function markTaskInProgress(cwd, taskId) {
|
|
261
260
|
try {
|
|
262
261
|
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
263
|
-
const filePath = join(homedir(), ".
|
|
262
|
+
const filePath = join(homedir(), ".gg-tasks", "projects", hash, "tasks.json");
|
|
264
263
|
const data = readFileSync(filePath, "utf-8");
|
|
265
264
|
const tasks = JSON.parse(data);
|
|
266
265
|
const updated = tasks.map((t) => (t.id === taskId ? { ...t, status: "in-progress" } : t));
|
|
@@ -279,14 +278,17 @@ export function App(props) {
|
|
|
279
278
|
// Hoisted before terminal title hook so it can reference them
|
|
280
279
|
const [lastUserMessage, setLastUserMessage] = useState("");
|
|
281
280
|
const [exitPending, setExitPending] = useState(false);
|
|
282
|
-
|
|
281
|
+
// Initialize from planModeRef (lives outside React in cli.ts) so plan
|
|
282
|
+
// mode survives /clear's unmount/remount, matching the prior behavior
|
|
283
|
+
// where /clear didn't toggle plan mode off.
|
|
284
|
+
const [planMode, setPlanMode] = useState(props.planModeRef?.current ?? false);
|
|
283
285
|
const planModeLocalRef = useRef(false);
|
|
284
286
|
planModeLocalRef.current = planMode;
|
|
285
287
|
// Terminal title — updated later after agentLoop is created
|
|
286
288
|
// (hoisted here so the hook is always called in the same order)
|
|
287
289
|
const [titleRunning, setTitleRunning] = useState(false);
|
|
288
|
-
const [sessionTitle, setSessionTitle] = useState(
|
|
289
|
-
const sessionTitleGeneratedRef = useRef(false);
|
|
290
|
+
const [sessionTitle, setSessionTitle] = useState(() => props.sessionStore?.sessionTitle);
|
|
291
|
+
const sessionTitleGeneratedRef = useRef(props.sessionStore?.sessionTitleGenerated ?? false);
|
|
290
292
|
useTerminalTitle({
|
|
291
293
|
isRunning: titleRunning,
|
|
292
294
|
sessionTitle,
|
|
@@ -297,6 +299,11 @@ export function App(props) {
|
|
|
297
299
|
// gatsby). Ink's Static (build/components/Static.js) starts with index=0
|
|
298
300
|
// so slice(0) returns the full array regardless of length.
|
|
299
301
|
const [history, setHistory] = useState(() => {
|
|
302
|
+
// sessionStore wins (lives across remount). Falls back to initialHistory
|
|
303
|
+
// (loaded from a session file at startup), then a fresh banner-only list.
|
|
304
|
+
const stored = props.sessionStore?.history;
|
|
305
|
+
if (stored && stored.length > 0)
|
|
306
|
+
return stored;
|
|
300
307
|
if (props.initialHistory && props.initialHistory.length > 0) {
|
|
301
308
|
return compactHistory(trimFlushedItems(props.initialHistory));
|
|
302
309
|
}
|
|
@@ -304,7 +311,9 @@ export function App(props) {
|
|
|
304
311
|
});
|
|
305
312
|
// Items from the current/last turn — rendered in the live area so they stay visible
|
|
306
313
|
const [liveItems, setLiveItems] = useState([]);
|
|
307
|
-
|
|
314
|
+
// overlay seeded from sessionStore (lives across remount). Falls back to
|
|
315
|
+
// props.initialOverlay (CLI launched with one), then null.
|
|
316
|
+
const [overlay, setOverlay] = useState(props.sessionStore?.overlay ?? props.initialOverlay ?? null);
|
|
308
317
|
const [taskCount, setTaskCount] = useState(() => getTaskCount(props.cwd));
|
|
309
318
|
const [eyesCount, setEyesCount] = useState(() => isEyesActive(props.cwd) ? journalCount({ status: "open" }, props.cwd) : undefined);
|
|
310
319
|
const [updatePending, setUpdatePending] = useState(() => getPendingUpdate(props.version) !== null);
|
|
@@ -325,14 +334,14 @@ export function App(props) {
|
|
|
325
334
|
const [currentProvider, setCurrentProvider] = useState(props.provider);
|
|
326
335
|
const [currentTools, setCurrentTools] = useState(props.tools);
|
|
327
336
|
const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
|
|
328
|
-
const messagesRef = useRef(props.messages);
|
|
329
|
-
const [planAutoExpand, setPlanAutoExpand] = useState(false);
|
|
330
|
-
const approvedPlanPathRef = useRef(
|
|
331
|
-
const planStepsRef = useRef([]);
|
|
332
|
-
const [planSteps, setPlanSteps] = useState([]);
|
|
337
|
+
const messagesRef = useRef(props.sessionStore?.messages ?? props.messages);
|
|
338
|
+
const [planAutoExpand, setPlanAutoExpand] = useState(props.sessionStore?.planAutoExpand ?? false);
|
|
339
|
+
const approvedPlanPathRef = useRef(props.sessionStore?.approvedPlanPath);
|
|
340
|
+
const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
|
|
341
|
+
const [planSteps, setPlanSteps] = useState(props.sessionStore?.planSteps ?? []);
|
|
333
342
|
const nextIdRef = useRef(0);
|
|
334
343
|
const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
|
|
335
|
-
const sessionPathRef = useRef(props.sessionPath);
|
|
344
|
+
const sessionPathRef = useRef(props.sessionStore?.sessionPath ?? props.sessionPath);
|
|
336
345
|
const persistedIndexRef = useRef(messagesRef.current.length);
|
|
337
346
|
/** Last actual API-reported input token count (from turn_end). */
|
|
338
347
|
const lastActualTokensRef = useRef(0);
|
|
@@ -352,6 +361,44 @@ export function App(props) {
|
|
|
352
361
|
pendingFlushRef.current = [...pendingFlushRef.current, ...items];
|
|
353
362
|
setFlushGeneration((g) => g + 1);
|
|
354
363
|
}, []);
|
|
364
|
+
// Mirror runtime state choices (model/provider/thinking) into renderApp's
|
|
365
|
+
// closure so unmount/remount preserves them.
|
|
366
|
+
const onRuntimeStateChange = props.onRuntimeStateChange;
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
onRuntimeStateChange?.({ model: currentModel });
|
|
369
|
+
}, [currentModel, onRuntimeStateChange]);
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
onRuntimeStateChange?.({ provider: currentProvider });
|
|
372
|
+
}, [currentProvider, onRuntimeStateChange]);
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
onRuntimeStateChange?.({
|
|
375
|
+
thinking: thinkingEnabled ? (props.thinking ?? "medium") : undefined,
|
|
376
|
+
});
|
|
377
|
+
}, [thinkingEnabled, props.thinking, onRuntimeStateChange]);
|
|
378
|
+
// Mirror session state into renderApp's closure so resetUI() can re-seed
|
|
379
|
+
// the conversation on remount. Each panel that previously did a bare ANSI
|
|
380
|
+
// screen clear (overlay open/close, plan accept/reject, /clear, startTask)
|
|
381
|
+
// now goes through resetUI; without these mirrors, the chat would vanish.
|
|
382
|
+
const sessionStore = props.sessionStore;
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
if (sessionStore)
|
|
385
|
+
sessionStore.history = history;
|
|
386
|
+
}, [history, sessionStore]);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (sessionStore)
|
|
389
|
+
sessionStore.planSteps = planSteps;
|
|
390
|
+
}, [planSteps, sessionStore]);
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
if (sessionStore)
|
|
393
|
+
sessionStore.sessionTitle = sessionTitle;
|
|
394
|
+
}, [sessionTitle, sessionStore]);
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
if (sessionStore)
|
|
397
|
+
sessionStore.overlay = overlay;
|
|
398
|
+
}, [overlay, sessionStore]);
|
|
399
|
+
// pendingAction is consumed via a useEffect AFTER agentLoop is created
|
|
400
|
+
// — see below where useAgentLoop is set up.
|
|
401
|
+
const pendingActionConsumedRef = useRef(false);
|
|
355
402
|
// Derive credentials for the current provider
|
|
356
403
|
const currentCreds = props.credentialsByProvider?.[currentProvider];
|
|
357
404
|
const activeApiKey = currentCreds?.accessToken ?? props.apiKey;
|
|
@@ -423,6 +470,13 @@ export function App(props) {
|
|
|
423
470
|
// premature "done" status that fires when the agent loop finishes
|
|
424
471
|
planOverlayPendingRef.current = true;
|
|
425
472
|
setTimeout(() => {
|
|
473
|
+
// NOTE: this is the one open-overlay path that does NOT remount via
|
|
474
|
+
// resetUI. It runs while the agent is still mid-turn (after the
|
|
475
|
+
// exit_plan tool returned but before onDone fires), and unmounting
|
|
476
|
+
// here would kill the in-flight agent stream. Keep the bare ANSI
|
|
477
|
+
// clear; the drift bug is tolerable across just the agent's
|
|
478
|
+
// wrap-up turn, and onApprove/onReject both remount cleanly via
|
|
479
|
+
// resetUI when the user resolves the plan.
|
|
426
480
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
427
481
|
setPlanAutoExpand(true);
|
|
428
482
|
setOverlay("plan");
|
|
@@ -709,17 +763,53 @@ export function App(props) {
|
|
|
709
763
|
if (flushed.length > 0) {
|
|
710
764
|
queueFlush(flushed);
|
|
711
765
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
766
|
+
// Split text on [DONE:N] markers so each marker renders inline as
|
|
767
|
+
// a styled "✓ Step N: <description>" item at the position the
|
|
768
|
+
// agent emitted it, instead of vanishing into stripped whitespace.
|
|
769
|
+
// Falls back to a single assistant item containing the
|
|
770
|
+
// marker-stripped text when there are no markers (keeps the
|
|
771
|
+
// common case zero-cost).
|
|
772
|
+
const segments = segmentDisplayText(text, planStepsRef.current);
|
|
773
|
+
const items = [];
|
|
774
|
+
let thinkingAttached = false;
|
|
775
|
+
for (const seg of segments) {
|
|
776
|
+
if (seg.kind === "text") {
|
|
777
|
+
items.push({
|
|
778
|
+
kind: "assistant",
|
|
779
|
+
text: stripDoneMarkers(seg.text),
|
|
780
|
+
// Attach thinking only to the first text segment so we
|
|
781
|
+
// don't render duplicate ThinkingBlocks when a turn
|
|
782
|
+
// contains multiple text chunks split by markers.
|
|
783
|
+
thinking: thinkingAttached ? undefined : thinking,
|
|
784
|
+
thinkingMs: thinkingAttached ? undefined : thinkingMs,
|
|
785
|
+
planMode: planModeLocalRef.current,
|
|
786
|
+
id: getId(),
|
|
787
|
+
});
|
|
788
|
+
thinkingAttached = true;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
items.push({
|
|
792
|
+
kind: "step_done",
|
|
793
|
+
stepNum: seg.stepNum,
|
|
794
|
+
description: seg.description,
|
|
795
|
+
id: getId(),
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// No segments at all (text was empty/whitespace, no markers).
|
|
800
|
+
// Still emit an assistant item so a thinking block renders if
|
|
801
|
+
// there was thinking content for this turn.
|
|
802
|
+
if (items.length === 0) {
|
|
803
|
+
items.push({
|
|
715
804
|
kind: "assistant",
|
|
716
|
-
text:
|
|
805
|
+
text: "",
|
|
717
806
|
thinking,
|
|
718
807
|
thinkingMs,
|
|
719
808
|
planMode: planModeLocalRef.current,
|
|
720
809
|
id: getId(),
|
|
721
|
-
}
|
|
722
|
-
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return items;
|
|
723
813
|
});
|
|
724
814
|
}, []),
|
|
725
815
|
onToolStart: useCallback((toolCallId, name, args) => {
|
|
@@ -1189,6 +1279,33 @@ export function App(props) {
|
|
|
1189
1279
|
useEffect(() => {
|
|
1190
1280
|
setTitleRunning(agentLoop.isRunning);
|
|
1191
1281
|
}, [agentLoop.isRunning]);
|
|
1282
|
+
// Consume sessionStore.pendingAction once on mount. Set by resetUI options
|
|
1283
|
+
// for paths that remount AND immediately drive the agent (plan accept,
|
|
1284
|
+
// plan reject, startTask, pixel fix). The action survives the unmount
|
|
1285
|
+
// because it lives in renderApp's closure (sessionStore), not React state.
|
|
1286
|
+
useEffect(() => {
|
|
1287
|
+
if (pendingActionConsumedRef.current)
|
|
1288
|
+
return;
|
|
1289
|
+
const action = sessionStore?.pendingAction;
|
|
1290
|
+
if (!action)
|
|
1291
|
+
return;
|
|
1292
|
+
pendingActionConsumedRef.current = true;
|
|
1293
|
+
if (sessionStore)
|
|
1294
|
+
sessionStore.pendingAction = undefined;
|
|
1295
|
+
if (action.infoText) {
|
|
1296
|
+
setLiveItems((prev) => [
|
|
1297
|
+
...prev,
|
|
1298
|
+
{ kind: "info", text: action.infoText, id: getId() },
|
|
1299
|
+
]);
|
|
1300
|
+
}
|
|
1301
|
+
setDoneStatus(null);
|
|
1302
|
+
void agentLoop.run(action.prompt).catch((err) => {
|
|
1303
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1304
|
+
log("ERROR", "error", errMsg);
|
|
1305
|
+
setLiveItems((prev) => [...prev, { kind: "error", message: errMsg, id: getId() }]);
|
|
1306
|
+
});
|
|
1307
|
+
// Intentional one-shot: run once on mount, never re-fire on re-render.
|
|
1308
|
+
}, []);
|
|
1192
1309
|
// Refresh eyes badge count when the agent settles (end of a turn) — a turn
|
|
1193
1310
|
// may have logged new rough/wish/blocked signals. Also covers the case where
|
|
1194
1311
|
// /eyes was run for the first time (manifest now exists).
|
|
@@ -1197,8 +1314,6 @@ export function App(props) {
|
|
|
1197
1314
|
setEyesCount(isEyesActive(props.cwd) ? journalCount({ status: "open" }, props.cwd) : undefined);
|
|
1198
1315
|
}
|
|
1199
1316
|
}, [agentLoop.isRunning, props.cwd]);
|
|
1200
|
-
// Terminal progress bar (OSC 9;4) — pulsing bar in supported terminals
|
|
1201
|
-
useTerminalProgress(agentLoop.isRunning, agentLoop.activeToolCalls.length > 0);
|
|
1202
1317
|
// Animated thinking border — derived from global animation tick
|
|
1203
1318
|
useAnimationActive();
|
|
1204
1319
|
const animTick = useAnimationTick();
|
|
@@ -1232,13 +1347,29 @@ export function App(props) {
|
|
|
1232
1347
|
if (trimmed === "/quit" || trimmed === "/q" || trimmed === "/exit") {
|
|
1233
1348
|
process.exit(0);
|
|
1234
1349
|
}
|
|
1235
|
-
// Handle /clear —
|
|
1350
|
+
// Handle /clear — tear down the entire Ink instance and rebuild fresh.
|
|
1351
|
+
// Patching Ink's internal frame tracking in place (log-update reset,
|
|
1352
|
+
// lastOutput cleared, fullStaticOutput dropped, staticKey bump) all
|
|
1353
|
+
// looked correct for one frame but left the live area drifting on
|
|
1354
|
+
// subsequent streaming responses — Ink's cursor math depends on
|
|
1355
|
+
// terminal-state assumptions that ANSI clearing breaks. The reliable
|
|
1356
|
+
// fix is unmount + render again. Runtime state (model, provider,
|
|
1357
|
+
// thinking) survives via renderApp's closure-held `runtimeState`,
|
|
1358
|
+
// mirrored from React state via the useEffects above.
|
|
1236
1359
|
if (trimmed === "/clear") {
|
|
1237
|
-
|
|
1238
|
-
|
|
1360
|
+
if (props.resetUI) {
|
|
1361
|
+
void (async () => {
|
|
1362
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, undefined);
|
|
1363
|
+
props.resetUI?.({
|
|
1364
|
+
wipeSession: true,
|
|
1365
|
+
messages: [{ role: "system", content: newPrompt }],
|
|
1366
|
+
});
|
|
1367
|
+
})();
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
// Fallback path (resetUI not wired — e.g. tests). Best-effort: clear
|
|
1371
|
+
// React state in place. The Ink-internal drift bug remains here.
|
|
1239
1372
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1240
|
-
// Discard any items queued for two-phase flush so they don't leak
|
|
1241
|
-
// into the new session after the Static remount.
|
|
1242
1373
|
pendingFlushRef.current = [];
|
|
1243
1374
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
1244
1375
|
setLiveItems([]);
|
|
@@ -1246,7 +1377,6 @@ export function App(props) {
|
|
|
1246
1377
|
approvedPlanPathRef.current = undefined;
|
|
1247
1378
|
planStepsRef.current = [];
|
|
1248
1379
|
setPlanSteps([]);
|
|
1249
|
-
// Rebuild system prompt without the approved plan
|
|
1250
1380
|
void (async () => {
|
|
1251
1381
|
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, undefined);
|
|
1252
1382
|
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
@@ -1255,8 +1385,6 @@ export function App(props) {
|
|
|
1255
1385
|
agentLoop.reset();
|
|
1256
1386
|
setSessionTitle(undefined);
|
|
1257
1387
|
sessionTitleGeneratedRef.current = false;
|
|
1258
|
-
// Bump staticKey to force Ink's <Static> to remount, discarding its
|
|
1259
|
-
// internal record of previously rendered items so they don't reappear.
|
|
1260
1388
|
setStaticKey((k) => k + 1);
|
|
1261
1389
|
setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
|
|
1262
1390
|
return;
|
|
@@ -1342,9 +1470,16 @@ export function App(props) {
|
|
|
1342
1470
|
}
|
|
1343
1471
|
// Handle /plans — open plan pane
|
|
1344
1472
|
if (trimmed === "/plans") {
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1473
|
+
if (props.resetUI && props.sessionStore) {
|
|
1474
|
+
props.sessionStore.overlay = "plan";
|
|
1475
|
+
props.sessionStore.planAutoExpand = false;
|
|
1476
|
+
props.resetUI();
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1480
|
+
setPlanAutoExpand(false);
|
|
1481
|
+
setOverlay("plan");
|
|
1482
|
+
}
|
|
1348
1483
|
return;
|
|
1349
1484
|
}
|
|
1350
1485
|
// Handle prompt-template commands (built-in + custom from .ezcoder/commands/)
|
|
@@ -1710,6 +1845,8 @@ export function App(props) {
|
|
|
1710
1845
|
return (_jsx(Box, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: theme.success, paddingX: 1, children: _jsxs(Text, { color: theme.success, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }, item.id));
|
|
1711
1846
|
case "plan_transition":
|
|
1712
1847
|
return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsxs(Text, { color: theme.planPrimary, bold: true, wrap: "wrap", children: [item.active ? "● " : "● ", item.text] }) }, item.id));
|
|
1848
|
+
case "step_done":
|
|
1849
|
+
return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "✓ " }), _jsx(Text, { color: theme.success, bold: true, children: `Step ${item.stepNum} done` }), item.description ? (_jsx(Text, { color: theme.textDim, children: ` — ${item.description}` })) : null] }) }, item.id));
|
|
1713
1850
|
case "queued":
|
|
1714
1851
|
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: "⏳ Queued: " }), _jsxs(Text, { color: theme.text, wrap: "wrap", children: [item.text, item.imageCount
|
|
1715
1852
|
? ` (+${item.imageCount} image${item.imageCount > 1 ? "s" : ""})`
|
|
@@ -1727,7 +1864,42 @@ export function App(props) {
|
|
|
1727
1864
|
// ── Start a task (shared by manual "work on it" and run-all) ──
|
|
1728
1865
|
const startTask = useCallback((title, prompt, taskId) => {
|
|
1729
1866
|
setTaskCount(getTaskCount(props.cwd));
|
|
1730
|
-
|
|
1867
|
+
const shortId = taskId.slice(0, 8);
|
|
1868
|
+
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
1869
|
+
`tasks({ action: "done", id: "${shortId}" })`;
|
|
1870
|
+
const fullPrompt = prompt + completionHint;
|
|
1871
|
+
if (props.resetUI && props.sessionStore) {
|
|
1872
|
+
// Preserve the current system prompt (may differ from the launch
|
|
1873
|
+
// config — e.g. plan mode toggled or skills changed).
|
|
1874
|
+
const sysMsg = messagesRef.current[0];
|
|
1875
|
+
const newMessages = sysMsg && sysMsg.role === "system" ? [sysMsg] : messagesRef.current.slice(0, 1);
|
|
1876
|
+
const taskItem = { kind: "task", title, id: String(nextIdRef.current++) };
|
|
1877
|
+
const sm = sessionManagerRef.current;
|
|
1878
|
+
void (async () => {
|
|
1879
|
+
let newSessionPath;
|
|
1880
|
+
if (sm) {
|
|
1881
|
+
try {
|
|
1882
|
+
const s = await sm.create(props.cwd, currentProvider, currentModel);
|
|
1883
|
+
newSessionPath = s.path;
|
|
1884
|
+
log("INFO", "tasks", "New session for task", { path: s.path });
|
|
1885
|
+
}
|
|
1886
|
+
catch {
|
|
1887
|
+
// session creation is best-effort
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (props.sessionStore)
|
|
1891
|
+
props.sessionStore.overlay = null;
|
|
1892
|
+
props.resetUI?.({
|
|
1893
|
+
wipeSession: true,
|
|
1894
|
+
messages: newMessages,
|
|
1895
|
+
history: [{ kind: "banner", id: "banner" }, taskItem],
|
|
1896
|
+
sessionPath: newSessionPath,
|
|
1897
|
+
pendingAction: { prompt: fullPrompt },
|
|
1898
|
+
});
|
|
1899
|
+
})();
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
// Fallback path (resetUI not wired — tests).
|
|
1731
1903
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1732
1904
|
setHistory([{ kind: "banner", id: "banner" }]);
|
|
1733
1905
|
setLiveItems([]);
|
|
@@ -1741,12 +1913,6 @@ export function App(props) {
|
|
|
1741
1913
|
log("INFO", "tasks", "New session for task", { path: s.path });
|
|
1742
1914
|
});
|
|
1743
1915
|
}
|
|
1744
|
-
// Inject completion instruction so the agent marks the task done
|
|
1745
|
-
const shortId = taskId.slice(0, 8);
|
|
1746
|
-
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
1747
|
-
`tasks({ action: "done", id: "${shortId}" })`;
|
|
1748
|
-
const fullPrompt = prompt + completionHint;
|
|
1749
|
-
// Show the short title in the TUI, but send the full prompt to the agent
|
|
1750
1916
|
const taskItem = { kind: "task", title, id: getId() };
|
|
1751
1917
|
setLastUserMessage(title);
|
|
1752
1918
|
setDoneStatus(null);
|
|
@@ -1765,11 +1931,18 @@ export function App(props) {
|
|
|
1765
1931
|
? { kind: "info", text: "Request was stopped.", id: getId() }
|
|
1766
1932
|
: { kind: "error", message: msg, id: getId() },
|
|
1767
1933
|
]);
|
|
1768
|
-
// Stop run-all if a task errors
|
|
1769
1934
|
setRunAllTasks(false);
|
|
1770
1935
|
}
|
|
1771
1936
|
})();
|
|
1772
|
-
}, [
|
|
1937
|
+
}, [
|
|
1938
|
+
props.cwd,
|
|
1939
|
+
props.resetUI,
|
|
1940
|
+
props.sessionStore,
|
|
1941
|
+
stdout,
|
|
1942
|
+
agentLoop,
|
|
1943
|
+
currentProvider,
|
|
1944
|
+
currentModel,
|
|
1945
|
+
]);
|
|
1773
1946
|
// Keep refs in sync for access from stale closures (onDone)
|
|
1774
1947
|
startTaskRef.current = startTask;
|
|
1775
1948
|
useEffect(() => {
|
|
@@ -1853,10 +2026,16 @@ export function App(props) {
|
|
|
1853
2026
|
const isPixelView = overlay === "pixel";
|
|
1854
2027
|
const isOverlayView = isTaskView || isSkillsView || isPlanView || isEyesView || isPixelView;
|
|
1855
2028
|
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: isOverlayView ? [] : history, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, `${resizeKey}-${staticKey}`), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
2029
|
+
if (props.resetUI && props.sessionStore) {
|
|
2030
|
+
props.sessionStore.overlay = null;
|
|
2031
|
+
props.resetUI();
|
|
2032
|
+
}
|
|
2033
|
+
else {
|
|
2034
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2035
|
+
setTaskCount(getTaskCount(props.cwd));
|
|
2036
|
+
setStaticKey((k) => k + 1);
|
|
2037
|
+
setOverlay(null);
|
|
2038
|
+
}
|
|
1860
2039
|
}, onWorkOnTask: (title, prompt, taskId) => {
|
|
1861
2040
|
setOverlay(null);
|
|
1862
2041
|
startTask(title, prompt, taskId);
|
|
@@ -1869,9 +2048,15 @@ export function App(props) {
|
|
|
1869
2048
|
startTask(next.title, next.prompt, next.id);
|
|
1870
2049
|
}
|
|
1871
2050
|
} })) : isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2051
|
+
if (props.resetUI && props.sessionStore) {
|
|
2052
|
+
props.sessionStore.overlay = null;
|
|
2053
|
+
props.resetUI();
|
|
2054
|
+
}
|
|
2055
|
+
else {
|
|
2056
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2057
|
+
setStaticKey((k) => k + 1);
|
|
2058
|
+
setOverlay(null);
|
|
2059
|
+
}
|
|
1875
2060
|
}, onFixOne: (entry) => {
|
|
1876
2061
|
setOverlay(null);
|
|
1877
2062
|
startPixelFix(entry.errorId);
|
|
@@ -1883,57 +2068,95 @@ export function App(props) {
|
|
|
1883
2068
|
setRunAllPixel(true);
|
|
1884
2069
|
startPixelFix(first.errorId);
|
|
1885
2070
|
} })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2071
|
+
if (props.resetUI && props.sessionStore) {
|
|
2072
|
+
props.sessionStore.overlay = null;
|
|
2073
|
+
props.resetUI();
|
|
2074
|
+
}
|
|
2075
|
+
else {
|
|
2076
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2077
|
+
setStaticKey((k) => k + 1);
|
|
2078
|
+
setOverlay(null);
|
|
2079
|
+
}
|
|
1889
2080
|
} })) : isEyesView ? (_jsx(EyesOverlay, { cwd: props.cwd, onClose: () => {
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2081
|
+
if (props.resetUI && props.sessionStore) {
|
|
2082
|
+
props.sessionStore.overlay = null;
|
|
2083
|
+
props.resetUI();
|
|
2084
|
+
}
|
|
2085
|
+
else {
|
|
2086
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2087
|
+
setEyesCount(isEyesActive(props.cwd) ? journalCount({ status: "open" }, props.cwd) : undefined);
|
|
2088
|
+
setStaticKey((k) => k + 1);
|
|
2089
|
+
setOverlay(null);
|
|
2090
|
+
}
|
|
1894
2091
|
}, onQueueMessage: (msg) => {
|
|
1895
2092
|
agentLoop.queueMessage(msg);
|
|
1896
2093
|
} })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
|
|
1897
2094
|
planOverlayPendingRef.current = false;
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2095
|
+
if (props.resetUI && props.sessionStore) {
|
|
2096
|
+
props.sessionStore.overlay = null;
|
|
2097
|
+
props.sessionStore.planAutoExpand = false;
|
|
2098
|
+
props.resetUI();
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2102
|
+
setStaticKey((k) => k + 1);
|
|
2103
|
+
setPlanAutoExpand(false);
|
|
2104
|
+
setOverlay(null);
|
|
2105
|
+
}
|
|
1902
2106
|
}, onApprove: (planPath) => {
|
|
1903
2107
|
log("INFO", "plan", "Plan approved — transitioning to implementation", {
|
|
1904
2108
|
planPath,
|
|
1905
2109
|
});
|
|
1906
|
-
// Plan overlay dismissed — allow future onDone to fire normally
|
|
1907
2110
|
planOverlayPendingRef.current = false;
|
|
1908
|
-
// Store approved plan path — will be injected into the new system prompt
|
|
1909
|
-
approvedPlanPathRef.current = planPath;
|
|
1910
|
-
// Extract plan steps for progress tracking
|
|
1911
|
-
void import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8").then((content) => {
|
|
1912
|
-
const steps = extractPlanSteps(content);
|
|
1913
|
-
planStepsRef.current = steps;
|
|
1914
|
-
setPlanSteps(steps);
|
|
1915
|
-
}));
|
|
1916
|
-
// Clear session for a fresh context focused on the plan
|
|
1917
|
-
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1918
|
-
setHistory([{ kind: "banner", id: "banner" }]);
|
|
1919
|
-
setLiveItems([]);
|
|
1920
|
-
setStaticKey((k) => k + 1);
|
|
1921
|
-
setPlanAutoExpand(false);
|
|
1922
|
-
setOverlay(null);
|
|
1923
|
-
// Rebuild system prompt with the approved plan, then reset the session
|
|
1924
2111
|
void (async () => {
|
|
1925
2112
|
try {
|
|
2113
|
+
// Read plan steps for progress tracking — handed to the new
|
|
2114
|
+
// mount via sessionStore.planSteps below.
|
|
2115
|
+
const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
|
|
2116
|
+
const steps = extractPlanSteps(planContent);
|
|
2117
|
+
// Build the new system prompt with the approved plan baked in.
|
|
1926
2118
|
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, false, planPath);
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
// Create a new session file
|
|
2119
|
+
// Create a new session file BEFORE remount so the new tree
|
|
2120
|
+
// picks it up via sessionStore.sessionPath.
|
|
2121
|
+
let newSessionPath;
|
|
1931
2122
|
const sm = sessionManagerRef.current;
|
|
1932
2123
|
if (sm) {
|
|
1933
2124
|
const s = await sm.create(props.cwd, currentProvider, currentModel);
|
|
1934
|
-
|
|
2125
|
+
newSessionPath = s.path;
|
|
2126
|
+
}
|
|
2127
|
+
if (props.resetUI && props.sessionStore) {
|
|
2128
|
+
// Clear the overlay so the new mount lands on the chat,
|
|
2129
|
+
// not back inside the plan pane.
|
|
2130
|
+
props.sessionStore.overlay = null;
|
|
2131
|
+
props.sessionStore.planAutoExpand = false;
|
|
2132
|
+
props.resetUI({
|
|
2133
|
+
wipeSession: true,
|
|
2134
|
+
messages: [{ role: "system", content: newPrompt }],
|
|
2135
|
+
approvedPlanPath: planPath,
|
|
2136
|
+
planSteps: steps,
|
|
2137
|
+
sessionPath: newSessionPath,
|
|
2138
|
+
pendingAction: {
|
|
2139
|
+
prompt: "The plan has been approved. Implement it now, following each step in order.",
|
|
2140
|
+
infoText: "Plan approved — starting fresh session for implementation",
|
|
2141
|
+
},
|
|
2142
|
+
});
|
|
2143
|
+
return;
|
|
1935
2144
|
}
|
|
1936
|
-
//
|
|
2145
|
+
// Fallback path (resetUI not wired — tests). Mutate in place.
|
|
2146
|
+
approvedPlanPathRef.current = planPath;
|
|
2147
|
+
planStepsRef.current = steps;
|
|
2148
|
+
setPlanSteps(steps);
|
|
2149
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2150
|
+
setHistory([{ kind: "banner", id: "banner" }]);
|
|
2151
|
+
setLiveItems([]);
|
|
2152
|
+
setStaticKey((k) => k + 1);
|
|
2153
|
+
setPlanAutoExpand(false);
|
|
2154
|
+
setOverlay(null);
|
|
2155
|
+
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
2156
|
+
agentLoop.reset();
|
|
2157
|
+
persistedIndexRef.current = messagesRef.current.length;
|
|
2158
|
+
if (newSessionPath)
|
|
2159
|
+
sessionPathRef.current = newSessionPath;
|
|
1937
2160
|
setLiveItems([
|
|
1938
2161
|
{
|
|
1939
2162
|
kind: "info",
|
|
@@ -1952,19 +2175,31 @@ export function App(props) {
|
|
|
1952
2175
|
})();
|
|
1953
2176
|
}, onReject: (planPath, feedback) => {
|
|
1954
2177
|
planOverlayPendingRef.current = false;
|
|
2178
|
+
const rejectionMsg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
|
|
2179
|
+
`Please revise the plan based on this feedback.`;
|
|
2180
|
+
if (props.resetUI && props.sessionStore) {
|
|
2181
|
+
props.sessionStore.overlay = null;
|
|
2182
|
+
props.sessionStore.planAutoExpand = false;
|
|
2183
|
+
// No wipeSession — keep history, messages, plan mode etc. The
|
|
2184
|
+
// agent picks up the rejection mid-conversation.
|
|
2185
|
+
props.resetUI({
|
|
2186
|
+
pendingAction: {
|
|
2187
|
+
prompt: rejectionMsg,
|
|
2188
|
+
infoText: `Plan rejected — "${feedback}"`,
|
|
2189
|
+
},
|
|
2190
|
+
});
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
1955
2193
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1956
2194
|
setStaticKey((k) => k + 1);
|
|
1957
2195
|
setPlanAutoExpand(false);
|
|
1958
2196
|
setOverlay(null);
|
|
1959
2197
|
setDoneStatus(null);
|
|
1960
|
-
// Send rejection + feedback to the agent
|
|
1961
|
-
const msg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
|
|
1962
|
-
`Please revise the plan based on this feedback.`;
|
|
1963
2198
|
setLiveItems((prev) => [
|
|
1964
2199
|
...prev,
|
|
1965
2200
|
{ kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
|
|
1966
2201
|
]);
|
|
1967
|
-
void agentLoop.run(
|
|
2202
|
+
void agentLoop.run(rejectionMsg).catch((err) => {
|
|
1968
2203
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1969
2204
|
log("ERROR", "error", errMsg);
|
|
1970
2205
|
setLiveItems((prev) => [...prev, { kind: "error", message: errMsg, id: getId() }]);
|
|
@@ -1973,14 +2208,32 @@ export function App(props) {
|
|
|
1973
2208
|
? THINKING_BORDER_COLORS[thinkingBorderFrame]
|
|
1974
2209
|
: "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, 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 }) })) : 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 &&
|
|
1975
2210
|
!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: () => {
|
|
1976
|
-
|
|
1977
|
-
|
|
2211
|
+
if (props.resetUI && props.sessionStore) {
|
|
2212
|
+
props.sessionStore.overlay = "tasks";
|
|
2213
|
+
props.resetUI();
|
|
2214
|
+
}
|
|
2215
|
+
else {
|
|
2216
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2217
|
+
setOverlay("tasks");
|
|
2218
|
+
}
|
|
1978
2219
|
}, onToggleSkills: () => {
|
|
1979
|
-
|
|
1980
|
-
|
|
2220
|
+
if (props.resetUI && props.sessionStore) {
|
|
2221
|
+
props.sessionStore.overlay = "skills";
|
|
2222
|
+
props.resetUI();
|
|
2223
|
+
}
|
|
2224
|
+
else {
|
|
2225
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2226
|
+
setOverlay("skills");
|
|
2227
|
+
}
|
|
1981
2228
|
}, onTogglePixel: () => {
|
|
1982
|
-
|
|
1983
|
-
|
|
2229
|
+
if (props.resetUI && props.sessionStore) {
|
|
2230
|
+
props.sessionStore.overlay = "pixel";
|
|
2231
|
+
props.resetUI();
|
|
2232
|
+
}
|
|
2233
|
+
else {
|
|
2234
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
2235
|
+
setOverlay("pixel");
|
|
2236
|
+
}
|
|
1984
2237
|
}, onTogglePlanMode: () => {
|
|
1985
2238
|
const next = !planMode;
|
|
1986
2239
|
setPlanMode(next);
|