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 +1 -1
- package/src/capture.ts +1 -3
- package/src/db.ts +1 -5
- package/src/index.ts +5 -5
- package/src/llm.ts +10 -9
- package/src/phase2.ts +1 -3
- package/src/source.ts +1 -4
- package/src/store.ts +0 -4
- package/src/workspace.ts +2 -11
- package/tools/control.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.1.
|
|
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
|
|
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
|
-
|
|
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
|
|
7
|
-
import { runPhase2
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|