opencode-codex-memory 0.7.1 → 0.7.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 CHANGED
@@ -52,7 +52,7 @@ If you want the mental model — learning, remembering, forgetting — see
52
52
 
53
53
  ```json
54
54
  {
55
- "plugin": ["opencode-codex-memory@0.7.1"]
55
+ "plugin": ["opencode-codex-memory@0.7.3"]
56
56
  }
57
57
  ```
58
58
 
@@ -72,7 +72,7 @@ V2 plugin syntax:
72
72
 
73
73
  ```jsonc
74
74
  {
75
- "plugins": [{ "package": "opencode-codex-memory@0.7.1" }],
75
+ "plugins": [{ "package": "opencode-codex-memory@0.7.3" }],
76
76
  }
77
77
  ```
78
78
 
@@ -198,7 +198,7 @@ To set options, turn the plugin entry into a `[name, options]` pair:
198
198
  ```json
199
199
  {
200
200
  "plugin": [
201
- ["opencode-codex-memory@0.7.1", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
201
+ ["opencode-codex-memory@0.7.3", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
202
202
  ]
203
203
  }
204
204
  ```
@@ -265,7 +265,7 @@ Off by default; no changes to Codex's own config are required.
265
265
  {
266
266
  "plugin": [
267
267
  [
268
- "opencode-codex-memory@0.7.1",
268
+ "opencode-codex-memory@0.7.3",
269
269
  { "codex_interop": { "import": true, "export": true } }
270
270
  ]
271
271
  ]
@@ -322,7 +322,7 @@ from the project memories Claude already keeps on your machine. **One-way only**
322
322
  ```json
323
323
  {
324
324
  "plugin": [
325
- ["opencode-codex-memory@0.7.1", { "claude_import": { "enabled": true } }]
325
+ ["opencode-codex-memory@0.7.3", { "claude_import": { "enabled": true } }]
326
326
  ]
327
327
  }
328
328
  ```
@@ -349,7 +349,7 @@ Claude names each project with an opaque id (a folder under
349
349
  {
350
350
  "plugin": [
351
351
  [
352
- "opencode-codex-memory@0.7.1",
352
+ "opencode-codex-memory@0.7.3",
353
353
  {
354
354
  "claude_import": {
355
355
  "enabled": true,
@@ -29,7 +29,7 @@ async function ensureInit(dir) {
29
29
  const gitDir = path.join(dir, ".git");
30
30
  let recreate = false;
31
31
  try {
32
- recreate = containsSymlink(gitDir) || !fs.lstatSync(gitDir).isDirectory();
32
+ recreate = gitMetadataUnusable(gitDir);
33
33
  }
34
34
  catch (err) {
35
35
  if (err.code !== "ENOENT")
@@ -41,17 +41,14 @@ async function ensureInit(dir) {
41
41
  await isogit.init({ fs, dir });
42
42
  }
43
43
  }
44
- function containsSymlink(root) {
45
- const st = fs.lstatSync(root);
46
- if (st.isSymbolicLink())
47
- return true;
48
- if (!st.isDirectory())
49
- return false;
50
- for (const name of fs.readdirSync(root)) {
51
- if (containsSymlink(path.join(root, name)))
52
- return true;
53
- }
54
- return false;
44
+ /**
45
+ * Only the `.git` entry itself. A recursive walk of git objects hangs on
46
+ * Windows junctions / symlink cycles and is not needed: we only refuse a
47
+ * `.git` that is a symlink or a non-directory.
48
+ */
49
+ function gitMetadataUnusable(gitDir) {
50
+ const st = fs.lstatSync(gitDir);
51
+ return st.isSymbolicLink() || !st.isDirectory();
55
52
  }
56
53
  // statusMatrix rows are [filepath, head, workdir, stage]; head !== workdir
57
54
  // means the working tree differs from HEAD (added, modified, or deleted).
@@ -2,7 +2,7 @@ import { checkRateLimit, isProviderCapacityError, noteProviderCapacityExhausted
2
2
  import { ensureLayout, rebuildRawMemories, writeRolloutSummaries, pruneExtensionResources, writeWorkspaceDiff, validateConsolidationArtifacts, removeMemorySymlinks, } from "./workspace.js";
3
3
  import { ensureBaseline, captureWorkspaceDiff, resetBaseline, DIFF_ARTIFACT } from "./git-baseline.js";
4
4
  import { consolidateViaSubagent, getPluginInput, SubagentCancelledError, SubagentShutdownError, } from "./llm.js";
5
- import { hostSessionLiveness } from "./host-client.js";
5
+ import { hostSessionLiveness, withHostTimeout } from "./host-client.js";
6
6
  import { invalidateCache } from "./source.js";
7
7
  import { memoryRoot } from "./paths.js";
8
8
  import { abortPhase2Consolidation, beginPhase2AbortScope, endPhase2AbortScope, isPluginShuttingDown, } from "./lifecycle.js";
@@ -16,6 +16,7 @@ export const DEFAULT_PHASE2_OPTIONS = {
16
16
  // Export runs only after a successful phase 2 (fresh, validated artifacts) and
17
17
  // must never fail the run — Codex's workspace is best-effort foreign territory.
18
18
  const PHASE2_LIVE_CHECK_CONCURRENCY = 8;
19
+ const GIT_TIMEOUT_MS = 120_000;
19
20
  /**
20
21
  * Codex get_phase2_input_selection re-validates each row against the live
21
22
  * threads table. We only drop a row on a confirmed 404 (same as session.deleted);
@@ -132,7 +133,7 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
132
133
  // ad-hoc notes added since then reach consolidation. Stale stage-1
133
134
  // output pruning happens in phase 1, before the rate gate (codex
134
135
  // start.rs ordering).
135
- if (!await ensureBaseline()) {
136
+ if (!await withHostTimeout(ensureBaseline(), GIT_TIMEOUT_MS, "ensureBaseline")) {
136
137
  store.markPhase2Failed(claim.ownershipToken, "git baseline failed");
137
138
  return { status: "baseline_failed" };
138
139
  }
@@ -171,7 +172,7 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
171
172
  console.warn("[opencode-codex-memory] claude import sync failed:", err);
172
173
  }
173
174
  }
174
- const diff = await captureWorkspaceDiff();
175
+ const diff = await withHostTimeout(captureWorkspaceDiff(), GIT_TIMEOUT_MS, "captureWorkspaceDiff");
175
176
  if (releaseIfShuttingDown(store, claim.ownershipToken)) {
176
177
  return { status: "shutting_down" };
177
178
  }
@@ -273,7 +274,7 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
273
274
  store.markPhase2Failed(claim.ownershipToken, `failed_invalid_artifacts: ${artifacts.reason}`);
274
275
  return { status: "failed_invalid_artifacts" };
275
276
  }
276
- if (!await resetBaseline()) {
277
+ if (!await withHostTimeout(resetBaseline(), GIT_TIMEOUT_MS, "resetBaseline")) {
277
278
  store.markPhase2Failed(claim.ownershipToken, "baseline reset failed");
278
279
  return { status: "baseline_reset_failed" };
279
280
  }
@@ -101,6 +101,7 @@ export declare class MemoryStore {
101
101
  phase2JobSnapshot(): {
102
102
  status: string;
103
103
  last_error: string | null;
104
+ started_at: number | null;
104
105
  finished_at: number | null;
105
106
  retry_at: number | null;
106
107
  lease_until: number | null;
package/dist/src/store.js CHANGED
@@ -434,7 +434,7 @@ export class MemoryStore {
434
434
  */
435
435
  phase2JobSnapshot() {
436
436
  const row = this.db
437
- .prepare(`SELECT status, finished_at, last_error, retry_at, lease_until, last_success_watermark FROM memory_jobs
437
+ .prepare(`SELECT status, started_at, finished_at, last_error, retry_at, lease_until, last_success_watermark FROM memory_jobs
438
438
  WHERE kind='memory_consolidate_global' AND job_key='global'`)
439
439
  .get();
440
440
  if (!row)
@@ -452,6 +452,7 @@ export class MemoryStore {
452
452
  return {
453
453
  status: row.status,
454
454
  last_error: row.last_error,
455
+ started_at: row.started_at,
455
456
  finished_at: row.finished_at,
456
457
  retry_at: row.retry_at,
457
458
  lease_until: row.lease_until,
@@ -0,0 +1,10 @@
1
+ type PluginSetup = (() => void | Promise<void>) | void;
2
+ declare function setup(ctx: unknown): Promise<PluginSetup>;
3
+ /** OpenCode 1.x TUI host: keep the package loadable. No sidebar on 1.x. */
4
+ declare function tui(): Promise<void>;
5
+ declare const _default: {
6
+ id: string;
7
+ setup: typeof setup;
8
+ tui: typeof tui;
9
+ };
10
+ export default _default;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Dual-host `./tui` entry.
3
+ *
4
+ * OpenCode 1.18.29+ (and 1.18.30) resolves `exports["./tui"]` and requires
5
+ * `default.tui()` — `readV1Plugin(..., "tui")` in strict mode. The Memory
6
+ * sidebar is OpenCode 2-only (`src/v2/tui.tsx`) and must not load until a
7
+ * V2 host calls setup(): the V2 TUI SDK is an optional peer and is missing
8
+ * on 1.x, so a static import there is "failed to load plugin".
9
+ */
10
+ const PLUGIN_ID = "opencode-codex-memory.tui";
11
+ async function setup(ctx) {
12
+ const { default: plugin } = await import("./v2/tui.js");
13
+ const fn = plugin.setup;
14
+ if (typeof fn !== "function")
15
+ return;
16
+ return fn(ctx);
17
+ }
18
+ /** OpenCode 1.x TUI host: keep the package loadable. No sidebar on 1.x. */
19
+ async function tui() { }
20
+ export default {
21
+ id: PLUGIN_ID,
22
+ setup,
23
+ tui,
24
+ };
@@ -192,6 +192,16 @@ export const memory_reset = tool({
192
192
  function fmtUnixSec(sec) {
193
193
  return sec ? new Date(sec * 1000).toISOString() : "none";
194
194
  }
195
+ function fmtElapsedSec(startedAtSec) {
196
+ if (!startedAtSec)
197
+ return "unknown";
198
+ const sec = Math.max(0, Math.floor(Date.now() / 1000) - startedAtSec);
199
+ if (sec < 60)
200
+ return `${sec}s`;
201
+ if (sec < 3600)
202
+ return `${Math.floor(sec / 60)}m ${sec % 60}s`;
203
+ return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`;
204
+ }
195
205
  function fmtWatermarkMs(ms) {
196
206
  if (ms === 0)
197
207
  return "0 (no consumed inputs)";
@@ -229,6 +239,9 @@ export const memory_inspect = tool({
229
239
  ? [
230
240
  `phase2_status: ${phase2.status}`,
231
241
  `phase2_last_error: ${phase2.last_error ?? "none"}`,
242
+ `phase2_started_at: ${fmtUnixSec(phase2.started_at)}`,
243
+ `phase2_running_for: ${phase2.status === "running" ? fmtElapsedSec(phase2.started_at) : "n/a"}`,
244
+ `phase2_lease_until: ${fmtUnixSec(phase2.lease_until)}`,
232
245
  `phase2_retry_at: ${fmtUnixSec(phase2.retry_at)}`,
233
246
  `phase2_last_attempt_finished_at: ${fmtUnixSec(phase2.finished_at)}`,
234
247
  `phase2_last_success_watermark: ${fmtWatermarkMs(phase2.last_success_watermark)}`,
@@ -238,6 +251,9 @@ export const memory_inspect = tool({
238
251
  : [
239
252
  "phase2_status: none",
240
253
  "phase2_last_error: none",
254
+ "phase2_started_at: none",
255
+ "phase2_running_for: n/a",
256
+ "phase2_lease_until: none",
241
257
  "phase2_retry_at: none",
242
258
  "phase2_last_attempt_finished_at: none",
243
259
  "phase2_last_success_watermark: none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-memory",
3
- "version": "0.7.1",
3
+ "version": "0.7.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",
@@ -18,8 +18,8 @@
18
18
  "types": "./dist/src/v2/index.d.ts"
19
19
  },
20
20
  "./tui": {
21
- "types": "./dist/src/v2/tui.d.ts",
22
- "import": "./dist/src/v2/tui.js"
21
+ "types": "./dist/src/tui.d.ts",
22
+ "import": "./dist/src/tui.js"
23
23
  },
24
24
  "./tools/*": {
25
25
  "import": "./dist/tools/*.js",