opencode-codex-memory 0.4.5 → 0.4.7
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 +3 -3
- package/dist/src/capture.d.ts +5 -11
- package/dist/src/capture.js +43 -50
- package/dist/src/llm.js +17 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ If you want the mental model before the details, jump to
|
|
|
52
52
|
|
|
53
53
|
```json
|
|
54
54
|
{
|
|
55
|
-
"plugin": ["opencode-codex-memory@0.4.
|
|
55
|
+
"plugin": ["opencode-codex-memory@0.4.7"]
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -239,7 +239,7 @@ To set options, turn the plugin entry into a `[name, options]` pair:
|
|
|
239
239
|
```json
|
|
240
240
|
{
|
|
241
241
|
"plugin": [
|
|
242
|
-
["opencode-codex-memory@0.4.
|
|
242
|
+
["opencode-codex-memory@0.4.7", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
|
|
243
243
|
]
|
|
244
244
|
}
|
|
245
245
|
```
|
|
@@ -298,7 +298,7 @@ directions:
|
|
|
298
298
|
```json
|
|
299
299
|
{
|
|
300
300
|
"plugin": [
|
|
301
|
-
["opencode-codex-memory@0.4.
|
|
301
|
+
["opencode-codex-memory@0.4.7", { "codex_interop": { "import": true, "export": true } }]
|
|
302
302
|
]
|
|
303
303
|
}
|
|
304
304
|
```
|
package/dist/src/capture.d.ts
CHANGED
|
@@ -5,17 +5,11 @@ export interface SessionRow {
|
|
|
5
5
|
directory: string | null;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Global session discovery through the official API:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* lifetime, and the whole pass is rate-limited (30s min interval).
|
|
14
|
-
*
|
|
15
|
-
* Fail-safe at two levels: a failed project.list skips the pass ([]), a
|
|
16
|
-
* failed per-project session.list skips that project — neither claims or
|
|
17
|
-
* finalizes any job. Transcript loading must NOT be fail-safe — see
|
|
18
|
-
* loadTranscript.
|
|
8
|
+
* Global session discovery through the official API:
|
|
9
|
+
* `GET /experimental/session?roots=true` (Session.listGlobal) — one call across
|
|
10
|
+
* all projects, sorted by most-recently-updated. Available since opencode
|
|
11
|
+
* 1.17.x. Fail-safe: any error skips the pass ([]); never finalizes a job.
|
|
12
|
+
* Transcript loading must NOT be fail-safe — see loadTranscript.
|
|
19
13
|
*/
|
|
20
14
|
export declare function listRecentSessions(limit?: number): Promise<SessionRow[]>;
|
|
21
15
|
export interface TranscriptMessage {
|
package/dist/src/capture.js
CHANGED
|
@@ -16,64 +16,57 @@ async function withTimeout(promise, ms, label) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
* Hey-api transport on PluginInput.client. The V1 SDK has no
|
|
20
|
+
* `experimental.*` namespace, but the host-built client already carries
|
|
21
|
+
* baseUrl + auth headers; `_client.get` is the supported escape hatch for
|
|
22
|
+
* routes the generated surface lags on (same pattern as the scope/roots casts
|
|
23
|
+
* we used to need on session.list).
|
|
24
|
+
*/
|
|
25
|
+
function pluginHttp() {
|
|
26
|
+
const http = getPluginInput()?.client?._client;
|
|
27
|
+
if (!http || typeof http.get !== "function")
|
|
28
|
+
return null;
|
|
29
|
+
return http;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Global session discovery through the official API:
|
|
33
|
+
* `GET /experimental/session?roots=true` (Session.listGlobal) — one call across
|
|
34
|
+
* all projects, sorted by most-recently-updated. Available since opencode
|
|
35
|
+
* 1.17.x. Fail-safe: any error skips the pass ([]); never finalizes a job.
|
|
36
|
+
* Transcript loading must NOT be fail-safe — see loadTranscript.
|
|
30
37
|
*/
|
|
31
38
|
export async function listRecentSessions(limit = SCAN_LIMIT) {
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
39
|
+
const http = pluginHttp();
|
|
40
|
+
if (!http)
|
|
34
41
|
return [];
|
|
35
|
-
let projects;
|
|
36
42
|
try {
|
|
37
|
-
const res = await withTimeout(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const res = await withTimeout(http.get({
|
|
44
|
+
url: "/experimental/session",
|
|
45
|
+
query: { roots: true, limit },
|
|
46
|
+
}), API_TIMEOUT_MS, "experimental.session.list");
|
|
47
|
+
if (!res || res.error || !Array.isArray(res.data)) {
|
|
48
|
+
throw new Error(`experimental.session.list failed: ${JSON.stringify(res?.error ?? {})}`);
|
|
49
|
+
}
|
|
50
|
+
const all = [];
|
|
51
|
+
for (const s of res.data) {
|
|
52
|
+
// Top-level sessions only: task-tool children are summarized into their
|
|
53
|
+
// parent, and the plugin's own sub-sessions must never be memorized
|
|
54
|
+
// (roots=true drops children server-side; keep both belts).
|
|
55
|
+
if (!s?.id || s.parentID)
|
|
56
|
+
continue;
|
|
57
|
+
if (s.title && s.title.startsWith("codex-memory-"))
|
|
58
|
+
continue;
|
|
59
|
+
all.push({ id: s.id, updated_at: s.time?.updated ?? 0, directory: s.directory ?? null });
|
|
60
|
+
}
|
|
61
|
+
// Server already orders by time_updated DESC; re-sort so a lagging host
|
|
62
|
+
// cannot invert eligibility order.
|
|
63
|
+
all.sort((a, b) => b.updated_at - a.updated_at);
|
|
64
|
+
return all.slice(0, limit);
|
|
41
65
|
}
|
|
42
66
|
catch (err) {
|
|
43
|
-
console.warn("[opencode-codex-memory]
|
|
67
|
+
console.warn("[opencode-codex-memory] session discovery failed; skipping pass:", err);
|
|
44
68
|
return [];
|
|
45
69
|
}
|
|
46
|
-
const all = [];
|
|
47
|
-
for (const project of projects) {
|
|
48
|
-
if (!project?.worktree)
|
|
49
|
-
continue;
|
|
50
|
-
try {
|
|
51
|
-
const res = await withTimeout(client.session.list({
|
|
52
|
-
// scope/roots/limit are in the server's ListQuery (accepted since
|
|
53
|
-
// opencode 1.14.30, well under our 1.18 floor); the pinned SDK types
|
|
54
|
-
// still omit them (SessionListData.query is just { directory } as of
|
|
55
|
-
// 1.18.1), hence the cast at the call site.
|
|
56
|
-
query: { directory: project.worktree, scope: "project", roots: true, limit },
|
|
57
|
-
}), API_TIMEOUT_MS, "session.list");
|
|
58
|
-
if (!res || res.error || !Array.isArray(res.data))
|
|
59
|
-
throw new Error(JSON.stringify(res?.error ?? {}));
|
|
60
|
-
for (const s of res.data) {
|
|
61
|
-
// Top-level sessions only: task-tool children are summarized into
|
|
62
|
-
// their parent, and the plugin's own sub-sessions must never be
|
|
63
|
-
// memorized (roots=true drops children server-side; keep both belts).
|
|
64
|
-
if (!s?.id || s.parentID)
|
|
65
|
-
continue;
|
|
66
|
-
if (s.title && s.title.startsWith("codex-memory-"))
|
|
67
|
-
continue;
|
|
68
|
-
all.push({ id: s.id, updated_at: s.time?.updated ?? 0, directory: s.directory ?? null });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
console.warn(`[opencode-codex-memory] session.list failed for ${project.worktree}; skipping project:`, err);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
all.sort((a, b) => b.updated_at - a.updated_at);
|
|
76
|
-
return all.slice(0, limit);
|
|
77
70
|
}
|
|
78
71
|
/** Official transcript surface: GET /session/{id}/message via the plugin's authenticated client. */
|
|
79
72
|
async function fetchMessagesViaApi(sessionId) {
|
package/dist/src/llm.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { memoryRoot } from "./paths.js";
|
|
3
4
|
let inputRef = null;
|
|
4
5
|
export function setPluginInput(input) {
|
|
5
6
|
inputRef = input;
|
|
@@ -20,11 +21,27 @@ const SUBSESSION_DELETE_TIMEOUT_MS = 10_000;
|
|
|
20
21
|
export function isMemorySubSession(sessionId) {
|
|
21
22
|
return activeSubSessions.has(sessionId);
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Host directory for memory sub-sessions. Must exist: OpenCode resolves it in
|
|
26
|
+
* SystemPrompt.environment and fails the turn with UnknownError/ENOENT when
|
|
27
|
+
* missing. Prefer the memory workspace itself — global, always ours, already
|
|
28
|
+
* granted to `memorize` via external_directory, and independent of whatever
|
|
29
|
+
* (possibly deleted) project PluginInput.directory points at.
|
|
30
|
+
*/
|
|
31
|
+
function resolveSubSessionDirectory() {
|
|
32
|
+
const root = memoryRoot();
|
|
33
|
+
fs.mkdirSync(root, { recursive: true });
|
|
34
|
+
return root;
|
|
35
|
+
}
|
|
23
36
|
async function createSession(agent, title) {
|
|
24
37
|
const input = getPluginInput();
|
|
25
38
|
if (!input)
|
|
26
39
|
throw new Error("plugin input not initialized");
|
|
40
|
+
const directory = resolveSubSessionDirectory();
|
|
27
41
|
const res = await input.client.session.create({
|
|
42
|
+
// directory is a query param (not body); without it the client inherits
|
|
43
|
+
// PluginInput.directory, which may be a deleted project path.
|
|
44
|
+
query: { directory },
|
|
28
45
|
body: {
|
|
29
46
|
title: title ?? `codex-memory-${agent}`,
|
|
30
47
|
metadata: { [SUBSESSION_METADATA_KEY]: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
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",
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"dev": "bun --watch src/index.ts",
|
|
32
32
|
"build": "tsc && rm -rf dist/src/templates && cp -R src/templates dist/src/templates && cp opencode.json dist/opencode.json",
|
|
33
33
|
"smoke": "bun scripts/smoke.ts",
|
|
34
|
+
"contract": "bun scripts/check-opencode-contract.ts",
|
|
35
|
+
"live:read": "bun scripts/live-readpath.ts",
|
|
36
|
+
"live:e2e": "bun scripts/live-e2e.ts",
|
|
34
37
|
"prepack": "npm run build && npm run smoke",
|
|
35
38
|
"test": "bun test",
|
|
36
39
|
"typecheck": "tsc --noEmit"
|