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
|
@@ -10,6 +10,7 @@ export async function createRunnerProcessClient({
|
|
|
10
10
|
runnerOptions,
|
|
11
11
|
ui,
|
|
12
12
|
onModelPayload = null,
|
|
13
|
+
onLspStatusChange = null,
|
|
13
14
|
entry = fileURLToPath(DEFAULT_ENTRY),
|
|
14
15
|
forkImpl = fork,
|
|
15
16
|
timeoutMs = 0,
|
|
@@ -46,6 +47,10 @@ export async function createRunnerProcessClient({
|
|
|
46
47
|
target: {
|
|
47
48
|
...createRuntimeUiEventTarget(ui),
|
|
48
49
|
modelPayload: (event) => onModelPayload?.(event),
|
|
50
|
+
lspStatusChange: async (event) => {
|
|
51
|
+
await active?.runner?.refreshState?.();
|
|
52
|
+
onLspStatusChange?.(event);
|
|
53
|
+
},
|
|
49
54
|
},
|
|
50
55
|
timeoutMs,
|
|
51
56
|
});
|
|
@@ -93,6 +93,7 @@ export async function createIsolatedRunner(options = {}, deps = {}) {
|
|
|
93
93
|
}),
|
|
94
94
|
logger,
|
|
95
95
|
onModelPayload: (event) => d.peer.notify("modelPayload", pickModelPayloadEvent(event)),
|
|
96
|
+
onLspStatusChange: (event) => d.peer.notify("lspStatusChange", pickLspStatusEvent(event)),
|
|
96
97
|
});
|
|
97
98
|
|
|
98
99
|
const originalDispose = runner.dispose;
|
|
@@ -109,3 +110,7 @@ export async function createIsolatedRunner(options = {}, deps = {}) {
|
|
|
109
110
|
function pickModelPayloadEvent({ estimatedTokens, provider, model, kind, turnId } = {}) {
|
|
110
111
|
return { estimatedTokens, provider, model, kind, turnId };
|
|
111
112
|
}
|
|
113
|
+
|
|
114
|
+
function pickLspStatusEvent({ id, root, status, reason, managed } = {}) {
|
|
115
|
+
return { id, root, status, reason, managed };
|
|
116
|
+
}
|
|
@@ -20,6 +20,7 @@ export async function createRunnerRuntimeHost({
|
|
|
20
20
|
ui,
|
|
21
21
|
projectMarchDir = null,
|
|
22
22
|
memoryTools = [],
|
|
23
|
+
historyStore = null,
|
|
23
24
|
shellRuntime = null,
|
|
24
25
|
lspService = null,
|
|
25
26
|
mcpTools = [],
|
|
@@ -56,6 +57,7 @@ export async function createRunnerRuntimeHost({
|
|
|
56
57
|
engine,
|
|
57
58
|
ui,
|
|
58
59
|
memoryTools,
|
|
60
|
+
historyStore,
|
|
59
61
|
shellRuntime,
|
|
60
62
|
lspService,
|
|
61
63
|
mcpTools,
|
|
@@ -22,6 +22,7 @@ export function createRunnerStateSnapshot(runner) {
|
|
|
22
22
|
availableThinkingLevels: runner.getAvailableThinkingLevels?.() ?? [],
|
|
23
23
|
canSwitchPiSession: runner.canSwitchPiSession?.() ?? false,
|
|
24
24
|
sessionStats: runner.getSessionStats?.() ?? null,
|
|
25
|
+
providerQuota: runner.getCachedProviderQuotaSnapshot?.() ?? null,
|
|
25
26
|
lspStatus: runner.getLspStatus?.() ?? null,
|
|
26
27
|
extensionDiagnostics: runner.getExtensionDiagnostics?.() ?? [],
|
|
27
28
|
extensionLifecycleState: runner.getExtensionLifecycleState?.() ?? null,
|
|
@@ -51,6 +51,7 @@ export function createRuntimeUiClient(eventBus) {
|
|
|
51
51
|
status: (text) => eventBus.emit({ type: "status", text }),
|
|
52
52
|
debugLines: (lines) => eventBus.emit({ type: "debug_lines", lines }),
|
|
53
53
|
recall: ({ source, hints }) => eventBus.emit({ type: "recall", source, hints }),
|
|
54
|
+
providerQuotaSnapshot: (snapshot) => eventBus.emit({ type: "provider_quota_snapshot", snapshot }),
|
|
54
55
|
editDiff: (path, diffLines) => eventBus.emit({ type: "edit_diff", path, diffLines }),
|
|
55
56
|
requestPermission: (request) => eventBus.request({ type: "permission_request", ...request }),
|
|
56
57
|
};
|
|
@@ -72,6 +73,7 @@ export function dispatchRuntimeUiEvent(ui, event) {
|
|
|
72
73
|
case "status": return ui.status?.(event.text);
|
|
73
74
|
case "debug_lines": return writeDebugLines(ui, event.lines);
|
|
74
75
|
case "recall": return ui.recall?.({ source: event.source, hints: event.hints });
|
|
76
|
+
case "provider_quota_snapshot": return ui.providerQuotaSnapshot?.(event.snapshot);
|
|
75
77
|
case "edit_diff": return ui.editDiff?.(event.path, event.diffLines);
|
|
76
78
|
case "permission_request": return ui.requestPermission?.({ toolName: event.toolName, params: event.params, category: event.category });
|
|
77
79
|
default: return undefined;
|
|
@@ -10,6 +10,7 @@ export function resolveRunnerSessionOptions({
|
|
|
10
10
|
engine,
|
|
11
11
|
ui,
|
|
12
12
|
memoryTools = [],
|
|
13
|
+
historyStore = null,
|
|
13
14
|
shellRuntime = null,
|
|
14
15
|
lspService = null,
|
|
15
16
|
mcpTools = [],
|
|
@@ -31,7 +32,7 @@ export function resolveRunnerSessionOptions({
|
|
|
31
32
|
?? (provider && modelId ? getModel(provider, modelId) : null);
|
|
32
33
|
if (!model) throw new Error(`Model not found: ${provider}/${modelId}`);
|
|
33
34
|
|
|
34
|
-
const customTools = createMarchCustomTools({ cwd, engine, ui, memoryTools, shellRuntime, lspService, mcpTools, webTools, lifecycle, permissionController, authStorage, projectMarchDir, stateRoot, getCurrentModel: () => getCurrentModel?.() ?? model });
|
|
35
|
+
const customTools = createMarchCustomTools({ cwd, engine, ui, memoryTools, historyStore, shellRuntime, lspService, mcpTools, webTools, lifecycle, permissionController, authStorage, projectMarchDir, stateRoot, getCurrentModel: () => getCurrentModel?.() ?? model });
|
|
35
36
|
const customToolNames = customTools.map((tool) => tool.name);
|
|
36
37
|
const tools = [
|
|
37
38
|
...customToolNames.filter((name) => name === "read"),
|
package/src/agent/tools.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCommandExecTool } from "./command-exec-tool.mjs";
|
|
2
|
+
import { createCodeSearchTool } from "./code-search/tool.mjs";
|
|
2
3
|
import { createContextStatsTool } from "./context-stats-tool.mjs";
|
|
3
4
|
import { createEditFileTool } from "./file-edit-tool.mjs";
|
|
4
5
|
import { createReadFileTool } from "./file-tools/read-file-tool.mjs";
|
|
@@ -12,10 +13,13 @@ import { initImageGen } from "../image-gen/index.mjs";
|
|
|
12
13
|
import { createSuperGrokTool } from "../supergrok/tool.mjs";
|
|
13
14
|
import { createBrowserTools } from "../browser/tools/index.mjs";
|
|
14
15
|
import { createRuntimeRestartTool } from "./lifecycle/runtime-restart-tool.mjs";
|
|
16
|
+
import { createHistorySearchTool } from "../history/tool.mjs";
|
|
15
17
|
|
|
16
|
-
export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shellRuntime = null, lspService = null, mcpTools = [], webTools = [], lifecycle = null, permissionController = null, authStorage = null, projectMarchDir = null, stateRoot = null, getCurrentModel = null }) {
|
|
18
|
+
export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], historyStore = null, shellRuntime = null, lspService = null, mcpTools = [], webTools = [], lifecycle = null, permissionController = null, authStorage = null, projectMarchDir = null, stateRoot = null, getCurrentModel = null }) {
|
|
17
19
|
const commandExecTool = createCommandExecTool({ cwd });
|
|
20
|
+
const codeSearchTool = createCodeSearchTool({ engine, stateRoot });
|
|
18
21
|
const contextStatsTool = createContextStatsTool({ engine });
|
|
22
|
+
const historySearchTool = createHistorySearchTool({ store: historyStore });
|
|
19
23
|
const editFileTool = createEditFileTool({ engine, ui, lspService });
|
|
20
24
|
const readFileTool = createReadFileTool({ engine });
|
|
21
25
|
const readImageTool = createReadImageTool({ engine, getCurrentModel });
|
|
@@ -30,9 +34,11 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shel
|
|
|
30
34
|
screenTool,
|
|
31
35
|
listWindowsTool,
|
|
32
36
|
contextStatsTool,
|
|
37
|
+
codeSearchTool,
|
|
33
38
|
commandExecTool,
|
|
34
39
|
editFileTool,
|
|
35
40
|
...createShellTools(shellRuntime),
|
|
41
|
+
...(historySearchTool ? [historySearchTool] : []),
|
|
36
42
|
...memoryTools,
|
|
37
43
|
...mcpTools,
|
|
38
44
|
...webTools,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { formatToolStartLine, formatToolSuccessSummary } from "../tool-summary.mjs";
|
|
2
2
|
|
|
3
|
+
const TOOL_ERROR_EXCERPT_LIMIT = 4000;
|
|
4
|
+
|
|
3
5
|
export function createTurnEventState() {
|
|
4
6
|
return {
|
|
5
7
|
draft: "",
|
|
@@ -9,6 +11,7 @@ export function createTurnEventState() {
|
|
|
9
11
|
assistantReplyOpen: false,
|
|
10
12
|
assistantContextParts: [],
|
|
11
13
|
activeToolContextPart: null,
|
|
14
|
+
toolCalls: [],
|
|
12
15
|
};
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -19,10 +22,12 @@ export function handleRunnerSessionEvent(event, { ui, engine, state }) {
|
|
|
19
22
|
if (event.type === "tool_execution_start") {
|
|
20
23
|
closeAssistantReply({ ui, state });
|
|
21
24
|
appendToolStartContext(state, event.toolName, event.args);
|
|
25
|
+
recordToolStart(state, event.toolName, event.args);
|
|
22
26
|
ui.toolStart(event.toolName, event.args);
|
|
23
27
|
}
|
|
24
28
|
if (event.type === "tool_execution_end") {
|
|
25
29
|
updateToolEndContext(state, event.toolName, event.isError, event.result);
|
|
30
|
+
recordToolEnd(state, event.toolName, event.isError, event.result);
|
|
26
31
|
ui.toolEnd(event.toolName, event.isError, event.result);
|
|
27
32
|
}
|
|
28
33
|
if (event.type === "auto_retry_start") {
|
|
@@ -109,3 +114,39 @@ function updateToolEndContext(state, name, isError, result) {
|
|
|
109
114
|
if (summary && summary !== "done") part.text = `${part.text.trimEnd()} (${summary})\n`;
|
|
110
115
|
state.activeToolContextPart = null;
|
|
111
116
|
}
|
|
117
|
+
|
|
118
|
+
function recordToolStart(state, name, args) {
|
|
119
|
+
state.toolCalls.push({ name, args: cloneJson(args), status: "running" });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function recordToolEnd(state, name, isError, result) {
|
|
123
|
+
const call = [...state.toolCalls].reverse().find((item) => item.name === name && item.status === "running");
|
|
124
|
+
if (!call) return;
|
|
125
|
+
call.status = isError ? "failed" : "success";
|
|
126
|
+
if (!isError) return;
|
|
127
|
+
const output = extractToolOutput(result);
|
|
128
|
+
call.error = {
|
|
129
|
+
message: output.split(/\r?\n/).find(Boolean) ?? "Tool call failed",
|
|
130
|
+
details: cloneJson(result?.details ?? null),
|
|
131
|
+
excerpt: truncate(output, TOOL_ERROR_EXCERPT_LIMIT),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function extractToolOutput(result) {
|
|
136
|
+
const content = result?.content;
|
|
137
|
+
if (!Array.isArray(content)) return typeof result === "string" ? result : "";
|
|
138
|
+
return content.filter((item) => item?.type === "text").map((item) => item.text ?? "").join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cloneJson(value) {
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function truncate(text, limit) {
|
|
150
|
+
const value = String(text ?? "");
|
|
151
|
+
return value.length > limit ? `${value.slice(0, limit)}\n[truncated]` : value;
|
|
152
|
+
}
|
|
@@ -17,6 +17,7 @@ export async function runRunnerTurn({
|
|
|
17
17
|
syncCurrentPiSidecar,
|
|
18
18
|
autoNameSession,
|
|
19
19
|
contextMode = "rebuild",
|
|
20
|
+
recordHistory = null,
|
|
20
21
|
}) {
|
|
21
22
|
const {
|
|
22
23
|
userRecallHints = [],
|
|
@@ -80,6 +81,7 @@ export async function runRunnerTurn({
|
|
|
80
81
|
midTurnRecallHints,
|
|
81
82
|
syncCurrentPiSidecar,
|
|
82
83
|
autoNameSession,
|
|
84
|
+
recordHistory,
|
|
83
85
|
});
|
|
84
86
|
return { draft: turnState.draft };
|
|
85
87
|
} finally {
|
|
@@ -127,19 +129,20 @@ function logSessionEvent(logger, event) {
|
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
function finalizeTurn({ prompt, userMessage, userRecallHints, currentProject, memoryStore, engine, ui, turnState, midTurnRecallHints, syncCurrentPiSidecar, autoNameSession }) {
|
|
132
|
+
function finalizeTurn({ prompt, userMessage, userRecallHints, currentProject, memoryStore, engine, ui, turnState, midTurnRecallHints, syncCurrentPiSidecar, autoNameSession, recordHistory }) {
|
|
131
133
|
closeAssistantReply({ ui, state: turnState });
|
|
132
134
|
const assistantRecallHints = flushAssistantRecall({ memoryStore, engine, turnState, currentProject });
|
|
133
135
|
engine.setPendingAssistantRecallHints?.(assistantRecallHints);
|
|
134
136
|
const recordedAssistantRecallHints = uniqueHints([...midTurnRecallHints, ...assistantRecallHints]);
|
|
135
137
|
|
|
136
|
-
engine.recordTurn({
|
|
138
|
+
const turn = engine.recordTurn({
|
|
137
139
|
userMessage: userMessage ?? prompt.slice(0, 300),
|
|
138
140
|
assistantMessage: turnState.draft,
|
|
139
141
|
assistantContext: compactAssistantContext(turnState),
|
|
140
142
|
userRecallHints,
|
|
141
143
|
assistantRecallHints: recordedAssistantRecallHints,
|
|
142
144
|
});
|
|
145
|
+
recordHistory?.({ ...turn, thinking: assistantThinkingText(turnState), toolCalls: turnState.toolCalls });
|
|
143
146
|
|
|
144
147
|
autoNameSession?.();
|
|
145
148
|
syncCurrentPiSidecar();
|
|
@@ -97,13 +97,16 @@ export const SLASH_COMMANDS = [
|
|
|
97
97
|
exactCommand({
|
|
98
98
|
name: "status",
|
|
99
99
|
description: "Show runtime status",
|
|
100
|
-
run: async ({ ui, runner, sessionState, sessionSource }) =>
|
|
101
|
-
runner
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
run: async ({ ui, runner, sessionState, sessionSource }) => {
|
|
101
|
+
await runner.getProviderQuotaSnapshot?.({ emit: true }).catch(() => null);
|
|
102
|
+
return writeLines(ui, statusCommand({
|
|
103
|
+
runner,
|
|
104
|
+
sessionState,
|
|
105
|
+
sessionSource,
|
|
106
|
+
extensionDiagnostics: runner.getExtensionDiagnostics?.() ?? [],
|
|
107
|
+
lifecycleState: runner.getExtensionLifecycleState?.() ?? null,
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
107
110
|
}),
|
|
108
111
|
exactCommand({
|
|
109
112
|
name: "notify",
|
|
@@ -10,15 +10,20 @@ export function statusCommand({
|
|
|
10
10
|
lifecycleState = null,
|
|
11
11
|
gitBranch = getGitBranch(runner.engine.cwd),
|
|
12
12
|
}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
const providerQuota = runner.getCachedProviderQuotaSnapshot?.() ?? null;
|
|
14
|
+
const lines = [
|
|
15
|
+
...formatStatusLines({
|
|
16
|
+
engine: runner.engine,
|
|
17
|
+
sessionState,
|
|
18
|
+
sessionStats: runner.getSessionStats?.() ?? null,
|
|
19
|
+
sessionSource,
|
|
20
|
+
extensionDiagnostics,
|
|
21
|
+
lifecycleState,
|
|
22
|
+
gitBranch,
|
|
23
|
+
}),
|
|
24
|
+
...formatProviderQuotaLines(providerQuota),
|
|
25
|
+
];
|
|
26
|
+
return lines.length > 0 ? lines : ["No provider quota available."];
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export function statusBarLine({
|
|
@@ -37,35 +42,48 @@ export function statusBarLine({
|
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
export function formatStatusLine({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
export function formatStatusLine(options) {
|
|
46
|
+
return formatStatusLines(options).join(" ");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatStatusLines({
|
|
45
50
|
extensionDiagnostics = [],
|
|
46
51
|
lifecycleState = null,
|
|
47
|
-
gitBranch = null,
|
|
48
52
|
}) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
53
|
+
const diagnosticSummary = formatExtensionDiagnosticSummary(extensionDiagnostics, lifecycleState);
|
|
54
|
+
return shouldShowDiagnostics(diagnosticSummary) ? [`Extensions: ${diagnosticSummary}`] : [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatProviderQuotaLines(providerQuota, { width = 20 } = {}) {
|
|
58
|
+
const windows = providerQuota?.limits?.flatMap((limit) => limit.windows ?? []) ?? [];
|
|
59
|
+
return windows.slice(0, 2).map((window) => formatProviderQuotaLine(window, { width }));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatProviderQuotaLine(window, { width = 20 } = {}) {
|
|
63
|
+
const label = window.label === "weekly" ? "Weekly limit:" : `${window.label} limit:`;
|
|
64
|
+
const left = formatPercent(window.remainingPercent);
|
|
65
|
+
return `${label.padEnd(28)} ${formatQuotaBar(window.remainingPercent, width)} ${left}% left (${formatQuotaReset(window.resetsAt)})`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatQuotaBar(percent, width = 20) {
|
|
69
|
+
const value = Math.max(0, Math.min(100, Number(percent) || 0));
|
|
70
|
+
const filled = Math.round((value / 100) * width);
|
|
71
|
+
return `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatQuotaReset(resetsAt) {
|
|
75
|
+
if (!resetsAt) return "reset unknown";
|
|
76
|
+
const date = new Date(resetsAt);
|
|
77
|
+
if (Number.isNaN(date.getTime())) return "reset unknown";
|
|
78
|
+
return `resets ${formatResetDate(date)}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatResetDate(date) {
|
|
82
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
83
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
84
|
+
const day = date.getDate();
|
|
85
|
+
const month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()];
|
|
86
|
+
return `${hours}:${minutes} on ${day} ${month}`;
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
export function formatStatusBarLine({
|
|
@@ -154,6 +172,10 @@ export function formatCompactTokenCount(tokens) {
|
|
|
154
172
|
return `${formatOneDecimal(value / 1000000)}M`;
|
|
155
173
|
}
|
|
156
174
|
|
|
175
|
+
function shouldShowDiagnostics(summary) {
|
|
176
|
+
return summary !== "ok" && summary.split(",").some((part) => !part.endsWith("info"));
|
|
177
|
+
}
|
|
178
|
+
|
|
157
179
|
export function formatExtensionDiagnosticSummary(extensionDiagnostics = [], lifecycleState = null) {
|
|
158
180
|
const diagnostics = [...extensionDiagnostics, ...(lifecycleState?.diagnostics ?? [])];
|
|
159
181
|
if (diagnostics.length === 0) return "ok";
|
|
@@ -194,3 +216,7 @@ function formatOneDecimal(value) {
|
|
|
194
216
|
const rounded = Math.round(value * 10) / 10;
|
|
195
217
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
196
218
|
}
|
|
219
|
+
|
|
220
|
+
function formatPercent(value) {
|
|
221
|
+
return Math.round(Math.max(0, Math.min(100, Number(value) || 0)));
|
|
222
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
|
|
4
4
|
const HISTORY_VERSION = 1;
|
|
5
5
|
const MAX_HISTORY_ITEMS = 100;
|
|
6
|
+
const LOCK_WAIT_MS = 2000;
|
|
7
|
+
const LOCK_STALE_MS = 5000;
|
|
6
8
|
|
|
7
9
|
export function createInputHistoryStore({ path, maxItems = MAX_HISTORY_ITEMS } = {}) {
|
|
8
10
|
return {
|
|
@@ -20,8 +22,10 @@ export function createInputHistoryStore({ path, maxItems = MAX_HISTORY_ITEMS } =
|
|
|
20
22
|
save(items) {
|
|
21
23
|
if (!path) return;
|
|
22
24
|
const normalized = normalizeItems(items, maxItems);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
withHistoryLock(path, () => {
|
|
26
|
+
const merged = mergeItems(normalized, this.load(), maxItems);
|
|
27
|
+
writeHistoryFile(path, merged);
|
|
28
|
+
});
|
|
25
29
|
},
|
|
26
30
|
};
|
|
27
31
|
}
|
|
@@ -33,3 +37,61 @@ function normalizeItems(items, maxItems) {
|
|
|
33
37
|
.filter(Boolean)
|
|
34
38
|
.slice(0, maxItems);
|
|
35
39
|
}
|
|
40
|
+
|
|
41
|
+
function mergeItems(primaryItems, existingItems, maxItems) {
|
|
42
|
+
const merged = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const item of [...primaryItems, ...existingItems]) {
|
|
45
|
+
if (seen.has(item)) continue;
|
|
46
|
+
seen.add(item);
|
|
47
|
+
merged.push(item);
|
|
48
|
+
if (merged.length >= maxItems) break;
|
|
49
|
+
}
|
|
50
|
+
return merged;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeHistoryFile(path, items) {
|
|
54
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
55
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
56
|
+
const payload = `${JSON.stringify({ version: HISTORY_VERSION, items }, null, 2)}\n`;
|
|
57
|
+
writeFileSync(tmpPath, payload, "utf8");
|
|
58
|
+
renameSync(tmpPath, path);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function withHistoryLock(path, fn) {
|
|
62
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
63
|
+
const lockPath = `${path}.lock`;
|
|
64
|
+
const fd = acquireLock(lockPath);
|
|
65
|
+
try {
|
|
66
|
+
return fn();
|
|
67
|
+
} finally {
|
|
68
|
+
closeSync(fd);
|
|
69
|
+
try { unlinkSync(lockPath); } catch {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function acquireLock(lockPath) {
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
while (true) {
|
|
76
|
+
try {
|
|
77
|
+
const fd = openSync(lockPath, "wx");
|
|
78
|
+
writeFileSync(fd, `${process.pid}\n`, "utf8");
|
|
79
|
+
return fd;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err?.code !== "EEXIST") throw err;
|
|
82
|
+
removeStaleLock(lockPath);
|
|
83
|
+
if (Date.now() - start >= LOCK_WAIT_MS) throw err;
|
|
84
|
+
sleepSync(25);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function removeStaleLock(lockPath) {
|
|
90
|
+
try {
|
|
91
|
+
if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) unlinkSync(lockPath);
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sleepSync(ms) {
|
|
96
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
97
|
+
}
|
package/src/cli/repl-loop.mjs
CHANGED
|
@@ -14,12 +14,13 @@ export async function runSingleShotPrompt({
|
|
|
14
14
|
refreshStatusBar,
|
|
15
15
|
modeState = null,
|
|
16
16
|
}) {
|
|
17
|
-
|
|
18
|
-
ui.writeln(turnInput.displayMessage);
|
|
19
|
-
ui.recall?.({ source: "user", hints: turnInput.userRecallHints });
|
|
20
|
-
if (turnInput.shouldRenderCarryoverRecall) ui.recall?.({ source: "assistant", hints: turnInput.carryoverRecallHints });
|
|
21
|
-
refreshStatusBar.startWorking?.();
|
|
17
|
+
memoryStore.beginTurn();
|
|
22
18
|
try {
|
|
19
|
+
const turnInput = prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState });
|
|
20
|
+
ui.writeln(turnInput.displayMessage);
|
|
21
|
+
ui.recall?.({ source: "user", hints: turnInput.userRecallHints });
|
|
22
|
+
if (turnInput.shouldRenderCarryoverRecall) ui.recall?.({ source: "assistant", hints: turnInput.carryoverRecallHints });
|
|
23
|
+
refreshStatusBar.startWorking?.();
|
|
23
24
|
const result = await runner.runTurn(turnInput.fullPrompt, turnInput.userMessage, turnInput.runOptions);
|
|
24
25
|
renderPendingAssistantRecallPreview({ runner, ui });
|
|
25
26
|
await handleTurnLifecycleAction(result?.lifecycleAction, { runner, ui });
|
|
@@ -127,8 +128,9 @@ function handleInlineCommand(trimmed, { cwd, ui, lastInlineShellCommand }) {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
async function runReplTurn({ prompt, runner, memoryStore, currentProject, ui, refreshStatusBar, setTurnRunning, modeState = null }) {
|
|
130
|
-
|
|
131
|
+
memoryStore.beginTurn();
|
|
131
132
|
try {
|
|
133
|
+
const turnInput = prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState });
|
|
132
134
|
ui.writeln(turnInput.displayMessage);
|
|
133
135
|
ui.recall?.({ source: "user", hints: turnInput.userRecallHints });
|
|
134
136
|
if (turnInput.shouldRenderCarryoverRecall) ui.recall?.({ source: "assistant", hints: turnInput.carryoverRecallHints });
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { basename, join, resolve } from "node:path";
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { createUI } from "../ui.mjs";
|
|
4
|
-
import { createPermissionController, MODE } from "../permissions.mjs";
|
|
5
4
|
import { loadKeybindings } from "../input/keybindings.mjs";
|
|
6
5
|
import { createInputHistoryStore } from "../input/history-store.mjs";
|
|
7
6
|
import { createModeState } from "../input/mode-state.mjs";
|
|
@@ -12,21 +11,16 @@ import { createMarchAuthStorage } from "../../auth/storage.mjs";
|
|
|
12
11
|
import { createRuntimeRunner } from "./create-runtime-runner.mjs";
|
|
13
12
|
import { createCliShellRuntime } from "../../shell/cli-runtime.mjs";
|
|
14
13
|
import { MarkdownMemoryStore } from "../../memory/markdown-store.mjs";
|
|
15
|
-
import { createMarkdownMemoryTools } from "../../memory/markdown-tools.mjs";
|
|
16
14
|
import { discoverProjectExtensionPaths } from "../../extensions/discovery.mjs";
|
|
17
15
|
import { loadProjectLifecycleHookManifests } from "../../extensions/lifecycle-manifest.mjs";
|
|
18
16
|
import { loadOrCreateProjectId, resumeStartupSession } from "./startup-session.mjs";
|
|
19
|
-
import { initializeMcp } from "../../mcp/index.mjs";
|
|
20
|
-
import { createWebToolsFromConfig } from "../../web/tools.mjs";
|
|
21
|
-
import { createModelContextDumper } from "../../debug/model-context-dumper.mjs";
|
|
22
17
|
import { createLogger, installProcessLogHandlers } from "../../debug/logger.mjs";
|
|
23
18
|
import { defaultProfilePaths, ensureProfileFiles } from "../../context/profiles.mjs";
|
|
24
|
-
import { createDesktopTurnNotifier } from "../../notification/desktop-notifier.mjs";
|
|
25
19
|
import { normalizeRemoteMemorySources } from "../../memory/remote/config.mjs";
|
|
26
20
|
import { resolveMemoryRoot } from "../../memory/root.mjs";
|
|
27
21
|
import { ensureBrowserDaemon } from "../../browser/client/lifecycle.mjs";
|
|
28
22
|
|
|
29
|
-
export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot
|
|
23
|
+
export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot } = {}) {
|
|
30
24
|
if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
|
|
31
25
|
await ensureBrowserDaemon({ stateRoot }).catch(() => {});
|
|
32
26
|
|
|
@@ -67,23 +61,10 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
67
61
|
ensureProfileFiles(profilePaths);
|
|
68
62
|
const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
|
|
69
63
|
const remoteMemorySources = normalizeRemoteMemorySources(config);
|
|
70
|
-
const memoryTools = createMarkdownMemoryTools(memoryStore, { remoteSources: remoteMemorySources });
|
|
71
64
|
const currentProject = basename(cwd);
|
|
72
65
|
const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
|
|
73
66
|
|
|
74
|
-
const
|
|
75
|
-
? { clientManager: null, mcpTools: [], mcpInjections: [], errors: [] }
|
|
76
|
-
: await initializeMcp({ projectDir: cwd });
|
|
77
|
-
for (const { server, error } of mcpInit.errors) {
|
|
78
|
-
if (!args.json) process.stderr.write(`[mcp] ${server}: ${error}\n`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const webTools = createWebToolsFromConfig(config);
|
|
82
|
-
const turnNotifier = createDesktopTurnNotifier({ enabled: Boolean(config.notifications?.turnEnd), config: config.notifications });
|
|
83
|
-
const permissionMode = args.permissionMode ?? MODE.BYPASS;
|
|
84
|
-
const permissionController = createPermissionController({ mode: permissionMode });
|
|
85
|
-
const usePiSessions = true;
|
|
86
|
-
const usePiRuntimeHost = true;
|
|
67
|
+
const permissionMode = args.permissionMode;
|
|
87
68
|
const sessionSource = "pi";
|
|
88
69
|
const sessionsRoot = join(projectMarchDir, "sessions");
|
|
89
70
|
const sessionState = {
|
|
@@ -92,10 +73,6 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
92
73
|
};
|
|
93
74
|
sessionState.sessionDir = join(sessionsRoot, sessionState.sessionId);
|
|
94
75
|
const contextDumpRoot = resolve(projectMarchDir, "context-dumps", sessionState.sessionId);
|
|
95
|
-
const modelContextDumper = createModelContextDumper({
|
|
96
|
-
enabled: args.dumpContext,
|
|
97
|
-
rootDir: contextDumpRoot,
|
|
98
|
-
});
|
|
99
76
|
|
|
100
77
|
const ui = createUI({
|
|
101
78
|
json: args.json,
|
|
@@ -132,10 +109,9 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
132
109
|
let runner;
|
|
133
110
|
try {
|
|
134
111
|
runner = await createRuntimeRunner({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
permissionController, modelContextDumper, turnNotifier, logger,
|
|
112
|
+
runnerOptions,
|
|
113
|
+
ui,
|
|
114
|
+
shellRuntime,
|
|
139
115
|
refreshStatusBar: (...args) => refreshStatusBar?.(...args),
|
|
140
116
|
});
|
|
141
117
|
} catch (err) {
|
|
@@ -1,61 +1,19 @@
|
|
|
1
|
-
import { createRunner } from "../../agent/runner.mjs";
|
|
2
1
|
import { createRunnerProcessClient } from "../../agent/runtime/runner-process-client.mjs";
|
|
3
|
-
import { resolvePiSessionManager } from "../../session/pi-manager.mjs";
|
|
4
2
|
|
|
5
3
|
export async function createRuntimeRunner({
|
|
6
|
-
useRuntimeProcess = false,
|
|
7
4
|
runnerOptions,
|
|
8
5
|
ui,
|
|
9
|
-
memoryStore,
|
|
10
|
-
memoryTools,
|
|
11
6
|
shellRuntime,
|
|
12
|
-
mcpTools,
|
|
13
|
-
mcpInjections,
|
|
14
|
-
mcpClientManager,
|
|
15
|
-
webTools,
|
|
16
|
-
usePiSessions,
|
|
17
|
-
usePiRuntimeHost,
|
|
18
|
-
authStorage,
|
|
19
|
-
permissionController,
|
|
20
|
-
modelContextDumper,
|
|
21
|
-
turnNotifier,
|
|
22
|
-
logger,
|
|
23
7
|
refreshStatusBar,
|
|
24
8
|
} = {}) {
|
|
25
9
|
const onModelPayload = ({ estimatedTokens }) => {
|
|
26
10
|
refreshStatusBar?.({ contextTokens: estimatedTokens });
|
|
27
11
|
};
|
|
12
|
+
const onLspStatusChange = () => {
|
|
13
|
+
refreshStatusBar?.();
|
|
14
|
+
};
|
|
28
15
|
|
|
29
|
-
const runner =
|
|
30
|
-
? (await createRunnerProcessClient({ runnerOptions, ui, onModelPayload })).runner
|
|
31
|
-
: await createRunner({
|
|
32
|
-
...runnerOptions,
|
|
33
|
-
ui,
|
|
34
|
-
memoryStore,
|
|
35
|
-
memoryTools,
|
|
36
|
-
shellRuntime,
|
|
37
|
-
mcpTools,
|
|
38
|
-
mcpInjections,
|
|
39
|
-
mcpClientManager,
|
|
40
|
-
webTools,
|
|
41
|
-
sessionManager: resolvePiSessionManager({
|
|
42
|
-
cwd: runnerOptions.cwd,
|
|
43
|
-
projectMarchDir: runnerOptions.projectMarchDir,
|
|
44
|
-
enabled: usePiSessions,
|
|
45
|
-
}),
|
|
46
|
-
useRuntimeHost: usePiRuntimeHost,
|
|
47
|
-
syncPiSidecar: usePiSessions || usePiRuntimeHost,
|
|
48
|
-
authStorage,
|
|
49
|
-
maxTurns: runnerOptions.config?.maxTurns ?? undefined,
|
|
50
|
-
trimBatch: runnerOptions.config?.trimBatch ?? undefined,
|
|
51
|
-
hostedTools: runnerOptions.config?.hostedTools,
|
|
52
|
-
permissionController,
|
|
53
|
-
modelContextDumper,
|
|
54
|
-
turnNotifier,
|
|
55
|
-
logger,
|
|
56
|
-
onModelPayload,
|
|
57
|
-
});
|
|
58
|
-
|
|
16
|
+
const { runner } = await createRunnerProcessClient({ runnerOptions, ui, onModelPayload, onLspStatusChange });
|
|
59
17
|
runner.shellRuntime ??= shellRuntime;
|
|
60
18
|
return runner;
|
|
61
19
|
}
|