opencode-codex-memory 0.7.4 → 0.7.6
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/citation-overlay.d.ts +1 -1
- package/dist/src/v2/citation-overlay.js +8 -3
- package/dist/src/v2/plugin.js +7 -1
- 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.6"]
|
|
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.6" }],
|
|
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.6", { "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.6", { "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.6", "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.6",
|
|
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.6", { "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.6",
|
|
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
|
}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* the TUI renders fenced `memory-citation` blocks, so the V2 context hook
|
|
4
4
|
* overlays this section onto the shared inject prompt.
|
|
5
5
|
*/
|
|
6
|
-
export declare const V2_CITATION_INSTRUCTIONS = "Memory citation requirements:\n\n- If ANY relevant memory files were used: append exactly one fenced code\n block with the language tag `memory-citation` as the VERY LAST content of\n the final reply.
|
|
6
|
+
export declare const V2_CITATION_INSTRUCTIONS = "Memory citation requirements:\n\n- If ANY relevant memory files were used: append exactly one fenced code\n block with the language tag `memory-citation` as the VERY LAST content of\n the final reply. Answer first, then a blank line, then the block. The\n opening fence must be on its own line at column 0 \u2014 never glue it to the\n last sentence, or markdown clients treat the block as paragraph text and\n collapse its newlines. The host renders this block natively.\n- Use this exact structure for programmatic parsing (blank line before the\n fence is required):\n````\nanswer text ends here.\n\n```memory-citation\nMEMORY.md:234-236|note=build command for the api service\nrollout_summaries/2026-02-17T21-23-02-ln3m-example.md:10-12|note=weekly report format\nsessions: ses_abc123 ses_def456\n```\n````\n- Do not wrap it in `<memory-citation>` XML tags; the fenced form replaces\n that older format.\n- Citation entry lines are for rendering:\n - one citation entry per line\n - format: `<file>:<line_start>-<line_end>|note=<how memory was used>`\n - use file paths relative to the memory base path (for example, `MEMORY.md`,\n `rollout_summaries/...`, `skills/...`)\n - only cite files actually used under the memory base path (do not cite\n workspace files as memory citations)\n - if you used `MEMORY.md` and then a rollout summary/skill file, cite both\n - list entries in order of importance (most important first)\n - `note` should be short, single-line, and use simple characters only (avoid\n unusual symbols, no newlines)\n- The final `sessions:` line is for us to track which past sessions you find\n useful:\n - one line, space-separated session ids after `sessions:`\n - session ids look like `ses_...` and appear in rollout summary files and\n MEMORY.md as `session_id:`\n - include unique ids only; do not repeat ids\n - omit the `sessions:` line if no session ids are available\n - do not include file paths or notes on this line\n - for every citation entry, try to find and cite the corresponding session id\n- Never include memory citations inside pull-request messages.\n- Never cite blank lines; double-check ranges.\n\n";
|
|
7
7
|
export declare function overlayV2CitationInstructions(prompt: string): string;
|
|
@@ -7,10 +7,15 @@ export const V2_CITATION_INSTRUCTIONS = `Memory citation requirements:
|
|
|
7
7
|
|
|
8
8
|
- If ANY relevant memory files were used: append exactly one fenced code
|
|
9
9
|
block with the language tag \`memory-citation\` as the VERY LAST content of
|
|
10
|
-
the final reply.
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
the final reply. Answer first, then a blank line, then the block. The
|
|
11
|
+
opening fence must be on its own line at column 0 — never glue it to the
|
|
12
|
+
last sentence, or markdown clients treat the block as paragraph text and
|
|
13
|
+
collapse its newlines. The host renders this block natively.
|
|
14
|
+
- Use this exact structure for programmatic parsing (blank line before the
|
|
15
|
+
fence is required):
|
|
13
16
|
\`\`\`\`
|
|
17
|
+
answer text ends here.
|
|
18
|
+
|
|
14
19
|
\`\`\`memory-citation
|
|
15
20
|
MEMORY.md:234-236|note=build command for the api service
|
|
16
21
|
rollout_summaries/2026-02-17T21-23-02-ln3m-example.md:10-12|note=weekly report format
|
package/dist/src/v2/plugin.js
CHANGED
|
@@ -25,6 +25,8 @@ 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";
|
|
@@ -278,8 +280,12 @@ export async function setup(ctx) {
|
|
|
278
280
|
clearConfigWarnings();
|
|
279
281
|
if (ctx.options)
|
|
280
282
|
applyPluginOptions(ctx.options);
|
|
281
|
-
else
|
|
283
|
+
else {
|
|
284
|
+
const previousDb = memoryDbPath();
|
|
282
285
|
resetPluginOptions();
|
|
286
|
+
if (memoryDbPath() !== previousDb)
|
|
287
|
+
closeDb();
|
|
288
|
+
}
|
|
283
289
|
await ensureV2Agents(ctx);
|
|
284
290
|
const statusRegistration = await ctx.rpc.register(MemoryStatusRpc, {
|
|
285
291
|
status: async (input) => {
|
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.6",
|
|
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",
|