om-memory-system 4.13.3 → 4.14.1

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/README.md CHANGED
@@ -1,197 +1,148 @@
1
+ <p align="center">
2
+ <img src=".github/screenshots/omms-logo.png" alt="OMMS logo" width="96" height="96" />
3
+ </p>
4
+
1
5
  # OMMS: Opinionated Modular Memory System
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
4
8
  [![npm downloads](https://img.shields.io/npm/dm/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
5
- [![license](https://img.shields.io/npm/l/om-memory-system.svg)](https://www.npmjs.com/package/om-memory-system)
9
+ [![MIT licence](https://img.shields.io/npm/l/om-memory-system.svg)](LICENSE.md)
10
+
11
+ OMMS gives AI coding agents long-term project memory. It captures technical
12
+ work, recalls relevant notes in later sessions and learns your working
13
+ preferences. One shared engine serves **OpenCode**, **Pi** and **Claude Code**,
14
+ so each host can retrieve memories captured by the others in the same project.
6
15
 
7
- ![OMMS banner](.github/banner.png)
16
+ Memories are stored locally, by default in `~/.omms/data`. Embeddings run
17
+ locally by default; the first use downloads a search model. Capture and profile
18
+ learning make additional model calls that can send conversation content to your
19
+ chosen provider and incur costs. Remote embeddings are optional. OMMS removes
20
+ text inside `<private>` tags before storage. See [Configuration](docs/configuration.md)
21
+ for model choices and privacy controls.
8
22
 
9
- OMMS gives your AI coding agent a long-term memory. As you work, it writes
10
- short notes about what was done and decided in each project: fixes, design
11
- choices, things that did not work. It brings the relevant notes back in later
12
- sessions, so the agent does not start from zero every time. It also learns how
13
- you like to work and keeps that as a user profile.
23
+ ## Set up
14
24
 
15
- It runs inside [OpenCode](https://opencode.ai) and the
25
+ Use Node.js **22.14 or later** for the terminal command and Claude Code hooks.
26
+ OpenCode v1 needs **1.18.29 or later**. OMMS also supports OpenCode v2 and the
16
27
  [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
17
- Both share one memory per project, so a note written in one is available in
18
- the other. Everything is stored locally on your machine.
19
28
 
20
- Claude Code is also supported, through a plugin of hooks. It needs Node.js and
21
- your own external API for capture. A global install is optional. See the
22
- [Claude Code adapter](docs/claude-code-adapter.md) guide.
29
+ > Automatic history import is enabled by default and makes model calls.
30
+ > To disable it before your first session, set `"autoBackfill": false` in
31
+ > `~/.config/omms/omms.jsonc`. See [Automatic import](docs/web-ui-memory.md#automatic-import).
23
32
 
24
- ## What it does
33
+ ### OpenCode
25
34
 
26
- - **Remembers automatically.** After each piece of work, a background model
27
- call summarises it into a memory. You do not have to ask.
28
- - **Recalls when relevant.** Matching memories are added to the agent's
29
- context: on every prompt in OpenCode v2 and Pi, at the start of a session in
30
- OpenCode v1.
31
- - **Learns your preferences.** A user profile of your habits builds up over
32
- time and follows you across projects.
33
- - **Imports your past sessions.** Pi and OpenCode import older history after
34
- startup. A command gives you a preview and manual control.
35
- - **Lets you look and edit.** A local web page shows every memory and your
36
- profile.
37
- - **Keeps private things private.** Text inside `<private>` tags is never
38
- stored.
35
+ Add the package to `~/.config/opencode/opencode.json`. Keep your existing settings.
36
+ On Windows, use `%USERPROFILE%\.config\opencode\opencode.json`.
39
37
 
40
- ![Project memory timeline](.github/screenshot-project-memory.png)
38
+ **OpenCode v2:**
41
39
 
42
- ![User profile viewer](.github/screenshot-user-profile.png)
40
+ ```json
41
+ { "plugins": ["om-memory-system"] }
42
+ ```
43
43
 
44
- ## Before you start
44
+ **OpenCode v1:**
45
45
 
46
- You need one of:
46
+ ```json
47
+ { "plugin": ["om-memory-system"] }
48
+ ```
47
49
 
48
- - **OpenCode** 1.18.29 or later (v1 plugin API) or OpenCode v2
49
- - **Pi coding agent**
50
- - **Claude Code**, through a plugin of hooks. Capture needs your own external API. See
51
- the [Claude Code adapter](docs/claude-code-adapter.md) guide for setup.
50
+ OpenCode v2 also supports:
52
51
 
53
- Nothing else is required. OMMS brings its own database. On first use it
54
- downloads a small embedding model (the part that makes memories searchable),
55
- so you need internet access once. The terminal import command also needs
56
- Node.js 22.14 or later.
52
+ ```bash
53
+ opencode plugin add om-memory-system
54
+ ```
57
55
 
58
- > [!NOTE]
59
- > OMMS is tested mostly on macOS and Linux. Windows runs only in the release
60
- > check, so it gets less testing. If something breaks on Windows,
61
- > [open an issue](https://github.com/cmdaltctr/omms/issues). If you fix it,
62
- > a pull request is welcome.
56
+ Restart OpenCode. The [OpenCode guide](docs/opencode-adapter.md#installation)
57
+ covers plugin management and its additional `cli.json` configuration.
63
58
 
64
- ## Set up
59
+ ### Pi
65
60
 
66
- ### 1. Install
61
+ ```bash
62
+ pi install npm:om-memory-system
63
+ ```
67
64
 
68
- **OpenCode.** Add `om-memory-system` to `~/.config/opencode/opencode.json`
69
- (on Windows, `%USERPROFILE%\.config\opencode\opencode.json`):
65
+ Restart Pi. See the [Pi guide](docs/pi-adapter.md).
70
66
 
71
- ```jsonc
72
- // OpenCode v2
73
- { "plugins": ["om-memory-system"] }
67
+ ### Claude Code
74
68
 
75
- // OpenCode v1
76
- { "plugin": ["om-memory-system"] }
77
- ```
69
+ Install the OMMS plugin using the [Claude Code guide](docs/claude-code-adapter.md#installation).
70
+ Capture and profile learning require a configured external API; retrieval and
71
+ manual memory operations remain available without it. A global npm install is optional.
78
72
 
79
- On OpenCode v2 you can run `opencode plugin add om-memory-system` instead.
80
- Restart OpenCode.
73
+ ### Model and web app
81
74
 
82
- **Pi.** Run:
75
+ With no model settings, OpenCode and Pi use the session model. You can choose a
76
+ host model or configure an external API under **Settings**. Claude Code uses
77
+ only the external API. See [Choosing the model](docs/configuration.md#choosing-the-model)
78
+ for the selection order and credentials.
83
79
 
84
- ```bash
85
- pi install npm:om-memory-system
86
- ```
80
+ Start a host session, then open [http://127.0.0.1:4747](http://127.0.0.1:4747).
81
+ Each host starts the shared web app when needed, unless it is disabled. Browse
82
+ captured notes on **Project memories**, or ask your agent to search memory for
83
+ an earlier decision.
87
84
 
88
- Restart Pi. You can install OMMS in both agents; they share the same memory.
85
+ ### Terminal command (optional)
89
86
 
90
- **Terminal command (optional).** A global install puts the `om-memory-system`
91
- command on your `PATH`. You do not need it. OMMS runs the newest copy it finds on
92
- the machine, so updating one host is enough. A global install from 4.3 or earlier
93
- needs one update. See
94
- [Updating and upgrading](docs/upgrading.md#one-update-updates-every-host).
95
- To install it:
87
+ A global install puts `om-memory-system` on your `PATH`:
96
88
 
97
89
  ```bash
98
- npm i -g om-memory-system # or: bun add -g om-memory-system
90
+ npm install -g om-memory-system
99
91
  om-memory-system --version
100
92
  ```
101
93
 
102
- ### 2. Choose which model writes memories (optional)
103
-
104
- With no settings, OMMS uses the model of the session you are working in. To
105
- use a different one, add it to `~/.config/omms/omms.jsonc`. If you have no
106
- config file yet, OMMS creates this one with comments on first start.
107
-
108
- ```jsonc
109
- {
110
- // A model you are already signed in to in OpenCode or Pi.
111
- // Use "inherit" to always follow the session's model.
112
- "opencodeProvider": "openai",
113
- "opencodeModel": "gpt-5.6-luna",
114
- "piProvider": "openai-codex",
115
- "piModel": "gpt-5.6-luna",
116
- }
117
- ```
94
+ See the [CLI reference](docs/cli.md) for manual memory operations and history imports.
118
95
 
119
- You can also use any OpenAI-compatible or Anthropic API with your own key, set
120
- up on the Settings page's **External API** card, and choose `"external"` as a
121
- host's model. See [Configuration](docs/configuration.md#choosing-the-model).
96
+ ## Inside OMMS
122
97
 
123
- ### 3. Check it works
98
+ These screenshots use synthetic examples and configuration. All visible prompts,
99
+ code snippets and outcomes are made up for the demonstration.
124
100
 
125
- Work normally for a few turns, then open `http://127.0.0.1:4747` in your
126
- browser. OpenCode or the login web app serves this page. New memories appear
127
- on the timeline. You can also ask the agent: "search memory for what we changed today".
101
+ ### Project memories
128
102
 
129
- ## Import your past history
103
+ Browse captured notes alongside example user input and technical outcomes.
130
104
 
131
- Older sessions import automatically after a host starts. This makes model
132
- calls. To turn it off, set `"autoBackfill": false` in
133
- `~/.config/omms/omms.jsonc` before you start the agent. For a manual import or
134
- a custom source, preview it inside the agent:
105
+ ![Project memories with synthetic English prompts, code and outcomes](.github/screenshots/project-memories.png)
135
106
 
136
- ```text
137
- /memory-import-opencode-history --dry-run
138
- /memory-import-pi-history --dry-run
139
- ```
107
+ ### Memory
108
+
109
+ Review history imports, manage profile learning and set memory limits. This page
110
+ also helps resolve missing project folders.
111
+
112
+ ![Memory page with synthetic import and profile examples](.github/screenshots/memory.png)
113
+
114
+ ### Settings
115
+
116
+ Choose models and manage access. Diagnostics show capture outcomes and component health.
140
117
 
141
- The preview shows how many model calls a real import needs. It uses the
142
- session's model; add `--model provider/id` for a cheaper one. There is also a
143
- terminal version, `npx om-memory-system`, that uses an API key. See the
144
- [OpenCode](docs/opencode-history-import.md) and [Pi](docs/pi-history-import.md)
145
- import guides, and the [CLI reference](docs/cli.md).
146
-
147
- ## Keeping OMMS up to date
148
-
149
- Pi shows a notice when a new version is out; update with
150
- `pi update npm:om-memory-system`. On OpenCode v2, the footer shows when a new version is out;
151
- run `opencode plugin update om-memory-system`. Restart the agent afterwards.
152
- [UPDATES.md](UPDATES.md) explains how updates work on every host.
153
- To stay on one version, install it with the number, for example
154
- `pi install npm:om-memory-system@3.1.1` or
155
- `opencode plugin add om-memory-system@3.1.1`; a pinned install is never
156
- updated. Coming from `opencode-mem`? Your memories move over automatically, with a
157
- backup first. See [Updating and upgrading](docs/upgrading.md) and
158
- [CHANGELOG.md](CHANGELOG.md).
118
+ ![Settings page with synthetic model configuration](.github/screenshots/settings.png)
159
119
 
160
120
  ## Documentation
161
121
 
162
- | Read this | To learn about |
163
- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
164
- | [Using memory day to day](docs/using-memory.md) | How capture and recall work, the `memory` tool, the user profile |
165
- | [Configuration](docs/configuration.md) | Settings, choosing the model, embeddings, troubleshooting |
166
- | [Web UI](docs/web-ui.md) | The memory explorer, opening it on a network safely |
167
- | [Settings page](docs/web-ui-settings.md) | Every card and control on the web Settings page |
168
- | [Moving projects](docs/moving-projects.md) | Nested repositories, moved folders, backup and restore |
169
- | [Updating and upgrading](docs/upgrading.md) | Updates, pinning a version, older stores |
170
- | [OpenCode adapter](docs/opencode-adapter.md) | How the OpenCode plugin hooks in |
171
- | [Pi adapter](docs/pi-adapter.md) | How the Pi extension hooks in |
172
- | [Claude Code adapter](docs/claude-code-adapter.md) | Installing the Claude Code plugin, hooks, the `memory` command |
173
- | [OpenCode history import](docs/opencode-history-import.md) | Importing past OpenCode sessions |
174
- | [Pi history import](docs/pi-history-import.md) | Importing past Pi sessions, moving machines |
175
- | [Claude Code history import](docs/claude-code-history-import.md) | Importing past Claude Code transcripts |
176
- | [CLI reference](docs/cli.md) | The `om-memory-system` terminal command |
177
- | [Migrating from opencode-mem](docs/omms-migration.md) | What changes when upgrading from the original plugin |
178
- | [For developers](docs/developers.md) | Building, testing, the public `tags` export, architecture |
179
- | [Contributing](CONTRIBUTING.md) | How to set up, test, and send a pull request |
180
-
181
- ## About this fork
182
-
183
- OMMS is [`cmdaltctr/omms`](https://github.com/cmdaltctr/omms), a fork of
184
- [`tickernelz/opencode-mem`](https://github.com/tickernelz/opencode-mem),
185
- published on npm as `om-memory-system`. The fork runs the memory engine as a
186
- shared core for both OpenCode and Pi (see [shared core](docs/shared-core.md)).
187
- Existing OpenCode memories and settings keep working.
188
-
189
- ## License and links
190
-
191
- MIT License. See [LICENSE.md](LICENSE.md).
192
-
193
- - Repository: https://github.com/cmdaltctr/omms
194
- - Upstream: https://github.com/tickernelz/opencode-mem
195
- - Issues: https://github.com/cmdaltctr/omms/issues
196
-
197
- Inspired by [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory).
122
+ | Guide | Covers |
123
+ | ------------------------------------------- | ------------------------------------------------------------ |
124
+ | [Using memory](docs/using-memory.md) | Daily use, capture, recall and the user profile |
125
+ | [Web UI](docs/web-ui.md) | Starting the app, browsing memories and safe network access |
126
+ | [Memory page](docs/web-ui-memory.md) | History imports, profile actions, limits and missing folders |
127
+ | [Settings page](docs/web-ui-settings.md) | Models, credentials, diagnostics and profile identities |
128
+ | [Configuration](docs/configuration.md) | Config files, model selection, privacy and embeddings |
129
+ | [Moving projects](docs/moving-projects.md) | Folder moves, backup and restore |
130
+ | [Updating and upgrading](docs/upgrading.md) | Updates, version pinning and older stores |
131
+ | [CLI reference](docs/cli.md) | Terminal commands |
132
+ | [For developers](docs/developers.md) | Building, testing and architecture |
133
+ | [Contributing](CONTRIBUTING.md) | Development workflow and pull requests |
134
+
135
+ History import guides: [OpenCode](docs/opencode-history-import.md),
136
+ [Pi](docs/pi-history-import.md) and [Claude Code](docs/claude-code-history-import.md).
137
+ See [UPDATES.md](UPDATES.md) for host update commands and
138
+ [CHANGELOG.md](CHANGELOG.md) for release changes. Existing `opencode-mem` users
139
+ can follow the [migration guide](docs/omms-migration.md).
140
+
141
+ ## Licence
142
+
143
+ MIT. See [LICENSE.md](LICENSE.md) for copyright and attribution, and
144
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for bundled dependencies.
145
+
146
+ [Repository](https://github.com/cmdaltctr/omms) ·
147
+ [Issues](https://github.com/cmdaltctr/omms/issues) ·
148
+ [Documentation index](docs/README.md)
@@ -58,6 +58,7 @@ export async function importClaudeHistory(deps, filters) {
58
58
  model: deps.profile.model,
59
59
  batchSize: deps.profile.batchSize,
60
60
  ...(deps.profile.onProgress ? { onProgress: deps.profile.onProgress } : {}),
61
+ ...(deps.profile.onEligiblePrompt ? { onEligiblePrompt: deps.profile.onEligiblePrompt } : {}),
61
62
  });
62
63
  }
63
64
  return report;
@@ -27,6 +27,9 @@ export interface ImportMatchOptions {
27
27
  project?: string;
28
28
  pathMaps: ImportPathMap[];
29
29
  cwd: string;
30
+ /** Grouped imports can retain a valid source with no matching sessions as no-work. */
31
+ allowEmpty?: boolean;
32
+ signal?: AbortSignal;
30
33
  }
31
34
  export declare class StaleSelectionError extends Error {
32
35
  readonly status = 409;
@@ -91,6 +94,8 @@ export interface ResolvedImportSelection {
91
94
  identity: ImportSourceIdentity;
92
95
  keys: string[];
93
96
  cutoff: number;
97
+ /** Source sessions still missing a project, excluding ignored directories; never import keys. */
98
+ unresolvedCount?: number;
94
99
  }
95
100
  /**
96
101
  * Re-resolve a selection against the source right before a job. New sessions
@@ -39,7 +39,10 @@ async function readRows(identity, options, snapshotMode) {
39
39
  }));
40
40
  }
41
41
  const { openOpencodeHistory } = await import("./opencode-reader.js");
42
- const history = await openOpencodeHistory(identity.realPath, {}, { shared: { key: importSourceKey(identity), mode: snapshotMode } });
42
+ const history = await openOpencodeHistory(identity.realPath, {}, {
43
+ shared: { key: importSourceKey(identity), mode: snapshotMode },
44
+ ...(options.signal ? { signal: options.signal } : {}),
45
+ });
43
46
  try {
44
47
  return history.sessions.map((session) => ({
45
48
  key: session.sessionId,
@@ -71,6 +74,7 @@ function revisionOf(identity, options, projectTag, keys) {
71
74
  .digest("hex");
72
75
  }
73
76
  export async function matchImportSessions(identity, options, snapshotMode) {
77
+ options.signal?.throwIfAborted();
74
78
  assertImportSourceUnchanged(identity);
75
79
  const { projectFilterTag } = await import("./importer.js");
76
80
  const projectTag = options.scope === "current-project"
@@ -207,6 +211,7 @@ export function validateImportSelection(value) {
207
211
  * "ids" selection stale. Neither ever imports a session the user did not see.
208
212
  */
209
213
  export async function resolveImportSelection(sourceToken, selection, options) {
214
+ options.signal?.throwIfAborted();
210
215
  const identity = readImportSourceToken(sourceToken, options.host);
211
216
  const matched = await matchImportSessions(identity, options, "reuse").catch((error) => {
212
217
  if (error.code === "expired")
@@ -228,7 +233,14 @@ export async function resolveImportSelection(sourceToken, selection, options) {
228
233
  }
229
234
  keys = selection.sessions.map((item) => item.key);
230
235
  }
231
- if (keys.length === 0)
236
+ if (keys.length === 0 && !options.allowEmpty)
232
237
  throw new Error("Choose sessions to import");
233
- return { identity, keys, cutoff: selection.listedAt };
238
+ const { CONFIG } = await import("../config.js");
239
+ const { visibleUnresolvedCount } = await import("../services/backfill-state.js");
240
+ return {
241
+ identity,
242
+ keys,
243
+ cutoff: selection.listedAt,
244
+ unresolvedCount: visibleUnresolvedCount(matched.unresolved.length, matched.unresolved.map((row) => ({ directory: row.recordedDirectory ?? "", sessions: 1 })), CONFIG.importIgnoredDirectories ?? []),
245
+ };
234
246
  }
@@ -95,6 +95,7 @@ export interface ImporterDeps {
95
95
  model?: ModelPort;
96
96
  batchSize?: number;
97
97
  onProgress?: (done: number, total: number) => void;
98
+ onEligiblePrompt?: (identity: string) => void;
98
99
  };
99
100
  }
100
101
  export interface ImportWindow extends CaptureConversation {
@@ -392,6 +392,7 @@ export async function importPiHistory(deps, filters) {
392
392
  model: deps.profile.model,
393
393
  batchSize: deps.profile.batchSize,
394
394
  ...(deps.profile.onProgress ? { onProgress: deps.profile.onProgress } : {}),
395
+ ...(deps.profile.onEligiblePrompt ? { onEligiblePrompt: deps.profile.onEligiblePrompt } : {}),
395
396
  });
396
397
  }
397
398
  return report;
@@ -28,6 +28,7 @@ export interface OpencodeImportOptions {
28
28
  profileModel?: ModelPort;
29
29
  /** Profile batches done and planned, during the profile step. */
30
30
  onProfileProgress?: (done: number, total: number) => void;
31
+ onProfilePrompt?: (identity: string) => void;
31
32
  /** `alreadyHandled`: units found already done so far, which need no model call. */
32
33
  onProgress?: (processed: number, total: number, promptPreview: string, alreadyHandled?: number) => void;
33
34
  signal?: AbortSignal;
@@ -143,6 +143,7 @@ export async function importOpencodeHistory(options) {
143
143
  batchSize: options.profileBatch,
144
144
  model: options.profileModel,
145
145
  ...(options.onProfileProgress ? { onProgress: options.onProfileProgress } : {}),
146
+ ...(options.onProfilePrompt ? { onEligiblePrompt: options.onProfilePrompt } : {}),
146
147
  });
147
148
  }
148
149
  return report;
@@ -28,6 +28,8 @@ export interface ProfileImportOptions {
28
28
  profileStore?: ProfileStore;
29
29
  /** Profile batches done and planned; called before the first batch and after each one. */
30
30
  onProgress?: (done: number, total: number) => void;
31
+ /** Dry-run estimate input only; no prompt content leaves this pipeline. */
32
+ onEligiblePrompt?: (identity: string) => void;
31
33
  }
32
34
  /** Record historical prompts and build or update the user profile in batches. */
33
35
  export declare function importProfileFromHistory(source: AsyncIterable<ImportSourceSession> | Iterable<ImportSourceSession>, options: ProfileImportOptions): Promise<ProfileImportReport>;
@@ -7,6 +7,8 @@ import { buildImportKey } from "./importer.js";
7
7
  import { existsSync } from "node:fs";
8
8
  import { ImportLedger, importLedgerDbPath } from "./ledger.js";
9
9
  import { drainProfileBacklog } from "./profile-backlog.js";
10
+ import { isTrivialPrompt } from "../core/trivial-prompt.js";
11
+ import { profilePromptIdentity } from "./web-import-profile-estimate.js";
10
12
  /** Record historical prompts and build or update the user profile in batches. */
11
13
  export async function importProfileFromHistory(source, options) {
12
14
  if (!options.dryRun && !options.model)
@@ -62,6 +64,8 @@ export async function importProfileFromHistory(source, options) {
62
64
  continue;
63
65
  }
64
66
  if (options.dryRun) {
67
+ if (!isTrivialPrompt(prompt))
68
+ options.onEligiblePrompt?.(profilePromptIdentity(session.sessionId, unit.userEntryId));
65
69
  report.promptsWouldRecord++;
66
70
  continue;
67
71
  }
@@ -18,6 +18,8 @@ export interface HistoryImportRun {
18
18
  signal?: AbortSignal;
19
19
  /** Profile batches done and planned, during the profile step after the exchanges. */
20
20
  onProfileProgress?: (done: number, total: number) => void;
21
+ /** Eligible dry-run profile prompt identities, kept out of public reports. */
22
+ onProfilePrompt?: (identity: string) => void;
21
23
  /** Record progress and take the host's lock for a run that calls models. */
22
24
  track?: {
23
25
  surface: ImportSurface;
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { profileFailureCode } from "../core/profile-failure.js";
2
3
  import { hostLabel } from "../types/host-label.js";
3
4
  const dryRunCapture = {
4
5
  summarize: async () => {
@@ -55,14 +56,20 @@ export async function runHistoryImport(host, args, run) {
55
56
  },
56
57
  });
57
58
  const handled = report.unitsImported + report.unitsSkipped + report.unitsFailed;
58
- await recorder.finish(recorder.pauseRequested ? "paused" : controller.signal.aborted ? "stopped" : "done", {
59
+ await recorder.finish(recorder.pauseRequested
60
+ ? "paused"
61
+ : controller.signal.aborted
62
+ ? "stopped"
63
+ : report.profile?.error
64
+ ? "failed"
65
+ : "done", {
59
66
  // The total stays the one progress last reported; a real run has no
60
67
  // "would import" count of its own.
61
68
  done: handled,
62
69
  imported: report.unitsImported,
63
70
  skipped: report.unitsSkipped,
64
71
  failed: report.unitsFailed,
65
- });
72
+ }, report.profile?.error ? profileFailureCode(report.profile.error) : undefined);
66
73
  // Only a run over every project and every session sees the full list.
67
74
  if (args.scope === "all-projects" && !run.selection)
68
75
  await recordUnresolved(host, report);
@@ -126,6 +133,7 @@ async function runUntracked(host, args, run) {
126
133
  ...(run.models.capture ? { provider: capture } : {}),
127
134
  ...(run.models.profile ? { profileModel: run.models.profile } : {}),
128
135
  ...(run.onProfileProgress ? { onProfileProgress: run.onProfileProgress } : {}),
136
+ ...(run.onProfilePrompt ? { onProfilePrompt: run.onProfilePrompt } : {}),
129
137
  });
130
138
  }
131
139
  const deps = {
@@ -138,6 +146,7 @@ async function runUntracked(host, args, run) {
138
146
  ...(run.models.profile ? { model: run.models.profile } : {}),
139
147
  ...(args.profileBatch ? { batchSize: args.profileBatch } : {}),
140
148
  ...(run.onProfileProgress ? { onProgress: run.onProfileProgress } : {}),
149
+ ...(run.onProfilePrompt ? { onEligiblePrompt: run.onProfilePrompt } : {}),
141
150
  },
142
151
  }
143
152
  : {}),
@@ -0,0 +1,57 @@
1
+ import { type ImportHost } from "./import-args.js";
2
+ import { type ImportReadiness } from "./import-readiness.js";
3
+ import { resolveImportSelection } from "./import-sessions.js";
4
+ import { type ImportSourceIdentity } from "./import-sources.js";
5
+ import { runHistoryImport, summarizeHistoryImportReport, type HistoryImportRun } from "./run-import.js";
6
+ import { type GroupImportRequest } from "./web-import-jobs.js";
7
+ import { type ProfileEstimate } from "./web-import-profile-estimate.js";
8
+ export type GroupHostState = "queued" | "running" | "done" | "failed" | "cancelled" | "no-work" | "not-run";
9
+ type LegacySummary = ReturnType<typeof summarizeHistoryImportReport>;
10
+ export type GroupImportSummary = Omit<LegacySummary, "profile"> & {
11
+ profile?: NonNullable<LegacySummary["profile"]> & {
12
+ promptsAlreadyHandled: number;
13
+ };
14
+ };
15
+ type Summary = GroupImportSummary;
16
+ export interface GroupHostJob {
17
+ host: ImportHost;
18
+ state: GroupHostState;
19
+ sessions: number;
20
+ phase: "preparing" | "memory" | "profile";
21
+ processed: number;
22
+ total: number;
23
+ profileProcessed: number;
24
+ profileTotal: number;
25
+ summary?: Summary;
26
+ profileEstimate?: ProfileEstimate;
27
+ /** Model readiness for a real run, also visible in a read-only preview. */
28
+ blocker?: string;
29
+ error?: string;
30
+ }
31
+ export interface GroupImportJob {
32
+ id: string;
33
+ dryRun: boolean;
34
+ state: "running" | "cancelling" | "cancelled" | "done" | "failed";
35
+ activeHost?: ImportHost;
36
+ hosts: GroupHostJob[];
37
+ sessions: number;
38
+ processed: number;
39
+ total: number;
40
+ summary?: Summary;
41
+ profileEstimate?: ProfileEstimate;
42
+ error?: string;
43
+ }
44
+ export interface GroupImportDeps {
45
+ /** Model construction and store warmup, never invoked by a preview. */
46
+ prepareModels?: (choice: string, directory: string, signal: AbortSignal, host: ImportHost) => Promise<HistoryImportRun["models"]>;
47
+ discardSelection?: (identity: ImportSourceIdentity) => Promise<void>;
48
+ waitingPrompts?: () => Promise<Set<string>>;
49
+ }
50
+ type Deps = GroupImportDeps & {
51
+ runner: typeof runHistoryImport;
52
+ resolveSelection: typeof resolveImportSelection;
53
+ readiness: () => Promise<ImportReadiness>;
54
+ };
55
+ /** Preflight every child before launching a bounded sequential group in the existing slot. */
56
+ export declare function startImportGroup(input: GroupImportRequest, directory: string, deps: Deps, publish: (job: GroupImportJob, controller: AbortController) => void): Promise<GroupImportJob>;
57
+ export {};