opencode-memoir 2.1.0 → 3.0.0
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 +59 -17
- package/dist/capture-lifecycle.d.ts +12 -0
- package/dist/capture-lifecycle.d.ts.map +1 -0
- package/dist/capture.d.ts +52 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/debug.d.ts +2 -5
- package/dist/debug.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +787 -136
- package/dist/index.js.map +4 -4
- package/dist/mcp-client.d.ts +47 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/path.d.ts +16 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/prompts/capture-task.tmpl +35 -0
- package/dist/prompts/onboard.tmpl +16 -0
- package/dist/prompts/startup-hint.tmpl +3 -0
- package/dist/prompts/subagent-system.tmpl +18 -0
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/store.d.ts +15 -15
- package/dist/store.d.ts.map +1 -1
- package/dist/subagent.d.ts +43 -0
- package/dist/subagent.d.ts.map +1 -0
- package/dist/turn-status.d.ts +2 -0
- package/dist/turn-status.d.ts.map +1 -0
- package/package.json +7 -4
- package/dist/memory-saver.d.ts +0 -6
- package/dist/memory-saver.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
OpenCode plugin for [Memoir](https://github.com/zhangfengcdt/memoir): git-versioned, taxonomy-structured memory for coding agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Launches the globally installed `memoir-mcp` console script and registers it as
|
|
6
|
+
a project-scoped remote MCP server.
|
|
6
7
|
|
|
7
8
|
## Install
|
|
8
9
|
|
|
@@ -29,6 +30,24 @@ OpenCode downloads and resolves the package from npm automatically. To pin a ver
|
|
|
29
30
|
1. Install the plugin (see above)
|
|
30
31
|
2. Start coding — the agent can use `memoir_memoir_recall`, `memoir_memoir_remember`, `memoir_memoir_get` MCP tools automatically
|
|
31
32
|
|
|
33
|
+
Automatic capture works without additional OpenCode flags. By default, capture
|
|
34
|
+
runs through the regular foreground subagent path for compatibility with older
|
|
35
|
+
OpenCode installations.
|
|
36
|
+
|
|
37
|
+
To run capture as a native background task, enable OpenCode's experimental
|
|
38
|
+
background-subagent support before starting OpenCode:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
With this flag, capture no longer blocks the parent session while the memoir
|
|
45
|
+
subagent extracts and stores durable facts.
|
|
46
|
+
|
|
47
|
+
OpenCode's umbrella `OPENCODE_EXPERIMENTAL=true` also enables background
|
|
48
|
+
subagents unless `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` explicitly
|
|
49
|
+
overrides it. The plugin mirrors that precedence.
|
|
50
|
+
|
|
32
51
|
## Store configuration
|
|
33
52
|
|
|
34
53
|
The plugin delegates store path resolution to `memoir-mcp`. Override via `MEMOIR_STORE` env var or `store` plugin option:
|
|
@@ -46,25 +65,43 @@ The plugin delegates store path resolution to `memoir-mcp`. Override via `MEMOIR
|
|
|
46
65
|
All optional:
|
|
47
66
|
|
|
48
67
|
| Variable | Effect |
|
|
49
|
-
|
|
68
|
+
| --- | --- |
|
|
50
69
|
| `MEMOIR_STORE` | Override store path (passed to memoir-mcp as `--store`) |
|
|
51
|
-
| `MEMOIR_DEBUG=1` |
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
70
|
+
| `MEMOIR_DEBUG=1` | Add verbose diagnostics and error stacks to the configured Memoir log; without it, normal lifecycle and concise error messages are still logged |
|
|
71
|
+
| `MEMOIR_LOG` | Log destination: unset uses `$XDG_STATE_HOME/opencode/memoir-plugin-YYYY-MM-DD.log`; `stderr` enables live stderr; any other value is an explicit file path |
|
|
72
|
+
| `MEMOIR_AUTO_SAVE` | Captures the previous completed turn when the next real user message arrives. **Enabled by default**; set `=0` to disable |
|
|
73
|
+
| `MEMOIR_AGENT_MODEL` | Model for the `memoir` subagent, as `provider/model`. Falls back to `small_model` → `model` → openCode default |
|
|
74
|
+
| `MEMOIR_CAPTURE_MIN_CHARS` | Local pre-filter; only transcripts at least this long are captured (default: 16, `0` = capture everything) |
|
|
75
|
+
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` | Optional OpenCode feature flag. Runs capture as a native background job; explicit `false` overrides the umbrella experimental flag |
|
|
54
76
|
|
|
55
77
|
## Hooks
|
|
56
78
|
|
|
57
79
|
| Hook | Purpose |
|
|
58
|
-
|
|
59
|
-
| `config` | Registers `memoir-mcp`
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `config` | Registers the `memoir` subagent, one project-scoped `memoir-mcp` remote MCP server, and the `/memoir:onboard` slash command |
|
|
60
82
|
| `shell.env` | Injects `MEMOIR_STORE` into shell environment |
|
|
61
|
-
| `chat.message` |
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
83
|
+
| `chat.message` | Captures the previous completed turn, tracks real parent messages, and auto-matches the memoir branch; waits for `promptAsync` to accept the visible capture task while ignoring synthetic and memoir-child messages |
|
|
84
|
+
| `tool.execute.before` | Marks memoir's `task` invocation with `background: true` when OpenCode background subagents are enabled |
|
|
85
|
+
| `tool.execute.after` + `event` | Tracks foreground completion and known background child idle/error events so checkout does not overlap an active capture |
|
|
86
|
+
| `experimental.chat.system.transform` | Compact status after real parent messages + startup hint and proactive recall once per session |
|
|
87
|
+
| `dispose` | Saves session markers when `MEMOIR_AUTO_SAVE=1` is explicit, closes the project MCP process, and clears instance state |
|
|
64
88
|
|
|
65
89
|
## How it works
|
|
66
90
|
|
|
67
|
-
Instead of wrapping the `memoir` CLI and re-implementing tools in TypeScript, this plugin registers `memoir-mcp` as a
|
|
91
|
+
Instead of wrapping the `memoir` CLI and re-implementing tools in TypeScript, this plugin registers `memoir-mcp` as a **remote** MCP server — one HTTP process per plugin/project instance. All memoir tools (`memoir_memoir_recall`, `memoir_memoir_remember`, `memoir_memoir_get`, etc.) are available natively to the main LLM.
|
|
92
|
+
|
|
93
|
+
Capturing is done by a dedicated visible, collapsible `memoir` subagent. It can use the dynamic `memoir_*` tool namespace except for the store-global `memoir_memoir_checkout`; branch checkout remains owned by the plugin so a subagent cannot move the shared store. Every non-Memoir tool remains denied. The capture task includes the live MCP tool names and descriptions so a small local model does not have to infer the catalog. Deterministic settings keep it suitable for that model. On each real `chat.message`, the plugin captures the previous completed turn: the incoming user message has not entered the transcript yet. This one-turn delay prevents capture activity from triggering another capture. Capture is submitted as a supported `SubtaskPartInput` through `promptAsync`; the hook waits for OpenCode's immediate `204 No Content` acceptance, not for subagent execution. When native background subagents are enabled, `tool.execute.before` adds `background: true` before `TaskTool.execute` starts the child.
|
|
94
|
+
|
|
95
|
+
The subagent is instructed to finish with a compact report of confirmed writes (`Captured N memories`, taxonomy paths, and brief reasons), or a single reason why nothing was captured. OpenCode returns that report to the parent agent through the normal task result. The prompt requires the report to reflect actual tool outcomes rather than intended writes.
|
|
96
|
+
|
|
97
|
+
After each real parent-session message, every model call in that turn receives a compact status from `memoir_status`, such as `[memoir] fix6 · memory available (31 memories)`. Synthetic notifications and memoir child activity do not update it, and the status does not instruct the model to perform recall.
|
|
98
|
+
|
|
99
|
+
- With `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, the plugin adds `background: true` in `tool.execute.before`. OpenCode runs the child through its native `BackgroundJob` path and immediately releases the parent session.
|
|
100
|
+
- `OPENCODE_EXPERIMENTAL=true` has the same effect unless the dedicated background-subagent flag is explicitly false, matching OpenCode's native precedence.
|
|
101
|
+
- Without either enabling flag, the plugin does not add `background`; OpenCode runs the same capture task through the original foreground subagent path. Memory capture remains available, but the parent session may wait for it to finish.
|
|
102
|
+
- Before changing the shared memoir branch, the plugin waits for active foreground tasks (`tool.execute.after`) and native background tasks (the known child session's terminal idle/error event). A timeout defers checkout instead of moving the store underneath a running capture.
|
|
103
|
+
|
|
104
|
+
At session start, `experimental.chat.system.transform` injects a recall hint plus a `memoir_summarize` snapshot so the agent begins with prior context.
|
|
68
105
|
|
|
69
106
|
## Development
|
|
70
107
|
|
|
@@ -76,7 +113,7 @@ Instead of wrapping the `memoir` CLI and re-implementing tools in TypeScript, th
|
|
|
76
113
|
### Setup
|
|
77
114
|
|
|
78
115
|
```bash
|
|
79
|
-
|
|
116
|
+
make install # install dependencies and Git hooks
|
|
80
117
|
npm run build # typecheck + emit dist
|
|
81
118
|
npm test # run the test suite
|
|
82
119
|
```
|
|
@@ -84,11 +121,17 @@ npm test # run the test suite
|
|
|
84
121
|
### Source layout
|
|
85
122
|
|
|
86
123
|
| File | Responsibility |
|
|
87
|
-
|
|
124
|
+
| --- | --- |
|
|
88
125
|
| `src/index.ts` | Plugin entry: MCP registration + all hooks + dispose |
|
|
89
|
-
| `src/
|
|
90
|
-
| `src/
|
|
91
|
-
| `src/
|
|
126
|
+
| `src/mcp-client.ts` | Project-scoped MCP process/client lifecycle and tool calls |
|
|
127
|
+
| `src/capture.ts` | Transcript extraction, filtering, task construction, and capture dispatch |
|
|
128
|
+
| `src/capture-lifecycle.ts` | Foreground/background capture tracking and checkout drain |
|
|
129
|
+
| `src/subagent.ts` | Subagent permissions, model selection, and OpenCode task dispatch |
|
|
130
|
+
| `src/store.ts` | Store path derivation and serialized store-branch matching |
|
|
131
|
+
| `src/path.ts` | Symlink-safe project and store path helpers |
|
|
132
|
+
| `src/prompts.ts` | Cached prompt-template loader |
|
|
133
|
+
| `src/turn-status.ts` | Compact model-facing status formatter |
|
|
134
|
+
| `src/debug.ts` | File/stderr lifecycle and debug logger |
|
|
92
135
|
|
|
93
136
|
## Publishing
|
|
94
137
|
|
|
@@ -97,4 +140,3 @@ Releases are fully automated — no manual `npm version` or `npm publish`.
|
|
|
97
140
|
1. Land changes on `main` via PR using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, etc.)
|
|
98
141
|
2. On push to `main`, `.github/workflows/semantic.yaml` runs [semantic-release](https://semantic-release.gitbook.io/)
|
|
99
142
|
3. The new tag triggers `.github/workflows/publish-npm.yaml`, which builds and runs `npm publish --provenance`
|
|
100
|
-
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Track actual memoir task execution, including native background jobs. */
|
|
2
|
+
export declare class CaptureLifecycle {
|
|
3
|
+
private readonly active;
|
|
4
|
+
private readonly callsByChild;
|
|
5
|
+
begin(callID: string): void;
|
|
6
|
+
background(callID: string, childSessionID: string): void;
|
|
7
|
+
finishCall(callID: string): void;
|
|
8
|
+
finishSession(sessionID: string): void;
|
|
9
|
+
drain(timeoutMs?: number): Promise<boolean>;
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=capture-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-lifecycle.d.ts","sourceRoot":"","sources":["../src/capture-lifecycle.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS3B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAOxD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQhC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKhC,KAAK,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAejD,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MemoirToolInfo } from "./mcp-client.js";
|
|
2
|
+
interface ChatPart {
|
|
3
|
+
type?: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface ChatMessage {
|
|
8
|
+
info?: {
|
|
9
|
+
id?: string;
|
|
10
|
+
role?: string;
|
|
11
|
+
};
|
|
12
|
+
parts?: ChatPart[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Render a list of messages into a plain-text transcript.
|
|
16
|
+
* Skips empty (no text) messages so tool-only turns stay quiet.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatSessionTranscript(messages: ChatMessage[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Extract the most recent completed turn: the last assistant message that has
|
|
21
|
+
* text, plus the user message(s) immediately preceding it. Returns null when
|
|
22
|
+
* there is no completed assistant turn yet.
|
|
23
|
+
*/
|
|
24
|
+
export declare function lastTurnTranscript(messages: ChatMessage[]): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Local, LLM-free pre-filter. When MEMOIR_CAPTURE_MIN_CHARS (default 16) is
|
|
27
|
+
* > 0, only transcripts at least that long are worth sending to the subagent.
|
|
28
|
+
* A value of 0 disables the filter (capture everything).
|
|
29
|
+
*/
|
|
30
|
+
export declare function shouldCaptureTurn(transcript: string | null, minChars?: number): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Build the capture task handed to the memoir subagent. It applies the
|
|
33
|
+
* durability checks, writes each fact with an explicit 3-level taxonomy path,
|
|
34
|
+
* and returns a compact report based only on confirmed tool outcomes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildTurnCaptureTask(transcript: string, tools?: MemoirToolInfo[]): string;
|
|
37
|
+
/**
|
|
38
|
+
* Capture the latest completed turn of a session into memoir via the subagent.
|
|
39
|
+
*
|
|
40
|
+
* Fire-and-forget: the caller should NOT await the subagent's completion.
|
|
41
|
+
* Deduped per session by the last assistant message id, so a turn is written
|
|
42
|
+
* at most once. Skips entirely when MEMOIR_AUTO_SAVE=0.
|
|
43
|
+
*
|
|
44
|
+
* @param client SDK client (transcript source + subagent spawner)
|
|
45
|
+
* @param sessionID parent session id
|
|
46
|
+
* @param directory working directory for the child session (unused — the
|
|
47
|
+
* running opencode instance is already bound to it; kept for call stability)
|
|
48
|
+
* @param lastCaptured per-session map of already-captured assistant ids
|
|
49
|
+
*/
|
|
50
|
+
export declare function captureTurn(client: unknown, sessionID: string, _directory: string, lastCaptured: Map<string, string>, tools?: MemoirToolInfo[]): Promise<void>;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQtD,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB;AAyDD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,CASvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAoBzE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,SAAoB,GAC3B,OAAO,CAGT;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,cAAc,EAAO,GAAG,MAAM,CAW7F;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,KAAK,GAAE,cAAc,EAAO,GAC3B,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* Only writes when MEMOIR_DEBUG=1 is set.
|
|
4
|
-
*/
|
|
5
|
-
export declare function debugLog(...args: unknown[]): void;
|
|
1
|
+
/** Single logging entrypoint; MEMOIR_DEBUG=1 controls detail, not emission. */
|
|
2
|
+
export declare function log(...args: unknown[]): void;
|
|
6
3
|
//# sourceMappingURL=debug.d.ts.map
|
package/dist/debug.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAkEA,+EAA+E;AAC/E,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE5C"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA+UrF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
|