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 +1 -1
- package/src/agent/turn/turn-runner.mjs +2 -1
- package/src/cli/repl-loop.mjs +20 -2
- package/src/cli/tui/markdown-renderer.mjs +6 -0
- package/src/config/loader.mjs +1 -1
- package/src/context/engine.mjs +43 -0
- package/src/notification/desktop-notifier.mjs +33 -1
- package/src/session/sidecar.mjs +1 -0
package/package.json
CHANGED
|
@@ -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),
|
package/src/cli/repl-loop.mjs
CHANGED
|
@@ -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);
|
package/src/config/loader.mjs
CHANGED
package/src/context/engine.mjs
CHANGED
|
@@ -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 =
|
|
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("&", "&")
|
|
90
|
+
.replaceAll("<", "<")
|
|
91
|
+
.replaceAll(">", ">");
|
|
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
|
}
|
package/src/session/sidecar.mjs
CHANGED
|
@@ -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
|
}
|