march-cli 0.1.36 → 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/code-search/tool.mjs +1 -1
- package/src/agent/runner/runner-utils.mjs +20 -0
- package/src/agent/runner.mjs +16 -18
- package/src/agent/runtime/remote-ui-client.mjs +0 -1
- package/src/agent/runtime/runner-process-client.mjs +7 -0
- package/src/agent/runtime/runner-process-factory.mjs +7 -3
- package/src/agent/runtime/runner-runtime-host.mjs +2 -2
- package/src/agent/runtime/ui-event-bridge.mjs +0 -2
- package/src/agent/session/session-options.mjs +2 -2
- package/src/agent/tools.mjs +5 -23
- package/src/agent/turn/turn-events.mjs +41 -0
- package/src/agent/turn/turn-runner.mjs +5 -2
- 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/history-store.mjs +65 -3
- package/src/cli/input/mode-state.mjs +1 -1
- package/src/cli/repl-loop.mjs +75 -25
- package/src/cli/startup/app-runtime.mjs +72 -31
- package/src/cli/startup/create-runtime-runner.mjs +5 -46
- package/src/cli/startup/startup-session.mjs +3 -13
- 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 -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/context/engine.mjs +4 -2
- package/src/context/system-core/base.md +4 -1
- package/src/extensions/lifecycle-adapter.mjs +1 -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 +5 -2
- package/src/notification/desktop-notifier.mjs +16 -8
- package/src/web-ui/command.mjs +2 -2
- 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 +5 -25
- package/src/web-ui/session-manager.mjs +2 -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/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 });
|
|
@@ -36,6 +37,10 @@ export async function runInteractiveRepl({
|
|
|
36
37
|
runner,
|
|
37
38
|
memoryStore,
|
|
38
39
|
currentProject,
|
|
40
|
+
currentProjectInfo = null,
|
|
41
|
+
workspaceSupervisor = null,
|
|
42
|
+
workspaceOutputRouter = null,
|
|
43
|
+
stateRoot = null,
|
|
39
44
|
sessionState,
|
|
40
45
|
sessionsRoot,
|
|
41
46
|
projectMarchDir,
|
|
@@ -57,7 +62,8 @@ export async function runInteractiveRepl({
|
|
|
57
62
|
let trimmed = line.trim();
|
|
58
63
|
if (!trimmed) continue;
|
|
59
64
|
|
|
60
|
-
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 });
|
|
61
67
|
if (handledInline.type === "handled") {
|
|
62
68
|
lastInlineShellCommand = handledInline.lastInlineShellCommand;
|
|
63
69
|
continue;
|
|
@@ -66,45 +72,70 @@ export async function runInteractiveRepl({
|
|
|
66
72
|
|
|
67
73
|
const slashResult = await handleSlashCommand(trimmed, {
|
|
68
74
|
ui,
|
|
69
|
-
runner,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
runner: active.runner,
|
|
76
|
+
workspaceSupervisor,
|
|
77
|
+
workspaceOutputRouter,
|
|
78
|
+
sessionState: active.sessionState,
|
|
79
|
+
sessionsRoot: active.sessionsRoot,
|
|
80
|
+
projectMarchDir: active.projectMarchDir,
|
|
73
81
|
sessionSource,
|
|
74
|
-
extensionPaths,
|
|
75
|
-
keybindings: keybindingConfig.keybindings,
|
|
76
|
-
keybindingDiagnostics: keybindingConfig.diagnostics,
|
|
77
|
-
promptTemplates: promptTemplateConfig.templates,
|
|
78
|
-
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,
|
|
79
87
|
modeState,
|
|
80
88
|
renderStartupBanner,
|
|
81
89
|
configHomeDir,
|
|
90
|
+
stateRoot,
|
|
91
|
+
currentProjectId: active.project?.projectId ?? null,
|
|
82
92
|
});
|
|
83
93
|
if (slashResult.exit) break;
|
|
84
94
|
if (slashResult.handled) {
|
|
85
|
-
|
|
95
|
+
const refreshedActive = getActiveRuntime({ workspaceSupervisor, cwd, runner, memoryStore, currentProject, currentProjectInfo, sessionState, sessionsRoot, projectMarchDir, extensionPaths, keybindingConfig, promptTemplateConfig });
|
|
96
|
+
refreshStatusBar(contextTokenRefreshOptions(slashResult, refreshedActive.runner));
|
|
86
97
|
continue;
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
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);
|
|
90
102
|
if (templateResult.type === "template") {
|
|
91
103
|
ui.writeln(brightBlack(`● template: ${templateResult.name}`));
|
|
92
104
|
trimmed = templateResult.prompt;
|
|
93
105
|
}
|
|
94
106
|
|
|
95
|
-
|
|
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,
|
|
96
114
|
prompt: trimmed,
|
|
97
|
-
runner,
|
|
98
|
-
memoryStore,
|
|
99
|
-
currentProject,
|
|
100
115
|
ui,
|
|
101
116
|
refreshStatusBar,
|
|
102
117
|
setTurnRunning,
|
|
118
|
+
workspaceSupervisor,
|
|
103
119
|
modeState,
|
|
104
120
|
});
|
|
105
121
|
}
|
|
106
122
|
}
|
|
107
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
|
+
}
|
|
108
139
|
export function contextTokenRefreshOptions(slashResult, runner) {
|
|
109
140
|
if (!slashResult?.refreshContextTokens) return undefined;
|
|
110
141
|
if (typeof runner.estimateContextTokens !== "function") return undefined;
|
|
@@ -126,9 +157,31 @@ function handleInlineCommand(trimmed, { cwd, ui, lastInlineShellCommand }) {
|
|
|
126
157
|
return { type: "none" };
|
|
127
158
|
}
|
|
128
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
|
+
|
|
129
181
|
async function runReplTurn({ prompt, runner, memoryStore, currentProject, ui, refreshStatusBar, setTurnRunning, modeState = null }) {
|
|
130
|
-
|
|
182
|
+
memoryStore.beginTurn();
|
|
131
183
|
try {
|
|
184
|
+
const turnInput = prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState });
|
|
132
185
|
ui.writeln(turnInput.displayMessage);
|
|
133
186
|
ui.recall?.({ source: "user", hints: turnInput.userRecallHints });
|
|
134
187
|
if (turnInput.shouldRenderCarryoverRecall) ui.recall?.({ source: "assistant", hints: turnInput.carryoverRecallHints });
|
|
@@ -141,10 +194,7 @@ async function runReplTurn({ prompt, runner, memoryStore, currentProject, ui, re
|
|
|
141
194
|
} catch (err) {
|
|
142
195
|
ui.writeln(`Error: ${err.message}`);
|
|
143
196
|
} finally {
|
|
144
|
-
setTurnRunning(false);
|
|
145
|
-
refreshStatusBar.stopWorking?.();
|
|
146
197
|
memoryStore.endTurn();
|
|
147
|
-
refreshStatusBar();
|
|
148
198
|
}
|
|
149
199
|
}
|
|
150
200
|
|
|
@@ -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,21 @@ 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";
|
|
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";
|
|
28
27
|
|
|
29
|
-
export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot
|
|
28
|
+
export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot } = {}) {
|
|
30
29
|
if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
|
|
31
30
|
await ensureBrowserDaemon({ stateRoot }).catch(() => {});
|
|
32
31
|
|
|
@@ -62,28 +61,15 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
62
61
|
const inputHistoryStore = createInputHistoryStore({ path: join(projectMarchDir, "input-history.json") });
|
|
63
62
|
const modeState = createModeState();
|
|
64
63
|
const namespace = loadOrCreateProjectId(projectMarchDir);
|
|
64
|
+
const currentProjectInfo = registerProject({ stateRoot, rootPath: cwd });
|
|
65
65
|
const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
|
|
66
66
|
const profilePaths = defaultProfilePaths();
|
|
67
67
|
ensureProfileFiles(profilePaths);
|
|
68
68
|
const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
|
|
69
69
|
const remoteMemorySources = normalizeRemoteMemorySources(config);
|
|
70
|
-
const memoryTools = createMarkdownMemoryTools(memoryStore, { remoteSources: remoteMemorySources });
|
|
71
70
|
const currentProject = basename(cwd);
|
|
72
71
|
const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
|
|
73
72
|
|
|
74
|
-
const mcpInit = useRuntimeProcess
|
|
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;
|
|
87
73
|
const sessionSource = "pi";
|
|
88
74
|
const sessionsRoot = join(projectMarchDir, "sessions");
|
|
89
75
|
const sessionState = {
|
|
@@ -92,10 +78,6 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
92
78
|
};
|
|
93
79
|
sessionState.sessionDir = join(sessionsRoot, sessionState.sessionId);
|
|
94
80
|
const contextDumpRoot = resolve(projectMarchDir, "context-dumps", sessionState.sessionId);
|
|
95
|
-
const modelContextDumper = createModelContextDumper({
|
|
96
|
-
enabled: args.dumpContext,
|
|
97
|
-
rootDir: contextDumpRoot,
|
|
98
|
-
});
|
|
99
81
|
|
|
100
82
|
const ui = createUI({
|
|
101
83
|
json: args.json,
|
|
@@ -105,7 +87,8 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
105
87
|
shellRuntime,
|
|
106
88
|
historyStore: inputHistoryStore,
|
|
107
89
|
});
|
|
108
|
-
|
|
90
|
+
const outputRouter = createWorkspaceOutputRouter({ ui, activeProjectId: currentProjectInfo.projectId, activeSessionId: sessionState.sessionId });
|
|
91
|
+
const runtimeUi = outputRouter.createProjectUi(currentProjectInfo.projectId, () => sessionState.sessionId);
|
|
109
92
|
let turnRunning = false;
|
|
110
93
|
let refreshStatusBar = null;
|
|
111
94
|
const runnerOptions = {
|
|
@@ -121,22 +104,26 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
121
104
|
namespace,
|
|
122
105
|
projectMarchDir,
|
|
123
106
|
extensionPaths,
|
|
124
|
-
permissionMode,
|
|
125
107
|
shellRuntime: Boolean(shellRuntime),
|
|
126
108
|
lifecycleHooks: lifecycleManifests.hooks,
|
|
127
109
|
lifecycleDiagnostics: lifecycleManifests.diagnostics,
|
|
128
110
|
modelContextDumper: { enabled: args.dumpContext, rootDir: contextDumpRoot },
|
|
129
111
|
remoteMemorySources,
|
|
112
|
+
notificationContext: { projectId: currentProjectInfo.projectId },
|
|
130
113
|
};
|
|
131
114
|
|
|
132
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
|
+
};
|
|
133
120
|
try {
|
|
134
121
|
runner = await createRuntimeRunner({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
permissionController, modelContextDumper, turnNotifier, logger,
|
|
122
|
+
runnerOptions,
|
|
123
|
+
ui: runtimeUi,
|
|
124
|
+
shellRuntime,
|
|
139
125
|
refreshStatusBar: (...args) => refreshStatusBar?.(...args),
|
|
126
|
+
onNotificationActivation,
|
|
140
127
|
});
|
|
141
128
|
} catch (err) {
|
|
142
129
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
@@ -145,6 +132,42 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
145
132
|
return { ok: false, code: 1, logger };
|
|
146
133
|
}
|
|
147
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
|
+
|
|
148
171
|
refreshStatusBar = createStatusLineUpdater({
|
|
149
172
|
ui,
|
|
150
173
|
runner,
|
|
@@ -174,6 +197,8 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
174
197
|
projectMarchDir,
|
|
175
198
|
ui,
|
|
176
199
|
});
|
|
200
|
+
workspaceSupervisor.refreshActiveRuntime();
|
|
201
|
+
outputRouter.setActiveSession(currentProjectInfo.projectId, sessionState.sessionId);
|
|
177
202
|
refreshStatusBar();
|
|
178
203
|
|
|
179
204
|
return {
|
|
@@ -182,8 +207,11 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
182
207
|
cwd,
|
|
183
208
|
ui,
|
|
184
209
|
runner,
|
|
210
|
+
workspaceSupervisor,
|
|
211
|
+
workspaceOutputRouter: outputRouter,
|
|
185
212
|
memoryStore,
|
|
186
213
|
currentProject,
|
|
214
|
+
currentProjectInfo,
|
|
187
215
|
sessionState,
|
|
188
216
|
sessionsRoot,
|
|
189
217
|
projectMarchDir,
|
|
@@ -199,3 +227,16 @@ export async function createCliAppRuntime({ args, config, cwd, argv, stateRoot,
|
|
|
199
227
|
setTurnRunning(value) { turnRunning = value; },
|
|
200
228
|
};
|
|
201
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
|
+
}
|
|
@@ -1,61 +1,20 @@
|
|
|
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,
|
|
8
|
+
onNotificationActivation = null,
|
|
24
9
|
} = {}) {
|
|
25
10
|
const onModelPayload = ({ estimatedTokens }) => {
|
|
26
11
|
refreshStatusBar?.({ contextTokens: estimatedTokens });
|
|
27
12
|
};
|
|
13
|
+
const onLspStatusChange = () => {
|
|
14
|
+
refreshStatusBar?.();
|
|
15
|
+
};
|
|
28
16
|
|
|
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
|
-
|
|
17
|
+
const { runner } = await createRunnerProcessClient({ runnerOptions, ui, onModelPayload, onLspStatusChange, onNotificationActivation });
|
|
59
18
|
runner.shellRuntime ??= shellRuntime;
|
|
60
19
|
return runner;
|
|
61
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,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export function createHistoryNavigationController({ editor, requestRender, isAutocompleteOpen = () => false, hasOverlay = () => false } = {}) {
|
|
4
|
+
let draftText = null;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
handleInput(data) {
|
|
8
|
+
if (isAutocompleteOpen() || hasOverlay()) return undefined;
|
|
9
|
+
|
|
10
|
+
if (matchesKey(data, "alt+up")) return moveWithinInput(-1);
|
|
11
|
+
if (matchesKey(data, "alt+down")) return moveWithinInput(1);
|
|
12
|
+
if (matchesKey(data, "up")) return navigateHistory(-1);
|
|
13
|
+
if (matchesKey(data, "down")) return navigateHistory(1);
|
|
14
|
+
|
|
15
|
+
return undefined;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function navigateHistory(direction) {
|
|
20
|
+
const history = Array.isArray(editor?.history) ? editor.history : [];
|
|
21
|
+
const currentIndex = Number.isInteger(editor?.historyIndex) ? editor.historyIndex : -1;
|
|
22
|
+
const nextIndex = currentIndex - direction;
|
|
23
|
+
if (nextIndex < -1 || nextIndex >= history.length) return undefined;
|
|
24
|
+
|
|
25
|
+
if (currentIndex === -1) draftText = editor.getText?.() ?? "";
|
|
26
|
+
editor.lastAction = null;
|
|
27
|
+
editor.historyIndex = nextIndex;
|
|
28
|
+
setEditorTextPreservingHistory(nextIndex === -1 ? draftText ?? "" : history[nextIndex] ?? "");
|
|
29
|
+
if (nextIndex === -1) draftText = null;
|
|
30
|
+
requestRender?.();
|
|
31
|
+
return { consume: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function moveWithinInput(direction) {
|
|
35
|
+
editor.lastAction = null;
|
|
36
|
+
if (direction < 0) {
|
|
37
|
+
if (editor.isOnFirstVisualLine?.()) editor.moveToLineStart?.();
|
|
38
|
+
else editor.moveCursor?.(-1, 0);
|
|
39
|
+
} else {
|
|
40
|
+
if (editor.isOnLastVisualLine?.()) editor.moveToLineEnd?.();
|
|
41
|
+
else editor.moveCursor?.(1, 0);
|
|
42
|
+
}
|
|
43
|
+
requestRender?.();
|
|
44
|
+
return { consume: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setEditorTextPreservingHistory(text) {
|
|
48
|
+
if (typeof editor.setTextInternal === "function") {
|
|
49
|
+
editor.setTextInternal(text);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const historyIndex = editor.historyIndex;
|
|
53
|
+
editor.setText?.(text);
|
|
54
|
+
editor.historyIndex = historyIndex;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -5,7 +5,6 @@ import { formatMessageAttachmentsForDisplay } from "../../session/attachment-dis
|
|
|
5
5
|
import { formatShellHints } from "../../shell/hints.mjs";
|
|
6
6
|
|
|
7
7
|
export function prepareTurnInput({ prompt, runner, memoryStore, currentProject, modeState = null }) {
|
|
8
|
-
memoryStore.beginTurn();
|
|
9
8
|
const engine = runner.engine ?? {};
|
|
10
9
|
const carryoverAlreadyRendered = engine.hasRenderedPendingAssistantRecallHints?.() ?? false;
|
|
11
10
|
const carryoverRecallHints = engine.takePendingAssistantRecallHints?.() ?? [];
|
package/src/cli/ui.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocompl
|
|
|
5
5
|
import { createJsonUI, createPlainUI } from "./fallback-ui.mjs";
|
|
6
6
|
import { createKeybindingDispatcher } from "./input/keybinding-dispatch.mjs";
|
|
7
7
|
import { OutputBuffer } from "./tui/output-buffer.mjs";
|
|
8
|
-
import { requestToolPermission } from "./tui/permission-request-ui.mjs";
|
|
9
8
|
import { runTuiExternalEditor } from "./tui/editor/external-editor-runner.mjs";
|
|
10
9
|
import { createRetryStatusController } from "./tui/status/retry-status.mjs";
|
|
11
10
|
import { createShellDrawerControls } from "./shell/shell-drawer-controls.mjs";
|
|
@@ -16,6 +15,7 @@ import { showEditorSelectList } from "./tui/select/editor-select-list.mjs";
|
|
|
16
15
|
import { StatusBar } from "./tui/status/status-bar.mjs";
|
|
17
16
|
import { MainPaneLayout } from "./tui/layout/main-pane-layout.mjs";
|
|
18
17
|
import { SafeRenderBoundary } from "./tui/layout/safe-render-boundary.mjs";
|
|
18
|
+
import { createHistoryNavigationController } from "./tui/input/history-navigation-controller.mjs";
|
|
19
19
|
import { createMouseSelectionController } from "./tui/input/mouse-selection-controller.mjs";
|
|
20
20
|
import { ScreenSelection } from "./tui/selection-screen.mjs";
|
|
21
21
|
import { writeEditDiff } from "./tui/tui-diff-rendering.mjs";
|
|
@@ -68,6 +68,12 @@ export function createTuiUI({
|
|
|
68
68
|
const retryStatus = createRetryStatusController({ output, requestRender, stopSpinner: spinnerStatus.stop });
|
|
69
69
|
const shellDrawerControls = createShellDrawerControls({ shellDrawer, output, requestRender });
|
|
70
70
|
const mouseSelectionController = createMouseSelectionController({ terminal, output, shellDrawer, shellDrawerControls, selection, writeClipboard, requestRender });
|
|
71
|
+
const historyNavigationController = createHistoryNavigationController({
|
|
72
|
+
editor,
|
|
73
|
+
requestRender,
|
|
74
|
+
isAutocompleteOpen: () => editor.isShowingAutocomplete(),
|
|
75
|
+
hasOverlay: () => tui.hasOverlay(),
|
|
76
|
+
});
|
|
71
77
|
|
|
72
78
|
let onEscapeHandler = null, onCtrlCHandler = null, onShiftTabHandler = null;
|
|
73
79
|
let onCtrlTHandler = null, onCtrlLHandler = null, onPasteImageHandler = null, onToggleModeHandler = null;
|
|
@@ -108,6 +114,8 @@ export function createTuiUI({
|
|
|
108
114
|
requestRender();
|
|
109
115
|
return { consume: true };
|
|
110
116
|
}
|
|
117
|
+
const historyNavigationResult = historyNavigationController.handleInput(data);
|
|
118
|
+
if (historyNavigationResult) return historyNavigationResult;
|
|
111
119
|
});
|
|
112
120
|
terminal.write("\x1b[?1049h");
|
|
113
121
|
terminal.write("\x1b[?1002h\x1b[?1006h");
|
|
@@ -239,11 +247,6 @@ export function createTuiUI({
|
|
|
239
247
|
requestRender();
|
|
240
248
|
},
|
|
241
249
|
|
|
242
|
-
requestPermission: async ({ toolName, params, category }) => {
|
|
243
|
-
ensureStarted();
|
|
244
|
-
spinnerStatus.stop();
|
|
245
|
-
return requestToolPermission({ toolName, params, category, output, selectList, requestRender });
|
|
246
|
-
},
|
|
247
250
|
|
|
248
251
|
setEscapeHandler: (fn) => { onEscapeHandler = fn; },
|
|
249
252
|
setCtrlCHandler: (fn) => { onCtrlCHandler = fn; },
|