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
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # OMMS — Opinionated Modular Memory System
1
+ # OMMS: Opinionated Modular Memory System
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
@@ -26,7 +26,8 @@ the other. Everything is stored locally on your machine.
26
26
  OpenCode v1.
27
27
  - **Learns your preferences.** A user profile of your habits builds up over
28
28
  time and follows you across projects.
29
- - **Imports your past sessions.** Pi and OpenCode import older history after startup. A command provides a preview and manual control.
29
+ - **Imports your past sessions.** Pi and OpenCode import older history after
30
+ startup. A command gives you a preview and manual control.
30
31
  - **Lets you look and edit.** A local web page shows every memory and your
31
32
  profile.
32
33
  - **Keeps private things private.** Text inside `<private>` tags is never
@@ -74,6 +75,14 @@ pi install npm:om-memory-system
74
75
 
75
76
  Restart Pi. You can install OMMS in both agents; they share the same memory.
76
77
 
78
+ **Terminal command (optional, recommended).** A global install lets the login
79
+ web app and the `om-memory-system` terminal commands run without `npx`:
80
+
81
+ ```bash
82
+ npm i -g om-memory-system # or: bun add -g om-memory-system
83
+ om-memory-system --version
84
+ ```
85
+
77
86
  ### 2. Choose which model writes memories (optional)
78
87
 
79
88
  With no settings, OMMS uses the model of the session you are working in. To
@@ -91,18 +100,22 @@ config file yet, OMMS creates this one with comments on first start.
91
100
  }
92
101
  ```
93
102
 
94
- You can also use any OpenAI-compatible or Anthropic API with your own key.
95
- See [Configuration](docs/configuration.md#choosing-the-model).
103
+ You can also use any OpenAI-compatible or Anthropic API with your own key, set
104
+ up on the Settings page's **External API** card, and choose `"external"` as a
105
+ host's model. See [Configuration](docs/configuration.md#choosing-the-model).
96
106
 
97
107
  ### 3. Check it works
98
108
 
99
109
  Work normally for a few turns, then open `http://127.0.0.1:4747` in your
100
- browser. OpenCode or the login web app serves this page. New memories appear on the timeline.
101
- You can also ask the agent: "search memory for what we changed today".
110
+ browser. OpenCode or the login web app serves this page. New memories appear
111
+ on the timeline. You can also ask the agent: "search memory for what we changed today".
102
112
 
103
113
  ## Import your past history
104
114
 
105
- Older sessions import automatically after a host starts. This makes model calls. To opt out, set `"autoBackfill": false` in `~/.config/omms/omms.jsonc` before starting. For a manual import or custom source, preview inside the agent:
115
+ Older sessions import automatically after a host starts. This makes model
116
+ calls. To turn it off, set `"autoBackfill": false` in
117
+ `~/.config/omms/omms.jsonc` before you start the agent. For a manual import or
118
+ a custom source, preview it inside the agent:
106
119
 
107
120
  ```text
108
121
  /memory-import-opencode-history --dry-run
@@ -134,6 +147,7 @@ backup first. See [Updating and upgrading](docs/upgrading.md) and
134
147
  | [Using memory day to day](docs/using-memory.md) | How capture and recall work, the `memory` tool, the user profile |
135
148
  | [Configuration](docs/configuration.md) | Settings, choosing the model, embeddings, troubleshooting |
136
149
  | [Web UI](docs/web-ui.md) | The memory explorer, opening it on a network safely |
150
+ | [Settings page](docs/web-ui-settings.md) | Every card and control on the web Settings page |
137
151
  | [Moving projects](docs/moving-projects.md) | Nested repositories, moved folders, backup and restore |
138
152
  | [Updating and upgrading](docs/upgrading.md) | Updates, pinning a version, older stores |
139
153
  | [OpenCode adapter](docs/opencode-adapter.md) | How the OpenCode plugin hooks in |
@@ -4,7 +4,7 @@ import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js
4
4
  import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
5
5
  import { classifyCaptureReply, normalizeStopReason, parseCaptureSummary, } from "../../core/extraction.js";
6
6
  import { log } from "../../services/logger.js";
7
- import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
7
+ import { loadOpencodeProvider } from "./opencode-provider-loader.js";
8
8
  function buildSystemPrompt(languageName) {
9
9
  return `You are a technical memory recorder for a software development project.
10
10
 
@@ -1,6 +1,6 @@
1
1
  import { selectImportModel } from "../../importer/model-selection.js";
2
2
  import type { HistoryImportModels } from "../../importer/run-import.js";
3
- import { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
3
+ import { createOpencodeImportModels } from "./opencode-import-models.js";
4
4
  interface BackfillConfig {
5
5
  opencodeBackfillModel?: string;
6
6
  opencodeProvider?: string;
@@ -1,11 +1,15 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { parseBackfillModel } from "../../importer/backfill-model.js";
3
+ import { resolveExternalBackfillModels } from "../../importer/external-backfill-models.js";
3
4
  import { selectImportModel } from "../../importer/model-selection.js";
4
5
  import { getAutoCaptureProviderStatus } from "../../services/ai/live-model-choice.js";
5
- import { createOpencodeImportModels, } from "../../services/ai/opencode-import-models.js";
6
+ import { createOpencodeImportModels } from "./opencode-import-models.js";
6
7
  /** Apply the live-model rule when inherit is selected; named models need sign-in. */
7
8
  export async function resolveOpencodeBackfillModels(config = CONFIG, context) {
8
9
  const choice = parseBackfillModel(config, "opencode");
10
+ if (choice === "external") {
11
+ return resolveExternalBackfillModels("opencode", config, context.externalModels);
12
+ }
9
13
  let ref;
10
14
  if (choice === "inherit") {
11
15
  const live = getAutoCaptureProviderStatus(config);
@@ -24,7 +28,7 @@ export async function resolveOpencodeBackfillModels(config = CONFIG, context) {
24
28
  if (!selected)
25
29
  throw new Error("OpenCode backfill: no configured default model");
26
30
  const parsed = parseBackfillModel({ opencodeBackfillModel: selected }, "opencode");
27
- if (parsed === "inherit")
31
+ if (typeof parsed === "string")
28
32
  throw new Error("OpenCode backfill: no configured default model");
29
33
  ref = { providerID: parsed.provider, modelID: parsed.model };
30
34
  }
@@ -5,3 +5,11 @@ export declare function startOpencodeBackfill(input: {
5
5
  configModel: () => Promise<string | null>;
6
6
  notify: (message: string) => void;
7
7
  }): Promise<void>;
8
+ /** Let Run now on a Settings page served by this process use OpenCode's models. */
9
+ export declare function registerOpencodeBackfillModels(input: {
10
+ directory: string;
11
+ connected: string[];
12
+ configModel: () => Promise<string | null>;
13
+ }): void;
14
+ /** Let web imports, Health, and Settings in this process use OpenCode's signed-in models. */
15
+ export declare function registerOpencodeImportModels(): void;
@@ -1,6 +1,10 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { scheduleAutoBackfill } from "../../importer/auto-backfill.js";
3
+ import { registerHostBackfillModels, registerOpencodeHostModels, } from "../../importer/backfill-controls.js";
4
+ import { listOpencodeClientModels } from "../../importer/settings-models.js";
3
5
  import { resolveOpencodeBackfillModels } from "./backfill-models.js";
6
+ import { createOpencodeImportModels } from "./opencode-import-models.js";
7
+ import { getV2Client, isProviderConnected } from "./opencode-provider.js";
4
8
  /** Start after connected providers load, without delaying plugin initialisation. */
5
9
  export function startOpencodeBackfill(input) {
6
10
  return scheduleAutoBackfill({
@@ -10,3 +14,18 @@ export function startOpencodeBackfill(input) {
10
14
  notify: input.notify,
11
15
  });
12
16
  }
17
+ /** Let Run now on a Settings page served by this process use OpenCode's models. */
18
+ export function registerOpencodeBackfillModels(input) {
19
+ registerHostBackfillModels("opencode", () => resolveOpencodeBackfillModels(CONFIG, input));
20
+ }
21
+ /** Let web imports, Health, and Settings in this process use OpenCode's signed-in models. */
22
+ export function registerOpencodeImportModels() {
23
+ registerOpencodeHostModels({
24
+ isProviderConnected: (providerID) => isProviderConnected(providerID),
25
+ createImportModels: (ref, directory) => createOpencodeImportModels(ref, directory),
26
+ async listSettingsModels() {
27
+ const client = getV2Client();
28
+ return client ? listOpencodeClientModels(client) : null;
29
+ },
30
+ });
31
+ }
@@ -1,6 +1,6 @@
1
- import { type OpencodeModelRef } from "../../services/ai/opencode-import-models.js";
2
- export { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
3
- export type { OpencodeModelRef } from "../../services/ai/opencode-import-models.js";
1
+ import { type OpencodeModelRef } from "./opencode-import-models.js";
2
+ export { createOpencodeImportModels } from "./opencode-import-models.js";
3
+ export type { OpencodeModelRef } from "./opencode-import-models.js";
4
4
  /**
5
5
  * OpenCode command surface for the historical-session importer:
6
6
  *
@@ -1,9 +1,9 @@
1
1
  import { isConfigured } from "../../config.js";
2
2
  import { historyImportUsage, importNeedsModel, parseHistoryImportArgs, tokenizeImportArgs, } from "../../importer/import-args.js";
3
3
  import { formatHistoryImportReport, summarizeHistoryImportReport, runHistoryImport, } from "../../importer/run-import.js";
4
- import { createOpencodeImportModels, } from "../../services/ai/opencode-import-models.js";
5
- export { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
6
- import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
4
+ import { createOpencodeImportModels } from "./opencode-import-models.js";
5
+ export { createOpencodeImportModels } from "./opencode-import-models.js";
6
+ import { loadOpencodeProvider } from "./opencode-provider-loader.js";
7
7
  import { beginManualImport } from "../../importer/manual-import-guard.js";
8
8
  import { memoryClient } from "../../services/client.js";
9
9
  import { log } from "../../services/logger.js";
@@ -68,6 +68,7 @@ export async function runOpencodeImportCommand(input) {
68
68
  const report = await runHistoryImport("opencode", parsed, {
69
69
  cwd: input.directory,
70
70
  models,
71
+ track: { surface: "slash" },
71
72
  onProgress: (processed, total, promptPreview) => {
72
73
  if (processed - lastProgress >= 25 || processed === total) {
73
74
  lastProgress = processed;
@@ -0,0 +1,4 @@
1
+ export { INTERNAL_CAPTURE_SESSION_TITLE, INTERNAL_CAPTURE_SESSION_TITLES, isInternalCaptureSessionTitle, } from "../../importer/opencode-internal-sessions.js";
2
+ export declare function trackInternalCaptureSession(sessionID: string): void;
3
+ export declare function untrackInternalCaptureSession(sessionID: string): void;
4
+ export declare function isTrackedInternalCaptureSession(sessionID: string): boolean;
@@ -1,21 +1,8 @@
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";
1
+ export { INTERNAL_CAPTURE_SESSION_TITLE, INTERNAL_CAPTURE_SESSION_TITLES, isInternalCaptureSessionTitle, } from "../../importer/opencode-internal-sessions.js";
5
2
  /** Grace period so session.idle can still match after best-effort delete. */
6
3
  const UNTRACK_GRACE_MS = 60_000;
7
4
  const trackedSessionIDs = new Set();
8
5
  const untrackTimers = new Map();
9
- /** Every title omms has used for its own sessions (capture, profile learning, profile cleanup). */
10
- export const INTERNAL_CAPTURE_SESSION_TITLES = [
11
- INTERNAL_CAPTURE_SESSION_TITLE,
12
- LEGACY_INTERNAL_CAPTURE_SESSION_TITLE,
13
- "omms profile cleanup",
14
- "opencode-mem profile cleanup",
15
- ];
16
- export function isInternalCaptureSessionTitle(title) {
17
- return (title === INTERNAL_CAPTURE_SESSION_TITLE || title === LEGACY_INTERNAL_CAPTURE_SESSION_TITLE);
18
- }
19
6
  export function trackInternalCaptureSession(sessionID) {
20
7
  const pending = untrackTimers.get(sessionID);
21
8
  if (pending) {
@@ -9,7 +9,7 @@ export async function createOpencodeImportModels(ref, directory) {
9
9
  throw new Error("the OpenCode client is not ready; retry in a moment");
10
10
  const { z } = await import("zod");
11
11
  const { buildCaptureSystemPrompt, createUserProfileAnalysisSchema, parseCaptureSummary } = await import("../../core/extraction.js");
12
- const { detectLanguage, getLanguageName } = await import("../language-detector.js");
12
+ const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
13
13
  const captureSchema = z.object({
14
14
  summary: z.string().optional(),
15
15
  type: z.string(),
@@ -1,7 +1,7 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
2
  export declare function shouldRunAutomaticProfileCleanup(previousPromptCount: number, addedPromptCount: number, interval?: number): boolean;
3
3
  export declare function performUserProfileLearning(ctx: PluginInput, directory: string): Promise<void>;
4
- import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX } from "../core/extraction.js";
4
+ import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX } from "../../core/extraction.js";
5
5
  export { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX };
6
6
  export declare function createUserProfileToolSchema(existingProfile: boolean): {
7
7
  type: "function";
@@ -1,11 +1,11 @@
1
- import { getTags } from "./tags.js";
2
- import { log } from "./logger.js";
3
- import { CONFIG, refreshConfigIfChanged } from "../config.js";
4
- import { resolveOpencodeHostModel } from "./ai/live-model-choice.js";
5
- import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
6
- import { userProfileManager } from "./user-profile/user-profile-manager.js";
7
- import { sortProfileItems } from "../utils/profile.js";
8
- import { loadOpencodeProvider } from "./ai/opencode-provider-loader.js";
1
+ import { getTags } from "../../services/tags.js";
2
+ import { log } from "../../services/logger.js";
3
+ import { CONFIG, refreshConfigIfChanged } from "../../config.js";
4
+ import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
5
+ import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
6
+ import { userProfileManager } from "../../services/user-profile/user-profile-manager.js";
7
+ import { sortProfileItems } from "../../utils/profile.js";
8
+ import { loadOpencodeProvider } from "./opencode-provider-loader.js";
9
9
  let isLearningRunning = false;
10
10
  export function shouldRunAutomaticProfileCleanup(previousPromptCount, addedPromptCount, interval = CONFIG.userProfileAutoCleanupInterval) {
11
11
  if (!CONFIG.userProfileAutoCleanupEnabled || !Number.isInteger(interval) || interval <= 0) {
@@ -23,7 +23,7 @@ async function runAutomaticProfileCleanup(userId) {
23
23
  const itemCount = profileData.preferences.length + profileData.patterns.length + profileData.workflows.length;
24
24
  if (itemCount < 2)
25
25
  return;
26
- const { aiCleanupProfile } = await import("./user-profile/ai-cleanup.js");
26
+ const { aiCleanupProfile } = await import("../../services/user-profile/ai-cleanup.js");
27
27
  const result = await aiCleanupProfile(profileData);
28
28
  if (result.diff.merged.length === 0 && result.diff.removed.length === 0)
29
29
  return;
@@ -338,7 +338,7 @@ CRITICAL: Only output observations grounded in the RECENT PROMPTS above. Write d
338
338
  }
339
339
  return truncate(base);
340
340
  }
341
- import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX, } from "../core/extraction.js";
341
+ import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX, } from "../../core/extraction.js";
342
342
  export { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX };
343
343
  export function createUserProfileToolSchema(existingProfile) {
344
344
  return {
@@ -497,7 +497,7 @@ async function analyzeUserProfile(context, existingProfile) {
497
497
  log("user-profile-learning: trying opencode provider");
498
498
  try {
499
499
  const { generateStructuredOutput } = await loadOpencodeProvider();
500
- const { getOpenCodeClient } = await import("./ai/profile-llm-client.js");
500
+ const { getOpenCodeClient } = await import("./profile-llm-client.js");
501
501
  log("user-profile-learning: opencode provider diag", {
502
502
  provider: resolveOpencodeHostModel(CONFIG)?.providerID,
503
503
  model: resolveOpencodeHostModel(CONFIG)?.modelID,
@@ -556,8 +556,8 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
556
556
  });
557
557
  throw new Error("External API not configured for user memory learning");
558
558
  }
559
- const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
560
- const { buildMemoryProviderConfig } = await import("./ai/provider-config.js");
559
+ const { AIProviderFactory } = await import("../../services/ai/ai-provider-factory.js");
560
+ const { buildMemoryProviderConfig } = await import("../../services/ai/provider-config.js");
561
561
  const providerConfig = buildMemoryProviderConfig(CONFIG);
562
562
  const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
563
563
  const systemPrompt = `You are a user behavior analyst for a coding assistant.
@@ -597,7 +597,7 @@ If no clear chains, return { "paths": [] }.`;
597
597
  try {
598
598
  const { z } = await import("zod");
599
599
  const { generateStructuredOutput } = await loadOpencodeProvider();
600
- const { getOpenCodeClient } = await import("./ai/profile-llm-client.js");
600
+ const { getOpenCodeClient } = await import("./profile-llm-client.js");
601
601
  let v2Client;
602
602
  try {
603
603
  v2Client = await getOpenCodeClient();
@@ -1,5 +1,5 @@
1
1
  import { CONFIG } from "../../config.js";
2
- import { resolveOpencodeHostModel } from "./live-model-choice.js";
2
+ import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
3
3
  import { loadOpencodeProvider } from "./opencode-provider-loader.js";
4
4
  let _cachedClient = null;
5
5
  let _cachedProvider = null;
@@ -0,0 +1,23 @@
1
+ import type { ModelPort } from "../../core/profile-analysis.js";
2
+ import type { OpencodeModelRef } from "./opencode-import-models.js";
3
+ type PromptInfo = {
4
+ error?: {
5
+ name: string;
6
+ data?: {
7
+ message?: string;
8
+ };
9
+ };
10
+ };
11
+ /**
12
+ * Extract assistant text from an OpenCode session.prompt result.
13
+ * AssistantMessage has no `text` field; content lives in `parts` (#177).
14
+ */
15
+ export declare function extractTextFromPromptResult(promptResult: unknown): {
16
+ info: PromptInfo | undefined;
17
+ rawText: string;
18
+ };
19
+ /** Adapt OpenCode's host model to the shared profile code, as `adaptPiProfileModel` does for Pi. */
20
+ export declare function adaptOpencodeProfileModel(v2Client: unknown, hostModel: OpencodeModelRef): ModelPort;
21
+ /** Route shared profile calls in this process to OpenCode's host model while one is configured. */
22
+ export declare function registerOpencodeProfileModel(): void;
23
+ export {};
@@ -0,0 +1,117 @@
1
+ import { CONFIG } from "../../config.js";
2
+ import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
3
+ import { log } from "../../services/logger.js";
4
+ import { OPENCODE_PROFILE_CLEANUP_TIMEOUT_MS } from "../../services/request-timeouts.js";
5
+ import { registerHostProfileModel } from "../../services/user-profile/profile-model.js";
6
+ import { loadOpencodeProvider } from "./opencode-provider-loader.js";
7
+ import { getOpenCodeClient } from "./profile-llm-client.js";
8
+ /**
9
+ * Extract assistant text from an OpenCode session.prompt result.
10
+ * AssistantMessage has no `text` field; content lives in `parts` (#177).
11
+ */
12
+ export function extractTextFromPromptResult(promptResult) {
13
+ const result = promptResult;
14
+ const info = result?.data?.info ?? result?.info;
15
+ const parts = result?.data?.parts ?? result?.parts ?? [];
16
+ const rawText = parts
17
+ .filter((p) => p.type === "text" && p.text)
18
+ .map((p) => p.text)
19
+ .join("\n")
20
+ .trim();
21
+ return { info, rawText };
22
+ }
23
+ function raceWithTimeout(promise, ms, message) {
24
+ let timer;
25
+ const timeout = new Promise((_, reject) => {
26
+ timer = setTimeout(() => reject(new Error(message)), ms);
27
+ });
28
+ return Promise.race([promise, timeout]).finally(() => {
29
+ if (timer !== undefined)
30
+ clearTimeout(timer);
31
+ });
32
+ }
33
+ /**
34
+ * Plain-text reply through a transient OpenCode session. Only AI profile cleanup
35
+ * calls it, so the session carries the cleanup title the history reader skips.
36
+ */
37
+ async function completeViaSession(v2Client, hostModel, systemPrompt, userPrompt) {
38
+ const t0 = Date.now();
39
+ // Only "inherit" (or no model configured) needs resolving to the session's recent model.
40
+ const model = hostModel.modelID === "inherit"
41
+ ? (await loadOpencodeProvider()).resolveOpencodeModelRef(hostModel)
42
+ : hostModel;
43
+ const created = (await raceWithTimeout(v2Client.session.create({
44
+ title: "omms profile cleanup",
45
+ directory: process.cwd(),
46
+ }), 30000, "session.create timeout"));
47
+ log("AI cleanup: session.create result", {
48
+ rawType: typeof created,
49
+ keys: Object.keys(created || {}),
50
+ hasData: !!created?.data,
51
+ dataId: created?.data?.id,
52
+ });
53
+ const sessionID = created?.data?.id || created?.id || created?.sessionID;
54
+ if (!sessionID)
55
+ throw new Error("session.create returned no session id");
56
+ log("AI cleanup: session created", { sessionID, createMs: Date.now() - t0 });
57
+ try {
58
+ const TIMEOUT_MS = OPENCODE_PROFILE_CLEANUP_TIMEOUT_MS;
59
+ const promptResult = await raceWithTimeout(v2Client.session.prompt({
60
+ sessionID,
61
+ model,
62
+ system: systemPrompt,
63
+ parts: [{ type: "text", text: userPrompt }],
64
+ // `noReply` suppresses assistant generation; cleanup needs the JSON reply (#177).
65
+ noReply: false,
66
+ }), TIMEOUT_MS, `opencodeClient prompt timeout after ${TIMEOUT_MS}ms`);
67
+ log("AI cleanup: session.prompt done", { promptMs: Date.now() - t0 });
68
+ const { info, rawText } = extractTextFromPromptResult(promptResult);
69
+ if (!info)
70
+ throw new Error("prompt response missing info");
71
+ if (info.error)
72
+ throw new Error(`opencode reported ${info.error.name}: ${info.error.data?.message ?? ""}`);
73
+ return rawText;
74
+ }
75
+ finally {
76
+ try {
77
+ await v2Client.session.delete({ sessionID });
78
+ }
79
+ catch {
80
+ // ignore cleanup failures for ephemeral sessions
81
+ }
82
+ }
83
+ }
84
+ /** Adapt OpenCode's host model to the shared profile code, as `adaptPiProfileModel` does for Pi. */
85
+ export function adaptOpencodeProfileModel(v2Client, hostModel) {
86
+ return {
87
+ provider: hostModel.providerID,
88
+ modelId: hostModel.modelID,
89
+ complete: (systemPrompt, userPrompt) => completeViaSession(v2Client, hostModel, systemPrompt, userPrompt),
90
+ async completeStructured(systemPrompt, userPrompt, schema) {
91
+ const client = await getOpenCodeClient();
92
+ const { generateStructuredOutput } = await loadOpencodeProvider();
93
+ return generateStructuredOutput({
94
+ client,
95
+ providerID: hostModel.providerID,
96
+ modelID: hostModel.modelID,
97
+ systemPrompt,
98
+ userPrompt,
99
+ schema,
100
+ });
101
+ },
102
+ };
103
+ }
104
+ /** Route shared profile calls in this process to OpenCode's host model while one is configured. */
105
+ export function registerOpencodeProfileModel() {
106
+ registerHostProfileModel(async () => {
107
+ const hostModel = resolveOpencodeHostModel(CONFIG);
108
+ if (!hostModel)
109
+ return null;
110
+ const v2Client = (await loadOpencodeProvider()).getV2Client();
111
+ if (!v2Client) {
112
+ log("profile model: opencode client unavailable, falling back to external API");
113
+ return null;
114
+ }
115
+ return adaptOpencodeProfileModel(v2Client, hostModel);
116
+ });
117
+ }
@@ -1,4 +1,5 @@
1
- export declare function isStructuredSummaryPromptMessage(userMessage: string): boolean;
1
+ import { isStructuredSummaryPromptMessage } from "../../core/internal-prompt.js";
2
+ export { isStructuredSummaryPromptMessage };
2
3
  /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
3
4
  export declare function isInternalPrompt(sessionID: string, userMessage: string): boolean;
4
5
  /**
@@ -1,14 +1,7 @@
1
- import { isInternalStructuredSession } from "../../services/ai/opencode-provider.js";
1
+ import { isStructuredSummaryPromptMessage } from "../../core/internal-prompt.js";
2
+ import { isInternalStructuredSession } from "./opencode-provider.js";
2
3
  import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
3
- export function isStructuredSummaryPromptMessage(userMessage) {
4
- // This is the plugin's own structured-summary or profile-analysis request.
5
- // OpenCode echoes it through chat.message like a normal user message, but
6
- // capturing it would create self-referential memories / an infinite learning loop.
7
- if (userMessage.includes("# User Profile Analysis")) {
8
- return true;
9
- }
10
- return userMessage.includes("Analyze this conversation.") && userMessage.includes('type="skip"');
11
- }
4
+ export { isStructuredSummaryPromptMessage };
12
5
  /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
13
6
  export function isInternalPrompt(sessionID, userMessage) {
14
7
  return isStructuredSummaryPromptMessage(userMessage) || isInternalStructuredSession(sessionID);
@@ -3,6 +3,10 @@ import { type PiModelContext } from "./provider.js";
3
3
  /** Resolve the Pi backfill independently of the manual import's session-model rule. */
4
4
  export declare function resolvePiBackfillModels(ctx: PiModelContext, config?: {
5
5
  piBackfillModel?: string;
6
+ memoryProvider?: string;
7
+ memoryModel?: string;
8
+ memoryApiUrl?: string;
9
+ memoryApiKey?: string;
6
10
  }): Promise<{
7
11
  model: string;
8
12
  models: HistoryImportModels;
@@ -1,11 +1,14 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { parseBackfillModel } from "../../importer/backfill-model.js";
3
+ import { resolveExternalBackfillModels } from "../../importer/external-backfill-models.js";
3
4
  import { createPiLiveModels } from "./live-model.js";
4
5
  import { adaptPiProfileModel } from "./profile.js";
5
6
  import { createPiCaptureProvider, resolveImportModel } from "./provider.js";
6
7
  /** Resolve the Pi backfill independently of the manual import's session-model rule. */
7
8
  export async function resolvePiBackfillModels(ctx, config = CONFIG) {
8
9
  const choice = parseBackfillModel(config, "pi");
10
+ if (choice === "external")
11
+ return resolveExternalBackfillModels("pi", config);
9
12
  if (choice === "inherit") {
10
13
  const live = createPiLiveModels(ctx);
11
14
  const profile = live.profile();
@@ -1,5 +1,5 @@
1
1
  import type { CaptureSummaryProvider } from "../../core/host.js";
2
- import { type PiSessionEntry } from "./conversation.js";
2
+ import { type PiSessionEntry } from "../../importer/pi-conversation.js";
3
3
  export interface PiCaptureState {
4
4
  /** User entry IDs terminally handled by a settled capture for this session. */
5
5
  handledUserEntries: Set<string>;
@@ -2,7 +2,7 @@ import { CONFIG } from "../../config.js";
2
2
  import { captureConversation } from "../../core/capture.js";
3
3
  import { log } from "../../services/logger.js";
4
4
  import { memoryClient } from "../../services/client.js";
5
- import { extractPiConversation } from "./conversation.js";
5
+ import { extractPiConversation } from "../../importer/pi-conversation.js";
6
6
  export function createPiCaptureState() {
7
7
  return { handledUserEntries: new Set(), running: false };
8
8
  }
@@ -111,6 +111,13 @@ export default function ommsPiExtension(pi) {
111
111
  code: error instanceof Error ? error.name : "unknown",
112
112
  }));
113
113
  }
114
+ // Run now on a Settings page served by this process can use Pi's signed-in models.
115
+ void Promise.all([
116
+ import("../../importer/backfill-controls.js"),
117
+ import("./backfill-models.js"),
118
+ ])
119
+ .then(([{ registerHostBackfillModels }, { resolvePiBackfillModels }]) => registerHostBackfillModels("pi", () => resolvePiBackfillModels(ctx)))
120
+ .catch(() => { });
114
121
  const globalScope = globalThis;
115
122
  const sessionId = ctx.sessionManager.getSessionId();
116
123
  if (isConfigured() &&
@@ -90,6 +90,7 @@ export function registerPiHistoryImportCommand(pi, getExtensionContext) {
90
90
  let lastProgressNotify = 0;
91
91
  const report = await runHistoryImport("pi", parsed, {
92
92
  cwd: ctx.cwd,
93
+ track: { surface: "slash" },
93
94
  models: selectedModel && !parsed.dryRun
94
95
  ? {
95
96
  capture: createPiCaptureProvider(() => selectedModel),
package/dist/cli/index.js CHANGED
@@ -14,6 +14,9 @@ Commands:
14
14
  web Start the web app in the foreground
15
15
  web install|uninstall|status Manage the web app login item
16
16
 
17
+ Options:
18
+ --version, -v Print the installed version
19
+
17
20
  Import commands take the same options; run one with --help to see them.
18
21
  Inside a session, /memory-import-opencode-history and /memory-import-pi-history
19
22
  use that session's model instead of an API key.`;
@@ -28,6 +31,11 @@ export function parseImportArgs(argv) {
28
31
  return { host, args };
29
32
  }
30
33
  export async function runCli(argv) {
34
+ if (argv[0] === "--version" || argv[0] === "-v") {
35
+ const { packageVersion } = await import("../services/package-version.js");
36
+ console.log(packageVersion());
37
+ return 0;
38
+ }
31
39
  if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
32
40
  console.log(usage);
33
41
  return 0;
@@ -73,7 +81,11 @@ export async function runCli(argv) {
73
81
  console.log(`Import model: ${model}`);
74
82
  }
75
83
  const { formatHistoryImportReport, historyImportFailed, runHistoryImport } = await import("../importer/run-import.js");
76
- const report = await runHistoryImport(host, args, { cwd: process.cwd(), models });
84
+ const report = await runHistoryImport(host, args, {
85
+ cwd: process.cwd(),
86
+ models,
87
+ track: { surface: "cli" },
88
+ });
77
89
  console.log(formatHistoryImportReport(host, report, model));
78
90
  return historyImportFailed(report) ? 1 : 0;
79
91
  }