@soleri/core 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/dist/agency/agency-manager.d.ts +47 -0
  2. package/dist/agency/agency-manager.d.ts.map +1 -0
  3. package/dist/agency/agency-manager.js +281 -0
  4. package/dist/agency/agency-manager.js.map +1 -0
  5. package/dist/agency/index.d.ts +3 -0
  6. package/dist/agency/index.d.ts.map +1 -0
  7. package/dist/agency/index.js +2 -0
  8. package/dist/agency/index.js.map +1 -0
  9. package/dist/agency/types.d.ts +69 -0
  10. package/dist/agency/types.d.ts.map +1 -0
  11. package/dist/agency/types.js +5 -0
  12. package/dist/agency/types.js.map +1 -0
  13. package/dist/brain/brain.d.ts +0 -1
  14. package/dist/brain/brain.d.ts.map +1 -1
  15. package/dist/brain/brain.js +14 -0
  16. package/dist/brain/brain.js.map +1 -1
  17. package/dist/brain/intelligence.d.ts +5 -1
  18. package/dist/brain/intelligence.d.ts.map +1 -1
  19. package/dist/brain/intelligence.js +83 -0
  20. package/dist/brain/intelligence.js.map +1 -1
  21. package/dist/brain/types.d.ts +21 -0
  22. package/dist/brain/types.d.ts.map +1 -1
  23. package/dist/chat/agent-loop-types.d.ts +82 -0
  24. package/dist/chat/agent-loop-types.d.ts.map +1 -0
  25. package/dist/chat/agent-loop-types.js +8 -0
  26. package/dist/chat/agent-loop-types.js.map +1 -0
  27. package/dist/chat/agent-loop.d.ts +19 -0
  28. package/dist/chat/agent-loop.d.ts.map +1 -0
  29. package/dist/chat/agent-loop.js +261 -0
  30. package/dist/chat/agent-loop.js.map +1 -0
  31. package/dist/chat/auth-manager.d.ts +49 -0
  32. package/dist/chat/auth-manager.d.ts.map +1 -0
  33. package/dist/chat/auth-manager.js +152 -0
  34. package/dist/chat/auth-manager.js.map +1 -0
  35. package/dist/chat/browser-session.d.ts +86 -0
  36. package/dist/chat/browser-session.d.ts.map +1 -0
  37. package/dist/chat/browser-session.js +143 -0
  38. package/dist/chat/browser-session.js.map +1 -0
  39. package/dist/chat/cancellation.d.ts +54 -0
  40. package/dist/chat/cancellation.d.ts.map +1 -0
  41. package/dist/chat/cancellation.js +80 -0
  42. package/dist/chat/cancellation.js.map +1 -0
  43. package/dist/chat/chat-session.d.ts +86 -0
  44. package/dist/chat/chat-session.d.ts.map +1 -0
  45. package/dist/chat/chat-session.js +252 -0
  46. package/dist/chat/chat-session.js.map +1 -0
  47. package/dist/chat/file-handler.d.ts +63 -0
  48. package/dist/chat/file-handler.d.ts.map +1 -0
  49. package/dist/chat/file-handler.js +182 -0
  50. package/dist/chat/file-handler.js.map +1 -0
  51. package/dist/chat/fragment-buffer.d.ts +49 -0
  52. package/dist/chat/fragment-buffer.d.ts.map +1 -0
  53. package/dist/chat/fragment-buffer.js +130 -0
  54. package/dist/chat/fragment-buffer.js.map +1 -0
  55. package/dist/chat/index.d.ts +24 -0
  56. package/dist/chat/index.d.ts.map +1 -0
  57. package/dist/chat/index.js +15 -0
  58. package/dist/chat/index.js.map +1 -0
  59. package/dist/chat/mcp-bridge.d.ts +60 -0
  60. package/dist/chat/mcp-bridge.d.ts.map +1 -0
  61. package/dist/chat/mcp-bridge.js +111 -0
  62. package/dist/chat/mcp-bridge.js.map +1 -0
  63. package/dist/chat/notifications.d.ts +82 -0
  64. package/dist/chat/notifications.d.ts.map +1 -0
  65. package/dist/chat/notifications.js +119 -0
  66. package/dist/chat/notifications.js.map +1 -0
  67. package/dist/chat/output-compressor.d.ts +30 -0
  68. package/dist/chat/output-compressor.d.ts.map +1 -0
  69. package/dist/chat/output-compressor.js +95 -0
  70. package/dist/chat/output-compressor.js.map +1 -0
  71. package/dist/chat/queue.d.ts +91 -0
  72. package/dist/chat/queue.d.ts.map +1 -0
  73. package/dist/chat/queue.js +146 -0
  74. package/dist/chat/queue.js.map +1 -0
  75. package/dist/chat/response-chunker.d.ts +29 -0
  76. package/dist/chat/response-chunker.d.ts.map +1 -0
  77. package/dist/chat/response-chunker.js +163 -0
  78. package/dist/chat/response-chunker.js.map +1 -0
  79. package/dist/chat/self-update.d.ts +62 -0
  80. package/dist/chat/self-update.d.ts.map +1 -0
  81. package/dist/chat/self-update.js +90 -0
  82. package/dist/chat/self-update.js.map +1 -0
  83. package/dist/chat/types.d.ts +105 -0
  84. package/dist/chat/types.d.ts.map +1 -0
  85. package/dist/chat/types.js +8 -0
  86. package/dist/chat/types.js.map +1 -0
  87. package/dist/chat/voice.d.ts +39 -0
  88. package/dist/chat/voice.d.ts.map +1 -0
  89. package/dist/chat/voice.js +80 -0
  90. package/dist/chat/voice.js.map +1 -0
  91. package/dist/claudemd/compose.d.ts +31 -0
  92. package/dist/claudemd/compose.d.ts.map +1 -0
  93. package/dist/claudemd/compose.js +105 -0
  94. package/dist/claudemd/compose.js.map +1 -0
  95. package/dist/claudemd/index.d.ts +5 -0
  96. package/dist/claudemd/index.d.ts.map +1 -0
  97. package/dist/claudemd/index.js +3 -0
  98. package/dist/claudemd/index.js.map +1 -0
  99. package/dist/claudemd/inject.d.ts +31 -0
  100. package/dist/claudemd/inject.d.ts.map +1 -0
  101. package/dist/claudemd/inject.js +157 -0
  102. package/dist/claudemd/inject.js.map +1 -0
  103. package/dist/claudemd/types.d.ts +41 -0
  104. package/dist/claudemd/types.d.ts.map +1 -0
  105. package/dist/claudemd/types.js +5 -0
  106. package/dist/claudemd/types.js.map +1 -0
  107. package/dist/context/context-engine.d.ts +31 -0
  108. package/dist/context/context-engine.d.ts.map +1 -0
  109. package/dist/context/context-engine.js +245 -0
  110. package/dist/context/context-engine.js.map +1 -0
  111. package/dist/context/index.d.ts +3 -0
  112. package/dist/context/index.d.ts.map +1 -0
  113. package/dist/context/index.js +2 -0
  114. package/dist/context/index.js.map +1 -0
  115. package/dist/context/types.d.ts +54 -0
  116. package/dist/context/types.d.ts.map +1 -0
  117. package/dist/context/types.js +5 -0
  118. package/dist/context/types.js.map +1 -0
  119. package/dist/enforcement/adapters/claude-code.d.ts +18 -0
  120. package/dist/enforcement/adapters/claude-code.d.ts.map +1 -0
  121. package/dist/enforcement/adapters/claude-code.js +106 -0
  122. package/dist/enforcement/adapters/claude-code.js.map +1 -0
  123. package/dist/enforcement/adapters/index.d.ts +2 -0
  124. package/dist/enforcement/adapters/index.d.ts.map +1 -0
  125. package/dist/enforcement/adapters/index.js +2 -0
  126. package/dist/enforcement/adapters/index.js.map +1 -0
  127. package/dist/enforcement/index.d.ts +4 -0
  128. package/dist/enforcement/index.d.ts.map +1 -0
  129. package/dist/enforcement/index.js +3 -0
  130. package/dist/enforcement/index.js.map +1 -0
  131. package/dist/enforcement/registry.d.ts +23 -0
  132. package/dist/enforcement/registry.d.ts.map +1 -0
  133. package/dist/enforcement/registry.js +63 -0
  134. package/dist/enforcement/registry.js.map +1 -0
  135. package/dist/enforcement/types.d.ts +51 -0
  136. package/dist/enforcement/types.d.ts.map +1 -0
  137. package/dist/enforcement/types.js +8 -0
  138. package/dist/enforcement/types.js.map +1 -0
  139. package/dist/facades/facade-factory.d.ts +10 -3
  140. package/dist/facades/facade-factory.d.ts.map +1 -1
  141. package/dist/facades/facade-factory.js +94 -5
  142. package/dist/facades/facade-factory.js.map +1 -1
  143. package/dist/facades/types.d.ts +15 -1
  144. package/dist/facades/types.d.ts.map +1 -1
  145. package/dist/facades/types.js +6 -0
  146. package/dist/facades/types.js.map +1 -1
  147. package/dist/health/health-registry.d.ts +40 -0
  148. package/dist/health/health-registry.d.ts.map +1 -0
  149. package/dist/health/health-registry.js +134 -0
  150. package/dist/health/health-registry.js.map +1 -0
  151. package/dist/health/index.d.ts +5 -0
  152. package/dist/health/index.d.ts.map +1 -0
  153. package/dist/health/index.js +3 -0
  154. package/dist/health/index.js.map +1 -0
  155. package/dist/health/vault-integrity.d.ts +13 -0
  156. package/dist/health/vault-integrity.d.ts.map +1 -0
  157. package/dist/health/vault-integrity.js +49 -0
  158. package/dist/health/vault-integrity.js.map +1 -0
  159. package/dist/index.d.ts +67 -6
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +51 -3
  162. package/dist/index.js.map +1 -1
  163. package/dist/intake/intake-pipeline.d.ts +0 -7
  164. package/dist/intake/intake-pipeline.d.ts.map +1 -1
  165. package/dist/intake/intake-pipeline.js +1 -1
  166. package/dist/intake/intake-pipeline.js.map +1 -1
  167. package/dist/intelligence/types.d.ts +1 -0
  168. package/dist/intelligence/types.d.ts.map +1 -1
  169. package/dist/migrations/index.d.ts +6 -0
  170. package/dist/migrations/index.d.ts.map +1 -0
  171. package/dist/migrations/index.js +5 -0
  172. package/dist/migrations/index.js.map +1 -0
  173. package/dist/migrations/migration-runner.d.ts +51 -0
  174. package/dist/migrations/migration-runner.d.ts.map +1 -0
  175. package/dist/migrations/migration-runner.js +141 -0
  176. package/dist/migrations/migration-runner.js.map +1 -0
  177. package/dist/packs/index.d.ts +10 -0
  178. package/dist/packs/index.d.ts.map +1 -0
  179. package/dist/packs/index.js +8 -0
  180. package/dist/packs/index.js.map +1 -0
  181. package/dist/packs/lockfile.d.ts +97 -0
  182. package/dist/packs/lockfile.d.ts.map +1 -0
  183. package/dist/packs/lockfile.js +129 -0
  184. package/dist/packs/lockfile.js.map +1 -0
  185. package/dist/packs/pack-installer.d.ts +41 -0
  186. package/dist/packs/pack-installer.d.ts.map +1 -0
  187. package/dist/packs/pack-installer.js +253 -0
  188. package/dist/packs/pack-installer.js.map +1 -0
  189. package/dist/packs/resolver.d.ts +51 -0
  190. package/dist/packs/resolver.d.ts.map +1 -0
  191. package/dist/packs/resolver.js +195 -0
  192. package/dist/packs/resolver.js.map +1 -0
  193. package/dist/packs/types.d.ts +186 -0
  194. package/dist/packs/types.d.ts.map +1 -0
  195. package/dist/packs/types.js +69 -0
  196. package/dist/packs/types.js.map +1 -0
  197. package/dist/persistence/postgres-provider.d.ts +42 -7
  198. package/dist/persistence/postgres-provider.d.ts.map +1 -1
  199. package/dist/persistence/postgres-provider.js +187 -46
  200. package/dist/persistence/postgres-provider.js.map +1 -1
  201. package/dist/planning/gap-analysis.d.ts.map +1 -1
  202. package/dist/planning/gap-analysis.js +3 -1
  203. package/dist/planning/gap-analysis.js.map +1 -1
  204. package/dist/playbooks/index.d.ts +2 -0
  205. package/dist/playbooks/index.d.ts.map +1 -1
  206. package/dist/playbooks/index.js +2 -0
  207. package/dist/playbooks/index.js.map +1 -1
  208. package/dist/playbooks/playbook-executor.d.ts +100 -0
  209. package/dist/playbooks/playbook-executor.d.ts.map +1 -0
  210. package/dist/playbooks/playbook-executor.js +207 -0
  211. package/dist/playbooks/playbook-executor.js.map +1 -0
  212. package/dist/plugins/index.d.ts +7 -0
  213. package/dist/plugins/index.d.ts.map +1 -0
  214. package/dist/plugins/index.js +7 -0
  215. package/dist/plugins/index.js.map +1 -0
  216. package/dist/plugins/plugin-loader.d.ts +28 -0
  217. package/dist/plugins/plugin-loader.d.ts.map +1 -0
  218. package/dist/plugins/plugin-loader.js +150 -0
  219. package/dist/plugins/plugin-loader.js.map +1 -0
  220. package/dist/plugins/plugin-registry.d.ts +58 -0
  221. package/dist/plugins/plugin-registry.d.ts.map +1 -0
  222. package/dist/plugins/plugin-registry.js +157 -0
  223. package/dist/plugins/plugin-registry.js.map +1 -0
  224. package/dist/plugins/types.d.ts +180 -0
  225. package/dist/plugins/types.d.ts.map +1 -0
  226. package/dist/plugins/types.js +48 -0
  227. package/dist/plugins/types.js.map +1 -0
  228. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  229. package/dist/runtime/admin-extra-ops.js +181 -8
  230. package/dist/runtime/admin-extra-ops.js.map +1 -1
  231. package/dist/runtime/capture-ops.d.ts.map +1 -1
  232. package/dist/runtime/capture-ops.js +106 -7
  233. package/dist/runtime/capture-ops.js.map +1 -1
  234. package/dist/runtime/deprecation.d.ts +33 -0
  235. package/dist/runtime/deprecation.d.ts.map +1 -0
  236. package/dist/runtime/deprecation.js +41 -0
  237. package/dist/runtime/deprecation.js.map +1 -0
  238. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  239. package/dist/runtime/facades/admin-facade.js +12 -1
  240. package/dist/runtime/facades/admin-facade.js.map +1 -1
  241. package/dist/runtime/facades/agency-facade.d.ts +7 -0
  242. package/dist/runtime/facades/agency-facade.d.ts.map +1 -0
  243. package/dist/runtime/facades/agency-facade.js +103 -0
  244. package/dist/runtime/facades/agency-facade.js.map +1 -0
  245. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  246. package/dist/runtime/facades/brain-facade.js +58 -0
  247. package/dist/runtime/facades/brain-facade.js.map +1 -1
  248. package/dist/runtime/facades/chat-facade.d.ts +7 -0
  249. package/dist/runtime/facades/chat-facade.d.ts.map +1 -0
  250. package/dist/runtime/facades/chat-facade.js +808 -0
  251. package/dist/runtime/facades/chat-facade.js.map +1 -0
  252. package/dist/runtime/facades/context-facade.d.ts +7 -0
  253. package/dist/runtime/facades/context-facade.d.ts.map +1 -0
  254. package/dist/runtime/facades/context-facade.js +45 -0
  255. package/dist/runtime/facades/context-facade.js.map +1 -0
  256. package/dist/runtime/facades/index.d.ts.map +1 -1
  257. package/dist/runtime/facades/index.js +18 -0
  258. package/dist/runtime/facades/index.js.map +1 -1
  259. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  260. package/dist/runtime/facades/vault-facade.js +247 -1
  261. package/dist/runtime/facades/vault-facade.js.map +1 -1
  262. package/dist/runtime/feature-flags.d.ts +18 -0
  263. package/dist/runtime/feature-flags.d.ts.map +1 -0
  264. package/dist/runtime/feature-flags.js +90 -0
  265. package/dist/runtime/feature-flags.js.map +1 -0
  266. package/dist/runtime/pack-ops.d.ts +9 -0
  267. package/dist/runtime/pack-ops.d.ts.map +1 -0
  268. package/dist/runtime/pack-ops.js +76 -0
  269. package/dist/runtime/pack-ops.js.map +1 -0
  270. package/dist/runtime/playbook-ops.d.ts +3 -7
  271. package/dist/runtime/playbook-ops.d.ts.map +1 -1
  272. package/dist/runtime/playbook-ops.js +101 -10
  273. package/dist/runtime/playbook-ops.js.map +1 -1
  274. package/dist/runtime/plugin-ops.d.ts +9 -0
  275. package/dist/runtime/plugin-ops.d.ts.map +1 -0
  276. package/dist/runtime/plugin-ops.js +235 -0
  277. package/dist/runtime/plugin-ops.js.map +1 -0
  278. package/dist/runtime/runtime.d.ts.map +1 -1
  279. package/dist/runtime/runtime.js +72 -5
  280. package/dist/runtime/runtime.js.map +1 -1
  281. package/dist/runtime/telemetry-ops.d.ts +10 -0
  282. package/dist/runtime/telemetry-ops.d.ts.map +1 -0
  283. package/dist/runtime/telemetry-ops.js +53 -0
  284. package/dist/runtime/telemetry-ops.js.map +1 -0
  285. package/dist/runtime/types.d.ts +35 -0
  286. package/dist/runtime/types.d.ts.map +1 -1
  287. package/dist/runtime/vault-sharing-ops.d.ts +13 -0
  288. package/dist/runtime/vault-sharing-ops.d.ts.map +1 -0
  289. package/dist/runtime/vault-sharing-ops.js +345 -0
  290. package/dist/runtime/vault-sharing-ops.js.map +1 -0
  291. package/dist/streams/index.d.ts +1 -1
  292. package/dist/streams/index.d.ts.map +1 -1
  293. package/dist/streams/index.js +1 -1
  294. package/dist/streams/index.js.map +1 -1
  295. package/dist/streams/replayable-stream.d.ts +13 -1
  296. package/dist/streams/replayable-stream.d.ts.map +1 -1
  297. package/dist/streams/replayable-stream.js +27 -3
  298. package/dist/streams/replayable-stream.js.map +1 -1
  299. package/dist/text/similarity.d.ts +0 -1
  300. package/dist/text/similarity.d.ts.map +1 -1
  301. package/dist/text/similarity.js +1 -1
  302. package/dist/text/similarity.js.map +1 -1
  303. package/dist/transport/http-server.d.ts +56 -0
  304. package/dist/transport/http-server.d.ts.map +1 -0
  305. package/dist/transport/http-server.js +210 -0
  306. package/dist/transport/http-server.js.map +1 -0
  307. package/dist/transport/index.d.ts +11 -0
  308. package/dist/transport/index.d.ts.map +1 -0
  309. package/dist/transport/index.js +10 -0
  310. package/dist/transport/index.js.map +1 -0
  311. package/dist/transport/lsp-server.d.ts +140 -0
  312. package/dist/transport/lsp-server.d.ts.map +1 -0
  313. package/dist/transport/lsp-server.js +239 -0
  314. package/dist/transport/lsp-server.js.map +1 -0
  315. package/dist/transport/rate-limiter.d.ts +35 -0
  316. package/dist/transport/rate-limiter.d.ts.map +1 -0
  317. package/dist/transport/rate-limiter.js +72 -0
  318. package/dist/transport/rate-limiter.js.map +1 -0
  319. package/dist/transport/session-manager.d.ts +49 -0
  320. package/dist/transport/session-manager.d.ts.map +1 -0
  321. package/dist/transport/session-manager.js +83 -0
  322. package/dist/transport/session-manager.js.map +1 -0
  323. package/dist/transport/token-auth.d.ts +29 -0
  324. package/dist/transport/token-auth.d.ts.map +1 -0
  325. package/dist/transport/token-auth.js +84 -0
  326. package/dist/transport/token-auth.js.map +1 -0
  327. package/dist/transport/types.d.ts +61 -0
  328. package/dist/transport/types.d.ts.map +1 -0
  329. package/dist/transport/types.js +5 -0
  330. package/dist/transport/types.js.map +1 -0
  331. package/dist/transport/ws-server.d.ts +78 -0
  332. package/dist/transport/ws-server.d.ts.map +1 -0
  333. package/dist/transport/ws-server.js +342 -0
  334. package/dist/transport/ws-server.js.map +1 -0
  335. package/dist/vault/git-vault-sync.d.ts +107 -0
  336. package/dist/vault/git-vault-sync.d.ts.map +1 -0
  337. package/dist/vault/git-vault-sync.js +251 -0
  338. package/dist/vault/git-vault-sync.js.map +1 -0
  339. package/dist/vault/knowledge-review.d.ts +67 -0
  340. package/dist/vault/knowledge-review.d.ts.map +1 -0
  341. package/dist/vault/knowledge-review.js +133 -0
  342. package/dist/vault/knowledge-review.js.map +1 -0
  343. package/dist/vault/obsidian-sync.d.ts +94 -0
  344. package/dist/vault/obsidian-sync.d.ts.map +1 -0
  345. package/dist/vault/obsidian-sync.js +247 -0
  346. package/dist/vault/obsidian-sync.js.map +1 -0
  347. package/dist/vault/scope-detector.d.ts +31 -0
  348. package/dist/vault/scope-detector.d.ts.map +1 -0
  349. package/dist/vault/scope-detector.js +182 -0
  350. package/dist/vault/scope-detector.js.map +1 -0
  351. package/dist/vault/vault-branching.d.ts +71 -0
  352. package/dist/vault/vault-branching.d.ts.map +1 -0
  353. package/dist/vault/vault-branching.js +180 -0
  354. package/dist/vault/vault-branching.js.map +1 -0
  355. package/dist/vault/vault-manager.d.ts +89 -0
  356. package/dist/vault/vault-manager.d.ts.map +1 -0
  357. package/dist/vault/vault-manager.js +199 -0
  358. package/dist/vault/vault-manager.js.map +1 -0
  359. package/dist/vault/vault-types.d.ts +30 -0
  360. package/dist/vault/vault-types.d.ts.map +1 -0
  361. package/dist/vault/vault-types.js +10 -0
  362. package/dist/vault/vault-types.js.map +1 -0
  363. package/dist/vault/vault.d.ts +10 -0
  364. package/dist/vault/vault.d.ts.map +1 -1
  365. package/dist/vault/vault.js +36 -3
  366. package/dist/vault/vault.js.map +1 -1
  367. package/package.json +1 -1
  368. package/src/__tests__/admin-extra-ops.test.ts +31 -11
  369. package/src/__tests__/agency-manager.test.ts +374 -0
  370. package/src/__tests__/agent-loop.test.ts +256 -0
  371. package/src/__tests__/capture-ops.test.ts +275 -0
  372. package/src/__tests__/chat-differentiators.test.ts +251 -0
  373. package/src/__tests__/chat-enhanced.test.ts +390 -0
  374. package/src/__tests__/chat-transport.test.ts +665 -0
  375. package/src/__tests__/claudemd.test.ts +282 -0
  376. package/src/__tests__/context-engine.test.ts +256 -0
  377. package/src/__tests__/core-ops.test.ts +97 -5
  378. package/src/__tests__/deprecation.test.ts +78 -0
  379. package/src/__tests__/enforcement.test.ts +153 -0
  380. package/src/__tests__/facade-factory.test.ts +271 -0
  381. package/src/__tests__/feature-flags.test.ts +138 -0
  382. package/src/__tests__/git-vault-sync.test.ts +230 -0
  383. package/src/__tests__/health-registry.test.ts +173 -0
  384. package/src/__tests__/knowledge-review.test.ts +104 -0
  385. package/src/__tests__/lsp-transport.test.ts +442 -0
  386. package/src/__tests__/migration-runner.test.ts +170 -0
  387. package/src/__tests__/normalize.test.ts +10 -0
  388. package/src/__tests__/obsidian-sync.test.ts +354 -0
  389. package/src/__tests__/pack-lockfile.test.ts +261 -0
  390. package/src/__tests__/pack-ops.test.ts +146 -0
  391. package/src/__tests__/pack-system.test.ts +423 -0
  392. package/src/__tests__/playbook-executor.test.ts +249 -0
  393. package/src/__tests__/playbook-ops-execution.test.ts +189 -0
  394. package/src/__tests__/plugin-ops.test.ts +411 -0
  395. package/src/__tests__/plugin-system.test.ts +509 -0
  396. package/src/__tests__/postgres-provider.test.ts +64 -6
  397. package/src/__tests__/replayable-stream.test.ts +112 -1
  398. package/src/__tests__/scope-detector.test.ts +121 -0
  399. package/src/__tests__/session-lifecycle.test.ts +259 -0
  400. package/src/__tests__/transport.test.ts +758 -0
  401. package/src/__tests__/vault-branching.test.ts +274 -0
  402. package/src/__tests__/vault-connect.test.ts +179 -0
  403. package/src/__tests__/vault-integrity.test.ts +71 -0
  404. package/src/__tests__/vault-manager.test.ts +238 -0
  405. package/src/__tests__/vault-scaling.test.ts +281 -0
  406. package/src/__tests__/vault-sharing.test.ts +270 -0
  407. package/src/__tests__/ws-transport.test.ts +479 -0
  408. package/src/agency/agency-manager.ts +326 -0
  409. package/src/agency/index.ts +13 -0
  410. package/src/agency/types.ts +88 -0
  411. package/src/brain/brain.ts +15 -11
  412. package/src/brain/intelligence.ts +103 -0
  413. package/src/brain/types.ts +26 -0
  414. package/src/chat/agent-loop-types.ts +99 -0
  415. package/src/chat/agent-loop.ts +357 -0
  416. package/src/chat/auth-manager.ts +171 -0
  417. package/src/chat/browser-session.ts +188 -0
  418. package/src/chat/cancellation.ts +99 -0
  419. package/src/chat/chat-session.ts +283 -0
  420. package/src/chat/file-handler.ts +230 -0
  421. package/src/chat/fragment-buffer.ts +160 -0
  422. package/src/chat/index.ts +72 -0
  423. package/src/chat/mcp-bridge.ts +135 -0
  424. package/src/chat/notifications.ts +164 -0
  425. package/src/chat/output-compressor.ts +116 -0
  426. package/src/chat/queue.ts +208 -0
  427. package/src/chat/response-chunker.ts +200 -0
  428. package/src/chat/self-update.ts +117 -0
  429. package/src/chat/types.ts +126 -0
  430. package/src/chat/voice.ts +134 -0
  431. package/src/claudemd/compose.ts +142 -0
  432. package/src/claudemd/index.ts +17 -0
  433. package/src/claudemd/inject.ts +170 -0
  434. package/src/claudemd/types.ts +45 -0
  435. package/src/context/context-engine.ts +302 -0
  436. package/src/context/index.ts +11 -0
  437. package/src/context/types.ts +69 -0
  438. package/src/enforcement/adapters/claude-code.ts +135 -0
  439. package/src/enforcement/adapters/index.ts +1 -0
  440. package/src/enforcement/index.ts +10 -0
  441. package/src/enforcement/registry.ts +82 -0
  442. package/src/enforcement/types.ts +56 -0
  443. package/src/facades/facade-factory.ts +138 -5
  444. package/src/facades/types.ts +21 -0
  445. package/src/health/health-registry.ts +165 -0
  446. package/src/health/index.ts +11 -0
  447. package/src/health/vault-integrity.ts +66 -0
  448. package/src/index.ts +294 -2
  449. package/src/intake/intake-pipeline.ts +1 -1
  450. package/src/intelligence/types.ts +1 -0
  451. package/src/migrations/index.ts +6 -0
  452. package/src/migrations/migration-runner.ts +185 -0
  453. package/src/packs/index.ts +20 -0
  454. package/src/packs/lockfile.ts +180 -0
  455. package/src/packs/pack-installer.ts +289 -0
  456. package/src/packs/resolver.ts +237 -0
  457. package/src/packs/types.ts +125 -0
  458. package/src/persistence/postgres-provider.ts +211 -58
  459. package/src/planning/gap-analysis.ts +52 -7
  460. package/src/playbooks/index.ts +11 -0
  461. package/src/playbooks/playbook-executor.ts +301 -0
  462. package/src/plugins/index.ts +19 -0
  463. package/src/plugins/plugin-loader.ts +183 -0
  464. package/src/plugins/plugin-registry.ts +187 -0
  465. package/src/plugins/types.ts +119 -0
  466. package/src/runtime/admin-extra-ops.ts +193 -8
  467. package/src/runtime/capture-ops.ts +113 -8
  468. package/src/runtime/deprecation.ts +58 -0
  469. package/src/runtime/facades/admin-facade.ts +16 -1
  470. package/src/runtime/facades/agency-facade.ts +111 -0
  471. package/src/runtime/facades/brain-facade.ts +60 -0
  472. package/src/runtime/facades/chat-facade.ts +918 -0
  473. package/src/runtime/facades/context-facade.ts +55 -0
  474. package/src/runtime/facades/index.ts +22 -1
  475. package/src/runtime/facades/vault-facade.ts +261 -1
  476. package/src/runtime/feature-flags.ts +101 -0
  477. package/src/runtime/pack-ops.ts +85 -0
  478. package/src/runtime/playbook-ops.ts +113 -9
  479. package/src/runtime/plugin-ops.ts +258 -0
  480. package/src/runtime/runtime.ts +84 -5
  481. package/src/runtime/telemetry-ops.ts +57 -0
  482. package/src/runtime/types.ts +35 -0
  483. package/src/runtime/vault-sharing-ops.ts +372 -0
  484. package/src/streams/index.ts +1 -1
  485. package/src/streams/replayable-stream.ts +34 -3
  486. package/src/text/similarity.ts +1 -1
  487. package/src/transport/http-server.ts +269 -0
  488. package/src/transport/index.ts +48 -0
  489. package/src/transport/lsp-server.ts +401 -0
  490. package/src/transport/rate-limiter.ts +97 -0
  491. package/src/transport/session-manager.ts +120 -0
  492. package/src/transport/token-auth.ts +96 -0
  493. package/src/transport/types.ts +66 -0
  494. package/src/transport/ws-server.ts +415 -0
  495. package/src/vault/git-vault-sync.ts +318 -0
  496. package/src/vault/knowledge-review.ts +221 -0
  497. package/src/vault/obsidian-sync.ts +346 -0
  498. package/src/vault/scope-detector.ts +219 -0
  499. package/src/vault/vault-branching.ts +264 -0
  500. package/src/vault/vault-manager.ts +237 -0
  501. package/src/vault/vault-types.ts +50 -0
  502. package/src/vault/vault.ts +41 -3
  503. package/src/governance/index.ts +0 -18
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Agent Loop Types — interfaces for the conversational agent execution engine.
3
+ *
4
+ * Transport-agnostic: the agent loop doesn't know about Telegram, Discord, etc.
5
+ * It takes messages and tools, calls the LLM, dispatches tool calls, and returns.
6
+ */
7
+
8
+ import type { ChatMessage } from './types.js';
9
+
10
+ // ─── Tool Types ──────────────────────────────────────────────────────
11
+
12
+ export interface AgentTool {
13
+ /** Tool name (unique identifier). */
14
+ name: string;
15
+ /** Human-readable description for the LLM. */
16
+ description: string;
17
+ /** JSON Schema for tool input parameters. */
18
+ inputSchema: Record<string, unknown>;
19
+ }
20
+
21
+ export interface ToolResult {
22
+ /** Output text (may be truncated). */
23
+ output: string;
24
+ /** Whether the tool execution errored. */
25
+ isError: boolean;
26
+ /** Optional base64-encoded image (e.g. screenshots). */
27
+ image?: { base64: string; mimeType: string };
28
+ }
29
+
30
+ export type ToolExecutor = (
31
+ toolName: string,
32
+ input: Record<string, unknown>,
33
+ ) => Promise<ToolResult>;
34
+
35
+ // ─── Agent Loop Config ──────────────────────────────────────────────
36
+
37
+ export interface AgentLoopConfig {
38
+ /** Anthropic API key. */
39
+ apiKey: string;
40
+ /** Model to use. Default: 'claude-sonnet-4-20250514'. */
41
+ model?: string;
42
+ /** System prompt. */
43
+ systemPrompt: string;
44
+ /** Available tools. */
45
+ tools: AgentTool[];
46
+ /** Tool executor function — routes tool calls to handlers. */
47
+ executor: ToolExecutor;
48
+ /** Max iterations (LLM calls) before stopping. Default: 200. */
49
+ maxIterations?: number;
50
+ /** Max output tokens per LLM call. Default: 16384. */
51
+ maxTokens?: number;
52
+ /** Optional AbortSignal for cancellation. */
53
+ signal?: AbortSignal;
54
+ /** Base URL for Anthropic API. Default: 'https://api.anthropic.com'. */
55
+ baseUrl?: string;
56
+ }
57
+
58
+ // ─── Callbacks ──────────────────────────────────────────────────────
59
+
60
+ export interface AgentCallbacks {
61
+ /** Called when the LLM invokes a tool. */
62
+ onToolUse?: (toolName: string, input: Record<string, unknown>) => void;
63
+ /** Called when a tool returns a result. */
64
+ onToolResult?: (toolName: string, result: ToolResult, durationMs: number) => void;
65
+ /** Called on each iteration (LLM call). */
66
+ onIteration?: (iteration: number) => void;
67
+ /** Called when an error occurs (non-fatal — loop may continue). */
68
+ onError?: (error: Error, context: string) => void;
69
+ }
70
+
71
+ // ─── Agent Loop Result ──────────────────────────────────────────────
72
+
73
+ export interface AgentLoopResult {
74
+ /** Final text response (concatenated from all text blocks). */
75
+ text: string;
76
+ /** Number of LLM iterations. */
77
+ iterations: number;
78
+ /** Total tool calls made. */
79
+ toolCalls: number;
80
+ /** New messages to append to session (assistant + tool_result pairs). */
81
+ newMessages: ChatMessage[];
82
+ /** Stop reason: 'end_turn', 'max_iterations', 'cancelled'. */
83
+ stopReason: 'end_turn' | 'max_iterations' | 'cancelled';
84
+ /** Usage stats. */
85
+ usage: { inputTokens: number; outputTokens: number };
86
+ }
87
+
88
+ // ─── MCP Bridge Types ───────────────────────────────────────────────
89
+
90
+ export interface McpToolRegistration {
91
+ name: string;
92
+ description: string;
93
+ inputSchema: Record<string, unknown>;
94
+ handler: (input: Record<string, unknown>) => Promise<unknown>;
95
+ }
96
+
97
+ // ─── Output Compressor ──────────────────────────────────────────────
98
+
99
+ export type OutputCompressor = (toolName: string, output: string, maxLength?: number) => string;
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Agent Loop — conversational agent execution engine.
3
+ *
4
+ * Pure computation: takes messages + tools, calls LLM, dispatches tool calls, returns result.
5
+ * Transport-specific concerns (typing indicators, progress messages) live in callbacks.
6
+ *
7
+ * Ported from Salvador's agent-loop.ts with improvements:
8
+ * - Transport-agnostic (no Grammy dependency)
9
+ * - AbortSignal support for clean cancellation
10
+ * - Pure HTTP (no Anthropic SDK dependency — zero deps)
11
+ * - Callbacks for all lifecycle events
12
+ */
13
+
14
+ import type { AgentLoopConfig, AgentLoopResult, AgentCallbacks } from './agent-loop-types.js';
15
+ import type { ChatMessage } from './types.js';
16
+
17
+ const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
18
+ const DEFAULT_MAX_ITERATIONS = 200;
19
+ const DEFAULT_MAX_TOKENS = 16384;
20
+ const DEFAULT_BASE_URL = 'https://api.anthropic.com';
21
+ const API_VERSION = '2023-06-01';
22
+
23
+ // ─── Public API ──────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Run the agent loop — iteratively call LLM and dispatch tools until done.
27
+ */
28
+ export async function runAgentLoop(
29
+ messages: ChatMessage[],
30
+ config: AgentLoopConfig,
31
+ callbacks?: AgentCallbacks,
32
+ ): Promise<AgentLoopResult> {
33
+ const model = config.model ?? DEFAULT_MODEL;
34
+ const maxIterations = config.maxIterations ?? DEFAULT_MAX_ITERATIONS;
35
+ const maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
36
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
37
+
38
+ // Convert ChatMessages to Anthropic format
39
+ const apiMessages = sanitizeMessages(messages);
40
+ const newMessages: ChatMessage[] = [];
41
+
42
+ let iterations = 0;
43
+ let toolCalls = 0;
44
+ let totalInputTokens = 0;
45
+ let totalOutputTokens = 0;
46
+ const textParts: string[] = [];
47
+
48
+ while (iterations < maxIterations) {
49
+ // Check for cancellation
50
+ if (config.signal?.aborted) {
51
+ return {
52
+ text: textParts.join('\n'),
53
+ iterations,
54
+ toolCalls,
55
+ newMessages,
56
+ stopReason: 'cancelled',
57
+ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
58
+ };
59
+ }
60
+
61
+ iterations++;
62
+ callbacks?.onIteration?.(iterations);
63
+
64
+ // Call the Anthropic API
65
+ let response: AnthropicResponse;
66
+ try {
67
+ response = await callAnthropic({
68
+ baseUrl,
69
+ apiKey: config.apiKey,
70
+ model,
71
+ systemPrompt: config.systemPrompt,
72
+ messages: apiMessages,
73
+ tools: config.tools.map((t) => ({
74
+ name: t.name,
75
+ description: t.description,
76
+ input_schema: t.inputSchema,
77
+ })),
78
+ maxTokens,
79
+ signal: config.signal,
80
+ });
81
+ } catch (error) {
82
+ if (config.signal?.aborted) {
83
+ return {
84
+ text: textParts.join('\n'),
85
+ iterations,
86
+ toolCalls,
87
+ newMessages,
88
+ stopReason: 'cancelled',
89
+ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
90
+ };
91
+ }
92
+ callbacks?.onError?.(error instanceof Error ? error : new Error(String(error)), 'api_call');
93
+ throw error;
94
+ }
95
+
96
+ totalInputTokens += response.usage?.input_tokens ?? 0;
97
+ totalOutputTokens += response.usage?.output_tokens ?? 0;
98
+
99
+ // Process content blocks
100
+ const assistantContent = response.content;
101
+ const assistantBlocks: unknown[] = [];
102
+ const toolUseBlocks: ToolUseBlock[] = [];
103
+
104
+ for (const block of assistantContent) {
105
+ assistantBlocks.push(block);
106
+
107
+ if (block.type === 'text') {
108
+ textParts.push(block.text);
109
+ } else if (block.type === 'tool_use') {
110
+ toolUseBlocks.push(block);
111
+ }
112
+ }
113
+
114
+ // Record assistant message
115
+ const assistantMsg: ChatMessage = {
116
+ role: 'assistant',
117
+ content: assistantContent
118
+ .filter((b): b is TextBlock => b.type === 'text')
119
+ .map((b) => b.text)
120
+ .join('\n'),
121
+ blocks: assistantBlocks,
122
+ timestamp: Date.now(),
123
+ };
124
+ apiMessages.push({ role: 'assistant', content: assistantContent });
125
+ newMessages.push(assistantMsg);
126
+
127
+ // If no tool calls, we're done
128
+ if (toolUseBlocks.length === 0 || response.stop_reason === 'end_turn') {
129
+ if (toolUseBlocks.length === 0) {
130
+ return {
131
+ text: textParts.join('\n'),
132
+ iterations,
133
+ toolCalls,
134
+ newMessages,
135
+ stopReason: 'end_turn',
136
+ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
137
+ };
138
+ }
139
+ }
140
+
141
+ // Execute tool calls
142
+ const toolResults: ApiToolResult[] = [];
143
+ for (const toolUse of toolUseBlocks) {
144
+ if (config.signal?.aborted) {
145
+ return {
146
+ text: textParts.join('\n'),
147
+ iterations,
148
+ toolCalls,
149
+ newMessages,
150
+ stopReason: 'cancelled',
151
+ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
152
+ };
153
+ }
154
+
155
+ toolCalls++;
156
+ callbacks?.onToolUse?.(toolUse.name, toolUse.input as Record<string, unknown>);
157
+
158
+ const startTime = Date.now();
159
+ let result;
160
+ try {
161
+ result = await config.executor(toolUse.name, toolUse.input as Record<string, unknown>);
162
+ } catch (error) {
163
+ result = {
164
+ output: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`,
165
+ isError: true,
166
+ };
167
+ callbacks?.onError?.(
168
+ error instanceof Error ? error : new Error(String(error)),
169
+ `tool:${toolUse.name}`,
170
+ );
171
+ }
172
+ const durationMs = Date.now() - startTime;
173
+ callbacks?.onToolResult?.(toolUse.name, result, durationMs);
174
+
175
+ // Build tool_result content
176
+ const resultContent: ApiContent[] = [{ type: 'text', text: result.output }];
177
+ if (result.image) {
178
+ resultContent.push({
179
+ type: 'image',
180
+ source: {
181
+ type: 'base64',
182
+ media_type: result.image.mimeType,
183
+ data: result.image.base64,
184
+ },
185
+ } as ApiContent);
186
+ }
187
+
188
+ toolResults.push({
189
+ type: 'tool_result',
190
+ tool_use_id: toolUse.id,
191
+ content: resultContent,
192
+ is_error: result.isError,
193
+ });
194
+ }
195
+
196
+ // Append tool results
197
+ apiMessages.push({ role: 'user', content: toolResults as unknown as ApiContent[] });
198
+ for (const tr of toolResults) {
199
+ newMessages.push({
200
+ role: 'tool',
201
+ content:
202
+ tr.content
203
+ .filter((c): c is TextBlock => (c as TextBlock).type === 'text')
204
+ .map((c) => (c as TextBlock).text)
205
+ .join('\n') || '',
206
+ blocks: [tr],
207
+ timestamp: Date.now(),
208
+ });
209
+ }
210
+ }
211
+
212
+ // Max iterations reached
213
+ return {
214
+ text: textParts.join('\n'),
215
+ iterations,
216
+ toolCalls,
217
+ newMessages,
218
+ stopReason: 'max_iterations',
219
+ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
220
+ };
221
+ }
222
+
223
+ // ─── Message Sanitization ───────────────────────────────────────────
224
+
225
+ /**
226
+ * Sanitize messages for the Anthropic API:
227
+ * - Ensure first message is from 'user'
228
+ * - Remove orphaned tool_result blocks
229
+ */
230
+ function sanitizeMessages(messages: ChatMessage[]): ApiMessage[] {
231
+ const apiMessages: ApiMessage[] = [];
232
+
233
+ for (const msg of messages) {
234
+ if (msg.role === 'tool') {
235
+ // Tool results are embedded in user messages — skip standalone ones
236
+ // They'll be re-created during the loop
237
+ continue;
238
+ }
239
+
240
+ if (msg.blocks && msg.blocks.length > 0) {
241
+ apiMessages.push({
242
+ role: msg.role === 'assistant' ? 'assistant' : 'user',
243
+ content: msg.blocks as ApiContent[],
244
+ });
245
+ } else {
246
+ apiMessages.push({
247
+ role: msg.role === 'assistant' ? 'assistant' : 'user',
248
+ content: msg.content,
249
+ });
250
+ }
251
+ }
252
+
253
+ // Ensure first message is from user
254
+ while (apiMessages.length > 0 && apiMessages[0].role !== 'user') {
255
+ apiMessages.shift();
256
+ }
257
+
258
+ return apiMessages;
259
+ }
260
+
261
+ // ─── Anthropic API Call ─────────────────────────────────────────────
262
+
263
+ interface AnthropicCallParams {
264
+ baseUrl: string;
265
+ apiKey: string;
266
+ model: string;
267
+ systemPrompt: string;
268
+ messages: ApiMessage[];
269
+ tools: ApiTool[];
270
+ maxTokens: number;
271
+ signal?: AbortSignal;
272
+ }
273
+
274
+ async function callAnthropic(params: AnthropicCallParams): Promise<AnthropicResponse> {
275
+ const url = `${params.baseUrl}/v1/messages`;
276
+
277
+ const body = {
278
+ model: params.model,
279
+ max_tokens: params.maxTokens,
280
+ system: params.systemPrompt,
281
+ messages: params.messages,
282
+ ...(params.tools.length > 0 ? { tools: params.tools } : {}),
283
+ };
284
+
285
+ const response = await fetch(url, {
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ 'x-api-key': params.apiKey,
290
+ 'anthropic-version': API_VERSION,
291
+ },
292
+ body: JSON.stringify(body),
293
+ signal: params.signal,
294
+ });
295
+
296
+ if (!response.ok) {
297
+ const errorText = await response.text().catch(() => 'Unknown error');
298
+ const error = new Error(`Anthropic API error ${response.status}: ${errorText}`);
299
+ (error as ApiError).status = response.status;
300
+ (error as ApiError).retryAfter = parseRetryAfter(response.headers);
301
+ throw error;
302
+ }
303
+
304
+ return (await response.json()) as AnthropicResponse;
305
+ }
306
+
307
+ function parseRetryAfter(headers: Headers): number | undefined {
308
+ const retryAfter = headers.get('retry-after');
309
+ if (!retryAfter) return undefined;
310
+ const seconds = Number(retryAfter);
311
+ return Number.isFinite(seconds) ? seconds * 1000 : undefined;
312
+ }
313
+
314
+ // ─── Anthropic API Types (minimal) ─────────────────────────────────
315
+
316
+ interface ApiError extends Error {
317
+ status: number;
318
+ retryAfter?: number;
319
+ }
320
+
321
+ interface TextBlock {
322
+ type: 'text';
323
+ text: string;
324
+ }
325
+
326
+ interface ToolUseBlock {
327
+ type: 'tool_use';
328
+ id: string;
329
+ name: string;
330
+ input: unknown;
331
+ }
332
+
333
+ type ApiContent = TextBlock | ToolUseBlock | Record<string, unknown>;
334
+
335
+ interface ApiMessage {
336
+ role: 'user' | 'assistant';
337
+ content: string | ApiContent[];
338
+ }
339
+
340
+ interface ApiTool {
341
+ name: string;
342
+ description: string;
343
+ input_schema: Record<string, unknown>;
344
+ }
345
+
346
+ interface ApiToolResult {
347
+ type: 'tool_result';
348
+ tool_use_id: string;
349
+ content: ApiContent[];
350
+ is_error: boolean;
351
+ }
352
+
353
+ interface AnthropicResponse {
354
+ content: (TextBlock | ToolUseBlock)[];
355
+ stop_reason: 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence';
356
+ usage?: { input_tokens: number; output_tokens: number };
357
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Chat Auth Manager — passphrase + allowlist authentication for chat transports.
3
+ *
4
+ * Ported from Salvador's bot.ts auth with improvements:
5
+ * - Configurable passphrase (not hardcoded "Hola Amigo!")
6
+ * - Rate limiting on failed attempts (lockout after N failures)
7
+ * - Allowlist support (optional user ID restriction)
8
+ * - Persistence to disk (survives restarts)
9
+ */
10
+
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ import type { ChatAuthConfig, AuthRecord } from './types.js';
14
+
15
+ const DEFAULT_MAX_FAILED_ATTEMPTS = 5;
16
+ const DEFAULT_LOCKOUT_MS = 300_000; // 5 minutes
17
+
18
+ interface PersistedAuthState {
19
+ authenticated: AuthRecord[];
20
+ }
21
+
22
+ export class ChatAuthManager {
23
+ private config: Required<ChatAuthConfig>;
24
+ private authenticated = new Map<string, number>(); // userId → timestamp
25
+ private failedAttempts = new Map<string, { count: number; lastAttempt: number }>();
26
+
27
+ constructor(config: ChatAuthConfig) {
28
+ this.config = {
29
+ storagePath: config.storagePath,
30
+ passphrase: config.passphrase ?? '',
31
+ allowedUsers: config.allowedUsers ?? [],
32
+ maxFailedAttempts: config.maxFailedAttempts ?? DEFAULT_MAX_FAILED_ATTEMPTS,
33
+ lockoutMs: config.lockoutMs ?? DEFAULT_LOCKOUT_MS,
34
+ };
35
+
36
+ this.loadFromDisk();
37
+ }
38
+
39
+ /**
40
+ * Whether authentication is enabled (passphrase is set).
41
+ */
42
+ get enabled(): boolean {
43
+ return this.config.passphrase.length > 0;
44
+ }
45
+
46
+ /**
47
+ * Check if a user is authenticated.
48
+ */
49
+ isAuthenticated(userId: string | number): boolean {
50
+ if (!this.enabled) return true;
51
+ const key = String(userId);
52
+
53
+ // Check allowlist first (if configured)
54
+ if (this.config.allowedUsers.length > 0) {
55
+ const allowed = this.config.allowedUsers.some((u) => String(u) === key);
56
+ if (!allowed) return false;
57
+ }
58
+
59
+ return this.authenticated.has(key);
60
+ }
61
+
62
+ /**
63
+ * Attempt authentication with a passphrase.
64
+ * Returns true if successful, false otherwise.
65
+ */
66
+ authenticate(userId: string | number, passphrase: string): boolean {
67
+ const key = String(userId);
68
+
69
+ // Check allowlist
70
+ if (this.config.allowedUsers.length > 0) {
71
+ const allowed = this.config.allowedUsers.some((u) => String(u) === key);
72
+ if (!allowed) return false;
73
+ }
74
+
75
+ // Check lockout
76
+ if (this.isLockedOut(key)) return false;
77
+
78
+ // Verify passphrase
79
+ if (passphrase === this.config.passphrase) {
80
+ this.authenticated.set(key, Date.now());
81
+ this.failedAttempts.delete(key);
82
+ this.persistToDisk();
83
+ return true;
84
+ }
85
+
86
+ // Record failed attempt
87
+ this.recordFailedAttempt(key);
88
+ return false;
89
+ }
90
+
91
+ /**
92
+ * Revoke authentication for a user.
93
+ */
94
+ revoke(userId: string | number): void {
95
+ this.authenticated.delete(String(userId));
96
+ this.persistToDisk();
97
+ }
98
+
99
+ /**
100
+ * Check if a user is temporarily locked out.
101
+ */
102
+ isLockedOut(userId: string | number): boolean {
103
+ const key = String(userId);
104
+ const record = this.failedAttempts.get(key);
105
+ if (!record) return false;
106
+
107
+ if (record.count >= this.config.maxFailedAttempts) {
108
+ const elapsed = Date.now() - record.lastAttempt;
109
+ if (elapsed < this.config.lockoutMs) return true;
110
+ // Lockout expired — reset
111
+ this.failedAttempts.delete(key);
112
+ }
113
+
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Number of authenticated users.
119
+ */
120
+ get authenticatedCount(): number {
121
+ return this.authenticated.size;
122
+ }
123
+
124
+ /**
125
+ * List authenticated user IDs.
126
+ */
127
+ listAuthenticated(): (string | number)[] {
128
+ return [...this.authenticated.keys()];
129
+ }
130
+
131
+ // ─── Private ───────────────────────────────────────────────────
132
+
133
+ private recordFailedAttempt(key: string): void {
134
+ const existing = this.failedAttempts.get(key);
135
+ if (existing) {
136
+ existing.count++;
137
+ existing.lastAttempt = Date.now();
138
+ } else {
139
+ this.failedAttempts.set(key, { count: 1, lastAttempt: Date.now() });
140
+ }
141
+ }
142
+
143
+ private persistToDisk(): void {
144
+ try {
145
+ mkdirSync(dirname(this.config.storagePath), { recursive: true });
146
+ const state: PersistedAuthState = {
147
+ authenticated: [...this.authenticated.entries()].map(([userId, ts]) => ({
148
+ userId,
149
+ authenticatedAt: ts,
150
+ })),
151
+ };
152
+ writeFileSync(this.config.storagePath, JSON.stringify(state), 'utf-8');
153
+ } catch {
154
+ // Persistence failure is non-critical
155
+ }
156
+ }
157
+
158
+ private loadFromDisk(): void {
159
+ if (!existsSync(this.config.storagePath)) return;
160
+
161
+ try {
162
+ const data = readFileSync(this.config.storagePath, 'utf-8');
163
+ const state = JSON.parse(data) as PersistedAuthState;
164
+ for (const record of state.authenticated) {
165
+ this.authenticated.set(String(record.userId), record.authenticatedAt);
166
+ }
167
+ } catch {
168
+ // Load failure is non-critical — start fresh
169
+ }
170
+ }
171
+ }