opencode-codex-memory 0.4.4 → 0.4.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 CHANGED
@@ -18,6 +18,8 @@ project is a faithful port of the memory system in OpenAI's Codex. It works out
18
18
  of the box with zero extra configuration and uses whatever models you already
19
19
  have set up in OpenCode.
20
20
 
21
+ See the [changelog](./CHANGELOG.md) for release history.
22
+
21
23
  **Local-first by design.** Memory is plain markdown files plus a small SQLite
22
24
  database on your own machine — no memory service to sign up for, no MCP server,
23
25
  no separate process, no sync. Installing it is one line in your `opencode.json`;
@@ -50,7 +52,7 @@ If you want the mental model before the details, jump to
50
52
 
51
53
  ```json
52
54
  {
53
- "plugin": ["opencode-codex-memory@0.4.4"]
55
+ "plugin": ["opencode-codex-memory@0.4.5"]
54
56
  }
55
57
  ```
56
58
 
@@ -237,7 +239,7 @@ To set options, turn the plugin entry into a `[name, options]` pair:
237
239
  ```json
238
240
  {
239
241
  "plugin": [
240
- ["opencode-codex-memory@0.4.4", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
242
+ ["opencode-codex-memory@0.4.5", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
241
243
  ]
242
244
  }
243
245
  ```
@@ -296,7 +298,7 @@ directions:
296
298
  ```json
297
299
  {
298
300
  "plugin": [
299
- ["opencode-codex-memory@0.4.4", { "codex_interop": { "import": true, "export": true } }]
301
+ ["opencode-codex-memory@0.4.5", { "codex_interop": { "import": true, "export": true } }]
300
302
  ]
301
303
  }
302
304
  ```
@@ -70,7 +70,22 @@ export declare class MemoryStore {
70
70
  * still back the consolidated artifacts.
71
71
  */
72
72
  markPhase2Succeeded(ownershipToken: string, selected?: Pick<Stage1Output, "session_id" | "source_updated_at">[]): void;
73
- /** Last recorded phase-2 success info (memory_inspect). Null when phase 2 never succeeded. */
73
+ /**
74
+ * Phase-2 job snapshot for memory_inspect. Always returns the global job row
75
+ * when it exists (including failed/running), so diagnostics are not limited
76
+ * to clean successes. `success_finished_at` is set only for a clean success
77
+ * (never a failure timestamp); `last_success_watermark` follows codex
78
+ * (preserved across later attempts; zero only counts while clean).
79
+ */
80
+ phase2JobSnapshot(): {
81
+ status: string;
82
+ last_error: string | null;
83
+ finished_at: number | null;
84
+ retry_at: number | null;
85
+ success_finished_at: number | null;
86
+ last_success_watermark: number | null;
87
+ } | null;
88
+ /** Last recorded phase-2 success info. Null when phase 2 never succeeded. */
74
89
  phase2LastSuccess(): {
75
90
  finished_at: number | null;
76
91
  last_success_watermark: number | null;
package/dist/src/store.js CHANGED
@@ -311,27 +311,50 @@ export class MemoryStore {
311
311
  mark.run(s.source_updated_at, s.session_id, s.source_updated_at);
312
312
  }).immediate();
313
313
  }
314
- /** Last recorded phase-2 success info (memory_inspect). Null when phase 2 never succeeded. */
315
- phase2LastSuccess() {
314
+ /**
315
+ * Phase-2 job snapshot for memory_inspect. Always returns the global job row
316
+ * when it exists (including failed/running), so diagnostics are not limited
317
+ * to clean successes. `success_finished_at` is set only for a clean success
318
+ * (never a failure timestamp); `last_success_watermark` follows codex
319
+ * (preserved across later attempts; zero only counts while clean).
320
+ */
321
+ phase2JobSnapshot() {
316
322
  const row = this.db
317
- .prepare(`SELECT status, finished_at, last_error, last_success_watermark FROM memory_jobs
323
+ .prepare(`SELECT status, finished_at, last_error, retry_at, last_success_watermark FROM memory_jobs
318
324
  WHERE kind='memory_consolidate_global' AND job_key='global'`)
319
325
  .get();
320
- if (!row || row.last_success_watermark === null)
326
+ if (!row)
321
327
  return null;
322
328
  const cleanSuccess = row.last_error === null &&
323
329
  row.finished_at !== null &&
324
330
  (row.status === "done" || row.status === "pending");
325
331
  // Codex initializes pending global jobs with watermark 0, so zero proves a
326
332
  // success only while the row itself is a clean completed attempt.
327
- if (row.last_success_watermark === 0 && !cleanSuccess)
333
+ const watermark = row.last_success_watermark === null
334
+ ? null
335
+ : row.last_success_watermark === 0 && !cleanSuccess
336
+ ? null
337
+ : row.last_success_watermark;
338
+ return {
339
+ status: row.status,
340
+ last_error: row.last_error,
341
+ finished_at: row.finished_at,
342
+ retry_at: row.retry_at,
343
+ // Codex preserves last_success_watermark across later attempts, while the
344
+ // job finished_at describes only the latest attempt. Never label a failure
345
+ // timestamp as a success finish time.
346
+ success_finished_at: cleanSuccess ? row.finished_at : null,
347
+ last_success_watermark: watermark,
348
+ };
349
+ }
350
+ /** Last recorded phase-2 success info. Null when phase 2 never succeeded. */
351
+ phase2LastSuccess() {
352
+ const snap = this.phase2JobSnapshot();
353
+ if (!snap || snap.last_success_watermark === null)
328
354
  return null;
329
355
  return {
330
- // Codex preserves last_success_watermark across later attempts, while
331
- // finished_at describes only the latest attempt. Expose them separately
332
- // so a failure timestamp is never labeled as a success timestamp.
333
- finished_at: cleanSuccess ? row.finished_at : null,
334
- last_success_watermark: row.last_success_watermark,
356
+ finished_at: snap.success_finished_at,
357
+ last_success_watermark: snap.last_success_watermark,
335
358
  };
336
359
  }
337
360
  markPhase2Failed(ownershipToken, error) {
@@ -150,11 +150,22 @@ export const memory_reset = tool({
150
150
  }
151
151
  },
152
152
  });
153
+ function fmtUnixSec(sec) {
154
+ return sec ? new Date(sec * 1000).toISOString() : "none";
155
+ }
156
+ function fmtWatermarkMs(ms) {
157
+ if (ms === 0)
158
+ return "0 (no consumed inputs)";
159
+ if (ms === null || ms === undefined)
160
+ return "none";
161
+ return new Date(ms).toISOString();
162
+ }
153
163
  export const memory_inspect = tool({
154
- description: "Inspect the current memory state. Returns: stage1_outputs count, last Phase 2 success watermark, " +
155
- "memory_summary token estimate, a listing of the memories directory, the effective plugin options, " +
156
- "and any configuration warnings (unknown/malformed options). Use it to verify the plugin " +
157
- "configuration took effect. Read-only.",
164
+ description: "Inspect the current memory state. Returns: stage1_outputs count, Phase 2 job status " +
165
+ "(including last error / retry time when failed), last Phase 2 success watermark, " +
166
+ "memory_summary token estimate (on-disk; injection caps at ~2500), a listing of the " +
167
+ "memories directory, the effective plugin options, and any configuration warnings " +
168
+ "(unknown/malformed options). Use it to verify the plugin configuration took effect. Read-only.",
158
169
  args: {},
159
170
  async execute() {
160
171
  try {
@@ -171,20 +182,30 @@ export const memory_inspect = tool({
171
182
  summaryTokens = estimateTokens(text);
172
183
  }
173
184
  const listing = listMemoriesDir();
174
- // The tool description promises the last Phase 2 success watermark.
175
- const phase2 = store.phase2LastSuccess();
176
- const watermark = phase2?.last_success_watermark === 0
177
- ? "0 (no consumed inputs)"
178
- : phase2?.last_success_watermark !== null && phase2?.last_success_watermark !== undefined
179
- ? new Date(phase2.last_success_watermark).toISOString()
180
- : "none";
181
- const finishedAt = phase2?.finished_at ? new Date(phase2.finished_at * 1000).toISOString() : "none";
185
+ const phase2 = store.phase2JobSnapshot();
186
+ const phase2Lines = phase2
187
+ ? [
188
+ `phase2_status: ${phase2.status}`,
189
+ `phase2_last_error: ${phase2.last_error ?? "none"}`,
190
+ `phase2_retry_at: ${fmtUnixSec(phase2.retry_at)}`,
191
+ `phase2_last_attempt_finished_at: ${fmtUnixSec(phase2.finished_at)}`,
192
+ `phase2_last_success_watermark: ${fmtWatermarkMs(phase2.last_success_watermark)}`,
193
+ // Clean-success finish only — never a failure timestamp.
194
+ `phase2_last_success_finished_at: ${fmtUnixSec(phase2.success_finished_at)}`,
195
+ ]
196
+ : [
197
+ "phase2_status: none",
198
+ "phase2_last_error: none",
199
+ "phase2_retry_at: none",
200
+ "phase2_last_attempt_finished_at: none",
201
+ "phase2_last_success_watermark: none",
202
+ "phase2_last_success_finished_at: none",
203
+ ];
182
204
  const out = [
183
205
  `stage1_outputs: ${outputs.length}`,
184
- `phase2_last_success_watermark: ${watermark}`,
185
- `phase2_last_finished_at: ${finishedAt}`,
206
+ ...phase2Lines,
186
207
  `memory_summary_chars: ${summaryChars}`,
187
- `memory_summary_tokens_est: ${summaryTokens}`,
208
+ `memory_summary_tokens_est: ${summaryTokens} (on disk; injection caps at ~2500)`,
188
209
  `memories_dir_entries: ${listing.length}`,
189
210
  "",
190
211
  ...renderEffectiveConfig(),
@@ -196,8 +217,14 @@ export const memory_inspect = tool({
196
217
  output: out,
197
218
  metadata: {
198
219
  stage1_count: outputs.length,
220
+ phase2_status: phase2?.status ?? null,
221
+ phase2_last_error: phase2?.last_error ?? null,
222
+ phase2_retry_at: phase2?.retry_at ?? null,
223
+ phase2_last_attempt_finished_at: phase2?.finished_at ?? null,
199
224
  phase2_last_success_watermark: phase2?.last_success_watermark ?? null,
200
- phase2_last_finished_at: phase2?.finished_at ?? null,
225
+ phase2_last_success_finished_at: phase2?.success_finished_at ?? null,
226
+ // Back-compat aliases used by earlier inspect consumers.
227
+ phase2_last_finished_at: phase2?.success_finished_at ?? null,
201
228
  summary_chars: summaryChars,
202
229
  summary_tokens_est: summaryTokens,
203
230
  files: listing,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-memory",
3
- "version": "0.4.4",
3
+ "version": "0.4.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",