opencode-memoir 3.3.1 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,22 +31,10 @@ OpenCode downloads and resolves the package from npm automatically. To pin a ver
31
31
  2. Start coding — the agent can use `memoir_memoir_recall`, `memoir_memoir_remember`, `memoir_memoir_get` MCP tools automatically
32
32
 
33
33
  Automatic capture works without additional OpenCode flags. By default, capture
34
- runs through the regular foreground subagent path for compatibility with older
35
- OpenCode installations.
36
-
37
- To run capture as a native background task, enable OpenCode's experimental
38
- background-subagent support before starting OpenCode:
39
-
40
- ```bash
41
- OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
42
- ```
43
-
44
- With this flag, capture no longer blocks the parent session while the memoir
45
- subagent extracts and stores durable facts.
46
-
47
- OpenCode's umbrella `OPENCODE_EXPERIMENTAL=true` also enables background
48
- subagents unless `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` explicitly
49
- overrides it. The plugin mirrors that precedence.
34
+ runs in a hidden throwaway session with no parent session, so it does not add a
35
+ subtask or response to the active conversation. Each completed turn is
36
+ snapshotted immediately; capture dispatch is then queued per parent session and
37
+ does not block the `chat.message` hook.
50
38
 
51
39
  ## Store configuration
52
40
 
@@ -68,11 +56,10 @@ All optional:
68
56
  | --- | --- |
69
57
  | `MEMOIR_STORE` | Override store path (passed to memoir-mcp as `--store`) |
70
58
  | `MEMOIR_DEBUG=1` | Add verbose diagnostics and error stacks to the configured Memoir log; without it, normal lifecycle and concise error messages are still logged |
71
- | `MEMOIR_LOG` | Log destination: unset uses `$XDG_STATE_HOME/opencode/memoir-plugin-YYYY-MM-DD.log`; `stderr` enables live stderr; any other value is an explicit file path |
72
- | `MEMOIR_AUTO_SAVE` | **Turn capture** (the previous completed turn is saved when the next real user message arrives) is **enabled by default**; set `=0` to disable it. Separate from that, persisting per-session markers at `dispose` only happens when this is set **explicitly to `1`** — by default the dispose-time markers are not written. |
59
+ | `MEMOIR_LOG` | Log destination: unset uses `$XDG_DATA_HOME/opencode/log/memoir/YYYY-MM-DD.log`; `stderr` enables live stderr; any other value is an explicit file path |
60
+ | `MEMOIR_AUTO_SAVE` | **Turn capture** (the previous completed turn is saved when the next real user message arrives) is **enabled by default**; set `=0` to disable it |
73
61
  | `MEMOIR_AGENT_MODEL` | Model for the `memoir` subagent, as `provider/model`. Falls back to `small_model` → `model` → openCode default |
74
62
  | `MEMOIR_CAPTURE_MIN_CHARS` | Local pre-filter; only transcripts at least this long are captured (default: 16, `0` = capture everything) |
75
- | `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` | Optional OpenCode feature flag. Runs capture as a native background job; explicit `false` overrides the umbrella experimental flag |
76
63
 
77
64
  ## Hooks
78
65
 
@@ -80,25 +67,31 @@ All optional:
80
67
  | --- | --- |
81
68
  | `config` | Registers the `memoir` subagent, one project-scoped `memoir-mcp` remote MCP server, and the `/memoir:onboard` slash command |
82
69
  | `shell.env` | Injects `MEMOIR_STORE` into shell environment |
83
- | `chat.message` | Captures the previous completed turn, tracks real parent messages, and auto-matches the memoir branch; waits for `promptAsync` to accept the visible capture task while ignoring synthetic and memoir-child messages |
84
- | `tool.execute.before` | Marks memoir's `task` invocation with `background: true` when OpenCode background subagents are enabled |
85
- | `tool.execute.after` + `event` | Tracks foreground completion and known background child idle/error events so checkout does not overlap an active capture |
86
- | `dispose` | Saves session markers when `MEMOIR_AUTO_SAVE=1` is explicit, closes the project MCP process, and clears instance state |
70
+ | `chat.message` | Queues capture of the previous completed turn, auto-matches the memoir branch, and returns without waiting for `promptAsync`; ignores synthetic and memoir-child messages |
71
+ | `event` | Completes and deletes hidden capture sessions when they become idle or fail |
72
+ | `dispose` | Drains queued and active captures, closes the project MCP process, and clears instance state |
87
73
 
88
74
  ## How it works
89
75
 
90
76
  Instead of wrapping the `memoir` CLI and re-implementing tools in TypeScript, this plugin registers `memoir-mcp` as a **remote** MCP server — one HTTP process per plugin/project instance. All memoir tools (`memoir_memoir_recall`, `memoir_memoir_remember`, `memoir_memoir_get`, etc.) are available natively to the main LLM.
91
77
 
92
- Capturing is done by a dedicated visible, collapsible `memoir` subagent. It can use the dynamic `memoir_*` tool namespace except for the store-global `memoir_memoir_checkout`; branch checkout remains owned by the plugin so a subagent cannot move the shared store. Every non-Memoir tool remains denied. The capture task includes the live MCP tool names and descriptions so a small local model does not have to infer the catalog. Deterministic settings keep it suitable for that model. On each real `chat.message`, the plugin captures the previous completed turn: the incoming user message has not entered the transcript yet. This one-turn delay prevents capture activity from triggering another capture. Capture is submitted as a supported `SubtaskPartInput` through `promptAsync`; the hook waits for OpenCode's immediate `204 No Content` acceptance, not for subagent execution. When native background subagents are enabled, `tool.execute.before` adds `background: true` before `TaskTool.execute` starts the child.
93
-
94
- The subagent is instructed to finish with a compact report of confirmed writes (`Captured N memories`, taxonomy paths, and brief reasons), or a single reason why nothing was captured. OpenCode returns that report to the parent agent through the normal task result. The prompt requires the report to reflect actual tool outcomes rather than intended writes.
95
-
96
- After each real parent-session message, every model call in that turn receives a compact status from `memoir_status`, such as `[memoir] fix6 · memory available (31 memories)`. Synthetic notifications and memoir child activity do not update it, and the status does not instruct the model to perform recall.
97
-
98
- - With `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, the plugin adds `background: true` in `tool.execute.before`. OpenCode runs the child through its native `BackgroundJob` path and immediately releases the parent session.
99
- - `OPENCODE_EXPERIMENTAL=true` has the same effect unless the dedicated background-subagent flag is explicitly false, matching OpenCode's native precedence.
100
- - Without either enabling flag, the plugin does not add `background`; OpenCode runs the same capture task through the original foreground subagent path. Memory capture remains available, but the parent session may wait for it to finish.
101
- - Before changing the shared memoir branch, the plugin waits for active foreground tasks (`tool.execute.after`) and native background tasks (the known child session's terminal idle/error event). A timeout defers checkout instead of moving the store underneath a running capture.
78
+ Capturing is done by the dedicated hidden `memoir` subagent. It can use the
79
+ dynamic `memoir_*` tool namespace except for the store-global
80
+ `memoir_memoir_checkout`; branch checkout remains owned by the plugin so a
81
+ subagent cannot move the shared store. Every non-Memoir tool remains denied.
82
+ The capture task includes the live MCP tool names and descriptions so a small
83
+ local model does not have to infer the catalog.
84
+
85
+ On each real `chat.message`, the plugin immediately snapshots the previous
86
+ completed turn: the incoming user message has not entered the transcript yet.
87
+ Only dispatch is serialized per parent session, so a delayed earlier submission
88
+ cannot make a later turn disappear from the queue. Branch matching remains
89
+ serialized for the shared store. A hidden throwaway session is then created
90
+ without a `parentID` and submitted through `promptAsync`. The hook itself does
91
+ not wait for that submission, and the subagent is instructed to store memories
92
+ without emitting a response. Terminal session events remove completed
93
+ throwaway sessions. During shutdown, `dispose` waits for queued submissions and
94
+ active capture sessions before closing the owned MCP process.
102
95
 
103
96
  ## Development
104
97
 
@@ -119,14 +112,13 @@ npm test # run the test suite
119
112
 
120
113
  | File | Responsibility |
121
114
  | --- | --- |
122
- | `src/index.ts` | Plugin entry: MCP registration + all hooks + dispose |
115
+ | `src/index.ts` | Plugin entry: async branch matching, MCP registration, capture queues, hooks, and dispose |
123
116
  | `src/mcp-client.ts` | Project-scoped MCP process/client lifecycle and tool calls |
124
117
  | `src/capture.ts` | Transcript extraction, filtering, task construction, and capture dispatch |
125
- | `src/capture-lifecycle.ts` | Foreground/background capture tracking and checkout drain |
126
118
  | `src/subagent.ts` | Subagent permissions, model selection, and OpenCode task dispatch |
127
- | `src/store.ts` | Store path derivation and serialized store-branch matching |
128
119
  | `src/path.ts` | Symlink-safe project and store path helpers |
129
120
  | `src/prompts.ts` | Cached prompt-template loader |
121
+ | `src/status.ts` | Decoder for `memoir_status` responses used by branch matching |
130
122
  | `src/debug.ts` | File/stderr lifecycle and debug logger |
131
123
 
132
124
  ## Publishing
package/dist/capture.d.ts CHANGED
@@ -10,6 +10,10 @@ export interface ChatMessage {
10
10
  };
11
11
  parts?: ChatPart[];
12
12
  }
13
+ export type CaptureSnapshot = {
14
+ turnId: string;
15
+ transcript: string | null;
16
+ };
13
17
  /**
14
18
  * Render a list of messages into a plain-text transcript.
15
19
  * Skips empty (no text) messages so tool-only turns stay quiet.
@@ -27,18 +31,11 @@ export declare function lastTurnTranscript(messages: ChatMessage[]): string | nu
27
31
  * A value of 0 disables the filter (capture everything).
28
32
  */
29
33
  export declare function shouldCaptureTurn(transcript: string | null, minChars?: number): transcript is string;
30
- /**
31
- * Capture the latest completed turn of a session into memoir via the subagent.
32
- *
33
- * Fire-and-forget: the caller should NOT await the subagent's completion.
34
- * Deduped per session by the last assistant message id, so a turn is written
35
- * at most once. Skips entirely when MEMOIR_AUTO_SAVE=0.
36
- *
37
- * @param client SDK client (transcript source + subagent spawner)
38
- * @param sessionID parent session id
39
- * @param lastCaptured per-session map of already-captured assistant ids
40
- * @param model optional model override for the memoir subagent
41
- */
42
- export declare function captureTurn(client: unknown, sessionID: string, lastCaptured: Map<string, string>, model?: string): Promise<void>;
34
+ /** Read and freeze the latest completed turn before dispatch serialization. */
35
+ export declare function prepareCaptureTurn(client: unknown, sessionID: string): Promise<CaptureSnapshot | null>;
36
+ /** Dispatch one immutable turn snapshot, deduped by assistant message id. */
37
+ export declare function dispatchCaptureSnapshot(client: unknown, sessionID: string, snapshot: CaptureSnapshot, lastCaptured: Map<string, string>, model?: string, onSessionCreated?: (sessionID: string) => (() => void) | undefined): Promise<void>;
38
+ /** Prepare and dispatch one completed turn; retained as the public convenience API. */
39
+ export declare function captureTurn(client: unknown, sessionID: string, lastCaptured: Map<string, string>, model?: string, onSessionCreated?: (sessionID: string) => (() => void) | undefined): Promise<void>;
43
40
  export {};
44
41
  //# sourceMappingURL=capture.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAOA,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;AAeD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAOA,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;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAyDF;;;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;AAeD,+EAA+E;AAC/E,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAgCjC;AAED,6EAA6E;AAC7E,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,eAAe,EACzB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,KAAK,CAAC,EAAE,MAAM,EACd,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GACjE,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,uFAAuF;AACvF,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,KAAK,CAAC,EAAE,MAAM,EACd,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GACjE,OAAO,CAAC,IAAI,CAAC,CAef"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import type { PluginModule } from "@opencode-ai/plugin";
3
- /** Get current git branch (empty string if not in a git repo). */
4
- export declare function currentGitBranch(cwd?: string): string;
3
+ /** Get current git branch without blocking the OpenCode event loop. */
4
+ export declare function currentGitBranch(cwd?: string): Promise<string>;
5
5
  /**
6
6
  * Track the actual branch of one memoir store. The checkout is store-global,
7
7
  * so a per-session cache would become stale as soon as another session changed
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAUrF,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;AAuJD,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAYrF,uEAAuE;AACvE,wBAAsB,gBAAgB,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAW3E;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;AA6OD,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;eAEa,MAAM"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/index.ts
2
- import { execFileSync as execFileSync2 } from "node:child_process";
2
+ import { execFile } from "node:child_process";
3
3
  import { basename } from "node:path";
4
+ import { promisify } from "node:util";
4
5
 
5
6
  // src/debug.ts
6
7
  import { appendFileSync, mkdirSync } from "node:fs";
@@ -111,7 +112,7 @@ function resolveMemoirModel(opts) {
111
112
  ];
112
113
  return candidates.find((candidate) => candidate?.includes("/"));
113
114
  }
114
- async function runMemoirSubagent(sdkClient, _parentSessionID, task, _model) {
115
+ async function runMemoirSubagent(sdkClient, _parentSessionID, task, _model, onSessionCreated) {
115
116
  const sessionApi = sdkClient?.session;
116
117
  if (!sessionApi?.create || !sessionApi?.promptAsync) {
117
118
  throw new Error("SDK client session API unavailable");
@@ -122,11 +123,18 @@ async function runMemoirSubagent(sdkClient, _parentSessionID, task, _model) {
122
123
  });
123
124
  const throwawayID = createRes?.data?.id;
124
125
  if (!throwawayID) throw new Error("Failed to create throwaway session");
125
- await sessionApi.promptAsync({
126
- path: { id: throwawayID },
127
- body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: "text", text: task }] }
128
- });
126
+ const rollback = onSessionCreated?.(throwawayID);
127
+ try {
128
+ await sessionApi.promptAsync({
129
+ path: { id: throwawayID },
130
+ body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: "text", text: task }] }
131
+ });
132
+ } catch (e) {
133
+ rollback?.();
134
+ throw e;
135
+ }
129
136
  log("memoir capture dispatched to throwaway session", throwawayID);
137
+ return throwawayID;
130
138
  } catch (e) {
131
139
  log("runMemoirSubagent failed", e);
132
140
  throw e;
@@ -199,44 +207,46 @@ function lastAssistantMessageId(messages) {
199
207
  }
200
208
  return null;
201
209
  }
202
- async function captureTurn(client, sessionID, lastCaptured, model) {
210
+ async function prepareCaptureTurn(client, sessionID) {
203
211
  if (!autoSaveEnabled()) {
204
212
  log("captureTurn: MEMOIR_AUTO_SAVE=0, skipping");
205
- return;
213
+ return null;
206
214
  }
207
215
  log("captureTurn: started for session", sessionID);
208
- try {
209
- const api = client?.session;
210
- if (!api?.messages) {
211
- log("captureTurn: client.session.messages unavailable");
212
- log("captureTurn: skipped \u2014 session messages API unavailable");
213
- return;
214
- }
215
- const res = await api.messages({
216
- path: { id: sessionID },
217
- query: { limit: 50 }
218
- });
219
- const raw = res.data;
220
- const messages = Array.isArray(raw) ? raw : [];
221
- if (messages.length === 0) return;
222
- const turnId = lastAssistantMessageId(messages);
223
- if (!turnId) return;
224
- if (lastCaptured.get(sessionID) === turnId) {
225
- log("captureTurn: skipped \u2014 already captured this turn");
226
- return;
227
- }
228
- const transcript = lastTurnTranscript(messages);
229
- if (!shouldCaptureTurn(transcript)) {
230
- log("captureTurn: skipped \u2014 transcript below min-chars");
231
- lastCaptured.set(sessionID, turnId);
232
- return;
233
- }
234
- log("captureTurn: submitting memoir subtask (transcript", transcript.length, "chars)");
235
- await runMemoirSubagent(client, sessionID, transcript, model);
236
- lastCaptured.set(sessionID, turnId);
237
- } catch (e) {
238
- log("captureTurn failed", e);
216
+ const api = client?.session;
217
+ if (!api?.messages) {
218
+ log("captureTurn: client.session.messages unavailable");
219
+ log("captureTurn: skipped \u2014 session messages API unavailable");
220
+ return null;
221
+ }
222
+ const res = await api.messages({
223
+ path: { id: sessionID },
224
+ query: { limit: 50 }
225
+ });
226
+ const raw = res.data;
227
+ const messages = Array.isArray(raw) ? raw : [];
228
+ if (messages.length === 0) return null;
229
+ const turnId = lastAssistantMessageId(messages);
230
+ if (!turnId) return null;
231
+ const transcript = lastTurnTranscript(messages);
232
+ if (!shouldCaptureTurn(transcript)) {
233
+ log("captureTurn: skipped \u2014 transcript below min-chars");
234
+ return { turnId, transcript: null };
235
+ }
236
+ return { turnId, transcript };
237
+ }
238
+ async function dispatchCaptureSnapshot(client, sessionID, snapshot, lastCaptured, model, onSessionCreated) {
239
+ if (lastCaptured.get(sessionID) === snapshot.turnId) {
240
+ log("captureTurn: skipped \u2014 already captured this turn");
241
+ return;
239
242
  }
243
+ if (snapshot.transcript === null) {
244
+ lastCaptured.set(sessionID, snapshot.turnId);
245
+ return;
246
+ }
247
+ log("captureTurn: submitting memoir subtask (transcript", snapshot.transcript.length, "chars)");
248
+ await runMemoirSubagent(client, sessionID, snapshot.transcript, model, onSessionCreated);
249
+ lastCaptured.set(sessionID, snapshot.turnId);
240
250
  }
241
251
 
242
252
  // src/mcp-client.ts
@@ -521,14 +531,15 @@ function parseMemoirStatus(raw) {
521
531
  }
522
532
 
523
533
  // src/index.ts
524
- function currentGitBranch(cwd = process.cwd()) {
534
+ var execFileAsync = promisify(execFile);
535
+ async function currentGitBranch(cwd = process.cwd()) {
525
536
  try {
526
- return execFileSync2("git", ["branch", "--show-current"], {
537
+ const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
527
538
  cwd: safeRealpath(cwd),
528
539
  encoding: "utf8",
529
- stdio: ["ignore", "pipe", "ignore"],
530
540
  timeout: 3e3
531
- }).trim();
541
+ });
542
+ return stdout.trim();
532
543
  } catch {
533
544
  return "";
534
545
  }
@@ -545,7 +556,7 @@ var MemoirBranchMatcher = class {
545
556
  return parseMemoirStatus(raw).branch ?? "";
546
557
  }
547
558
  async matchNow(client, cwd, drain) {
548
- const codeBranch = currentGitBranch(cwd);
559
+ const codeBranch = await currentGitBranch(cwd);
549
560
  if (!codeBranch) return;
550
561
  if (await this.currentBranch(client) === codeBranch) return;
551
562
  if (drain && !await drain()) return;
@@ -573,7 +584,9 @@ var MemoirOpenCode = async (input, rawOptions) => {
573
584
  const runtime = new MemoirRuntime(mcpCommand, environment, directory);
574
585
  const branchMatcher = new MemoirBranchMatcher();
575
586
  const lastCaptured = /* @__PURE__ */ new Map();
576
- const capturePending = /* @__PURE__ */ new Set();
587
+ const captureQueues = /* @__PURE__ */ new Map();
588
+ const activeCaptures = /* @__PURE__ */ new Map();
589
+ let disposing = false;
577
590
  let agentModel;
578
591
  let mcpServer;
579
592
  const connectClient = async () => {
@@ -584,17 +597,70 @@ var MemoirOpenCode = async (input, rawOptions) => {
584
597
  return null;
585
598
  }
586
599
  };
587
- const dispatchCapture = async (sid) => {
588
- if (!sdkClient || capturePending.has(sid)) return;
589
- capturePending.add(sid);
590
- try {
591
- await captureTurn(sdkClient, sid, lastCaptured, agentModel);
592
- } catch (e) {
593
- log("dispatchCapture failed", e);
594
- } finally {
595
- capturePending.delete(sid);
600
+ const trackCapture = (sessionID) => {
601
+ if (activeCaptures.has(sessionID)) return void 0;
602
+ let resolve2;
603
+ const done = new Promise((doneResolve) => {
604
+ resolve2 = doneResolve;
605
+ });
606
+ activeCaptures.set(sessionID, { done, resolve: resolve2 });
607
+ return () => finishCapture(sessionID);
608
+ };
609
+ const finishCapture = (sessionID) => {
610
+ const capture = activeCaptures.get(sessionID);
611
+ if (!capture) return;
612
+ activeCaptures.delete(sessionID);
613
+ capture.resolve();
614
+ const sessionApi = sdkClient?.session;
615
+ if (sessionApi?.delete) {
616
+ void sessionApi.delete({ path: { id: sessionID } }).catch((e) => {
617
+ log("failed to delete completed capture session", sessionID, e);
618
+ });
596
619
  }
597
620
  };
621
+ const drainCaptures = async (timeoutMs = 1e4) => {
622
+ const pending = [...activeCaptures.values()].map((capture) => capture.done);
623
+ if (pending.length === 0) return true;
624
+ let timer;
625
+ const timedOut = new Promise((resolve2) => {
626
+ timer = setTimeout(() => resolve2(false), timeoutMs);
627
+ });
628
+ const drained = Promise.all(pending).then(() => true);
629
+ const result = await Promise.race([drained, timedOut]);
630
+ if (timer) clearTimeout(timer);
631
+ if (!result) log("capture drain timed out");
632
+ return result;
633
+ };
634
+ const dispatchCapture = (sid) => {
635
+ if (!sdkClient || disposing) return;
636
+ const snapshot = prepareCaptureTurn(sdkClient, sid).catch((e) => {
637
+ log("capture snapshot failed", e);
638
+ return null;
639
+ });
640
+ const previous = captureQueues.get(sid) ?? Promise.resolve();
641
+ const current = previous.catch(() => void 0).then(async () => {
642
+ const prepared = await snapshot;
643
+ if (!prepared) return;
644
+ const client = await connectClient();
645
+ if (client) {
646
+ await branchMatcher.match(client, directory, drainCaptures);
647
+ }
648
+ await dispatchCaptureSnapshot(
649
+ sdkClient,
650
+ sid,
651
+ prepared,
652
+ lastCaptured,
653
+ agentModel,
654
+ trackCapture
655
+ );
656
+ }).catch((e) => {
657
+ log("dispatchCapture failed", e);
658
+ });
659
+ captureQueues.set(sid, current);
660
+ void current.then(() => {
661
+ if (captureQueues.get(sid) === current) captureQueues.delete(sid);
662
+ });
663
+ };
598
664
  const hooks = {
599
665
  name: "memoir",
600
666
  config: async (config) => {
@@ -647,17 +713,24 @@ var MemoirOpenCode = async (input, rawOptions) => {
647
713
  );
648
714
  const isSynthetic = parts.length > 0 && parts.every((part) => part.synthetic === true);
649
715
  if (input2.agent === MEMOIR_AGENT_NAME || isMemoirSubtask || isSynthetic) return;
650
- const client = await connectClient();
651
- if (client) {
652
- await branchMatcher.match(client, directory);
653
- }
654
716
  log("chat.message: submitting capture for previous completed turn", sid);
655
- await dispatchCapture(sid);
717
+ dispatchCapture(sid);
656
718
  } catch (e) {
657
719
  log("chat.message: failed", e);
658
720
  }
659
721
  },
722
+ event: async (input2) => {
723
+ const event = input2.event;
724
+ const sessionID = event?.properties?.sessionID;
725
+ if (sessionID && (event.type === "session.idle" || event.type === "session.error") && activeCaptures.has(sessionID)) {
726
+ finishCapture(sessionID);
727
+ }
728
+ },
660
729
  dispose: async () => {
730
+ disposing = true;
731
+ await Promise.all([...captureQueues.values()]);
732
+ await drainCaptures();
733
+ captureQueues.clear();
661
734
  branchMatcher.clear();
662
735
  lastCaptured.clear();
663
736
  await runtime.close();
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/debug.ts", "../src/prompts.ts", "../src/subagent.ts", "../src/capture.ts", "../src/mcp-client.ts", "../src/path.ts", "../src/status.ts"],
4
- "sourcesContent": ["import { execFileSync } from \"node:child_process\";\nimport { basename } from \"node:path\";\nimport type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Config, Plugin, PluginInput, PluginModule } from \"@opencode-ai/plugin\";\nimport type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { captureTurn } from \"./capture.js\";\nimport { log, setProjectContext } from \"./debug.js\";\nimport { callMemoirTool, MemoirRuntime } from \"./mcp-client.js\";\nimport { deriveStorePath, safeRealpath } from \"./path.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { parseMemoirStatus } from \"./status.js\";\nimport { buildMemoirAgent, MEMOIR_AGENT_NAME, resolveMemoirModel } from \"./subagent.js\";\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\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 lastCaptured = new Map<string, string>();\n const capturePending = new Set<string>();\n let agentModel: string | undefined;\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 || capturePending.has(sid)) return;\n capturePending.add(sid);\n try {\n await captureTurn(sdkClient, sid, lastCaptured, agentModel);\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 setProjectContext(basename(storePath));\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 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\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 const client = await connectClient();\n if (client) {\n await branchMatcher.match(client, directory);\n }\n\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 dispose: async (): Promise<void> => {\n branchMatcher.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", "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/log/memoir/YYYY-MM-DD.log\n * ($X = XDG_DATA_HOME, falls back to ~/.local/share/...)\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\nlet projectSlug: string | null = null;\n\n/** Set the project slug for log identification. Called once during config hook. */\nexport function setProjectContext(slug: string): void {\n projectSlug = slug;\n}\n\n/** Reset project context. Used in tests. */\nexport function resetProjectContext(): void {\n projectSlug = null;\n}\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 dataHome = process.env.XDG_DATA_HOME || join(homedir(), \".local\", \"share\");\n const date = new Date().toISOString().slice(0, 10);\n return join(dataHome, \"opencode\", \"log\", \"memoir\", `${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 slug = projectSlug ? `[${projectSlug}] ` : \"\";\n const line = `[memoir ${ts}] ${slug}${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 hidden: true,\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/**\n * Dispatch a capture task on a throwaway session.\n *\n * Creates a temporary session (no parentID \u2192 invisible in the UI), then fires\n * the memoir agent there via promptAsync. The LLM work runs asynchronously;\n * promptAsync returns 204 immediately after forking.\n */\nexport async function runMemoirSubagent(\n sdkClient: unknown,\n _parentSessionID: string,\n task: string,\n _model?: string,\n): Promise<void> {\n type SessionCreate = (options: {\n body?: { title?: string; agent?: string };\n }) => Promise<{ data?: { id: string } }>;\n type SessionPromptAsync = (options: {\n path: { id: string };\n body: { agent?: string; parts: Array<{ type: string; text: string }> };\n }) => Promise<unknown>;\n\n const sessionApi = (\n sdkClient as\n | { session?: { create?: SessionCreate; promptAsync?: SessionPromptAsync } }\n | null\n | undefined\n )?.session;\n if (!sessionApi?.create || !sessionApi?.promptAsync) {\n throw new Error(\"SDK client session API unavailable\");\n }\n\n try {\n const createRes = await sessionApi.create({\n body: { title: \"capture\", agent: MEMOIR_AGENT_NAME },\n });\n const throwawayID = createRes?.data?.id;\n if (!throwawayID) throw new Error(\"Failed to create throwaway session\");\n\n await sessionApi.promptAsync({\n path: { id: throwawayID },\n body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: \"text\", text: task }] },\n });\n log(\"memoir capture dispatched to throwaway session\", throwawayID);\n } catch (e) {\n log(\"runMemoirSubagent failed\", e);\n throw e;\n }\n}\n", "import { log } from \"./debug.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// 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 * @param model optional model override for the memoir subagent\n */\nexport async function captureTurn(\n client: unknown,\n sessionID: string,\n lastCaptured: Map<string, string>,\n model?: 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, transcript, model);\n lastCaptured.set(sessionID, turnId);\n } catch (e) {\n log(\"captureTurn failed\", e);\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 for (const line of chunk.toString().split(\"\\n\")) {\n const trimmed = line.trimEnd();\n if (trimmed) log(\"memoir-mcp:\", trimmed);\n }\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 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", "/**\n * Parsed shape of the `memoir_status` tool response.\n *\n * The branch matcher (store.ts) reads this payload, so the parse lives here\n * once instead of being duplicated across callers.\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"],
5
- "mappings": "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",
6
- "names": ["execFileSync", "homedir", "join", "resolve", "execFileSync", "input"]
4
+ "sourcesContent": ["import { execFile } from \"node:child_process\";\nimport { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Config, Plugin, PluginInput, PluginModule } from \"@opencode-ai/plugin\";\nimport type { AgentConfig } from \"@opencode-ai/sdk\";\nimport { dispatchCaptureSnapshot, prepareCaptureTurn } from \"./capture.js\";\nimport { log, setProjectContext } from \"./debug.js\";\nimport { callMemoirTool, MemoirRuntime } from \"./mcp-client.js\";\nimport { deriveStorePath, safeRealpath } from \"./path.js\";\nimport { loadPrompt } from \"./prompts.js\";\nimport { parseMemoirStatus } from \"./status.js\";\nimport { buildMemoirAgent, MEMOIR_AGENT_NAME, resolveMemoirModel } from \"./subagent.js\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Get current git branch without blocking the OpenCode event loop. */\nexport async function currentGitBranch(cwd = process.cwd()): Promise<string> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"branch\", \"--show-current\"], {\n cwd: safeRealpath(cwd),\n encoding: \"utf8\",\n timeout: 3_000,\n });\n return stdout.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 = await 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\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 lastCaptured = new Map<string, string>();\n const captureQueues = new Map<string, Promise<void>>();\n const activeCaptures = new Map<string, { done: Promise<void>; resolve: () => void }>();\n let disposing = false;\n let agentModel: string | undefined;\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 trackCapture = (sessionID: string): (() => void) | undefined => {\n if (activeCaptures.has(sessionID)) return undefined;\n let resolve!: () => void;\n const done = new Promise<void>((doneResolve) => {\n resolve = doneResolve;\n });\n activeCaptures.set(sessionID, { done, resolve });\n return () => finishCapture(sessionID);\n };\n\n const finishCapture = (sessionID: string): void => {\n const capture = activeCaptures.get(sessionID);\n if (!capture) return;\n activeCaptures.delete(sessionID);\n capture.resolve();\n\n const sessionApi = (\n sdkClient as\n | { session?: { delete?: (input: { path: { id: string } }) => Promise<unknown> } }\n | null\n | undefined\n )?.session;\n if (sessionApi?.delete) {\n void sessionApi.delete({ path: { id: sessionID } }).catch((e: unknown) => {\n log(\"failed to delete completed capture session\", sessionID, e);\n });\n }\n };\n\n const drainCaptures = async (timeoutMs = 10_000): Promise<boolean> => {\n const pending = [...activeCaptures.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 drain timed out\");\n return result;\n };\n\n const dispatchCapture = (sid: string): void => {\n if (!sdkClient || disposing) return;\n\n // Start transcript retrieval immediately so every chat.message snapshots\n // its own completed turn even while an earlier dispatch is still pending.\n const snapshot = prepareCaptureTurn(sdkClient, sid).catch((e: unknown) => {\n log(\"capture snapshot failed\", e);\n return null;\n });\n const previous = captureQueues.get(sid) ?? Promise.resolve();\n const current = previous\n .catch(() => undefined)\n .then(async () => {\n const prepared = await snapshot;\n if (!prepared) return;\n\n const client = await connectClient();\n if (client) {\n await branchMatcher.match(client, directory, drainCaptures);\n }\n await dispatchCaptureSnapshot(\n sdkClient,\n sid,\n prepared,\n lastCaptured,\n agentModel,\n trackCapture,\n );\n })\n .catch((e: unknown) => {\n log(\"dispatchCapture failed\", e);\n });\n\n captureQueues.set(sid, current);\n void current.then(() => {\n if (captureQueues.get(sid) === current) captureQueues.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 setProjectContext(basename(storePath));\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 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\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 log(\"chat.message: submitting capture for previous completed turn\", sid);\n dispatchCapture(sid);\n } catch (e) {\n log(\"chat.message: failed\", e);\n }\n },\n\n event: async (input: {\n event?: { type?: string; properties?: { sessionID?: string } };\n }): Promise<void> => {\n const event = input.event;\n const sessionID = event?.properties?.sessionID;\n if (\n sessionID &&\n (event.type === \"session.idle\" || event.type === \"session.error\") &&\n activeCaptures.has(sessionID)\n ) {\n finishCapture(sessionID);\n }\n },\n\n dispose: async (): Promise<void> => {\n disposing = true;\n await Promise.all([...captureQueues.values()]);\n await drainCaptures();\n captureQueues.clear();\n branchMatcher.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", "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/log/memoir/YYYY-MM-DD.log\n * ($X = XDG_DATA_HOME, falls back to ~/.local/share/...)\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\nlet projectSlug: string | null = null;\n\n/** Set the project slug for log identification. Called once during config hook. */\nexport function setProjectContext(slug: string): void {\n projectSlug = slug;\n}\n\n/** Reset project context. Used in tests. */\nexport function resetProjectContext(): void {\n projectSlug = null;\n}\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 dataHome = process.env.XDG_DATA_HOME || join(homedir(), \".local\", \"share\");\n const date = new Date().toISOString().slice(0, 10);\n return join(dataHome, \"opencode\", \"log\", \"memoir\", `${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 slug = projectSlug ? `[${projectSlug}] ` : \"\";\n const line = `[memoir ${ts}] ${slug}${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\" gives the throwaway session the restricted agent configuration;\n * `hidden: true` and the absence of a parentID keep capture out of the active\n * conversation.\n */\nexport function buildMemoirAgent(model?: string): MemoirAgentSpec {\n const agent: MemoirAgentSpec = {\n mode: \"subagent\",\n hidden: true,\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/**\n * Dispatch a capture task on a throwaway session.\n *\n * Creates a temporary session (no parentID \u2192 invisible in the UI), then fires\n * the memoir agent there via promptAsync. The LLM work runs asynchronously;\n * promptAsync returns 204 immediately after forking.\n */\nexport async function runMemoirSubagent(\n sdkClient: unknown,\n _parentSessionID: string,\n task: string,\n _model?: string,\n onSessionCreated?: (sessionID: string) => (() => void) | undefined,\n): Promise<string> {\n type SessionCreate = (options: {\n body?: { title?: string; agent?: string };\n }) => Promise<{ data?: { id: string } }>;\n type SessionPromptAsync = (options: {\n path: { id: string };\n body: { agent?: string; parts: Array<{ type: string; text: string }> };\n }) => Promise<unknown>;\n\n const sessionApi = (\n sdkClient as\n | { session?: { create?: SessionCreate; promptAsync?: SessionPromptAsync } }\n | null\n | undefined\n )?.session;\n if (!sessionApi?.create || !sessionApi?.promptAsync) {\n throw new Error(\"SDK client session API unavailable\");\n }\n\n try {\n const createRes = await sessionApi.create({\n body: { title: \"capture\", agent: MEMOIR_AGENT_NAME },\n });\n const throwawayID = createRes?.data?.id;\n if (!throwawayID) throw new Error(\"Failed to create throwaway session\");\n const rollback = onSessionCreated?.(throwawayID);\n\n try {\n await sessionApi.promptAsync({\n path: { id: throwawayID },\n body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: \"text\", text: task }] },\n });\n } catch (e) {\n rollback?.();\n throw e;\n }\n log(\"memoir capture dispatched to throwaway session\", throwawayID);\n return throwawayID;\n } catch (e) {\n log(\"runMemoirSubagent failed\", e);\n throw e;\n }\n}\n", "import { log } from \"./debug.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\nexport type CaptureSnapshot = {\n turnId: string;\n transcript: string | null;\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// 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/** Read and freeze the latest completed turn before dispatch serialization. */\nexport async function prepareCaptureTurn(\n client: unknown,\n sessionID: string,\n): Promise<CaptureSnapshot | null> {\n if (!autoSaveEnabled()) {\n log(\"captureTurn: MEMOIR_AUTO_SAVE=0, skipping\");\n return null;\n }\n log(\"captureTurn: started for session\", sessionID);\n\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 null;\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 null;\n\n const turnId = lastAssistantMessageId(messages);\n if (!turnId) return null;\n\n const transcript = lastTurnTranscript(messages);\n if (!shouldCaptureTurn(transcript)) {\n log(\"captureTurn: skipped \u2014 transcript below min-chars\");\n return { turnId, transcript: null };\n }\n\n return { turnId, transcript };\n}\n\n/** Dispatch one immutable turn snapshot, deduped by assistant message id. */\nexport async function dispatchCaptureSnapshot(\n client: unknown,\n sessionID: string,\n snapshot: CaptureSnapshot,\n lastCaptured: Map<string, string>,\n model?: string,\n onSessionCreated?: (sessionID: string) => (() => void) | undefined,\n): Promise<void> {\n if (lastCaptured.get(sessionID) === snapshot.turnId) {\n log(\"captureTurn: skipped \u2014 already captured this turn\");\n return;\n }\n if (snapshot.transcript === null) {\n lastCaptured.set(sessionID, snapshot.turnId);\n return;\n }\n\n log(\"captureTurn: submitting memoir subtask (transcript\", snapshot.transcript.length, \"chars)\");\n await runMemoirSubagent(client, sessionID, snapshot.transcript, model, onSessionCreated);\n lastCaptured.set(sessionID, snapshot.turnId);\n}\n\n/** Prepare and dispatch one completed turn; retained as the public convenience API. */\nexport async function captureTurn(\n client: unknown,\n sessionID: string,\n lastCaptured: Map<string, string>,\n model?: string,\n onSessionCreated?: (sessionID: string) => (() => void) | undefined,\n): Promise<void> {\n try {\n const snapshot = await prepareCaptureTurn(client, sessionID);\n if (!snapshot) return;\n await dispatchCaptureSnapshot(\n client,\n sessionID,\n snapshot,\n lastCaptured,\n model,\n onSessionCreated,\n );\n } catch (e) {\n log(\"captureTurn failed\", e);\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 for (const line of chunk.toString().split(\"\\n\")) {\n const trimmed = line.trimEnd();\n if (trimmed) log(\"memoir-mcp:\", trimmed);\n }\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 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", "/**\n * Parsed shape of the `memoir_status` tool response.\n *\n * The branch matcher (store.ts) reads this payload, so the parse lives here\n * once instead of being duplicated across callers.\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"],
5
+ "mappings": "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",
6
+ "names": ["homedir", "join", "resolve", "resolve", "input"]
7
7
  }
@@ -13,10 +13,9 @@ export declare const MEMOIR_CHECKOUT_TOOL = "memoir_memoir_checkout";
13
13
  * never touch the filesystem, shell, network, or the store-global branch.
14
14
  * opencode restricts an agent's tools through its `permission` ruleset — see
15
15
  * MemoirAgentSpec for why the checkout deny must follow the broad allow. Mode
16
- * "subagent" makes
17
- * opencode spawn it as a detached child session, rendered as a collapsed task
18
- * (visible in the parent timeline) when prompted via
19
- * client.session.prompt({ body: { agent: "memoir" } }).
16
+ * "subagent" gives the throwaway session the restricted agent configuration;
17
+ * `hidden: true` and the absence of a parentID keep capture out of the active
18
+ * conversation.
20
19
  */
21
20
  export declare function buildMemoirAgent(model?: string): MemoirAgentSpec;
22
21
  interface ModelResolution {
@@ -44,6 +43,6 @@ export declare function resolveMemoirModel(opts: ModelResolution): string | unde
44
43
  * the memoir agent there via promptAsync. The LLM work runs asynchronously;
45
44
  * promptAsync returns 204 immediately after forking.
46
45
  */
47
- export declare function runMemoirSubagent(sdkClient: unknown, _parentSessionID: string, task: string, _model?: string): Promise<void>;
46
+ export declare function runMemoirSubagent(sdkClient: unknown, _parentSessionID: string, task: string, _model?: string, onSessionCreated?: (sessionID: string) => (() => void) | undefined): Promise<string>;
48
47
  export {};
49
48
  //# sourceMappingURL=subagent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subagent.d.ts","sourceRoot":"","sources":["../src/subagent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAepD,KAAK,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAU7D;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,eAAe,CAkBhE;AAED,UAAU,eAAe;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQ5E;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,OAAO,EAClB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAmCf"}
1
+ {"version":3,"file":"subagent.d.ts","sourceRoot":"","sources":["../src/subagent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAepD,KAAK,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAU7D;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,eAAe,CAkBhE;AAED,UAAU,eAAe;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAQ5E;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,OAAO,EAClB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GACjE,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-memoir",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "description": "Memoir memory plugin for OpenCode — Git-versioned, taxonomy-structured AI memory.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",