om-memory-system 3.4.0-next.23 → 3.4.0-next.27

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 (122) hide show
  1. package/README.md +21 -7
  2. package/dist/adapters/opencode/auto-capture-summary.js +1 -1
  3. package/dist/adapters/opencode/backfill-models.d.ts +1 -1
  4. package/dist/adapters/opencode/backfill-models.js +6 -2
  5. package/dist/adapters/opencode/backfill-startup.d.ts +8 -0
  6. package/dist/adapters/opencode/backfill-startup.js +19 -0
  7. package/dist/adapters/opencode/import-command.d.ts +3 -3
  8. package/dist/adapters/opencode/import-command.js +4 -3
  9. package/dist/adapters/opencode/internal-capture-sessions.d.ts +4 -0
  10. package/dist/{services/ai → adapters/opencode}/internal-capture-sessions.js +1 -14
  11. package/dist/{services/ai → adapters/opencode}/opencode-import-models.js +1 -1
  12. package/dist/{services/user-memory-learning.d.ts → adapters/opencode/profile-learning.d.ts} +1 -1
  13. package/dist/{services/user-memory-learning.js → adapters/opencode/profile-learning.js} +14 -14
  14. package/dist/{services/ai → adapters/opencode}/profile-llm-client.js +1 -1
  15. package/dist/adapters/opencode/profile-model.d.ts +23 -0
  16. package/dist/adapters/opencode/profile-model.js +117 -0
  17. package/dist/adapters/opencode/user-prompt.d.ts +2 -1
  18. package/dist/adapters/opencode/user-prompt.js +3 -10
  19. package/dist/adapters/pi/backfill-models.d.ts +4 -0
  20. package/dist/adapters/pi/backfill-models.js +3 -0
  21. package/dist/adapters/pi/capture.d.ts +1 -1
  22. package/dist/adapters/pi/capture.js +1 -1
  23. package/dist/adapters/pi/extension.js +7 -0
  24. package/dist/adapters/pi/import-command.js +1 -0
  25. package/dist/cli/index.js +13 -1
  26. package/dist/config.d.ts +11 -4
  27. package/dist/config.js +29 -6
  28. package/dist/core/internal-prompt.d.ts +6 -0
  29. package/dist/core/internal-prompt.js +11 -0
  30. package/dist/core/profile-analysis.d.ts +3 -0
  31. package/dist/importer/auto-backfill.d.ts +5 -0
  32. package/dist/importer/auto-backfill.js +29 -10
  33. package/dist/importer/backfill-controls.d.ts +53 -0
  34. package/dist/importer/backfill-controls.js +126 -0
  35. package/dist/importer/backfill-lock.d.ts +1 -0
  36. package/dist/importer/backfill-lock.js +2 -2
  37. package/dist/importer/backfill-model.d.ts +1 -1
  38. package/dist/importer/backfill-model.js +2 -0
  39. package/dist/importer/external-api-test.d.ts +18 -0
  40. package/dist/importer/external-api-test.js +50 -0
  41. package/dist/importer/external-backfill-models.d.ts +15 -0
  42. package/dist/importer/external-backfill-models.js +14 -0
  43. package/dist/importer/import-path-maps.d.ts +14 -0
  44. package/dist/importer/import-path-maps.js +49 -0
  45. package/dist/importer/import-progress.d.ts +27 -0
  46. package/dist/importer/import-progress.js +38 -0
  47. package/dist/importer/import-readiness.js +1 -1
  48. package/dist/importer/import-runs.d.ts +43 -0
  49. package/dist/importer/import-runs.js +141 -0
  50. package/dist/importer/import-sessions.js +9 -9
  51. package/dist/importer/importer.d.ts +2 -1
  52. package/dist/importer/importer.js +2 -2
  53. package/dist/importer/map-suggestions.d.ts +39 -0
  54. package/dist/importer/map-suggestions.js +133 -0
  55. package/dist/importer/opencode-import.d.ts +2 -1
  56. package/dist/{services/ai/internal-capture-sessions.d.ts → importer/opencode-internal-sessions.d.ts} +0 -3
  57. package/dist/importer/opencode-internal-sessions.js +14 -0
  58. package/dist/importer/opencode-reader.js +1 -1
  59. package/dist/{adapters/pi/conversation.d.ts → importer/pi-conversation.d.ts} +1 -1
  60. package/dist/importer/profile-import.js +3 -2
  61. package/dist/importer/run-import.d.ts +21 -3
  62. package/dist/importer/run-import.js +82 -2
  63. package/dist/importer/session-loader.d.ts +1 -1
  64. package/dist/importer/settings-health.js +8 -1
  65. package/dist/{services → importer}/settings-models.d.ts +4 -5
  66. package/dist/{services → importer}/settings-models.js +24 -13
  67. package/dist/importer/web-import-api.d.ts +3 -0
  68. package/dist/importer/web-import-api.js +3 -0
  69. package/dist/importer/web-import-jobs.js +5 -5
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +22 -9
  72. package/dist/services/ai/live-model-choice.d.ts +4 -1
  73. package/dist/services/ai/live-model-choice.js +18 -0
  74. package/dist/services/api-handlers.d.ts +6 -1
  75. package/dist/services/api-handlers.js +71 -62
  76. package/dist/services/backfill-state.d.ts +30 -0
  77. package/dist/services/backfill-state.js +47 -0
  78. package/dist/services/capture-diagnostics.d.ts +2 -8
  79. package/dist/services/capture-diagnostics.js +3 -49
  80. package/dist/services/global-config-writer.d.ts +2 -0
  81. package/dist/services/global-config-writer.js +37 -6
  82. package/dist/services/global-version.d.ts +31 -0
  83. package/dist/services/global-version.js +78 -0
  84. package/dist/services/memory-key-source.d.ts +35 -0
  85. package/dist/services/memory-key-source.js +123 -0
  86. package/dist/services/package-version.d.ts +2 -0
  87. package/dist/services/package-version.js +21 -0
  88. package/dist/services/private-path.d.ts +13 -0
  89. package/dist/services/private-path.js +54 -0
  90. package/dist/services/settings-snapshot.d.ts +17 -0
  91. package/dist/services/settings-snapshot.js +51 -14
  92. package/dist/services/turso/vector-search.d.ts +1 -1
  93. package/dist/services/turso/vector-search.js +5 -2
  94. package/dist/services/user-profile/ai-cleanup.d.ts +0 -16
  95. package/dist/services/user-profile/ai-cleanup.js +17 -91
  96. package/dist/services/user-profile/profile-model.d.ts +8 -0
  97. package/dist/services/user-profile/profile-model.js +31 -0
  98. package/dist/services/user-profile/user-profile-manager.d.ts +1 -1
  99. package/dist/services/user-profile/user-profile-manager.js +20 -74
  100. package/dist/services/web-server.d.ts +2 -0
  101. package/dist/services/web-server.js +75 -2
  102. package/dist/shared/api/schemas.d.ts +1 -1
  103. package/dist/v2/plugin.js +1 -1
  104. package/dist/web/assets/index-DvZhF1BJ.css +2 -0
  105. package/dist/web/assets/index-jqmX5i_i.js +124 -0
  106. package/dist/web/index.html +2 -2
  107. package/package.json +6 -1
  108. package/dist/web/assets/index-B-_gMSn9.js +0 -124
  109. package/dist/web/assets/index-D5ak5KsI.css +0 -2
  110. /package/dist/{services/ai → adapters/opencode}/opencode-diagnostics.d.ts +0 -0
  111. /package/dist/{services/ai → adapters/opencode}/opencode-diagnostics.js +0 -0
  112. /package/dist/{services/ai → adapters/opencode}/opencode-host-config.d.ts +0 -0
  113. /package/dist/{services/ai → adapters/opencode}/opencode-host-config.js +0 -0
  114. /package/dist/{services/ai → adapters/opencode}/opencode-import-models.d.ts +0 -0
  115. /package/dist/{services/ai → adapters/opencode}/opencode-provider-loader.d.ts +0 -0
  116. /package/dist/{services/ai → adapters/opencode}/opencode-provider-loader.js +0 -0
  117. /package/dist/{services/ai → adapters/opencode}/opencode-provider.d.ts +0 -0
  118. /package/dist/{services/ai → adapters/opencode}/opencode-provider.js +0 -0
  119. /package/dist/{services/ai → adapters/opencode}/opencode-sdk-client.d.ts +0 -0
  120. /package/dist/{services/ai → adapters/opencode}/opencode-sdk-client.js +0 -0
  121. /package/dist/{services/ai → adapters/opencode}/profile-llm-client.d.ts +0 -0
  122. /package/dist/{adapters/pi/conversation.js → importer/pi-conversation.js} +0 -0
@@ -26,7 +26,8 @@ export interface OpencodeImportOptions {
26
26
  profileBatch?: number;
27
27
  provider?: CaptureSummaryProvider;
28
28
  profileModel?: ModelPort;
29
- onProgress?: (processed: number, total: number, promptPreview: string) => void;
29
+ /** `alreadyHandled`: units found already done so far, which need no model call. */
30
+ onProgress?: (processed: number, total: number, promptPreview: string, alreadyHandled?: number) => void;
30
31
  signal?: AbortSignal;
31
32
  }
32
33
  export interface OpencodeImportReport extends ImportReport {
@@ -3,6 +3,3 @@ export declare const INTERNAL_CAPTURE_SESSION_TITLE = "omms capture";
3
3
  /** Every title omms has used for its own sessions (capture, profile learning, profile cleanup). */
4
4
  export declare const INTERNAL_CAPTURE_SESSION_TITLES: readonly ["omms capture", "opencode-mem capture", "omms profile cleanup", "opencode-mem profile cleanup"];
5
5
  export declare function isInternalCaptureSessionTitle(title: string | undefined | null): boolean;
6
- export declare function trackInternalCaptureSession(sessionID: string): void;
7
- export declare function untrackInternalCaptureSession(sessionID: string): void;
8
- export declare function isTrackedInternalCaptureSession(sessionID: string): boolean;
@@ -0,0 +1,14 @@
1
+ /** Title used for transient structured-output sessions (capture / profile learning). */
2
+ export const INTERNAL_CAPTURE_SESSION_TITLE = "omms capture";
3
+ /** Title used by opencode-mem builds; sessions left by older versions stay internal. */
4
+ const LEGACY_INTERNAL_CAPTURE_SESSION_TITLE = "opencode-mem capture";
5
+ /** Every title omms has used for its own sessions (capture, profile learning, profile cleanup). */
6
+ export const INTERNAL_CAPTURE_SESSION_TITLES = [
7
+ INTERNAL_CAPTURE_SESSION_TITLE,
8
+ LEGACY_INTERNAL_CAPTURE_SESSION_TITLE,
9
+ "omms profile cleanup",
10
+ "opencode-mem profile cleanup",
11
+ ];
12
+ export function isInternalCaptureSessionTitle(title) {
13
+ return (title === INTERNAL_CAPTURE_SESSION_TITLE || title === LEGACY_INTERNAL_CAPTURE_SESSION_TITLE);
14
+ }
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { DatabaseSync } from "node:sqlite";
6
- import { INTERNAL_CAPTURE_SESSION_TITLES } from "../services/ai/internal-capture-sessions.js";
6
+ import { INTERNAL_CAPTURE_SESSION_TITLES } from "./opencode-internal-sessions.js";
7
7
  import { acquirePrivateSnapshot, opencodeSnapshots, OpencodeSnapshotError, } from "./opencode-snapshot.js";
8
8
  export const DEFAULT_OPENCODE_DB = join(homedir(), ".local/share/opencode/opencode.db");
9
9
  function validateSchema(db) {
@@ -1,4 +1,4 @@
1
- import type { CaptureConversation } from "../../core/host.js";
1
+ import type { CaptureConversation } from "../core/host.js";
2
2
  /**
3
3
  * Minimal structural view of Pi session entries (see Pi session-format docs).
4
4
  * Kept structural so the normaliser stays testable without importing Pi types.
@@ -1,4 +1,4 @@
1
- import { isInternalPrompt } from "../adapters/opencode/user-prompt.js";
1
+ import { isStructuredSummaryPromptMessage } from "../core/internal-prompt.js";
2
2
  import { analyzeProfile } from "../core/profile-analysis.js";
3
3
  import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
4
4
  import { getTags } from "../services/tags.js";
@@ -37,7 +37,8 @@ export async function importProfileFromHistory(source, options) {
37
37
  const prompt = stripPrivateContent(unit.userPrompt).trim();
38
38
  if (!prompt ||
39
39
  isFullyPrivate(unit.userPrompt) ||
40
- isInternalPrompt(session.sessionId, prompt)) {
40
+ // OpenCode history already leaves out omms's own capture sessions by title.
41
+ isStructuredSummaryPromptMessage(prompt)) {
41
42
  continue;
42
43
  }
43
44
  const key = buildImportKey(session.sessionId, unit, options.host);
@@ -1,8 +1,9 @@
1
1
  import type { CaptureSummaryProvider } from "../core/host.js";
2
2
  import type { ModelPort } from "../core/profile-analysis.js";
3
3
  import type { HistoryImportArgs, ImportHost } from "./import-args.js";
4
- import type { ImportReport } from "./importer.js";
4
+ import type { ImportPathMap, ImportReport } from "./importer.js";
5
5
  import type { UnresolvedProject } from "./opencode-project.js";
6
+ import type { ImportSurface } from "./import-runs.js";
6
7
  /** The two model roles an import uses; absent in a dry run or when the step is skipped. */
7
8
  export interface HistoryImportModels {
8
9
  capture?: CaptureSummaryProvider;
@@ -12,8 +13,17 @@ export interface HistoryImportRun {
12
13
  /** Directory that relative paths and the default project resolve against. */
13
14
  cwd: string;
14
15
  models: HistoryImportModels;
15
- onProgress?: (processed: number, total: number, promptPreview: string) => void;
16
+ /** `alreadyHandled`: units found already done so far, which need no model call. */
17
+ onProgress?: (processed: number, total: number, promptPreview: string, alreadyHandled?: number) => void;
16
18
  signal?: AbortSignal;
19
+ /** Record progress and take the host's lock for a run that calls models. */
20
+ track?: {
21
+ surface: ImportSurface;
22
+ lockHeld?: boolean;
23
+ expectedTotal?: number;
24
+ };
25
+ /** Saved directory maps; defaults to the global `importPathMaps`. */
26
+ savedPathMaps?: readonly ImportPathMap[];
17
27
  /** Web selection, already re-resolved against the source; `args.source` is its real path. */
18
28
  selection?: {
19
29
  keys: string[];
@@ -26,7 +36,15 @@ export type HistoryImportReport = ImportReport & {
26
36
  childSessionsFolded?: number;
27
37
  unresolvedProjects?: UnresolvedProject[];
28
38
  };
29
- /** Run one host's history import with already-parsed, validated arguments. */
39
+ /** Another process or surface holds this host's import lock. */
40
+ export declare class ImportAlreadyRunningError extends Error {
41
+ constructor(host: ImportHost);
42
+ }
43
+ /**
44
+ * Run one host's history import with already-parsed, validated arguments.
45
+ * With `track`, a run that calls models takes the host's cross-process lock
46
+ * and records its progress; a dry run never does either.
47
+ */
30
48
  export declare function runHistoryImport(host: ImportHost, args: HistoryImportArgs, run: HistoryImportRun): Promise<HistoryImportReport>;
31
49
  /** The same plain-text report for every host and surface. */
32
50
  export declare function formatHistoryImportReport(host: ImportHost, report: HistoryImportReport, model?: string): string;
@@ -4,11 +4,91 @@ const dryRunCapture = {
4
4
  throw new Error("A dry-run cannot call the capture model");
5
5
  },
6
6
  };
7
- /** Run one host's history import with already-parsed, validated arguments. */
7
+ /** Another process or surface holds this host's import lock. */
8
+ export class ImportAlreadyRunningError extends Error {
9
+ constructor(host) {
10
+ super(`${host === "pi" ? "A Pi" : "An OpenCode"} import is already running`);
11
+ this.name = "ImportAlreadyRunningError";
12
+ }
13
+ }
14
+ /**
15
+ * Run one host's history import with already-parsed, validated arguments.
16
+ * With `track`, a run that calls models takes the host's cross-process lock
17
+ * and records its progress; a dry run never does either.
18
+ */
8
19
  export async function runHistoryImport(host, args, run) {
20
+ if (!run.track || args.dryRun)
21
+ return runUntracked(host, args, run);
22
+ let release = null;
23
+ if (!run.track.lockHeld) {
24
+ const { tryAcquireBackfillLock } = await import("./backfill-lock.js");
25
+ release = await tryAcquireBackfillLock(host);
26
+ if (!release)
27
+ throw new ImportAlreadyRunningError(host);
28
+ }
29
+ try {
30
+ const { startImportRun } = await import("./import-runs.js");
31
+ const { workProgress } = await import("./import-progress.js");
32
+ const controller = new AbortController();
33
+ const onAbort = () => controller.abort();
34
+ run.signal?.addEventListener("abort", onAbort, { once: true });
35
+ if (run.signal?.aborted)
36
+ controller.abort();
37
+ const recorder = await startImportRun(host, run.track.surface, {
38
+ onPause: () => controller.abort(),
39
+ });
40
+ try {
41
+ const report = await runUntracked(host, args, {
42
+ ...run,
43
+ signal: controller.signal,
44
+ onProgress: (processed, total, preview, alreadyHandled = 0) => {
45
+ // Units already in the ledger need no model call: leave them out of
46
+ // the progress so the bar and the time left describe real work.
47
+ const work = workProgress(processed, total, alreadyHandled, run.track.expectedTotal);
48
+ recorder.progress(work.done, work.total);
49
+ run.onProgress?.(processed, total, preview, alreadyHandled);
50
+ },
51
+ });
52
+ const handled = report.unitsImported + report.unitsSkipped + report.unitsFailed;
53
+ await recorder.finish(recorder.pauseRequested ? "paused" : controller.signal.aborted ? "stopped" : "done", {
54
+ // The total stays the one progress last reported; a real run has no
55
+ // "would import" count of its own.
56
+ done: handled,
57
+ imported: report.unitsImported,
58
+ skipped: report.unitsSkipped,
59
+ failed: report.unitsFailed,
60
+ });
61
+ await recordUnresolved(host, report);
62
+ return report;
63
+ }
64
+ catch (error) {
65
+ await recorder
66
+ .finish(recorder.pauseRequested ? "paused" : controller.signal.aborted ? "stopped" : "failed", {}, error)
67
+ .catch(() => { });
68
+ throw error;
69
+ }
70
+ finally {
71
+ run.signal?.removeEventListener("abort", onAbort);
72
+ }
73
+ }
74
+ finally {
75
+ await release?.();
76
+ }
77
+ }
78
+ async function recordUnresolved(host, report) {
79
+ try {
80
+ const { recordUnresolvedDirectories, unresolvedDirectoriesOf } = await import("../services/backfill-state.js");
81
+ await recordUnresolvedDirectories(host, unresolvedDirectoriesOf(report));
82
+ }
83
+ catch {
84
+ // The list is a convenience for the Settings page; the import itself succeeded.
85
+ }
86
+ }
87
+ async function runUntracked(host, args, run) {
9
88
  const at = (path) => resolve(run.cwd, path);
10
89
  const project = args.scope === "current-project" ? at(args.project ?? ".") : undefined;
11
- const pathMaps = args.pathMaps.map((map) => ({ from: map.from, to: at(map.to) }));
90
+ const { runPathMaps } = await import("./import-path-maps.js");
91
+ const pathMaps = await runPathMaps(args.pathMaps, run.cwd, run.savedPathMaps);
12
92
  const capture = run.models.capture ?? dryRunCapture;
13
93
  if (host === "opencode") {
14
94
  const { importOpencodeHistory } = await import("./opencode-import.js");
@@ -1,4 +1,4 @@
1
- import type { PiSessionEntry } from "../adapters/pi/conversation.js";
1
+ import type { PiSessionEntry } from "./pi-conversation.js";
2
2
  /**
3
3
  * Loads a Pi session file for import through Pi's own exported session model
4
4
  * (`SessionManager.open`), which handles parsing and legacy-version migration
@@ -4,6 +4,13 @@ import { isLoopbackHost } from "../services/web-api-auth.js";
4
4
  import { safeHealthError } from "../services/safe-health-error.js";
5
5
  export { safeHealthError } from "../services/safe-health-error.js";
6
6
  const PROBE = "Reply with a short acknowledgement.";
7
+ async function createOpencodeImportModels(ref, directory) {
8
+ const { getOpencodeHostModels } = await import("./backfill-controls.js");
9
+ const opencode = getOpencodeHostModels();
10
+ if (!opencode)
11
+ throw new Error("the OpenCode client is not ready; retry in a moment");
12
+ return opencode.createImportModels(ref, directory);
13
+ }
7
14
  export function captureFailureHealth(byModel, byReason) {
8
15
  const total = byModel.reduce((count, row) => count + row.total, 0);
9
16
  const failed = byModel.reduce((count, row) => count + row.failed, 0);
@@ -93,7 +100,7 @@ export async function runSettingsHealth(input) {
93
100
  if (ref?.modelID === "inherit")
94
101
  throw new Error("A session model needs an active session to test");
95
102
  const capture = ref
96
- ? (await (await import("../services/ai/opencode-import-models.js")).createOpencodeImportModels(ref, input.directory)).capture
103
+ ? (await createOpencodeImportModels(ref, input.directory)).capture
97
104
  : (await import("./model-selection.js")).selectImportModel({}).capture;
98
105
  await capture.summarize({
99
106
  userPrompt: PROBE,
@@ -1,15 +1,14 @@
1
1
  import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
2
+ import type { SettingsModel } from "./backfill-controls.js";
3
+ /** Connected models from an OpenCode client; null when the client returns no list. */
4
+ export declare function listOpencodeClientModels(client: Pick<OpencodeClient, "provider">): Promise<SettingsModel[] | null>;
2
5
  export declare function listOpencodeSettingsModels(client?: Pick<OpencodeClient, "provider">): Promise<{
3
6
  available: false;
4
7
  reason: string;
5
8
  models?: undefined;
6
9
  } | {
7
10
  available: true;
8
- models: {
9
- provider: string;
10
- model: string;
11
- name: string;
12
- }[];
11
+ models: SettingsModel[];
13
12
  reason?: undefined;
14
13
  }>;
15
14
  export declare function listPiSettingsModels(loadSdk?: () => Promise<typeof import("@earendil-works/pi-coding-agent")>): Promise<{
@@ -1,19 +1,30 @@
1
+ /** Connected models from an OpenCode client; null when the client returns no list. */
2
+ export async function listOpencodeClientModels(client) {
3
+ const result = await client.provider.list();
4
+ if (!result.data)
5
+ return null;
6
+ const connected = new Set(result.data.connected);
7
+ return result.data.all
8
+ .filter((provider) => connected.has(provider.id))
9
+ .flatMap((provider) => Object.entries(provider.models).map(([model, info]) => ({
10
+ provider: provider.id,
11
+ model,
12
+ name: info.name,
13
+ })));
14
+ }
1
15
  export async function listOpencodeSettingsModels(client) {
2
16
  try {
3
- const active = client ?? (await import("./ai/opencode-provider.js")).getV2Client();
4
- if (!active)
5
- return { available: false, reason: "OpenCode model list unavailable" };
6
- const result = await active.provider.list();
7
- if (!result.data)
17
+ let models;
18
+ if (client) {
19
+ models = await listOpencodeClientModels(client);
20
+ }
21
+ else {
22
+ const { getOpencodeHostModels } = await import("./backfill-controls.js");
23
+ const opencode = getOpencodeHostModels();
24
+ models = opencode ? await opencode.listSettingsModels() : null;
25
+ }
26
+ if (!models)
8
27
  return { available: false, reason: "OpenCode model list unavailable" };
9
- const connected = new Set(result.data.connected);
10
- const models = result.data.all
11
- .filter((provider) => connected.has(provider.id))
12
- .flatMap((provider) => Object.entries(provider.models).map(([model, info]) => ({
13
- provider: provider.id,
14
- model,
15
- name: info.name,
16
- })));
17
28
  return { available: true, models };
18
29
  }
19
30
  catch {
@@ -6,3 +6,6 @@ export { importReadiness } from "./import-readiness.js";
6
6
  export { browseImportSources, validateImportSource } from "./import-sources.js";
7
7
  export { listImportSessions, validateSessionListRequest } from "./import-sessions.js";
8
8
  export { opencodeSnapshots, sweepOrphanSnapshots } from "./opencode-snapshot.js";
9
+ export { BackfillControls } from "./backfill-controls.js";
10
+ export { directoryMapsView } from "./map-suggestions.js";
11
+ export { testExternalApi } from "./external-api-test.js";
@@ -6,3 +6,6 @@ export { importReadiness } from "./import-readiness.js";
6
6
  export { browseImportSources, validateImportSource } from "./import-sources.js";
7
7
  export { listImportSessions, validateSessionListRequest } from "./import-sessions.js";
8
8
  export { opencodeSnapshots, sweepOrphanSnapshots } from "./opencode-snapshot.js";
9
+ export { BackfillControls } from "./backfill-controls.js";
10
+ export { directoryMapsView } from "./map-suggestions.js";
11
+ export { testExternalApi } from "./external-api-test.js";
@@ -173,12 +173,11 @@ export class SettingsImportJobs {
173
173
  }
174
174
  else {
175
175
  const [providerID = "", ...id] = input.modelChoice.split("/");
176
- const { loadOpencodeProvider } = await import("../services/ai/opencode-provider-loader.js");
177
- const { isProviderConnected } = await loadOpencodeProvider();
178
- if (!isProviderConnected(providerID))
176
+ const { getOpencodeHostModels } = await import("./backfill-controls.js");
177
+ const opencode = getOpencodeHostModels();
178
+ if (!opencode?.isProviderConnected(providerID))
179
179
  throw new Error("OpenCode provider is not connected");
180
- const { createOpencodeImportModels } = await import("../services/ai/opencode-import-models.js");
181
- models = await createOpencodeImportModels({ providerID, modelID: id.join("/") }, directory);
180
+ models = await opencode.createImportModels({ providerID, modelID: id.join("/") }, directory);
182
181
  }
183
182
  const { memoryClient } = await import("../services/client.js");
184
183
  await memoryClient.warmup();
@@ -187,6 +186,7 @@ export class SettingsImportJobs {
187
186
  cwd: directory,
188
187
  models,
189
188
  signal: controller.signal,
189
+ track: { surface: "web" },
190
190
  selection: {
191
191
  keys: selection.keys,
192
192
  cutoff: selection.cutoff,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  import { isStructuredSummaryPromptMessage } from "./adapters/opencode/user-prompt.js";
3
- import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle } from "./services/ai/internal-capture-sessions.js";
3
+ import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle } from "./adapters/opencode/internal-capture-sessions.js";
4
4
  export { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle };
5
5
  export { isStructuredSummaryPromptMessage };
6
6
  /**
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { isInternalPrompt, isStructuredSummaryPromptMessage, recordUserPrompt, }
8
8
  import { executeMemoryOperation } from "./core/memory-operations.js";
9
9
  import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, latestUserMessageModel, runOpencodeImportCommand, } from "./adapters/opencode/import-command.js";
10
10
  import { formatMemoriesForCompaction } from "./core/retrieval.js";
11
- import { performUserProfileLearning } from "./services/user-memory-learning.js";
11
+ import { performUserProfileLearning } from "./adapters/opencode/profile-learning.js";
12
12
  import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
13
13
  import { startWebServer, WebServer } from "./services/web-server.js";
14
14
  import { pruneTraces } from "./services/capture-diagnostics.js";
@@ -19,10 +19,11 @@ import { isConfigured, CONFIG, initConfigWithLegacyMigration } from "./config.js
19
19
  import { resolveOpencodeHostModel } from "./services/ai/live-model-choice.js";
20
20
  import { log } from "./services/logger.js";
21
21
  import { getLanguageName } from "./services/language-detector.js";
22
- import { getHostClientConfig } from "./services/ai/opencode-host-config.js";
23
- import { loadOpencodeProvider } from "./services/ai/opencode-provider-loader.js";
24
- import { STRUCTURED_OUTPUT_AGENT, STRUCTURED_OUTPUT_TOOLS, } from "./services/ai/opencode-provider.js";
25
- import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle, isTrackedInternalCaptureSession, } from "./services/ai/internal-capture-sessions.js";
22
+ import { getHostClientConfig } from "./adapters/opencode/opencode-host-config.js";
23
+ import { loadOpencodeProvider } from "./adapters/opencode/opencode-provider-loader.js";
24
+ import { STRUCTURED_OUTPUT_AGENT, STRUCTURED_OUTPUT_TOOLS, } from "./adapters/opencode/opencode-provider.js";
25
+ import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle, isTrackedInternalCaptureSession, } from "./adapters/opencode/internal-capture-sessions.js";
26
+ import { registerOpencodeProfileModel } from "./adapters/opencode/profile-model.js";
26
27
  export { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle };
27
28
  export { isStructuredSummaryPromptMessage };
28
29
  function extractSessionTitle(response) {
@@ -209,6 +210,13 @@ export const OmmsPlugin = async (ctx) => {
209
210
  })();
210
211
  }
211
212
  await configureOpencodeHostTransport(ctx);
213
+ // Shared profile dedup, conflict, description, and cleanup calls use OpenCode's host model.
214
+ registerOpencodeProfileModel();
215
+ // Web imports, Health, and Settings served by this process can list and use OpenCode's models.
216
+ // Awaited so the web server never serves those routes before the registry is filled.
217
+ await import("./adapters/opencode/backfill-startup.js")
218
+ .then(({ registerOpencodeImportModels }) => registerOpencodeImportModels())
219
+ .catch(() => { });
212
220
  (async () => {
213
221
  let connected = [];
214
222
  try {
@@ -231,15 +239,20 @@ export const OmmsPlugin = async (ctx) => {
231
239
  catch (error) {
232
240
  log("Failed to initialize opencode provider state", { error: String(error) });
233
241
  }
242
+ const configModel = async () => {
243
+ const response = await ctx.client.config.get();
244
+ return unwrapSdkData(response)?.model ?? null;
245
+ };
246
+ // Run now on a Settings page served by this process can use OpenCode's signed-in models.
247
+ void import("./adapters/opencode/backfill-startup.js")
248
+ .then(({ registerOpencodeBackfillModels }) => registerOpencodeBackfillModels({ connected, directory, configModel }))
249
+ .catch(() => { });
234
250
  if (CONFIG.autoBackfill && process.env.OMMS_DISABLE_AUTO_BACKFILL !== "1" && isConfigured()) {
235
251
  void import("./adapters/opencode/backfill-startup.js")
236
252
  .then(({ startOpencodeBackfill }) => startOpencodeBackfill({
237
253
  connected,
238
254
  directory,
239
- configModel: async () => {
240
- const response = await ctx.client.config.get();
241
- return unwrapSdkData(response)?.model ?? null;
242
- },
255
+ configModel,
243
256
  notify: (message) => {
244
257
  void ctx.client.tui
245
258
  ?.showToast({
@@ -14,7 +14,8 @@ export interface AutoCaptureProviderRuntimeConfig {
14
14
  /**
15
15
  * Where live capture and profile learning send model calls, the same on both hosts:
16
16
  * - `opencode`: the configured host model (`opencodeModel`/`piModel`; "inherit" = session model)
17
- * - `manual`: no host model, so the external API (`memoryModel`/`memoryApiUrl`/`memoryApiKey`)
17
+ * - `manual`: the external API (`memoryModel`/`memoryApiUrl`/`memoryApiKey`), because the
18
+ * host model is "external" or no host model is set
18
19
  * - `session`: neither is set, so the session's own model through the host's sign-in
19
20
  */
20
21
  export type AutoCaptureProviderStatus = {
@@ -50,3 +51,5 @@ export declare function resolvePiLiveModel(config: AutoCaptureProviderRuntimeCon
50
51
  }): PiLiveModelChoice;
51
52
  /** True when the external API is fully configured and can serve as a fallback. */
52
53
  export declare function isExternalModelReady(config: AutoCaptureProviderRuntimeConfig): boolean;
54
+ /** Why the external API cannot be used as a host's chosen model, or an empty list. */
55
+ export declare function externalModelIssues(config: AutoCaptureProviderRuntimeConfig): string[];
@@ -33,12 +33,21 @@ function getManualProviderStatus(config) {
33
33
  issues,
34
34
  };
35
35
  }
36
+ /** The host chose the external API: it is the primary call, so there is no fallback. */
37
+ function externalChoiceStatus(config) {
38
+ const manual = getManualProviderStatus(config);
39
+ return manual.ready
40
+ ? { ready: true, mode: "manual", issues: [] }
41
+ : { ready: false, issues: manual.issues };
42
+ }
36
43
  export function getAutoCaptureProviderStatus(config) {
37
44
  const hasOpencodeProvider = hasValue(config.opencodeProvider);
38
45
  const hasOpencodeModel = hasValue(config.opencodeModel);
39
46
  if (config.opencodeModel?.trim() === "inherit") {
40
47
  return { ready: true, mode: "session", issues: [] };
41
48
  }
49
+ if (config.opencodeModel?.trim() === "external")
50
+ return externalChoiceStatus(config);
42
51
  if (hasOpencodeProvider && hasOpencodeModel) {
43
52
  return { ready: true, mode: "opencode", issues: [] };
44
53
  }
@@ -70,6 +79,10 @@ export function resolveOpencodeHostModel(config) {
70
79
  export function resolvePiLiveModel(config) {
71
80
  if (config.piModel?.trim() === "inherit")
72
81
  return { kind: "session" };
82
+ if (config.piModel?.trim() === "external") {
83
+ const status = externalChoiceStatus(config);
84
+ return status.ready ? { kind: "manual" } : { kind: "unready", issues: status.issues };
85
+ }
73
86
  if (hasValue(config.piProvider) && hasValue(config.piModel)) {
74
87
  return { kind: "pi", provider: config.piProvider.trim(), model: config.piModel.trim() };
75
88
  }
@@ -84,3 +97,8 @@ export function resolvePiLiveModel(config) {
84
97
  export function isExternalModelReady(config) {
85
98
  return getManualProviderStatus(config).ready;
86
99
  }
100
+ /** Why the external API cannot be used as a host's chosen model, or an empty list. */
101
+ export function externalModelIssues(config) {
102
+ const manual = getManualProviderStatus(config);
103
+ return manual.ready ? [] : manual.issues;
104
+ }
@@ -41,7 +41,7 @@ interface PaginatedResponse<T> {
41
41
  export declare function handleListTags(): Promise<ApiResponse<{
42
42
  project: TagInfo[];
43
43
  }>>;
44
- export declare function handleListMemories(tag?: string, page?: number, pageSize?: number, includePrompts?: boolean): Promise<ApiResponse<PaginatedResponse<Memory | any>>>;
44
+ export declare function handleListMemories(tag?: string, page?: number, pageSize?: number, includePrompts?: boolean, keyword?: string): Promise<ApiResponse<PaginatedResponse<Memory | any>>>;
45
45
  export declare function handleAddMemory(data: {
46
46
  content: string;
47
47
  containerTag: string;
@@ -178,6 +178,11 @@ interface MigrationProgress {
178
178
  errors: string[];
179
179
  }
180
180
  export declare function handleGetTagMigrationProgress(): Promise<ApiResponse<MigrationProgress>>;
181
+ /**
182
+ * Tag the memories that have no tags, a few per request. Only untagged
183
+ * memories are touched: tagged ones keep their vectors. A memory that fails
184
+ * is recorded and passed over, so one bad row cannot stall the run.
185
+ */
181
186
  export declare function handleRunTagMigrationBatch(batchSize?: number): Promise<ApiResponse<{
182
187
  processed: number;
183
188
  total: number;