opencode-codex-memory 0.1.3 → 0.1.6

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.
Files changed (60) hide show
  1. package/dist/opencode.json +37 -0
  2. package/dist/src/capture.d.ts +19 -0
  3. package/dist/src/capture.js +120 -0
  4. package/dist/src/citation.d.ts +14 -0
  5. package/dist/src/citation.js +81 -0
  6. package/dist/src/db.d.ts +3 -0
  7. package/dist/src/db.js +78 -0
  8. package/dist/src/git-baseline.d.ts +24 -0
  9. package/dist/src/git-baseline.js +150 -0
  10. package/dist/src/index.d.ts +163 -0
  11. package/dist/src/index.js +365 -0
  12. package/dist/src/llm.d.ts +19 -0
  13. package/dist/src/llm.js +251 -0
  14. package/dist/src/path-guard.d.ts +10 -0
  15. package/dist/src/path-guard.js +44 -0
  16. package/dist/src/paths.d.ts +4 -0
  17. package/dist/src/paths.js +23 -0
  18. package/dist/src/phase1.d.ts +11 -0
  19. package/dist/src/phase1.js +104 -0
  20. package/dist/src/phase2.d.ts +11 -0
  21. package/dist/src/phase2.js +83 -0
  22. package/dist/src/ratelimit.d.ts +5 -0
  23. package/dist/src/ratelimit.js +20 -0
  24. package/dist/src/redact.d.ts +8 -0
  25. package/dist/src/redact.js +37 -0
  26. package/dist/src/source.d.ts +3 -0
  27. package/dist/src/source.js +46 -0
  28. package/dist/src/store.d.ts +96 -0
  29. package/dist/src/store.js +346 -0
  30. package/dist/src/token.d.ts +8 -0
  31. package/dist/src/token.js +19 -0
  32. package/dist/src/workspace.d.ts +8 -0
  33. package/dist/src/workspace.js +194 -0
  34. package/dist/tools/control.d.ts +29 -0
  35. package/dist/tools/control.js +153 -0
  36. package/dist/tools/memory.d.ts +52 -0
  37. package/dist/tools/memory.js +322 -0
  38. package/package.json +24 -6
  39. package/src/capture.ts +0 -135
  40. package/src/citation.ts +0 -94
  41. package/src/db.ts +0 -80
  42. package/src/git-baseline.ts +0 -162
  43. package/src/index.ts +0 -366
  44. package/src/llm.ts +0 -267
  45. package/src/path-guard.ts +0 -44
  46. package/src/paths.ts +0 -29
  47. package/src/phase1.ts +0 -116
  48. package/src/phase2.ts +0 -99
  49. package/src/ratelimit.ts +0 -26
  50. package/src/redact.ts +0 -44
  51. package/src/source.ts +0 -59
  52. package/src/store.ts +0 -430
  53. package/src/token.ts +0 -21
  54. package/src/workspace.ts +0 -181
  55. package/tools/control.ts +0 -145
  56. package/tools/memory.ts +0 -318
  57. /package/{src → dist/src}/templates/consolidation.md +0 -0
  58. /package/{src → dist/src}/templates/read_path.md +0 -0
  59. /package/{src → dist/src}/templates/stage_one_input.md +0 -0
  60. /package/{src → dist/src}/templates/stage_one_system.md +0 -0
@@ -0,0 +1,163 @@
1
+ import type { PluginInput, PluginOptions } from "@opencode-ai/plugin";
2
+ export declare function takeNewCitations(partKey: string, ids: string[]): string[];
3
+ declare const _default: {
4
+ id: string;
5
+ server(input: PluginInput, opts?: PluginOptions): Promise<{
6
+ tool: {
7
+ memory_read: {
8
+ description: string;
9
+ args: {
10
+ path: import("zod").ZodString;
11
+ line_offset: import("zod").ZodOptional<import("zod").ZodNumber>;
12
+ max_lines: import("zod").ZodOptional<import("zod").ZodNumber>;
13
+ };
14
+ execute(args: {
15
+ path: string;
16
+ line_offset?: number | undefined;
17
+ max_lines?: number | undefined;
18
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
19
+ };
20
+ memory_search: {
21
+ description: string;
22
+ args: {
23
+ query: import("zod").ZodOptional<import("zod").ZodString>;
24
+ case_sensitive: import("zod").ZodDefault<import("zod").ZodBoolean>;
25
+ since: import("zod").ZodOptional<import("zod").ZodString>;
26
+ until: import("zod").ZodOptional<import("zod").ZodString>;
27
+ limit: import("zod").ZodDefault<import("zod").ZodNumber>;
28
+ };
29
+ execute(args: {
30
+ case_sensitive: boolean;
31
+ limit: number;
32
+ query?: string | undefined;
33
+ since?: string | undefined;
34
+ until?: string | undefined;
35
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
36
+ };
37
+ memory_list: {
38
+ description: string;
39
+ args: {
40
+ path: import("zod").ZodDefault<import("zod").ZodString>;
41
+ max_results: import("zod").ZodDefault<import("zod").ZodNumber>;
42
+ };
43
+ execute(args: {
44
+ path: string;
45
+ max_results: number;
46
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
47
+ };
48
+ memory_add_note: {
49
+ description: string;
50
+ args: {
51
+ note: import("zod").ZodString;
52
+ title: import("zod").ZodOptional<import("zod").ZodString>;
53
+ };
54
+ execute(args: {
55
+ note: string;
56
+ title?: string | undefined;
57
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
58
+ };
59
+ memory_reset: {
60
+ description: string;
61
+ args: {
62
+ confirm: import("zod").ZodBoolean;
63
+ };
64
+ execute(args: {
65
+ confirm: boolean;
66
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
67
+ };
68
+ memory_inspect: {
69
+ description: string;
70
+ args: {};
71
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
72
+ };
73
+ memory_mode: {
74
+ description: string;
75
+ args: {
76
+ mode: import("zod").ZodEnum<{
77
+ enabled: "enabled";
78
+ disabled: "disabled";
79
+ polluted: "polluted";
80
+ }>;
81
+ sessionId: import("zod").ZodOptional<import("zod").ZodString>;
82
+ };
83
+ execute(args: {
84
+ mode: "enabled" | "disabled" | "polluted";
85
+ sessionId?: string | undefined;
86
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
87
+ };
88
+ } | {
89
+ memory_reset: {
90
+ description: string;
91
+ args: {
92
+ confirm: import("zod").ZodBoolean;
93
+ };
94
+ execute(args: {
95
+ confirm: boolean;
96
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
97
+ };
98
+ memory_inspect: {
99
+ description: string;
100
+ args: {};
101
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
102
+ };
103
+ memory_mode: {
104
+ description: string;
105
+ args: {
106
+ mode: import("zod").ZodEnum<{
107
+ enabled: "enabled";
108
+ disabled: "disabled";
109
+ polluted: "polluted";
110
+ }>;
111
+ sessionId: import("zod").ZodOptional<import("zod").ZodString>;
112
+ };
113
+ execute(args: {
114
+ mode: "enabled" | "disabled" | "polluted";
115
+ sessionId?: string | undefined;
116
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
117
+ };
118
+ memory_read?: undefined;
119
+ memory_search?: undefined;
120
+ memory_list?: undefined;
121
+ memory_add_note?: undefined;
122
+ };
123
+ config(input: {
124
+ agent?: Record<string, unknown>;
125
+ }): Promise<void>;
126
+ "experimental.chat.system.transform"(input: {
127
+ sessionID?: string;
128
+ model: unknown;
129
+ }, output: {
130
+ system: string[];
131
+ }): Promise<void>;
132
+ "experimental.chat.messages.transform"(_input: unknown, output: {
133
+ messages: {
134
+ info: {
135
+ role?: string;
136
+ };
137
+ parts: {
138
+ type: string;
139
+ text?: string;
140
+ }[];
141
+ }[];
142
+ }): Promise<void>;
143
+ event(input: {
144
+ event: {
145
+ type: string;
146
+ properties: unknown;
147
+ };
148
+ }): Promise<void>;
149
+ dispose(): Promise<void>;
150
+ }>;
151
+ };
152
+ export default _default;
153
+ /**
154
+ * Registers the memorize / memorize-extract sub-agents through the config
155
+ * hook so installing the plugin requires no manual agent setup. Definitions
156
+ * are read from the plugin's bundled opencode.json (single source of truth
157
+ * with the dev checkout). A user-defined agent of the same name always wins —
158
+ * only missing entries are filled. opencode-specific packaging: codex ships
159
+ * its memory agents inside the binary.
160
+ */
161
+ export declare function injectAgentDefinitions(config: {
162
+ agent?: Record<string, unknown>;
163
+ }): void;
@@ -0,0 +1,365 @@
1
+ import { ensureMemoryLayout, buildMemorySystemPrompt, invalidateCache } from "./source.js";
2
+ import { stripCitations, extractCitedSessionIds } from "./citation.js";
3
+ import { memory_read, memory_search, memory_list, memory_add_note } from "../tools/memory.js";
4
+ import { memory_reset, memory_inspect, memory_mode } from "../tools/control.js";
5
+ import { MemoryStore } from "./store.js";
6
+ import { runPhase1 } from "./phase1.js";
7
+ import { runPhase2 } from "./phase2.js";
8
+ import { setPluginInput, cleanupOldSubSessions, isMemorySubSession } from "./llm.js";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ let phase1InFlight = false;
12
+ let pluginClient = null;
13
+ // Configured MCP server names, fetched lazily; null until first successful fetch.
14
+ let mcpServerNames = null;
15
+ // Option names and defaults mirror codex's MemoriesToml/MemoriesConfig
16
+ // (codex-rs/config/src/types.rs). Keep them 1:1 so the drift script and manual
17
+ // syncing stay trivial; do not rename for taste.
18
+ let pluginOptions = {
19
+ generate_memories: true,
20
+ use_memories: true,
21
+ dedicated_tools: true,
22
+ disable_on_external_context: false,
23
+ max_raw_memories_for_consolidation: 256,
24
+ max_unused_days: 30,
25
+ max_rollout_age_days: 10,
26
+ max_rollouts_per_startup: 2,
27
+ min_rollout_idle_hours: 6,
28
+ };
29
+ // Deliberately uncached: openDb() is already a singleton, and caching a store
30
+ // here would hold a stale handle across closeDb() (e.g. after memory_reset).
31
+ function getStore() {
32
+ return new MemoryStore();
33
+ }
34
+ // Citation blocks arrive via message.part.updated once per streaming delta,
35
+ // so the same completed block is seen many times. Track which session ids
36
+ // were already recorded per part to count each citation once.
37
+ const recordedCitations = new Map();
38
+ const MAX_TRACKED_PARTS = 500;
39
+ export function takeNewCitations(partKey, ids) {
40
+ let seen = recordedCitations.get(partKey);
41
+ if (!seen) {
42
+ seen = new Set();
43
+ recordedCitations.set(partKey, seen);
44
+ if (recordedCitations.size > MAX_TRACKED_PARTS) {
45
+ const oldest = recordedCitations.keys().next().value;
46
+ if (oldest !== undefined)
47
+ recordedCitations.delete(oldest);
48
+ }
49
+ }
50
+ const fresh = ids.filter((id) => !seen.has(id));
51
+ for (const id of fresh)
52
+ seen.add(id);
53
+ return fresh;
54
+ }
55
+ export default {
56
+ id: "opencode-codex-memory",
57
+ async server(input, opts) {
58
+ setPluginInput(input);
59
+ pluginClient = input.client;
60
+ if (opts)
61
+ applyPluginOptions(opts);
62
+ void cleanupOldSubSessions().catch(() => { });
63
+ return buildHooks();
64
+ },
65
+ };
66
+ const KNOWN_OPTION_KEYS = new Set([
67
+ "generate_memories",
68
+ "use_memories",
69
+ "dedicated_tools",
70
+ "disable_on_external_context",
71
+ "extract_model",
72
+ "consolidation_model",
73
+ "max_raw_memories_for_consolidation",
74
+ "max_unused_days",
75
+ "max_rollout_age_days",
76
+ "max_rollouts_per_startup",
77
+ "min_rollout_idle_hours",
78
+ ]);
79
+ // codex clamps numeric knobs in From<MemoriesToml> for MemoriesConfig
80
+ // (config/src/types.rs); mirror the exact ranges. Non-finite values fall back
81
+ // to the default.
82
+ function clampInt(value, min, max, fallback) {
83
+ if (typeof value !== "number" || !Number.isFinite(value))
84
+ return fallback;
85
+ return Math.min(max, Math.max(min, Math.floor(value)));
86
+ }
87
+ function applyPluginOptions(opts) {
88
+ for (const key of Object.keys(opts)) {
89
+ if (!KNOWN_OPTION_KEYS.has(key)) {
90
+ // codex uses deny_unknown_fields; a plugin can only warn. Covers typos
91
+ // and the deliberately unimplemented min_rate_limit_remaining_percent.
92
+ console.warn(`[opencode-codex-memory] unknown/unsupported option '${key}' ignored`);
93
+ }
94
+ }
95
+ if (typeof opts.generate_memories === "boolean")
96
+ pluginOptions.generate_memories = opts.generate_memories;
97
+ if (typeof opts.use_memories === "boolean")
98
+ pluginOptions.use_memories = opts.use_memories;
99
+ if (typeof opts.dedicated_tools === "boolean")
100
+ pluginOptions.dedicated_tools = opts.dedicated_tools;
101
+ if (typeof opts.disable_on_external_context === "boolean")
102
+ pluginOptions.disable_on_external_context = opts.disable_on_external_context;
103
+ if (typeof opts.extract_model === "string")
104
+ pluginOptions.extract_model = opts.extract_model;
105
+ if (typeof opts.consolidation_model === "string")
106
+ pluginOptions.consolidation_model = opts.consolidation_model;
107
+ if ("max_raw_memories_for_consolidation" in opts)
108
+ pluginOptions.max_raw_memories_for_consolidation = clampInt(opts.max_raw_memories_for_consolidation, 1, 4096, 256);
109
+ if ("max_unused_days" in opts)
110
+ pluginOptions.max_unused_days = clampInt(opts.max_unused_days, 0, 365, 30);
111
+ if ("max_rollout_age_days" in opts)
112
+ pluginOptions.max_rollout_age_days = clampInt(opts.max_rollout_age_days, 0, 90, 10);
113
+ if ("max_rollouts_per_startup" in opts)
114
+ pluginOptions.max_rollouts_per_startup = clampInt(opts.max_rollouts_per_startup, 1, 128, 2);
115
+ if ("min_rollout_idle_hours" in opts)
116
+ pluginOptions.min_rollout_idle_hours = clampInt(opts.min_rollout_idle_hours, 1, 48, 6);
117
+ }
118
+ /**
119
+ * codex marks every MCP server as memory-polluting unconditionally
120
+ * (codex-mcp server.rs pollutes_memory: true). opencode registers MCP tools
121
+ * as "<server>_<tool>", so match tool names against the configured server
122
+ * list. Fails closed to the web-tools-only check when the list is unavailable.
123
+ */
124
+ async function isExternalContextTool(toolName) {
125
+ if (toolName === "websearch" || toolName === "webfetch")
126
+ return true;
127
+ if (!mcpServerNames && pluginClient) {
128
+ try {
129
+ const res = await pluginClient.mcp.status();
130
+ const servers = res?.data ?? res;
131
+ if (servers && typeof servers === "object") {
132
+ mcpServerNames = new Set(Object.keys(servers));
133
+ }
134
+ }
135
+ catch {
136
+ // MCP status unavailable (older opencode); keep web-tools-only checks.
137
+ }
138
+ }
139
+ if (!mcpServerNames)
140
+ return false;
141
+ for (const server of mcpServerNames) {
142
+ if (toolName.startsWith(`${server}_`))
143
+ return true;
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Registers the memorize / memorize-extract sub-agents through the config
149
+ * hook so installing the plugin requires no manual agent setup. Definitions
150
+ * are read from the plugin's bundled opencode.json (single source of truth
151
+ * with the dev checkout). A user-defined agent of the same name always wins —
152
+ * only missing entries are filled. opencode-specific packaging: codex ships
153
+ * its memory agents inside the binary.
154
+ */
155
+ export function injectAgentDefinitions(config) {
156
+ let defs;
157
+ try {
158
+ const raw = fs.readFileSync(path.join(import.meta.dirname, "..", "opencode.json"), "utf8");
159
+ defs = JSON.parse(raw).agent ?? {};
160
+ }
161
+ catch (err) {
162
+ console.warn("[opencode-codex-memory] could not load bundled agent definitions:", err);
163
+ return;
164
+ }
165
+ config.agent ??= {};
166
+ for (const [name, def] of Object.entries(defs)) {
167
+ if (!config.agent[name])
168
+ config.agent[name] = def;
169
+ }
170
+ }
171
+ function buildHooks() {
172
+ const base = {
173
+ async config(input) {
174
+ try {
175
+ // The write pipeline is the only consumer of the sub-agents; with
176
+ // generation off they would just pollute the user's agent list.
177
+ if (!pluginOptions.generate_memories)
178
+ return;
179
+ injectAgentDefinitions(input);
180
+ }
181
+ catch (err) {
182
+ console.error("[opencode-codex-memory] config hook error:", err);
183
+ }
184
+ },
185
+ async "experimental.chat.system.transform"(input, output) {
186
+ try {
187
+ if (!pluginOptions.use_memories)
188
+ return;
189
+ if (input.sessionID && isMemorySubSession(input.sessionID))
190
+ return;
191
+ ensureMemoryLayout();
192
+ const memoryPrompt = buildMemorySystemPrompt();
193
+ if (memoryPrompt) {
194
+ output.system.push(memoryPrompt);
195
+ }
196
+ }
197
+ catch (err) {
198
+ console.error("[opencode-codex-memory] system.transform error:", err);
199
+ }
200
+ },
201
+ async "experimental.chat.messages.transform"(_input, output) {
202
+ try {
203
+ for (const msg of output.messages) {
204
+ if (msg.info?.role !== "assistant")
205
+ continue;
206
+ for (const part of msg.parts) {
207
+ if (part.type === "text" && typeof part.text === "string" && part.text.includes("<memory-citation>")) {
208
+ const before = part.text;
209
+ part.text = stripCitations(part.text);
210
+ if (part.text.includes("<memory-citation>")) {
211
+ console.warn("[opencode-codex-memory] citation marker still present after stripCitations — hook contract may have changed");
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ catch (err) {
218
+ console.error("[opencode-codex-memory] messages.transform error:", err);
219
+ }
220
+ },
221
+ async event(input) {
222
+ try {
223
+ const ev = input.event;
224
+ if (ev.type === "message.part.updated") {
225
+ const part = ev.properties.part;
226
+ if (!part || part.type !== "text" || typeof part.text !== "string")
227
+ return;
228
+ if (part.sessionID && isMemorySubSession(part.sessionID))
229
+ return;
230
+ if (!part.text.includes("<memory-citation>"))
231
+ return;
232
+ let ids = [];
233
+ try {
234
+ ids = extractCitedSessionIds(part.text);
235
+ }
236
+ catch {
237
+ return;
238
+ }
239
+ const fresh = takeNewCitations(`${part.sessionID ?? ""}:${part.id ?? ""}`, ids);
240
+ if (fresh.length > 0) {
241
+ try {
242
+ getStore().recordUsage(fresh);
243
+ }
244
+ catch (e) {
245
+ console.error("[opencode-codex-memory] recordUsage failed:", e);
246
+ }
247
+ }
248
+ return;
249
+ }
250
+ if (ev.type === "tool.execute.after") {
251
+ // Mirrors codex: external context (web search or any MCP tool) only
252
+ // pollutes the session's memory when disable_on_external_context is
253
+ // enabled. Off by default.
254
+ if (!pluginOptions.disable_on_external_context)
255
+ return;
256
+ const props = ev.properties;
257
+ const toolName = props?.tool ?? "";
258
+ if (props.sessionID && (await isExternalContextTool(toolName))) {
259
+ try {
260
+ getStore().markPolluted(props.sessionID);
261
+ }
262
+ catch (e) {
263
+ console.error("[opencode-codex-memory] markPolluted failed:", e);
264
+ }
265
+ }
266
+ return;
267
+ }
268
+ if (ev.type === "session.deleted") {
269
+ // Mirrors codex delete_thread_memory: drop the extracted memory and
270
+ // its job when the session is deleted; the file disappears at the
271
+ // next phase-2 rebuild and the diff drives forgetting.
272
+ const props = ev.properties;
273
+ const sid = props?.info?.id;
274
+ if (sid) {
275
+ try {
276
+ getStore().deleteSessionMemory(sid);
277
+ }
278
+ catch (e) {
279
+ console.error("[opencode-codex-memory] deleteSessionMemory failed:", e);
280
+ }
281
+ }
282
+ return;
283
+ }
284
+ if (ev.type === "session.idle") {
285
+ const props = ev.properties;
286
+ const sid = props?.sessionID;
287
+ if (!sid || isMemorySubSession(sid))
288
+ return;
289
+ // codex stamps memory_mode at thread creation from generate_memories:
290
+ // sessions seen while generation is off stay excluded permanently,
291
+ // even if the option is re-enabled later.
292
+ try {
293
+ getStore().stampMemoryModeIfAbsent(sid, pluginOptions.generate_memories ? "enabled" : "disabled");
294
+ }
295
+ catch (e) {
296
+ console.error("[opencode-codex-memory] stampMemoryModeIfAbsent failed:", e);
297
+ }
298
+ void triggerPhase1(sid);
299
+ return;
300
+ }
301
+ }
302
+ catch (err) {
303
+ console.error("[opencode-codex-memory] event error:", err);
304
+ }
305
+ },
306
+ async dispose() {
307
+ invalidateCache();
308
+ },
309
+ };
310
+ // Control tools (reset/inspect/mode) are always available. The memory
311
+ // read/search/list/add-note tools require BOTH use_memories and
312
+ // dedicated_tools, mirroring codex's MemoriesExtension: use_memories=false
313
+ // disables the whole extension including its tools (extension.rs).
314
+ const tool = pluginOptions.use_memories && pluginOptions.dedicated_tools
315
+ ? {
316
+ memory_read,
317
+ memory_search,
318
+ memory_list,
319
+ memory_add_note,
320
+ memory_reset,
321
+ memory_inspect,
322
+ memory_mode,
323
+ }
324
+ : {
325
+ memory_reset,
326
+ memory_inspect,
327
+ memory_mode,
328
+ };
329
+ return { ...base, tool };
330
+ }
331
+ async function triggerPhase1(currentSessionId) {
332
+ if (phase1InFlight || !pluginOptions.generate_memories)
333
+ return;
334
+ phase1InFlight = true;
335
+ try {
336
+ await runPhase1(getStore(), {
337
+ maxAgeDays: pluginOptions.max_rollout_age_days,
338
+ minIdleHours: pluginOptions.min_rollout_idle_hours,
339
+ maxClaimed: pluginOptions.max_rollouts_per_startup,
340
+ excludeSession: currentSessionId,
341
+ extractModel: pluginOptions.extract_model,
342
+ });
343
+ }
344
+ catch (err) {
345
+ console.error("[opencode-codex-memory] phase1 error:", err);
346
+ }
347
+ finally {
348
+ phase1InFlight = false;
349
+ }
350
+ void triggerPhase2();
351
+ }
352
+ async function triggerPhase2() {
353
+ try {
354
+ // runPhase2 has its own in-flight guard
355
+ await runPhase2(getStore(), {
356
+ maxRaw: pluginOptions.max_raw_memories_for_consolidation,
357
+ maxUnusedDays: pluginOptions.max_unused_days,
358
+ extensionRetentionDays: 7,
359
+ consolidationModel: pluginOptions.consolidation_model,
360
+ });
361
+ }
362
+ catch (err) {
363
+ console.error("[opencode-codex-memory] phase2 error:", err);
364
+ }
365
+ }
@@ -0,0 +1,19 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ export interface ExtractionResult {
3
+ raw_memory: string;
4
+ rollout_summary: string;
5
+ rollout_slug: string | null;
6
+ }
7
+ export declare function setPluginInput(input: PluginInput): void;
8
+ export declare function isMemorySubSession(sessionId: string): boolean;
9
+ export interface ExtractOptions {
10
+ cwd?: string;
11
+ model?: string;
12
+ }
13
+ /** Returns null when the extractor reported a no-op (nothing worth remembering). */
14
+ export declare function extractViaSubagent(sessionId: string, transcript: string, opts?: ExtractOptions): Promise<ExtractionResult | null>;
15
+ export declare function consolidateViaSubagent(memoryRoot: string, diffFileName: string, model?: string): Promise<void>;
16
+ export declare function cleanupOldSubSessions(maxAgeMinutes?: number): Promise<void>;
17
+ export declare function fillTemplate(tmpl: string, vars: Record<string, string>): string;
18
+ /** Parses the stage-1 JSON output. Returns null for the all-empty no-op response. */
19
+ export declare function parseExtraction(raw: string): ExtractionResult | null;