march-cli 0.1.37 → 0.1.38
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/runner/runner-utils.mjs +20 -0
- package/src/agent/runner.mjs +9 -10
- package/src/agent/runtime/remote-ui-client.mjs +0 -1
- package/src/agent/runtime/runner-process-client.mjs +2 -0
- package/src/agent/runtime/runner-process-factory.mjs +2 -3
- package/src/agent/runtime/runner-runtime-host.mjs +0 -2
- package/src/agent/runtime/ui-event-bridge.mjs +0 -2
- package/src/agent/session/session-options.mjs +1 -2
- package/src/agent/tools.mjs +2 -23
- package/src/cli/args.mjs +0 -3
- package/src/cli/commands/registry/slash-command-registry.mjs +2 -0
- package/src/cli/fallback-ui.mjs +0 -2
- package/src/cli/input/mode-state.mjs +1 -1
- package/src/cli/repl-loop.mjs +67 -19
- package/src/cli/startup/app-runtime.mjs +69 -4
- package/src/cli/startup/create-runtime-runner.mjs +2 -1
- package/src/cli/startup/startup-session.mjs +3 -13
- package/src/cli/ui.mjs +0 -6
- package/src/cli/workspace/command.mjs +147 -0
- package/src/cli/workspace/output-router.mjs +108 -0
- package/src/cli/workspace/project-runtime.mjs +92 -0
- package/src/config/features.mjs +0 -1
- package/src/extensions/lifecycle-adapter.mjs +1 -1
- package/src/main.mjs +4 -0
- package/src/notification/desktop-notifier.mjs +16 -8
- package/src/web-ui/dist/assets/index-BQtl1uQs.css +1 -0
- package/src/web-ui/dist/assets/index-DrlJis_D.js +1845 -0
- package/src/web-ui/dist/index.html +13 -0
- package/src/web-ui/runtime-host.mjs +1 -2
- package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +2 -10
- package/src/web-ui/src/mockData.ts +1 -8
- package/src/web-ui/src/model.ts +0 -2
- package/src/web-ui/src/runtime/client.ts +0 -1
- package/src/web-ui/src/runtime/runtimeTimeline.ts +1 -3
- package/src/web-ui/src/styles/shell.css +1 -2
- package/src/web-ui/src/timelineAdapter.ts +1 -2
- package/src/workspace/project-id.mjs +14 -0
- package/src/workspace/project-registry.mjs +74 -0
- package/src/workspace/session-index.mjs +75 -0
- package/src/workspace/supervisor.mjs +172 -0
- package/src/cli/permissions.mjs +0 -103
- package/src/cli/tui/permission-request-ui.mjs +0 -18
package/package.json
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { installCodexTransportCompression } from "./codex-transport-compression.mjs";
|
|
2
|
+
import { installCodexWebSocketEventDebug } from "./codex-websocket-event-debug.mjs";
|
|
3
|
+
import { installCodexLargeContextGuard } from "./codex-large-context-guard.mjs";
|
|
4
|
+
|
|
5
|
+
export function installRunnerProcessGuards() {
|
|
6
|
+
installCodexLargeContextGuard();
|
|
7
|
+
installCodexTransportCompression();
|
|
8
|
+
installCodexWebSocketEventDebug();
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
export function providerContextToPayload(providerContext) {
|
|
2
12
|
return {
|
|
3
13
|
messages: [
|
|
@@ -22,3 +32,13 @@ export function notifyTurnEndDetached(turnNotifier, event, onResult = () => {})
|
|
|
22
32
|
pending.then(onResult, () => {});
|
|
23
33
|
return pending;
|
|
24
34
|
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export function buildNotificationActivation({ notificationContext, sessionStats }) {
|
|
38
|
+
if (!notificationContext?.projectId) return null;
|
|
39
|
+
return {
|
|
40
|
+
type: "workspace-session",
|
|
41
|
+
projectId: notificationContext.projectId,
|
|
42
|
+
sessionId: sessionStats?.sessionId ?? null,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/src/agent/runner.mjs
CHANGED
|
@@ -11,11 +11,9 @@ import { runRunnerCleanup } from "./runner/runner-cleanup.mjs";
|
|
|
11
11
|
import { createRunnerRuntimeHost } from "./runtime/runner-runtime-host.mjs";
|
|
12
12
|
import { createRuntimeUiBridge } from "./runtime/ui-event-bridge.mjs";
|
|
13
13
|
import { getRunnerSessionStats, syncEngineSessionState } from "./runner/runner-session-state.mjs";
|
|
14
|
-
import { notifyTurnEndBestEffort, notifyTurnEndDetached, providerContextToPayload } from "./runner/runner-utils.mjs";
|
|
14
|
+
import { buildNotificationActivation, installRunnerProcessGuards, notifyTurnEndBestEffort, notifyTurnEndDetached, providerContextToPayload } from "./runner/runner-utils.mjs";
|
|
15
15
|
import { dumpCodexTransportDebug, getCodexTransportDebugSnapshot } from "./runner/codex-transport-debug.mjs";
|
|
16
|
-
import {
|
|
17
|
-
import { installCodexTransportCompression } from "./runner/codex-transport-compression.mjs";
|
|
18
|
-
import { applyCodexLargeContextGuardToPayload, installCodexLargeContextGuard } from "./runner/codex-large-context-guard.mjs";
|
|
16
|
+
import { applyCodexLargeContextGuardToPayload } from "./runner/codex-large-context-guard.mjs";
|
|
19
17
|
import { resolveRunnerSessionOptions } from "./session/session-options.mjs";
|
|
20
18
|
import { createSessionBinding } from "./session/session-binding.mjs";
|
|
21
19
|
import { maybeAutoNameSession } from "./session/session-auto-name.mjs";
|
|
@@ -32,10 +30,8 @@ import { appendRunnerTurnHistory, createRunnerHistoryStore } from "../history/ru
|
|
|
32
30
|
export { MARCH_BASE_TOOL_NAMES, installModelPayloadDumper };
|
|
33
31
|
export { createDefaultSessionManager, resolveRunnerSessionManager } from "./runner/runner-init.mjs";
|
|
34
32
|
export { getRunnerSessionStats, syncEngineSessionState } from "./runner/runner-session-state.mjs";
|
|
35
|
-
export async function createRunner({ cwd, modelId = null, provider = null, providers = {}, stateRoot, ui, memoryRoot = null, profilePaths = null, memoryStore = null, memoryTools = [], remoteMemorySources = [], shellRuntime = null, mcpTools = [], mcpInjections = [], mcpClientManager = null, webTools = [], namespace = "", sessionManager = null, useRuntimeHost = false, projectMarchDir = null, syncPiSidecar = false, extensionPaths = [], lifecycleHooks = [], lifecycleDiagnostics = [], authStorage = null,
|
|
36
|
-
|
|
37
|
-
installCodexTransportCompression();
|
|
38
|
-
installCodexWebSocketEventDebug();
|
|
33
|
+
export async function createRunner({ cwd, modelId = null, provider = null, providers = {}, stateRoot, ui, memoryRoot = null, profilePaths = null, memoryStore = null, memoryTools = [], remoteMemorySources = [], shellRuntime = null, mcpTools = [], mcpInjections = [], mcpClientManager = null, webTools = [], namespace = "", sessionManager = null, useRuntimeHost = false, projectMarchDir = null, syncPiSidecar = false, extensionPaths = [], lifecycleHooks = [], lifecycleDiagnostics = [], authStorage = null, modelContextDumper = null, turnNotifier = null, logger = null, onModelPayload = null, onLspStatusChange = null, createAgentSessionImpl = createAgentSession, createAgentSessionRuntimeImpl, createRuntimeServices, createRuntimeSessionFromServices, maxTurns, trimBatch, serviceTier = null, hostedTools = {}, notificationContext = null }) {
|
|
34
|
+
installRunnerProcessGuards();
|
|
39
35
|
if (!useRuntimeHost && extensionPaths.length > 0) throw new Error("--extension requires the default pi runtime host path");
|
|
40
36
|
const authConfig = authStorage ? { authStorage, hasAuth: true } : createMarchAuthStorage({ provider: provider ?? "deepseek", providers, cwd });
|
|
41
37
|
if (!authConfig.hasAuth) throw new Error("No providers configured. Run: march provider --config");
|
|
@@ -71,7 +67,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
71
67
|
sessionManager: resolvedSessionManager, sessionBinding, engine, ui: runtimeUi,
|
|
72
68
|
projectMarchDir,
|
|
73
69
|
memoryTools, memoryStore, historyStore, shellRuntime, lspService, mcpTools, webTools,
|
|
74
|
-
lifecycle,
|
|
70
|
+
lifecycle, extensionPaths, hostedTools,
|
|
75
71
|
onRebind: (session) => {
|
|
76
72
|
installModelPayloadDumper(session, modelContextDumper, () => currentModelCallKind, onLoggedModelPayload, injectMarchSystemContext);
|
|
77
73
|
syncEngineSessionState(engine, session);
|
|
@@ -83,7 +79,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
83
79
|
} else {
|
|
84
80
|
const sessionOptions = resolveRunnerSessionOptions({
|
|
85
81
|
cwd, stateRoot, provider, modelId, modelRegistry, engine, ui: runtimeUi,
|
|
86
|
-
memoryTools, historyStore, shellRuntime, lspService, mcpTools, webTools, lifecycle,
|
|
82
|
+
memoryTools, historyStore, shellRuntime, lspService, mcpTools, webTools, lifecycle,
|
|
87
83
|
authStorage: resolvedAuth, projectMarchDir,
|
|
88
84
|
getCurrentModel: () => sessionBinding.get()?.model ?? selectedModel,
|
|
89
85
|
});
|
|
@@ -139,6 +135,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
139
135
|
sessionName: engine.sessionName,
|
|
140
136
|
draft: result?.draft ?? "",
|
|
141
137
|
durationMs: Date.now() - turnStartedAt,
|
|
138
|
+
activation: buildNotificationActivation({ notificationContext, sessionStats: getRunnerSessionStats(sessionBinding.get(), runtimeHost) }),
|
|
142
139
|
}, (notificationResult) => { lastNotificationResult = notificationResult; });
|
|
143
140
|
const lifecycleAction = lifecycle.takePendingAction();
|
|
144
141
|
if (lifecycleAction) result.lifecycleAction = lifecycleAction;
|
|
@@ -150,6 +147,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
150
147
|
sessionName: engine.sessionName,
|
|
151
148
|
errorMessage: err?.message ?? String(err),
|
|
152
149
|
durationMs: Date.now() - turnStartedAt,
|
|
150
|
+
activation: buildNotificationActivation({ notificationContext, sessionStats: getRunnerSessionStats(sessionBinding.get(), runtimeHost) }),
|
|
153
151
|
}, (notificationResult) => { lastNotificationResult = notificationResult; });
|
|
154
152
|
turnLog.endError(err);
|
|
155
153
|
throw err;
|
|
@@ -257,6 +255,7 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
257
255
|
]);
|
|
258
256
|
},
|
|
259
257
|
};
|
|
258
|
+
return runner;
|
|
260
259
|
function syncCurrentPiSidecar() {
|
|
261
260
|
return syncPiSessionSidecar({
|
|
262
261
|
enabled: syncPiSidecar, projectMarchDir, engine,
|
|
@@ -15,6 +15,5 @@ export function createRemoteRuntimeUiClient(peer) {
|
|
|
15
15
|
debugLines: (lines) => peer.notify("uiEvent", { type: "debug_lines", lines }),
|
|
16
16
|
recall: ({ source, hints }) => peer.notify("uiEvent", { type: "recall", source, hints }),
|
|
17
17
|
editDiff: (path, diffLines) => peer.notify("uiEvent", { type: "edit_diff", path, diffLines }),
|
|
18
|
-
requestPermission: (request) => peer.call("uiRequest", { type: "permission_request", ...request }),
|
|
19
18
|
};
|
|
20
19
|
}
|
|
@@ -11,6 +11,7 @@ export async function createRunnerProcessClient({
|
|
|
11
11
|
ui,
|
|
12
12
|
onModelPayload = null,
|
|
13
13
|
onLspStatusChange = null,
|
|
14
|
+
onNotificationActivation = null,
|
|
14
15
|
entry = fileURLToPath(DEFAULT_ENTRY),
|
|
15
16
|
forkImpl = fork,
|
|
16
17
|
timeoutMs = 0,
|
|
@@ -51,6 +52,7 @@ export async function createRunnerProcessClient({
|
|
|
51
52
|
await active?.runner?.refreshState?.();
|
|
52
53
|
onLspStatusChange?.(event);
|
|
53
54
|
},
|
|
55
|
+
notificationActivation: (activation) => onNotificationActivation?.(activation),
|
|
54
56
|
},
|
|
55
57
|
timeoutMs,
|
|
56
58
|
});
|
|
@@ -8,7 +8,6 @@ import { createMarkdownMemoryTools } from "../../memory/markdown-tools.mjs";
|
|
|
8
8
|
import { normalizeRemoteMemorySources } from "../../memory/remote/config.mjs";
|
|
9
9
|
import { initializeMcp } from "../../mcp/index.mjs";
|
|
10
10
|
import { createWebToolsFromConfig } from "../../web/tools.mjs";
|
|
11
|
-
import { createPermissionController } from "../../cli/permissions.mjs";
|
|
12
11
|
import { resolvePiSessionManager } from "../../session/pi-manager.mjs";
|
|
13
12
|
import { createModelContextDumper } from "../../debug/model-context-dumper.mjs";
|
|
14
13
|
import { createLogger, installProcessLogHandlers } from "../../debug/logger.mjs";
|
|
@@ -23,7 +22,6 @@ const DEFAULT_DEPS = {
|
|
|
23
22
|
createMarkdownMemoryTools,
|
|
24
23
|
initializeMcp,
|
|
25
24
|
createWebToolsFromConfig,
|
|
26
|
-
createPermissionController,
|
|
27
25
|
resolvePiSessionManager,
|
|
28
26
|
createModelContextDumper,
|
|
29
27
|
createMarchAuthStorage,
|
|
@@ -85,11 +83,12 @@ export async function createIsolatedRunner(options = {}, deps = {}) {
|
|
|
85
83
|
maxTurns: options.config?.maxTurns ?? undefined,
|
|
86
84
|
trimBatch: options.config?.trimBatch ?? undefined,
|
|
87
85
|
hostedTools: options.config?.hostedTools,
|
|
88
|
-
|
|
86
|
+
notificationContext: options.notificationContext,
|
|
89
87
|
modelContextDumper: d.createModelContextDumper(options.modelContextDumper ?? { enabled: false }),
|
|
90
88
|
turnNotifier: d.createDesktopTurnNotifier({
|
|
91
89
|
enabled: Boolean(options.config?.notifications?.turnEnd),
|
|
92
90
|
config: options.config?.notifications,
|
|
91
|
+
onActivation: (activation) => d.peer.notify("notificationActivation", activation),
|
|
93
92
|
}),
|
|
94
93
|
logger,
|
|
95
94
|
onModelPayload: (event) => d.peer.notify("modelPayload", pickModelPayloadEvent(event)),
|
|
@@ -26,7 +26,6 @@ export async function createRunnerRuntimeHost({
|
|
|
26
26
|
mcpTools = [],
|
|
27
27
|
webTools = [],
|
|
28
28
|
lifecycle = null,
|
|
29
|
-
permissionController = null,
|
|
30
29
|
extensionPaths = [],
|
|
31
30
|
hostedTools = {},
|
|
32
31
|
onRebind = null,
|
|
@@ -63,7 +62,6 @@ export async function createRunnerRuntimeHost({
|
|
|
63
62
|
mcpTools,
|
|
64
63
|
webTools,
|
|
65
64
|
lifecycle,
|
|
66
|
-
permissionController,
|
|
67
65
|
authStorage,
|
|
68
66
|
projectMarchDir,
|
|
69
67
|
hostedTools,
|
|
@@ -53,7 +53,6 @@ export function createRuntimeUiClient(eventBus) {
|
|
|
53
53
|
recall: ({ source, hints }) => eventBus.emit({ type: "recall", source, hints }),
|
|
54
54
|
providerQuotaSnapshot: (snapshot) => eventBus.emit({ type: "provider_quota_snapshot", snapshot }),
|
|
55
55
|
editDiff: (path, diffLines) => eventBus.emit({ type: "edit_diff", path, diffLines }),
|
|
56
|
-
requestPermission: (request) => eventBus.request({ type: "permission_request", ...request }),
|
|
57
56
|
};
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -75,7 +74,6 @@ export function dispatchRuntimeUiEvent(ui, event) {
|
|
|
75
74
|
case "recall": return ui.recall?.({ source: event.source, hints: event.hints });
|
|
76
75
|
case "provider_quota_snapshot": return ui.providerQuotaSnapshot?.(event.snapshot);
|
|
77
76
|
case "edit_diff": return ui.editDiff?.(event.path, event.diffLines);
|
|
78
|
-
case "permission_request": return ui.requestPermission?.({ toolName: event.toolName, params: event.params, category: event.category });
|
|
79
77
|
default: return undefined;
|
|
80
78
|
}
|
|
81
79
|
}
|
|
@@ -16,7 +16,6 @@ export function resolveRunnerSessionOptions({
|
|
|
16
16
|
mcpTools = [],
|
|
17
17
|
webTools = [],
|
|
18
18
|
lifecycle = null,
|
|
19
|
-
permissionController = null,
|
|
20
19
|
authStorage = null,
|
|
21
20
|
projectMarchDir = null,
|
|
22
21
|
stateRoot = null,
|
|
@@ -32,7 +31,7 @@ export function resolveRunnerSessionOptions({
|
|
|
32
31
|
?? (provider && modelId ? getModel(provider, modelId) : null);
|
|
33
32
|
if (!model) throw new Error(`Model not found: ${provider}/${modelId}`);
|
|
34
33
|
|
|
35
|
-
const customTools = createMarchCustomTools({ cwd, engine, ui, memoryTools, historyStore, shellRuntime, lspService, mcpTools, webTools, lifecycle,
|
|
34
|
+
const customTools = createMarchCustomTools({ cwd, engine, ui, memoryTools, historyStore, shellRuntime, lspService, mcpTools, webTools, lifecycle, authStorage, projectMarchDir, stateRoot, getCurrentModel: () => getCurrentModel?.() ?? model });
|
|
36
35
|
const customToolNames = customTools.map((tool) => tool.name);
|
|
37
36
|
const tools = [
|
|
38
37
|
...customToolNames.filter((name) => name === "read"),
|
package/src/agent/tools.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import { createReadImageTool } from "./file-tools/read-image-tool.mjs";
|
|
|
7
7
|
import { createSendBinaryTool } from "./output/send-binary-tool.mjs";
|
|
8
8
|
import { createScreenTool } from "./screen-tools/screen-tool.mjs";
|
|
9
9
|
import { createListWindowsTool } from "./screen-tools/list-windows-tool.mjs";
|
|
10
|
-
import { toolText } from "./tool-result.mjs";
|
|
11
10
|
import { createShellTools } from "../shell/tools.mjs";
|
|
12
11
|
import { initImageGen } from "../image-gen/index.mjs";
|
|
13
12
|
import { createSuperGrokTool } from "../supergrok/tool.mjs";
|
|
@@ -15,7 +14,7 @@ import { createBrowserTools } from "../browser/tools/index.mjs";
|
|
|
15
14
|
import { createRuntimeRestartTool } from "./lifecycle/runtime-restart-tool.mjs";
|
|
16
15
|
import { createHistorySearchTool } from "../history/tool.mjs";
|
|
17
16
|
|
|
18
|
-
export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], historyStore = null, shellRuntime = null, lspService = null, mcpTools = [], webTools = [], lifecycle = null,
|
|
17
|
+
export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], historyStore = null, shellRuntime = null, lspService = null, mcpTools = [], webTools = [], lifecycle = null, authStorage = null, projectMarchDir = null, stateRoot = null, getCurrentModel = null }) {
|
|
19
18
|
const commandExecTool = createCommandExecTool({ cwd });
|
|
20
19
|
const codeSearchTool = createCodeSearchTool({ engine, stateRoot });
|
|
21
20
|
const contextStatsTool = createContextStatsTool({ engine });
|
|
@@ -47,25 +46,5 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], hist
|
|
|
47
46
|
...(authStorage ? [createSuperGrokTool({ authStorage, projectMarchDir })] : []),
|
|
48
47
|
...(authStorage ? initImageGen({ authStorage, projectMarchDir }) : []),
|
|
49
48
|
];
|
|
50
|
-
|
|
51
|
-
if (!permissionController) return tools;
|
|
52
|
-
|
|
53
|
-
return tools.map((tool) => {
|
|
54
|
-
const execute = tool.execute;
|
|
55
|
-
if (!execute) return tool;
|
|
56
|
-
const wrapped = async (toolCallId, params, signal, onUpdate) => {
|
|
57
|
-
const decision = await permissionController.requestApproval(
|
|
58
|
-
tool.name,
|
|
59
|
-
params,
|
|
60
|
-
ui.requestPermission
|
|
61
|
-
? (ctx) => ui.requestPermission(ctx)
|
|
62
|
-
: null,
|
|
63
|
-
);
|
|
64
|
-
if (decision.behavior === "deny") {
|
|
65
|
-
return toolText(`Permission denied: ${decision.message}`, { error: true, permissionDenied: true });
|
|
66
|
-
}
|
|
67
|
-
return execute(toolCallId, params, signal, onUpdate);
|
|
68
|
-
};
|
|
69
|
-
return { ...tool, execute: wrapped };
|
|
70
|
-
});
|
|
49
|
+
return tools;
|
|
71
50
|
}
|
package/src/cli/args.mjs
CHANGED
|
@@ -17,7 +17,6 @@ export function parseCliArgs(argv) {
|
|
|
17
17
|
"pi-runtime-host": { type: "boolean" },
|
|
18
18
|
"shell-runtime": { type: "boolean" },
|
|
19
19
|
"no-shell-runtime": { type: "boolean" },
|
|
20
|
-
"permission-mode": { type: "string" },
|
|
21
20
|
host: { type: "string" },
|
|
22
21
|
port: { type: "string" },
|
|
23
22
|
"api-port": { type: "string" },
|
|
@@ -47,7 +46,6 @@ export function parseCliArgs(argv) {
|
|
|
47
46
|
piSessions: values["pi-sessions"] ?? false,
|
|
48
47
|
piRuntimeHost: values["pi-runtime-host"] ?? false,
|
|
49
48
|
shellRuntime: values["no-shell-runtime"] ? false : true,
|
|
50
|
-
permissionMode: values["permission-mode"] ?? "bypassPermissions",
|
|
51
49
|
host: values.host ?? null,
|
|
52
50
|
port: values.port ?? null,
|
|
53
51
|
apiPort: values["api-port"] ?? null,
|
|
@@ -97,7 +95,6 @@ Options:
|
|
|
97
95
|
--pi-runtime-host Force pi AgentSessionRuntime host path
|
|
98
96
|
--shell-runtime Enable interactive PTY shell tools (default)
|
|
99
97
|
--no-shell-runtime Disable interactive PTY shell tools and shell pane
|
|
100
|
-
--permission-mode <mode> Permission mode: default, bypassPermissions, dontAsk (default: bypassPermissions)
|
|
101
98
|
-e, --extension <path>
|
|
102
99
|
Load a pi extension path in the default runtime host (repeatable)
|
|
103
100
|
--host <host> With memory serve/web, bind host (default: 127.0.0.1)
|
|
@@ -12,6 +12,7 @@ import { handleSessionNameCommand, parseSessionNameCommand } from "../../session
|
|
|
12
12
|
import { handleShellCommand, parseShellCommand } from "../../shell/shell-command.mjs";
|
|
13
13
|
import { handleProviderCommand, parseProviderCommand } from "../provider-command.mjs";
|
|
14
14
|
import { handleModeCommand, parseModeCommand } from "../mode-command.mjs";
|
|
15
|
+
import { WORKSPACE_SLASH_COMMANDS } from "../../workspace/command.mjs";
|
|
15
16
|
|
|
16
17
|
export const SLASH_COMMANDS = [
|
|
17
18
|
exactCommand({
|
|
@@ -94,6 +95,7 @@ export const SLASH_COMMANDS = [
|
|
|
94
95
|
parse: parseThinkingCommand,
|
|
95
96
|
run: async (ctx, command) => writeLines(ctx.ui, await handleThinkingCommand(command, { runner: ctx.runner, ui: ctx.ui })),
|
|
96
97
|
}),
|
|
98
|
+
...WORKSPACE_SLASH_COMMANDS,
|
|
97
99
|
exactCommand({
|
|
98
100
|
name: "status",
|
|
99
101
|
description: "Show runtime status",
|
package/src/cli/fallback-ui.mjs
CHANGED
|
@@ -48,7 +48,6 @@ export function createJsonUI() {
|
|
|
48
48
|
editDiff: (path, diffLines) => {
|
|
49
49
|
stdout.write(JSON.stringify({ type: "edit_diff", path, diff: diffLines }) + "\n");
|
|
50
50
|
},
|
|
51
|
-
requestPermission: async () => true,
|
|
52
51
|
setEscapeHandler: () => {},
|
|
53
52
|
setCtrlCHandler: () => {},
|
|
54
53
|
setShiftTabHandler: () => {},
|
|
@@ -137,7 +136,6 @@ export function createPlainUI() {
|
|
|
137
136
|
else stdout.write(`${dim(` ${d.text}`)}\n`);
|
|
138
137
|
}
|
|
139
138
|
},
|
|
140
|
-
requestPermission: async () => true,
|
|
141
139
|
setEscapeHandler: () => {},
|
|
142
140
|
setCtrlCHandler: () => {},
|
|
143
141
|
setShiftTabHandler: () => {},
|
|
@@ -34,7 +34,7 @@ export function formatModeReminder(mode = MODES.DO) {
|
|
|
34
34
|
"</mode>";
|
|
35
35
|
}
|
|
36
36
|
return "<mode>\n" +
|
|
37
|
-
"You are in do mode. You may implement changes when the user asks for execution, following normal
|
|
37
|
+
"You are in do mode. You may implement changes when the user asks for execution, following normal project rules.\n" +
|
|
38
38
|
"</mode>";
|
|
39
39
|
}
|
|
40
40
|
|
package/src/cli/repl-loop.mjs
CHANGED
|
@@ -37,6 +37,10 @@ export async function runInteractiveRepl({
|
|
|
37
37
|
runner,
|
|
38
38
|
memoryStore,
|
|
39
39
|
currentProject,
|
|
40
|
+
currentProjectInfo = null,
|
|
41
|
+
workspaceSupervisor = null,
|
|
42
|
+
workspaceOutputRouter = null,
|
|
43
|
+
stateRoot = null,
|
|
40
44
|
sessionState,
|
|
41
45
|
sessionsRoot,
|
|
42
46
|
projectMarchDir,
|
|
@@ -58,7 +62,8 @@ export async function runInteractiveRepl({
|
|
|
58
62
|
let trimmed = line.trim();
|
|
59
63
|
if (!trimmed) continue;
|
|
60
64
|
|
|
61
|
-
const
|
|
65
|
+
const active = getActiveRuntime({ workspaceSupervisor, cwd, runner, memoryStore, currentProject, currentProjectInfo, sessionState, sessionsRoot, projectMarchDir, extensionPaths, keybindingConfig, promptTemplateConfig });
|
|
66
|
+
const handledInline = handleInlineCommand(trimmed, { cwd: active.cwd, ui, lastInlineShellCommand });
|
|
62
67
|
if (handledInline.type === "handled") {
|
|
63
68
|
lastInlineShellCommand = handledInline.lastInlineShellCommand;
|
|
64
69
|
continue;
|
|
@@ -67,45 +72,70 @@ export async function runInteractiveRepl({
|
|
|
67
72
|
|
|
68
73
|
const slashResult = await handleSlashCommand(trimmed, {
|
|
69
74
|
ui,
|
|
70
|
-
runner,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
runner: active.runner,
|
|
76
|
+
workspaceSupervisor,
|
|
77
|
+
workspaceOutputRouter,
|
|
78
|
+
sessionState: active.sessionState,
|
|
79
|
+
sessionsRoot: active.sessionsRoot,
|
|
80
|
+
projectMarchDir: active.projectMarchDir,
|
|
74
81
|
sessionSource,
|
|
75
|
-
extensionPaths,
|
|
76
|
-
keybindings: keybindingConfig.keybindings,
|
|
77
|
-
keybindingDiagnostics: keybindingConfig.diagnostics,
|
|
78
|
-
promptTemplates: promptTemplateConfig.templates,
|
|
79
|
-
promptTemplateDiagnostics: promptTemplateConfig.diagnostics,
|
|
82
|
+
extensionPaths: active.extensionPaths,
|
|
83
|
+
keybindings: active.keybindingConfig.keybindings,
|
|
84
|
+
keybindingDiagnostics: active.keybindingConfig.diagnostics,
|
|
85
|
+
promptTemplates: active.promptTemplateConfig.templates,
|
|
86
|
+
promptTemplateDiagnostics: active.promptTemplateConfig.diagnostics,
|
|
80
87
|
modeState,
|
|
81
88
|
renderStartupBanner,
|
|
82
89
|
configHomeDir,
|
|
90
|
+
stateRoot,
|
|
91
|
+
currentProjectId: active.project?.projectId ?? null,
|
|
83
92
|
});
|
|
84
93
|
if (slashResult.exit) break;
|
|
85
94
|
if (slashResult.handled) {
|
|
86
|
-
|
|
95
|
+
const refreshedActive = getActiveRuntime({ workspaceSupervisor, cwd, runner, memoryStore, currentProject, currentProjectInfo, sessionState, sessionsRoot, projectMarchDir, extensionPaths, keybindingConfig, promptTemplateConfig });
|
|
96
|
+
refreshStatusBar(contextTokenRefreshOptions(slashResult, refreshedActive.runner));
|
|
87
97
|
continue;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
const
|
|
100
|
+
const turnActive = getActiveRuntime({ workspaceSupervisor, cwd, runner, memoryStore, currentProject, currentProjectInfo, sessionState, sessionsRoot, projectMarchDir, extensionPaths, keybindingConfig, promptTemplateConfig });
|
|
101
|
+
const templateResult = expandPromptTemplate(trimmed, turnActive.promptTemplateConfig.templates);
|
|
91
102
|
if (templateResult.type === "template") {
|
|
92
103
|
ui.writeln(brightBlack(`● template: ${templateResult.name}`));
|
|
93
104
|
trimmed = templateResult.prompt;
|
|
94
105
|
}
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
if (turnActive.turnTask) {
|
|
108
|
+
ui.writeln("This session is still running. Use /switch to start or inspect another session.");
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
startReplTurn({
|
|
113
|
+
runtime: turnActive,
|
|
97
114
|
prompt: trimmed,
|
|
98
|
-
runner,
|
|
99
|
-
memoryStore,
|
|
100
|
-
currentProject,
|
|
101
115
|
ui,
|
|
102
116
|
refreshStatusBar,
|
|
103
117
|
setTurnRunning,
|
|
118
|
+
workspaceSupervisor,
|
|
104
119
|
modeState,
|
|
105
120
|
});
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
export function getActiveRuntime({ workspaceSupervisor, cwd, runner, memoryStore, currentProject, currentProjectInfo, sessionState, sessionsRoot, projectMarchDir, extensionPaths, keybindingConfig, promptTemplateConfig }) {
|
|
125
|
+
return workspaceSupervisor?.getActive?.() ?? {
|
|
126
|
+
project: currentProjectInfo,
|
|
127
|
+
cwd,
|
|
128
|
+
runner,
|
|
129
|
+
memoryStore,
|
|
130
|
+
currentProject,
|
|
131
|
+
sessionState,
|
|
132
|
+
sessionsRoot,
|
|
133
|
+
projectMarchDir,
|
|
134
|
+
extensionPaths,
|
|
135
|
+
keybindingConfig,
|
|
136
|
+
promptTemplateConfig,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
109
139
|
export function contextTokenRefreshOptions(slashResult, runner) {
|
|
110
140
|
if (!slashResult?.refreshContextTokens) return undefined;
|
|
111
141
|
if (typeof runner.estimateContextTokens !== "function") return undefined;
|
|
@@ -127,6 +157,27 @@ function handleInlineCommand(trimmed, { cwd, ui, lastInlineShellCommand }) {
|
|
|
127
157
|
return { type: "none" };
|
|
128
158
|
}
|
|
129
159
|
|
|
160
|
+
function startReplTurn({ runtime, prompt, ui, refreshStatusBar, setTurnRunning, workspaceSupervisor = null, modeState = null }) {
|
|
161
|
+
const turnUi = runtime.ui ?? ui;
|
|
162
|
+
const task = runReplTurn({
|
|
163
|
+
prompt,
|
|
164
|
+
runner: runtime.runner,
|
|
165
|
+
memoryStore: runtime.memoryStore,
|
|
166
|
+
currentProject: runtime.currentProject,
|
|
167
|
+
ui: turnUi,
|
|
168
|
+
refreshStatusBar,
|
|
169
|
+
setTurnRunning,
|
|
170
|
+
modeState,
|
|
171
|
+
}).finally(() => {
|
|
172
|
+
if (runtime.turnTask === task) runtime.turnTask = null;
|
|
173
|
+
const hasRunningTurn = Boolean(workspaceSupervisor?.hasRunningTurn?.());
|
|
174
|
+
setTurnRunning(hasRunningTurn);
|
|
175
|
+
if (!hasRunningTurn) refreshStatusBar.stopWorking?.();
|
|
176
|
+
refreshStatusBar();
|
|
177
|
+
});
|
|
178
|
+
runtime.turnTask = task;
|
|
179
|
+
}
|
|
180
|
+
|
|
130
181
|
async function runReplTurn({ prompt, runner, memoryStore, currentProject, ui, refreshStatusBar, setTurnRunning, modeState = null }) {
|
|
131
182
|
memoryStore.beginTurn();
|
|
132
183
|
try {
|
|
@@ -143,10 +194,7 @@ async function runReplTurn({ prompt, runner, memoryStore, currentProject, ui, re
|
|
|
143
194
|
} catch (err) {
|
|
144
195
|
ui.writeln(`Error: ${err.message}`);
|
|
145
196
|
} finally {
|
|
146
|
-
setTurnRunning(false);
|
|
147
|
-
refreshStatusBar.stopWorking?.();
|
|
148
197
|
memoryStore.endTurn();
|
|
149
|
-
refreshStatusBar();
|
|
150
198
|
}
|
|
151
199
|
}
|
|
152
200
|
|
|
@@ -19,6 +19,11 @@ import { defaultProfilePaths, ensureProfileFiles } from "../../context/profiles.
|
|
|
19
19
|
import { normalizeRemoteMemorySources } from "../../memory/remote/config.mjs";
|
|
20
20
|
import { resolveMemoryRoot } from "../../memory/root.mjs";
|
|
21
21
|
import { ensureBrowserDaemon } from "../../browser/client/lifecycle.mjs";
|
|
22
|
+
import { registerProject } from "../../workspace/project-registry.mjs";
|
|
23
|
+
import { listWorkspaceSessions } from "../../workspace/session-index.mjs";
|
|
24
|
+
import { createWorkspaceSessionSupervisor } from "../../workspace/supervisor.mjs";
|
|
25
|
+
import { createWorkspaceProjectRuntime } from "../workspace/project-runtime.mjs";
|
|
26
|
+
import { createWorkspaceOutputRouter } from "../workspace/output-router.mjs";
|
|
22
27
|
|
|
23
28
|
export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot } = {}) {
|
|
24
29
|
if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
|
|
@@ -56,6 +61,7 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
56
61
|
const inputHistoryStore = createInputHistoryStore({ path: join(projectMarchDir, "input-history.json") });
|
|
57
62
|
const modeState = createModeState();
|
|
58
63
|
const namespace = loadOrCreateProjectId(projectMarchDir);
|
|
64
|
+
const currentProjectInfo = registerProject({ stateRoot, rootPath: cwd });
|
|
59
65
|
const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
|
|
60
66
|
const profilePaths = defaultProfilePaths();
|
|
61
67
|
ensureProfileFiles(profilePaths);
|
|
@@ -64,7 +70,6 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
64
70
|
const currentProject = basename(cwd);
|
|
65
71
|
const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
|
|
66
72
|
|
|
67
|
-
const permissionMode = args.permissionMode;
|
|
68
73
|
const sessionSource = "pi";
|
|
69
74
|
const sessionsRoot = join(projectMarchDir, "sessions");
|
|
70
75
|
const sessionState = {
|
|
@@ -82,7 +87,8 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
82
87
|
shellRuntime,
|
|
83
88
|
historyStore: inputHistoryStore,
|
|
84
89
|
});
|
|
85
|
-
|
|
90
|
+
const outputRouter = createWorkspaceOutputRouter({ ui, activeProjectId: currentProjectInfo.projectId, activeSessionId: sessionState.sessionId });
|
|
91
|
+
const runtimeUi = outputRouter.createProjectUi(currentProjectInfo.projectId, () => sessionState.sessionId);
|
|
86
92
|
let turnRunning = false;
|
|
87
93
|
let refreshStatusBar = null;
|
|
88
94
|
const runnerOptions = {
|
|
@@ -98,21 +104,26 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
98
104
|
namespace,
|
|
99
105
|
projectMarchDir,
|
|
100
106
|
extensionPaths,
|
|
101
|
-
permissionMode,
|
|
102
107
|
shellRuntime: Boolean(shellRuntime),
|
|
103
108
|
lifecycleHooks: lifecycleManifests.hooks,
|
|
104
109
|
lifecycleDiagnostics: lifecycleManifests.diagnostics,
|
|
105
110
|
modelContextDumper: { enabled: args.dumpContext, rootDir: contextDumpRoot },
|
|
106
111
|
remoteMemorySources,
|
|
112
|
+
notificationContext: { projectId: currentProjectInfo.projectId },
|
|
107
113
|
};
|
|
108
114
|
|
|
109
115
|
let runner;
|
|
116
|
+
let workspaceSupervisor = null;
|
|
117
|
+
const onNotificationActivation = (activation) => {
|
|
118
|
+
handleNotificationActivation({ activation, stateRoot, workspaceSupervisor, outputRouter, ui }).catch((err) => ui.writeln(`Notification activation failed: ${err.message}`));
|
|
119
|
+
};
|
|
110
120
|
try {
|
|
111
121
|
runner = await createRuntimeRunner({
|
|
112
122
|
runnerOptions,
|
|
113
|
-
ui,
|
|
123
|
+
ui: runtimeUi,
|
|
114
124
|
shellRuntime,
|
|
115
125
|
refreshStatusBar: (...args) => refreshStatusBar?.(...args),
|
|
126
|
+
onNotificationActivation,
|
|
116
127
|
});
|
|
117
128
|
} catch (err) {
|
|
118
129
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
@@ -121,6 +132,42 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
121
132
|
return { ok: false, code: 1, logger };
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
const initialRuntime = {
|
|
136
|
+
project: currentProjectInfo,
|
|
137
|
+
cwd,
|
|
138
|
+
currentProject,
|
|
139
|
+
runner,
|
|
140
|
+
ui: runtimeUi,
|
|
141
|
+
memoryStore,
|
|
142
|
+
sessionState,
|
|
143
|
+
sessionsRoot,
|
|
144
|
+
projectMarchDir,
|
|
145
|
+
extensionPaths,
|
|
146
|
+
keybindingConfig,
|
|
147
|
+
promptTemplateConfig,
|
|
148
|
+
};
|
|
149
|
+
workspaceSupervisor = createWorkspaceSessionSupervisor({
|
|
150
|
+
initialRuntime,
|
|
151
|
+
createProjectRuntime: (project) => createWorkspaceProjectRuntime({
|
|
152
|
+
project,
|
|
153
|
+
args,
|
|
154
|
+
config,
|
|
155
|
+
stateRoot,
|
|
156
|
+
memoryRoot,
|
|
157
|
+
profilePaths,
|
|
158
|
+
createMemoryStore: () => new MarkdownMemoryStore({ root: memoryRoot }),
|
|
159
|
+
provider,
|
|
160
|
+
serviceTier,
|
|
161
|
+
model,
|
|
162
|
+
remoteMemorySources,
|
|
163
|
+
createUi: (runtimeSessionState) => outputRouter.createProjectUi(project.projectId, () => runtimeSessionState.sessionId),
|
|
164
|
+
refreshStatusBar: (...args) => refreshStatusBar?.(...args),
|
|
165
|
+
onNotificationActivation,
|
|
166
|
+
}),
|
|
167
|
+
onActivate: ({ projectId, sessionId }) => outputRouter.setActiveSession(projectId, sessionId),
|
|
168
|
+
});
|
|
169
|
+
runner = workspaceSupervisor.runner;
|
|
170
|
+
|
|
124
171
|
refreshStatusBar = createStatusLineUpdater({
|
|
125
172
|
ui,
|
|
126
173
|
runner,
|
|
@@ -150,6 +197,8 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
150
197
|
projectMarchDir,
|
|
151
198
|
ui,
|
|
152
199
|
});
|
|
200
|
+
workspaceSupervisor.refreshActiveRuntime();
|
|
201
|
+
outputRouter.setActiveSession(currentProjectInfo.projectId, sessionState.sessionId);
|
|
153
202
|
refreshStatusBar();
|
|
154
203
|
|
|
155
204
|
return {
|
|
@@ -158,8 +207,11 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
158
207
|
cwd,
|
|
159
208
|
ui,
|
|
160
209
|
runner,
|
|
210
|
+
workspaceSupervisor,
|
|
211
|
+
workspaceOutputRouter: outputRouter,
|
|
161
212
|
memoryStore,
|
|
162
213
|
currentProject,
|
|
214
|
+
currentProjectInfo,
|
|
163
215
|
sessionState,
|
|
164
216
|
sessionsRoot,
|
|
165
217
|
projectMarchDir,
|
|
@@ -175,3 +227,16 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot }
|
|
|
175
227
|
setTurnRunning(value) { turnRunning = value; },
|
|
176
228
|
};
|
|
177
229
|
}
|
|
230
|
+
|
|
231
|
+
async function handleNotificationActivation({ activation, stateRoot, workspaceSupervisor, outputRouter, ui }) {
|
|
232
|
+
if (activation?.type !== "workspace-session" || !activation.projectId) return;
|
|
233
|
+
if (!workspaceSupervisor) throw new Error("workspace supervisor is not ready");
|
|
234
|
+
const projects = await listWorkspaceSessions({ stateRoot, currentProjectId: workspaceSupervisor.getActive?.()?.project?.projectId ?? null });
|
|
235
|
+
const runtime = await workspaceSupervisor.activateWorkspaceSessionById({
|
|
236
|
+
projects,
|
|
237
|
+
projectId: activation.projectId,
|
|
238
|
+
sessionId: activation.sessionId,
|
|
239
|
+
});
|
|
240
|
+
outputRouter?.replayBufferedCalls?.(activation.projectId, activation.sessionId);
|
|
241
|
+
ui.writeln(`Activated session from notification: ${runtime.project.displayName}`);
|
|
242
|
+
}
|
|
@@ -5,6 +5,7 @@ export async function createRuntimeRunner({
|
|
|
5
5
|
ui,
|
|
6
6
|
shellRuntime,
|
|
7
7
|
refreshStatusBar,
|
|
8
|
+
onNotificationActivation = null,
|
|
8
9
|
} = {}) {
|
|
9
10
|
const onModelPayload = ({ estimatedTokens }) => {
|
|
10
11
|
refreshStatusBar?.({ contextTokens: estimatedTokens });
|
|
@@ -13,7 +14,7 @@ export async function createRuntimeRunner({
|
|
|
13
14
|
refreshStatusBar?.();
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
const { runner } = await createRunnerProcessClient({ runnerOptions, ui, onModelPayload, onLspStatusChange });
|
|
17
|
+
const { runner } = await createRunnerProcessClient({ runnerOptions, ui, onModelPayload, onLspStatusChange, onNotificationActivation });
|
|
17
18
|
runner.shellRuntime ??= shellRuntime;
|
|
18
19
|
return runner;
|
|
19
20
|
}
|
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
1
|
+
import { loadOrCreateProjectId } from "../../workspace/project-id.mjs";
|
|
4
2
|
import { listPiSessionInfos } from "../../session/pi-manager.mjs";
|
|
3
|
+
|
|
5
4
|
import { loadPiSessionTranscriptTurns } from "../../session/transcript.mjs";
|
|
6
5
|
import { resumePiSessionById } from "../session/pi-session-switch-command.mjs";
|
|
7
6
|
|
|
8
|
-
export
|
|
9
|
-
if (!existsSync(projectMarchDir)) mkdirSync(projectMarchDir, { recursive: true });
|
|
10
|
-
const idFile = resolve(projectMarchDir, "project-id");
|
|
11
|
-
if (existsSync(idFile)) {
|
|
12
|
-
return readFileSync(idFile, "utf8").trim();
|
|
13
|
-
}
|
|
14
|
-
const id = randomUUID();
|
|
15
|
-
writeFileSync(idFile, id, "utf8");
|
|
16
|
-
return id;
|
|
17
|
-
}
|
|
7
|
+
export { loadOrCreateProjectId };
|
|
18
8
|
|
|
19
9
|
export async function resumeStartupSession({
|
|
20
10
|
resumeId,
|