plugmem 0.3.0 → 0.4.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 +87 -16
- package/index.d.ts +718 -0
- package/index.js +323 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -44,14 +44,16 @@ the CLI.**
|
|
|
44
44
|
## Usage (TypeScript)
|
|
45
45
|
|
|
46
46
|
Every argument and result is typed — napi generates `index.d.ts`, so a TS host
|
|
47
|
-
gets full autocomplete and checking
|
|
47
|
+
gets full autocomplete and checking. A memory is opened with the static
|
|
48
|
+
`Plugmem.open`, not `new`: opening replays a journal and maps a snapshot, and a
|
|
49
|
+
JavaScript constructor has no way to hand that to a worker thread.
|
|
48
50
|
|
|
49
51
|
```typescript
|
|
50
52
|
import { Plugmem } from "plugmem";
|
|
51
53
|
|
|
52
|
-
const db =
|
|
54
|
+
const db = await Plugmem.open("agent.plugmem"); // or { readOnly: true }
|
|
53
55
|
|
|
54
|
-
const out = db.remember({
|
|
56
|
+
const out = await db.remember({
|
|
55
57
|
text: "prefers tokio",
|
|
56
58
|
entity: "user",
|
|
57
59
|
tags: ["pref"],
|
|
@@ -61,15 +63,15 @@ const out = db.remember({
|
|
|
61
63
|
out.id; // number
|
|
62
64
|
out.similar; // Similar[] — the engine surfaces conflicts, you decide
|
|
63
65
|
|
|
64
|
-
const res = db.recall({ query: "runtime?", k: 5 });
|
|
66
|
+
const res = await db.recall({ query: "runtime?", k: 5 });
|
|
65
67
|
res.rendered; // the prompt-ready block
|
|
66
68
|
res.facts; // RecalledFact[] — { id, score, entity, recordedAt, … }
|
|
67
69
|
|
|
68
70
|
const card = db.get(out.id);
|
|
69
71
|
card.metadata; // Record<string,string> — keys sorted, {} when none
|
|
70
72
|
|
|
71
|
-
db.revise(out.id, { text: "prefers async-std" });
|
|
72
|
-
db.link({ src: "user", rel: "works_at", dst: "acme" });
|
|
73
|
+
await db.revise(out.id, { text: "prefers async-std" });
|
|
74
|
+
await db.link({ src: "user", rel: "works_at", dst: "acme" });
|
|
73
75
|
db.unlink({ src: "user", rel: "works_at", dst: "acme" }); // closes the current edge
|
|
74
76
|
|
|
75
77
|
await db.checkpoint(); // async (see below)
|
|
@@ -86,7 +88,7 @@ data directory. See the [full settings reference](https://github.com/m62624/plug
|
|
|
86
88
|
for all fields and OS-specific paths.
|
|
87
89
|
|
|
88
90
|
```typescript
|
|
89
|
-
const db =
|
|
91
|
+
const db = await Plugmem.open(undefined, { config: "./plugmem.toml" });
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
```toml
|
|
@@ -108,7 +110,10 @@ provider's HTTP call runs outside the engine lock. Without one, there is no
|
|
|
108
110
|
embedder and vector recall is skipped (lexical, tag, graph and time recall still
|
|
109
111
|
answer). The optional `dim` open option sets the embedding size when there is no
|
|
110
112
|
config; if the config configured an embedder, its dimension governs and `dim`
|
|
111
|
-
must agree. A `{ readOnly: true }` handle
|
|
113
|
+
must agree. A `{ readOnly: true }` handle cannot auto-embed inside the engine —
|
|
114
|
+
embedding into a zero-copy mapping is what read-only exists to avoid — so this
|
|
115
|
+
binding embeds the query itself before the read, exactly as the CLI and the MCP
|
|
116
|
+
server do. A text `recall` therefore reaches the vector source in both modes.
|
|
112
117
|
|
|
113
118
|
## The verbs
|
|
114
119
|
|
|
@@ -122,6 +127,37 @@ engine logic is entirely the host's.
|
|
|
122
127
|
`recall`, `get`, `tagsOf`, `stats`, `export`, `exportPage(cursor?)`, `verify`,
|
|
123
128
|
plus `generation()` (the pinned snapshot generation) and `refresh()` (advance
|
|
124
129
|
to the writer's latest checkpoint); the write verbs throw.
|
|
130
|
+
**Both**: `path()` — the file the handle resolved to, which is the only way to
|
|
131
|
+
learn it when the constructor was given no path.
|
|
132
|
+
|
|
133
|
+
### Errors
|
|
134
|
+
|
|
135
|
+
Every failure plugmem itself decides carries a stable `code`, so a program
|
|
136
|
+
branches on it instead of on wording:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
try {
|
|
140
|
+
db = await Plugmem.open("agent.plugmem");
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err.code === "PLUGMEM_LOCKED") retryLater();
|
|
143
|
+
else throw err;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`PLUGMEM_LOCKED`, `PLUGMEM_NEEDS_CHECKPOINT`, `PLUGMEM_CONFIG` and
|
|
148
|
+
`PLUGMEM_OPEN` come from opening; `PLUGMEM_INVALID_ARG` and
|
|
149
|
+
`PLUGMEM_INVALID_NAME` from an argument that was refused; `PLUGMEM_CLOSED`,
|
|
150
|
+
`PLUGMEM_READ_ONLY`, `PLUGMEM_WRITER_ONLY` and `PLUGMEM_BUSY` from calling a
|
|
151
|
+
verb the handle cannot serve; `PLUGMEM_ENGINE` from the engine itself, carrying
|
|
152
|
+
the host's own message.
|
|
153
|
+
|
|
154
|
+
The code is there whether the verb threw or the promise rejected — the two are
|
|
155
|
+
the same contract, so nothing has to be handled twice.
|
|
156
|
+
|
|
157
|
+
An argument that shapes an answer is refused rather than dropped: `range` must
|
|
158
|
+
be exactly `[from, to]`, and `range`, `asOf` and `validFrom` must each be a
|
|
159
|
+
finite, non-negative instant. Silently ignoring one produced an answer computed
|
|
160
|
+
without it — indistinguishable from a correct one.
|
|
125
161
|
|
|
126
162
|
### What the host has and this does not
|
|
127
163
|
|
|
@@ -141,7 +177,7 @@ when it already owns the input records.
|
|
|
141
177
|
|
|
142
178
|
## Many memories in one directory (optional)
|
|
143
179
|
|
|
144
|
-
**Default: one memory, one file.** `
|
|
180
|
+
**Default: one memory, one file.** `Plugmem.open(path)` and nothing here applies.
|
|
145
181
|
|
|
146
182
|
A process that serves many independent memories — one per conversation, per
|
|
147
183
|
tenant, per project — can point at a directory and address them by name:
|
|
@@ -153,12 +189,12 @@ const ws = new Workspace("/srv/memories", { maxOpen: 16, idleTimeoutMs: 60_000 }
|
|
|
153
189
|
|
|
154
190
|
// `open` hands back the same `Plugmem` class, so a named memory has exactly the
|
|
155
191
|
// verbs a path-opened one has. A first write to an unused name creates it.
|
|
156
|
-
ws.open("chat-42").remember({ text: "prefers tokio" });
|
|
192
|
+
await (await ws.open("chat-42")).remember({ text: "prefers tokio" });
|
|
157
193
|
|
|
158
194
|
// Do not know the name? Ask what each memory is for. Owners are searchable too,
|
|
159
195
|
// even though an owner is a graph edge rather than text.
|
|
160
|
-
ws.describe("chat-42", { description: "release planning", owner: "ann" });
|
|
161
|
-
const hits: DbEntry[] = ws.find("release planning");
|
|
196
|
+
await ws.describe("chat-42", { description: "release planning", owner: "ann" });
|
|
197
|
+
const hits: DbEntry[] = await ws.find("release planning"); // → [{ db: "chat-42", … }]
|
|
162
198
|
```
|
|
163
199
|
|
|
164
200
|
A name is `[a-z0-9][a-z0-9_-]*` and **cannot represent a path**, so it resolves
|
|
@@ -178,6 +214,11 @@ wrong one is unreachable from the other rather than merely misplaced. And **who
|
|
|
178
214
|
may reach which memory is not this package's responsibility** — the name comes
|
|
179
215
|
from your code, so put the policy there.
|
|
180
216
|
|
|
217
|
+
Every `Workspace` verb that touches a database returns a promise — `open`,
|
|
218
|
+
`list`, `entries`, `find`, `describe`, `archive`, `reindex`, `verify` — because
|
|
219
|
+
the registry is itself a plugmem memory and a named memory is a real file being
|
|
220
|
+
opened. `closeIdle()` and `openCount()` stay synchronous.
|
|
221
|
+
|
|
181
222
|
`reindex()` and `verify()` return promises: they open and read every memory in
|
|
182
223
|
the directory, which is not work for the main thread.
|
|
183
224
|
|
|
@@ -216,10 +257,40 @@ a no-op report when there is nothing to purge, reindex or optimize.
|
|
|
216
257
|
version; the heavier ones buy bytes and index freshness. `"full"` is the only
|
|
217
258
|
one that repacks the edge arenas, which a relink-heavy workload fragments.
|
|
218
259
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
260
|
+
### What is async, and why
|
|
261
|
+
|
|
262
|
+
`remember`, `revise`, `recall`, `rememberMany`, `exportPage`, `maintain` and
|
|
263
|
+
`checkpoint` return promises. Everything else — `get`, `stats`, `tagsOf`,
|
|
264
|
+
`forget`, `link`, `unlink`, `verify`, `export`, `path`, `generation`,
|
|
265
|
+
`refresh` — is synchronous.
|
|
266
|
+
|
|
267
|
+
The line is drawn at blocking work. Node runs all JavaScript on **one** thread,
|
|
268
|
+
so a native call that waits on an embedder's HTTP round trip or on an fsync
|
|
269
|
+
freezes every timer, socket and callback in the process for as long as it takes.
|
|
270
|
+
The verbs above can do exactly that — with an `[embedder]` configured,
|
|
271
|
+
`remember` and a text `recall` each cost a request to the provider — so they run
|
|
272
|
+
on a libuv worker and hand JavaScript a promise. The rest touch only mapped
|
|
273
|
+
memory and return in microseconds, where a promise would be pure ceremony.
|
|
274
|
+
|
|
275
|
+
Arguments are still checked on your thread: a refused one **throws** at the call
|
|
276
|
+
site rather than rejecting later, so a mistake in your code and a failure in the
|
|
277
|
+
engine never arrive the same way.
|
|
278
|
+
|
|
279
|
+
### Bringing your own embedding
|
|
280
|
+
|
|
281
|
+
`remember`, `revise`, `rememberMany` and `recall` all take an optional
|
|
282
|
+
`vector` — a precomputed embedding whose length must equal the configured `dim`:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const own = await myEmbedder(text); // your model, your pipeline
|
|
286
|
+
await db.remember({ text, vector: own }); // nothing is sent to `[embedder]`
|
|
287
|
+
const res = await db.recall({ query: text, vector: own });
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Given one, it **replaces** the embedder for that call — the engine embeds only
|
|
291
|
+
when the field is absent. Use it for vectors you already have, for a model that
|
|
292
|
+
is not an OpenAI-shaped HTTP endpoint, or for a deterministic test with no
|
|
293
|
+
network. The CLI (`--vector`) and the MCP tools (`vector`) take the same thing.
|
|
223
294
|
|
|
224
295
|
`close()` releases the file and its lock; every verb afterwards throws, and it is
|
|
225
296
|
idempotent (the handle is also released on garbage collection, but `close()`
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
/** Options for [`Plugmem::new`]. */
|
|
7
|
+
export interface OpenOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Embedding dimension (`Config::dim`). Omit or 0 to keep vectors off. When
|
|
10
|
+
* the config file configures an `[embedder]`, that embedder's dimension is
|
|
11
|
+
* authoritative and this — if given — must agree with it.
|
|
12
|
+
*/
|
|
13
|
+
dim?: number
|
|
14
|
+
/**
|
|
15
|
+
* Open read-only over another process's writer (requires a checkpointed
|
|
16
|
+
* database). The write verbs then throw; `generation`/`refresh` appear.
|
|
17
|
+
*
|
|
18
|
+
* A text `recall` still reaches the vector source: the engine cannot embed
|
|
19
|
+
* on a read-only handle (replaying into it would defeat the zero-copy
|
|
20
|
+
* open), so this binding embeds the query itself before asking — the same
|
|
21
|
+
* thing the CLI and the MCP server do for their read-only paths.
|
|
22
|
+
*/
|
|
23
|
+
readOnly?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* Path to a `config.toml` (`[database]` / `[engine]` / `[embedder]` /
|
|
26
|
+
* `[maintenance]`). When the constructor path is omitted, `[database].path`
|
|
27
|
+
* participates in database-path resolution.
|
|
28
|
+
* Omitted, the standard discovery applies — `$PLUGMEM_CONFIG`, then
|
|
29
|
+
* `$XDG_CONFIG_HOME/plugmem/config.toml` — exactly as the CLI and MCP
|
|
30
|
+
* server resolve it. The `[embedder]` section is what makes a text-only
|
|
31
|
+
* `remember`/`recall` auto-embed; with no config there is no embedder
|
|
32
|
+
* (lexical, tag, graph and time recall still answer).
|
|
33
|
+
*/
|
|
34
|
+
config?: string
|
|
35
|
+
}
|
|
36
|
+
/** One typed edge on a remembered fact: `entity` gains relation `rel`. */
|
|
37
|
+
export interface LinkRef {
|
|
38
|
+
/** The relation name. */
|
|
39
|
+
rel: string
|
|
40
|
+
/** The target entity name. */
|
|
41
|
+
entity: string
|
|
42
|
+
}
|
|
43
|
+
/** Arguments for [`Plugmem::remember`] / [`Plugmem::revise`]. */
|
|
44
|
+
export interface RememberArgs {
|
|
45
|
+
/** The fact text (required). */
|
|
46
|
+
text: string
|
|
47
|
+
/** Subject entity name. */
|
|
48
|
+
entity?: string
|
|
49
|
+
/** Tag strings. */
|
|
50
|
+
tags?: Array<string>
|
|
51
|
+
/** Typed edges to attach. */
|
|
52
|
+
links?: Array<LinkRef>
|
|
53
|
+
/**
|
|
54
|
+
* Opaque metadata as a key→value map (a URI to the real payload, a mime
|
|
55
|
+
* type, an external key). The engine never interprets it.
|
|
56
|
+
*/
|
|
57
|
+
metadata?: Record<string, string>
|
|
58
|
+
/** Validity start, unix milliseconds (default: the fact's record time). */
|
|
59
|
+
validFrom?: number
|
|
60
|
+
/**
|
|
61
|
+
* A precomputed embedding. Its length must equal the configured `dim`.
|
|
62
|
+
*
|
|
63
|
+
* Given, it **replaces** the embedder: nothing is sent to the provider.
|
|
64
|
+
* That is the host's own precedence — it embeds only when this is absent —
|
|
65
|
+
* and it is the route for a vector you already have, or for a model that
|
|
66
|
+
* is not an OpenAI-shaped HTTP endpoint.
|
|
67
|
+
*/
|
|
68
|
+
vector?: Array<number>
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Arguments for [`Plugmem::recall`] — every field optional; lexical/tag/graph/
|
|
72
|
+
* time still answer with no query vector.
|
|
73
|
+
*/
|
|
74
|
+
export interface RecallArgs {
|
|
75
|
+
/** Free-text query (embedded by the engine when an embedder is configured). */
|
|
76
|
+
query?: string
|
|
77
|
+
/** Restrict to facts carrying all these tags. */
|
|
78
|
+
tags?: Array<string>
|
|
79
|
+
/** Anchor entities for the graph source. */
|
|
80
|
+
entities?: Array<string>
|
|
81
|
+
/** "What was true at" this instant, unix milliseconds (bitemporal as-of). */
|
|
82
|
+
asOf?: number
|
|
83
|
+
/** Time window `[from, to)` over `recorded_at`, unix milliseconds. */
|
|
84
|
+
range?: Array<number>
|
|
85
|
+
/** Max facts to return (0 = engine default). */
|
|
86
|
+
k?: number
|
|
87
|
+
/** Include closed revisions (default false). */
|
|
88
|
+
closed?: boolean
|
|
89
|
+
/**
|
|
90
|
+
* A precomputed embedding. Its length must equal the configured `dim`.
|
|
91
|
+
*
|
|
92
|
+
* Given, it **replaces** the embedder: nothing is sent to the provider.
|
|
93
|
+
* That is the host's own precedence — it embeds only when this is absent —
|
|
94
|
+
* and it is the route for a vector you already have, or for a model that
|
|
95
|
+
* is not an OpenAI-shaped HTTP endpoint.
|
|
96
|
+
*/
|
|
97
|
+
vector?: Array<number>
|
|
98
|
+
}
|
|
99
|
+
/** Arguments for [`Plugmem::link`]. */
|
|
100
|
+
export interface LinkArgs {
|
|
101
|
+
/** Source entity name. */
|
|
102
|
+
src: string
|
|
103
|
+
/** Relation name. */
|
|
104
|
+
rel: string
|
|
105
|
+
/** Destination entity name. */
|
|
106
|
+
dst: string
|
|
107
|
+
}
|
|
108
|
+
/** One similar / potentially-conflicting live fact surfaced by `remember`. */
|
|
109
|
+
export interface Similar {
|
|
110
|
+
/** The existing fact's id. */
|
|
111
|
+
id: number
|
|
112
|
+
/** Match strength (higher = closer). */
|
|
113
|
+
score: number
|
|
114
|
+
/** What triggered the hint: `"LexicalOverlap"` or `"VectorCosine"`. */
|
|
115
|
+
reason: string
|
|
116
|
+
}
|
|
117
|
+
/** The result of `remember` / `revise`. */
|
|
118
|
+
export interface RememberOutcome {
|
|
119
|
+
/** The new fact's id. */
|
|
120
|
+
id: number
|
|
121
|
+
/** The subject entity id, if one was named. */
|
|
122
|
+
entity?: number
|
|
123
|
+
/**
|
|
124
|
+
* Similar / potentially-conflicting live facts (best first; the engine
|
|
125
|
+
* never merges on its own — the caller decides).
|
|
126
|
+
*/
|
|
127
|
+
similar: Array<Similar>
|
|
128
|
+
}
|
|
129
|
+
/** One recalled fact. */
|
|
130
|
+
export interface RecalledFact {
|
|
131
|
+
/** The fact id. */
|
|
132
|
+
id: number
|
|
133
|
+
/** Fused score (reciprocal-rank fusion + recency). */
|
|
134
|
+
score: number
|
|
135
|
+
/** Bit set of the sources that surfaced it. */
|
|
136
|
+
sources: number
|
|
137
|
+
/** Subject entity id (a sentinel when none). */
|
|
138
|
+
entity: number
|
|
139
|
+
/** Knowledge axis: when the memory learned it (unix ms). */
|
|
140
|
+
recordedAt: number
|
|
141
|
+
/** Truth axis start (unix ms). */
|
|
142
|
+
validFrom: number
|
|
143
|
+
/** Truth axis end (unix ms), or the open sentinel — see the module note. */
|
|
144
|
+
validTo: number
|
|
145
|
+
}
|
|
146
|
+
/** One edge walked by the graph source. */
|
|
147
|
+
export interface RecalledEdge {
|
|
148
|
+
/** Source entity id. */
|
|
149
|
+
src: number
|
|
150
|
+
/** Relation term id. */
|
|
151
|
+
rel: number
|
|
152
|
+
/** Destination entity id. */
|
|
153
|
+
dst: number
|
|
154
|
+
/** Provenance fact id (a sentinel when none). */
|
|
155
|
+
provenance: number
|
|
156
|
+
}
|
|
157
|
+
/** A recall response: the structured hits plus the prompt-ready block. */
|
|
158
|
+
export interface RecallResult {
|
|
159
|
+
/** Selected facts, descending fused score. */
|
|
160
|
+
facts: Array<RecalledFact>
|
|
161
|
+
/** Edges the graph source walked (deduplicated). */
|
|
162
|
+
edges: Array<RecalledEdge>
|
|
163
|
+
/** The compact prompt block (empty when nothing was found). */
|
|
164
|
+
rendered: string
|
|
165
|
+
/** `true` when selection stopped at `k`/the token budget with more left. */
|
|
166
|
+
truncated: boolean
|
|
167
|
+
}
|
|
168
|
+
/** Engine size counters. */
|
|
169
|
+
export interface Stats {
|
|
170
|
+
/** Fact records stored (live, closed and tombstoned-awaiting-maintain). */
|
|
171
|
+
facts: number
|
|
172
|
+
/** Entities. */
|
|
173
|
+
entities: number
|
|
174
|
+
/** Interned terms (tokens, tags, relations, names). */
|
|
175
|
+
terms: number
|
|
176
|
+
/** Directed edges. */
|
|
177
|
+
edges: number
|
|
178
|
+
/** Historical edge versions, including closed versions. */
|
|
179
|
+
edgeVersions: number
|
|
180
|
+
/** Quantized vector slots. */
|
|
181
|
+
vectors: number
|
|
182
|
+
/** Tombstoned fact records awaiting physical purge. */
|
|
183
|
+
tombstones: number
|
|
184
|
+
/** Vector slots covered by HNSW. */
|
|
185
|
+
hnswIndexed: number
|
|
186
|
+
/** The next fact id to be assigned. */
|
|
187
|
+
nextFact: number
|
|
188
|
+
/** The next entity id to be assigned. */
|
|
189
|
+
nextEntity: number
|
|
190
|
+
/** The next edge-version id to be assigned. */
|
|
191
|
+
nextEdge: number
|
|
192
|
+
/** Total bytes held by the engine's pools. */
|
|
193
|
+
poolBytes: number
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The raw record behind a [`FactSnapshot`] — temporality and flags. (Internal
|
|
197
|
+
* pointers — the blob/vector slots and the reserved `kind` — are omitted.)
|
|
198
|
+
*/
|
|
199
|
+
export interface FactRecord {
|
|
200
|
+
/** The fact id. */
|
|
201
|
+
id: number
|
|
202
|
+
/** Subject entity id (a sentinel when none). */
|
|
203
|
+
entity: number
|
|
204
|
+
/** Bit set of fact flags (tombstone / closed / has-vector). */
|
|
205
|
+
flags: number
|
|
206
|
+
/** Predecessor in the revision chain (a sentinel when none). */
|
|
207
|
+
revises: number
|
|
208
|
+
/** Knowledge axis: when the memory learned it (unix ms). */
|
|
209
|
+
recordedAt: number
|
|
210
|
+
/** Truth axis start (unix ms). */
|
|
211
|
+
validFrom: number
|
|
212
|
+
/** Truth axis end (unix ms), or the open sentinel — see the module note. */
|
|
213
|
+
validTo: number
|
|
214
|
+
}
|
|
215
|
+
/** One fact's full card (from `get`). */
|
|
216
|
+
export interface FactSnapshot {
|
|
217
|
+
/** The raw record (temporality, flags, references). */
|
|
218
|
+
record: FactRecord
|
|
219
|
+
/** The fact text. */
|
|
220
|
+
text: string
|
|
221
|
+
/**
|
|
222
|
+
* The fact's metadata as a key→value map (empty when it has none). Opaque
|
|
223
|
+
* to the engine — a URI to the real payload, a mime type, an external key.
|
|
224
|
+
*/
|
|
225
|
+
metadata: Record<string, string>
|
|
226
|
+
}
|
|
227
|
+
/** One exported fact — the id-free, import-ready shape. */
|
|
228
|
+
export interface ExportedFact {
|
|
229
|
+
/** The fact text. */
|
|
230
|
+
text: string
|
|
231
|
+
/** Subject entity name, if any. */
|
|
232
|
+
entity?: string
|
|
233
|
+
/** Tag strings. */
|
|
234
|
+
tags: Array<string>
|
|
235
|
+
/** Metadata as a key→value map (empty when none); preserved on import. */
|
|
236
|
+
metadata: Record<string, string>
|
|
237
|
+
/** When the memory learned it (unix ms; informational). */
|
|
238
|
+
recordedAt: number
|
|
239
|
+
/** Validity start (unix ms; preserved on import). */
|
|
240
|
+
validFrom: number
|
|
241
|
+
}
|
|
242
|
+
/** One bounded page returned by `exportPage`. */
|
|
243
|
+
export interface ExportPage {
|
|
244
|
+
/** Open facts in fact-id order; never longer than the native page bound. */
|
|
245
|
+
facts: Array<ExportedFact>
|
|
246
|
+
/** Pass this opaque cursor to the next call; absent when the scan is done. */
|
|
247
|
+
nextCursor?: number
|
|
248
|
+
}
|
|
249
|
+
/** The report of a `maintain` pass. */
|
|
250
|
+
export interface MaintainReport {
|
|
251
|
+
/** Tombstoned facts physically removed by this pass. */
|
|
252
|
+
purged: number
|
|
253
|
+
/** On-disk image bytes before the pass. */
|
|
254
|
+
bytesBefore: number
|
|
255
|
+
/** On-disk image bytes after the pass. */
|
|
256
|
+
bytesAfter: number
|
|
257
|
+
/** No storage/index rewrite was needed. */
|
|
258
|
+
noOp: boolean
|
|
259
|
+
/** Tombstones present before the pass. */
|
|
260
|
+
tombstonesBefore: number
|
|
261
|
+
/** Fact records before the pass. */
|
|
262
|
+
factsBefore: number
|
|
263
|
+
/** Fact records after the pass. */
|
|
264
|
+
factsAfter: number
|
|
265
|
+
/** Vector slots before the pass. */
|
|
266
|
+
vectorsBefore: number
|
|
267
|
+
/** Vector slots after the pass. */
|
|
268
|
+
vectorsAfter: number
|
|
269
|
+
/** HNSW coverage before the pass. */
|
|
270
|
+
hnswIndexedBefore: number
|
|
271
|
+
/** HNSW coverage after the pass. */
|
|
272
|
+
hnswIndexedAfter: number
|
|
273
|
+
/** Physical compaction ran. */
|
|
274
|
+
structuralCompacted: boolean
|
|
275
|
+
/** BM25 was compacted from existing postings. */
|
|
276
|
+
bm25Compacted: boolean
|
|
277
|
+
/** BM25 was rebuilt from text. */
|
|
278
|
+
bm25Reindexed: boolean
|
|
279
|
+
/** HNSW was rebuilt from empty. */
|
|
280
|
+
hnswRebuilt: boolean
|
|
281
|
+
/** HNSW was carried/remapped. */
|
|
282
|
+
hnswRemapped: boolean
|
|
283
|
+
/** Vector slots inserted into HNSW. */
|
|
284
|
+
hnswInserted: number
|
|
285
|
+
/**
|
|
286
|
+
* The edge arenas were rewritten page-dense (`full` only). No edge
|
|
287
|
+
* version is ever dropped, so the version count is unchanged — only the
|
|
288
|
+
* bytes shrink.
|
|
289
|
+
*/
|
|
290
|
+
edgesCompacted: boolean
|
|
291
|
+
/** Current edges before the pass. */
|
|
292
|
+
edgesBefore: number
|
|
293
|
+
/** Historical edge versions before the pass. */
|
|
294
|
+
edgeVersionsBefore: number
|
|
295
|
+
}
|
|
296
|
+
/** How much work a `maintain` pass should do. */
|
|
297
|
+
export const enum MaintainMode {
|
|
298
|
+
/**
|
|
299
|
+
* Only pending work: purge tombstones, refresh a stale text index, and
|
|
300
|
+
* advance the vector graph within a bounded budget. Cheap to run often.
|
|
301
|
+
*/
|
|
302
|
+
Auto = 'auto',
|
|
303
|
+
/** Physically purge tombstoned facts and compact storage and indexes. */
|
|
304
|
+
Compact = 'compact',
|
|
305
|
+
/** Rebuild the text index by re-reading and re-tokenizing every fact. */
|
|
306
|
+
ReindexText = 'reindex-text',
|
|
307
|
+
/** Build or advance the vector graph without compacting anything else. */
|
|
308
|
+
OptimizeVectors = 'optimize-vectors',
|
|
309
|
+
/**
|
|
310
|
+
* Rebuild every rebuildable structure, fully optimize vectors and repack
|
|
311
|
+
* the edge arenas. O(database) work; no history is ever dropped.
|
|
312
|
+
*/
|
|
313
|
+
Full = 'full'
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* One memory as the workspace registry knows it.
|
|
317
|
+
*
|
|
318
|
+
* Hand-mapped rather than serde-round-tripped like the rest of this file:
|
|
319
|
+
* `DbEntry` carries a `DbName`, which is a validated newtype with no serde
|
|
320
|
+
* derive, and giving one to a public host type only so a wrapper could
|
|
321
|
+
* round-trip it would be the wrong direction of dependency.
|
|
322
|
+
*/
|
|
323
|
+
export interface DbEntry {
|
|
324
|
+
/** The memory's name — its identity, and what `Workspace.open` takes. */
|
|
325
|
+
db: string
|
|
326
|
+
/** What it is for. */
|
|
327
|
+
description: string
|
|
328
|
+
/** Its tags. */
|
|
329
|
+
tags: Array<string>
|
|
330
|
+
/** Its owner, if recorded. */
|
|
331
|
+
owner?: string
|
|
332
|
+
/** Whether it is labelled archived. */
|
|
333
|
+
archived: boolean
|
|
334
|
+
}
|
|
335
|
+
/** What a `Workspace.reindex()` pass did. */
|
|
336
|
+
export interface ReindexReport {
|
|
337
|
+
/** Memories whose own description was copied into the registry. */
|
|
338
|
+
indexed: Array<string>
|
|
339
|
+
/**
|
|
340
|
+
* Memories nobody has described. Not a fault — a memory works without a
|
|
341
|
+
* description; it just cannot be found by one.
|
|
342
|
+
*/
|
|
343
|
+
undescribed: Array<string>
|
|
344
|
+
/**
|
|
345
|
+
* Memories another process holds open, so this pass could not read them.
|
|
346
|
+
* Named rather than skipped silently: the registry is knowingly incomplete.
|
|
347
|
+
*/
|
|
348
|
+
busy: Array<string>
|
|
349
|
+
}
|
|
350
|
+
/** Something `Workspace.verify()` found. Reported, never repaired. */
|
|
351
|
+
export interface WorkspaceProblem {
|
|
352
|
+
/** The memory it concerns (empty for a kind this binding does not know). */
|
|
353
|
+
db: string
|
|
354
|
+
/**
|
|
355
|
+
* What kind: `"missing"`, `"undescribed"`, `"stale"`, `"unreadable"`,
|
|
356
|
+
* `"ambiguous-self"`. The same vocabulary the CLI prints in `--json`.
|
|
357
|
+
*/
|
|
358
|
+
issue: string
|
|
359
|
+
/** More detail, where the kind carries any. */
|
|
360
|
+
detail?: string
|
|
361
|
+
}
|
|
362
|
+
/** Options for [`Workspace::new`]. */
|
|
363
|
+
export interface WorkspaceOptions {
|
|
364
|
+
/**
|
|
365
|
+
* Embedding dimension, as in [`crate::db::OpenOptions`]. Applies to every
|
|
366
|
+
* memory in the workspace and to the registry.
|
|
367
|
+
*/
|
|
368
|
+
dim?: number
|
|
369
|
+
/**
|
|
370
|
+
* Path to a `config.toml`. Resolution is the standard one when omitted.
|
|
371
|
+
* Its `[workspace]` section supplies the pool defaults below.
|
|
372
|
+
*/
|
|
373
|
+
config?: string
|
|
374
|
+
/**
|
|
375
|
+
* Memories kept open at once; the least recently used is closed to make
|
|
376
|
+
* room. Defaults to `[workspace].max_open`, else 16.
|
|
377
|
+
*/
|
|
378
|
+
maxOpen?: number
|
|
379
|
+
/**
|
|
380
|
+
* Milliseconds a memory may sit unused before `closeIdle()` closes it.
|
|
381
|
+
* `0` disables the sweep. Defaults to `[workspace].idle_timeout_ms`, else
|
|
382
|
+
* 60000.
|
|
383
|
+
*
|
|
384
|
+
* This is a liveness setting, not a memory one: an open memory holds the
|
|
385
|
+
* file's exclusive lock, so a long-running process that never let go would
|
|
386
|
+
* make its memories unreachable from anything else on the machine.
|
|
387
|
+
*/
|
|
388
|
+
idleTimeoutMs?: number
|
|
389
|
+
}
|
|
390
|
+
/** What to record about a memory — the argument of [`Workspace::describe`]. */
|
|
391
|
+
export interface DescribeArgs {
|
|
392
|
+
/**
|
|
393
|
+
* What this memory is for, in the words someone would search with. This is
|
|
394
|
+
* the text `find` matches.
|
|
395
|
+
*/
|
|
396
|
+
description: string
|
|
397
|
+
/** Tags to filter by. */
|
|
398
|
+
tags?: Array<string>
|
|
399
|
+
/**
|
|
400
|
+
* Who it belongs to. Recorded as a graph edge, so `find("ann")` returns
|
|
401
|
+
* what Ann owns even though no description mentions her.
|
|
402
|
+
*/
|
|
403
|
+
owner?: string
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The engine/package version (the workspace version; the npm package tracks
|
|
407
|
+
* it release-for-release).
|
|
408
|
+
*/
|
|
409
|
+
export declare function version(): string
|
|
410
|
+
/** A short, version-free description pointing the caller at the skill. */
|
|
411
|
+
export declare function about(): string
|
|
412
|
+
/** One config.toml setting returned by [`settings_help`]. */
|
|
413
|
+
export interface SettingHelpItem {
|
|
414
|
+
/** TOML section name. */
|
|
415
|
+
section: string
|
|
416
|
+
/** TOML key name. */
|
|
417
|
+
key: string
|
|
418
|
+
/** Human-readable value type. */
|
|
419
|
+
valueType: string
|
|
420
|
+
/** Displayed default value. */
|
|
421
|
+
defaultValue: string
|
|
422
|
+
/** Setting behavior. */
|
|
423
|
+
description: string
|
|
424
|
+
/** Owning surface: shared, CLI or MCP. */
|
|
425
|
+
scope: string
|
|
426
|
+
}
|
|
427
|
+
/** Complete config.toml help returned by [`settings_help`]. */
|
|
428
|
+
export interface SettingsHelpResult {
|
|
429
|
+
/** Config discovery order from highest to lowest precedence. */
|
|
430
|
+
configPathPrecedence: Array<string>
|
|
431
|
+
/** Resolved platform default config path, if the OS exposes a user home. */
|
|
432
|
+
defaultConfigPath?: string
|
|
433
|
+
/** Every supported config.toml setting. */
|
|
434
|
+
settings: Array<SettingHelpItem>
|
|
435
|
+
}
|
|
436
|
+
/** Return the complete settings catalogue without opening a database. */
|
|
437
|
+
export declare function settingsHelp(): SettingsHelpResult
|
|
438
|
+
/**
|
|
439
|
+
* The companion skill for napi consumers: the canonical `SKILL.md` with the
|
|
440
|
+
* CLI/MCP "Run it" appendix removed (a napi host has one transport and always
|
|
441
|
+
* ships skill and engine from the same release, so that ceremony never applies).
|
|
442
|
+
*/
|
|
443
|
+
export declare function skill(): string
|
|
444
|
+
/** The canonical, unstripped `SKILL.md` (what CLI/MCP consumers read). */
|
|
445
|
+
export declare function skillFull(): string
|
|
446
|
+
/**
|
|
447
|
+
* The `<!-- skill-version: X.Y.Z -->` marker value from the canonical skill
|
|
448
|
+
* (read from the raw text, so the marker living inside the stripped block is
|
|
449
|
+
* still visible here).
|
|
450
|
+
*/
|
|
451
|
+
export declare function skillVersion(): string
|
|
452
|
+
/**
|
|
453
|
+
* A memory over one plugmem file — the napi mirror of [`plugmem_host::Database`]
|
|
454
|
+
* (writer) or [`plugmem_host::ReadOnlyDatabase`] (with `{ readOnly: true }`).
|
|
455
|
+
* Construct it, call the verbs, and `close()` it to release the file when done.
|
|
456
|
+
*/
|
|
457
|
+
export declare class Plugmem {
|
|
458
|
+
/**
|
|
459
|
+
* Opens (or creates) the memory at `path` and resolves with the handle. If
|
|
460
|
+
* `path` is omitted, resolution is `PLUGMEM_DB` > `[database].path` > the
|
|
461
|
+
* platform data path — and [`path()`](Plugmem::path) reports what that was.
|
|
462
|
+
*
|
|
463
|
+
* **A static method, not a constructor, and that is the point.** Opening
|
|
464
|
+
* takes the file's exclusive lock, replays the journal and maps the
|
|
465
|
+
* snapshot — work proportional to what is on disk. A JavaScript
|
|
466
|
+
* constructor must evaluate to its object immediately, so `new` has no way
|
|
467
|
+
* to hand that to a worker: it would run on the one thread that executes
|
|
468
|
+
* JavaScript and freeze the process for the length of the replay. A static
|
|
469
|
+
* method can return a `Promise`, so it does.
|
|
470
|
+
*
|
|
471
|
+
* @throws synchronously on a config error (the file is read before any
|
|
472
|
+
* work is scheduled); rejects if another writer holds the lock
|
|
473
|
+
* (`PLUGMEM_LOCKED`), if `readOnly` is set on a database with no published
|
|
474
|
+
* snapshot (`PLUGMEM_NEEDS_CHECKPOINT`), or on an IO error.
|
|
475
|
+
*/
|
|
476
|
+
static open(path?: string | undefined | null, options?: OpenOptions | undefined | null): Promise<Plugmem>
|
|
477
|
+
/**
|
|
478
|
+
* The file this memory is open on.
|
|
479
|
+
*
|
|
480
|
+
* Worth having because the constructor may resolve the path rather than be
|
|
481
|
+
* given one — `PLUGMEM_DB`, then `[database].path`, then the platform data
|
|
482
|
+
* path — and `new Plugmem()` with no argument otherwise leaves the caller
|
|
483
|
+
* unable to say which file it just wrote to.
|
|
484
|
+
*/
|
|
485
|
+
path(): string
|
|
486
|
+
/**
|
|
487
|
+
* Stores a fact and resolves with its id plus similar/conflicting live
|
|
488
|
+
* facts.
|
|
489
|
+
*
|
|
490
|
+
* **Async** (returns a `Promise`): with an `[embedder]` configured this
|
|
491
|
+
* makes an HTTP call to the provider, and a write waits for the journal's
|
|
492
|
+
* durability policy. Both are blocking work, and Node has exactly one
|
|
493
|
+
* thread that runs JavaScript — doing them on it would freeze every timer,
|
|
494
|
+
* socket and callback in the process for the whole round trip. It runs on
|
|
495
|
+
* a libuv worker instead. Arguments are still checked synchronously, so a
|
|
496
|
+
* refused one throws here rather than rejecting later.
|
|
497
|
+
* @throws synchronously in read-only mode.
|
|
498
|
+
*/
|
|
499
|
+
remember(args: RememberArgs): Promise<RememberOutcome>
|
|
500
|
+
/**
|
|
501
|
+
* Stores a batch of facts and resolves with one outcome per input.
|
|
502
|
+
*
|
|
503
|
+
* A batch may call a remote embedder and always performs one journal sync,
|
|
504
|
+
* so it runs on napi-rs' libuv worker pool.
|
|
505
|
+
*/
|
|
506
|
+
rememberMany(args: Array<RememberArgs>): Promise<RememberOutcome[]>
|
|
507
|
+
/**
|
|
508
|
+
* Closes fact `id`, records `args` as its successor, and resolves with the
|
|
509
|
+
* outcome. **Async** for the same reasons as
|
|
510
|
+
* [`remember`](Plugmem::remember).
|
|
511
|
+
* @throws synchronously in read-only mode.
|
|
512
|
+
*/
|
|
513
|
+
revise(id: number, args: RememberArgs): Promise<RememberOutcome>
|
|
514
|
+
/**
|
|
515
|
+
* Ranked, fused recall. Resolves with the structured result (its `rendered`
|
|
516
|
+
* field is the prompt-ready block; `facts`/`edges` are the structured hits).
|
|
517
|
+
*
|
|
518
|
+
* **Async** (returns a `Promise`): a text query with an `[embedder]`
|
|
519
|
+
* configured costs an HTTP round trip, and blocking the one thread that
|
|
520
|
+
* runs JavaScript for it would stall the whole process. The query shape is
|
|
521
|
+
* still validated synchronously.
|
|
522
|
+
*/
|
|
523
|
+
recall(args?: RecallArgs | undefined | null): Promise<RecallResult>
|
|
524
|
+
/**
|
|
525
|
+
* Tombstones fact `id` (physically purged at the next `maintain`) and
|
|
526
|
+
* resolves with whether it was a live fact.
|
|
527
|
+
*
|
|
528
|
+
* **Async**: every write syncs the journal, and the host's post-write
|
|
529
|
+
* policy can fire a whole maintenance pass or a reshard from here — work
|
|
530
|
+
* proportional to the database, which the JS thread must not be holding.
|
|
531
|
+
* @throws synchronously in read-only mode.
|
|
532
|
+
*/
|
|
533
|
+
forget(id: number): Promise<boolean>
|
|
534
|
+
/**
|
|
535
|
+
* Upserts a typed edge `src -rel-> dst`. **Async** for the same reason as
|
|
536
|
+
* [`forget`](Plugmem::forget). @throws synchronously in read-only mode.
|
|
537
|
+
*/
|
|
538
|
+
link(args: LinkArgs): Promise<void>
|
|
539
|
+
/**
|
|
540
|
+
* Closes the current typed edge `src -rel-> dst`, resolving with whether
|
|
541
|
+
* one was open. **Async** for the same reason as
|
|
542
|
+
* [`forget`](Plugmem::forget). @throws synchronously in read-only mode.
|
|
543
|
+
*/
|
|
544
|
+
unlink(args: LinkArgs): Promise<boolean>
|
|
545
|
+
/** One fact's full card by `id`, or `null` if unknown/tombstoned. */
|
|
546
|
+
get(id: number): FactSnapshot | null
|
|
547
|
+
/** Engine size counters. */
|
|
548
|
+
stats(): Stats
|
|
549
|
+
/**
|
|
550
|
+
* Every currently-open fact, as one array (id-free, import-ready).
|
|
551
|
+
*
|
|
552
|
+
* **Async, but still unbounded**: the scan is off the JS thread, yet the
|
|
553
|
+
* whole memory is materialized into a single array before it resolves, so
|
|
554
|
+
* the peak memory is the whole export. `exportPage` is the same data in
|
|
555
|
+
* bounded pages — prefer it for anything but a small memory or a script.
|
|
556
|
+
*/
|
|
557
|
+
export(): Promise<ExportedFact[]>
|
|
558
|
+
/**
|
|
559
|
+
* Returns at most 128 inspected fact ids' open facts on a libuv worker
|
|
560
|
+
* thread. A sparse page can be empty and still carry `nextCursor`.
|
|
561
|
+
*
|
|
562
|
+
* Pass `nextCursor` back as `cursor` until it is absent. Each Promise owns
|
|
563
|
+
* exactly one bounded page and resolves only after its native scan has
|
|
564
|
+
* completed; there is no callback queue and no database lock held while JS
|
|
565
|
+
* processes the result. A writer may change between page calls, so do not
|
|
566
|
+
* mutate it during a snapshot-style backup; a read-only handle is stable.
|
|
567
|
+
*/
|
|
568
|
+
exportPage(cursor?: number | undefined | null): Promise<ExportPage>
|
|
569
|
+
/** One fact's tags, or an empty array for an unknown or tombstoned id. */
|
|
570
|
+
tagsOf(id: number): Array<string>
|
|
571
|
+
/**
|
|
572
|
+
* Content-integrity check; rejects on the first inconsistency found.
|
|
573
|
+
*
|
|
574
|
+
* **Async**: this is a full sweep — every fact's text and metadata, the
|
|
575
|
+
* vector mapping, and both directions of every edge. On a large memory
|
|
576
|
+
* that is seconds of work, and it belongs on a worker.
|
|
577
|
+
*/
|
|
578
|
+
verify(): Promise<void>
|
|
579
|
+
/**
|
|
580
|
+
* Runs policy-driven maintenance; resolves with the before/after report.
|
|
581
|
+
* **Async** (returns a `Promise`): the pass may do disk I/O (compaction,
|
|
582
|
+
* HNSW work), so it runs on a libuv worker thread and never blocks the
|
|
583
|
+
* event loop. @throws synchronously in read-only mode.
|
|
584
|
+
*
|
|
585
|
+
* `mode` defaults to `auto`, which does only what is pending. `full`
|
|
586
|
+
* rebuilds everything and repacks the edge arenas — O(database) work, and
|
|
587
|
+
* the only mode that reclaims edge-history page slack.
|
|
588
|
+
*/
|
|
589
|
+
maintain(mode?: 'auto' | 'compact' | 'reindex-text' | 'optimize-vectors' | 'full'): Promise<MaintainReport>
|
|
590
|
+
/**
|
|
591
|
+
* Flushes the journal into a fresh snapshot. **Async** (returns a `Promise`):
|
|
592
|
+
* it writes and fsyncs a snapshot file, so it runs on a libuv worker thread.
|
|
593
|
+
* @throws synchronously in read-only mode.
|
|
594
|
+
*/
|
|
595
|
+
checkpoint(): Promise<void>
|
|
596
|
+
/**
|
|
597
|
+
* The pinned snapshot generation (read-only mode only).
|
|
598
|
+
* @throws on a writer.
|
|
599
|
+
*/
|
|
600
|
+
generation(): number
|
|
601
|
+
/**
|
|
602
|
+
* Advance to the writer's latest published checkpoint (read-only mode only);
|
|
603
|
+
* returns whether a newer generation was adopted. @throws on a writer.
|
|
604
|
+
*/
|
|
605
|
+
refresh(): boolean
|
|
606
|
+
/**
|
|
607
|
+
* Releases the file and its lock. Every verb afterwards throws; calling it
|
|
608
|
+
* again is a no-op. (The handle is also released when the object is GC'd,
|
|
609
|
+
* but `close()` makes the moment explicit — e.g. before a read-only reopen.)
|
|
610
|
+
*/
|
|
611
|
+
close(): void
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* A directory of named memories — the napi mirror of
|
|
615
|
+
* [`plugmem_host::Workspace`].
|
|
616
|
+
*/
|
|
617
|
+
export declare class Workspace {
|
|
618
|
+
/**
|
|
619
|
+
* Opens the workspace rooted at `root`. Creates nothing: the directories
|
|
620
|
+
* appear when a memory is first written.
|
|
621
|
+
*
|
|
622
|
+
* @throws on a config error.
|
|
623
|
+
*/
|
|
624
|
+
constructor(root: string, options?: WorkspaceOptions | undefined | null)
|
|
625
|
+
/**
|
|
626
|
+
* Opens the memory named `db` and returns it as a [`Plugmem`] — the same
|
|
627
|
+
* class, and the same verbs, as a memory opened by path.
|
|
628
|
+
*
|
|
629
|
+
* `create` defaults to `true`: a first use of an unused name brings that
|
|
630
|
+
* memory into being, which is what makes a new conversation need no
|
|
631
|
+
* registration step. Pass `false` to require that it already exists, which
|
|
632
|
+
* is what a read should do so a misspelled name is diagnosed rather than
|
|
633
|
+
* answered with nothing.
|
|
634
|
+
*
|
|
635
|
+
* @throws if the name is not a usable memory name, if it does not exist and
|
|
636
|
+
* `create` is false, or if another process holds it.
|
|
637
|
+
* **Async**: a first open replays the memory's journal and maps its
|
|
638
|
+
* snapshot, and making room in the pool closes another memory — file work
|
|
639
|
+
* that the one thread running JavaScript must not be holding.
|
|
640
|
+
*/
|
|
641
|
+
open(db: string, create?: boolean | undefined | null): Promise<Plugmem>
|
|
642
|
+
/**
|
|
643
|
+
* Every memory in the directory, sorted by name.
|
|
644
|
+
*
|
|
645
|
+
* Reads the filesystem, not the registry: a memory that exists but was
|
|
646
|
+
* never described still appears.
|
|
647
|
+
* **Async**: it reads the directory.
|
|
648
|
+
*/
|
|
649
|
+
list(): Promise<string[]>
|
|
650
|
+
/**
|
|
651
|
+
* Every described memory, sorted by name.
|
|
652
|
+
*
|
|
653
|
+
* **Async**: the registry is itself a memory, so this opens and reads a
|
|
654
|
+
* database.
|
|
655
|
+
*/
|
|
656
|
+
entries(): Promise<DbEntry[]>
|
|
657
|
+
/**
|
|
658
|
+
* The memories whose descriptions best match `query`, best first.
|
|
659
|
+
*
|
|
660
|
+
* This is the answer to "I do not know the name": ask in words, get names
|
|
661
|
+
* back, then open by name. A person's name works too — an owner is a graph
|
|
662
|
+
* edge, and the graph source reaches it.
|
|
663
|
+
* **Async**: this is a full recall against the registry memory — the same
|
|
664
|
+
* hybrid retrieval any other recall runs.
|
|
665
|
+
*/
|
|
666
|
+
find(query: string, k?: number | undefined | null): Promise<DbEntry[]>
|
|
667
|
+
/**
|
|
668
|
+
* Records what a memory is for — in the memory itself and in the registry,
|
|
669
|
+
* so the registry can always be rebuilt from the memories. Creates the
|
|
670
|
+
* memory if it does not exist.
|
|
671
|
+
*
|
|
672
|
+
* Called again for the same memory this revises rather than duplicating,
|
|
673
|
+
* so the history of what it used to be for is kept.
|
|
674
|
+
* **Async**: it writes twice — into the memory itself and into the
|
|
675
|
+
* registry — and each write syncs a journal.
|
|
676
|
+
*/
|
|
677
|
+
describe(db: string, args: DescribeArgs): Promise<void>
|
|
678
|
+
/**
|
|
679
|
+
* Labels a memory archived, keeping its description. Returns whether
|
|
680
|
+
* anything changed. Archiving does not close, move or delete anything.
|
|
681
|
+
*
|
|
682
|
+
* @throws if the memory has no registry record to archive.
|
|
683
|
+
* **Async**: a registry write.
|
|
684
|
+
*/
|
|
685
|
+
archive(db: string): Promise<boolean>
|
|
686
|
+
/**
|
|
687
|
+
* Rebuilds the registry from the memories' own descriptions.
|
|
688
|
+
*
|
|
689
|
+
* Runs on a libuv thread: it opens and reads every memory in the
|
|
690
|
+
* directory, which is not work for the main thread. A memory another
|
|
691
|
+
* process holds open cannot be read and is named in the report rather than
|
|
692
|
+
* skipped silently.
|
|
693
|
+
*/
|
|
694
|
+
reindex(): Promise<ReindexReport>
|
|
695
|
+
/**
|
|
696
|
+
* Checks the registry against the directory. Reports every disagreement
|
|
697
|
+
* and repairs nothing — a workspace is a directory a person can edit, and
|
|
698
|
+
* guessing at their intent is how a consistency check loses data.
|
|
699
|
+
*/
|
|
700
|
+
verify(): Promise<WorkspaceProblem[]>
|
|
701
|
+
/**
|
|
702
|
+
* Closes every memory unused for longer than the idle timeout, returning
|
|
703
|
+
* how many were closed. Call it on a timer: nothing else releases the file
|
|
704
|
+
* lock on a memory nobody is asking about.
|
|
705
|
+
*/
|
|
706
|
+
closeIdle(): number
|
|
707
|
+
/** How many memories are open right now. */
|
|
708
|
+
openCount(): number
|
|
709
|
+
/**
|
|
710
|
+
* Closes every pooled memory and the registry, releasing their file locks,
|
|
711
|
+
* and closes the workspace. Every method then throws.
|
|
712
|
+
*
|
|
713
|
+
* A [`Plugmem`] handed out by `open()` is **not** closed by this: it is its
|
|
714
|
+
* own handle and holds its own lock until it is closed or garbage
|
|
715
|
+
* collected.
|
|
716
|
+
*/
|
|
717
|
+
close(): void
|
|
718
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* prettier-ignore */
|
|
4
|
+
|
|
5
|
+
/* auto-generated by NAPI-RS */
|
|
6
|
+
|
|
7
|
+
const { existsSync, readFileSync } = require('fs')
|
|
8
|
+
const { join } = require('path')
|
|
9
|
+
|
|
10
|
+
const { platform, arch } = process
|
|
11
|
+
|
|
12
|
+
let nativeBinding = null
|
|
13
|
+
let localFileExisted = false
|
|
14
|
+
let loadError = null
|
|
15
|
+
|
|
16
|
+
function isMusl() {
|
|
17
|
+
// For Node 10
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== 'function') {
|
|
19
|
+
try {
|
|
20
|
+
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
21
|
+
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const { glibcVersionRuntime } = process.report.getReport().header
|
|
27
|
+
return !glibcVersionRuntime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (platform) {
|
|
32
|
+
case 'android':
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
localFileExisted = existsSync(join(__dirname, 'plugmem.android-arm64.node'))
|
|
36
|
+
try {
|
|
37
|
+
if (localFileExisted) {
|
|
38
|
+
nativeBinding = require('./plugmem.android-arm64.node')
|
|
39
|
+
} else {
|
|
40
|
+
nativeBinding = require('plugmem-android-arm64')
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
loadError = e
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
case 'arm':
|
|
47
|
+
localFileExisted = existsSync(join(__dirname, 'plugmem.android-arm-eabi.node'))
|
|
48
|
+
try {
|
|
49
|
+
if (localFileExisted) {
|
|
50
|
+
nativeBinding = require('./plugmem.android-arm-eabi.node')
|
|
51
|
+
} else {
|
|
52
|
+
nativeBinding = require('plugmem-android-arm-eabi')
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
loadError = e
|
|
56
|
+
}
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
case 'win32':
|
|
63
|
+
switch (arch) {
|
|
64
|
+
case 'x64':
|
|
65
|
+
localFileExisted = existsSync(
|
|
66
|
+
join(__dirname, 'plugmem.win32-x64-msvc.node')
|
|
67
|
+
)
|
|
68
|
+
try {
|
|
69
|
+
if (localFileExisted) {
|
|
70
|
+
nativeBinding = require('./plugmem.win32-x64-msvc.node')
|
|
71
|
+
} else {
|
|
72
|
+
nativeBinding = require('plugmem-win32-x64-msvc')
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
loadError = e
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
case 'ia32':
|
|
79
|
+
localFileExisted = existsSync(
|
|
80
|
+
join(__dirname, 'plugmem.win32-ia32-msvc.node')
|
|
81
|
+
)
|
|
82
|
+
try {
|
|
83
|
+
if (localFileExisted) {
|
|
84
|
+
nativeBinding = require('./plugmem.win32-ia32-msvc.node')
|
|
85
|
+
} else {
|
|
86
|
+
nativeBinding = require('plugmem-win32-ia32-msvc')
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
loadError = e
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case 'arm64':
|
|
93
|
+
localFileExisted = existsSync(
|
|
94
|
+
join(__dirname, 'plugmem.win32-arm64-msvc.node')
|
|
95
|
+
)
|
|
96
|
+
try {
|
|
97
|
+
if (localFileExisted) {
|
|
98
|
+
nativeBinding = require('./plugmem.win32-arm64-msvc.node')
|
|
99
|
+
} else {
|
|
100
|
+
nativeBinding = require('plugmem-win32-arm64-msvc')
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
loadError = e
|
|
104
|
+
}
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
case 'darwin':
|
|
111
|
+
localFileExisted = existsSync(join(__dirname, 'plugmem.darwin-universal.node'))
|
|
112
|
+
try {
|
|
113
|
+
if (localFileExisted) {
|
|
114
|
+
nativeBinding = require('./plugmem.darwin-universal.node')
|
|
115
|
+
} else {
|
|
116
|
+
nativeBinding = require('plugmem-darwin-universal')
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
} catch {}
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case 'x64':
|
|
122
|
+
localFileExisted = existsSync(join(__dirname, 'plugmem.darwin-x64.node'))
|
|
123
|
+
try {
|
|
124
|
+
if (localFileExisted) {
|
|
125
|
+
nativeBinding = require('./plugmem.darwin-x64.node')
|
|
126
|
+
} else {
|
|
127
|
+
nativeBinding = require('plugmem-darwin-x64')
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
loadError = e
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
case 'arm64':
|
|
134
|
+
localFileExisted = existsSync(
|
|
135
|
+
join(__dirname, 'plugmem.darwin-arm64.node')
|
|
136
|
+
)
|
|
137
|
+
try {
|
|
138
|
+
if (localFileExisted) {
|
|
139
|
+
nativeBinding = require('./plugmem.darwin-arm64.node')
|
|
140
|
+
} else {
|
|
141
|
+
nativeBinding = require('plugmem-darwin-arm64')
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
loadError = e
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
case 'freebsd':
|
|
152
|
+
if (arch !== 'x64') {
|
|
153
|
+
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
154
|
+
}
|
|
155
|
+
localFileExisted = existsSync(join(__dirname, 'plugmem.freebsd-x64.node'))
|
|
156
|
+
try {
|
|
157
|
+
if (localFileExisted) {
|
|
158
|
+
nativeBinding = require('./plugmem.freebsd-x64.node')
|
|
159
|
+
} else {
|
|
160
|
+
nativeBinding = require('plugmem-freebsd-x64')
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
loadError = e
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case 'linux':
|
|
167
|
+
switch (arch) {
|
|
168
|
+
case 'x64':
|
|
169
|
+
if (isMusl()) {
|
|
170
|
+
localFileExisted = existsSync(
|
|
171
|
+
join(__dirname, 'plugmem.linux-x64-musl.node')
|
|
172
|
+
)
|
|
173
|
+
try {
|
|
174
|
+
if (localFileExisted) {
|
|
175
|
+
nativeBinding = require('./plugmem.linux-x64-musl.node')
|
|
176
|
+
} else {
|
|
177
|
+
nativeBinding = require('plugmem-linux-x64-musl')
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
loadError = e
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
localFileExisted = existsSync(
|
|
184
|
+
join(__dirname, 'plugmem.linux-x64-gnu.node')
|
|
185
|
+
)
|
|
186
|
+
try {
|
|
187
|
+
if (localFileExisted) {
|
|
188
|
+
nativeBinding = require('./plugmem.linux-x64-gnu.node')
|
|
189
|
+
} else {
|
|
190
|
+
nativeBinding = require('plugmem-linux-x64-gnu')
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadError = e
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case 'arm64':
|
|
198
|
+
if (isMusl()) {
|
|
199
|
+
localFileExisted = existsSync(
|
|
200
|
+
join(__dirname, 'plugmem.linux-arm64-musl.node')
|
|
201
|
+
)
|
|
202
|
+
try {
|
|
203
|
+
if (localFileExisted) {
|
|
204
|
+
nativeBinding = require('./plugmem.linux-arm64-musl.node')
|
|
205
|
+
} else {
|
|
206
|
+
nativeBinding = require('plugmem-linux-arm64-musl')
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
loadError = e
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
localFileExisted = existsSync(
|
|
213
|
+
join(__dirname, 'plugmem.linux-arm64-gnu.node')
|
|
214
|
+
)
|
|
215
|
+
try {
|
|
216
|
+
if (localFileExisted) {
|
|
217
|
+
nativeBinding = require('./plugmem.linux-arm64-gnu.node')
|
|
218
|
+
} else {
|
|
219
|
+
nativeBinding = require('plugmem-linux-arm64-gnu')
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
loadError = e
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
case 'arm':
|
|
227
|
+
if (isMusl()) {
|
|
228
|
+
localFileExisted = existsSync(
|
|
229
|
+
join(__dirname, 'plugmem.linux-arm-musleabihf.node')
|
|
230
|
+
)
|
|
231
|
+
try {
|
|
232
|
+
if (localFileExisted) {
|
|
233
|
+
nativeBinding = require('./plugmem.linux-arm-musleabihf.node')
|
|
234
|
+
} else {
|
|
235
|
+
nativeBinding = require('plugmem-linux-arm-musleabihf')
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
loadError = e
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
localFileExisted = existsSync(
|
|
242
|
+
join(__dirname, 'plugmem.linux-arm-gnueabihf.node')
|
|
243
|
+
)
|
|
244
|
+
try {
|
|
245
|
+
if (localFileExisted) {
|
|
246
|
+
nativeBinding = require('./plugmem.linux-arm-gnueabihf.node')
|
|
247
|
+
} else {
|
|
248
|
+
nativeBinding = require('plugmem-linux-arm-gnueabihf')
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
loadError = e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
case 'riscv64':
|
|
256
|
+
if (isMusl()) {
|
|
257
|
+
localFileExisted = existsSync(
|
|
258
|
+
join(__dirname, 'plugmem.linux-riscv64-musl.node')
|
|
259
|
+
)
|
|
260
|
+
try {
|
|
261
|
+
if (localFileExisted) {
|
|
262
|
+
nativeBinding = require('./plugmem.linux-riscv64-musl.node')
|
|
263
|
+
} else {
|
|
264
|
+
nativeBinding = require('plugmem-linux-riscv64-musl')
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
loadError = e
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
localFileExisted = existsSync(
|
|
271
|
+
join(__dirname, 'plugmem.linux-riscv64-gnu.node')
|
|
272
|
+
)
|
|
273
|
+
try {
|
|
274
|
+
if (localFileExisted) {
|
|
275
|
+
nativeBinding = require('./plugmem.linux-riscv64-gnu.node')
|
|
276
|
+
} else {
|
|
277
|
+
nativeBinding = require('plugmem-linux-riscv64-gnu')
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
loadError = e
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break
|
|
284
|
+
case 's390x':
|
|
285
|
+
localFileExisted = existsSync(
|
|
286
|
+
join(__dirname, 'plugmem.linux-s390x-gnu.node')
|
|
287
|
+
)
|
|
288
|
+
try {
|
|
289
|
+
if (localFileExisted) {
|
|
290
|
+
nativeBinding = require('./plugmem.linux-s390x-gnu.node')
|
|
291
|
+
} else {
|
|
292
|
+
nativeBinding = require('plugmem-linux-s390x-gnu')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loadError = e
|
|
296
|
+
}
|
|
297
|
+
break
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
|
300
|
+
}
|
|
301
|
+
break
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!nativeBinding) {
|
|
307
|
+
if (loadError) {
|
|
308
|
+
throw loadError
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to load native binding`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { Plugmem, MaintainMode, Workspace, version, about, settingsHelp, skill, skillFull, skillVersion } = nativeBinding
|
|
314
|
+
|
|
315
|
+
module.exports.Plugmem = Plugmem
|
|
316
|
+
module.exports.MaintainMode = MaintainMode
|
|
317
|
+
module.exports.Workspace = Workspace
|
|
318
|
+
module.exports.version = version
|
|
319
|
+
module.exports.about = about
|
|
320
|
+
module.exports.settingsHelp = settingsHelp
|
|
321
|
+
module.exports.skill = skill
|
|
322
|
+
module.exports.skillFull = skillFull
|
|
323
|
+
module.exports.skillVersion = skillVersion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugmem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Native Node.js addon for plugmem: an embedded long-term memory engine for LLM agents (remember / recall / revise / forget over one local file).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"typecheck": "tsc -p tsconfig.json"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"plugmem-linux-x64-gnu": "0.
|
|
45
|
-
"plugmem-linux-arm64-gnu": "0.
|
|
46
|
-
"plugmem-darwin-x64": "0.
|
|
47
|
-
"plugmem-darwin-arm64": "0.
|
|
48
|
-
"plugmem-win32-x64-msvc": "0.
|
|
49
|
-
"plugmem-win32-arm64-msvc": "0.
|
|
44
|
+
"plugmem-linux-x64-gnu": "0.4.0",
|
|
45
|
+
"plugmem-linux-arm64-gnu": "0.4.0",
|
|
46
|
+
"plugmem-darwin-x64": "0.4.0",
|
|
47
|
+
"plugmem-darwin-arm64": "0.4.0",
|
|
48
|
+
"plugmem-win32-x64-msvc": "0.4.0",
|
|
49
|
+
"plugmem-win32-arm64-msvc": "0.4.0"
|
|
50
50
|
}
|
|
51
51
|
}
|