@log0u7/opencode-memory 0.1.2 → 0.3.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/CHANGELOG.md +60 -1
- package/README.md +17 -4
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +1 -0
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/memory-ops.d.ts +55 -0
- package/dist/lib/memory-ops.d.ts.map +1 -0
- package/dist/lib/memory-ops.js +125 -0
- package/dist/lib/memory-ops.js.map +1 -0
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js +5 -3
- package/dist/lib/sqlite.js.map +1 -1
- package/dist/lib/store.d.ts +40 -2
- package/dist/lib/store.d.ts.map +1 -1
- package/dist/lib/store.js +157 -21
- package/dist/lib/store.js.map +1 -1
- package/dist/plugin-v2.d.ts +3 -0
- package/dist/plugin-v2.d.ts.map +1 -0
- package/dist/plugin-v2.js +124 -0
- package/dist/plugin-v2.js.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +40 -78
- package/dist/plugin.js.map +1 -1
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,66 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.3.0] - 2026-10-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Deterministic injection: the first request of each session carries a bounded list of the
|
|
13
|
+
project's most recent entries (titles, kinds, dates, no bodies, ~1200 characters worst
|
|
14
|
+
case), so using memory no longer depends on the model remembering to call
|
|
15
|
+
`memory_search`. The tool stays for deeper lookups.
|
|
16
|
+
- Compaction inventory: the compaction context now lists the stored entries next to the
|
|
17
|
+
save instruction, so the model summarises from an inventory instead of recall and stops
|
|
18
|
+
re-saving what is already stored.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Both plugin entry points (`server` for OpenCode 1, `setup` for OpenCode 2) now delegate
|
|
23
|
+
to one shared core (`src/lib/memory-ops.ts`): tool behaviour, error-to-text mapping,
|
|
24
|
+
caps and rendering live in a single place, and each host is thin SDK glue. No
|
|
25
|
+
behavioural change.
|
|
26
|
+
- The system guidance no longer tells the model to search at task start (the injection
|
|
27
|
+
covers it); the credential warning and both tool mentions stay.
|
|
28
|
+
- CONTRIBUTING.md states the merge-policy rationale instead of a solo-maintainer
|
|
29
|
+
preference: `--no-ff` keeps one merge commit per PR, which reads as the PR list via
|
|
30
|
+
`git log --merges --first-parent main` and is a single atomic revert unit, with a
|
|
31
|
+
five-commit PR ceiling bounding the cost. Documentation only, no package change.
|
|
32
|
+
- `memory_save` rewrites an entry that repeats a scope and title instead of adding a
|
|
33
|
+
duplicate, which stopped the store from growing with every compaction.
|
|
34
|
+
- Saving purges rows past their `expires_at` instead of only hiding them at query time.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- `memory_forget` by query or id could delete entries of other projects: deletes are now
|
|
39
|
+
scoped to the caller's scope (plus `global`), like `memory_search` and `memory_list`.
|
|
40
|
+
- A model-supplied `limit` crashed `memory_search`/`memory_list` (fractional, `NaN`) or
|
|
41
|
+
returned the whole store (`-1`, which SQLite reads as unbounded). Limits are clamped to
|
|
42
|
+
1..50, and tool results are bounded: bodies render as 500-character previews.
|
|
43
|
+
- A credential in an entry `title` or `kind` bypassed the secret guard; the scan now covers
|
|
44
|
+
title, kind, body and tags, and `kind` has a length cap.
|
|
45
|
+
- Search returned nothing for partial queries (`"migration strategy"` found no entry
|
|
46
|
+
containing "migration", and `"migrat"` found nothing): a strict AND match is now retried
|
|
47
|
+
as a prefix OR match, and an empty query returns no results instead of failing.
|
|
48
|
+
- The two plugin entry points defaulted to different scopes for the same project (v1 used
|
|
49
|
+
the session worktree, v2 the plugin directory) and disagreed about trailing slashes.
|
|
50
|
+
Both now derive one canonical scope key.
|
|
51
|
+
- The published package could not be loaded by a Node host: `src/lib/sqlite.ts` called a
|
|
52
|
+
bare `require("node:sqlite")`, which is undefined in an ES module. The Node branch now
|
|
53
|
+
uses `createRequire(import.meta.url)`.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
- README advertised an FTS5 "recency boost" that the code never implemented; the actual
|
|
58
|
+
ranking (BM25, newest first on ties) is documented instead.
|
|
59
|
+
|
|
60
|
+
## [0.2.0] - 2026-10-12
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- Dual-export plugin entry for OpenCode 2: one published package runs on both
|
|
65
|
+
OpenCode 1.18 and 2.x (official migration pattern, "Support V1 and V2 from
|
|
66
|
+
one package"). v1 hosts call `server()` (unchanged); v2 hosts call `setup()`
|
|
67
|
+
(`Plugin.define`). Both entries drive the same engine.
|
|
9
68
|
|
|
10
69
|
## [0.1.2] - 2026-09-23
|
|
11
70
|
|
package/README.md
CHANGED
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/log0u7/opencode-memory/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@log0u7/opencode-memory)
|
|
5
|
+
[](https://www.npmjs.com/package/@log0u7/opencode-memory)
|
|
6
|
+
[](https://github.com/log0u7/opencode-memory/blob/main/package.json)
|
|
5
7
|
[](LICENSE)
|
|
6
8
|
|
|
7
9
|
Working memory for [OpenCode](https://opencode.ai): a shared SQLite store that all sessions on a machine read and write, so decisions, facts, and compaction summaries survive session boundaries.
|
|
8
10
|
|
|
9
|
-
Every session gets four tools (`memory_save`, `memory_search`, `memory_list`, `memory_forget`). Entries are scoped per project worktree (plus a `global` scope), searched with SQLite FTS5 (BM25 ranking with a
|
|
11
|
+
Every session gets four tools (`memory_save`, `memory_search`, `memory_list`, `memory_forget`). Entries are scoped per project worktree (plus a `global` scope), searched with SQLite FTS5 (BM25 ranking, strict match first with a prefix fallback). Recent entries are also injected into the prompt: once per session, and again during compaction. 100% local, no cloud, no API keys.
|
|
12
|
+
|
|
13
|
+
## Safety limits
|
|
14
|
+
|
|
15
|
+
- Results are capped: at most 50 entries per call, and each body is rendered as a 500-character preview. A tool result has a bounded size whatever the model asks for.
|
|
16
|
+
- `memory_save` rewrites an entry that repeats a scope and title instead of stacking duplicates, and purges expired rows.
|
|
17
|
+
- `memory_forget` only deletes inside the caller's scope (plus the shared `global` scope).
|
|
18
|
+
- Entries that look like secrets (`sk-…`, `ghp_…`, `xoxb-…`) are refused, in the title, kind, body and tags alike.
|
|
10
19
|
|
|
11
20
|
## Install
|
|
12
21
|
|
|
@@ -46,7 +55,7 @@ memory_forget(id="...")
|
|
|
46
55
|
| Tool | Args | Description |
|
|
47
56
|
|---|---|---|
|
|
48
57
|
| `memory_save` | `kind`, `title`, `body`, `scope?`, `tags?`, `expires_at?` | Save an entry. Default scope is the session worktree. |
|
|
49
|
-
| `memory_search` | `query`, `scope?`, `limit?` |
|
|
58
|
+
| `memory_search` | `query`, `scope?`, `limit?` | FTS5 BM25 search; strict match first, prefix OR fallback when nothing matches. |
|
|
50
59
|
| `memory_list` | `scope?`, `limit?` | Recent entries, newest first. |
|
|
51
60
|
| `memory_forget` | `id` or `query` | Delete one entry by id, or all matching a query. |
|
|
52
61
|
|
|
@@ -55,8 +64,12 @@ Entry `kind` is free-form; conventions that work well: `decision`, `fact`, `pref
|
|
|
55
64
|
### Notes
|
|
56
65
|
|
|
57
66
|
- All sessions on the machine share one store (WAL mode, concurrent-safe).
|
|
58
|
-
- Entries that look like secrets (`sk-…`, `ghp_…`, `xoxb-…`) are refused.
|
|
59
|
-
-
|
|
67
|
+
- Entries that look like secrets (`sk-…`, `ghp_…`, `xoxb-…`) are refused, in the title, kind, body and tags.
|
|
68
|
+
- Every repeated `memory_save` of the same title in the same scope rewrites the entry, so the store does not grow with duplicates, and expired rows are purged on save.
|
|
69
|
+
- `memory_forget` never leaves the scope it is called from, so a project cannot delete another project's entries.
|
|
70
|
+
- On the first request of a session, the most recent entries are injected into the system prompt as a bounded list (titles only), so the model does not have to remember to search. `memory_search` remains the tool for deeper lookups.
|
|
71
|
+
- On session compaction, the plugin lists the stored entries in the compaction context and asks the compaction prompt to emit durable decisions, so the model summarises from an inventory instead of recall.
|
|
72
|
+
- Injections stay bounded (~1200 characters worst case) and nothing is injected, nor any store file created, when memory is unused.
|
|
60
73
|
|
|
61
74
|
## Development
|
|
62
75
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Dual entrypoint: v1 hosts call `server()`, v2 hosts call `setup()`
|
|
10
|
+
* (see https://opencode.ai/v2/docs/build/plugins/migrate-v1).
|
|
11
|
+
*/
|
|
12
|
+
export declare const pluginModule: {
|
|
9
13
|
id: string;
|
|
14
|
+
setup: (context: import("@opencode/plugin/promise/plugin").Context) => Promise<import("@opencode/plugin/promise/plugin").Cleanup | void> | import("@opencode/plugin/promise/plugin").Cleanup | void;
|
|
10
15
|
server: import("@opencode-ai/plugin").Plugin;
|
|
11
16
|
};
|
|
12
17
|
export default pluginModule;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;CAGiE,CAAC;eAE5E,YAAY;AAE3B,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
+
import { MemoryV2 } from "./plugin-v2.js";
|
|
8
9
|
import { MemoryPlugin } from "./plugin.js";
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Dual entrypoint: v1 hosts call `server()`, v2 hosts call `setup()`
|
|
12
|
+
* (see https://opencode.ai/v2/docs/build/plugins/migrate-v1).
|
|
13
|
+
*/
|
|
14
|
+
export const pluginModule = {
|
|
15
|
+
...MemoryV2,
|
|
11
16
|
server: MemoryPlugin,
|
|
12
17
|
};
|
|
13
18
|
export default pluginModule;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,YAAY,GAAG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,GAAG,QAAQ;IACX,MAAM,EAAE,YAAY;CACoE,CAAC;AAE3F,eAAe,YAAY,CAAC;AAI5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/lib/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AA+BF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAWnD"}
|
package/dist/lib/db.js
CHANGED
|
@@ -27,6 +27,7 @@ CREATE TRIGGER IF NOT EXISTS entries_au AFTER UPDATE ON entries BEGIN
|
|
|
27
27
|
INSERT INTO entries_fts(entries_fts, rowid, title, body, tags) VALUES ('delete', old.rowid, old.title, old.body, old.tags);
|
|
28
28
|
INSERT INTO entries_fts(rowid, title, body, tags) VALUES (new.rowid, new.title, new.body, new.tags);
|
|
29
29
|
END;
|
|
30
|
+
CREATE INDEX IF NOT EXISTS entries_scope_title ON entries(scope, lower(trim(title)));
|
|
30
31
|
`;
|
|
31
32
|
export function openMemoryDb(path) {
|
|
32
33
|
if (path !== ":memory:") {
|
package/dist/lib/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAO1D,MAAM,MAAM,GAAG
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAO1D,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2Bd,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAElB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MemoryDb } from "./db.js";
|
|
2
|
+
export type SaveArgs = {
|
|
3
|
+
kind: string;
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
tags?: string[] | undefined;
|
|
7
|
+
expires_at?: number | null | undefined;
|
|
8
|
+
};
|
|
9
|
+
export type SearchArgs = {
|
|
10
|
+
query: string;
|
|
11
|
+
limit?: number | undefined;
|
|
12
|
+
};
|
|
13
|
+
export type ListArgs = {
|
|
14
|
+
limit?: number | undefined;
|
|
15
|
+
};
|
|
16
|
+
export type ForgetArgs = {
|
|
17
|
+
id?: string | undefined;
|
|
18
|
+
query?: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
/** One shared behaviour behind both plugin entry points. Every operation returns the
|
|
21
|
+
* model-facing text directly, so a caller cannot bypass caps or error handling. */
|
|
22
|
+
export type MemoryOps = {
|
|
23
|
+
save: (args: SaveArgs, scope: string) => string;
|
|
24
|
+
search: (args: SearchArgs, scope: string) => string;
|
|
25
|
+
list: (args: ListArgs, scope: string) => string;
|
|
26
|
+
forget: (args: ForgetArgs, scope: string) => string;
|
|
27
|
+
};
|
|
28
|
+
export declare function createMemoryOps(getDb: () => MemoryDb): MemoryOps;
|
|
29
|
+
/** How much of the store is summarised into a prompt, worst case. */
|
|
30
|
+
export declare const INVENTORY_MAX_CHARS = 1200;
|
|
31
|
+
/** Tool descriptions shared by both entry points so the two hosts cannot drift. */
|
|
32
|
+
export declare const TOOL_DESCRIPTIONS: {
|
|
33
|
+
readonly save: "Save a durable entry to shared working memory (decisions, facts, preferences, gotchas). Never store credentials.";
|
|
34
|
+
readonly search: "Search shared working memory (all sessions on this machine). Results include this project's scope and global entries.";
|
|
35
|
+
readonly list: "List recent memory entries for a scope (default: this project), newest first.";
|
|
36
|
+
readonly forget: "Delete memory entries by id, or all entries matching a query. Deletes only inside this project's scope (plus global entries); an entry saved under another scope needs that scope to be removed.";
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Produces at most one injection per session key, and only on the first request where
|
|
40
|
+
* the producer has something: a session that starts before any entry exists still gets
|
|
41
|
+
* its inventory as soon as one is available. Keys are capped so a long-lived host
|
|
42
|
+
* cannot grow the set forever; an absent sessionID (v1 marks it optional) degrades to
|
|
43
|
+
* one injection per plugin instance, never per request.
|
|
44
|
+
*/
|
|
45
|
+
export declare function oncePerSession(): {
|
|
46
|
+
inject: (key: string | undefined, produce: () => string | null) => string | null;
|
|
47
|
+
reset: () => void;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Bounded summary of recent entries, injected into the first request of a session and
|
|
51
|
+
* into the compaction context. `null` when there is nothing worth injecting, so a
|
|
52
|
+
* caller pushes nothing at all.
|
|
53
|
+
*/
|
|
54
|
+
export declare function memoryInventory(getDb: () => MemoryDb, dbPath: string, scope: string): string | null;
|
|
55
|
+
//# sourceMappingURL=memory-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-ops.d.ts","sourceRoot":"","sources":["../../src/lib/memory-ops.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACxC,CAAC;AACF,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AACvE,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AACtD,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjF;mFACmF;AACnF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACrD,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,QAAQ,GAAG,SAAS,CA4ChE;AAED,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAIxC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB;aAC5B,IAAI,EAAE,kHAAkH;aACxH,MAAM,EACJ,uHAAuH;aACzH,IAAI,EAAE,+EAA+E;aACrF,MAAM,EACJ,kMAAkM;CAC5L,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,cAAc;kBAGP,MAAM,GAAG,SAAS,WAAW,MAAM,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAAI;;EAgBtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAqBf"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { forget, list, renderEntries, save, search, SecretDetectedError } from "./store.js";
|
|
3
|
+
export function createMemoryOps(getDb) {
|
|
4
|
+
return {
|
|
5
|
+
save: (args, scope) => run(() => {
|
|
6
|
+
const entry = save(getDb(), {
|
|
7
|
+
scope,
|
|
8
|
+
kind: args.kind,
|
|
9
|
+
title: args.title,
|
|
10
|
+
body: args.body,
|
|
11
|
+
tags: args.tags ?? [],
|
|
12
|
+
expires_at: args.expires_at ?? null,
|
|
13
|
+
});
|
|
14
|
+
return `saved ${entry.id} (${entry.scope})`;
|
|
15
|
+
}),
|
|
16
|
+
search: (args, scope) => run(() => {
|
|
17
|
+
const hits = search(getDb(), {
|
|
18
|
+
query: args.query,
|
|
19
|
+
scope,
|
|
20
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
21
|
+
});
|
|
22
|
+
return hits.length === 0 ? "no matching memory entries" : renderEntries(hits);
|
|
23
|
+
}),
|
|
24
|
+
list: (args, scope) => run(() => {
|
|
25
|
+
const entries = list(getDb(), {
|
|
26
|
+
scope,
|
|
27
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
28
|
+
});
|
|
29
|
+
return entries.length === 0 ? "no memory entries" : renderEntries(entries);
|
|
30
|
+
}),
|
|
31
|
+
forget: (args, scope) => run(() => {
|
|
32
|
+
if (args.id !== undefined) {
|
|
33
|
+
const deleted = forget(getDb(), { id: args.id, scope });
|
|
34
|
+
return `deleted ${deleted} entr${deleted === 1 ? "y" : "ies"}`;
|
|
35
|
+
}
|
|
36
|
+
if (args.query !== undefined) {
|
|
37
|
+
const deleted = forget(getDb(), { query: args.query, scope });
|
|
38
|
+
return `deleted ${deleted} entr${deleted === 1 ? "y" : "ies"}`;
|
|
39
|
+
}
|
|
40
|
+
return "provide id or query";
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** How much of the store is summarised into a prompt, worst case. */
|
|
45
|
+
export const INVENTORY_MAX_CHARS = 1200;
|
|
46
|
+
const INVENTORY_HEADER = "Memory already stored for this project (newest first):";
|
|
47
|
+
/** Tool descriptions shared by both entry points so the two hosts cannot drift. */
|
|
48
|
+
export const TOOL_DESCRIPTIONS = {
|
|
49
|
+
save: "Save a durable entry to shared working memory (decisions, facts, preferences, gotchas). Never store credentials.",
|
|
50
|
+
search: "Search shared working memory (all sessions on this machine). Results include this project's scope and global entries.",
|
|
51
|
+
list: "List recent memory entries for a scope (default: this project), newest first.",
|
|
52
|
+
forget: "Delete memory entries by id, or all entries matching a query. Deletes only inside this project's scope (plus global entries); an entry saved under another scope needs that scope to be removed.",
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Produces at most one injection per session key, and only on the first request where
|
|
56
|
+
* the producer has something: a session that starts before any entry exists still gets
|
|
57
|
+
* its inventory as soon as one is available. Keys are capped so a long-lived host
|
|
58
|
+
* cannot grow the set forever; an absent sessionID (v1 marks it optional) degrades to
|
|
59
|
+
* one injection per plugin instance, never per request.
|
|
60
|
+
*/
|
|
61
|
+
export function oncePerSession() {
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const MAX_KEYS = 512;
|
|
64
|
+
const inject = (key, produce) => {
|
|
65
|
+
const k = key ?? "__instance__";
|
|
66
|
+
if (seen.has(k)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const produced = produce();
|
|
70
|
+
if (produced === null) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
seen.add(k);
|
|
74
|
+
if (seen.size > MAX_KEYS) {
|
|
75
|
+
seen.clear();
|
|
76
|
+
}
|
|
77
|
+
return produced;
|
|
78
|
+
};
|
|
79
|
+
return { inject, reset: () => seen.clear() };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Bounded summary of recent entries, injected into the first request of a session and
|
|
83
|
+
* into the compaction context. `null` when there is nothing worth injecting, so a
|
|
84
|
+
* caller pushes nothing at all.
|
|
85
|
+
*/
|
|
86
|
+
export function memoryInventory(getDb, dbPath, scope) {
|
|
87
|
+
if (!existsSync(dbPath)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const entries = list(getDb(), { scope, limit: 50 });
|
|
91
|
+
if (entries.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const lines = [INVENTORY_HEADER];
|
|
95
|
+
let size = INVENTORY_HEADER.length;
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const line = `- [${entry.kind}] ${entry.title} (updated ${dayOf(entry.updated_at)})`;
|
|
98
|
+
if (size + line.length + 1 > INVENTORY_MAX_CHARS) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
lines.push(line);
|
|
102
|
+
size += line.length + 1;
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
function dayOf(epochMs) {
|
|
107
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
108
|
+
}
|
|
109
|
+
// Tool results are shown to the model: caught errors become readable text, so a
|
|
110
|
+
// rejected save is guidance instead of a crashed tool call.
|
|
111
|
+
function run(fn) {
|
|
112
|
+
try {
|
|
113
|
+
return fn();
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error instanceof SecretDetectedError) {
|
|
117
|
+
return "refused: the content looks like a credential; secrets must never be stored in memory";
|
|
118
|
+
}
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
return `error: ${error.message}`;
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=memory-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-ops.js","sourceRoot":"","sources":["../../src/lib/memory-ops.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IACvD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAqED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAanD"}
|
package/dist/lib/sqlite.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
function createPreparedConn(db) {
|
|
2
3
|
return {
|
|
3
4
|
all(sql, params) {
|
|
@@ -46,9 +47,10 @@ export function openSqlite(path) {
|
|
|
46
47
|
if (globalRuntime.Bun) {
|
|
47
48
|
return createBunConn(new globalRuntime.Bun.sqlite.Database(path));
|
|
48
49
|
}
|
|
49
|
-
// node:sqlite ships with Node >= 22.5
|
|
50
|
-
//
|
|
51
|
-
const { DatabaseSync } =
|
|
50
|
+
// node:sqlite ships with Node >= 22.5. createRequire keeps the load lazy for
|
|
51
|
+
// runtimes without node:sqlite: a bare require is not defined in an ES module.
|
|
52
|
+
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
52
53
|
return createPreparedConn(new DatabaseSync(path));
|
|
53
54
|
}
|
|
55
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
54
56
|
//# sourceMappingURL=sqlite.js.map
|
package/dist/lib/sqlite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgC5C,SAAS,kBAAkB,CAAC,EAAsB;IAChD,OAAO;QACL,GAAG,CAAc,GAAW,EAAE,MAAkB;YAC9C,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAQ,CAAC;QAC5C,CAAC;QACD,GAAG,CAAc,GAAW,EAAE,MAAkB;YAC9C,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,OAAQ,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAmB,IAAI,IAAI,CAAC;QAChE,CAAC;QACD,GAAG,CAAC,GAAW,EAAE,MAAkB;YACjC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,GAAW;YACd,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QACD,KAAK;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,EAAqB;IAC1C,OAAO;QACL,GAAG,CAAc,GAAW,EAAE,MAAkB;YAC9C,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAQ,CAAC;QACrD,CAAC;QACD,GAAG,CAAc,GAAW,EAAE,MAAkB;YAC9C,OAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAmB,IAAI,IAAI,CAAC;QACzE,CAAC;QACD,GAAG,CAAC,GAAW,EAAE,MAAkB;YACjC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,GAAW;YACd,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,KAAK;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,kFAAkF;AAClF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,aAAa,GAAG,UAErB,CAAC;IAEF,IAAI,aAAa,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,aAAa,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,aAAa,CAAsB,CAAC;IACzE,OAAO,kBAAkB,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC"}
|
package/dist/lib/store.d.ts
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import type { MemoryDb } from "./db.js";
|
|
2
2
|
import type { Entry, EntryInput } from "../types/entry.js";
|
|
3
|
+
export declare const TAG_MAX = 64;
|
|
4
|
+
export declare const KIND_MAX = 64;
|
|
5
|
+
/** Hard ceiling on how many entries one read can return, whatever the caller asks. */
|
|
6
|
+
export declare const MAX_LIMIT = 50;
|
|
7
|
+
/** A rendered body is a preview, never the full text: it bounds a tool result. */
|
|
8
|
+
export declare const BODY_PREVIEW_MAX = 500;
|
|
9
|
+
export declare const DEFAULT_LIMIT: {
|
|
10
|
+
search: number;
|
|
11
|
+
list: number;
|
|
12
|
+
};
|
|
13
|
+
/** Scope used when no worktree and no directory is known, so rows stay addressable. */
|
|
14
|
+
export declare const FALLBACK_SCOPE = "/";
|
|
3
15
|
export declare class SecretDetectedError extends Error {
|
|
4
16
|
constructor();
|
|
5
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* One canonical scope string for one location. Callers hand in a worktree, a
|
|
20
|
+
* directory, or both, so an entry written by either plugin entry point is found
|
|
21
|
+
* by the other.
|
|
22
|
+
*/
|
|
23
|
+
export declare function canonicalScope(...candidates: Array<string | undefined>): string;
|
|
24
|
+
/** A model-supplied limit is untrusted: SQLite reads a negative LIMIT as unbounded,
|
|
25
|
+
* so anything below one falls back to the default, and anything above the store
|
|
26
|
+
* ceiling is clamped. */
|
|
27
|
+
export declare function clampLimit(requested: number | undefined, fallback: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Model-facing rendering of a result set. Compact JSON and truncated bodies keep a
|
|
30
|
+
* tool result inside a known size regardless of how much is stored.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderEntries(entries: Entry[]): string;
|
|
33
|
+
export declare function sweep(db: MemoryDb, options?: {
|
|
34
|
+
now?: number;
|
|
35
|
+
}): number;
|
|
6
36
|
export declare function save(db: MemoryDb, input: EntryInput, options?: {
|
|
7
37
|
now?: number;
|
|
8
38
|
}): Entry;
|
|
@@ -17,9 +47,17 @@ export declare function list(db: MemoryDb, query: {
|
|
|
17
47
|
limit?: number;
|
|
18
48
|
now?: number;
|
|
19
49
|
}): Entry[];
|
|
20
|
-
export
|
|
50
|
+
export type ForgetTarget = {
|
|
51
|
+
scope: string;
|
|
52
|
+
} & ({
|
|
21
53
|
id: string;
|
|
22
54
|
} | {
|
|
23
55
|
query: string;
|
|
24
|
-
})
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Deletes inside one scope (plus the shared `global` scope), never across the whole
|
|
59
|
+
* machine: the store is shared, so an unscoped delete would silently hit other
|
|
60
|
+
* projects.
|
|
61
|
+
*/
|
|
62
|
+
export declare function forget(db: MemoryDb, target: ForgetTarget): number;
|
|
25
63
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/lib/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAK3D,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,sFAAsF;AACtF,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,aAAa;IAAK,MAAM;IAAM,IAAI;CAAM,CAAC;AAEtD,uFAAuF;AACvF,eAAO,MAAM,cAAc,MAAM,CAAC;AAQlC,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,cAGC;CACF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAM/E;AA+BD;;yBAEyB;AACzB,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKlF;AASD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAEtD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAI1E;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,KAAK,CAuE3F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE,KAAK,EAAE,CAqBT;AAED,wBAAgB,IAAI,CAClB,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,KAAK,EAAE,CAcT;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAwBjE"}
|
package/dist/lib/store.js
CHANGED
|
@@ -2,6 +2,15 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
const TITLE_MAX = 120;
|
|
3
3
|
const BODY_MAX = 8000;
|
|
4
4
|
const TAGS_MAX = 12;
|
|
5
|
+
export const TAG_MAX = 64;
|
|
6
|
+
export const KIND_MAX = 64;
|
|
7
|
+
/** Hard ceiling on how many entries one read can return, whatever the caller asks. */
|
|
8
|
+
export const MAX_LIMIT = 50;
|
|
9
|
+
/** A rendered body is a preview, never the full text: it bounds a tool result. */
|
|
10
|
+
export const BODY_PREVIEW_MAX = 500;
|
|
11
|
+
export const DEFAULT_LIMIT = { search: 10, list: 20 };
|
|
12
|
+
/** Scope used when no worktree and no directory is known, so rows stay addressable. */
|
|
13
|
+
export const FALLBACK_SCOPE = "/";
|
|
5
14
|
const SECRET_PATTERNS = [
|
|
6
15
|
/\bsk-[A-Za-z0-9_-]{20,}\b/,
|
|
7
16
|
/\bghp_[A-Za-z0-9]{30,}\b/,
|
|
@@ -13,6 +22,18 @@ export class SecretDetectedError extends Error {
|
|
|
13
22
|
this.name = "SecretDetectedError";
|
|
14
23
|
}
|
|
15
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* One canonical scope string for one location. Callers hand in a worktree, a
|
|
27
|
+
* directory, or both, so an entry written by either plugin entry point is found
|
|
28
|
+
* by the other.
|
|
29
|
+
*/
|
|
30
|
+
export function canonicalScope(...candidates) {
|
|
31
|
+
const first = candidates.find((c) => typeof c === "string" && c.trim().length > 0);
|
|
32
|
+
if (first === undefined) {
|
|
33
|
+
return FALLBACK_SCOPE;
|
|
34
|
+
}
|
|
35
|
+
return first.trim().replace(/\/+$/, "") || FALLBACK_SCOPE;
|
|
36
|
+
}
|
|
16
37
|
function validateEntry(input) {
|
|
17
38
|
if (input.title.length === 0 || input.title.length > TITLE_MAX) {
|
|
18
39
|
throw new TypeError(`title must be 1..${TITLE_MAX} characters`);
|
|
@@ -20,20 +41,91 @@ function validateEntry(input) {
|
|
|
20
41
|
if (input.body.length > BODY_MAX) {
|
|
21
42
|
throw new TypeError(`body must be at most ${BODY_MAX} characters`);
|
|
22
43
|
}
|
|
44
|
+
if (input.kind.length === 0 || input.kind.length > KIND_MAX) {
|
|
45
|
+
throw new TypeError(`kind must be 1..${KIND_MAX} characters`);
|
|
46
|
+
}
|
|
23
47
|
if (input.tags.length > TAGS_MAX) {
|
|
24
48
|
throw new TypeError(`at most ${TAGS_MAX} tags allowed`);
|
|
25
49
|
}
|
|
26
|
-
|
|
50
|
+
// A tool result stays bounded, so a tag cannot smuggle an unbounded payload either.
|
|
51
|
+
if (input.tags.some((tag) => tag.length > TAG_MAX)) {
|
|
52
|
+
throw new TypeError(`each tag must be at most ${TAG_MAX} characters`);
|
|
53
|
+
}
|
|
54
|
+
if (SECRET_PATTERNS.some((pattern) => looksLikeSecret(input, pattern))) {
|
|
27
55
|
throw new SecretDetectedError();
|
|
28
56
|
}
|
|
29
57
|
}
|
|
58
|
+
function looksLikeSecret(input, pattern) {
|
|
59
|
+
if (pattern.test(input.title) || pattern.test(input.kind) || pattern.test(input.body)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return input.tags.some((t) => pattern.test(t));
|
|
63
|
+
}
|
|
64
|
+
/** A model-supplied limit is untrusted: SQLite reads a negative LIMIT as unbounded,
|
|
65
|
+
* so anything below one falls back to the default, and anything above the store
|
|
66
|
+
* ceiling is clamped. */
|
|
67
|
+
export function clampLimit(requested, fallback) {
|
|
68
|
+
if (requested === undefined || !Number.isFinite(requested) || requested < 1) {
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
return Math.min(Math.trunc(requested), MAX_LIMIT);
|
|
72
|
+
}
|
|
73
|
+
function previewBody(body) {
|
|
74
|
+
if (body.length <= BODY_PREVIEW_MAX) {
|
|
75
|
+
return body;
|
|
76
|
+
}
|
|
77
|
+
return `${body.slice(0, BODY_PREVIEW_MAX)}… [truncated]`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Model-facing rendering of a result set. Compact JSON and truncated bodies keep a
|
|
81
|
+
* tool result inside a known size regardless of how much is stored.
|
|
82
|
+
*/
|
|
83
|
+
export function renderEntries(entries) {
|
|
84
|
+
return JSON.stringify(entries.map((entry) => ({ ...entry, body: previewBody(entry.body) })));
|
|
85
|
+
}
|
|
86
|
+
export function sweep(db, options = {}) {
|
|
87
|
+
const now = options.now ?? Date.now();
|
|
88
|
+
db.conn.run("DELETE FROM entries WHERE expires_at IS NOT NULL AND expires_at <= ?", [now]);
|
|
89
|
+
return db.conn.get("SELECT changes() AS changes")?.changes ?? 0;
|
|
90
|
+
}
|
|
30
91
|
export function save(db, input, options = {}) {
|
|
31
92
|
validateEntry(input);
|
|
93
|
+
const scope = canonicalScope(input.scope);
|
|
32
94
|
const now = options.now ?? Date.now();
|
|
95
|
+
// Expired rows are dead weight in search, so drop them with the save.
|
|
96
|
+
sweep(db, { now });
|
|
97
|
+
// A repeated title in the same scope is the same knowledge: rewrite it instead of
|
|
98
|
+
// stacking duplicates.
|
|
99
|
+
// ponytail: SQLite lower() folds ASCII only, so a non-ASCII title re-saved in a
|
|
100
|
+
// different case is not deduped; and the read-then-write is not transactional, so two
|
|
101
|
+
// concurrent saves of the same title can both insert. Ceiling is a rare duplicate, not
|
|
102
|
+
// data loss. Upgrade path: a normalized key column plus BEGIN IMMEDIATE.
|
|
103
|
+
const existing = db.conn.get("SELECT id, created_at FROM entries WHERE scope = ? AND lower(trim(title)) = ?", [scope, input.title.trim().toLowerCase()]);
|
|
104
|
+
if (existing) {
|
|
105
|
+
db.conn.run("UPDATE entries SET kind = ?, body = ?, tags = ?, updated_at = ?, expires_at = ? WHERE id = ?", [
|
|
106
|
+
input.kind,
|
|
107
|
+
input.body,
|
|
108
|
+
JSON.stringify(input.tags),
|
|
109
|
+
now,
|
|
110
|
+
input.expires_at ?? null,
|
|
111
|
+
existing.id,
|
|
112
|
+
]);
|
|
113
|
+
return {
|
|
114
|
+
id: existing.id,
|
|
115
|
+
scope,
|
|
116
|
+
kind: input.kind,
|
|
117
|
+
title: input.title,
|
|
118
|
+
body: input.body,
|
|
119
|
+
tags: input.tags,
|
|
120
|
+
created_at: existing.created_at,
|
|
121
|
+
updated_at: now,
|
|
122
|
+
expires_at: input.expires_at,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
33
125
|
const id = randomUUID();
|
|
34
126
|
db.conn.run("INSERT INTO entries (id, scope, kind, title, body, tags, created_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
35
127
|
id,
|
|
36
|
-
|
|
128
|
+
scope,
|
|
37
129
|
input.kind,
|
|
38
130
|
input.title,
|
|
39
131
|
input.body,
|
|
@@ -44,7 +136,7 @@ export function save(db, input, options = {}) {
|
|
|
44
136
|
]);
|
|
45
137
|
return {
|
|
46
138
|
id,
|
|
47
|
-
scope
|
|
139
|
+
scope,
|
|
48
140
|
kind: input.kind,
|
|
49
141
|
title: input.title,
|
|
50
142
|
body: input.body,
|
|
@@ -55,34 +147,60 @@ export function save(db, input, options = {}) {
|
|
|
55
147
|
};
|
|
56
148
|
}
|
|
57
149
|
export function search(db, query) {
|
|
58
|
-
const
|
|
150
|
+
const scope = canonicalScope(query.scope);
|
|
151
|
+
const limit = clampLimit(query.limit, DEFAULT_LIMIT.search);
|
|
59
152
|
const now = query.now ?? Date.now();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
AND (e.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
153
|
+
for (const match of searchCandidates(query.query)) {
|
|
154
|
+
const hits = db.conn
|
|
155
|
+
.all(`SELECT e.* FROM entries_fts JOIN entries e ON e.rowid = entries_fts.rowid
|
|
156
|
+
WHERE entries_fts MATCH ? AND (e.scope = ? OR e.scope = 'global')
|
|
157
|
+
AND (e.expires_at IS NULL OR e.expires_at > ?)
|
|
158
|
+
ORDER BY bm25(entries_fts) ASC, e.updated_at DESC
|
|
159
|
+
LIMIT ?`, [match, scope, now, limit])
|
|
160
|
+
.map(rowToEntry);
|
|
161
|
+
if (hits.length > 0) {
|
|
162
|
+
return hits;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return [];
|
|
67
166
|
}
|
|
68
167
|
export function list(db, query) {
|
|
69
|
-
const
|
|
168
|
+
const scope = canonicalScope(query.scope);
|
|
169
|
+
const limit = clampLimit(query.limit, DEFAULT_LIMIT.list);
|
|
70
170
|
const now = query.now ?? Date.now();
|
|
71
171
|
return db.conn
|
|
72
172
|
.all(`SELECT * FROM entries
|
|
73
173
|
WHERE (scope = ? OR scope = 'global') AND (expires_at IS NULL OR expires_at > ?)
|
|
74
174
|
ORDER BY updated_at DESC, id ASC
|
|
75
|
-
LIMIT ?`, [
|
|
175
|
+
LIMIT ?`, [scope, now, limit])
|
|
76
176
|
.map(rowToEntry);
|
|
77
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Deletes inside one scope (plus the shared `global` scope), never across the whole
|
|
180
|
+
* machine: the store is shared, so an unscoped delete would silently hit other
|
|
181
|
+
* projects.
|
|
182
|
+
*/
|
|
78
183
|
export function forget(db, target) {
|
|
184
|
+
const scope = canonicalScope(target.scope);
|
|
79
185
|
if ("id" in target) {
|
|
80
|
-
db.conn.run("DELETE FROM entries WHERE id = ?", [
|
|
81
|
-
|
|
186
|
+
db.conn.run("DELETE FROM entries WHERE id = ? AND (scope = ? OR scope = 'global')", [
|
|
187
|
+
target.id,
|
|
188
|
+
scope,
|
|
189
|
+
]);
|
|
190
|
+
return changesOf(db);
|
|
191
|
+
}
|
|
192
|
+
// Deleting is precision-first: only the strict all-token match, never the fuzzy
|
|
193
|
+
// fallback. A wrong read costs the model a retry, a wrong delete is unrecoverable.
|
|
194
|
+
const match = strictMatch(target.query);
|
|
195
|
+
if (match === null) {
|
|
196
|
+
return 0;
|
|
82
197
|
}
|
|
83
198
|
db.conn.run(`DELETE FROM entries WHERE id IN (
|
|
84
199
|
SELECT e.id FROM entries_fts JOIN entries e ON e.rowid = entries_fts.rowid WHERE entries_fts MATCH ?
|
|
85
|
-
)`, [
|
|
200
|
+
) AND (scope = ? OR scope = 'global')`, [match, scope]);
|
|
201
|
+
return changesOf(db);
|
|
202
|
+
}
|
|
203
|
+
function changesOf(db) {
|
|
86
204
|
return db.conn.get("SELECT changes() AS changes")?.changes ?? 0;
|
|
87
205
|
}
|
|
88
206
|
function rowToEntry(row) {
|
|
@@ -107,13 +225,31 @@ function parseTags(raw) {
|
|
|
107
225
|
return [];
|
|
108
226
|
}
|
|
109
227
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
228
|
+
/**
|
|
229
|
+
* FTS5 candidates, most precise first, each quoted so arbitrary model input stays a
|
|
230
|
+
* literal search. The strict AND match is tried before the looser prefix OR match:
|
|
231
|
+
* recall matters more than precision once the strict match found nothing.
|
|
232
|
+
*/
|
|
233
|
+
function searchCandidates(raw) {
|
|
234
|
+
const strict = strictMatch(raw);
|
|
235
|
+
const loose = prefixMatch(raw);
|
|
236
|
+
return [strict, loose].filter((candidate) => candidate !== null);
|
|
237
|
+
}
|
|
238
|
+
/** Every token must appear. Used alone by destructive paths, where a false positive
|
|
239
|
+
* cannot be undone. */
|
|
240
|
+
function strictMatch(raw) {
|
|
241
|
+
const tokens = tokenize(raw);
|
|
242
|
+
return tokens.length === 0 ? null : tokens.map((token) => `"${token}"`).join(" ");
|
|
243
|
+
}
|
|
244
|
+
/** Any token may match, each as a prefix. Read-only recall only. */
|
|
245
|
+
function prefixMatch(raw) {
|
|
246
|
+
const tokens = tokenize(raw);
|
|
247
|
+
return tokens.length === 0 ? null : tokens.map((token) => `"${token}" *`).join(" OR ");
|
|
248
|
+
}
|
|
249
|
+
function tokenize(raw) {
|
|
113
250
|
return raw
|
|
114
251
|
.split(/\s+/)
|
|
115
252
|
.filter(Boolean)
|
|
116
|
-
.map((token) =>
|
|
117
|
-
.join(" ");
|
|
253
|
+
.map((token) => token.replaceAll('"', ""));
|
|
118
254
|
}
|
|
119
255
|
//# sourceMappingURL=store.js.map
|
package/dist/lib/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,QAAQ,GAAG,IAAI,CAAC;AACtB,MAAM,QAAQ,GAAG,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-v2.d.ts","sourceRoot":"","sources":["../src/plugin-v2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAmB1C,eAAO,MAAM,QAAQ,eA4InB,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
import { openMemoryDb } from "./lib/db.js";
|
|
3
|
+
import { TOOL_DESCRIPTIONS, createMemoryOps, memoryInventory, oncePerSession, } from "./lib/memory-ops.js";
|
|
4
|
+
import { SYSTEM_GUIDANCE, COMPACTION_CONTEXT, defaultScope, defaultDbPath, } from "./plugin.js";
|
|
5
|
+
const textPart = (text) => ({ type: "text", text });
|
|
6
|
+
export const MemoryV2 = Plugin.define({
|
|
7
|
+
id: "@log0u7/opencode-memory",
|
|
8
|
+
async setup(ctx) {
|
|
9
|
+
const options = ctx.options;
|
|
10
|
+
const dbPath = defaultDbPath(options);
|
|
11
|
+
let db = null;
|
|
12
|
+
const getDb = () => (db ??= openMemoryDb(dbPath));
|
|
13
|
+
const ops = createMemoryOps(getDb);
|
|
14
|
+
// Request and compaction hooks carry no directory: the instance scope is the project.
|
|
15
|
+
const projectScope = defaultScope(undefined, ctx.location.directory);
|
|
16
|
+
const injected = oncePerSession();
|
|
17
|
+
await ctx.tool.transform((editor) => {
|
|
18
|
+
editor.add({
|
|
19
|
+
name: "memory_save",
|
|
20
|
+
description: TOOL_DESCRIPTIONS.save,
|
|
21
|
+
input: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
kind: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Entry type, e.g. decision, fact, preference, gotcha",
|
|
27
|
+
},
|
|
28
|
+
title: { type: "string", description: "Short title (max 120 chars)" },
|
|
29
|
+
body: { type: "string", description: "Entry content (max 8000 chars)" },
|
|
30
|
+
scope: { type: "string", description: "Scope key; defaults to the project directory" },
|
|
31
|
+
tags: { type: "array", items: { type: "string" }, description: "Optional tags" },
|
|
32
|
+
expires_at: { type: "number", description: "Optional epoch ms expiry" },
|
|
33
|
+
},
|
|
34
|
+
required: ["kind", "title", "body"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
async execute(args) {
|
|
38
|
+
const a = args;
|
|
39
|
+
return {
|
|
40
|
+
content: ops.save(a, defaultScope(undefined, a.scope ?? ctx.location.directory)),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
editor.add({
|
|
45
|
+
name: "memory_search",
|
|
46
|
+
description: TOOL_DESCRIPTIONS.search,
|
|
47
|
+
input: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
query: { type: "string", description: "Free-text query" },
|
|
51
|
+
scope: { type: "string", description: "Scope key; defaults to the project directory" },
|
|
52
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
53
|
+
},
|
|
54
|
+
required: ["query"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
async execute(args) {
|
|
58
|
+
const a = args;
|
|
59
|
+
return {
|
|
60
|
+
content: ops.search({ query: a.query, ...(a.limit !== undefined ? { limit: a.limit } : {}) }, defaultScope(undefined, a.scope ?? ctx.location.directory)),
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
editor.add({
|
|
65
|
+
name: "memory_list",
|
|
66
|
+
description: TOOL_DESCRIPTIONS.list,
|
|
67
|
+
input: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
scope: { type: "string", description: "Scope key; defaults to the project directory" },
|
|
71
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
72
|
+
},
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
},
|
|
75
|
+
async execute(args) {
|
|
76
|
+
const a = args;
|
|
77
|
+
return {
|
|
78
|
+
content: ops.list(a.limit !== undefined ? { limit: a.limit } : {}, defaultScope(undefined, a.scope ?? ctx.location.directory)),
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
editor.add({
|
|
83
|
+
name: "memory_forget",
|
|
84
|
+
description: TOOL_DESCRIPTIONS.forget,
|
|
85
|
+
input: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
id: { type: "string", description: "Entry id to delete" },
|
|
89
|
+
query: { type: "string", description: "Delete every entry matching this query" },
|
|
90
|
+
},
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
},
|
|
93
|
+
async execute(args) {
|
|
94
|
+
const a = args;
|
|
95
|
+
return { content: ops.forget(a, defaultScope(undefined, ctx.location.directory)) };
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
// The v1 system transform maps to the v2 context hook: guidance first, then the
|
|
100
|
+
// once-per-session inventory.
|
|
101
|
+
await ctx.session.hook("context", (event) => {
|
|
102
|
+
event.system.push(textPart(SYSTEM_GUIDANCE));
|
|
103
|
+
const inventory = injected.inject(event.sessionID, () => memoryInventory(getDb, dbPath, projectScope));
|
|
104
|
+
if (inventory !== null) {
|
|
105
|
+
event.system.push(textPart(inventory));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// experimental.session.compacting -> compaction hook
|
|
109
|
+
await ctx.session.hook("compaction", (event) => {
|
|
110
|
+
event.system.push(textPart(COMPACTION_CONTEXT));
|
|
111
|
+
const inventory = memoryInventory(getDb, dbPath, projectScope);
|
|
112
|
+
if (inventory !== null) {
|
|
113
|
+
event.system.push(textPart(inventory));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const cleanup = async () => {
|
|
117
|
+
injected.reset();
|
|
118
|
+
db?.close();
|
|
119
|
+
db = null;
|
|
120
|
+
};
|
|
121
|
+
return cleanup;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=plugin-v2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-v2.js","sourceRoot":"","sources":["../src/plugin-v2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,aAAa,GAEd,MAAM,aAAa,CAAC;AAErB,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,CAAC;AAErE,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,EAAE,EAAE,yBAAyB;IAC7B,KAAK,CAAC,KAAK,CAAC,GAAG;QACb,MAAM,OAAO,GAAG,GAAG,CAAC,OAAwB,CAAC;QAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,EAAE,GAAoB,IAAI,CAAC;QAC/B,MAAM,KAAK,GAAG,GAAa,EAAE,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAEnC,sFAAsF;QACtF,MAAM,YAAY,GAAG,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAElC,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,CAAC;gBACT,IAAI,EAAE,aAAa;gBACnB,WAAW,EAAE,iBAAiB,CAAC,IAAI;gBACnC,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,qDAAqD;yBACnE;wBACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6BAA6B,EAAE;wBACrE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;wBACvE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8CAA8C,EAAE;wBACtF,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE;wBAChF,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;qBACxE;oBACD,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;oBACnC,oBAAoB,EAAE,KAAK;iBAC5B;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI;oBAChB,MAAM,CAAC,GAAG,IAOT,CAAC;oBACF,OAAO;wBACL,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;qBACjF,CAAC;gBACJ,CAAC;aACF,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC;gBACT,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE,iBAAiB,CAAC,MAAM;gBACrC,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;wBACzD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8CAA8C,EAAE;wBACtF,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;qBACnE;oBACD,QAAQ,EAAE,CAAC,OAAO,CAAC;oBACnB,oBAAoB,EAAE,KAAK;iBAC5B;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI;oBAChB,MAAM,CAAC,GAAG,IAAyD,CAAC;oBACpE,OAAO;wBACL,OAAO,EAAE,GAAG,CAAC,MAAM,CACjB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EACxE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC3D;qBACF,CAAC;gBACJ,CAAC;aACF,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC;gBACT,IAAI,EAAE,aAAa;gBACnB,WAAW,EAAE,iBAAiB,CAAC,IAAI;gBACnC,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8CAA8C,EAAE;wBACtF,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;qBACnE;oBACD,oBAAoB,EAAE,KAAK;iBAC5B;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI;oBAChB,MAAM,CAAC,GAAG,IAA0C,CAAC;oBACrD,OAAO;wBACL,OAAO,EAAE,GAAG,CAAC,IAAI,CACf,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAC/C,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC3D;qBACF,CAAC;gBACJ,CAAC;aACF,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC;gBACT,IAAI,EAAE,eAAe;gBACrB,WAAW,EAAE,iBAAiB,CAAC,MAAM;gBACrC,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;wBACzD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wCAAwC,EAAE;qBACjF;oBACD,oBAAoB,EAAE,KAAK;iBAC5B;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI;oBAChB,MAAM,CAAC,GAAG,IAAuC,CAAC;oBAClD,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;gBACrF,CAAC;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,gFAAgF;QAChF,8BAA8B;QAC9B,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1C,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;YAC7C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,CACtD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAC7C,CAAC;YACF,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,qDAAqD;QACrD,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE;YAC7C,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAChD,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAC/D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;YACzB,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,EAAE,EAAE,KAAK,EAAE,CAAC;YACZ,EAAE,GAAG,IAAI,CAAC;QACZ,CAAC,CAAC;QACF,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAC,CAAC"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
export type PluginOptions = {
|
|
3
|
+
dbPath?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const SYSTEM_GUIDANCE = "You have persistent working memory shared with all OpenCode sessions on this machine.\nRecent entries are listed above when present; run memory_search for anything older or more specific.\nWhen you make a durable decision or learn a lasting fact, call memory_save (kinds: decision, fact, preference, gotcha).\nNever store credentials, tokens, or API keys in memory; the store refuses them.";
|
|
6
|
+
export declare const COMPACTION_CONTEXT: string;
|
|
7
|
+
export declare function defaultDbPath(options?: PluginOptions): string;
|
|
8
|
+
/**
|
|
9
|
+
* Scope of a session: the v1 tool context carries a worktree, so that is the canonical
|
|
10
|
+
* key. `directory` is the fallback so the same project does not get two scopes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function defaultScope(worktree?: string, directory?: string): string;
|
|
2
13
|
export declare const MemoryPlugin: Plugin;
|
|
3
14
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAYzD,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,eAAe,0YAGoD,CAAC;AAEjF,eAAO,MAAM,kBAAkB,QAEoE,CAAC;AAEpG,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAM7D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,eAAO,MAAM,YAAY,EAAE,MAmG1B,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
import { openMemoryDb } from "./lib/db.js";
|
|
2
|
-
import { forget, list, save, search, SecretDetectedError } from "./lib/store.js";
|
|
3
1
|
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { openMemoryDb } from "./lib/db.js";
|
|
3
|
+
import { TOOL_DESCRIPTIONS, createMemoryOps, memoryInventory, oncePerSession, } from "./lib/memory-ops.js";
|
|
4
|
+
import { canonicalScope } from "./lib/store.js";
|
|
5
|
+
export const SYSTEM_GUIDANCE = `You have persistent working memory shared with all OpenCode sessions on this machine.
|
|
6
|
+
Recent entries are listed above when present; run memory_search for anything older or more specific.
|
|
6
7
|
When you make a durable decision or learn a lasting fact, call memory_save (kinds: decision, fact, preference, gotcha).
|
|
7
8
|
Never store credentials, tokens, or API keys in memory; the store refuses them.`;
|
|
8
|
-
const COMPACTION_CONTEXT = "The session is being compacted. After compaction, call memory_save for every durable decision still relevant " +
|
|
9
|
+
export const COMPACTION_CONTEXT = "The session is being compacted. After compaction, call memory_save for every durable decision still relevant " +
|
|
9
10
|
"(kind=decision) and any newly learned facts (kind=fact), so other sessions keep this knowledge.";
|
|
10
|
-
function defaultDbPath(options) {
|
|
11
|
+
export function defaultDbPath(options) {
|
|
11
12
|
if (typeof options?.dbPath === "string" && options.dbPath.length > 0) {
|
|
12
13
|
return options.dbPath;
|
|
13
14
|
}
|
|
14
15
|
const dataHome = process.env.XDG_DATA_HOME ?? `${process.env.HOME ?? ""}/.local/share`;
|
|
15
16
|
return `${dataHome}/opencode-memory/memory.db`;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Scope of a session: the v1 tool context carries a worktree, so that is the canonical
|
|
20
|
+
* key. `directory` is the fallback so the same project does not get two scopes.
|
|
21
|
+
*/
|
|
22
|
+
export function defaultScope(worktree, directory) {
|
|
23
|
+
return canonicalScope(worktree, directory);
|
|
24
|
+
}
|
|
25
|
+
export const MemoryPlugin = async (input, options) => {
|
|
26
|
+
const dbPath = defaultDbPath(options);
|
|
18
27
|
let db = null;
|
|
19
|
-
const getDb = () =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
28
|
+
const getDb = () => (db ??= openMemoryDb(dbPath));
|
|
29
|
+
const ops = createMemoryOps(getDb);
|
|
30
|
+
// Request and compaction hooks carry no directory: the instance scope is the project.
|
|
31
|
+
const projectScope = defaultScope(input.worktree, input.directory);
|
|
32
|
+
const injected = oncePerSession();
|
|
25
33
|
const hooks = {
|
|
26
34
|
tool: {
|
|
27
35
|
memory_save: tool({
|
|
28
|
-
description:
|
|
36
|
+
description: TOOL_DESCRIPTIONS.save,
|
|
29
37
|
args: {
|
|
30
38
|
kind: tool.schema
|
|
31
39
|
.string()
|
|
@@ -39,20 +47,10 @@ export const MemoryPlugin = async (_input, options) => {
|
|
|
39
47
|
tags: tool.schema.array(tool.schema.string()).optional().describe("Optional tags"),
|
|
40
48
|
expires_at: tool.schema.number().optional().describe("Optional epoch ms expiry"),
|
|
41
49
|
},
|
|
42
|
-
execute: (args, context) =>
|
|
43
|
-
const entry = save(getDb(), {
|
|
44
|
-
scope: args.scope ?? context.worktree,
|
|
45
|
-
kind: args.kind,
|
|
46
|
-
title: args.title,
|
|
47
|
-
body: args.body,
|
|
48
|
-
tags: args.tags ?? [],
|
|
49
|
-
expires_at: args.expires_at ?? null,
|
|
50
|
-
});
|
|
51
|
-
return `saved ${entry.id} (${entry.scope})`;
|
|
52
|
-
}),
|
|
50
|
+
execute: async (args, context) => ops.save(args, defaultScope(args.scope ?? context.worktree ?? context.directory)),
|
|
53
51
|
}),
|
|
54
52
|
memory_search: tool({
|
|
55
|
-
description:
|
|
53
|
+
description: TOOL_DESCRIPTIONS.search,
|
|
56
54
|
args: {
|
|
57
55
|
query: tool.schema.string().describe("Free-text query"),
|
|
58
56
|
scope: tool.schema
|
|
@@ -61,20 +59,10 @@ export const MemoryPlugin = async (_input, options) => {
|
|
|
61
59
|
.describe("Scope key; defaults to this session's worktree"),
|
|
62
60
|
limit: tool.schema.number().optional().describe("Max results (default 10)"),
|
|
63
61
|
},
|
|
64
|
-
execute: (args, context) =>
|
|
65
|
-
const hits = search(getDb(), {
|
|
66
|
-
query: args.query,
|
|
67
|
-
scope: args.scope ?? context.worktree,
|
|
68
|
-
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
69
|
-
});
|
|
70
|
-
if (hits.length === 0) {
|
|
71
|
-
return "no matching memory entries";
|
|
72
|
-
}
|
|
73
|
-
return JSON.stringify(hits, null, 2);
|
|
74
|
-
}),
|
|
62
|
+
execute: async (args, context) => ops.search({ query: args.query, ...(args.limit !== undefined ? { limit: args.limit } : {}) }, defaultScope(args.scope ?? context.worktree ?? context.directory)),
|
|
75
63
|
}),
|
|
76
64
|
memory_list: tool({
|
|
77
|
-
description:
|
|
65
|
+
description: TOOL_DESCRIPTIONS.list,
|
|
78
66
|
args: {
|
|
79
67
|
scope: tool.schema
|
|
80
68
|
.string()
|
|
@@ -82,65 +70,39 @@ export const MemoryPlugin = async (_input, options) => {
|
|
|
82
70
|
.describe("Scope key; defaults to this session's worktree"),
|
|
83
71
|
limit: tool.schema.number().optional().describe("Max results (default 20)"),
|
|
84
72
|
},
|
|
85
|
-
execute: (args, context) =>
|
|
86
|
-
const entries = list(getDb(), {
|
|
87
|
-
scope: args.scope ?? context.worktree,
|
|
88
|
-
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
89
|
-
});
|
|
90
|
-
if (entries.length === 0) {
|
|
91
|
-
return "no memory entries";
|
|
92
|
-
}
|
|
93
|
-
return JSON.stringify(entries, null, 2);
|
|
94
|
-
}),
|
|
73
|
+
execute: async (args, context) => ops.list(args.limit !== undefined ? { limit: args.limit } : {}, defaultScope(args.scope ?? context.worktree ?? context.directory)),
|
|
95
74
|
}),
|
|
96
75
|
memory_forget: tool({
|
|
97
|
-
description:
|
|
76
|
+
description: TOOL_DESCRIPTIONS.forget,
|
|
98
77
|
args: {
|
|
99
78
|
id: tool.schema.string().optional().describe("Entry id to delete"),
|
|
100
|
-
query: tool.schema.string().optional().describe("Delete every entry matching
|
|
79
|
+
query: tool.schema.string().optional().describe("Delete every entry matching a query"),
|
|
101
80
|
},
|
|
102
|
-
execute: (args,
|
|
103
|
-
if (args.id !== undefined) {
|
|
104
|
-
const deleted = forget(getDb(), { id: args.id });
|
|
105
|
-
return `deleted ${deleted} entr${deleted === 1 ? "y" : "ies"}`;
|
|
106
|
-
}
|
|
107
|
-
if (args.query !== undefined) {
|
|
108
|
-
const deleted = forget(getDb(), { query: args.query });
|
|
109
|
-
return `deleted ${deleted} entr${deleted === 1 ? "y" : "ies"}`;
|
|
110
|
-
}
|
|
111
|
-
return "provide id or query";
|
|
112
|
-
}),
|
|
81
|
+
execute: async (args, context) => ops.forget(args, defaultScope(context.worktree, context.directory)),
|
|
113
82
|
}),
|
|
114
83
|
},
|
|
115
84
|
};
|
|
116
85
|
return {
|
|
117
86
|
...hooks,
|
|
118
|
-
"experimental.chat.system.transform": async (
|
|
87
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
119
88
|
output.system.push(SYSTEM_GUIDANCE);
|
|
89
|
+
const inventory = injected.inject(input.sessionID, () => memoryInventory(getDb, dbPath, projectScope));
|
|
90
|
+
if (inventory !== null) {
|
|
91
|
+
output.system.push(inventory);
|
|
92
|
+
}
|
|
120
93
|
},
|
|
121
94
|
"experimental.session.compacting": async (_input, output) => {
|
|
122
95
|
output.context.push(COMPACTION_CONTEXT);
|
|
96
|
+
const inventory = memoryInventory(getDb, dbPath, projectScope);
|
|
97
|
+
if (inventory !== null) {
|
|
98
|
+
output.context.push(inventory);
|
|
99
|
+
}
|
|
123
100
|
},
|
|
124
101
|
dispose: async () => {
|
|
102
|
+
injected.reset();
|
|
125
103
|
db?.close();
|
|
126
104
|
db = null;
|
|
127
105
|
},
|
|
128
106
|
};
|
|
129
107
|
};
|
|
130
|
-
// Tool results are shown to the model: caught errors become readable text, so
|
|
131
|
-
// a rejected save is guidance instead of a crashed tool call.
|
|
132
|
-
async function run(fn) {
|
|
133
|
-
try {
|
|
134
|
-
return fn();
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
if (error instanceof SecretDetectedError) {
|
|
138
|
-
return "refused: the content looks like a credential; secrets must never be stored in memory";
|
|
139
|
-
}
|
|
140
|
-
if (error instanceof Error) {
|
|
141
|
-
return `error: ${error.message}`;
|
|
142
|
-
}
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
108
|
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@log0u7/opencode-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Working memory for OpenCode: shared SQLite store with hybrid search, shared across all sessions on a machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,11 +33,12 @@
|
|
|
33
33
|
"node": ">=22.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@biomejs/biome": "2.5.
|
|
37
|
-
"@opencode-ai/plugin": "1.18.
|
|
38
|
-
"@
|
|
36
|
+
"@biomejs/biome": "2.5.14",
|
|
37
|
+
"@opencode-ai/plugin": "1.18.31",
|
|
38
|
+
"@opencode/plugin": "2.0.26",
|
|
39
|
+
"@types/node": "^26.6.2",
|
|
39
40
|
"@vitest/coverage-v8": "^5.0.1",
|
|
40
|
-
"lefthook": "2.1.
|
|
41
|
+
"lefthook": "2.1.14",
|
|
41
42
|
"typescript": "7.0.2",
|
|
42
43
|
"vitest": "^5.0.0"
|
|
43
44
|
},
|