@hoilab/ada-cli 0.84.21 → 0.84.22

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 (344) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.es.md +163 -0
  3. package/README.md +99 -652
  4. package/dist/cli/args.d.ts +3 -0
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +11 -0
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/file-processor.d.ts.map +1 -1
  9. package/dist/cli/file-processor.js +2 -1
  10. package/dist/cli/file-processor.js.map +1 -1
  11. package/dist/cli/home-screen.d.ts.map +1 -1
  12. package/dist/cli/home-screen.js +2 -2
  13. package/dist/cli/home-screen.js.map +1 -1
  14. package/dist/cli/startup-ui.d.ts.map +1 -1
  15. package/dist/cli/startup-ui.js +9 -3
  16. package/dist/cli/startup-ui.js.map +1 -1
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +2 -1
  19. package/dist/config.js.map +1 -1
  20. package/dist/core/agent-session.d.ts +57 -7
  21. package/dist/core/agent-session.d.ts.map +1 -1
  22. package/dist/core/agent-session.js +449 -73
  23. package/dist/core/agent-session.js.map +1 -1
  24. package/dist/core/auth-storage.d.ts.map +1 -1
  25. package/dist/core/auth-storage.js +4 -3
  26. package/dist/core/auth-storage.js.map +1 -1
  27. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  28. package/dist/core/compaction/branch-summarization.js +3 -0
  29. package/dist/core/compaction/branch-summarization.js.map +1 -1
  30. package/dist/core/compaction/compaction.d.ts +4 -3
  31. package/dist/core/compaction/compaction.d.ts.map +1 -1
  32. package/dist/core/compaction/compaction.js +41 -32
  33. package/dist/core/compaction/compaction.js.map +1 -1
  34. package/dist/core/defaults.d.ts +1 -0
  35. package/dist/core/defaults.d.ts.map +1 -1
  36. package/dist/core/defaults.js +9 -0
  37. package/dist/core/defaults.js.map +1 -1
  38. package/dist/core/engine-personality.d.ts +12 -0
  39. package/dist/core/engine-personality.d.ts.map +1 -0
  40. package/dist/core/engine-personality.js +53 -0
  41. package/dist/core/engine-personality.js.map +1 -0
  42. package/dist/core/extensions/index.d.ts +1 -1
  43. package/dist/core/extensions/index.d.ts.map +1 -1
  44. package/dist/core/extensions/index.js.map +1 -1
  45. package/dist/core/extensions/loader.d.ts.map +1 -1
  46. package/dist/core/extensions/loader.js +10 -5
  47. package/dist/core/extensions/loader.js.map +1 -1
  48. package/dist/core/extensions/types.d.ts +23 -4
  49. package/dist/core/extensions/types.d.ts.map +1 -1
  50. package/dist/core/extensions/types.js.map +1 -1
  51. package/dist/core/keybindings.d.ts.map +1 -1
  52. package/dist/core/keybindings.js +2 -1
  53. package/dist/core/keybindings.js.map +1 -1
  54. package/dist/core/memory-engine/audit-export.d.ts +54 -0
  55. package/dist/core/memory-engine/audit-export.d.ts.map +1 -0
  56. package/dist/core/memory-engine/audit-export.js +85 -0
  57. package/dist/core/memory-engine/audit-export.js.map +1 -0
  58. package/dist/core/memory-engine/audit.d.ts.map +1 -1
  59. package/dist/core/memory-engine/audit.js +7 -8
  60. package/dist/core/memory-engine/audit.js.map +1 -1
  61. package/dist/core/memory-engine/engine.d.ts +2 -3
  62. package/dist/core/memory-engine/engine.d.ts.map +1 -1
  63. package/dist/core/memory-engine/engine.js +148 -50
  64. package/dist/core/memory-engine/engine.js.map +1 -1
  65. package/dist/core/memory-engine/inverted-index.d.ts.map +1 -1
  66. package/dist/core/memory-engine/inverted-index.js +67 -10
  67. package/dist/core/memory-engine/inverted-index.js.map +1 -1
  68. package/dist/core/memory-engine/keychain.d.ts.map +1 -1
  69. package/dist/core/memory-engine/keychain.js +1 -6
  70. package/dist/core/memory-engine/keychain.js.map +1 -1
  71. package/dist/core/memory-engine/observation-store.d.ts +1 -0
  72. package/dist/core/memory-engine/observation-store.d.ts.map +1 -1
  73. package/dist/core/memory-engine/observation-store.js +37 -8
  74. package/dist/core/memory-engine/observation-store.js.map +1 -1
  75. package/dist/core/memory-engine/scanner.d.ts.map +1 -1
  76. package/dist/core/memory-engine/scanner.js +25 -5
  77. package/dist/core/memory-engine/scanner.js.map +1 -1
  78. package/dist/core/memory-engine/security.d.ts.map +1 -1
  79. package/dist/core/memory-engine/security.js +131 -23
  80. package/dist/core/memory-engine/security.js.map +1 -1
  81. package/dist/core/memory-engine/session-indexer.d.ts.map +1 -1
  82. package/dist/core/memory-engine/session-indexer.js +8 -2
  83. package/dist/core/memory-engine/session-indexer.js.map +1 -1
  84. package/dist/core/memory-engine/tools.d.ts +2 -2
  85. package/dist/core/memory-engine/tools.d.ts.map +1 -1
  86. package/dist/core/memory-engine/tools.js +14 -7
  87. package/dist/core/memory-engine/tools.js.map +1 -1
  88. package/dist/core/memory-engine/vault.d.ts.map +1 -1
  89. package/dist/core/memory-engine/vault.js +2 -2
  90. package/dist/core/memory-engine/vault.js.map +1 -1
  91. package/dist/core/model-config.d.ts.map +1 -1
  92. package/dist/core/model-config.js +2 -1
  93. package/dist/core/model-config.js.map +1 -1
  94. package/dist/core/model-resolver.d.ts +1 -0
  95. package/dist/core/model-resolver.d.ts.map +1 -1
  96. package/dist/core/model-resolver.js +12 -6
  97. package/dist/core/model-resolver.js.map +1 -1
  98. package/dist/core/models-store.d.ts.map +1 -1
  99. package/dist/core/models-store.js +2 -1
  100. package/dist/core/models-store.js.map +1 -1
  101. package/dist/core/package-manager.d.ts.map +1 -1
  102. package/dist/core/package-manager.js +11 -6
  103. package/dist/core/package-manager.js.map +1 -1
  104. package/dist/core/pi-manifest.d.ts.map +1 -1
  105. package/dist/core/pi-manifest.js +2 -1
  106. package/dist/core/pi-manifest.js.map +1 -1
  107. package/dist/core/prompt-optimizer.d.ts +53 -0
  108. package/dist/core/prompt-optimizer.d.ts.map +1 -0
  109. package/dist/core/prompt-optimizer.js +138 -0
  110. package/dist/core/prompt-optimizer.js.map +1 -0
  111. package/dist/core/provider-attribution.d.ts.map +1 -1
  112. package/dist/core/provider-attribution.js +1 -1
  113. package/dist/core/provider-attribution.js.map +1 -1
  114. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  115. package/dist/core/remote-catalog-provider.js +2 -1
  116. package/dist/core/remote-catalog-provider.js.map +1 -1
  117. package/dist/core/resource-loader.d.ts +50 -0
  118. package/dist/core/resource-loader.d.ts.map +1 -1
  119. package/dist/core/resource-loader.js +233 -7
  120. package/dist/core/resource-loader.js.map +1 -1
  121. package/dist/core/sdk.d.ts +8 -0
  122. package/dist/core/sdk.d.ts.map +1 -1
  123. package/dist/core/sdk.js +11 -1
  124. package/dist/core/sdk.js.map +1 -1
  125. package/dist/core/session-manager.d.ts.map +1 -1
  126. package/dist/core/session-manager.js +2 -1
  127. package/dist/core/session-manager.js.map +1 -1
  128. package/dist/core/settings-diagnostics.d.ts +9 -0
  129. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  130. package/dist/core/settings-diagnostics.js +21 -0
  131. package/dist/core/settings-diagnostics.js.map +1 -0
  132. package/dist/core/settings-manager.d.ts +52 -0
  133. package/dist/core/settings-manager.d.ts.map +1 -1
  134. package/dist/core/settings-manager.js +204 -10
  135. package/dist/core/settings-manager.js.map +1 -1
  136. package/dist/core/skills.d.ts.map +1 -1
  137. package/dist/core/skills.js +51 -33
  138. package/dist/core/skills.js.map +1 -1
  139. package/dist/core/slash-commands.d.ts.map +1 -1
  140. package/dist/core/slash-commands.js +2 -1
  141. package/dist/core/slash-commands.js.map +1 -1
  142. package/dist/core/tools/edit-diff.d.ts +0 -5
  143. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  144. package/dist/core/tools/edit-diff.js +2 -5
  145. package/dist/core/tools/edit-diff.js.map +1 -1
  146. package/dist/core/tools/edit.d.ts.map +1 -1
  147. package/dist/core/tools/edit.js +20 -4
  148. package/dist/core/tools/edit.js.map +1 -1
  149. package/dist/core/tools/index.d.ts.map +1 -1
  150. package/dist/core/tools/index.js +2 -2
  151. package/dist/core/tools/index.js.map +1 -1
  152. package/dist/core/tools/project-memory-tool.d.ts.map +1 -1
  153. package/dist/core/tools/project-memory-tool.js +4 -1
  154. package/dist/core/tools/project-memory-tool.js.map +1 -1
  155. package/dist/core/trust-manager.d.ts.map +1 -1
  156. package/dist/core/trust-manager.js +2 -1
  157. package/dist/core/trust-manager.js.map +1 -1
  158. package/dist/core/workflows/agent-runner.d.ts +86 -0
  159. package/dist/core/workflows/agent-runner.d.ts.map +1 -0
  160. package/dist/core/workflows/agent-runner.js +225 -0
  161. package/dist/core/workflows/agent-runner.js.map +1 -0
  162. package/dist/core/workflows/builtins.d.ts +41 -0
  163. package/dist/core/workflows/builtins.d.ts.map +1 -0
  164. package/dist/core/workflows/builtins.js +282 -0
  165. package/dist/core/workflows/builtins.js.map +1 -0
  166. package/dist/core/workflows/goal-engine/engine.d.ts +134 -0
  167. package/dist/core/workflows/goal-engine/engine.d.ts.map +1 -0
  168. package/dist/core/workflows/goal-engine/engine.js +462 -0
  169. package/dist/core/workflows/goal-engine/engine.js.map +1 -0
  170. package/dist/core/workflows/goal-engine/errors.d.ts +7 -0
  171. package/dist/core/workflows/goal-engine/errors.d.ts.map +1 -0
  172. package/dist/core/workflows/goal-engine/errors.js +9 -0
  173. package/dist/core/workflows/goal-engine/errors.js.map +1 -0
  174. package/dist/core/workflows/goal-engine/index.d.ts +13 -0
  175. package/dist/core/workflows/goal-engine/index.d.ts.map +1 -0
  176. package/dist/core/workflows/goal-engine/index.js +8 -0
  177. package/dist/core/workflows/goal-engine/index.js.map +1 -0
  178. package/dist/core/workflows/goal-engine/persistence.d.ts +31 -0
  179. package/dist/core/workflows/goal-engine/persistence.d.ts.map +1 -0
  180. package/dist/core/workflows/goal-engine/persistence.js +145 -0
  181. package/dist/core/workflows/goal-engine/persistence.js.map +1 -0
  182. package/dist/core/workflows/goal-engine/prompts.d.ts +37 -0
  183. package/dist/core/workflows/goal-engine/prompts.d.ts.map +1 -0
  184. package/dist/core/workflows/goal-engine/prompts.js +131 -0
  185. package/dist/core/workflows/goal-engine/prompts.js.map +1 -0
  186. package/dist/core/workflows/goal-engine/state.d.ts +41 -0
  187. package/dist/core/workflows/goal-engine/state.d.ts.map +1 -0
  188. package/dist/core/workflows/goal-engine/state.js +397 -0
  189. package/dist/core/workflows/goal-engine/state.js.map +1 -0
  190. package/dist/core/workflows/goal-engine/tools.d.ts +31 -0
  191. package/dist/core/workflows/goal-engine/tools.d.ts.map +1 -0
  192. package/dist/core/workflows/goal-engine/tools.js +211 -0
  193. package/dist/core/workflows/goal-engine/tools.js.map +1 -0
  194. package/dist/core/workflows/goal-engine/types.d.ts +286 -0
  195. package/dist/core/workflows/goal-engine/types.d.ts.map +1 -0
  196. package/dist/core/workflows/goal-engine/types.js +17 -0
  197. package/dist/core/workflows/goal-engine/types.js.map +1 -0
  198. package/dist/core/workflows/goal-engine/worktree-fingerprint.d.ts +23 -0
  199. package/dist/core/workflows/goal-engine/worktree-fingerprint.d.ts.map +1 -0
  200. package/dist/core/workflows/goal-engine/worktree-fingerprint.js +72 -0
  201. package/dist/core/workflows/goal-engine/worktree-fingerprint.js.map +1 -0
  202. package/dist/core/workflows/graph-sync.d.ts +67 -0
  203. package/dist/core/workflows/graph-sync.d.ts.map +1 -0
  204. package/dist/core/workflows/graph-sync.js +188 -0
  205. package/dist/core/workflows/graph-sync.js.map +1 -0
  206. package/dist/core/workflows/graph.d.ts +143 -0
  207. package/dist/core/workflows/graph.d.ts.map +1 -0
  208. package/dist/core/workflows/graph.js +319 -0
  209. package/dist/core/workflows/graph.js.map +1 -0
  210. package/dist/core/workflows/index.d.ts +18 -0
  211. package/dist/core/workflows/index.d.ts.map +1 -0
  212. package/dist/core/workflows/index.js +14 -0
  213. package/dist/core/workflows/index.js.map +1 -0
  214. package/dist/core/workflows/journal.d.ts +68 -0
  215. package/dist/core/workflows/journal.d.ts.map +1 -0
  216. package/dist/core/workflows/journal.js +83 -0
  217. package/dist/core/workflows/journal.js.map +1 -0
  218. package/dist/core/workflows/manager.d.ts +112 -0
  219. package/dist/core/workflows/manager.d.ts.map +1 -0
  220. package/dist/core/workflows/manager.js +327 -0
  221. package/dist/core/workflows/manager.js.map +1 -0
  222. package/dist/core/workflows/resolve-script.d.ts +35 -0
  223. package/dist/core/workflows/resolve-script.d.ts.map +1 -0
  224. package/dist/core/workflows/resolve-script.js +66 -0
  225. package/dist/core/workflows/resolve-script.js.map +1 -0
  226. package/dist/core/workflows/runtime.d.ts +49 -0
  227. package/dist/core/workflows/runtime.d.ts.map +1 -0
  228. package/dist/core/workflows/runtime.js +301 -0
  229. package/dist/core/workflows/runtime.js.map +1 -0
  230. package/dist/core/workflows/shared-store.d.ts +31 -0
  231. package/dist/core/workflows/shared-store.d.ts.map +1 -0
  232. package/dist/core/workflows/shared-store.js +80 -0
  233. package/dist/core/workflows/shared-store.js.map +1 -0
  234. package/dist/core/workflows/subagent-host.d.ts +50 -0
  235. package/dist/core/workflows/subagent-host.d.ts.map +1 -0
  236. package/dist/core/workflows/subagent-host.js +225 -0
  237. package/dist/core/workflows/subagent-host.js.map +1 -0
  238. package/dist/core/workflows/tool-workflow.d.ts +22 -0
  239. package/dist/core/workflows/tool-workflow.d.ts.map +1 -0
  240. package/dist/core/workflows/tool-workflow.js +120 -0
  241. package/dist/core/workflows/tool-workflow.js.map +1 -0
  242. package/dist/core/workflows/types.d.ts +142 -0
  243. package/dist/core/workflows/types.d.ts.map +1 -0
  244. package/dist/core/workflows/types.js +26 -0
  245. package/dist/core/workflows/types.js.map +1 -0
  246. package/dist/core/workflows/worktree.d.ts +42 -0
  247. package/dist/core/workflows/worktree.d.ts.map +1 -0
  248. package/dist/core/workflows/worktree.js +107 -0
  249. package/dist/core/workflows/worktree.js.map +1 -0
  250. package/dist/extensions/llama/index.d.ts.map +1 -1
  251. package/dist/extensions/llama/index.js +2 -0
  252. package/dist/extensions/llama/index.js.map +1 -1
  253. package/dist/extensions/llama/provider.d.ts.map +1 -1
  254. package/dist/extensions/llama/provider.js +6 -2
  255. package/dist/extensions/llama/provider.js.map +1 -1
  256. package/dist/index.d.ts +16 -12
  257. package/dist/index.d.ts.map +1 -1
  258. package/dist/index.js +14 -11
  259. package/dist/index.js.map +1 -1
  260. package/dist/main.d.ts +4 -0
  261. package/dist/main.d.ts.map +1 -1
  262. package/dist/main.js +20 -15
  263. package/dist/main.js.map +1 -1
  264. package/dist/migrations.d.ts.map +1 -1
  265. package/dist/migrations.js +4 -3
  266. package/dist/migrations.js.map +1 -1
  267. package/dist/modes/interactive/components/home-screen.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/home-screen.js +2 -2
  269. package/dist/modes/interactive/components/home-screen.js.map +1 -1
  270. package/dist/modes/interactive/components/model-selector.d.ts +2 -3
  271. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/model-selector.js +16 -6
  273. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  274. package/dist/modes/interactive/components/prompt-optimization.d.ts +16 -0
  275. package/dist/modes/interactive/components/prompt-optimization.d.ts.map +1 -0
  276. package/dist/modes/interactive/components/prompt-optimization.js +46 -0
  277. package/dist/modes/interactive/components/prompt-optimization.js.map +1 -0
  278. package/dist/modes/interactive/components/settings-selector.d.ts +19 -1
  279. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  280. package/dist/modes/interactive/components/settings-selector.js +247 -46
  281. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  282. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  283. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  284. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  285. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  286. package/dist/modes/interactive/components/thinking-selector.d.ts +8 -3
  287. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/thinking-selector.js +29 -3
  289. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  290. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  291. package/dist/modes/interactive/external-editor.js +2 -1
  292. package/dist/modes/interactive/external-editor.js.map +1 -1
  293. package/dist/modes/interactive/interactive-mode.d.ts +24 -0
  294. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  295. package/dist/modes/interactive/interactive-mode.js +677 -34
  296. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  297. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  298. package/dist/modes/interactive/theme/theme.js +4 -3
  299. package/dist/modes/interactive/theme/theme.js.map +1 -1
  300. package/dist/modes/json-event.d.ts +16 -13
  301. package/dist/modes/json-event.d.ts.map +1 -1
  302. package/dist/modes/json-event.js +20 -6
  303. package/dist/modes/json-event.js.map +1 -1
  304. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  305. package/dist/modes/rpc/rpc-mode.js +173 -0
  306. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  307. package/dist/modes/rpc/rpc-types.d.ts +29 -0
  308. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  309. package/dist/modes/rpc/rpc-types.js.map +1 -1
  310. package/dist/package-manager-cli.d.ts.map +1 -1
  311. package/dist/package-manager-cli.js +1 -1
  312. package/dist/package-manager-cli.js.map +1 -1
  313. package/dist/skills/ada-engine/SKILL.md +153 -0
  314. package/dist/utils/frontmatter.d.ts.map +1 -1
  315. package/dist/utils/frontmatter.js +2 -1
  316. package/dist/utils/frontmatter.js.map +1 -1
  317. package/dist/utils/management-http.d.ts +5 -3
  318. package/dist/utils/management-http.d.ts.map +1 -1
  319. package/dist/utils/management-http.js +14 -10
  320. package/dist/utils/management-http.js.map +1 -1
  321. package/dist/utils/text.d.ts +8 -0
  322. package/dist/utils/text.d.ts.map +1 -0
  323. package/dist/utils/text.js +9 -0
  324. package/dist/utils/text.js.map +1 -0
  325. package/docs/compaction.md +15 -0
  326. package/docs/custom-provider.md +5 -2
  327. package/docs/extensions.md +11 -2
  328. package/docs/json.md +7 -2
  329. package/docs/llama-cpp.md +2 -0
  330. package/docs/models.md +8 -2
  331. package/docs/rpc.md +9 -3
  332. package/docs/settings.md +2 -2
  333. package/docs/skills.md +4 -3
  334. package/examples/extensions/border-status-editor.ts +1 -1
  335. package/examples/extensions/git-checkpoint.ts +2 -2
  336. package/examples/extensions/notify.ts +3 -1
  337. package/examples/extensions/subagent/README.md +1 -1
  338. package/examples/extensions/subagent/index.ts +6 -1
  339. package/examples/extensions/titlebar-spinner.ts +1 -1
  340. package/examples/rpc-extension-ui.ts +1 -1
  341. package/examples/sdk/06-extensions.ts +1 -1
  342. package/examples/sdk/README.md +1 -1
  343. package/npm-shrinkwrap.json +2 -12
  344. package/package.json +3 -3
@@ -8,11 +8,23 @@ const THREAT_PATTERNS = [
8
8
  { pattern: /you\s+are\s+now\s+/i, id: "role_hijack", severity: "high" },
9
9
  { pattern: /do\s+not\s+tell\s+the\s+user/i, id: "deception_hide", severity: "high" },
10
10
  { pattern: /system\s+prompt\s+override/i, id: "sys_prompt_override", severity: "high" },
11
- { pattern: /disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i, id: "disregard_rules", severity: "high" },
12
- { pattern: /act\s+as\s+(if|though)\s+you\s+(have\s+no|don'?t\s+have)\s+(restrictions|limits|rules)/i, id: "bypass_restrictions", severity: "high" },
11
+ {
12
+ pattern: /disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i,
13
+ id: "disregard_rules",
14
+ severity: "high",
15
+ },
16
+ {
17
+ pattern: /act\s+as\s+(if|though)\s+you\s+(have\s+no|don'?t\s+have)\s+(restrictions|limits|rules)/i,
18
+ id: "bypass_restrictions",
19
+ severity: "high",
20
+ },
13
21
  { pattern: /curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: "exfil_curl", severity: "high" },
14
22
  { pattern: /wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: "exfil_wget", severity: "high" },
15
- { pattern: /cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i, id: "read_secrets", severity: "high" },
23
+ {
24
+ pattern: /cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i,
25
+ id: "read_secrets",
26
+ severity: "high",
27
+ },
16
28
  { pattern: /authorized_keys/i, id: "ssh_backdoor", severity: "high" },
17
29
  { pattern: /\$HOME\/\.ssh|~\/\.ssh/i, id: "ssh_access", severity: "high" },
18
30
  ];
@@ -39,8 +51,16 @@ const SECRET_PATTERNS = [
39
51
  { pattern: /\btoken\s*[=:]\s*\S{10,}\b/i, id: "token_assignment", severity: "medium" },
40
52
  ];
41
53
  const INVISIBLE_CHARS = new Set([
42
- "\u200b", "\u200c", "\u200d", "\u2060", "\ufeff",
43
- "\u202a", "\u202b", "\u202c", "\u202d", "\u202e",
54
+ "\u200b",
55
+ "\u200c",
56
+ "\u200d",
57
+ "\u2060",
58
+ "\ufeff",
59
+ "\u202a",
60
+ "\u202b",
61
+ "\u202c",
62
+ "\u202d",
63
+ "\u202e",
44
64
  ]);
45
65
  /**
46
66
  * Scan memory content before persisting. Returns an error message when the
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/core/memory-engine/scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Content scanner — blocks prompt-injection, exfiltration and secret leaks\n * from ever being persisted into memory (pattern set captured from\n * pi-hermes-memory's content-scanner, which ported it from Hermes).\n */\n\ninterface ScanIssue {\n\tpattern: RegExp;\n\tid: string;\n\tseverity: \"high\" | \"medium\";\n}\n\nconst THREAT_PATTERNS: ScanIssue[] = [\n\t{ pattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, id: \"prompt_injection\", severity: \"high\" },\n\t{ pattern: /you\\s+are\\s+now\\s+/i, id: \"role_hijack\", severity: \"high\" },\n\t{ pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, id: \"deception_hide\", severity: \"high\" },\n\t{ pattern: /system\\s+prompt\\s+override/i, id: \"sys_prompt_override\", severity: \"high\" },\n\t{ pattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i, id: \"disregard_rules\", severity: \"high\" },\n\t{ pattern: /act\\s+as\\s+(if|though)\\s+you\\s+(have\\s+no|don'?t\\s+have)\\s+(restrictions|limits|rules)/i, id: \"bypass_restrictions\", severity: \"high\" },\n\t{ pattern: /curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: \"exfil_curl\", severity: \"high\" },\n\t{ pattern: /wget\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: \"exfil_wget\", severity: \"high\" },\n\t{ pattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i, id: \"read_secrets\", severity: \"high\" },\n\t{ pattern: /authorized_keys/i, id: \"ssh_backdoor\", severity: \"high\" },\n\t{ pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, id: \"ssh_access\", severity: \"high\" },\n];\n\nconst SECRET_PATTERNS: ScanIssue[] = [\n\t{ pattern: /\\bsk-ant-api\\S{10,}\\b/, id: \"anthropic_api_key\", severity: \"high\" },\n\t{ pattern: /\\bsk-or-v1-\\S{10,}\\b/, id: \"openrouter_api_key\", severity: \"high\" },\n\t{ pattern: /\\bsk-\\S{20,}\\b/, id: \"openai_api_key\", severity: \"high\" },\n\t{ pattern: /\\bAKIA[0-9A-Z]{16}\\b/, id: \"aws_access_key\", severity: \"high\" },\n\t{ pattern: /\\bghp_\\S{10,}\\b/, id: \"github_personal_token\", severity: \"high\" },\n\t{ pattern: /\\bghu_\\S{10,}\\b/, id: \"github_user_token\", severity: \"high\" },\n\t{ pattern: /\\bxoxb-\\S{10,}\\b/, id: \"slack_bot_token\", severity: \"high\" },\n\t{ pattern: /\\bxapp-\\S{10,}\\b/, id: \"slack_app_token\", severity: \"high\" },\n\t{ pattern: /\\bntn_\\S{10,}\\b/, id: \"notion_token\", severity: \"high\" },\n\t{ pattern: /\\bBearer\\s+\\S{20,}\\b/, id: \"bearer_auth_token\", severity: \"high\" },\n\t{ pattern: /-----BEGIN\\s+(?:RSA\\s+)?PRIVATE\\sKEY-----/, id: \"private_key_block\", severity: \"high\" },\n\t{ pattern: /\\bANTHROPIC_API_KEY\\b/, id: \"env_anthropic_key\", severity: \"medium\" },\n\t{ pattern: /\\bOPENAI_API_KEY\\b/, id: \"env_openai_key\", severity: \"medium\" },\n\t{ pattern: /\\bOPENROUTER_API_KEY\\b/, id: \"env_openrouter_key\", severity: \"medium\" },\n\t{ pattern: /\\bGITHUB_TOKEN\\b/, id: \"env_github_token\", severity: \"medium\" },\n\t{ pattern: /\\bAWS_SECRET_ACCESS_KEY\\b/, id: \"env_aws_secret\", severity: \"medium\" },\n\t{ pattern: /\\bDATABASE_URL\\b/, id: \"env_database_url\", severity: \"medium\" },\n\t{ pattern: /\\bpassword\\s*[=:]\\s*\\S{6,}\\b/i, id: \"password_assignment\", severity: \"medium\" },\n\t{ pattern: /\\bsecret\\s*[=:]\\s*\\S{6,}\\b/i, id: \"secret_assignment\", severity: \"medium\" },\n\t{ pattern: /\\btoken\\s*[=:]\\s*\\S{10,}\\b/i, id: \"token_assignment\", severity: \"medium\" },\n];\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\", \"\\u200c\", \"\\u200d\", \"\\u2060\", \"\\ufeff\",\n\t\"\\u202a\", \"\\u202b\", \"\\u202c\", \"\\u202d\", \"\\u202e\",\n]);\n\n/**\n * Scan memory content before persisting. Returns an error message when the\n * content must be rejected, or null when it is safe to store.\n */\nexport function scanContent(content: string): string | null {\n\tconst visible = [...content].filter((ch) => !INVISIBLE_CHARS.has(ch)).join(\"\");\n\tif (visible !== content) {\n\t\treturn \"Content contains invisible/zero-width characters and was rejected for safety.\";\n\t}\n\tfor (const issue of THREAT_PATTERNS) {\n\t\tif (issue.pattern.test(content)) {\n\t\t\treturn `Content was rejected: looks like prompt injection or data exfiltration (${issue.id}).`;\n\t\t}\n\t}\n\tfor (const issue of SECRET_PATTERNS) {\n\t\tif (issue.pattern.test(content)) {\n\t\t\treturn `Content was rejected: possible secret/credential detected (${issue.id}). Secrets are never stored in memory.`;\n\t\t}\n\t}\n\treturn null;\n}\n"]}
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/core/memory-engine/scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Content scanner — blocks prompt-injection, exfiltration and secret leaks\n * from ever being persisted into memory (pattern set captured from\n * pi-hermes-memory's content-scanner, which ported it from Hermes).\n */\n\ninterface ScanIssue {\n\tpattern: RegExp;\n\tid: string;\n\tseverity: \"high\" | \"medium\";\n}\n\nconst THREAT_PATTERNS: ScanIssue[] = [\n\t{ pattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, id: \"prompt_injection\", severity: \"high\" },\n\t{ pattern: /you\\s+are\\s+now\\s+/i, id: \"role_hijack\", severity: \"high\" },\n\t{ pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, id: \"deception_hide\", severity: \"high\" },\n\t{ pattern: /system\\s+prompt\\s+override/i, id: \"sys_prompt_override\", severity: \"high\" },\n\t{\n\t\tpattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i,\n\t\tid: \"disregard_rules\",\n\t\tseverity: \"high\",\n\t},\n\t{\n\t\tpattern: /act\\s+as\\s+(if|though)\\s+you\\s+(have\\s+no|don'?t\\s+have)\\s+(restrictions|limits|rules)/i,\n\t\tid: \"bypass_restrictions\",\n\t\tseverity: \"high\",\n\t},\n\t{ pattern: /curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: \"exfil_curl\", severity: \"high\" },\n\t{ pattern: /wget\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, id: \"exfil_wget\", severity: \"high\" },\n\t{\n\t\tpattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i,\n\t\tid: \"read_secrets\",\n\t\tseverity: \"high\",\n\t},\n\t{ pattern: /authorized_keys/i, id: \"ssh_backdoor\", severity: \"high\" },\n\t{ pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, id: \"ssh_access\", severity: \"high\" },\n];\n\nconst SECRET_PATTERNS: ScanIssue[] = [\n\t{ pattern: /\\bsk-ant-api\\S{10,}\\b/, id: \"anthropic_api_key\", severity: \"high\" },\n\t{ pattern: /\\bsk-or-v1-\\S{10,}\\b/, id: \"openrouter_api_key\", severity: \"high\" },\n\t{ pattern: /\\bsk-\\S{20,}\\b/, id: \"openai_api_key\", severity: \"high\" },\n\t{ pattern: /\\bAKIA[0-9A-Z]{16}\\b/, id: \"aws_access_key\", severity: \"high\" },\n\t{ pattern: /\\bghp_\\S{10,}\\b/, id: \"github_personal_token\", severity: \"high\" },\n\t{ pattern: /\\bghu_\\S{10,}\\b/, id: \"github_user_token\", severity: \"high\" },\n\t{ pattern: /\\bxoxb-\\S{10,}\\b/, id: \"slack_bot_token\", severity: \"high\" },\n\t{ pattern: /\\bxapp-\\S{10,}\\b/, id: \"slack_app_token\", severity: \"high\" },\n\t{ pattern: /\\bntn_\\S{10,}\\b/, id: \"notion_token\", severity: \"high\" },\n\t{ pattern: /\\bBearer\\s+\\S{20,}\\b/, id: \"bearer_auth_token\", severity: \"high\" },\n\t{ pattern: /-----BEGIN\\s+(?:RSA\\s+)?PRIVATE\\sKEY-----/, id: \"private_key_block\", severity: \"high\" },\n\t{ pattern: /\\bANTHROPIC_API_KEY\\b/, id: \"env_anthropic_key\", severity: \"medium\" },\n\t{ pattern: /\\bOPENAI_API_KEY\\b/, id: \"env_openai_key\", severity: \"medium\" },\n\t{ pattern: /\\bOPENROUTER_API_KEY\\b/, id: \"env_openrouter_key\", severity: \"medium\" },\n\t{ pattern: /\\bGITHUB_TOKEN\\b/, id: \"env_github_token\", severity: \"medium\" },\n\t{ pattern: /\\bAWS_SECRET_ACCESS_KEY\\b/, id: \"env_aws_secret\", severity: \"medium\" },\n\t{ pattern: /\\bDATABASE_URL\\b/, id: \"env_database_url\", severity: \"medium\" },\n\t{ pattern: /\\bpassword\\s*[=:]\\s*\\S{6,}\\b/i, id: \"password_assignment\", severity: \"medium\" },\n\t{ pattern: /\\bsecret\\s*[=:]\\s*\\S{6,}\\b/i, id: \"secret_assignment\", severity: \"medium\" },\n\t{ pattern: /\\btoken\\s*[=:]\\s*\\S{10,}\\b/i, id: \"token_assignment\", severity: \"medium\" },\n];\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\",\n\t\"\\u200c\",\n\t\"\\u200d\",\n\t\"\\u2060\",\n\t\"\\ufeff\",\n\t\"\\u202a\",\n\t\"\\u202b\",\n\t\"\\u202c\",\n\t\"\\u202d\",\n\t\"\\u202e\",\n]);\n\n/**\n * Scan memory content before persisting. Returns an error message when the\n * content must be rejected, or null when it is safe to store.\n */\nexport function scanContent(content: string): string | null {\n\tconst visible = [...content].filter((ch) => !INVISIBLE_CHARS.has(ch)).join(\"\");\n\tif (visible !== content) {\n\t\treturn \"Content contains invisible/zero-width characters and was rejected for safety.\";\n\t}\n\tfor (const issue of THREAT_PATTERNS) {\n\t\tif (issue.pattern.test(content)) {\n\t\t\treturn `Content was rejected: looks like prompt injection or data exfiltration (${issue.id}).`;\n\t\t}\n\t}\n\tfor (const issue of SECRET_PATTERNS) {\n\t\tif (issue.pattern.test(content)) {\n\t\t\treturn `Content was rejected: possible secret/credential detected (${issue.id}). Secrets are never stored in memory.`;\n\t\t}\n\t}\n\treturn null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAC1B,YAAY,GACZ,SAAS,GACT,mBAAmB,GACnB,WAAW,GACX,WAAW,GACX,QAAQ,GACR,UAAU,GACV,SAAS,GACT,WAAW,GACX,cAAc,CAAC;AAElB,MAAM,WAAW,iBAAiB;IACjC,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAC;CACpB;AAsJD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,YAAuB,EAAE,MAAM,GAAE,IAAI,GAAG,IAAW,GAAG,iBAAiB,CA+ChI;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,YAAuB,GAAG,MAAM,CAmBpF","sourcesContent":["/**\n * Security & privacy classification for the Ada Memory Engine.\n *\n * Enterprise-grade guards:\n * - Credentials/secrets/payment data are ALWAYS blocked at every level.\n * - PII (email, phone, national IDs, IBAN, DOB) blocking depends on the\n * configured privacy level (RGPD / AI Act friendly).\n * - Every rejection is written to the audit log with a reason.\n * - Prompt-injection/exfiltration patterns are always blocked.\n */\n\nexport type PrivacyLevel = \"strict\" | \"balanced\" | \"permissive\";\n\nexport type SensitiveCategory =\n\t| \"credential\"\n\t| \"payment\"\n\t| \"secret-assignment\"\n\t| \"pii-email\"\n\t| \"pii-phone\"\n\t| \"pii-id\"\n\t| \"pii-bank\"\n\t| \"pii-dob\"\n\t| \"injection\"\n\t| \"exfiltration\";\n\nexport interface SensitivityResult {\n\t/** True when the content must NOT be stored. */\n\tblocked: boolean;\n\t/** Categories detected. */\n\tcategories: SensitiveCategory[];\n\t/** Human-readable reason (in the configured language). */\n\treason: string;\n\t/** True when content contains permitted-but-noteworthy categories. */\n\tnoteworthy: boolean;\n}\n\ninterface Pattern {\n\tcategory: SensitiveCategory;\n\tpattern: RegExp;\n\t/** Always blocked regardless of privacy level. */\n\talways?: boolean;\n\t/** Blocked at this level and stricter. */\n\tblockAt?: PrivacyLevel;\n\t/** English reason label. */\n\tlabel: string;\n}\n\n// ─── Credentials & secrets — ALWAYS blocked ───\nconst ALWAYS_BLOCKED: Pattern[] = [\n\t{ category: \"credential\", pattern: /\\bsk-ant-api\\S{10,}\\b/, always: true, label: \"Anthropic API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-or-v1-\\S{10,}\\b/, always: true, label: \"OpenRouter API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-[A-Za-z0-9]{20,}\\b/, always: true, label: \"OpenAI-style API key\" },\n\t{ category: \"credential\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/, always: true, label: \"AWS access key\" },\n\t{ category: \"credential\", pattern: /\\bghp_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub personal token\" },\n\t{ category: \"credential\", pattern: /\\bghu_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub user token\" },\n\t{ category: \"credential\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack token\" },\n\t{ category: \"credential\", pattern: /\\bxapp-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack app token\" },\n\t{ category: \"credential\", pattern: /\\bntn_[A-Za-z0-9]{20,}\\b/, always: true, label: \"Notion token\" },\n\t{ category: \"credential\", pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}/, always: true, label: \"JWT token\" },\n\t{ category: \"credential\", pattern: /-----BEGIN\\s+(?:RSA\\s+|EC\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/, always: true, label: \"Private key block\" },\n\t{ category: \"credential\", pattern: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{20,}\\b/i, always: true, label: \"Bearer auth token\" },\n\t{ category: \"credential\", pattern: /\\bapi[_-]?key\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i, always: true, label: \"API key assignment\" },\n\t{ category: \"credential\", pattern: /\\b(client|app|consumer)_?secret\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i, always: true, label: \"OAuth client secret\" },\n\t{ category: \"credential\", pattern: /\\bwebhook[_-]?url\\s*[=:]\\s*https?:\\/\\/[^\\s]+/i, always: true, label: \"Webhook URL\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(password|passwd|pwd)\\s*[=:]\\s*\\S{6,}\\b/i, always: true, label: \"password value\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(?:contraseña|contraseña|clave)\\s*[=:]\\s*\\S{6,}\\b/i, always: true, label: \"contraseña valor\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(?:contraseña|password|passwd|pwd|clave)\\b[^\\n]{0,40}\\b\\d{6,}\\b/i, always: true, label: \"password-like value\" },\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\t// Secret keyword followed by a value on the same line: the match spans\n\t\t// keyword + value so redaction never leaves the value exposed. The\n\t\t// value must be an alnum token (≥4 chars) containing a digit or symbol,\n\t\t// which avoids false positives like \"contraseña por defecto\".\n\t\tpattern: /\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key|secret)\\b[^\\n]{0,40}\\b(?=[A-Za-z0-9_\\-!@#$%^&*]*\\d)[A-Za-z0-9_\\-!@#$%^&*]{4,}/i,\n\t\talways: true,\n\t\tlabel: \"secret value after keyword\",\n\t},\n\t{ category: \"secret-assignment\", pattern: /\\b(?:recuerda|guarda|memoriza|remember|save|store)\\b.{0,50}\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\\b/i, always: true, label: \"request to store a secret\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(secret|token|api[_-]?key|access[_-]?key)\\s*[=:]\\s*\\S{10,}\\b/i, always: true, label: \"secret/token value\" },\n\t{ category: \"credential\", pattern: /\\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\\b/i, always: true, label: \"provider API key env var\" },\n\t{ category: \"credential\", pattern: /\\bGITHUB_TOKEN\\b|\\bAWS_SECRET_ACCESS_KEY\\b|\\bDATABASE_URL\\b/i, always: true, label: \"secret env var name\" },\n\t{ category: \"credential\", pattern: /(?:mongodb|postgres|mysql|redis|amqp):\\/\\/[^\\s]*:[^\\s@]+@/, always: true, label: \"connection string with credentials\" },\n];\n\n// ─── Payment data — ALWAYS blocked ───\nconst PAYMENT: Pattern[] = [\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/,\n\t\talways: true,\n\t\tlabel: \"payment card number\",\n\t},\n\t{ category: \"payment\", pattern: /\\bcvv\\s*[=:]\\s*\\d{3,4}\\b/i, always: true, label: \"card verification value\" },\n\t{ category: \"payment\", pattern: /\\b(?:expiry|exp)\\s*[=:]\\s*(?:0[1-9]|1[0-2])\\/?\\d{2}\\b/i, always: true, label: \"card expiry\" },\n];\n\n// ─── PII — level-dependent ───\nconst PII: Pattern[] = [\n\t{\n\t\tcategory: \"pii-email\",\n\t\tpattern: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"personal email address\",\n\t},\n\t{\n\t\tcategory: \"pii-phone\",\n\t\tpattern: /(?:\\+\\d{1,3}[\\s-]?)?(?:\\(?\\d{2,4}\\)?[\\s-]?)?\\d{3}[\\s-]?\\d{3}[\\s-]?\\d{3,4}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"phone number\",\n\t},\n\t{\n\t\tcategory: \"pii-id\",\n\t\tpattern: /\\b\\d{8}[A-Z]\\b|\\b[A-Z]\\d{7}[A-Z0-9]\\b/, // DNI / NIE (Spain)\n\t\tblockAt: \"strict\",\n\t\tlabel: \"national ID (DNI/NIE)\",\n\t},\n\t{\n\t\tcategory: \"pii-bank\",\n\t\tpattern: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/, // IBAN\n\t\tblockAt: \"balanced\",\n\t\tlabel: \"IBAN / bank account\",\n\t},\n\t{\n\t\tcategory: \"pii-dob\",\n\t\tpattern: /\\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\\s*[=:]\\s*(?:\\d{1,2}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{2,4})/i,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"date of birth\",\n\t},\n];\n\n// ─── Prompt injection / exfiltration — ALWAYS blocked ───\nconst INJECTION: Pattern[] = [\n\t{ category: \"injection\", pattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, always: true, label: \"prompt injection\" },\n\t{ category: \"injection\", pattern: /you\\s+are\\s+now\\s+/i, always: true, label: \"role hijack\" },\n\t{ category: \"injection\", pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, always: true, label: \"deceptive instruction\" },\n\t{ category: \"injection\", pattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i, always: true, label: \"instruction override\" },\n\t{ category: \"injection\", pattern: /system\\s+prompt\\s+override/i, always: true, label: \"system prompt override\" },\n\t{ category: \"exfiltration\", pattern: /curl\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: \"credential exfiltration (curl)\" },\n\t{ category: \"exfiltration\", pattern: /wget\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: \"credential exfiltration (wget)\" },\n\t{ category: \"exfiltration\", pattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i, always: true, label: \"secret file read\" },\n\t{ category: \"exfiltration\", pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, always: true, label: \"SSH key path\" },\n];\n\nconst LEVEL_ORDER: Record<PrivacyLevel, number> = { strict: 0, balanced: 1, permissive: 2 };\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\", \"\\u200c\", \"\\u200d\", \"\\u2060\", \"\\ufeff\",\n\t\"\\u202a\", \"\\u202b\", \"\\u202c\", \"\\u202d\", \"\\u202e\",\n]);\n\nconst REASON_EN = {\n\tcredential: \"Looks like a credential or secret (secrets are never stored in memory)\",\n\tpayment: \"Looks like payment card data (never stored in memory)\",\n\t\"secret-assignment\": \"Looks like a password/secret/token value (never stored in memory)\",\n\t\"pii-email\": \"Looks like a personal email address (blocked by the strict privacy level)\",\n\t\"pii-phone\": \"Looks like a phone number (blocked by the strict privacy level)\",\n\t\"pii-id\": \"Looks like a national ID (DNI/NIE) (blocked by the strict privacy level)\",\n\t\"pii-bank\": \"Looks like a bank account / IBAN (blocked by the privacy level)\",\n\t\"pii-dob\": \"Looks like a date of birth (blocked by the strict privacy level)\",\n\tinjection: \"Looks like a prompt-injection attempt (never stored)\",\n\texfiltration: \"Looks like data exfiltration (never stored)\",\n};\n\nconst REASON_ES: Record<SensitiveCategory, string> = {\n\tcredential: \"Parece una credencial o secreto (los secretos nunca se almacenan en memoria)\",\n\tpayment: \"Parecen datos de tarjeta de pago (nunca se almacenan en memoria)\",\n\t\"secret-assignment\": \"Parece una contraseña/secreto/token (nunca se almacena en memoria)\",\n\t\"pii-email\": \"Parece un correo electrónico personal (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-phone\": \"Parece un número de teléfono (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-id\": \"Parece un documento de identidad (DNI/NIE) (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-bank\": \"Parece una cuenta bancaria / IBAN (bloqueado por el nivel de privacidad)\",\n\t\"pii-dob\": \"Parece una fecha de nacimiento (bloqueada por el nivel de privacidad estricto)\",\n\tinjection: \"Parece un intento de prompt injection (nunca se almacena)\",\n\texfiltration: \"Parece exfiltración de datos (nunca se almacena)\",\n};\n\nfunction testPattern(pattern: Pattern, content: string): boolean {\n\ttry {\n\t\treturn pattern.pattern.test(content);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Classify content against the security policy.\n *\n * @param content Text to classify.\n * @param level Configured privacy level.\n * @param locale \"es\" or \"en\" for the reason language.\n */\nexport function classifySensitive(content: string, level: PrivacyLevel = \"strict\", locale: \"es\" | \"en\" = \"es\"): SensitivityResult {\n\tconst categories = new Set<SensitiveCategory>();\n\tconst reasons: string[] = [];\n\tconst labels: string[] = [];\n\n\t// Invisible characters are always suspicious (injection vectors).\n\tif ([...content].some((ch) => INVISIBLE_CHARS.has(ch))) {\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [\"injection\"],\n\t\t\treason: locale === \"es\"\n\t\t\t\t? \"El contenido contiene caracteres invisibles/zero-width y fue rechazado por seguridad.\"\n\t\t\t\t: \"Content contains invisible/zero-width characters and was rejected for security.\",\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\tconst allPatterns = [...ALWAYS_BLOCKED, ...PAYMENT, ...PII, ...INJECTION];\n\tfor (const pattern of allPatterns) {\n\t\tif (!testPattern(pattern, content)) continue;\n\t\tconst levelIdx = LEVEL_ORDER[level];\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst blocked = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tcategories.add(pattern.category);\n\t\tif (blocked) {\n\t\t\tlabels.push(pattern.label);\n\t\t\tif (!reasons.includes(pattern.category)) reasons.push(pattern.category);\n\t\t}\n\t}\n\n\tif (reasons.length > 0) {\n\t\tconst localeMap = locale === \"es\" ? REASON_ES : REASON_EN;\n\t\tconst detail = reasons.map((r) => localeMap[r as SensitiveCategory]).join(\"; \");\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [...categories],\n\t\t\treason: detail,\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tblocked: false,\n\t\tcategories: [...categories],\n\t\treason: \"\",\n\t\tnoteworthy: categories.size > 0,\n\t};\n}\n\n/**\n * Redact sensitive data from user text so it never reaches the LLM, the\n * session file, or the chat transcript. Only high-confidence patterns are\n * redacted (credentials, payment data, explicit secret assignments, and\n * PII like emails / national IDs / IBANs / DOB).\n */\nexport function redactSensitive(text: string, level: PrivacyLevel = \"strict\"): string {\n\tlet out = text;\n\tconst levelIdx = LEVEL_ORDER[level];\n\tfor (const pattern of [...ALWAYS_BLOCKED, ...PAYMENT, ...PII]) {\n\t\tif (pattern.category === \"pii-phone\") {\n\t\t\t// Phone redaction requires separators or a + prefix to avoid\n\t\t\t// mangling ordinary numbers in code.\n\t\t\tif (!/(?:\\+|\\s|\\()?\\d{3}[\\s.-]\\d{3}[\\s.-]\\d{3,4}/.test(text)) continue;\n\t\t}\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst active = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tif (!active) continue;\n\t\ttry {\n\t\t\tout = out.replace(pattern.pattern, (match) => \"█\".repeat(Math.min(match.length, 14)));\n\t\t} catch {\n\t\t\t// A bad pattern must never break redaction.\n\t\t}\n\t}\n\treturn out;\n}\n"]}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAC1B,YAAY,GACZ,SAAS,GACT,mBAAmB,GACnB,WAAW,GACX,WAAW,GACX,QAAQ,GACR,UAAU,GACV,SAAS,GACT,WAAW,GACX,cAAc,CAAC;AAElB,MAAM,WAAW,iBAAiB;IACjC,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAC;CACpB;AAqQD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,YAAuB,EAC9B,MAAM,GAAE,IAAI,GAAG,IAAW,GACxB,iBAAiB,CAgDnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,YAAuB,GAAG,MAAM,CAmBpF","sourcesContent":["/**\n * Security & privacy classification for the Ada Memory Engine.\n *\n * Enterprise-grade guards:\n * - Credentials/secrets/payment data are ALWAYS blocked at every level.\n * - PII (email, phone, national IDs, IBAN, DOB) blocking depends on the\n * configured privacy level (RGPD / AI Act friendly).\n * - Every rejection is written to the audit log with a reason.\n * - Prompt-injection/exfiltration patterns are always blocked.\n */\n\nexport type PrivacyLevel = \"strict\" | \"balanced\" | \"permissive\";\n\nexport type SensitiveCategory =\n\t| \"credential\"\n\t| \"payment\"\n\t| \"secret-assignment\"\n\t| \"pii-email\"\n\t| \"pii-phone\"\n\t| \"pii-id\"\n\t| \"pii-bank\"\n\t| \"pii-dob\"\n\t| \"injection\"\n\t| \"exfiltration\";\n\nexport interface SensitivityResult {\n\t/** True when the content must NOT be stored. */\n\tblocked: boolean;\n\t/** Categories detected. */\n\tcategories: SensitiveCategory[];\n\t/** Human-readable reason (in the configured language). */\n\treason: string;\n\t/** True when content contains permitted-but-noteworthy categories. */\n\tnoteworthy: boolean;\n}\n\ninterface Pattern {\n\tcategory: SensitiveCategory;\n\tpattern: RegExp;\n\t/** Always blocked regardless of privacy level. */\n\talways?: boolean;\n\t/** Blocked at this level and stricter. */\n\tblockAt?: PrivacyLevel;\n\t/** English reason label. */\n\tlabel: string;\n}\n\n// ─── Credentials & secrets — ALWAYS blocked ───\nconst ALWAYS_BLOCKED: Pattern[] = [\n\t{ category: \"credential\", pattern: /\\bsk-ant-api\\S{10,}\\b/, always: true, label: \"Anthropic API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-or-v1-\\S{10,}\\b/, always: true, label: \"OpenRouter API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-[A-Za-z0-9]{20,}\\b/, always: true, label: \"OpenAI-style API key\" },\n\t{ category: \"credential\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/, always: true, label: \"AWS access key\" },\n\t{ category: \"credential\", pattern: /\\bghp_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub personal token\" },\n\t{ category: \"credential\", pattern: /\\bghu_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub user token\" },\n\t{ category: \"credential\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack token\" },\n\t{ category: \"credential\", pattern: /\\bxapp-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack app token\" },\n\t{ category: \"credential\", pattern: /\\bntn_[A-Za-z0-9]{20,}\\b/, always: true, label: \"Notion token\" },\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}/,\n\t\talways: true,\n\t\tlabel: \"JWT token\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /-----BEGIN\\s+(?:RSA\\s+|EC\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/,\n\t\talways: true,\n\t\tlabel: \"Private key block\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{20,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"Bearer auth token\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bapi[_-]?key\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"API key assignment\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\b(client|app|consumer)_?secret\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"OAuth client secret\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bwebhook[_-]?url\\s*[=:]\\s*https?:\\/\\/[^\\s]+/i,\n\t\talways: true,\n\t\tlabel: \"Webhook URL\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(password|passwd|pwd)\\s*[=:]\\s*\\S{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"password value\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(?:contraseña|contraseña|clave)\\s*[=:]\\s*\\S{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"contraseña valor\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(?:contraseña|password|passwd|pwd|clave)\\b[^\\n]{0,40}\\b\\d{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"password-like value\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\t// Secret keyword followed by a value on the same line: the match spans\n\t\t// keyword + value so redaction never leaves the value exposed. The\n\t\t// value must be an alnum token (≥4 chars) containing a digit or symbol,\n\t\t// which avoids false positives like \"contraseña por defecto\".\n\t\tpattern:\n\t\t\t/\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key|secret)\\b[^\\n]{0,40}\\b(?=[A-Za-z0-9_\\-!@#$%^&*]*\\d)[A-Za-z0-9_\\-!@#$%^&*]{4,}/i,\n\t\talways: true,\n\t\tlabel: \"secret value after keyword\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern:\n\t\t\t/\\b(?:recuerda|guarda|memoriza|remember|save|store)\\b.{0,50}\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\\b/i,\n\t\talways: true,\n\t\tlabel: \"request to store a secret\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(secret|token|api[_-]?key|access[_-]?key)\\s*[=:]\\s*\\S{10,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"secret/token value\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\\b/i,\n\t\talways: true,\n\t\tlabel: \"provider API key env var\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bGITHUB_TOKEN\\b|\\bAWS_SECRET_ACCESS_KEY\\b|\\bDATABASE_URL\\b/i,\n\t\talways: true,\n\t\tlabel: \"secret env var name\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /(?:mongodb|postgres|mysql|redis|amqp):\\/\\/[^\\s]*:[^\\s@]+@/,\n\t\talways: true,\n\t\tlabel: \"connection string with credentials\",\n\t},\n];\n\n// ─── Payment data — ALWAYS blocked ───\nconst PAYMENT: Pattern[] = [\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/,\n\t\talways: true,\n\t\tlabel: \"payment card number\",\n\t},\n\t{ category: \"payment\", pattern: /\\bcvv\\s*[=:]\\s*\\d{3,4}\\b/i, always: true, label: \"card verification value\" },\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:expiry|exp)\\s*[=:]\\s*(?:0[1-9]|1[0-2])\\/?\\d{2}\\b/i,\n\t\talways: true,\n\t\tlabel: \"card expiry\",\n\t},\n];\n\n// ─── PII — level-dependent ───\nconst PII: Pattern[] = [\n\t{\n\t\tcategory: \"pii-email\",\n\t\tpattern: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"personal email address\",\n\t},\n\t{\n\t\tcategory: \"pii-phone\",\n\t\tpattern: /(?:\\+\\d{1,3}[\\s-]?)?(?:\\(?\\d{2,4}\\)?[\\s-]?)?\\d{3}[\\s-]?\\d{3}[\\s-]?\\d{3,4}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"phone number\",\n\t},\n\t{\n\t\tcategory: \"pii-id\",\n\t\tpattern: /\\b\\d{8}[A-Z]\\b|\\b[A-Z]\\d{7}[A-Z0-9]\\b/, // DNI / NIE (Spain)\n\t\tblockAt: \"strict\",\n\t\tlabel: \"national ID (DNI/NIE)\",\n\t},\n\t{\n\t\tcategory: \"pii-bank\",\n\t\tpattern: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/, // IBAN\n\t\tblockAt: \"balanced\",\n\t\tlabel: \"IBAN / bank account\",\n\t},\n\t{\n\t\tcategory: \"pii-dob\",\n\t\tpattern:\n\t\t\t/\\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\\s*[=:]\\s*(?:\\d{1,2}[/\\-.]\\d{1,2}[/\\-.]\\d{2,4})/i,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"date of birth\",\n\t},\n];\n\n// ─── Prompt injection / exfiltration — ALWAYS blocked ───\nconst INJECTION: Pattern[] = [\n\t{\n\t\tcategory: \"injection\",\n\t\tpattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i,\n\t\talways: true,\n\t\tlabel: \"prompt injection\",\n\t},\n\t{ category: \"injection\", pattern: /you\\s+are\\s+now\\s+/i, always: true, label: \"role hijack\" },\n\t{ category: \"injection\", pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, always: true, label: \"deceptive instruction\" },\n\t{\n\t\tcategory: \"injection\",\n\t\tpattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i,\n\t\talways: true,\n\t\tlabel: \"instruction override\",\n\t},\n\t{ category: \"injection\", pattern: /system\\s+prompt\\s+override/i, always: true, label: \"system prompt override\" },\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /curl\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,\n\t\talways: true,\n\t\tlabel: \"credential exfiltration (curl)\",\n\t},\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /wget\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,\n\t\talways: true,\n\t\tlabel: \"credential exfiltration (wget)\",\n\t},\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i,\n\t\talways: true,\n\t\tlabel: \"secret file read\",\n\t},\n\t{ category: \"exfiltration\", pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, always: true, label: \"SSH key path\" },\n];\n\nconst LEVEL_ORDER: Record<PrivacyLevel, number> = { strict: 0, balanced: 1, permissive: 2 };\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\",\n\t\"\\u200c\",\n\t\"\\u200d\",\n\t\"\\u2060\",\n\t\"\\ufeff\",\n\t\"\\u202a\",\n\t\"\\u202b\",\n\t\"\\u202c\",\n\t\"\\u202d\",\n\t\"\\u202e\",\n]);\n\nconst REASON_EN = {\n\tcredential: \"Looks like a credential or secret (secrets are never stored in memory)\",\n\tpayment: \"Looks like payment card data (never stored in memory)\",\n\t\"secret-assignment\": \"Looks like a password/secret/token value (never stored in memory)\",\n\t\"pii-email\": \"Looks like a personal email address (blocked by the strict privacy level)\",\n\t\"pii-phone\": \"Looks like a phone number (blocked by the strict privacy level)\",\n\t\"pii-id\": \"Looks like a national ID (DNI/NIE) (blocked by the strict privacy level)\",\n\t\"pii-bank\": \"Looks like a bank account / IBAN (blocked by the privacy level)\",\n\t\"pii-dob\": \"Looks like a date of birth (blocked by the strict privacy level)\",\n\tinjection: \"Looks like a prompt-injection attempt (never stored)\",\n\texfiltration: \"Looks like data exfiltration (never stored)\",\n};\n\nconst REASON_ES: Record<SensitiveCategory, string> = {\n\tcredential: \"Parece una credencial o secreto (los secretos nunca se almacenan en memoria)\",\n\tpayment: \"Parecen datos de tarjeta de pago (nunca se almacenan en memoria)\",\n\t\"secret-assignment\": \"Parece una contraseña/secreto/token (nunca se almacena en memoria)\",\n\t\"pii-email\": \"Parece un correo electrónico personal (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-phone\": \"Parece un número de teléfono (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-id\": \"Parece un documento de identidad (DNI/NIE) (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-bank\": \"Parece una cuenta bancaria / IBAN (bloqueado por el nivel de privacidad)\",\n\t\"pii-dob\": \"Parece una fecha de nacimiento (bloqueada por el nivel de privacidad estricto)\",\n\tinjection: \"Parece un intento de prompt injection (nunca se almacena)\",\n\texfiltration: \"Parece exfiltración de datos (nunca se almacena)\",\n};\n\nfunction testPattern(pattern: Pattern, content: string): boolean {\n\ttry {\n\t\treturn pattern.pattern.test(content);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Classify content against the security policy.\n *\n * @param content Text to classify.\n * @param level Configured privacy level.\n * @param locale \"es\" or \"en\" for the reason language.\n */\nexport function classifySensitive(\n\tcontent: string,\n\tlevel: PrivacyLevel = \"strict\",\n\tlocale: \"es\" | \"en\" = \"es\",\n): SensitivityResult {\n\tconst categories = new Set<SensitiveCategory>();\n\tconst reasons: string[] = [];\n\tconst labels: string[] = [];\n\n\t// Invisible characters are always suspicious (injection vectors).\n\tif ([...content].some((ch) => INVISIBLE_CHARS.has(ch))) {\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [\"injection\"],\n\t\t\treason:\n\t\t\t\tlocale === \"es\"\n\t\t\t\t\t? \"El contenido contiene caracteres invisibles/zero-width y fue rechazado por seguridad.\"\n\t\t\t\t\t: \"Content contains invisible/zero-width characters and was rejected for security.\",\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\tconst allPatterns = [...ALWAYS_BLOCKED, ...PAYMENT, ...PII, ...INJECTION];\n\tfor (const pattern of allPatterns) {\n\t\tif (!testPattern(pattern, content)) continue;\n\t\tconst levelIdx = LEVEL_ORDER[level];\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst blocked = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tcategories.add(pattern.category);\n\t\tif (blocked) {\n\t\t\tlabels.push(pattern.label);\n\t\t\tif (!reasons.includes(pattern.category)) reasons.push(pattern.category);\n\t\t}\n\t}\n\n\tif (reasons.length > 0) {\n\t\tconst localeMap = locale === \"es\" ? REASON_ES : REASON_EN;\n\t\tconst detail = reasons.map((r) => localeMap[r as SensitiveCategory]).join(\"; \");\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [...categories],\n\t\t\treason: detail,\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tblocked: false,\n\t\tcategories: [...categories],\n\t\treason: \"\",\n\t\tnoteworthy: categories.size > 0,\n\t};\n}\n\n/**\n * Redact sensitive data from user text so it never reaches the LLM, the\n * session file, or the chat transcript. Only high-confidence patterns are\n * redacted (credentials, payment data, explicit secret assignments, and\n * PII like emails / national IDs / IBANs / DOB).\n */\nexport function redactSensitive(text: string, level: PrivacyLevel = \"strict\"): string {\n\tlet out = text;\n\tconst levelIdx = LEVEL_ORDER[level];\n\tfor (const pattern of [...ALWAYS_BLOCKED, ...PAYMENT, ...PII]) {\n\t\tif (pattern.category === \"pii-phone\") {\n\t\t\t// Phone redaction requires separators or a + prefix to avoid\n\t\t\t// mangling ordinary numbers in code.\n\t\t\tif (!/(?:\\+|\\s|\\()?\\d{3}[\\s.-]\\d{3}[\\s.-]\\d{3,4}/.test(text)) continue;\n\t\t}\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst active = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tif (!active) continue;\n\t\ttry {\n\t\t\tout = out.replace(pattern.pattern, (match) => \"█\".repeat(Math.min(match.length, 14)));\n\t\t} catch {\n\t\t\t// A bad pattern must never break redaction.\n\t\t}\n\t}\n\treturn out;\n}\n"]}
@@ -19,15 +19,60 @@ const ALWAYS_BLOCKED = [
19
19
  { category: "credential", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, always: true, label: "Slack token" },
20
20
  { category: "credential", pattern: /\bxapp-[A-Za-z0-9-]{10,}\b/, always: true, label: "Slack app token" },
21
21
  { category: "credential", pattern: /\bntn_[A-Za-z0-9]{20,}\b/, always: true, label: "Notion token" },
22
- { category: "credential", pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/, always: true, label: "JWT token" },
23
- { category: "credential", pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/, always: true, label: "Private key block" },
24
- { category: "credential", pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}\b/i, always: true, label: "Bearer auth token" },
25
- { category: "credential", pattern: /\bapi[_-]?key\s*[=:]\s*[A-Za-z0-9_-]{16,}\b/i, always: true, label: "API key assignment" },
26
- { category: "credential", pattern: /\b(client|app|consumer)_?secret\s*[=:]\s*[A-Za-z0-9_-]{16,}\b/i, always: true, label: "OAuth client secret" },
27
- { category: "credential", pattern: /\bwebhook[_-]?url\s*[=:]\s*https?:\/\/[^\s]+/i, always: true, label: "Webhook URL" },
28
- { category: "secret-assignment", pattern: /\b(password|passwd|pwd)\s*[=:]\s*\S{6,}\b/i, always: true, label: "password value" },
29
- { category: "secret-assignment", pattern: /\b(?:contraseña|contraseña|clave)\s*[=:]\s*\S{6,}\b/i, always: true, label: "contraseña valor" },
30
- { category: "secret-assignment", pattern: /\b(?:contraseña|password|passwd|pwd|clave)\b[^\n]{0,40}\b\d{6,}\b/i, always: true, label: "password-like value" },
22
+ {
23
+ category: "credential",
24
+ pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
25
+ always: true,
26
+ label: "JWT token",
27
+ },
28
+ {
29
+ category: "credential",
30
+ pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/,
31
+ always: true,
32
+ label: "Private key block",
33
+ },
34
+ {
35
+ category: "credential",
36
+ pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}\b/i,
37
+ always: true,
38
+ label: "Bearer auth token",
39
+ },
40
+ {
41
+ category: "credential",
42
+ pattern: /\bapi[_-]?key\s*[=:]\s*[A-Za-z0-9_-]{16,}\b/i,
43
+ always: true,
44
+ label: "API key assignment",
45
+ },
46
+ {
47
+ category: "credential",
48
+ pattern: /\b(client|app|consumer)_?secret\s*[=:]\s*[A-Za-z0-9_-]{16,}\b/i,
49
+ always: true,
50
+ label: "OAuth client secret",
51
+ },
52
+ {
53
+ category: "credential",
54
+ pattern: /\bwebhook[_-]?url\s*[=:]\s*https?:\/\/[^\s]+/i,
55
+ always: true,
56
+ label: "Webhook URL",
57
+ },
58
+ {
59
+ category: "secret-assignment",
60
+ pattern: /\b(password|passwd|pwd)\s*[=:]\s*\S{6,}\b/i,
61
+ always: true,
62
+ label: "password value",
63
+ },
64
+ {
65
+ category: "secret-assignment",
66
+ pattern: /\b(?:contraseña|contraseña|clave)\s*[=:]\s*\S{6,}\b/i,
67
+ always: true,
68
+ label: "contraseña valor",
69
+ },
70
+ {
71
+ category: "secret-assignment",
72
+ pattern: /\b(?:contraseña|password|passwd|pwd|clave)\b[^\n]{0,40}\b\d{6,}\b/i,
73
+ always: true,
74
+ label: "password-like value",
75
+ },
31
76
  {
32
77
  category: "secret-assignment",
33
78
  // Secret keyword followed by a value on the same line: the match spans
@@ -38,11 +83,36 @@ const ALWAYS_BLOCKED = [
38
83
  always: true,
39
84
  label: "secret value after keyword",
40
85
  },
41
- { category: "secret-assignment", pattern: /\b(?:recuerda|guarda|memoriza|remember|save|store)\b.{0,50}\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\b/i, always: true, label: "request to store a secret" },
42
- { category: "secret-assignment", pattern: /\b(secret|token|api[_-]?key|access[_-]?key)\s*[=:]\s*\S{10,}\b/i, always: true, label: "secret/token value" },
43
- { category: "credential", pattern: /\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\b/i, always: true, label: "provider API key env var" },
44
- { category: "credential", pattern: /\bGITHUB_TOKEN\b|\bAWS_SECRET_ACCESS_KEY\b|\bDATABASE_URL\b/i, always: true, label: "secret env var name" },
45
- { category: "credential", pattern: /(?:mongodb|postgres|mysql|redis|amqp):\/\/[^\s]*:[^\s@]+@/, always: true, label: "connection string with credentials" },
86
+ {
87
+ category: "secret-assignment",
88
+ pattern: /\b(?:recuerda|guarda|memoriza|remember|save|store)\b.{0,50}\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\b/i,
89
+ always: true,
90
+ label: "request to store a secret",
91
+ },
92
+ {
93
+ category: "secret-assignment",
94
+ pattern: /\b(secret|token|api[_-]?key|access[_-]?key)\s*[=:]\s*\S{10,}\b/i,
95
+ always: true,
96
+ label: "secret/token value",
97
+ },
98
+ {
99
+ category: "credential",
100
+ pattern: /\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\b/i,
101
+ always: true,
102
+ label: "provider API key env var",
103
+ },
104
+ {
105
+ category: "credential",
106
+ pattern: /\bGITHUB_TOKEN\b|\bAWS_SECRET_ACCESS_KEY\b|\bDATABASE_URL\b/i,
107
+ always: true,
108
+ label: "secret env var name",
109
+ },
110
+ {
111
+ category: "credential",
112
+ pattern: /(?:mongodb|postgres|mysql|redis|amqp):\/\/[^\s]*:[^\s@]+@/,
113
+ always: true,
114
+ label: "connection string with credentials",
115
+ },
46
116
  ];
47
117
  // ─── Payment data — ALWAYS blocked ───
48
118
  const PAYMENT = [
@@ -53,7 +123,12 @@ const PAYMENT = [
53
123
  label: "payment card number",
54
124
  },
55
125
  { category: "payment", pattern: /\bcvv\s*[=:]\s*\d{3,4}\b/i, always: true, label: "card verification value" },
56
- { category: "payment", pattern: /\b(?:expiry|exp)\s*[=:]\s*(?:0[1-9]|1[0-2])\/?\d{2}\b/i, always: true, label: "card expiry" },
126
+ {
127
+ category: "payment",
128
+ pattern: /\b(?:expiry|exp)\s*[=:]\s*(?:0[1-9]|1[0-2])\/?\d{2}\b/i,
129
+ always: true,
130
+ label: "card expiry",
131
+ },
57
132
  ];
58
133
  // ─── PII — level-dependent ───
59
134
  const PII = [
@@ -83,27 +158,60 @@ const PII = [
83
158
  },
84
159
  {
85
160
  category: "pii-dob",
86
- pattern: /\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\s*[=:]\s*(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4})/i,
161
+ pattern: /\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\s*[=:]\s*(?:\d{1,2}[/\-.]\d{1,2}[/\-.]\d{2,4})/i,
87
162
  blockAt: "strict",
88
163
  label: "date of birth",
89
164
  },
90
165
  ];
91
166
  // ─── Prompt injection / exfiltration — ALWAYS blocked ───
92
167
  const INJECTION = [
93
- { category: "injection", pattern: /ignore\s+(previous|all|above|prior)\s+instructions/i, always: true, label: "prompt injection" },
168
+ {
169
+ category: "injection",
170
+ pattern: /ignore\s+(previous|all|above|prior)\s+instructions/i,
171
+ always: true,
172
+ label: "prompt injection",
173
+ },
94
174
  { category: "injection", pattern: /you\s+are\s+now\s+/i, always: true, label: "role hijack" },
95
175
  { category: "injection", pattern: /do\s+not\s+tell\s+the\s+user/i, always: true, label: "deceptive instruction" },
96
- { category: "injection", pattern: /disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i, always: true, label: "instruction override" },
176
+ {
177
+ category: "injection",
178
+ pattern: /disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i,
179
+ always: true,
180
+ label: "instruction override",
181
+ },
97
182
  { category: "injection", pattern: /system\s+prompt\s+override/i, always: true, label: "system prompt override" },
98
- { category: "exfiltration", pattern: /curl\s+[^\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: "credential exfiltration (curl)" },
99
- { category: "exfiltration", pattern: /wget\s+[^\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: "credential exfiltration (wget)" },
100
- { category: "exfiltration", pattern: /cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i, always: true, label: "secret file read" },
183
+ {
184
+ category: "exfiltration",
185
+ pattern: /curl\s+[^\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,
186
+ always: true,
187
+ label: "credential exfiltration (curl)",
188
+ },
189
+ {
190
+ category: "exfiltration",
191
+ pattern: /wget\s+[^\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,
192
+ always: true,
193
+ label: "credential exfiltration (wget)",
194
+ },
195
+ {
196
+ category: "exfiltration",
197
+ pattern: /cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i,
198
+ always: true,
199
+ label: "secret file read",
200
+ },
101
201
  { category: "exfiltration", pattern: /\$HOME\/\.ssh|~\/\.ssh/i, always: true, label: "SSH key path" },
102
202
  ];
103
203
  const LEVEL_ORDER = { strict: 0, balanced: 1, permissive: 2 };
104
204
  const INVISIBLE_CHARS = new Set([
105
- "\u200b", "\u200c", "\u200d", "\u2060", "\ufeff",
106
- "\u202a", "\u202b", "\u202c", "\u202d", "\u202e",
205
+ "\u200b",
206
+ "\u200c",
207
+ "\u200d",
208
+ "\u2060",
209
+ "\ufeff",
210
+ "\u202a",
211
+ "\u202b",
212
+ "\u202c",
213
+ "\u202d",
214
+ "\u202e",
107
215
  ]);
108
216
  const REASON_EN = {
109
217
  credential: "Looks like a credential or secret (secrets are never stored in memory)",
@@ -1 +1 @@
1
- {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/core/memory-engine/security.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Security & privacy classification for the Ada Memory Engine.\n *\n * Enterprise-grade guards:\n * - Credentials/secrets/payment data are ALWAYS blocked at every level.\n * - PII (email, phone, national IDs, IBAN, DOB) blocking depends on the\n * configured privacy level (RGPD / AI Act friendly).\n * - Every rejection is written to the audit log with a reason.\n * - Prompt-injection/exfiltration patterns are always blocked.\n */\n\nexport type PrivacyLevel = \"strict\" | \"balanced\" | \"permissive\";\n\nexport type SensitiveCategory =\n\t| \"credential\"\n\t| \"payment\"\n\t| \"secret-assignment\"\n\t| \"pii-email\"\n\t| \"pii-phone\"\n\t| \"pii-id\"\n\t| \"pii-bank\"\n\t| \"pii-dob\"\n\t| \"injection\"\n\t| \"exfiltration\";\n\nexport interface SensitivityResult {\n\t/** True when the content must NOT be stored. */\n\tblocked: boolean;\n\t/** Categories detected. */\n\tcategories: SensitiveCategory[];\n\t/** Human-readable reason (in the configured language). */\n\treason: string;\n\t/** True when content contains permitted-but-noteworthy categories. */\n\tnoteworthy: boolean;\n}\n\ninterface Pattern {\n\tcategory: SensitiveCategory;\n\tpattern: RegExp;\n\t/** Always blocked regardless of privacy level. */\n\talways?: boolean;\n\t/** Blocked at this level and stricter. */\n\tblockAt?: PrivacyLevel;\n\t/** English reason label. */\n\tlabel: string;\n}\n\n// ─── Credentials & secrets — ALWAYS blocked ───\nconst ALWAYS_BLOCKED: Pattern[] = [\n\t{ category: \"credential\", pattern: /\\bsk-ant-api\\S{10,}\\b/, always: true, label: \"Anthropic API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-or-v1-\\S{10,}\\b/, always: true, label: \"OpenRouter API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-[A-Za-z0-9]{20,}\\b/, always: true, label: \"OpenAI-style API key\" },\n\t{ category: \"credential\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/, always: true, label: \"AWS access key\" },\n\t{ category: \"credential\", pattern: /\\bghp_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub personal token\" },\n\t{ category: \"credential\", pattern: /\\bghu_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub user token\" },\n\t{ category: \"credential\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack token\" },\n\t{ category: \"credential\", pattern: /\\bxapp-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack app token\" },\n\t{ category: \"credential\", pattern: /\\bntn_[A-Za-z0-9]{20,}\\b/, always: true, label: \"Notion token\" },\n\t{ category: \"credential\", pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}/, always: true, label: \"JWT token\" },\n\t{ category: \"credential\", pattern: /-----BEGIN\\s+(?:RSA\\s+|EC\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/, always: true, label: \"Private key block\" },\n\t{ category: \"credential\", pattern: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{20,}\\b/i, always: true, label: \"Bearer auth token\" },\n\t{ category: \"credential\", pattern: /\\bapi[_-]?key\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i, always: true, label: \"API key assignment\" },\n\t{ category: \"credential\", pattern: /\\b(client|app|consumer)_?secret\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i, always: true, label: \"OAuth client secret\" },\n\t{ category: \"credential\", pattern: /\\bwebhook[_-]?url\\s*[=:]\\s*https?:\\/\\/[^\\s]+/i, always: true, label: \"Webhook URL\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(password|passwd|pwd)\\s*[=:]\\s*\\S{6,}\\b/i, always: true, label: \"password value\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(?:contraseña|contraseña|clave)\\s*[=:]\\s*\\S{6,}\\b/i, always: true, label: \"contraseña valor\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(?:contraseña|password|passwd|pwd|clave)\\b[^\\n]{0,40}\\b\\d{6,}\\b/i, always: true, label: \"password-like value\" },\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\t// Secret keyword followed by a value on the same line: the match spans\n\t\t// keyword + value so redaction never leaves the value exposed. The\n\t\t// value must be an alnum token (≥4 chars) containing a digit or symbol,\n\t\t// which avoids false positives like \"contraseña por defecto\".\n\t\tpattern: /\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key|secret)\\b[^\\n]{0,40}\\b(?=[A-Za-z0-9_\\-!@#$%^&*]*\\d)[A-Za-z0-9_\\-!@#$%^&*]{4,}/i,\n\t\talways: true,\n\t\tlabel: \"secret value after keyword\",\n\t},\n\t{ category: \"secret-assignment\", pattern: /\\b(?:recuerda|guarda|memoriza|remember|save|store)\\b.{0,50}\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\\b/i, always: true, label: \"request to store a secret\" },\n\t{ category: \"secret-assignment\", pattern: /\\b(secret|token|api[_-]?key|access[_-]?key)\\s*[=:]\\s*\\S{10,}\\b/i, always: true, label: \"secret/token value\" },\n\t{ category: \"credential\", pattern: /\\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\\b/i, always: true, label: \"provider API key env var\" },\n\t{ category: \"credential\", pattern: /\\bGITHUB_TOKEN\\b|\\bAWS_SECRET_ACCESS_KEY\\b|\\bDATABASE_URL\\b/i, always: true, label: \"secret env var name\" },\n\t{ category: \"credential\", pattern: /(?:mongodb|postgres|mysql|redis|amqp):\\/\\/[^\\s]*:[^\\s@]+@/, always: true, label: \"connection string with credentials\" },\n];\n\n// ─── Payment data — ALWAYS blocked ───\nconst PAYMENT: Pattern[] = [\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/,\n\t\talways: true,\n\t\tlabel: \"payment card number\",\n\t},\n\t{ category: \"payment\", pattern: /\\bcvv\\s*[=:]\\s*\\d{3,4}\\b/i, always: true, label: \"card verification value\" },\n\t{ category: \"payment\", pattern: /\\b(?:expiry|exp)\\s*[=:]\\s*(?:0[1-9]|1[0-2])\\/?\\d{2}\\b/i, always: true, label: \"card expiry\" },\n];\n\n// ─── PII — level-dependent ───\nconst PII: Pattern[] = [\n\t{\n\t\tcategory: \"pii-email\",\n\t\tpattern: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"personal email address\",\n\t},\n\t{\n\t\tcategory: \"pii-phone\",\n\t\tpattern: /(?:\\+\\d{1,3}[\\s-]?)?(?:\\(?\\d{2,4}\\)?[\\s-]?)?\\d{3}[\\s-]?\\d{3}[\\s-]?\\d{3,4}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"phone number\",\n\t},\n\t{\n\t\tcategory: \"pii-id\",\n\t\tpattern: /\\b\\d{8}[A-Z]\\b|\\b[A-Z]\\d{7}[A-Z0-9]\\b/, // DNI / NIE (Spain)\n\t\tblockAt: \"strict\",\n\t\tlabel: \"national ID (DNI/NIE)\",\n\t},\n\t{\n\t\tcategory: \"pii-bank\",\n\t\tpattern: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/, // IBAN\n\t\tblockAt: \"balanced\",\n\t\tlabel: \"IBAN / bank account\",\n\t},\n\t{\n\t\tcategory: \"pii-dob\",\n\t\tpattern: /\\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\\s*[=:]\\s*(?:\\d{1,2}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{2,4})/i,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"date of birth\",\n\t},\n];\n\n// ─── Prompt injection / exfiltration — ALWAYS blocked ───\nconst INJECTION: Pattern[] = [\n\t{ category: \"injection\", pattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i, always: true, label: \"prompt injection\" },\n\t{ category: \"injection\", pattern: /you\\s+are\\s+now\\s+/i, always: true, label: \"role hijack\" },\n\t{ category: \"injection\", pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, always: true, label: \"deceptive instruction\" },\n\t{ category: \"injection\", pattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i, always: true, label: \"instruction override\" },\n\t{ category: \"injection\", pattern: /system\\s+prompt\\s+override/i, always: true, label: \"system prompt override\" },\n\t{ category: \"exfiltration\", pattern: /curl\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: \"credential exfiltration (curl)\" },\n\t{ category: \"exfiltration\", pattern: /wget\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i, always: true, label: \"credential exfiltration (wget)\" },\n\t{ category: \"exfiltration\", pattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i, always: true, label: \"secret file read\" },\n\t{ category: \"exfiltration\", pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, always: true, label: \"SSH key path\" },\n];\n\nconst LEVEL_ORDER: Record<PrivacyLevel, number> = { strict: 0, balanced: 1, permissive: 2 };\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\", \"\\u200c\", \"\\u200d\", \"\\u2060\", \"\\ufeff\",\n\t\"\\u202a\", \"\\u202b\", \"\\u202c\", \"\\u202d\", \"\\u202e\",\n]);\n\nconst REASON_EN = {\n\tcredential: \"Looks like a credential or secret (secrets are never stored in memory)\",\n\tpayment: \"Looks like payment card data (never stored in memory)\",\n\t\"secret-assignment\": \"Looks like a password/secret/token value (never stored in memory)\",\n\t\"pii-email\": \"Looks like a personal email address (blocked by the strict privacy level)\",\n\t\"pii-phone\": \"Looks like a phone number (blocked by the strict privacy level)\",\n\t\"pii-id\": \"Looks like a national ID (DNI/NIE) (blocked by the strict privacy level)\",\n\t\"pii-bank\": \"Looks like a bank account / IBAN (blocked by the privacy level)\",\n\t\"pii-dob\": \"Looks like a date of birth (blocked by the strict privacy level)\",\n\tinjection: \"Looks like a prompt-injection attempt (never stored)\",\n\texfiltration: \"Looks like data exfiltration (never stored)\",\n};\n\nconst REASON_ES: Record<SensitiveCategory, string> = {\n\tcredential: \"Parece una credencial o secreto (los secretos nunca se almacenan en memoria)\",\n\tpayment: \"Parecen datos de tarjeta de pago (nunca se almacenan en memoria)\",\n\t\"secret-assignment\": \"Parece una contraseña/secreto/token (nunca se almacena en memoria)\",\n\t\"pii-email\": \"Parece un correo electrónico personal (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-phone\": \"Parece un número de teléfono (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-id\": \"Parece un documento de identidad (DNI/NIE) (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-bank\": \"Parece una cuenta bancaria / IBAN (bloqueado por el nivel de privacidad)\",\n\t\"pii-dob\": \"Parece una fecha de nacimiento (bloqueada por el nivel de privacidad estricto)\",\n\tinjection: \"Parece un intento de prompt injection (nunca se almacena)\",\n\texfiltration: \"Parece exfiltración de datos (nunca se almacena)\",\n};\n\nfunction testPattern(pattern: Pattern, content: string): boolean {\n\ttry {\n\t\treturn pattern.pattern.test(content);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Classify content against the security policy.\n *\n * @param content Text to classify.\n * @param level Configured privacy level.\n * @param locale \"es\" or \"en\" for the reason language.\n */\nexport function classifySensitive(content: string, level: PrivacyLevel = \"strict\", locale: \"es\" | \"en\" = \"es\"): SensitivityResult {\n\tconst categories = new Set<SensitiveCategory>();\n\tconst reasons: string[] = [];\n\tconst labels: string[] = [];\n\n\t// Invisible characters are always suspicious (injection vectors).\n\tif ([...content].some((ch) => INVISIBLE_CHARS.has(ch))) {\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [\"injection\"],\n\t\t\treason: locale === \"es\"\n\t\t\t\t? \"El contenido contiene caracteres invisibles/zero-width y fue rechazado por seguridad.\"\n\t\t\t\t: \"Content contains invisible/zero-width characters and was rejected for security.\",\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\tconst allPatterns = [...ALWAYS_BLOCKED, ...PAYMENT, ...PII, ...INJECTION];\n\tfor (const pattern of allPatterns) {\n\t\tif (!testPattern(pattern, content)) continue;\n\t\tconst levelIdx = LEVEL_ORDER[level];\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst blocked = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tcategories.add(pattern.category);\n\t\tif (blocked) {\n\t\t\tlabels.push(pattern.label);\n\t\t\tif (!reasons.includes(pattern.category)) reasons.push(pattern.category);\n\t\t}\n\t}\n\n\tif (reasons.length > 0) {\n\t\tconst localeMap = locale === \"es\" ? REASON_ES : REASON_EN;\n\t\tconst detail = reasons.map((r) => localeMap[r as SensitiveCategory]).join(\"; \");\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [...categories],\n\t\t\treason: detail,\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tblocked: false,\n\t\tcategories: [...categories],\n\t\treason: \"\",\n\t\tnoteworthy: categories.size > 0,\n\t};\n}\n\n/**\n * Redact sensitive data from user text so it never reaches the LLM, the\n * session file, or the chat transcript. Only high-confidence patterns are\n * redacted (credentials, payment data, explicit secret assignments, and\n * PII like emails / national IDs / IBANs / DOB).\n */\nexport function redactSensitive(text: string, level: PrivacyLevel = \"strict\"): string {\n\tlet out = text;\n\tconst levelIdx = LEVEL_ORDER[level];\n\tfor (const pattern of [...ALWAYS_BLOCKED, ...PAYMENT, ...PII]) {\n\t\tif (pattern.category === \"pii-phone\") {\n\t\t\t// Phone redaction requires separators or a + prefix to avoid\n\t\t\t// mangling ordinary numbers in code.\n\t\t\tif (!/(?:\\+|\\s|\\()?\\d{3}[\\s.-]\\d{3}[\\s.-]\\d{3,4}/.test(text)) continue;\n\t\t}\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst active = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tif (!active) continue;\n\t\ttry {\n\t\t\tout = out.replace(pattern.pattern, (match) => \"█\".repeat(Math.min(match.length, 14)));\n\t\t} catch {\n\t\t\t// A bad pattern must never break redaction.\n\t\t}\n\t}\n\treturn out;\n}\n"]}
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/core/memory-engine/security.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Security & privacy classification for the Ada Memory Engine.\n *\n * Enterprise-grade guards:\n * - Credentials/secrets/payment data are ALWAYS blocked at every level.\n * - PII (email, phone, national IDs, IBAN, DOB) blocking depends on the\n * configured privacy level (RGPD / AI Act friendly).\n * - Every rejection is written to the audit log with a reason.\n * - Prompt-injection/exfiltration patterns are always blocked.\n */\n\nexport type PrivacyLevel = \"strict\" | \"balanced\" | \"permissive\";\n\nexport type SensitiveCategory =\n\t| \"credential\"\n\t| \"payment\"\n\t| \"secret-assignment\"\n\t| \"pii-email\"\n\t| \"pii-phone\"\n\t| \"pii-id\"\n\t| \"pii-bank\"\n\t| \"pii-dob\"\n\t| \"injection\"\n\t| \"exfiltration\";\n\nexport interface SensitivityResult {\n\t/** True when the content must NOT be stored. */\n\tblocked: boolean;\n\t/** Categories detected. */\n\tcategories: SensitiveCategory[];\n\t/** Human-readable reason (in the configured language). */\n\treason: string;\n\t/** True when content contains permitted-but-noteworthy categories. */\n\tnoteworthy: boolean;\n}\n\ninterface Pattern {\n\tcategory: SensitiveCategory;\n\tpattern: RegExp;\n\t/** Always blocked regardless of privacy level. */\n\talways?: boolean;\n\t/** Blocked at this level and stricter. */\n\tblockAt?: PrivacyLevel;\n\t/** English reason label. */\n\tlabel: string;\n}\n\n// ─── Credentials & secrets — ALWAYS blocked ───\nconst ALWAYS_BLOCKED: Pattern[] = [\n\t{ category: \"credential\", pattern: /\\bsk-ant-api\\S{10,}\\b/, always: true, label: \"Anthropic API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-or-v1-\\S{10,}\\b/, always: true, label: \"OpenRouter API key\" },\n\t{ category: \"credential\", pattern: /\\bsk-[A-Za-z0-9]{20,}\\b/, always: true, label: \"OpenAI-style API key\" },\n\t{ category: \"credential\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/, always: true, label: \"AWS access key\" },\n\t{ category: \"credential\", pattern: /\\bghp_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub personal token\" },\n\t{ category: \"credential\", pattern: /\\bghu_[A-Za-z0-9]{20,}\\b/, always: true, label: \"GitHub user token\" },\n\t{ category: \"credential\", pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack token\" },\n\t{ category: \"credential\", pattern: /\\bxapp-[A-Za-z0-9-]{10,}\\b/, always: true, label: \"Slack app token\" },\n\t{ category: \"credential\", pattern: /\\bntn_[A-Za-z0-9]{20,}\\b/, always: true, label: \"Notion token\" },\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}/,\n\t\talways: true,\n\t\tlabel: \"JWT token\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /-----BEGIN\\s+(?:RSA\\s+|EC\\s+|OPENSSH\\s+)?PRIVATE\\s+KEY-----/,\n\t\talways: true,\n\t\tlabel: \"Private key block\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{20,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"Bearer auth token\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bapi[_-]?key\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"API key assignment\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\b(client|app|consumer)_?secret\\s*[=:]\\s*[A-Za-z0-9_-]{16,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"OAuth client secret\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bwebhook[_-]?url\\s*[=:]\\s*https?:\\/\\/[^\\s]+/i,\n\t\talways: true,\n\t\tlabel: \"Webhook URL\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(password|passwd|pwd)\\s*[=:]\\s*\\S{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"password value\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(?:contraseña|contraseña|clave)\\s*[=:]\\s*\\S{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"contraseña valor\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(?:contraseña|password|passwd|pwd|clave)\\b[^\\n]{0,40}\\b\\d{6,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"password-like value\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\t// Secret keyword followed by a value on the same line: the match spans\n\t\t// keyword + value so redaction never leaves the value exposed. The\n\t\t// value must be an alnum token (≥4 chars) containing a digit or symbol,\n\t\t// which avoids false positives like \"contraseña por defecto\".\n\t\tpattern:\n\t\t\t/\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key|secret)\\b[^\\n]{0,40}\\b(?=[A-Za-z0-9_\\-!@#$%^&*]*\\d)[A-Za-z0-9_\\-!@#$%^&*]{4,}/i,\n\t\talways: true,\n\t\tlabel: \"secret value after keyword\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern:\n\t\t\t/\\b(?:recuerda|guarda|memoriza|remember|save|store)\\b.{0,50}\\b(?:contraseña|password|passwd|pwd|clave|token|api[_-]?key)\\b/i,\n\t\talways: true,\n\t\tlabel: \"request to store a secret\",\n\t},\n\t{\n\t\tcategory: \"secret-assignment\",\n\t\tpattern: /\\b(secret|token|api[_-]?key|access[_-]?key)\\s*[=:]\\s*\\S{10,}\\b/i,\n\t\talways: true,\n\t\tlabel: \"secret/token value\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\b(ANTHROPIC|OPENAI|OPENROUTER|GEMINI|DEEPSEEK|MISTRAL|AZURE)_?(API)?_?KEY\\b/i,\n\t\talways: true,\n\t\tlabel: \"provider API key env var\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /\\bGITHUB_TOKEN\\b|\\bAWS_SECRET_ACCESS_KEY\\b|\\bDATABASE_URL\\b/i,\n\t\talways: true,\n\t\tlabel: \"secret env var name\",\n\t},\n\t{\n\t\tcategory: \"credential\",\n\t\tpattern: /(?:mongodb|postgres|mysql|redis|amqp):\\/\\/[^\\s]*:[^\\s@]+@/,\n\t\talways: true,\n\t\tlabel: \"connection string with credentials\",\n\t},\n];\n\n// ─── Payment data — ALWAYS blocked ───\nconst PAYMENT: Pattern[] = [\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/,\n\t\talways: true,\n\t\tlabel: \"payment card number\",\n\t},\n\t{ category: \"payment\", pattern: /\\bcvv\\s*[=:]\\s*\\d{3,4}\\b/i, always: true, label: \"card verification value\" },\n\t{\n\t\tcategory: \"payment\",\n\t\tpattern: /\\b(?:expiry|exp)\\s*[=:]\\s*(?:0[1-9]|1[0-2])\\/?\\d{2}\\b/i,\n\t\talways: true,\n\t\tlabel: \"card expiry\",\n\t},\n];\n\n// ─── PII — level-dependent ───\nconst PII: Pattern[] = [\n\t{\n\t\tcategory: \"pii-email\",\n\t\tpattern: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"personal email address\",\n\t},\n\t{\n\t\tcategory: \"pii-phone\",\n\t\tpattern: /(?:\\+\\d{1,3}[\\s-]?)?(?:\\(?\\d{2,4}\\)?[\\s-]?)?\\d{3}[\\s-]?\\d{3}[\\s-]?\\d{3,4}\\b/,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"phone number\",\n\t},\n\t{\n\t\tcategory: \"pii-id\",\n\t\tpattern: /\\b\\d{8}[A-Z]\\b|\\b[A-Z]\\d{7}[A-Z0-9]\\b/, // DNI / NIE (Spain)\n\t\tblockAt: \"strict\",\n\t\tlabel: \"national ID (DNI/NIE)\",\n\t},\n\t{\n\t\tcategory: \"pii-bank\",\n\t\tpattern: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/, // IBAN\n\t\tblockAt: \"balanced\",\n\t\tlabel: \"IBAN / bank account\",\n\t},\n\t{\n\t\tcategory: \"pii-dob\",\n\t\tpattern:\n\t\t\t/\\b(?:birth|birthday|born|nacimiento|fecha de nacimiento)\\s*[=:]\\s*(?:\\d{1,2}[/\\-.]\\d{1,2}[/\\-.]\\d{2,4})/i,\n\t\tblockAt: \"strict\",\n\t\tlabel: \"date of birth\",\n\t},\n];\n\n// ─── Prompt injection / exfiltration — ALWAYS blocked ───\nconst INJECTION: Pattern[] = [\n\t{\n\t\tcategory: \"injection\",\n\t\tpattern: /ignore\\s+(previous|all|above|prior)\\s+instructions/i,\n\t\talways: true,\n\t\tlabel: \"prompt injection\",\n\t},\n\t{ category: \"injection\", pattern: /you\\s+are\\s+now\\s+/i, always: true, label: \"role hijack\" },\n\t{ category: \"injection\", pattern: /do\\s+not\\s+tell\\s+the\\s+user/i, always: true, label: \"deceptive instruction\" },\n\t{\n\t\tcategory: \"injection\",\n\t\tpattern: /disregard\\s+(your|all|any)\\s+(instructions|rules|guidelines)/i,\n\t\talways: true,\n\t\tlabel: \"instruction override\",\n\t},\n\t{ category: \"injection\", pattern: /system\\s+prompt\\s+override/i, always: true, label: \"system prompt override\" },\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /curl\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,\n\t\talways: true,\n\t\tlabel: \"credential exfiltration (curl)\",\n\t},\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /wget\\s+[^\\n]*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,\n\t\talways: true,\n\t\tlabel: \"credential exfiltration (wget)\",\n\t},\n\t{\n\t\tcategory: \"exfiltration\",\n\t\tpattern: /cat\\s+[^\\n]*(\\.env|credentials|\\.netrc|\\.pgpass|\\.npmrc|\\.pypirc)/i,\n\t\talways: true,\n\t\tlabel: \"secret file read\",\n\t},\n\t{ category: \"exfiltration\", pattern: /\\$HOME\\/\\.ssh|~\\/\\.ssh/i, always: true, label: \"SSH key path\" },\n];\n\nconst LEVEL_ORDER: Record<PrivacyLevel, number> = { strict: 0, balanced: 1, permissive: 2 };\n\nconst INVISIBLE_CHARS = new Set([\n\t\"\\u200b\",\n\t\"\\u200c\",\n\t\"\\u200d\",\n\t\"\\u2060\",\n\t\"\\ufeff\",\n\t\"\\u202a\",\n\t\"\\u202b\",\n\t\"\\u202c\",\n\t\"\\u202d\",\n\t\"\\u202e\",\n]);\n\nconst REASON_EN = {\n\tcredential: \"Looks like a credential or secret (secrets are never stored in memory)\",\n\tpayment: \"Looks like payment card data (never stored in memory)\",\n\t\"secret-assignment\": \"Looks like a password/secret/token value (never stored in memory)\",\n\t\"pii-email\": \"Looks like a personal email address (blocked by the strict privacy level)\",\n\t\"pii-phone\": \"Looks like a phone number (blocked by the strict privacy level)\",\n\t\"pii-id\": \"Looks like a national ID (DNI/NIE) (blocked by the strict privacy level)\",\n\t\"pii-bank\": \"Looks like a bank account / IBAN (blocked by the privacy level)\",\n\t\"pii-dob\": \"Looks like a date of birth (blocked by the strict privacy level)\",\n\tinjection: \"Looks like a prompt-injection attempt (never stored)\",\n\texfiltration: \"Looks like data exfiltration (never stored)\",\n};\n\nconst REASON_ES: Record<SensitiveCategory, string> = {\n\tcredential: \"Parece una credencial o secreto (los secretos nunca se almacenan en memoria)\",\n\tpayment: \"Parecen datos de tarjeta de pago (nunca se almacenan en memoria)\",\n\t\"secret-assignment\": \"Parece una contraseña/secreto/token (nunca se almacena en memoria)\",\n\t\"pii-email\": \"Parece un correo electrónico personal (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-phone\": \"Parece un número de teléfono (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-id\": \"Parece un documento de identidad (DNI/NIE) (bloqueado por el nivel de privacidad estricto)\",\n\t\"pii-bank\": \"Parece una cuenta bancaria / IBAN (bloqueado por el nivel de privacidad)\",\n\t\"pii-dob\": \"Parece una fecha de nacimiento (bloqueada por el nivel de privacidad estricto)\",\n\tinjection: \"Parece un intento de prompt injection (nunca se almacena)\",\n\texfiltration: \"Parece exfiltración de datos (nunca se almacena)\",\n};\n\nfunction testPattern(pattern: Pattern, content: string): boolean {\n\ttry {\n\t\treturn pattern.pattern.test(content);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Classify content against the security policy.\n *\n * @param content Text to classify.\n * @param level Configured privacy level.\n * @param locale \"es\" or \"en\" for the reason language.\n */\nexport function classifySensitive(\n\tcontent: string,\n\tlevel: PrivacyLevel = \"strict\",\n\tlocale: \"es\" | \"en\" = \"es\",\n): SensitivityResult {\n\tconst categories = new Set<SensitiveCategory>();\n\tconst reasons: string[] = [];\n\tconst labels: string[] = [];\n\n\t// Invisible characters are always suspicious (injection vectors).\n\tif ([...content].some((ch) => INVISIBLE_CHARS.has(ch))) {\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [\"injection\"],\n\t\t\treason:\n\t\t\t\tlocale === \"es\"\n\t\t\t\t\t? \"El contenido contiene caracteres invisibles/zero-width y fue rechazado por seguridad.\"\n\t\t\t\t\t: \"Content contains invisible/zero-width characters and was rejected for security.\",\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\tconst allPatterns = [...ALWAYS_BLOCKED, ...PAYMENT, ...PII, ...INJECTION];\n\tfor (const pattern of allPatterns) {\n\t\tif (!testPattern(pattern, content)) continue;\n\t\tconst levelIdx = LEVEL_ORDER[level];\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst blocked = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tcategories.add(pattern.category);\n\t\tif (blocked) {\n\t\t\tlabels.push(pattern.label);\n\t\t\tif (!reasons.includes(pattern.category)) reasons.push(pattern.category);\n\t\t}\n\t}\n\n\tif (reasons.length > 0) {\n\t\tconst localeMap = locale === \"es\" ? REASON_ES : REASON_EN;\n\t\tconst detail = reasons.map((r) => localeMap[r as SensitiveCategory]).join(\"; \");\n\t\treturn {\n\t\t\tblocked: true,\n\t\t\tcategories: [...categories],\n\t\t\treason: detail,\n\t\t\tnoteworthy: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tblocked: false,\n\t\tcategories: [...categories],\n\t\treason: \"\",\n\t\tnoteworthy: categories.size > 0,\n\t};\n}\n\n/**\n * Redact sensitive data from user text so it never reaches the LLM, the\n * session file, or the chat transcript. Only high-confidence patterns are\n * redacted (credentials, payment data, explicit secret assignments, and\n * PII like emails / national IDs / IBANs / DOB).\n */\nexport function redactSensitive(text: string, level: PrivacyLevel = \"strict\"): string {\n\tlet out = text;\n\tconst levelIdx = LEVEL_ORDER[level];\n\tfor (const pattern of [...ALWAYS_BLOCKED, ...PAYMENT, ...PII]) {\n\t\tif (pattern.category === \"pii-phone\") {\n\t\t\t// Phone redaction requires separators or a + prefix to avoid\n\t\t\t// mangling ordinary numbers in code.\n\t\t\tif (!/(?:\\+|\\s|\\()?\\d{3}[\\s.-]\\d{3}[\\s.-]\\d{3,4}/.test(text)) continue;\n\t\t}\n\t\tconst blockAtIdx = pattern.blockAt ? LEVEL_ORDER[pattern.blockAt] : 0;\n\t\tconst active = pattern.always === true || levelIdx <= blockAtIdx;\n\t\tif (!active) continue;\n\t\ttry {\n\t\t\tout = out.replace(pattern.pattern, (match) => \"█\".repeat(Math.min(match.length, 14)));\n\t\t} catch {\n\t\t\t// A bad pattern must never break redaction.\n\t\t}\n\t}\n\treturn out;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-indexer.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/session-indexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACrC,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CACjB;AAYD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWpD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA8C1E;AAMD,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,IAAI,CAA8C;IAC1D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,OAAO,EAAE,qBAAqB,EAKzC;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAS1B;YAEa,gBAAgB;IAuB9B;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAkC3C;IAED,+DAA+D;IAC/D,aAAa,CAAC,OAAO,EAAE;QACtB,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,GAAG,IAAI,CAaP;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,UAAU,EAAE,CAqB1H;IAED,gCAAgC;IAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1B;CACD","sourcesContent":["/**\n * SessionIndexer — indexes past conversations (layer 3c) from the session\n * JSONL files written by session-manager.ts.\n *\n * - Incremental backfill: meta.json tracks per-file size+mtime, so only\n * changed files are re-parsed on startup.\n * - Live indexing: messages are upserted as they are emitted.\n * - The index is a derived artifact; it can be rebuilt at any time.\n */\n\nimport { readFile, writeFile, mkdir, stat, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { InvertedIndex } from \"./inverted-index.ts\";\n\nexport interface ParsedSessionFile {\n\tsessionId: string;\n\tcwd: string | null;\n\tsessionName: string | null;\n\tmessages: Array<{\n\t\tid: string;\n\t\trole: \"user\" | \"assistant\";\n\t\tcontent: string;\n\t\ttimestamp: string;\n\t}>;\n}\n\nexport interface SessionIndexerOptions {\n\t/** Agent root (~/.ada/agent). Sessions live under <agentDir>/sessions. */\n\tagentDir: string;\n\t/** Directory for the engine index artifacts. */\n\tindexDir: string;\n}\n\ninterface FileMeta {\n\tsize: number;\n\tmtimeMs: number;\n}\n\ninterface SessionMetaFile {\n\tversion: 1;\n\tfiles: Record<string, FileMeta>;\n}\n\n/** Extract message text from the JSONL line's message.content array. */\nexport function extractText(content: unknown): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\tconst parts: string[] = [];\n\tfor (const block of content) {\n\t\tif (block && typeof block === \"object\" && (block as { type?: string }).type === \"text\") {\n\t\t\tconst text = (block as { text?: unknown }).text;\n\t\t\tif (typeof text === \"string\") parts.push(text);\n\t\t}\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nexport function parseSessionFile(content: string): ParsedSessionFile | null {\n\tlet sessionId: string | null = null;\n\tlet cwd: string | null = null;\n\tlet sessionName: string | null = null;\n\tconst messages: ParsedSessionFile[\"messages\"] = [];\n\n\tfor (const line of content.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed) continue;\n\t\tlet entry: Record<string, unknown>;\n\t\ttry {\n\t\t\tentry = JSON.parse(trimmed) as Record<string, unknown>;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\n\t\tif (entry.type === \"session\") {\n\t\t\tif (typeof entry.id === \"string\") sessionId = entry.id;\n\t\t\tif (typeof entry.cwd === \"string\") cwd = entry.cwd;\n\t\t\tif (typeof entry.name === \"string\" && entry.name) sessionName = entry.name;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type === \"session_name\" && typeof entry.name === \"string\") {\n\t\t\tsessionName = entry.name;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type !== \"message\") continue;\n\n\t\tconst msg = entry.message as { role?: unknown; content?: unknown; timestamp?: unknown } | undefined;\n\t\tif (!msg || typeof msg !== \"object\") continue;\n\t\tconst role = msg.role;\n\t\tif (role !== \"user\" && role !== \"assistant\") continue;\n\t\tconst text = extractText(msg.content);\n\t\tif (!text.trim()) continue;\n\t\tconst messageId = typeof entry.id === \"string\" ? entry.id : `${sessionId ?? \"?\"}:${messages.length}`;\n\t\tmessages.push({\n\t\t\tid: messageId,\n\t\t\trole,\n\t\t\tcontent: text.slice(0, 4000),\n\t\t\ttimestamp: typeof entry.timestamp === \"string\" ? entry.timestamp : String(Date.now()),\n\t\t});\n\t}\n\n\tif (!sessionId) return null;\n\treturn { sessionId, cwd, sessionName, messages };\n}\n\nfunction sessionRoot(agentDir: string): string {\n\treturn join(agentDir, \"sessions\");\n}\n\nexport interface SessionHit {\n\tsessionId: string;\n\tsessionName: string | null;\n\tproject: string;\n\trole: \"user\" | \"assistant\";\n\tcontent: string;\n\ttimestamp: string;\n\tscore: number;\n}\n\nexport class SessionIndexer {\n\tprivate index: InvertedIndex;\n\tprivate meta: SessionMetaFile = { version: 1, files: {} };\n\tprivate metaPath: string;\n\tprivate indexFilePath: string;\n\tprivate agentDir: string;\n\n\tconstructor(options: SessionIndexerOptions) {\n\t\tthis.agentDir = options.agentDir;\n\t\tthis.metaPath = join(options.indexDir, \"sessions-meta.json\");\n\t\tthis.indexFilePath = join(options.indexDir, \"sessions.idx.json\");\n\t\tthis.index = new InvertedIndex(\"sessions\");\n\t}\n\n\tasync load(): Promise<void> {\n\t\tawait this.index.load(this.indexFilePath);\n\t\ttry {\n\t\t\tconst raw = await readFile(this.metaPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw) as SessionMetaFile;\n\t\t\tif (parsed.version === 1) this.meta = parsed;\n\t\t} catch {\n\t\t\tthis.meta = { version: 1, files: {} };\n\t\t}\n\t}\n\n\tprivate async listSessionFiles(): Promise<string[]> {\n\t\tconst root = sessionRoot(this.agentDir);\n\t\tconst out: string[] = [];\n\t\tconst walk = async (dir: string): Promise<void> => {\n\t\t\tlet entries;\n\t\t\ttry {\n\t\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst full = join(dir, entry.name);\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tawait walk(full);\n\t\t\t\t} else if (entry.name.endsWith(\".jsonl\")) {\n\t\t\t\t\tout.push(full);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tawait walk(root);\n\t\treturn out.sort();\n\t}\n\n\t/**\n\t * Incremental backfill: index files whose size/mtime changed since the\n\t * last run. Returns the number of files indexed.\n\t */\n\tasync backfillIncremental(): Promise<number> {\n\t\tconst files = await this.listSessionFiles();\n\t\tlet indexed = 0;\n\t\tfor (const file of files) {\n\t\t\tlet st;\n\t\t\ttry {\n\t\t\t\tst = await stat(file);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst rel = relative(this.agentDir, file);\n\t\t\tconst previous = this.meta.files[rel];\n\t\t\tif (previous && previous.size === st.size && previous.mtimeMs === st.mtimeMs) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet content: string;\n\t\t\ttry {\n\t\t\t\tcontent = await readFile(file, \"utf-8\");\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst parsed = parseSessionFile(content);\n\t\t\tif (!parsed) continue;\n\t\t\tfor (const msg of parsed.messages) {\n\t\t\t\tthis.index.upsert(\n\t\t\t\t\t`${parsed.sessionId}:${msg.id}`,\n\t\t\t\t\tmsg.content,\n\t\t\t\t\t{ role: msg.role, timestamp: msg.timestamp, cwd: parsed.cwd, sessionId: parsed.sessionId, sessionName: parsed.sessionName },\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.meta.files[rel] = { size: st.size, mtimeMs: st.mtimeMs };\n\t\t\tindexed++;\n\t\t}\n\t\treturn indexed;\n\t}\n\n\t/** Live upsert of a single message (called on message_end). */\n\tupsertMessage(message: {\n\t\tid: string;\n\t\tsessionId: string;\n\t\trole: \"user\" | \"assistant\";\n\t\tcontent: string;\n\t\ttimestamp: string;\n\t\tcwd?: string | null;\n\t\tsessionName?: string | null;\n\t}): void {\n\t\tif (!message.content.trim()) return;\n\t\tthis.index.upsert(\n\t\t\t`${message.sessionId}:${message.id}`,\n\t\t\tmessage.content.slice(0, 4000),\n\t\t\t{\n\t\t\t\trole: message.role,\n\t\t\t\ttimestamp: message.timestamp,\n\t\t\t\tcwd: message.cwd ?? null,\n\t\t\t\tsessionId: message.sessionId,\n\t\t\t\tsessionName: message.sessionName ?? null,\n\t\t\t},\n\t\t);\n\t}\n\n\tsearch(query: string, options: { project?: string | null; role?: \"user\" | \"assistant\"; limit?: number } = {}): SessionHit[] {\n\t\tconst limit = options.limit ?? 10;\n\t\tconst results = this.index.search(query, limit * 8, 0.15);\n\t\tconst out: SessionHit[] = [];\n\t\tfor (const result of results) {\n\t\t\tconst meta = this.index.getDocMeta(result.id);\n\t\t\tif (!meta) continue;\n\t\t\tif (options.project && meta.cwd !== options.project) continue;\n\t\t\tif (options.role && meta.role !== options.role) continue;\n\t\t\tout.push({\n\t\t\t\tsessionId: (meta.sessionId as string) ?? \"\",\n\t\t\t\tsessionName: (meta.sessionName as string | null) ?? null,\n\t\t\t\tproject: (meta.cwd as string) ?? \"\",\n\t\t\t\trole: (meta.role as \"user\" | \"assistant\") ?? \"user\",\n\t\t\t\tcontent: result.snippet,\n\t\t\t\ttimestamp: (meta.timestamp as string) ?? \"\",\n\t\t\t\tscore: result.score,\n\t\t\t});\n\t\t\tif (out.length >= limit) break;\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Persist index + metadata. */\n\tasync save(): Promise<void> {\n\t\tawait mkdir(this.metaPath.replace(/[^/]+$/, \"\"), { recursive: true });\n\t\tawait this.index.save(this.indexFilePath);\n\t\tawait writeFile(this.metaPath, JSON.stringify(this.meta), \"utf-8\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-indexer.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/session-indexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACrC,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CACjB;AAYD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWpD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA8C1E;AAMD,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,IAAI,CAA8C;IAC1D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,OAAO,EAAE,qBAAqB,EAKzC;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAS1B;YAEa,gBAAgB;IAuB9B;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAoC3C;IAED,+DAA+D;IAC/D,aAAa,CAAC,OAAO,EAAE;QACtB,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,GAAG,IAAI,CASP;IAED,MAAM,CACL,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpF,UAAU,EAAE,CAqBd;IAED,gCAAgC;IAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAI1B;CACD","sourcesContent":["/**\n * SessionIndexer — indexes past conversations (layer 3c) from the session\n * JSONL files written by session-manager.ts.\n *\n * - Incremental backfill: meta.json tracks per-file size+mtime, so only\n * changed files are re-parsed on startup.\n * - Live indexing: messages are upserted as they are emitted.\n * - The index is a derived artifact; it can be rebuilt at any time.\n */\n\nimport type { Dirent, Stats } from \"node:fs\";\nimport { mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { InvertedIndex } from \"./inverted-index.ts\";\n\nexport interface ParsedSessionFile {\n\tsessionId: string;\n\tcwd: string | null;\n\tsessionName: string | null;\n\tmessages: Array<{\n\t\tid: string;\n\t\trole: \"user\" | \"assistant\";\n\t\tcontent: string;\n\t\ttimestamp: string;\n\t}>;\n}\n\nexport interface SessionIndexerOptions {\n\t/** Agent root (~/.ada/agent). Sessions live under <agentDir>/sessions. */\n\tagentDir: string;\n\t/** Directory for the engine index artifacts. */\n\tindexDir: string;\n}\n\ninterface FileMeta {\n\tsize: number;\n\tmtimeMs: number;\n}\n\ninterface SessionMetaFile {\n\tversion: 1;\n\tfiles: Record<string, FileMeta>;\n}\n\n/** Extract message text from the JSONL line's message.content array. */\nexport function extractText(content: unknown): string {\n\tif (typeof content === \"string\") return content;\n\tif (!Array.isArray(content)) return \"\";\n\tconst parts: string[] = [];\n\tfor (const block of content) {\n\t\tif (block && typeof block === \"object\" && (block as { type?: string }).type === \"text\") {\n\t\t\tconst text = (block as { text?: unknown }).text;\n\t\t\tif (typeof text === \"string\") parts.push(text);\n\t\t}\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nexport function parseSessionFile(content: string): ParsedSessionFile | null {\n\tlet sessionId: string | null = null;\n\tlet cwd: string | null = null;\n\tlet sessionName: string | null = null;\n\tconst messages: ParsedSessionFile[\"messages\"] = [];\n\n\tfor (const line of content.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed) continue;\n\t\tlet entry: Record<string, unknown>;\n\t\ttry {\n\t\t\tentry = JSON.parse(trimmed) as Record<string, unknown>;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\n\t\tif (entry.type === \"session\") {\n\t\t\tif (typeof entry.id === \"string\") sessionId = entry.id;\n\t\t\tif (typeof entry.cwd === \"string\") cwd = entry.cwd;\n\t\t\tif (typeof entry.name === \"string\" && entry.name) sessionName = entry.name;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type === \"session_name\" && typeof entry.name === \"string\") {\n\t\t\tsessionName = entry.name;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type !== \"message\") continue;\n\n\t\tconst msg = entry.message as { role?: unknown; content?: unknown; timestamp?: unknown } | undefined;\n\t\tif (!msg || typeof msg !== \"object\") continue;\n\t\tconst role = msg.role;\n\t\tif (role !== \"user\" && role !== \"assistant\") continue;\n\t\tconst text = extractText(msg.content);\n\t\tif (!text.trim()) continue;\n\t\tconst messageId = typeof entry.id === \"string\" ? entry.id : `${sessionId ?? \"?\"}:${messages.length}`;\n\t\tmessages.push({\n\t\t\tid: messageId,\n\t\t\trole,\n\t\t\tcontent: text.slice(0, 4000),\n\t\t\ttimestamp: typeof entry.timestamp === \"string\" ? entry.timestamp : String(Date.now()),\n\t\t});\n\t}\n\n\tif (!sessionId) return null;\n\treturn { sessionId, cwd, sessionName, messages };\n}\n\nfunction sessionRoot(agentDir: string): string {\n\treturn join(agentDir, \"sessions\");\n}\n\nexport interface SessionHit {\n\tsessionId: string;\n\tsessionName: string | null;\n\tproject: string;\n\trole: \"user\" | \"assistant\";\n\tcontent: string;\n\ttimestamp: string;\n\tscore: number;\n}\n\nexport class SessionIndexer {\n\tprivate index: InvertedIndex;\n\tprivate meta: SessionMetaFile = { version: 1, files: {} };\n\tprivate metaPath: string;\n\tprivate indexFilePath: string;\n\tprivate agentDir: string;\n\n\tconstructor(options: SessionIndexerOptions) {\n\t\tthis.agentDir = options.agentDir;\n\t\tthis.metaPath = join(options.indexDir, \"sessions-meta.json\");\n\t\tthis.indexFilePath = join(options.indexDir, \"sessions.idx.json\");\n\t\tthis.index = new InvertedIndex(\"sessions\");\n\t}\n\n\tasync load(): Promise<void> {\n\t\tawait this.index.load(this.indexFilePath);\n\t\ttry {\n\t\t\tconst raw = await readFile(this.metaPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw) as SessionMetaFile;\n\t\t\tif (parsed.version === 1) this.meta = parsed;\n\t\t} catch {\n\t\t\tthis.meta = { version: 1, files: {} };\n\t\t}\n\t}\n\n\tprivate async listSessionFiles(): Promise<string[]> {\n\t\tconst root = sessionRoot(this.agentDir);\n\t\tconst out: string[] = [];\n\t\tconst walk = async (dir: string): Promise<void> => {\n\t\t\tlet entries: Dirent[] | undefined;\n\t\t\ttry {\n\t\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst full = join(dir, entry.name);\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tawait walk(full);\n\t\t\t\t} else if (entry.name.endsWith(\".jsonl\")) {\n\t\t\t\t\tout.push(full);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tawait walk(root);\n\t\treturn out.sort();\n\t}\n\n\t/**\n\t * Incremental backfill: index files whose size/mtime changed since the\n\t * last run. Returns the number of files indexed.\n\t */\n\tasync backfillIncremental(): Promise<number> {\n\t\tconst files = await this.listSessionFiles();\n\t\tlet indexed = 0;\n\t\tfor (const file of files) {\n\t\t\tlet st: Stats | undefined;\n\t\t\ttry {\n\t\t\t\tst = await stat(file);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst rel = relative(this.agentDir, file);\n\t\t\tconst previous = this.meta.files[rel];\n\t\t\tif (previous && previous.size === st.size && previous.mtimeMs === st.mtimeMs) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet content: string;\n\t\t\ttry {\n\t\t\t\tcontent = await readFile(file, \"utf-8\");\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst parsed = parseSessionFile(content);\n\t\t\tif (!parsed) continue;\n\t\t\tfor (const msg of parsed.messages) {\n\t\t\t\tthis.index.upsert(`${parsed.sessionId}:${msg.id}`, msg.content, {\n\t\t\t\t\trole: msg.role,\n\t\t\t\t\ttimestamp: msg.timestamp,\n\t\t\t\t\tcwd: parsed.cwd,\n\t\t\t\t\tsessionId: parsed.sessionId,\n\t\t\t\t\tsessionName: parsed.sessionName,\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.meta.files[rel] = { size: st.size, mtimeMs: st.mtimeMs };\n\t\t\tindexed++;\n\t\t}\n\t\treturn indexed;\n\t}\n\n\t/** Live upsert of a single message (called on message_end). */\n\tupsertMessage(message: {\n\t\tid: string;\n\t\tsessionId: string;\n\t\trole: \"user\" | \"assistant\";\n\t\tcontent: string;\n\t\ttimestamp: string;\n\t\tcwd?: string | null;\n\t\tsessionName?: string | null;\n\t}): void {\n\t\tif (!message.content.trim()) return;\n\t\tthis.index.upsert(`${message.sessionId}:${message.id}`, message.content.slice(0, 4000), {\n\t\t\trole: message.role,\n\t\t\ttimestamp: message.timestamp,\n\t\t\tcwd: message.cwd ?? null,\n\t\t\tsessionId: message.sessionId,\n\t\t\tsessionName: message.sessionName ?? null,\n\t\t});\n\t}\n\n\tsearch(\n\t\tquery: string,\n\t\toptions: { project?: string | null; role?: \"user\" | \"assistant\"; limit?: number } = {},\n\t): SessionHit[] {\n\t\tconst limit = options.limit ?? 10;\n\t\tconst results = this.index.search(query, limit * 8, 0.15);\n\t\tconst out: SessionHit[] = [];\n\t\tfor (const result of results) {\n\t\t\tconst meta = this.index.getDocMeta(result.id);\n\t\t\tif (!meta) continue;\n\t\t\tif (options.project && meta.cwd !== options.project) continue;\n\t\t\tif (options.role && meta.role !== options.role) continue;\n\t\t\tout.push({\n\t\t\t\tsessionId: (meta.sessionId as string) ?? \"\",\n\t\t\t\tsessionName: (meta.sessionName as string | null) ?? null,\n\t\t\t\tproject: (meta.cwd as string) ?? \"\",\n\t\t\t\trole: (meta.role as \"user\" | \"assistant\") ?? \"user\",\n\t\t\t\tcontent: result.snippet,\n\t\t\t\ttimestamp: (meta.timestamp as string) ?? \"\",\n\t\t\t\tscore: result.score,\n\t\t\t});\n\t\t\tif (out.length >= limit) break;\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Persist index + metadata. */\n\tasync save(): Promise<void> {\n\t\tawait mkdir(this.metaPath.replace(/[^/]+$/, \"\"), { recursive: true });\n\t\tawait this.index.save(this.indexFilePath);\n\t\tawait writeFile(this.metaPath, JSON.stringify(this.meta), \"utf-8\");\n\t}\n}\n"]}
@@ -7,7 +7,7 @@
7
7
  * - Live indexing: messages are upserted as they are emitted.
8
8
  * - The index is a derived artifact; it can be rebuilt at any time.
9
9
  */
10
- import { readFile, writeFile, mkdir, stat, readdir } from "node:fs/promises";
10
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
11
11
  import { join, relative } from "node:path";
12
12
  import { InvertedIndex } from "./inverted-index.js";
13
13
  /** Extract message text from the JSONL line's message.content array. */
@@ -162,7 +162,13 @@ export class SessionIndexer {
162
162
  if (!parsed)
163
163
  continue;
164
164
  for (const msg of parsed.messages) {
165
- this.index.upsert(`${parsed.sessionId}:${msg.id}`, msg.content, { role: msg.role, timestamp: msg.timestamp, cwd: parsed.cwd, sessionId: parsed.sessionId, sessionName: parsed.sessionName });
165
+ this.index.upsert(`${parsed.sessionId}:${msg.id}`, msg.content, {
166
+ role: msg.role,
167
+ timestamp: msg.timestamp,
168
+ cwd: parsed.cwd,
169
+ sessionId: parsed.sessionId,
170
+ sessionName: parsed.sessionName,
171
+ });
166
172
  }
167
173
  this.meta.files[rel] = { size: st.size, mtimeMs: st.mtimeMs };
168
174
  indexed++;