@prestyj/cli 4.4.0 → 4.5.0
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.d.ts.map +1 -1
- package/dist/cli.js +9 -1
- 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 +5 -2
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +10 -0
- package/dist/config.test.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/goal-worker.js +1 -1
- package/dist/core/goal-worker.js.map +1 -1
- package/dist/core/goal-worker.test.js +1 -1
- package/dist/core/goal-worker.test.js.map +1 -1
- package/dist/core/mcp/defaults.d.ts.map +1 -1
- package/dist/core/mcp/defaults.js +39 -3
- package/dist/core/mcp/defaults.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +3 -1
- package/dist/core/prompt-commands.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/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +18 -6
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +24 -0
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/enter-plan.d.ts +1 -1
- package/dist/tools/enter-plan.d.ts.map +1 -1
- package/dist/tools/enter-plan.js +8 -1
- package/dist/tools/enter-plan.js.map +1 -1
- package/dist/tools/index.d.ts +6 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/ui/App.d.ts +4 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +140 -4
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +1 -1
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/components/ChatScreen.d.ts +1 -0
- package/dist/ui/components/ChatScreen.d.ts.map +1 -1
- package/dist/ui/components/ChatScreen.js +1 -1
- package/dist/ui/components/ChatScreen.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +2 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +7 -1
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/render.d.ts +9 -1
- 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/terminal-history-status-renderers.d.ts +1 -1
- package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
- package/dist/ui/terminal-history-status-renderers.js +1 -0
- package/dist/ui/terminal-history-status-renderers.js.map +1 -1
- package/dist/ui/transcript/presentation.d.ts.map +1 -1
- package/dist/ui/transcript/presentation.js +1 -0
- package/dist/ui/transcript/presentation.js.map +1 -1
- package/package.json +4 -3
package/dist/ui/App.js
CHANGED
|
@@ -282,6 +282,10 @@ export function App(props) {
|
|
|
282
282
|
const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
|
|
283
283
|
const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
|
|
284
284
|
const startTaskRef = useRef(() => { });
|
|
285
|
+
// True while a task-pane task (single or run-all) is executing. Used to
|
|
286
|
+
// suppress the interactive plan-mode approval pane, which would otherwise
|
|
287
|
+
// stall an unattended task run waiting for the user to approve a plan.
|
|
288
|
+
const taskRunningRef = useRef(props.sessionStore?.taskRunning ?? false);
|
|
285
289
|
const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
|
|
286
290
|
const currentPixelFixRef = useRef(null);
|
|
287
291
|
const startPixelFixRef = useRef(() => { });
|
|
@@ -343,6 +347,8 @@ export function App(props) {
|
|
|
343
347
|
createSessionStats({ sessionId: props.sessionStore?.sessionId ?? props.sessionId }));
|
|
344
348
|
const [idealReviewEnabled, setIdealReviewEnabled] = useState(props.sessionStore?.idealReviewEnabled ?? props.idealReviewEnabled ?? true);
|
|
345
349
|
const idealReviewEnabledRef = useRef(idealReviewEnabled);
|
|
350
|
+
const [autoApprovePlansEnabled, setAutoApprovePlansEnabled] = useState(props.sessionStore?.autoApprovePlans ?? props.autoApprovePlans ?? true);
|
|
351
|
+
const autoApprovePlansEnabledRef = useRef(autoApprovePlansEnabled);
|
|
346
352
|
/** Last actual API-reported input token count (from turn_end). */
|
|
347
353
|
const lastActualTokensRef = useRef(0);
|
|
348
354
|
/** Timestamp (ms) when lastActualTokensRef was last updated by turn_end. */
|
|
@@ -368,11 +374,21 @@ export function App(props) {
|
|
|
368
374
|
*/
|
|
369
375
|
const triggerAutoSetupRef = useRef(async () => { });
|
|
370
376
|
const getId = () => `ui-${nextIdRef.current++}`;
|
|
377
|
+
// Single predicate for "no human is present to approve a plan": task-list
|
|
378
|
+
// runs (single + run-all, via taskRunningRef) and pixel-fix runs (single +
|
|
379
|
+
// run-all-pixel, via currentPixelFixRef). Every autonomous context routes
|
|
380
|
+
// through here so future ones only update one place.
|
|
381
|
+
const isUnattendedRun = () => taskRunningRef.current || currentPixelFixRef.current !== null;
|
|
371
382
|
useEffect(() => {
|
|
372
383
|
idealReviewEnabledRef.current = idealReviewEnabled;
|
|
373
384
|
if (props.sessionStore)
|
|
374
385
|
props.sessionStore.idealReviewEnabled = idealReviewEnabled;
|
|
375
386
|
}, [idealReviewEnabled, props.sessionStore]);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
autoApprovePlansEnabledRef.current = autoApprovePlansEnabled;
|
|
389
|
+
if (props.sessionStore)
|
|
390
|
+
props.sessionStore.autoApprovePlans = autoApprovePlansEnabled;
|
|
391
|
+
}, [autoApprovePlansEnabled, props.sessionStore]);
|
|
376
392
|
const sessionStore = props.sessionStore;
|
|
377
393
|
const { planMode, goalMode, rebuildSystemPrompt, replaceSystemPrompt, setPlanModeAndPrompt, setGoalModeAndPrompt, } = useModeState({
|
|
378
394
|
initialPlanMode: props.sessionStore?.planMode ?? props.planModeRef?.current ?? false,
|
|
@@ -577,8 +593,25 @@ export function App(props) {
|
|
|
577
593
|
void props
|
|
578
594
|
.connectInitialMcpTools()
|
|
579
595
|
.then((mcpTools) => {
|
|
580
|
-
if (cancelled
|
|
596
|
+
if (cancelled)
|
|
581
597
|
return;
|
|
598
|
+
if (mcpTools.length === 0) {
|
|
599
|
+
// No MCP tools connected (e.g. kencode-search failed to boot). The
|
|
600
|
+
// system prompt was built without them, so the model is never told
|
|
601
|
+
// to call tools it doesn't have — but flag it so the user knows
|
|
602
|
+
// public-code research is degraded this session.
|
|
603
|
+
log("WARN", "mcp", "No MCP tools connected at startup");
|
|
604
|
+
setLiveItems((prev) => [
|
|
605
|
+
...prev,
|
|
606
|
+
{
|
|
607
|
+
kind: "info",
|
|
608
|
+
text: "Research tools (kencode-search) didn't connect — public-code search is unavailable this session.",
|
|
609
|
+
id: getId(),
|
|
610
|
+
},
|
|
611
|
+
]);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
log("INFO", "mcp", `MCP tools ready: ${mcpTools.length} tool(s)`);
|
|
582
615
|
setCurrentTools((prev) => {
|
|
583
616
|
const next = [...prev.filter((tool) => !tool.name.startsWith("mcp__")), ...mcpTools];
|
|
584
617
|
currentToolsRef.current = next;
|
|
@@ -1373,6 +1406,11 @@ export function App(props) {
|
|
|
1373
1406
|
duration: `${durationMs}ms`,
|
|
1374
1407
|
toolsUsed: toolsUsed.join(",") || "none",
|
|
1375
1408
|
});
|
|
1409
|
+
// The task run (if any) is finished. Run-all re-arms this below when it
|
|
1410
|
+
// auto-starts the next task.
|
|
1411
|
+
taskRunningRef.current = false;
|
|
1412
|
+
if (props.sessionStore)
|
|
1413
|
+
props.sessionStore.taskRunning = false;
|
|
1376
1414
|
const doneDecision = getDoneFlushDecision({
|
|
1377
1415
|
planOverlayPending: planOverlayPendingRef.current,
|
|
1378
1416
|
});
|
|
@@ -1935,6 +1973,12 @@ export function App(props) {
|
|
|
1935
1973
|
if (sessionStore) {
|
|
1936
1974
|
sessionStore.pendingAction = undefined;
|
|
1937
1975
|
}
|
|
1976
|
+
// Re-arm the task-running flag after the remount: startTask sets it but the
|
|
1977
|
+
// resetUI() remount drops the plain ref, so without this an unattended task
|
|
1978
|
+
// run would look interactive (and stall on plan-mode approval).
|
|
1979
|
+
if (sessionStore?.taskRunning) {
|
|
1980
|
+
taskRunningRef.current = true;
|
|
1981
|
+
}
|
|
1938
1982
|
setDoneStatus(null);
|
|
1939
1983
|
if (action.planEvent) {
|
|
1940
1984
|
const ev = action.planEvent;
|
|
@@ -1993,6 +2037,26 @@ export function App(props) {
|
|
|
1993
2037
|
]);
|
|
1994
2038
|
return;
|
|
1995
2039
|
}
|
|
2040
|
+
if (trimmed === "/autoplan-on" || trimmed === "/autoplan-off") {
|
|
2041
|
+
const next = trimmed === "/autoplan-on";
|
|
2042
|
+
setAutoApprovePlansEnabled(next);
|
|
2043
|
+
if (props.settingsFile) {
|
|
2044
|
+
const sm = new SettingsManager(props.settingsFile);
|
|
2045
|
+
await sm.load();
|
|
2046
|
+
await sm.set("autoApprovePlans", next);
|
|
2047
|
+
}
|
|
2048
|
+
setLiveItems((prev) => [
|
|
2049
|
+
...prev,
|
|
2050
|
+
{
|
|
2051
|
+
kind: "info",
|
|
2052
|
+
text: next
|
|
2053
|
+
? "Auto-approve plans enabled — unattended runs approve plans and keep going. Use /autoplan-off to disable."
|
|
2054
|
+
: "Auto-approve plans disabled — unattended runs decline plan mode instead. Use /autoplan-on to enable.",
|
|
2055
|
+
id: getId(),
|
|
2056
|
+
},
|
|
2057
|
+
]);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
1996
2060
|
// /rewind — open the checkpoint picker (needs React state + the store).
|
|
1997
2061
|
if (trimmed === "/rewind") {
|
|
1998
2062
|
const store = props.checkpointStore;
|
|
@@ -2450,6 +2514,14 @@ export function App(props) {
|
|
|
2450
2514
|
: "Enable pre-final ideal review",
|
|
2451
2515
|
sectionTitle: "built-in",
|
|
2452
2516
|
},
|
|
2517
|
+
{
|
|
2518
|
+
name: autoApprovePlansEnabled ? "autoplan-off" : "autoplan-on",
|
|
2519
|
+
aliases: [],
|
|
2520
|
+
description: autoApprovePlansEnabled
|
|
2521
|
+
? "Disable auto-approving plans in unattended runs"
|
|
2522
|
+
: "Enable auto-approving plans in unattended runs",
|
|
2523
|
+
sectionTitle: "built-in",
|
|
2524
|
+
},
|
|
2453
2525
|
{
|
|
2454
2526
|
name: "rewind",
|
|
2455
2527
|
aliases: [],
|
|
@@ -2471,7 +2543,7 @@ export function App(props) {
|
|
|
2471
2543
|
sectionTitle: "built-in",
|
|
2472
2544
|
},
|
|
2473
2545
|
];
|
|
2474
|
-
}, [customCommands, idealReviewEnabled]);
|
|
2546
|
+
}, [customCommands, idealReviewEnabled, autoApprovePlansEnabled]);
|
|
2475
2547
|
const renderItem = (item, index, items) => renderTranscriptItem({
|
|
2476
2548
|
item,
|
|
2477
2549
|
index,
|
|
@@ -2557,6 +2629,9 @@ export function App(props) {
|
|
|
2557
2629
|
// onDone can invoke it from stale closures.
|
|
2558
2630
|
const startTask = useCallback((title, prompt, taskId) => {
|
|
2559
2631
|
const taskCwd = cwdRef.current;
|
|
2632
|
+
taskRunningRef.current = true;
|
|
2633
|
+
if (props.sessionStore)
|
|
2634
|
+
props.sessionStore.taskRunning = true;
|
|
2560
2635
|
const shortId = taskId.slice(0, 8);
|
|
2561
2636
|
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
2562
2637
|
`tasks({ action: "done", id: "${shortId}" })`;
|
|
@@ -2868,13 +2943,48 @@ export function App(props) {
|
|
|
2868
2943
|
setOverlay(null);
|
|
2869
2944
|
};
|
|
2870
2945
|
const handleEnterPlanMode = useCallback(async (reason) => {
|
|
2946
|
+
// During an unattended run with auto-approve disabled, plan mode would open
|
|
2947
|
+
// an approval pane and stall the loop waiting on a human. Decline plan mode
|
|
2948
|
+
// so the agent implements directly. When auto-approve is enabled we DO allow
|
|
2949
|
+
// plan mode — handleExitPlanMode auto-approves the plan in place and the run
|
|
2950
|
+
// keeps going, preserving the explore→plan→implement process.
|
|
2951
|
+
if (isUnattendedRun() && !autoApprovePlansEnabledRef.current)
|
|
2952
|
+
return false;
|
|
2871
2953
|
await setPlanModeAndPrompt(true);
|
|
2872
2954
|
setLiveItems((prev) => [
|
|
2873
2955
|
...prev,
|
|
2874
2956
|
{ kind: "plan_transition", text: reason ?? "", id: getId(), active: true },
|
|
2875
2957
|
]);
|
|
2958
|
+
return true;
|
|
2876
2959
|
}, [setPlanModeAndPrompt]);
|
|
2877
|
-
const handleExitPlanMode = useCallback(async (
|
|
2960
|
+
const handleExitPlanMode = useCallback(async (planPath) => {
|
|
2961
|
+
// Unattended run + auto-approve enabled: approve the plan IN PLACE (no
|
|
2962
|
+
// remount, no message wipe) so the appended task-completion hint survives
|
|
2963
|
+
// and run-all can advance. Swap the system prompt from "Plan Mode (ACTIVE)"
|
|
2964
|
+
// to the "Approved Plan" + step-tracking section, surface an auditable
|
|
2965
|
+
// transcript row, and tell the model to implement now.
|
|
2966
|
+
if (isUnattendedRun() && autoApprovePlansEnabledRef.current) {
|
|
2967
|
+
log("INFO", "plan", "Plan auto-approved during unattended run", { planPath });
|
|
2968
|
+
let steps = [];
|
|
2969
|
+
try {
|
|
2970
|
+
const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
|
|
2971
|
+
steps = extractPlanSteps(planContent);
|
|
2972
|
+
}
|
|
2973
|
+
catch {
|
|
2974
|
+
// Plan with no readable steps still proceeds (just no step widget).
|
|
2975
|
+
}
|
|
2976
|
+
approvedPlanPathRef.current = planPath;
|
|
2977
|
+
planStepsRef.current = steps;
|
|
2978
|
+
setPlanSteps(steps);
|
|
2979
|
+
await setPlanModeAndPrompt(false);
|
|
2980
|
+
await replaceSystemPrompt({ approvedPlanPath: planPath, planMode: false });
|
|
2981
|
+
setLiveItems((prev) => [
|
|
2982
|
+
...prev,
|
|
2983
|
+
{ kind: "plan_event", event: "auto-approved", detail: planPath, id: getId() },
|
|
2984
|
+
]);
|
|
2985
|
+
return (`Plan auto-approved (unattended run). The approved plan is at ${planPath}. ` +
|
|
2986
|
+
`Implement it now, following each step in order.`);
|
|
2987
|
+
}
|
|
2878
2988
|
await setPlanModeAndPrompt(false);
|
|
2879
2989
|
planOverlayPendingRef.current = true;
|
|
2880
2990
|
setPlanAutoExpand(true);
|
|
@@ -2884,13 +2994,38 @@ export function App(props) {
|
|
|
2884
2994
|
}
|
|
2885
2995
|
setOverlay("plan");
|
|
2886
2996
|
return "Plan submitted for user review. Wait for the user to approve, reject, or dismiss it before implementing.";
|
|
2887
|
-
}, [props.sessionStore, setPlanModeAndPrompt]);
|
|
2997
|
+
}, [props.sessionStore, setPlanModeAndPrompt, replaceSystemPrompt]);
|
|
2888
2998
|
useEffect(() => {
|
|
2889
2999
|
if (!props.planCallbacks)
|
|
2890
3000
|
return;
|
|
2891
3001
|
props.planCallbacks.onEnterPlan = handleEnterPlanMode;
|
|
2892
3002
|
props.planCallbacks.onExitPlan = handleExitPlanMode;
|
|
2893
3003
|
}, [handleEnterPlanMode, handleExitPlanMode, props.planCallbacks]);
|
|
3004
|
+
// Toggle auto-approve-plans (Ctrl+O / /autoplan-on|off). Persists the setting
|
|
3005
|
+
// so it survives restarts and pushes a transient info row for feedback.
|
|
3006
|
+
const handleToggleAutoApprove = useCallback(() => {
|
|
3007
|
+
const next = !autoApprovePlansEnabledRef.current;
|
|
3008
|
+
setAutoApprovePlansEnabled(next);
|
|
3009
|
+
if (props.settingsFile) {
|
|
3010
|
+
const sm = new SettingsManager(props.settingsFile);
|
|
3011
|
+
void sm
|
|
3012
|
+
.load()
|
|
3013
|
+
.then(() => sm.set("autoApprovePlans", next))
|
|
3014
|
+
.catch(() => {
|
|
3015
|
+
/* persistence is best-effort */
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
setLiveItems((prev) => [
|
|
3019
|
+
...prev,
|
|
3020
|
+
{
|
|
3021
|
+
kind: "info",
|
|
3022
|
+
text: next
|
|
3023
|
+
? "Auto-approve plans enabled — unattended runs approve plans and keep going. Use /autoplan-off to disable."
|
|
3024
|
+
: "Auto-approve plans disabled — unattended runs decline plan mode instead. Use /autoplan-on to enable.",
|
|
3025
|
+
id: getId(),
|
|
3026
|
+
},
|
|
3027
|
+
]);
|
|
3028
|
+
}, [props.settingsFile]);
|
|
2894
3029
|
const handleCloseGoalOverlay = () => {
|
|
2895
3030
|
if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
|
|
2896
3031
|
props.sessionStore.overlay = null;
|
|
@@ -3186,6 +3321,7 @@ export function App(props) {
|
|
|
3186
3321
|
onTogglePixel: () => openOverlay("pixel"),
|
|
3187
3322
|
onToggleGoal: () => openOverlay("goal"),
|
|
3188
3323
|
onToggleMarkdown: () => setRenderMarkdown((prev) => !prev),
|
|
3324
|
+
onToggleAutoApprove: handleToggleAutoApprove,
|
|
3189
3325
|
cwd: props.cwd,
|
|
3190
3326
|
commands: allCommands,
|
|
3191
3327
|
mouseScroll: props.fullscreen,
|