om-memory-system 3.2.0-next.9 → 3.3.0-next.17
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/dist/adapters/opencode/auto-capture-summary.js +50 -8
- package/dist/adapters/opencode/import-command.d.ts +3 -7
- package/dist/adapters/opencode/import-command.js +3 -54
- package/dist/adapters/pi/extension.js +3 -0
- package/dist/adapters/pi/profile.js +2 -1
- package/dist/adapters/pi/provider.js +41 -14
- package/dist/config.d.ts +18 -1
- package/dist/config.js +103 -9
- package/dist/core/capture-context.js +1 -1
- package/dist/core/capture.d.ts +4 -0
- package/dist/core/capture.js +30 -0
- package/dist/core/extraction.d.ts +23 -1
- package/dist/core/extraction.js +41 -0
- package/dist/core/host.d.ts +23 -0
- package/dist/core/host.js +9 -1
- package/dist/core/profile-analysis.js +9 -1
- package/dist/importer/discovery.d.ts +32 -5
- package/dist/importer/discovery.js +88 -36
- package/dist/importer/import-args.d.ts +23 -1
- package/dist/importer/import-args.js +29 -1
- package/dist/importer/import-project.d.ts +16 -0
- package/dist/importer/import-project.js +35 -0
- package/dist/importer/import-readiness.d.ts +47 -0
- package/dist/importer/import-readiness.js +54 -0
- package/dist/importer/import-sessions.d.ts +98 -0
- package/dist/importer/import-sessions.js +222 -0
- package/dist/importer/import-sources.d.ts +47 -0
- package/dist/importer/import-sources.js +147 -0
- package/dist/importer/importer.d.ts +31 -1
- package/dist/importer/importer.js +82 -24
- package/dist/importer/opencode-import.d.ts +11 -1
- package/dist/importer/opencode-import.js +133 -69
- package/dist/importer/opencode-project.d.ts +1 -4
- package/dist/importer/opencode-project.js +3 -36
- package/dist/importer/opencode-reader.d.ts +25 -5
- package/dist/importer/opencode-reader.js +92 -106
- package/dist/importer/opencode-snapshot.d.ts +82 -0
- package/dist/importer/opencode-snapshot.js +276 -0
- package/dist/importer/profile-import.d.ts +1 -0
- package/dist/importer/profile-import.js +7 -1
- package/dist/importer/run-import.d.ts +10 -0
- package/dist/importer/run-import.js +21 -0
- package/dist/importer/settings-health.d.ts +25 -0
- package/dist/importer/settings-health.js +136 -0
- package/dist/importer/web-import-api.d.ts +8 -0
- package/dist/importer/web-import-api.js +8 -0
- package/dist/importer/web-import-jobs.d.ts +53 -0
- package/dist/importer/web-import-jobs.js +227 -0
- package/dist/index.js +4 -0
- package/dist/services/ai/live-model-choice.js +6 -3
- package/dist/services/ai/opencode-import-models.d.ts +11 -0
- package/dist/services/ai/opencode-import-models.js +55 -0
- package/dist/services/ai/opencode-provider.d.ts +9 -0
- package/dist/services/ai/opencode-provider.js +31 -4
- package/dist/services/ai/providers/anthropic-messages.js +14 -6
- package/dist/services/ai/providers/base-provider.d.ts +8 -0
- package/dist/services/ai/providers/base-provider.js +12 -0
- package/dist/services/ai/providers/google-gemini.js +21 -7
- package/dist/services/ai/providers/openai-chat-completion.js +20 -7
- package/dist/services/ai/providers/openai-responses.js +11 -0
- package/dist/services/capture-attempt-store.d.ts +21 -0
- package/dist/services/capture-attempt-store.js +108 -0
- package/dist/services/capture-diagnostics.d.ts +66 -0
- package/dist/services/capture-diagnostics.js +175 -0
- package/dist/services/cleanup-service.js +9 -0
- package/dist/services/global-config-writer.d.ts +10 -0
- package/dist/services/global-config-writer.js +106 -0
- package/dist/services/log-path.d.ts +2 -0
- package/dist/services/log-path.js +15 -0
- package/dist/services/logger.js +1 -12
- package/dist/services/settings-log.d.ts +5 -0
- package/dist/services/settings-log.js +36 -0
- package/dist/services/settings-models.d.ts +35 -0
- package/dist/services/settings-models.js +39 -0
- package/dist/services/settings-snapshot.d.ts +24 -0
- package/dist/services/settings-snapshot.js +71 -0
- package/dist/services/settings-traces.d.ts +7 -0
- package/dist/services/settings-traces.js +39 -0
- package/dist/services/user-memory-learning.js +2 -1
- package/dist/services/web-server.d.ts +5 -0
- package/dist/services/web-server.js +176 -3
- package/dist/v2/legacy-client.js +19 -2
- package/dist/web/assets/index-BMkPht7g.js +124 -0
- package/dist/web/assets/index-D5ak5KsI.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/web/assets/index-DPjEpD1h.css +0 -2
- package/dist/web/assets/index-do26c4JJ.js +0 -122
|
@@ -5,7 +5,7 @@ import { join, dirname, extname, normalize, sep } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
7
|
import { corsPreflightResponse, disallowedCorsResponse, isAllowedBrowserOrigin } from "./cors.js";
|
|
8
|
-
import { assertWebServerNetworkAuth, authorizeApiRequest } from "./web-api-auth.js";
|
|
8
|
+
import { assertWebServerNetworkAuth, authorizeApiRequest, isLoopbackHost } from "./web-api-auth.js";
|
|
9
9
|
import { getOrCreateAuthToken, isAuthorizedApiRequest } from "./auth-token.js";
|
|
10
10
|
import { WebAuth } from "./web-auth.js";
|
|
11
11
|
import { NODE_HTTP_IDLE_TIMEOUT_MS } from "./request-timeouts.js";
|
|
@@ -145,6 +145,21 @@ export class WebServer {
|
|
|
145
145
|
portsExhaustedNotified = false;
|
|
146
146
|
takeoverFailures = 0;
|
|
147
147
|
maxFallbackPort;
|
|
148
|
+
settingsImportJobs;
|
|
149
|
+
/** Run an import-page action; errors carry their own status and never file contents. */
|
|
150
|
+
async importResponse(action, status = 200) {
|
|
151
|
+
try {
|
|
152
|
+
return this.jsonResponse(await action(), status);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const code = error.status;
|
|
156
|
+
return this.jsonResponse({ error: error instanceof Error ? error.message : "Import request failed" }, typeof code === "number" ? code : 400);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async importJobs() {
|
|
160
|
+
const { SettingsImportJobs } = await import("../importer/web-import-jobs.js");
|
|
161
|
+
return (this.settingsImportJobs ??= new SettingsImportJobs());
|
|
162
|
+
}
|
|
148
163
|
constructor(config) {
|
|
149
164
|
this.config = config;
|
|
150
165
|
this.maxFallbackPort = config.port + 10;
|
|
@@ -166,6 +181,10 @@ export class WebServer {
|
|
|
166
181
|
if (!this.config.enabled) {
|
|
167
182
|
return;
|
|
168
183
|
}
|
|
184
|
+
// Remove OpenCode snapshot copies left by processes that quit mid-import.
|
|
185
|
+
void import("../importer/web-import-api.js")
|
|
186
|
+
.then(({ sweepOrphanSnapshots }) => sweepOrphanSnapshots())
|
|
187
|
+
.catch(() => { });
|
|
169
188
|
assertWebServerNetworkAuth(this.config.host, this.config.apiToken, this.config.auth?.isEnabled() ?? false);
|
|
170
189
|
try {
|
|
171
190
|
this.server = serveFetch({
|
|
@@ -288,6 +307,8 @@ export class WebServer {
|
|
|
288
307
|
this.server.stop();
|
|
289
308
|
this.server = null;
|
|
290
309
|
this.isOwner = false;
|
|
310
|
+
const { opencodeSnapshots } = await import("../importer/web-import-api.js");
|
|
311
|
+
await opencodeSnapshots.closeAll();
|
|
291
312
|
}
|
|
292
313
|
isRunning() {
|
|
293
314
|
return this.server !== null;
|
|
@@ -343,7 +364,10 @@ export class WebServer {
|
|
|
343
364
|
if (!authCheck.ok && authCheck.response)
|
|
344
365
|
return authCheck.response;
|
|
345
366
|
}
|
|
346
|
-
if (path.startsWith("/api/") &&
|
|
367
|
+
if (path.startsWith("/api/") &&
|
|
368
|
+
path !== "/api/health" &&
|
|
369
|
+
!(path.startsWith("/api/settings") && auth?.isEnabled()) &&
|
|
370
|
+
!isAuthorizedApiRequest(req)) {
|
|
347
371
|
const configuredTokenFailure = this.config.apiToken
|
|
348
372
|
? authorizeApiRequest(req, this.config.apiToken)
|
|
349
373
|
: null;
|
|
@@ -352,6 +376,17 @@ export class WebServer {
|
|
|
352
376
|
this.jsonResponse({ success: false, error: "Unauthorized" }, 401));
|
|
353
377
|
}
|
|
354
378
|
}
|
|
379
|
+
if (path.startsWith("/api/settings") && !["GET", "HEAD"].includes(method)) {
|
|
380
|
+
if (!/^application\/json(?:\s*;|$)/i.test(req.headers.get("content-type") ?? "")) {
|
|
381
|
+
return this.jsonResponse({ error: "JSON content type required" }, 415);
|
|
382
|
+
}
|
|
383
|
+
if (!isLoopbackHost(this.config.host) && !auth?.isEnabled()) {
|
|
384
|
+
const denied = authorizeApiRequest(req, this.config.apiToken);
|
|
385
|
+
if (!this.config.apiToken || denied) {
|
|
386
|
+
return denied ?? this.jsonResponse({ error: "Unauthorized" }, 401);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
355
390
|
try {
|
|
356
391
|
if (path === "/api/health" && method === "GET") {
|
|
357
392
|
return this.jsonResponse({
|
|
@@ -360,7 +395,7 @@ export class WebServer {
|
|
|
360
395
|
authEnabled: auth?.isEnabled() ?? false,
|
|
361
396
|
});
|
|
362
397
|
}
|
|
363
|
-
if (path === "/" || path === "/index.html") {
|
|
398
|
+
if (path === "/" || path === "/index.html" || path === "/settings") {
|
|
364
399
|
return this.serveStaticFile("index.html", "text/html");
|
|
365
400
|
}
|
|
366
401
|
if (path === "/favicon.ico") {
|
|
@@ -376,6 +411,144 @@ export class WebServer {
|
|
|
376
411
|
}
|
|
377
412
|
}
|
|
378
413
|
}
|
|
414
|
+
if (path === "/api/settings" && method === "GET") {
|
|
415
|
+
const { getSettingsSnapshot } = await import("./settings-snapshot.js");
|
|
416
|
+
return this.jsonResponse(getSettingsSnapshot(this.config.directory ?? process.cwd()));
|
|
417
|
+
}
|
|
418
|
+
if (path === "/api/settings" && method === "PATCH") {
|
|
419
|
+
const body = (await req.json());
|
|
420
|
+
if (!body ||
|
|
421
|
+
typeof body.edits !== "object" ||
|
|
422
|
+
!body.edits ||
|
|
423
|
+
Array.isArray(body.edits) ||
|
|
424
|
+
typeof body.revision !== "string") {
|
|
425
|
+
return this.jsonResponse({ error: "Edits and revision required" }, 400);
|
|
426
|
+
}
|
|
427
|
+
if (body.edits.captureTrace === true &&
|
|
428
|
+
!isLoopbackHost(this.config.host) &&
|
|
429
|
+
!auth?.isEnabled()) {
|
|
430
|
+
return this.jsonResponse({ error: "Tracing requires Basic Auth on network hosts" }, 403);
|
|
431
|
+
}
|
|
432
|
+
const { writeGlobalConfigKeys, ConfigConflictError } = await import("./global-config-writer.js");
|
|
433
|
+
let result;
|
|
434
|
+
try {
|
|
435
|
+
result = await writeGlobalConfigKeys(body.edits, body.revision);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
return this.jsonResponse({ error: error instanceof Error ? error.message : "Invalid settings" }, error instanceof ConfigConflictError ? 409 : 400);
|
|
439
|
+
}
|
|
440
|
+
// The file is already saved; a reload problem must not report the save as failed.
|
|
441
|
+
try {
|
|
442
|
+
const { refreshConfigIfChanged } = await import("../config.js");
|
|
443
|
+
refreshConfigIfChanged(this.config.directory ?? process.cwd());
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
log("Settings saved, but reloading them failed", {
|
|
447
|
+
error: error instanceof Error ? error.message : String(error),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return this.jsonResponse(result);
|
|
451
|
+
}
|
|
452
|
+
if (path === "/api/settings/models" && method === "GET") {
|
|
453
|
+
const { listOpencodeSettingsModels, listPiSettingsModels } = await import("./settings-models.js");
|
|
454
|
+
const host = url.searchParams.get("host");
|
|
455
|
+
if (host === "pi")
|
|
456
|
+
return this.jsonResponse(await listPiSettingsModels());
|
|
457
|
+
if (host === "opencode")
|
|
458
|
+
return this.jsonResponse(await listOpencodeSettingsModels());
|
|
459
|
+
return this.jsonResponse({ error: "Invalid host" }, 400);
|
|
460
|
+
}
|
|
461
|
+
if (path === "/api/settings/diagnostics" && method === "GET") {
|
|
462
|
+
const { queryCaptureAttempts } = await import("./capture-attempt-store.js");
|
|
463
|
+
const days = Math.max(1, Math.min(90, Number(url.searchParams.get("days")) || 7));
|
|
464
|
+
return this.jsonResponse(await queryCaptureAttempts(Date.now() - days * 86400000, Date.now()));
|
|
465
|
+
}
|
|
466
|
+
if (path === "/api/settings/traces" && method === "GET") {
|
|
467
|
+
const { listSettingsTraces } = await import("./settings-traces.js");
|
|
468
|
+
return this.jsonResponse({ traces: await listSettingsTraces() });
|
|
469
|
+
}
|
|
470
|
+
if (path.startsWith("/api/settings/traces/") && (method === "GET" || method === "DELETE")) {
|
|
471
|
+
const file = decodeURIComponent(path.slice("/api/settings/traces/".length));
|
|
472
|
+
if (!/^capture-\d{4}-\d{2}-\d{2}\.jsonl$/.test(file)) {
|
|
473
|
+
return this.jsonResponse({ error: "Invalid trace file name" }, 400);
|
|
474
|
+
}
|
|
475
|
+
const { readSettingsTrace, deleteSettingsTrace } = await import("./settings-traces.js");
|
|
476
|
+
if (method === "DELETE") {
|
|
477
|
+
await deleteSettingsTrace(file);
|
|
478
|
+
return this.jsonResponse({ deleted: true });
|
|
479
|
+
}
|
|
480
|
+
return this.jsonResponse({ file, content: await readSettingsTrace(file) });
|
|
481
|
+
}
|
|
482
|
+
if (path === "/api/settings/health" && method === "POST") {
|
|
483
|
+
const body = (await req.json());
|
|
484
|
+
if (!body ||
|
|
485
|
+
typeof body !== "object" ||
|
|
486
|
+
(body.testModels !== undefined && typeof body.testModels !== "boolean")) {
|
|
487
|
+
return this.jsonResponse({ error: "Invalid health options" }, 400);
|
|
488
|
+
}
|
|
489
|
+
const { runSettingsHealth } = await import("../importer/settings-health.js");
|
|
490
|
+
return this.jsonResponse(await runSettingsHealth({
|
|
491
|
+
directory: this.config.directory ?? process.cwd(),
|
|
492
|
+
host: this.config.host,
|
|
493
|
+
authEnabled: auth?.isEnabled() ?? false,
|
|
494
|
+
apiTokenSet: Boolean(this.config.apiToken),
|
|
495
|
+
testModels: body.testModels === true,
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
if (path === "/api/settings/imports/readiness" && method === "GET") {
|
|
499
|
+
const { importReadiness } = await import("../importer/web-import-api.js");
|
|
500
|
+
return this.jsonResponse(await importReadiness());
|
|
501
|
+
}
|
|
502
|
+
if (path === "/api/settings/imports/sources/browse" && method === "POST") {
|
|
503
|
+
// Listing folders would expose the machine's layout to remote users.
|
|
504
|
+
if (!isLoopbackHost(this.config.host)) {
|
|
505
|
+
return this.jsonResponse({ error: "Browsing is available only on a loopback bind; enter a path instead" }, 403);
|
|
506
|
+
}
|
|
507
|
+
const body = (await req.json());
|
|
508
|
+
if (body?.host !== "pi" && body?.host !== "opencode") {
|
|
509
|
+
return this.jsonResponse({ error: "Choose Pi or OpenCode" }, 400);
|
|
510
|
+
}
|
|
511
|
+
const { browseImportSources } = await import("../importer/web-import-api.js");
|
|
512
|
+
return this.importResponse(() => browseImportSources(body.host, body.path));
|
|
513
|
+
}
|
|
514
|
+
if (path === "/api/settings/imports/sources/validate" && method === "POST") {
|
|
515
|
+
const body = (await req.json());
|
|
516
|
+
if (body?.host !== "pi" && body?.host !== "opencode") {
|
|
517
|
+
return this.jsonResponse({ error: "Choose Pi or OpenCode" }, 400);
|
|
518
|
+
}
|
|
519
|
+
const { validateImportSource } = await import("../importer/web-import-api.js");
|
|
520
|
+
return this.importResponse(() => validateImportSource(body.host, body.path));
|
|
521
|
+
}
|
|
522
|
+
if (path === "/api/settings/imports/sessions" && method === "POST") {
|
|
523
|
+
const body = (await req.json());
|
|
524
|
+
const { listImportSessions, validateSessionListRequest } = await import("../importer/web-import-api.js");
|
|
525
|
+
return this.importResponse(async () => {
|
|
526
|
+
const request = validateSessionListRequest(body);
|
|
527
|
+
return listImportSessions(request, {
|
|
528
|
+
...request.match,
|
|
529
|
+
cwd: this.config.directory ?? process.cwd(),
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (path === "/api/settings/imports" && method === "POST") {
|
|
534
|
+
const jobs = await this.importJobs();
|
|
535
|
+
return this.importResponse(async () => jobs.start(await req.json(), this.config.directory ?? process.cwd()), 202);
|
|
536
|
+
}
|
|
537
|
+
if (path === "/api/settings/imports/current" && method === "GET") {
|
|
538
|
+
return this.jsonResponse({ job: (await this.importJobs()).current() });
|
|
539
|
+
}
|
|
540
|
+
if (path === "/api/settings/imports/current/cancel" && method === "POST") {
|
|
541
|
+
try {
|
|
542
|
+
return this.jsonResponse({ job: (await this.importJobs()).cancel() });
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
return this.jsonResponse({ error: error instanceof Error ? error.message : "No running import" }, 409);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (path === "/api/settings/log" && method === "GET") {
|
|
549
|
+
const { readSettingsLog } = await import("./settings-log.js");
|
|
550
|
+
return this.jsonResponse(await readSettingsLog(Number(url.searchParams.get("lines") ?? 200), url.searchParams.get("filter") === "capture"));
|
|
551
|
+
}
|
|
379
552
|
if (path === "/api/tags" && method === "GET") {
|
|
380
553
|
const result = await handleListTags();
|
|
381
554
|
return this.jsonResponse(result);
|
package/dist/v2/legacy-client.js
CHANGED
|
@@ -112,7 +112,15 @@ export function createLegacyClient(ctx) {
|
|
|
112
112
|
const connected = [
|
|
113
113
|
...new Set(models.data.map((model) => model.providerID).filter(Boolean)),
|
|
114
114
|
];
|
|
115
|
-
|
|
115
|
+
// The Settings page lists each connected provider's models from `all`, as
|
|
116
|
+
// the V1 SDK's provider list returns them.
|
|
117
|
+
const all = connected.map((id) => ({
|
|
118
|
+
id,
|
|
119
|
+
models: Object.fromEntries(models.data
|
|
120
|
+
.filter((model) => model.providerID === id)
|
|
121
|
+
.map((model) => [model.id, { name: model.name || model.id }])),
|
|
122
|
+
}));
|
|
123
|
+
return { data: { connected, all } };
|
|
116
124
|
},
|
|
117
125
|
},
|
|
118
126
|
tui: {
|
|
@@ -222,8 +230,17 @@ export function toLegacyEvent(raw) {
|
|
|
222
230
|
const envelope = raw?.payload ?? raw;
|
|
223
231
|
const source = envelope?.type === "sync" && envelope.syncEvent ? envelope.syncEvent : envelope;
|
|
224
232
|
const rawType = typeof source?.type === "string" ? source.type.replace(/\.1$/, "") : source?.type;
|
|
225
|
-
const type = rawType === "session.compaction.ended" ? "session.compacted" : rawType;
|
|
226
233
|
const data = source?.data ?? {};
|
|
234
|
+
// OpenCode 2.0.14 ends a turn with `session.execution.succeeded` and emits no
|
|
235
|
+
// `session.idle`; some builds report idleness through `session.status`. The
|
|
236
|
+
// V1 capture path listens for `session.idle`, so both map onto it.
|
|
237
|
+
// Subscribed events carry their payload in `data`; bus-style ones in `properties`.
|
|
238
|
+
const payload = source?.data ?? source?.properties ?? {};
|
|
239
|
+
if (rawType === "session.execution.succeeded" ||
|
|
240
|
+
(rawType === "session.status" && payload?.status?.type === "idle")) {
|
|
241
|
+
return { type: "session.idle", properties: { sessionID: payload.sessionID } };
|
|
242
|
+
}
|
|
243
|
+
const type = rawType === "session.compaction.ended" ? "session.compacted" : rawType;
|
|
227
244
|
if (source && typeof source === "object" && "properties" in source) {
|
|
228
245
|
return { type, properties: source.properties };
|
|
229
246
|
}
|