om-memory-system 4.14.0-next.106 → 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 +106 -149
- package/dist/adapters/claude-code/hook-command.d.ts +3 -1
- package/dist/adapters/claude-code/hook-command.js +14 -0
- package/dist/adapters/claude-code/plugin-self-update.d.ts +27 -0
- package/dist/adapters/claude-code/plugin-self-update.js +103 -0
- package/dist/importer/claude-import.js +1 -0
- package/dist/importer/import-sessions.d.ts +5 -0
- package/dist/importer/import-sessions.js +15 -3
- package/dist/importer/importer.d.ts +1 -0
- package/dist/importer/importer.js +1 -0
- package/dist/importer/opencode-import.d.ts +1 -0
- package/dist/importer/opencode-import.js +1 -0
- package/dist/importer/profile-import.d.ts +2 -0
- package/dist/importer/profile-import.js +4 -0
- package/dist/importer/run-import.d.ts +2 -0
- package/dist/importer/run-import.js +11 -2
- package/dist/importer/web-import-group.d.ts +57 -0
- package/dist/importer/web-import-group.js +355 -0
- package/dist/importer/web-import-jobs.d.ts +12 -6
- package/dist/importer/web-import-jobs.js +48 -2
- package/dist/importer/web-import-profile-estimate.d.ts +14 -0
- package/dist/importer/web-import-profile-estimate.js +37 -0
- package/dist/services/turso/cross-process-write-lock.js +55 -18
- package/dist/services/web-server.js +1 -1
- package/dist/services/web-update.d.ts +7 -2
- package/dist/services/web-update.js +20 -7
- package/dist/web/assets/index-C9EubnKm.js +127 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-BH1QAU9V.js +0 -125
package/README.md
CHANGED
|
@@ -1,191 +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
|
[](https://www.npmjs.com/package/om-memory-system)
|
|
4
8
|
[](https://www.npmjs.com/package/om-memory-system)
|
|
5
|
-
[](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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
[
|
|
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
|
-
|
|
33
|
+
### OpenCode
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
38
|
+
**OpenCode v2:**
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
```json
|
|
41
|
+
{ "plugins": ["om-memory-system"] }
|
|
42
|
+
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
**OpenCode v1:**
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
```json
|
|
47
|
+
{ "plugin": ["om-memory-system"] }
|
|
48
|
+
```
|
|
47
49
|
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Node.js 22.14 or later.
|
|
52
|
+
```bash
|
|
53
|
+
opencode plugin add om-memory-system
|
|
54
|
+
```
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
Restart OpenCode. The [OpenCode guide](docs/opencode-adapter.md#installation)
|
|
57
|
+
covers plugin management and its additional `cli.json` configuration.
|
|
59
58
|
|
|
60
|
-
###
|
|
59
|
+
### Pi
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pi install npm:om-memory-system
|
|
63
|
+
```
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
(on Windows, `%USERPROFILE%\.config\opencode\opencode.json`):
|
|
65
|
+
Restart Pi. See the [Pi guide](docs/pi-adapter.md).
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
// OpenCode v2
|
|
67
|
-
{ "plugins": ["om-memory-system"] }
|
|
67
|
+
### Claude Code
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
Restart OpenCode.
|
|
73
|
+
### Model and web app
|
|
75
74
|
|
|
76
|
-
|
|
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.
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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.
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
### Terminal command (optional)
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
command on your `PATH`. You do not need it. OMMS runs the newest copy it finds on
|
|
86
|
-
the machine, so updating one host is enough. A global install from 4.3 or earlier
|
|
87
|
-
needs one update. See
|
|
88
|
-
[Updating and upgrading](docs/upgrading.md#one-update-updates-every-host).
|
|
89
|
-
To install it:
|
|
87
|
+
A global install puts `om-memory-system` on your `PATH`:
|
|
90
88
|
|
|
91
89
|
```bash
|
|
92
|
-
npm
|
|
90
|
+
npm install -g om-memory-system
|
|
93
91
|
om-memory-system --version
|
|
94
92
|
```
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
With no settings, OMMS uses the model of the session you are working in. To
|
|
99
|
-
use a different one, add it to `~/.config/omms/omms.jsonc`. If you have no
|
|
100
|
-
config file yet, OMMS creates this one with comments on first start.
|
|
101
|
-
|
|
102
|
-
```jsonc
|
|
103
|
-
{
|
|
104
|
-
// A model you are already signed in to in OpenCode or Pi.
|
|
105
|
-
// Use "inherit" to always follow the session's model.
|
|
106
|
-
"opencodeProvider": "openai",
|
|
107
|
-
"opencodeModel": "gpt-5.6-luna",
|
|
108
|
-
"piProvider": "openai-codex",
|
|
109
|
-
"piModel": "gpt-5.6-luna",
|
|
110
|
-
}
|
|
111
|
-
```
|
|
94
|
+
See the [CLI reference](docs/cli.md) for manual memory operations and history imports.
|
|
112
95
|
|
|
113
|
-
|
|
114
|
-
up on the Settings page's **External API** card, and choose `"external"` as a
|
|
115
|
-
host's model. See [Configuration](docs/configuration.md#choosing-the-model).
|
|
96
|
+
## Inside OMMS
|
|
116
97
|
|
|
117
|
-
|
|
98
|
+
These screenshots use synthetic examples and configuration. All visible prompts,
|
|
99
|
+
code snippets and outcomes are made up for the demonstration.
|
|
118
100
|
|
|
119
|
-
|
|
120
|
-
browser. OpenCode or the login web app serves this page. New memories appear
|
|
121
|
-
on the timeline. You can also ask the agent: "search memory for what we changed today".
|
|
101
|
+
### Project memories
|
|
122
102
|
|
|
123
|
-
|
|
103
|
+
Browse captured notes alongside example user input and technical outcomes.
|
|
124
104
|
|
|
125
|
-
|
|
126
|
-
calls. To turn it off, set `"autoBackfill": false` in
|
|
127
|
-
`~/.config/omms/omms.jsonc` before you start the agent. For a manual import or
|
|
128
|
-
a custom source, preview it inside the agent:
|
|
105
|
+

|
|
129
106
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+

|
|
113
|
+
|
|
114
|
+
### Settings
|
|
115
|
+
|
|
116
|
+
Choose models and manage access. Diagnostics show capture outcomes and component health.
|
|
134
117
|
|
|
135
|
-
|
|
136
|
-
session's model; add `--model provider/id` for a cheaper one. There is also a
|
|
137
|
-
terminal version, `npx om-memory-system`, that uses an API key. See the
|
|
138
|
-
[OpenCode](docs/opencode-history-import.md) and [Pi](docs/pi-history-import.md)
|
|
139
|
-
import guides, and the [CLI reference](docs/cli.md).
|
|
140
|
-
|
|
141
|
-
## Keeping OMMS up to date
|
|
142
|
-
|
|
143
|
-
Pi shows a notice when a new version is out; update with
|
|
144
|
-
`pi update npm:om-memory-system`. On OpenCode v2, the footer shows when a new version is out;
|
|
145
|
-
run `opencode plugin update om-memory-system`. Restart the agent afterwards.
|
|
146
|
-
[UPDATES.md](UPDATES.md) explains how updates work on every host.
|
|
147
|
-
To stay on one version, install it with the number, for example
|
|
148
|
-
`pi install npm:om-memory-system@3.1.1` or
|
|
149
|
-
`opencode plugin add om-memory-system@3.1.1`; a pinned install is never
|
|
150
|
-
updated. Coming from `opencode-mem`? Your memories move over automatically, with a
|
|
151
|
-
backup first. See [Updating and upgrading](docs/upgrading.md) and
|
|
152
|
-
[CHANGELOG.md](CHANGELOG.md).
|
|
118
|
+

|
|
153
119
|
|
|
154
120
|
## Documentation
|
|
155
121
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
| [Using memory
|
|
159
|
-
| [
|
|
160
|
-
| [
|
|
161
|
-
| [Settings page](docs/web-ui-settings.md)
|
|
162
|
-
| [
|
|
163
|
-
| [
|
|
164
|
-
| [
|
|
165
|
-
| [
|
|
166
|
-
| [
|
|
167
|
-
| [
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
##
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
[
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
## License and links
|
|
184
|
-
|
|
185
|
-
MIT License. See [LICENSE.md](LICENSE.md).
|
|
186
|
-
|
|
187
|
-
- Repository: https://github.com/cmdaltctr/omms
|
|
188
|
-
- Upstream: https://github.com/tickernelz/opencode-mem
|
|
189
|
-
- Issues: https://github.com/cmdaltctr/omms/issues
|
|
190
|
-
|
|
191
|
-
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)
|
|
@@ -4,4 +4,6 @@ import { type ClaudeStatusOptions } from "./status.js";
|
|
|
4
4
|
* `om-memory-system claude-hook <event>`. Always returns 0: a hook must never
|
|
5
5
|
* block or break a Claude Code session, whatever goes wrong.
|
|
6
6
|
*/
|
|
7
|
-
export declare function runClaudeHookCommand(argv: string[], io?: ClaudeHookOptions & ClaudeStatusOptions
|
|
7
|
+
export declare function runClaudeHookCommand(argv: string[], io?: ClaudeHookOptions & ClaudeStatusOptions & {
|
|
8
|
+
skipSelfUpdate?: boolean;
|
|
9
|
+
}): Promise<number>;
|
|
@@ -9,6 +9,18 @@ async function printStatus(io) {
|
|
|
9
9
|
const status = await buildClaudeStatus({ ...inputs, fetch: io.fetch ?? globalThis.fetch });
|
|
10
10
|
(io.writeStdout ?? ((text) => process.stdout.write(text)))(`${JSON.stringify(status)}\n`);
|
|
11
11
|
}
|
|
12
|
+
/** Update the Claude plugin in the background when npm has a newer release. Never throws. */
|
|
13
|
+
async function selfUpdate(io) {
|
|
14
|
+
try {
|
|
15
|
+
const { startPluginSelfUpdate, pluginSelfUpdateDeps } = await import("./plugin-self-update.js");
|
|
16
|
+
const log = io.log ??
|
|
17
|
+
(async (message, data) => (await import("../../services/logger.js")).log(message, data));
|
|
18
|
+
await startPluginSelfUpdate(pluginSelfUpdateDeps(log));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* The memory hook already ran; a failed update check changes nothing. */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
/**
|
|
13
25
|
* `om-memory-system claude-hook <event>`. Always returns 0: a hook must never
|
|
14
26
|
* block or break a Claude Code session, whatever goes wrong.
|
|
@@ -22,6 +34,8 @@ export async function runClaudeHookCommand(argv, io = {}) {
|
|
|
22
34
|
}
|
|
23
35
|
if (isHookEvent(event)) {
|
|
24
36
|
await runClaudeHook(event, io);
|
|
37
|
+
if (event === "session-start" && !io.skipSelfUpdate)
|
|
38
|
+
await selfUpdate(io);
|
|
25
39
|
return 0;
|
|
26
40
|
}
|
|
27
41
|
// Only a known name reaches the log; argv is not trusted text.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A second session does not start another update for the same release within this time. */
|
|
2
|
+
export declare const SELF_UPDATE_RETRY_MS: number;
|
|
3
|
+
export type PluginSelfUpdateCode = "started" | "current" | "recent" | "disabled" | "no-plugin" | "unreadable-plugin" | "unreachable" | "spawn-failed";
|
|
4
|
+
export interface PluginSelfUpdateDeps {
|
|
5
|
+
/** False when `OMMS_DISABLE_UPDATE_CHECK` is `1`. */
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** `CLAUDE_PLUGIN_ROOT`; undefined outside a Claude plugin. */
|
|
8
|
+
pluginRoot: string | undefined;
|
|
9
|
+
readPluginVersion: (root: string) => string;
|
|
10
|
+
fetch: typeof fetch;
|
|
11
|
+
now: () => number;
|
|
12
|
+
readMarker: () => {
|
|
13
|
+
version: string;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
} | null;
|
|
16
|
+
writeMarker: (value: {
|
|
17
|
+
version: string;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
/** Start a process that outlives this hook. */
|
|
21
|
+
spawnDetached: (command: string, args: string[]) => void;
|
|
22
|
+
log: (message: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Start a background plugin update when npm has a newer release. Never throws. */
|
|
25
|
+
export declare function startPluginSelfUpdate(deps: PluginSelfUpdateDeps): Promise<PluginSelfUpdateCode>;
|
|
26
|
+
/** Production inputs: the real plugin manifest, npm, and a marker file in ~/.omms. */
|
|
27
|
+
export declare function pluginSelfUpdateDeps(log: PluginSelfUpdateDeps["log"], env?: NodeJS.ProcessEnv): PluginSelfUpdateDeps;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { availableUpdate, latestNpmVersion } from "../../services/update-check.js";
|
|
6
|
+
// Claude Code installs the OMMS plugin from the `stable` branch, but its own
|
|
7
|
+
// update pass runs at its own time and can leave a new session on the old
|
|
8
|
+
// plugin. At session start OMMS compares the plugin with npm `latest` and, when
|
|
9
|
+
// it is older, runs Claude Code's own update commands in the background. The
|
|
10
|
+
// new copy loads in the next session or after /reload-plugins.
|
|
11
|
+
/** A second session does not start another update for the same release within this time. */
|
|
12
|
+
export const SELF_UPDATE_RETRY_MS = 30 * 60 * 1000;
|
|
13
|
+
/** Node script that refreshes the marketplace, then updates the plugin. Run detached. */
|
|
14
|
+
const UPDATE_SCRIPT = `
|
|
15
|
+
const { spawnSync } = require("node:child_process");
|
|
16
|
+
const shell = process.platform === "win32";
|
|
17
|
+
for (const args of [["plugin", "marketplace", "update", "omms"], ["plugin", "update", "omms@omms"]]) {
|
|
18
|
+
const run = spawnSync("claude", args, { stdio: "ignore", shell, timeout: 120000 });
|
|
19
|
+
if (run.status !== 0) process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
/** Start a background plugin update when npm has a newer release. Never throws. */
|
|
23
|
+
export async function startPluginSelfUpdate(deps) {
|
|
24
|
+
const code = await decide(deps);
|
|
25
|
+
if (code.code === "started" || code.code === "spawn-failed") {
|
|
26
|
+
await deps.log("Claude plugin self-update", {
|
|
27
|
+
code: code.code,
|
|
28
|
+
plugin: code.plugin,
|
|
29
|
+
latest: code.latest,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return code.code;
|
|
33
|
+
}
|
|
34
|
+
async function decide(deps) {
|
|
35
|
+
if (!deps.enabled)
|
|
36
|
+
return { code: "disabled" };
|
|
37
|
+
if (!deps.pluginRoot)
|
|
38
|
+
return { code: "no-plugin" };
|
|
39
|
+
let plugin;
|
|
40
|
+
try {
|
|
41
|
+
plugin = deps.readPluginVersion(deps.pluginRoot);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { code: "unreadable-plugin" };
|
|
45
|
+
}
|
|
46
|
+
const latest = await latestNpmVersion(deps.fetch);
|
|
47
|
+
if (latest === null)
|
|
48
|
+
return { code: "unreachable" };
|
|
49
|
+
const newer = availableUpdate(plugin, latest);
|
|
50
|
+
if (!newer)
|
|
51
|
+
return { code: "current" };
|
|
52
|
+
const marker = deps.readMarker();
|
|
53
|
+
const now = deps.now();
|
|
54
|
+
if (marker?.version === newer && now - marker.startedAt <= SELF_UPDATE_RETRY_MS) {
|
|
55
|
+
return { code: "recent" };
|
|
56
|
+
}
|
|
57
|
+
// Write first, so a session that starts during the update does not start another.
|
|
58
|
+
deps.writeMarker({ version: newer, startedAt: now });
|
|
59
|
+
try {
|
|
60
|
+
deps.spawnDetached(process.execPath, ["-e", UPDATE_SCRIPT]);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { code: "spawn-failed", plugin, latest: newer };
|
|
64
|
+
}
|
|
65
|
+
return { code: "started", plugin, latest: newer };
|
|
66
|
+
}
|
|
67
|
+
/** Production inputs: the real plugin manifest, npm, and a marker file in ~/.omms. */
|
|
68
|
+
export function pluginSelfUpdateDeps(log, env = process.env) {
|
|
69
|
+
const markerPath = join(homedir(), ".omms", "claude-plugin-update.json");
|
|
70
|
+
return {
|
|
71
|
+
enabled: env.OMMS_DISABLE_UPDATE_CHECK !== "1",
|
|
72
|
+
pluginRoot: env.CLAUDE_PLUGIN_ROOT || undefined,
|
|
73
|
+
readPluginVersion: (root) => {
|
|
74
|
+
const manifest = JSON.parse(readFileSync(join(root, ".claude-plugin", "plugin.json"), "utf8"));
|
|
75
|
+
if (typeof manifest.version !== "string")
|
|
76
|
+
throw new Error("no version");
|
|
77
|
+
return manifest.version;
|
|
78
|
+
},
|
|
79
|
+
fetch: globalThis.fetch,
|
|
80
|
+
now: Date.now,
|
|
81
|
+
readMarker: () => {
|
|
82
|
+
try {
|
|
83
|
+
const value = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
84
|
+
return typeof value.version === "string" && typeof value.startedAt === "number"
|
|
85
|
+
? { version: value.version, startedAt: value.startedAt }
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
writeMarker: (value) => {
|
|
93
|
+
mkdirSync(dirname(markerPath), { recursive: true });
|
|
94
|
+
writeFileSync(markerPath, JSON.stringify(value));
|
|
95
|
+
},
|
|
96
|
+
spawnDetached: (command, args) => {
|
|
97
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
98
|
+
child.on("error", () => { });
|
|
99
|
+
child.unref();
|
|
100
|
+
},
|
|
101
|
+
log,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -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, {}, {
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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;
|