@prestyj/cli 4.2.15 → 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.
Files changed (286) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +15 -7
  3. package/dist/cli.js +457 -225
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +66 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +22 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +184 -23
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/agents.d.ts +0 -2
  14. package/dist/core/agents.d.ts.map +1 -1
  15. package/dist/core/agents.js +1 -5
  16. package/dist/core/agents.js.map +1 -1
  17. package/dist/core/auth-storage.d.ts +2 -0
  18. package/dist/core/auth-storage.d.ts.map +1 -1
  19. package/dist/core/auth-storage.js +66 -17
  20. package/dist/core/auth-storage.js.map +1 -1
  21. package/dist/core/auto-update.js +1 -1
  22. package/dist/core/auto-update.js.map +1 -1
  23. package/dist/core/compaction/compactor.d.ts +36 -5
  24. package/dist/core/compaction/compactor.d.ts.map +1 -1
  25. package/dist/core/compaction/compactor.js +222 -63
  26. package/dist/core/compaction/compactor.js.map +1 -1
  27. package/dist/core/compaction/compactor.test.js +377 -32
  28. package/dist/core/compaction/compactor.test.js.map +1 -1
  29. package/dist/core/compaction/token-estimator.d.ts +5 -0
  30. package/dist/core/compaction/token-estimator.d.ts.map +1 -1
  31. package/dist/core/compaction/token-estimator.js +41 -2
  32. package/dist/core/compaction/token-estimator.js.map +1 -1
  33. package/dist/core/compaction/token-estimator.test.js +43 -21
  34. package/dist/core/compaction/token-estimator.test.js.map +1 -1
  35. package/dist/core/event-bus.d.ts +6 -0
  36. package/dist/core/event-bus.d.ts.map +1 -1
  37. package/dist/core/event-bus.js +7 -0
  38. package/dist/core/event-bus.js.map +1 -1
  39. package/dist/core/file-lock.d.ts +6 -0
  40. package/dist/core/file-lock.d.ts.map +1 -0
  41. package/dist/core/file-lock.js +76 -0
  42. package/dist/core/file-lock.js.map +1 -0
  43. package/dist/core/index.d.ts +2 -2
  44. package/dist/core/index.d.ts.map +1 -1
  45. package/dist/core/index.js +1 -1
  46. package/dist/core/index.js.map +1 -1
  47. package/dist/core/mcp/client.d.ts +0 -26
  48. package/dist/core/mcp/client.d.ts.map +1 -1
  49. package/dist/core/mcp/client.js +34 -141
  50. package/dist/core/mcp/client.js.map +1 -1
  51. package/dist/core/mcp/defaults.d.ts.map +1 -1
  52. package/dist/core/mcp/defaults.js +4 -8
  53. package/dist/core/mcp/defaults.js.map +1 -1
  54. package/dist/core/mcp/types.d.ts +0 -2
  55. package/dist/core/mcp/types.d.ts.map +1 -1
  56. package/dist/core/model-registry.d.ts +2 -1
  57. package/dist/core/model-registry.d.ts.map +1 -1
  58. package/dist/core/model-registry.js +16 -5
  59. package/dist/core/model-registry.js.map +1 -1
  60. package/dist/core/oauth/anthropic.d.ts.map +1 -1
  61. package/dist/core/oauth/anthropic.js +3 -3
  62. package/dist/core/oauth/anthropic.js.map +1 -1
  63. package/dist/core/prompt-commands.d.ts.map +1 -1
  64. package/dist/core/prompt-commands.js +21 -0
  65. package/dist/core/prompt-commands.js.map +1 -1
  66. package/dist/core/session-manager.d.ts +34 -2
  67. package/dist/core/session-manager.d.ts.map +1 -1
  68. package/dist/core/session-manager.js +160 -54
  69. package/dist/core/session-manager.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +2 -2
  71. package/dist/core/skills.d.ts +2 -1
  72. package/dist/core/skills.d.ts.map +1 -1
  73. package/dist/core/skills.js +8 -8
  74. package/dist/core/skills.js.map +1 -1
  75. package/dist/core/slash-commands.d.ts +4 -0
  76. package/dist/core/slash-commands.d.ts.map +1 -1
  77. package/dist/core/slash-commands.js +22 -0
  78. package/dist/core/slash-commands.js.map +1 -1
  79. package/dist/core/telegram.d.ts +94 -0
  80. package/dist/core/telegram.d.ts.map +1 -0
  81. package/dist/core/telegram.js +227 -0
  82. package/dist/core/telegram.js.map +1 -0
  83. package/dist/interactive.d.ts.map +1 -1
  84. package/dist/interactive.js +36 -5
  85. package/dist/interactive.js.map +1 -1
  86. package/dist/modes/index.d.ts +1 -0
  87. package/dist/modes/index.d.ts.map +1 -1
  88. package/dist/modes/index.js +1 -0
  89. package/dist/modes/index.js.map +1 -1
  90. package/dist/modes/json-mode.d.ts.map +1 -1
  91. package/dist/modes/json-mode.js +2 -1
  92. package/dist/modes/json-mode.js.map +1 -1
  93. package/dist/modes/print-mode.d.ts.map +1 -1
  94. package/dist/modes/print-mode.js +2 -1
  95. package/dist/modes/print-mode.js.map +1 -1
  96. package/dist/modes/rpc-mode.d.ts +28 -0
  97. package/dist/modes/rpc-mode.d.ts.map +1 -0
  98. package/dist/modes/rpc-mode.js +146 -0
  99. package/dist/modes/rpc-mode.js.map +1 -0
  100. package/dist/modes/serve-mode.d.ts +21 -0
  101. package/dist/modes/serve-mode.d.ts.map +1 -0
  102. package/dist/modes/serve-mode.js +650 -0
  103. package/dist/modes/serve-mode.js.map +1 -0
  104. package/dist/system-prompt.d.ts +1 -1
  105. package/dist/system-prompt.d.ts.map +1 -1
  106. package/dist/system-prompt.js +45 -10
  107. package/dist/system-prompt.js.map +1 -1
  108. package/dist/tools/bash.d.ts +4 -1
  109. package/dist/tools/bash.d.ts.map +1 -1
  110. package/dist/tools/bash.js +58 -5
  111. package/dist/tools/bash.js.map +1 -1
  112. package/dist/tools/edit.d.ts +4 -1
  113. package/dist/tools/edit.d.ts.map +1 -1
  114. package/dist/tools/edit.js +9 -5
  115. package/dist/tools/edit.js.map +1 -1
  116. package/dist/tools/enter-plan.d.ts +8 -0
  117. package/dist/tools/enter-plan.d.ts.map +1 -0
  118. package/dist/tools/enter-plan.js +28 -0
  119. package/dist/tools/enter-plan.js.map +1 -0
  120. package/dist/tools/exit-plan.d.ts +8 -0
  121. package/dist/tools/exit-plan.d.ts.map +1 -0
  122. package/dist/tools/exit-plan.js +35 -0
  123. package/dist/tools/exit-plan.js.map +1 -0
  124. package/dist/tools/grep.d.ts +2 -1
  125. package/dist/tools/grep.d.ts.map +1 -1
  126. package/dist/tools/grep.js +7 -8
  127. package/dist/tools/grep.js.map +1 -1
  128. package/dist/tools/index.d.ts +17 -0
  129. package/dist/tools/index.d.ts.map +1 -1
  130. package/dist/tools/index.js +26 -7
  131. package/dist/tools/index.js.map +1 -1
  132. package/dist/tools/ls.d.ts +2 -1
  133. package/dist/tools/ls.d.ts.map +1 -1
  134. package/dist/tools/ls.js +4 -4
  135. package/dist/tools/ls.js.map +1 -1
  136. package/dist/tools/operations.d.ts +39 -0
  137. package/dist/tools/operations.d.ts.map +1 -0
  138. package/dist/tools/operations.js +27 -0
  139. package/dist/tools/operations.js.map +1 -0
  140. package/dist/tools/path-utils.d.ts +5 -0
  141. package/dist/tools/path-utils.d.ts.map +1 -1
  142. package/dist/tools/path-utils.js +18 -0
  143. package/dist/tools/path-utils.js.map +1 -1
  144. package/dist/tools/plan-mode.test.d.ts +2 -0
  145. package/dist/tools/plan-mode.test.d.ts.map +1 -0
  146. package/dist/tools/plan-mode.test.js +214 -0
  147. package/dist/tools/plan-mode.test.js.map +1 -0
  148. package/dist/tools/read.d.ts +2 -1
  149. package/dist/tools/read.d.ts.map +1 -1
  150. package/dist/tools/read.js +6 -5
  151. package/dist/tools/read.js.map +1 -1
  152. package/dist/tools/skill.d.ts +10 -0
  153. package/dist/tools/skill.d.ts.map +1 -0
  154. package/dist/tools/skill.js +36 -0
  155. package/dist/tools/skill.js.map +1 -0
  156. package/dist/tools/subagent.d.ts +3 -1
  157. package/dist/tools/subagent.d.ts.map +1 -1
  158. package/dist/tools/subagent.js +46 -4
  159. package/dist/tools/subagent.js.map +1 -1
  160. package/dist/tools/tasks.d.ts +2 -2
  161. package/dist/tools/web-fetch.d.ts.map +1 -1
  162. package/dist/tools/web-fetch.js +47 -0
  163. package/dist/tools/web-fetch.js.map +1 -1
  164. package/dist/tools/write.d.ts +4 -1
  165. package/dist/tools/write.d.ts.map +1 -1
  166. package/dist/tools/write.js +17 -6
  167. package/dist/tools/write.js.map +1 -1
  168. package/dist/ui/App.d.ts +35 -1
  169. package/dist/ui/App.d.ts.map +1 -1
  170. package/dist/ui/App.js +367 -73
  171. package/dist/ui/App.js.map +1 -1
  172. package/dist/ui/components/ActivityIndicator.d.ts +2 -1
  173. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
  174. package/dist/ui/components/ActivityIndicator.js +25 -4
  175. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  176. package/dist/ui/components/AnimationContext.d.ts +22 -0
  177. package/dist/ui/components/AnimationContext.d.ts.map +1 -0
  178. package/dist/ui/components/AnimationContext.js +35 -0
  179. package/dist/ui/components/AnimationContext.js.map +1 -0
  180. package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
  181. package/dist/ui/components/AssistantMessage.js +6 -1
  182. package/dist/ui/components/AssistantMessage.js.map +1 -1
  183. package/dist/ui/components/Banner.d.ts.map +1 -1
  184. package/dist/ui/components/Banner.js +4 -7
  185. package/dist/ui/components/Banner.js.map +1 -1
  186. package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
  187. package/dist/ui/components/CompactionNotice.js +4 -8
  188. package/dist/ui/components/CompactionNotice.js.map +1 -1
  189. package/dist/ui/components/Footer.d.ts +2 -1
  190. package/dist/ui/components/Footer.d.ts.map +1 -1
  191. package/dist/ui/components/Footer.js +8 -15
  192. package/dist/ui/components/Footer.js.map +1 -1
  193. package/dist/ui/components/InputArea.d.ts +3 -1
  194. package/dist/ui/components/InputArea.d.ts.map +1 -1
  195. package/dist/ui/components/InputArea.js +23 -25
  196. package/dist/ui/components/InputArea.js.map +1 -1
  197. package/dist/ui/components/PlanApproval.d.ts +8 -0
  198. package/dist/ui/components/PlanApproval.d.ts.map +1 -0
  199. package/dist/ui/components/PlanApproval.js +44 -0
  200. package/dist/ui/components/PlanApproval.js.map +1 -0
  201. package/dist/ui/components/PlanBanner.d.ts +6 -0
  202. package/dist/ui/components/PlanBanner.d.ts.map +1 -0
  203. package/dist/ui/components/PlanBanner.js +14 -0
  204. package/dist/ui/components/PlanBanner.js.map +1 -0
  205. package/dist/ui/components/PlanOverlay.d.ts +11 -0
  206. package/dist/ui/components/PlanOverlay.d.ts.map +1 -0
  207. package/dist/ui/components/PlanOverlay.js +263 -0
  208. package/dist/ui/components/PlanOverlay.js.map +1 -0
  209. package/dist/ui/components/ServerToolExecution.d.ts +1 -0
  210. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
  211. package/dist/ui/components/ServerToolExecution.js +12 -3
  212. package/dist/ui/components/ServerToolExecution.js.map +1 -1
  213. package/dist/ui/components/SkillsOverlay.d.ts +7 -0
  214. package/dist/ui/components/SkillsOverlay.d.ts.map +1 -0
  215. package/dist/ui/components/SkillsOverlay.js +158 -0
  216. package/dist/ui/components/SkillsOverlay.js.map +1 -0
  217. package/dist/ui/components/Spinner.d.ts.map +1 -1
  218. package/dist/ui/components/Spinner.js +5 -9
  219. package/dist/ui/components/Spinner.js.map +1 -1
  220. package/dist/ui/components/StreamingArea.d.ts +2 -1
  221. package/dist/ui/components/StreamingArea.d.ts.map +1 -1
  222. package/dist/ui/components/StreamingArea.js +11 -4
  223. package/dist/ui/components/StreamingArea.js.map +1 -1
  224. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  225. package/dist/ui/components/SubAgentPanel.js +27 -29
  226. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  227. package/dist/ui/components/TaskOverlay.d.ts.map +1 -1
  228. package/dist/ui/components/TaskOverlay.js +7 -3
  229. package/dist/ui/components/TaskOverlay.js.map +1 -1
  230. package/dist/ui/components/ThinkingBlock.d.ts.map +1 -1
  231. package/dist/ui/components/ThinkingBlock.js +4 -1
  232. package/dist/ui/components/ThinkingBlock.js.map +1 -1
  233. package/dist/ui/components/ThinkingIndicator.d.ts.map +1 -1
  234. package/dist/ui/components/ThinkingIndicator.js +8 -33
  235. package/dist/ui/components/ThinkingIndicator.js.map +1 -1
  236. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  237. package/dist/ui/components/ToolExecution.js +95 -41
  238. package/dist/ui/components/ToolExecution.js.map +1 -1
  239. package/dist/ui/components/ToolGroupExecution.d.ts +7 -0
  240. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -0
  241. package/dist/ui/components/ToolGroupExecution.js +115 -0
  242. package/dist/ui/components/ToolGroupExecution.js.map +1 -0
  243. package/dist/ui/components/UserMessage.js +1 -1
  244. package/dist/ui/components/UserMessage.js.map +1 -1
  245. package/dist/ui/components/index.d.ts +1 -0
  246. package/dist/ui/components/index.d.ts.map +1 -1
  247. package/dist/ui/components/index.js +1 -0
  248. package/dist/ui/components/index.js.map +1 -1
  249. package/dist/ui/hooks/useAgentLoop.d.ts +8 -0
  250. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  251. package/dist/ui/hooks/useAgentLoop.js +320 -220
  252. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  253. package/dist/ui/hooks/useTerminalSize.d.ts +21 -9
  254. package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -1
  255. package/dist/ui/hooks/useTerminalSize.js +33 -16
  256. package/dist/ui/hooks/useTerminalSize.js.map +1 -1
  257. package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
  258. package/dist/ui/hooks/useTerminalTitle.js +8 -16
  259. package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
  260. package/dist/ui/live-item-flush.d.ts +11 -1
  261. package/dist/ui/live-item-flush.d.ts.map +1 -1
  262. package/dist/ui/live-item-flush.js +81 -2
  263. package/dist/ui/live-item-flush.js.map +1 -1
  264. package/dist/ui/login.d.ts +1 -1
  265. package/dist/ui/login.d.ts.map +1 -1
  266. package/dist/ui/login.js +30 -6
  267. package/dist/ui/login.js.map +1 -1
  268. package/dist/ui/render.d.ts +11 -0
  269. package/dist/ui/render.d.ts.map +1 -1
  270. package/dist/ui/render.js +24 -10
  271. package/dist/ui/render.js.map +1 -1
  272. package/dist/ui/sessions.d.ts +2 -0
  273. package/dist/ui/sessions.d.ts.map +1 -0
  274. package/dist/ui/sessions.js +208 -0
  275. package/dist/ui/sessions.js.map +1 -0
  276. package/dist/ui/theme/dark.json +3 -1
  277. package/dist/ui/theme/light.json +3 -1
  278. package/dist/ui/theme/theme.d.ts +2 -0
  279. package/dist/ui/theme/theme.d.ts.map +1 -1
  280. package/dist/utils/error-handler.d.ts.map +1 -1
  281. package/dist/utils/error-handler.js +17 -5
  282. package/dist/utils/error-handler.js.map +1 -1
  283. package/dist/utils/image.d.ts.map +1 -1
  284. package/dist/utils/image.js +60 -5
  285. package/dist/utils/image.js.map +1 -1
  286. package/package.json +9 -9
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 { pruneHistory, flushOnTurnText, flushOnTurnEnd } from "./live-item-flush.js";
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 an ezcoder issue — try again shortly.";
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 an ezcoder issue.";
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
- // pruneHistory, flushOnTurnText, flushOnTurnEnd, MAX_HISTORY_ITEMS
72
- // are imported from ./live-item-flush.ts
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) => pruneHistory([...prev, ...props.initialHistory]));
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) => pruneHistory([...h, ...flushed]));
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) => pruneHistory([...h, ...flushed]));
746
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
622
747
  }
623
748
  return remaining;
624
749
  });
@@ -662,11 +787,77 @@ export function App(props) {
662
787
  onAborted: useCallback(() => {
663
788
  log("WARN", "agent", "Agent run aborted by user");
664
789
  setRunAllTasks(false);
790
+ setDoneStatus(null);
665
791
  setLiveItems((prev) => {
666
- const next = prev.map((item) => item.kind === "subagent_group" ? { ...item, aborted: true } : 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
+ });
667
830
  return [...next, { kind: "info", text: "Request was stopped.", id: getId() }];
668
831
  });
669
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
+ }, []),
670
861
  });
671
862
  // Phase 2 of the two-phase flush: after onDone clears liveItems (phase 1)
672
863
  // and Ink renders the smaller live area (updating its internal line
@@ -679,7 +870,7 @@ export function App(props) {
679
870
  if (pendingFlushRef.current.length > 0) {
680
871
  const items = pendingFlushRef.current;
681
872
  pendingFlushRef.current = [];
682
- setHistory((h) => pruneHistory([...h, ...items]));
873
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(items)]));
683
874
  }
684
875
  });
685
876
  // Sync terminal title with agent loop state
@@ -687,25 +878,11 @@ export function App(props) {
687
878
  setTitlePhase(agentLoop.activityPhase);
688
879
  setTitleRunning(agentLoop.isRunning);
689
880
  }, [agentLoop.activityPhase, agentLoop.isRunning]);
690
- // Animated thinking border
691
- const [thinkingBorderFrame, setThinkingBorderFrame] = useState(0);
692
- useEffect(() => {
693
- if (agentLoop.activityPhase !== "thinking")
694
- return;
695
- const timer = setInterval(() => {
696
- setThinkingBorderFrame((f) => (f + 1) % THINKING_BORDER_COLORS.length);
697
- }, 1000);
698
- return () => clearInterval(timer);
699
- }, [agentLoop.activityPhase]);
700
- // Success flash on turn completion
701
- const [doneFlash, setDoneFlash] = useState(false);
702
- useEffect(() => {
703
- if (doneStatus) {
704
- setDoneFlash(true);
705
- const timer = setTimeout(() => setDoneFlash(false), 600);
706
- return () => clearTimeout(timer);
707
- }
708
- }, [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;
709
886
  const handleSubmit = useCallback(async (input, inputImages = [], pasteInfo) => {
710
887
  const trimmed = input.trim();
711
888
  if (trimmed.startsWith("/")) {
@@ -746,6 +923,30 @@ export function App(props) {
746
923
  setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
747
924
  return;
748
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
+ }
749
950
  // Handle prompt-template commands (built-in + custom from .ezcoder/commands/)
750
951
  if (trimmed.startsWith("/")) {
751
952
  const parts = trimmed.slice(1).split(" ");
@@ -759,7 +960,7 @@ export function App(props) {
759
960
  // Move live items into history before starting
760
961
  setLiveItems((prev) => {
761
962
  if (prev.length > 0) {
762
- setHistory((h) => pruneHistory([...h, ...prev]));
963
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
763
964
  }
764
965
  return [];
765
966
  });
@@ -799,31 +1000,8 @@ export function App(props) {
799
1000
  return;
800
1001
  }
801
1002
  }
802
- // Move any remaining live items into history (Static) before starting new turn
803
- setLiveItems((prev) => {
804
- if (prev.length > 0) {
805
- setHistory((h) => pruneHistory([...h, ...prev]));
806
- }
807
- return [];
808
- });
809
- // Build display text — strip image paths, show badges instead
1003
+ // ── Build user content (shared by normal + queued paths) ──
810
1004
  const hasImages = inputImages.length > 0;
811
- let displayText = input;
812
- if (hasImages) {
813
- const { cleanText } = await extractImagePaths(input, props.cwd);
814
- displayText = cleanText;
815
- }
816
- const userItem = {
817
- kind: "user",
818
- text: displayText,
819
- imageCount: hasImages ? inputImages.length : undefined,
820
- pasteInfo,
821
- id: getId(),
822
- };
823
- setLastUserMessage(input);
824
- setDoneStatus(null);
825
- setLiveItems([userItem]);
826
- // Build user content — plain string or content array with images
827
1005
  const modelInfo = getModel(currentModel);
828
1006
  const modelSupportsImages = modelInfo?.supportsImages ?? true;
829
1007
  let userContent;
@@ -867,6 +1045,47 @@ export function App(props) {
867
1045
  else {
868
1046
  userContent = input;
869
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]);
870
1089
  // Run agent
871
1090
  try {
872
1091
  await agentLoop.run(userContent);
@@ -885,6 +1104,7 @@ export function App(props) {
885
1104
  }, [agentLoop, props.onSlashCommand, compactConversation]);
886
1105
  const handleAbort = useCallback(() => {
887
1106
  if (agentLoop.isRunning) {
1107
+ agentLoop.clearQueue();
888
1108
  agentLoop.abort();
889
1109
  }
890
1110
  else {
@@ -935,15 +1155,10 @@ export function App(props) {
935
1155
  apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
936
1156
  }
937
1157
  try {
938
- const allConfigs = getMCPServers(newProvider, apiKey);
939
- const eagerConfigs = allConfigs.filter((c) => !c.lazy);
940
- const lazyConfigs = allConfigs.filter((c) => c.lazy);
941
- const mcpTools = await props.mcpManager.connectAll(eagerConfigs);
942
- const lazyTools = props.mcpManager.registerLazy(lazyConfigs);
1158
+ const mcpTools = await props.mcpManager.connectAll(getMCPServers(newProvider, apiKey));
943
1159
  setCurrentTools((prev) => [
944
1160
  ...prev.filter((t) => !t.name.startsWith("mcp__")),
945
1161
  ...mcpTools,
946
- ...lazyTools,
947
1162
  ]);
948
1163
  log("INFO", "mcp", `MCP servers reconnected for provider ${newProvider}`);
949
1164
  }
@@ -978,6 +1193,8 @@ export function App(props) {
978
1193
  { name: "compact", aliases: ["c"], description: "Compact conversation" },
979
1194
  { name: "clear", aliases: [], description: "Clear session and terminal" },
980
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" },
981
1198
  ...PROMPT_COMMANDS.map((cmd) => ({
982
1199
  name: cmd.name,
983
1200
  aliases: cmd.aliases,
@@ -991,6 +1208,8 @@ export function App(props) {
991
1208
  ], [customCommands]);
992
1209
  const renderItem = (item) => {
993
1210
  switch (item.kind) {
1211
+ case "tombstone":
1212
+ return null;
994
1213
  case "banner":
995
1214
  return (_jsx(Banner, { version: props.version, model: props.model, provider: props.provider, cwd: props.cwd, taskCount: taskCount }, item.id));
996
1215
  case "user":
@@ -1003,16 +1222,22 @@ export function App(props) {
1003
1222
  return _jsx(ToolExecution, { status: "running", name: item.name, args: item.args }, item.id);
1004
1223
  case "tool_done":
1005
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);
1006
1227
  case "server_tool_start":
1007
1228
  return (_jsx(ServerToolExecution, { status: "running", name: item.name, input: item.input, startedAt: item.startedAt }, item.id));
1008
1229
  case "server_tool_done":
1009
- 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));
1010
1231
  case "error": {
1011
1232
  const providerHint = getProviderErrorHint(item.message);
1012
- 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));
1013
1234
  }
1014
1235
  case "info":
1015
- 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));
1016
1241
  case "compacting":
1017
1242
  return _jsx(CompactionSpinner, {}, item.id);
1018
1243
  case "compacted":
@@ -1075,7 +1300,10 @@ export function App(props) {
1075
1300
  runAllTasksRef.current = runAllTasks;
1076
1301
  }, [runAllTasks]);
1077
1302
  const isTaskView = overlay === "tasks";
1078
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: isTaskView ? [] : 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: () => {
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: () => {
1079
1307
  stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1080
1308
  setTaskCount(getTaskCount(props.cwd));
1081
1309
  setStaticKey((k) => k + 1);
@@ -1091,11 +1319,77 @@ export function App(props) {
1091
1319
  markTaskInProgress(props.cwd, next.id);
1092
1320
  startTask(next.title, next.prompt, next.id);
1093
1321
  }
1094
- } })) : (_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 })] }), agentLoop.isRunning && agentLoop.activityPhase !== "idle" ? (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: agentLoop.activityPhase === "thinking"
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"
1095
1378
  ? THINKING_BORDER_COLORS[thinkingBorderFrame]
1096
- : "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: doneFlash ? theme.success : theme.textDim, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
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: () => {
1097
1380
  stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1098
1381
  setOverlay("tasks");
1099
- }, 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 })), bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate }))] }))] }));
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 }))] }))] }));
1100
1394
  }
1101
1395
  //# sourceMappingURL=App.js.map