opencode-memoir 3.3.4 → 3.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,7 +38,9 @@ does not block the `chat.message` hook.
38
38
 
39
39
  ## Store configuration
40
40
 
41
- The plugin delegates store path resolution to `memoir-mcp`. Override via `MEMOIR_STORE` env var or `store` plugin option:
41
+ The plugin derives a store path under `~/.memoir/` from the project repository.
42
+ Linked Git worktrees of the same repository share that store by default.
43
+ Override via `MEMOIR_STORE` env var or `store` plugin option:
42
44
 
43
45
  ```jsonc
44
46
  {
@@ -48,6 +50,14 @@ The plugin delegates store path resolution to `memoir-mcp`. Override via `MEMOIR
48
50
  }
49
51
  ```
50
52
 
53
+ Run only one active OpenCode instance per Memoir store. Concurrent instances
54
+ sharing a store are unsupported, including instances in different Git worktrees:
55
+ one instance can switch the store's branch while another is still capturing,
56
+ causing memories to be saved on the wrong branch. Finish one instance before
57
+ starting another, or configure a separate store for each concurrent instance.
58
+ Separate stores have independent memory and history. This restriction also
59
+ applies to multiple project instances hosted by a single OpenCode server.
60
+
51
61
  ## Environment variables
52
62
 
53
63
  All optional:
@@ -84,14 +94,21 @@ local model does not have to infer the catalog.
84
94
 
85
95
  On each real `chat.message`, the plugin immediately snapshots the previous
86
96
  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
97
+ `CaptureCoordinator` serializes branch matching and capture submission together
98
+ across parent sessions within each plugin instance. Snapshots are read immediately,
99
+ so a delayed earlier submission cannot make a later turn disappear from the queue.
100
+ An accepted capture is tracked before the next submission may switch branches;
101
+ switching waits for active captures to finish. Within that instance, captures on the same branch can
102
+ continue running in parallel after submission. A hidden throwaway session is created
90
103
  without a `parentID` and submitted through `promptAsync`. The hook itself does
91
104
  not wait for that submission, and the subagent is instructed to store memories
92
105
  without emitting a response. Terminal session events remove completed
93
106
  throwaway sessions. During shutdown, `dispose` waits for queued submissions and
94
- active capture sessions before closing the owned MCP process.
107
+ active capture sessions before closing the owned MCP process. Capture state distinguishes
108
+ prompt acceptance from terminal completion. A background `session.error` triggers
109
+ one retry of the original snapshot on the same project branch. Further errors,
110
+ a branch change, or shutdown stop retries. Completion means the subagent finished;
111
+ it can legitimately decide that there is nothing durable to store.
95
112
 
96
113
  ## Development
97
114
 
@@ -106,13 +123,24 @@ active capture sessions before closing the owned MCP process.
106
123
  make install # install dependencies and Git hooks
107
124
  npm run build # typecheck + emit dist
108
125
  npm test # run the test suite
126
+ make integration # optional live OpenCode + memoir-mcp protocol check
109
127
  ```
110
128
 
129
+ The integration check requires `opencode` and `memoir-mcp` on PATH. It uses a
130
+ temporary Git project, isolated OpenCode configuration/data, and a temporary
131
+ Memoir store. A deterministic local OpenAI-compatible endpoint triggers a
132
+ background error and verifies one retry, memory persistence, branch selection,
133
+ subagent tool permissions, and temporary-session cleanup. OpenCode may download
134
+ the configured `@ai-sdk/openai-compatible` provider package; no external model
135
+ inference or provider credentials are required. Temporary services and files are
136
+ removed after the check.
137
+
111
138
  ### Source layout
112
139
 
113
140
  | File | Responsibility |
114
141
  | --- | --- |
115
- | `src/index.ts` | Plugin entry: async branch matching, MCP registration, capture queues, hooks, and dispose |
142
+ | `src/index.ts` | Plugin entry: async branch matching, MCP registration, hooks, and dispose |
143
+ | `src/capture-coordinator.ts` | Capture queues, deduplication state, hidden-session tracking, and shutdown |
116
144
  | `src/mcp-client.ts` | Project-scoped MCP process/client lifecycle and tool calls |
117
145
  | `src/capture.ts` | Transcript extraction, filtering, task construction, and capture dispatch |
118
146
  | `src/subagent.ts` | Subagent permissions, model selection, and OpenCode task dispatch |
@@ -0,0 +1,30 @@
1
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ type CaptureCoordinatorOptions = {
3
+ connectClient: () => Promise<Client | null>;
4
+ matchBranch: (client: Client, drain: () => Promise<boolean>) => Promise<boolean>;
5
+ currentBranch: () => Promise<string>;
6
+ model: () => string | undefined;
7
+ };
8
+ type TerminalState = "completed" | "failed";
9
+ /** Owns capture submission, completion, retry, and shutdown for one instance. */
10
+ export declare class CaptureCoordinator {
11
+ private readonly sdkClient;
12
+ private readonly options;
13
+ private readonly tasks;
14
+ private readonly recent;
15
+ private submissions;
16
+ private readonly activeCaptures;
17
+ private disposing;
18
+ constructor(sdkClient: unknown, options: CaptureCoordinatorOptions);
19
+ private deleteSession;
20
+ private release;
21
+ private remember;
22
+ finish: (sessionID: string, state?: TerminalState) => void;
23
+ drain: (timeoutMs?: number) => Promise<boolean>;
24
+ private schedule;
25
+ private submit;
26
+ enqueue: (parent: string) => void;
27
+ close(): Promise<void>;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=capture-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-coordinator.d.ts","sourceRoot":"","sources":["../src/capture-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKxE,KAAK,yBAAyB,GAAG;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjF,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CACjC,CAAC;AAGF,KAAK,aAAa,GAAG,WAAW,GAAG,QAAQ,CAAC;AAmB5C,iFAAiF;AACjF,qBAAa,kBAAkB;IAS3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAT1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IAExD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoC;IACnE,OAAO,CAAC,SAAS,CAAS;IAE1B,YACmB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,yBAAyB,EACjD;IAEJ,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,QAAQ;IAUhB,MAAM,cAAe,MAAM,UAAS,aAAa,KAAiB,IAAI,CAoBpE;IAEF,KAAK,0BAA+B,OAAO,CAAC,OAAO,CAAC,CAWlD;IAEF,OAAO,CAAC,QAAQ;YAMF,MAAM;IA0DpB,OAAO,WAAY,MAAM,KAAG,IAAI,CAoB9B;IAEI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAM3B;CACF"}
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare function currentGitBranch(cwd?: string): Promise<string>;
9
9
  */
10
10
  export declare class MemoirBranchMatcher {
11
11
  private matching;
12
- match(client: Client, cwd: string, drain?: () => Promise<boolean>): Promise<void>;
12
+ match(client: Client, cwd: string, drain?: () => Promise<boolean>): Promise<boolean>;
13
13
  private currentBranch;
14
14
  private matchNow;
15
15
  clear(): void;
@@ -1 +1 @@
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"}
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,OAAO,CAAC,CAOnF;YAEa,aAAa;YAQb,QAAQ;IAyBtB,KAAK,IAAI,IAAI,CAEZ;CACF;AAqJD,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;eAEa,MAAM"}
package/dist/index.js CHANGED
@@ -125,10 +125,14 @@ async function runMemoirSubagent(sdkClient, _parentSessionID, task, _model, onSe
125
125
  if (!throwawayID) throw new Error("Failed to create throwaway session");
126
126
  const rollback = onSessionCreated?.(throwawayID);
127
127
  try {
128
- await sessionApi.promptAsync({
128
+ const result = await sessionApi.promptAsync({
129
129
  path: { id: throwawayID },
130
130
  body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: "text", text: task }] }
131
131
  });
132
+ const error = result?.error;
133
+ if (error !== void 0) {
134
+ throw new Error(`Capture submission failed: ${JSON.stringify(error)}`);
135
+ }
132
136
  } catch (e) {
133
137
  rollback?.();
134
138
  throw e;
@@ -235,19 +239,165 @@ async function prepareCaptureTurn(client, sessionID) {
235
239
  }
236
240
  return { turnId, transcript };
237
241
  }
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;
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);
250
- }
242
+
243
+ // src/capture-coordinator.ts
244
+ var MAX_ATTEMPTS = 2;
245
+ var RECENT_TURNS = 256;
246
+ var CaptureCoordinator = class {
247
+ constructor(sdkClient, options) {
248
+ this.sdkClient = sdkClient;
249
+ this.options = options;
250
+ }
251
+ sdkClient;
252
+ options;
253
+ tasks = /* @__PURE__ */ new Map();
254
+ // Terminal history contains no transcripts and is bounded for long sessions.
255
+ recent = /* @__PURE__ */ new Map();
256
+ submissions = Promise.resolve();
257
+ activeCaptures = /* @__PURE__ */ new Map();
258
+ disposing = false;
259
+ deleteSession(sessionID) {
260
+ const api = this.sdkClient?.session;
261
+ if (api?.delete) {
262
+ void api.delete({ path: { id: sessionID } }).catch((e) => {
263
+ log("failed to delete completed capture session", sessionID, e);
264
+ });
265
+ }
266
+ }
267
+ release(sessionID) {
268
+ const capture = this.activeCaptures.get(sessionID);
269
+ if (!capture) return void 0;
270
+ this.activeCaptures.delete(sessionID);
271
+ capture.resolve();
272
+ this.deleteSession(sessionID);
273
+ return capture;
274
+ }
275
+ remember(task, state) {
276
+ task.state = state;
277
+ this.tasks.delete(task.key);
278
+ this.recent.set(task.key, state);
279
+ if (this.recent.size > RECENT_TURNS) {
280
+ const oldest = this.recent.keys().next().value;
281
+ if (oldest !== void 0) this.recent.delete(oldest);
282
+ }
283
+ }
284
+ finish = (sessionID, state = "completed") => {
285
+ const capture = this.activeCaptures.get(sessionID);
286
+ if (!capture) return;
287
+ if (capture.task.state === "submitting") {
288
+ if (capture.terminal !== "failed") capture.terminal = state;
289
+ return;
290
+ }
291
+ this.release(sessionID);
292
+ const task = capture.task;
293
+ if (state === "failed" && !this.disposing && task.attempts < MAX_ATTEMPTS) {
294
+ task.state = "queued";
295
+ log("retrying failed capture", task.parent, task.snapshot.turnId);
296
+ this.schedule(() => this.submit(task));
297
+ } else {
298
+ this.remember(task, state);
299
+ if (state === "failed")
300
+ log("capture failed; no further attempts", task.parent, task.snapshot.turnId);
301
+ }
302
+ };
303
+ drain = async (timeoutMs = 1e4) => {
304
+ const pending = [...this.activeCaptures.values()].map((capture) => capture.done);
305
+ if (pending.length === 0) return true;
306
+ let timer;
307
+ const timedOut = new Promise((resolve2) => {
308
+ timer = setTimeout(() => resolve2(false), timeoutMs);
309
+ });
310
+ const result = await Promise.race([Promise.all(pending).then(() => true), timedOut]);
311
+ if (timer) clearTimeout(timer);
312
+ if (!result) log("capture drain timed out");
313
+ return result;
314
+ };
315
+ schedule(work) {
316
+ this.submissions = this.submissions.then(work).catch((e) => {
317
+ log("dispatchCapture failed", e);
318
+ });
319
+ }
320
+ async submit(task) {
321
+ try {
322
+ if (task.attempts > 0 && await this.options.currentBranch() !== task.branch) {
323
+ log("capture retry cancelled: project branch changed", task.parent);
324
+ this.remember(task, "failed");
325
+ return;
326
+ }
327
+ const client = await this.options.connectClient();
328
+ if (client) {
329
+ if (!await this.options.matchBranch(client, this.drain)) {
330
+ this.tasks.delete(task.key);
331
+ log("capture deferred: memoir branch could not be confirmed", task.parent);
332
+ return;
333
+ }
334
+ } else if (await this.options.currentBranch()) {
335
+ this.tasks.delete(task.key);
336
+ log("capture deferred: memoir client unavailable", task.parent);
337
+ return;
338
+ }
339
+ const branch = await this.options.currentBranch();
340
+ if (task.attempts > 0 && branch !== task.branch) {
341
+ this.remember(task, "failed");
342
+ log("capture retry cancelled: project branch changed during matching", task.parent);
343
+ return;
344
+ }
345
+ task.branch ??= branch;
346
+ task.state = "submitting";
347
+ task.attempts++;
348
+ const id = await runMemoirSubagent(
349
+ this.sdkClient,
350
+ task.parent,
351
+ task.snapshot.transcript ?? "",
352
+ this.options.model(),
353
+ (sessionID) => {
354
+ let resolve2;
355
+ const done = new Promise((complete) => {
356
+ resolve2 = complete;
357
+ });
358
+ this.activeCaptures.set(sessionID, { task, done, resolve: resolve2 });
359
+ return () => {
360
+ this.release(sessionID);
361
+ };
362
+ }
363
+ );
364
+ task.state = "accepted";
365
+ const terminal = this.activeCaptures.get(id)?.terminal;
366
+ if (terminal) this.finish(id, terminal);
367
+ } catch (e) {
368
+ task.state = "failed";
369
+ this.tasks.delete(task.key);
370
+ throw e;
371
+ }
372
+ }
373
+ enqueue = (parent) => {
374
+ if (!this.sdkClient || this.disposing) return;
375
+ const snapshot = prepareCaptureTurn(this.sdkClient, parent).catch((e) => {
376
+ log("capture snapshot failed", e);
377
+ return null;
378
+ });
379
+ this.schedule(async () => {
380
+ const prepared = await snapshot;
381
+ if (!prepared) return;
382
+ const key = JSON.stringify([parent, prepared.turnId]);
383
+ if (this.tasks.has(key) || this.recent.has(key)) return;
384
+ const task = { key, parent, snapshot: prepared, state: "queued", attempts: 0 };
385
+ if (prepared.transcript === null) {
386
+ this.remember(task, "completed");
387
+ return;
388
+ }
389
+ this.tasks.set(key, task);
390
+ await this.submit(task);
391
+ });
392
+ };
393
+ async close() {
394
+ this.disposing = true;
395
+ await this.submissions;
396
+ await this.drain();
397
+ this.tasks.clear();
398
+ this.recent.clear();
399
+ }
400
+ };
251
401
 
252
402
  // src/mcp-client.ts
253
403
  import { spawn } from "node:child_process";
@@ -259,7 +409,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
259
409
  import { execFileSync } from "node:child_process";
260
410
  import { realpathSync } from "node:fs";
261
411
  import { homedir as homedir2 } from "node:os";
262
- import { join as join2, resolve } from "node:path";
412
+ import { dirname as dirname2, join as join2, resolve } from "node:path";
263
413
  function safeRealpath(p) {
264
414
  try {
265
415
  return realpathSync(p);
@@ -284,6 +434,14 @@ function deriveStorePath(cwd = process.cwd(), override) {
284
434
  timeout: 3e3
285
435
  }).trim();
286
436
  projectDir = safeRealpath(gitRoot);
437
+ const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
438
+ cwd: realCwd,
439
+ encoding: "utf8",
440
+ stdio: ["ignore", "pipe", "ignore"],
441
+ timeout: 3e3
442
+ }).trim();
443
+ const sharedGitDir = safeRealpath(resolve(realCwd, commonDir));
444
+ projectDir = sharedGitDir.endsWith("/.git") ? dirname2(sharedGitDir) : sharedGitDir;
287
445
  } catch {
288
446
  projectDir = resolve(realCwd);
289
447
  }
@@ -314,10 +472,14 @@ async function pickFreeHighPort(tries = 20) {
314
472
  }
315
473
  throw new Error("memoir: could not find a free high port for the HTTP server");
316
474
  }
317
- function waitForPort(port, timeoutMs = 1e4) {
475
+ function waitForPort(port, proc, timeoutMs = 1e4) {
318
476
  const deadline = Date.now() + timeoutMs;
319
477
  return new Promise((resolve2, reject) => {
320
478
  const attempt = () => {
479
+ if (proc.exitCode !== null || proc.signalCode !== null) {
480
+ reject(new Error("memoir: HTTP server exited before becoming ready"));
481
+ return;
482
+ }
321
483
  const sock = createConnection({ port, host: "127.0.0.1" });
322
484
  sock.once("connect", () => {
323
485
  sock.destroy();
@@ -351,6 +513,7 @@ var MemoirRuntime = class {
351
513
  serverProc = null;
352
514
  serverUrl = null;
353
515
  serverPort = null;
516
+ publishedPort = null;
354
517
  startingServer = null;
355
518
  cleanupClient() {
356
519
  this.state = null;
@@ -396,18 +559,28 @@ var MemoirRuntime = class {
396
559
  throw new Error(`memoir: failed to spawn ${this.command[0]}: ${errorMessage(e)}`);
397
560
  });
398
561
  this.serverProc = proc;
562
+ proc.once("exit", () => {
563
+ if (this.serverProc !== proc) return;
564
+ log("memoir-mcp exited, resetting runtime state");
565
+ this.serverProc = null;
566
+ this.serverUrl = null;
567
+ const client = this.state?.client;
568
+ this.cleanupClient();
569
+ if (client) void client.close().catch((e) => log("exited MCP client close failed", e));
570
+ });
399
571
  try {
400
- await waitForPort(port);
572
+ await waitForPort(port, proc);
401
573
  } catch {
402
574
  proc.kill();
403
575
  await exited;
404
576
  this.serverProc = null;
405
577
  this.serverUrl = null;
406
- this.serverPort = null;
578
+ this.serverPort = this.publishedPort;
407
579
  this.cleanupClient();
408
580
  continue;
409
581
  }
410
582
  this.serverUrl = url;
583
+ this.publishedPort = port;
411
584
  log("memoir-mcp HTTP server up at", url.toString());
412
585
  return url;
413
586
  }
@@ -417,12 +590,13 @@ var MemoirRuntime = class {
417
590
  try {
418
591
  return await start;
419
592
  } catch (e) {
420
- this.startingServer = null;
421
593
  this.serverUrl = null;
422
594
  if (this.serverProc) this.serverProc.kill();
423
595
  this.serverProc = null;
424
- this.serverPort = null;
596
+ this.serverPort = this.publishedPort;
425
597
  throw e;
598
+ } finally {
599
+ if (this.startingServer === start) this.startingServer = null;
426
600
  }
427
601
  }
428
602
  /** Get the lazily-connected internal client for this plugin instance. */
@@ -436,6 +610,7 @@ var MemoirRuntime = class {
436
610
  transport: new StreamableHTTPClientTransport(url)
437
611
  };
438
612
  transport.onclose = () => {
613
+ if (this.state?.client !== client) return;
439
614
  log("mcp-client: transport closed, resetting state");
440
615
  this.cleanupClient();
441
616
  };
@@ -466,7 +641,7 @@ var MemoirRuntime = class {
466
641
  this.serverProc = null;
467
642
  try {
468
643
  await new Promise((resolve2) => {
469
- if (proc.exitCode !== null) {
644
+ if (proc.exitCode !== null || proc.signalCode !== null) {
470
645
  resolve2();
471
646
  return;
472
647
  }
@@ -522,7 +697,7 @@ function parseMemoirStatus(raw) {
522
697
  try {
523
698
  const status = JSON.parse(raw);
524
699
  return {
525
- branch: typeof status.branch === "string" ? status.branch : void 0,
700
+ branch: typeof status.branch === "string" || status.branch === null ? status.branch : void 0,
526
701
  memory_count: typeof status.memory_count === "number" ? status.memory_count : void 0
527
702
  };
528
703
  } catch {
@@ -548,23 +723,30 @@ var MemoirBranchMatcher = class {
548
723
  matching = Promise.resolve();
549
724
  match(client, cwd, drain) {
550
725
  const next = this.matching.then(() => this.matchNow(client, cwd, drain));
551
- this.matching = next.catch(() => void 0);
726
+ this.matching = next.then(
727
+ () => void 0,
728
+ () => void 0
729
+ );
552
730
  return next;
553
731
  }
554
732
  async currentBranch(client) {
555
733
  const raw = await callMemoirTool(client, "memoir_status");
556
- return parseMemoirStatus(raw).branch ?? "";
734
+ const branch = parseMemoirStatus(raw).branch;
735
+ return branch === null ? "" : branch ?? null;
557
736
  }
558
737
  async matchNow(client, cwd, drain) {
559
738
  const codeBranch = await currentGitBranch(cwd);
560
- if (!codeBranch) return;
561
- if (await this.currentBranch(client) === codeBranch) return;
562
- if (drain && !await drain()) return;
563
- if (await this.currentBranch(client) === codeBranch) return;
739
+ if (!codeBranch) return true;
740
+ const storeBranch = await this.currentBranch(client);
741
+ if (storeBranch === null) return false;
742
+ if (storeBranch === codeBranch) return true;
743
+ if (drain && !await drain()) return false;
744
+ if (await this.currentBranch(client) === codeBranch) return true;
564
745
  await callMemoirTool(client, "memoir_checkout", {
565
746
  target: codeBranch,
566
747
  create: true
567
748
  });
749
+ return await this.currentBranch(client) === codeBranch;
568
750
  }
569
751
  clear() {
570
752
  this.matching = Promise.resolve();
@@ -583,10 +765,6 @@ var MemoirOpenCode = async (input, rawOptions) => {
583
765
  const environment = storePath ? { MEMOIR_STORE: storePath } : void 0;
584
766
  const runtime = new MemoirRuntime(mcpCommand, environment, directory);
585
767
  const branchMatcher = new MemoirBranchMatcher();
586
- const lastCaptured = /* @__PURE__ */ new Map();
587
- const captureQueues = /* @__PURE__ */ new Map();
588
- const activeCaptures = /* @__PURE__ */ new Map();
589
- let disposing = false;
590
768
  let agentModel;
591
769
  let mcpServer;
592
770
  const connectClient = async () => {
@@ -597,70 +775,12 @@ var MemoirOpenCode = async (input, rawOptions) => {
597
775
  return null;
598
776
  }
599
777
  };
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
- });
619
- }
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
- };
778
+ const captures = new CaptureCoordinator(sdkClient, {
779
+ connectClient,
780
+ matchBranch: (client, drain) => branchMatcher.match(client, directory, drain),
781
+ currentBranch: () => currentGitBranch(directory),
782
+ model: () => agentModel
783
+ });
664
784
  const hooks = {
665
785
  name: "memoir",
666
786
  config: async (config) => {
@@ -714,7 +834,7 @@ var MemoirOpenCode = async (input, rawOptions) => {
714
834
  const isSynthetic = parts.length > 0 && parts.every((part) => part.synthetic === true);
715
835
  if (input2.agent === MEMOIR_AGENT_NAME || isMemoirSubtask || isSynthetic) return;
716
836
  log("chat.message: submitting capture for previous completed turn", sid);
717
- dispatchCapture(sid);
837
+ captures.enqueue(sid);
718
838
  } catch (e) {
719
839
  log("chat.message: failed", e);
720
840
  }
@@ -722,17 +842,13 @@ var MemoirOpenCode = async (input, rawOptions) => {
722
842
  event: async (input2) => {
723
843
  const event = input2.event;
724
844
  const sessionID = event?.properties?.sessionID;
725
- if (sessionID && (event.type === "session.idle" || event.type === "session.error") && activeCaptures.has(sessionID)) {
726
- finishCapture(sessionID);
845
+ if (sessionID && (event.type === "session.idle" || event.type === "session.error")) {
846
+ captures.finish(sessionID, event.type === "session.error" ? "failed" : "completed");
727
847
  }
728
848
  },
729
849
  dispose: async () => {
730
- disposing = true;
731
- await Promise.all([...captureQueues.values()]);
732
- await drainCaptures();
733
- captureQueues.clear();
850
+ await captures.close();
734
851
  branchMatcher.clear();
735
- lastCaptured.clear();
736
852
  await runtime.close();
737
853
  }
738
854
  };
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 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 { 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"]
3
+ "sources": ["../src/index.ts", "../src/debug.ts", "../src/prompts.ts", "../src/subagent.ts", "../src/capture.ts", "../src/capture-coordinator.ts", "../src/mcp-client.ts", "../src/path.ts", "../src/status.ts"],
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 { CaptureCoordinator } from \"./capture-coordinator.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<boolean> {\n const next = this.matching.then(() => this.matchNow(client, cwd, drain));\n this.matching = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n private async currentBranch(client: Client): Promise<string | null> {\n const raw = await callMemoirTool(client, \"memoir_status\");\n const branch = parseMemoirStatus(raw).branch;\n // A new store has a valid status with no HEAD yet. Missing/malformed\n // status is different: checkout cannot be confirmed safely in that case.\n return branch === null ? \"\" : (branch ?? null);\n }\n\n private async matchNow(\n client: Client,\n cwd: string,\n drain?: () => Promise<boolean>,\n ): Promise<boolean> {\n const codeBranch = await currentGitBranch(cwd);\n if (!codeBranch) return true;\n\n const storeBranch = await this.currentBranch(client);\n if (storeBranch === null) return false;\n if (storeBranch === codeBranch) return true;\n if (drain && !(await drain())) return false;\n // A capture or external client may have changed HEAD while we waited.\n if ((await this.currentBranch(client)) === codeBranch) return true;\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 return (await this.currentBranch(client)) === codeBranch;\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 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 captures = new CaptureCoordinator(sdkClient, {\n connectClient,\n matchBranch: (client, drain) => branchMatcher.match(client, directory, drain),\n currentBranch: () => currentGitBranch(directory),\n model: () => agentModel,\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 captures.enqueue(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 (sessionID && (event.type === \"session.idle\" || event.type === \"session.error\")) {\n captures.finish(sessionID, event.type === \"session.error\" ? \"failed\" : \"completed\");\n }\n },\n\n dispose: async (): Promise<void> => {\n await captures.close();\n branchMatcher.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 const result = await sessionApi.promptAsync({\n path: { id: throwawayID },\n body: { agent: MEMOIR_AGENT_NAME, parts: [{ type: \"text\", text: task }] },\n });\n const error = (result as { error?: unknown } | null | undefined)?.error;\n if (error !== undefined) {\n throw new Error(`Capture submission failed: ${JSON.stringify(error)}`);\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 { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { type CaptureSnapshot, prepareCaptureTurn } from \"./capture.js\";\nimport { log } from \"./debug.js\";\nimport { runMemoirSubagent } from \"./subagent.js\";\n\ntype CaptureCoordinatorOptions = {\n connectClient: () => Promise<Client | null>;\n matchBranch: (client: Client, drain: () => Promise<boolean>) => Promise<boolean>;\n currentBranch: () => Promise<string>;\n model: () => string | undefined;\n};\n\ntype CaptureState = \"queued\" | \"submitting\" | \"accepted\" | \"completed\" | \"failed\";\ntype TerminalState = \"completed\" | \"failed\";\ntype CaptureTask = {\n key: string;\n parent: string;\n snapshot: CaptureSnapshot;\n state: CaptureState;\n attempts: number;\n branch?: string;\n};\ntype ActiveCapture = {\n task: CaptureTask;\n done: Promise<void>;\n resolve: () => void;\n terminal?: TerminalState;\n};\n\nconst MAX_ATTEMPTS = 2;\nconst RECENT_TURNS = 256;\n\n/** Owns capture submission, completion, retry, and shutdown for one instance. */\nexport class CaptureCoordinator {\n private readonly tasks = new Map<string, CaptureTask>();\n // Terminal history contains no transcripts and is bounded for long sessions.\n private readonly recent = new Map<string, TerminalState>();\n private submissions: Promise<void> = Promise.resolve();\n private readonly activeCaptures = new Map<string, ActiveCapture>();\n private disposing = false;\n\n constructor(\n private readonly sdkClient: unknown,\n private readonly options: CaptureCoordinatorOptions,\n ) {}\n\n private deleteSession(sessionID: string): void {\n const api = (\n this.sdkClient as\n | {\n session?: { delete?: (input: { path: { id: string } }) => Promise<unknown> };\n }\n | null\n | undefined\n )?.session;\n if (api?.delete) {\n void api.delete({ path: { id: sessionID } }).catch((e: unknown) => {\n log(\"failed to delete completed capture session\", sessionID, e);\n });\n }\n }\n\n private release(sessionID: string): ActiveCapture | undefined {\n const capture = this.activeCaptures.get(sessionID);\n if (!capture) return undefined;\n this.activeCaptures.delete(sessionID);\n capture.resolve();\n this.deleteSession(sessionID);\n return capture;\n }\n\n private remember(task: CaptureTask, state: TerminalState): void {\n task.state = state;\n this.tasks.delete(task.key);\n this.recent.set(task.key, state);\n if (this.recent.size > RECENT_TURNS) {\n const oldest = this.recent.keys().next().value;\n if (oldest !== undefined) this.recent.delete(oldest);\n }\n }\n\n finish = (sessionID: string, state: TerminalState = \"completed\"): void => {\n const capture = this.activeCaptures.get(sessionID);\n if (!capture) return;\n // Events can arrive before promptAsync returns. Keep the branch reserved\n // until acceptance and let an error take precedence over an idle event.\n if (capture.task.state === \"submitting\") {\n if (capture.terminal !== \"failed\") capture.terminal = state;\n return;\n }\n this.release(sessionID);\n const task = capture.task;\n if (state === \"failed\" && !this.disposing && task.attempts < MAX_ATTEMPTS) {\n task.state = \"queued\";\n log(\"retrying failed capture\", task.parent, task.snapshot.turnId);\n this.schedule(() => this.submit(task));\n } else {\n this.remember(task, state);\n if (state === \"failed\")\n log(\"capture failed; no further attempts\", task.parent, task.snapshot.turnId);\n }\n };\n\n drain = async (timeoutMs = 10_000): Promise<boolean> => {\n const pending = [...this.activeCaptures.values()].map((capture) => capture.done);\n if (pending.length === 0) return true;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n const result = await Promise.race([Promise.all(pending).then(() => true as const), timedOut]);\n if (timer) clearTimeout(timer);\n if (!result) log(\"capture drain timed out\");\n return result;\n };\n\n private schedule(work: () => Promise<void>): void {\n this.submissions = this.submissions.then(work).catch((e: unknown) => {\n log(\"dispatchCapture failed\", e);\n });\n }\n\n private async submit(task: CaptureTask): Promise<void> {\n try {\n // A retry must not move an old transcript into a newly selected branch.\n if (task.attempts > 0 && (await this.options.currentBranch()) !== task.branch) {\n log(\"capture retry cancelled: project branch changed\", task.parent);\n this.remember(task, \"failed\");\n return;\n }\n const client = await this.options.connectClient();\n if (client) {\n if (!(await this.options.matchBranch(client, this.drain))) {\n this.tasks.delete(task.key);\n log(\"capture deferred: memoir branch could not be confirmed\", task.parent);\n return;\n }\n } else if (await this.options.currentBranch()) {\n this.tasks.delete(task.key);\n log(\"capture deferred: memoir client unavailable\", task.parent);\n return;\n }\n const branch = await this.options.currentBranch();\n // Matching can wait for active captures; recheck the project after that\n // wait so an old retry cannot be submitted on a newly selected branch.\n if (task.attempts > 0 && branch !== task.branch) {\n this.remember(task, \"failed\");\n log(\"capture retry cancelled: project branch changed during matching\", task.parent);\n return;\n }\n task.branch ??= branch;\n task.state = \"submitting\";\n task.attempts++;\n const id = await runMemoirSubagent(\n this.sdkClient,\n task.parent,\n task.snapshot.transcript ?? \"\",\n this.options.model(),\n (sessionID) => {\n let resolve!: () => void;\n const done = new Promise<void>((complete) => {\n resolve = complete;\n });\n this.activeCaptures.set(sessionID, { task, done, resolve });\n // Submission rejection is distinct from a background session error.\n return () => {\n this.release(sessionID);\n };\n },\n );\n task.state = \"accepted\";\n const terminal = this.activeCaptures.get(id)?.terminal;\n if (terminal) this.finish(id, terminal);\n } catch (e) {\n task.state = \"failed\";\n this.tasks.delete(task.key);\n throw e;\n }\n }\n\n enqueue = (parent: string): void => {\n if (!this.sdkClient || this.disposing) return;\n // Read immediately, even when an earlier submission is still queued.\n const snapshot = prepareCaptureTurn(this.sdkClient, parent).catch((e: unknown) => {\n log(\"capture snapshot failed\", e);\n return null;\n });\n this.schedule(async () => {\n const prepared = await snapshot;\n if (!prepared) return;\n const key = JSON.stringify([parent, prepared.turnId]);\n if (this.tasks.has(key) || this.recent.has(key)) return;\n const task: CaptureTask = { key, parent, snapshot: prepared, state: \"queued\", attempts: 0 };\n if (prepared.transcript === null) {\n this.remember(task, \"completed\");\n return;\n }\n this.tasks.set(key, task);\n await this.submit(task);\n });\n };\n\n async close(): Promise<void> {\n this.disposing = true;\n await this.submissions;\n await this.drain();\n this.tasks.clear();\n this.recent.clear();\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, proc: ChildProcess, timeoutMs = 10_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n return new Promise((resolve, reject) => {\n const attempt = () => {\n if (proc.exitCode !== null || proc.signalCode !== null) {\n reject(new Error(\"memoir: HTTP server exited before becoming ready\"));\n return;\n }\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 publishedPort: 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 proc.once(\"exit\", () => {\n if (this.serverProc !== proc) return;\n log(\"memoir-mcp exited, resetting runtime state\");\n this.serverProc = null;\n this.serverUrl = null;\n const client = this.state?.client;\n this.cleanupClient();\n if (client) void client.close().catch((e) => log(\"exited MCP client close failed\", e));\n });\n\n try {\n await waitForPort(port, proc);\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 = this.publishedPort;\n this.cleanupClient();\n continue;\n }\n\n this.serverUrl = url;\n this.publishedPort = port;\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.serverUrl = null;\n if (this.serverProc) this.serverProc.kill();\n this.serverProc = null;\n this.serverPort = this.publishedPort;\n throw e;\n } finally {\n if (this.startingServer === start) this.startingServer = null;\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 if (this.state?.client !== client) return;\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 || proc.signalCode !== 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 { dirname, 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 const commonDir = execFileSync(\"git\", [\"rev-parse\", \"--git-common-dir\"], {\n cwd: realCwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 3_000,\n }).trim();\n const sharedGitDir = safeRealpath(resolve(realCwd, commonDir));\n // Preserve existing store names for ordinary repos and use the same root\n // for linked worktrees. Separate/bare Git directories identify themselves.\n projectDir = sharedGitDir.endsWith(\"/.git\") ? dirname(sharedGitDir) : sharedGitDir;\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 | null;\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:\n typeof status.branch === \"string\" || status.branch === null ? 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": ["resolve", "homedir", "dirname", "join", "resolve", "input"]
7
7
  }
@@ -22,6 +22,7 @@ export declare class MemoirRuntime {
22
22
  private serverProc;
23
23
  private serverUrl;
24
24
  private serverPort;
25
+ private publishedPort;
25
26
  private startingServer;
26
27
  constructor(command: string[], env?: Record<string, string> | undefined, cwd?: string | undefined, dependencies?: MemoirRuntimeDependencies);
27
28
  private cleanupClient;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAQnG,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,6BAA6B,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC;CACzD,CAAC;AAqDF;;;;GAIG;AACH,qBAAa,aAAa;IAStB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAX/B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,cAAc,CAA6B;IAEnD,YACmB,OAAO,EAAE,MAAM,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,YAAY,GAAE,yBAA8B,EAC3D;IAEJ,OAAO,CAAC,aAAa;IAKf,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,CAmF1B;IAED,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B/B;IAED,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAwC3B;CACF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB"}
1
+ {"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAQnG,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,6BAA6B,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC;CACzD,CAAC;AAyDF;;;;GAIG;AACH,qBAAa,aAAa;IAUtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZ/B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAA6B;IAEnD,YACmB,OAAO,EAAE,MAAM,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,YAAY,GAAE,yBAA8B,EAC3D;IAEJ,OAAO,CAAC,aAAa;IAKf,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,CA8F1B;IAED,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CA8B/B;IAED,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAwC3B;CACF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAKA,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBtF"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAKA,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA8BtF"}
package/dist/status.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * once instead of being duplicated across callers.
6
6
  */
7
7
  export type MemoirStatus = {
8
- branch?: string;
8
+ branch?: string | null;
9
9
  memory_count?: number;
10
10
  };
11
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,CAW9E"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,CAY9E"}
@@ -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;;;;;;;;;;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"}
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,CA8CjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-memoir",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
4
4
  "description": "Memoir memory plugin for OpenCode — Git-versioned, taxonomy-structured AI memory.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",