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.
Files changed (88) hide show
  1. package/dist/adapters/opencode/auto-capture-summary.js +50 -8
  2. package/dist/adapters/opencode/import-command.d.ts +3 -7
  3. package/dist/adapters/opencode/import-command.js +3 -54
  4. package/dist/adapters/pi/extension.js +3 -0
  5. package/dist/adapters/pi/profile.js +2 -1
  6. package/dist/adapters/pi/provider.js +41 -14
  7. package/dist/config.d.ts +18 -1
  8. package/dist/config.js +103 -9
  9. package/dist/core/capture-context.js +1 -1
  10. package/dist/core/capture.d.ts +4 -0
  11. package/dist/core/capture.js +30 -0
  12. package/dist/core/extraction.d.ts +23 -1
  13. package/dist/core/extraction.js +41 -0
  14. package/dist/core/host.d.ts +23 -0
  15. package/dist/core/host.js +9 -1
  16. package/dist/core/profile-analysis.js +9 -1
  17. package/dist/importer/discovery.d.ts +32 -5
  18. package/dist/importer/discovery.js +88 -36
  19. package/dist/importer/import-args.d.ts +23 -1
  20. package/dist/importer/import-args.js +29 -1
  21. package/dist/importer/import-project.d.ts +16 -0
  22. package/dist/importer/import-project.js +35 -0
  23. package/dist/importer/import-readiness.d.ts +47 -0
  24. package/dist/importer/import-readiness.js +54 -0
  25. package/dist/importer/import-sessions.d.ts +98 -0
  26. package/dist/importer/import-sessions.js +222 -0
  27. package/dist/importer/import-sources.d.ts +47 -0
  28. package/dist/importer/import-sources.js +147 -0
  29. package/dist/importer/importer.d.ts +31 -1
  30. package/dist/importer/importer.js +82 -24
  31. package/dist/importer/opencode-import.d.ts +11 -1
  32. package/dist/importer/opencode-import.js +133 -69
  33. package/dist/importer/opencode-project.d.ts +1 -4
  34. package/dist/importer/opencode-project.js +3 -36
  35. package/dist/importer/opencode-reader.d.ts +25 -5
  36. package/dist/importer/opencode-reader.js +92 -106
  37. package/dist/importer/opencode-snapshot.d.ts +82 -0
  38. package/dist/importer/opencode-snapshot.js +276 -0
  39. package/dist/importer/profile-import.d.ts +1 -0
  40. package/dist/importer/profile-import.js +7 -1
  41. package/dist/importer/run-import.d.ts +10 -0
  42. package/dist/importer/run-import.js +21 -0
  43. package/dist/importer/settings-health.d.ts +25 -0
  44. package/dist/importer/settings-health.js +136 -0
  45. package/dist/importer/web-import-api.d.ts +8 -0
  46. package/dist/importer/web-import-api.js +8 -0
  47. package/dist/importer/web-import-jobs.d.ts +53 -0
  48. package/dist/importer/web-import-jobs.js +227 -0
  49. package/dist/index.js +4 -0
  50. package/dist/services/ai/live-model-choice.js +6 -3
  51. package/dist/services/ai/opencode-import-models.d.ts +11 -0
  52. package/dist/services/ai/opencode-import-models.js +55 -0
  53. package/dist/services/ai/opencode-provider.d.ts +9 -0
  54. package/dist/services/ai/opencode-provider.js +31 -4
  55. package/dist/services/ai/providers/anthropic-messages.js +14 -6
  56. package/dist/services/ai/providers/base-provider.d.ts +8 -0
  57. package/dist/services/ai/providers/base-provider.js +12 -0
  58. package/dist/services/ai/providers/google-gemini.js +21 -7
  59. package/dist/services/ai/providers/openai-chat-completion.js +20 -7
  60. package/dist/services/ai/providers/openai-responses.js +11 -0
  61. package/dist/services/capture-attempt-store.d.ts +21 -0
  62. package/dist/services/capture-attempt-store.js +108 -0
  63. package/dist/services/capture-diagnostics.d.ts +66 -0
  64. package/dist/services/capture-diagnostics.js +175 -0
  65. package/dist/services/cleanup-service.js +9 -0
  66. package/dist/services/global-config-writer.d.ts +10 -0
  67. package/dist/services/global-config-writer.js +106 -0
  68. package/dist/services/log-path.d.ts +2 -0
  69. package/dist/services/log-path.js +15 -0
  70. package/dist/services/logger.js +1 -12
  71. package/dist/services/settings-log.d.ts +5 -0
  72. package/dist/services/settings-log.js +36 -0
  73. package/dist/services/settings-models.d.ts +35 -0
  74. package/dist/services/settings-models.js +39 -0
  75. package/dist/services/settings-snapshot.d.ts +24 -0
  76. package/dist/services/settings-snapshot.js +71 -0
  77. package/dist/services/settings-traces.d.ts +7 -0
  78. package/dist/services/settings-traces.js +39 -0
  79. package/dist/services/user-memory-learning.js +2 -1
  80. package/dist/services/web-server.d.ts +5 -0
  81. package/dist/services/web-server.js +176 -3
  82. package/dist/v2/legacy-client.js +19 -2
  83. package/dist/web/assets/index-BMkPht7g.js +124 -0
  84. package/dist/web/assets/index-D5ak5KsI.css +2 -0
  85. package/dist/web/index.html +2 -2
  86. package/package.json +2 -1
  87. package/dist/web/assets/index-DPjEpD1h.css +0 -2
  88. 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/") && path !== "/api/health" && !isAuthorizedApiRequest(req)) {
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);
@@ -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
- return { data: { connected } };
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
  }