opencode-codex-memory 0.4.7 → 0.4.8
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 +29 -3
- package/dist/opencode.json +1 -1
- package/dist/src/capture.d.ts +2 -7
- package/dist/src/capture.js +32 -23
- package/dist/src/codex-interop.d.ts +8 -0
- package/dist/src/codex-interop.js +22 -0
- package/dist/src/diagnostics.d.ts +26 -0
- package/dist/src/diagnostics.js +37 -0
- package/dist/src/host-client.d.ts +72 -0
- package/dist/src/host-client.js +91 -0
- package/dist/src/index.js +27 -6
- package/dist/src/lifecycle.d.ts +26 -0
- package/dist/src/lifecycle.js +45 -0
- package/dist/src/llm.d.ts +2 -0
- package/dist/src/llm.js +15 -36
- package/dist/src/phase1.js +35 -3
- package/dist/src/phase2.d.ts +1 -2
- package/dist/src/phase2.js +49 -10
- package/dist/src/ratelimit.d.ts +4 -0
- package/dist/src/ratelimit.js +31 -14
- package/dist/src/store.d.ts +31 -0
- package/dist/src/store.js +62 -0
- package/dist/tools/control.js +53 -9
- package/dist/tools/memory.js +8 -1
- package/opencode.json +1 -1
- package/package.json +1 -1
package/dist/tools/control.js
CHANGED
|
@@ -8,7 +8,9 @@ import { estimateTokens } from "../src/token.js";
|
|
|
8
8
|
import { assertMemoryRootSafe, readRegularFileNoFollow } from "../src/path-guard.js";
|
|
9
9
|
import { isPhase2InFlight } from "../src/phase2.js";
|
|
10
10
|
import { pluginOptions, getConfigWarnings } from "../src/options.js";
|
|
11
|
-
import { resolveCodexInterop } from "../src/codex-interop.js";
|
|
11
|
+
import { codexInteropMtimes, resolveCodexInterop } from "../src/codex-interop.js";
|
|
12
|
+
import { formatDiagnosticLine, getDiscoveryStatus, getRecentDiagnostics, } from "../src/diagnostics.js";
|
|
13
|
+
import { isPluginShuttingDown } from "../src/lifecycle.js";
|
|
12
14
|
function isSymlinkedRoot() {
|
|
13
15
|
try {
|
|
14
16
|
assertMemoryRootSafe();
|
|
@@ -73,6 +75,11 @@ function renderEffectiveConfig() {
|
|
|
73
75
|
else {
|
|
74
76
|
const reachable = fs.existsSync(resolved.codexMemoryRoot);
|
|
75
77
|
lines.push(` codex_interop: import=${ci.import} export=${ci.export}`, ` codex memories: ${resolved.codexMemoryRoot}${reachable ? "" : " (not found yet — nothing is imported/exported until Codex's memory feature creates it)"}`);
|
|
78
|
+
if (reachable) {
|
|
79
|
+
const mt = codexInteropMtimes(resolved.codexMemoryRoot);
|
|
80
|
+
const fmt = (ms) => (ms == null ? "none" : new Date(ms).toISOString());
|
|
81
|
+
lines.push(` last import mtimes: MEMORY.md=${fmt(mt.importMemoryMd)} summary=${fmt(mt.importSummary)}`, ` last export mtimes: MEMORY.md=${fmt(mt.exportMemoryMd)} summary=${fmt(mt.exportSummary)}`);
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
const warnings = getConfigWarnings();
|
|
@@ -129,9 +136,11 @@ export const memory_reset = tool({
|
|
|
129
136
|
}
|
|
130
137
|
// A consolidation running in THIS process would recreate files right
|
|
131
138
|
// after the wipe (the sub-agent edits live artifacts and resets the git
|
|
132
|
-
// baseline). Refuse instead of racing it.
|
|
133
|
-
//
|
|
134
|
-
//
|
|
139
|
+
// baseline). Refuse instead of racing it. clearMemoryData also leaves a
|
|
140
|
+
// phase-2 cooldown marker so the next idle/chat hook cannot first-run-claim
|
|
141
|
+
// phase 2 and re-seed the root via ensureLayout. Cross-process consolidators
|
|
142
|
+
// already in flight remain ownership-guarded (their final mark becomes a
|
|
143
|
+
// no-op once the row is replaced) but may leave stray files — same window
|
|
135
144
|
// codex has between CLI clear and a running daemon.
|
|
136
145
|
if (isPhase2InFlight()) {
|
|
137
146
|
return { output: "Reset refused: memory consolidation is currently running. Try again in a few minutes." };
|
|
@@ -161,11 +170,12 @@ function fmtWatermarkMs(ms) {
|
|
|
161
170
|
return new Date(ms).toISOString();
|
|
162
171
|
}
|
|
163
172
|
export const memory_inspect = tool({
|
|
164
|
-
description: "Inspect the current memory state. Returns: stage1_outputs count,
|
|
165
|
-
"(including last error / retry time
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
173
|
+
description: "Inspect the current memory state. Returns: stage1_outputs count, stage-1 job status " +
|
|
174
|
+
"breakdown and recent errors, Phase 2 job status (including last error / retry time), " +
|
|
175
|
+
"last discovery outcome, pipeline diagnostics, memory_summary token estimate " +
|
|
176
|
+
"(on-disk; injection caps at ~2500), a listing of the memories directory, the " +
|
|
177
|
+
"effective plugin options, and any configuration warnings. Use it to verify " +
|
|
178
|
+
"configuration and debug why memory is not building. Read-only.",
|
|
169
179
|
args: {},
|
|
170
180
|
async execute() {
|
|
171
181
|
try {
|
|
@@ -173,6 +183,7 @@ export const memory_inspect = tool({
|
|
|
173
183
|
assertMemoryRootSafe();
|
|
174
184
|
const store = new MemoryStore();
|
|
175
185
|
const outputs = store.stage1Outputs();
|
|
186
|
+
const stage1Jobs = store.stage1JobSnapshot();
|
|
176
187
|
const summaryPath = memorySummaryPath();
|
|
177
188
|
let summaryChars = 0;
|
|
178
189
|
let summaryTokens = 0;
|
|
@@ -201,15 +212,44 @@ export const memory_inspect = tool({
|
|
|
201
212
|
"phase2_last_success_watermark: none",
|
|
202
213
|
"phase2_last_success_finished_at: none",
|
|
203
214
|
];
|
|
215
|
+
const stage1StatusParts = Object.entries(stage1Jobs.by_status)
|
|
216
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
217
|
+
.map(([s, c]) => `${s}=${c}`);
|
|
218
|
+
const stage1Lines = [
|
|
219
|
+
`stage1_jobs: ${stage1StatusParts.length > 0 ? stage1StatusParts.join(" ") : "none"}`,
|
|
220
|
+
...stage1Jobs.recent_errors.map((e) => ` stage1_error ${e.session_id} (${e.status}): ${e.last_error.slice(0, 200)}${e.retry_at ? ` retry_at=${fmtUnixSec(e.retry_at)}` : ""}`),
|
|
221
|
+
];
|
|
222
|
+
const discovery = getDiscoveryStatus();
|
|
223
|
+
const discoveryLine = discovery
|
|
224
|
+
? `discovery: ${discovery.ok ? "ok" : "failed"} count=${discovery.count} at=${new Date(discovery.at).toISOString()}${discovery.error ? ` error=${discovery.error}` : ""}`
|
|
225
|
+
: "discovery: never ran (no phase-1 pass yet this process)";
|
|
226
|
+
const idleHours = pluginOptions.min_rollout_idle_hours;
|
|
227
|
+
const eligibilityHint = `eligibility: sessions must be idle ≥ ${idleHours}h and younger than ${pluginOptions.max_rollout_age_days}d ` +
|
|
228
|
+
`(generate_memories=${pluginOptions.generate_memories}). ` +
|
|
229
|
+
`For faster local testing, set min_rollout_idle_hours to 1 (clamp floor).`;
|
|
230
|
+
const processLines = [
|
|
231
|
+
`phase2_in_flight: ${isPhase2InFlight()}`,
|
|
232
|
+
`plugin_shutting_down: ${isPluginShuttingDown()}`,
|
|
233
|
+
];
|
|
234
|
+
const diagnostics = getRecentDiagnostics(12);
|
|
235
|
+
const diagnosticLines = diagnostics.length > 0
|
|
236
|
+
? ["recent_events:", ...diagnostics.map((e) => ` ${formatDiagnosticLine(e)}`)]
|
|
237
|
+
: ["recent_events: none"];
|
|
204
238
|
const out = [
|
|
205
239
|
`stage1_outputs: ${outputs.length}`,
|
|
240
|
+
...stage1Lines,
|
|
206
241
|
...phase2Lines,
|
|
242
|
+
discoveryLine,
|
|
243
|
+
eligibilityHint,
|
|
244
|
+
...processLines,
|
|
207
245
|
`memory_summary_chars: ${summaryChars}`,
|
|
208
246
|
`memory_summary_tokens_est: ${summaryTokens} (on disk; injection caps at ~2500)`,
|
|
209
247
|
`memories_dir_entries: ${listing.length}`,
|
|
210
248
|
"",
|
|
211
249
|
...renderEffectiveConfig(),
|
|
212
250
|
"",
|
|
251
|
+
...diagnosticLines,
|
|
252
|
+
"",
|
|
213
253
|
"Files:",
|
|
214
254
|
listing.length > 0 ? listing.join("\n") : "(empty)",
|
|
215
255
|
].join("\n");
|
|
@@ -217,6 +257,8 @@ export const memory_inspect = tool({
|
|
|
217
257
|
output: out,
|
|
218
258
|
metadata: {
|
|
219
259
|
stage1_count: outputs.length,
|
|
260
|
+
stage1_jobs: stage1Jobs.by_status,
|
|
261
|
+
stage1_recent_errors: stage1Jobs.recent_errors,
|
|
220
262
|
phase2_status: phase2?.status ?? null,
|
|
221
263
|
phase2_last_error: phase2?.last_error ?? null,
|
|
222
264
|
phase2_retry_at: phase2?.retry_at ?? null,
|
|
@@ -225,11 +267,13 @@ export const memory_inspect = tool({
|
|
|
225
267
|
phase2_last_success_finished_at: phase2?.success_finished_at ?? null,
|
|
226
268
|
// Back-compat aliases used by earlier inspect consumers.
|
|
227
269
|
phase2_last_finished_at: phase2?.success_finished_at ?? null,
|
|
270
|
+
discovery,
|
|
228
271
|
summary_chars: summaryChars,
|
|
229
272
|
summary_tokens_est: summaryTokens,
|
|
230
273
|
files: listing,
|
|
231
274
|
effective_options: { ...pluginOptions, codex_interop: { ...pluginOptions.codex_interop } },
|
|
232
275
|
config_warnings: [...getConfigWarnings()],
|
|
276
|
+
recent_events: diagnostics,
|
|
233
277
|
},
|
|
234
278
|
};
|
|
235
279
|
}
|
package/dist/tools/memory.js
CHANGED
|
@@ -106,7 +106,14 @@ export const memory_list = tool({
|
|
|
106
106
|
const fullPath = safeResolveMemoryPath(args.path || ".");
|
|
107
107
|
if (!fs.existsSync(fullPath))
|
|
108
108
|
return { output: `Not found: ${args.path}` };
|
|
109
|
-
|
|
109
|
+
// lstat: do not follow a TOCTOU symlink swap after safeResolve checked.
|
|
110
|
+
// Symlinked dirs are rejected by design (same as path-guard / codex), not
|
|
111
|
+
// a regression from the TOCTOU fix — report that explicitly.
|
|
112
|
+
const st = fs.lstatSync(fullPath);
|
|
113
|
+
if (st.isSymbolicLink()) {
|
|
114
|
+
return { output: `memory_list error: symlinks are not allowed in the memory workspace: ${args.path}` };
|
|
115
|
+
}
|
|
116
|
+
if (!st.isDirectory())
|
|
110
117
|
return { output: `memory_list error: not a directory: ${args.path}` };
|
|
111
118
|
const entries = visibleEntries(fullPath).sort((a, b) => a.name.localeCompare(b.name));
|
|
112
119
|
const truncated = entries.length > args.max_results;
|
package/opencode.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"agent": {
|
|
4
4
|
"memorize": {
|
|
5
5
|
"mode": "subagent",
|
|
6
|
-
"prompt": "You are a memory consolidation agent. Read the workspace diff file and update MEMORY.md, memory_summary.md, and skills/
|
|
6
|
+
"prompt": "You are a memory consolidation agent. Read the workspace diff file and update MEMORY.md, memory_summary.md, and skills/ under the memory workspace only. Do not read or edit project source files outside that memory root. Keep memory_summary.md under 10000 chars (2500 tokens). Prune stale entries. Do not access the network.",
|
|
7
7
|
"permission": {
|
|
8
8
|
"*": "deny",
|
|
9
9
|
"bash": "deny",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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",
|