@soleri/core 2.10.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 (499) 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/playbooks/index.d.ts +2 -0
  202. package/dist/playbooks/index.d.ts.map +1 -1
  203. package/dist/playbooks/index.js +2 -0
  204. package/dist/playbooks/index.js.map +1 -1
  205. package/dist/playbooks/playbook-executor.d.ts +100 -0
  206. package/dist/playbooks/playbook-executor.d.ts.map +1 -0
  207. package/dist/playbooks/playbook-executor.js +207 -0
  208. package/dist/playbooks/playbook-executor.js.map +1 -0
  209. package/dist/plugins/index.d.ts +7 -0
  210. package/dist/plugins/index.d.ts.map +1 -0
  211. package/dist/plugins/index.js +7 -0
  212. package/dist/plugins/index.js.map +1 -0
  213. package/dist/plugins/plugin-loader.d.ts +28 -0
  214. package/dist/plugins/plugin-loader.d.ts.map +1 -0
  215. package/dist/plugins/plugin-loader.js +150 -0
  216. package/dist/plugins/plugin-loader.js.map +1 -0
  217. package/dist/plugins/plugin-registry.d.ts +58 -0
  218. package/dist/plugins/plugin-registry.d.ts.map +1 -0
  219. package/dist/plugins/plugin-registry.js +157 -0
  220. package/dist/plugins/plugin-registry.js.map +1 -0
  221. package/dist/plugins/types.d.ts +180 -0
  222. package/dist/plugins/types.d.ts.map +1 -0
  223. package/dist/plugins/types.js +48 -0
  224. package/dist/plugins/types.js.map +1 -0
  225. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  226. package/dist/runtime/admin-extra-ops.js +181 -8
  227. package/dist/runtime/admin-extra-ops.js.map +1 -1
  228. package/dist/runtime/capture-ops.d.ts.map +1 -1
  229. package/dist/runtime/capture-ops.js +106 -7
  230. package/dist/runtime/capture-ops.js.map +1 -1
  231. package/dist/runtime/deprecation.d.ts +33 -0
  232. package/dist/runtime/deprecation.d.ts.map +1 -0
  233. package/dist/runtime/deprecation.js +41 -0
  234. package/dist/runtime/deprecation.js.map +1 -0
  235. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  236. package/dist/runtime/facades/admin-facade.js +12 -1
  237. package/dist/runtime/facades/admin-facade.js.map +1 -1
  238. package/dist/runtime/facades/agency-facade.d.ts +7 -0
  239. package/dist/runtime/facades/agency-facade.d.ts.map +1 -0
  240. package/dist/runtime/facades/agency-facade.js +103 -0
  241. package/dist/runtime/facades/agency-facade.js.map +1 -0
  242. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  243. package/dist/runtime/facades/brain-facade.js +58 -0
  244. package/dist/runtime/facades/brain-facade.js.map +1 -1
  245. package/dist/runtime/facades/chat-facade.d.ts +7 -0
  246. package/dist/runtime/facades/chat-facade.d.ts.map +1 -0
  247. package/dist/runtime/facades/chat-facade.js +808 -0
  248. package/dist/runtime/facades/chat-facade.js.map +1 -0
  249. package/dist/runtime/facades/context-facade.d.ts +7 -0
  250. package/dist/runtime/facades/context-facade.d.ts.map +1 -0
  251. package/dist/runtime/facades/context-facade.js +45 -0
  252. package/dist/runtime/facades/context-facade.js.map +1 -0
  253. package/dist/runtime/facades/index.d.ts.map +1 -1
  254. package/dist/runtime/facades/index.js +18 -0
  255. package/dist/runtime/facades/index.js.map +1 -1
  256. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  257. package/dist/runtime/facades/vault-facade.js +247 -1
  258. package/dist/runtime/facades/vault-facade.js.map +1 -1
  259. package/dist/runtime/feature-flags.d.ts +18 -0
  260. package/dist/runtime/feature-flags.d.ts.map +1 -0
  261. package/dist/runtime/feature-flags.js +90 -0
  262. package/dist/runtime/feature-flags.js.map +1 -0
  263. package/dist/runtime/pack-ops.d.ts +9 -0
  264. package/dist/runtime/pack-ops.d.ts.map +1 -0
  265. package/dist/runtime/pack-ops.js +76 -0
  266. package/dist/runtime/pack-ops.js.map +1 -0
  267. package/dist/runtime/playbook-ops.d.ts +3 -7
  268. package/dist/runtime/playbook-ops.d.ts.map +1 -1
  269. package/dist/runtime/playbook-ops.js +101 -10
  270. package/dist/runtime/playbook-ops.js.map +1 -1
  271. package/dist/runtime/plugin-ops.d.ts +9 -0
  272. package/dist/runtime/plugin-ops.d.ts.map +1 -0
  273. package/dist/runtime/plugin-ops.js +235 -0
  274. package/dist/runtime/plugin-ops.js.map +1 -0
  275. package/dist/runtime/runtime.d.ts.map +1 -1
  276. package/dist/runtime/runtime.js +72 -5
  277. package/dist/runtime/runtime.js.map +1 -1
  278. package/dist/runtime/telemetry-ops.d.ts +10 -0
  279. package/dist/runtime/telemetry-ops.d.ts.map +1 -0
  280. package/dist/runtime/telemetry-ops.js +53 -0
  281. package/dist/runtime/telemetry-ops.js.map +1 -0
  282. package/dist/runtime/types.d.ts +35 -0
  283. package/dist/runtime/types.d.ts.map +1 -1
  284. package/dist/runtime/vault-sharing-ops.d.ts +13 -0
  285. package/dist/runtime/vault-sharing-ops.d.ts.map +1 -0
  286. package/dist/runtime/vault-sharing-ops.js +345 -0
  287. package/dist/runtime/vault-sharing-ops.js.map +1 -0
  288. package/dist/streams/index.d.ts +1 -1
  289. package/dist/streams/index.d.ts.map +1 -1
  290. package/dist/streams/index.js +1 -1
  291. package/dist/streams/index.js.map +1 -1
  292. package/dist/streams/replayable-stream.d.ts +13 -1
  293. package/dist/streams/replayable-stream.d.ts.map +1 -1
  294. package/dist/streams/replayable-stream.js +27 -3
  295. package/dist/streams/replayable-stream.js.map +1 -1
  296. package/dist/text/similarity.d.ts +0 -1
  297. package/dist/text/similarity.d.ts.map +1 -1
  298. package/dist/text/similarity.js +1 -1
  299. package/dist/text/similarity.js.map +1 -1
  300. package/dist/transport/http-server.d.ts +56 -0
  301. package/dist/transport/http-server.d.ts.map +1 -0
  302. package/dist/transport/http-server.js +210 -0
  303. package/dist/transport/http-server.js.map +1 -0
  304. package/dist/transport/index.d.ts +11 -0
  305. package/dist/transport/index.d.ts.map +1 -0
  306. package/dist/transport/index.js +10 -0
  307. package/dist/transport/index.js.map +1 -0
  308. package/dist/transport/lsp-server.d.ts +140 -0
  309. package/dist/transport/lsp-server.d.ts.map +1 -0
  310. package/dist/transport/lsp-server.js +239 -0
  311. package/dist/transport/lsp-server.js.map +1 -0
  312. package/dist/transport/rate-limiter.d.ts +35 -0
  313. package/dist/transport/rate-limiter.d.ts.map +1 -0
  314. package/dist/transport/rate-limiter.js +72 -0
  315. package/dist/transport/rate-limiter.js.map +1 -0
  316. package/dist/transport/session-manager.d.ts +49 -0
  317. package/dist/transport/session-manager.d.ts.map +1 -0
  318. package/dist/transport/session-manager.js +83 -0
  319. package/dist/transport/session-manager.js.map +1 -0
  320. package/dist/transport/token-auth.d.ts +29 -0
  321. package/dist/transport/token-auth.d.ts.map +1 -0
  322. package/dist/transport/token-auth.js +84 -0
  323. package/dist/transport/token-auth.js.map +1 -0
  324. package/dist/transport/types.d.ts +61 -0
  325. package/dist/transport/types.d.ts.map +1 -0
  326. package/dist/transport/types.js +5 -0
  327. package/dist/transport/types.js.map +1 -0
  328. package/dist/transport/ws-server.d.ts +78 -0
  329. package/dist/transport/ws-server.d.ts.map +1 -0
  330. package/dist/transport/ws-server.js +342 -0
  331. package/dist/transport/ws-server.js.map +1 -0
  332. package/dist/vault/git-vault-sync.d.ts +107 -0
  333. package/dist/vault/git-vault-sync.d.ts.map +1 -0
  334. package/dist/vault/git-vault-sync.js +251 -0
  335. package/dist/vault/git-vault-sync.js.map +1 -0
  336. package/dist/vault/knowledge-review.d.ts +67 -0
  337. package/dist/vault/knowledge-review.d.ts.map +1 -0
  338. package/dist/vault/knowledge-review.js +133 -0
  339. package/dist/vault/knowledge-review.js.map +1 -0
  340. package/dist/vault/obsidian-sync.d.ts +94 -0
  341. package/dist/vault/obsidian-sync.d.ts.map +1 -0
  342. package/dist/vault/obsidian-sync.js +247 -0
  343. package/dist/vault/obsidian-sync.js.map +1 -0
  344. package/dist/vault/scope-detector.d.ts +31 -0
  345. package/dist/vault/scope-detector.d.ts.map +1 -0
  346. package/dist/vault/scope-detector.js +182 -0
  347. package/dist/vault/scope-detector.js.map +1 -0
  348. package/dist/vault/vault-branching.d.ts +71 -0
  349. package/dist/vault/vault-branching.d.ts.map +1 -0
  350. package/dist/vault/vault-branching.js +180 -0
  351. package/dist/vault/vault-branching.js.map +1 -0
  352. package/dist/vault/vault-manager.d.ts +89 -0
  353. package/dist/vault/vault-manager.d.ts.map +1 -0
  354. package/dist/vault/vault-manager.js +199 -0
  355. package/dist/vault/vault-manager.js.map +1 -0
  356. package/dist/vault/vault-types.d.ts +30 -0
  357. package/dist/vault/vault-types.d.ts.map +1 -0
  358. package/dist/vault/vault-types.js +10 -0
  359. package/dist/vault/vault-types.js.map +1 -0
  360. package/dist/vault/vault.d.ts +10 -0
  361. package/dist/vault/vault.d.ts.map +1 -1
  362. package/dist/vault/vault.js +36 -3
  363. package/dist/vault/vault.js.map +1 -1
  364. package/package.json +1 -1
  365. package/src/__tests__/admin-extra-ops.test.ts +31 -11
  366. package/src/__tests__/agency-manager.test.ts +374 -0
  367. package/src/__tests__/agent-loop.test.ts +256 -0
  368. package/src/__tests__/capture-ops.test.ts +275 -0
  369. package/src/__tests__/chat-differentiators.test.ts +251 -0
  370. package/src/__tests__/chat-enhanced.test.ts +390 -0
  371. package/src/__tests__/chat-transport.test.ts +665 -0
  372. package/src/__tests__/claudemd.test.ts +282 -0
  373. package/src/__tests__/context-engine.test.ts +256 -0
  374. package/src/__tests__/core-ops.test.ts +97 -5
  375. package/src/__tests__/deprecation.test.ts +78 -0
  376. package/src/__tests__/enforcement.test.ts +153 -0
  377. package/src/__tests__/facade-factory.test.ts +271 -0
  378. package/src/__tests__/feature-flags.test.ts +138 -0
  379. package/src/__tests__/git-vault-sync.test.ts +230 -0
  380. package/src/__tests__/health-registry.test.ts +173 -0
  381. package/src/__tests__/knowledge-review.test.ts +104 -0
  382. package/src/__tests__/lsp-transport.test.ts +442 -0
  383. package/src/__tests__/migration-runner.test.ts +170 -0
  384. package/src/__tests__/normalize.test.ts +10 -0
  385. package/src/__tests__/obsidian-sync.test.ts +354 -0
  386. package/src/__tests__/pack-lockfile.test.ts +261 -0
  387. package/src/__tests__/pack-ops.test.ts +146 -0
  388. package/src/__tests__/pack-system.test.ts +423 -0
  389. package/src/__tests__/playbook-executor.test.ts +249 -0
  390. package/src/__tests__/playbook-ops-execution.test.ts +189 -0
  391. package/src/__tests__/plugin-ops.test.ts +411 -0
  392. package/src/__tests__/plugin-system.test.ts +509 -0
  393. package/src/__tests__/postgres-provider.test.ts +64 -6
  394. package/src/__tests__/replayable-stream.test.ts +112 -1
  395. package/src/__tests__/scope-detector.test.ts +121 -0
  396. package/src/__tests__/session-lifecycle.test.ts +259 -0
  397. package/src/__tests__/transport.test.ts +758 -0
  398. package/src/__tests__/vault-branching.test.ts +274 -0
  399. package/src/__tests__/vault-connect.test.ts +179 -0
  400. package/src/__tests__/vault-integrity.test.ts +71 -0
  401. package/src/__tests__/vault-manager.test.ts +238 -0
  402. package/src/__tests__/vault-scaling.test.ts +281 -0
  403. package/src/__tests__/vault-sharing.test.ts +270 -0
  404. package/src/__tests__/ws-transport.test.ts +479 -0
  405. package/src/agency/agency-manager.ts +326 -0
  406. package/src/agency/index.ts +13 -0
  407. package/src/agency/types.ts +88 -0
  408. package/src/brain/brain.ts +15 -11
  409. package/src/brain/intelligence.ts +103 -0
  410. package/src/brain/types.ts +26 -0
  411. package/src/chat/agent-loop-types.ts +99 -0
  412. package/src/chat/agent-loop.ts +357 -0
  413. package/src/chat/auth-manager.ts +171 -0
  414. package/src/chat/browser-session.ts +188 -0
  415. package/src/chat/cancellation.ts +99 -0
  416. package/src/chat/chat-session.ts +283 -0
  417. package/src/chat/file-handler.ts +230 -0
  418. package/src/chat/fragment-buffer.ts +160 -0
  419. package/src/chat/index.ts +72 -0
  420. package/src/chat/mcp-bridge.ts +135 -0
  421. package/src/chat/notifications.ts +164 -0
  422. package/src/chat/output-compressor.ts +116 -0
  423. package/src/chat/queue.ts +208 -0
  424. package/src/chat/response-chunker.ts +200 -0
  425. package/src/chat/self-update.ts +117 -0
  426. package/src/chat/types.ts +126 -0
  427. package/src/chat/voice.ts +134 -0
  428. package/src/claudemd/compose.ts +142 -0
  429. package/src/claudemd/index.ts +17 -0
  430. package/src/claudemd/inject.ts +170 -0
  431. package/src/claudemd/types.ts +45 -0
  432. package/src/context/context-engine.ts +302 -0
  433. package/src/context/index.ts +11 -0
  434. package/src/context/types.ts +69 -0
  435. package/src/enforcement/adapters/claude-code.ts +135 -0
  436. package/src/enforcement/adapters/index.ts +1 -0
  437. package/src/enforcement/index.ts +10 -0
  438. package/src/enforcement/registry.ts +82 -0
  439. package/src/enforcement/types.ts +56 -0
  440. package/src/facades/facade-factory.ts +138 -5
  441. package/src/facades/types.ts +21 -0
  442. package/src/health/health-registry.ts +165 -0
  443. package/src/health/index.ts +11 -0
  444. package/src/health/vault-integrity.ts +66 -0
  445. package/src/index.ts +294 -2
  446. package/src/intake/intake-pipeline.ts +1 -1
  447. package/src/intelligence/types.ts +1 -0
  448. package/src/migrations/index.ts +6 -0
  449. package/src/migrations/migration-runner.ts +185 -0
  450. package/src/packs/index.ts +20 -0
  451. package/src/packs/lockfile.ts +180 -0
  452. package/src/packs/pack-installer.ts +289 -0
  453. package/src/packs/resolver.ts +237 -0
  454. package/src/packs/types.ts +125 -0
  455. package/src/persistence/postgres-provider.ts +211 -58
  456. package/src/playbooks/index.ts +11 -0
  457. package/src/playbooks/playbook-executor.ts +301 -0
  458. package/src/plugins/index.ts +19 -0
  459. package/src/plugins/plugin-loader.ts +183 -0
  460. package/src/plugins/plugin-registry.ts +187 -0
  461. package/src/plugins/types.ts +119 -0
  462. package/src/runtime/admin-extra-ops.ts +193 -8
  463. package/src/runtime/capture-ops.ts +113 -8
  464. package/src/runtime/deprecation.ts +58 -0
  465. package/src/runtime/facades/admin-facade.ts +16 -1
  466. package/src/runtime/facades/agency-facade.ts +111 -0
  467. package/src/runtime/facades/brain-facade.ts +60 -0
  468. package/src/runtime/facades/chat-facade.ts +918 -0
  469. package/src/runtime/facades/context-facade.ts +55 -0
  470. package/src/runtime/facades/index.ts +22 -1
  471. package/src/runtime/facades/vault-facade.ts +261 -1
  472. package/src/runtime/feature-flags.ts +101 -0
  473. package/src/runtime/pack-ops.ts +85 -0
  474. package/src/runtime/playbook-ops.ts +113 -9
  475. package/src/runtime/plugin-ops.ts +258 -0
  476. package/src/runtime/runtime.ts +84 -5
  477. package/src/runtime/telemetry-ops.ts +57 -0
  478. package/src/runtime/types.ts +35 -0
  479. package/src/runtime/vault-sharing-ops.ts +372 -0
  480. package/src/streams/index.ts +1 -1
  481. package/src/streams/replayable-stream.ts +34 -3
  482. package/src/text/similarity.ts +1 -1
  483. package/src/transport/http-server.ts +269 -0
  484. package/src/transport/index.ts +48 -0
  485. package/src/transport/lsp-server.ts +401 -0
  486. package/src/transport/rate-limiter.ts +97 -0
  487. package/src/transport/session-manager.ts +120 -0
  488. package/src/transport/token-auth.ts +96 -0
  489. package/src/transport/types.ts +66 -0
  490. package/src/transport/ws-server.ts +415 -0
  491. package/src/vault/git-vault-sync.ts +318 -0
  492. package/src/vault/knowledge-review.ts +221 -0
  493. package/src/vault/obsidian-sync.ts +346 -0
  494. package/src/vault/scope-detector.ts +219 -0
  495. package/src/vault/vault-branching.ts +264 -0
  496. package/src/vault/vault-manager.ts +237 -0
  497. package/src/vault/vault-types.ts +50 -0
  498. package/src/vault/vault.ts +41 -3
  499. package/src/governance/index.ts +0 -18
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Token Authentication — bearer token generation, validation, and persistence.
3
+ *
4
+ * Uses constant-time comparison (crypto.timingSafeEqual) to prevent timing attacks.
5
+ * Token priority: {AGENT_ID}_HTTP_TOKEN env > ~/.{agentId}/http-token file.
6
+ */
7
+
8
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+ import type { IncomingMessage, ServerResponse } from 'node:http';
13
+
14
+ /** Generate a 32-byte hex token (64 characters). */
15
+ export function generateToken(): string {
16
+ return randomBytes(32).toString('hex');
17
+ }
18
+
19
+ /**
20
+ * Load token with priority: env var > file.
21
+ * The env var name is derived from the agentId: MY_AGENT_HTTP_TOKEN.
22
+ */
23
+ export function loadToken(agentId: string): string | undefined {
24
+ // Environment variable takes priority
25
+ const envKey = `${agentId.replace(/-/g, '_').toUpperCase()}_HTTP_TOKEN`;
26
+ const envToken = process.env[envKey];
27
+ if (envToken && envToken.trim().length > 0) {
28
+ return envToken.trim();
29
+ }
30
+
31
+ // Fall back to file
32
+ const tokenFile = join(homedir(), `.${agentId}`, 'http-token');
33
+ try {
34
+ if (existsSync(tokenFile)) {
35
+ const fileToken = readFileSync(tokenFile, 'utf-8').trim();
36
+ if (fileToken.length > 0) return fileToken;
37
+ }
38
+ } catch {
39
+ // File read failed
40
+ }
41
+
42
+ return undefined;
43
+ }
44
+
45
+ /** Save token to ~/.{agentId}/http-token with 0600 permissions. */
46
+ export function saveToken(agentId: string, token: string): void {
47
+ const dir = join(homedir(), `.${agentId}`);
48
+ mkdirSync(dir, { recursive: true });
49
+ writeFileSync(join(dir, 'http-token'), token, { mode: 0o600 });
50
+ }
51
+
52
+ /** Load existing token or generate and save a new one. */
53
+ export function getOrGenerateToken(agentId: string): string {
54
+ const existing = loadToken(agentId);
55
+ if (existing) return existing;
56
+ const token = generateToken();
57
+ saveToken(agentId, token);
58
+ return token;
59
+ }
60
+
61
+ /**
62
+ * Constant-time bearer token validation.
63
+ * Returns true if the Authorization header contains a valid Bearer token.
64
+ */
65
+ export function validateBearerToken(authHeader: string | undefined, expected: string): boolean {
66
+ if (!authHeader || !authHeader.startsWith('Bearer ')) return false;
67
+
68
+ const provided = authHeader.slice(7);
69
+ const expectedBuf = Buffer.from(expected, 'utf-8');
70
+ const providedBuf = Buffer.from(provided, 'utf-8');
71
+
72
+ if (expectedBuf.length !== providedBuf.length) {
73
+ // Compare against self to avoid timing leak on length mismatch
74
+ timingSafeEqual(expectedBuf, expectedBuf);
75
+ return false;
76
+ }
77
+
78
+ return timingSafeEqual(providedBuf, expectedBuf);
79
+ }
80
+
81
+ /**
82
+ * Validate an incoming HTTP request's bearer token.
83
+ * Returns true if valid, sends 401 and returns false if not.
84
+ */
85
+ export function authenticateRequest(
86
+ req: IncomingMessage,
87
+ res: ServerResponse,
88
+ expectedToken: string,
89
+ ): boolean {
90
+ const authHeader = req.headers.authorization;
91
+ if (validateBearerToken(authHeader, expectedToken)) return true;
92
+
93
+ res.writeHead(401, { 'Content-Type': 'application/json' });
94
+ res.end(JSON.stringify({ error: 'Unauthorized' }));
95
+ return false;
96
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Transport Types — configuration for HTTP/SSE transport layer.
3
+ */
4
+
5
+ /** Supported transport modes */
6
+ export type TransportMode = 'stdio' | 'http' | 'ws' | 'lsp';
7
+
8
+ /** HTTP transport configuration */
9
+ export interface HttpTransportConfig {
10
+ /** Port to listen on (default: 3100) */
11
+ port: number;
12
+ /** Host to bind to (default: 127.0.0.1) */
13
+ host: string;
14
+ /** CORS allowed origins (empty = no CORS headers) */
15
+ corsOrigins: string[];
16
+ /** Bearer token for authentication */
17
+ authToken: string;
18
+ /** Session TTL in milliseconds (default: 3600000 = 1 hour). 0 to disable. */
19
+ sessionTTL?: number;
20
+ /** Session reaper interval in milliseconds (default: 60000 = 1 minute) */
21
+ reaperInterval?: number;
22
+ /** Rate limit: max requests per window (default: 100) */
23
+ rateLimit?: number;
24
+ /** Rate limit window in milliseconds (default: 60000 = 1 minute) */
25
+ rateLimitWindow?: number;
26
+ }
27
+
28
+ /** WebSocket transport configuration */
29
+ export interface WsTransportConfig {
30
+ /** Bearer token for authentication (validated on upgrade) */
31
+ authToken: string;
32
+ /** Heartbeat interval in ms (default: 30000). 0 to disable. */
33
+ heartbeatInterval?: number;
34
+ /** Max message size in bytes (default: 1MB) */
35
+ maxMessageSize?: number;
36
+ /** Session TTL in milliseconds (default: 3600000 = 1 hour). 0 to disable. */
37
+ sessionTTL?: number;
38
+ /** Session reaper interval in milliseconds (default: 60000 = 1 minute) */
39
+ reaperInterval?: number;
40
+ }
41
+
42
+ /** LSP transport configuration */
43
+ export interface LspTransportConfig {
44
+ /** Agent capabilities to expose as LSP features */
45
+ capabilities?: LspCapabilities;
46
+ }
47
+
48
+ /** Which agent features to expose via LSP */
49
+ export interface LspCapabilities {
50
+ /** Expose vault search as completions (default: true) */
51
+ completions?: boolean;
52
+ /** Expose quality gate violations as diagnostics (default: true) */
53
+ diagnostics?: boolean;
54
+ /** Expose pattern documentation as hover (default: true) */
55
+ hover?: boolean;
56
+ /** Expose agent ops as code actions (default: false) */
57
+ codeActions?: boolean;
58
+ }
59
+
60
+ /** Combined transport configuration */
61
+ export interface TransportConfig {
62
+ mode: TransportMode;
63
+ http?: HttpTransportConfig;
64
+ ws?: WsTransportConfig;
65
+ lsp?: LspTransportConfig;
66
+ }
@@ -0,0 +1,415 @@
1
+ /**
2
+ * WebSocket MCP Server — RFC 6455 implementation using only node:http + node:crypto.
3
+ *
4
+ * Provides bidirectional JSON-RPC 2.0 communication over WebSocket.
5
+ * Can run standalone or share an HTTP server with HttpMcpServer via upgrade handler.
6
+ *
7
+ * Features:
8
+ * - RFC 6455 handshake (Sec-WebSocket-Key / Accept)
9
+ * - Text frame encode/decode (opcode 0x1) with masking support
10
+ * - Ping/pong heartbeat (configurable interval, default 30s)
11
+ * - Bearer token auth on upgrade request
12
+ * - Session management via shared SessionManager
13
+ * - Max message size enforcement (default 1MB)
14
+ * - Clean close handshake (opcode 0x8)
15
+ */
16
+
17
+ import { createHash } from 'node:crypto';
18
+ import { createServer, type Server, type IncomingMessage } from 'node:http';
19
+ import type { Duplex } from 'node:stream';
20
+ import type { WsTransportConfig } from './types.js';
21
+ import { validateBearerToken } from './token-auth.js';
22
+ import { SessionManager, type Session } from './session-manager.js';
23
+
24
+ // =============================================================================
25
+ // CONSTANTS
26
+ // =============================================================================
27
+
28
+ /** RFC 6455 magic GUID for Sec-WebSocket-Accept */
29
+ const WS_MAGIC = '258EAFA5-E914-47DA-95CA-5AB5A4085CE3';
30
+
31
+ const DEFAULT_HEARTBEAT_MS = 30_000;
32
+ const DEFAULT_MAX_MESSAGE_SIZE = 1_048_576; // 1 MB
33
+
34
+ // Frame opcodes
35
+ const OPCODE_TEXT = 0x01;
36
+ const OPCODE_CLOSE = 0x08;
37
+ const OPCODE_PING = 0x09;
38
+ const OPCODE_PONG = 0x0a;
39
+
40
+ // =============================================================================
41
+ // TYPES
42
+ // =============================================================================
43
+
44
+ export interface WsConnection {
45
+ /** Session associated with this connection */
46
+ session: Session;
47
+ /** The underlying TCP socket */
48
+ socket: Duplex;
49
+ /** Whether the connection is alive (for heartbeat) */
50
+ alive: boolean;
51
+ }
52
+
53
+ export interface WsServerCallbacks {
54
+ /** Called when a new WebSocket connection is established. Must create session. */
55
+ onConnect: (conn: WsConnection) => Promise<void>;
56
+ /** Called when a text message is received. */
57
+ onMessage: (conn: WsConnection, data: unknown) => Promise<void>;
58
+ /** Called when a connection is closed. */
59
+ onClose: (conn: WsConnection) => void;
60
+ /** Optional: called on errors. */
61
+ onError?: (conn: WsConnection, error: Error) => void;
62
+ }
63
+
64
+ export interface WsServerStats {
65
+ connections: number;
66
+ uptime: number;
67
+ }
68
+
69
+ // =============================================================================
70
+ // WEBSOCKET SERVER
71
+ // =============================================================================
72
+
73
+ export class WsMcpServer {
74
+ private server: Server | undefined;
75
+ private ownServer = false; // true if we created the server ourselves
76
+ private sessions: SessionManager;
77
+ private config: WsTransportConfig;
78
+ private callbacks: WsServerCallbacks;
79
+ private connections = new Map<string, WsConnection>();
80
+ private heartbeatTimer: ReturnType<typeof setInterval> | undefined;
81
+ private startedAt = 0;
82
+
83
+ constructor(config: WsTransportConfig, callbacks: WsServerCallbacks) {
84
+ this.config = config;
85
+ this.callbacks = callbacks;
86
+ this.sessions = new SessionManager({
87
+ ttl: config.sessionTTL,
88
+ reaperInterval: config.reaperInterval,
89
+ });
90
+ }
91
+
92
+ /** Get the session manager. */
93
+ get sessionManager(): SessionManager {
94
+ return this.sessions;
95
+ }
96
+
97
+ /**
98
+ * Attach to an existing HTTP server as an upgrade handler.
99
+ * Call this instead of start() when sharing a server with HttpMcpServer.
100
+ */
101
+ attachTo(server: Server): void {
102
+ this.startedAt = Date.now();
103
+ this.server = server;
104
+ this.ownServer = false;
105
+ server.on('upgrade', (req, socket, head) => {
106
+ this.handleUpgrade(req, socket, head);
107
+ });
108
+ this.sessions.startReaper();
109
+ this.startHeartbeat();
110
+ }
111
+
112
+ /** Start a standalone WebSocket server. */
113
+ async start(port: number, host = '127.0.0.1'): Promise<void> {
114
+ this.startedAt = Date.now();
115
+ this.server = createServer((_req, res) => {
116
+ res.writeHead(426, { 'Content-Type': 'text/plain' });
117
+ res.end('Upgrade Required');
118
+ });
119
+ this.ownServer = true;
120
+
121
+ this.server.on('upgrade', (req, socket, head) => {
122
+ this.handleUpgrade(req, socket, head);
123
+ });
124
+
125
+ await new Promise<void>((resolve) => {
126
+ this.server!.listen(port, host, () => resolve());
127
+ });
128
+
129
+ this.sessions.startReaper();
130
+ this.startHeartbeat();
131
+ }
132
+
133
+ /** Stop the server and close all connections. */
134
+ async stop(): Promise<void> {
135
+ this.stopHeartbeat();
136
+ this.sessions.close();
137
+
138
+ // Close all WebSocket connections
139
+ for (const conn of this.connections.values()) {
140
+ this.sendClose(conn.socket, 1001, 'Server shutting down');
141
+ conn.socket.destroy();
142
+ }
143
+ this.connections.clear();
144
+
145
+ if (this.ownServer && this.server) {
146
+ await new Promise<void>((resolve, reject) => {
147
+ this.server!.close((err) => (err ? reject(err) : resolve()));
148
+ });
149
+ this.server = undefined;
150
+ }
151
+ }
152
+
153
+ /** Get server stats. */
154
+ getStats(): WsServerStats {
155
+ return {
156
+ connections: this.connections.size,
157
+ uptime: this.startedAt > 0 ? Date.now() - this.startedAt : 0,
158
+ };
159
+ }
160
+
161
+ /** Send a text message to a specific connection. */
162
+ send(sessionId: string, data: unknown): boolean {
163
+ const conn = this.connections.get(sessionId);
164
+ if (!conn) return false;
165
+ this.sendText(conn.socket, JSON.stringify(data));
166
+ return true;
167
+ }
168
+
169
+ /** Broadcast a text message to all connections. */
170
+ broadcast(data: unknown): number {
171
+ const text = JSON.stringify(data);
172
+ let count = 0;
173
+ for (const conn of this.connections.values()) {
174
+ this.sendText(conn.socket, text);
175
+ count++;
176
+ }
177
+ return count;
178
+ }
179
+
180
+ // ─── Upgrade Handling ──────────────────────────────────────────────
181
+
182
+ private handleUpgrade(req: IncomingMessage, socket: Duplex, _head: Buffer): void {
183
+ // Validate WebSocket upgrade headers
184
+ const upgradeHeader = req.headers.upgrade;
185
+ if (!upgradeHeader || upgradeHeader.toLowerCase() !== 'websocket') {
186
+ socket.destroy();
187
+ return;
188
+ }
189
+
190
+ const wsKey = req.headers['sec-websocket-key'];
191
+ if (!wsKey) {
192
+ socket.destroy();
193
+ return;
194
+ }
195
+
196
+ // Auth: extract token from query string or Authorization header
197
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
198
+ const queryToken = url.searchParams.get('token');
199
+ const authHeader = req.headers.authorization;
200
+ const tokenValid = queryToken
201
+ ? queryToken === this.config.authToken
202
+ : validateBearerToken(authHeader, this.config.authToken);
203
+
204
+ if (!tokenValid) {
205
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
206
+ socket.destroy();
207
+ return;
208
+ }
209
+
210
+ // Complete WebSocket handshake
211
+ const acceptKey = createHash('sha1')
212
+ .update(wsKey + WS_MAGIC)
213
+ .digest('base64');
214
+
215
+ const sessionId = this.sessions.generateId();
216
+
217
+ socket.write(
218
+ 'HTTP/1.1 101 Switching Protocols\r\n' +
219
+ 'Upgrade: websocket\r\n' +
220
+ 'Connection: Upgrade\r\n' +
221
+ `Sec-WebSocket-Accept: ${acceptKey}\r\n` +
222
+ `mcp-session-id: ${sessionId}\r\n` +
223
+ '\r\n',
224
+ );
225
+
226
+ // Create session and connection
227
+ const session = this.sessions.add(sessionId, socket, null);
228
+ const conn: WsConnection = { session, socket, alive: true };
229
+ this.connections.set(sessionId, conn);
230
+
231
+ // Set up frame reader
232
+ const maxSize = this.config.maxMessageSize ?? DEFAULT_MAX_MESSAGE_SIZE;
233
+ let buffer = Buffer.alloc(0);
234
+
235
+ socket.on('data', (chunk: Buffer) => {
236
+ buffer = Buffer.concat([buffer, chunk]);
237
+
238
+ // Process all complete frames in the buffer
239
+ while (buffer.length >= 2) {
240
+ const frame = this.parseFrame(buffer);
241
+ if (!frame) break; // incomplete frame
242
+
243
+ buffer = buffer.subarray(frame.totalLength);
244
+
245
+ if (frame.opcode === OPCODE_TEXT) {
246
+ if (frame.payload.length > maxSize) {
247
+ this.sendClose(socket, 1009, 'Message too large');
248
+ socket.destroy();
249
+ return;
250
+ }
251
+ const text = frame.payload.toString('utf-8');
252
+ try {
253
+ const parsed: unknown = JSON.parse(text);
254
+ this.callbacks.onMessage(conn, parsed).catch((err) => {
255
+ this.callbacks.onError?.(conn, err instanceof Error ? err : new Error(String(err)));
256
+ });
257
+ } catch {
258
+ // Invalid JSON — ignore or notify
259
+ this.callbacks.onError?.(conn, new Error('Invalid JSON received'));
260
+ }
261
+ } else if (frame.opcode === OPCODE_PING) {
262
+ this.sendPong(socket, frame.payload);
263
+ } else if (frame.opcode === OPCODE_PONG) {
264
+ conn.alive = true;
265
+ } else if (frame.opcode === OPCODE_CLOSE) {
266
+ this.sendClose(socket, 1000, '');
267
+ socket.destroy();
268
+ }
269
+ }
270
+ });
271
+
272
+ // Cleanup on disconnect — guard against double-fire from close+end+error
273
+ let cleaned = false;
274
+ const cleanup = (err?: Error) => {
275
+ if (cleaned) return;
276
+ cleaned = true;
277
+ this.connections.delete(sessionId);
278
+ this.sessions.remove(sessionId);
279
+ if (err) this.callbacks.onError?.(conn, err);
280
+ this.callbacks.onClose(conn);
281
+ };
282
+
283
+ socket.on('close', () => cleanup());
284
+ socket.on('end', () => cleanup());
285
+ socket.on('error', (err) => cleanup(err));
286
+
287
+ // Notify callback
288
+ this.callbacks.onConnect(conn).catch((err) => {
289
+ this.callbacks.onError?.(conn, err instanceof Error ? err : new Error(String(err)));
290
+ });
291
+ }
292
+
293
+ // ─── Frame Parsing (RFC 6455) ──────────────────────────────────────
294
+
295
+ private parseFrame(buf: Buffer): { opcode: number; payload: Buffer; totalLength: number } | null {
296
+ if (buf.length < 2) return null;
297
+
298
+ const opcode = buf[0] & 0x0f;
299
+ const masked = (buf[1] & 0x80) !== 0;
300
+ let payloadLength = buf[1] & 0x7f;
301
+ let offset = 2;
302
+
303
+ if (payloadLength === 126) {
304
+ if (buf.length < 4) return null;
305
+ payloadLength = buf.readUInt16BE(2);
306
+ offset = 4;
307
+ } else if (payloadLength === 127) {
308
+ if (buf.length < 10) return null;
309
+ // Read as 64-bit, but JS numbers are limited to 2^53
310
+ payloadLength = Number(buf.readBigUInt64BE(2));
311
+ offset = 10;
312
+ }
313
+
314
+ const maskSize = masked ? 4 : 0;
315
+ const totalLength = offset + maskSize + payloadLength;
316
+ if (buf.length < totalLength) return null;
317
+
318
+ let payload: Buffer;
319
+ if (masked) {
320
+ const mask = buf.subarray(offset, offset + 4);
321
+ payload = Buffer.alloc(payloadLength);
322
+ for (let i = 0; i < payloadLength; i++) {
323
+ payload[i] = buf[offset + 4 + i] ^ mask[i & 3];
324
+ }
325
+ } else {
326
+ payload = buf.subarray(offset, offset + payloadLength);
327
+ }
328
+
329
+ return { opcode, payload, totalLength };
330
+ }
331
+
332
+ // ─── Frame Encoding ────────────────────────────────────────────────
333
+
334
+ private encodeFrame(opcode: number, payload: Buffer): Buffer {
335
+ const len = payload.length;
336
+ let header: Buffer;
337
+
338
+ if (len < 126) {
339
+ header = Buffer.alloc(2);
340
+ header[0] = 0x80 | opcode; // FIN + opcode
341
+ header[1] = len;
342
+ } else if (len < 65536) {
343
+ header = Buffer.alloc(4);
344
+ header[0] = 0x80 | opcode;
345
+ header[1] = 126;
346
+ header.writeUInt16BE(len, 2);
347
+ } else {
348
+ header = Buffer.alloc(10);
349
+ header[0] = 0x80 | opcode;
350
+ header[1] = 127;
351
+ header.writeBigUInt64BE(BigInt(len), 2);
352
+ }
353
+
354
+ return Buffer.concat([header, payload]);
355
+ }
356
+
357
+ private sendText(socket: Duplex, text: string): void {
358
+ const payload = Buffer.from(text, 'utf-8');
359
+ const frame = this.encodeFrame(OPCODE_TEXT, payload);
360
+ socket.write(frame);
361
+ }
362
+
363
+ private sendPong(socket: Duplex, payload: Buffer): void {
364
+ const frame = this.encodeFrame(OPCODE_PONG, payload);
365
+ socket.write(frame);
366
+ }
367
+
368
+ private sendClose(socket: Duplex, code: number, reason: string): void {
369
+ const reasonBuf = Buffer.from(reason, 'utf-8');
370
+ const payload = Buffer.alloc(2 + reasonBuf.length);
371
+ payload.writeUInt16BE(code, 0);
372
+ reasonBuf.copy(payload, 2);
373
+ const frame = this.encodeFrame(OPCODE_CLOSE, payload);
374
+ socket.write(frame);
375
+ }
376
+
377
+ // ─── Heartbeat ─────────────────────────────────────────────────────
378
+
379
+ private startHeartbeat(): void {
380
+ const interval = this.config.heartbeatInterval ?? DEFAULT_HEARTBEAT_MS;
381
+ if (interval <= 0) return;
382
+
383
+ this.heartbeatTimer = setInterval(() => {
384
+ for (const [id, conn] of this.connections) {
385
+ if (!conn.alive) {
386
+ // No pong received since last ping — dead connection
387
+ this.sendClose(conn.socket, 1001, 'Heartbeat timeout');
388
+ conn.socket.destroy();
389
+ this.connections.delete(id);
390
+ this.sessions.remove(id);
391
+ this.callbacks.onClose(conn);
392
+ continue;
393
+ }
394
+ conn.alive = false;
395
+ const ping = this.encodeFrame(OPCODE_PING, Buffer.alloc(0));
396
+ conn.socket.write(ping);
397
+ }
398
+ }, interval);
399
+
400
+ if (
401
+ this.heartbeatTimer &&
402
+ typeof this.heartbeatTimer === 'object' &&
403
+ 'unref' in this.heartbeatTimer
404
+ ) {
405
+ (this.heartbeatTimer as NodeJS.Timeout).unref();
406
+ }
407
+ }
408
+
409
+ private stopHeartbeat(): void {
410
+ if (this.heartbeatTimer) {
411
+ clearInterval(this.heartbeatTimer);
412
+ this.heartbeatTimer = undefined;
413
+ }
414
+ }
415
+ }