opencode-memoir 3.0.3 → 3.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"capture-lifecycle.d.ts","sourceRoot":"","sources":["../src/capture-lifecycle.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS3B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAOxD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQhC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKhC,KAAK,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAejD,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"capture-lifecycle.d.ts","sourceRoot":"","sources":["../src/capture-lifecycle.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAO1B;IAED,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAKvD;IAED,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAM/B;IAED,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGrC;IAEK,KAAK,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAahD;IAED,KAAK,IAAI,IAAI,CAEZ;CACF"}
package/dist/capture.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { MemoirToolInfo } from "./mcp-client.js";
2
1
  interface ChatPart {
3
2
  type?: string;
4
3
  text?: string;
@@ -33,7 +32,7 @@ export declare function shouldCaptureTurn(transcript: string | null, minChars?:
33
32
  * durability checks, writes each fact with an explicit 3-level taxonomy path,
34
33
  * and returns a compact report based only on confirmed tool outcomes.
35
34
  */
36
- export declare function buildTurnCaptureTask(transcript: string, tools?: MemoirToolInfo[]): string;
35
+ export declare function buildTurnCaptureTask(transcript: string): string;
37
36
  /**
38
37
  * Capture the latest completed turn of a session into memoir via the subagent.
39
38
  *
@@ -45,6 +44,6 @@ export declare function buildTurnCaptureTask(transcript: string, tools?: MemoirT
45
44
  * @param sessionID parent session id
46
45
  * @param lastCaptured per-session map of already-captured assistant ids
47
46
  */
48
- export declare function captureTurn(client: unknown, sessionID: string, lastCaptured: Map<string, string>, tools?: MemoirToolInfo[]): Promise<void>;
47
+ export declare function captureTurn(client: unknown, sessionID: string, lastCaptured: Map<string, string>): Promise<void>;
49
48
  export {};
50
49
  //# sourceMappingURL=capture.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQtD,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;AAyDD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,CASvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAoBzE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,SAAoB,GAC3B,UAAU,IAAI,MAAM,CAGtB;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,cAAc,EAAO,GAAG,MAAM,CAW7F;AAeD;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,KAAK,GAAE,cAAc,EAAO,GAC3B,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAQA,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;AAyDD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,CASvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAoBzE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,SAAoB,GAC3B,UAAU,IAAI,MAAM,CAGtB;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAeD;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
@@ -1 +1 @@
1
- {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAkEA,+EAA+E;AAC/E,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE5C"}
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAgFA,+EAA+E;AAC/E,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA8UrF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAyUrF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;eAEa,MAAM"}
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  import { appendFileSync, mkdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
+ var cachedLogFile = null;
6
+ var cachedLogRaw;
7
+ var cachedLogDate;
5
8
  function resolveLogFile() {
6
9
  const raw = process.env.MEMOIR_LOG;
7
10
  if (raw === "stderr") return null;
@@ -10,6 +13,16 @@ function resolveLogFile() {
10
13
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
11
14
  return join(stateHome, "opencode", `memoir-plugin-${date}.log`);
12
15
  }
16
+ function getCachedLogFile() {
17
+ const raw = process.env.MEMOIR_LOG;
18
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19
+ if (cachedLogRaw !== raw || cachedLogDate !== date) {
20
+ cachedLogFile = resolveLogFile();
21
+ cachedLogRaw = raw;
22
+ cachedLogDate = date;
23
+ }
24
+ return cachedLogFile;
25
+ }
13
26
  function formatValue(value) {
14
27
  if (value instanceof Error) {
15
28
  if (process.env.MEMOIR_DEBUG === "1") {
@@ -30,7 +43,7 @@ function writeLog(args) {
30
43
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
31
44
  const line = `[memoir ${ts}] ${formatArgs(args)}
32
45
  `;
33
- const logFilePath = resolveLogFile();
46
+ const logFilePath = getCachedLogFile();
34
47
  if (logFilePath) {
35
48
  try {
36
49
  mkdirSync(dirname(logFilePath), { recursive: true });
@@ -101,7 +114,7 @@ async function runMemoirSubagent(client, parentSessionID, task) {
101
114
  {
102
115
  type: "subtask",
103
116
  agent: MEMOIR_AGENT_NAME,
104
- description: "memoir capture",
117
+ description: "capture",
105
118
  prompt: task,
106
119
  command: ""
107
120
  }
@@ -174,15 +187,8 @@ function shouldCaptureTurn(transcript, minChars = captureMinChars()) {
174
187
  return transcript !== null && transcript.length >= minChars;
175
188
  }
176
189
  var CAPTURE_TASK_TEMPLATE = loadPrompt("capture-task.tmpl");
177
- function buildTurnCaptureTask(transcript, tools = []) {
178
- const captureTools = tools.filter((tool) => tool.name !== MEMOIR_CHECKOUT_TOOL);
179
- const toolsSection = captureTools.length ? `Available memory tools (name \u2014 what it does):
180
- ${captureTools.map((tool) => `- ${tool.name} \u2014 ${tool.description}`).join("\n")}
181
- ` : "";
182
- return CAPTURE_TASK_TEMPLATE.replace("{{TOOLS_SECTION}}", toolsSection).replace(
183
- "{{TRANSCRIPT}}",
184
- transcript
185
- );
190
+ function buildTurnCaptureTask(transcript) {
191
+ return CAPTURE_TASK_TEMPLATE.replace("{{TRANSCRIPT}}", transcript);
186
192
  }
187
193
  function lastAssistantMessageId(messages) {
188
194
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -192,7 +198,7 @@ function lastAssistantMessageId(messages) {
192
198
  }
193
199
  return null;
194
200
  }
195
- async function captureTurn(client, sessionID, lastCaptured, tools = []) {
201
+ async function captureTurn(client, sessionID, lastCaptured) {
196
202
  if (!autoSaveEnabled()) {
197
203
  log("captureTurn: MEMOIR_AUTO_SAVE=0, skipping");
198
204
  return;
@@ -225,7 +231,7 @@ async function captureTurn(client, sessionID, lastCaptured, tools = []) {
225
231
  return;
226
232
  }
227
233
  log("captureTurn: submitting memoir subtask (transcript", transcript.length, "chars)");
228
- await runMemoirSubagent(client, sessionID, buildTurnCaptureTask(transcript, tools));
234
+ await runMemoirSubagent(client, sessionID, buildTurnCaptureTask(transcript));
229
235
  lastCaptured.set(sessionID, turnId);
230
236
  } catch (e) {
231
237
  log("captureTurn failed", e);
@@ -382,61 +388,68 @@ var MemoirRuntime = class {
382
388
  serverUrl = null;
383
389
  serverPort = null;
384
390
  startingServer = null;
385
- tools = null;
386
391
  cleanupClient() {
387
392
  this.state = null;
388
393
  this.connecting = null;
389
- this.tools = null;
390
394
  }
391
395
  async start() {
392
396
  if (this.serverUrl && this.serverProc) return this.serverUrl;
393
397
  if (this.startingServer) return this.startingServer;
394
398
  const start = (async () => {
395
- const port = this.serverPort ?? await pickFreeHighPort();
396
- this.serverPort = port;
397
- const url = new URL(`http://127.0.0.1:${port}/mcp`);
398
- const args = [
399
- ...this.command.slice(1),
400
- "--http",
401
- "--host",
402
- "127.0.0.1",
403
- "--port",
404
- String(port)
405
- ];
406
- const proc = spawn(this.command[0], args, {
407
- // Merge with the parent env so PATH (and other essentials) survive —
408
- // passing only `env` would wipe PATH and break command resolution.
409
- env: { ...process.env, ...this.env },
410
- // Run in the real (symlink-resolved) project dir so memoir-mcp's
411
- // portable store slug is identical whether the repo is entered via a
412
- // symlink or its real path.
413
- cwd: this.cwd ? safeRealpath(this.cwd) : process.cwd(),
414
- stdio: ["ignore", "ignore", "pipe"]
415
- });
416
- this.serverProc = proc;
417
- if (proc.stderr) {
418
- proc.stderr.on("data", (chunk) => {
419
- log("memoir-mcp:", chunk.toString().trimEnd());
399
+ const maxTries = 5;
400
+ for (let attempt = 0; attempt < maxTries; attempt++) {
401
+ const port = this.serverPort ?? await pickFreeHighPort();
402
+ this.serverPort = port;
403
+ const url = new URL(`http://127.0.0.1:${port}/mcp`);
404
+ const args = [
405
+ ...this.command.slice(1),
406
+ "--http",
407
+ "--host",
408
+ "127.0.0.1",
409
+ "--port",
410
+ String(port)
411
+ ];
412
+ const proc = spawn(this.command[0], args, {
413
+ env: { ...process.env, ...this.env },
414
+ cwd: this.cwd ? safeRealpath(this.cwd) : process.cwd(),
415
+ stdio: ["ignore", "ignore", "pipe"]
420
416
  });
417
+ if (proc.stderr) {
418
+ proc.stderr.on("data", (chunk) => {
419
+ log("memoir-mcp:", chunk.toString().trimEnd());
420
+ });
421
+ }
422
+ const exited = new Promise((resolve2) => {
423
+ proc.once("exit", (code) => resolve2(code));
424
+ });
425
+ await new Promise((resolve2, reject) => {
426
+ proc.once("spawn", resolve2);
427
+ proc.once("error", reject);
428
+ }).catch((e) => {
429
+ throw new Error(`memoir: failed to spawn ${this.command[0]}: ${errorMessage(e)}`);
430
+ });
431
+ this.serverProc = proc;
432
+ try {
433
+ await waitForPort(port);
434
+ } catch {
435
+ proc.kill();
436
+ await exited;
437
+ this.serverProc = null;
438
+ this.serverUrl = null;
439
+ this.serverPort = null;
440
+ this.cleanupClient();
441
+ continue;
442
+ }
443
+ if (this.serverProc === proc) {
444
+ this.serverUrl = url;
445
+ log("memoir-mcp HTTP server up at", url.toString());
446
+ return url;
447
+ }
448
+ this.serverUrl = url;
449
+ log("memoir-mcp HTTP server up at", url.toString());
450
+ return url;
421
451
  }
422
- proc.on("exit", (code) => {
423
- log("memoir-mcp HTTP server exited with code", code);
424
- if (this.serverProc !== proc) return;
425
- this.serverProc = null;
426
- this.serverUrl = null;
427
- this.startingServer = null;
428
- this.cleanupClient();
429
- });
430
- await new Promise((resolve2, reject) => {
431
- proc.once("spawn", resolve2);
432
- proc.once("error", reject);
433
- }).catch((e) => {
434
- throw new Error(`memoir: failed to spawn ${this.command[0]}: ${errorMessage(e)}`);
435
- });
436
- await waitForPort(port);
437
- this.serverUrl = url;
438
- log("memoir-mcp HTTP server up at", url.toString());
439
- return url;
452
+ throw new Error("memoir: could not start HTTP server after several retries");
440
453
  })();
441
454
  this.startingServer = start;
442
455
  try {
@@ -446,6 +459,7 @@ var MemoirRuntime = class {
446
459
  this.serverUrl = null;
447
460
  if (this.serverProc) this.serverProc.kill();
448
461
  this.serverProc = null;
462
+ this.serverPort = null;
449
463
  throw e;
450
464
  }
451
465
  }
@@ -475,24 +489,6 @@ var MemoirRuntime = class {
475
489
  }
476
490
  return this.connecting;
477
491
  }
478
- /** Discover the live tool catalog once per connection for the small-model prompt. */
479
- async listTools(client) {
480
- if (this.tools) return this.tools;
481
- try {
482
- const result = await client.listTools();
483
- this.tools = (result.tools ?? []).map((tool) => ({
484
- // OpenCode exposes remote MCP tools as <server>_<raw-name>. The server
485
- // is registered as "memoir", so the prompt must use the same names the
486
- // subagent actually sees (for example memoir_memoir_remember).
487
- name: `memoir_${tool.name}`,
488
- description: typeof tool.description === "string" ? tool.description : ""
489
- }));
490
- return this.tools;
491
- } catch (e) {
492
- log("MemoirRuntime.listTools failed", e);
493
- return [];
494
- }
495
- }
496
492
  /** Close the MCP client and stop this instance's HTTP server. */
497
493
  async close() {
498
494
  if (this.state) {
@@ -512,7 +508,10 @@ var MemoirRuntime = class {
512
508
  resolve2();
513
509
  return;
514
510
  }
515
- const timer = setTimeout(resolve2, 1e3);
511
+ const timer = setTimeout(() => {
512
+ proc.kill("SIGKILL");
513
+ resolve2();
514
+ }, 1e3);
516
515
  timer.unref();
517
516
  proc.once("exit", () => {
518
517
  clearTimeout(timer);
@@ -531,14 +530,13 @@ var MemoirRuntime = class {
531
530
  this.serverUrl = null;
532
531
  this.startingServer = null;
533
532
  this.connecting = null;
534
- this.tools = null;
535
533
  }
536
534
  };
537
535
  async function callMemoirTool(client, name, args) {
538
536
  try {
539
537
  const result = await client.callTool({
540
538
  name,
541
- arguments: args
539
+ arguments: args ?? {}
542
540
  });
543
541
  if (result.isError) {
544
542
  log(`callMemoirTool: ${name} returned error`);
@@ -663,17 +661,13 @@ var MemoirOpenCode = async (input, rawOptions) => {
663
661
  return null;
664
662
  }
665
663
  };
666
- const discoverTools = async () => {
667
- const client = await connectClient();
668
- return client ? runtime.listTools(client) : [];
669
- };
670
664
  const dispatchCapture = async (sid) => {
671
665
  if (!sdkClient || !parentSessions.has(sid) || capturePending.has(sid)) return;
672
666
  capturePending.add(sid);
673
667
  try {
674
668
  const client = await connectClient();
675
669
  if (client) await branchMatcher.match(client, directory, () => captureLifecycle.drain());
676
- await captureTurn(sdkClient, sid, lastCaptured, await discoverTools());
670
+ await captureTurn(sdkClient, sid, lastCaptured);
677
671
  } catch (e) {
678
672
  log("dispatchCapture failed", e);
679
673
  } finally {
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/debug.ts", "../src/prompts.ts", "../src/subagent.ts", "../src/capture.ts", "../src/capture-lifecycle.ts", "../src/mcp-client.ts", "../src/path.ts", "../src/store.ts", "../src/status.ts", "../src/turn-status.ts", "../src/index.ts"],
4
- "sourcesContent": ["import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Plugin logging.\n *\n * Logs go to a file (never stderr) so they do not pollute the opencode\n * terminal. The destination is controlled by MEMOIR_LOG:\n * - unset \u2192 $XDG_STATE_HOME/opencode/memoir-plugin-YYYY-MM-DD.log\n * (falls back to ~/.local/state/opencode/...; daily rotation)\n * - \"stderr\" \u2192 stderr, for live local debugging without a separate `tail`\n * - <path> \u2192 an explicit log file path\n *\n * `log(...)` always writes. MEMOIR_DEBUG=1 adds diagnostic detail: Error\n * objects include their name and stack instead of only their message. For\n * local verification, `tail -f` the log file.\n */\n\n/**\n * Resolve the log destination on each write so MEMOIR_LOG changes (and tests)\n * take effect without a reimport. `null` means \"write to stderr\".\n */\nfunction resolveLogFile(): string | null {\n const raw = process.env.MEMOIR_LOG;\n if (raw === \"stderr\") return null;\n if (raw) return raw;\n const stateHome = process.env.XDG_STATE_HOME || join(homedir(), \".local\", \"state\");\n const date = new Date().toISOString().slice(0, 10);\n return join(stateHome, \"opencode\", `memoir-plugin-${date}.log`);\n}\n\nfunction formatValue(value: unknown): string {\n if (value instanceof Error) {\n if (process.env.MEMOIR_DEBUG === \"1\") {\n return value.stack ?? `${value.name}: ${value.message}`;\n }\n return value.message;\n }\n return String(value);\n}\n\nfunction formatArgs(args: unknown[]): string {\n const [message, ...details] = args;\n if (typeof message === \"string\" && details[0] instanceof Error) {\n return `${message.replace(/:\\s*$/, \"\")}: ${details.map(formatValue).join(\" \")}`;\n }\n return args.map(formatValue).join(\" \");\n}\n\nfunction writeLog(args: unknown[]): void {\n const ts = new Date().toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n const line = `[memoir ${ts}] ${formatArgs(args)}\\n`;\n const logFilePath = resolveLogFile();\n if (logFilePath) {\n try {\n mkdirSync(dirname(logFilePath), { recursive: true });\n appendFileSync(logFilePath, line);\n return;\n } catch {\n // fall through to stderr if the file is unavailable\n }\n }\n process.stderr.write(line);\n}\n\n/** Single logging entrypoint; MEMOIR_DEBUG=1 controls detail, not emission. */\nexport function log(...args: unknown[]): void {\n writeLog(args);\n}\n", "import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nconst cache = new Map<string, string>();\n\n/**\n * Load a prompt template from src/prompts (dev / tsx) or dist/prompts (bundled).\n * Cached per file. Templates are plain text with ${TOKEN} placeholders that the\n * caller substitutes. Keeping prompts as standalone files (not TS string\n * literals) makes them easy to read and edit in isolation \u2014 mirroring how the\n * upstream memoir plugin keeps hooks/prompts/*.tmpl as first-class artifacts.\n */\nexport function loadPrompt(file: string): string {\n const cached = cache.get(file);\n if (cached !== undefined) return cached;\n const path = fileURLToPath(new URL(`./prompts/${file}`, import.meta.url));\n const text = readFileSync(path, \"utf8\");\n cache.set(file, text);\n return text;\n}\n", "import type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { log } from \"./debug.js\";\nimport { loadPrompt } from \"./prompts.js\";\n\n// opencode restricts an agent's available tools through its `permission`\n// ruleset (NOT a `tools` allowlist \u2014 that field is ignored for config agents).\n// `visibleTools` hides a tool only when the LAST rule matching its name has\n// `{ pattern: \"*\", action: \"deny\" }`. So we deny \"*\" first, then re-allow\n// the memoir_* MCP tools (and doom_loop) after it, then deny checkout. Order\n// matters: the specific checkout deny must be LAST so findLast keeps branch\n// ownership in the plugin while every other memoir tool remains available.\n//\n// The SDK's AgentConfig.permission type is narrower than what opencode's\n// runtime accepts (object form \u2192 Permission.fromConfig), hence the loose\n// intersection below.\ntype MemoirAgentPermission = Record<string, string>;\nexport type MemoirAgentSpec = AgentConfig & {\n permission: MemoirAgentPermission;\n steps?: number;\n};\n\nexport const MEMOIR_AGENT_NAME = \"memoir\";\nexport const MEMOIR_CHECKOUT_TOOL = \"memoir_memoir_checkout\";\n\n// System prompt for the memoir subagent, loaded from prompts/subagent-system.tmpl.\n// It is intentionally tool-free: the subagent is locked to the memoir_* tools\n// via its `permission` ruleset (see buildMemoirAgent), and opencode already\n// exposes those tools to it. The exact tool catalog \u2014 names + descriptions \u2014 is\n// injected per call into the task prompt by buildTurnCaptureTask, so this prompt\n// stays generic and reusable across memoir-mcp versions.\nconst MEMOIR_AGENT_PROMPT = loadPrompt(\"subagent-system.tmpl\");\n\n/**\n * Build the memoir subagent config.\n *\n * Locked to memoir_* tools except checkout (everything else denied) so it can\n * never touch the filesystem, shell, network, or the store-global branch.\n * opencode restricts an agent's tools through its `permission` ruleset \u2014 see\n * MemoirAgentSpec for why the checkout deny must follow the broad allow. Mode\n * \"subagent\" makes\n * opencode spawn it as a detached child session, rendered as a collapsed task\n * (visible in the parent timeline) when prompted via\n * client.session.prompt({ body: { agent: \"memoir\" } }).\n */\nexport function buildMemoirAgent(model?: string): MemoirAgentSpec {\n const agent: MemoirAgentSpec = {\n mode: \"subagent\",\n description:\n \"Memoir memory agent \u2014 captures durable facts and recalls prior context via the memoir MCP server.\",\n prompt: MEMOIR_AGENT_PROMPT,\n permission: {\n \"*\": \"deny\",\n doom_loop: \"allow\",\n \"memoir_*\": \"allow\",\n [MEMOIR_CHECKOUT_TOOL]: \"deny\",\n },\n temperature: 0,\n steps: 8,\n };\n if (model) agent.model = model;\n return agent;\n}\n\ninterface ModelResolution {\n summarizeModel?: string;\n smallModel?: string;\n model?: string;\n}\n\n/**\n * Resolve the model the memoir subagent should use.\n *\n * Priority:\n * 1. MEMOIR_AGENT_MODEL env (provider/model)\n * 2. config.small_model\n * 3. config.model\n * 4. omit \u2014 let opencode pick the session default\n *\n * Only \"provider/model\" shaped values are accepted; anything else is ignored\n * so we never hand opencode a bare model id it cannot resolve.\n */\nexport function resolveMemoirModel(opts: ModelResolution): string | undefined {\n const candidates = [\n process.env.MEMOIR_AGENT_MODEL?.trim(),\n opts.summarizeModel,\n opts.smallModel,\n opts.model,\n ];\n return candidates.find((candidate) => candidate?.includes(\"/\"));\n}\n\n/** Queue a visible subtask through OpenCode's supported promptAsync input API. */\nexport async function runMemoirSubagent(\n client: unknown,\n parentSessionID: string,\n task: string,\n): Promise<void> {\n type SubtaskPartInput = {\n type: \"subtask\";\n agent: string;\n description: string;\n prompt: string;\n command?: string;\n };\n type PromptAsync = (options: {\n path: { id: string };\n body: { parts: SubtaskPartInput[] };\n }) => Promise<unknown>;\n\n const api = (client as { session?: { promptAsync?: PromptAsync } } | null | undefined)?.session;\n if (!api?.promptAsync) {\n throw new Error(\"client.session.promptAsync unavailable\");\n }\n\n try {\n await api.promptAsync({\n path: { id: parentSessionID },\n body: {\n parts: [\n {\n type: \"subtask\",\n agent: MEMOIR_AGENT_NAME,\n description: \"memoir capture\",\n prompt: task,\n command: \"\",\n },\n ],\n },\n });\n log(\"memoir subtask accepted (session\", parentSessionID, \", task\", task.length, \"chars)\");\n } catch (e) {\n log(\"runMemoirSubagent failed\", e);\n throw e;\n }\n}\n", "import { log } from \"./debug.js\";\nimport type { MemoirToolInfo } from \"./mcp-client.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { MEMOIR_CHECKOUT_TOOL, runMemoirSubagent } from \"./subagent.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ChatPart {\n type?: string;\n text?: string;\n [key: string]: unknown;\n}\n\nexport interface ChatMessage {\n info?: { id?: string; role?: string };\n parts?: ChatPart[];\n}\n\n// Minimal structural view of the SDK client we use to pull a session transcript.\n// The SDK exposes this as `client.session.messages` (singular `session`).\ntype SessionMessagesClient = {\n session: {\n messages(input: {\n path: { id: string };\n query?: { limit?: number };\n }): Promise<{ data?: unknown }>;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CAPTURE_MIN_CHARS = 16;\n\nfunction captureMinChars(): number {\n const raw = process.env.MEMOIR_CAPTURE_MIN_CHARS?.trim();\n if (raw === undefined || raw === \"\") return DEFAULT_CAPTURE_MIN_CHARS;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : DEFAULT_CAPTURE_MIN_CHARS;\n}\n\nfunction autoSaveEnabled(): boolean {\n // ENABLED by default; only an explicit \"0\" disables it.\n return process.env.MEMOIR_AUTO_SAVE !== \"0\";\n}\n\n// ---------------------------------------------------------------------------\n// Transcript helpers\n// ---------------------------------------------------------------------------\n\nfunction messageText(msg: ChatMessage): string {\n const parts = msg.parts ?? [];\n return parts\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\")\n .map((p) => p.text as string)\n .join(\"\\n\")\n .trim();\n}\n\nfunction formatRole(role: string | undefined): string {\n switch (role) {\n case \"user\":\n return \"USER\";\n case \"assistant\":\n return \"ASSISTANT\";\n case \"system\":\n return \"SYSTEM\";\n default:\n return (role ?? \"unknown\").toUpperCase();\n }\n}\n\n/**\n * Render a list of messages into a plain-text transcript.\n * Skips empty (no text) messages so tool-only turns stay quiet.\n */\nexport function formatSessionTranscript(messages: ChatMessage[]): string {\n return messages\n .map((m) => {\n const text = messageText(m);\n if (!text) return null;\n return `${formatRole(m.info?.role)}\\n${text}`;\n })\n .filter((line): line is string => line !== null)\n .join(\"\\n\\n\");\n}\n\n/**\n * Extract the most recent completed turn: the last assistant message that has\n * text, plus the user message(s) immediately preceding it. Returns null when\n * there is no completed assistant turn yet.\n */\nexport function lastTurnTranscript(messages: ChatMessage[]): string | null {\n let assistantIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"assistant\" && messageText(messages[i])) {\n assistantIdx = i;\n break;\n }\n }\n if (assistantIdx < 0) return null;\n\n const userIdx = (() => {\n for (let i = assistantIdx - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"user\") return i;\n }\n return -1;\n })();\n if (userIdx < 0) return null;\n\n const turn = messages.slice(userIdx, assistantIdx + 1);\n return formatSessionTranscript(turn);\n}\n\n/**\n * Local, LLM-free pre-filter. When MEMOIR_CAPTURE_MIN_CHARS (default 16) is\n * > 0, only transcripts at least that long are worth sending to the subagent.\n * A value of 0 disables the filter (capture everything).\n */\nexport function shouldCaptureTurn(\n transcript: string | null,\n minChars = captureMinChars(),\n): transcript is string {\n if (minChars <= 0) return transcript !== null && transcript.length > 0;\n return transcript !== null && transcript.length >= minChars;\n}\n\n// ---------------------------------------------------------------------------\n// Task builder (mirrors plugins/claude-code/hooks/stop_capture.tmpl)\n// ---------------------------------------------------------------------------\n\nconst CAPTURE_TASK_TEMPLATE = loadPrompt(\"capture-task.tmpl\");\n\n/**\n * Build the capture task handed to the memoir subagent. It applies the\n * durability checks, writes each fact with an explicit 3-level taxonomy path,\n * and returns a compact report based only on confirmed tool outcomes.\n */\nexport function buildTurnCaptureTask(transcript: string, tools: MemoirToolInfo[] = []): string {\n const captureTools = tools.filter((tool) => tool.name !== MEMOIR_CHECKOUT_TOOL);\n const toolsSection = captureTools.length\n ? `Available memory tools (name \u2014 what it does):\\n${captureTools\n .map((tool) => `- ${tool.name} \u2014 ${tool.description}`)\n .join(\"\\n\")}\\n`\n : \"\";\n return CAPTURE_TASK_TEMPLATE.replace(\"{{TOOLS_SECTION}}\", toolsSection).replace(\n \"{{TRANSCRIPT}}\",\n transcript,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Capture orchestration\n// ---------------------------------------------------------------------------\n\nfunction lastAssistantMessageId(messages: ChatMessage[]): string | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"assistant\" && messageText(messages[i])) {\n return messages[i].info?.id ?? null;\n }\n }\n return null;\n}\n\n/**\n * Capture the latest completed turn of a session into memoir via the subagent.\n *\n * Fire-and-forget: the caller should NOT await the subagent's completion.\n * Deduped per session by the last assistant message id, so a turn is written\n * at most once. Skips entirely when MEMOIR_AUTO_SAVE=0.\n *\n * @param client SDK client (transcript source + subagent spawner)\n * @param sessionID parent session id\n * @param lastCaptured per-session map of already-captured assistant ids\n */\nexport async function captureTurn(\n client: unknown,\n sessionID: string,\n lastCaptured: Map<string, string>,\n tools: MemoirToolInfo[] = [],\n): Promise<void> {\n if (!autoSaveEnabled()) {\n log(\"captureTurn: MEMOIR_AUTO_SAVE=0, skipping\");\n return;\n }\n log(\"captureTurn: started for session\", sessionID);\n\n try {\n const api = (client as SessionMessagesClient | null | undefined)?.session;\n if (!api?.messages) {\n log(\"captureTurn: client.session.messages unavailable\");\n log(\"captureTurn: skipped \u2014 session messages API unavailable\");\n return;\n }\n\n const res = await api.messages({\n path: { id: sessionID },\n query: { limit: 50 },\n });\n const raw = res.data;\n const messages: ChatMessage[] = Array.isArray(raw) ? (raw as ChatMessage[]) : [];\n if (messages.length === 0) return;\n\n const turnId = lastAssistantMessageId(messages);\n if (!turnId) return;\n if (lastCaptured.get(sessionID) === turnId) {\n log(\"captureTurn: skipped \u2014 already captured this turn\");\n return;\n }\n\n const transcript = lastTurnTranscript(messages);\n if (!shouldCaptureTurn(transcript)) {\n log(\"captureTurn: skipped \u2014 transcript below min-chars\");\n lastCaptured.set(sessionID, turnId);\n return;\n }\n\n log(\"captureTurn: submitting memoir subtask (transcript\", transcript.length, \"chars)\");\n await runMemoirSubagent(client, sessionID, buildTurnCaptureTask(transcript, tools));\n lastCaptured.set(sessionID, turnId);\n } catch (e) {\n log(\"captureTurn failed\", e);\n }\n}\n", "import { log } from \"./debug.js\";\n\ntype ActiveCapture = {\n done: Promise<void>;\n resolve: () => void;\n childSessionID?: string;\n};\n\n/** Track actual memoir task execution, including native background jobs. */\nexport class CaptureLifecycle {\n private readonly active = new Map<string, ActiveCapture>();\n private readonly callsByChild = new Map<string, string>();\n\n begin(callID: string): void {\n if (this.active.has(callID)) return;\n let resolve!: () => void;\n const done = new Promise<void>((doneResolve) => {\n resolve = doneResolve;\n });\n this.active.set(callID, { done, resolve });\n }\n\n background(callID: string, childSessionID: string): void {\n const capture = this.active.get(callID);\n if (!capture) return;\n capture.childSessionID = childSessionID;\n this.callsByChild.set(childSessionID, callID);\n }\n\n finishCall(callID: string): void {\n const capture = this.active.get(callID);\n if (!capture) return;\n if (capture.childSessionID) this.callsByChild.delete(capture.childSessionID);\n this.active.delete(callID);\n capture.resolve();\n }\n\n finishSession(sessionID: string): void {\n const callID = this.callsByChild.get(sessionID);\n if (callID) this.finishCall(callID);\n }\n\n async drain(timeoutMs = 10_000): Promise<boolean> {\n const pending = [...this.active.values()].map((capture) => capture.done);\n if (pending.length === 0) return true;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n const drained = Promise.all(pending).then(() => true as const);\n const result = await Promise.race([drained, timedOut]);\n if (timer) clearTimeout(timer);\n if (!result) log(\"capture lifecycle: drain timed out; branch switch deferred\");\n return result;\n }\n\n clear(): void {\n for (const callID of [...this.active.keys()]) this.finishCall(callID);\n }\n}\n", "import { type ChildProcess, spawn } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { log } from \"./debug.js\";\nimport { safeRealpath } from \"./path.js\";\n\ninterface MemoirClientState {\n client: Client;\n}\n\nexport type MemoirToolInfo = {\n name: string;\n description: string;\n};\n\ntype ClientConnection = {\n client: Client;\n transport: StreamableHTTPClientTransport;\n};\n\nexport type MemoirRuntimeDependencies = {\n createClientConnection?: (url: URL) => ClientConnection;\n};\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n// Random free port from the upper (ephemeral) range.\nconst LOW_PORT = 49152;\nconst HIGH_PORT = 65535;\n\nfunction randomHighPort(): number {\n return LOW_PORT + Math.floor(Math.random() * (HIGH_PORT - LOW_PORT + 1));\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = createServer();\n srv.once(\"error\", () => resolve(false));\n srv.once(\"listening\", () => srv.close(() => resolve(true)));\n srv.listen(port, \"127.0.0.1\");\n });\n}\n\nasync function pickFreeHighPort(tries = 20): Promise<number> {\n for (let i = 0; i < tries; i++) {\n const port = randomHighPort();\n if (await isPortFree(port)) return port;\n }\n throw new Error(\"memoir: could not find a free high port for the HTTP server\");\n}\n\nfunction waitForPort(port: number, timeoutMs = 10_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n return new Promise((resolve, reject) => {\n const attempt = () => {\n const sock = createConnection({ port, host: \"127.0.0.1\" });\n sock.once(\"connect\", () => {\n sock.destroy();\n resolve();\n });\n sock.once(\"error\", () => {\n sock.destroy();\n if (Date.now() > deadline) {\n reject(new Error(\"memoir: HTTP server did not start in time\"));\n } else {\n setTimeout(attempt, 100);\n }\n });\n };\n attempt();\n });\n}\n\n/**\n * One memoir-mcp process and internal MCP client owned by one plugin instance.\n * OpenCode can host several project directories in one process, so none of\n * this state may live at module scope.\n */\nexport class MemoirRuntime {\n private state: MemoirClientState | null = null;\n private connecting: Promise<Client> | null = null;\n private serverProc: ChildProcess | null = null;\n private serverUrl: URL | null = null;\n private serverPort: number | null = null;\n private startingServer: Promise<URL> | null = null;\n private tools: MemoirToolInfo[] | null = null;\n\n constructor(\n private readonly command: string[],\n private readonly env?: Record<string, string>,\n private readonly cwd?: string,\n private readonly dependencies: MemoirRuntimeDependencies = {},\n ) {}\n\n private cleanupClient(): void {\n this.state = null;\n this.connecting = null;\n this.tools = null;\n }\n\n async start(): Promise<URL> {\n if (this.serverUrl && this.serverProc) return this.serverUrl;\n if (this.startingServer) return this.startingServer;\n\n const start = (async () => {\n // Keep the first selected port for this plugin instance. OpenCode stores\n // the remote URL during config and cannot be pointed at a new random URL\n // after a child-process crash.\n const port = this.serverPort ?? (await pickFreeHighPort());\n this.serverPort = port;\n const url = new URL(`http://127.0.0.1:${port}/mcp`);\n\n // HTTP flags appended to the base command (which already carries --store).\n const args = [\n ...this.command.slice(1),\n \"--http\",\n \"--host\",\n \"127.0.0.1\",\n \"--port\",\n String(port),\n ];\n\n const proc = spawn(this.command[0], args, {\n // Merge with the parent env so PATH (and other essentials) survive \u2014\n // passing only `env` would wipe PATH and break command resolution.\n env: { ...process.env, ...this.env },\n // Run in the real (symlink-resolved) project dir so memoir-mcp's\n // portable store slug is identical whether the repo is entered via a\n // symlink or its real path.\n cwd: this.cwd ? safeRealpath(this.cwd) : process.cwd(),\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n this.serverProc = proc;\n\n // Route the child's stderr to the debug log instead of the UI.\n if (proc.stderr) {\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n log(\"memoir-mcp:\", chunk.toString().trimEnd());\n });\n }\n proc.on(\"exit\", (code) => {\n log(\"memoir-mcp HTTP server exited with code\", code);\n // close() may already have killed this process and started another.\n // A late exit from the old child must not wipe the replacement state.\n if (this.serverProc !== proc) return;\n this.serverProc = null;\n this.serverUrl = null;\n this.startingServer = null;\n this.cleanupClient();\n });\n\n await new Promise<void>((resolve, reject) => {\n proc.once(\"spawn\", resolve);\n proc.once(\"error\", reject);\n }).catch((e: unknown) => {\n throw new Error(`memoir: failed to spawn ${this.command[0]}: ${errorMessage(e)}`);\n });\n\n await waitForPort(port);\n this.serverUrl = url;\n log(\"memoir-mcp HTTP server up at\", url.toString());\n return url;\n })();\n\n this.startingServer = start;\n try {\n return await start;\n } catch (e) {\n this.startingServer = null;\n this.serverUrl = null;\n if (this.serverProc) this.serverProc.kill();\n this.serverProc = null;\n throw e;\n }\n }\n\n /** Get the lazily-connected internal client for this plugin instance. */\n async connect(): Promise<Client> {\n if (this.state) return this.state.client;\n\n if (!this.connecting) {\n this.connecting = (async () => {\n const url = await this.start();\n const { client, transport } = this.dependencies.createClientConnection?.(url) ?? {\n client: new Client({ name: \"opencode-memoir\", version: \"1.0.0\" }, { capabilities: {} }),\n transport: new StreamableHTTPClientTransport(url),\n };\n\n transport.onclose = () => {\n log(\"mcp-client: transport closed, resetting state\");\n this.cleanupClient();\n };\n\n try {\n await client.connect(transport);\n } catch (e) {\n this.cleanupClient();\n throw e;\n }\n\n this.state = { client };\n return client;\n })();\n }\n\n return this.connecting;\n }\n\n /** Discover the live tool catalog once per connection for the small-model prompt. */\n async listTools(client: Client): Promise<MemoirToolInfo[]> {\n if (this.tools) return this.tools;\n try {\n const result = await client.listTools();\n this.tools = (result.tools ?? []).map((tool) => ({\n // OpenCode exposes remote MCP tools as <server>_<raw-name>. The server\n // is registered as \"memoir\", so the prompt must use the same names the\n // subagent actually sees (for example memoir_memoir_remember).\n name: `memoir_${tool.name}`,\n description: typeof tool.description === \"string\" ? tool.description : \"\",\n }));\n return this.tools;\n } catch (e) {\n log(\"MemoirRuntime.listTools failed\", e);\n return [];\n }\n }\n\n /** Close the MCP client and stop this instance's HTTP server. */\n async close(): Promise<void> {\n if (this.state) {\n try {\n await this.state.client.close();\n } catch (e) {\n log(\"closeMemoirClient: close failed\", e);\n }\n this.cleanupClient();\n }\n if (this.serverProc) {\n const proc = this.serverProc;\n this.serverProc = null;\n try {\n await new Promise<void>((resolve) => {\n if (proc.exitCode !== null) {\n resolve();\n return;\n }\n const timer = setTimeout(resolve, 1_000);\n timer.unref();\n proc.once(\"exit\", () => {\n clearTimeout(timer);\n resolve();\n });\n proc.once(\"error\", () => {\n clearTimeout(timer);\n resolve();\n });\n proc.kill();\n });\n } catch (e) {\n log(\"closeMemoirClient: kill failed\", e);\n }\n }\n this.serverUrl = null;\n this.startingServer = null;\n this.connecting = null;\n this.tools = null;\n }\n}\n\n/**\n * Wrapper around client.callTool that extracts text content.\n * Returns the first `{ type: \"text\" }` content block's text, or null.\n */\nexport async function callMemoirTool(\n client: Client,\n name: string,\n args?: Record<string, unknown>,\n): Promise<string | null> {\n try {\n const result = (await client.callTool({\n name,\n arguments: args,\n })) as { content: Array<{ type: string; text?: string }>; isError?: boolean };\n if (result.isError) {\n log(`callMemoirTool: ${name} returned error`);\n return null;\n }\n for (const block of result.content) {\n if (block.type === \"text\" && block.text != null) {\n return block.text;\n }\n }\n return null;\n } catch (e) {\n log(`callMemoirTool: ${name} failed`, e);\n return null;\n }\n}\n", "import { execFileSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\n/** Resolve a path through symlinks, falling back to the input on failure. */\nexport function safeRealpath(p: string): string {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/** Turn an absolute path into a filesystem-safe slug for store naming. */\nexport function slugify(p: string): string {\n return p.replace(/[/.]/g, \"-\");\n}\n\n/**\n * Resolve the memoir store path.\n * 1. `override` (plugin `store` option)\n * 2. MEMOIR_STORE env var\n * 3. Git root \u2192 `~/.memoir/<slug>` (all worktrees share one store)\n * 4. Resolved cwd \u2192 `~/.memoir/<slug>` for non-git dirs\n *\n * The project directory is realpath'd before slugifying so the store is\n * identical whether the repo is entered via a symlink or its real path.\n */\nexport function deriveStorePath(cwd: string = process.cwd(), override?: string): string {\n if (override) return override;\n const envStore = process.env.MEMOIR_STORE;\n if (envStore) return envStore;\n\n const realCwd = safeRealpath(cwd);\n let projectDir: string;\n try {\n const gitRoot = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd: realCwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3_000,\n }).trim();\n projectDir = safeRealpath(gitRoot);\n } catch {\n projectDir = resolve(realCwd);\n }\n\n return join(homedir(), \".memoir\", slugify(projectDir));\n}\n", "import { execFileSync } from \"node:child_process\";\nimport type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { callMemoirTool } from \"./mcp-client.js\";\nimport { deriveStorePath as deriveStorePathBase, safeRealpath } from \"./path.js\";\nimport { parseMemoirStatus } from \"./status.js\";\n\n/** Resolve the memoir store path, honoring the plugin `store` option override. */\nexport function deriveStorePath(cwd?: string, override?: string): string {\n return deriveStorePathBase(cwd, override);\n}\n\n/** Get current git branch (empty string if not in a git repo). */\nexport function currentGitBranch(cwd = process.cwd()): string {\n try {\n return execFileSync(\"git\", [\"branch\", \"--show-current\"], {\n cwd: safeRealpath(cwd),\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3_000,\n }).trim();\n } catch {\n return \"\";\n }\n}\n\n/**\n * Track the actual branch of one memoir store. The checkout is store-global,\n * so a per-session cache would become stale as soon as another session changed\n * the branch.\n */\nexport class MemoirBranchMatcher {\n private matching: Promise<void> = Promise.resolve();\n\n match(client: Client, cwd: string, drain?: () => Promise<boolean>): Promise<void> {\n const next = this.matching.then(() => this.matchNow(client, cwd, drain));\n this.matching = next.catch(() => undefined);\n return next;\n }\n\n private async currentBranch(client: Client): Promise<string> {\n const raw = await callMemoirTool(client, \"memoir_status\");\n return parseMemoirStatus(raw).branch ?? \"\";\n }\n\n private async matchNow(\n client: Client,\n cwd: string,\n drain?: () => Promise<boolean>,\n ): Promise<void> {\n const codeBranch = currentGitBranch(cwd);\n if (!codeBranch) return;\n\n if ((await this.currentBranch(client)) === codeBranch) return;\n if (drain && !(await drain())) return;\n // A capture or external client may have changed HEAD while we waited.\n if ((await this.currentBranch(client)) === codeBranch) return;\n\n // memoir_checkout (per the v0.2.4 contract) switches to a branch, creating\n // it on demand when `create` is true. A single call replaces the old\n // branches-list \u2192 branch \u2192 checkout CLI sequence.\n await callMemoirTool(client, \"memoir_checkout\", {\n target: codeBranch,\n create: true,\n });\n }\n\n clear(): void {\n this.matching = Promise.resolve();\n }\n}\n", "/**\n * Parsed shape of the `memoir_status` tool response.\n *\n * Both the per-turn model status (turn-status.ts) and the branch matcher\n * (store.ts) read this payload, so the parse lives here once instead of being\n * duplicated with two slightly different decoders.\n */\nexport type MemoirStatus = {\n branch?: string;\n memory_count?: number;\n};\n\n/**\n * Decode a `memoir_status` text payload into a typed, sanitized object.\n * Returns an empty object on a missing or malformed payload so callers can\n * treat every field as optional without guarding parse errors themselves.\n */\nexport function parseMemoirStatus(raw: string | null | undefined): MemoirStatus {\n if (!raw) return {};\n try {\n const status = JSON.parse(raw) as MemoirStatus;\n return {\n branch: typeof status.branch === \"string\" ? status.branch : undefined,\n memory_count: typeof status.memory_count === \"number\" ? status.memory_count : undefined,\n };\n } catch {\n return {};\n }\n}\n", "import { parseMemoirStatus } from \"./status.js\";\n\nexport function buildTurnStatus(statusRaw: string | null): string {\n const { branch, memory_count } = parseMemoirStatus(statusRaw);\n const memoryCount = memory_count ?? 0;\n if (!branch && memoryCount <= 0) return \"\";\n if (!branch) return `[memoir] memory available (${memoryCount} memories)`;\n return `[memoir] ${branch}${memoryCount > 0 ? ` \u00B7 memory available (${memoryCount} memories)` : \"\"}`;\n}\n", "import type { Config, Plugin, PluginInput, PluginModule } from \"@opencode-ai/plugin\";\nimport type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { captureTurn } from \"./capture.js\";\nimport { CaptureLifecycle } from \"./capture-lifecycle.js\";\nimport { log } from \"./debug.js\";\nimport { callMemoirTool, MemoirRuntime } from \"./mcp-client.js\";\nimport { safeRealpath } from \"./path.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { deriveStorePath, MemoirBranchMatcher } from \"./store.js\";\nimport { buildMemoirAgent, MEMOIR_AGENT_NAME, resolveMemoirModel } from \"./subagent.js\";\nimport { buildTurnStatus } from \"./turn-status.js\";\n\nfunction envFlag(name: string): boolean | undefined {\n const value = process.env[name]?.trim().toLowerCase();\n if (value === undefined || value === \"\") return undefined;\n return value === \"1\" || value === \"true\";\n}\n\nfunction backgroundSubagentsEnabled(): boolean {\n return (\n envFlag(\"OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS\") ??\n envFlag(\"OPENCODE_EXPERIMENTAL\") ??\n false\n );\n}\n\nconst MemoirOpenCode: Plugin = async (input, rawOptions) => {\n const opts = (rawOptions ?? {}) as { store?: string };\n // SDK client (for subagent spawning + transcript reads) and working dir come\n // from the plugin input. Guard for callers that pass no input.\n const pluginInput = (input ?? {}) as PluginInput;\n const sdkClient = pluginInput.client;\n const directory = safeRealpath(pluginInput.directory ?? process.cwd());\n const storePath = deriveStorePath(directory, opts.store);\n\n const mcpCommand = [\"memoir-mcp\"];\n if (storePath) {\n mcpCommand.push(\"--store\", storePath);\n }\n\n const environment = storePath ? { MEMOIR_STORE: storePath } : undefined;\n const runtime = new MemoirRuntime(mcpCommand, environment, directory);\n const branchMatcher = new MemoirBranchMatcher();\n const sessionsWithStartupHint = new Set<string>();\n const parentSessions = new Set<string>();\n const lastCaptured = new Map<string, string>();\n const capturePending = new Set<string>();\n const captureLifecycle = new CaptureLifecycle();\n const messageCounts = new Map<string, number>();\n const turnStatus = new Map<string, string>();\n\n // The plugin owns a single shared memoir-mcp HTTP server (started in the\n // config hook). opencode connects to it as a remote MCP server; the plugin's\n // own hooks use the same URL via the internal client. One process, no stdio\n // double-spawn. Populated once the HTTP server is up.\n let mcpServer: { type: \"remote\"; url: string; enabled: boolean } | undefined;\n\n // Plugin's own MCP client (separate from the LLM-facing mcpServer).\n // Lazily connected on first use so merely loading the plugin \u2014 or running\n // hooks that never touch memoir \u2014 spawns no subprocess.\n const connectClient = async () => {\n try {\n return await runtime.connect();\n } catch (e) {\n log(\"plugin: failed to connect memoir client\", e);\n return null;\n }\n };\n\n const discoverTools = async () => {\n const client = await connectClient();\n return client ? runtime.listTools(client) : [];\n };\n\n const dispatchCapture = async (sid: string): Promise<void> => {\n if (!sdkClient || !parentSessions.has(sid) || capturePending.has(sid)) return;\n capturePending.add(sid);\n try {\n const client = await connectClient();\n if (client) await branchMatcher.match(client, directory, () => captureLifecycle.drain());\n await captureTurn(sdkClient, sid, lastCaptured, await discoverTools());\n } catch (e) {\n log(\"dispatchCapture failed\", e);\n } finally {\n capturePending.delete(sid);\n }\n };\n\n const hooks: Record<string, unknown> = {\n name: \"memoir\",\n\n config: async (config: Config): Promise<void> => {\n log(\"config hook: mcpCommand =\", JSON.stringify(mcpCommand));\n\n try {\n const url = await runtime.start();\n mcpServer = { type: \"remote\", url: url.toString(), enabled: true };\n log(\"memoir MCP server registered at\", url.toString(), \"| store:\", storePath);\n } catch (e) {\n log(\"config hook: failed to start memoir HTTP server\", e);\n mcpServer = undefined;\n }\n\n const mcp = config.mcp ?? {};\n if (mcpServer) mcp.memoir = mcpServer;\n config.mcp = mcp;\n\n // Register on the returned hooks object too \u2014 opencode reads hooks.mcp\n // for the MCP server, and it must reflect the (now known) URL.\n if (mcpServer) hooks.mcp = { memoir: mcpServer };\n\n // Register the memoir subagent (capture + recall). Its model resolves\n // from MEMOIR_AGENT_MODEL \u2192 small_model \u2192 model \u2192 opencode default.\n // opencode reads the agent from `config.agent` (singular), not `agents`.\n const agentModel = resolveMemoirModel({\n summarizeModel: process.env.MEMOIR_AGENT_MODEL,\n smallModel: (config as { small_model?: string }).small_model,\n model: (config as { model?: string }).model,\n });\n const agentField = config as unknown as {\n agent?: Record<string, AgentConfig>;\n };\n agentField.agent = {\n ...(agentField.agent ?? {}),\n [MEMOIR_AGENT_NAME]: buildMemoirAgent(agentModel) as unknown as AgentConfig,\n };\n log(\"memoir subagent registered | model:\", agentModel ?? \"(opencode default)\");\n log(\n \"memoir capture execution mode:\",\n backgroundSubagentsEnabled() ? \"native background\" : \"foreground\",\n \"| OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS:\",\n process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS ?? \"(unset)\",\n \"| OPENCODE_EXPERIMENTAL:\",\n process.env.OPENCODE_EXPERIMENTAL ?? \"(unset)\",\n );\n\n config.command = config.command ?? {};\n config.command[\"memoir:onboard\"] = {\n description: \"Populate or refresh Memoir onboarding for this project\",\n template: loadPrompt(\"onboard.tmpl\"),\n };\n },\n\n \"shell.env\": async (\n _input: unknown,\n output: { env: Record<string, string> },\n ): Promise<void> => {\n try {\n if (storePath) {\n output.env.MEMOIR_STORE = storePath;\n }\n } catch (e) {\n log(\"shell.env: failed\", e);\n }\n },\n\n \"chat.message\": async (\n input: { sessionID: string; agent?: string; messageID?: string; variant?: string },\n output: {\n parts?: Array<{ type: string; text?: string; synthetic?: boolean; agent?: string }>;\n },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n const parts = output.parts ?? [];\n const isMemoirSubtask = parts.some(\n (part) => part.type === \"subtask\" && part.agent === MEMOIR_AGENT_NAME,\n );\n const isSynthetic = parts.length > 0 && parts.every((part) => part.synthetic === true);\n if (input.agent === MEMOIR_AGENT_NAME || isMemoirSubtask || isSynthetic) return;\n\n parentSessions.add(sid);\n messageCounts.set(sid, (messageCounts.get(sid) ?? 0) + 1);\n\n const client = await connectClient();\n if (client) {\n await branchMatcher.match(client, directory, () => captureLifecycle.drain());\n const status = await callMemoirTool(client, \"memoir_status\", {});\n const line = buildTurnStatus(status);\n if (line) turnStatus.set(sid, line);\n else turnStatus.delete(sid);\n }\n\n // chat.message runs before the new user message is persisted, so the\n // transcript still ends at the previous completed assistant turn.\n // This deliberate one-turn delay avoids capture triggering its own\n // session activity (and therefore recursively triggering itself).\n // promptAsync returns 204 after forking the prompt in OpenCode. Await\n // that acceptance so the visible capture task is queued before this\n // hook releases the parent prompt; subagent execution is not awaited.\n log(\"chat.message: submitting capture for previous completed turn\", sid);\n await dispatchCapture(sid);\n } catch (e) {\n log(\"chat.message: failed\", e);\n }\n },\n\n \"tool.execute.before\": async (\n input: { tool: string; callID: string },\n output: { args?: Record<string, unknown> },\n ): Promise<void> => {\n try {\n if (input.tool !== \"task\" || output.args?.subagent_type !== MEMOIR_AGENT_NAME) return;\n captureLifecycle.begin(input.callID);\n if (!backgroundSubagentsEnabled()) {\n log(\n \"memoir task remains foreground: OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS is disabled\",\n );\n return;\n }\n\n // A `subtask` prompt part does not expose TaskTool's `background`\n // parameter. SessionPrompt passes the mutable task arguments through\n // this hook immediately before TaskTool.execute, so opt only memoir's\n // capture task into OpenCode's native BackgroundJob path here.\n output.args.background = true;\n log(\"memoir capture task promoted to native background job\");\n } catch (e) {\n log(\"tool.execute.before: failed\", e);\n }\n },\n\n \"tool.execute.after\": async (\n input: { tool: string; callID: string; args?: Record<string, unknown> },\n output?: { metadata?: { background?: boolean; sessionId?: string } },\n ): Promise<void> => {\n try {\n if (input.tool !== \"task\" || input.args?.subagent_type !== MEMOIR_AGENT_NAME) return;\n const childSessionID = output?.metadata?.sessionId;\n if (output?.metadata?.background === true && childSessionID) {\n captureLifecycle.background(input.callID, childSessionID);\n return;\n }\n captureLifecycle.finishCall(input.callID);\n } catch (e) {\n log(\"tool.execute.after: failed\", e);\n captureLifecycle.finishCall(input.callID);\n }\n },\n\n event: async (input: { event?: { type?: string; properties?: { sessionID?: string } } }) => {\n try {\n const event = input.event;\n if (event?.type !== \"session.idle\" && event?.type !== \"session.error\") return;\n const sessionID = event.properties?.sessionID;\n if (sessionID) captureLifecycle.finishSession(sessionID);\n } catch (e) {\n log(\"event: failed\", e);\n }\n },\n\n \"experimental.chat.system.transform\": async (\n input: { sessionID?: string },\n output: { system?: string[] },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n\n const status = turnStatus.get(sid);\n if (status) output.system?.push(status);\n\n if (input.sessionID && parentSessions.has(sid) && !sessionsWithStartupHint.has(sid)) {\n sessionsWithStartupHint.add(sid);\n log(\"system.transform: startup hint injected for session\", sid);\n\n // Hint: the main agent owns proactive recall/store via memory tools.\n // Tool names are intentionally omitted \u2014 the agent already sees its\n // allowed memoir_* tools, so naming them here would just be hardcode.\n // The text lives in prompts/startup-hint.tmpl for easy editing.\n output.system?.unshift(loadPrompt(\"startup-hint.tmpl\"));\n\n // Proactive context: surface what memoir already holds + the active\n // store status so the agent starts oriented. Both are cheap and\n // LLM-free. The client is fetched once and reused for both calls.\n const client = await connectClient();\n if (client) {\n if (process.env.MEMOIR_SUMMARIZE !== \"0\") {\n const summary = await callMemoirTool(client, \"memoir_summarize\", {\n depth: 1,\n });\n if (summary) {\n output.system?.unshift(\n `[memoir] Prior context already stored (memoir):\\n${summary}`,\n );\n log(\"system.transform: proactive recall injected for session\", sid);\n }\n } else {\n log(\"system.transform: recall skipped (MEMOIR_SUMMARIZE=0)\");\n }\n\n const status = await callMemoirTool(client, \"memoir_status\", {});\n if (status) {\n output.system?.unshift(`[memoir] Memory store status:\\n${status}`);\n log(\"system.transform: store status injected for session\", sid);\n }\n }\n }\n } catch (e) {\n log(\"system.transform: failed\", e);\n }\n },\n\n dispose: async (): Promise<void> => {\n try {\n if (process.env.MEMOIR_AUTO_SAVE === \"1\") {\n const client = await connectClient();\n if (client) {\n for (const [sid, count] of messageCounts) {\n await callMemoirTool(client, \"memoir_remember\", {\n content: `Session ended \u2014 ${count} user messages exchanged`,\n path: `session.${sid}`,\n merge_policy: \"replace\",\n });\n }\n }\n }\n } catch (e) {\n log(\"dispose: save failed\", e);\n }\n sessionsWithStartupHint.clear();\n parentSessions.clear();\n capturePending.clear();\n captureLifecycle.clear();\n branchMatcher.clear();\n messageCounts.clear();\n turnStatus.clear();\n lastCaptured.clear();\n await runtime.close();\n },\n };\n\n return hooks;\n};\n\nconst plugin: PluginModule = {\n id: \"opencode-memoir\",\n server: MemoirOpenCode,\n};\n\nexport default plugin;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Plugin logging.\n *\n * Logs go to a file (never stderr) so they do not pollute the opencode\n * terminal. The destination is controlled by MEMOIR_LOG:\n * - unset \u2192 $X/opencode/memoir-plugin-YYYY-MM-DD.log\n * ($X = XDG_STATE_HOME, falls back to ~/.local/state/...)\n * - \"stderr\" \u2192 stderr, for live local debugging\n * - <path> \u2192 an explicit log file path\n *\n * The log path is cached and re-resolved only when MEMOIR_LOG or the date changes,\n * so writes are fast (single appendFileSync) after the first call.\n *\n * `log(...)` always writes. MEMOIR_DEBUG=1 adds diagnostic detail: Error\n * objects include their name and stack instead of only their message. For\n * local verification, `tail -f` the log file.\n */\n\nlet cachedLogFile: string | null = null;\nlet cachedLogRaw: string | undefined;\nlet cachedLogDate: string | undefined;\n\nfunction resolveLogFile(): string | null {\n const raw = process.env.MEMOIR_LOG;\n if (raw === \"stderr\") return null;\n if (raw) return raw;\n const stateHome = process.env.XDG_STATE_HOME || join(homedir(), \".local\", \"state\");\n const date = new Date().toISOString().slice(0, 10);\n return join(stateHome, \"opencode\", `memoir-plugin-${date}.log`);\n}\n\nfunction getCachedLogFile(): string | null {\n const raw = process.env.MEMOIR_LOG;\n const date = new Date().toISOString().slice(0, 10);\n if (cachedLogRaw !== raw || cachedLogDate !== date) {\n cachedLogFile = resolveLogFile();\n cachedLogRaw = raw;\n cachedLogDate = date;\n }\n return cachedLogFile;\n}\n\nfunction formatValue(value: unknown): string {\n if (value instanceof Error) {\n if (process.env.MEMOIR_DEBUG === \"1\") {\n return value.stack ?? `${value.name}: ${value.message}`;\n }\n return value.message;\n }\n return String(value);\n}\n\nfunction formatArgs(args: unknown[]): string {\n const [message, ...details] = args;\n if (typeof message === \"string\" && details[0] instanceof Error) {\n return `${message.replace(/:\\s*$/, \"\")}: ${details.map(formatValue).join(\" \")}`;\n }\n return args.map(formatValue).join(\" \");\n}\n\nfunction writeLog(args: unknown[]): void {\n const ts = new Date().toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n const line = `[memoir ${ts}] ${formatArgs(args)}\\n`;\n const logFilePath = getCachedLogFile();\n if (logFilePath) {\n try {\n mkdirSync(dirname(logFilePath), { recursive: true });\n appendFileSync(logFilePath, line);\n return;\n } catch {\n // fall through to stderr if the file is unavailable\n }\n }\n process.stderr.write(line);\n}\n\n/** Single logging entrypoint; MEMOIR_DEBUG=1 controls detail, not emission. */\nexport function log(...args: unknown[]): void {\n writeLog(args);\n}\n", "import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nconst cache = new Map<string, string>();\n\n/**\n * Load a prompt template from src/prompts (dev / tsx) or dist/prompts (bundled).\n * Cached per file. Templates are plain text with ${TOKEN} placeholders that the\n * caller substitutes. Keeping prompts as standalone files (not TS string\n * literals) makes them easy to read and edit in isolation \u2014 mirroring how the\n * upstream memoir plugin keeps hooks/prompts/*.tmpl as first-class artifacts.\n */\nexport function loadPrompt(file: string): string {\n const cached = cache.get(file);\n if (cached !== undefined) return cached;\n const path = fileURLToPath(new URL(`./prompts/${file}`, import.meta.url));\n const text = readFileSync(path, \"utf8\");\n cache.set(file, text);\n return text;\n}\n", "import type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { log } from \"./debug.js\";\nimport { loadPrompt } from \"./prompts.js\";\n\n// opencode restricts an agent's available tools through its `permission`\n// ruleset (NOT a `tools` allowlist \u2014 that field is ignored for config agents).\n// `visibleTools` hides a tool only when the LAST rule matching its name has\n// `{ pattern: \"*\", action: \"deny\" }`. So we deny \"*\" first, then re-allow\n// the memoir_* MCP tools (and doom_loop) after it, then deny checkout. Order\n// matters: the specific checkout deny must be LAST so findLast keeps branch\n// ownership in the plugin while every other memoir tool remains available.\n//\n// The SDK's AgentConfig.permission type is narrower than what opencode's\n// runtime accepts (object form \u2192 Permission.fromConfig), hence the loose\n// intersection below.\ntype MemoirAgentPermission = Record<string, string>;\nexport type MemoirAgentSpec = AgentConfig & {\n permission: MemoirAgentPermission;\n steps?: number;\n};\n\nexport const MEMOIR_AGENT_NAME = \"memoir\";\nexport const MEMOIR_CHECKOUT_TOOL = \"memoir_memoir_checkout\";\n\n// System prompt for the memoir subagent, loaded from prompts/subagent-system.tmpl.\n// It is intentionally tool-free: the subagent is locked to the memoir_* tools\n// via its `permission` ruleset (see buildMemoirAgent), and opencode already\n// exposes those tools to it. The task prompt no longer carries the tool catalog\n// (it was removed); the subagent relies on its static memoir_* permissions\n// instead, so this prompt stays generic and reusable across memoir-mcp versions.\nconst MEMOIR_AGENT_PROMPT = loadPrompt(\"subagent-system.tmpl\");\n\n/**\n * Build the memoir subagent config.\n *\n * Locked to memoir_* tools except checkout (everything else denied) so it can\n * never touch the filesystem, shell, network, or the store-global branch.\n * opencode restricts an agent's tools through its `permission` ruleset \u2014 see\n * MemoirAgentSpec for why the checkout deny must follow the broad allow. Mode\n * \"subagent\" makes\n * opencode spawn it as a detached child session, rendered as a collapsed task\n * (visible in the parent timeline) when prompted via\n * client.session.prompt({ body: { agent: \"memoir\" } }).\n */\nexport function buildMemoirAgent(model?: string): MemoirAgentSpec {\n const agent: MemoirAgentSpec = {\n mode: \"subagent\",\n description:\n \"Memoir memory agent \u2014 captures durable facts and recalls prior context via the memoir MCP server.\",\n prompt: MEMOIR_AGENT_PROMPT,\n permission: {\n \"*\": \"deny\",\n doom_loop: \"allow\",\n \"memoir_*\": \"allow\",\n [MEMOIR_CHECKOUT_TOOL]: \"deny\",\n },\n temperature: 0,\n steps: 8,\n };\n if (model) agent.model = model;\n return agent;\n}\n\ninterface ModelResolution {\n summarizeModel?: string;\n smallModel?: string;\n model?: string;\n}\n\n/**\n * Resolve the model the memoir subagent should use.\n *\n * Priority:\n * 1. MEMOIR_AGENT_MODEL env (provider/model)\n * 2. config.small_model\n * 3. config.model\n * 4. omit \u2014 let opencode pick the session default\n *\n * Only \"provider/model\" shaped values are accepted; anything else is ignored\n * so we never hand opencode a bare model id it cannot resolve.\n */\nexport function resolveMemoirModel(opts: ModelResolution): string | undefined {\n const candidates = [\n process.env.MEMOIR_AGENT_MODEL?.trim(),\n opts.summarizeModel,\n opts.smallModel,\n opts.model,\n ];\n return candidates.find((candidate) => candidate?.includes(\"/\"));\n}\n\n/** Queue a visible subtask through OpenCode's supported promptAsync input API. */\nexport async function runMemoirSubagent(\n client: unknown,\n parentSessionID: string,\n task: string,\n): Promise<void> {\n type SubtaskPartInput = {\n type: \"subtask\";\n agent: string;\n description: string;\n prompt: string;\n command?: string;\n };\n type PromptAsync = (options: {\n path: { id: string };\n body: { parts: SubtaskPartInput[] };\n }) => Promise<unknown>;\n\n const api = (client as { session?: { promptAsync?: PromptAsync } } | null | undefined)?.session;\n if (!api?.promptAsync) {\n throw new Error(\"client.session.promptAsync unavailable\");\n }\n\n try {\n await api.promptAsync({\n path: { id: parentSessionID },\n body: {\n parts: [\n {\n type: \"subtask\",\n agent: MEMOIR_AGENT_NAME,\n description: \"capture\",\n prompt: task,\n command: \"\",\n },\n ],\n },\n });\n log(\"memoir subtask accepted (session\", parentSessionID, \", task\", task.length, \"chars)\");\n } catch (e) {\n log(\"runMemoirSubagent failed\", e);\n throw e;\n }\n}\n", "import { log } from \"./debug.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { runMemoirSubagent } from \"./subagent.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ChatPart {\n type?: string;\n text?: string;\n [key: string]: unknown;\n}\n\nexport interface ChatMessage {\n info?: { id?: string; role?: string };\n parts?: ChatPart[];\n}\n\n// Minimal structural view of the SDK client we use to pull a session transcript.\n// The SDK exposes this as `client.session.messages` (singular `session`).\ntype SessionMessagesClient = {\n session: {\n messages(input: {\n path: { id: string };\n query?: { limit?: number };\n }): Promise<{ data?: unknown }>;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_CAPTURE_MIN_CHARS = 16;\n\nfunction captureMinChars(): number {\n const raw = process.env.MEMOIR_CAPTURE_MIN_CHARS?.trim();\n if (raw === undefined || raw === \"\") return DEFAULT_CAPTURE_MIN_CHARS;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) ? n : DEFAULT_CAPTURE_MIN_CHARS;\n}\n\nfunction autoSaveEnabled(): boolean {\n // ENABLED by default; only an explicit \"0\" disables it.\n return process.env.MEMOIR_AUTO_SAVE !== \"0\";\n}\n\n// ---------------------------------------------------------------------------\n// Transcript helpers\n// ---------------------------------------------------------------------------\n\nfunction messageText(msg: ChatMessage): string {\n const parts = msg.parts ?? [];\n return parts\n .filter((p) => p.type === \"text\" && typeof p.text === \"string\")\n .map((p) => p.text as string)\n .join(\"\\n\")\n .trim();\n}\n\nfunction formatRole(role: string | undefined): string {\n switch (role) {\n case \"user\":\n return \"USER\";\n case \"assistant\":\n return \"ASSISTANT\";\n case \"system\":\n return \"SYSTEM\";\n default:\n return (role ?? \"unknown\").toUpperCase();\n }\n}\n\n/**\n * Render a list of messages into a plain-text transcript.\n * Skips empty (no text) messages so tool-only turns stay quiet.\n */\nexport function formatSessionTranscript(messages: ChatMessage[]): string {\n return messages\n .map((m) => {\n const text = messageText(m);\n if (!text) return null;\n return `${formatRole(m.info?.role)}\\n${text}`;\n })\n .filter((line): line is string => line !== null)\n .join(\"\\n\\n\");\n}\n\n/**\n * Extract the most recent completed turn: the last assistant message that has\n * text, plus the user message(s) immediately preceding it. Returns null when\n * there is no completed assistant turn yet.\n */\nexport function lastTurnTranscript(messages: ChatMessage[]): string | null {\n let assistantIdx = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"assistant\" && messageText(messages[i])) {\n assistantIdx = i;\n break;\n }\n }\n if (assistantIdx < 0) return null;\n\n const userIdx = (() => {\n for (let i = assistantIdx - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"user\") return i;\n }\n return -1;\n })();\n if (userIdx < 0) return null;\n\n const turn = messages.slice(userIdx, assistantIdx + 1);\n return formatSessionTranscript(turn);\n}\n\n/**\n * Local, LLM-free pre-filter. When MEMOIR_CAPTURE_MIN_CHARS (default 16) is\n * > 0, only transcripts at least that long are worth sending to the subagent.\n * A value of 0 disables the filter (capture everything).\n */\nexport function shouldCaptureTurn(\n transcript: string | null,\n minChars = captureMinChars(),\n): transcript is string {\n if (minChars <= 0) return transcript !== null && transcript.length > 0;\n return transcript !== null && transcript.length >= minChars;\n}\n\n// ---------------------------------------------------------------------------\n// Task builder (mirrors plugins/claude-code/hooks/stop_capture.tmpl)\n// ---------------------------------------------------------------------------\n\nconst CAPTURE_TASK_TEMPLATE = loadPrompt(\"capture-task.tmpl\");\n\n/**\n * Build the capture task handed to the memoir subagent. It applies the\n * durability checks, writes each fact with an explicit 3-level taxonomy path,\n * and returns a compact report based only on confirmed tool outcomes.\n */\nexport function buildTurnCaptureTask(transcript: string): string {\n return CAPTURE_TASK_TEMPLATE.replace(\"{{TRANSCRIPT}}\", transcript);\n}\n\n// ---------------------------------------------------------------------------\n// Capture orchestration\n// ---------------------------------------------------------------------------\n\nfunction lastAssistantMessageId(messages: ChatMessage[]): string | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].info?.role === \"assistant\" && messageText(messages[i])) {\n return messages[i].info?.id ?? null;\n }\n }\n return null;\n}\n\n/**\n * Capture the latest completed turn of a session into memoir via the subagent.\n *\n * Fire-and-forget: the caller should NOT await the subagent's completion.\n * Deduped per session by the last assistant message id, so a turn is written\n * at most once. Skips entirely when MEMOIR_AUTO_SAVE=0.\n *\n * @param client SDK client (transcript source + subagent spawner)\n * @param sessionID parent session id\n * @param lastCaptured per-session map of already-captured assistant ids\n */\nexport async function captureTurn(\n client: unknown,\n sessionID: string,\n lastCaptured: Map<string, string>,\n): Promise<void> {\n if (!autoSaveEnabled()) {\n log(\"captureTurn: MEMOIR_AUTO_SAVE=0, skipping\");\n return;\n }\n log(\"captureTurn: started for session\", sessionID);\n\n try {\n const api = (client as SessionMessagesClient | null | undefined)?.session;\n if (!api?.messages) {\n log(\"captureTurn: client.session.messages unavailable\");\n log(\"captureTurn: skipped \u2014 session messages API unavailable\");\n return;\n }\n\n const res = await api.messages({\n path: { id: sessionID },\n query: { limit: 50 },\n });\n const raw = res.data;\n const messages: ChatMessage[] = Array.isArray(raw) ? (raw as ChatMessage[]) : [];\n if (messages.length === 0) return;\n\n const turnId = lastAssistantMessageId(messages);\n if (!turnId) return;\n if (lastCaptured.get(sessionID) === turnId) {\n log(\"captureTurn: skipped \u2014 already captured this turn\");\n return;\n }\n\n const transcript = lastTurnTranscript(messages);\n if (!shouldCaptureTurn(transcript)) {\n log(\"captureTurn: skipped \u2014 transcript below min-chars\");\n lastCaptured.set(sessionID, turnId);\n return;\n }\n\n log(\"captureTurn: submitting memoir subtask (transcript\", transcript.length, \"chars)\");\n await runMemoirSubagent(client, sessionID, buildTurnCaptureTask(transcript));\n lastCaptured.set(sessionID, turnId);\n } catch (e) {\n log(\"captureTurn failed\", e);\n }\n}\n", "import { log } from \"./debug.js\";\n\ntype ActiveCapture = {\n done: Promise<void>;\n resolve: () => void;\n childSessionID?: string;\n};\n\n/** Track actual memoir task execution, including native background jobs. */\nexport class CaptureLifecycle {\n private readonly active = new Map<string, ActiveCapture>();\n private readonly callsByChild = new Map<string, string>();\n\n begin(callID: string): void {\n if (this.active.has(callID)) return;\n let resolve!: () => void;\n const done = new Promise<void>((doneResolve) => {\n resolve = doneResolve;\n });\n this.active.set(callID, { done, resolve });\n }\n\n background(callID: string, childSessionID: string): void {\n const capture = this.active.get(callID);\n if (!capture) return;\n capture.childSessionID = childSessionID;\n this.callsByChild.set(childSessionID, callID);\n }\n\n finishCall(callID: string): void {\n const capture = this.active.get(callID);\n if (!capture) return;\n if (capture.childSessionID) this.callsByChild.delete(capture.childSessionID);\n this.active.delete(callID);\n capture.resolve();\n }\n\n finishSession(sessionID: string): void {\n const callID = this.callsByChild.get(sessionID);\n if (callID) this.finishCall(callID);\n }\n\n async drain(timeoutMs = 10_000): Promise<boolean> {\n const pending = [...this.active.values()].map((capture) => capture.done);\n if (pending.length === 0) return true;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n const drained = Promise.all(pending).then(() => true as const);\n const result = await Promise.race([drained, timedOut]);\n if (timer) clearTimeout(timer);\n if (!result) log(\"capture lifecycle: drain timed out; branch switch deferred\");\n return result;\n }\n\n clear(): void {\n for (const callID of [...this.active.keys()]) this.finishCall(callID);\n }\n}\n", "import { type ChildProcess, spawn } from \"node:child_process\";\nimport { createConnection, createServer } from \"node:net\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { log } from \"./debug.js\";\nimport { safeRealpath } from \"./path.js\";\n\ninterface MemoirClientState {\n client: Client;\n}\n\ntype ClientConnection = {\n client: Client;\n transport: StreamableHTTPClientTransport;\n};\n\nexport type MemoirRuntimeDependencies = {\n createClientConnection?: (url: URL) => ClientConnection;\n};\n\nfunction errorMessage(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n// Random free port from the upper (ephemeral) range.\nconst LOW_PORT = 49152;\nconst HIGH_PORT = 65535;\n\nfunction randomHighPort(): number {\n return LOW_PORT + Math.floor(Math.random() * (HIGH_PORT - LOW_PORT + 1));\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = createServer();\n srv.once(\"error\", () => resolve(false));\n srv.once(\"listening\", () => srv.close(() => resolve(true)));\n srv.listen(port, \"127.0.0.1\");\n });\n}\n\nasync function pickFreeHighPort(tries = 20): Promise<number> {\n for (let i = 0; i < tries; i++) {\n const port = randomHighPort();\n if (await isPortFree(port)) return port;\n }\n throw new Error(\"memoir: could not find a free high port for the HTTP server\");\n}\n\nfunction waitForPort(port: number, timeoutMs = 10_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n return new Promise((resolve, reject) => {\n const attempt = () => {\n const sock = createConnection({ port, host: \"127.0.0.1\" });\n sock.once(\"connect\", () => {\n sock.destroy();\n resolve();\n });\n sock.once(\"error\", () => {\n sock.destroy();\n if (Date.now() > deadline) {\n reject(new Error(\"memoir: HTTP server did not start in time\"));\n } else {\n setTimeout(attempt, 100);\n }\n });\n };\n attempt();\n });\n}\n\n/**\n * One memoir-mcp process and internal MCP client owned by one plugin instance.\n * OpenCode can host several project directories in one process, so none of\n * this state may live at module scope.\n */\nexport class MemoirRuntime {\n private state: MemoirClientState | null = null;\n private connecting: Promise<Client> | null = null;\n private serverProc: ChildProcess | null = null;\n private serverUrl: URL | null = null;\n private serverPort: number | null = null;\n private startingServer: Promise<URL> | null = null;\n\n constructor(\n private readonly command: string[],\n private readonly env?: Record<string, string>,\n private readonly cwd?: string,\n private readonly dependencies: MemoirRuntimeDependencies = {},\n ) {}\n\n private cleanupClient(): void {\n this.state = null;\n this.connecting = null;\n }\n\n async start(): Promise<URL> {\n if (this.serverUrl && this.serverProc) return this.serverUrl;\n if (this.startingServer) return this.startingServer;\n\n const start = (async (): Promise<URL> => {\n // Retry loop: pickFreeHighPort has a TOCTOU gap (isPortFree \u2192 spawn).\n // If waitForPort times out the port is likely taken \u2014 kill the child and\n // retry with a fresh port.\n const maxTries = 5;\n for (let attempt = 0; attempt < maxTries; attempt++) {\n const port = this.serverPort ?? (await pickFreeHighPort());\n this.serverPort = port;\n const url = new URL(`http://127.0.0.1:${port}/mcp`);\n\n const args = [\n ...this.command.slice(1),\n \"--http\",\n \"--host\",\n \"127.0.0.1\",\n \"--port\",\n String(port),\n ];\n\n const proc = spawn(this.command[0], args, {\n env: { ...process.env, ...this.env },\n cwd: this.cwd ? safeRealpath(this.cwd) : process.cwd(),\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n\n if (proc.stderr) {\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n log(\"memoir-mcp:\", chunk.toString().trimEnd());\n });\n }\n\n const exited = new Promise<number | null>((resolve) => {\n proc.once(\"exit\", (code) => resolve(code));\n });\n\n await new Promise<void>((resolve, reject) => {\n proc.once(\"spawn\", resolve);\n proc.once(\"error\", reject);\n }).catch((e: unknown) => {\n throw new Error(`memoir: failed to spawn ${this.command[0]}: ${errorMessage(e)}`);\n });\n\n this.serverProc = proc;\n\n try {\n await waitForPort(port);\n } catch {\n // Port may be taken \u2014 kill the child and retry.\n proc.kill();\n await exited;\n this.serverProc = null;\n this.serverUrl = null;\n this.serverPort = null;\n this.cleanupClient();\n continue;\n }\n\n // Late-exit guard: the port check succeeded but another session may\n // have already torn this child down. Only register the URL if our proc\n // is still the active one.\n if (this.serverProc === proc) {\n this.serverUrl = url;\n log(\"memoir-mcp HTTP server up at\", url.toString());\n return url;\n }\n\n this.serverUrl = url;\n log(\"memoir-mcp HTTP server up at\", url.toString());\n return url;\n }\n\n throw new Error(\"memoir: could not start HTTP server after several retries\");\n })();\n\n this.startingServer = start;\n try {\n return await start;\n } catch (e) {\n this.startingServer = null;\n this.serverUrl = null;\n if (this.serverProc) this.serverProc.kill();\n this.serverProc = null;\n this.serverPort = null;\n throw e;\n }\n }\n\n /** Get the lazily-connected internal client for this plugin instance. */\n async connect(): Promise<Client> {\n if (this.state) return this.state.client;\n\n if (!this.connecting) {\n this.connecting = (async () => {\n const url = await this.start();\n const { client, transport } = this.dependencies.createClientConnection?.(url) ?? {\n client: new Client({ name: \"opencode-memoir\", version: \"1.0.0\" }, { capabilities: {} }),\n transport: new StreamableHTTPClientTransport(url),\n };\n\n transport.onclose = () => {\n log(\"mcp-client: transport closed, resetting state\");\n this.cleanupClient();\n };\n\n try {\n await client.connect(transport);\n } catch (e) {\n this.cleanupClient();\n throw e;\n }\n\n this.state = { client };\n return client;\n })();\n }\n\n return this.connecting;\n }\n\n /** Close the MCP client and stop this instance's HTTP server. */\n async close(): Promise<void> {\n if (this.state) {\n try {\n await this.state.client.close();\n } catch (e) {\n log(\"closeMemoirClient: close failed\", e);\n }\n this.cleanupClient();\n }\n if (this.serverProc) {\n const proc = this.serverProc;\n this.serverProc = null;\n try {\n await new Promise<void>((resolve) => {\n if (proc.exitCode !== null) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n proc.kill(\"SIGKILL\");\n resolve();\n }, 1_000);\n timer.unref();\n proc.once(\"exit\", () => {\n clearTimeout(timer);\n resolve();\n });\n proc.once(\"error\", () => {\n clearTimeout(timer);\n resolve();\n });\n proc.kill();\n });\n } catch (e) {\n log(\"closeMemoirClient: kill failed\", e);\n }\n }\n this.serverUrl = null;\n this.startingServer = null;\n this.connecting = null;\n }\n}\n\n/**\n * Wrapper around client.callTool that extracts text content.\n * Returns the first `{ type: \"text\" }` content block's text, or null.\n */\nexport async function callMemoirTool(\n client: Client,\n name: string,\n args?: Record<string, unknown>,\n): Promise<string | null> {\n try {\n const result = (await client.callTool({\n name,\n arguments: args ?? {},\n })) as { content: Array<{ type: string; text?: string }>; isError?: boolean };\n if (result.isError) {\n log(`callMemoirTool: ${name} returned error`);\n return null;\n }\n for (const block of result.content) {\n if (block.type === \"text\" && block.text != null) {\n return block.text;\n }\n }\n return null;\n } catch (e) {\n log(`callMemoirTool: ${name} failed`, e);\n return null;\n }\n}\n", "import { execFileSync } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\n/** Resolve a path through symlinks, falling back to the input on failure. */\nexport function safeRealpath(p: string): string {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/** Turn an absolute path into a filesystem-safe slug for store naming. */\nexport function slugify(p: string): string {\n return p.replace(/[/.]/g, \"-\");\n}\n\n/**\n * Resolve the memoir store path.\n * 1. `override` (plugin `store` option)\n * 2. MEMOIR_STORE env var\n * 3. Git root \u2192 `~/.memoir/<slug>` (all worktrees share one store)\n * 4. Resolved cwd \u2192 `~/.memoir/<slug>` for non-git dirs\n *\n * The project directory is realpath'd before slugifying so the store is\n * identical whether the repo is entered via a symlink or its real path.\n */\nexport function deriveStorePath(cwd: string = process.cwd(), override?: string): string {\n if (override) return override;\n const envStore = process.env.MEMOIR_STORE;\n if (envStore) return envStore;\n\n const realCwd = safeRealpath(cwd);\n let projectDir: string;\n try {\n const gitRoot = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd: realCwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3_000,\n }).trim();\n projectDir = safeRealpath(gitRoot);\n } catch {\n projectDir = resolve(realCwd);\n }\n\n return join(homedir(), \".memoir\", slugify(projectDir));\n}\n", "import { execFileSync } from \"node:child_process\";\nimport type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { callMemoirTool } from \"./mcp-client.js\";\nimport { deriveStorePath as deriveStorePathBase, safeRealpath } from \"./path.js\";\nimport { parseMemoirStatus } from \"./status.js\";\n\n/** Resolve the memoir store path, honoring the plugin `store` option override. */\nexport function deriveStorePath(cwd?: string, override?: string): string {\n return deriveStorePathBase(cwd, override);\n}\n\n/** Get current git branch (empty string if not in a git repo). */\nexport function currentGitBranch(cwd = process.cwd()): string {\n try {\n return execFileSync(\"git\", [\"branch\", \"--show-current\"], {\n cwd: safeRealpath(cwd),\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3_000,\n }).trim();\n } catch {\n return \"\";\n }\n}\n\n/**\n * Track the actual branch of one memoir store. The checkout is store-global,\n * so a per-session cache would become stale as soon as another session changed\n * the branch.\n */\nexport class MemoirBranchMatcher {\n private matching: Promise<void> = Promise.resolve();\n\n match(client: Client, cwd: string, drain?: () => Promise<boolean>): Promise<void> {\n const next = this.matching.then(() => this.matchNow(client, cwd, drain));\n this.matching = next.catch(() => undefined);\n return next;\n }\n\n private async currentBranch(client: Client): Promise<string> {\n const raw = await callMemoirTool(client, \"memoir_status\");\n return parseMemoirStatus(raw).branch ?? \"\";\n }\n\n private async matchNow(\n client: Client,\n cwd: string,\n drain?: () => Promise<boolean>,\n ): Promise<void> {\n const codeBranch = currentGitBranch(cwd);\n if (!codeBranch) return;\n\n if ((await this.currentBranch(client)) === codeBranch) return;\n if (drain && !(await drain())) return;\n // A capture or external client may have changed HEAD while we waited.\n if ((await this.currentBranch(client)) === codeBranch) return;\n\n // memoir_checkout (per the v0.2.4 contract) switches to a branch, creating\n // it on demand when `create` is true. A single call replaces the old\n // branches-list \u2192 branch \u2192 checkout CLI sequence.\n await callMemoirTool(client, \"memoir_checkout\", {\n target: codeBranch,\n create: true,\n });\n }\n\n clear(): void {\n this.matching = Promise.resolve();\n }\n}\n", "/**\n * Parsed shape of the `memoir_status` tool response.\n *\n * Both the per-turn model status (turn-status.ts) and the branch matcher\n * (store.ts) read this payload, so the parse lives here once instead of being\n * duplicated with two slightly different decoders.\n */\nexport type MemoirStatus = {\n branch?: string;\n memory_count?: number;\n};\n\n/**\n * Decode a `memoir_status` text payload into a typed, sanitized object.\n * Returns an empty object on a missing or malformed payload so callers can\n * treat every field as optional without guarding parse errors themselves.\n */\nexport function parseMemoirStatus(raw: string | null | undefined): MemoirStatus {\n if (!raw) return {};\n try {\n const status = JSON.parse(raw) as MemoirStatus;\n return {\n branch: typeof status.branch === \"string\" ? status.branch : undefined,\n memory_count: typeof status.memory_count === \"number\" ? status.memory_count : undefined,\n };\n } catch {\n return {};\n }\n}\n", "import { parseMemoirStatus } from \"./status.js\";\n\nexport function buildTurnStatus(statusRaw: string | null): string {\n const { branch, memory_count } = parseMemoirStatus(statusRaw);\n const memoryCount = memory_count ?? 0;\n if (!branch && memoryCount <= 0) return \"\";\n if (!branch) return `[memoir] memory available (${memoryCount} memories)`;\n return `[memoir] ${branch}${memoryCount > 0 ? ` \u00B7 memory available (${memoryCount} memories)` : \"\"}`;\n}\n", "import type { Config, Plugin, PluginInput, PluginModule } from \"@opencode-ai/plugin\";\nimport type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { captureTurn } from \"./capture.js\";\nimport { CaptureLifecycle } from \"./capture-lifecycle.js\";\nimport { log } from \"./debug.js\";\nimport { callMemoirTool, MemoirRuntime } from \"./mcp-client.js\";\nimport { safeRealpath } from \"./path.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { deriveStorePath, MemoirBranchMatcher } from \"./store.js\";\nimport { buildMemoirAgent, MEMOIR_AGENT_NAME, resolveMemoirModel } from \"./subagent.js\";\nimport { buildTurnStatus } from \"./turn-status.js\";\n\nfunction envFlag(name: string): boolean | undefined {\n const value = process.env[name]?.trim().toLowerCase();\n if (value === undefined || value === \"\") return undefined;\n return value === \"1\" || value === \"true\";\n}\n\nfunction backgroundSubagentsEnabled(): boolean {\n return (\n envFlag(\"OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS\") ??\n envFlag(\"OPENCODE_EXPERIMENTAL\") ??\n false\n );\n}\n\nconst MemoirOpenCode: Plugin = async (input, rawOptions) => {\n const opts = (rawOptions ?? {}) as { store?: string };\n // SDK client (for subagent spawning + transcript reads) and working dir come\n // from the plugin input. Guard for callers that pass no input.\n const pluginInput = (input ?? {}) as PluginInput;\n const sdkClient = pluginInput.client;\n const directory = safeRealpath(pluginInput.directory ?? process.cwd());\n const storePath = deriveStorePath(directory, opts.store);\n\n const mcpCommand = [\"memoir-mcp\"];\n if (storePath) {\n mcpCommand.push(\"--store\", storePath);\n }\n\n const environment = storePath ? { MEMOIR_STORE: storePath } : undefined;\n const runtime = new MemoirRuntime(mcpCommand, environment, directory);\n const branchMatcher = new MemoirBranchMatcher();\n const sessionsWithStartupHint = new Set<string>();\n const parentSessions = new Set<string>();\n const lastCaptured = new Map<string, string>();\n const capturePending = new Set<string>();\n const captureLifecycle = new CaptureLifecycle();\n const messageCounts = new Map<string, number>();\n const turnStatus = new Map<string, string>();\n\n // The plugin owns a single shared memoir-mcp HTTP server (started in the\n // config hook). opencode connects to it as a remote MCP server; the plugin's\n // own hooks use the same URL via the internal client. One process, no stdio\n // double-spawn. Populated once the HTTP server is up.\n let mcpServer: { type: \"remote\"; url: string; enabled: boolean } | undefined;\n\n // Plugin's own MCP client (separate from the LLM-facing mcpServer).\n // Lazily connected on first use so merely loading the plugin \u2014 or running\n // hooks that never touch memoir \u2014 spawns no subprocess.\n const connectClient = async () => {\n try {\n return await runtime.connect();\n } catch (e) {\n log(\"plugin: failed to connect memoir client\", e);\n return null;\n }\n };\n\n const dispatchCapture = async (sid: string): Promise<void> => {\n if (!sdkClient || !parentSessions.has(sid) || capturePending.has(sid)) return;\n capturePending.add(sid);\n try {\n const client = await connectClient();\n if (client) await branchMatcher.match(client, directory, () => captureLifecycle.drain());\n await captureTurn(sdkClient, sid, lastCaptured);\n } catch (e) {\n log(\"dispatchCapture failed\", e);\n } finally {\n capturePending.delete(sid);\n }\n };\n\n const hooks: Record<string, unknown> = {\n name: \"memoir\",\n\n config: async (config: Config): Promise<void> => {\n log(\"config hook: mcpCommand =\", JSON.stringify(mcpCommand));\n\n try {\n const url = await runtime.start();\n mcpServer = { type: \"remote\", url: url.toString(), enabled: true };\n log(\"memoir MCP server registered at\", url.toString(), \"| store:\", storePath);\n } catch (e) {\n log(\"config hook: failed to start memoir HTTP server\", e);\n mcpServer = undefined;\n }\n\n const mcp = config.mcp ?? {};\n if (mcpServer) mcp.memoir = mcpServer;\n config.mcp = mcp;\n\n // Register on the returned hooks object too \u2014 opencode reads hooks.mcp\n // for the MCP server, and it must reflect the (now known) URL.\n if (mcpServer) hooks.mcp = { memoir: mcpServer };\n\n // Register the memoir subagent (capture + recall). Its model resolves\n // from MEMOIR_AGENT_MODEL \u2192 small_model \u2192 model \u2192 opencode default.\n // opencode reads the agent from `config.agent` (singular), not `agents`.\n const agentModel = resolveMemoirModel({\n summarizeModel: process.env.MEMOIR_AGENT_MODEL,\n smallModel: (config as { small_model?: string }).small_model,\n model: (config as { model?: string }).model,\n });\n const agentField = config as unknown as {\n agent?: Record<string, AgentConfig>;\n };\n agentField.agent = {\n ...(agentField.agent ?? {}),\n [MEMOIR_AGENT_NAME]: buildMemoirAgent(agentModel) as unknown as AgentConfig,\n };\n log(\"memoir subagent registered | model:\", agentModel ?? \"(opencode default)\");\n log(\n \"memoir capture execution mode:\",\n backgroundSubagentsEnabled() ? \"native background\" : \"foreground\",\n \"| OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS:\",\n process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS ?? \"(unset)\",\n \"| OPENCODE_EXPERIMENTAL:\",\n process.env.OPENCODE_EXPERIMENTAL ?? \"(unset)\",\n );\n\n config.command = config.command ?? {};\n config.command[\"memoir:onboard\"] = {\n description: \"Populate or refresh Memoir onboarding for this project\",\n template: loadPrompt(\"onboard.tmpl\"),\n };\n },\n\n \"shell.env\": async (\n _input: unknown,\n output: { env: Record<string, string> },\n ): Promise<void> => {\n try {\n if (storePath) {\n output.env.MEMOIR_STORE = storePath;\n }\n } catch (e) {\n log(\"shell.env: failed\", e);\n }\n },\n\n \"chat.message\": async (\n input: { sessionID: string; agent?: string; messageID?: string; variant?: string },\n output: {\n parts?: Array<{ type: string; text?: string; synthetic?: boolean; agent?: string }>;\n },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n const parts = output.parts ?? [];\n const isMemoirSubtask = parts.some(\n (part) => part.type === \"subtask\" && part.agent === MEMOIR_AGENT_NAME,\n );\n const isSynthetic = parts.length > 0 && parts.every((part) => part.synthetic === true);\n if (input.agent === MEMOIR_AGENT_NAME || isMemoirSubtask || isSynthetic) return;\n\n parentSessions.add(sid);\n messageCounts.set(sid, (messageCounts.get(sid) ?? 0) + 1);\n\n const client = await connectClient();\n if (client) {\n await branchMatcher.match(client, directory, () => captureLifecycle.drain());\n const status = await callMemoirTool(client, \"memoir_status\", {});\n const line = buildTurnStatus(status);\n if (line) turnStatus.set(sid, line);\n else turnStatus.delete(sid);\n }\n\n // chat.message runs before the new user message is persisted, so the\n // transcript still ends at the previous completed assistant turn.\n // This deliberate one-turn delay avoids capture triggering its own\n // session activity (and therefore recursively triggering itself).\n // promptAsync returns 204 after forking the prompt in OpenCode. Await\n // that acceptance so the visible capture task is queued before this\n // hook releases the parent prompt; subagent execution is not awaited.\n log(\"chat.message: submitting capture for previous completed turn\", sid);\n await dispatchCapture(sid);\n } catch (e) {\n log(\"chat.message: failed\", e);\n }\n },\n\n \"tool.execute.before\": async (\n input: { tool: string; callID: string },\n output: { args?: Record<string, unknown> },\n ): Promise<void> => {\n try {\n if (input.tool !== \"task\" || output.args?.subagent_type !== MEMOIR_AGENT_NAME) return;\n captureLifecycle.begin(input.callID);\n if (!backgroundSubagentsEnabled()) {\n log(\n \"memoir task remains foreground: OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS is disabled\",\n );\n return;\n }\n\n // A `subtask` prompt part does not expose TaskTool's `background`\n // parameter. SessionPrompt passes the mutable task arguments through\n // this hook immediately before TaskTool.execute, so opt only memoir's\n // capture task into OpenCode's native BackgroundJob path here.\n output.args.background = true;\n log(\"memoir capture task promoted to native background job\");\n } catch (e) {\n log(\"tool.execute.before: failed\", e);\n }\n },\n\n \"tool.execute.after\": async (\n input: { tool: string; callID: string; args?: Record<string, unknown> },\n output?: { metadata?: { background?: boolean; sessionId?: string } },\n ): Promise<void> => {\n try {\n if (input.tool !== \"task\" || input.args?.subagent_type !== MEMOIR_AGENT_NAME) return;\n const childSessionID = output?.metadata?.sessionId;\n if (output?.metadata?.background === true && childSessionID) {\n captureLifecycle.background(input.callID, childSessionID);\n return;\n }\n captureLifecycle.finishCall(input.callID);\n } catch (e) {\n log(\"tool.execute.after: failed\", e);\n captureLifecycle.finishCall(input.callID);\n }\n },\n\n event: async (input: { event?: { type?: string; properties?: { sessionID?: string } } }) => {\n try {\n const event = input.event;\n if (event?.type !== \"session.idle\" && event?.type !== \"session.error\") return;\n const sessionID = event.properties?.sessionID;\n if (sessionID) captureLifecycle.finishSession(sessionID);\n } catch (e) {\n log(\"event: failed\", e);\n }\n },\n\n \"experimental.chat.system.transform\": async (\n input: { sessionID?: string },\n output: { system?: string[] },\n ): Promise<void> => {\n try {\n const sid = input.sessionID ?? \"default\";\n\n const status = turnStatus.get(sid);\n if (status) output.system?.push(status);\n\n if (input.sessionID && parentSessions.has(sid) && !sessionsWithStartupHint.has(sid)) {\n sessionsWithStartupHint.add(sid);\n log(\"system.transform: startup hint injected for session\", sid);\n\n // Hint: the main agent owns proactive recall/store via memory tools.\n // Tool names are intentionally omitted \u2014 the agent already sees its\n // allowed memoir_* tools, so naming them here would just be hardcode.\n // The text lives in prompts/startup-hint.tmpl for easy editing.\n output.system?.unshift(loadPrompt(\"startup-hint.tmpl\"));\n\n // Proactive context: surface what memoir already holds + the active\n // store status so the agent starts oriented. Both are cheap and\n // LLM-free. The client is fetched once and reused for both calls.\n const client = await connectClient();\n if (client) {\n if (process.env.MEMOIR_SUMMARIZE !== \"0\") {\n const summary = await callMemoirTool(client, \"memoir_summarize\", {\n depth: 1,\n });\n if (summary) {\n output.system?.unshift(\n `[memoir] Prior context already stored (memoir):\\n${summary}`,\n );\n log(\"system.transform: proactive recall injected for session\", sid);\n }\n } else {\n log(\"system.transform: recall skipped (MEMOIR_SUMMARIZE=0)\");\n }\n\n const status = await callMemoirTool(client, \"memoir_status\", {});\n if (status) {\n output.system?.unshift(`[memoir] Memory store status:\\n${status}`);\n log(\"system.transform: store status injected for session\", sid);\n }\n }\n }\n } catch (e) {\n log(\"system.transform: failed\", e);\n }\n },\n\n dispose: async (): Promise<void> => {\n try {\n if (process.env.MEMOIR_AUTO_SAVE === \"1\") {\n const client = await connectClient();\n if (client) {\n for (const [sid, count] of messageCounts) {\n await callMemoirTool(client, \"memoir_remember\", {\n content: `Session ended \u2014 ${count} user messages exchanged`,\n path: `session.${sid}`,\n merge_policy: \"replace\",\n });\n }\n }\n }\n } catch (e) {\n log(\"dispose: save failed\", e);\n }\n sessionsWithStartupHint.clear();\n parentSessions.clear();\n capturePending.clear();\n captureLifecycle.clear();\n branchMatcher.clear();\n messageCounts.clear();\n turnStatus.clear();\n lastCaptured.clear();\n await runtime.close();\n },\n };\n\n return hooks;\n};\n\nconst plugin: PluginModule = {\n id: \"opencode-memoir\",\n server: MemoirOpenCode,\n};\n\nexport default plugin;\n"],
5
+ "mappings": ";AAAA,SAAS,gBAAgB,iBAAiB;AAC1C,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAoB9B,IAAI,gBAA+B;AACnC,IAAI;AACJ,IAAI;AAEJ,SAAS,iBAAgC;AACvC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,IAAK,QAAO;AAChB,QAAM,YAAY,QAAQ,IAAI,kBAAkB,KAAK,QAAQ,GAAG,UAAU,OAAO;AACjF,QAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACjD,SAAO,KAAK,WAAW,YAAY,iBAAiB,IAAI,MAAM;AAChE;AAEA,SAAS,mBAAkC;AACzC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACjD,MAAI,iBAAiB,OAAO,kBAAkB,MAAM;AAClD,oBAAgB,eAAe;AAC/B,mBAAe;AACf,oBAAgB;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAwB;AAC3C,MAAI,iBAAiB,OAAO;AAC1B,QAAI,QAAQ,IAAI,iBAAiB,KAAK;AACpC,aAAO,MAAM,SAAS,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO;AAAA,IACvD;AACA,WAAO,MAAM;AAAA,EACf;AACA,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,WAAW,MAAyB;AAC3C,QAAM,CAAC,SAAS,GAAG,OAAO,IAAI;AAC9B,MAAI,OAAO,YAAY,YAAY,QAAQ,CAAC,aAAa,OAAO;AAC9D,WAAO,GAAG,QAAQ,QAAQ,SAAS,EAAE,CAAC,KAAK,QAAQ,IAAI,WAAW,EAAE,KAAK,GAAG,CAAC;AAAA,EAC/E;AACA,SAAO,KAAK,IAAI,WAAW,EAAE,KAAK,GAAG;AACvC;AAEA,SAAS,SAAS,MAAuB;AACvC,QAAM,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,KAAK,GAAG,EAAE,QAAQ,KAAK,EAAE;AACrE,QAAM,OAAO,WAAW,EAAE,KAAK,WAAW,IAAI,CAAC;AAAA;AAC/C,QAAM,cAAc,iBAAiB;AACrC,MAAI,aAAa;AACf,QAAI;AACF,gBAAU,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,qBAAe,aAAa,IAAI;AAChC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,UAAQ,OAAO,MAAM,IAAI;AAC3B;AAGO,SAAS,OAAO,MAAuB;AAC5C,WAAS,IAAI;AACf;;;ACnFA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAE9B,IAAM,QAAQ,oBAAI,IAAoB;AAS/B,SAAS,WAAW,MAAsB;AAC/C,QAAM,SAAS,MAAM,IAAI,IAAI;AAC7B,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,OAAO,cAAc,IAAI,IAAI,aAAa,IAAI,IAAI,YAAY,GAAG,CAAC;AACxE,QAAM,OAAO,aAAa,MAAM,MAAM;AACtC,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO;AACT;;;ACEO,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAQpC,IAAM,sBAAsB,WAAW,sBAAsB;AActD,SAAS,iBAAiB,OAAiC;AAChE,QAAM,QAAyB;AAAA,IAC7B,MAAM;AAAA,IACN,aACE;AAAA,IACF,QAAQ;AAAA,IACR,YAAY;AAAA,MACV,KAAK;AAAA,MACL,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,CAAC,oBAAoB,GAAG;AAAA,IAC1B;AAAA,IACA,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AACA,MAAI,MAAO,OAAM,QAAQ;AACzB,SAAO;AACT;AAoBO,SAAS,mBAAmB,MAA2C;AAC5E,QAAM,aAAa;AAAA,IACjB,QAAQ,IAAI,oBAAoB,KAAK;AAAA,IACrC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,SAAO,WAAW,KAAK,CAAC,cAAc,WAAW,SAAS,GAAG,CAAC;AAChE;AAGA,eAAsB,kBACpB,QACA,iBACA,MACe;AAaf,QAAM,MAAO,QAA2E;AACxF,MAAI,CAAC,KAAK,aAAa;AACrB,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,MAAI;AACF,UAAM,IAAI,YAAY;AAAA,MACpB,MAAM,EAAE,IAAI,gBAAgB;AAAA,MAC5B,MAAM;AAAA,QACJ,OAAO;AAAA,UACL;AAAA,YACE,MAAM;AAAA,YACN,OAAO;AAAA,YACP,aAAa;AAAA,YACb,QAAQ;AAAA,YACR,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AACD,QAAI,oCAAoC,iBAAiB,UAAU,KAAK,QAAQ,QAAQ;AAAA,EAC1F,SAAS,GAAG;AACV,QAAI,4BAA4B,CAAC;AACjC,UAAM;AAAA,EACR;AACF;;;ACpGA,IAAM,4BAA4B;AAElC,SAAS,kBAA0B;AACjC,QAAM,MAAM,QAAQ,IAAI,0BAA0B,KAAK;AACvD,MAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;AAC5C,QAAM,IAAI,OAAO,SAAS,KAAK,EAAE;AACjC,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,kBAA2B;AAElC,SAAO,QAAQ,IAAI,qBAAqB;AAC1C;AAMA,SAAS,YAAY,KAA0B;AAC7C,QAAM,QAAQ,IAAI,SAAS,CAAC;AAC5B,SAAO,MACJ,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,QAAQ,EAC7D,IAAI,CAAC,MAAM,EAAE,IAAc,EAC3B,KAAK,IAAI,EACT,KAAK;AACV;AAEA,SAAS,WAAW,MAAkC;AACpD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,cAAQ,QAAQ,WAAW,YAAY;AAAA,EAC3C;AACF;AAMO,SAAS,wBAAwB,UAAiC;AACvE,SAAO,SACJ,IAAI,CAAC,MAAM;AACV,UAAM,OAAO,YAAY,CAAC;AAC1B,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,GAAG,WAAW,EAAE,MAAM,IAAI,CAAC;AAAA,EAAK,IAAI;AAAA,EAC7C,CAAC,EACA,OAAO,CAAC,SAAyB,SAAS,IAAI,EAC9C,KAAK,MAAM;AAChB;AAOO,SAAS,mBAAmB,UAAwC;AACzE,MAAI,eAAe;AACnB,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,QAAI,SAAS,CAAC,EAAE,MAAM,SAAS,eAAe,YAAY,SAAS,CAAC,CAAC,GAAG;AACtE,qBAAe;AACf;AAAA,IACF;AAAA,EACF;AACA,MAAI,eAAe,EAAG,QAAO;AAE7B,QAAM,WAAW,MAAM;AACrB,aAAS,IAAI,eAAe,GAAG,KAAK,GAAG,KAAK;AAC1C,UAAI,SAAS,CAAC,EAAE,MAAM,SAAS,OAAQ,QAAO;AAAA,IAChD;AACA,WAAO;AAAA,EACT,GAAG;AACH,MAAI,UAAU,EAAG,QAAO;AAExB,QAAM,OAAO,SAAS,MAAM,SAAS,eAAe,CAAC;AACrD,SAAO,wBAAwB,IAAI;AACrC;AAOO,SAAS,kBACd,YACA,WAAW,gBAAgB,GACL;AACtB,MAAI,YAAY,EAAG,QAAO,eAAe,QAAQ,WAAW,SAAS;AACrE,SAAO,eAAe,QAAQ,WAAW,UAAU;AACrD;AAMA,IAAM,wBAAwB,WAAW,mBAAmB;AAOrD,SAAS,qBAAqB,YAA4B;AAC/D,SAAO,sBAAsB,QAAQ,kBAAkB,UAAU;AACnE;AAMA,SAAS,uBAAuB,UAAwC;AACtE,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,QAAI,SAAS,CAAC,EAAE,MAAM,SAAS,eAAe,YAAY,SAAS,CAAC,CAAC,GAAG;AACtE,aAAO,SAAS,CAAC,EAAE,MAAM,MAAM;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAaA,eAAsB,YACpB,QACA,WACA,cACe;AACf,MAAI,CAAC,gBAAgB,GAAG;AACtB,QAAI,2CAA2C;AAC/C;AAAA,EACF;AACA,MAAI,oCAAoC,SAAS;AAEjD,MAAI;AACF,UAAM,MAAO,QAAqD;AAClE,QAAI,CAAC,KAAK,UAAU;AAClB,UAAI,kDAAkD;AACtD,UAAI,8DAAyD;AAC7D;AAAA,IACF;AAEA,UAAM,MAAM,MAAM,IAAI,SAAS;AAAA,MAC7B,MAAM,EAAE,IAAI,UAAU;AAAA,MACtB,OAAO,EAAE,OAAO,GAAG;AAAA,IACrB,CAAC;AACD,UAAM,MAAM,IAAI;AAChB,UAAM,WAA0B,MAAM,QAAQ,GAAG,IAAK,MAAwB,CAAC;AAC/E,QAAI,SAAS,WAAW,EAAG;AAE3B,UAAM,SAAS,uBAAuB,QAAQ;AAC9C,QAAI,CAAC,OAAQ;AACb,QAAI,aAAa,IAAI,SAAS,MAAM,QAAQ;AAC1C,UAAI,wDAAmD;AACvD;AAAA,IACF;AAEA,UAAM,aAAa,mBAAmB,QAAQ;AAC9C,QAAI,CAAC,kBAAkB,UAAU,GAAG;AAClC,UAAI,wDAAmD;AACvD,mBAAa,IAAI,WAAW,MAAM;AAClC;AAAA,IACF;AAEA,QAAI,sDAAsD,WAAW,QAAQ,QAAQ;AACrF,UAAM,kBAAkB,QAAQ,WAAW,qBAAqB,UAAU,CAAC;AAC3E,iBAAa,IAAI,WAAW,MAAM;AAAA,EACpC,SAAS,GAAG;AACV,QAAI,sBAAsB,CAAC;AAAA,EAC7B;AACF;;;AC9MO,IAAM,mBAAN,MAAuB;AAAA,EACX,SAAS,oBAAI,IAA2B;AAAA,EACxC,eAAe,oBAAI,IAAoB;AAAA,EAExD,MAAM,QAAsB;AAC1B,QAAI,KAAK,OAAO,IAAI,MAAM,EAAG;AAC7B,QAAIA;AACJ,UAAM,OAAO,IAAI,QAAc,CAAC,gBAAgB;AAC9C,MAAAA,WAAU;AAAA,IACZ,CAAC;AACD,SAAK,OAAO,IAAI,QAAQ,EAAE,MAAM,SAAAA,SAAQ,CAAC;AAAA,EAC3C;AAAA,EAEA,WAAW,QAAgB,gBAA8B;AACvD,UAAM,UAAU,KAAK,OAAO,IAAI,MAAM;AACtC,QAAI,CAAC,QAAS;AACd,YAAQ,iBAAiB;AACzB,SAAK,aAAa,IAAI,gBAAgB,MAAM;AAAA,EAC9C;AAAA,EAEA,WAAW,QAAsB;AAC/B,UAAM,UAAU,KAAK,OAAO,IAAI,MAAM;AACtC,QAAI,CAAC,QAAS;AACd,QAAI,QAAQ,eAAgB,MAAK,aAAa,OAAO,QAAQ,cAAc;AAC3E,SAAK,OAAO,OAAO,MAAM;AACzB,YAAQ,QAAQ;AAAA,EAClB;AAAA,EAEA,cAAc,WAAyB;AACrC,UAAM,SAAS,KAAK,aAAa,IAAI,SAAS;AAC9C,QAAI,OAAQ,MAAK,WAAW,MAAM;AAAA,EACpC;AAAA,EAEA,MAAM,MAAM,YAAY,KAA0B;AAChD,UAAM,UAAU,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,QAAQ,IAAI;AACvE,QAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAI;AACJ,UAAM,WAAW,IAAI,QAAe,CAACA,aAAY;AAC/C,cAAQ,WAAW,MAAMA,SAAQ,KAAK,GAAG,SAAS;AAAA,IACpD,CAAC;AACD,UAAM,UAAU,QAAQ,IAAI,OAAO,EAAE,KAAK,MAAM,IAAa;AAC7D,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC;AACrD,QAAI,MAAO,cAAa,KAAK;AAC7B,QAAI,CAAC,OAAQ,KAAI,4DAA4D;AAC7E,WAAO;AAAA,EACT;AAAA,EAEA,QAAc;AACZ,eAAW,UAAU,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC,EAAG,MAAK,WAAW,MAAM;AAAA,EACtE;AACF;;;AC5DA,SAA4B,aAAa;AACzC,SAAS,kBAAkB,oBAAoB;AAC/C,SAAS,cAAc;AACvB,SAAS,qCAAqC;;;ACH9C,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;AAGvB,SAAS,aAAa,GAAmB;AAC9C,MAAI;AACF,WAAO,aAAa,CAAC;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,QAAQ,GAAmB;AACzC,SAAO,EAAE,QAAQ,SAAS,GAAG;AAC/B;AAYO,SAAS,gBAAgB,MAAc,QAAQ,IAAI,GAAG,UAA2B;AACtF,MAAI,SAAU,QAAO;AACrB,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,SAAU,QAAO;AAErB,QAAM,UAAU,aAAa,GAAG;AAChC,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,aAAa,OAAO,CAAC,aAAa,iBAAiB,GAAG;AAAA,MACpE,KAAK;AAAA,MACL,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,MAClC,SAAS;AAAA,IACX,CAAC,EAAE,KAAK;AACR,iBAAa,aAAa,OAAO;AAAA,EACnC,QAAQ;AACN,iBAAa,QAAQ,OAAO;AAAA,EAC9B;AAEA,SAAOA,MAAKD,SAAQ,GAAG,WAAW,QAAQ,UAAU,CAAC;AACvD;;;AD7BA,SAAS,aAAa,GAAoB;AACxC,SAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAClD;AAGA,IAAM,WAAW;AACjB,IAAM,YAAY;AAElB,SAAS,iBAAyB;AAChC,SAAO,WAAW,KAAK,MAAM,KAAK,OAAO,KAAK,YAAY,WAAW,EAAE;AACzE;AAEA,SAAS,WAAW,MAAgC;AAClD,SAAO,IAAI,QAAQ,CAACE,aAAY;AAC9B,UAAM,MAAM,aAAa;AACzB,QAAI,KAAK,SAAS,MAAMA,SAAQ,KAAK,CAAC;AACtC,QAAI,KAAK,aAAa,MAAM,IAAI,MAAM,MAAMA,SAAQ,IAAI,CAAC,CAAC;AAC1D,QAAI,OAAO,MAAM,WAAW;AAAA,EAC9B,CAAC;AACH;AAEA,eAAe,iBAAiB,QAAQ,IAAqB;AAC3D,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,OAAO,eAAe;AAC5B,QAAI,MAAM,WAAW,IAAI,EAAG,QAAO;AAAA,EACrC;AACA,QAAM,IAAI,MAAM,6DAA6D;AAC/E;AAEA,SAAS,YAAY,MAAc,YAAY,KAAuB;AACpE,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,UAAM,UAAU,MAAM;AACpB,YAAM,OAAO,iBAAiB,EAAE,MAAM,MAAM,YAAY,CAAC;AACzD,WAAK,KAAK,WAAW,MAAM;AACzB,aAAK,QAAQ;AACb,QAAAA,SAAQ;AAAA,MACV,CAAC;AACD,WAAK,KAAK,SAAS,MAAM;AACvB,aAAK,QAAQ;AACb,YAAI,KAAK,IAAI,IAAI,UAAU;AACzB,iBAAO,IAAI,MAAM,2CAA2C,CAAC;AAAA,QAC/D,OAAO;AACL,qBAAW,SAAS,GAAG;AAAA,QACzB;AAAA,MACF,CAAC;AAAA,IACH;AACA,YAAQ;AAAA,EACV,CAAC;AACH;AAOO,IAAM,gBAAN,MAAoB;AAAA,EAQzB,YACmB,SACA,KACA,KACA,eAA0C,CAAC,GAC5D;AAJiB;AACA;AACA;AACA;AAAA,EAChB;AAAA,EAJgB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAXX,QAAkC;AAAA,EAClC,aAAqC;AAAA,EACrC,aAAkC;AAAA,EAClC,YAAwB;AAAA,EACxB,aAA4B;AAAA,EAC5B,iBAAsC;AAAA,EAStC,gBAAsB;AAC5B,SAAK,QAAQ;AACb,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,QAAsB;AAC1B,QAAI,KAAK,aAAa,KAAK,WAAY,QAAO,KAAK;AACnD,QAAI,KAAK,eAAgB,QAAO,KAAK;AAErC,UAAM,SAAS,YAA0B;AAIvC,YAAM,WAAW;AACjB,eAAS,UAAU,GAAG,UAAU,UAAU,WAAW;AACnD,cAAM,OAAO,KAAK,cAAe,MAAM,iBAAiB;AACxD,aAAK,aAAa;AAClB,cAAM,MAAM,IAAI,IAAI,oBAAoB,IAAI,MAAM;AAElD,cAAM,OAAO;AAAA,UACX,GAAG,KAAK,QAAQ,MAAM,CAAC;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO,IAAI;AAAA,QACb;AAEA,cAAM,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,MAAM;AAAA,UACxC,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI;AAAA,UACnC,KAAK,KAAK,MAAM,aAAa,KAAK,GAAG,IAAI,QAAQ,IAAI;AAAA,UACrD,OAAO,CAAC,UAAU,UAAU,MAAM;AAAA,QACpC,CAAC;AAED,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACxC,gBAAI,eAAe,MAAM,SAAS,EAAE,QAAQ,CAAC;AAAA,UAC/C,CAAC;AAAA,QACH;AAEA,cAAM,SAAS,IAAI,QAAuB,CAACA,aAAY;AACrD,eAAK,KAAK,QAAQ,CAAC,SAASA,SAAQ,IAAI,CAAC;AAAA,QAC3C,CAAC;AAED,cAAM,IAAI,QAAc,CAACA,UAAS,WAAW;AAC3C,eAAK,KAAK,SAASA,QAAO;AAC1B,eAAK,KAAK,SAAS,MAAM;AAAA,QAC3B,CAAC,EAAE,MAAM,CAAC,MAAe;AACvB,gBAAM,IAAI,MAAM,2BAA2B,KAAK,QAAQ,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,EAAE;AAAA,QAClF,CAAC;AAED,aAAK,aAAa;AAElB,YAAI;AACF,gBAAM,YAAY,IAAI;AAAA,QACxB,QAAQ;AAEN,eAAK,KAAK;AACV,gBAAM;AACN,eAAK,aAAa;AAClB,eAAK,YAAY;AACjB,eAAK,aAAa;AAClB,eAAK,cAAc;AACnB;AAAA,QACF;AAKA,YAAI,KAAK,eAAe,MAAM;AAC5B,eAAK,YAAY;AACjB,cAAI,gCAAgC,IAAI,SAAS,CAAC;AAClD,iBAAO;AAAA,QACT;AAEA,aAAK,YAAY;AACjB,YAAI,gCAAgC,IAAI,SAAS,CAAC;AAClD,eAAO;AAAA,MACT;AAEA,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E,GAAG;AAEH,SAAK,iBAAiB;AACtB,QAAI;AACF,aAAO,MAAM;AAAA,IACf,SAAS,GAAG;AACV,WAAK,iBAAiB;AACtB,WAAK,YAAY;AACjB,UAAI,KAAK,WAAY,MAAK,WAAW,KAAK;AAC1C,WAAK,aAAa;AAClB,WAAK,aAAa;AAClB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,UAA2B;AAC/B,QAAI,KAAK,MAAO,QAAO,KAAK,MAAM;AAElC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,cAAc,YAAY;AAC7B,cAAM,MAAM,MAAM,KAAK,MAAM;AAC7B,cAAM,EAAE,QAAQ,UAAU,IAAI,KAAK,aAAa,yBAAyB,GAAG,KAAK;AAAA,UAC/E,QAAQ,IAAI,OAAO,EAAE,MAAM,mBAAmB,SAAS,QAAQ,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC;AAAA,UACtF,WAAW,IAAI,8BAA8B,GAAG;AAAA,QAClD;AAEA,kBAAU,UAAU,MAAM;AACxB,cAAI,+CAA+C;AACnD,eAAK,cAAc;AAAA,QACrB;AAEA,YAAI;AACF,gBAAM,OAAO,QAAQ,SAAS;AAAA,QAChC,SAAS,GAAG;AACV,eAAK,cAAc;AACnB,gBAAM;AAAA,QACR;AAEA,aAAK,QAAQ,EAAE,OAAO;AACtB,eAAO;AAAA,MACT,GAAG;AAAA,IACL;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAO;AACd,UAAI;AACF,cAAM,KAAK,MAAM,OAAO,MAAM;AAAA,MAChC,SAAS,GAAG;AACV,YAAI,mCAAmC,CAAC;AAAA,MAC1C;AACA,WAAK,cAAc;AAAA,IACrB;AACA,QAAI,KAAK,YAAY;AACnB,YAAM,OAAO,KAAK;AAClB,WAAK,aAAa;AAClB,UAAI;AACF,cAAM,IAAI,QAAc,CAACA,aAAY;AACnC,cAAI,KAAK,aAAa,MAAM;AAC1B,YAAAA,SAAQ;AACR;AAAA,UACF;AACA,gBAAM,QAAQ,WAAW,MAAM;AAC7B,iBAAK,KAAK,SAAS;AACnB,YAAAA,SAAQ;AAAA,UACV,GAAG,GAAK;AACR,gBAAM,MAAM;AACZ,eAAK,KAAK,QAAQ,MAAM;AACtB,yBAAa,KAAK;AAClB,YAAAA,SAAQ;AAAA,UACV,CAAC;AACD,eAAK,KAAK,SAAS,MAAM;AACvB,yBAAa,KAAK;AAClB,YAAAA,SAAQ;AAAA,UACV,CAAC;AACD,eAAK,KAAK;AAAA,QACZ,CAAC;AAAA,MACH,SAAS,GAAG;AACV,YAAI,kCAAkC,CAAC;AAAA,MACzC;AAAA,IACF;AACA,SAAK,YAAY;AACjB,SAAK,iBAAiB;AACtB,SAAK,aAAa;AAAA,EACpB;AACF;AAMA,eAAsB,eACpB,QACA,MACA,MACwB;AACxB,MAAI;AACF,UAAM,SAAU,MAAM,OAAO,SAAS;AAAA,MACpC;AAAA,MACA,WAAW,QAAQ,CAAC;AAAA,IACtB,CAAC;AACD,QAAI,OAAO,SAAS;AAClB,UAAI,mBAAmB,IAAI,iBAAiB;AAC5C,aAAO;AAAA,IACT;AACA,eAAW,SAAS,OAAO,SAAS;AAClC,UAAI,MAAM,SAAS,UAAU,MAAM,QAAQ,MAAM;AAC/C,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,mBAAmB,IAAI,WAAW,CAAC;AACvC,WAAO;AAAA,EACT;AACF;;;AEnSA,SAAS,gBAAAC,qBAAoB;;;ACiBtB,SAAS,kBAAkB,KAA8C;AAC9E,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO;AAAA,MACL,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,MAC5D,cAAc,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe;AAAA,IAChF;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;ADrBO,SAASC,iBAAgB,KAAc,UAA2B;AACvE,SAAO,gBAAoB,KAAK,QAAQ;AAC1C;AAGO,SAAS,iBAAiB,MAAM,QAAQ,IAAI,GAAW;AAC5D,MAAI;AACF,WAAOC,cAAa,OAAO,CAAC,UAAU,gBAAgB,GAAG;AAAA,MACvD,KAAK,aAAa,GAAG;AAAA,MACrB,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,MAClC,SAAS;AAAA,IACX,CAAC,EAAE,KAAK;AAAA,EACV,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,IAAM,sBAAN,MAA0B;AAAA,EACvB,WAA0B,QAAQ,QAAQ;AAAA,EAElD,MAAM,QAAgB,KAAa,OAA+C;AAChF,UAAM,OAAO,KAAK,SAAS,KAAK,MAAM,KAAK,SAAS,QAAQ,KAAK,KAAK,CAAC;AACvE,SAAK,WAAW,KAAK,MAAM,MAAM,MAAS;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,cAAc,QAAiC;AAC3D,UAAM,MAAM,MAAM,eAAe,QAAQ,eAAe;AACxD,WAAO,kBAAkB,GAAG,EAAE,UAAU;AAAA,EAC1C;AAAA,EAEA,MAAc,SACZ,QACA,KACA,OACe;AACf,UAAM,aAAa,iBAAiB,GAAG;AACvC,QAAI,CAAC,WAAY;AAEjB,QAAK,MAAM,KAAK,cAAc,MAAM,MAAO,WAAY;AACvD,QAAI,SAAS,CAAE,MAAM,MAAM,EAAI;AAE/B,QAAK,MAAM,KAAK,cAAc,MAAM,MAAO,WAAY;AAKvD,UAAM,eAAe,QAAQ,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,WAAW,QAAQ,QAAQ;AAAA,EAClC;AACF;;;AEnEO,SAAS,gBAAgB,WAAkC;AAChE,QAAM,EAAE,QAAQ,aAAa,IAAI,kBAAkB,SAAS;AAC5D,QAAM,cAAc,gBAAgB;AACpC,MAAI,CAAC,UAAU,eAAe,EAAG,QAAO;AACxC,MAAI,CAAC,OAAQ,QAAO,8BAA8B,WAAW;AAC7D,SAAO,YAAY,MAAM,GAAG,cAAc,IAAI,2BAAwB,WAAW,eAAe,EAAE;AACpG;;;ACIA,SAAS,QAAQ,MAAmC;AAClD,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK,EAAE,YAAY;AACpD,MAAI,UAAU,UAAa,UAAU,GAAI,QAAO;AAChD,SAAO,UAAU,OAAO,UAAU;AACpC;AAEA,SAAS,6BAAsC;AAC7C,SACE,QAAQ,4CAA4C,KACpD,QAAQ,uBAAuB,KAC/B;AAEJ;AAEA,IAAM,iBAAyB,OAAO,OAAO,eAAe;AAC1D,QAAM,OAAQ,cAAc,CAAC;AAG7B,QAAM,cAAe,SAAS,CAAC;AAC/B,QAAM,YAAY,YAAY;AAC9B,QAAM,YAAY,aAAa,YAAY,aAAa,QAAQ,IAAI,CAAC;AACrE,QAAM,YAAYC,iBAAgB,WAAW,KAAK,KAAK;AAEvD,QAAM,aAAa,CAAC,YAAY;AAChC,MAAI,WAAW;AACb,eAAW,KAAK,WAAW,SAAS;AAAA,EACtC;AAEA,QAAM,cAAc,YAAY,EAAE,cAAc,UAAU,IAAI;AAC9D,QAAM,UAAU,IAAI,cAAc,YAAY,aAAa,SAAS;AACpE,QAAM,gBAAgB,IAAI,oBAAoB;AAC9C,QAAM,0BAA0B,oBAAI,IAAY;AAChD,QAAM,iBAAiB,oBAAI,IAAY;AACvC,QAAM,eAAe,oBAAI,IAAoB;AAC7C,QAAM,iBAAiB,oBAAI,IAAY;AACvC,QAAM,mBAAmB,IAAI,iBAAiB;AAC9C,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,aAAa,oBAAI,IAAoB;AAM3C,MAAI;AAKJ,QAAM,gBAAgB,YAAY;AAChC,QAAI;AACF,aAAO,MAAM,QAAQ,QAAQ;AAAA,IAC/B,SAAS,GAAG;AACV,UAAI,2CAA2C,CAAC;AAChD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,kBAAkB,OAAO,QAA+B;AAC5D,QAAI,CAAC,aAAa,CAAC,eAAe,IAAI,GAAG,KAAK,eAAe,IAAI,GAAG,EAAG;AACvE,mBAAe,IAAI,GAAG;AACtB,QAAI;AACF,YAAM,SAAS,MAAM,cAAc;AACnC,UAAI,OAAQ,OAAM,cAAc,MAAM,QAAQ,WAAW,MAAM,iBAAiB,MAAM,CAAC;AACvF,YAAM,YAAY,WAAW,KAAK,YAAY;AAAA,IAChD,SAAS,GAAG;AACV,UAAI,0BAA0B,CAAC;AAAA,IACjC,UAAE;AACA,qBAAe,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,QAAiC;AAAA,IACrC,MAAM;AAAA,IAEN,QAAQ,OAAO,WAAkC;AAC/C,UAAI,6BAA6B,KAAK,UAAU,UAAU,CAAC;AAE3D,UAAI;AACF,cAAM,MAAM,MAAM,QAAQ,MAAM;AAChC,oBAAY,EAAE,MAAM,UAAU,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK;AACjE,YAAI,mCAAmC,IAAI,SAAS,GAAG,YAAY,SAAS;AAAA,MAC9E,SAAS,GAAG;AACV,YAAI,mDAAmD,CAAC;AACxD,oBAAY;AAAA,MACd;AAEA,YAAM,MAAM,OAAO,OAAO,CAAC;AAC3B,UAAI,UAAW,KAAI,SAAS;AAC5B,aAAO,MAAM;AAIb,UAAI,UAAW,OAAM,MAAM,EAAE,QAAQ,UAAU;AAK/C,YAAM,aAAa,mBAAmB;AAAA,QACpC,gBAAgB,QAAQ,IAAI;AAAA,QAC5B,YAAa,OAAoC;AAAA,QACjD,OAAQ,OAA8B;AAAA,MACxC,CAAC;AACD,YAAM,aAAa;AAGnB,iBAAW,QAAQ;AAAA,QACjB,GAAI,WAAW,SAAS,CAAC;AAAA,QACzB,CAAC,iBAAiB,GAAG,iBAAiB,UAAU;AAAA,MAClD;AACA,UAAI,uCAAuC,cAAc,oBAAoB;AAC7E;AAAA,QACE;AAAA,QACA,2BAA2B,IAAI,sBAAsB;AAAA,QACrD;AAAA,QACA,QAAQ,IAAI,8CAA8C;AAAA,QAC1D;AAAA,QACA,QAAQ,IAAI,yBAAyB;AAAA,MACvC;AAEA,aAAO,UAAU,OAAO,WAAW,CAAC;AACpC,aAAO,QAAQ,gBAAgB,IAAI;AAAA,QACjC,aAAa;AAAA,QACb,UAAU,WAAW,cAAc;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,aAAa,OACX,QACA,WACkB;AAClB,UAAI;AACF,YAAI,WAAW;AACb,iBAAO,IAAI,eAAe;AAAA,QAC5B;AAAA,MACF,SAAS,GAAG;AACV,YAAI,qBAAqB,CAAC;AAAA,MAC5B;AAAA,IACF;AAAA,IAEA,gBAAgB,OACdC,QACA,WAGkB;AAClB,UAAI;AACF,cAAM,MAAMA,OAAM,aAAa;AAC/B,cAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,cAAM,kBAAkB,MAAM;AAAA,UAC5B,CAAC,SAAS,KAAK,SAAS,aAAa,KAAK,UAAU;AAAA,QACtD;AACA,cAAM,cAAc,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,SAAS,KAAK,cAAc,IAAI;AACrF,YAAIA,OAAM,UAAU,qBAAqB,mBAAmB,YAAa;AAEzE,uBAAe,IAAI,GAAG;AACtB,sBAAc,IAAI,MAAM,cAAc,IAAI,GAAG,KAAK,KAAK,CAAC;AAExD,cAAM,SAAS,MAAM,cAAc;AACnC,YAAI,QAAQ;AACV,gBAAM,cAAc,MAAM,QAAQ,WAAW,MAAM,iBAAiB,MAAM,CAAC;AAC3E,gBAAM,SAAS,MAAM,eAAe,QAAQ,iBAAiB,CAAC,CAAC;AAC/D,gBAAM,OAAO,gBAAgB,MAAM;AACnC,cAAI,KAAM,YAAW,IAAI,KAAK,IAAI;AAAA,cAC7B,YAAW,OAAO,GAAG;AAAA,QAC5B;AASA,YAAI,gEAAgE,GAAG;AACvE,cAAM,gBAAgB,GAAG;AAAA,MAC3B,SAAS,GAAG;AACV,YAAI,wBAAwB,CAAC;AAAA,MAC/B;AAAA,IACF;AAAA,IAEA,uBAAuB,OACrBA,QACA,WACkB;AAClB,UAAI;AACF,YAAIA,OAAM,SAAS,UAAU,OAAO,MAAM,kBAAkB,kBAAmB;AAC/E,yBAAiB,MAAMA,OAAM,MAAM;AACnC,YAAI,CAAC,2BAA2B,GAAG;AACjC;AAAA,YACE;AAAA,UACF;AACA;AAAA,QACF;AAMA,eAAO,KAAK,aAAa;AACzB,YAAI,uDAAuD;AAAA,MAC7D,SAAS,GAAG;AACV,YAAI,+BAA+B,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,IAEA,sBAAsB,OACpBA,QACA,WACkB;AAClB,UAAI;AACF,YAAIA,OAAM,SAAS,UAAUA,OAAM,MAAM,kBAAkB,kBAAmB;AAC9E,cAAM,iBAAiB,QAAQ,UAAU;AACzC,YAAI,QAAQ,UAAU,eAAe,QAAQ,gBAAgB;AAC3D,2BAAiB,WAAWA,OAAM,QAAQ,cAAc;AACxD;AAAA,QACF;AACA,yBAAiB,WAAWA,OAAM,MAAM;AAAA,MAC1C,SAAS,GAAG;AACV,YAAI,8BAA8B,CAAC;AACnC,yBAAiB,WAAWA,OAAM,MAAM;AAAA,MAC1C;AAAA,IACF;AAAA,IAEA,OAAO,OAAOA,WAA8E;AAC1F,UAAI;AACF,cAAM,QAAQA,OAAM;AACpB,YAAI,OAAO,SAAS,kBAAkB,OAAO,SAAS,gBAAiB;AACvE,cAAM,YAAY,MAAM,YAAY;AACpC,YAAI,UAAW,kBAAiB,cAAc,SAAS;AAAA,MACzD,SAAS,GAAG;AACV,YAAI,iBAAiB,CAAC;AAAA,MACxB;AAAA,IACF;AAAA,IAEA,sCAAsC,OACpCA,QACA,WACkB;AAClB,UAAI;AACF,cAAM,MAAMA,OAAM,aAAa;AAE/B,cAAM,SAAS,WAAW,IAAI,GAAG;AACjC,YAAI,OAAQ,QAAO,QAAQ,KAAK,MAAM;AAEtC,YAAIA,OAAM,aAAa,eAAe,IAAI,GAAG,KAAK,CAAC,wBAAwB,IAAI,GAAG,GAAG;AACnF,kCAAwB,IAAI,GAAG;AAC/B,cAAI,uDAAuD,GAAG;AAM9D,iBAAO,QAAQ,QAAQ,WAAW,mBAAmB,CAAC;AAKtD,gBAAM,SAAS,MAAM,cAAc;AACnC,cAAI,QAAQ;AACV,gBAAI,QAAQ,IAAI,qBAAqB,KAAK;AACxC,oBAAM,UAAU,MAAM,eAAe,QAAQ,oBAAoB;AAAA,gBAC/D,OAAO;AAAA,cACT,CAAC;AACD,kBAAI,SAAS;AACX,uBAAO,QAAQ;AAAA,kBACb;AAAA,EAAoD,OAAO;AAAA,gBAC7D;AACA,oBAAI,2DAA2D,GAAG;AAAA,cACpE;AAAA,YACF,OAAO;AACL,kBAAI,uDAAuD;AAAA,YAC7D;AAEA,kBAAMC,UAAS,MAAM,eAAe,QAAQ,iBAAiB,CAAC,CAAC;AAC/D,gBAAIA,SAAQ;AACV,qBAAO,QAAQ,QAAQ;AAAA,EAAkCA,OAAM,EAAE;AACjE,kBAAI,uDAAuD,GAAG;AAAA,YAChE;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACV,YAAI,4BAA4B,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,IAEA,SAAS,YAA2B;AAClC,UAAI;AACF,YAAI,QAAQ,IAAI,qBAAqB,KAAK;AACxC,gBAAM,SAAS,MAAM,cAAc;AACnC,cAAI,QAAQ;AACV,uBAAW,CAAC,KAAK,KAAK,KAAK,eAAe;AACxC,oBAAM,eAAe,QAAQ,mBAAmB;AAAA,gBAC9C,SAAS,wBAAmB,KAAK;AAAA,gBACjC,MAAM,WAAW,GAAG;AAAA,gBACpB,cAAc;AAAA,cAChB,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACV,YAAI,wBAAwB,CAAC;AAAA,MAC/B;AACA,8BAAwB,MAAM;AAC9B,qBAAe,MAAM;AACrB,qBAAe,MAAM;AACrB,uBAAiB,MAAM;AACvB,oBAAc,MAAM;AACpB,oBAAc,MAAM;AACpB,iBAAW,MAAM;AACjB,mBAAa,MAAM;AACnB,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAM,SAAuB;AAAA,EAC3B,IAAI;AAAA,EACJ,QAAQ;AACV;AAEA,IAAO,gBAAQ;",
6
6
  "names": ["resolve", "homedir", "join", "resolve", "execFileSync", "deriveStorePath", "execFileSync", "deriveStorePath", "input", "status"]
7
7
  }
@@ -1,9 +1,5 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
- export type MemoirToolInfo = {
4
- name: string;
5
- description: string;
6
- };
7
3
  type ClientConnection = {
8
4
  client: Client;
9
5
  transport: StreamableHTTPClientTransport;
@@ -27,14 +23,11 @@ export declare class MemoirRuntime {
27
23
  private serverUrl;
28
24
  private serverPort;
29
25
  private startingServer;
30
- private tools;
31
26
  constructor(command: string[], env?: Record<string, string> | undefined, cwd?: string | undefined, dependencies?: MemoirRuntimeDependencies);
32
27
  private cleanupClient;
33
28
  start(): Promise<URL>;
34
29
  /** Get the lazily-connected internal client for this plugin instance. */
35
30
  connect(): Promise<Client>;
36
- /** Discover the live tool catalog once per connection for the small-model prompt. */
37
- listTools(client: Client): Promise<MemoirToolInfo[]>;
38
31
  /** Close the MCP client and stop this instance's HTTP server. */
39
32
  close(): Promise<void>;
40
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAQnG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,6BAA6B,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC;CACzD,CAAC;AAqDF;;;;GAIG;AACH,qBAAa,aAAa;IAUtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZ/B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,KAAK,CAAiC;gBAG3B,OAAO,EAAE,MAAM,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,YAAY,GAAE,yBAA8B;IAG/D,OAAO,CAAC,aAAa;IAMf,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC;IA4E3B,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IA+BhC,qFAAqF;IAC/E,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAkB1D,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAuC7B;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB"}
1
+ {"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAQnG,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,6BAA6B,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC;CACzD,CAAC;AAqDF;;;;GAIG;AACH,qBAAa,aAAa;IAStB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAX/B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,cAAc,CAA6B;IAEnD,YACmB,OAAO,EAAE,MAAM,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,YAAY,GAAE,yBAA8B,EAC3D;IAEJ,OAAO,CAAC,aAAa;IAKf,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,CAyF1B;IAED,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B/B;IAED,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAwC3B;CACF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB"}
@@ -27,7 +27,6 @@ Final report format:
27
27
  - Report only actual tool outcomes. Do not repeat the transcript, expose hidden
28
28
  reasoning, or add advice and commentary. Keep the entire report under 8 lines.
29
29
 
30
- {{TOOLS_SECTION}}
31
30
  Store in the default namespace.
32
31
 
33
32
  --- TURN TRANSCRIPT ---
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKxE,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,GAAG,SAAgB,GAAG,MAAM,CAW5D;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAoC;IAEpD,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAMnE,aAAa;YAKb,QAAQ;IAsBtB,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKxE,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,GAAG,SAAgB,GAAG,MAAM,CAW5D;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAoC;IAEpD,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhF;YAEa,aAAa;YAKb,QAAQ;IAsBtB,KAAK,IAAI,IAAI,CAEZ;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-memoir",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "description": "Memoir memory plugin for OpenCode — Git-versioned, taxonomy-structured AI memory.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "@types/node": "^26.0.0",
54
54
  "esbuild": "^0.28.0",
55
55
  "tsx": "^4.22.4",
56
- "typescript": "^6.0.3"
56
+ "typescript": "^7.0.2"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=20"