march-cli 0.1.0

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.
Files changed (217) hide show
  1. package/bin/march.mjs +13 -0
  2. package/package.json +36 -0
  3. package/src/agent/command-exec-tool.mjs +91 -0
  4. package/src/agent/context-stats-tool.mjs +57 -0
  5. package/src/agent/editing/diff-apply.mjs +28 -0
  6. package/src/agent/editing/diff-format.mjs +57 -0
  7. package/src/agent/file-edit-tool.mjs +276 -0
  8. package/src/agent/find-tool.mjs +112 -0
  9. package/src/agent/model-payload-dumper.mjs +201 -0
  10. package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -0
  11. package/src/agent/provider/payload-messages.mjs +138 -0
  12. package/src/agent/read-file-tool.mjs +112 -0
  13. package/src/agent/runner/fast-model.mjs +36 -0
  14. package/src/agent/runner/runner-cleanup.mjs +12 -0
  15. package/src/agent/runner/runner-init.mjs +15 -0
  16. package/src/agent/runner/runner-session-state.mjs +40 -0
  17. package/src/agent/runner.mjs +266 -0
  18. package/src/agent/runtime/runner-runtime-host.mjs +73 -0
  19. package/src/agent/runtime/runtime-factory.mjs +42 -0
  20. package/src/agent/runtime/runtime-host.mjs +34 -0
  21. package/src/agent/session/session-auto-name.mjs +41 -0
  22. package/src/agent/session/session-binding.mjs +12 -0
  23. package/src/agent/session/session-options.mjs +46 -0
  24. package/src/agent/tool-names.mjs +1 -0
  25. package/src/agent/tool-result.mjs +3 -0
  26. package/src/agent/tools.mjs +54 -0
  27. package/src/agent/turn/turn-events.mjs +64 -0
  28. package/src/agent/turn/turn-runner.mjs +103 -0
  29. package/src/auth/login-command.mjs +90 -0
  30. package/src/auth/storage.mjs +33 -0
  31. package/src/cli/args.mjs +71 -0
  32. package/src/cli/commands/copy-command.mjs +73 -0
  33. package/src/cli/commands/export-command.mjs +206 -0
  34. package/src/cli/commands/extensions-command.mjs +53 -0
  35. package/src/cli/commands/help-command.mjs +7 -0
  36. package/src/cli/commands/model-command.mjs +110 -0
  37. package/src/cli/commands/paste-image-command.mjs +43 -0
  38. package/src/cli/commands/provider-command.mjs +55 -0
  39. package/src/cli/commands/status-command.mjs +157 -0
  40. package/src/cli/commands/thinking-command.mjs +80 -0
  41. package/src/cli/fallback-ui.mjs +156 -0
  42. package/src/cli/input/attachment-tokens.mjs +20 -0
  43. package/src/cli/input/autocomplete.mjs +106 -0
  44. package/src/cli/input/external-editor.mjs +39 -0
  45. package/src/cli/input/history-store.mjs +35 -0
  46. package/src/cli/input/image-clipboard.mjs +55 -0
  47. package/src/cli/input/keybinding-dispatch.mjs +76 -0
  48. package/src/cli/input/keybindings.mjs +96 -0
  49. package/src/cli/input/mode-state.mjs +43 -0
  50. package/src/cli/input/prompt-templates.mjs +84 -0
  51. package/src/cli/input/select-with-keyboard.mjs +67 -0
  52. package/src/cli/permissions.mjs +103 -0
  53. package/src/cli/repl-commands.mjs +86 -0
  54. package/src/cli/repl-loop.mjs +157 -0
  55. package/src/cli/selector-list.mjs +21 -0
  56. package/src/cli/session/pi-session-switch-command.mjs +41 -0
  57. package/src/cli/session/session-command.mjs +23 -0
  58. package/src/cli/session/session-list-command.mjs +68 -0
  59. package/src/cli/session/session-name-command.mjs +26 -0
  60. package/src/cli/session/session-source-command.mjs +89 -0
  61. package/src/cli/session/session-switch-command.mjs +1 -0
  62. package/src/cli/shell/shell-command.mjs +55 -0
  63. package/src/cli/shell/shell-drawer-controls.mjs +33 -0
  64. package/src/cli/shell/shell-drawer.mjs +192 -0
  65. package/src/cli/shell/shell-split-layout.mjs +70 -0
  66. package/src/cli/slash-commands.mjs +176 -0
  67. package/src/cli/startup/startup-banner.mjs +17 -0
  68. package/src/cli/startup/startup-session.mjs +51 -0
  69. package/src/cli/status-line-updater.mjs +74 -0
  70. package/src/cli/tool-output.mjs +9 -0
  71. package/src/cli/tui/editor/external-editor-runner.mjs +24 -0
  72. package/src/cli/tui/input/mouse-selection-controller.mjs +89 -0
  73. package/src/cli/tui/input/mouse-tracking.mjs +20 -0
  74. package/src/cli/tui/layout/main-pane-layout.mjs +38 -0
  75. package/src/cli/tui/layout/safe-render-boundary.mjs +46 -0
  76. package/src/cli/tui/markdown-renderer.mjs +279 -0
  77. package/src/cli/tui/output/scroll-state.mjs +79 -0
  78. package/src/cli/tui/output/tool-card-renderer.mjs +59 -0
  79. package/src/cli/tui/output-buffer.mjs +297 -0
  80. package/src/cli/tui/permission-request-ui.mjs +18 -0
  81. package/src/cli/tui/recall-rendering.mjs +25 -0
  82. package/src/cli/tui/select/editor-select-list.mjs +111 -0
  83. package/src/cli/tui/selection-screen.mjs +212 -0
  84. package/src/cli/tui/status/retry-status.mjs +72 -0
  85. package/src/cli/tui/status/spinner-status.mjs +42 -0
  86. package/src/cli/tui/status/status-bar.mjs +88 -0
  87. package/src/cli/tui/syntax/highlighting.mjs +277 -0
  88. package/src/cli/tui/syntax/languages.mjs +91 -0
  89. package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -0
  90. package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -0
  91. package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -0
  92. package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -0
  93. package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -0
  94. package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -0
  95. package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -0
  96. package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -0
  97. package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -0
  98. package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -0
  99. package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -0
  100. package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -0
  101. package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -0
  102. package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -0
  103. package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -0
  104. package/src/cli/tui/syntax/tree-sitter/tree-sitter-bash.wasm +0 -0
  105. package/src/cli/tui/syntax/tree-sitter/tree-sitter-c-sharp.wasm +0 -0
  106. package/src/cli/tui/syntax/tree-sitter/tree-sitter-c.wasm +0 -0
  107. package/src/cli/tui/syntax/tree-sitter/tree-sitter-cpp.wasm +0 -0
  108. package/src/cli/tui/syntax/tree-sitter/tree-sitter-css.wasm +0 -0
  109. package/src/cli/tui/syntax/tree-sitter/tree-sitter-diff.wasm +0 -0
  110. package/src/cli/tui/syntax/tree-sitter/tree-sitter-go.wasm +0 -0
  111. package/src/cli/tui/syntax/tree-sitter/tree-sitter-html.wasm +0 -0
  112. package/src/cli/tui/syntax/tree-sitter/tree-sitter-java.wasm +0 -0
  113. package/src/cli/tui/syntax/tree-sitter/tree-sitter-json.wasm +0 -0
  114. package/src/cli/tui/syntax/tree-sitter/tree-sitter-php.wasm +0 -0
  115. package/src/cli/tui/syntax/tree-sitter/tree-sitter-python.wasm +0 -0
  116. package/src/cli/tui/syntax/tree-sitter/tree-sitter-ruby.wasm +0 -0
  117. package/src/cli/tui/syntax/tree-sitter/tree-sitter-rust.wasm +0 -0
  118. package/src/cli/tui/syntax/tree-sitter/tree-sitter-toml.wasm +0 -0
  119. package/src/cli/tui/syntax/tree-sitter/tree-sitter-tsx.wasm +0 -0
  120. package/src/cli/tui/syntax/tree-sitter/tree-sitter-typescript.wasm +0 -0
  121. package/src/cli/tui/syntax/tree-sitter/tree-sitter-yaml.wasm +0 -0
  122. package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -0
  123. package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -0
  124. package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -0
  125. package/src/cli/tui/tool-rendering.mjs +194 -0
  126. package/src/cli/tui/tui-diff-rendering.mjs +157 -0
  127. package/src/cli/tui/tui-handlers.mjs +110 -0
  128. package/src/cli/tui/tui-input-controller.mjs +61 -0
  129. package/src/cli/tui/ui-theme.mjs +148 -0
  130. package/src/cli/ui.mjs +299 -0
  131. package/src/config/config-json.mjs +73 -0
  132. package/src/config/dotenv.mjs +20 -0
  133. package/src/config/features.mjs +75 -0
  134. package/src/config/loader.mjs +109 -0
  135. package/src/config/settings-command.mjs +97 -0
  136. package/src/context/diagnostics.mjs +70 -0
  137. package/src/context/engine.mjs +148 -0
  138. package/src/context/injections.mjs +26 -0
  139. package/src/context/project-context.mjs +20 -0
  140. package/src/context/session-status.mjs +15 -0
  141. package/src/context/shell-layers.mjs +23 -0
  142. package/src/context/system-core/base.md +60 -0
  143. package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -0
  144. package/src/context/system-core/prompts/default.md +3 -0
  145. package/src/context/system-core.mjs +35 -0
  146. package/src/debug/model-context-dumper.mjs +52 -0
  147. package/src/extensions/discovery.mjs +40 -0
  148. package/src/extensions/lifecycle-adapter.mjs +210 -0
  149. package/src/extensions/lifecycle-manifest.mjs +69 -0
  150. package/src/image-gen/index.mjs +7 -0
  151. package/src/image-gen/provider.mjs +231 -0
  152. package/src/image-gen/tool.mjs +84 -0
  153. package/src/lsp/client.mjs +204 -0
  154. package/src/lsp/diagnostic-store.mjs +39 -0
  155. package/src/lsp/servers.mjs +212 -0
  156. package/src/lsp/service.mjs +65 -0
  157. package/src/main.mjs +294 -0
  158. package/src/mcp/client.mjs +195 -0
  159. package/src/mcp/config.mjs +130 -0
  160. package/src/mcp/index.mjs +48 -0
  161. package/src/mcp/tools.mjs +98 -0
  162. package/src/memory/database.mjs +219 -0
  163. package/src/memory/glossary.mjs +124 -0
  164. package/src/memory/graph/graph-cascades.mjs +109 -0
  165. package/src/memory/graph/graph-diagnostics.mjs +73 -0
  166. package/src/memory/graph/graph-path-removal.mjs +50 -0
  167. package/src/memory/graph/graph-path-utils.mjs +17 -0
  168. package/src/memory/graph/graph-primitives.mjs +103 -0
  169. package/src/memory/graph/graph-read.mjs +159 -0
  170. package/src/memory/graph.mjs +282 -0
  171. package/src/memory/markdown/markdown-delete.mjs +23 -0
  172. package/src/memory/markdown/markdown-format.mjs +128 -0
  173. package/src/memory/markdown/markdown-recall.mjs +28 -0
  174. package/src/memory/markdown/ripgrep.mjs +16 -0
  175. package/src/memory/markdown/sqlite-index.mjs +87 -0
  176. package/src/memory/markdown-store.mjs +286 -0
  177. package/src/memory/markdown-tools.mjs +103 -0
  178. package/src/memory/search.mjs +142 -0
  179. package/src/memory/snapshot.mjs +86 -0
  180. package/src/memory/system-views.mjs +120 -0
  181. package/src/memory/tools.mjs +282 -0
  182. package/src/notification/desktop-notifier.mjs +85 -0
  183. package/src/platform/open-file.mjs +28 -0
  184. package/src/provider/config-command.mjs +129 -0
  185. package/src/provider/presets.mjs +72 -0
  186. package/src/session/attachment-display.mjs +16 -0
  187. package/src/session/attachment-references.mjs +65 -0
  188. package/src/session/attachments.mjs +140 -0
  189. package/src/session/persist.mjs +1 -0
  190. package/src/session/pi-manager.mjs +34 -0
  191. package/src/session/session-utils.mjs +16 -0
  192. package/src/session/sidecar-sync.mjs +19 -0
  193. package/src/session/sidecar.mjs +68 -0
  194. package/src/session/transcript.mjs +83 -0
  195. package/src/session/tree.mjs +42 -0
  196. package/src/shell/cli-runtime.mjs +11 -0
  197. package/src/shell/hints.mjs +12 -0
  198. package/src/shell/node-pty-adapter.mjs +81 -0
  199. package/src/shell/runtime-state.mjs +126 -0
  200. package/src/shell/runtime.mjs +244 -0
  201. package/src/shell/screen-buffer.mjs +136 -0
  202. package/src/shell/tool-read.mjs +74 -0
  203. package/src/shell/tools.mjs +299 -0
  204. package/src/supergrok/actions/image-generate.mjs +60 -0
  205. package/src/supergrok/actions/search.mjs +78 -0
  206. package/src/supergrok/auth.mjs +36 -0
  207. package/src/supergrok/constants.mjs +18 -0
  208. package/src/supergrok/oauth-provider.mjs +278 -0
  209. package/src/supergrok/provider.mjs +36 -0
  210. package/src/supergrok/response.mjs +76 -0
  211. package/src/supergrok/tool.mjs +61 -0
  212. package/src/text/ansi.mjs +3 -0
  213. package/src/web/config-command.mjs +43 -0
  214. package/src/web/fetch.mjs +78 -0
  215. package/src/web/presets.mjs +16 -0
  216. package/src/web/search.mjs +83 -0
  217. package/src/web/tools.mjs +107 -0
@@ -0,0 +1,266 @@
1
+ import {
2
+ createAgentSession,
3
+ ModelRegistry,
4
+ SettingsManager,
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { createMarchAuthStorage } from "../auth/storage.mjs";
7
+ import { ContextEngine } from "../context/engine.mjs";
8
+ import { createMarchLifecycleAdapter } from "../extensions/lifecycle-adapter.mjs";
9
+ import { syncPiSessionSidecar } from "../session/sidecar-sync.mjs";
10
+ import { LspService } from "../lsp/service.mjs";
11
+ import { formatRecallHints } from "../memory/markdown-store.mjs";
12
+ import { appendProviderUserMessage, installModelPayloadDumper, replaceProviderContextMessages } from "./model-payload-dumper.mjs";
13
+ import { resolveInitialModel, resolveRunnerSessionManager } from "./runner/runner-init.mjs";
14
+ import { runRunnerCleanup } from "./runner/runner-cleanup.mjs";
15
+ import { createRunnerRuntimeHost } from "./runtime/runner-runtime-host.mjs";
16
+ import { getRunnerSessionStats, syncEngineSessionState } from "./runner/runner-session-state.mjs";
17
+ import { resolveRunnerSessionOptions } from "./session/session-options.mjs";
18
+ import { createSessionBinding } from "./session/session-binding.mjs";
19
+ import { maybeAutoNameSession } from "./session/session-auto-name.mjs";
20
+ import { MARCH_BASE_TOOL_NAMES } from "./tool-names.mjs";
21
+ import { runRunnerTurn } from "./turn/turn-runner.mjs";
22
+ import { appendFastVariants, createFastModelEntry, fromFastEntryModel, isFastProvider } from "./runner/fast-model.mjs";
23
+ import { registerSuperGrokProvider } from "../supergrok/provider.mjs";
24
+
25
+ export { MARCH_BASE_TOOL_NAMES };
26
+ export { installModelPayloadDumper } from "./model-payload-dumper.mjs";
27
+ export { createDefaultSessionManager, resolveRunnerSessionManager } from "./runner/runner-init.mjs";
28
+ export { getRunnerSessionStats, syncEngineSessionState } from "./runner/runner-session-state.mjs";
29
+
30
+ export async function createRunner({ cwd, modelId = null, provider = null, providers = {}, stateRoot, ui, memoryStore = null, memoryTools = [], shellRuntime = null, mcpTools = [], mcpInjections = [], mcpClientManager = null, webTools = [], namespace = "", sessionManager = null, useRuntimeHost = false, projectMarchDir = null, syncPiSidecar = false, extensionPaths = [], lifecycleHooks = [], lifecycleDiagnostics = [], authStorage = null, permissionController = null, modelContextDumper = null, turnNotifier = null, onModelPayload = null, createAgentSessionImpl = createAgentSession, createAgentSessionRuntimeImpl, createRuntimeServices, createRuntimeSessionFromServices, maxTurns, trimBatch, serviceTier = null }) {
31
+ if (!useRuntimeHost && extensionPaths.length > 0) {
32
+ throw new Error("--extension requires the default pi runtime host path");
33
+ }
34
+ const authConfig = authStorage
35
+ ? { authStorage, hasAuth: true }
36
+ : createMarchAuthStorage({ provider: provider ?? "deepseek", providers, cwd });
37
+ if (!authConfig.hasAuth) throw new Error("No providers configured. Run: march provider --config");
38
+ const resolvedAuth = authConfig.authStorage;
39
+ const modelRegistry = ModelRegistry.create(resolvedAuth);
40
+ registerSuperGrokProvider(modelRegistry);
41
+ const selectedModel = resolveInitialModel({ modelRegistry, provider, modelId });
42
+ if (!selectedModel) throw new Error("No authenticated models available. Run: march provider --config");
43
+ provider = selectedModel.provider;
44
+ modelId = selectedModel.id;
45
+ const settingsManager = SettingsManager.inMemory({
46
+ compaction: { enabled: false },
47
+ retry: { enabled: true, maxRetries: 3, baseDelayMs: 2000 },
48
+ });
49
+ const lspService = new LspService({ cwd });
50
+ const engine = new ContextEngine({ cwd, modelId, provider, namespace, shellRuntime, lspService, injections: mcpInjections, maxTurns, trimBatch });
51
+ const resolvedSessionManager = resolveRunnerSessionManager(cwd, sessionManager);
52
+ const sessionBinding = createSessionBinding(null);
53
+ let currentModelCallKind = "model";
54
+ let currentPromptForContext = "";
55
+ let currentTurnContextMode = "rebuild";
56
+ let nextTurnContextMode = "rebuild";
57
+ let pendingMidTurnRecallHints = [];
58
+ let runtimeHost = null;
59
+ let lifecycleAdapter = null;
60
+ let _currentFastEntry = null;
61
+ if (useRuntimeHost) {
62
+ runtimeHost = await createRunnerRuntimeHost({
63
+ cwd, stateRoot, provider, modelId,
64
+ authStorage: resolvedAuth, settingsManager, modelRegistry,
65
+ sessionManager: resolvedSessionManager, sessionBinding, engine, ui,
66
+ projectMarchDir,
67
+ memoryTools, memoryStore, shellRuntime, lspService, mcpTools, webTools,
68
+ permissionController, extensionPaths,
69
+ onRebind: (session) => {
70
+ installModelPayloadDumper(session, modelContextDumper, () => currentModelCallKind, onModelPayload, injectMarchSystemContext);
71
+ syncEngineSessionState(engine, session);
72
+ },
73
+ createAgentSessionRuntimeImpl,
74
+ createServices: createRuntimeServices,
75
+ createFromServices: createRuntimeSessionFromServices,
76
+ });
77
+ } else {
78
+ const sessionOptions = resolveRunnerSessionOptions({
79
+ cwd, provider, modelId, modelRegistry, engine, ui,
80
+ memoryTools, shellRuntime, lspService, mcpTools, webTools, permissionController,
81
+ authStorage: resolvedAuth, projectMarchDir,
82
+ });
83
+ const { session } = await createAgentSessionImpl({
84
+ cwd, agentDir: stateRoot, ...sessionOptions,
85
+ authStorage: resolvedAuth, modelRegistry,
86
+ sessionManager: resolvedSessionManager, settingsManager,
87
+ });
88
+ sessionBinding.set(session);
89
+ installModelPayloadDumper(session, modelContextDumper, () => currentModelCallKind, onModelPayload, injectMarchSystemContext);
90
+ }
91
+ syncEngineSessionState(engine, sessionBinding.get());
92
+ lifecycleAdapter = createMarchLifecycleAdapter({
93
+ cwd, projectMarchDir, extensionPaths, sessionBinding, engine,
94
+ getSessionStats: () => getRunnerSessionStats(sessionBinding.get(), runtimeHost),
95
+ getRuntimeDiagnostics: () => runtimeHost?.getDiagnostics?.() ?? [],
96
+ manifestHooks: lifecycleHooks,
97
+ manifestDiagnostics: lifecycleDiagnostics,
98
+ });
99
+ if (serviceTier === "priority" && selectedModel && isFastProvider(selectedModel.provider)) {
100
+ _currentFastEntry = createFastModelEntry(selectedModel).model;
101
+ }
102
+ return {
103
+ engine,
104
+ get session() { return sessionBinding.get(); },
105
+ shellRuntime,
106
+ async runTurn(prompt, userMessage, { userRecallHints = [], currentProject = "" } = {}) {
107
+ currentPromptForContext = prompt;
108
+ const contextMode = nextTurnContextMode;
109
+ currentTurnContextMode = contextMode;
110
+ nextTurnContextMode = "rebuild";
111
+ pendingMidTurnRecallHints = [];
112
+ try {
113
+ const result = await runRunnerTurn({
114
+ prompt, userMessage, options: { userRecallHints, currentProject },
115
+ sessionBinding, engine, ui, projectMarchDir, memoryStore,
116
+ setModelCallKind: (kind) => { currentModelCallKind = kind; },
117
+ onMidTurnRecallHints: (hints) => { pendingMidTurnRecallHints.push(...hints); },
118
+ syncCurrentPiSidecar,
119
+ autoNameSession,
120
+ contextMode,
121
+ });
122
+ await notifyTurnEndBestEffort(turnNotifier, {
123
+ status: "success",
124
+ sessionName: engine.sessionName,
125
+ draft: result?.draft ?? "",
126
+ });
127
+ return result;
128
+ } catch (err) {
129
+ await notifyTurnEndBestEffort(turnNotifier, {
130
+ status: "error",
131
+ sessionName: engine.sessionName,
132
+ errorMessage: err?.message ?? String(err),
133
+ });
134
+ throw err;
135
+ } finally {
136
+ currentTurnContextMode = "rebuild";
137
+ }
138
+ },
139
+ abort() {
140
+ const activeSession = sessionBinding.get();
141
+ activeSession.abortRetry?.();
142
+ const result = activeSession.abort();
143
+ nextTurnContextMode = "continueExistingPiTranscript";
144
+ return result;
145
+ },
146
+ async cycleModel() {
147
+ const result = await sessionBinding.get().cycleModel();
148
+ _currentFastEntry = null;
149
+ if (result?.model) {
150
+ engine.setRuntimeState({
151
+ modelId: result.model.id, provider: result.model.provider,
152
+ thinkingLevel: result.thinkingLevel,
153
+ });
154
+ }
155
+ return result;
156
+ },
157
+ getCurrentModel() { return _currentFastEntry ?? sessionBinding.get().model; },
158
+ async setModel(model) {
159
+ const activeSession = sessionBinding.get();
160
+ const { baseId, isFast } = fromFastEntryModel(model);
161
+ const baseEntry = sessionBinding.get().scopedModels.find((e) => e.model.id === baseId);
162
+ if (!baseEntry) throw new Error(`Model not found: ${baseId}`);
163
+ await activeSession.setModel(baseEntry.model);
164
+ _currentFastEntry = isFast ? model : null;
165
+ engine.setRuntimeState({
166
+ modelId: activeSession.model?.id ?? baseId,
167
+ provider: activeSession.model?.provider ?? model.provider,
168
+ thinkingLevel: activeSession.thinkingLevel,
169
+ });
170
+ return model;
171
+ },
172
+ getScopedModels() { return appendFastVariants(sessionBinding.get().scopedModels); },
173
+ getConfiguredProviders() {
174
+ const configured = Object.values(providers ?? {}).map((profile) => profile?.type).filter(Boolean);
175
+ const available = (modelRegistry.getAvailable?.() ?? []).map((model) => model.provider);
176
+ return [...new Set([...configured, ...available])];
177
+ },
178
+ getSessionStats() { return getRunnerSessionStats(sessionBinding.get(), runtimeHost); },
179
+ setSessionName(name) {
180
+ const activeSession = sessionBinding.get();
181
+ activeSession.setSessionName?.(name);
182
+ engine.setSessionName(name);
183
+ syncCurrentPiSidecar();
184
+ return engine.sessionName;
185
+ },
186
+ canSwitchPiSession() { return Boolean(runtimeHost); },
187
+ async startNewSession() {
188
+ if (!runtimeHost) throw new Error("pi runtime host is not enabled");
189
+ nextTurnContextMode = "rebuild";
190
+ syncCurrentPiSidecar();
191
+ const result = await runtimeHost.newSession();
192
+ if (result?.cancelled) return { cancelled: true };
193
+ engine.restoreSession({}, [], { replace: true });
194
+ shellRuntime?.killAll?.();
195
+ const stats = getRunnerSessionStats(sessionBinding.get(), runtimeHost);
196
+ return { sessionId: stats.sessionId, sessionFile: stats.sessionFile };
197
+ },
198
+ getExtensionDiagnostics() { return runtimeHost?.getDiagnostics?.() ?? []; },
199
+ getExtensionLifecycleState() { return lifecycleAdapter.getState(); },
200
+ async switchPiSession(sessionPath) {
201
+ if (!runtimeHost) throw new Error("pi runtime host is not enabled");
202
+ nextTurnContextMode = "rebuild";
203
+ return runtimeHost.switchSession(sessionPath);
204
+ },
205
+ cycleThinkingLevel() {
206
+ const level = sessionBinding.get().cycleThinkingLevel();
207
+ engine.setRuntimeState({ thinkingLevel: level });
208
+ return level;
209
+ },
210
+ getThinkingLevel() { return sessionBinding.get().thinkingLevel; },
211
+ setThinkingLevel(level) {
212
+ const activeSession = sessionBinding.get();
213
+ activeSession.setThinkingLevel(level);
214
+ engine.setRuntimeState({ thinkingLevel: activeSession.thinkingLevel });
215
+ return activeSession.thinkingLevel;
216
+ },
217
+ getAvailableThinkingLevels() { return sessionBinding.get().getAvailableThinkingLevels(); },
218
+ async dispose() {
219
+ await runRunnerCleanup([
220
+ () => runtimeHost?.dispose() ?? sessionBinding.get().dispose(),
221
+ () => shellRuntime?.dispose?.() ?? shellRuntime?.killAll?.(),
222
+ () => lspService.dispose(),
223
+ () => mcpClientManager?.disconnectAll?.(),
224
+ ]);
225
+ },
226
+ };
227
+ function syncCurrentPiSidecar() {
228
+ return syncPiSessionSidecar({
229
+ enabled: syncPiSidecar, projectMarchDir, engine,
230
+ sessionStats: getRunnerSessionStats(sessionBinding.get(), runtimeHost),
231
+ });
232
+ }
233
+ function autoNameSession() {
234
+ return maybeAutoNameSession({
235
+ engine,
236
+ session: sessionBinding.get(),
237
+ setSessionName: (name) => {
238
+ const activeSession = sessionBinding.get();
239
+ activeSession.setSessionName?.(name);
240
+ engine.setSessionName(name);
241
+ return engine.sessionName;
242
+ },
243
+ });
244
+ }
245
+ function injectMarchSystemContext(payload, { kind } = {}) {
246
+ if (kind !== "user") return payload;
247
+ let nextPayload = currentTurnContextMode === "continueExistingPiTranscript"
248
+ ? payload
249
+ : replaceProviderContextMessages(payload, engine.buildProviderContext(currentPromptForContext));
250
+ if (_currentFastEntry) nextPayload = { ...nextPayload, service_tier: "priority" };
251
+ if (pendingMidTurnRecallHints.length > 0) {
252
+ nextPayload = appendProviderUserMessage(nextPayload, formatRecallHints("assistant", pendingMidTurnRecallHints));
253
+ pendingMidTurnRecallHints = [];
254
+ }
255
+ return nextPayload;
256
+ }
257
+ }
258
+
259
+ async function notifyTurnEndBestEffort(turnNotifier, event) {
260
+ if (!turnNotifier?.notifyTurnEnd) return;
261
+ try {
262
+ await turnNotifier.notifyTurnEnd(event);
263
+ } catch {
264
+ // Notification must never change turn behavior.
265
+ }
266
+ }
@@ -0,0 +1,73 @@
1
+ import { createAgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
+ import { createMarchRuntimeFactory } from "./runtime-factory.mjs";
3
+ import { createRuntimeHost } from "./runtime-host.mjs";
4
+ import { resolveRunnerSessionOptions } from "../session/session-options.mjs";
5
+ import { registerSuperGrokProvider } from "../../supergrok/provider.mjs";
6
+
7
+ export async function createRunnerRuntimeHost({
8
+ cwd,
9
+ stateRoot,
10
+ provider,
11
+ modelId,
12
+ authStorage,
13
+ settingsManager,
14
+ modelRegistry,
15
+ sessionManager,
16
+ sessionBinding,
17
+ engine,
18
+ ui,
19
+ projectMarchDir = null,
20
+ memoryTools = [],
21
+ shellRuntime = null,
22
+ lspService = null,
23
+ mcpTools = [],
24
+ webTools = [],
25
+ permissionController = null,
26
+ extensionPaths = [],
27
+ onRebind = null,
28
+ createAgentSessionRuntimeImpl = createAgentSessionRuntime,
29
+ createServices,
30
+ createFromServices,
31
+ }) {
32
+ const createRuntime = createMarchRuntimeFactory({
33
+ agentDir: stateRoot,
34
+ authStorage,
35
+ settingsManager,
36
+ modelRegistry,
37
+ createServices,
38
+ createFromServices,
39
+ resourceLoaderOptions: {
40
+ additionalExtensionPaths: extensionPaths,
41
+ },
42
+ resolveSessionOptions: ({ cwd: sessionCwd, services }) => {
43
+ const activeModelRegistry = services.modelRegistry ?? modelRegistry;
44
+ registerSuperGrokProvider(activeModelRegistry);
45
+ return resolveRunnerSessionOptions({
46
+ cwd: sessionCwd,
47
+ provider,
48
+ modelId,
49
+ modelRegistry: activeModelRegistry,
50
+ engine,
51
+ ui,
52
+ memoryTools,
53
+ shellRuntime,
54
+ lspService,
55
+ mcpTools,
56
+ webTools,
57
+ permissionController,
58
+ authStorage,
59
+ projectMarchDir,
60
+ });
61
+ },
62
+ });
63
+
64
+ const runtime = await createAgentSessionRuntimeImpl(createRuntime, {
65
+ cwd,
66
+ agentDir: stateRoot,
67
+ sessionManager,
68
+ });
69
+
70
+ const host = createRuntimeHost({ runtime, sessionBinding, onRebind });
71
+ if (onRebind) await onRebind(runtime.session);
72
+ return host;
73
+ }
@@ -0,0 +1,42 @@
1
+ import {
2
+ createAgentSessionFromServices,
3
+ createAgentSessionServices,
4
+ } from "@earendil-works/pi-coding-agent";
5
+
6
+ export function createMarchRuntimeFactory({
7
+ agentDir,
8
+ authStorage,
9
+ settingsManager,
10
+ modelRegistry,
11
+ resolveSessionOptions,
12
+ resourceLoaderOptions = {},
13
+ createServices = createAgentSessionServices,
14
+ createFromServices = createAgentSessionFromServices,
15
+ }) {
16
+ if (typeof resolveSessionOptions !== "function") {
17
+ throw new Error("resolveSessionOptions is required");
18
+ }
19
+
20
+ return async ({ cwd, sessionManager, sessionStartEvent }) => {
21
+ const services = await createServices({
22
+ cwd,
23
+ agentDir,
24
+ authStorage,
25
+ settingsManager,
26
+ modelRegistry,
27
+ resourceLoaderOptions,
28
+ });
29
+ const sessionOptions = await resolveSessionOptions({ cwd, services });
30
+ const result = await createFromServices({
31
+ services,
32
+ sessionManager,
33
+ sessionStartEvent,
34
+ ...sessionOptions,
35
+ });
36
+ return {
37
+ ...result,
38
+ services,
39
+ diagnostics: services.diagnostics,
40
+ };
41
+ };
42
+ }
@@ -0,0 +1,34 @@
1
+ export function createRuntimeHost({ runtime, sessionBinding, onRebind = null }) {
2
+ const bindSession = async (session) => {
3
+ sessionBinding.set(session);
4
+ if (onRebind) await onRebind(session);
5
+ return session;
6
+ };
7
+
8
+ runtime.setRebindSession?.(bindSession);
9
+ sessionBinding.set(runtime.session);
10
+
11
+ return {
12
+ get runtime() {
13
+ return runtime;
14
+ },
15
+ getSession() {
16
+ return sessionBinding.get();
17
+ },
18
+ getDiagnostics() {
19
+ return runtime.diagnostics ?? [];
20
+ },
21
+ async switchSession(sessionPath, options) {
22
+ return runtime.switchSession(sessionPath, options);
23
+ },
24
+ async newSession(options) {
25
+ return runtime.newSession(options);
26
+ },
27
+ async fork(entryId, options) {
28
+ return runtime.fork(entryId, options);
29
+ },
30
+ async dispose() {
31
+ return runtime.dispose();
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,41 @@
1
+ const MAX_SESSION_NAME_LENGTH = 60;
2
+
3
+ export function maybeAutoNameSession({ engine, session, setSessionName }) {
4
+ if (engine?.sessionName || session?.sessionName) return null;
5
+ if (!Array.isArray(engine?.turns) || engine.turns.length !== 1) return null;
6
+ if (typeof setSessionName !== "function") return null;
7
+
8
+ const title = generateSessionName(engine.turns[0]);
9
+ if (!title) return null;
10
+ return setSessionName(title);
11
+ }
12
+
13
+ export function generateSessionName(turn) {
14
+ const text = normalizeTitleSource(turn?.userMessage) || normalizeTitleSource(turn?.assistantMessage);
15
+ if (!text) return "New session";
16
+ return truncateTitle(stripPromptNoise(text), MAX_SESSION_NAME_LENGTH) || "New session";
17
+ }
18
+
19
+ function normalizeTitleSource(value) {
20
+ return String(value ?? "")
21
+ .replace(/```[\s\S]*?```/g, " ")
22
+ .replace(/`([^`]+)`/g, "$1")
23
+ .replace(/\s+/g, " ")
24
+ .trim();
25
+ }
26
+
27
+ function stripPromptNoise(text) {
28
+ return text
29
+ .replace(/^[@#>\-\s]+/, "")
30
+ .replace(/^(please|pls|can you|could you|help me|帮我|请)\s+/i, "")
31
+ .replace(/[.。!?!?,:;,:;]+$/g, "")
32
+ .trim();
33
+ }
34
+
35
+ function truncateTitle(text, maxLength) {
36
+ if (text.length <= maxLength) return text;
37
+ const sliced = text.slice(0, maxLength).trimEnd();
38
+ const lastSpace = sliced.lastIndexOf(" ");
39
+ if (lastSpace >= Math.floor(maxLength * 0.6)) return sliced.slice(0, lastSpace);
40
+ return sliced;
41
+ }
@@ -0,0 +1,12 @@
1
+ export function createSessionBinding(initialSession) {
2
+ let current = initialSession;
3
+ return {
4
+ get() {
5
+ return current;
6
+ },
7
+ set(nextSession) {
8
+ current = nextSession;
9
+ return current;
10
+ },
11
+ };
12
+ }
@@ -0,0 +1,46 @@
1
+ import { getModel } from "@earendil-works/pi-ai";
2
+ import { MARCH_BASE_TOOL_NAMES } from "../tool-names.mjs";
3
+ import { createMarchCustomTools } from "../tools.mjs";
4
+
5
+ export function resolveRunnerSessionOptions({
6
+ cwd,
7
+ provider,
8
+ modelId,
9
+ modelRegistry,
10
+ engine,
11
+ ui,
12
+ memoryTools = [],
13
+ shellRuntime = null,
14
+ lspService = null,
15
+ mcpTools = [],
16
+ webTools = [],
17
+ permissionController = null,
18
+ authStorage = null,
19
+ projectMarchDir = null,
20
+ }) {
21
+ if (engine.cwd !== cwd) {
22
+ throw new Error(`Runtime session cwd mismatch: engine=${engine.cwd}, session=${cwd}`);
23
+ }
24
+
25
+ const availableModels = modelRegistry.getAvailable?.() ?? [];
26
+ const model = (provider && modelId ? modelRegistry.find(provider, modelId) : null)
27
+ ?? availableModels[0]
28
+ ?? (provider && modelId ? getModel(provider, modelId) : null);
29
+ if (!model) throw new Error(`Model not found: ${provider}/${modelId}`);
30
+
31
+ const customTools = createMarchCustomTools({ cwd, engine, ui, memoryTools, shellRuntime, lspService, mcpTools, webTools, permissionController, authStorage, projectMarchDir });
32
+ const customToolNames = customTools.map((tool) => tool.name);
33
+ const tools = [
34
+ ...customToolNames.filter((name) => name === "read"),
35
+ ...MARCH_BASE_TOOL_NAMES,
36
+ ...customToolNames.filter((name) => name !== "read"),
37
+ ];
38
+
39
+ return {
40
+ model,
41
+ thinkingLevel: "medium",
42
+ customTools,
43
+ tools,
44
+ scopedModels: availableModels.map((model) => ({ model })),
45
+ };
46
+ }
@@ -0,0 +1 @@
1
+ export const MARCH_BASE_TOOL_NAMES = ["grep", "ls"];
@@ -0,0 +1,3 @@
1
+ export function toolText(text, details = {}) {
2
+ return { content: [{ type: "text", text }], details };
3
+ }
@@ -0,0 +1,54 @@
1
+ import { defineTool } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ import { createCommandExecTool } from "./command-exec-tool.mjs";
4
+ import { createContextStatsTool } from "./context-stats-tool.mjs";
5
+ import { createEditFileTool } from "./file-edit-tool.mjs";
6
+ import { createFindTool } from "./find-tool.mjs";
7
+ import { createReadFileTool } from "./read-file-tool.mjs";
8
+ import { toolText } from "./tool-result.mjs";
9
+ import { createShellTools } from "../shell/tools.mjs";
10
+ import { createWebTools } from "../web/tools.mjs";
11
+ import { initImageGen } from "../image-gen/index.mjs";
12
+ import { createSuperGrokTool } from "../supergrok/tool.mjs";
13
+ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shellRuntime = null, lspService = null, mcpTools = [], webTools = [], permissionController = null, authStorage = null, projectMarchDir = null }) {
14
+ const commandExecTool = createCommandExecTool({ cwd });
15
+ const contextStatsTool = createContextStatsTool({ engine });
16
+ const editFileTool = createEditFileTool({ engine, ui, lspService });
17
+ const findTool = createFindTool({ cwd });
18
+ const readFileTool = createReadFileTool({ engine });
19
+
20
+ const tools = [
21
+ readFileTool,
22
+ contextStatsTool,
23
+ commandExecTool,
24
+ editFileTool,
25
+ findTool,
26
+ ...createShellTools(shellRuntime),
27
+ ...memoryTools,
28
+ ...mcpTools,
29
+ ...webTools,
30
+ ...(authStorage ? [createSuperGrokTool({ authStorage, projectMarchDir })] : []),
31
+ ...(authStorage ? initImageGen({ authStorage, projectMarchDir }) : []),
32
+ ];
33
+
34
+ if (!permissionController) return tools;
35
+
36
+ return tools.map((tool) => {
37
+ const execute = tool.execute;
38
+ if (!execute) return tool;
39
+ const wrapped = async (toolCallId, params) => {
40
+ const decision = await permissionController.requestApproval(
41
+ tool.name,
42
+ params,
43
+ ui.requestPermission
44
+ ? (ctx) => ui.requestPermission(ctx)
45
+ : null,
46
+ );
47
+ if (decision.behavior === "deny") {
48
+ return toolText(`Permission denied: ${decision.message}`, { error: true, permissionDenied: true });
49
+ }
50
+ return execute(toolCallId, params);
51
+ };
52
+ return { ...tool, execute: wrapped };
53
+ });
54
+ }
@@ -0,0 +1,64 @@
1
+ export function createTurnEventState() {
2
+ return {
3
+ draft: "",
4
+ thinkingText: "",
5
+ thinkingAccumulator: "",
6
+ assistantReplyOpen: false,
7
+ };
8
+ }
9
+
10
+ export function handleRunnerSessionEvent(event, { ui, engine, state }) {
11
+ if (event.type === "message_update" && event.assistantMessageEvent) {
12
+ handleAssistantMessageEvent(event.assistantMessageEvent, { ui, state });
13
+ }
14
+ if (event.type === "tool_execution_start") {
15
+ closeAssistantReply({ ui, state });
16
+ ui.toolStart(event.toolName, event.args);
17
+ }
18
+ if (event.type === "tool_execution_end") {
19
+ ui.toolEnd(event.toolName, event.isError, event.result);
20
+ }
21
+ if (event.type === "auto_retry_start") {
22
+ ui.retryStart?.({
23
+ attempt: event.attempt,
24
+ maxAttempts: event.maxAttempts,
25
+ delayMs: event.delayMs,
26
+ errorMessage: event.errorMessage,
27
+ });
28
+ }
29
+ if (event.type === "auto_retry_end") {
30
+ ui.retryEnd?.({
31
+ success: event.success,
32
+ attempt: event.attempt,
33
+ finalError: event.finalError,
34
+ });
35
+ }
36
+ }
37
+
38
+ export function closeAssistantReply({ ui, state }) {
39
+ if (!state.assistantReplyOpen) return;
40
+ ui.assistantReplyEnd?.();
41
+ state.assistantReplyOpen = false;
42
+ }
43
+
44
+ function handleAssistantMessageEvent(event, { ui, state }) {
45
+ if (event.type === "text_delta") {
46
+ state.draft += event.delta;
47
+ state.assistantReplyOpen = true;
48
+ ui.textDelta(event.delta);
49
+ }
50
+ if (event.type === "thinking_start") {
51
+ state.thinkingText = "";
52
+ ui.thinkingStart();
53
+ }
54
+ if (event.type === "thinking_delta") {
55
+ state.thinkingText += event.delta;
56
+ ui.thinkingDelta(event.delta);
57
+ }
58
+ if (event.type === "thinking_end" && state.thinkingText) {
59
+ const tokens = Math.round(state.thinkingText.length / 4);
60
+ ui.thinkingEnd(tokens);
61
+ state.thinkingAccumulator += state.thinkingText;
62
+ state.thinkingText = "";
63
+ }
64
+ }