@openacme/memory 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Utkarsh Kanwat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Stale-memory mitigation. CC `memdir/memoryAge.ts` lift. Human age
3
+ * strings (not ISO) because models are poor at date arithmetic — "47
4
+ * days ago" triggers staleness reasoning that a raw timestamp doesn't.
5
+ */
6
+ /** Floor-rounded days since mtime. Future mtimes clamp to 0. */
7
+ export declare function memoryAgeDays(mtimeMs: number): number;
8
+ export declare function memoryAge(mtimeMs: number): string;
9
+ /** Plain-text caveat for entries >1 day old. Recall blocks use this
10
+ * inside their own wrapper; standalone callers want `memoryFreshnessNote`. */
11
+ export declare function memoryFreshnessText(mtimeMs: number): string;
12
+ /** Wrapped variant for `view` of standalone entry files. */
13
+ export declare function memoryFreshnessNote(mtimeMs: number): string;
14
+ //# sourceMappingURL=freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;+EAC+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI3D"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Stale-memory mitigation. CC `memdir/memoryAge.ts` lift. Human age
3
+ * strings (not ISO) because models are poor at date arithmetic — "47
4
+ * days ago" triggers staleness reasoning that a raw timestamp doesn't.
5
+ */
6
+ const ONE_DAY_MS = 86_400_000;
7
+ /** Floor-rounded days since mtime. Future mtimes clamp to 0. */
8
+ export function memoryAgeDays(mtimeMs) {
9
+ return Math.max(0, Math.floor((Date.now() - mtimeMs) / ONE_DAY_MS));
10
+ }
11
+ export function memoryAge(mtimeMs) {
12
+ const d = memoryAgeDays(mtimeMs);
13
+ if (d === 0)
14
+ return "today";
15
+ if (d === 1)
16
+ return "yesterday";
17
+ return `${d} days ago`;
18
+ }
19
+ /** Plain-text caveat for entries >1 day old. Recall blocks use this
20
+ * inside their own wrapper; standalone callers want `memoryFreshnessNote`. */
21
+ export function memoryFreshnessText(mtimeMs) {
22
+ const d = memoryAgeDays(mtimeMs);
23
+ if (d <= 1)
24
+ return "";
25
+ return (`This memory is ${d} days old. ` +
26
+ `Memories are point-in-time observations, not live state — ` +
27
+ `claims about code behavior or file:line citations may be outdated. ` +
28
+ `Verify against current code before asserting as fact.`);
29
+ }
30
+ /** Wrapped variant for `view` of standalone entry files. */
31
+ export function memoryFreshnessNote(mtimeMs) {
32
+ const text = memoryFreshnessText(mtimeMs);
33
+ if (!text)
34
+ return "";
35
+ return `<system-reminder>${text}</system-reminder>\n`;
36
+ }
37
+ //# sourceMappingURL=freshness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.js","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IAChC,OAAO,GAAG,CAAC,WAAW,CAAC;AACzB,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,CACL,kBAAkB,CAAC,aAAa;QAChC,4DAA4D;QAC5D,qEAAqE;QACrE,uDAAuD,CACxD,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,oBAAoB,IAAI,sBAAsB,CAAC;AACxD,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { MemoryStore, DEFAULT_MEMORY_CHAR_LIMIT, type IndexSnapshot, } from "./store.js";
2
+ export { memoryAge, memoryAgeDays, memoryFreshnessNote, memoryFreshnessText, } from "./freshness.js";
3
+ export { scanMemoryFiles, formatMemoryManifest, parseFrontmatterDescription, type MemoryHeader, } from "./scan.js";
4
+ export { scanMemoryContent, type ScanResult } from "./threat-scanner.js";
5
+ export { MEMORY_THREAT_PATTERNS, INVISIBLE_CHARS } from "./threat-patterns.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,yBAAyB,EACzB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEzE,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { MemoryStore, DEFAULT_MEMORY_CHAR_LIMIT, } from "./store.js";
2
+ export { memoryAge, memoryAgeDays, memoryFreshnessNote, memoryFreshnessText, } from "./freshness.js";
3
+ export { scanMemoryFiles, formatMemoryManifest, parseFrontmatterDescription, } from "./scan.js";
4
+ export { scanMemoryContent } from "./threat-scanner.js";
5
+ export { MEMORY_THREAT_PATTERNS, INVISIBLE_CHARS } from "./threat-patterns.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,yBAAyB,GAE1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,2BAA2B,GAE5B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAmB,MAAM,qBAAqB,CAAC;AAEzE,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/scan.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Memory-directory scan + manifest formatting. Shared by the recall
3
+ * selector and the extractor. CC `memdir/memoryScan.ts` lift; we drop
4
+ * the type taxonomy and parse `description` via a tight regex (keeps
5
+ * @openacme/memory dependency-free).
6
+ */
7
+ export interface MemoryHeader {
8
+ /** Path relative to memoryDir. */
9
+ filename: string;
10
+ /** Absolute filesystem path. */
11
+ filePath: string;
12
+ mtimeMs: number;
13
+ description: string | null;
14
+ }
15
+ declare function readHead(filePath: string, maxLines: number): Promise<string>;
16
+ /** Pull the `description` field out of YAML frontmatter, if present. */
17
+ export declare function parseFrontmatterDescription(head: string): string | null;
18
+ /** Recursive scan, sorted newest-first, capped at MAX_MEMORY_FILES.
19
+ * Best-effort: per-file read failures dropped silently. */
20
+ export declare function scanMemoryFiles(memoryDir: string, signal?: AbortSignal): Promise<MemoryHeader[]>;
21
+ /** Format: `- <filename> (<iso-ts>): <description?>` per line. */
22
+ export declare function formatMemoryManifest(headers: MemoryHeader[]): string;
23
+ export declare const __test: {
24
+ readHead: typeof readHead;
25
+ MAX_MEMORY_FILES: number;
26
+ FRONTMATTER_MAX_LINES: number;
27
+ };
28
+ export {};
29
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,iBAAe,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B3E;AAED,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUvE;AAED;4DAC4D;AAC5D,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,EAAE,CAAC,CAuCzB;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CASpE;AAED,eAAO,MAAM,MAAM;;;;CAAwD,CAAC"}
package/dist/scan.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Memory-directory scan + manifest formatting. Shared by the recall
3
+ * selector and the extractor. CC `memdir/memoryScan.ts` lift; we drop
4
+ * the type taxonomy and parse `description` via a tight regex (keeps
5
+ * @openacme/memory dependency-free).
6
+ */
7
+ import { readdir, stat } from "node:fs/promises";
8
+ import { basename, join } from "node:path";
9
+ import { createReadStream } from "node:fs";
10
+ const MAX_MEMORY_FILES = 200;
11
+ const FRONTMATTER_MAX_LINES = 30;
12
+ const INDEX_FILE = "MEMORY.md";
13
+ async function readHead(filePath, maxLines) {
14
+ return new Promise((resolve, reject) => {
15
+ const chunks = [];
16
+ let lines = 0;
17
+ let buf = "";
18
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
19
+ let settled = false;
20
+ const finish = (s) => {
21
+ if (settled)
22
+ return;
23
+ settled = true;
24
+ stream.destroy();
25
+ resolve(s);
26
+ };
27
+ stream.on("data", (chunk) => {
28
+ buf += chunk;
29
+ let idx;
30
+ while ((idx = buf.indexOf("\n")) >= 0 && lines < maxLines) {
31
+ chunks.push(buf.slice(0, idx + 1));
32
+ buf = buf.slice(idx + 1);
33
+ lines++;
34
+ }
35
+ if (lines >= maxLines)
36
+ finish(chunks.join(""));
37
+ });
38
+ stream.on("end", () => finish(chunks.join("") + buf));
39
+ stream.on("error", (e) => {
40
+ if (settled)
41
+ return;
42
+ settled = true;
43
+ reject(e);
44
+ });
45
+ });
46
+ }
47
+ /** Pull the `description` field out of YAML frontmatter, if present. */
48
+ export function parseFrontmatterDescription(head) {
49
+ if (!head.startsWith("---"))
50
+ return null;
51
+ const close = head.indexOf("\n---", 3);
52
+ if (close < 0)
53
+ return null;
54
+ const block = head.slice(3, close);
55
+ // Tolerate optional surrounding quotes.
56
+ const m = /^description:\s*(?:"([^"]*)"|'([^']*)'|(.*))\s*$/m.exec(block);
57
+ if (!m)
58
+ return null;
59
+ const v = (m[1] ?? m[2] ?? m[3] ?? "").trim();
60
+ return v.length > 0 ? v : null;
61
+ }
62
+ /** Recursive scan, sorted newest-first, capped at MAX_MEMORY_FILES.
63
+ * Best-effort: per-file read failures dropped silently. */
64
+ export async function scanMemoryFiles(memoryDir, signal) {
65
+ let entries;
66
+ try {
67
+ entries = await readdir(memoryDir, { recursive: true });
68
+ }
69
+ catch {
70
+ return [];
71
+ }
72
+ if (signal?.aborted)
73
+ return [];
74
+ const candidates = entries.filter((f) => f.endsWith(".md") &&
75
+ basename(f) !== INDEX_FILE &&
76
+ !f.split("/").some((seg) => seg.startsWith(".")));
77
+ const results = await Promise.allSettled(candidates.map(async (rel) => {
78
+ const filePath = join(memoryDir, rel);
79
+ const [head, st] = await Promise.all([
80
+ readHead(filePath, FRONTMATTER_MAX_LINES),
81
+ stat(filePath),
82
+ ]);
83
+ return {
84
+ filename: rel,
85
+ filePath,
86
+ mtimeMs: st.mtimeMs,
87
+ description: parseFrontmatterDescription(head),
88
+ };
89
+ }));
90
+ return results
91
+ .filter((r) => r.status === "fulfilled")
92
+ .map((r) => r.value)
93
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)
94
+ .slice(0, MAX_MEMORY_FILES);
95
+ }
96
+ /** Format: `- <filename> (<iso-ts>): <description?>` per line. */
97
+ export function formatMemoryManifest(headers) {
98
+ return headers
99
+ .map((m) => {
100
+ const ts = new Date(m.mtimeMs).toISOString();
101
+ return m.description
102
+ ? `- ${m.filename} (${ts}): ${m.description}`
103
+ : `- ${m.filename} (${ts})`;
104
+ })
105
+ .join("\n");
106
+ }
107
+ export const __test = { readHead, MAX_MEMORY_FILES, FRONTMATTER_MAX_LINES };
108
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Per-agent persistent memory store — directory-backed, six-op shape
3
+ * matching Anthropic's `memory_20250818` tool spec.
4
+ *
5
+ * Layout per agent:
6
+ *
7
+ * <agentsDir>/<agentId>/memory/
8
+ * MEMORY.md ← always-injected index of one-line pointers
9
+ * <topic>.md ← agent-created entry files (read on demand)
10
+ * <subdir>/<topic>.md ← agent-organized; we don't enforce layout
11
+ *
12
+ * Tool calls reference paths in the agent's virtual view: `/memories/...`.
13
+ * The store translates to filesystem paths internally and rejects anything
14
+ * that escapes the per-agent root (path-traversal protection per Anthropic
15
+ * security note).
16
+ *
17
+ * Concurrency: per-agent in-process async mutex serializes
18
+ * read-modify-write. Cross-process safety is NOT provided — atomic rename
19
+ * keeps readers consistent, and we assume one process owns a given dataDir
20
+ * at a time.
21
+ *
22
+ * Caps:
23
+ * - Per-file line cap = 999,999 (Anthropic spec verbatim).
24
+ * - MEMORY.md index has a write-time char cap (`memoryCharLimit`,
25
+ * default 2200 — Hermes default). Writes that would push it over
26
+ * return OpenAcme's existing guidance string ("Memory at X/Y
27
+ * chars. ...Replace or remove existing entries first.") so the
28
+ * agent has consolidation pressure rather than infinite append.
29
+ * - Per-entry files have NO char cap (they're not auto-injected;
30
+ * only the per-file line cap applies).
31
+ *
32
+ * Return strings: each op returns the EXACT string Anthropic specifies
33
+ * (line-number format, directory listing format, error wording). Models
34
+ * are trained against those literals; matching them is what makes
35
+ * `memory_20250818`-trained behavior carry over without prompt
36
+ * engineering.
37
+ */
38
+ /** Hermes default. Single source of truth for the index char cap. */
39
+ export declare const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
40
+ export interface IndexSnapshot {
41
+ /** Raw `MEMORY.md` content, untruncated. Empty string if the file is
42
+ * missing or empty. */
43
+ content: string;
44
+ /** Byte length of `content`. */
45
+ used: number;
46
+ /** Configured char cap (write-time). */
47
+ limit: number;
48
+ /** Number of `.md` files under `<agentDir>/memory/` excluding
49
+ * `MEMORY.md` itself. Used by prompt builder to decide when to append
50
+ * Anthropic's "keep your memory folder organized" instruction. */
51
+ entryCount: number;
52
+ }
53
+ declare function formatBytes(n: number): string;
54
+ /** Format a 1-indexed line number in 6-char right-aligned form (Anthropic). */
55
+ declare function fmtLineNo(n: number): string;
56
+ /** Render file contents with Anthropic's line-number format. */
57
+ declare function withLineNumbers(content: string): string;
58
+ /**
59
+ * Per-agent memory store. One instance per `agentsDir` — multiple
60
+ * instances with different roots get independent mutex maps, which
61
+ * matters for tests and multi-tenant deployments.
62
+ */
63
+ export declare class MemoryStore {
64
+ readonly agentsDir: string;
65
+ private readonly inFlight;
66
+ constructor(agentsDir: string);
67
+ /** Absolute path to an agent's memory directory. Does not create it. */
68
+ dirPath(agentId: string): string;
69
+ /** Absolute path to the index file for an agent. Does not create it. */
70
+ indexPath(agentId: string): string;
71
+ /**
72
+ * Translate a virtual `/memories/...` path (as the agent sees it) to
73
+ * an absolute filesystem path under `<agentsDir>/<agentId>/memory/`.
74
+ * Rejects:
75
+ * - Paths not starting with `/memories`
76
+ * - URL-encoded traversal sequences (`%2e%2e%2f`, etc.)
77
+ * - Resolved paths that escape the per-agent root
78
+ *
79
+ * Returns the absolute path on success, or an error string in the
80
+ * same wording Anthropic uses for "path does not exist" so the model
81
+ * sees a familiar shape.
82
+ */
83
+ private translatePath;
84
+ /** True if the virtual path points at the index file specifically. */
85
+ private isIndexPath;
86
+ /**
87
+ * Snapshot of the agent's index for system-prompt injection.
88
+ * Synchronous; safe for render paths. Counts entry files for the
89
+ * prompt builder's "cluttered memory" trigger.
90
+ */
91
+ readIndex(agentId: string, charLimit: number): IndexSnapshot;
92
+ /**
93
+ * `view` — show directory contents (2 levels deep) or file contents
94
+ * with optional line range. For entry files (anything other than
95
+ * `MEMORY.md`) older than 1 day, prepends `memoryFreshnessNote`.
96
+ *
97
+ * Synchronous; no mutex needed (read-only).
98
+ */
99
+ view(agentId: string, virtualPath: string, viewRange?: readonly [number, number]): string;
100
+ /**
101
+ * Directory listing in Anthropic's format, adapted for OpenAcme:
102
+ * the `node_modules` exclusion clause from the upstream spec is
103
+ * dropped because OpenAcme's memory dir can never contain one (no
104
+ * shell access, no package manager runs against it). We still
105
+ * exclude hidden dotfiles so internal artifacts (future
106
+ * coordination locks, sidecar state) stay out of the agent's view.
107
+ */
108
+ private viewDirectory;
109
+ /**
110
+ * `create` — create a new file. Errors if a file already exists at
111
+ * the target path. For writes targeting `MEMORY.md`, enforces the
112
+ * write-time char cap with OpenAcme's guidance string. For all
113
+ * files, enforces the 999,999-line cap.
114
+ */
115
+ create(agentId: string, virtualPath: string, fileText: string, indexCharLimit?: number): Promise<string>;
116
+ /**
117
+ * `str_replace` — replace `oldStr` with `newStr` in a file. Errors
118
+ * on missing file, missing match, or multiple matches (Anthropic's
119
+ * exact wording).
120
+ */
121
+ strReplace(agentId: string, virtualPath: string, oldStr: string, newStr: string, indexCharLimit?: number): Promise<string>;
122
+ /**
123
+ * `insert` — insert text at a specific line in a file. `insertLine` is
124
+ * 0-indexed in Anthropic's spec (0 = before first line, N = after last).
125
+ */
126
+ insert(agentId: string, virtualPath: string, insertLine: number, insertText: string, indexCharLimit?: number): Promise<string>;
127
+ /**
128
+ * `delete` — recursively delete a file or directory. Anthropic's spec
129
+ * says "Deletes the directory and all its contents recursively" so
130
+ * we use `rm -rf` semantics for directories.
131
+ */
132
+ delete(agentId: string, virtualPath: string): Promise<string>;
133
+ /**
134
+ * `rename` — move or rename a file/directory. Errors if source is
135
+ * missing or destination already exists (no overwrite).
136
+ */
137
+ rename(agentId: string, oldVirtualPath: string, newVirtualPath: string): Promise<string>;
138
+ /** Atomic write — tmp file + fsync + rename. */
139
+ private writeFileAtomic;
140
+ /**
141
+ * Per-agent mutex. Map stores only the settle-completion of each
142
+ * outstanding write, never its rejection — so a thrown error inside
143
+ * one writer can't poison the chain for the next.
144
+ */
145
+ private withMutex;
146
+ }
147
+ export declare const __test: {
148
+ withLineNumbers: typeof withLineNumbers;
149
+ formatBytes: typeof formatBytes;
150
+ fmtLineNo: typeof fmtLineNo;
151
+ };
152
+ export {};
153
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAmBH,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAQ9C,MAAM,WAAW,aAAa;IAC5B;4BACwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd;;uEAEmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,iBAAS,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtC;AAED,+EAA+E;AAC/E,iBAAS,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpC;AAED,gEAAgE;AAChE,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGhD;AAiCD;;;;GAIG;AACH,qBAAa,WAAW;IAGV,QAAQ,CAAC,SAAS,EAAE,MAAM;IAFtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;gBAExC,SAAS,EAAE,MAAM;IAItC,wEAAwE;IACxE,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAShC,wEAAwE;IACxE,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IA8CrB,sEAAsE;IACtE,OAAO,CAAC,WAAW;IASnB;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa;IAiC5D;;;;;;OAMG;IACH,IAAI,CACF,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM;IAoET;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA2DrB;;;;;OAKG;IACG,MAAM,CACV,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC;IAqClB;;;;OAIG;IACG,UAAU,CACd,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC;IAkElB;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC;IAwDlB;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BnE;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC;IA6BlB,gDAAgD;YAClC,eAAe;IAiC7B;;;;OAIG;YACW,SAAS;CAYxB;AAGD,eAAO,MAAM,MAAM;;;;CAA8C,CAAC"}
package/dist/store.js ADDED
@@ -0,0 +1,620 @@
1
+ /**
2
+ * Per-agent persistent memory store — directory-backed, six-op shape
3
+ * matching Anthropic's `memory_20250818` tool spec.
4
+ *
5
+ * Layout per agent:
6
+ *
7
+ * <agentsDir>/<agentId>/memory/
8
+ * MEMORY.md ← always-injected index of one-line pointers
9
+ * <topic>.md ← agent-created entry files (read on demand)
10
+ * <subdir>/<topic>.md ← agent-organized; we don't enforce layout
11
+ *
12
+ * Tool calls reference paths in the agent's virtual view: `/memories/...`.
13
+ * The store translates to filesystem paths internally and rejects anything
14
+ * that escapes the per-agent root (path-traversal protection per Anthropic
15
+ * security note).
16
+ *
17
+ * Concurrency: per-agent in-process async mutex serializes
18
+ * read-modify-write. Cross-process safety is NOT provided — atomic rename
19
+ * keeps readers consistent, and we assume one process owns a given dataDir
20
+ * at a time.
21
+ *
22
+ * Caps:
23
+ * - Per-file line cap = 999,999 (Anthropic spec verbatim).
24
+ * - MEMORY.md index has a write-time char cap (`memoryCharLimit`,
25
+ * default 2200 — Hermes default). Writes that would push it over
26
+ * return OpenAcme's existing guidance string ("Memory at X/Y
27
+ * chars. ...Replace or remove existing entries first.") so the
28
+ * agent has consolidation pressure rather than infinite append.
29
+ * - Per-entry files have NO char cap (they're not auto-injected;
30
+ * only the per-file line cap applies).
31
+ *
32
+ * Return strings: each op returns the EXACT string Anthropic specifies
33
+ * (line-number format, directory listing format, error wording). Models
34
+ * are trained against those literals; matching them is what makes
35
+ * `memory_20250818`-trained behavior carry over without prompt
36
+ * engineering.
37
+ */
38
+ import { randomBytes } from "node:crypto";
39
+ import * as fs from "node:fs";
40
+ import * as fsp from "node:fs/promises";
41
+ import * as path from "node:path";
42
+ import { memoryFreshnessNote } from "./freshness.js";
43
+ // ── Constants ──────────────────────────────────────────────────────────
44
+ const MEMORIES_ROOT = "/memories";
45
+ const MEMORY_DIR = "memory";
46
+ const INDEX_FILE = "MEMORY.md";
47
+ const TMP_PREFIX = ".mem_";
48
+ /** Per-file line cap (Anthropic spec). */
49
+ const MAX_FILE_LINES = 999_999;
50
+ /** Hermes default. Single source of truth for the index char cap. */
51
+ export const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
52
+ /** Same constraint agent-store uses; duplicated here so this module stays
53
+ * independent of `@openacme/config`. */
54
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
55
+ // ── Pure helpers ───────────────────────────────────────────────────────
56
+ function formatBytes(n) {
57
+ if (n < 1024)
58
+ return `${n}`;
59
+ if (n < 1024 * 1024)
60
+ return `${(n / 1024).toFixed(1)}K`;
61
+ if (n < 1024 * 1024 * 1024)
62
+ return `${(n / 1024 / 1024).toFixed(1)}M`;
63
+ return `${(n / 1024 / 1024 / 1024).toFixed(1)}G`;
64
+ }
65
+ /** Format a 1-indexed line number in 6-char right-aligned form (Anthropic). */
66
+ function fmtLineNo(n) {
67
+ return String(n).padStart(6, " ");
68
+ }
69
+ /** Render file contents with Anthropic's line-number format. */
70
+ function withLineNumbers(content) {
71
+ const lines = content.length === 0 ? [""] : content.split("\n");
72
+ return lines.map((line, i) => `${fmtLineNo(i + 1)}\t${line}`).join("\n");
73
+ }
74
+ /** Find ALL line numbers (1-indexed) where `needle` appears in `haystack`. */
75
+ function findOccurrenceLines(haystack, needle) {
76
+ const lines = [];
77
+ let from = 0;
78
+ while (true) {
79
+ const idx = haystack.indexOf(needle, from);
80
+ if (idx < 0)
81
+ break;
82
+ // 1-indexed line number of the occurrence start
83
+ const lineNo = haystack.slice(0, idx).split("\n").length;
84
+ lines.push(lineNo);
85
+ from = idx + needle.length;
86
+ }
87
+ return lines;
88
+ }
89
+ /** Count occurrences of `needle` in `haystack`. */
90
+ function countOccurrences(haystack, needle) {
91
+ if (needle.length === 0)
92
+ return 0;
93
+ let count = 0;
94
+ let from = 0;
95
+ while (true) {
96
+ const idx = haystack.indexOf(needle, from);
97
+ if (idx < 0)
98
+ break;
99
+ count++;
100
+ from = idx + needle.length;
101
+ }
102
+ return count;
103
+ }
104
+ // ── MemoryStore ────────────────────────────────────────────────────────
105
+ /**
106
+ * Per-agent memory store. One instance per `agentsDir` — multiple
107
+ * instances with different roots get independent mutex maps, which
108
+ * matters for tests and multi-tenant deployments.
109
+ */
110
+ export class MemoryStore {
111
+ agentsDir;
112
+ inFlight = new Map();
113
+ constructor(agentsDir) {
114
+ this.agentsDir = agentsDir;
115
+ }
116
+ // ── Path helpers ─────────────────────────────────────────────────────
117
+ /** Absolute path to an agent's memory directory. Does not create it. */
118
+ dirPath(agentId) {
119
+ if (!SAFE_ID.test(agentId)) {
120
+ throw new Error(`Invalid agent id ${JSON.stringify(agentId)}: must match ${SAFE_ID}`);
121
+ }
122
+ return path.join(this.agentsDir, agentId, MEMORY_DIR);
123
+ }
124
+ /** Absolute path to the index file for an agent. Does not create it. */
125
+ indexPath(agentId) {
126
+ return path.join(this.dirPath(agentId), INDEX_FILE);
127
+ }
128
+ /**
129
+ * Translate a virtual `/memories/...` path (as the agent sees it) to
130
+ * an absolute filesystem path under `<agentsDir>/<agentId>/memory/`.
131
+ * Rejects:
132
+ * - Paths not starting with `/memories`
133
+ * - URL-encoded traversal sequences (`%2e%2e%2f`, etc.)
134
+ * - Resolved paths that escape the per-agent root
135
+ *
136
+ * Returns the absolute path on success, or an error string in the
137
+ * same wording Anthropic uses for "path does not exist" so the model
138
+ * sees a familiar shape.
139
+ */
140
+ translatePath(agentId, virtualPath) {
141
+ if (typeof virtualPath !== "string" || virtualPath.length === 0) {
142
+ return {
143
+ ok: false,
144
+ error: `The path ${virtualPath} does not exist. Please provide a valid path.`,
145
+ };
146
+ }
147
+ // Reject URL-encoded traversal before any normalization
148
+ const lowered = virtualPath.toLowerCase();
149
+ if (lowered.includes("%2e") || lowered.includes("%2f") || lowered.includes("%5c")) {
150
+ return {
151
+ ok: false,
152
+ error: `The path ${virtualPath} does not exist. Please provide a valid path.`,
153
+ };
154
+ }
155
+ if (!virtualPath.startsWith(MEMORIES_ROOT)) {
156
+ return {
157
+ ok: false,
158
+ error: `The path ${virtualPath} does not exist. Please provide a valid path.`,
159
+ };
160
+ }
161
+ // Strip /memories prefix; remainder may be "" (root), "/foo", "/foo/bar.md"
162
+ const remainder = virtualPath.slice(MEMORIES_ROOT.length);
163
+ if (remainder.length > 0 && !remainder.startsWith("/")) {
164
+ // e.g. /memoriesfoo — not actually under /memories
165
+ return {
166
+ ok: false,
167
+ error: `The path ${virtualPath} does not exist. Please provide a valid path.`,
168
+ };
169
+ }
170
+ const root = this.dirPath(agentId);
171
+ const abs = path.resolve(root, "." + remainder);
172
+ // Defense in depth: ensure the resolved path is still under root.
173
+ const rel = path.relative(root, abs);
174
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
175
+ return {
176
+ ok: false,
177
+ error: `The path ${virtualPath} does not exist. Please provide a valid path.`,
178
+ };
179
+ }
180
+ return { ok: true, abs };
181
+ }
182
+ /** True if the virtual path points at the index file specifically. */
183
+ isIndexPath(virtualPath) {
184
+ return (virtualPath === `${MEMORIES_ROOT}/${INDEX_FILE}` ||
185
+ virtualPath === `${MEMORIES_ROOT}/${INDEX_FILE}/`);
186
+ }
187
+ // ── Index accessor (for prompt builder) ──────────────────────────────
188
+ /**
189
+ * Snapshot of the agent's index for system-prompt injection.
190
+ * Synchronous; safe for render paths. Counts entry files for the
191
+ * prompt builder's "cluttered memory" trigger.
192
+ */
193
+ readIndex(agentId, charLimit) {
194
+ const indexFile = this.indexPath(agentId);
195
+ let content = "";
196
+ try {
197
+ content = fs.readFileSync(indexFile, "utf-8");
198
+ }
199
+ catch (e) {
200
+ if (e.code !== "ENOENT")
201
+ throw e;
202
+ }
203
+ const trimmed = content.trim();
204
+ const used = trimmed.length;
205
+ // Walk the dir for entry files (non-recursive — rare but possible
206
+ // sub-dirs are counted under the prompt-builder's "cluttered" cap
207
+ // via reading the same scan elsewhere; for the simple count signal
208
+ // a flat enumeration is enough).
209
+ let entryCount = 0;
210
+ const dir = this.dirPath(agentId);
211
+ try {
212
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
213
+ for (const e of entries) {
214
+ if (e.isFile() && e.name.endsWith(".md") && e.name !== INDEX_FILE) {
215
+ entryCount++;
216
+ }
217
+ }
218
+ }
219
+ catch (e) {
220
+ if (e.code !== "ENOENT")
221
+ throw e;
222
+ }
223
+ return { content: trimmed, used, limit: charLimit, entryCount };
224
+ }
225
+ // ── view ─────────────────────────────────────────────────────────────
226
+ /**
227
+ * `view` — show directory contents (2 levels deep) or file contents
228
+ * with optional line range. For entry files (anything other than
229
+ * `MEMORY.md`) older than 1 day, prepends `memoryFreshnessNote`.
230
+ *
231
+ * Synchronous; no mutex needed (read-only).
232
+ */
233
+ view(agentId, virtualPath, viewRange) {
234
+ const t = this.translatePath(agentId, virtualPath);
235
+ if (!t.ok)
236
+ return t.error;
237
+ const abs = t.abs;
238
+ // Special case: viewing the root `/memories` of an agent that hasn't
239
+ // touched memory yet. Return the empty-listing form rather than
240
+ // "does not exist" — matches Anthropic's example (the header is
241
+ // emitted even for empty memory dirs) and matches the agent's
242
+ // mental model that the dir always exists.
243
+ const isRoot = virtualPath === MEMORIES_ROOT || virtualPath === `${MEMORIES_ROOT}/`;
244
+ let stat;
245
+ try {
246
+ stat = fs.statSync(abs);
247
+ }
248
+ catch (e) {
249
+ if (e.code === "ENOENT") {
250
+ if (isRoot)
251
+ return this.viewDirectory(agentId, virtualPath, abs);
252
+ return `The path ${virtualPath} does not exist. Please provide a valid path.`;
253
+ }
254
+ throw e;
255
+ }
256
+ if (stat.isDirectory()) {
257
+ return this.viewDirectory(agentId, virtualPath, abs);
258
+ }
259
+ // File path. Read content; honor optional view_range.
260
+ let raw;
261
+ try {
262
+ raw = fs.readFileSync(abs, "utf-8");
263
+ }
264
+ catch (e) {
265
+ if (e.code === "ENOENT") {
266
+ return `The path ${virtualPath} does not exist. Please provide a valid path.`;
267
+ }
268
+ throw e;
269
+ }
270
+ const lines = raw.split("\n");
271
+ if (lines.length > MAX_FILE_LINES) {
272
+ return `File ${virtualPath} exceeds maximum line limit of 999,999 lines.`;
273
+ }
274
+ let displayLines = lines;
275
+ let startOffset = 0;
276
+ if (viewRange) {
277
+ const [from, to] = viewRange;
278
+ const start = Math.max(1, from) - 1;
279
+ const end = to <= 0 ? lines.length : Math.min(lines.length, to);
280
+ displayLines = lines.slice(start, end);
281
+ startOffset = start;
282
+ }
283
+ const numbered = displayLines
284
+ .map((line, i) => `${fmtLineNo(i + 1 + startOffset)}\t${line}`)
285
+ .join("\n");
286
+ const header = `Here's the content of ${virtualPath} with line numbers:`;
287
+ let out = `${header}\n${numbered}`;
288
+ // Freshness wrap for entry files (not the index).
289
+ if (!this.isIndexPath(virtualPath)) {
290
+ const note = memoryFreshnessNote(stat.mtimeMs);
291
+ if (note.length > 0)
292
+ out = note + out;
293
+ }
294
+ return out;
295
+ }
296
+ /**
297
+ * Directory listing in Anthropic's format, adapted for OpenAcme:
298
+ * the `node_modules` exclusion clause from the upstream spec is
299
+ * dropped because OpenAcme's memory dir can never contain one (no
300
+ * shell access, no package manager runs against it). We still
301
+ * exclude hidden dotfiles so internal artifacts (future
302
+ * coordination locks, sidecar state) stay out of the agent's view.
303
+ */
304
+ viewDirectory(agentId, virtualPath, absRoot) {
305
+ const lines = [
306
+ `Here're the files and directories up to 2 levels deep in ${virtualPath}, excluding hidden items:`,
307
+ ];
308
+ // Root entry — total size of contents at depth ≤ 2.
309
+ let rootBytes = 0;
310
+ const out = [];
311
+ const collect = (currentAbs, currentVirtual, depth) => {
312
+ if (depth > 2)
313
+ return;
314
+ let dirents;
315
+ try {
316
+ dirents = fs.readdirSync(currentAbs, { withFileTypes: true });
317
+ }
318
+ catch {
319
+ return;
320
+ }
321
+ for (const d of dirents) {
322
+ if (d.name.startsWith("."))
323
+ continue;
324
+ const childAbs = path.join(currentAbs, d.name);
325
+ const childVirtual = `${currentVirtual}/${d.name}`;
326
+ if (d.isDirectory()) {
327
+ out.push({ virtual: childVirtual, bytes: 0 });
328
+ collect(childAbs, childVirtual, depth + 1);
329
+ }
330
+ else if (d.isFile()) {
331
+ let size = 0;
332
+ try {
333
+ size = fs.statSync(childAbs).size;
334
+ }
335
+ catch {
336
+ // ignore — listing should not fail because one file vanished
337
+ }
338
+ out.push({ virtual: childVirtual, bytes: size });
339
+ rootBytes += size;
340
+ }
341
+ }
342
+ };
343
+ // Ensure the dir exists; if not, fabricate an empty listing rather
344
+ // than erroring (Anthropic's example shows the root header even when
345
+ // there are no children — empty memory dir is normal).
346
+ if (fs.existsSync(absRoot)) {
347
+ collect(absRoot, virtualPath.replace(/\/$/, ""), 1);
348
+ }
349
+ lines.push(`${formatBytes(rootBytes)}\t${virtualPath.replace(/\/$/, "")}`);
350
+ for (const e of out) {
351
+ lines.push(`${formatBytes(e.bytes)}\t${e.virtual}`);
352
+ }
353
+ return lines.join("\n");
354
+ }
355
+ // ── create ───────────────────────────────────────────────────────────
356
+ /**
357
+ * `create` — create a new file. Errors if a file already exists at
358
+ * the target path. For writes targeting `MEMORY.md`, enforces the
359
+ * write-time char cap with OpenAcme's guidance string. For all
360
+ * files, enforces the 999,999-line cap.
361
+ */
362
+ async create(agentId, virtualPath, fileText, indexCharLimit) {
363
+ const t = this.translatePath(agentId, virtualPath);
364
+ if (!t.ok)
365
+ return t.error;
366
+ const abs = t.abs;
367
+ return this.withMutex(agentId, async () => {
368
+ // Existence check
369
+ try {
370
+ await fsp.stat(abs);
371
+ return `Error: File ${virtualPath} already exists`;
372
+ }
373
+ catch (e) {
374
+ if (e.code !== "ENOENT")
375
+ throw e;
376
+ }
377
+ const lineCount = fileText.split("\n").length;
378
+ if (lineCount > MAX_FILE_LINES) {
379
+ return `File ${virtualPath} exceeds maximum line limit of 999,999 lines.`;
380
+ }
381
+ // Write-time cap on MEMORY.md
382
+ if (this.isIndexPath(virtualPath) && typeof indexCharLimit === "number") {
383
+ if (fileText.trim().length > indexCharLimit) {
384
+ return (`Memory at 0/${indexCharLimit} chars. Adding this entry ` +
385
+ `(${fileText.trim().length} chars) would exceed the limit. ` +
386
+ `Replace or remove existing entries first.`);
387
+ }
388
+ }
389
+ await this.writeFileAtomic(abs, fileText);
390
+ return `File created successfully at: ${virtualPath}`;
391
+ });
392
+ }
393
+ // ── str_replace ──────────────────────────────────────────────────────
394
+ /**
395
+ * `str_replace` — replace `oldStr` with `newStr` in a file. Errors
396
+ * on missing file, missing match, or multiple matches (Anthropic's
397
+ * exact wording).
398
+ */
399
+ async strReplace(agentId, virtualPath, oldStr, newStr, indexCharLimit) {
400
+ const t = this.translatePath(agentId, virtualPath);
401
+ if (!t.ok)
402
+ return `Error: ${t.error}`;
403
+ const abs = t.abs;
404
+ return this.withMutex(agentId, async () => {
405
+ let stat;
406
+ try {
407
+ stat = await fsp.stat(abs);
408
+ }
409
+ catch (e) {
410
+ if (e.code === "ENOENT") {
411
+ return `Error: The path ${virtualPath} does not exist. Please provide a valid path.`;
412
+ }
413
+ throw e;
414
+ }
415
+ // Directory passed → "file does not exist" error per Anthropic spec
416
+ if (stat.isDirectory()) {
417
+ return `Error: The path ${virtualPath} does not exist. Please provide a valid path.`;
418
+ }
419
+ const content = await fsp.readFile(abs, "utf-8");
420
+ const occurrences = countOccurrences(content, oldStr);
421
+ if (occurrences === 0) {
422
+ return `No replacement was performed, old_str \`${oldStr}\` did not appear verbatim in ${virtualPath}.`;
423
+ }
424
+ if (occurrences > 1) {
425
+ const lineNos = findOccurrenceLines(content, oldStr);
426
+ return `No replacement was performed. Multiple occurrences of old_str \`${oldStr}\` in lines: ${lineNos.join(", ")}. Please ensure it is unique`;
427
+ }
428
+ const next = content.replace(oldStr, newStr);
429
+ // Line-cap check on the result
430
+ if (next.split("\n").length > MAX_FILE_LINES) {
431
+ return `File ${virtualPath} exceeds maximum line limit of 999,999 lines.`;
432
+ }
433
+ // Write-time cap on MEMORY.md
434
+ if (this.isIndexPath(virtualPath) && typeof indexCharLimit === "number") {
435
+ const newLen = next.trim().length;
436
+ const oldLen = content.trim().length;
437
+ if (newLen > indexCharLimit) {
438
+ return (`Memory at ${oldLen}/${indexCharLimit} chars. Replacement would push it to ` +
439
+ `${newLen}/${indexCharLimit}. Shorten or remove other entries first.`);
440
+ }
441
+ }
442
+ await this.writeFileAtomic(abs, next);
443
+ // Return success + a snippet-with-line-numbers per Anthropic spec
444
+ const snippetLineNo = content.slice(0, content.indexOf(oldStr)).split("\n").length;
445
+ const snippetLines = next.split("\n");
446
+ const start = Math.max(0, snippetLineNo - 3);
447
+ const end = Math.min(snippetLines.length, snippetLineNo + 5);
448
+ const snippet = snippetLines
449
+ .slice(start, end)
450
+ .map((line, i) => `${fmtLineNo(i + 1 + start)}\t${line}`)
451
+ .join("\n");
452
+ return `The memory file has been edited.\n${snippet}`;
453
+ });
454
+ }
455
+ // ── insert ───────────────────────────────────────────────────────────
456
+ /**
457
+ * `insert` — insert text at a specific line in a file. `insertLine` is
458
+ * 0-indexed in Anthropic's spec (0 = before first line, N = after last).
459
+ */
460
+ async insert(agentId, virtualPath, insertLine, insertText, indexCharLimit) {
461
+ const t = this.translatePath(agentId, virtualPath);
462
+ if (!t.ok)
463
+ return `Error: ${t.error}`;
464
+ const abs = t.abs;
465
+ return this.withMutex(agentId, async () => {
466
+ let stat;
467
+ try {
468
+ stat = await fsp.stat(abs);
469
+ }
470
+ catch (e) {
471
+ if (e.code === "ENOENT") {
472
+ return `Error: The path ${virtualPath} does not exist`;
473
+ }
474
+ throw e;
475
+ }
476
+ if (stat.isDirectory()) {
477
+ return `Error: The path ${virtualPath} does not exist`;
478
+ }
479
+ const content = await fsp.readFile(abs, "utf-8");
480
+ const lines = content.split("\n");
481
+ const nLines = lines.length;
482
+ if (insertLine < 0 || insertLine > nLines) {
483
+ return `Error: Invalid \`insert_line\` parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${nLines}]`;
484
+ }
485
+ // Splice in insertText (treated as a single block — caller is
486
+ // responsible for trailing newlines if they want a clean break).
487
+ const next = [
488
+ ...lines.slice(0, insertLine),
489
+ insertText,
490
+ ...lines.slice(insertLine),
491
+ ].join("\n");
492
+ if (next.split("\n").length > MAX_FILE_LINES) {
493
+ return `File ${virtualPath} exceeds maximum line limit of 999,999 lines.`;
494
+ }
495
+ if (this.isIndexPath(virtualPath) && typeof indexCharLimit === "number") {
496
+ const newLen = next.trim().length;
497
+ const oldLen = content.trim().length;
498
+ if (newLen > indexCharLimit) {
499
+ return (`Memory at ${oldLen}/${indexCharLimit} chars. Insertion would push it to ` +
500
+ `${newLen}/${indexCharLimit}. Shorten or remove other entries first.`);
501
+ }
502
+ }
503
+ await this.writeFileAtomic(abs, next);
504
+ return `The file ${virtualPath} has been edited.`;
505
+ });
506
+ }
507
+ // ── delete ───────────────────────────────────────────────────────────
508
+ /**
509
+ * `delete` — recursively delete a file or directory. Anthropic's spec
510
+ * says "Deletes the directory and all its contents recursively" so
511
+ * we use `rm -rf` semantics for directories.
512
+ */
513
+ async delete(agentId, virtualPath) {
514
+ const t = this.translatePath(agentId, virtualPath);
515
+ if (!t.ok)
516
+ return `Error: ${t.error}`;
517
+ const abs = t.abs;
518
+ return this.withMutex(agentId, async () => {
519
+ let stat;
520
+ try {
521
+ stat = await fsp.stat(abs);
522
+ }
523
+ catch (e) {
524
+ if (e.code === "ENOENT") {
525
+ return `Error: The path ${virtualPath} does not exist`;
526
+ }
527
+ throw e;
528
+ }
529
+ if (stat.isDirectory()) {
530
+ await fsp.rm(abs, { recursive: true, force: true });
531
+ }
532
+ else {
533
+ await fsp.unlink(abs);
534
+ }
535
+ return `Successfully deleted ${virtualPath}`;
536
+ });
537
+ }
538
+ // ── rename ───────────────────────────────────────────────────────────
539
+ /**
540
+ * `rename` — move or rename a file/directory. Errors if source is
541
+ * missing or destination already exists (no overwrite).
542
+ */
543
+ async rename(agentId, oldVirtualPath, newVirtualPath) {
544
+ const t1 = this.translatePath(agentId, oldVirtualPath);
545
+ if (!t1.ok)
546
+ return `Error: ${t1.error.replace("does not exist. Please provide a valid path.", "does not exist")}`;
547
+ const t2 = this.translatePath(agentId, newVirtualPath);
548
+ if (!t2.ok)
549
+ return `Error: ${t2.error.replace("does not exist. Please provide a valid path.", "does not exist")}`;
550
+ return this.withMutex(agentId, async () => {
551
+ try {
552
+ await fsp.stat(t1.abs);
553
+ }
554
+ catch (e) {
555
+ if (e.code === "ENOENT") {
556
+ return `Error: The path ${oldVirtualPath} does not exist`;
557
+ }
558
+ throw e;
559
+ }
560
+ try {
561
+ await fsp.stat(t2.abs);
562
+ return `Error: The destination ${newVirtualPath} already exists`;
563
+ }
564
+ catch (e) {
565
+ if (e.code !== "ENOENT")
566
+ throw e;
567
+ }
568
+ await fsp.mkdir(path.dirname(t2.abs), { recursive: true });
569
+ await fsp.rename(t1.abs, t2.abs);
570
+ return `Successfully renamed ${oldVirtualPath} to ${newVirtualPath}`;
571
+ });
572
+ }
573
+ // ── Internals ────────────────────────────────────────────────────────
574
+ /** Atomic write — tmp file + fsync + rename. */
575
+ async writeFileAtomic(absPath, content) {
576
+ const dir = path.dirname(absPath);
577
+ await fsp.mkdir(dir, { recursive: true });
578
+ const tmp = path.join(dir, `${TMP_PREFIX}${randomBytes(8).toString("hex")}.tmp`);
579
+ let fh = null;
580
+ try {
581
+ fh = await fsp.open(tmp, "w");
582
+ await fh.writeFile(content, "utf-8");
583
+ await fh.sync();
584
+ await fh.close();
585
+ fh = null;
586
+ await fsp.rename(tmp, absPath);
587
+ }
588
+ catch (e) {
589
+ if (fh) {
590
+ try {
591
+ await fh.close();
592
+ }
593
+ catch {
594
+ // ignore
595
+ }
596
+ }
597
+ try {
598
+ await fsp.unlink(tmp);
599
+ }
600
+ catch {
601
+ // ignore — best-effort cleanup
602
+ }
603
+ throw e;
604
+ }
605
+ }
606
+ /**
607
+ * Per-agent mutex. Map stores only the settle-completion of each
608
+ * outstanding write, never its rejection — so a thrown error inside
609
+ * one writer can't poison the chain for the next.
610
+ */
611
+ async withMutex(agentId, work) {
612
+ const prev = this.inFlight.get(agentId) ?? Promise.resolve();
613
+ const result = prev.then(work, work);
614
+ this.inFlight.set(agentId, result.then(() => undefined, () => undefined));
615
+ return result;
616
+ }
617
+ }
618
+ // Export so tests can verify the line-numbering helper.
619
+ export const __test = { withLineNumbers, formatBytes, fmtLineNo };
620
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Threat-pattern data for the memory scanner. Ported verbatim from Hermes
3
+ * `tools/memory_tool.py:67-89`. Kept in its own file (no logic) so the
4
+ * pattern set is easy to audit and update independently of the scanner.
5
+ *
6
+ * `re.IGNORECASE` becomes the JS `i` flag; the regex syntax itself is
7
+ * identical (no Python-only features in these patterns).
8
+ */
9
+ export declare const MEMORY_THREAT_PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
10
+ export declare const INVISIBLE_CHARS: ReadonlySet<string>;
11
+ //# sourceMappingURL=threat-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-patterns.d.ts","sourceRoot":"","sources":["../src/threat-patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAmB3E,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAW9C,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Threat-pattern data for the memory scanner. Ported verbatim from Hermes
3
+ * `tools/memory_tool.py:67-89`. Kept in its own file (no logic) so the
4
+ * pattern set is easy to audit and update independently of the scanner.
5
+ *
6
+ * `re.IGNORECASE` becomes the JS `i` flag; the regex syntax itself is
7
+ * identical (no Python-only features in these patterns).
8
+ */
9
+ export const MEMORY_THREAT_PATTERNS = [
10
+ // Prompt injection
11
+ [/ignore\s+(previous|all|above|prior)\s+instructions/i, "prompt_injection"],
12
+ [/you\s+are\s+now\s+/i, "role_hijack"],
13
+ [/do\s+not\s+tell\s+the\s+user/i, "deception_hide"],
14
+ [/system\s+prompt\s+override/i, "sys_prompt_override"],
15
+ [/disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i, "disregard_rules"],
16
+ [
17
+ /act\s+as\s+(if|though)\s+you\s+(have\s+no|don't\s+have)\s+(restrictions|limits|rules)/i,
18
+ "bypass_restrictions",
19
+ ],
20
+ // Exfiltration via curl/wget with secrets
21
+ [/curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, "exfil_curl"],
22
+ [/wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, "exfil_wget"],
23
+ [/cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i, "read_secrets"],
24
+ // Persistence via shell rc / ssh
25
+ [/authorized_keys/i, "ssh_backdoor"],
26
+ [/\$HOME\/\.ssh|~\/\.ssh/i, "ssh_access"],
27
+ [/\$HOME\/\.openacme\/\.env|~\/\.openacme\/\.env/i, "openacme_env"],
28
+ ];
29
+ export const INVISIBLE_CHARS = new Set([
30
+ "​",
31
+ "‌",
32
+ "‍",
33
+ "⁠",
34
+ "",
35
+ "‪",
36
+ "‫",
37
+ "‬",
38
+ "‭",
39
+ "‮",
40
+ ]);
41
+ //# sourceMappingURL=threat-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-patterns.js","sourceRoot":"","sources":["../src/threat-patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAA6C;IAC9E,mBAAmB;IACnB,CAAC,qDAAqD,EAAE,kBAAkB,CAAC;IAC3E,CAAC,qBAAqB,EAAE,aAAa,CAAC;IACtC,CAAC,+BAA+B,EAAE,gBAAgB,CAAC;IACnD,CAAC,6BAA6B,EAAE,qBAAqB,CAAC;IACtD,CAAC,+DAA+D,EAAE,iBAAiB,CAAC;IACpF;QACE,wFAAwF;QACxF,qBAAqB;KACtB;IACD,0CAA0C;IAC1C,CAAC,kEAAkE,EAAE,YAAY,CAAC;IAClF,CAAC,kEAAkE,EAAE,YAAY,CAAC;IAClF,CAAC,oEAAoE,EAAE,cAAc,CAAC;IACtF,iCAAiC;IACjC,CAAC,kBAAkB,EAAE,cAAc,CAAC;IACpC,CAAC,yBAAyB,EAAE,YAAY,CAAC;IACzC,CAAC,iDAAiD,EAAE,cAAc,CAAC;CACpE,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAS;IAClE,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;CACJ,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Memory content threat scanner.
3
+ *
4
+ * Memory entries get injected into the system prompt verbatim, so they're
5
+ * a privileged channel — anything that would prompt-inject the agent or
6
+ * exfiltrate secrets must be blocked at the gate. Scanner is a pure
7
+ * function over the pattern set in `./threat-patterns.ts`.
8
+ */
9
+ export type ScanResult = {
10
+ ok: true;
11
+ } | {
12
+ ok: false;
13
+ reason: string;
14
+ };
15
+ /**
16
+ * Scan content destined for MEMORY.md. Returns `{ok:true}` if safe;
17
+ * otherwise an error string in the same format Hermes uses (so error
18
+ * wording stays consistent between ports).
19
+ */
20
+ export declare function scanMemoryContent(content: string): ScanResult;
21
+ //# sourceMappingURL=threat-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-scanner.d.ts","sourceRoot":"","sources":["../src/threat-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAqB7D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Memory content threat scanner.
3
+ *
4
+ * Memory entries get injected into the system prompt verbatim, so they're
5
+ * a privileged channel — anything that would prompt-inject the agent or
6
+ * exfiltrate secrets must be blocked at the gate. Scanner is a pure
7
+ * function over the pattern set in `./threat-patterns.ts`.
8
+ */
9
+ import { INVISIBLE_CHARS, MEMORY_THREAT_PATTERNS } from "./threat-patterns.js";
10
+ /**
11
+ * Scan content destined for MEMORY.md. Returns `{ok:true}` if safe;
12
+ * otherwise an error string in the same format Hermes uses (so error
13
+ * wording stays consistent between ports).
14
+ */
15
+ export function scanMemoryContent(content) {
16
+ for (const ch of content) {
17
+ if (INVISIBLE_CHARS.has(ch)) {
18
+ const codepoint = ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
19
+ return {
20
+ ok: false,
21
+ reason: `Blocked: content contains invisible unicode character U+${codepoint} (possible injection).`,
22
+ };
23
+ }
24
+ }
25
+ for (const [pattern, id] of MEMORY_THREAT_PATTERNS) {
26
+ if (pattern.test(content)) {
27
+ return {
28
+ ok: false,
29
+ reason: `Blocked: content matches threat pattern '${id}'. Memory entries are injected into the system prompt and must not contain injection or exfiltration payloads.`,
30
+ };
31
+ }
32
+ }
33
+ return { ok: true };
34
+ }
35
+ //# sourceMappingURL=threat-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-scanner.js","sourceRoot":"","sources":["../src/threat-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAM/E;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACjF,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,2DAA2D,SAAS,wBAAwB;aACrG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,sBAAsB,EAAE,CAAC;QACnD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,4CAA4C,EAAE,gHAAgH;aACvK,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@openacme/memory",
3
+ "version": "0.4.0",
4
+ "description": "Per-agent persistent memory for OpenAcme — bounded MEMORY.md store with atomic writes and threat scanning.",
5
+ "license": "MIT",
6
+ "author": "Utkarsh Kanwat",
7
+ "homepage": "https://github.com/ukanwat/OpenAcme/tree/main/packages/memory#readme",
8
+ "bugs": "https://github.com/ukanwat/OpenAcme/issues",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ukanwat/OpenAcme.git",
12
+ "directory": "packages/memory"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": false
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "devDependencies": {
33
+ "@types/node": "^22.15.3",
34
+ "typescript": "5.9.2",
35
+ "vitest": "^2.1.9",
36
+ "@repo/typescript-config": "0.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc --build",
40
+ "check-types": "tsc --noEmit",
41
+ "dev": "tsc --watch",
42
+ "test": "vitest run"
43
+ }
44
+ }