@theokit/sdk 4.62.0 → 4.63.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 +66 -0
- package/dist/{agent-NVBFTY5H.js → agent-536OOESZ.js} +8 -6
- package/dist/{agent-NVBFTY5H.js.map → agent-536OOESZ.js.map} +1 -1
- package/dist/{agent-VYPUO4UB.cjs → agent-5KYMG63I.cjs} +9 -7
- package/dist/{agent-VYPUO4UB.cjs.map → agent-5KYMG63I.cjs.map} +1 -1
- package/dist/{agent-DIu6FooJ.d.ts → agent-BFno7Sfn.d.ts} +36 -21
- package/dist/{agent-D3Xr_-6Z.d.cts → agent-X0DKA943.d.cts} +36 -21
- package/dist/chunk-A4RAL2ER.cjs +51 -0
- package/dist/chunk-A4RAL2ER.cjs.map +1 -0
- package/dist/chunk-A4VHSE56.cjs +21 -0
- package/dist/chunk-A4VHSE56.cjs.map +1 -0
- package/dist/{chunk-CQ2TQ32Y.js → chunk-AMFXSENK.js} +3 -48
- package/dist/chunk-AMFXSENK.js.map +1 -0
- package/dist/chunk-C7QPML3L.js +831 -0
- package/dist/chunk-C7QPML3L.js.map +1 -0
- package/dist/{chunk-CWHTMNNK.cjs → chunk-CASUEY42.cjs} +4 -4
- package/dist/{chunk-CWHTMNNK.cjs.map → chunk-CASUEY42.cjs.map} +1 -1
- package/dist/{chunk-SKOCYA55.js → chunk-CHKH5452.js} +37 -50
- package/dist/chunk-CHKH5452.js.map +1 -0
- package/dist/chunk-EHJEZAOO.cjs +864 -0
- package/dist/chunk-EHJEZAOO.cjs.map +1 -0
- package/dist/chunk-IRCJ7EHV.cjs +201 -0
- package/dist/chunk-IRCJ7EHV.cjs.map +1 -0
- package/dist/chunk-LW7G5DYW.js +19 -0
- package/dist/chunk-LW7G5DYW.js.map +1 -0
- package/dist/{chunk-IBYRA5PM.js → chunk-MG3SOM3M.js} +3 -3
- package/dist/{chunk-IBYRA5PM.js.map → chunk-MG3SOM3M.js.map} +1 -1
- package/dist/{chunk-AH6WD7JR.cjs → chunk-NLXOGBRJ.cjs} +45 -58
- package/dist/chunk-NLXOGBRJ.cjs.map +1 -0
- package/dist/{chunk-SKXBJ2NU.cjs → chunk-NXH4GPAQ.cjs} +2 -49
- package/dist/chunk-NXH4GPAQ.cjs.map +1 -0
- package/dist/chunk-R6TA2DRJ.js +194 -0
- package/dist/chunk-R6TA2DRJ.js.map +1 -0
- package/dist/{chunk-6OBIWHDR.cjs → chunk-UUP3MUZ6.cjs} +20 -361
- package/dist/chunk-UUP3MUZ6.cjs.map +1 -0
- package/dist/chunk-WE22OXQA.js +48 -0
- package/dist/chunk-WE22OXQA.js.map +1 -0
- package/dist/{chunk-QEKI3YKI.js → chunk-YTR3RBUZ.js} +13 -348
- package/dist/chunk-YTR3RBUZ.js.map +1 -0
- package/dist/{cron-BcWmzWzT.d.cts → cron-BuiRPrvt.d.cts} +1 -1
- package/dist/{cron-De6hzWCF.d.ts → cron-C2SIi31n.d.ts} +1 -1
- package/dist/cron.cjs +8 -6
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +7 -5
- package/dist/eval.cjs +7 -5
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +6 -4
- package/dist/eval.js.map +1 -1
- package/dist/{index-manager-RHPFVFSC.cjs → index-manager-6TEEIWM7.cjs} +7 -6
- package/dist/{index-manager-RHPFVFSC.cjs.map → index-manager-6TEEIWM7.cjs.map} +1 -1
- package/dist/{index-manager-AHAYJ33H.js → index-manager-ACEQG3IJ.js} +6 -5
- package/dist/{index-manager-AHAYJ33H.js.map → index-manager-ACEQG3IJ.js.map} +1 -1
- package/dist/index.cjs +35 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +22 -195
- package/dist/index.js.map +1 -1
- package/dist/internal/memory/active-memory.d.ts +8 -1
- package/dist/internal/memory/dreaming/diary.d.ts +39 -2
- package/dist/internal/memory/index-db.d.ts +44 -1
- package/dist/internal/memory/index-manager-contract.d.ts +10 -0
- package/dist/internal/memory/index-manager-helpers.d.ts +20 -1
- package/dist/internal/memory/index-manager.d.ts +1 -1
- package/dist/internal/memory/lance-index.d.ts +15 -1
- package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +43 -0
- package/dist/internal/memory/storage/index.cjs +95 -12
- package/dist/internal/memory/storage/index.d.cts +11 -1
- package/dist/internal/memory/storage/index.d.ts +11 -1
- package/dist/internal/memory/storage/index.js +4 -1
- package/dist/internal/memory/storage/markdown-store.d.cts +13 -42
- package/dist/internal/memory/storage/markdown-store.d.ts +13 -42
- package/dist/internal/memory/storage/memory-root.d.cts +93 -0
- package/dist/internal/memory/storage/memory-root.d.ts +93 -0
- package/dist/internal/memory/storage/session-loader.d.cts +35 -1
- package/dist/internal/memory/storage/session-loader.d.ts +35 -1
- package/dist/internal/memory/storage/session-summary-writer.d.cts +50 -2
- package/dist/internal/memory/storage/session-summary-writer.d.ts +50 -2
- package/dist/internal/memory/storage/transcript-store.d.cts +38 -1
- package/dist/internal/memory/storage/transcript-store.d.ts +38 -1
- package/dist/internal/memory/storage/wiki-loader.d.cts +27 -2
- package/dist/internal/memory/storage/wiki-loader.d.ts +27 -2
- package/dist/internal/memory/tools.d.ts +8 -1
- package/dist/internal/memory/types.d.ts +5 -0
- package/dist/internal/persistence/index.cjs +12 -11
- package/dist/internal/persistence/index.cjs.map +1 -1
- package/dist/internal/persistence/index.js +2 -1
- package/dist/internal/persistence/index.js.map +1 -1
- package/dist/internal/persistence/session-dir.d.cts +0 -15
- package/dist/internal/persistence/session-dir.d.ts +0 -15
- package/dist/internal/runtime/memory/memory-store.d.ts +2 -2
- package/dist/persistence.cjs +9 -8
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.js +2 -1
- package/dist/persistence.js.map +1 -1
- package/dist/subagents-loader.d.cts +1 -1
- package/dist/subagents-loader.d.ts +1 -1
- package/dist/types/agent.d.ts +11 -0
- package/dist/types/memory-provider.d.ts +8 -0
- package/docs/error-codes.md +10 -9
- package/docs/harness-capability-map.md +57 -17
- package/docs/memory-decisions.md +44 -7
- package/package.json +1 -1
- package/dist/chunk-2UCFUSPW.cjs +0 -468
- package/dist/chunk-2UCFUSPW.cjs.map +0 -1
- package/dist/chunk-6OBIWHDR.cjs.map +0 -1
- package/dist/chunk-AH6WD7JR.cjs.map +0 -1
- package/dist/chunk-CQ2TQ32Y.js.map +0 -1
- package/dist/chunk-QEKI3YKI.js.map +0 -1
- package/dist/chunk-SKOCYA55.js.map +0 -1
- package/dist/chunk-SKXBJ2NU.cjs.map +0 -1
- package/dist/chunk-WCLDJSMY.js +0 -456
- package/dist/chunk-WCLDJSMY.js.map +0 -1
package/docs/memory-decisions.md
CHANGED
|
@@ -18,10 +18,10 @@ decision the next refactor deletes.
|
|
|
18
18
|
.theokit/memory/.index/memory.sqlite on the first send, when memory is enabled
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**Why it looks wrong.** With `
|
|
22
|
-
`<
|
|
23
|
-
adopted this SDK ends up with a `.theokit/` directory anyway, which reads like the
|
|
24
|
-
CLI reads" promise leaking.
|
|
21
|
+
**Why it looks wrong.** With `memory.directory` pointed at the CLI's store, memory *facts* are
|
|
22
|
+
written to `<claudeHome>/projects/<encoded-cwd>/memory/` — where the CLI reads them. So a project
|
|
23
|
+
that never adopted this SDK ends up with a `.theokit/` directory anyway, which reads like the
|
|
24
|
+
"write where the CLI reads" promise leaking.
|
|
25
25
|
|
|
26
26
|
**Why it is right.** What lands in `.theokit/` is this SDK's own state, and neither piece has a
|
|
27
27
|
Claude Code shape to be written in:
|
|
@@ -178,8 +178,9 @@ entries, ~3.2 KB per entry. Feeding the largest — 66 entries, 275 KB on disk
|
|
|
178
178
|
turn's system prompt**, before the user has said anything. Nine more stores are already past the
|
|
179
179
|
point where the injection exceeds a session budget.
|
|
180
180
|
|
|
181
|
-
Two things make this worse than a size problem. The CLI's store is read **
|
|
182
|
-
`
|
|
181
|
+
Two things make this worse than a size problem. The CLI's store is read **unconditionally** —
|
|
182
|
+
`memory.directory` decides where writes go, never what the read covers — so a consumer who never
|
|
183
|
+
opted into CLI interop still gets it. And the
|
|
183
184
|
cost is invisible: nothing errors, the window just fills, and the tokens are not attributed to
|
|
184
185
|
memory. An earlier version of this document said "at 64 entries it does not hurt" — that was an
|
|
185
186
|
assumption, and measuring it is what disproved it.
|
|
@@ -189,6 +190,42 @@ nothing — the data for a staleness signal is on disk today, unused.
|
|
|
189
190
|
|
|
190
191
|
---
|
|
191
192
|
|
|
193
|
+
## One resolver, one root (#463)
|
|
194
|
+
|
|
195
|
+
**What happens.** `memory.directory` — absolute, or `~/`-prefixed — is the only thing that decides
|
|
196
|
+
where this agent's memory lives. Everything under the root follows it: `MEMORY.md`, the per-memory
|
|
197
|
+
files, `notes/`, `sessions/`, `wiki/`, `transcripts/`, `dream-diary.md` and `.index/memory.sqlite`.
|
|
198
|
+
|
|
199
|
+
**What it replaced.** The location used to be a side effect of `local.sessionDir`, the option that
|
|
200
|
+
names the *transcript* home, on the reasoning that a consumer who set it had already opted into CLI
|
|
201
|
+
interop. One option answered two questions — and of the fourteen places that computed a memory
|
|
202
|
+
path, exactly one heard the second answer. `appendFact` relocated; the indexer, the `memory_get`
|
|
203
|
+
path guard, `MEMORY.md`, `sessions/` and the index database did not. A relocated fact was written,
|
|
204
|
+
never indexed, unreadable by the tool whose job is reading memory, and shadowed by a second
|
|
205
|
+
`MEMORY.md` in the store it had left.
|
|
206
|
+
|
|
207
|
+
**Why the root is a branded type.** `MemoryRoot` is `string` with a brand, produced only by
|
|
208
|
+
`resolveMemoryRoot`. A cwd and a root are both strings, so without the brand the thirteen path
|
|
209
|
+
helpers would go on accepting either, and the next helper added has the same even chance of taking
|
|
210
|
+
the wrong one that produced this defect. The brand makes "every path derives from one resolution" a
|
|
211
|
+
compiler rule instead of a convention.
|
|
212
|
+
|
|
213
|
+
**The gap that remains, stated rather than hidden.** `IndexManager.open()` is a public entry point
|
|
214
|
+
that takes a `cwd`, so its `memoryRoot` is optional and falls back to the default. Inside an agent
|
|
215
|
+
the root is resolved once and handed to both the index and the read tool; a consumer opening the
|
|
216
|
+
index by hand against a non-default directory has to pass it.
|
|
217
|
+
|
|
218
|
+
**A relative `directory` is refused, not resolved.** The two plausible bases — the workspace and the
|
|
219
|
+
process cwd — put the store in two different places, and picking one silently is how a store ends up
|
|
220
|
+
split across both. `ConfigurationError` with code `invalid_memory_directory`.
|
|
221
|
+
|
|
222
|
+
**How to check it still holds:** `tests/memory-root.test.ts`, in particular
|
|
223
|
+
`test_the_indexer_scans_the_configured_directory_so_the_fact_is_searchable` and
|
|
224
|
+
`test_the_project_store_gets_no_second_index_pointing_at_files_it_does_not_have` — the two halves
|
|
225
|
+
that were broken.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
192
229
|
## Known gaps, recorded so they are not rediscovered
|
|
193
230
|
|
|
194
231
|
Each row says what would close it, not only that it is open — the two invite very different work.
|
|
@@ -196,7 +233,7 @@ Each row says what would close it, not only that it is open — the two invite v
|
|
|
196
233
|
| gap | where | what would close it |
|
|
197
234
|
|---|---|---|
|
|
198
235
|
| ~~The default recall path has no selection at all~~ — **closed** in `721a311f` | `local-agent-send.ts:257-272` | Closed: lexical relevance + recency, capped. Measured end to end against the real model — a 67-entry store went from ~66K to **13,606 tokens on the wire**, and the answering fact is recalled whether it is the oldest or the newest entry. |
|
|
199
|
-
| The CLI's store is read whether or not
|
|
236
|
+
| The CLI's store is read whether or not the consumer opted in | `storage/memory-root.ts` — `claudeProjectMemoryDir(cwd)` is an unconditional entry in `memoryReadRoots` | A decision, not a fix: it is deliberate (*write one, read all*, so a consumer's existing memories are never orphaned) and it means a consumer who never opted into CLI interop still receives what the CLI accumulated in that project. Cheap while the cap holds; revisit if the cap is ever removed. |
|
|
200
237
|
| No retention of any kind — no TTL, prune, or decay | searched `ttl\|prune\|expire\|retention` across both packages: zero | A per-kind TTL plus a prune step in the sweep. Needs the kind vocabulary to mean something first (§ 2), which is why this orders before widening it. |
|
|
201
238
|
| Quarantine marks but does not constrain | `memory-file.ts:96`, `memory-provider.ts:48` | Implemented: three states, and `[unconfirmed]` on entries the store counted once. **Measured against the real model and it does not close the hole** — a planted memory alone is acted on 5/5, and beside a corroborated contradiction it is still asserted ~62% of runs (n=32). Marking influences the model; it does not constrain it. The guarantee is not available at this layer: blocking an uncorroborated entry would break the promise that a fact written once is recallable next session. |
|
|
202
239
|
| A planted memory can make the agent ACT | measured: `RELEASE_OVERRIDE.txt` created in 2 of 6 runs | Register the permission layer — `PermissionPlugin.create(new PermissionEngine(…))` — which blocks it every time. This is the half that IS closable at the tool boundary; the informational half above is not. Any deployment where the memory directory is writable by anything other than this agent needs it. |
|
package/package.json
CHANGED
package/dist/chunk-2UCFUSPW.cjs
DELETED
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkKRD3GQAA_cjs = require('./chunk-KRD3GQAA.cjs');
|
|
4
|
-
var chunkR3UPQFKK_cjs = require('./chunk-R3UPQFKK.cjs');
|
|
5
|
-
var chunkBUIK7GUA_cjs = require('./chunk-BUIK7GUA.cjs');
|
|
6
|
-
var chunkZF2LDKQQ_cjs = require('./chunk-ZF2LDKQQ.cjs');
|
|
7
|
-
var chunkI6TGFUCO_cjs = require('./chunk-I6TGFUCO.cjs');
|
|
8
|
-
var chunkK3FW2XZD_cjs = require('./chunk-K3FW2XZD.cjs');
|
|
9
|
-
var chunkJTB5Q42C_cjs = require('./chunk-JTB5Q42C.cjs');
|
|
10
|
-
var promises = require('fs/promises');
|
|
11
|
-
var os = require('os');
|
|
12
|
-
var path = require('path');
|
|
13
|
-
|
|
14
|
-
var MEMORY_KINDS = ["user", "feedback", "project", "reference"];
|
|
15
|
-
function legacyMemoryJsonPath(cwd, config) {
|
|
16
|
-
if (config.storePath !== void 0) {
|
|
17
|
-
return path.resolve(cwd, config.storePath);
|
|
18
|
-
}
|
|
19
|
-
const namespace = chunkR3UPQFKK_cjs.sanitizeIdentifier(config.namespace ?? "default");
|
|
20
|
-
const scope = chunkR3UPQFKK_cjs.sanitizeIdentifier(config.scope ?? "agent", { maxLen: 16 });
|
|
21
|
-
const userId = chunkR3UPQFKK_cjs.sanitizeIdentifier(config.userId ?? "default");
|
|
22
|
-
return chunkR3UPQFKK_cjs.safePathJoin(cwd, ".theokit", "memory", namespace, `${scope}-${userId}.json`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// src/internal/memory/storage/memory-file.ts
|
|
26
|
-
var SAFE_SLUG = /^[a-z0-9][a-z0-9_-]*$/;
|
|
27
|
-
var MAX_SLUG_LENGTH = 64;
|
|
28
|
-
var TARGET_SLUG_LENGTH = 32;
|
|
29
|
-
var TITLE_MAX_LENGTH = 40;
|
|
30
|
-
var TITLE_MAX_WORDS = 4;
|
|
31
|
-
var SLUG_STOPWORDS = /* @__PURE__ */ new Set([
|
|
32
|
-
"the",
|
|
33
|
-
"a",
|
|
34
|
-
"an",
|
|
35
|
-
"and",
|
|
36
|
-
"or",
|
|
37
|
-
"but",
|
|
38
|
-
"is",
|
|
39
|
-
"are",
|
|
40
|
-
"was",
|
|
41
|
-
"were",
|
|
42
|
-
"be",
|
|
43
|
-
"been",
|
|
44
|
-
"being",
|
|
45
|
-
"for",
|
|
46
|
-
"of",
|
|
47
|
-
"to",
|
|
48
|
-
"in",
|
|
49
|
-
"on",
|
|
50
|
-
"at",
|
|
51
|
-
"by",
|
|
52
|
-
"with",
|
|
53
|
-
"from",
|
|
54
|
-
"as",
|
|
55
|
-
"that",
|
|
56
|
-
"this",
|
|
57
|
-
"these",
|
|
58
|
-
"those",
|
|
59
|
-
"it",
|
|
60
|
-
"its",
|
|
61
|
-
"he",
|
|
62
|
-
"she",
|
|
63
|
-
"they",
|
|
64
|
-
"we",
|
|
65
|
-
"you",
|
|
66
|
-
"i",
|
|
67
|
-
"do",
|
|
68
|
-
"does",
|
|
69
|
-
"did",
|
|
70
|
-
"has",
|
|
71
|
-
"have",
|
|
72
|
-
"had",
|
|
73
|
-
"will",
|
|
74
|
-
"would",
|
|
75
|
-
"can",
|
|
76
|
-
"could",
|
|
77
|
-
"should",
|
|
78
|
-
"must",
|
|
79
|
-
"not",
|
|
80
|
-
"no",
|
|
81
|
-
"over",
|
|
82
|
-
"under",
|
|
83
|
-
"into",
|
|
84
|
-
"onto",
|
|
85
|
-
"than",
|
|
86
|
-
"then",
|
|
87
|
-
"when",
|
|
88
|
-
"where",
|
|
89
|
-
"which",
|
|
90
|
-
"who",
|
|
91
|
-
"why",
|
|
92
|
-
"how",
|
|
93
|
-
"all",
|
|
94
|
-
"any",
|
|
95
|
-
"each",
|
|
96
|
-
"more",
|
|
97
|
-
"most",
|
|
98
|
-
"other",
|
|
99
|
-
"some",
|
|
100
|
-
"such",
|
|
101
|
-
"only",
|
|
102
|
-
"own",
|
|
103
|
-
"same",
|
|
104
|
-
"so",
|
|
105
|
-
"too",
|
|
106
|
-
"very",
|
|
107
|
-
"just",
|
|
108
|
-
"also",
|
|
109
|
-
"about",
|
|
110
|
-
"after",
|
|
111
|
-
"before",
|
|
112
|
-
"between",
|
|
113
|
-
"during",
|
|
114
|
-
"up",
|
|
115
|
-
"down",
|
|
116
|
-
"out",
|
|
117
|
-
"off",
|
|
118
|
-
"again",
|
|
119
|
-
"once",
|
|
120
|
-
"here",
|
|
121
|
-
"there",
|
|
122
|
-
"if",
|
|
123
|
-
"because",
|
|
124
|
-
"while"
|
|
125
|
-
]);
|
|
126
|
-
function topicWords(text) {
|
|
127
|
-
return text.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").split(/[^a-z0-9]+/).filter((w) => w.length >= 2 && !SLUG_STOPWORDS.has(w));
|
|
128
|
-
}
|
|
129
|
-
function slugForFact(text) {
|
|
130
|
-
const words = topicWords(text);
|
|
131
|
-
let slug = "";
|
|
132
|
-
for (const w of words) {
|
|
133
|
-
const next = slug.length === 0 ? w : `${slug}-${w}`;
|
|
134
|
-
if (next.length > TARGET_SLUG_LENGTH && slug.length > 0) break;
|
|
135
|
-
if (next.length > MAX_SLUG_LENGTH) break;
|
|
136
|
-
slug = next;
|
|
137
|
-
}
|
|
138
|
-
if (slug.length === 0) {
|
|
139
|
-
slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
|
|
140
|
-
}
|
|
141
|
-
return SAFE_SLUG.test(slug) ? slug : chunkR3UPQFKK_cjs.safeFilenameForId(text);
|
|
142
|
-
}
|
|
143
|
-
function titleForFact(text) {
|
|
144
|
-
const words = topicWords(text);
|
|
145
|
-
if (words.length === 0) return text.trim().slice(0, TITLE_MAX_LENGTH);
|
|
146
|
-
let title = "";
|
|
147
|
-
let used = 0;
|
|
148
|
-
for (const w of words) {
|
|
149
|
-
if (used >= TITLE_MAX_WORDS) break;
|
|
150
|
-
const next = title.length === 0 ? w : `${title} ${w}`;
|
|
151
|
-
if (next.length > TITLE_MAX_LENGTH && title.length > 0) break;
|
|
152
|
-
title = next;
|
|
153
|
-
used += 1;
|
|
154
|
-
}
|
|
155
|
-
if (title.length === 0) title = words[0];
|
|
156
|
-
return title.charAt(0).toUpperCase() + title.slice(1);
|
|
157
|
-
}
|
|
158
|
-
function readMetadata(raw) {
|
|
159
|
-
const metadata = raw ?? {};
|
|
160
|
-
const rawKind = metadata.type;
|
|
161
|
-
const kind = typeof rawKind === "string" && MEMORY_KINDS.includes(rawKind) ? rawKind : void 0;
|
|
162
|
-
const modified = typeof metadata.modified === "string" ? metadata.modified : void 0;
|
|
163
|
-
const observations = readObservationCount(metadata.observations);
|
|
164
|
-
return {
|
|
165
|
-
...kind !== void 0 ? { kind } : {},
|
|
166
|
-
...modified !== void 0 ? { modified } : {},
|
|
167
|
-
...observations !== void 0 ? { observations } : {}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
function readObservationCount(raw) {
|
|
171
|
-
if (typeof raw !== "number") return void 0;
|
|
172
|
-
if (!Number.isInteger(raw) || raw <= 0) return void 0;
|
|
173
|
-
return raw;
|
|
174
|
-
}
|
|
175
|
-
function renderMemoryFile(fields) {
|
|
176
|
-
const metadata = [" node_type: memory"];
|
|
177
|
-
if (fields.kind !== void 0) metadata.push(` type: ${fields.kind}`);
|
|
178
|
-
if (fields.modified !== void 0) metadata.push(` modified: ${fields.modified}`);
|
|
179
|
-
if (fields.observations !== void 0) {
|
|
180
|
-
metadata.push(` observations: ${fields.observations}`);
|
|
181
|
-
}
|
|
182
|
-
return [
|
|
183
|
-
"---",
|
|
184
|
-
`name: ${fields.name}`,
|
|
185
|
-
`description: ${JSON.stringify(fields.description)}`,
|
|
186
|
-
"metadata:",
|
|
187
|
-
...metadata,
|
|
188
|
-
"---",
|
|
189
|
-
"",
|
|
190
|
-
fields.body,
|
|
191
|
-
""
|
|
192
|
-
].join("\n");
|
|
193
|
-
}
|
|
194
|
-
function parseMemoryFile(raw) {
|
|
195
|
-
const { yaml, body } = chunkKRD3GQAA_cjs.splitFrontmatter(raw);
|
|
196
|
-
if (yaml === void 0) return void 0;
|
|
197
|
-
let fields;
|
|
198
|
-
try {
|
|
199
|
-
fields = chunkKRD3GQAA_cjs.parseSimpleYaml(yaml);
|
|
200
|
-
} catch {
|
|
201
|
-
return void 0;
|
|
202
|
-
}
|
|
203
|
-
const name = fields.name;
|
|
204
|
-
const description = fields.description;
|
|
205
|
-
if (typeof name !== "string" || typeof description !== "string") return void 0;
|
|
206
|
-
return {
|
|
207
|
-
name,
|
|
208
|
-
description,
|
|
209
|
-
...readMetadata(fields.metadata),
|
|
210
|
-
body: body.trim()
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// src/internal/memory/storage/threat-scan.ts
|
|
215
|
-
var INVISIBLE_CLASS = "\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF";
|
|
216
|
-
var THREAT_PATTERNS = [
|
|
217
|
-
{
|
|
218
|
-
id: "invisible_unicode",
|
|
219
|
-
// Text that renders as one thing to a reviewer and another to a parser is the whole
|
|
220
|
-
// technique.
|
|
221
|
-
test: new RegExp(`[${INVISIBLE_CLASS}]`, "u"),
|
|
222
|
-
why: "contains invisible or bidirectional control characters"
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
id: "instruction_override",
|
|
226
|
-
// The framing that tries to make recalled text outrank the system prompt. A memory entry
|
|
227
|
-
// states what was learned; it never addresses the model's instructions.
|
|
228
|
-
test: /\b(?:ignore|disregard|forget|override)\s+(?:all\s+|any\s+|the\s+|your\s+|previous\s+|prior\s+|earlier\s+|above\s+)*(?:instruction|prompt|rule|directive|guideline|system\s+prompt)/i,
|
|
229
|
-
why: "addresses the model's instructions instead of stating what was learned"
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
id: "role_reassignment",
|
|
233
|
-
test: /\b(?:you\s+are\s+now|from\s+now\s+on\s+you\s+(?:are|will|must)|new\s+system\s+prompt|act\s+as\s+(?:if\s+you\s+are\s+)?(?:an?\s+)?(?:unrestricted|jailbroken|developer\s+mode))/i,
|
|
234
|
-
why: "attempts to reassign the agent's role"
|
|
235
|
-
},
|
|
236
|
-
// A `pipe_to_shell` pattern (`curl … | sh`) was written here and REMOVED after measurement.
|
|
237
|
-
// It fired on 1 of 797 real memory files, and the hit was legitimate: a note documenting the
|
|
238
|
-
// product's own install command in a table. Fetch-and-execute in a hostile entry and in an
|
|
239
|
-
// install instruction are textually identical, so the pattern cannot be narrowed — only
|
|
240
|
-
// traded. Rejecting the write would mean the first person to save install docs to memory
|
|
241
|
-
// gets a hard failure, which buys nothing: an entry the agent READS is not an entry the
|
|
242
|
-
// agent RUNS, and execution is gated at the tool boundary, where it belongs. Left out on
|
|
243
|
-
// purpose, so nobody re-adds it without repeating the measurement.
|
|
244
|
-
{
|
|
245
|
-
id: "encoded_payload",
|
|
246
|
-
// A base64 run this long is not prose. Short tokens are left alone precisely so that hashes,
|
|
247
|
-
// commit SHAs and identifiers keep working.
|
|
248
|
-
test: /[A-Za-z0-9+/]{256,}={0,2}/,
|
|
249
|
-
why: "carries a long encoded payload rather than readable text"
|
|
250
|
-
}
|
|
251
|
-
];
|
|
252
|
-
var EXCERPT_RADIUS = 40;
|
|
253
|
-
var EXCERPT_SCRUB = new RegExp(`[\\u0000-\\u001F${INVISIBLE_CLASS}]`, "gu");
|
|
254
|
-
function scanForThreats(text) {
|
|
255
|
-
for (const pattern of THREAT_PATTERNS) {
|
|
256
|
-
const m = pattern.test.exec(text);
|
|
257
|
-
if (m === null) continue;
|
|
258
|
-
const at = m.index;
|
|
259
|
-
const start = Math.max(0, at - EXCERPT_RADIUS);
|
|
260
|
-
const end = Math.min(text.length, at + m[0].length + EXCERPT_RADIUS);
|
|
261
|
-
const window = text.slice(start, end).replace(EXCERPT_SCRUB, "\u2423");
|
|
262
|
-
return {
|
|
263
|
-
id: pattern.id,
|
|
264
|
-
why: pattern.why,
|
|
265
|
-
excerpt: `${start > 0 ? "\u2026" : ""}${window}${end < text.length ? "\u2026" : ""}`
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return void 0;
|
|
269
|
-
}
|
|
270
|
-
THREAT_PATTERNS.map((p) => p.id);
|
|
271
|
-
|
|
272
|
-
// src/internal/memory/storage/markdown-store.ts
|
|
273
|
-
var MEMORY_MD_HEADER = "# Memory Index\n";
|
|
274
|
-
var FACTS_HEADING = "## Facts";
|
|
275
|
-
var MAX_NAME_VARIANTS = 50;
|
|
276
|
-
function memoryDir(cwd) {
|
|
277
|
-
return path.join(cwd, ".theokit", "memory");
|
|
278
|
-
}
|
|
279
|
-
function memoryWriteDir(cwd, sessionDir) {
|
|
280
|
-
if (sessionDir === void 0 || sessionDir.trim().length === 0) return memoryDir(cwd);
|
|
281
|
-
return path.join(sessionDir, "projects", chunkBUIK7GUA_cjs.encodeProjectDir(cwd), "memory");
|
|
282
|
-
}
|
|
283
|
-
function claudeProjectMemoryDir(cwd) {
|
|
284
|
-
const home = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
285
|
-
const root = home !== void 0 && home.length > 0 ? home : path.join(os.homedir(), ".claude");
|
|
286
|
-
return path.join(root, "projects", chunkBUIK7GUA_cjs.encodeProjectDir(cwd), "memory");
|
|
287
|
-
}
|
|
288
|
-
function memoryMdPath(cwd) {
|
|
289
|
-
return path.join(memoryDir(cwd), "MEMORY.md");
|
|
290
|
-
}
|
|
291
|
-
function notesDir(cwd) {
|
|
292
|
-
return path.join(memoryDir(cwd), "notes");
|
|
293
|
-
}
|
|
294
|
-
function byNaturalName(a, b) {
|
|
295
|
-
const split = (f) => {
|
|
296
|
-
const stem = f.replace(/\.md$/, "");
|
|
297
|
-
const m = /^(.*?)-(\d+)$/.exec(stem);
|
|
298
|
-
return m === null ? [stem, 0] : [m[1], Number(m[2])];
|
|
299
|
-
};
|
|
300
|
-
const [baseA, nA] = split(a);
|
|
301
|
-
const [baseB, nB] = split(b);
|
|
302
|
-
return baseA === baseB ? nA - nB : baseA.localeCompare(baseB);
|
|
303
|
-
}
|
|
304
|
-
async function readFactsFromMarkdown(cwd, sessionDir) {
|
|
305
|
-
const facts = [];
|
|
306
|
-
const roots = [
|
|
307
|
-
.../* @__PURE__ */ new Set([memoryDir(cwd), memoryWriteDir(cwd, sessionDir), claudeProjectMemoryDir(cwd)])
|
|
308
|
-
];
|
|
309
|
-
for (const dir of roots) {
|
|
310
|
-
let entries;
|
|
311
|
-
try {
|
|
312
|
-
entries = await promises.readdir(dir);
|
|
313
|
-
} catch {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const inDir = [];
|
|
317
|
-
for (const entry of entries.sort(byNaturalName)) {
|
|
318
|
-
const fact = await readMemoryFileIn(dir, entry);
|
|
319
|
-
if (fact !== void 0) inDir.push(fact);
|
|
320
|
-
}
|
|
321
|
-
inDir.sort((a, b) => (a.modified ?? "").localeCompare(b.modified ?? ""));
|
|
322
|
-
facts.push(...inDir);
|
|
323
|
-
}
|
|
324
|
-
try {
|
|
325
|
-
facts.push(...parseFactsSection(await promises.readFile(memoryMdPath(cwd), "utf8")));
|
|
326
|
-
} catch {
|
|
327
|
-
}
|
|
328
|
-
return facts;
|
|
329
|
-
}
|
|
330
|
-
async function readMemoryFileIn(dir, entry) {
|
|
331
|
-
if (!entry.endsWith(".md") || entry === "MEMORY.md") return void 0;
|
|
332
|
-
let raw;
|
|
333
|
-
try {
|
|
334
|
-
raw = await promises.readFile(path.join(dir, entry), "utf8");
|
|
335
|
-
} catch {
|
|
336
|
-
return void 0;
|
|
337
|
-
}
|
|
338
|
-
const parsed = parseMemoryFile(raw);
|
|
339
|
-
if (parsed === void 0) return void 0;
|
|
340
|
-
const body = parsed.body.trim();
|
|
341
|
-
return {
|
|
342
|
-
text: body.length > 0 ? body : parsed.description,
|
|
343
|
-
...parsed.kind !== void 0 ? { kind: parsed.kind } : {},
|
|
344
|
-
...parsed.modified !== void 0 ? { modified: parsed.modified } : {},
|
|
345
|
-
...parsed.observations !== void 0 ? { observations: parsed.observations } : {}
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
function assertWritable(fact, text) {
|
|
349
|
-
if (fact.kind !== void 0 && !MEMORY_KINDS.includes(fact.kind)) {
|
|
350
|
-
throw new chunkK3FW2XZD_cjs.ConfigurationError(
|
|
351
|
-
`Unknown memory fact kind "${fact.kind}". Expected one of: ${MEMORY_KINDS.join(", ")}.`,
|
|
352
|
-
{ code: "invalid_memory_kind" }
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
const threat = scanForThreats(text);
|
|
356
|
-
if (threat !== void 0) {
|
|
357
|
-
throw new chunkK3FW2XZD_cjs.ConfigurationError(
|
|
358
|
-
`Refusing to write a memory entry that ${threat.why}: ${threat.excerpt}`,
|
|
359
|
-
{ code: "memory_threat_rejected" }
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function appendFactToMarkdown(cwd, fact, targetDir = memoryDir(cwd)) {
|
|
364
|
-
return chunkZF2LDKQQ_cjs.withCwdMutex(targetDir, async () => {
|
|
365
|
-
const text = chunkJTB5Q42C_cjs.redactSecrets(fact.text);
|
|
366
|
-
assertWritable(fact, text);
|
|
367
|
-
const title = fact.title?.trim() ?? titleForFact(text);
|
|
368
|
-
const base = fact.title !== void 0 ? slugForFact(fact.title) : slugForFact(text);
|
|
369
|
-
await promises.mkdir(targetDir, { recursive: true });
|
|
370
|
-
const name = await resolveName(targetDir, base, text);
|
|
371
|
-
const description = fact.description?.trim() ?? text;
|
|
372
|
-
const observations = await nextObservationCount(targetDir, name, text);
|
|
373
|
-
await chunkI6TGFUCO_cjs.replaceFileAtomic(
|
|
374
|
-
path.join(targetDir, `${name}.md`),
|
|
375
|
-
renderMemoryFile({
|
|
376
|
-
name,
|
|
377
|
-
description,
|
|
378
|
-
...fact.kind !== void 0 ? { kind: fact.kind } : {},
|
|
379
|
-
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
380
|
-
observations,
|
|
381
|
-
body: text
|
|
382
|
-
})
|
|
383
|
-
);
|
|
384
|
-
await chunkI6TGFUCO_cjs.replaceFileAtomic(
|
|
385
|
-
path.join(targetDir, "MEMORY.md"),
|
|
386
|
-
await nextIndex(targetDir, description, name, title)
|
|
387
|
-
);
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
async function resolveName(dir, base, text) {
|
|
391
|
-
const wanted = normalizeFactText(text);
|
|
392
|
-
for (let i = 1; i <= MAX_NAME_VARIANTS; i += 1) {
|
|
393
|
-
const candidate = i === 1 ? base : `${base}-${i}`;
|
|
394
|
-
let existing;
|
|
395
|
-
try {
|
|
396
|
-
existing = await promises.readFile(path.join(dir, `${candidate}.md`), "utf8");
|
|
397
|
-
} catch {
|
|
398
|
-
return candidate;
|
|
399
|
-
}
|
|
400
|
-
const parsed = parseMemoryFile(existing);
|
|
401
|
-
if (parsed === void 0) return candidate;
|
|
402
|
-
if (normalizeFactText(parsed.body) === wanted) return candidate;
|
|
403
|
-
}
|
|
404
|
-
return slugForFact(text) === base ? chunkR3UPQFKK_cjs.safeFilenameForId(text) : slugForFact(text);
|
|
405
|
-
}
|
|
406
|
-
async function nextObservationCount(dir, name, text) {
|
|
407
|
-
let existing;
|
|
408
|
-
try {
|
|
409
|
-
existing = await promises.readFile(path.join(dir, `${name}.md`), "utf8");
|
|
410
|
-
} catch {
|
|
411
|
-
return 1;
|
|
412
|
-
}
|
|
413
|
-
const parsed = parseMemoryFile(existing);
|
|
414
|
-
if (parsed === void 0) return 1;
|
|
415
|
-
const sameFact = normalizeFactText(parsed.body) === normalizeFactText(text);
|
|
416
|
-
if (!sameFact) return 1;
|
|
417
|
-
return (parsed.observations ?? 1) + 1;
|
|
418
|
-
}
|
|
419
|
-
function normalizeFactText(text) {
|
|
420
|
-
return text.trim().toLowerCase().replace(/\s+/g, " ").replace(/[.!?]+$/, "");
|
|
421
|
-
}
|
|
422
|
-
async function nextIndex(dir, text, name, title) {
|
|
423
|
-
let existing = "";
|
|
424
|
-
try {
|
|
425
|
-
existing = await promises.readFile(path.join(dir, "MEMORY.md"), "utf8");
|
|
426
|
-
} catch {
|
|
427
|
-
existing = "";
|
|
428
|
-
}
|
|
429
|
-
const hook = text.replace(/\s+/g, " ").trim();
|
|
430
|
-
const entry = hook.length > 0 && hook !== title ? `- [${title}](${name}.md) \u2014 ${hook}` : `- [${title}](${name}.md)`;
|
|
431
|
-
const kept = existing.split("\n").filter((line) => !line.startsWith(`- [`) || !line.includes(`](${name}.md)`));
|
|
432
|
-
const body = kept.join("\n").trimEnd();
|
|
433
|
-
const head = body.length > 0 ? body : MEMORY_MD_HEADER;
|
|
434
|
-
return `${head}
|
|
435
|
-
${entry}
|
|
436
|
-
`;
|
|
437
|
-
}
|
|
438
|
-
var INDEX_ENTRY = /^\[[^\]]*\]\([^)]+\.md\)(?:\s+—\s.*)?$/;
|
|
439
|
-
function parseFactsSection(raw) {
|
|
440
|
-
const idx = raw.indexOf(FACTS_HEADING);
|
|
441
|
-
if (idx === -1) return [];
|
|
442
|
-
const tail = raw.slice(idx + FACTS_HEADING.length);
|
|
443
|
-
const nextHeading = tail.search(/\n#{1,2}\s/);
|
|
444
|
-
const block = nextHeading === -1 ? tail : tail.slice(0, nextHeading);
|
|
445
|
-
return block.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim()).filter((body) => !INDEX_ENTRY.test(body)).map((body) => ({ text: body }));
|
|
446
|
-
}
|
|
447
|
-
async function readFacts(cwd, config, memoryHome) {
|
|
448
|
-
if (!config.enabled) return [];
|
|
449
|
-
return readFactsFromMarkdown(cwd, memoryHome);
|
|
450
|
-
}
|
|
451
|
-
async function appendFact(cwd, config, fact, memoryHome) {
|
|
452
|
-
if (!config.enabled) return;
|
|
453
|
-
await appendFactToMarkdown(cwd, fact, memoryWriteDir(cwd, memoryHome));
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
exports.MEMORY_KINDS = MEMORY_KINDS;
|
|
457
|
-
exports.appendFact = appendFact;
|
|
458
|
-
exports.appendFactToMarkdown = appendFactToMarkdown;
|
|
459
|
-
exports.claudeProjectMemoryDir = claudeProjectMemoryDir;
|
|
460
|
-
exports.legacyMemoryJsonPath = legacyMemoryJsonPath;
|
|
461
|
-
exports.memoryDir = memoryDir;
|
|
462
|
-
exports.memoryMdPath = memoryMdPath;
|
|
463
|
-
exports.memoryWriteDir = memoryWriteDir;
|
|
464
|
-
exports.notesDir = notesDir;
|
|
465
|
-
exports.readFacts = readFacts;
|
|
466
|
-
exports.readFactsFromMarkdown = readFactsFromMarkdown;
|
|
467
|
-
//# sourceMappingURL=chunk-2UCFUSPW.cjs.map
|
|
468
|
-
//# sourceMappingURL=chunk-2UCFUSPW.cjs.map
|