@hilbras/remembra 3.0.0 → 3.2.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 +63 -0
- package/README.md +20 -0
- package/dist/http.d.ts +17 -1
- package/dist/http.js +81 -23
- package/dist/http.js.map +1 -1
- package/dist/index.js +66 -40
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +25 -1
- package/dist/service.js +72 -9
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +2 -0
- package/dist/store.js +61 -9
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +200 -2
- package/dist/types.js +93 -7
- package/dist/types.js.map +1 -1
- package/docs/chatgpt.md +3 -1
- package/docs/clients.md +3 -0
- package/docs/security.md +89 -0
- package/docs/tools.md +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
7
7
|
> (3.0.0 = v3). Earlier releases used independent semver: 0.1.0 = v1,
|
|
8
8
|
> 0.2.0 = v1.5, 0.3.0 = v2, 0.4.0 = v3.
|
|
9
9
|
|
|
10
|
+
## [3.2.0] — 2026-09-23
|
|
11
|
+
|
|
12
|
+
**Phase 2 of the deep audit** — data integrity, portability, and shared schemas.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`remembra export <file>.json` / `remembra import <file>.json`** — full
|
|
16
|
+
snapshot backup incl. archived memories. Import validates the *whole* file
|
|
17
|
+
before writing (atomic rollback on any invalid entry) and is idempotent
|
|
18
|
+
(existing ids and exact duplicates are skipped). Fixes audit #8.
|
|
19
|
+
- **Schema version field** — every memory file now carries `version: 1` in
|
|
20
|
+
frontmatter; files without it (v1–v3.1) parse as v1. Fixes audit #10.
|
|
21
|
+
- `REMEMBRA_DEBUG=1` — opt-in storage-root path logging.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Log hygiene (audit #7)**: startup no longer prints the storage root path
|
|
25
|
+
(gated behind `REMEMBRA_DEBUG`); embedding errors truncated to 200 chars;
|
|
26
|
+
swallowed `touch()` errors now logged (audit #15); unparseable memory files
|
|
27
|
+
warn once instead of being silently skipped.
|
|
28
|
+
- **Simultaneous digests** — digest runs are serialized through a lock, so
|
|
29
|
+
parallel sessions can no longer double-store duplicates.
|
|
30
|
+
- **Merge LLM failure fails open** — a failed merge stores the new fact fresh
|
|
31
|
+
instead of aborting the digest mid-way (never lose data).
|
|
32
|
+
- **Shared input schemas (audit #13)** — MCP tools, HTTP routes and the
|
|
33
|
+
service all parse the same Zod shapes from `types.ts` (single source of
|
|
34
|
+
truth; the `type` enum is no longer declared twice).
|
|
35
|
+
- `?limit=abc` on `/memories/search` no longer yields empty results (NaN guard).
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- MCP `memory_store` now applies `.default()` for `tags`/`importance` at the
|
|
39
|
+
schema layer (behavior unchanged; validation moved to shared schemas).
|
|
40
|
+
|
|
41
|
+
### Added (tests)
|
|
42
|
+
12 Phase-2 tests: schema version + backward compat, malformed-file recovery,
|
|
43
|
+
empty transcript, extraction rollback, merge fail-open, parallel stores,
|
|
44
|
+
serialized digests, export/import round-trip, cross-id dedup, atomic import
|
|
45
|
+
rollback. **71/71 total.**
|
|
46
|
+
|
|
47
|
+
## [3.1.0] — 2026-09-23
|
|
48
|
+
|
|
49
|
+
**Security hardening** in response to the deep audit (`HILBRAS-MEMORY-DEEP-AUDIT.md`).
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- **P0 directory traversal**: scopes containing `..` are rejected by validation,
|
|
53
|
+
and `fileFor()` verifies the resolved path stays under `REMEMBRA_HOME`
|
|
54
|
+
(defense in depth). Penetration test added.
|
|
55
|
+
- **Default-deny HTTP**: no API key → binds `127.0.0.1` only; non-loopback
|
|
56
|
+
`REMEMBRA_HOST` without a key refuses to start.
|
|
57
|
+
- **Timing-safe API key comparison** (`crypto.timingSafeEqual`).
|
|
58
|
+
- **Request body size limit** (10 MiB default, `REMEMBRA_MAX_BODY`) → `413`.
|
|
59
|
+
- **Atomic writes** (temp file + `rename`) — crash can no longer leave
|
|
60
|
+
half-written memory files.
|
|
61
|
+
- **ID length** 8 → 12 hex chars (2⁴⁸) + existence check on store (collision-safe).
|
|
62
|
+
- **Digest validation** on both transports (`DigestInput` Zod schema).
|
|
63
|
+
- **`Content-Length`** on all HTTP responses.
|
|
64
|
+
- `memory_list` MCP tool now exposes `includeArchived`; HTTP accepts
|
|
65
|
+
`includeArchived=true`.
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
- `docs/security.md` — trust model (incl. role prompt-injection guidance),
|
|
69
|
+
enforced protections, deployment checklist.
|
|
70
|
+
- 14 security tests (traversal pen test ×2, listen policy, body limit,
|
|
71
|
+
digest validation, atomic-write leftovers, ID uniqueness).
|
|
72
|
+
|
|
10
73
|
## [3.0.0] — 2026-09-23
|
|
11
74
|
|
|
12
75
|
**Same content as 0.4.0** — version renumbered so the package version equals the
|
package/README.md
CHANGED
|
@@ -96,6 +96,25 @@ Then wire a Custom GPT to the API — full walkthrough in **[docs/chatgpt.md](do
|
|
|
96
96
|
All routes except `/health` require `x-api-key` (or `Authorization: Bearer`) when
|
|
97
97
|
`REMEMBRA_API_KEY` is set.
|
|
98
98
|
|
|
99
|
+
> 🔒 **Auth is now default-deny**: without `REMEMBRA_API_KEY` the server binds
|
|
100
|
+
> to `127.0.0.1` only, and a non-loopback `REMEMBRA_HOST` without a key refuses
|
|
101
|
+
> to start. Public deployments (ChatGPT) must set a key. See
|
|
102
|
+
> [security.md](docs/security.md).
|
|
103
|
+
|
|
104
|
+
## Backup & restore
|
|
105
|
+
|
|
106
|
+
Your memories are plain files under `~/.remembra` — plus a portable snapshot
|
|
107
|
+
format for moving between machines or disaster recovery:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
remembra export memories.json # full snapshot (incl. archived)
|
|
111
|
+
remembra import memories.json # idempotent: existing ids/dups skipped
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Import validates the **whole file before writing anything** — a corrupt or
|
|
115
|
+
tampered snapshot is rejected atomically, never half-imported. Of course,
|
|
116
|
+
`rsync`/`git` on `~/.remembra` works too.
|
|
117
|
+
|
|
99
118
|
## Tools
|
|
100
119
|
|
|
101
120
|
| Tool | Purpose |
|
|
@@ -119,6 +138,7 @@ Full reference: **[docs/tools.md](docs/tools.md)**
|
|
|
119
138
|
| [ChatGPT setup](docs/chatgpt.md) | HTTP API + Custom GPT walkthrough |
|
|
120
139
|
| [AI providers](docs/providers.md) | Digest LLM + embeddings configuration |
|
|
121
140
|
| [Lifecycle](docs/lifecycle.md) | Decay, archiving, auto-delete, contradiction merging |
|
|
141
|
+
| [Security](docs/security.md) | Trust model, enforced protections, deployment checklist |
|
|
122
142
|
| [Contributing](CONTRIBUTING.md) | Dev workflow and guidelines |
|
|
123
143
|
| [Changelog](CHANGELOG.md) | Release history |
|
|
124
144
|
|
package/dist/http.d.ts
CHANGED
|
@@ -2,9 +2,23 @@ import http from "node:http";
|
|
|
2
2
|
import { MemoryService } from "./service.js";
|
|
3
3
|
interface HttpOptions {
|
|
4
4
|
port?: number;
|
|
5
|
+
/** Bind address. Defaults: loopback when no API key, all interfaces when keyed. */
|
|
6
|
+
host?: string;
|
|
5
7
|
/** If set, all endpoints except GET /health require `x-api-key` or `Authorization: Bearer`. */
|
|
6
8
|
apiKey?: string;
|
|
9
|
+
/** Max request body bytes (default: REMEMBRA_MAX_BODY or 10 MiB). */
|
|
10
|
+
maxBodyBytes?: number;
|
|
7
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Listen policy (P1 audit: default-deny):
|
|
14
|
+
* - explicit REMEMBRA_HOST: non-loopback requires an API key (refuse otherwise)
|
|
15
|
+
* - no host + API key: all interfaces (public ChatGPT deployments)
|
|
16
|
+
* - no host + no key: loopback only (safe local default)
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveListen(host: string | undefined, hasKey: boolean): {
|
|
19
|
+
host?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
};
|
|
8
22
|
/**
|
|
9
23
|
* Minimal HTTP API over the same handlers the MCP tools use.
|
|
10
24
|
*
|
|
@@ -12,7 +26,9 @@ interface HttpOptions {
|
|
|
12
26
|
* GET /health → liveness (no auth)
|
|
13
27
|
* POST /memories → store a memory
|
|
14
28
|
* GET /memories/search → ?query=&scope=&type=&limit=
|
|
15
|
-
* GET /memories → ?scope=&type=
|
|
29
|
+
* GET /memories → ?scope=&type=&includeArchived=
|
|
30
|
+
* POST /memories/digest → LLM extraction
|
|
31
|
+
* POST /maintain → decay sweep + vector backfill
|
|
16
32
|
* DELETE /memories/:id → forget
|
|
17
33
|
*/
|
|
18
34
|
export declare function createHttpServer(service: MemoryService, opts?: HttpOptions): http.Server;
|
package/dist/http.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import { timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { DigestInput } from "./types.js";
|
|
4
|
+
const DEFAULT_MAX_BODY = 10 * 1024 * 1024; // transcripts can be large — 10 MiB
|
|
5
|
+
/**
|
|
6
|
+
* Listen policy (P1 audit: default-deny):
|
|
7
|
+
* - explicit REMEMBRA_HOST: non-loopback requires an API key (refuse otherwise)
|
|
8
|
+
* - no host + API key: all interfaces (public ChatGPT deployments)
|
|
9
|
+
* - no host + no key: loopback only (safe local default)
|
|
10
|
+
*/
|
|
11
|
+
export function resolveListen(host, hasKey) {
|
|
12
|
+
if (host) {
|
|
13
|
+
const loopback = host === "localhost" || host.startsWith("127.") || host === "::1";
|
|
14
|
+
if (!loopback && !hasKey) {
|
|
15
|
+
return {
|
|
16
|
+
error: `Refusing to listen on ${host} without REMEMBRA_API_KEY. Set a key, or bind to 127.0.0.1.`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { host };
|
|
20
|
+
}
|
|
21
|
+
return hasKey ? {} : { host: "127.0.0.1" };
|
|
22
|
+
}
|
|
2
23
|
/**
|
|
3
24
|
* Minimal HTTP API over the same handlers the MCP tools use.
|
|
4
25
|
*
|
|
@@ -6,10 +27,16 @@ import http from "node:http";
|
|
|
6
27
|
* GET /health → liveness (no auth)
|
|
7
28
|
* POST /memories → store a memory
|
|
8
29
|
* GET /memories/search → ?query=&scope=&type=&limit=
|
|
9
|
-
* GET /memories → ?scope=&type=
|
|
30
|
+
* GET /memories → ?scope=&type=&includeArchived=
|
|
31
|
+
* POST /memories/digest → LLM extraction
|
|
32
|
+
* POST /maintain → decay sweep + vector backfill
|
|
10
33
|
* DELETE /memories/:id → forget
|
|
11
34
|
*/
|
|
12
35
|
export function createHttpServer(service, opts = {}) {
|
|
36
|
+
const maxBody = opts.maxBodyBytes ?? Number(process.env.REMEMBRA_MAX_BODY ?? DEFAULT_MAX_BODY);
|
|
37
|
+
const listenTarget = resolveListen(opts.host ?? process.env.REMEMBRA_HOST, Boolean(opts.apiKey));
|
|
38
|
+
if (listenTarget.error)
|
|
39
|
+
throw new Error(listenTarget.error);
|
|
13
40
|
const server = http.createServer(async (req, res) => {
|
|
14
41
|
try {
|
|
15
42
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -27,30 +54,25 @@ export function createHttpServer(service, opts = {}) {
|
|
|
27
54
|
}
|
|
28
55
|
// POST /memories/digest — must be checked before /memories/:id DELETE patterns
|
|
29
56
|
if (req.method === "POST" && path === "/memories/digest") {
|
|
30
|
-
const body = await readBody(req);
|
|
31
|
-
|
|
32
|
-
return send(res, 400, { error: "Body must include a non-empty `transcript` string" });
|
|
33
|
-
}
|
|
34
|
-
const result = await service.digest({
|
|
35
|
-
transcript: body.transcript,
|
|
36
|
-
scope: body.scope,
|
|
37
|
-
source: body.source,
|
|
38
|
-
});
|
|
57
|
+
const body = await readBody(req, maxBody);
|
|
58
|
+
const result = await service.digest(DigestInput.parse(body));
|
|
39
59
|
return send(res, 200, result);
|
|
40
60
|
}
|
|
41
61
|
// POST /memories
|
|
42
62
|
if (req.method === "POST" && path === "/memories") {
|
|
43
|
-
const body = await readBody(req);
|
|
63
|
+
const body = await readBody(req, maxBody);
|
|
44
64
|
const result = await service.store(body);
|
|
45
65
|
return send(res, 201, result);
|
|
46
66
|
}
|
|
47
67
|
// GET /memories/search
|
|
48
68
|
if (req.method === "GET" && path === "/memories/search") {
|
|
69
|
+
const rawLimit = url.searchParams.get("limit");
|
|
70
|
+
const limit = rawLimit && Number.isFinite(Number(rawLimit)) ? Number(rawLimit) : undefined;
|
|
49
71
|
const result = await service.search({
|
|
50
72
|
query: url.searchParams.get("query") ?? url.searchParams.get("q") ?? undefined,
|
|
51
73
|
scope: url.searchParams.get("scope") ?? undefined,
|
|
52
74
|
type: url.searchParams.get("type") ?? undefined,
|
|
53
|
-
limit
|
|
75
|
+
limit,
|
|
54
76
|
});
|
|
55
77
|
return send(res, 200, result);
|
|
56
78
|
}
|
|
@@ -59,6 +81,7 @@ export function createHttpServer(service, opts = {}) {
|
|
|
59
81
|
const result = await service.list({
|
|
60
82
|
scope: url.searchParams.get("scope") ?? undefined,
|
|
61
83
|
type: url.searchParams.get("type") ?? undefined,
|
|
84
|
+
includeArchived: url.searchParams.get("includeArchived") === "true",
|
|
62
85
|
});
|
|
63
86
|
return send(res, 200, result);
|
|
64
87
|
}
|
|
@@ -72,34 +95,69 @@ export function createHttpServer(service, opts = {}) {
|
|
|
72
95
|
}
|
|
73
96
|
catch (err) {
|
|
74
97
|
const name = err.name;
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
const status = name === "ZodError" || name === "BadRequestError"
|
|
99
|
+
? 400
|
|
100
|
+
: name === "PayloadTooLarge"
|
|
101
|
+
? 413
|
|
102
|
+
: 500;
|
|
103
|
+
send(res, status, { error: err instanceof Error ? err.message : String(err) });
|
|
79
104
|
}
|
|
80
105
|
});
|
|
81
106
|
const port = opts.port ?? Number(process.env.REMEMBRA_PORT ?? 8787);
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
const address = listenTarget.host;
|
|
108
|
+
server.listen(port, address, () => {
|
|
109
|
+
const where = address ?? "0.0.0.0";
|
|
110
|
+
console.error(`Remembra HTTP API listening on ${where}:${port}${opts.apiKey ? " (auth required)" : " (no auth — loopback only)"}`);
|
|
84
111
|
});
|
|
85
112
|
return server;
|
|
86
113
|
}
|
|
114
|
+
/** Constant-time API key comparison (P1 audit: timing side-channel). */
|
|
87
115
|
function authorized(req, key) {
|
|
88
116
|
const header = req.headers["x-api-key"];
|
|
89
117
|
const auth = req.headers.authorization;
|
|
90
118
|
const provided = (typeof header === "string" ? header : undefined) ??
|
|
91
119
|
(auth?.startsWith("Bearer ") ? auth.slice(7) : undefined);
|
|
92
|
-
|
|
120
|
+
if (provided === undefined)
|
|
121
|
+
return false;
|
|
122
|
+
const a = Buffer.from(provided);
|
|
123
|
+
const b = Buffer.from(key);
|
|
124
|
+
if (a.length !== b.length) {
|
|
125
|
+
// Still do one compare of equal-length buffers to blunt timing signal.
|
|
126
|
+
timingSafeEqual(b, b);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return timingSafeEqual(a, b);
|
|
93
130
|
}
|
|
94
131
|
function send(res, status, body) {
|
|
95
132
|
const data = JSON.stringify(body, null, 2);
|
|
96
|
-
res.writeHead(status, {
|
|
133
|
+
res.writeHead(status, {
|
|
134
|
+
"content-type": "application/json; charset=utf-8",
|
|
135
|
+
"content-length": Buffer.byteLength(data),
|
|
136
|
+
});
|
|
97
137
|
res.end(data);
|
|
98
138
|
}
|
|
99
|
-
function readBody(req) {
|
|
139
|
+
function readBody(req, maxBytes) {
|
|
100
140
|
return new Promise((resolve, reject) => {
|
|
101
|
-
|
|
102
|
-
|
|
141
|
+
const declared = Number(req.headers["content-length"] ?? 0);
|
|
142
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
143
|
+
const err = new Error(`Request body too large (limit ${maxBytes} bytes)`);
|
|
144
|
+
err.name = "PayloadTooLarge";
|
|
145
|
+
reject(err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const chunks = [];
|
|
149
|
+
let total = 0;
|
|
150
|
+
req.on("data", (c) => {
|
|
151
|
+
total += c.length;
|
|
152
|
+
if (total > maxBytes) {
|
|
153
|
+
const err = new Error(`Request body too large (limit ${maxBytes} bytes)`);
|
|
154
|
+
err.name = "PayloadTooLarge";
|
|
155
|
+
reject(err);
|
|
156
|
+
req.destroy();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
chunks.push(c);
|
|
160
|
+
});
|
|
103
161
|
req.on("end", () => {
|
|
104
162
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
105
163
|
if (!raw)
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { z } from "zod";
|
|
5
5
|
import { MemoryStore } from "./store.js";
|
|
6
6
|
import { MemoryService } from "./service.js";
|
|
7
7
|
import { createHttpServer } from "./http.js";
|
|
8
|
+
import { DigestInput, storeInputShape, digestInputShape, searchInputShape, listInputShape, forgetInputShape, } from "./types.js";
|
|
8
9
|
const store = new MemoryStore(MemoryStore.defaultRoot());
|
|
9
10
|
const service = new MemoryService(store);
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
const httpFlag = argv.includes("--http");
|
|
13
|
+
const maintainFlag = argv.includes("maintain");
|
|
14
|
+
const portArg = argv.indexOf("--port");
|
|
15
|
+
const port = portArg !== -1 ? Number(argv[portArg + 1]) : undefined;
|
|
16
|
+
if (argv[0] === "export") {
|
|
17
|
+
// CLI backup: `remembra export <file.json>` — full snapshot incl. archived.
|
|
18
|
+
const out = argv[1];
|
|
19
|
+
if (!out) {
|
|
20
|
+
console.error("Usage: remembra export <file.json>");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const snapshot = await service.exportSnapshot();
|
|
24
|
+
await fs.writeFile(out, JSON.stringify(snapshot, null, 2), "utf8");
|
|
25
|
+
console.log(`Exported ${snapshot.memories.length} memories to ${out}`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
else if (argv[0] === "import") {
|
|
29
|
+
// CLI restore: `remembra import <file.json>` — validates whole file first,
|
|
30
|
+
// then skips existing ids/duplicates (idempotent re-import).
|
|
31
|
+
const input = argv[1];
|
|
32
|
+
if (!input) {
|
|
33
|
+
console.error("Usage: remembra import <file.json>");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
let data;
|
|
37
|
+
try {
|
|
38
|
+
data = JSON.parse(await fs.readFile(input, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`Cannot read snapshot ${input}: ${err instanceof Error ? err.message : err}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const result = await service.importSnapshot(data);
|
|
46
|
+
console.log(`Import: ${result.imported} imported, ${result.skipped} skipped`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.error(`Import rejected (nothing written): ${err instanceof Error ? err.message : err}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (maintainFlag) {
|
|
15
55
|
// CLI maintenance: `remembra maintain` — one-shot, prints JSON, exits.
|
|
16
56
|
const result = await service.maintain();
|
|
17
57
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -19,33 +59,31 @@ if (maintainFlag) {
|
|
|
19
59
|
}
|
|
20
60
|
else if (httpFlag) {
|
|
21
61
|
// HTTP mode: long-running API for non-MCP clients (ChatGPT, scripts, ...).
|
|
22
|
-
createHttpServer(service, {
|
|
62
|
+
const httpServer = createHttpServer(service, {
|
|
23
63
|
port,
|
|
24
64
|
apiKey: process.env.REMEMBRA_API_KEY,
|
|
25
65
|
});
|
|
66
|
+
// Graceful shutdown: stop accepting, drain in-flight requests, then exit.
|
|
67
|
+
const shutdown = (sig) => {
|
|
68
|
+
console.error(`Remembra: received ${sig}, shutting down`);
|
|
69
|
+
httpServer.close(() => process.exit(0));
|
|
70
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
71
|
+
};
|
|
72
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
73
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
26
74
|
}
|
|
27
75
|
else {
|
|
28
76
|
// MCP mode (default): stdio transport launched by an MCP client.
|
|
29
77
|
await startMcp();
|
|
30
78
|
}
|
|
31
79
|
async function startMcp() {
|
|
32
|
-
const server = new McpServer({ name: "remembra", version: "3.
|
|
80
|
+
const server = new McpServer({ name: "remembra", version: "3.2.0" });
|
|
33
81
|
server.registerTool("memory_store", {
|
|
34
82
|
title: "Store a memory",
|
|
35
83
|
description: "Persist a fact, decision, role or history entry so it survives context window resets. " +
|
|
36
84
|
"Use type 'fact' for stable knowledge, 'decision' for choices already made, " +
|
|
37
85
|
"'role' for standing instructions/roles, 'history' for condensed chronology of past work.",
|
|
38
|
-
inputSchema:
|
|
39
|
-
type: z.enum(["fact", "decision", "role", "history"]),
|
|
40
|
-
content: z.string().describe("The memory itself, written as a standalone statement"),
|
|
41
|
-
scope: z
|
|
42
|
-
.string()
|
|
43
|
-
.optional()
|
|
44
|
-
.describe("'global' for always-relevant memories, or a project path/id for project-scoped ones"),
|
|
45
|
-
tags: z.array(z.string()).optional(),
|
|
46
|
-
importance: z.number().int().min(1).max(5).optional().describe("1=minor, 5=critical (default 3)"),
|
|
47
|
-
source: z.string().optional().describe("Originating session or client"),
|
|
48
|
-
},
|
|
86
|
+
inputSchema: storeInputShape,
|
|
49
87
|
}, async (args) => {
|
|
50
88
|
const result = await service.store(args);
|
|
51
89
|
return { content: [{ type: "text", text: result.message }] };
|
|
@@ -55,15 +93,9 @@ async function startMcp() {
|
|
|
55
93
|
description: "Extract facts, decisions, roles and history from a conversation transcript and store " +
|
|
56
94
|
"them automatically (exact duplicates are skipped). Call at the end of a session with " +
|
|
57
95
|
"the transcript or a detailed summary of it. Requires REMEMBRA_LLM + an API key.",
|
|
58
|
-
inputSchema:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.describe("Conversation transcript or a detailed summary of the session"),
|
|
62
|
-
scope: z.string().optional().describe("Scope for extracted memories (default: global)"),
|
|
63
|
-
source: z.string().optional().describe("Originating session/client"),
|
|
64
|
-
},
|
|
65
|
-
}, async ({ transcript, scope, source }) => {
|
|
66
|
-
const result = await service.digest({ transcript, scope, source });
|
|
96
|
+
inputSchema: digestInputShape,
|
|
97
|
+
}, async (args) => {
|
|
98
|
+
const result = await service.digest(DigestInput.parse(args));
|
|
67
99
|
const text = `Digest complete: ${result.extracted} extracted, ${result.stored.length} stored, ` +
|
|
68
100
|
`${result.merged} merged/revived, ${result.skippedDuplicates} duplicates skipped.` +
|
|
69
101
|
(result.ids.length ? `\nStored ids: ${result.ids.join(", ")}` : "");
|
|
@@ -87,12 +119,7 @@ async function startMcp() {
|
|
|
87
119
|
title: "Search memories",
|
|
88
120
|
description: "Retrieve relevant memories from external storage. Call this at the start of a session " +
|
|
89
121
|
"(or whenever prior context might exist) to recover facts, decisions, roles and history.",
|
|
90
|
-
inputSchema:
|
|
91
|
-
query: z.string().optional().describe("Keywords to match (omit to get a scope/recency-ranked list)"),
|
|
92
|
-
scope: z.string().optional().describe("Current project path or workspace id to filter by"),
|
|
93
|
-
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
94
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
95
|
-
},
|
|
122
|
+
inputSchema: searchInputShape,
|
|
96
123
|
}, async (args) => {
|
|
97
124
|
const result = await service.search(args);
|
|
98
125
|
return { content: [{ type: "text", text: result.text }] };
|
|
@@ -100,10 +127,7 @@ async function startMcp() {
|
|
|
100
127
|
server.registerTool("memory_list", {
|
|
101
128
|
title: "List memories",
|
|
102
129
|
description: "List stored memories, optionally filtered by scope or type.",
|
|
103
|
-
inputSchema:
|
|
104
|
-
scope: z.string().optional(),
|
|
105
|
-
type: z.enum(["fact", "decision", "role", "history"]).optional(),
|
|
106
|
-
},
|
|
130
|
+
inputSchema: listInputShape,
|
|
107
131
|
}, async (args) => {
|
|
108
132
|
const result = await service.list(args);
|
|
109
133
|
return { content: [{ type: "text", text: result.text }] };
|
|
@@ -111,7 +135,7 @@ async function startMcp() {
|
|
|
111
135
|
server.registerTool("memory_forget", {
|
|
112
136
|
title: "Delete a memory",
|
|
113
137
|
description: "Permanently delete a memory by its id.",
|
|
114
|
-
inputSchema:
|
|
138
|
+
inputSchema: forgetInputShape,
|
|
115
139
|
}, async ({ id }) => {
|
|
116
140
|
const result = await service.forget(id);
|
|
117
141
|
return {
|
|
@@ -121,6 +145,8 @@ async function startMcp() {
|
|
|
121
145
|
});
|
|
122
146
|
const transport = new StdioServerTransport();
|
|
123
147
|
await server.connect(transport);
|
|
124
|
-
|
|
148
|
+
// Log hygiene (audit #7): filesystem paths only under REMEMBRA_DEBUG.
|
|
149
|
+
const rootNote = process.env.REMEMBRA_DEBUG ? ` (root: ${MemoryStore.defaultRoot()})` : "";
|
|
150
|
+
console.error(`Remembra MCP server running${rootNote}`);
|
|
125
151
|
}
|
|
126
152
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/service.d.ts
CHANGED
|
@@ -86,17 +86,41 @@ export declare class MemoryService {
|
|
|
86
86
|
* Session digest (v2) with contradiction-merge (v3):
|
|
87
87
|
* extract worth-keeping memories, skip exact duplicates, and let the LLM
|
|
88
88
|
* merge items that evolved from a stored memory (superseded text preserved).
|
|
89
|
+
*
|
|
90
|
+
* Serialized through a lock: simultaneous digests could otherwise both
|
|
91
|
+
* read the same active set and double-store duplicates.
|
|
89
92
|
*/
|
|
90
93
|
digest(opts: {
|
|
91
94
|
transcript: string;
|
|
92
95
|
scope?: string;
|
|
93
96
|
source?: string;
|
|
94
97
|
}): Promise<DigestResult>;
|
|
98
|
+
private digestLock;
|
|
99
|
+
private doDigest;
|
|
95
100
|
/**
|
|
96
101
|
* Explicit maintenance (decision v3-Q1): decay sweep + vector backfill.
|
|
97
102
|
* Exposed as the `memory_maintain` tool, POST /maintain, and the CLI.
|
|
103
|
+
*/ maintain(): Promise<MaintainResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Full snapshot for backup (audit #8): every memory incl. archived.
|
|
106
|
+
* Written by `remembra export <file>` as JSON.
|
|
107
|
+
*/
|
|
108
|
+
exportSnapshot(): Promise<{
|
|
109
|
+
format: string;
|
|
110
|
+
version: number;
|
|
111
|
+
exportedAt: string;
|
|
112
|
+
memories: Memory[];
|
|
113
|
+
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Restore from a snapshot (audit #8). All-or-nothing validation: the whole
|
|
116
|
+
* file is Zod-parsed before anything is written, so a corrupt/tampered
|
|
117
|
+
* snapshot can never half-import. Existing ids and exact duplicates are
|
|
118
|
+
* skipped, making re-import idempotent.
|
|
98
119
|
*/
|
|
99
|
-
|
|
120
|
+
importSnapshot(data: unknown): Promise<{
|
|
121
|
+
imported: number;
|
|
122
|
+
skipped: number;
|
|
123
|
+
}>;
|
|
100
124
|
/** Decay lifecycle: unused actives → archived → auto-deleted past TTL. */
|
|
101
125
|
private decayPass;
|
|
102
126
|
private maybeRunDecay;
|