opencode-codex-memory 0.1.2 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-memory",
3
- "version": "0.1.2",
3
+ "version": "0.1.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": "src/index.ts",
package/src/capture.ts CHANGED
@@ -43,7 +43,6 @@ export interface TranscriptMessage {
43
43
  type: string
44
44
  role?: string
45
45
  text?: string
46
- seq: number
47
46
  }
48
47
 
49
48
  export function loadTranscript(sessionId: string): TranscriptMessage[] {
@@ -59,7 +58,7 @@ export function loadTranscript(sessionId: string): TranscriptMessage[] {
59
58
  ORDER BY p.time_created ASC`,
60
59
  )
61
60
  .all(sessionId) as { data: string; msg_data: string }[]
62
- return rows.map((r, i) => {
61
+ return rows.map((r) => {
63
62
  let parsed: any = {}
64
63
  try {
65
64
  parsed = JSON.parse(r.data)
@@ -72,7 +71,6 @@ export function loadTranscript(sessionId: string): TranscriptMessage[] {
72
71
  } catch {
73
72
  }
74
73
  return {
75
- seq: i,
76
74
  type: parsed.type ?? "unknown",
77
75
  role,
78
76
  text: extractText(parsed),
package/src/db.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Database } from "bun:sqlite"
2
2
  import { memoryDbPath } from "./paths.js"
3
3
 
4
- export const SCHEMA_V1 = [
4
+ const SCHEMA_V1 = [
5
5
  `CREATE TABLE IF NOT EXISTS memory_stage1_outputs (
6
6
  session_id TEXT PRIMARY KEY,
7
7
  source_updated_at INTEGER NOT NULL,
@@ -77,8 +77,4 @@ export function closeDb(): void {
77
77
  dbInstance.close()
78
78
  dbInstance = null
79
79
  }
80
- }
81
-
82
- export function resetDbForTest(): void {
83
- closeDb()
84
80
  }
package/src/index.ts CHANGED
@@ -3,14 +3,13 @@ import { stripCitations, extractCitedSessionIds } from "./citation.js"
3
3
  import { memory_read, memory_search, memory_list, memory_add_note } from "../tools/memory.js"
4
4
  import { memory_reset, memory_inspect, memory_mode } from "../tools/control.js"
5
5
  import { MemoryStore } from "./store.js"
6
- import { runPhase1, DEFAULT_PHASE1_OPTIONS } from "./phase1.js"
7
- import { runPhase2, DEFAULT_PHASE2_OPTIONS } from "./phase2.js"
6
+ import { runPhase1 } from "./phase1.js"
7
+ import { runPhase2 } from "./phase2.js"
8
8
  import { setPluginInput, cleanupOldSubSessions, isMemorySubSession } from "./llm.js"
9
9
  import type { PluginInput, PluginOptions } from "@opencode-ai/plugin"
10
10
  import fs from "fs"
11
11
  import path from "path"
12
12
 
13
- let store: MemoryStore | null = null
14
13
  let phase1InFlight = false
15
14
  let pluginClient: PluginInput["client"] | null = null
16
15
  // Configured MCP server names, fetched lazily; null until first successful fetch.
@@ -43,9 +42,10 @@ let pluginOptions: {
43
42
  min_rollout_idle_hours: 6,
44
43
  }
45
44
 
45
+ // Deliberately uncached: openDb() is already a singleton, and caching a store
46
+ // here would hold a stale handle across closeDb() (e.g. after memory_reset).
46
47
  function getStore(): MemoryStore {
47
- if (!store) store = new MemoryStore()
48
- return store
48
+ return new MemoryStore()
49
49
  }
50
50
 
51
51
  // Citation blocks arrive via message.part.updated once per streaming delta,
package/src/llm.ts CHANGED
@@ -14,7 +14,7 @@ export function setPluginInput(input: PluginInput): void {
14
14
  inputRef = input
15
15
  }
16
16
 
17
- export function getPluginInput(): PluginInput | null {
17
+ function getPluginInput(): PluginInput | null {
18
18
  return inputRef
19
19
  }
20
20
 
@@ -167,7 +167,10 @@ export async function consolidateViaSubagent(memoryRoot: string, diffFileName: s
167
167
  }
168
168
  }
169
169
 
170
- export async function cleanupOldSubSessions(maxAgeMinutes = 30): Promise<void> {
170
+ // Must exceed the longest legitimate sub-session lifetime (consolidation may
171
+ // run up to CONSOLIDATION_TIMEOUT_MS = 60min), or a second opencode instance /
172
+ // plugin reload would delete a working sub-session mid-run.
173
+ export async function cleanupOldSubSessions(maxAgeMinutes = 90): Promise<void> {
171
174
  const input = getPluginInput()
172
175
  if (!input) return
173
176
  try {
@@ -207,7 +210,7 @@ async function deleteSession(id: string): Promise<void> {
207
210
  export function fillTemplate(tmpl: string, vars: Record<string, string>): string {
208
211
  let out = tmpl
209
212
  for (const [key, value] of Object.entries(vars)) {
210
- out = out.replace(`{{ ${key} }}`, () => value)
213
+ out = out.replaceAll(`{{ ${key} }}`, () => value)
211
214
  }
212
215
  return out
213
216
  }
@@ -221,12 +224,10 @@ function buildExtractionInput(sessionId: string, cwd: string, transcript: string
221
224
  }
222
225
 
223
226
  function buildConsolidationPrompt(memoryRoot: string, diffFileName: string): string {
224
- const tmpl = readTemplate("consolidation.md")
225
- let out = tmpl
226
- // These placeholders appear many times in the template; replace all occurrences.
227
- out = out.split("{{ memory_root }}").join(memoryRoot)
228
- out = out.split("{{ phase2_workspace_diff_file }}").join(diffFileName)
229
- return out
227
+ return fillTemplate(readTemplate("consolidation.md"), {
228
+ memory_root: memoryRoot,
229
+ phase2_workspace_diff_file: diffFileName,
230
+ })
230
231
  }
231
232
 
232
233
  function readTemplate(name: string): string {
package/src/phase2.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MemoryStore, PHASE2_COOLDOWN_MS } from "./store.js"
1
+ import { MemoryStore } from "./store.js"
2
2
  import { ensureLayout, rebuildRawMemories, writeRolloutSummaries, pruneExtensionResources, writeWorkspaceDiff } from "./workspace.js"
3
3
  import { ensureBaseline, captureWorkspaceDiff, resetBaseline, DIFF_ARTIFACT } from "./git-baseline.js"
4
4
  import { consolidateViaSubagent } from "./llm.js"
@@ -97,5 +97,3 @@ export async function runPhase2(store: MemoryStore, opts: Phase2Options = DEFAUL
97
97
  phase2InFlight = false
98
98
  }
99
99
  }
100
-
101
- export const PHASE2_COOLDOWN = PHASE2_COOLDOWN_MS
package/src/source.ts CHANGED
@@ -55,8 +55,5 @@ export function buildMemorySystemPrompt(): string | null {
55
55
  }
56
56
 
57
57
  export function ensureMemoryLayout(): void {
58
- const root = memoryRoot()
59
- if (!fs.existsSync(root)) {
60
- fs.mkdirSync(root, { recursive: true })
61
- }
58
+ fs.mkdirSync(memoryRoot(), { recursive: true })
62
59
  }
package/src/store.ts CHANGED
@@ -26,10 +26,6 @@ export interface Stage1Output {
26
26
  last_usage: number | null
27
27
  }
28
28
 
29
- export type ClaimResult =
30
- | { type: "claimed"; sessionId: string; workerId: string; ownershipToken: string }
31
- | { type: "skipped" }
32
-
33
29
  export interface Stage1Claim {
34
30
  sessionId: string
35
31
  ownershipToken: string
package/src/workspace.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash } from "crypto"
1
2
  import fs from "fs"
2
3
  import path from "path"
3
4
  import { memoryRoot } from "./paths.js"
@@ -65,17 +66,7 @@ export function rolloutSummaryFileStem(o: Pick<Stage1Output, "session_id" | "sou
65
66
  const ts = new Date(o.source_updated_at)
66
67
  const pad = (n: number) => String(n).padStart(2, "0")
67
68
  const timestamp = `${ts.getUTCFullYear()}-${pad(ts.getUTCMonth() + 1)}-${pad(ts.getUTCDate())}T${pad(ts.getUTCHours())}-${pad(ts.getUTCMinutes())}-${pad(ts.getUTCSeconds())}`
68
- let h = 0
69
- for (let i = 0; i < o.session_id.length; i++) {
70
- h = (h * 31 + o.session_id.charCodeAt(i)) >>> 0
71
- }
72
- const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
73
- let hash = ""
74
- let v = h % 36 ** 4
75
- for (let i = 0; i < 4; i++) {
76
- hash = alphabet[v % 36] + hash
77
- v = Math.floor(v / 36)
78
- }
69
+ const hash = createHash("sha1").update(o.session_id).digest("hex").slice(0, 4)
79
70
  const prefix = `${timestamp}-${hash}`
80
71
  const slug = (o.rollout_slug ?? "")
81
72
  .toLowerCase()
package/tools/control.ts CHANGED
@@ -4,7 +4,6 @@ import { tool } from "@opencode-ai/plugin"
4
4
  import { memoryRoot, memorySummaryPath } from "@/paths"
5
5
  import { MemoryStore } from "@/store"
6
6
  import { invalidateCache } from "@/source"
7
- import { closeDb } from "@/db"
8
7
  import { estimateTokens } from "@/token"
9
8
 
10
9
  function isSymlinkedRoot(): boolean {
@@ -72,7 +71,8 @@ export const memory_reset = tool({
72
71
  const store = new MemoryStore()
73
72
  store.clearMemoryData()
74
73
  wipeMemoriesDir()
75
- closeDb()
74
+ // codex keeps its state DB pool open across resets (clear_memory_roots_contents
75
+ // only wipes directories); closing here would strand cached handles elsewhere.
76
76
  invalidateCache()
77
77
  return { output: "Memory reset complete. Extracted memories and jobs cleared, memories directory (incl. git history) wiped, cache invalidated. Per-session memory modes were preserved." }
78
78
  } catch (err) {