@mragentix/cli 4.2.37 → 4.2.39

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 (195) hide show
  1. package/dist/cli.js +179 -8
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +64 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +13 -1
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/branding.d.ts +11 -0
  10. package/dist/core/branding.d.ts.map +1 -0
  11. package/dist/core/branding.js +11 -0
  12. package/dist/core/branding.js.map +1 -0
  13. package/dist/core/compaction/compactor.d.ts +1 -0
  14. package/dist/core/compaction/compactor.d.ts.map +1 -1
  15. package/dist/core/compaction/compactor.js +8 -1
  16. package/dist/core/compaction/compactor.js.map +1 -1
  17. package/dist/core/index.d.ts +2 -0
  18. package/dist/core/index.d.ts.map +1 -1
  19. package/dist/core/index.js +2 -0
  20. package/dist/core/index.js.map +1 -1
  21. package/dist/core/skills.d.ts +2 -1
  22. package/dist/core/skills.d.ts.map +1 -1
  23. package/dist/core/skills.js +8 -8
  24. package/dist/core/skills.js.map +1 -1
  25. package/dist/core/slash-commands.d.ts.map +1 -1
  26. package/dist/core/slash-commands.js +97 -1
  27. package/dist/core/slash-commands.js.map +1 -1
  28. package/dist/core/update.d.ts +34 -0
  29. package/dist/core/update.d.ts.map +1 -0
  30. package/dist/core/update.js +231 -0
  31. package/dist/core/update.js.map +1 -0
  32. package/dist/interactive.d.ts.map +1 -1
  33. package/dist/interactive.js +17 -5
  34. package/dist/interactive.js.map +1 -1
  35. package/dist/modes/json-mode.d.ts.map +1 -1
  36. package/dist/modes/json-mode.js +2 -1
  37. package/dist/modes/json-mode.js.map +1 -1
  38. package/dist/modes/print-mode.d.ts.map +1 -1
  39. package/dist/modes/print-mode.js +2 -1
  40. package/dist/modes/print-mode.js.map +1 -1
  41. package/dist/modes/rpc-mode.d.ts.map +1 -1
  42. package/dist/modes/rpc-mode.js +2 -1
  43. package/dist/modes/rpc-mode.js.map +1 -1
  44. package/dist/modes/serve-mode.d.ts.map +1 -1
  45. package/dist/modes/serve-mode.js +7 -6
  46. package/dist/modes/serve-mode.js.map +1 -1
  47. package/dist/system-prompt.d.ts +1 -1
  48. package/dist/system-prompt.d.ts.map +1 -1
  49. package/dist/system-prompt.js +40 -2
  50. package/dist/system-prompt.js.map +1 -1
  51. package/dist/tools/bash.d.ts +3 -1
  52. package/dist/tools/bash.d.ts.map +1 -1
  53. package/dist/tools/bash.js +4 -1
  54. package/dist/tools/bash.js.map +1 -1
  55. package/dist/tools/edit-diff.test.d.ts +2 -0
  56. package/dist/tools/edit-diff.test.d.ts.map +1 -0
  57. package/dist/tools/edit-diff.test.js +67 -0
  58. package/dist/tools/edit-diff.test.js.map +1 -0
  59. package/dist/tools/edit.d.ts +3 -1
  60. package/dist/tools/edit.d.ts.map +1 -1
  61. package/dist/tools/edit.js +4 -1
  62. package/dist/tools/edit.js.map +1 -1
  63. package/dist/tools/edit.test.d.ts +2 -0
  64. package/dist/tools/edit.test.d.ts.map +1 -0
  65. package/dist/tools/edit.test.js +93 -0
  66. package/dist/tools/edit.test.js.map +1 -0
  67. package/dist/tools/enter-plan.d.ts +8 -0
  68. package/dist/tools/enter-plan.d.ts.map +1 -0
  69. package/dist/tools/enter-plan.js +28 -0
  70. package/dist/tools/enter-plan.js.map +1 -0
  71. package/dist/tools/exit-plan.d.ts +8 -0
  72. package/dist/tools/exit-plan.d.ts.map +1 -0
  73. package/dist/tools/exit-plan.js +36 -0
  74. package/dist/tools/exit-plan.js.map +1 -0
  75. package/dist/tools/index.d.ts +10 -0
  76. package/dist/tools/index.d.ts.map +1 -1
  77. package/dist/tools/index.js +20 -4
  78. package/dist/tools/index.js.map +1 -1
  79. package/dist/tools/path-utils.test.d.ts +2 -0
  80. package/dist/tools/path-utils.test.d.ts.map +1 -0
  81. package/dist/tools/path-utils.test.js +49 -0
  82. package/dist/tools/path-utils.test.js.map +1 -0
  83. package/dist/tools/plan-mode.test.d.ts +2 -0
  84. package/dist/tools/plan-mode.test.d.ts.map +1 -0
  85. package/dist/tools/plan-mode.test.js +214 -0
  86. package/dist/tools/plan-mode.test.js.map +1 -0
  87. package/dist/tools/read.test.d.ts +2 -0
  88. package/dist/tools/read.test.d.ts.map +1 -0
  89. package/dist/tools/read.test.js +81 -0
  90. package/dist/tools/read.test.js.map +1 -0
  91. package/dist/tools/skill.d.ts +10 -0
  92. package/dist/tools/skill.d.ts.map +1 -0
  93. package/dist/tools/skill.js +36 -0
  94. package/dist/tools/skill.js.map +1 -0
  95. package/dist/tools/subagent.d.ts +3 -1
  96. package/dist/tools/subagent.d.ts.map +1 -1
  97. package/dist/tools/subagent.js +4 -1
  98. package/dist/tools/subagent.js.map +1 -1
  99. package/dist/tools/truncate-utils.test.d.ts +2 -0
  100. package/dist/tools/truncate-utils.test.d.ts.map +1 -0
  101. package/dist/tools/truncate-utils.test.js +93 -0
  102. package/dist/tools/truncate-utils.test.js.map +1 -0
  103. package/dist/tools/web-fetch.js +1 -1
  104. package/dist/tools/web-fetch.js.map +1 -1
  105. package/dist/tools/write.d.ts +3 -1
  106. package/dist/tools/write.d.ts.map +1 -1
  107. package/dist/tools/write.js +11 -1
  108. package/dist/tools/write.js.map +1 -1
  109. package/dist/tools/write.test.js +65 -52
  110. package/dist/tools/write.test.js.map +1 -1
  111. package/dist/ui/App.d.ts +18 -1
  112. package/dist/ui/App.d.ts.map +1 -1
  113. package/dist/ui/App.js +343 -31
  114. package/dist/ui/App.js.map +1 -1
  115. package/dist/ui/components/ActivityIndicator.d.ts +2 -1
  116. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
  117. package/dist/ui/components/ActivityIndicator.js +25 -4
  118. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  119. package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
  120. package/dist/ui/components/AssistantMessage.js +6 -1
  121. package/dist/ui/components/AssistantMessage.js.map +1 -1
  122. package/dist/ui/components/Banner.d.ts.map +1 -1
  123. package/dist/ui/components/Banner.js +12 -6
  124. package/dist/ui/components/Banner.js.map +1 -1
  125. package/dist/ui/components/Footer.d.ts +2 -1
  126. package/dist/ui/components/Footer.d.ts.map +1 -1
  127. package/dist/ui/components/Footer.js +30 -16
  128. package/dist/ui/components/Footer.js.map +1 -1
  129. package/dist/ui/components/InputArea.d.ts +3 -1
  130. package/dist/ui/components/InputArea.d.ts.map +1 -1
  131. package/dist/ui/components/InputArea.js +27 -17
  132. package/dist/ui/components/InputArea.js.map +1 -1
  133. package/dist/ui/components/Markdown.d.ts +5 -1
  134. package/dist/ui/components/Markdown.d.ts.map +1 -1
  135. package/dist/ui/components/Markdown.js +14 -6
  136. package/dist/ui/components/Markdown.js.map +1 -1
  137. package/dist/ui/components/PlanApproval.d.ts +8 -0
  138. package/dist/ui/components/PlanApproval.d.ts.map +1 -0
  139. package/dist/ui/components/PlanApproval.js +58 -0
  140. package/dist/ui/components/PlanApproval.js.map +1 -0
  141. package/dist/ui/components/PlanBanner.d.ts +6 -0
  142. package/dist/ui/components/PlanBanner.d.ts.map +1 -0
  143. package/dist/ui/components/PlanBanner.js +28 -0
  144. package/dist/ui/components/PlanBanner.js.map +1 -0
  145. package/dist/ui/components/PlanOverlay.d.ts +11 -0
  146. package/dist/ui/components/PlanOverlay.d.ts.map +1 -0
  147. package/dist/ui/components/PlanOverlay.js +267 -0
  148. package/dist/ui/components/PlanOverlay.js.map +1 -0
  149. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
  150. package/dist/ui/components/ServerToolExecution.js +11 -3
  151. package/dist/ui/components/ServerToolExecution.js.map +1 -1
  152. package/dist/ui/components/SkillsOverlay.d.ts +7 -0
  153. package/dist/ui/components/SkillsOverlay.d.ts.map +1 -0
  154. package/dist/ui/components/SkillsOverlay.js +158 -0
  155. package/dist/ui/components/SkillsOverlay.js.map +1 -0
  156. package/dist/ui/components/Spinner.js +1 -1
  157. package/dist/ui/components/Spinner.js.map +1 -1
  158. package/dist/ui/components/StreamingArea.d.ts +2 -1
  159. package/dist/ui/components/StreamingArea.d.ts.map +1 -1
  160. package/dist/ui/components/StreamingArea.js +7 -2
  161. package/dist/ui/components/StreamingArea.js.map +1 -1
  162. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  163. package/dist/ui/components/SubAgentPanel.js +21 -11
  164. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  165. package/dist/ui/components/TaskOverlay.d.ts.map +1 -1
  166. package/dist/ui/components/TaskOverlay.js +8 -8
  167. package/dist/ui/components/TaskOverlay.js.map +1 -1
  168. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  169. package/dist/ui/components/ToolExecution.js +33 -13
  170. package/dist/ui/components/ToolExecution.js.map +1 -1
  171. package/dist/ui/components/ToolGroupExecution.js +1 -1
  172. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  173. package/dist/ui/hooks/useAgentLoop.d.ts +8 -0
  174. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  175. package/dist/ui/hooks/useAgentLoop.js +318 -236
  176. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  177. package/dist/ui/hooks/useTerminalSize.d.ts +18 -16
  178. package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -1
  179. package/dist/ui/hooks/useTerminalSize.js +31 -24
  180. package/dist/ui/hooks/useTerminalSize.js.map +1 -1
  181. package/dist/ui/login.d.ts.map +1 -1
  182. package/dist/ui/login.js +1 -5
  183. package/dist/ui/login.js.map +1 -1
  184. package/dist/ui/render.d.ts +11 -0
  185. package/dist/ui/render.d.ts.map +1 -1
  186. package/dist/ui/render.js +7 -2
  187. package/dist/ui/render.js.map +1 -1
  188. package/dist/ui/sessions.d.ts.map +1 -1
  189. package/dist/ui/sessions.js +1 -5
  190. package/dist/ui/sessions.js.map +1 -1
  191. package/dist/ui/theme/dark.json +3 -1
  192. package/dist/ui/theme/light.json +3 -1
  193. package/dist/ui/theme/theme.d.ts +2 -0
  194. package/dist/ui/theme/theme.d.ts.map +1 -1
  195. package/package.json +3 -3
package/dist/ui/App.js CHANGED
@@ -21,8 +21,10 @@ import { ActivityIndicator } from "./components/ActivityIndicator.js";
21
21
  import { InputArea } from "./components/InputArea.js";
22
22
  import { Footer } from "./components/Footer.js";
23
23
  import { Banner } from "./components/Banner.js";
24
+ import { PlanOverlay } from "./components/PlanOverlay.js";
24
25
  import { ModelSelector } from "./components/ModelSelector.js";
25
26
  import { TaskOverlay } from "./components/TaskOverlay.js";
27
+ import { SkillsOverlay } from "./components/SkillsOverlay.js";
26
28
  import { BackgroundTasksBar } from "./components/BackgroundTasksBar.js";
27
29
  import { useTheme } from "./theme/theme.js";
28
30
  import { useAnimationTick, deriveFrame } from "./components/AnimationContext.js";
@@ -36,7 +38,9 @@ import { shouldCompact, compact } from "../core/compaction/compactor.js";
36
38
  import { estimateConversationTokens } from "../core/compaction/token-estimator.js";
37
39
  import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
38
40
  import { loadCustomCommands } from "../core/custom-commands.js";
41
+ import { buildSystemPrompt } from "../system-prompt.js";
39
42
  import { getMCPServers } from "../core/mcp/index.js";
43
+ import { fetchUpstreamChanges, mergeUpstream, restoreBranding, bumpVersion, buildAll, publishToNpm, pushToGithub, selfUpdate, } from "../core/update.js";
40
44
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd } from "./live-item-flush.js";
41
45
  // ── Provider Error Hints ──────────────────────────────────
42
46
  /** Detect provider-side errors and return a user-facing hint. */
@@ -224,7 +228,7 @@ function markTaskInProgress(cwd, taskId) {
224
228
  export function App(props) {
225
229
  const theme = useTheme();
226
230
  const { stdout } = useStdout();
227
- const { resizeKey } = useTerminalSize();
231
+ const { columns, resizeKey } = useTerminalSize();
228
232
  // Terminal title — updated later after agentLoop is created
229
233
  // (hoisted here so the hook is always called in the same order)
230
234
  const [titlePhase, setTitlePhase] = useState("idle");
@@ -260,6 +264,9 @@ export function App(props) {
260
264
  const [currentTools, setCurrentTools] = useState(props.tools);
261
265
  const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
262
266
  const messagesRef = useRef(props.messages);
267
+ const [planMode, setPlanMode] = useState(false);
268
+ const [planAutoExpand, setPlanAutoExpand] = useState(false);
269
+ const approvedPlanPathRef = useRef(undefined);
263
270
  const nextIdRef = useRef(0);
264
271
  const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
265
272
  const sessionPathRef = useRef(props.sessionPath);
@@ -284,6 +291,56 @@ export function App(props) {
284
291
  useEffect(() => {
285
292
  reloadCustomCommands();
286
293
  }, [reloadCustomCommands]);
294
+ // ── Plan mode wiring ─────────────────────────────────────
295
+ // Sync planModeRef with React state
296
+ useEffect(() => {
297
+ if (props.planModeRef) {
298
+ props.planModeRef.current = planMode;
299
+ }
300
+ }, [planMode, props.planModeRef]);
301
+ // Rebuild system prompt when plan mode changes
302
+ useEffect(() => {
303
+ void (async () => {
304
+ const newPrompt = await buildSystemPrompt(props.cwd, props.skills, planMode, approvedPlanPathRef.current);
305
+ if (messagesRef.current[0]?.role === "system") {
306
+ messagesRef.current[0] = {
307
+ role: "system",
308
+ content: newPrompt,
309
+ };
310
+ }
311
+ })();
312
+ }, [planMode, props.cwd, props.skills]);
313
+ // Wire onEnterPlan callback ref
314
+ useEffect(() => {
315
+ if (props.onEnterPlanRef) {
316
+ props.onEnterPlanRef.current = (reason) => {
317
+ setPlanMode(true);
318
+ const msg = reason ? `Plan mode activated: ${reason}` : "Plan mode activated";
319
+ setLiveItems((prev) => [...prev, { kind: "info", text: msg, id: getId() }]);
320
+ };
321
+ }
322
+ }, [props.onEnterPlanRef]);
323
+ // Wire onExitPlan callback ref
324
+ useEffect(() => {
325
+ if (props.onExitPlanRef) {
326
+ props.onExitPlanRef.current = async (planPath) => {
327
+ // Deactivate plan mode, store approved plan path, open pane
328
+ setPlanMode(false);
329
+ approvedPlanPathRef.current = planPath;
330
+ // Use setTimeout to open pane after the current tool execution completes,
331
+ // so the turn can finish and the UI transitions cleanly
332
+ setTimeout(() => {
333
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
334
+ setPlanAutoExpand(true);
335
+ setOverlay("plan");
336
+ }, 300);
337
+ return ("Plan submitted. Exiting plan mode.\n" +
338
+ "The plan pane is opening for user review.\n" +
339
+ "Plan saved at: " +
340
+ planPath);
341
+ };
342
+ }
343
+ }, [props.onExitPlanRef, stdout]);
287
344
  const persistNewMessages = useCallback(async () => {
288
345
  const sm = sessionManagerRef.current;
289
346
  const sp = sessionPathRef.current;
@@ -730,6 +787,7 @@ export function App(props) {
730
787
  onAborted: useCallback(() => {
731
788
  log("WARN", "agent", "Agent run aborted by user");
732
789
  setRunAllTasks(false);
790
+ setDoneStatus(null);
733
791
  setLiveItems((prev) => {
734
792
  const next = prev.map((item) => {
735
793
  if (item.kind === "subagent_group")
@@ -772,6 +830,34 @@ export function App(props) {
772
830
  return [...next, { kind: "info", text: "Request was stopped.", id: getId() }];
773
831
  });
774
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
+ }, []),
775
861
  });
776
862
  // Phase 2 of the two-phase flush: after onDone clears liveItems (phase 1)
777
863
  // and Ink renders the smaller live area (updating its internal line
@@ -837,6 +923,137 @@ export function App(props) {
837
923
  setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
838
924
  return;
839
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 /update — upstream sync pipeline
944
+ if (trimmed === "/update" || trimmed === "/u") {
945
+ try {
946
+ const status = fetchUpstreamChanges();
947
+ if (!status.hasUpdates) {
948
+ setLiveItems((prev) => [
949
+ ...prev,
950
+ { kind: "info", text: "✅ Already up to date with upstream.", id: getId() },
951
+ ]);
952
+ }
953
+ else {
954
+ const commitList = status.commits.map((c) => ` ${c}`).join("\n");
955
+ const msg = [
956
+ `📦 Found ${status.commits.length} new commit(s) from upstream:\n`,
957
+ commitList,
958
+ "",
959
+ status.diffStat,
960
+ "",
961
+ "Run /update confirm to merge, build, and publish.",
962
+ ].join("\n");
963
+ setLiveItems((prev) => [...prev, { kind: "info", text: msg, id: getId() }]);
964
+ }
965
+ }
966
+ catch (err) {
967
+ setLiveItems((prev) => [
968
+ ...prev,
969
+ { kind: "error", message: `${err.message}`, id: getId() },
970
+ ]);
971
+ }
972
+ return;
973
+ }
974
+ if (trimmed === "/update confirm") {
975
+ setLiveItems((prev) => [
976
+ ...prev,
977
+ { kind: "info", text: "🔄 Running update pipeline...", id: getId() },
978
+ ]);
979
+ try {
980
+ const steps = [];
981
+ steps.push("🔄 Merging upstream changes...");
982
+ const merge = mergeUpstream();
983
+ if (!merge.ok) {
984
+ steps.push(` ❌ ${merge.error}`);
985
+ setLiveItems((prev) => [
986
+ ...prev,
987
+ { kind: "error", message: steps.join("\n"), id: getId() },
988
+ ]);
989
+ return;
990
+ }
991
+ steps.push(" ✓ Merged");
992
+ steps.push("🎨 Restoring branding...");
993
+ const branding = restoreBranding();
994
+ steps.push(` ✓ ${branding.filesModified} file(s) updated`);
995
+ steps.push("📝 Bumping version...");
996
+ const version = bumpVersion();
997
+ steps.push(` ✓ ${version.oldVersion} → ${version.newVersion}`);
998
+ steps.push("🔨 Building all packages...");
999
+ const build = buildAll();
1000
+ if (!build.ok) {
1001
+ steps.push(` ❌ Build failed:\n${build.output}`);
1002
+ setLiveItems((prev) => [
1003
+ ...prev,
1004
+ { kind: "error", message: steps.join("\n"), id: getId() },
1005
+ ]);
1006
+ return;
1007
+ }
1008
+ steps.push(" ✓ Built");
1009
+ steps.push("📦 Publishing to npm...");
1010
+ const pub = publishToNpm();
1011
+ for (const r of pub.results)
1012
+ steps.push(` ${r}`);
1013
+ if (!pub.ok) {
1014
+ setLiveItems((prev) => [
1015
+ ...prev,
1016
+ { kind: "error", message: steps.join("\n"), id: getId() },
1017
+ ]);
1018
+ return;
1019
+ }
1020
+ steps.push("🚀 Pushing to GitHub...");
1021
+ const push = pushToGithub(version.newVersion);
1022
+ if (!push.ok) {
1023
+ steps.push(` ❌ ${push.error}`);
1024
+ setLiveItems((prev) => [
1025
+ ...prev,
1026
+ { kind: "error", message: steps.join("\n"), id: getId() },
1027
+ ]);
1028
+ return;
1029
+ }
1030
+ steps.push(" ✓ Pushed");
1031
+ steps.push("⬆️ Updating global install...");
1032
+ const update = selfUpdate();
1033
+ steps.push(update.ok ? " ✓ Updated" : ` ⚠️ Self-update failed: ${update.output}`);
1034
+ steps.push("");
1035
+ steps.push(`✅ Updated to v${version.newVersion}! Restart mragentix to use the new version.`);
1036
+ setLiveItems((prev) => [...prev, { kind: "info", text: steps.join("\n"), id: getId() }]);
1037
+ }
1038
+ catch (err) {
1039
+ setLiveItems((prev) => [
1040
+ ...prev,
1041
+ {
1042
+ kind: "error",
1043
+ message: `Pipeline error: ${err.message}`,
1044
+ id: getId(),
1045
+ },
1046
+ ]);
1047
+ }
1048
+ return;
1049
+ }
1050
+ // Handle /plans — open plan pane
1051
+ if (trimmed === "/plans") {
1052
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1053
+ setPlanAutoExpand(false);
1054
+ setOverlay("plan");
1055
+ return;
1056
+ }
840
1057
  // Handle prompt-template commands (built-in + custom from .mragentix/commands/)
841
1058
  if (trimmed.startsWith("/")) {
842
1059
  const parts = trimmed.slice(1).split(" ");
@@ -890,31 +1107,8 @@ export function App(props) {
890
1107
  return;
891
1108
  }
892
1109
  }
893
- // Move any remaining live items into history (Static) before starting new turn
894
- setLiveItems((prev) => {
895
- if (prev.length > 0) {
896
- setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
897
- }
898
- return [];
899
- });
900
- // Build display text — strip image paths, show badges instead
1110
+ // ── Build user content (shared by normal + queued paths) ──
901
1111
  const hasImages = inputImages.length > 0;
902
- let displayText = input;
903
- if (hasImages) {
904
- const { cleanText } = await extractImagePaths(input, props.cwd);
905
- displayText = cleanText;
906
- }
907
- const userItem = {
908
- kind: "user",
909
- text: displayText,
910
- imageCount: hasImages ? inputImages.length : undefined,
911
- pasteInfo,
912
- id: getId(),
913
- };
914
- setLastUserMessage(input);
915
- setDoneStatus(null);
916
- setLiveItems([userItem]);
917
- // Build user content — plain string or content array with images
918
1112
  const modelInfo = getModel(currentModel);
919
1113
  const modelSupportsImages = modelInfo?.supportsImages ?? true;
920
1114
  let userContent;
@@ -958,6 +1152,47 @@ export function App(props) {
958
1152
  else {
959
1153
  userContent = input;
960
1154
  }
1155
+ // ── Queue message if agent is already running ──
1156
+ if (agentLoop.isRunning) {
1157
+ log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
1158
+ agentLoop.queueMessage(userContent);
1159
+ let displayText = input;
1160
+ if (hasImages) {
1161
+ const { cleanText } = await extractImagePaths(input, props.cwd);
1162
+ displayText = cleanText;
1163
+ }
1164
+ const queuedItem = {
1165
+ kind: "queued",
1166
+ text: displayText,
1167
+ imageCount: hasImages ? inputImages.length : undefined,
1168
+ id: getId(),
1169
+ };
1170
+ setLiveItems((prev) => [...prev, queuedItem]);
1171
+ return;
1172
+ }
1173
+ // Move any remaining live items into history (Static) before starting new turn
1174
+ setLiveItems((prev) => {
1175
+ if (prev.length > 0) {
1176
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
1177
+ }
1178
+ return [];
1179
+ });
1180
+ // Build display text — strip image paths, show badges instead
1181
+ let displayText = input;
1182
+ if (hasImages) {
1183
+ const { cleanText } = await extractImagePaths(input, props.cwd);
1184
+ displayText = cleanText;
1185
+ }
1186
+ const userItem = {
1187
+ kind: "user",
1188
+ text: displayText,
1189
+ imageCount: hasImages ? inputImages.length : undefined,
1190
+ pasteInfo,
1191
+ id: getId(),
1192
+ };
1193
+ setLastUserMessage(input);
1194
+ setDoneStatus(null);
1195
+ setLiveItems([userItem]);
961
1196
  // Run agent
962
1197
  try {
963
1198
  await agentLoop.run(userContent);
@@ -976,6 +1211,7 @@ export function App(props) {
976
1211
  }, [agentLoop, props.onSlashCommand, compactConversation]);
977
1212
  const handleAbort = useCallback(() => {
978
1213
  if (agentLoop.isRunning) {
1214
+ agentLoop.clearQueue();
979
1215
  agentLoop.abort();
980
1216
  }
981
1217
  else {
@@ -1064,6 +1300,9 @@ export function App(props) {
1064
1300
  { name: "compact", aliases: ["c"], description: "Compact conversation" },
1065
1301
  { name: "clear", aliases: [], description: "Clear session and terminal" },
1066
1302
  { name: "quit", aliases: ["q", "exit"], description: "Exit the agent" },
1303
+ { name: "plan", aliases: [], description: "Toggle plan mode (on/off)" },
1304
+ { name: "plans", aliases: [], description: "Open plans pane" },
1305
+ { name: "update", aliases: ["u"], description: "Pull upstream changes, rebuild & publish" },
1067
1306
  ...PROMPT_COMMANDS.map((cmd) => ({
1068
1307
  name: cmd.name,
1069
1308
  aliases: cmd.aliases,
@@ -1099,10 +1338,14 @@ export function App(props) {
1099
1338
  return (_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs, resultType: item.resultType }, item.id));
1100
1339
  case "error": {
1101
1340
  const providerHint = getProviderErrorHint(item.message);
1102
- 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));
1341
+ 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));
1103
1342
  }
1104
1343
  case "info":
1105
- return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textDim, children: item.text }) }, item.id));
1344
+ return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsx(Text, { color: theme.textDim, wrap: "wrap", children: item.text }) }, item.id));
1345
+ case "queued":
1346
+ 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
1347
+ ? ` (+${item.imageCount} image${item.imageCount > 1 ? "s" : ""})`
1348
+ : ""] })] }, item.id));
1106
1349
  case "compacting":
1107
1350
  return _jsx(CompactionSpinner, {}, item.id);
1108
1351
  case "compacted":
@@ -1165,7 +1408,10 @@ export function App(props) {
1165
1408
  runAllTasksRef.current = runAllTasks;
1166
1409
  }, [runAllTasks]);
1167
1410
  const isTaskView = overlay === "tasks";
1168
- 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: () => {
1411
+ const isSkillsView = overlay === "skills";
1412
+ const isPlanView = overlay === "plan";
1413
+ const isOverlayView = isTaskView || isSkillsView || isPlanView;
1414
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: isOverlayView ? [] : history, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, `${resizeKey}-${staticKey}`), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
1169
1415
  stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1170
1416
  setTaskCount(getTaskCount(props.cwd));
1171
1417
  setStaticKey((k) => k + 1);
@@ -1181,11 +1427,77 @@ export function App(props) {
1181
1427
  markTaskInProgress(props.cwd, next.id);
1182
1428
  startTask(next.title, next.prompt, next.id);
1183
1429
  }
1184
- } })) : (_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"
1430
+ } })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
1431
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1432
+ setStaticKey((k) => k + 1);
1433
+ setOverlay(null);
1434
+ } })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
1435
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1436
+ setStaticKey((k) => k + 1);
1437
+ setPlanAutoExpand(false);
1438
+ setOverlay(null);
1439
+ }, onApprove: (planPath) => {
1440
+ // Store approved plan path — will be injected into the new system prompt
1441
+ approvedPlanPathRef.current = planPath;
1442
+ // Clear session for a fresh context focused on the plan
1443
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1444
+ setHistory([{ kind: "banner", id: "banner" }]);
1445
+ setLiveItems([]);
1446
+ setStaticKey((k) => k + 1);
1447
+ setPlanAutoExpand(false);
1448
+ setOverlay(null);
1449
+ // Rebuild system prompt with the approved plan, then reset the session
1450
+ void (async () => {
1451
+ const newPrompt = await buildSystemPrompt(props.cwd, props.skills, false, planPath);
1452
+ messagesRef.current = [{ role: "system", content: newPrompt }];
1453
+ agentLoop.reset();
1454
+ persistedIndexRef.current = messagesRef.current.length;
1455
+ // Create a new session file
1456
+ const sm = sessionManagerRef.current;
1457
+ if (sm) {
1458
+ const s = await sm.create(props.cwd, currentProvider, currentModel);
1459
+ sessionPathRef.current = s.path;
1460
+ }
1461
+ // Start implementation with a clean context
1462
+ setLiveItems([
1463
+ {
1464
+ kind: "info",
1465
+ text: "Plan approved — starting fresh session for implementation",
1466
+ id: getId(),
1467
+ },
1468
+ ]);
1469
+ setDoneStatus(null);
1470
+ await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
1471
+ })();
1472
+ }, onReject: (planPath, feedback) => {
1473
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1474
+ setStaticKey((k) => k + 1);
1475
+ setPlanAutoExpand(false);
1476
+ setOverlay(null);
1477
+ // Send rejection + feedback to the agent
1478
+ const msg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
1479
+ `Please revise the plan based on this feedback.`;
1480
+ setLiveItems((prev) => [
1481
+ ...prev,
1482
+ { kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
1483
+ ]);
1484
+ void agentLoop.run(msg);
1485
+ } })) : (_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"
1185
1486
  ? THINKING_BORDER_COLORS[thinkingBorderFrame]
1186
- : "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: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
1487
+ : "transparent", paddingLeft: 1, paddingRight: 1, width: columns, 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: () => {
1187
1488
  stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1188
1489
  setOverlay("tasks");
1189
- }, 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 }))] }))] }));
1490
+ }, onToggleSkills: () => {
1491
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1492
+ setOverlay("skills");
1493
+ }, onTogglePlanMode: () => {
1494
+ const next = !planMode;
1495
+ setPlanMode(next);
1496
+ log("INFO", "plan", `Plan mode ${next ? "enabled" : "disabled"}`);
1497
+ setLiveItems((items) => [
1498
+ ...items,
1499
+ { kind: "info", text: `Plan mode ${next ? "on" : "off"}`, id: getId() },
1500
+ ]);
1501
+ }, 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 }))] }))] }));
1190
1502
  }
1191
1503
  //# sourceMappingURL=App.js.map