march-cli 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "March CLI — terminal-native coding agent with context reconstruction",
5
5
  "type": "module",
6
6
  "main": "./src/main.mjs",
@@ -29,6 +29,7 @@ export async function runRunnerTurn({
29
29
  if (hints.length > 0) {
30
30
  midTurnRecallHints.push(...hints);
31
31
  onMidTurnRecallHints?.(hints);
32
+ ui.memoryHint?.({ source: "assistant", hints });
32
33
  }
33
34
  }
34
35
  });
@@ -51,8 +52,8 @@ export async function runRunnerTurn({
51
52
 
52
53
  closeAssistantReply({ ui, state: turnState });
53
54
  const assistantRecallHints = flushAssistantRecall({ memoryStore, engine, turnState, currentProject });
55
+ engine.setPendingAssistantRecallHints?.(assistantRecallHints);
54
56
  const recordedAssistantRecallHints = uniqueHints([...midTurnRecallHints, ...assistantRecallHints]);
55
- ui.memoryHint?.({ source: "assistant", hints: recordedAssistantRecallHints });
56
57
 
57
58
  engine.recordTurn({
58
59
  userMessage: userMessage ?? prompt.slice(0, 300),
@@ -18,16 +18,21 @@ export async function runSingleShotPrompt({
18
18
  modeState = null,
19
19
  }) {
20
20
  memoryStore.beginTurn();
21
+ const carryoverAlreadyRendered = runner.engine.hasRenderedPendingAssistantRecallHints?.() ?? false;
22
+ const carryoverRecallHints = runner.engine.takePendingAssistantRecallHints?.() ?? [];
21
23
  const userRecallHints = memoryStore.recallForUser(prompt, { currentProject, excludedIds: runner.engine.getRecentRecallMemoryIds?.() ?? [] });
22
24
  const recallBlock = formatRecallHints("user", userRecallHints);
25
+ const carryoverRecallBlock = formatRecallHints("assistant", carryoverRecallHints);
23
26
  const shellHints = formatShellHints(runner.shellRuntime);
24
27
  const modePrompt = appendModeReminder(prompt, modeState?.get?.());
25
- const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, shellHints);
28
+ const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, carryoverRecallBlock, shellHints);
26
29
  ui.writeln(formatUserDisplayMessage(prompt));
27
30
  ui.memoryHint?.({ source: "user", hints: userRecallHints });
31
+ if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.memoryHint?.({ source: "assistant", hints: carryoverRecallHints });
28
32
  refreshStatusBar.startWorking?.();
29
33
  try {
30
34
  await runner.runTurn(fullPrompt, prompt, { userRecallHints, currentProject });
35
+ renderPendingAssistantRecallPreview({ runner, ui });
31
36
  } finally {
32
37
  refreshStatusBar.stopWorking?.();
33
38
  memoryStore.endTurn();
@@ -126,17 +131,22 @@ function handleInlineCommand(trimmed, { cwd, ui, lastInlineShellCommand }) {
126
131
 
127
132
  async function runReplTurn({ prompt, args, runner, memoryStore, currentProject, ui, refreshStatusBar, setTurnRunning, modeState = null }) {
128
133
  memoryStore.beginTurn();
134
+ const carryoverAlreadyRendered = runner.engine.hasRenderedPendingAssistantRecallHints?.() ?? false;
135
+ const carryoverRecallHints = runner.engine.takePendingAssistantRecallHints?.() ?? [];
129
136
  const userRecallHints = memoryStore.recallForUser(prompt, { currentProject, excludedIds: runner.engine.getRecentRecallMemoryIds?.() ?? [] });
130
137
  const recallBlock = formatRecallHints("user", userRecallHints);
138
+ const carryoverRecallBlock = formatRecallHints("assistant", carryoverRecallHints);
131
139
  const shellHints = formatShellHints(runner.shellRuntime);
132
140
  const modePrompt = appendModeReminder(prompt, modeState?.get?.());
133
- const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, shellHints);
141
+ const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, carryoverRecallBlock, shellHints);
134
142
  try {
135
143
  ui.writeln(formatUserDisplayMessage(prompt));
136
144
  ui.memoryHint?.({ source: "user", hints: userRecallHints });
145
+ if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.memoryHint?.({ source: "assistant", hints: carryoverRecallHints });
137
146
  setTurnRunning(true);
138
147
  refreshStatusBar.startWorking?.();
139
148
  await runner.runTurn(fullPrompt, prompt, { userRecallHints, currentProject });
149
+ renderPendingAssistantRecallPreview({ runner, ui });
140
150
  ui.writeln("");
141
151
  } catch (err) {
142
152
  ui.writeln(`Error: ${err.message}`);
@@ -155,3 +165,11 @@ export function formatUserDisplayMessage(prompt) {
155
165
  function appendPromptBlocks(prompt, ...blocks) {
156
166
  return [prompt, ...blocks.filter(Boolean)].join("\n\n");
157
167
  }
168
+
169
+ function renderPendingAssistantRecallPreview({ runner, ui }) {
170
+ if (runner.engine.hasRenderedPendingAssistantRecallHints?.()) return;
171
+ const hints = runner.engine.peekPendingAssistantRecallHints?.() ?? [];
172
+ if (hints.length === 0) return;
173
+ ui.memoryHint?.({ source: "assistant", hints });
174
+ runner.engine.markPendingAssistantRecallHintsRendered?.();
175
+ }
@@ -167,6 +167,12 @@ function appendWrappedRuns(lines, runs, width, indent = 0, firstPrefix = null) {
167
167
  const maxWidth = Math.max(1, width);
168
168
  for (const run of runs) {
169
169
  for (const ch of run.text) {
170
+ if (ch === "\n") {
171
+ lines.push(current);
172
+ current = restPrefix;
173
+ currentWidth = visibleWidth(restPrefix);
174
+ continue;
175
+ }
170
176
  const charWidth = visibleWidth(ch);
171
177
  if (currentWidth + charWidth > maxWidth && currentWidth > visibleWidth(stripAnsi(prefix))) {
172
178
  lines.push(current);
@@ -49,7 +49,7 @@ function mergeLayers(layers) {
49
49
  maxTurns: null,
50
50
  trimBatch: null,
51
51
  memoryRoot: null,
52
- notifications: { turnEnd: false },
52
+ notifications: { turnEnd: true },
53
53
  };
54
54
 
55
55
  for (const layer of layers) {
@@ -13,6 +13,8 @@ export class ContextEngine {
13
13
  this.provider = provider;
14
14
  this.thinkingLevel = thinkingLevel;
15
15
  this.turns = [];
16
+ this.pendingAssistantRecallHints = [];
17
+ this.pendingAssistantRecallHintsRendered = false;
16
18
  this.sessionName = "";
17
19
  this.toolDefs = [];
18
20
  this.namespace = namespace;
@@ -86,6 +88,30 @@ export class ContextEngine {
86
88
  return ids;
87
89
  }
88
90
 
91
+ setPendingAssistantRecallHints(hints = []) {
92
+ this.pendingAssistantRecallHints = uniqueHints(hints);
93
+ this.pendingAssistantRecallHintsRendered = false;
94
+ }
95
+
96
+ peekPendingAssistantRecallHints() {
97
+ return this.pendingAssistantRecallHints;
98
+ }
99
+
100
+ hasRenderedPendingAssistantRecallHints() {
101
+ return this.pendingAssistantRecallHintsRendered;
102
+ }
103
+
104
+ markPendingAssistantRecallHintsRendered() {
105
+ if (this.pendingAssistantRecallHints.length > 0) this.pendingAssistantRecallHintsRendered = true;
106
+ }
107
+
108
+ takePendingAssistantRecallHints() {
109
+ const hints = this.pendingAssistantRecallHints;
110
+ this.pendingAssistantRecallHints = [];
111
+ this.pendingAssistantRecallHintsRendered = false;
112
+ return hints;
113
+ }
114
+
89
115
  resolvePath(raw) {
90
116
  return resolve(this.cwd, raw);
91
117
  }
@@ -107,9 +133,15 @@ export class ContextEngine {
107
133
  restoreSession(data, _pool, { replace = false } = {}) {
108
134
  if (replace) {
109
135
  this.turns = [];
136
+ this.pendingAssistantRecallHints = [];
137
+ this.pendingAssistantRecallHintsRendered = false;
110
138
  this.sessionName = "";
111
139
  }
112
140
  if (data.turns) this.turns = data.turns;
141
+ if (Array.isArray(data.pendingAssistantRecallHints)) {
142
+ this.pendingAssistantRecallHints = uniqueHints(data.pendingAssistantRecallHints);
143
+ this.pendingAssistantRecallHintsRendered = false;
144
+ }
113
145
  if (typeof data.sessionName === "string") this.sessionName = data.sessionName;
114
146
  this.setRuntimeState(data);
115
147
  }
@@ -147,3 +179,14 @@ function appendCurrentUser(recentChat, userMessage) {
147
179
  const currentUser = String(userMessage ?? "").trimEnd();
148
180
  return `${recentChat}\n\n[current_user]\n${currentUser}`;
149
181
  }
182
+
183
+ function uniqueHints(hints = []) {
184
+ const seen = new Set();
185
+ const unique = [];
186
+ for (const hint of hints) {
187
+ if (!hint?.id || seen.has(hint.id)) continue;
188
+ seen.add(hint.id);
189
+ unique.push(hint);
190
+ }
191
+ return unique;
192
+ }
@@ -25,7 +25,7 @@ export async function sendDesktopNotification({ platform = process.platform, spa
25
25
 
26
26
  const safeTitle = normalizeNotificationText(title) || "March";
27
27
  const safeMessage = normalizeNotificationText(message) || "Turn finished";
28
- const script = buildWindowsBalloonScript({ title: safeTitle, message: safeMessage });
28
+ const script = buildWindowsNotificationScript({ title: safeTitle, message: safeMessage });
29
29
 
30
30
  try {
31
31
  const child = spawnProcess("powershell.exe", [
@@ -63,6 +63,38 @@ export function buildWindowsBalloonScript({ title, message, timeoutMs = DEFAULT_
63
63
  ].join("; ");
64
64
  }
65
65
 
66
+ export function buildWindowsNotificationScript({ title, message, timeoutMs = DEFAULT_BALLOON_TIMEOUT_MS }) {
67
+ const toastXml = escapePowerShellDoubleQuotedString(buildToastXml({ title, message }));
68
+ const balloonScript = buildWindowsBalloonScript({ title, message, timeoutMs });
69
+ return [
70
+ "try {",
71
+ "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null",
72
+ "[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null",
73
+ "$xml = New-Object Windows.Data.Xml.Dom.XmlDocument",
74
+ `$xml.LoadXml("${toastXml}")`,
75
+ "$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)",
76
+ "[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('PowerShell').Show($toast)",
77
+ "} catch {",
78
+ balloonScript,
79
+ "}",
80
+ ].join("; ");
81
+ }
82
+
83
+ function buildToastXml({ title, message }) {
84
+ return `<toast><visual><binding template="ToastGeneric"><text>${escapeXmlText(title)}</text><text>${escapeXmlText(message)}</text></binding></visual></toast>`;
85
+ }
86
+
87
+ function escapeXmlText(text) {
88
+ return String(text)
89
+ .replaceAll("&", "&amp;")
90
+ .replaceAll("<", "&lt;")
91
+ .replaceAll(">", "&gt;");
92
+ }
93
+
94
+ function escapePowerShellDoubleQuotedString(text) {
95
+ return String(text).replace(/[`"$]/g, "`$&");
96
+ }
97
+
66
98
  function defaultTurnTitle(status) {
67
99
  return status === "error" ? "March turn failed" : "March is ready";
68
100
  }
@@ -22,6 +22,7 @@ export function captureContextSidecar(engine, metadata = {}) {
22
22
  sessionName: engine.sessionName ?? "",
23
23
  thinkingLevel: engine.thinkingLevel,
24
24
  namespace: engine.namespace,
25
+ pendingAssistantRecallHints: engine.pendingAssistantRecallHints ?? [],
25
26
  turns: engine.turns,
26
27
  };
27
28
  }