opencode-codex-memory 0.4.2 → 0.4.3
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 +7 -7
- package/dist/src/llm.d.ts +8 -0
- package/dist/src/llm.js +44 -4
- package/dist/src/phase2.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,11 +48,11 @@ If you want the mental model before the details, jump to
|
|
|
48
48
|
|
|
49
49
|
1. Add the plugin to your `~/.config/opencode/opencode.json`:
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"plugin": ["opencode-codex-memory@0.4.
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"plugin": ["opencode-codex-memory@0.4.3"]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
56
|
|
|
57
57
|
**Pin the version** (here and for any OpenCode plugin). OpenCode installs a
|
|
58
58
|
plugin spec once into its package cache and never re-resolves it, so a bare
|
|
@@ -237,7 +237,7 @@ To set options, turn the plugin entry into a `[name, options]` pair:
|
|
|
237
237
|
```json
|
|
238
238
|
{
|
|
239
239
|
"plugin": [
|
|
240
|
-
["opencode-codex-memory@0.4.
|
|
240
|
+
["opencode-codex-memory@0.4.3", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
|
|
241
241
|
]
|
|
242
242
|
}
|
|
243
243
|
```
|
|
@@ -296,7 +296,7 @@ directions:
|
|
|
296
296
|
```json
|
|
297
297
|
{
|
|
298
298
|
"plugin": [
|
|
299
|
-
["opencode-codex-memory@0.4.
|
|
299
|
+
["opencode-codex-memory@0.4.3", { "codex_interop": { "import": true, "export": true } }]
|
|
300
300
|
]
|
|
301
301
|
}
|
|
302
302
|
```
|
package/dist/src/llm.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ export declare function isMemorySubSession(sessionId: string): boolean;
|
|
|
15
15
|
export declare class SubagentTimeoutError extends Error {
|
|
16
16
|
constructor(timeoutMs: number);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when a sub-agent session could not be closed. Codex treats a failed
|
|
20
|
+
* consolidation-agent shutdown as "the agent may still be alive", so the caller
|
|
21
|
+
* must keep its job lease instead of completing the job (phase2.rs).
|
|
22
|
+
*/
|
|
23
|
+
export declare class SubagentShutdownError extends Error {
|
|
24
|
+
constructor(sessionId: string);
|
|
25
|
+
}
|
|
18
26
|
export interface ExtractOptions {
|
|
19
27
|
cwd?: string;
|
|
20
28
|
model?: string;
|
package/dist/src/llm.js
CHANGED
|
@@ -81,6 +81,17 @@ export class SubagentTimeoutError extends Error {
|
|
|
81
81
|
this.name = "SubagentTimeoutError";
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Thrown when a sub-agent session could not be closed. Codex treats a failed
|
|
86
|
+
* consolidation-agent shutdown as "the agent may still be alive", so the caller
|
|
87
|
+
* must keep its job lease instead of completing the job (phase2.rs).
|
|
88
|
+
*/
|
|
89
|
+
export class SubagentShutdownError extends Error {
|
|
90
|
+
constructor(sessionId) {
|
|
91
|
+
super(`failed to close memory sub-session ${sessionId}`);
|
|
92
|
+
this.name = "SubagentShutdownError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
84
95
|
async function abortSession(sessionId) {
|
|
85
96
|
const input = getPluginInput();
|
|
86
97
|
const session = input?.client?.session;
|
|
@@ -224,6 +235,9 @@ export async function extractViaSubagent(sessionId, transcript, opts = {}) {
|
|
|
224
235
|
return parseExtraction(extractAssistantText(data));
|
|
225
236
|
}
|
|
226
237
|
finally {
|
|
238
|
+
// Fire-and-forget on purpose (unlike consolidation): stage 1 has no codex
|
|
239
|
+
// agent-shutdown step, and memorize-extract has no write tools, so a
|
|
240
|
+
// lingering extract session cannot touch the memory root.
|
|
227
241
|
void deleteSession(subId).catch(() => { });
|
|
228
242
|
}
|
|
229
243
|
}
|
|
@@ -234,15 +248,28 @@ const CONSOLIDATION_TIMEOUT_MS = 3600_000;
|
|
|
234
248
|
export async function consolidateViaSubagent(memoryRoot, diffFileName, model) {
|
|
235
249
|
const agent = "memorize";
|
|
236
250
|
const subId = await createSession(agent, "codex-memory-consolidate");
|
|
251
|
+
let promptError;
|
|
252
|
+
let promptFailed = false;
|
|
237
253
|
try {
|
|
238
254
|
const prompt = buildConsolidationPrompt(memoryRoot, diffFileName);
|
|
239
255
|
// consolidation_model option > opencode model (main) > session default.
|
|
240
256
|
const resolved = model ?? (await getConfigModels()).model;
|
|
241
257
|
await promptSession(subId, prompt, agent, { model: resolved, timeoutMs: CONSOLIDATION_TIMEOUT_MS });
|
|
242
258
|
}
|
|
243
|
-
|
|
244
|
-
|
|
259
|
+
catch (err) {
|
|
260
|
+
promptError = err;
|
|
261
|
+
promptFailed = true;
|
|
245
262
|
}
|
|
263
|
+
// codex phase2.rs awaits the consolidation agent's shutdown BEFORE artifacts
|
|
264
|
+
// are validated and the job is finished, and a failed shutdown outranks the
|
|
265
|
+
// run result: the caller must keep its lease rather than release it to a
|
|
266
|
+
// worker that could race a consolidator which is still alive. The
|
|
267
|
+
// consolidation agent holds write access to the memory root, so this is the
|
|
268
|
+
// difference between one writer and two.
|
|
269
|
+
if (!(await deleteSession(subId)))
|
|
270
|
+
throw new SubagentShutdownError(subId);
|
|
271
|
+
if (promptFailed)
|
|
272
|
+
throw promptError;
|
|
246
273
|
}
|
|
247
274
|
// Must exceed the longest legitimate sub-session lifetime (consolidation may
|
|
248
275
|
// run up to CONSOLIDATION_TIMEOUT_MS = 60min), or a second opencode instance /
|
|
@@ -294,25 +321,38 @@ export async function cleanupOldSubSessions(maxAgeMinutes = 90, timeoutMs = SUBS
|
|
|
294
321
|
function isPluginSubSessionTitle(title) {
|
|
295
322
|
return title === "codex-memory-consolidate" || /^codex-memory-extract-ses_[A-Za-z0-9]+$/.test(title ?? "");
|
|
296
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Closes a sub-session. Returns true when the delete call itself succeeded —
|
|
326
|
+
* the port's equivalent of codex's `shutdown_consolidation_agent` returning Ok
|
|
327
|
+
* (runtime.rs). A false return means the sub-agent may still be running.
|
|
328
|
+
*
|
|
329
|
+
* The 404 confirmation below is a separate, stricter question (is the session
|
|
330
|
+
* really gone?) and only governs ownership tracking, never the shutdown result:
|
|
331
|
+
* hosts without `session.get` would otherwise never report a clean shutdown.
|
|
332
|
+
*/
|
|
297
333
|
async function deleteSession(id) {
|
|
298
334
|
const input = getPluginInput();
|
|
299
335
|
if (!input)
|
|
300
|
-
return;
|
|
336
|
+
return false;
|
|
301
337
|
try {
|
|
302
338
|
const res = await input.client.session.delete({ path: { id } });
|
|
303
339
|
if (res.error) {
|
|
304
340
|
console.warn(`[opencode-codex-memory] failed to delete sub-session ${id}: ${JSON.stringify(res.error)}`);
|
|
305
|
-
return;
|
|
341
|
+
return false;
|
|
306
342
|
}
|
|
307
343
|
// OpenCode's Session.remove logs and swallows some internal failures while
|
|
308
344
|
// the HTTP route still returns success. Only a confirmed 404 proves the
|
|
309
345
|
// session is gone; otherwise retain ownership so hooks keep skipping it.
|
|
346
|
+
// codex runtime.rs drops the thread from its manager the same way: only
|
|
347
|
+
// after shutdown succeeded.
|
|
310
348
|
if (await sessionDeletionConfirmed(input.client, id)) {
|
|
311
349
|
activeSubSessions.delete(id);
|
|
312
350
|
}
|
|
351
|
+
return true;
|
|
313
352
|
}
|
|
314
353
|
catch (err) {
|
|
315
354
|
console.warn(`[opencode-codex-memory] error deleting sub-session ${id}:`, err);
|
|
355
|
+
return false;
|
|
316
356
|
}
|
|
317
357
|
}
|
|
318
358
|
async function sessionDeletionConfirmed(client, id) {
|
package/dist/src/phase2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ensureLayout, rebuildRawMemories, writeRolloutSummaries, pruneExtensionResources, writeWorkspaceDiff, validateConsolidationArtifacts, } from "./workspace.js";
|
|
2
2
|
import { ensureBaseline, captureWorkspaceDiff, resetBaseline, DIFF_ARTIFACT } from "./git-baseline.js";
|
|
3
|
-
import { consolidateViaSubagent } from "./llm.js";
|
|
3
|
+
import { consolidateViaSubagent, SubagentShutdownError } from "./llm.js";
|
|
4
4
|
import { invalidateCache } from "./source.js";
|
|
5
5
|
import { memoryRoot } from "./paths.js";
|
|
6
6
|
import { checkRateLimit } from "./ratelimit.js";
|
|
@@ -105,6 +105,17 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS, rateLimitC
|
|
|
105
105
|
try {
|
|
106
106
|
await consolidateViaSubagent(memoryRoot(), DIFF_ARTIFACT, opts.consolidationModel);
|
|
107
107
|
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// codex phase2.rs: when the consolidation agent's shutdown fails, keep
|
|
110
|
+
// the existing lease until it expires so another worker cannot race a
|
|
111
|
+
// consolidator whose shutdown has not completed. Neither succeed nor
|
|
112
|
+
// fail the job — marking it failed would release the lease immediately.
|
|
113
|
+
if (err instanceof SubagentShutdownError) {
|
|
114
|
+
console.warn(`[opencode-codex-memory] ${err.message}; holding the phase2 lease until it expires`);
|
|
115
|
+
return { status: "shutdown_failed" };
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
108
119
|
finally {
|
|
109
120
|
clearInterval(heartbeat);
|
|
110
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|