@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{agent-NVBFTY5H.js → agent-536OOESZ.js} +8 -6
  3. package/dist/{agent-NVBFTY5H.js.map → agent-536OOESZ.js.map} +1 -1
  4. package/dist/{agent-VYPUO4UB.cjs → agent-5KYMG63I.cjs} +9 -7
  5. package/dist/{agent-VYPUO4UB.cjs.map → agent-5KYMG63I.cjs.map} +1 -1
  6. package/dist/{agent-DIu6FooJ.d.ts → agent-BFno7Sfn.d.ts} +36 -21
  7. package/dist/{agent-D3Xr_-6Z.d.cts → agent-X0DKA943.d.cts} +36 -21
  8. package/dist/chunk-A4RAL2ER.cjs +51 -0
  9. package/dist/chunk-A4RAL2ER.cjs.map +1 -0
  10. package/dist/chunk-A4VHSE56.cjs +21 -0
  11. package/dist/chunk-A4VHSE56.cjs.map +1 -0
  12. package/dist/{chunk-CQ2TQ32Y.js → chunk-AMFXSENK.js} +3 -48
  13. package/dist/chunk-AMFXSENK.js.map +1 -0
  14. package/dist/chunk-C7QPML3L.js +831 -0
  15. package/dist/chunk-C7QPML3L.js.map +1 -0
  16. package/dist/{chunk-CWHTMNNK.cjs → chunk-CASUEY42.cjs} +4 -4
  17. package/dist/{chunk-CWHTMNNK.cjs.map → chunk-CASUEY42.cjs.map} +1 -1
  18. package/dist/{chunk-SKOCYA55.js → chunk-CHKH5452.js} +37 -50
  19. package/dist/chunk-CHKH5452.js.map +1 -0
  20. package/dist/chunk-EHJEZAOO.cjs +864 -0
  21. package/dist/chunk-EHJEZAOO.cjs.map +1 -0
  22. package/dist/chunk-IRCJ7EHV.cjs +201 -0
  23. package/dist/chunk-IRCJ7EHV.cjs.map +1 -0
  24. package/dist/chunk-LW7G5DYW.js +19 -0
  25. package/dist/chunk-LW7G5DYW.js.map +1 -0
  26. package/dist/{chunk-IBYRA5PM.js → chunk-MG3SOM3M.js} +3 -3
  27. package/dist/{chunk-IBYRA5PM.js.map → chunk-MG3SOM3M.js.map} +1 -1
  28. package/dist/{chunk-AH6WD7JR.cjs → chunk-NLXOGBRJ.cjs} +45 -58
  29. package/dist/chunk-NLXOGBRJ.cjs.map +1 -0
  30. package/dist/{chunk-SKXBJ2NU.cjs → chunk-NXH4GPAQ.cjs} +2 -49
  31. package/dist/chunk-NXH4GPAQ.cjs.map +1 -0
  32. package/dist/chunk-R6TA2DRJ.js +194 -0
  33. package/dist/chunk-R6TA2DRJ.js.map +1 -0
  34. package/dist/{chunk-6OBIWHDR.cjs → chunk-UUP3MUZ6.cjs} +20 -361
  35. package/dist/chunk-UUP3MUZ6.cjs.map +1 -0
  36. package/dist/chunk-WE22OXQA.js +48 -0
  37. package/dist/chunk-WE22OXQA.js.map +1 -0
  38. package/dist/{chunk-QEKI3YKI.js → chunk-YTR3RBUZ.js} +13 -348
  39. package/dist/chunk-YTR3RBUZ.js.map +1 -0
  40. package/dist/{cron-BcWmzWzT.d.cts → cron-BuiRPrvt.d.cts} +1 -1
  41. package/dist/{cron-De6hzWCF.d.ts → cron-C2SIi31n.d.ts} +1 -1
  42. package/dist/cron.cjs +8 -6
  43. package/dist/cron.d.cts +2 -2
  44. package/dist/cron.d.ts +2 -2
  45. package/dist/cron.js +7 -5
  46. package/dist/eval.cjs +7 -5
  47. package/dist/eval.cjs.map +1 -1
  48. package/dist/eval.js +6 -4
  49. package/dist/eval.js.map +1 -1
  50. package/dist/{index-manager-RHPFVFSC.cjs → index-manager-6TEEIWM7.cjs} +7 -6
  51. package/dist/{index-manager-RHPFVFSC.cjs.map → index-manager-6TEEIWM7.cjs.map} +1 -1
  52. package/dist/{index-manager-AHAYJ33H.js → index-manager-ACEQG3IJ.js} +6 -5
  53. package/dist/{index-manager-AHAYJ33H.js.map → index-manager-ACEQG3IJ.js.map} +1 -1
  54. package/dist/index.cjs +35 -208
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +8 -3
  57. package/dist/index.d.ts +8 -3
  58. package/dist/index.js +22 -195
  59. package/dist/index.js.map +1 -1
  60. package/dist/internal/memory/active-memory.d.ts +8 -1
  61. package/dist/internal/memory/dreaming/diary.d.ts +39 -2
  62. package/dist/internal/memory/index-db.d.ts +44 -1
  63. package/dist/internal/memory/index-manager-contract.d.ts +10 -0
  64. package/dist/internal/memory/index-manager-helpers.d.ts +20 -1
  65. package/dist/internal/memory/index-manager.d.ts +1 -1
  66. package/dist/internal/memory/lance-index.d.ts +15 -1
  67. package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +43 -0
  68. package/dist/internal/memory/storage/index.cjs +95 -12
  69. package/dist/internal/memory/storage/index.d.cts +11 -1
  70. package/dist/internal/memory/storage/index.d.ts +11 -1
  71. package/dist/internal/memory/storage/index.js +4 -1
  72. package/dist/internal/memory/storage/markdown-store.d.cts +13 -42
  73. package/dist/internal/memory/storage/markdown-store.d.ts +13 -42
  74. package/dist/internal/memory/storage/memory-root.d.cts +93 -0
  75. package/dist/internal/memory/storage/memory-root.d.ts +93 -0
  76. package/dist/internal/memory/storage/session-loader.d.cts +35 -1
  77. package/dist/internal/memory/storage/session-loader.d.ts +35 -1
  78. package/dist/internal/memory/storage/session-summary-writer.d.cts +50 -2
  79. package/dist/internal/memory/storage/session-summary-writer.d.ts +50 -2
  80. package/dist/internal/memory/storage/transcript-store.d.cts +38 -1
  81. package/dist/internal/memory/storage/transcript-store.d.ts +38 -1
  82. package/dist/internal/memory/storage/wiki-loader.d.cts +27 -2
  83. package/dist/internal/memory/storage/wiki-loader.d.ts +27 -2
  84. package/dist/internal/memory/tools.d.ts +8 -1
  85. package/dist/internal/memory/types.d.ts +5 -0
  86. package/dist/internal/persistence/index.cjs +12 -11
  87. package/dist/internal/persistence/index.cjs.map +1 -1
  88. package/dist/internal/persistence/index.js +2 -1
  89. package/dist/internal/persistence/index.js.map +1 -1
  90. package/dist/internal/persistence/session-dir.d.cts +0 -15
  91. package/dist/internal/persistence/session-dir.d.ts +0 -15
  92. package/dist/internal/runtime/memory/memory-store.d.ts +2 -2
  93. package/dist/persistence.cjs +9 -8
  94. package/dist/persistence.cjs.map +1 -1
  95. package/dist/persistence.js +2 -1
  96. package/dist/persistence.js.map +1 -1
  97. package/dist/subagents-loader.d.cts +1 -1
  98. package/dist/subagents-loader.d.ts +1 -1
  99. package/dist/types/agent.d.ts +11 -0
  100. package/dist/types/memory-provider.d.ts +8 -0
  101. package/docs/error-codes.md +10 -9
  102. package/docs/harness-capability-map.md +57 -17
  103. package/docs/memory-decisions.md +44 -7
  104. package/package.json +1 -1
  105. package/dist/chunk-2UCFUSPW.cjs +0 -468
  106. package/dist/chunk-2UCFUSPW.cjs.map +0 -1
  107. package/dist/chunk-6OBIWHDR.cjs.map +0 -1
  108. package/dist/chunk-AH6WD7JR.cjs.map +0 -1
  109. package/dist/chunk-CQ2TQ32Y.js.map +0 -1
  110. package/dist/chunk-QEKI3YKI.js.map +0 -1
  111. package/dist/chunk-SKOCYA55.js.map +0 -1
  112. package/dist/chunk-SKXBJ2NU.cjs.map +0 -1
  113. package/dist/chunk-WCLDJSMY.js +0 -456
  114. package/dist/chunk-WCLDJSMY.js.map +0 -1
@@ -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 `local.sessionDir` set, memory *facts* are written to
22
- `<sessionDir>/projects/<encoded-cwd>/memory/` — where the CLI reads them. So a project that never
23
- adopted this SDK ends up with a `.theokit/` directory anyway, which reads like the "write where 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 **whether or not
182
- `local.sessionDir` is set**, so a consumer who never opted into CLI interop still gets it. And the
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 `local.sessionDir` is set | `markdown-store.ts:114` — `claudeProjectMemoryDir(cwd)` is an unconditional read root | 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. |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theokit/sdk",
3
- "version": "4.62.0",
3
+ "version": "4.63.0",
4
4
  "description": "TypeScript SDK for the Theo agent harness — same surface, local or cloud.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/usetheokit/theokit-sdk#readme",
@@ -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