march-cli 0.1.35 → 0.1.37
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/code-search/cache.mjs +133 -0
- package/src/agent/code-search/chunk-rules.mjs +107 -0
- package/src/agent/code-search/chunker.mjs +125 -0
- package/src/agent/code-search/engine.mjs +109 -0
- package/src/agent/code-search/languages.mjs +25 -0
- package/src/agent/code-search/parser-pool.mjs +29 -0
- package/src/agent/code-search/rerank.mjs +43 -0
- package/src/agent/code-search/retrieval/bm25.mjs +47 -0
- package/src/agent/code-search/retrieval/fusion.mjs +18 -0
- package/src/agent/code-search/retrieval/model2vec.mjs +96 -0
- package/src/agent/code-search/retrieval/safetensors.mjs +49 -0
- package/src/agent/code-search/retrieval/vector.mjs +107 -0
- package/src/agent/code-search/retrieval/wordpiece.mjs +82 -0
- package/src/agent/code-search/scanner.mjs +84 -0
- package/src/agent/code-search/tokenize.mjs +16 -0
- package/src/agent/code-search/tool.mjs +75 -0
- package/src/agent/runner/provider-quota-runtime.mjs +38 -0
- package/src/agent/runner.mjs +14 -10
- package/src/agent/runtime/remote-runner-client.mjs +2 -0
- package/src/agent/runtime/runner-ipc-target.mjs +7 -0
- package/src/agent/runtime/runner-process-client.mjs +5 -0
- package/src/agent/runtime/runner-process-factory.mjs +5 -0
- package/src/agent/runtime/runner-runtime-host.mjs +2 -0
- package/src/agent/runtime/state/runner-state.mjs +1 -0
- package/src/agent/runtime/ui-event-bridge.mjs +2 -0
- package/src/agent/session/session-options.mjs +2 -1
- package/src/agent/tools.mjs +7 -1
- package/src/agent/turn/turn-events.mjs +41 -0
- package/src/agent/turn/turn-runner.mjs +5 -2
- package/src/cli/commands/registry/slash-command-registry.mjs +10 -7
- package/src/cli/commands/status-command.mjs +61 -35
- package/src/cli/input/history-store.mjs +65 -3
- package/src/cli/repl-loop.mjs +8 -6
- package/src/cli/startup/app-runtime.mjs +5 -29
- package/src/cli/startup/create-runtime-runner.mjs +4 -46
- package/src/cli/tui/input/history-navigation-controller.mjs +56 -0
- package/src/cli/turn/turn-input-preparer.mjs +0 -1
- package/src/cli/ui.mjs +9 -0
- package/src/context/engine.mjs +4 -2
- package/src/context/system-core/base.md +9 -1
- package/src/history/runner.mjs +11 -0
- package/src/history/store.mjs +129 -0
- package/src/history/tool.mjs +39 -0
- package/src/lsp/client.mjs +12 -5
- package/src/lsp/service.mjs +15 -3
- package/src/main.mjs +1 -2
- package/src/provider/quota/codex.mjs +278 -0
- package/src/provider/quota/index.mjs +46 -0
- package/src/provider/quota/transport-observer.mjs +99 -0
- package/src/web-ui/command.mjs +2 -2
- package/src/web-ui/runtime-host.mjs +7 -23
- package/src/web-ui/server.mjs +1 -0
- package/src/web-ui/session-manager.mjs +4 -2
- package/src/web-ui/src/components/AppShell.tsx +1 -0
- package/src/web-ui/src/components/RightSidebar.tsx +47 -2
- package/src/web-ui/src/model.ts +20 -0
- package/src/web-ui/src/runtime/client.ts +8 -1
- package/src/web-ui/src/runtime/useWebRuntime.ts +13 -1
- package/src/web-ui/src/styles/shell.css +10 -0
- package/src/web-ui/dist/assets/index-BUmhnID4.css +0 -1
- package/src/web-ui/dist/assets/index-CtuqTjcB.js +0 -1845
- package/src/web-ui/dist/index.html +0 -13
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export function createHistoryNavigationController({ editor, requestRender, isAutocompleteOpen = () => false, hasOverlay = () => false } = {}) {
|
|
4
|
+
let draftText = null;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
handleInput(data) {
|
|
8
|
+
if (isAutocompleteOpen() || hasOverlay()) return undefined;
|
|
9
|
+
|
|
10
|
+
if (matchesKey(data, "alt+up")) return moveWithinInput(-1);
|
|
11
|
+
if (matchesKey(data, "alt+down")) return moveWithinInput(1);
|
|
12
|
+
if (matchesKey(data, "up")) return navigateHistory(-1);
|
|
13
|
+
if (matchesKey(data, "down")) return navigateHistory(1);
|
|
14
|
+
|
|
15
|
+
return undefined;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function navigateHistory(direction) {
|
|
20
|
+
const history = Array.isArray(editor?.history) ? editor.history : [];
|
|
21
|
+
const currentIndex = Number.isInteger(editor?.historyIndex) ? editor.historyIndex : -1;
|
|
22
|
+
const nextIndex = currentIndex - direction;
|
|
23
|
+
if (nextIndex < -1 || nextIndex >= history.length) return undefined;
|
|
24
|
+
|
|
25
|
+
if (currentIndex === -1) draftText = editor.getText?.() ?? "";
|
|
26
|
+
editor.lastAction = null;
|
|
27
|
+
editor.historyIndex = nextIndex;
|
|
28
|
+
setEditorTextPreservingHistory(nextIndex === -1 ? draftText ?? "" : history[nextIndex] ?? "");
|
|
29
|
+
if (nextIndex === -1) draftText = null;
|
|
30
|
+
requestRender?.();
|
|
31
|
+
return { consume: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function moveWithinInput(direction) {
|
|
35
|
+
editor.lastAction = null;
|
|
36
|
+
if (direction < 0) {
|
|
37
|
+
if (editor.isOnFirstVisualLine?.()) editor.moveToLineStart?.();
|
|
38
|
+
else editor.moveCursor?.(-1, 0);
|
|
39
|
+
} else {
|
|
40
|
+
if (editor.isOnLastVisualLine?.()) editor.moveToLineEnd?.();
|
|
41
|
+
else editor.moveCursor?.(1, 0);
|
|
42
|
+
}
|
|
43
|
+
requestRender?.();
|
|
44
|
+
return { consume: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setEditorTextPreservingHistory(text) {
|
|
48
|
+
if (typeof editor.setTextInternal === "function") {
|
|
49
|
+
editor.setTextInternal(text);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const historyIndex = editor.historyIndex;
|
|
53
|
+
editor.setText?.(text);
|
|
54
|
+
editor.historyIndex = historyIndex;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -5,7 +5,6 @@ import { formatMessageAttachmentsForDisplay } from "../../session/attachment-dis
|
|
|
5
5
|
import { formatShellHints } from "../../shell/hints.mjs";
|
|
6
6
|
|
|
7
7
|
export function prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState = null }) {
|
|
8
|
-
memoryStore.beginTurn();
|
|
9
8
|
const engine = runner.engine ?? {};
|
|
10
9
|
const carryoverAlreadyRendered = engine.hasRenderedPendingAssistantRecallHints?.() ?? false;
|
|
11
10
|
const carryoverRecallHints = engine.takePendingAssistantRecallHints?.() ?? [];
|
package/src/cli/ui.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { showEditorSelectList } from "./tui/select/editor-select-list.mjs";
|
|
|
16
16
|
import { StatusBar } from "./tui/status/status-bar.mjs";
|
|
17
17
|
import { MainPaneLayout } from "./tui/layout/main-pane-layout.mjs";
|
|
18
18
|
import { SafeRenderBoundary } from "./tui/layout/safe-render-boundary.mjs";
|
|
19
|
+
import { createHistoryNavigationController } from "./tui/input/history-navigation-controller.mjs";
|
|
19
20
|
import { createMouseSelectionController } from "./tui/input/mouse-selection-controller.mjs";
|
|
20
21
|
import { ScreenSelection } from "./tui/selection-screen.mjs";
|
|
21
22
|
import { writeEditDiff } from "./tui/tui-diff-rendering.mjs";
|
|
@@ -68,6 +69,12 @@ export function createTuiUI({
|
|
|
68
69
|
const retryStatus = createRetryStatusController({ output, requestRender, stopSpinner: spinnerStatus.stop });
|
|
69
70
|
const shellDrawerControls = createShellDrawerControls({ shellDrawer, output, requestRender });
|
|
70
71
|
const mouseSelectionController = createMouseSelectionController({ terminal, output, shellDrawer, shellDrawerControls, selection, writeClipboard, requestRender });
|
|
72
|
+
const historyNavigationController = createHistoryNavigationController({
|
|
73
|
+
editor,
|
|
74
|
+
requestRender,
|
|
75
|
+
isAutocompleteOpen: () => editor.isShowingAutocomplete(),
|
|
76
|
+
hasOverlay: () => tui.hasOverlay(),
|
|
77
|
+
});
|
|
71
78
|
|
|
72
79
|
let onEscapeHandler = null, onCtrlCHandler = null, onShiftTabHandler = null;
|
|
73
80
|
let onCtrlTHandler = null, onCtrlLHandler = null, onPasteImageHandler = null, onToggleModeHandler = null;
|
|
@@ -108,6 +115,8 @@ export function createTuiUI({
|
|
|
108
115
|
requestRender();
|
|
109
116
|
return { consume: true };
|
|
110
117
|
}
|
|
118
|
+
const historyNavigationResult = historyNavigationController.handleInput(data);
|
|
119
|
+
if (historyNavigationResult) return historyNavigationResult;
|
|
111
120
|
});
|
|
112
121
|
terminal.write("\x1b[?1049h");
|
|
113
122
|
terminal.write("\x1b[?1002h\x1b[?1006h");
|
package/src/context/engine.mjs
CHANGED
|
@@ -71,18 +71,20 @@ export class ContextEngine {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
recordTurn({ userMessage, assistantMessage, assistantContext = "", userRecallHints = [], assistantRecallHints = [] }) {
|
|
74
|
-
|
|
74
|
+
const turn = {
|
|
75
75
|
index: this.turns.length + 1,
|
|
76
76
|
userMessage,
|
|
77
77
|
assistantMessage: assistantMessage ?? "",
|
|
78
78
|
assistantContext: assistantContext ?? "",
|
|
79
79
|
userRecallHints,
|
|
80
80
|
assistantRecallHints,
|
|
81
|
-
}
|
|
81
|
+
};
|
|
82
|
+
this.turns.push(turn);
|
|
82
83
|
if (this.turns.length > this.maxTurns) {
|
|
83
84
|
const keep = Math.max(1, this.maxTurns - this.trimBatch);
|
|
84
85
|
this.turns = this.turns.slice(-keep);
|
|
85
86
|
}
|
|
87
|
+
return turn;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
getRecentRecallMemoryIds() {
|
|
@@ -69,7 +69,10 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
69
69
|
|
|
70
70
|
<editing_contract>
|
|
71
71
|
- Use read(path) for file inspection with 1-based line numbers.
|
|
72
|
-
- Use
|
|
72
|
+
- Use code_search first when locating unknown implementations, responsibility boundaries, cross-module flows, or concept-level behavior.
|
|
73
|
+
- Use grep(pattern) and find(pattern) for exact symbol, string, filename, or call-site confirmation.
|
|
74
|
+
- Use ls(path) to inspect directory shape when structure matters.
|
|
75
|
+
- Treat code_search as a semantic map, not proof; verify important results with grep/read before editing or concluding.
|
|
73
76
|
- Prefer dedicated read/search/edit tools over shell commands for file inspection and modification.
|
|
74
77
|
- Use command_exec for one-shot commands. Use terminal_* only for interactive programs, long-running processes, or when preserving terminal state matters.
|
|
75
78
|
- Keep the working directory stable; use paths instead of cd unless the user asks otherwise.
|
|
@@ -103,6 +106,11 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
103
106
|
- Use memory_save() to create memories or update whole metadata fields on an existing memory. 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'.
|
|
104
107
|
- 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.
|
|
105
108
|
- Distinguish "migrating a Skill to memory" from "learning a Skill": migration preserves the complete Skill folder under memory_root/skills/ and creates a memory entry as its index; that memory should describe what the Skill is for and reference the copied Skill folder path so future recall knows how to use it. 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.
|
|
109
|
+
- Do not proactively modify the agent profile. Update `agent.md` only when the user explicitly asks to change persistent agent behavior.
|
|
110
|
+
- Proactively maintain the user profile when stable, reusable user preferences, working style, goals, or identity signals appear in conversation.
|
|
111
|
+
- For user profile updates, distinguish explicit facts from inferred preferences. Write explicit facts directly; write inferred items as preferences or tendencies, and avoid overstating confidence.
|
|
112
|
+
- Do not store transient task details, sensitive information, or one-off opinions in the user profile. Use memory for project-specific reusable knowledge and current conversation for short-lived context.
|
|
113
|
+
- When a user profile update is non-obvious or potentially sensitive, ask before writing; otherwise update it as part of normal task completion and mention it briefly in the final summary.
|
|
106
114
|
- Unlike recall blocks, 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.
|
|
107
115
|
- 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.
|
|
108
116
|
</memory_system>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { HistoryStore } from "./store.mjs";
|
|
3
|
+
|
|
4
|
+
export function createRunnerHistoryStore({ stateRoot, cwd } = {}) {
|
|
5
|
+
if (!stateRoot) return null;
|
|
6
|
+
return new HistoryStore({ root: join(stateRoot, "history"), cwd });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function appendRunnerTurnHistory({ store, turn, sessionStats, modelId, provider }) {
|
|
10
|
+
return store?.appendTurn({ turn, sessionStats, runtime: { modelId, provider } });
|
|
11
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { searchMarkdownRoot } from "../memory/search.mjs";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_HISTORY_LIMIT = 20;
|
|
7
|
+
const MAX_HISTORY_LIMIT = 50;
|
|
8
|
+
|
|
9
|
+
export class HistoryStore {
|
|
10
|
+
constructor({ root, cwd, now = () => new Date() } = {}) {
|
|
11
|
+
if (!root) throw new Error("history root is required");
|
|
12
|
+
this.root = root;
|
|
13
|
+
this.cwd = cwd;
|
|
14
|
+
this.now = now;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
appendTurn({ turn, sessionStats = {}, runtime = {} } = {}) {
|
|
18
|
+
if (!turn) return null;
|
|
19
|
+
const sessionId = sanitizeId(sessionStats.sessionId ?? sessionStats.sessionFile ?? "session");
|
|
20
|
+
const filePath = join(this.#projectDir(), `${sessionId}.md`);
|
|
21
|
+
mkdirSync(this.#projectDir(), { recursive: true });
|
|
22
|
+
if (!existsSync(filePath)) writeFileSync(filePath, this.#fileHeader({ sessionId, sessionStats }), "utf8");
|
|
23
|
+
appendFileSync(filePath, this.#formatTurn({ turn, sessionId, sessionStats, runtime }), "utf8");
|
|
24
|
+
return filePath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
searchRipgrep(query, { allProjects = false, sessionId = null, limit = DEFAULT_HISTORY_LIMIT, context = 2, syntax = "regex", case: caseMode = "smart" } = {}) {
|
|
28
|
+
const root = allProjects ? this.root : this.#projectDir();
|
|
29
|
+
const glob = sessionId ? [`**/${sanitizeId(sessionId)}.md`] : [];
|
|
30
|
+
return searchMarkdownRoot({
|
|
31
|
+
root,
|
|
32
|
+
query,
|
|
33
|
+
limit: clampInt(limit, 1, MAX_HISTORY_LIMIT, DEFAULT_HISTORY_LIMIT),
|
|
34
|
+
context,
|
|
35
|
+
syntax,
|
|
36
|
+
caseMode,
|
|
37
|
+
glob,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#projectDir() {
|
|
42
|
+
return join(this.root, "projects", cwdHash(this.cwd));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#fileHeader({ sessionId, sessionStats }) {
|
|
46
|
+
return [
|
|
47
|
+
`# March History · ${sessionId}`,
|
|
48
|
+
"",
|
|
49
|
+
`- cwd: ${this.cwd}`,
|
|
50
|
+
`- project: ${basename(this.cwd) || this.cwd}`,
|
|
51
|
+
sessionStats.sessionFile ? `- session_file: ${sessionStats.sessionFile}` : null,
|
|
52
|
+
"",
|
|
53
|
+
].filter(Boolean).join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#formatTurn({ turn, sessionId, sessionStats, runtime }) {
|
|
57
|
+
const now = this.now().toISOString();
|
|
58
|
+
return [
|
|
59
|
+
`\n## Turn ${turn.index ?? "?"} · ${now}`,
|
|
60
|
+
"",
|
|
61
|
+
"metadata:",
|
|
62
|
+
`- session: ${sessionId}`,
|
|
63
|
+
`- cwd: ${this.cwd}`,
|
|
64
|
+
runtime.provider ? `- provider: ${runtime.provider}` : null,
|
|
65
|
+
runtime.modelId ? `- model: ${runtime.modelId}` : null,
|
|
66
|
+
sessionStats.sessionName ? `- session_name: ${sessionStats.sessionName}` : null,
|
|
67
|
+
"",
|
|
68
|
+
"### User",
|
|
69
|
+
safeBlock(turn.userMessage),
|
|
70
|
+
formatRecallSection("User memory recall", turn.userRecallHints),
|
|
71
|
+
"### Assistant",
|
|
72
|
+
safeBlock(turn.assistantMessage),
|
|
73
|
+
turn.thinking ? ["### Thinking", safeBlock(turn.thinking)].join("\n") : null,
|
|
74
|
+
"### Tool calls",
|
|
75
|
+
formatToolCalls(turn.toolCalls),
|
|
76
|
+
formatRecallSection("Assistant memory recall", turn.assistantRecallHints),
|
|
77
|
+
"",
|
|
78
|
+
].filter(Boolean).join("\n");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatToolCalls(calls = []) {
|
|
83
|
+
if (!Array.isArray(calls) || calls.length === 0) return "(none)";
|
|
84
|
+
return calls.map((call) => {
|
|
85
|
+
const lines = [`- ${call.name ?? "unknown"} status=${call.status ?? "unknown"}`];
|
|
86
|
+
const args = JSON.stringify(call.args ?? null);
|
|
87
|
+
if (args && args !== "null") lines.push(` args: ${args}`);
|
|
88
|
+
if (call.status === "failed") {
|
|
89
|
+
lines.push(" error:");
|
|
90
|
+
if (call.error?.message) lines.push(` message: ${escapeSingleLine(call.error.message)}`);
|
|
91
|
+
if (call.error?.details) lines.push(` details: ${JSON.stringify(call.error.details)}`);
|
|
92
|
+
if (call.error?.excerpt) lines.push(" excerpt:", fence(call.error.excerpt));
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}).join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatRecallSection(title, hints = []) {
|
|
99
|
+
if (!Array.isArray(hints) || hints.length === 0) return null;
|
|
100
|
+
return [`### ${title}`, ...hints.map((hint) => `- ${hint.id ?? "unknown"} | ${hint.name ?? hint.title ?? ""} | ${hint.description ?? ""}`)].join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function safeBlock(value) {
|
|
104
|
+
const text = String(value ?? "").trim();
|
|
105
|
+
return text || "(empty)";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function fence(value) {
|
|
109
|
+
return " ```text\n" + String(value ?? "").trimEnd() + "\n ```";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sanitizeId(value) {
|
|
113
|
+
const raw = String(value ?? "session").trim() || "session";
|
|
114
|
+
return raw.replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, 120) || "session";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function cwdHash(cwd) {
|
|
118
|
+
return createHash("sha1").update(String(cwd ?? "")).digest("hex").slice(0, 16);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function escapeSingleLine(value) {
|
|
122
|
+
return JSON.stringify(String(value ?? "").replace(/\s+/g, " "));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function clampInt(value, min, max, fallback) {
|
|
126
|
+
const number = Number(value);
|
|
127
|
+
if (!Number.isFinite(number)) return fallback;
|
|
128
|
+
return Math.min(max, Math.max(min, Math.floor(number)));
|
|
129
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { toolText } from "../agent/tool-result.mjs";
|
|
4
|
+
|
|
5
|
+
export function createHistorySearchTool({ store } = {}) {
|
|
6
|
+
if (!store) return null;
|
|
7
|
+
return defineTool({
|
|
8
|
+
name: "history_search",
|
|
9
|
+
label: "History Search",
|
|
10
|
+
description: "Search archived March turn history with ripgrep. Use it when you need details from previous sessions or earlier turns. History stores user/assistant text, visible thinking, tool call metadata, memory recall hints, and failed tool error excerpts; successful tool results are not stored.",
|
|
11
|
+
parameters: Type.Object({
|
|
12
|
+
query: Type.String({ description: "Ripgrep query/pattern to search in archived turn history" }),
|
|
13
|
+
allProjects: Type.Optional(Type.Boolean({ description: "Search all project histories instead of the current cwd history. Default false." })),
|
|
14
|
+
sessionId: Type.Optional(Type.String({ description: "Limit search to a specific session id when known" })),
|
|
15
|
+
syntax: Type.Optional(Type.Union([Type.Literal("regex"), Type.Literal("literal")], { description: "Pattern syntax. Default: regex" })),
|
|
16
|
+
case: Type.Optional(Type.Union([Type.Literal("smart"), Type.Literal("sensitive"), Type.Literal("insensitive")], { description: "Case matching mode. Default: smart" })),
|
|
17
|
+
context: Type.Optional(Type.Number({ description: "Context lines around each match. Default: 2" })),
|
|
18
|
+
limit: Type.Optional(Type.Number({ description: "Maximum matches to return. Default: 20, max: 50" })),
|
|
19
|
+
}),
|
|
20
|
+
execute: async (_toolCallId, params) => {
|
|
21
|
+
try {
|
|
22
|
+
const results = store.searchRipgrep(params.query, params);
|
|
23
|
+
if (results.length === 0) return toolText(`history_search found no matches for ${JSON.stringify(params.query)}.`);
|
|
24
|
+
return toolText(formatHistorySearchResults(results), { results });
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return toolText(`Error: ${err.message}`, { error: true });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatHistorySearchResults(results) {
|
|
33
|
+
const lines = [`history_search found ${results.length} match${results.length === 1 ? "" : "es"}.`];
|
|
34
|
+
results.forEach((result, index) => {
|
|
35
|
+
lines.push("", `[${index + 1}] ${result.path}:${result.line}`);
|
|
36
|
+
if (result.excerpt?.text) lines.push(result.excerpt.text);
|
|
37
|
+
});
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
package/src/lsp/client.mjs
CHANGED
|
@@ -73,13 +73,14 @@ export function languageIdForPath(path) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export class LspClient {
|
|
76
|
-
constructor({ serverId, command, args = [], cwd, initialization = {}, store }) {
|
|
76
|
+
constructor({ serverId, command, args = [], cwd, initialization = {}, store, onStatusChange = null }) {
|
|
77
77
|
this.serverId = serverId;
|
|
78
78
|
this.command = command;
|
|
79
79
|
this.args = args;
|
|
80
80
|
this.cwd = cwd;
|
|
81
81
|
this.initialization = initialization;
|
|
82
82
|
this.store = store;
|
|
83
|
+
this.onStatusChange = onStatusChange;
|
|
83
84
|
this.status = "starting";
|
|
84
85
|
this.process = null;
|
|
85
86
|
this.buffer = Buffer.alloc(0);
|
|
@@ -98,7 +99,7 @@ export class LspClient {
|
|
|
98
99
|
});
|
|
99
100
|
this.process.stdout.on("data", (chunk) => this.#onData(chunk));
|
|
100
101
|
this.process.on("exit", () => {
|
|
101
|
-
this
|
|
102
|
+
this.#setStatus("failed");
|
|
102
103
|
for (const pending of this.pending.values()) pending.reject(new Error("LSP exited"));
|
|
103
104
|
this.pending.clear();
|
|
104
105
|
});
|
|
@@ -124,7 +125,7 @@ export class LspClient {
|
|
|
124
125
|
}), INITIALIZE_TIMEOUT_MS);
|
|
125
126
|
this.syncKind = getSyncKind(initialized?.capabilities);
|
|
126
127
|
this.#notify("initialized", {});
|
|
127
|
-
this
|
|
128
|
+
this.#setStatus("ready");
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
touchFile(path) {
|
|
@@ -132,7 +133,7 @@ export class LspClient {
|
|
|
132
133
|
const text = readFileSync(path, "utf8");
|
|
133
134
|
const uri = pathToFileURL(path).href;
|
|
134
135
|
const existing = this.documents.get(path);
|
|
135
|
-
this
|
|
136
|
+
this.#setStatus("busy");
|
|
136
137
|
if (existing) {
|
|
137
138
|
const version = existing.version + 1;
|
|
138
139
|
this.documents.set(path, { version, text });
|
|
@@ -199,7 +200,7 @@ export class LspClient {
|
|
|
199
200
|
uri: message.params?.uri,
|
|
200
201
|
diagnostics: message.params?.diagnostics ?? [],
|
|
201
202
|
});
|
|
202
|
-
this
|
|
203
|
+
this.#setStatus("idle");
|
|
203
204
|
return;
|
|
204
205
|
}
|
|
205
206
|
|
|
@@ -208,6 +209,12 @@ export class LspClient {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
#setStatus(status) {
|
|
213
|
+
if (this.status === status) return;
|
|
214
|
+
this.status = status;
|
|
215
|
+
this.onStatusChange?.({ id: this.serverId, root: this.cwd, status });
|
|
216
|
+
}
|
|
217
|
+
|
|
211
218
|
#requestResult(method) {
|
|
212
219
|
if (method === "workspace/configuration") return [];
|
|
213
220
|
if (method === "workspace/workspaceFolders") return [{ name: "workspace", uri: pathToFileURL(this.cwd).href }];
|
package/src/lsp/service.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import { LspDiagnosticStore } from "./diagnostic-store.mjs";
|
|
|
3
3
|
import { resolveLspServerStatus } from "./servers.mjs";
|
|
4
4
|
|
|
5
5
|
export class LspService {
|
|
6
|
-
constructor({ cwd, onEvent = null }) {
|
|
6
|
+
constructor({ cwd, onEvent = null, onStatusChange = null }) {
|
|
7
7
|
this.cwd = cwd;
|
|
8
8
|
this.onEvent = onEvent;
|
|
9
|
+
this.onStatusChange = onStatusChange;
|
|
9
10
|
this.store = new LspDiagnosticStore();
|
|
10
11
|
this.clients = new Map();
|
|
11
12
|
this.spawning = new Map();
|
|
@@ -19,6 +20,7 @@ export class LspService {
|
|
|
19
20
|
if (result.status === "unavailable") {
|
|
20
21
|
this.unavailable.set(result.id, result);
|
|
21
22
|
this.#emitOnce(`unavailable:${result.id}:${result.reason}`, result);
|
|
23
|
+
this.#emitStatusChange(result);
|
|
22
24
|
return result;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -34,7 +36,9 @@ export class LspService {
|
|
|
34
36
|
return { status: "starting", id: server.id, root: server.root };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
const startingEvent = { status: "starting", id: server.id, root: server.root, managed: server.managed };
|
|
40
|
+
this.#emitOnce(`starting:${key}`, startingEvent);
|
|
41
|
+
this.#emitStatusChange(startingEvent);
|
|
38
42
|
const task = this.#startClient(server, key).then((client) => {
|
|
39
43
|
client?.touchFile(path);
|
|
40
44
|
return client;
|
|
@@ -76,18 +80,22 @@ export class LspService {
|
|
|
76
80
|
cwd: server.root,
|
|
77
81
|
initialization: server.initialization,
|
|
78
82
|
store: this.store,
|
|
83
|
+
onStatusChange: (event) => this.#emitStatusChange(event),
|
|
79
84
|
});
|
|
80
85
|
try {
|
|
81
86
|
await client.start();
|
|
82
87
|
this.clients.set(key, client);
|
|
83
88
|
this.unavailable.delete(server.id);
|
|
84
|
-
|
|
89
|
+
const attachedEvent = { status: "attached", id: server.id, root: server.root, managed: server.managed };
|
|
90
|
+
this.#emitOnce(`attached:${key}`, attachedEvent);
|
|
91
|
+
this.#emitStatusChange(attachedEvent);
|
|
85
92
|
return client;
|
|
86
93
|
} catch (err) {
|
|
87
94
|
client.status = "failed";
|
|
88
95
|
const event = { status: "failed", id: server.id, root: server.root, reason: err.message };
|
|
89
96
|
this.unavailable.set(server.id, event);
|
|
90
97
|
this.#emitOnce(`failed:${key}:${err.message}`, event);
|
|
98
|
+
this.#emitStatusChange(event);
|
|
91
99
|
return null;
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -97,6 +105,10 @@ export class LspService {
|
|
|
97
105
|
this.announced.add(key);
|
|
98
106
|
this.onEvent?.(event);
|
|
99
107
|
}
|
|
108
|
+
|
|
109
|
+
#emitStatusChange(event) {
|
|
110
|
+
this.onStatusChange?.(event);
|
|
111
|
+
}
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
function summarizeStatus(servers) {
|
package/src/main.mjs
CHANGED
|
@@ -26,13 +26,12 @@ export async function run(argv) {
|
|
|
26
26
|
|
|
27
27
|
const config = loadConfig(cwd);
|
|
28
28
|
const stateRoot = join(homedir(), ".march");
|
|
29
|
-
const useRuntimeProcess = process.env.MARCH_RUNTIME_PROCESS !== "0";
|
|
30
29
|
installNetworkEnvironment(config.network);
|
|
31
30
|
|
|
32
31
|
const earlyCommand = await runEarlyCliCommand(args, { config, cwd, stateRoot });
|
|
33
32
|
if (earlyCommand.handled) return earlyCommand.code;
|
|
34
33
|
|
|
35
|
-
const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot
|
|
34
|
+
const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot });
|
|
36
35
|
if (!app.ok) return app.code;
|
|
37
36
|
|
|
38
37
|
const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, {
|