@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.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.
- package/CHANGELOG.md +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
package/src/lsp/client.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import { isEnoent, logger, ptree, untilAborted } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
3
4
|
import { applyWorkspaceEdit } from "./edits";
|
|
@@ -147,6 +148,7 @@ const CLIENT_CAPABILITIES = {
|
|
|
147
148
|
failureHandling: "textOnlyTransactional",
|
|
148
149
|
},
|
|
149
150
|
configuration: true,
|
|
151
|
+
workspaceFolders: true,
|
|
150
152
|
symbol: {
|
|
151
153
|
dynamicRegistration: false,
|
|
152
154
|
symbolKind: {
|
|
@@ -172,49 +174,69 @@ const CLIENT_CAPABILITIES = {
|
|
|
172
174
|
// LSP Message Protocol
|
|
173
175
|
// =============================================================================
|
|
174
176
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*/
|
|
179
|
-
function parseMessage(
|
|
180
|
-
buffer: Buffer,
|
|
181
|
-
): { message: LspJsonRpcResponse | LspJsonRpcNotification; remaining: Buffer } | null {
|
|
182
|
-
// Only decode enough to find the header
|
|
183
|
-
const headerEndIndex = findHeaderEnd(buffer);
|
|
184
|
-
if (headerEndIndex === -1) return null;
|
|
185
|
-
|
|
186
|
-
const headerText = new TextDecoder().decode(buffer.slice(0, headerEndIndex));
|
|
187
|
-
const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
|
|
188
|
-
if (!contentLengthMatch) return null;
|
|
189
|
-
|
|
190
|
-
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
191
|
-
const messageStart = headerEndIndex + 4; // Skip \r\n\r\n
|
|
192
|
-
const messageEnd = messageStart + contentLength;
|
|
193
|
-
|
|
194
|
-
if (buffer.length < messageEnd) return null;
|
|
195
|
-
|
|
196
|
-
const messageBytes = buffer.subarray(messageStart, messageEnd);
|
|
197
|
-
const messageText = new TextDecoder().decode(messageBytes);
|
|
198
|
-
const remaining = buffer.subarray(messageEnd);
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
message: JSON.parse(messageText),
|
|
202
|
-
remaining,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
177
|
+
// Reused for all full (non-streaming) decodes; each decode() resets state, so a
|
|
178
|
+
// single instance is safe and avoids per-message TextDecoder allocation.
|
|
179
|
+
const MESSAGE_DECODER = new TextDecoder("utf-8");
|
|
205
180
|
|
|
206
181
|
/**
|
|
207
|
-
*
|
|
182
|
+
* Locate the `\r\n\r\n` header terminator across the pending chunk list.
|
|
183
|
+
* Returns the absolute byte index of the first `\r`, or -1 when not present.
|
|
184
|
+
* Equivalent to scanning the contiguous concatenation of the chunks.
|
|
208
185
|
*/
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
function findHeaderEndInChunks(chunks: Buffer[]): number {
|
|
187
|
+
let global = 0;
|
|
188
|
+
let b0 = -1;
|
|
189
|
+
let b1 = -1;
|
|
190
|
+
let b2 = -1;
|
|
191
|
+
for (const chunk of chunks) {
|
|
192
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
193
|
+
const b3 = chunk[i];
|
|
194
|
+
if (b0 === 13 && b1 === 10 && b2 === 13 && b3 === 10) {
|
|
195
|
+
return global - 3;
|
|
196
|
+
}
|
|
197
|
+
b0 = b1;
|
|
198
|
+
b1 = b2;
|
|
199
|
+
b2 = b3;
|
|
200
|
+
global++;
|
|
213
201
|
}
|
|
214
202
|
}
|
|
215
203
|
return -1;
|
|
216
204
|
}
|
|
217
205
|
|
|
206
|
+
/** Copy the byte range [from, to) out of the pending chunk list into one Buffer. */
|
|
207
|
+
function copyChunkRange(chunks: Buffer[], from: number, to: number): Buffer {
|
|
208
|
+
const out = Buffer.allocUnsafe(to - from);
|
|
209
|
+
let global = 0;
|
|
210
|
+
let written = 0;
|
|
211
|
+
for (const chunk of chunks) {
|
|
212
|
+
const chunkEnd = global + chunk.length;
|
|
213
|
+
if (chunkEnd > from && global < to) {
|
|
214
|
+
const start = Math.max(from, global) - global;
|
|
215
|
+
const end = Math.min(to, chunkEnd) - global;
|
|
216
|
+
chunk.copy(out, written, start, end);
|
|
217
|
+
written += end - start;
|
|
218
|
+
}
|
|
219
|
+
global = chunkEnd;
|
|
220
|
+
if (global >= to) break;
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Drop the first `count` bytes from the pending chunk list in place. */
|
|
226
|
+
function dropChunkFront(chunks: Buffer[], count: number): void {
|
|
227
|
+
let removed = 0;
|
|
228
|
+
while (chunks.length > 0) {
|
|
229
|
+
const head = chunks[0];
|
|
230
|
+
if (removed + head.length <= count) {
|
|
231
|
+
removed += head.length;
|
|
232
|
+
chunks.shift();
|
|
233
|
+
} else {
|
|
234
|
+
chunks[0] = head.subarray(count - removed);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
218
240
|
async function writeMessage(
|
|
219
241
|
sink: Bun.FileSink,
|
|
220
242
|
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
|
|
@@ -247,22 +269,43 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
247
269
|
|
|
248
270
|
const reader = (client.proc.stdout as ReadableStream<Uint8Array>).getReader();
|
|
249
271
|
|
|
272
|
+
// Incoming bytes are buffered as a list of chunks and only joined when a full
|
|
273
|
+
// message is framed. Concatenating the accumulator on every read was O(n^2)
|
|
274
|
+
// for messages that span many reads (e.g. a large initial diagnostics burst).
|
|
275
|
+
const pendingChunks: Buffer[] = [];
|
|
276
|
+
let pendingLen = 0;
|
|
277
|
+
if (client.messageBuffer.length > 0) {
|
|
278
|
+
const seed = Buffer.from(client.messageBuffer);
|
|
279
|
+
pendingChunks.push(seed);
|
|
280
|
+
pendingLen = seed.length;
|
|
281
|
+
}
|
|
282
|
+
|
|
250
283
|
try {
|
|
251
284
|
while (true) {
|
|
252
285
|
const { done, value } = await reader.read();
|
|
253
286
|
if (done) break;
|
|
254
287
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
288
|
+
pendingChunks.push(Buffer.from(value));
|
|
289
|
+
pendingLen += value.length;
|
|
290
|
+
|
|
291
|
+
// Drain every complete message currently buffered.
|
|
292
|
+
while (true) {
|
|
293
|
+
const headerEnd = findHeaderEndInChunks(pendingChunks);
|
|
294
|
+
if (headerEnd === -1) break;
|
|
295
|
+
|
|
296
|
+
const headerText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, 0, headerEnd));
|
|
297
|
+
const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
|
|
298
|
+
if (!contentLengthMatch) break;
|
|
299
|
+
|
|
300
|
+
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
301
|
+
const messageStart = headerEnd + 4; // Skip \r\n\r\n
|
|
302
|
+
const messageEnd = messageStart + contentLength;
|
|
303
|
+
if (pendingLen < messageEnd) break;
|
|
258
304
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
while (parsed) {
|
|
264
|
-
const { message, remaining } = parsed;
|
|
265
|
-
workingBuffer = remaining;
|
|
305
|
+
const messageText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, messageStart, messageEnd));
|
|
306
|
+
const message: LspJsonRpcResponse | LspJsonRpcNotification = JSON.parse(messageText);
|
|
307
|
+
dropChunkFront(pendingChunks, messageEnd);
|
|
308
|
+
pendingLen -= messageEnd;
|
|
266
309
|
|
|
267
310
|
// Route message
|
|
268
311
|
if ("id" in message && message.id !== undefined) {
|
|
@@ -299,12 +342,7 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
299
342
|
}
|
|
300
343
|
}
|
|
301
344
|
}
|
|
302
|
-
|
|
303
|
-
parsed = parseMessage(workingBuffer);
|
|
304
345
|
}
|
|
305
|
-
|
|
306
|
-
// Atomically commit processed buffer
|
|
307
|
-
client.messageBuffer = workingBuffer;
|
|
308
346
|
}
|
|
309
347
|
} catch (err) {
|
|
310
348
|
// Connection closed or error - reject all pending requests
|
|
@@ -313,11 +351,34 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
313
351
|
}
|
|
314
352
|
client.pendingRequests.clear();
|
|
315
353
|
} finally {
|
|
354
|
+
// Persist any unparsed remainder so a restarted reader resumes mid-message.
|
|
355
|
+
client.messageBuffer =
|
|
356
|
+
pendingChunks.length === 0
|
|
357
|
+
? new Uint8Array(0)
|
|
358
|
+
: pendingChunks.length === 1
|
|
359
|
+
? pendingChunks[0]
|
|
360
|
+
: Buffer.concat(pendingChunks, pendingLen);
|
|
316
361
|
reader.releaseLock();
|
|
317
362
|
client.isReading = false;
|
|
318
363
|
}
|
|
319
364
|
}
|
|
320
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Build the workspace folder list advertised to the server. Identical shape
|
|
368
|
+
* for `initialize` params and `workspace/workspaceFolders` server requests.
|
|
369
|
+
*/
|
|
370
|
+
function currentWorkspaceFolders(client: LspClient): Array<{ uri: string; name: string }> {
|
|
371
|
+
return [{ uri: fileToUri(client.cwd), name: path.basename(client.cwd) || "workspace" }];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Handle workspace/workspaceFolders requests from the server.
|
|
376
|
+
*/
|
|
377
|
+
async function handleWorkspaceFoldersRequest(client: LspClient, message: LspJsonRpcRequest): Promise<void> {
|
|
378
|
+
if (typeof message.id !== "number") return;
|
|
379
|
+
await sendResponse(client, message.id, currentWorkspaceFolders(client), "workspace/workspaceFolders");
|
|
380
|
+
}
|
|
381
|
+
|
|
321
382
|
/**
|
|
322
383
|
* Handle workspace/configuration requests from the server.
|
|
323
384
|
*/
|
|
@@ -364,6 +425,10 @@ async function handleServerRequest(client: LspClient, message: LspJsonRpcRequest
|
|
|
364
425
|
await handleConfigurationRequest(client, message);
|
|
365
426
|
return;
|
|
366
427
|
}
|
|
428
|
+
if (message.method === "workspace/workspaceFolders") {
|
|
429
|
+
await handleWorkspaceFoldersRequest(client, message);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
367
432
|
if (message.method === "workspace/applyEdit") {
|
|
368
433
|
await handleApplyEditRequest(client, message);
|
|
369
434
|
return;
|
|
@@ -412,7 +477,66 @@ async function sendResponse(
|
|
|
412
477
|
/** Timeout for warmup initialize requests (5 seconds) */
|
|
413
478
|
export const WARMUP_TIMEOUT_MS = 5000;
|
|
414
479
|
|
|
415
|
-
/** Max time to
|
|
480
|
+
/** Max time to poll rust-analyzer after progress ends but before Cargo workspaces are ready. */
|
|
481
|
+
const RUST_ANALYZER_WORKSPACE_READY_TIMEOUT_MS = 5_000;
|
|
482
|
+
const RUST_ANALYZER_WORKSPACE_READY_POLL_MS = 100;
|
|
483
|
+
const RUST_ANALYZER_WORKSPACE_READY_SETTLE_MS = 2_000;
|
|
484
|
+
const RUST_ANALYZER_STATUS_REQUEST_TIMEOUT_MS = 1_000;
|
|
485
|
+
const rustAnalyzerReadyClients = new WeakSet<LspClient>();
|
|
486
|
+
|
|
487
|
+
function commandBasename(command: string): string {
|
|
488
|
+
const slash = command.lastIndexOf("/");
|
|
489
|
+
const backslash = command.lastIndexOf("\\");
|
|
490
|
+
const separator = Math.max(slash, backslash);
|
|
491
|
+
return separator === -1 ? command : command.slice(separator + 1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function isRustAnalyzerClient(client: LspClient): boolean {
|
|
495
|
+
return (
|
|
496
|
+
commandBasename(client.config.command) === "rust-analyzer" ||
|
|
497
|
+
(client.config.resolvedCommand ? commandBasename(client.config.resolvedCommand) === "rust-analyzer" : false)
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function isRustAnalyzerStatusTimeout(err: unknown): boolean {
|
|
502
|
+
return err instanceof Error && err.message.startsWith("LSP request rust-analyzer/analyzerStatus timed out after ");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function waitForRustAnalyzerWorkspace(client: LspClient, signal?: AbortSignal): Promise<void> {
|
|
506
|
+
if (rustAnalyzerReadyClients.has(client)) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const timings = client.config.workspaceReadyTimings;
|
|
510
|
+
const timeoutMs = timings?.timeoutMs ?? RUST_ANALYZER_WORKSPACE_READY_TIMEOUT_MS;
|
|
511
|
+
const pollMs = timings?.pollMs ?? RUST_ANALYZER_WORKSPACE_READY_POLL_MS;
|
|
512
|
+
const settleMs = timings?.settleMs ?? RUST_ANALYZER_WORKSPACE_READY_SETTLE_MS;
|
|
513
|
+
const statusRequestTimeoutMs = timings?.statusRequestTimeoutMs ?? RUST_ANALYZER_STATUS_REQUEST_TIMEOUT_MS;
|
|
514
|
+
const started = Date.now();
|
|
515
|
+
const deadline = started + timeoutMs;
|
|
516
|
+
while (true) {
|
|
517
|
+
throwIfAborted(signal);
|
|
518
|
+
let status: unknown;
|
|
519
|
+
try {
|
|
520
|
+
status = await sendRequest(client, "rust-analyzer/analyzerStatus", {}, signal, statusRequestTimeoutMs);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
if (!isRustAnalyzerStatusTimeout(err) || Date.now() >= deadline) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
await Bun.sleep(pollMs);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const ready = typeof status === "string" && !status.startsWith("No workspaces");
|
|
529
|
+
if (ready && Date.now() - started >= settleMs) {
|
|
530
|
+
rustAnalyzerReadyClients.add(client);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (Date.now() >= deadline) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
await Bun.sleep(pollMs);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
416
540
|
const PROJECT_LOAD_TIMEOUT_MS = 15_000;
|
|
417
541
|
|
|
418
542
|
/** Max time to wait for graceful LSP shutdown and process exit. */
|
|
@@ -530,7 +654,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
530
654
|
rootPath: cwd,
|
|
531
655
|
capabilities: CLIENT_CAPABILITIES,
|
|
532
656
|
initializationOptions: config.initOptions ?? {},
|
|
533
|
-
workspaceFolders:
|
|
657
|
+
workspaceFolders: currentWorkspaceFolders(client),
|
|
534
658
|
},
|
|
535
659
|
undefined, // signal
|
|
536
660
|
initTimeoutMs,
|
|
@@ -635,6 +759,9 @@ export async function waitForProjectLoaded(client: LspClient, signal?: AbortSign
|
|
|
635
759
|
? [new Promise<void>(resolve => signal.addEventListener("abort", () => resolve(), { once: true }))]
|
|
636
760
|
: []),
|
|
637
761
|
]);
|
|
762
|
+
if (isRustAnalyzerClient(client)) {
|
|
763
|
+
await waitForRustAnalyzerWorkspace(client, signal);
|
|
764
|
+
}
|
|
638
765
|
}
|
|
639
766
|
|
|
640
767
|
/**
|
package/src/lsp/index.ts
CHANGED
|
@@ -304,6 +304,32 @@ const SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS = 3000;
|
|
|
304
304
|
const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400;
|
|
305
305
|
const MAX_GLOB_DIAGNOSTIC_TARGETS = 20;
|
|
306
306
|
const WORKSPACE_SYMBOL_LIMIT = 200;
|
|
307
|
+
const PROJECT_INDEXED_ACTIONS: ReadonlySet<string> = new Set([
|
|
308
|
+
"definition",
|
|
309
|
+
"type_definition",
|
|
310
|
+
"implementation",
|
|
311
|
+
"references",
|
|
312
|
+
"rename",
|
|
313
|
+
"hover",
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
const RUST_WORKSPACE_MARKERS = ["Cargo.toml", "rust-analyzer.toml"] as const;
|
|
317
|
+
|
|
318
|
+
function hasRustWorkspaceAncestor(filePath: string): boolean {
|
|
319
|
+
let dir = path.dirname(filePath);
|
|
320
|
+
while (true) {
|
|
321
|
+
for (const marker of RUST_WORKSPACE_MARKERS) {
|
|
322
|
+
if (fs.existsSync(path.join(dir, marker))) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const parent = path.dirname(dir);
|
|
327
|
+
if (parent === dir) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
dir = parent;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
307
333
|
|
|
308
334
|
function limitDiagnosticMessages(messages: string[]): string[] {
|
|
309
335
|
if (messages.length <= DIAGNOSTIC_MESSAGE_LIMIT) {
|
|
@@ -1940,10 +1966,21 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1940
1966
|
try {
|
|
1941
1967
|
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
1942
1968
|
const targetFile = resolvedFile;
|
|
1969
|
+
const isRustAnalyzerServer =
|
|
1970
|
+
serverName === "rust-analyzer" ||
|
|
1971
|
+
path.basename(serverConfig.command) === "rust-analyzer" ||
|
|
1972
|
+
(serverConfig.resolvedCommand ? path.basename(serverConfig.resolvedCommand) === "rust-analyzer" : false);
|
|
1973
|
+
const needsProjectIndex =
|
|
1974
|
+
targetFile !== null && PROJECT_INDEXED_ACTIONS.has(action) && isProjectAwareLspServer(serverConfig);
|
|
1975
|
+
const rustWorkspaceWait =
|
|
1976
|
+
needsProjectIndex && isRustAnalyzerServer && targetFile !== null && hasRustWorkspaceAncestor(targetFile);
|
|
1943
1977
|
|
|
1944
1978
|
if (targetFile) {
|
|
1945
1979
|
await ensureFileOpen(client, targetFile, signal);
|
|
1946
1980
|
}
|
|
1981
|
+
if (rustWorkspaceWait) {
|
|
1982
|
+
await waitForProjectLoaded(client, signal);
|
|
1983
|
+
}
|
|
1947
1984
|
|
|
1948
1985
|
// For project-aware servers, references/rename/definition without a `symbol`
|
|
1949
1986
|
// silently falls back to the first non-whitespace column on the line, which
|
|
@@ -1968,10 +2005,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1968
2005
|
|
|
1969
2006
|
let output: string;
|
|
1970
2007
|
|
|
1971
|
-
|
|
1972
|
-
// to ensure the server has indexed all project files.
|
|
1973
|
-
const crossFileActions = new Set(["definition", "type_definition", "implementation", "references", "rename"]);
|
|
1974
|
-
if (crossFileActions.has(action)) {
|
|
2008
|
+
if (needsProjectIndex && !isRustAnalyzerServer) {
|
|
1975
2009
|
await waitForProjectLoaded(client, signal);
|
|
1976
2010
|
}
|
|
1977
2011
|
|
package/src/lsp/render.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
truncateToWidth,
|
|
22
22
|
} from "../tools/render-utils";
|
|
23
23
|
import { renderStatusLine } from "../tui";
|
|
24
|
-
import { CachedOutputBlock } from "../tui/output-block";
|
|
24
|
+
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
25
25
|
import type { LspParams, LspToolDetails } from "./types";
|
|
26
26
|
|
|
27
27
|
// =============================================================================
|
|
@@ -138,7 +138,7 @@ export function renderResult(
|
|
|
138
138
|
|
|
139
139
|
const outputBlock = new CachedOutputBlock();
|
|
140
140
|
|
|
141
|
-
return {
|
|
141
|
+
return markFramedBlockComponent({
|
|
142
142
|
render(width: number): string[] {
|
|
143
143
|
// Read mutable state at render time
|
|
144
144
|
const { expanded, isPartial, spinnerFrame } = options;
|
|
@@ -194,7 +194,7 @@ export function renderResult(
|
|
|
194
194
|
invalidate() {
|
|
195
195
|
outputBlock.invalidate();
|
|
196
196
|
},
|
|
197
|
-
};
|
|
197
|
+
});
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// =============================================================================
|
package/src/lsp/types.ts
CHANGED
|
@@ -356,6 +356,16 @@ export interface ServerConfig {
|
|
|
356
356
|
disabled?: boolean;
|
|
357
357
|
/** Per-server warmup timeout in milliseconds. Overrides the global WARMUP_TIMEOUT_MS for this server during startup. */
|
|
358
358
|
warmupTimeoutMs?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Per-server overrides for rust-analyzer workspace-ready polling. When omitted, the module
|
|
361
|
+
* defaults are used. Primarily a tuning/test seam to bound the multi-second settle window.
|
|
362
|
+
*/
|
|
363
|
+
workspaceReadyTimings?: {
|
|
364
|
+
timeoutMs?: number;
|
|
365
|
+
pollMs?: number;
|
|
366
|
+
settleMs?: number;
|
|
367
|
+
statusRequestTimeoutMs?: number;
|
|
368
|
+
};
|
|
359
369
|
capabilities?: ServerCapabilities;
|
|
360
370
|
/** If true, this is a linter/formatter server (e.g., Biome) - used only for diagnostics/actions, not type intelligence */
|
|
361
371
|
isLinter?: boolean;
|
package/src/main.ts
CHANGED
|
@@ -40,19 +40,13 @@ import {
|
|
|
40
40
|
resolveActiveProjectRegistryPath,
|
|
41
41
|
} from "./discovery/helpers";
|
|
42
42
|
import { injectOmpExtensionCliRoots } from "./discovery/omp-extension-roots";
|
|
43
|
-
import { exportFromFile } from "./export/html";
|
|
44
43
|
import { ExtensionRunner } from "./extensibility/extensions/runner";
|
|
45
44
|
import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
46
|
-
import {
|
|
47
|
-
getInstalledPluginsRegistryPath,
|
|
48
|
-
getMarketplacesCacheDir,
|
|
49
|
-
getMarketplacesRegistryPath,
|
|
50
|
-
getPluginsCacheDir,
|
|
51
|
-
MarketplaceManager,
|
|
52
|
-
} from "./extensibility/plugins/marketplace";
|
|
45
|
+
import { scheduleMarketplaceAutoUpdate } from "./extensibility/plugins/marketplace-auto-update";
|
|
53
46
|
import type { MCPManager } from "./mcp";
|
|
54
|
-
import { InteractiveMode
|
|
55
|
-
import {
|
|
47
|
+
import { InteractiveMode } from "./modes/interactive-mode";
|
|
48
|
+
import type { PrintModeOptions } from "./modes/print-mode";
|
|
49
|
+
import { CURRENT_SETUP_VERSION } from "./modes/setup-version";
|
|
56
50
|
import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
|
|
57
51
|
import type { SubmittedUserInput } from "./modes/types";
|
|
58
52
|
import {
|
|
@@ -72,6 +66,13 @@ import type { LspStartupServerInfo } from "./tools";
|
|
|
72
66
|
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
|
|
73
67
|
import { EventBus } from "./utils/event-bus";
|
|
74
68
|
|
|
69
|
+
type RunAcpMode = (createSession: AcpSessionFactory) => Promise<never>;
|
|
70
|
+
type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promise<void>;
|
|
71
|
+
type RunRpcMode = (
|
|
72
|
+
session: AgentSession,
|
|
73
|
+
setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
74
|
+
) => Promise<never>;
|
|
75
|
+
|
|
75
76
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
76
77
|
if (!settings.get("startup.checkUpdate")) {
|
|
77
78
|
return;
|
|
@@ -261,17 +262,26 @@ async function runInteractiveMode(
|
|
|
261
262
|
eventBus,
|
|
262
263
|
);
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
265
|
+
// Cold-launch gate: the full setup wizard (every scene + the overlay and
|
|
266
|
+
// their TUI/OAuth/search/theme deps) is heavy, yet the common case only needs
|
|
267
|
+
// to know whether the stored setup version is current. Lazy-load the wizard
|
|
268
|
+
// barrel only when setup is stale or forced; otherwise skip it entirely.
|
|
269
|
+
const storedSetupVersion = settings.get("setupVersion");
|
|
270
|
+
const setupWizard =
|
|
271
|
+
forceSetupWizard || storedSetupVersion < CURRENT_SETUP_VERSION ? await import("./modes/setup-wizard") : undefined;
|
|
272
|
+
const setupScenes = setupWizard
|
|
273
|
+
? await setupWizard.selectSetupScenes(storedSetupVersion, setupWizard.ALL_SCENES, mode, {
|
|
274
|
+
resuming,
|
|
275
|
+
isTTY: process.stdin.isTTY && process.stdout.isTTY,
|
|
276
|
+
setupWizardEnabled: settings.get("startup.setupWizard"),
|
|
277
|
+
force: forceSetupWizard,
|
|
278
|
+
})
|
|
279
|
+
: [];
|
|
280
|
+
|
|
281
|
+
await mode.init({ suppressWelcomeIntro: resuming || setupScenes.length > 0 });
|
|
282
|
+
|
|
283
|
+
if (setupWizard && setupScenes.length > 0) {
|
|
284
|
+
await setupWizard.runSetupWizard(mode, setupScenes);
|
|
275
285
|
}
|
|
276
286
|
|
|
277
287
|
versionCheckPromise
|
|
@@ -716,7 +726,7 @@ async function buildSessionOptions(
|
|
|
716
726
|
interface RunRootCommandDependencies {
|
|
717
727
|
createAgentSession?: typeof createAgentSession;
|
|
718
728
|
discoverAuthStorage?: typeof discoverAuthStorage;
|
|
719
|
-
runAcpMode?:
|
|
729
|
+
runAcpMode?: RunAcpMode;
|
|
720
730
|
settings?: Settings;
|
|
721
731
|
forceSetupWizard?: boolean;
|
|
722
732
|
}
|
|
@@ -773,6 +783,7 @@ export async function runRootCommand(
|
|
|
773
783
|
let result: string;
|
|
774
784
|
try {
|
|
775
785
|
const outputPath = parsedArgs.messages.length > 0 ? parsedArgs.messages[0] : undefined;
|
|
786
|
+
const { exportFromFile } = await import("./export/html");
|
|
776
787
|
result = await exportFromFile(parsedArgs.export, outputPath);
|
|
777
788
|
} catch (error: unknown) {
|
|
778
789
|
const message = error instanceof Error ? error.message : "Failed to export session";
|
|
@@ -940,33 +951,11 @@ export async function runRootCommand(
|
|
|
940
951
|
|
|
941
952
|
await pluginPreloadPromise;
|
|
942
953
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const mgr = new MarketplaceManager({
|
|
949
|
-
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
950
|
-
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
951
|
-
projectInstalledRegistryPath: (await resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
952
|
-
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
953
|
-
pluginsCacheDir: getPluginsCacheDir(),
|
|
954
|
-
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
955
|
-
});
|
|
956
|
-
await mgr.refreshStaleMarketplaces();
|
|
957
|
-
const updates = await mgr.checkForUpdates();
|
|
958
|
-
if (updates.length === 0) return;
|
|
959
|
-
if (autoUpdate === "auto") {
|
|
960
|
-
await mgr.upgradeAllPlugins();
|
|
961
|
-
logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s)`);
|
|
962
|
-
} else {
|
|
963
|
-
logger.debug(`${updates.length} marketplace plugin update(s) available — /marketplace upgrade`);
|
|
964
|
-
}
|
|
965
|
-
} catch {
|
|
966
|
-
// Silently ignore — network failure, corrupt data, offline.
|
|
967
|
-
}
|
|
968
|
-
})();
|
|
969
|
-
}
|
|
954
|
+
scheduleMarketplaceAutoUpdate({
|
|
955
|
+
autoUpdate: settingsInstance.get("marketplace.autoUpdate"),
|
|
956
|
+
resolveActiveProjectRegistryPath,
|
|
957
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
958
|
+
});
|
|
970
959
|
|
|
971
960
|
const { options: sessionOptions } = await logger.time(
|
|
972
961
|
"buildSessionOptions",
|
|
@@ -988,7 +977,7 @@ export async function runRootCommand(
|
|
|
988
977
|
// Both are no-ops when OTEL_EXPORTER_OTLP_ENDPOINT is unset. An empty config
|
|
989
978
|
// is enough to enable telemetry — content capture is governed by the
|
|
990
979
|
// standard OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT env var.
|
|
991
|
-
initTelemetryExport();
|
|
980
|
+
await initTelemetryExport();
|
|
992
981
|
if (isTelemetryExportEnabled()) {
|
|
993
982
|
sessionOptions.telemetry = {};
|
|
994
983
|
}
|
|
@@ -1027,7 +1016,9 @@ export async function runRootCommand(
|
|
|
1027
1016
|
rawArgs,
|
|
1028
1017
|
createSession,
|
|
1029
1018
|
});
|
|
1030
|
-
|
|
1019
|
+
// Branch-only protocol runner: keep ACP server code out of normal interactive startup.
|
|
1020
|
+
const runAcpMode = deps.runAcpMode ?? (await import("./modes/acp/acp-mode")).runAcpMode;
|
|
1021
|
+
await runAcpMode(createAcpSession);
|
|
1031
1022
|
} else {
|
|
1032
1023
|
// Resolve extension-registered CLI flags before creating the session so a
|
|
1033
1024
|
// bad `@file` fails fast WITHOUT leaving a junk session/breadcrumb
|
|
@@ -1091,6 +1082,8 @@ export async function runRootCommand(
|
|
|
1091
1082
|
}
|
|
1092
1083
|
|
|
1093
1084
|
if (mode === "rpc" || mode === "rpc-ui") {
|
|
1085
|
+
// Branch-only protocol runner: keep RPC host code out of normal interactive startup.
|
|
1086
|
+
const runRpcMode: RunRpcMode = (await import("./modes/rpc/rpc-mode")).runRpcMode;
|
|
1094
1087
|
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
|
|
1095
1088
|
} else if (isInteractive) {
|
|
1096
1089
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
@@ -1109,7 +1102,7 @@ export async function runRootCommand(
|
|
|
1109
1102
|
|
|
1110
1103
|
if ($env.PI_TIMING) {
|
|
1111
1104
|
logger.printTimings();
|
|
1112
|
-
if (
|
|
1105
|
+
if (logger.shouldExitAfterTimings()) {
|
|
1113
1106
|
process.exit(0);
|
|
1114
1107
|
}
|
|
1115
1108
|
}
|
|
@@ -1132,6 +1125,8 @@ export async function runRootCommand(
|
|
|
1132
1125
|
initialImages,
|
|
1133
1126
|
);
|
|
1134
1127
|
} else {
|
|
1128
|
+
// Branch-only single-shot runner: keep print-mode code out of normal interactive startup.
|
|
1129
|
+
const runPrintMode: RunPrintMode = (await import("./modes/print-mode")).runPrintMode;
|
|
1135
1130
|
await runPrintMode(session, {
|
|
1136
1131
|
mode,
|
|
1137
1132
|
messages: initialArgs.messages,
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type {
|
|
2
|
+
MnemopiBackendConfig,
|
|
3
|
+
MnemopiLlmMode,
|
|
4
|
+
MnemopiProviderOptions,
|
|
5
|
+
MnemopiScoping,
|
|
6
|
+
} from "../mnemopi/config";
|
|
7
|
+
export type {
|
|
8
|
+
MnemopiMemoryEditOperation,
|
|
9
|
+
MnemopiMemoryEditOptions,
|
|
10
|
+
MnemopiMemoryEditResult,
|
|
11
|
+
MnemopiSessionState,
|
|
12
|
+
MnemopiSessionStateOptions,
|
|
13
|
+
} from "../mnemopi/state";
|
|
2
14
|
export * from "./local-backend";
|
|
3
15
|
export * from "./off-backend";
|
|
4
16
|
export * from "./resolve";
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { Settings } from "../config/settings";
|
|
2
|
-
import { hindsightBackend } from "../hindsight";
|
|
3
|
-
import { mnemopiBackend } from "../mnemopi";
|
|
4
2
|
import { localBackend } from "./local-backend";
|
|
5
3
|
import { offBackend } from "./off-backend";
|
|
6
4
|
import type { MemoryBackend } from "./types";
|
|
@@ -18,10 +16,10 @@ import type { MemoryBackend } from "./types";
|
|
|
18
16
|
* `memories.enabled` remains accepted only as a legacy migration input. Once
|
|
19
17
|
* a config is loaded, `memory.backend` is the sole runtime selector.
|
|
20
18
|
*/
|
|
21
|
-
export function resolveMemoryBackend(settings: Settings): MemoryBackend {
|
|
19
|
+
export async function resolveMemoryBackend(settings: Settings): Promise<MemoryBackend> {
|
|
22
20
|
const id = settings.get("memory.backend");
|
|
23
|
-
if (id === "hindsight") return hindsightBackend;
|
|
24
|
-
if (id === "mnemopi") return mnemopiBackend;
|
|
21
|
+
if (id === "hindsight") return (await import("../hindsight/backend")).hindsightBackend;
|
|
22
|
+
if (id === "mnemopi") return (await import("../mnemopi/backend")).mnemopiBackend;
|
|
25
23
|
if (id === "local") return localBackend;
|
|
26
24
|
return offBackend;
|
|
27
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory backend abstraction.
|
|
3
3
|
*
|
|
4
|
-
* Backends are mutually exclusive — `resolveMemoryBackend(settings)`
|
|
4
|
+
* Backends are mutually exclusive — `await resolveMemoryBackend(settings)` resolves
|
|
5
5
|
* exactly one. Implementations MUST be self-contained: they own the per-session
|
|
6
6
|
* state they create in `start()` and tear it down on `clear()`.
|
|
7
7
|
*/
|