march-cli 0.1.2 → 0.1.4
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 +3 -1
- package/src/agent/runner.mjs +41 -10
- package/src/agent/turn/turn-runner.mjs +191 -18
- package/src/assets/march-icon.png +0 -0
- package/src/cli/repl-loop.mjs +27 -3
- package/src/cli/slash-commands.mjs +17 -1
- package/src/cli/tui/input/mouse-selection-controller.mjs +3 -1
- package/src/cli/tui/markdown-renderer.mjs +6 -0
- package/src/cli/tui/output/scroll-state.mjs +2 -2
- package/src/cli/tui/output/text-line-renderer.mjs +50 -0
- package/src/cli/tui/output-buffer.mjs +40 -53
- package/src/cli/tui/render/render-scheduler.mjs +26 -0
- package/src/cli/tui/tool-rendering.mjs +3 -0
- package/src/cli/ui.mjs +6 -7
- package/src/config/loader.mjs +1 -1
- package/src/context/center-memory.mjs +14 -0
- package/src/context/engine.mjs +49 -1
- package/src/context/system-core/base.md +5 -1
- package/src/main.mjs +4 -0
- package/src/notification/desktop-notifier.mjs +197 -20
- package/src/session/sidecar.mjs +1 -0
|
@@ -1,59 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { R, brightBlack, dim } from "./ui-theme.mjs";
|
|
1
|
+
import { brightBlack, dim } from "./ui-theme.mjs";
|
|
3
2
|
import { renderToolCardBlock } from "./output/tool-card-renderer.mjs";
|
|
4
3
|
import { renderMarkdown, renderStreamingMarkdown } from "./markdown-renderer.mjs";
|
|
5
4
|
import { renderEditDiffBlock } from "./tui-diff-rendering.mjs";
|
|
6
5
|
import { OutputScrollState } from "./output/scroll-state.mjs";
|
|
6
|
+
import { appendTextLines, wrapLine } from "./output/text-line-renderer.mjs";
|
|
7
7
|
|
|
8
8
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
9
|
|
|
10
|
-
function wrapLine(text, maxWidth) {
|
|
11
|
-
if (maxWidth <= 0) return [""];
|
|
12
|
-
const result = [];
|
|
13
|
-
let cur = "";
|
|
14
|
-
let curW = 0;
|
|
15
|
-
let activeSgr = "";
|
|
16
|
-
for (let i = 0; i < text.length;) {
|
|
17
|
-
if (text[i] === "\x1b") {
|
|
18
|
-
const match = text.slice(i).match(/^\x1b\[[0-?]*[ -/]*[@-~]/);
|
|
19
|
-
if (match) {
|
|
20
|
-
const seq = match[0];
|
|
21
|
-
cur += seq;
|
|
22
|
-
activeSgr = updateActiveSgr(activeSgr, seq);
|
|
23
|
-
i += seq.length;
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const ch = text[i];
|
|
28
|
-
const w = visibleWidth(ch);
|
|
29
|
-
if (curW + w > maxWidth) {
|
|
30
|
-
result.push(activeSgr ? `${cur}${R}` : cur);
|
|
31
|
-
cur = activeSgr + ch;
|
|
32
|
-
curW = w;
|
|
33
|
-
} else {
|
|
34
|
-
cur += ch;
|
|
35
|
-
curW += w;
|
|
36
|
-
}
|
|
37
|
-
i += 1;
|
|
38
|
-
}
|
|
39
|
-
if (cur) result.push(cur);
|
|
40
|
-
return result.length > 0 ? result : [""];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function updateActiveSgr(activeSgr, seq) {
|
|
44
|
-
if (!seq.endsWith("m")) return activeSgr;
|
|
45
|
-
const body = seq.slice(2, -1);
|
|
46
|
-
if (body === "" || body.split(";").includes("0")) return "";
|
|
47
|
-
return seq;
|
|
48
|
-
}
|
|
49
10
|
|
|
50
|
-
function appendTextLines(lines, textLines, width) {
|
|
51
|
-
for (const line of textLines) {
|
|
52
|
-
for (const part of String(line ?? "").split(/\r?\n/)) {
|
|
53
|
-
for (const wrapped of wrapLine(part, width)) lines.push(wrapped);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
11
|
|
|
58
12
|
function currentTextToBlocks(textLines, sealed, cache = null) {
|
|
59
13
|
const blocks = [];
|
|
@@ -117,6 +71,7 @@ export class OutputBuffer {
|
|
|
117
71
|
this._activeThinking = null;
|
|
118
72
|
this.overlayStatus = null;
|
|
119
73
|
this.scrollState = new OutputScrollState();
|
|
74
|
+
this._segmentLinesCache = new Map();
|
|
120
75
|
}
|
|
121
76
|
|
|
122
77
|
get scrollOffset() {
|
|
@@ -133,6 +88,7 @@ export class OutputBuffer {
|
|
|
133
88
|
this._activeThinking = null;
|
|
134
89
|
this.overlayStatus = null;
|
|
135
90
|
this.scrollState.clear();
|
|
91
|
+
this._segmentLinesCache = new Map();
|
|
136
92
|
}
|
|
137
93
|
|
|
138
94
|
write(text) {
|
|
@@ -174,6 +130,7 @@ export class OutputBuffer {
|
|
|
174
130
|
this._flushText();
|
|
175
131
|
const seg = { type: "thinking", tokens: 0, content: [] };
|
|
176
132
|
this.segments.push(seg);
|
|
133
|
+
this._invalidateSegmentLines();
|
|
177
134
|
this._activeThinking = seg;
|
|
178
135
|
}
|
|
179
136
|
|
|
@@ -197,12 +154,14 @@ export class OutputBuffer {
|
|
|
197
154
|
this.overlayStatus = null;
|
|
198
155
|
this._flushText();
|
|
199
156
|
this.segments.push({ type: "thinking", tokens, content: content.split("\n") });
|
|
157
|
+
this._invalidateSegmentLines();
|
|
200
158
|
}
|
|
201
159
|
|
|
202
160
|
addBlock(block) {
|
|
203
161
|
this.overlayStatus = null;
|
|
204
162
|
this._flushText();
|
|
205
163
|
this.segments.push(block);
|
|
164
|
+
this._invalidateSegmentLines();
|
|
206
165
|
}
|
|
207
166
|
|
|
208
167
|
setOverlayStatus(lines) {
|
|
@@ -220,6 +179,7 @@ export class OutputBuffer {
|
|
|
220
179
|
_flushText() {
|
|
221
180
|
if (this.currentText.length <= 1 && this.currentText[0].text === "") return false;
|
|
222
181
|
this.segments.push(...currentTextToBlocks(this.currentText, true));
|
|
182
|
+
this._invalidateSegmentLines();
|
|
223
183
|
this.currentText = [{ text: "", markdown: false }];
|
|
224
184
|
this.currentTextCache = new Map();
|
|
225
185
|
return true;
|
|
@@ -234,8 +194,8 @@ export class OutputBuffer {
|
|
|
234
194
|
this.spinnerIdx = (this.spinnerIdx + 1) % SPINNER_FRAMES.length;
|
|
235
195
|
}
|
|
236
196
|
|
|
237
|
-
scroll(delta) {
|
|
238
|
-
return this.scrollState.scroll(delta);
|
|
197
|
+
scroll(delta, options) {
|
|
198
|
+
return this.scrollState.scroll(delta, options);
|
|
239
199
|
}
|
|
240
200
|
|
|
241
201
|
getScrollStep() {
|
|
@@ -262,10 +222,17 @@ export class OutputBuffer {
|
|
|
262
222
|
seg.expanded = expanded;
|
|
263
223
|
changed = true;
|
|
264
224
|
}
|
|
225
|
+
if (changed) this._invalidateSegmentLines();
|
|
265
226
|
return changed;
|
|
266
227
|
}
|
|
267
228
|
|
|
268
|
-
invalidate() {
|
|
229
|
+
invalidate() {
|
|
230
|
+
this._invalidateSegmentLines();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_invalidateSegmentLines() {
|
|
234
|
+
this._segmentLinesCache.clear();
|
|
235
|
+
}
|
|
269
236
|
|
|
270
237
|
render(width) {
|
|
271
238
|
const allLines = this._computeLines(width);
|
|
@@ -278,8 +245,9 @@ export class OutputBuffer {
|
|
|
278
245
|
}
|
|
279
246
|
|
|
280
247
|
_computeLines(width) {
|
|
281
|
-
const lines = [];
|
|
282
|
-
|
|
248
|
+
const lines = [...this._renderCachedSegmentLines(width)];
|
|
249
|
+
const dynamicStart = this._cachedSegmentPrefixCount();
|
|
250
|
+
for (const seg of this.segments.slice(dynamicStart)) {
|
|
283
251
|
for (const line of renderBlock(seg, width)) lines.push(line);
|
|
284
252
|
}
|
|
285
253
|
for (const block of currentTextToBlocks(this.currentText, false, this.currentTextCache)) {
|
|
@@ -294,4 +262,23 @@ export class OutputBuffer {
|
|
|
294
262
|
}
|
|
295
263
|
return lines;
|
|
296
264
|
}
|
|
265
|
+
|
|
266
|
+
_renderCachedSegmentLines(width) {
|
|
267
|
+
const prefixCount = this._cachedSegmentPrefixCount();
|
|
268
|
+
const cached = this._segmentLinesCache.get(width);
|
|
269
|
+
if (cached?.prefixCount === prefixCount) return cached.lines;
|
|
270
|
+
|
|
271
|
+
const lines = [];
|
|
272
|
+
for (let i = 0; i < prefixCount; i += 1) {
|
|
273
|
+
for (const line of renderBlock(this.segments[i], width)) lines.push(line);
|
|
274
|
+
}
|
|
275
|
+
this._segmentLinesCache.set(width, { prefixCount, lines });
|
|
276
|
+
return lines;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
_cachedSegmentPrefixCount() {
|
|
280
|
+
if (!this._activeThinking) return this.segments.length;
|
|
281
|
+
const index = this.segments.indexOf(this._activeThinking);
|
|
282
|
+
return index < 0 ? this.segments.length : index;
|
|
283
|
+
}
|
|
297
284
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function createRenderScheduler({ requestRender, delayMs = 50 }) {
|
|
2
|
+
let timer = null;
|
|
3
|
+
|
|
4
|
+
function renderNow() {
|
|
5
|
+
clearPending();
|
|
6
|
+
requestRender();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderSoon() {
|
|
10
|
+
if (timer) return;
|
|
11
|
+
// Streaming deltas are append-only, so coalesce them without delaying input-driven renders.
|
|
12
|
+
timer = setTimeout(() => {
|
|
13
|
+
timer = null;
|
|
14
|
+
requestRender();
|
|
15
|
+
}, delayMs);
|
|
16
|
+
timer.unref?.();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function clearPending() {
|
|
20
|
+
if (!timer) return;
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
timer = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { renderNow, renderSoon, clearPending };
|
|
26
|
+
}
|
|
@@ -90,6 +90,9 @@ export function formatToolSuccessSummary(name, result, out = "") {
|
|
|
90
90
|
const matches = result?.details?.count ?? countNonEmptyLines(out);
|
|
91
91
|
return `${matches} file${matches === 1 ? "" : "s"}`;
|
|
92
92
|
}
|
|
93
|
+
if (name === "memory_open") {
|
|
94
|
+
return compactText(result?.details?.entry?.name ?? compactPath(result?.details?.path ?? ""));
|
|
95
|
+
}
|
|
93
96
|
return "";
|
|
94
97
|
}
|
|
95
98
|
|
package/src/cli/ui.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { createTuiInputController } from "./tui/tui-input-controller.mjs";
|
|
|
23
23
|
import { writeMemoryHint } from "./tui/recall-rendering.mjs";
|
|
24
24
|
import { writeToolEnd, writeToolStart } from "./tui/tool-rendering.mjs";
|
|
25
25
|
import { EDITOR_THEME, brightBlack } from "./tui/ui-theme.mjs";
|
|
26
|
+
import { createRenderScheduler } from "./tui/render/render-scheduler.mjs";
|
|
26
27
|
import { writeTranscriptToOutput } from "../session/transcript.mjs";
|
|
27
28
|
|
|
28
29
|
export { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocomplete.mjs";
|
|
@@ -59,10 +60,8 @@ export function createTuiUI({
|
|
|
59
60
|
let mouseOn = true;
|
|
60
61
|
let toolsExpanded = false;
|
|
61
62
|
const activeToolBlocks = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
tui.requestRender();
|
|
65
|
-
}
|
|
63
|
+
const renderScheduler = createRenderScheduler({ requestRender: () => tui.requestRender() });
|
|
64
|
+
const requestRender = renderScheduler.renderNow;
|
|
66
65
|
|
|
67
66
|
const spinnerStatus = createSpinnerStatusController({ output, requestRender });
|
|
68
67
|
const retryStatus = createRetryStatusController({ output, requestRender, stopSpinner: spinnerStatus.stop });
|
|
@@ -172,7 +171,7 @@ export function createTuiUI({
|
|
|
172
171
|
|
|
173
172
|
thinkingDelta: (delta) => {
|
|
174
173
|
output.appendThinking(delta);
|
|
175
|
-
|
|
174
|
+
renderScheduler.renderSoon();
|
|
176
175
|
},
|
|
177
176
|
|
|
178
177
|
thinkingEnd: (tokens) => {
|
|
@@ -197,7 +196,7 @@ export function createTuiUI({
|
|
|
197
196
|
textDelta: (delta) => {
|
|
198
197
|
ensureStarted(); retryStatus.stop(); spinnerStatus.stop();
|
|
199
198
|
output.writeMarkdown(delta);
|
|
200
|
-
|
|
199
|
+
renderScheduler.renderSoon();
|
|
201
200
|
},
|
|
202
201
|
assistantReplyEnd: () => {
|
|
203
202
|
ensureStarted();
|
|
@@ -278,8 +277,8 @@ export function createTuiUI({
|
|
|
278
277
|
toggleToolOutput,
|
|
279
278
|
toggleShellDrawer: () => shellDrawerControls.toggle(),
|
|
280
279
|
requestExit: () => inputController.requestExit(),
|
|
281
|
-
|
|
282
280
|
close: async () => {
|
|
281
|
+
renderScheduler.clearPending();
|
|
283
282
|
spinnerStatus.stop();
|
|
284
283
|
retryStatus.stop();
|
|
285
284
|
if (started) {
|
package/src/config/loader.mjs
CHANGED
|
@@ -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, desktop: true, bell: false, command: null, minDurationMs: 0, sound: true },
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
for (const layer of layers) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function defaultCenterMemoryPath() {
|
|
6
|
+
return join(homedir(), ".march", "memory", "center.md");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildCenterMemory(path = defaultCenterMemoryPath()) {
|
|
10
|
+
if (!path || !existsSync(path)) return null;
|
|
11
|
+
const content = readFileSync(path, "utf8").trimEnd();
|
|
12
|
+
if (!content.trim()) return null;
|
|
13
|
+
return `[center_memory]\n--- ${path} ---\n${content}`;
|
|
14
|
+
}
|
package/src/context/engine.mjs
CHANGED
|
@@ -3,16 +3,20 @@ import { buildSessionIdentity } from "./session-status.mjs";
|
|
|
3
3
|
import { buildSystemCore, resolveSystemCorePromptKey } from "./system-core.mjs";
|
|
4
4
|
import { buildInjectionsLayer } from "./injections.mjs";
|
|
5
5
|
import { buildProjectContext } from "./project-context.mjs";
|
|
6
|
+
import { buildCenterMemory } from "./center-memory.mjs";
|
|
6
7
|
import { formatRecallHints } from "../memory/markdown-store.mjs";
|
|
7
8
|
|
|
8
9
|
export class ContextEngine {
|
|
9
|
-
constructor({ cwd, modelId, provider = "deepseek", thinkingLevel = "medium", namespace = "", memoryRoot = null, shellRuntime = null, lspService = null, injections = [], maxTurns, trimBatch }) {
|
|
10
|
+
constructor({ cwd, modelId, provider = "deepseek", thinkingLevel = "medium", namespace = "", memoryRoot = null, centerMemoryPath = null, shellRuntime = null, lspService = null, injections = [], maxTurns, trimBatch }) {
|
|
10
11
|
this.cwd = cwd;
|
|
11
12
|
this.memoryRoot = memoryRoot;
|
|
13
|
+
this.centerMemoryPath = centerMemoryPath;
|
|
12
14
|
this.modelId = modelId;
|
|
13
15
|
this.provider = provider;
|
|
14
16
|
this.thinkingLevel = thinkingLevel;
|
|
15
17
|
this.turns = [];
|
|
18
|
+
this.pendingAssistantRecallHints = [];
|
|
19
|
+
this.pendingAssistantRecallHintsRendered = false;
|
|
16
20
|
this.sessionName = "";
|
|
17
21
|
this.toolDefs = [];
|
|
18
22
|
this.namespace = namespace;
|
|
@@ -58,6 +62,9 @@ export class ContextEngine {
|
|
|
58
62
|
const projectCtx = buildProjectContext(this.cwd);
|
|
59
63
|
if (projectCtx) layers.push({ name: "project_context", text: projectCtx });
|
|
60
64
|
|
|
65
|
+
const centerMemory = buildCenterMemory(this.centerMemoryPath);
|
|
66
|
+
if (centerMemory) layers.push({ name: "center_memory", text: centerMemory });
|
|
67
|
+
|
|
61
68
|
layers.push({ name: "recent_chat", text: this.#buildRecentChat() });
|
|
62
69
|
|
|
63
70
|
return layers;
|
|
@@ -86,6 +93,30 @@ export class ContextEngine {
|
|
|
86
93
|
return ids;
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
setPendingAssistantRecallHints(hints = []) {
|
|
97
|
+
this.pendingAssistantRecallHints = uniqueHints(hints);
|
|
98
|
+
this.pendingAssistantRecallHintsRendered = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
peekPendingAssistantRecallHints() {
|
|
102
|
+
return this.pendingAssistantRecallHints;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hasRenderedPendingAssistantRecallHints() {
|
|
106
|
+
return this.pendingAssistantRecallHintsRendered;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
markPendingAssistantRecallHintsRendered() {
|
|
110
|
+
if (this.pendingAssistantRecallHints.length > 0) this.pendingAssistantRecallHintsRendered = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
takePendingAssistantRecallHints() {
|
|
114
|
+
const hints = this.pendingAssistantRecallHints;
|
|
115
|
+
this.pendingAssistantRecallHints = [];
|
|
116
|
+
this.pendingAssistantRecallHintsRendered = false;
|
|
117
|
+
return hints;
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
resolvePath(raw) {
|
|
90
121
|
return resolve(this.cwd, raw);
|
|
91
122
|
}
|
|
@@ -107,9 +138,15 @@ export class ContextEngine {
|
|
|
107
138
|
restoreSession(data, _pool, { replace = false } = {}) {
|
|
108
139
|
if (replace) {
|
|
109
140
|
this.turns = [];
|
|
141
|
+
this.pendingAssistantRecallHints = [];
|
|
142
|
+
this.pendingAssistantRecallHintsRendered = false;
|
|
110
143
|
this.sessionName = "";
|
|
111
144
|
}
|
|
112
145
|
if (data.turns) this.turns = data.turns;
|
|
146
|
+
if (Array.isArray(data.pendingAssistantRecallHints)) {
|
|
147
|
+
this.pendingAssistantRecallHints = uniqueHints(data.pendingAssistantRecallHints);
|
|
148
|
+
this.pendingAssistantRecallHintsRendered = false;
|
|
149
|
+
}
|
|
113
150
|
if (typeof data.sessionName === "string") this.sessionName = data.sessionName;
|
|
114
151
|
this.setRuntimeState(data);
|
|
115
152
|
}
|
|
@@ -147,3 +184,14 @@ function appendCurrentUser(recentChat, userMessage) {
|
|
|
147
184
|
const currentUser = String(userMessage ?? "").trimEnd();
|
|
148
185
|
return `${recentChat}\n\n[current_user]\n${currentUser}`;
|
|
149
186
|
}
|
|
187
|
+
|
|
188
|
+
function uniqueHints(hints = []) {
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
const unique = [];
|
|
191
|
+
for (const hint of hints) {
|
|
192
|
+
if (!hint?.id || seen.has(hint.id)) continue;
|
|
193
|
+
seen.add(hint.id);
|
|
194
|
+
unique.push(hint);
|
|
195
|
+
}
|
|
196
|
+
return unique;
|
|
197
|
+
}
|
|
@@ -7,7 +7,7 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
7
7
|
- Be concise and direct. Match the response shape to the task; simple questions get simple answers.
|
|
8
8
|
- Assume users may not see tool calls. Before the first tool call, say in one sentence what you are about to do. While working, give brief updates when you find something important, change direction, or hit a blocker.
|
|
9
9
|
- Don't narrate hidden reasoning. State decisions, results, and relevant next steps.
|
|
10
|
-
- End with
|
|
10
|
+
- End with a brief summary of what you did during the task, including what changed, verification status, and what's next if anything; keep it concise, but don't omit the execution overview.
|
|
11
11
|
- Report outcomes truthfully. If tests fail or a step was skipped, say so plainly with the relevant output or reason.
|
|
12
12
|
</communication_contract>
|
|
13
13
|
|
|
@@ -57,4 +57,8 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
57
57
|
- Use memory_search(query) for full-text search across all memories.
|
|
58
58
|
- To edit an existing memory, use memory_open(id) to get its path, then edit_file with mode="patch" for targeted edits.
|
|
59
59
|
- Use memory_save() to create memories or update whole fields. Before creating a new memory, first search/open related memories and merge updates into an existing memory when they share the same topic, project, or decision thread; prefer modifying the existing memory file over creating a scattered new one. Tags are the primary retrieval key for future recall. Prefer lowercase kebab-case tags like 'march-cli', 'tooling', 'permissions'.
|
|
60
|
+
- When learning multiple related external workflows or skills, maintain memory as an evolving domain library: start with the specific source name when only one item exists, then rename and rewrite the memory title/description as the scope grows; merge new related learnings into the same memory, preserving each source's unique traits while distilling reusable principles.
|
|
61
|
+
- Distinguish "migrating a Skill to memory" from "learning a Skill": migration preserves the complete Skill folder under memory_root/skills/ and creates a memory index with purpose, source, entry files, and local path; learning only reads and internalizes the Skill's methods, scenarios, and principles into ordinary memory without copying source files. Infer the action from the user's wording, and ask when ambiguous.
|
|
62
|
+
- Unlike memory hints, this system-core center is always visible in every model call. Only update the center for instructions that must always be followed; use memory for contextual, project-specific, or recall-dependent knowledge.
|
|
63
|
+
- If execution takes a meaningful detour, create or update a memory after the task. A detour means the initial plan or assumption failed, multiple approaches were tried, and the final successful path contains reusable project knowledge. Record the failed assumption, what was tried, and the successful approach. Prefer updating an existing related memory over creating a new one.
|
|
60
64
|
</memory_system>
|
package/src/main.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import { formatStartupBanner } from "./cli/startup/startup-banner.mjs";
|
|
|
28
28
|
import { initializeMcp } from "./mcp/index.mjs";
|
|
29
29
|
import { createWebToolsFromConfig } from "./web/tools.mjs";
|
|
30
30
|
import { createModelContextDumper } from "./debug/model-context-dumper.mjs";
|
|
31
|
+
import { defaultCenterMemoryPath } from "./context/center-memory.mjs";
|
|
31
32
|
import { runProviderConfigCommand } from "./provider/config-command.mjs";
|
|
32
33
|
import { runWebSearchConfigCommand } from "./web/config-command.mjs";
|
|
33
34
|
import { createDesktopTurnNotifier } from "./notification/desktop-notifier.mjs";
|
|
@@ -91,6 +92,7 @@ export async function run(argv) {
|
|
|
91
92
|
const modeState = createModeState();
|
|
92
93
|
const namespace = loadOrCreateProjectId(projectMarchDir);
|
|
93
94
|
const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
|
|
95
|
+
const centerMemoryPath = defaultCenterMemoryPath();
|
|
94
96
|
const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
|
|
95
97
|
const memoryTools = createMarkdownMemoryTools(memoryStore);
|
|
96
98
|
const currentProject = basename(cwd);
|
|
@@ -111,6 +113,7 @@ export async function run(argv) {
|
|
|
111
113
|
const webTools = createWebToolsFromConfig(config);
|
|
112
114
|
const turnNotifier = createDesktopTurnNotifier({
|
|
113
115
|
enabled: Boolean(config.notifications?.turnEnd),
|
|
116
|
+
config: config.notifications,
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
// Permission controller
|
|
@@ -156,6 +159,7 @@ export async function run(argv) {
|
|
|
156
159
|
stateRoot,
|
|
157
160
|
ui,
|
|
158
161
|
memoryRoot,
|
|
162
|
+
centerMemoryPath,
|
|
159
163
|
memoryStore,
|
|
160
164
|
memoryTools,
|
|
161
165
|
shellRuntime,
|