opencode-codex-memory 0.7.3 → 0.7.5
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 +76 -16
- package/dist/src/db.js +3 -0
- package/dist/src/index.js +34 -3
- package/dist/src/options.d.ts +5 -3
- package/dist/src/options.js +3 -0
- package/dist/src/paths.d.ts +27 -0
- package/dist/src/paths.js +56 -9
- package/dist/src/v2/plugin.js +12 -4
- package/dist/src/v2/service.d.ts +6 -2
- package/dist/src/v2/service.js +18 -3
- package/dist/src/v2/shim.d.ts +12 -2
- package/dist/src/v2/shim.js +106 -33
- package/dist/tools/control.js +14 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,9 +23,10 @@ See the [changelog](./CHANGELOG.md) for release history.
|
|
|
23
23
|
**Local-first by design.** Memory is plain markdown files plus a small SQLite
|
|
24
24
|
database on your own machine — no memory service to sign up for, no MCP server,
|
|
25
25
|
no separate process, no sync. Installing it is one line in your `opencode.json`;
|
|
26
|
-
from there everything lives under `~/.local/share/opencode
|
|
27
|
-
|
|
28
|
-
machine beyond the model
|
|
26
|
+
from there everything lives under `~/.local/share/opencode/` by default (or a
|
|
27
|
+
directory you set with `home`), so you can read it, grep it, edit it, or delete
|
|
28
|
+
it like anything else you own. Nothing leaves your machine beyond the model
|
|
29
|
+
calls OpenCode already makes.
|
|
29
30
|
|
|
30
31
|
If you *do* also use the Codex CLI or Claude Code on the same machine: the
|
|
31
32
|
plugin can bring their memories in (and, for Codex, push ours back). Off by
|
|
@@ -52,7 +53,7 @@ If you want the mental model — learning, remembering, forgetting — see
|
|
|
52
53
|
|
|
53
54
|
```json
|
|
54
55
|
{
|
|
55
|
-
"plugin": ["opencode-codex-memory@0.7.
|
|
56
|
+
"plugin": ["opencode-codex-memory@0.7.5"]
|
|
56
57
|
}
|
|
57
58
|
```
|
|
58
59
|
|
|
@@ -72,7 +73,7 @@ V2 plugin syntax:
|
|
|
72
73
|
|
|
73
74
|
```jsonc
|
|
74
75
|
{
|
|
75
|
-
"plugins": [{ "package": "opencode-codex-memory@0.7.
|
|
76
|
+
"plugins": [{ "package": "opencode-codex-memory@0.7.5" }],
|
|
76
77
|
}
|
|
77
78
|
```
|
|
78
79
|
|
|
@@ -126,9 +127,15 @@ echo 'I prefer TypeScript strict mode and 2-space indentation.' \
|
|
|
126
127
|
└── extensions/ad_hoc/notes/ # things you explicitly asked it to remember
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
By default this is OpenCode's own data directory — `$XDG_DATA_HOME/opencode`
|
|
130
131
|
when that's set, otherwise `~/.local/share/opencode` (same resolution on macOS,
|
|
131
|
-
Linux, and Windows).
|
|
132
|
+
Linux, and Windows). Relocating OpenCode's local dir (for example by setting
|
|
133
|
+
`XDG_DATA_HOME`) therefore moves memory with it. To pin **only** `memory.db`
|
|
134
|
+
and `memories/` somewhere else — a sandbox mount, without the rest of
|
|
135
|
+
OpenCode's database — set `home`. That pin is always enforced; OpenCode's data
|
|
136
|
+
dir and `XDG_DATA_HOME` no longer apply. Precedence:
|
|
137
|
+
[Relocating memory](#relocating-memory-sandbox-mounts). Do not symlink
|
|
138
|
+
`memories/` — the plugin refuses a symlinked memory root.
|
|
132
139
|
|
|
133
140
|
It's all plain files and a local SQLite database. Read them, edit them, delete
|
|
134
141
|
them — it's yours. (The `memories/` folder also holds a few working files and
|
|
@@ -143,11 +150,13 @@ leave job state, Git baseline, and memory files out of sync. Include hidden
|
|
|
143
150
|
files, especially `memories/.git/`, and SQLite sidecars such as `memory.db-wal`
|
|
144
151
|
or `memory.db-shm` when present.
|
|
145
152
|
|
|
146
|
-
The directory is `$XDG_DATA_HOME/opencode` when `XDG_DATA_HOME` is set,
|
|
147
|
-
otherwise `~/.local/share/opencode`.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
The default directory is `$XDG_DATA_HOME/opencode` when `XDG_DATA_HOME` is set,
|
|
154
|
+
otherwise `~/.local/share/opencode`. If you set `home` (or
|
|
155
|
+
`OPENCODE_CODEX_MEMORY_HOME`), back up that directory instead — it is the pair
|
|
156
|
+
`memory.db` + `memories/`. Copy it to a dated backup location. To restore, stop
|
|
157
|
+
OpenCode, replace the current home with the backup copy, then start OpenCode
|
|
158
|
+
again. Do not restore while OpenCode is running or copy only `memory.db` or
|
|
159
|
+
only `memories/`.
|
|
151
160
|
|
|
152
161
|
## Privacy & safety
|
|
153
162
|
|
|
@@ -192,13 +201,14 @@ Codex's `[memories]` config so the two stay easy to compare:
|
|
|
192
201
|
| `max_unused_days` | `30` | Prune memories unused for this long |
|
|
193
202
|
| `codex_interop` | `{ "import": false, "export": false }` | Two-way memory exchange with a local Codex CLI (see below) |
|
|
194
203
|
| `claude_import` | `{ "enabled": false }` | One-way import of Claude Code project memories (see below) |
|
|
204
|
+
| `home` | unset (follows OpenCode data dir) | Pin `memory.db` + `memories/` here. When set, OpenCode's data dir and `XDG_DATA_HOME` are ignored. See [Relocating memory](#relocating-memory-sandbox-mounts) |
|
|
195
205
|
|
|
196
206
|
To set options, turn the plugin entry into a `[name, options]` pair:
|
|
197
207
|
|
|
198
208
|
```json
|
|
199
209
|
{
|
|
200
210
|
"plugin": [
|
|
201
|
-
["opencode-codex-memory@0.7.
|
|
211
|
+
["opencode-codex-memory@0.7.5", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
|
|
202
212
|
]
|
|
203
213
|
}
|
|
204
214
|
```
|
|
@@ -253,6 +263,56 @@ nearest OpenCode variant on `none < minimal < low < medium < high < xhigh < max`
|
|
|
253
263
|
> maintenance tools (`memory_reset`, `memory_inspect`, `memory_mode`) stay
|
|
254
264
|
> available either way.
|
|
255
265
|
|
|
266
|
+
### Relocating memory (sandbox mounts)
|
|
267
|
+
|
|
268
|
+
First match wins:
|
|
269
|
+
|
|
270
|
+
1. **`home` plugin option** — pinned. Always enforced. OpenCode's data
|
|
271
|
+
directory and `XDG_DATA_HOME` do not apply.
|
|
272
|
+
2. **`OPENCODE_CODEX_MEMORY_HOME`** — same pin, only if `home` is unset.
|
|
273
|
+
3. **OpenCode data dir** (default) — `$XDG_DATA_HOME/opencode` when that env
|
|
274
|
+
is set, otherwise `~/.local/share/opencode`. Memory moves with OpenCode's
|
|
275
|
+
local directory.
|
|
276
|
+
|
|
277
|
+
To mount **only** memory into a sandbox, use (1) or (2) and point at a
|
|
278
|
+
dedicated directory:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"plugin": [
|
|
283
|
+
["opencode-codex-memory@0.7.5", { "home": "/path/to/opencode-memory" }]
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
OpenCode 2:
|
|
289
|
+
|
|
290
|
+
```jsonc
|
|
291
|
+
{
|
|
292
|
+
"plugins": [
|
|
293
|
+
{ "package": "opencode-codex-memory@0.7.5", "options": { "home": "/path/to/opencode-memory" } }
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`~` is expanded. The path must be absolute — a project-relative path would
|
|
299
|
+
make memory follow the cwd, and memory is global. Same layout as the default,
|
|
300
|
+
just elsewhere:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
/path/to/opencode-memory/
|
|
304
|
+
├── memory.db
|
|
305
|
+
└── memories/
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Mount that directory into the sandbox at the **same absolute path**.
|
|
309
|
+
|
|
310
|
+
This does not move existing files. Stop OpenCode, copy `memory.db` (and
|
|
311
|
+
`memory.db-wal` / `memory.db-shm` if present) plus `memories/` into the new
|
|
312
|
+
home, then start it again. Do not symlink `memories/` in place of `home` —
|
|
313
|
+
a symlinked memory root is refused. Ask the agent to run `memory_inspect` to
|
|
314
|
+
confirm the resolved path.
|
|
315
|
+
|
|
256
316
|
### Sharing memory with the Codex CLI
|
|
257
317
|
|
|
258
318
|
If you use OpenCode and the Codex CLI on the same machine, turn this on so each
|
|
@@ -265,7 +325,7 @@ Off by default; no changes to Codex's own config are required.
|
|
|
265
325
|
{
|
|
266
326
|
"plugin": [
|
|
267
327
|
[
|
|
268
|
-
"opencode-codex-memory@0.7.
|
|
328
|
+
"opencode-codex-memory@0.7.5",
|
|
269
329
|
{ "codex_interop": { "import": true, "export": true } }
|
|
270
330
|
]
|
|
271
331
|
]
|
|
@@ -322,7 +382,7 @@ from the project memories Claude already keeps on your machine. **One-way only**
|
|
|
322
382
|
```json
|
|
323
383
|
{
|
|
324
384
|
"plugin": [
|
|
325
|
-
["opencode-codex-memory@0.7.
|
|
385
|
+
["opencode-codex-memory@0.7.5", { "claude_import": { "enabled": true } }]
|
|
326
386
|
]
|
|
327
387
|
}
|
|
328
388
|
```
|
|
@@ -349,7 +409,7 @@ Claude names each project with an opaque id (a folder under
|
|
|
349
409
|
{
|
|
350
410
|
"plugin": [
|
|
351
411
|
[
|
|
352
|
-
"opencode-codex-memory@0.7.
|
|
412
|
+
"opencode-codex-memory@0.7.5",
|
|
353
413
|
{
|
|
354
414
|
"claude_import": {
|
|
355
415
|
"enabled": true,
|
package/dist/src/db.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
2
4
|
import { memoryDbPath } from "./paths.js";
|
|
3
5
|
const SCHEMA_V1 = [
|
|
4
6
|
`CREATE TABLE IF NOT EXISTS memory_stage1_outputs (
|
|
@@ -46,6 +48,7 @@ export function openDb() {
|
|
|
46
48
|
if (dbInstance)
|
|
47
49
|
return dbInstance;
|
|
48
50
|
const dbPath = memoryDbPath();
|
|
51
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
49
52
|
const db = new Database(dbPath, { create: true, readwrite: true, strict: false });
|
|
50
53
|
try {
|
|
51
54
|
// Match codex's memories-DB open options (runtime.rs): WAL, NORMAL sync,
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensureMemoryLayout, buildMemorySystemPrompt, invalidateCache } from "./source.js";
|
|
2
|
-
import { memoryRoot } from "./paths.js";
|
|
2
|
+
import { memoryRoot, memoryDbPath, setConfiguredHome, resolveHomePath } from "./paths.js";
|
|
3
|
+
import { closeDb } from "./db.js";
|
|
3
4
|
import { stripCitations, extractCitedSessionIds, hasCitationMarkup } from "./citation.js";
|
|
4
5
|
import { memory_read, memory_search, memory_list, memory_add_note } from "../tools/memory.js";
|
|
5
6
|
import { memory_reset, memory_inspect, memory_mode } from "../tools/control.js";
|
|
@@ -137,8 +138,12 @@ export default {
|
|
|
137
138
|
clearConfigWarnings();
|
|
138
139
|
if (opts)
|
|
139
140
|
applyPluginOptions(opts);
|
|
140
|
-
else
|
|
141
|
+
else {
|
|
142
|
+
const previousDb = memoryDbPath();
|
|
141
143
|
resetPluginOptions();
|
|
144
|
+
if (memoryDbPath() !== previousDb)
|
|
145
|
+
closeDb();
|
|
146
|
+
}
|
|
142
147
|
// Finish bounded reseeding before hooks can see a surviving memory
|
|
143
148
|
// sub-session after a plugin reload.
|
|
144
149
|
await cleanupOldSubSessions();
|
|
@@ -165,6 +170,7 @@ const KNOWN_OPTION_KEYS = new Set([
|
|
|
165
170
|
"max_rollout_age_days",
|
|
166
171
|
"max_rollouts_per_startup",
|
|
167
172
|
"min_rollout_idle_hours",
|
|
173
|
+
"home",
|
|
168
174
|
"codex_interop",
|
|
169
175
|
"claude_import",
|
|
170
176
|
]);
|
|
@@ -183,6 +189,7 @@ function clampInt(key, value, min, max, fallback) {
|
|
|
183
189
|
export function applyPluginOptions(opts) {
|
|
184
190
|
// Fresh pass per apply so memory_inspect never shows warnings for keys the
|
|
185
191
|
// caller has since fixed. server() clears too, for boots without options.
|
|
192
|
+
const previousDb = memoryDbPath();
|
|
186
193
|
clearConfigWarnings();
|
|
187
194
|
resetPluginOptions();
|
|
188
195
|
const raw = opts;
|
|
@@ -220,6 +227,28 @@ export function applyPluginOptions(opts) {
|
|
|
220
227
|
pluginOptions.max_rollouts_per_startup = clampInt("max_rollouts_per_startup", opts.max_rollouts_per_startup, 1, 128, 2);
|
|
221
228
|
if ("min_rollout_idle_hours" in opts)
|
|
222
229
|
pluginOptions.min_rollout_idle_hours = clampInt("min_rollout_idle_hours", opts.min_rollout_idle_hours, 1, 48, 6);
|
|
230
|
+
if ("home" in raw) {
|
|
231
|
+
if (typeof raw.home !== "string") {
|
|
232
|
+
recordConfigWarning("home must be a string; using the OpenCode data dir");
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const resolved = resolveHomePath(raw.home);
|
|
236
|
+
if (!resolved) {
|
|
237
|
+
recordConfigWarning("home must be an absolute path (tilde ~ is ok); using the OpenCode data dir");
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
pluginOptions.home = resolved;
|
|
241
|
+
setConfiguredHome(resolved);
|
|
242
|
+
const envHome = process.env.OPENCODE_CODEX_MEMORY_HOME;
|
|
243
|
+
if (envHome) {
|
|
244
|
+
const envResolved = resolveHomePath(envHome);
|
|
245
|
+
if (envResolved && envResolved !== resolved) {
|
|
246
|
+
recordConfigWarning("OPENCODE_CODEX_MEMORY_HOME ignored because home option is set");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
223
252
|
if ("codex_interop" in opts) {
|
|
224
253
|
const raw = opts.codex_interop;
|
|
225
254
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -282,6 +311,8 @@ export function applyPluginOptions(opts) {
|
|
|
282
311
|
recordConfigWarning("claude_import must be an object like { enabled, claude_home, projects }; ignored");
|
|
283
312
|
}
|
|
284
313
|
}
|
|
314
|
+
if (memoryDbPath() !== previousDb)
|
|
315
|
+
closeDb();
|
|
285
316
|
}
|
|
286
317
|
/**
|
|
287
318
|
* codex marks every MCP server as memory-polluting unconditionally
|
|
@@ -376,7 +407,7 @@ export function injectAgentDefinitions(config) {
|
|
|
376
407
|
}
|
|
377
408
|
// Sub-sessions use directory=memoryRoot (llm.ts), so memory paths are usually
|
|
378
409
|
// in-bounds. Keep an explicit external_directory allow for the memory root as
|
|
379
|
-
// belt-and-suspenders (path is
|
|
410
|
+
// belt-and-suspenders (path is home/env-dependent; out-ranks `"*": deny`).
|
|
380
411
|
const memorize = defs["memorize"];
|
|
381
412
|
if (memorize?.permission && !("external_directory" in memorize.permission)) {
|
|
382
413
|
memorize.permission["external_directory"] = { [path.join(memoryRoot(), "*")]: "allow" };
|
package/dist/src/options.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ import type { ClaudeImportOptions } from "./claude-import.js";
|
|
|
7
7
|
*
|
|
8
8
|
* Option names and defaults mirror codex's MemoriesToml/MemoriesConfig
|
|
9
9
|
* (codex-rs/config/src/types.rs). Keep them 1:1 so the drift script and
|
|
10
|
-
* manual syncing stay trivial; do not rename for taste. codex_interop
|
|
11
|
-
* claude_import are opencode-facing knobs
|
|
12
|
-
*
|
|
10
|
+
* manual syncing stay trivial; do not rename for taste. codex_interop,
|
|
11
|
+
* claude_import, and home are opencode-facing knobs (Codex has no equivalent
|
|
12
|
+
* of relocating just the memory files away from the host data dir).
|
|
13
13
|
*/
|
|
14
14
|
export interface PluginOptionsState {
|
|
15
15
|
generate_memories: boolean;
|
|
@@ -18,6 +18,8 @@ export interface PluginOptionsState {
|
|
|
18
18
|
disable_on_external_context: boolean;
|
|
19
19
|
extract_model?: string;
|
|
20
20
|
consolidation_model?: string;
|
|
21
|
+
/** Resolved absolute plugin home (parent of memory.db + memories/). Unset = default. */
|
|
22
|
+
home?: string;
|
|
21
23
|
max_raw_memories_for_consolidation: number;
|
|
22
24
|
max_unused_days: number;
|
|
23
25
|
max_rollout_age_days: number;
|
package/dist/src/options.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setConfiguredHome } from "./paths.js";
|
|
1
2
|
const DEFAULT_PLUGIN_OPTIONS = {
|
|
2
3
|
generate_memories: true,
|
|
3
4
|
use_memories: true,
|
|
@@ -19,6 +20,8 @@ export const pluginOptions = {
|
|
|
19
20
|
export function resetPluginOptions() {
|
|
20
21
|
delete pluginOptions.extract_model;
|
|
21
22
|
delete pluginOptions.consolidation_model;
|
|
23
|
+
delete pluginOptions.home;
|
|
24
|
+
setConfiguredHome(undefined);
|
|
22
25
|
Object.assign(pluginOptions, DEFAULT_PLUGIN_OPTIONS, {
|
|
23
26
|
codex_interop: { ...DEFAULT_PLUGIN_OPTIONS.codex_interop },
|
|
24
27
|
claude_import: { ...DEFAULT_PLUGIN_OPTIONS.claude_import },
|
package/dist/src/paths.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
export type MemoryHomeSource = "test" | "option" | "env" | "default";
|
|
2
|
+
/**
|
|
3
|
+
* Memory lives under a dedicated home, mirroring codex's
|
|
4
|
+
* `<codex_home>/memories` (codex-rs `find_codex_home` + `from_codex_home`) but
|
|
5
|
+
* with an opencode-specific default. opencode resolves its data dir as
|
|
6
|
+
* `path.join(xdgData, "opencode")` via the `xdg-basedir` lib
|
|
7
|
+
* (packages/core/src/global.ts), so we reuse the SAME lib to stay byte-identical
|
|
8
|
+
* across platforms and `XDG_DATA_HOME` overrides. opencode does not surface this
|
|
9
|
+
* directory through the plugin API (`/path` gives home/config/state/worktree/
|
|
10
|
+
* directory, not data), so it must be recomputed here.
|
|
11
|
+
*
|
|
12
|
+
* User-facing precedence (first match). `home` / OPENCODE_CODEX_MEMORY_HOME
|
|
13
|
+
* pin this plugin's files and do NOT follow OpenCode's data dir or XDG_DATA_HOME:
|
|
14
|
+
* 1. plugin option `home` — always enforced when set
|
|
15
|
+
* 2. `OPENCODE_CODEX_MEMORY_HOME` — same pin if `home` is unset
|
|
16
|
+
* 3. OpenCode data dir (`$XDG_DATA_HOME/opencode` or `~/.local/share/opencode`)
|
|
17
|
+
*
|
|
18
|
+
* Tests only: `OPENCODE_CODEX_MEMORY_TEST_ROOT` wins over all of the above
|
|
19
|
+
* (write-pipeline sandbox + unit tests). Not a user relocation knob.
|
|
20
|
+
*
|
|
21
|
+
* (1) and (2) exist so a sandbox can mount `memory.db` + `memories/` without
|
|
22
|
+
* the rest of OpenCode's local database. They are not Codex knobs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveHomePath(raw: string): string | null;
|
|
25
|
+
export declare function setConfiguredHome(home: string | undefined): void;
|
|
26
|
+
export declare function dataRoot(): string;
|
|
27
|
+
export declare function memoryHomeSource(): MemoryHomeSource;
|
|
1
28
|
export declare function memoryRoot(): string;
|
|
2
29
|
export declare function memoryDbPath(): string;
|
|
3
30
|
export declare function memorySummaryPath(): string;
|
package/dist/src/paths.js
CHANGED
|
@@ -3,30 +3,77 @@ import os from "os";
|
|
|
3
3
|
import { xdgData } from "xdg-basedir";
|
|
4
4
|
const MEMORY_DIR_NAME = "memories";
|
|
5
5
|
const MEMORY_DB_NAME = "memory.db";
|
|
6
|
-
const
|
|
6
|
+
const TEST_ROOT_ENV = "OPENCODE_CODEX_MEMORY_TEST_ROOT";
|
|
7
|
+
const HOME_ENV = "OPENCODE_CODEX_MEMORY_HOME";
|
|
7
8
|
const OPENCODE_APP_DIR = "opencode";
|
|
9
|
+
/** Plugin-option override of the memory home (parent of memory.db + memories/). */
|
|
10
|
+
let configuredHome;
|
|
8
11
|
/**
|
|
9
|
-
* Memory lives under
|
|
12
|
+
* Memory lives under a dedicated home, mirroring codex's
|
|
10
13
|
* `<codex_home>/memories` (codex-rs `find_codex_home` + `from_codex_home`) but
|
|
11
|
-
* with
|
|
14
|
+
* with an opencode-specific default. opencode resolves its data dir as
|
|
12
15
|
* `path.join(xdgData, "opencode")` via the `xdg-basedir` lib
|
|
13
16
|
* (packages/core/src/global.ts), so we reuse the SAME lib to stay byte-identical
|
|
14
17
|
* across platforms and `XDG_DATA_HOME` overrides. opencode does not surface this
|
|
15
18
|
* directory through the plugin API (`/path` gives home/config/state/worktree/
|
|
16
19
|
* directory, not data), so it must be recomputed here.
|
|
17
20
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* User-facing precedence (first match). `home` / OPENCODE_CODEX_MEMORY_HOME
|
|
22
|
+
* pin this plugin's files and do NOT follow OpenCode's data dir or XDG_DATA_HOME:
|
|
23
|
+
* 1. plugin option `home` — always enforced when set
|
|
24
|
+
* 2. `OPENCODE_CODEX_MEMORY_HOME` — same pin if `home` is unset
|
|
25
|
+
* 3. OpenCode data dir (`$XDG_DATA_HOME/opencode` or `~/.local/share/opencode`)
|
|
26
|
+
*
|
|
27
|
+
* Tests only: `OPENCODE_CODEX_MEMORY_TEST_ROOT` wins over all of the above
|
|
28
|
+
* (write-pipeline sandbox + unit tests). Not a user relocation knob.
|
|
29
|
+
*
|
|
30
|
+
* (1) and (2) exist so a sandbox can mount `memory.db` + `memories/` without
|
|
31
|
+
* the rest of OpenCode's local database. They are not Codex knobs.
|
|
20
32
|
*/
|
|
21
|
-
function
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
33
|
+
export function resolveHomePath(raw) {
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (!trimmed)
|
|
36
|
+
return null;
|
|
37
|
+
let expanded = trimmed;
|
|
38
|
+
if (trimmed === "~")
|
|
39
|
+
expanded = os.homedir();
|
|
40
|
+
else if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
|
|
41
|
+
expanded = path.join(os.homedir(), trimmed.slice(2));
|
|
42
|
+
}
|
|
43
|
+
if (!path.isAbsolute(expanded))
|
|
44
|
+
return null;
|
|
45
|
+
return path.normalize(expanded);
|
|
46
|
+
}
|
|
47
|
+
export function setConfiguredHome(home) {
|
|
48
|
+
configuredHome = home;
|
|
49
|
+
}
|
|
50
|
+
export function dataRoot() {
|
|
51
|
+
const testRoot = process.env[TEST_ROOT_ENV];
|
|
52
|
+
if (testRoot)
|
|
53
|
+
return testRoot;
|
|
54
|
+
if (configuredHome)
|
|
55
|
+
return configuredHome;
|
|
56
|
+
const envHome = process.env[HOME_ENV];
|
|
57
|
+
if (envHome) {
|
|
58
|
+
const resolved = resolveHomePath(envHome);
|
|
59
|
+
if (resolved)
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
25
62
|
// xdgData = XDG_DATA_HOME || ~/.local/share (identical on every platform).
|
|
26
63
|
// The `??` mirrors xdg-basedir's own guard for a missing home directory.
|
|
27
64
|
const base = xdgData ?? path.join(os.homedir(), ".local", "share");
|
|
28
65
|
return path.join(base, OPENCODE_APP_DIR);
|
|
29
66
|
}
|
|
67
|
+
export function memoryHomeSource() {
|
|
68
|
+
if (process.env[TEST_ROOT_ENV])
|
|
69
|
+
return "test";
|
|
70
|
+
if (configuredHome)
|
|
71
|
+
return "option";
|
|
72
|
+
const envHome = process.env[HOME_ENV];
|
|
73
|
+
if (envHome && resolveHomePath(envHome))
|
|
74
|
+
return "env";
|
|
75
|
+
return "default";
|
|
76
|
+
}
|
|
30
77
|
export function memoryRoot() {
|
|
31
78
|
return path.join(dataRoot(), MEMORY_DIR_NAME);
|
|
32
79
|
}
|
package/dist/src/v2/plugin.js
CHANGED
|
@@ -25,12 +25,14 @@ import { runPhase1 } from "../phase1.js";
|
|
|
25
25
|
import { runPhase2 } from "../phase2.js";
|
|
26
26
|
import { setPluginInput, setSubSessionDirectory, cleanupOldSubSessions, isMemorySubSession, abortActiveSubSessions, } from "../llm.js";
|
|
27
27
|
import { pluginOptions, clearConfigWarnings, resetPluginOptions } from "../options.js";
|
|
28
|
+
import { memoryDbPath } from "../paths.js";
|
|
29
|
+
import { closeDb } from "../db.js";
|
|
28
30
|
import { beginPluginShutdown, isPluginShuttingDown, resetPluginLifecycle } from "../lifecycle.js";
|
|
29
31
|
import { hostMcpStatus } from "../host-client.js";
|
|
30
32
|
import { recordDiagnostic } from "../diagnostics.js";
|
|
31
33
|
import { resetAgentHealth } from "../agent-health.js";
|
|
32
34
|
import { applyPluginOptions, handleSessionDeleted } from "../index.js";
|
|
33
|
-
import { setV2Context, buildV1ClientShim, } from "./shim.js";
|
|
35
|
+
import { setV2Context, buildV1ClientShim, rememberV2Session, } from "./shim.js";
|
|
34
36
|
import { ensureV2Agents } from "./agents.js";
|
|
35
37
|
import { buildV2Tools } from "./tools.js";
|
|
36
38
|
import { MemoryStatusRpc } from "./status-rpc.js";
|
|
@@ -178,7 +180,8 @@ async function classifyExternalContextTool(toolName) {
|
|
|
178
180
|
}
|
|
179
181
|
return false;
|
|
180
182
|
}
|
|
181
|
-
function stampAndPump(sid) {
|
|
183
|
+
function stampAndPump(sid, directory) {
|
|
184
|
+
rememberV2Session(sid, directory ?? null);
|
|
182
185
|
try {
|
|
183
186
|
getStore().stampMemoryModeIfAbsent(sid, pluginOptions.generate_memories ? "enabled" : "disabled");
|
|
184
187
|
}
|
|
@@ -277,8 +280,12 @@ export async function setup(ctx) {
|
|
|
277
280
|
clearConfigWarnings();
|
|
278
281
|
if (ctx.options)
|
|
279
282
|
applyPluginOptions(ctx.options);
|
|
280
|
-
else
|
|
283
|
+
else {
|
|
284
|
+
const previousDb = memoryDbPath();
|
|
281
285
|
resetPluginOptions();
|
|
286
|
+
if (memoryDbPath() !== previousDb)
|
|
287
|
+
closeDb();
|
|
288
|
+
}
|
|
282
289
|
await ensureV2Agents(ctx);
|
|
283
290
|
const statusRegistration = await ctx.rpc.register(MemoryStatusRpc, {
|
|
284
291
|
status: async (input) => {
|
|
@@ -336,7 +343,7 @@ export async function setup(ctx) {
|
|
|
336
343
|
return;
|
|
337
344
|
if (!markV2TurnSeen(sid))
|
|
338
345
|
return;
|
|
339
|
-
stampAndPump(sid);
|
|
346
|
+
stampAndPump(sid, ctx.location?.directory);
|
|
340
347
|
}
|
|
341
348
|
catch (err) {
|
|
342
349
|
console.error("[opencode-codex-memory] v2 prompt hook error:", err);
|
|
@@ -436,6 +443,7 @@ export async function setup(ctx) {
|
|
|
436
443
|
if (e.type === "session.execution.succeeded" || e.type === "session.execution.ended") {
|
|
437
444
|
const sid = sessionIdFromV2Event(data);
|
|
438
445
|
if (sid && !isMemorySubSession(sid)) {
|
|
446
|
+
rememberV2Session(sid, ctx.location?.directory);
|
|
439
447
|
trackBackgroundTask(triggerPhase1(sid));
|
|
440
448
|
}
|
|
441
449
|
}
|
package/dist/src/v2/service.d.ts
CHANGED
|
@@ -64,10 +64,14 @@ export declare function serviceHeaders(endpoint: V2ServiceEndpoint): Record<stri
|
|
|
64
64
|
export declare function parseReadyStatus(body: unknown): V2ServiceStatus | null;
|
|
65
65
|
export declare function readRegisteredEndpoint(file?: string): Promise<V2ServiceEndpoint | undefined>;
|
|
66
66
|
export declare function fetchServiceStatus(endpoint: V2ServiceEndpoint, headers: Record<string, string> | undefined, signal?: AbortSignal): Promise<V2ServiceStatus>;
|
|
67
|
+
/** Local loopback only — IDE `serve --port 0` shares opencode.db with `--service`. */
|
|
68
|
+
export declare function isLoopbackEndpointUrl(url: string): boolean;
|
|
67
69
|
/**
|
|
68
70
|
* Find a ready, registered OpenCode service without starting or replacing one.
|
|
69
|
-
* A missing service is a normal unavailable result
|
|
70
|
-
*
|
|
71
|
+
* A missing service is a normal unavailable result. A PID mismatch on a
|
|
72
|
+
* non-loopback URL is a safety failure (would operate on another host).
|
|
73
|
+
* Loopback PID mismatch is the IDE isolated-serve case: same machine, shared
|
|
74
|
+
* session database, so global list/get/messages still go through that service.
|
|
71
75
|
*/
|
|
72
76
|
export declare function discoverOwnService(dependencies?: V2ServiceDependencies, timeoutMs?: number): Promise<{
|
|
73
77
|
endpoint: V2ServiceEndpoint;
|
package/dist/src/v2/service.js
CHANGED
|
@@ -154,10 +154,22 @@ async function probeEndpoint(deps, endpoint, client, signal) {
|
|
|
154
154
|
}
|
|
155
155
|
return fetchServiceStatus(endpoint, deps.service.headers(endpoint), signal);
|
|
156
156
|
}
|
|
157
|
+
/** Local loopback only — IDE `serve --port 0` shares opencode.db with `--service`. */
|
|
158
|
+
export function isLoopbackEndpointUrl(url) {
|
|
159
|
+
try {
|
|
160
|
+
const hostname = new URL(url).hostname;
|
|
161
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
157
167
|
/**
|
|
158
168
|
* Find a ready, registered OpenCode service without starting or replacing one.
|
|
159
|
-
* A missing service is a normal unavailable result
|
|
160
|
-
*
|
|
169
|
+
* A missing service is a normal unavailable result. A PID mismatch on a
|
|
170
|
+
* non-loopback URL is a safety failure (would operate on another host).
|
|
171
|
+
* Loopback PID mismatch is the IDE isolated-serve case: same machine, shared
|
|
172
|
+
* session database, so global list/get/messages still go through that service.
|
|
161
173
|
*/
|
|
162
174
|
export async function discoverOwnService(dependencies, timeoutMs = SERVICE_REQUEST_TIMEOUT_MS) {
|
|
163
175
|
const deps = dependencies ?? testDependencies ?? productionDependencies();
|
|
@@ -168,7 +180,10 @@ export async function discoverOwnService(dependencies, timeoutMs = SERVICE_REQUE
|
|
|
168
180
|
const controller = new AbortController();
|
|
169
181
|
const health = await withServiceTimeout(probeEndpoint(deps, endpoint, client, controller.signal), timeoutMs, controller);
|
|
170
182
|
if (health.pid !== process.pid) {
|
|
171
|
-
|
|
183
|
+
if (!isLoopbackEndpointUrl(endpoint.url)) {
|
|
184
|
+
throw new Error(`registered OpenCode service PID ${String(health.pid)} does not match plugin host PID ${process.pid}`);
|
|
185
|
+
}
|
|
186
|
+
console.warn(`[opencode-codex-memory] registered OpenCode service PID ${String(health.pid)} is a different local process than plugin host PID ${process.pid}; using it for global session list`);
|
|
172
187
|
}
|
|
173
188
|
return { endpoint, client, health };
|
|
174
189
|
}
|
package/dist/src/v2/shim.d.ts
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* run byte-identical) by presenting a V1-shaped client façade backed by the
|
|
6
6
|
* V2 plugin context. Only genuinely missing V2 surfaces are adapted:
|
|
7
7
|
*
|
|
8
|
-
* - session list/discovery → the
|
|
9
|
-
*
|
|
8
|
+
* - session list/discovery → ctx.session.list when the host exposes it,
|
|
9
|
+
* else the authenticated public client for THIS process's registered
|
|
10
|
+
* service. A PID mismatch (IDE `serve --port 0` vs `serve --service`)
|
|
11
|
+
* does not list another host; it falls back to sessions this process
|
|
12
|
+
* has observed.
|
|
10
13
|
* - session.prompt agent/system/model/format/variant → V2 create-time
|
|
11
14
|
* agent/model (via switchAgent/switchModel) + generate.text for the
|
|
12
15
|
* json_schema extraction path (V2 prompts carry text only).
|
|
@@ -27,6 +30,13 @@ export declare function setV2Context(ctx: V2Context | null): void;
|
|
|
27
30
|
export declare function isReleasedSubSession(id: string): boolean;
|
|
28
31
|
/** Stable synthetic id for extraction helpers (see create below). */
|
|
29
32
|
export declare const EXTRACT_STUB_SESSION_ID = "codex-memory-extract-stub";
|
|
33
|
+
/**
|
|
34
|
+
* Isolated OpenCode 2 serves (IntelliJ/desktop `serve --port 0`) are not the
|
|
35
|
+
* registered `--service` process, so global session.list is unavailable.
|
|
36
|
+
* Remember sessions this process has actually seen so phase 1 can still
|
|
37
|
+
* extract them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function rememberV2Session(id: string, directory?: string | null, title?: string): void;
|
|
30
40
|
/** Test seam. */
|
|
31
41
|
export declare function resetV2ShimStateForTest(): void;
|
|
32
42
|
/**
|
package/dist/src/v2/shim.js
CHANGED
|
@@ -59,9 +59,58 @@ export function isReleasedSubSession(id) {
|
|
|
59
59
|
}
|
|
60
60
|
/** Stable synthetic id for extraction helpers (see create below). */
|
|
61
61
|
export const EXTRACT_STUB_SESSION_ID = "codex-memory-extract-stub";
|
|
62
|
+
const OBSERVED_CAP = 5000;
|
|
63
|
+
const observedSessions = new Map();
|
|
64
|
+
/**
|
|
65
|
+
* Isolated OpenCode 2 serves (IntelliJ/desktop `serve --port 0`) are not the
|
|
66
|
+
* registered `--service` process, so global session.list is unavailable.
|
|
67
|
+
* Remember sessions this process has actually seen so phase 1 can still
|
|
68
|
+
* extract them.
|
|
69
|
+
*/
|
|
70
|
+
export function rememberV2Session(id, directory, title) {
|
|
71
|
+
if (!id || id === EXTRACT_STUB_SESSION_ID)
|
|
72
|
+
return;
|
|
73
|
+
if (releasedSubSessions.has(id))
|
|
74
|
+
return;
|
|
75
|
+
observedSessions.delete(id);
|
|
76
|
+
observedSessions.set(id, {
|
|
77
|
+
updated_at: Date.now(),
|
|
78
|
+
directory: directory ?? null,
|
|
79
|
+
title: typeof title === "string" ? title : "",
|
|
80
|
+
});
|
|
81
|
+
while (observedSessions.size > OBSERVED_CAP) {
|
|
82
|
+
const oldest = observedSessions.keys().next().value;
|
|
83
|
+
if (oldest === undefined)
|
|
84
|
+
break;
|
|
85
|
+
observedSessions.delete(oldest);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function listObservedSessions(limit, cursor, search) {
|
|
89
|
+
const timestampCursor = typeof cursor === "number" ? cursor : undefined;
|
|
90
|
+
const needle = typeof search === "string" && search.length > 0 ? search.toLowerCase() : undefined;
|
|
91
|
+
const rows = [...observedSessions.entries()].sort((a, b) => b[1].updated_at - a[1].updated_at);
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const [id, rec] of rows) {
|
|
94
|
+
if (timestampCursor !== undefined && rec.updated_at >= timestampCursor)
|
|
95
|
+
continue;
|
|
96
|
+
if (needle && !`${id}\n${rec.title}`.toLowerCase().includes(needle))
|
|
97
|
+
continue;
|
|
98
|
+
out.push({
|
|
99
|
+
id,
|
|
100
|
+
parentID: null,
|
|
101
|
+
...(rec.title ? { title: rec.title } : {}),
|
|
102
|
+
directory: rec.directory,
|
|
103
|
+
time: { updated: rec.updated_at },
|
|
104
|
+
});
|
|
105
|
+
if (out.length >= limit)
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
62
110
|
/** Test seam. */
|
|
63
111
|
export function resetV2ShimStateForTest() {
|
|
64
112
|
releasedSubSessions.clear();
|
|
113
|
+
observedSessions.clear();
|
|
65
114
|
invalidateOwnService();
|
|
66
115
|
}
|
|
67
116
|
// ---------------------------------------------------------------------------
|
|
@@ -367,10 +416,13 @@ export function buildV1ClientShim() {
|
|
|
367
416
|
if (typeof localList === "function") {
|
|
368
417
|
return paginateSessionList((input) => localList(input), limit, cursor, search);
|
|
369
418
|
}
|
|
370
|
-
const client = await
|
|
371
|
-
if (typeof client.session.list
|
|
419
|
+
const client = await ownServiceClient();
|
|
420
|
+
if (client && typeof client.session.list === "function") {
|
|
421
|
+
return paginateSessionList((input) => client.session.list(input), limit, cursor, search);
|
|
422
|
+
}
|
|
423
|
+
if (client)
|
|
372
424
|
throw new Error("registered service does not support session.list");
|
|
373
|
-
return
|
|
425
|
+
return { data: listObservedSessions(limit, cursor, search) };
|
|
374
426
|
}
|
|
375
427
|
const session = {
|
|
376
428
|
create: async (opts) => {
|
|
@@ -408,25 +460,30 @@ export function buildV1ClientShim() {
|
|
|
408
460
|
try {
|
|
409
461
|
if (isReleasedSubSession(opts.path.id))
|
|
410
462
|
throw Object.assign(new Error("SessionNotFound"), { _tag: "SessionNotFoundError" });
|
|
411
|
-
const client = await
|
|
412
|
-
if (typeof client
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
463
|
+
const client = await ownServiceClient();
|
|
464
|
+
if (typeof client?.message?.list === "function") {
|
|
465
|
+
const messages = [];
|
|
466
|
+
const seenCursors = new Set();
|
|
467
|
+
let cursor;
|
|
468
|
+
while (true) {
|
|
469
|
+
const response = await client.message.list(cursor ? { sessionID: opts.path.id, cursor } : { sessionID: opts.path.id, order: "asc" });
|
|
470
|
+
const rows = responseRows(response);
|
|
471
|
+
if (!rows)
|
|
472
|
+
throw new Error("registered service returned an invalid message list");
|
|
473
|
+
messages.push(...rows);
|
|
474
|
+
const next = responseNextCursor(response);
|
|
475
|
+
if (!next || seenCursors.has(next))
|
|
476
|
+
break;
|
|
477
|
+
seenCursors.add(next);
|
|
478
|
+
cursor = next;
|
|
479
|
+
}
|
|
480
|
+
return { data: adaptV2Messages(messages) };
|
|
428
481
|
}
|
|
429
|
-
|
|
482
|
+
const raw = await ctx().session.context({
|
|
483
|
+
sessionID: opts.path.id,
|
|
484
|
+
});
|
|
485
|
+
const rows = Array.isArray(raw) ? raw : responseRows(raw) ?? [];
|
|
486
|
+
return { data: adaptV2Messages(rows) };
|
|
430
487
|
}
|
|
431
488
|
catch (e) {
|
|
432
489
|
return { error: e };
|
|
@@ -463,18 +520,23 @@ export function buildV1ClientShim() {
|
|
|
463
520
|
}
|
|
464
521
|
try {
|
|
465
522
|
const client = await ownServiceClient();
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
523
|
+
if (client?.session?.get) {
|
|
524
|
+
const info = await client.session.get({ sessionID: opts.path.id });
|
|
525
|
+
if (info?.error && isNotFoundError(info.error)) {
|
|
526
|
+
markReleased(opts.path.id);
|
|
527
|
+
return {};
|
|
528
|
+
}
|
|
529
|
+
const data = und(info);
|
|
530
|
+
if (data && typeof data === "object") {
|
|
531
|
+
return { error: shutdownError ?? new Error("session still exists after interrupt") };
|
|
532
|
+
}
|
|
475
533
|
return { error: shutdownError ?? new Error("session still exists after interrupt") };
|
|
476
534
|
}
|
|
477
|
-
|
|
535
|
+
// Isolated serve: no session.remove on plugin ctx. interrupt+wait already
|
|
536
|
+
// finished, so the helper is idle. Holding the phase-2 lease until it
|
|
537
|
+
// expires would block consolidation for an hour.
|
|
538
|
+
markReleased(opts.path.id);
|
|
539
|
+
return {};
|
|
478
540
|
}
|
|
479
541
|
catch (e) {
|
|
480
542
|
if (isNotFoundError(e)) {
|
|
@@ -489,8 +551,19 @@ export function buildV1ClientShim() {
|
|
|
489
551
|
if (isReleasedSubSession(opts.path.id)) {
|
|
490
552
|
return { response: { status: 404 }, error: { _tag: "SessionNotFoundError" } };
|
|
491
553
|
}
|
|
492
|
-
const client = await
|
|
493
|
-
|
|
554
|
+
const client = await ownServiceClient();
|
|
555
|
+
if (client?.session?.get) {
|
|
556
|
+
try {
|
|
557
|
+
const info = und(await client.session.get({ sessionID: opts.path.id }));
|
|
558
|
+
return { data: info };
|
|
559
|
+
}
|
|
560
|
+
catch (e) {
|
|
561
|
+
if (isNotFoundError(e))
|
|
562
|
+
return { response: { status: 404 }, error: e };
|
|
563
|
+
return { error: e };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const info = und(await ctx().session.get({ sessionID: opts.path.id }));
|
|
494
567
|
return { data: info };
|
|
495
568
|
}
|
|
496
569
|
catch (e) {
|
package/dist/tools/control.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
import { memoryRoot, memorySummaryPath } from "../src/paths.js";
|
|
4
|
+
import { memoryRoot, memorySummaryPath, dataRoot, memoryHomeSource } from "../src/paths.js";
|
|
5
5
|
import { MemoryStore } from "../src/store.js";
|
|
6
6
|
import { invalidateCache } from "../src/source.js";
|
|
7
7
|
import { estimateTokens } from "../src/token.js";
|
|
@@ -42,6 +42,18 @@ function wipeMemoriesDir() {
|
|
|
42
42
|
fs.unlinkSync(abs);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
function homeSourceLabel(source) {
|
|
46
|
+
switch (source) {
|
|
47
|
+
case "test":
|
|
48
|
+
return "test root";
|
|
49
|
+
case "option":
|
|
50
|
+
return "option";
|
|
51
|
+
case "env":
|
|
52
|
+
return "OPENCODE_CODEX_MEMORY_HOME";
|
|
53
|
+
case "default":
|
|
54
|
+
return "OpenCode data dir";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
45
57
|
/**
|
|
46
58
|
* Renders the effective (post-parse, post-clamp) plugin options plus any
|
|
47
59
|
* problems recorded while applying them. The plugin never hard-fails on bad
|
|
@@ -65,6 +77,7 @@ function renderEffectiveConfig() {
|
|
|
65
77
|
` max_rollout_age_days: ${o.max_rollout_age_days}`,
|
|
66
78
|
` max_rollouts_per_startup: ${o.max_rollouts_per_startup}`,
|
|
67
79
|
` min_rollout_idle_hours: ${o.min_rollout_idle_hours}`,
|
|
80
|
+
` home: ${dataRoot()} (${homeSourceLabel(memoryHomeSource())})`,
|
|
68
81
|
];
|
|
69
82
|
const ci = o.codex_interop;
|
|
70
83
|
if (!ci.import && !ci.export) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Persistent memory plugin for opencode — ports codex's two-phase memory system (extraction → consolidation → injection → citation feedback)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|