@prestyj/cli 4.2.16 → 4.2.44
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/LICENSE +1 -1
- package/README.md +15 -7
- package/dist/cli.js +458 -238
- 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 +66 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +184 -23
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +0 -2
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +1 -5
- package/dist/core/agents.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +66 -17
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.js +1 -1
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +36 -5
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +222 -63
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +377 -32
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts +5 -0
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +41 -2
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +43 -21
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +6 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +7 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/file-lock.d.ts +6 -0
- package/dist/core/file-lock.d.ts.map +1 -0
- package/dist/core/file-lock.js +76 -0
- package/dist/core/file-lock.js.map +1 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/client.d.ts +0 -26
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +34 -141
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/defaults.d.ts.map +1 -1
- package/dist/core/mcp/defaults.js +4 -8
- package/dist/core/mcp/defaults.js.map +1 -1
- package/dist/core/mcp/types.d.ts +0 -2
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/model-registry.d.ts +2 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -5
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/oauth/anthropic.d.ts.map +1 -1
- package/dist/core/oauth/anthropic.js +3 -3
- package/dist/core/oauth/anthropic.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +21 -0
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/session-manager.d.ts +34 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +160 -54
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -2
- package/dist/core/skills.d.ts +2 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +8 -8
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +4 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +22 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/telegram.d.ts +94 -0
- package/dist/core/telegram.d.ts.map +1 -0
- package/dist/core/telegram.js +227 -0
- package/dist/core/telegram.js.map +1 -0
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +36 -5
- package/dist/interactive.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +2 -1
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +2 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc-mode.d.ts +28 -0
- package/dist/modes/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc-mode.js +146 -0
- package/dist/modes/rpc-mode.js.map +1 -0
- package/dist/modes/serve-mode.d.ts +21 -0
- package/dist/modes/serve-mode.d.ts.map +1 -0
- package/dist/modes/serve-mode.js +650 -0
- package/dist/modes/serve-mode.js.map +1 -0
- package/dist/system-prompt.d.ts +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +45 -10
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.d.ts +4 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +58 -5
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -5
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/enter-plan.d.ts +8 -0
- package/dist/tools/enter-plan.d.ts.map +1 -0
- package/dist/tools/enter-plan.js +28 -0
- package/dist/tools/enter-plan.js.map +1 -0
- package/dist/tools/exit-plan.d.ts +8 -0
- package/dist/tools/exit-plan.d.ts.map +1 -0
- package/dist/tools/exit-plan.js +35 -0
- package/dist/tools/exit-plan.js.map +1 -0
- package/dist/tools/grep.d.ts +2 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +7 -8
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +26 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ls.d.ts +2 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +4 -4
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/operations.d.ts +39 -0
- package/dist/tools/operations.d.ts.map +1 -0
- package/dist/tools/operations.js +27 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js +18 -0
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/plan-mode.test.d.ts +2 -0
- package/dist/tools/plan-mode.test.d.ts.map +1 -0
- package/dist/tools/plan-mode.test.js +214 -0
- package/dist/tools/plan-mode.test.js.map +1 -0
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +6 -5
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/skill.d.ts +10 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +36 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/tools/subagent.d.ts +3 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +46 -4
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +47 -0
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/write.d.ts +4 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +17 -6
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/App.d.ts +35 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +367 -91
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +2 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +25 -4
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AnimationContext.d.ts +22 -0
- package/dist/ui/components/AnimationContext.d.ts.map +1 -0
- package/dist/ui/components/AnimationContext.js +35 -0
- package/dist/ui/components/AnimationContext.js.map +1 -0
- package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
- package/dist/ui/components/AssistantMessage.js +6 -1
- package/dist/ui/components/AssistantMessage.js.map +1 -1
- package/dist/ui/components/Banner.d.ts.map +1 -1
- package/dist/ui/components/Banner.js +4 -7
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
- package/dist/ui/components/CompactionNotice.js +4 -8
- package/dist/ui/components/CompactionNotice.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +2 -1
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +8 -15
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +3 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +23 -25
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/PlanApproval.d.ts +8 -0
- package/dist/ui/components/PlanApproval.d.ts.map +1 -0
- package/dist/ui/components/PlanApproval.js +44 -0
- package/dist/ui/components/PlanApproval.js.map +1 -0
- package/dist/ui/components/PlanBanner.d.ts +6 -0
- package/dist/ui/components/PlanBanner.d.ts.map +1 -0
- package/dist/ui/components/PlanBanner.js +14 -0
- package/dist/ui/components/PlanBanner.js.map +1 -0
- package/dist/ui/components/PlanOverlay.d.ts +11 -0
- package/dist/ui/components/PlanOverlay.d.ts.map +1 -0
- package/dist/ui/components/PlanOverlay.js +263 -0
- package/dist/ui/components/PlanOverlay.js.map +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ServerToolExecution.js +12 -3
- package/dist/ui/components/ServerToolExecution.js.map +1 -1
- package/dist/ui/components/SkillsOverlay.d.ts +7 -0
- package/dist/ui/components/SkillsOverlay.d.ts.map +1 -0
- package/dist/ui/components/SkillsOverlay.js +158 -0
- package/dist/ui/components/SkillsOverlay.js.map +1 -0
- package/dist/ui/components/Spinner.d.ts.map +1 -1
- package/dist/ui/components/Spinner.js +5 -9
- package/dist/ui/components/Spinner.js.map +1 -1
- package/dist/ui/components/StreamingArea.d.ts +2 -1
- package/dist/ui/components/StreamingArea.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.js +11 -4
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +27 -29
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/components/TaskOverlay.d.ts.map +1 -1
- package/dist/ui/components/TaskOverlay.js +7 -3
- package/dist/ui/components/TaskOverlay.js.map +1 -1
- package/dist/ui/components/ThinkingBlock.d.ts.map +1 -1
- package/dist/ui/components/ThinkingBlock.js +4 -1
- package/dist/ui/components/ThinkingBlock.js.map +1 -1
- package/dist/ui/components/ThinkingIndicator.d.ts.map +1 -1
- package/dist/ui/components/ThinkingIndicator.js +8 -33
- package/dist/ui/components/ThinkingIndicator.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +95 -41
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/ToolGroupExecution.d.ts +7 -0
- package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -0
- package/dist/ui/components/ToolGroupExecution.js +115 -0
- package/dist/ui/components/ToolGroupExecution.js.map +1 -0
- package/dist/ui/components/UserMessage.js +1 -1
- package/dist/ui/components/UserMessage.js.map +1 -1
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.d.ts.map +1 -1
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +8 -0
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +320 -220
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useTerminalSize.d.ts +21 -9
- package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalSize.js +33 -16
- package/dist/ui/hooks/useTerminalSize.js.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +8 -16
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/live-item-flush.d.ts +11 -1
- package/dist/ui/live-item-flush.d.ts.map +1 -1
- package/dist/ui/live-item-flush.js +81 -2
- package/dist/ui/live-item-flush.js.map +1 -1
- package/dist/ui/login.d.ts +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +30 -6
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +11 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +24 -10
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/sessions.d.ts +2 -0
- package/dist/ui/sessions.d.ts.map +1 -0
- package/dist/ui/sessions.js +208 -0
- package/dist/ui/sessions.js.map +1 -0
- package/dist/ui/theme/dark.json +3 -1
- package/dist/ui/theme/light.json +3 -1
- package/dist/ui/theme/theme.d.ts +2 -0
- package/dist/ui/theme/theme.d.ts.map +1 -1
- package/dist/utils/error-handler.d.ts.map +1 -1
- package/dist/utils/error-handler.js +17 -5
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +60 -5
- package/dist/utils/image.js.map +1 -1
- package/package.json +8 -8
package/dist/ui/App.js
CHANGED
|
@@ -12,6 +12,7 @@ import { useAgentLoop } from "./hooks/useAgentLoop.js";
|
|
|
12
12
|
import { UserMessage } from "./components/UserMessage.js";
|
|
13
13
|
import { AssistantMessage } from "./components/AssistantMessage.js";
|
|
14
14
|
import { ToolExecution } from "./components/ToolExecution.js";
|
|
15
|
+
import { ToolGroupExecution } from "./components/ToolGroupExecution.js";
|
|
15
16
|
import { ServerToolExecution } from "./components/ServerToolExecution.js";
|
|
16
17
|
import { SubAgentPanel } from "./components/SubAgentPanel.js";
|
|
17
18
|
import { CompactionSpinner, CompactionDone } from "./components/CompactionNotice.js";
|
|
@@ -20,10 +21,13 @@ import { ActivityIndicator } from "./components/ActivityIndicator.js";
|
|
|
20
21
|
import { InputArea } from "./components/InputArea.js";
|
|
21
22
|
import { Footer } from "./components/Footer.js";
|
|
22
23
|
import { Banner } from "./components/Banner.js";
|
|
24
|
+
import { PlanOverlay } from "./components/PlanOverlay.js";
|
|
23
25
|
import { ModelSelector } from "./components/ModelSelector.js";
|
|
24
26
|
import { TaskOverlay } from "./components/TaskOverlay.js";
|
|
27
|
+
import { SkillsOverlay } from "./components/SkillsOverlay.js";
|
|
25
28
|
import { BackgroundTasksBar } from "./components/BackgroundTasksBar.js";
|
|
26
29
|
import { useTheme } from "./theme/theme.js";
|
|
30
|
+
import { useAnimationTick, deriveFrame } from "./components/AnimationContext.js";
|
|
27
31
|
import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
|
|
28
32
|
import { getGitBranch } from "../utils/git.js";
|
|
29
33
|
import { getModel, getContextWindow } from "../core/model-registry.js";
|
|
@@ -34,28 +38,29 @@ import { shouldCompact, compact } from "../core/compaction/compactor.js";
|
|
|
34
38
|
import { estimateConversationTokens } from "../core/compaction/token-estimator.js";
|
|
35
39
|
import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
|
|
36
40
|
import { loadCustomCommands } from "../core/custom-commands.js";
|
|
41
|
+
import { buildSystemPrompt } from "../system-prompt.js";
|
|
37
42
|
import { getMCPServers } from "../core/mcp/index.js";
|
|
38
|
-
import {
|
|
43
|
+
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd } from "./live-item-flush.js";
|
|
39
44
|
// ── Provider Error Hints ──────────────────────────────────
|
|
40
45
|
/** Detect provider-side errors and return a user-facing hint. */
|
|
41
46
|
function getProviderErrorHint(message) {
|
|
42
47
|
const lower = message.toLowerCase();
|
|
43
|
-
// Billing / insufficient balance (some providers use 429 for this)
|
|
44
|
-
if (lower.includes("insufficient balance") ||
|
|
45
|
-
lower.includes("quota exceeded") ||
|
|
46
|
-
lower.includes("billing")) {
|
|
47
|
-
return "Your account has insufficient balance or no active resource package. Top up your account at the provider's dashboard.";
|
|
48
|
-
}
|
|
49
48
|
if (lower.includes("overloaded") || lower.includes("engine_overloaded")) {
|
|
50
49
|
return "This is a provider-side issue — their servers are under heavy load. Try again in a moment.";
|
|
51
50
|
}
|
|
51
|
+
if (lower.includes("insufficient balance") ||
|
|
52
|
+
lower.includes("no resource package") ||
|
|
53
|
+
lower.includes("quota exceeded") ||
|
|
54
|
+
lower.includes("recharge")) {
|
|
55
|
+
return "The provider reports a billing or quota issue. Check your account balance or resource package.";
|
|
56
|
+
}
|
|
52
57
|
if (lower.includes("rate limit") ||
|
|
53
58
|
lower.includes("too many requests") ||
|
|
54
59
|
lower.includes("429")) {
|
|
55
60
|
return "You've hit the provider's rate limit. Wait a moment before retrying.";
|
|
56
61
|
}
|
|
57
62
|
if (lower.includes("502") || lower.includes("bad gateway")) {
|
|
58
|
-
return "The provider returned a server error. This is not
|
|
63
|
+
return "The provider returned a server error. This is not a ezcoder issue — try again shortly.";
|
|
59
64
|
}
|
|
60
65
|
if (lower.includes("503") || lower.includes("service unavailable")) {
|
|
61
66
|
return "The provider's service is temporarily unavailable. Try again in a moment.";
|
|
@@ -64,12 +69,39 @@ function getProviderErrorHint(message) {
|
|
|
64
69
|
return "The request to the provider timed out. Their servers may be slow — try again.";
|
|
65
70
|
}
|
|
66
71
|
if (lower.includes("500") && lower.includes("internal server error")) {
|
|
67
|
-
return "The provider experienced an internal error. This is not
|
|
72
|
+
return "The provider experienced an internal error. This is not a ezcoder issue.";
|
|
73
|
+
}
|
|
74
|
+
if (lower.includes("does not recognize the requested model") ||
|
|
75
|
+
(lower.includes("model") &&
|
|
76
|
+
(lower.includes("not exist") || lower.includes("not found") || lower.includes("no access")))) {
|
|
77
|
+
return "Use /model to switch to a different model, or check that your account has access to the requested model.";
|
|
68
78
|
}
|
|
69
79
|
return null;
|
|
70
80
|
}
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
/** Tools that get aggregated into a single compact group when concurrent. */
|
|
82
|
+
const AGGREGATABLE_TOOLS = new Set(["read", "grep", "find", "ls"]);
|
|
83
|
+
/**
|
|
84
|
+
* Cap memory by replacing old items with tiny tombstones. Ink's <Static>
|
|
85
|
+
* tracks rendered items by array length — the array must never shrink, but
|
|
86
|
+
* we can swap out heavy objects for lightweight `{ kind: "tombstone", id }`
|
|
87
|
+
* entries so GC can reclaim the original data.
|
|
88
|
+
*/
|
|
89
|
+
const MAX_LIVE_HISTORY = 200;
|
|
90
|
+
function compactHistory(items) {
|
|
91
|
+
if (items.length <= MAX_LIVE_HISTORY)
|
|
92
|
+
return items;
|
|
93
|
+
const cutoff = items.length - MAX_LIVE_HISTORY;
|
|
94
|
+
const compacted = new Array(items.length);
|
|
95
|
+
for (let i = 0; i < cutoff; i++) {
|
|
96
|
+
const it = items[i];
|
|
97
|
+
compacted[i] = it.kind === "tombstone" ? it : { kind: "tombstone", id: it.id };
|
|
98
|
+
}
|
|
99
|
+
for (let i = cutoff; i < items.length; i++) {
|
|
100
|
+
compacted[i] = items[i];
|
|
101
|
+
}
|
|
102
|
+
return compacted;
|
|
103
|
+
}
|
|
104
|
+
// flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
|
|
73
105
|
// ── Duration summary ─────────────────────────────────────
|
|
74
106
|
function formatDuration(ms) {
|
|
75
107
|
const totalSec = Math.round(ms / 1000);
|
|
@@ -211,7 +243,7 @@ export function App(props) {
|
|
|
211
243
|
useEffect(() => {
|
|
212
244
|
if (isRestoredSession && !restoredRef.current) {
|
|
213
245
|
restoredRef.current = true;
|
|
214
|
-
setHistory((prev) =>
|
|
246
|
+
setHistory((prev) => compactHistory([...prev, ...trimFlushedItems(props.initialHistory)]));
|
|
215
247
|
}
|
|
216
248
|
}, [isRestoredSession, props.initialHistory]);
|
|
217
249
|
// Items from the current/last turn — rendered in the live area so they stay visible
|
|
@@ -231,6 +263,9 @@ export function App(props) {
|
|
|
231
263
|
const [currentTools, setCurrentTools] = useState(props.tools);
|
|
232
264
|
const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
|
|
233
265
|
const messagesRef = useRef(props.messages);
|
|
266
|
+
const [planMode, setPlanMode] = useState(false);
|
|
267
|
+
const [planAutoExpand, setPlanAutoExpand] = useState(false);
|
|
268
|
+
const approvedPlanPathRef = useRef(undefined);
|
|
234
269
|
const nextIdRef = useRef(0);
|
|
235
270
|
const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
|
|
236
271
|
const sessionPathRef = useRef(props.sessionPath);
|
|
@@ -255,6 +290,56 @@ export function App(props) {
|
|
|
255
290
|
useEffect(() => {
|
|
256
291
|
reloadCustomCommands();
|
|
257
292
|
}, [reloadCustomCommands]);
|
|
293
|
+
// ── Plan mode wiring ─────────────────────────────────────
|
|
294
|
+
// Sync planModeRef with React state
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (props.planModeRef) {
|
|
297
|
+
props.planModeRef.current = planMode;
|
|
298
|
+
}
|
|
299
|
+
}, [planMode, props.planModeRef]);
|
|
300
|
+
// Rebuild system prompt when plan mode changes
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
void (async () => {
|
|
303
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, approvedPlanPathRef.current);
|
|
304
|
+
if (messagesRef.current[0]?.role === "system") {
|
|
305
|
+
messagesRef.current[0] = {
|
|
306
|
+
role: "system",
|
|
307
|
+
content: newPrompt,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
})();
|
|
311
|
+
}, [planMode, props.cwd, props.skills]);
|
|
312
|
+
// Wire onEnterPlan callback ref
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
if (props.onEnterPlanRef) {
|
|
315
|
+
props.onEnterPlanRef.current = (reason) => {
|
|
316
|
+
setPlanMode(true);
|
|
317
|
+
const msg = reason ? `Plan mode activated: ${reason}` : "Plan mode activated";
|
|
318
|
+
setLiveItems((prev) => [...prev, { kind: "info", text: msg, id: getId() }]);
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}, [props.onEnterPlanRef]);
|
|
322
|
+
// Wire onExitPlan callback ref
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (props.onExitPlanRef) {
|
|
325
|
+
props.onExitPlanRef.current = async (planPath) => {
|
|
326
|
+
// Deactivate plan mode, store approved plan path, open pane
|
|
327
|
+
setPlanMode(false);
|
|
328
|
+
approvedPlanPathRef.current = planPath;
|
|
329
|
+
// Use setTimeout to open pane after the current tool execution completes,
|
|
330
|
+
// so the turn can finish and the UI transitions cleanly
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
333
|
+
setPlanAutoExpand(true);
|
|
334
|
+
setOverlay("plan");
|
|
335
|
+
}, 300);
|
|
336
|
+
return ("Plan submitted. Exiting plan mode.\n" +
|
|
337
|
+
"The plan pane is opening for user review.\n" +
|
|
338
|
+
"Plan saved at: " +
|
|
339
|
+
planPath);
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}, [props.onExitPlanRef, stdout]);
|
|
258
343
|
const persistNewMessages = useCallback(async () => {
|
|
259
344
|
const sm = sessionManagerRef.current;
|
|
260
345
|
const sp = sessionPathRef.current;
|
|
@@ -311,6 +396,7 @@ export function App(props) {
|
|
|
311
396
|
apiKey: compactApiKey,
|
|
312
397
|
contextWindow,
|
|
313
398
|
signal: undefined,
|
|
399
|
+
approvedPlanPath: approvedPlanPathRef.current,
|
|
314
400
|
});
|
|
315
401
|
// Replace spinner with completed notice
|
|
316
402
|
setLiveItems((prev) => prev.map((item) => item.id === spinId
|
|
@@ -440,7 +526,7 @@ export function App(props) {
|
|
|
440
526
|
setLiveItems((prev) => {
|
|
441
527
|
const flushed = flushOnTurnText(prev);
|
|
442
528
|
if (flushed.length > 0) {
|
|
443
|
-
setHistory((h) =>
|
|
529
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
444
530
|
}
|
|
445
531
|
return [{ kind: "assistant", text, thinking, thinkingMs, id: getId() }];
|
|
446
532
|
});
|
|
@@ -471,6 +557,31 @@ export function App(props) {
|
|
|
471
557
|
return [...prev, { kind: "subagent_group", agents: [newAgent], id: getId() }];
|
|
472
558
|
});
|
|
473
559
|
}
|
|
560
|
+
else if (AGGREGATABLE_TOOLS.has(name)) {
|
|
561
|
+
// Group concurrent read-only tools into a single compact item
|
|
562
|
+
setLiveItems((prev) => {
|
|
563
|
+
// Find an active tool group (has at least one running tool)
|
|
564
|
+
const groupIdx = prev.findIndex((item) => item.kind === "tool_group" &&
|
|
565
|
+
item.tools.some((t) => t.status === "running"));
|
|
566
|
+
if (groupIdx !== -1) {
|
|
567
|
+
const group = prev[groupIdx];
|
|
568
|
+
const next = [...prev];
|
|
569
|
+
next[groupIdx] = {
|
|
570
|
+
...group,
|
|
571
|
+
tools: [...group.tools, { toolCallId, name, args, status: "running" }],
|
|
572
|
+
};
|
|
573
|
+
return next;
|
|
574
|
+
}
|
|
575
|
+
return [
|
|
576
|
+
...prev,
|
|
577
|
+
{
|
|
578
|
+
kind: "tool_group",
|
|
579
|
+
tools: [{ toolCallId, name, args, status: "running" }],
|
|
580
|
+
id: getId(),
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
});
|
|
584
|
+
}
|
|
474
585
|
else {
|
|
475
586
|
setLiveItems((prev) => [
|
|
476
587
|
...prev,
|
|
@@ -533,6 +644,20 @@ export function App(props) {
|
|
|
533
644
|
}
|
|
534
645
|
else {
|
|
535
646
|
setLiveItems((prev) => {
|
|
647
|
+
// Check if this tool is in a tool_group
|
|
648
|
+
const groupIdx = prev.findIndex((item) => item.kind === "tool_group" &&
|
|
649
|
+
item.tools.some((t) => t.toolCallId === toolCallId));
|
|
650
|
+
if (groupIdx !== -1) {
|
|
651
|
+
const group = prev[groupIdx];
|
|
652
|
+
const next = [...prev];
|
|
653
|
+
next[groupIdx] = {
|
|
654
|
+
...group,
|
|
655
|
+
tools: group.tools.map((t) => t.toolCallId === toolCallId
|
|
656
|
+
? { ...t, status: "done", result, isError }
|
|
657
|
+
: t),
|
|
658
|
+
};
|
|
659
|
+
return next;
|
|
660
|
+
}
|
|
536
661
|
// Find the matching tool_start and replace it with tool_done
|
|
537
662
|
const startIdx = prev.findIndex((item) => item.kind === "tool_start" && item.toolCallId === toolCallId);
|
|
538
663
|
if (startIdx !== -1) {
|
|
@@ -618,7 +743,7 @@ export function App(props) {
|
|
|
618
743
|
setLiveItems((prev) => {
|
|
619
744
|
const { flushed, remaining } = flushOnTurnEnd(prev, stopReason);
|
|
620
745
|
if (flushed.length > 0) {
|
|
621
|
-
setHistory((h) =>
|
|
746
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
|
|
622
747
|
}
|
|
623
748
|
return remaining;
|
|
624
749
|
});
|
|
@@ -646,23 +771,6 @@ export function App(props) {
|
|
|
646
771
|
// (allow the two-phase flush to complete first)
|
|
647
772
|
if (runAllTasksRef.current) {
|
|
648
773
|
setTimeout(() => {
|
|
649
|
-
// Memory watchdog: log and GC if RSS is high between tasks
|
|
650
|
-
const memUsage = process.memoryUsage();
|
|
651
|
-
const rssMB = Math.round(memUsage.rss / 1024 / 1024);
|
|
652
|
-
const RSS_THRESHOLD_MB = 2048;
|
|
653
|
-
if (rssMB > RSS_THRESHOLD_MB) {
|
|
654
|
-
log("WARN", "memory", `High RSS between tasks: ${rssMB} MB`, {
|
|
655
|
-
rss: String(memUsage.rss),
|
|
656
|
-
heapUsed: String(memUsage.heapUsed),
|
|
657
|
-
external: String(memUsage.external),
|
|
658
|
-
});
|
|
659
|
-
globalThis.gc?.();
|
|
660
|
-
const afterMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
661
|
-
log("WARN", "memory", `Post-GC RSS: ${afterMB} MB (was ${rssMB} MB)`);
|
|
662
|
-
if (afterMB > RSS_THRESHOLD_MB) {
|
|
663
|
-
log("ERROR", "memory", `RSS still above threshold after GC (${afterMB} MB). Consider restarting.`);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
774
|
const cwd = cwdRef.current;
|
|
667
775
|
const next = getNextPendingTask(cwd);
|
|
668
776
|
if (next) {
|
|
@@ -679,11 +787,77 @@ export function App(props) {
|
|
|
679
787
|
onAborted: useCallback(() => {
|
|
680
788
|
log("WARN", "agent", "Agent run aborted by user");
|
|
681
789
|
setRunAllTasks(false);
|
|
790
|
+
setDoneStatus(null);
|
|
682
791
|
setLiveItems((prev) => {
|
|
683
|
-
const next = prev.map((item) =>
|
|
792
|
+
const next = prev.map((item) => {
|
|
793
|
+
if (item.kind === "subagent_group")
|
|
794
|
+
return { ...item, aborted: true };
|
|
795
|
+
// Convert running tools to stopped state so spinners stop
|
|
796
|
+
if (item.kind === "tool_start") {
|
|
797
|
+
return {
|
|
798
|
+
kind: "tool_done",
|
|
799
|
+
name: item.name,
|
|
800
|
+
args: item.args,
|
|
801
|
+
result: "Stopped.",
|
|
802
|
+
isError: true,
|
|
803
|
+
durationMs: 0,
|
|
804
|
+
id: item.id,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (item.kind === "server_tool_start") {
|
|
808
|
+
return {
|
|
809
|
+
kind: "server_tool_done",
|
|
810
|
+
name: item.name,
|
|
811
|
+
input: item.input,
|
|
812
|
+
resultType: "aborted",
|
|
813
|
+
data: null,
|
|
814
|
+
durationMs: 0,
|
|
815
|
+
id: item.id,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (item.kind === "tool_group") {
|
|
819
|
+
const tools = item.tools.map((t) => t.status === "running"
|
|
820
|
+
? { ...t, status: "done", result: "Stopped.", isError: true }
|
|
821
|
+
: t);
|
|
822
|
+
return { ...item, tools };
|
|
823
|
+
}
|
|
824
|
+
// Remove compaction spinner (compaction can't complete after abort)
|
|
825
|
+
if (item.kind === "compacting") {
|
|
826
|
+
return { kind: "tombstone", id: item.id };
|
|
827
|
+
}
|
|
828
|
+
return item;
|
|
829
|
+
});
|
|
684
830
|
return [...next, { kind: "info", text: "Request was stopped.", id: getId() }];
|
|
685
831
|
});
|
|
686
832
|
}, []),
|
|
833
|
+
onQueuedStart: useCallback((content) => {
|
|
834
|
+
// When a queued message starts processing, show it as a UserItem
|
|
835
|
+
// and flush prior items to history
|
|
836
|
+
const displayText = typeof content === "string"
|
|
837
|
+
? content
|
|
838
|
+
: content
|
|
839
|
+
.filter((c) => c.type === "text")
|
|
840
|
+
.map((c) => c.text)
|
|
841
|
+
.join("\n");
|
|
842
|
+
const imageCount = typeof content === "string"
|
|
843
|
+
? undefined
|
|
844
|
+
: content.filter((c) => c.type === "image").length || undefined;
|
|
845
|
+
setLiveItems((prev) => {
|
|
846
|
+
if (prev.length > 0) {
|
|
847
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
|
|
848
|
+
}
|
|
849
|
+
return [];
|
|
850
|
+
});
|
|
851
|
+
const userItem = {
|
|
852
|
+
kind: "user",
|
|
853
|
+
text: displayText,
|
|
854
|
+
imageCount,
|
|
855
|
+
id: getId(),
|
|
856
|
+
};
|
|
857
|
+
setLastUserMessage(displayText);
|
|
858
|
+
setDoneStatus(null);
|
|
859
|
+
setLiveItems([userItem]);
|
|
860
|
+
}, []),
|
|
687
861
|
});
|
|
688
862
|
// Phase 2 of the two-phase flush: after onDone clears liveItems (phase 1)
|
|
689
863
|
// and Ink renders the smaller live area (updating its internal line
|
|
@@ -696,7 +870,7 @@ export function App(props) {
|
|
|
696
870
|
if (pendingFlushRef.current.length > 0) {
|
|
697
871
|
const items = pendingFlushRef.current;
|
|
698
872
|
pendingFlushRef.current = [];
|
|
699
|
-
setHistory((h) =>
|
|
873
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(items)]));
|
|
700
874
|
}
|
|
701
875
|
});
|
|
702
876
|
// Sync terminal title with agent loop state
|
|
@@ -704,25 +878,11 @@ export function App(props) {
|
|
|
704
878
|
setTitlePhase(agentLoop.activityPhase);
|
|
705
879
|
setTitleRunning(agentLoop.isRunning);
|
|
706
880
|
}, [agentLoop.activityPhase, agentLoop.isRunning]);
|
|
707
|
-
// Animated thinking border
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const timer = setInterval(() => {
|
|
713
|
-
setThinkingBorderFrame((f) => (f + 1) % THINKING_BORDER_COLORS.length);
|
|
714
|
-
}, 1000);
|
|
715
|
-
return () => clearInterval(timer);
|
|
716
|
-
}, [agentLoop.activityPhase]);
|
|
717
|
-
// Success flash on turn completion
|
|
718
|
-
const [doneFlash, setDoneFlash] = useState(false);
|
|
719
|
-
useEffect(() => {
|
|
720
|
-
if (doneStatus) {
|
|
721
|
-
setDoneFlash(true);
|
|
722
|
-
const timer = setTimeout(() => setDoneFlash(false), 600);
|
|
723
|
-
return () => clearTimeout(timer);
|
|
724
|
-
}
|
|
725
|
-
}, [doneStatus]);
|
|
881
|
+
// Animated thinking border — derived from global animation tick
|
|
882
|
+
const animTick = useAnimationTick();
|
|
883
|
+
const thinkingBorderFrame = agentLoop.activityPhase === "thinking"
|
|
884
|
+
? deriveFrame(animTick, 1000, THINKING_BORDER_COLORS.length)
|
|
885
|
+
: 0;
|
|
726
886
|
const handleSubmit = useCallback(async (input, inputImages = [], pasteInfo) => {
|
|
727
887
|
const trimmed = input.trim();
|
|
728
888
|
if (trimmed.startsWith("/")) {
|
|
@@ -763,6 +923,30 @@ export function App(props) {
|
|
|
763
923
|
setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
|
|
764
924
|
return;
|
|
765
925
|
}
|
|
926
|
+
// Handle /plan — toggle plan mode
|
|
927
|
+
if (trimmed === "/plan" || trimmed === "/plan on") {
|
|
928
|
+
setPlanMode(true);
|
|
929
|
+
setLiveItems((prev) => [
|
|
930
|
+
...prev,
|
|
931
|
+
{ kind: "info", text: "Plan mode activated", id: getId() },
|
|
932
|
+
]);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (trimmed === "/plan off") {
|
|
936
|
+
setPlanMode(false);
|
|
937
|
+
setLiveItems((prev) => [
|
|
938
|
+
...prev,
|
|
939
|
+
{ kind: "info", text: "Plan mode deactivated", id: getId() },
|
|
940
|
+
]);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
// Handle /plans — open plan pane
|
|
944
|
+
if (trimmed === "/plans") {
|
|
945
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
946
|
+
setPlanAutoExpand(false);
|
|
947
|
+
setOverlay("plan");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
766
950
|
// Handle prompt-template commands (built-in + custom from .ezcoder/commands/)
|
|
767
951
|
if (trimmed.startsWith("/")) {
|
|
768
952
|
const parts = trimmed.slice(1).split(" ");
|
|
@@ -776,7 +960,7 @@ export function App(props) {
|
|
|
776
960
|
// Move live items into history before starting
|
|
777
961
|
setLiveItems((prev) => {
|
|
778
962
|
if (prev.length > 0) {
|
|
779
|
-
setHistory((h) =>
|
|
963
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
|
|
780
964
|
}
|
|
781
965
|
return [];
|
|
782
966
|
});
|
|
@@ -816,31 +1000,8 @@ export function App(props) {
|
|
|
816
1000
|
return;
|
|
817
1001
|
}
|
|
818
1002
|
}
|
|
819
|
-
//
|
|
820
|
-
setLiveItems((prev) => {
|
|
821
|
-
if (prev.length > 0) {
|
|
822
|
-
setHistory((h) => pruneHistory([...h, ...prev]));
|
|
823
|
-
}
|
|
824
|
-
return [];
|
|
825
|
-
});
|
|
826
|
-
// Build display text — strip image paths, show badges instead
|
|
1003
|
+
// ── Build user content (shared by normal + queued paths) ──
|
|
827
1004
|
const hasImages = inputImages.length > 0;
|
|
828
|
-
let displayText = input;
|
|
829
|
-
if (hasImages) {
|
|
830
|
-
const { cleanText } = await extractImagePaths(input, props.cwd);
|
|
831
|
-
displayText = cleanText;
|
|
832
|
-
}
|
|
833
|
-
const userItem = {
|
|
834
|
-
kind: "user",
|
|
835
|
-
text: displayText,
|
|
836
|
-
imageCount: hasImages ? inputImages.length : undefined,
|
|
837
|
-
pasteInfo,
|
|
838
|
-
id: getId(),
|
|
839
|
-
};
|
|
840
|
-
setLastUserMessage(input);
|
|
841
|
-
setDoneStatus(null);
|
|
842
|
-
setLiveItems([userItem]);
|
|
843
|
-
// Build user content — plain string or content array with images
|
|
844
1005
|
const modelInfo = getModel(currentModel);
|
|
845
1006
|
const modelSupportsImages = modelInfo?.supportsImages ?? true;
|
|
846
1007
|
let userContent;
|
|
@@ -884,6 +1045,47 @@ export function App(props) {
|
|
|
884
1045
|
else {
|
|
885
1046
|
userContent = input;
|
|
886
1047
|
}
|
|
1048
|
+
// ── Queue message if agent is already running ──
|
|
1049
|
+
if (agentLoop.isRunning) {
|
|
1050
|
+
log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
|
|
1051
|
+
agentLoop.queueMessage(userContent);
|
|
1052
|
+
let displayText = input;
|
|
1053
|
+
if (hasImages) {
|
|
1054
|
+
const { cleanText } = await extractImagePaths(input, props.cwd);
|
|
1055
|
+
displayText = cleanText;
|
|
1056
|
+
}
|
|
1057
|
+
const queuedItem = {
|
|
1058
|
+
kind: "queued",
|
|
1059
|
+
text: displayText,
|
|
1060
|
+
imageCount: hasImages ? inputImages.length : undefined,
|
|
1061
|
+
id: getId(),
|
|
1062
|
+
};
|
|
1063
|
+
setLiveItems((prev) => [...prev, queuedItem]);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
// Move any remaining live items into history (Static) before starting new turn
|
|
1067
|
+
setLiveItems((prev) => {
|
|
1068
|
+
if (prev.length > 0) {
|
|
1069
|
+
setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
|
|
1070
|
+
}
|
|
1071
|
+
return [];
|
|
1072
|
+
});
|
|
1073
|
+
// Build display text — strip image paths, show badges instead
|
|
1074
|
+
let displayText = input;
|
|
1075
|
+
if (hasImages) {
|
|
1076
|
+
const { cleanText } = await extractImagePaths(input, props.cwd);
|
|
1077
|
+
displayText = cleanText;
|
|
1078
|
+
}
|
|
1079
|
+
const userItem = {
|
|
1080
|
+
kind: "user",
|
|
1081
|
+
text: displayText,
|
|
1082
|
+
imageCount: hasImages ? inputImages.length : undefined,
|
|
1083
|
+
pasteInfo,
|
|
1084
|
+
id: getId(),
|
|
1085
|
+
};
|
|
1086
|
+
setLastUserMessage(input);
|
|
1087
|
+
setDoneStatus(null);
|
|
1088
|
+
setLiveItems([userItem]);
|
|
887
1089
|
// Run agent
|
|
888
1090
|
try {
|
|
889
1091
|
await agentLoop.run(userContent);
|
|
@@ -902,6 +1104,7 @@ export function App(props) {
|
|
|
902
1104
|
}, [agentLoop, props.onSlashCommand, compactConversation]);
|
|
903
1105
|
const handleAbort = useCallback(() => {
|
|
904
1106
|
if (agentLoop.isRunning) {
|
|
1107
|
+
agentLoop.clearQueue();
|
|
905
1108
|
agentLoop.abort();
|
|
906
1109
|
}
|
|
907
1110
|
else {
|
|
@@ -952,15 +1155,10 @@ export function App(props) {
|
|
|
952
1155
|
apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
|
|
953
1156
|
}
|
|
954
1157
|
try {
|
|
955
|
-
const
|
|
956
|
-
const eagerConfigs = allConfigs.filter((c) => !c.lazy);
|
|
957
|
-
const lazyConfigs = allConfigs.filter((c) => c.lazy);
|
|
958
|
-
const mcpTools = await props.mcpManager.connectAll(eagerConfigs);
|
|
959
|
-
const lazyTools = props.mcpManager.registerLazy(lazyConfigs);
|
|
1158
|
+
const mcpTools = await props.mcpManager.connectAll(getMCPServers(newProvider, apiKey));
|
|
960
1159
|
setCurrentTools((prev) => [
|
|
961
1160
|
...prev.filter((t) => !t.name.startsWith("mcp__")),
|
|
962
1161
|
...mcpTools,
|
|
963
|
-
...lazyTools,
|
|
964
1162
|
]);
|
|
965
1163
|
log("INFO", "mcp", `MCP servers reconnected for provider ${newProvider}`);
|
|
966
1164
|
}
|
|
@@ -995,6 +1193,8 @@ export function App(props) {
|
|
|
995
1193
|
{ name: "compact", aliases: ["c"], description: "Compact conversation" },
|
|
996
1194
|
{ name: "clear", aliases: [], description: "Clear session and terminal" },
|
|
997
1195
|
{ name: "quit", aliases: ["q", "exit"], description: "Exit the agent" },
|
|
1196
|
+
{ name: "plan", aliases: [], description: "Toggle plan mode (on/off)" },
|
|
1197
|
+
{ name: "plans", aliases: [], description: "Open plans pane" },
|
|
998
1198
|
...PROMPT_COMMANDS.map((cmd) => ({
|
|
999
1199
|
name: cmd.name,
|
|
1000
1200
|
aliases: cmd.aliases,
|
|
@@ -1008,6 +1208,8 @@ export function App(props) {
|
|
|
1008
1208
|
], [customCommands]);
|
|
1009
1209
|
const renderItem = (item) => {
|
|
1010
1210
|
switch (item.kind) {
|
|
1211
|
+
case "tombstone":
|
|
1212
|
+
return null;
|
|
1011
1213
|
case "banner":
|
|
1012
1214
|
return (_jsx(Banner, { version: props.version, model: props.model, provider: props.provider, cwd: props.cwd, taskCount: taskCount }, item.id));
|
|
1013
1215
|
case "user":
|
|
@@ -1020,16 +1222,22 @@ export function App(props) {
|
|
|
1020
1222
|
return _jsx(ToolExecution, { status: "running", name: item.name, args: item.args }, item.id);
|
|
1021
1223
|
case "tool_done":
|
|
1022
1224
|
return (_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError }, item.id));
|
|
1225
|
+
case "tool_group":
|
|
1226
|
+
return _jsx(ToolGroupExecution, { tools: item.tools }, item.id);
|
|
1023
1227
|
case "server_tool_start":
|
|
1024
1228
|
return (_jsx(ServerToolExecution, { status: "running", name: item.name, input: item.input, startedAt: item.startedAt }, item.id));
|
|
1025
1229
|
case "server_tool_done":
|
|
1026
|
-
return (_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs }, item.id));
|
|
1230
|
+
return (_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs, resultType: item.resultType }, item.id));
|
|
1027
1231
|
case "error": {
|
|
1028
1232
|
const providerHint = getProviderErrorHint(item.message);
|
|
1029
|
-
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: theme.error, children: ["✗ ", item.message] }), providerHint && (_jsxs(Text, { color: theme.textDim, children: [" Hint: ", providerHint] }))] }, item.id));
|
|
1233
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 1, children: [_jsxs(Text, { color: theme.error, wrap: "wrap", children: ["✗ ", item.message] }), providerHint && (_jsxs(Text, { color: theme.textDim, wrap: "wrap", children: [" Hint: ", providerHint] }))] }, item.id));
|
|
1030
1234
|
}
|
|
1031
1235
|
case "info":
|
|
1032
|
-
return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textDim, children: item.text }) }, item.id));
|
|
1236
|
+
return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsx(Text, { color: theme.textDim, wrap: "wrap", children: item.text }) }, item.id));
|
|
1237
|
+
case "queued":
|
|
1238
|
+
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
|
|
1239
|
+
? ` (+${item.imageCount} image${item.imageCount > 1 ? "s" : ""})`
|
|
1240
|
+
: ""] })] }, item.id));
|
|
1033
1241
|
case "compacting":
|
|
1034
1242
|
return _jsx(CompactionSpinner, {}, item.id);
|
|
1035
1243
|
case "compacted":
|
|
@@ -1049,7 +1257,6 @@ export function App(props) {
|
|
|
1049
1257
|
setLiveItems([]);
|
|
1050
1258
|
messagesRef.current = messagesRef.current.slice(0, 1);
|
|
1051
1259
|
agentLoop.reset();
|
|
1052
|
-
globalThis.gc?.();
|
|
1053
1260
|
persistedIndexRef.current = messagesRef.current.length;
|
|
1054
1261
|
const sm = sessionManagerRef.current;
|
|
1055
1262
|
if (sm) {
|
|
@@ -1093,7 +1300,10 @@ export function App(props) {
|
|
|
1093
1300
|
runAllTasksRef.current = runAllTasks;
|
|
1094
1301
|
}, [runAllTasks]);
|
|
1095
1302
|
const isTaskView = overlay === "tasks";
|
|
1096
|
-
|
|
1303
|
+
const isSkillsView = overlay === "skills";
|
|
1304
|
+
const isPlanView = overlay === "plan";
|
|
1305
|
+
const isOverlayView = isTaskView || isSkillsView || isPlanView;
|
|
1306
|
+
return (_jsxs(Box, { flexDirection: "column", 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: () => {
|
|
1097
1307
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1098
1308
|
setTaskCount(getTaskCount(props.cwd));
|
|
1099
1309
|
setStaticKey((k) => k + 1);
|
|
@@ -1109,11 +1319,77 @@ export function App(props) {
|
|
|
1109
1319
|
markTaskInProgress(props.cwd, next.id);
|
|
1110
1320
|
startTask(next.title, next.prompt, next.id);
|
|
1111
1321
|
}
|
|
1112
|
-
} })) :
|
|
1322
|
+
} })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
|
|
1323
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1324
|
+
setStaticKey((k) => k + 1);
|
|
1325
|
+
setOverlay(null);
|
|
1326
|
+
} })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
|
|
1327
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1328
|
+
setStaticKey((k) => k + 1);
|
|
1329
|
+
setPlanAutoExpand(false);
|
|
1330
|
+
setOverlay(null);
|
|
1331
|
+
}, onApprove: (planPath) => {
|
|
1332
|
+
// Store approved plan path — will be injected into the new system prompt
|
|
1333
|
+
approvedPlanPathRef.current = planPath;
|
|
1334
|
+
// Clear session for a fresh context focused on the plan
|
|
1335
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1336
|
+
setHistory([{ kind: "banner", id: "banner" }]);
|
|
1337
|
+
setLiveItems([]);
|
|
1338
|
+
setStaticKey((k) => k + 1);
|
|
1339
|
+
setPlanAutoExpand(false);
|
|
1340
|
+
setOverlay(null);
|
|
1341
|
+
// Rebuild system prompt with the approved plan, then reset the session
|
|
1342
|
+
void (async () => {
|
|
1343
|
+
const newPrompt = await buildSystemPrompt(props.cwd, props.skills, false, planPath);
|
|
1344
|
+
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
1345
|
+
agentLoop.reset();
|
|
1346
|
+
persistedIndexRef.current = messagesRef.current.length;
|
|
1347
|
+
// Create a new session file
|
|
1348
|
+
const sm = sessionManagerRef.current;
|
|
1349
|
+
if (sm) {
|
|
1350
|
+
const s = await sm.create(props.cwd, currentProvider, currentModel);
|
|
1351
|
+
sessionPathRef.current = s.path;
|
|
1352
|
+
}
|
|
1353
|
+
// Start implementation with a clean context
|
|
1354
|
+
setLiveItems([
|
|
1355
|
+
{
|
|
1356
|
+
kind: "info",
|
|
1357
|
+
text: "Plan approved — starting fresh session for implementation",
|
|
1358
|
+
id: getId(),
|
|
1359
|
+
},
|
|
1360
|
+
]);
|
|
1361
|
+
setDoneStatus(null);
|
|
1362
|
+
await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
|
|
1363
|
+
})();
|
|
1364
|
+
}, onReject: (planPath, feedback) => {
|
|
1365
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1366
|
+
setStaticKey((k) => k + 1);
|
|
1367
|
+
setPlanAutoExpand(false);
|
|
1368
|
+
setOverlay(null);
|
|
1369
|
+
// Send rejection + feedback to the agent
|
|
1370
|
+
const msg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
|
|
1371
|
+
`Please revise the plan based on this feedback.`;
|
|
1372
|
+
setLiveItems((prev) => [
|
|
1373
|
+
...prev,
|
|
1374
|
+
{ kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
|
|
1375
|
+
]);
|
|
1376
|
+
void agentLoop.run(msg);
|
|
1377
|
+
} })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 1, children: [liveItems.map((item) => renderItem(item)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: agentLoop.streamingText, streamingThinking: agentLoop.streamingThinking, showThinking: props.showThinking, thinkingMs: agentLoop.thinkingMs, planMode: planMode })] }), agentLoop.isRunning && agentLoop.activityPhase !== "idle" ? (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: agentLoop.activityPhase === "thinking"
|
|
1113
1378
|
? THINKING_BORDER_COLORS[thinkingBorderFrame]
|
|
1114
|
-
: "transparent", paddingLeft: 1, paddingRight: 1, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name) }) })) : (doneStatus && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color:
|
|
1379
|
+
: "transparent", paddingLeft: 1, paddingRight: 1, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), planMode: planMode }) })) : (doneStatus && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), agentLoop.queuedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.accent, children: ["⏳ ", agentLoop.queuedCount, " message", agentLoop.queuedCount > 1 ? "s" : "", " queued"] }) })), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
|
|
1115
1380
|
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1116
1381
|
setOverlay("tasks");
|
|
1117
|
-
},
|
|
1382
|
+
}, onToggleSkills: () => {
|
|
1383
|
+
stdout?.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1384
|
+
setOverlay("skills");
|
|
1385
|
+
}, onTogglePlanMode: () => {
|
|
1386
|
+
const next = !planMode;
|
|
1387
|
+
setPlanMode(next);
|
|
1388
|
+
log("INFO", "plan", `Plan mode ${next ? "enabled" : "disabled"}`);
|
|
1389
|
+
setLiveItems((items) => [
|
|
1390
|
+
...items,
|
|
1391
|
+
{ kind: "info", text: `Plan mode ${next ? "on" : "off"}`, id: getId() },
|
|
1392
|
+
]);
|
|
1393
|
+
}, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: props.cwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled, planMode: planMode })), bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate }))] }))] }));
|
|
1118
1394
|
}
|
|
1119
1395
|
//# sourceMappingURL=App.js.map
|