@vibesdotdev/session 0.0.0-stage → 1.0.0-rc.1

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 (1059) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +69 -2
  3. package/dist/backup/session.sessions.target.descriptor.d.ts +47 -0
  4. package/dist/backup/session.sessions.target.descriptor.d.ts.map +1 -0
  5. package/dist/backup/session.sessions.target.descriptor.js +41 -0
  6. package/dist/backup/session.sessions.target.descriptor.js.map +1 -0
  7. package/dist/cli/chat/session.chat.descriptor.d.ts +4 -0
  8. package/dist/cli/chat/session.chat.descriptor.d.ts.map +1 -0
  9. package/dist/cli/chat/session.chat.descriptor.js +19 -0
  10. package/dist/cli/chat/session.chat.descriptor.js.map +1 -0
  11. package/dist/cli/chat/session.chat.impl.d.ts +6 -0
  12. package/dist/cli/chat/session.chat.impl.d.ts.map +1 -0
  13. package/dist/cli/chat/session.chat.impl.js +37 -0
  14. package/dist/cli/chat/session.chat.impl.js.map +1 -0
  15. package/dist/cli/chat/session.jobs.descriptor.d.ts +4 -0
  16. package/dist/cli/chat/session.jobs.descriptor.d.ts.map +1 -0
  17. package/dist/cli/chat/session.jobs.descriptor.js +17 -0
  18. package/dist/cli/chat/session.jobs.descriptor.js.map +1 -0
  19. package/dist/cli/chat/session.jobs.impl.d.ts +6 -0
  20. package/dist/cli/chat/session.jobs.impl.d.ts.map +1 -0
  21. package/dist/cli/chat/session.jobs.impl.js +29 -0
  22. package/dist/cli/chat/session.jobs.impl.js.map +1 -0
  23. package/dist/cli/chat/session.participants.descriptor.d.ts +4 -0
  24. package/dist/cli/chat/session.participants.descriptor.d.ts.map +1 -0
  25. package/dist/cli/chat/session.participants.descriptor.js +18 -0
  26. package/dist/cli/chat/session.participants.descriptor.js.map +1 -0
  27. package/dist/cli/chat/session.participants.impl.d.ts +12 -0
  28. package/dist/cli/chat/session.participants.impl.d.ts.map +1 -0
  29. package/dist/cli/chat/session.participants.impl.js +37 -0
  30. package/dist/cli/chat/session.participants.impl.js.map +1 -0
  31. package/dist/cli/command.d.ts +44 -0
  32. package/dist/cli/command.d.ts.map +1 -0
  33. package/dist/cli/command.js +57 -0
  34. package/dist/cli/command.js.map +1 -0
  35. package/dist/cli/display.d.ts +10 -0
  36. package/dist/cli/display.d.ts.map +1 -0
  37. package/dist/cli/display.js +16 -0
  38. package/dist/cli/display.js.map +1 -0
  39. package/dist/cli/index.d.ts +8 -0
  40. package/dist/cli/index.d.ts.map +1 -0
  41. package/dist/cli/index.js +7 -0
  42. package/dist/cli/index.js.map +1 -0
  43. package/dist/cli/lifecycle/session.archive.descriptor.d.ts +4 -0
  44. package/dist/cli/lifecycle/session.archive.descriptor.d.ts.map +1 -0
  45. package/dist/cli/lifecycle/session.archive.descriptor.js +16 -0
  46. package/dist/cli/lifecycle/session.archive.descriptor.js.map +1 -0
  47. package/dist/cli/lifecycle/session.archive.impl.d.ts +6 -0
  48. package/dist/cli/lifecycle/session.archive.impl.d.ts.map +1 -0
  49. package/dist/cli/lifecycle/session.archive.impl.js +18 -0
  50. package/dist/cli/lifecycle/session.archive.impl.js.map +1 -0
  51. package/dist/cli/lifecycle/session.init.descriptor.d.ts +4 -0
  52. package/dist/cli/lifecycle/session.init.descriptor.d.ts.map +1 -0
  53. package/dist/cli/lifecycle/session.init.descriptor.js +31 -0
  54. package/dist/cli/lifecycle/session.init.descriptor.js.map +1 -0
  55. package/dist/cli/lifecycle/session.init.impl.d.ts +6 -0
  56. package/dist/cli/lifecycle/session.init.impl.d.ts.map +1 -0
  57. package/dist/cli/lifecycle/session.init.impl.js +38 -0
  58. package/dist/cli/lifecycle/session.init.impl.js.map +1 -0
  59. package/dist/cli/lifecycle/session.list.descriptor.d.ts +4 -0
  60. package/dist/cli/lifecycle/session.list.descriptor.d.ts.map +1 -0
  61. package/dist/cli/lifecycle/session.list.descriptor.js +27 -0
  62. package/dist/cli/lifecycle/session.list.descriptor.js.map +1 -0
  63. package/dist/cli/lifecycle/session.list.impl.d.ts +12 -0
  64. package/dist/cli/lifecycle/session.list.impl.d.ts.map +1 -0
  65. package/dist/cli/lifecycle/session.list.impl.js +46 -0
  66. package/dist/cli/lifecycle/session.list.impl.js.map +1 -0
  67. package/dist/cli/lifecycle/session.pause.descriptor.d.ts +4 -0
  68. package/dist/cli/lifecycle/session.pause.descriptor.d.ts.map +1 -0
  69. package/dist/cli/lifecycle/session.pause.descriptor.js +13 -0
  70. package/dist/cli/lifecycle/session.pause.descriptor.js.map +1 -0
  71. package/dist/cli/lifecycle/session.pause.impl.d.ts +6 -0
  72. package/dist/cli/lifecycle/session.pause.impl.d.ts.map +1 -0
  73. package/dist/cli/lifecycle/session.pause.impl.js +18 -0
  74. package/dist/cli/lifecycle/session.pause.impl.js.map +1 -0
  75. package/dist/cli/lifecycle/session.resume.descriptor.d.ts +4 -0
  76. package/dist/cli/lifecycle/session.resume.descriptor.d.ts.map +1 -0
  77. package/dist/cli/lifecycle/session.resume.descriptor.js +21 -0
  78. package/dist/cli/lifecycle/session.resume.descriptor.js.map +1 -0
  79. package/dist/cli/lifecycle/session.resume.impl.d.ts +6 -0
  80. package/dist/cli/lifecycle/session.resume.impl.d.ts.map +1 -0
  81. package/dist/cli/lifecycle/session.resume.impl.js +18 -0
  82. package/dist/cli/lifecycle/session.resume.impl.js.map +1 -0
  83. package/dist/cli/lifecycle/session.search.descriptor.d.ts +4 -0
  84. package/dist/cli/lifecycle/session.search.descriptor.d.ts.map +1 -0
  85. package/dist/cli/lifecycle/session.search.descriptor.js +17 -0
  86. package/dist/cli/lifecycle/session.search.descriptor.js.map +1 -0
  87. package/dist/cli/lifecycle/session.search.impl.d.ts +12 -0
  88. package/dist/cli/lifecycle/session.search.impl.d.ts.map +1 -0
  89. package/dist/cli/lifecycle/session.search.impl.js +43 -0
  90. package/dist/cli/lifecycle/session.search.impl.js.map +1 -0
  91. package/dist/cli/lifecycle/session.status.descriptor.d.ts +4 -0
  92. package/dist/cli/lifecycle/session.status.descriptor.d.ts.map +1 -0
  93. package/dist/cli/lifecycle/session.status.descriptor.js +13 -0
  94. package/dist/cli/lifecycle/session.status.descriptor.js.map +1 -0
  95. package/dist/cli/lifecycle/session.status.impl.d.ts +6 -0
  96. package/dist/cli/lifecycle/session.status.impl.d.ts.map +1 -0
  97. package/dist/cli/lifecycle/session.status.impl.js +38 -0
  98. package/dist/cli/lifecycle/session.status.impl.js.map +1 -0
  99. package/dist/cli/output.d.ts +14 -0
  100. package/dist/cli/output.d.ts.map +1 -0
  101. package/dist/cli/output.js +19 -0
  102. package/dist/cli/output.js.map +1 -0
  103. package/dist/cli/schemas/commands.d.ts +709 -0
  104. package/dist/cli/schemas/commands.d.ts.map +1 -0
  105. package/dist/cli/schemas/commands.js +359 -0
  106. package/dist/cli/schemas/commands.js.map +1 -0
  107. package/dist/cli/scratchpad/session.scratchpad.cli-group.descriptor.d.ts +4 -0
  108. package/dist/cli/scratchpad/session.scratchpad.cli-group.descriptor.d.ts.map +1 -0
  109. package/dist/cli/scratchpad/session.scratchpad.cli-group.descriptor.js +13 -0
  110. package/dist/cli/scratchpad/session.scratchpad.cli-group.descriptor.js.map +1 -0
  111. package/dist/cli/scratchpad/session.scratchpad.list.descriptor.d.ts +4 -0
  112. package/dist/cli/scratchpad/session.scratchpad.list.descriptor.d.ts.map +1 -0
  113. package/dist/cli/scratchpad/session.scratchpad.list.descriptor.js +16 -0
  114. package/dist/cli/scratchpad/session.scratchpad.list.descriptor.js.map +1 -0
  115. package/dist/cli/scratchpad/session.scratchpad.list.impl.d.ts +6 -0
  116. package/dist/cli/scratchpad/session.scratchpad.list.impl.d.ts.map +1 -0
  117. package/dist/cli/scratchpad/session.scratchpad.list.impl.js +33 -0
  118. package/dist/cli/scratchpad/session.scratchpad.list.impl.js.map +1 -0
  119. package/dist/cli/scratchpad/session.scratchpad.read.descriptor.d.ts +4 -0
  120. package/dist/cli/scratchpad/session.scratchpad.read.descriptor.d.ts.map +1 -0
  121. package/dist/cli/scratchpad/session.scratchpad.read.descriptor.js +13 -0
  122. package/dist/cli/scratchpad/session.scratchpad.read.descriptor.js.map +1 -0
  123. package/dist/cli/scratchpad/session.scratchpad.read.impl.d.ts +6 -0
  124. package/dist/cli/scratchpad/session.scratchpad.read.impl.d.ts.map +1 -0
  125. package/dist/cli/scratchpad/session.scratchpad.read.impl.js +18 -0
  126. package/dist/cli/scratchpad/session.scratchpad.read.impl.js.map +1 -0
  127. package/dist/cli/scratchpad/session.scratchpad.search.descriptor.d.ts +4 -0
  128. package/dist/cli/scratchpad/session.scratchpad.search.descriptor.d.ts.map +1 -0
  129. package/dist/cli/scratchpad/session.scratchpad.search.descriptor.js +18 -0
  130. package/dist/cli/scratchpad/session.scratchpad.search.descriptor.js.map +1 -0
  131. package/dist/cli/scratchpad/session.scratchpad.search.impl.d.ts +6 -0
  132. package/dist/cli/scratchpad/session.scratchpad.search.impl.d.ts.map +1 -0
  133. package/dist/cli/scratchpad/session.scratchpad.search.impl.js +29 -0
  134. package/dist/cli/scratchpad/session.scratchpad.search.impl.js.map +1 -0
  135. package/dist/cli/scratchpad/session.scratchpad.write.descriptor.d.ts +4 -0
  136. package/dist/cli/scratchpad/session.scratchpad.write.descriptor.d.ts.map +1 -0
  137. package/dist/cli/scratchpad/session.scratchpad.write.descriptor.js +23 -0
  138. package/dist/cli/scratchpad/session.scratchpad.write.descriptor.js.map +1 -0
  139. package/dist/cli/scratchpad/session.scratchpad.write.impl.d.ts +6 -0
  140. package/dist/cli/scratchpad/session.scratchpad.write.impl.d.ts.map +1 -0
  141. package/dist/cli/scratchpad/session.scratchpad.write.impl.js +18 -0
  142. package/dist/cli/scratchpad/session.scratchpad.write.impl.js.map +1 -0
  143. package/dist/cli/services/session/jobs.d.ts +61 -0
  144. package/dist/cli/services/session/jobs.d.ts.map +1 -0
  145. package/dist/cli/services/session/jobs.js +36 -0
  146. package/dist/cli/services/session/jobs.js.map +1 -0
  147. package/dist/cli/services/session/participant.d.ts +9 -0
  148. package/dist/cli/services/session/participant.d.ts.map +1 -0
  149. package/dist/cli/services/session/participant.js +93 -0
  150. package/dist/cli/services/session/participant.js.map +1 -0
  151. package/dist/cli/services/session/participants.d.ts +84 -0
  152. package/dist/cli/services/session/participants.d.ts.map +1 -0
  153. package/dist/cli/services/session/participants.js +33 -0
  154. package/dist/cli/services/session/participants.js.map +1 -0
  155. package/dist/cli/services/session/refusals.d.ts +15 -0
  156. package/dist/cli/services/session/refusals.d.ts.map +1 -0
  157. package/dist/cli/services/session/refusals.js +28 -0
  158. package/dist/cli/services/session/refusals.js.map +1 -0
  159. package/dist/cli/services/session/scratchpad.d.ts +89 -0
  160. package/dist/cli/services/session/scratchpad.d.ts.map +1 -0
  161. package/dist/cli/services/session/scratchpad.js +63 -0
  162. package/dist/cli/services/session/scratchpad.js.map +1 -0
  163. package/dist/cli/services/session/sessions.d.ts +277 -0
  164. package/dist/cli/services/session/sessions.d.ts.map +1 -0
  165. package/dist/cli/services/session/sessions.js +149 -0
  166. package/dist/cli/services/session/sessions.js.map +1 -0
  167. package/dist/cli/services/session/todos.d.ts +132 -0
  168. package/dist/cli/services/session/todos.d.ts.map +1 -0
  169. package/dist/cli/services/session/todos.js +85 -0
  170. package/dist/cli/services/session/todos.js.map +1 -0
  171. package/dist/cli/services/session-cli-context.d.ts +29 -0
  172. package/dist/cli/services/session-cli-context.d.ts.map +1 -0
  173. package/dist/cli/services/session-cli-context.js +21 -0
  174. package/dist/cli/services/session-cli-context.js.map +1 -0
  175. package/dist/cli/services/session.cli.consumer.d.ts +58 -0
  176. package/dist/cli/services/session.cli.consumer.d.ts.map +1 -0
  177. package/dist/cli/services/session.cli.consumer.js +13 -0
  178. package/dist/cli/services/session.cli.consumer.js.map +1 -0
  179. package/dist/cli/session-cli.plugin.d.ts +3 -0
  180. package/dist/cli/session-cli.plugin.d.ts.map +1 -0
  181. package/dist/cli/session-cli.plugin.js +150 -0
  182. package/dist/cli/session-cli.plugin.js.map +1 -0
  183. package/dist/cli/session.cli-group.descriptor.d.ts +4 -0
  184. package/dist/cli/session.cli-group.descriptor.d.ts.map +1 -0
  185. package/dist/cli/session.cli-group.descriptor.js +12 -0
  186. package/dist/cli/session.cli-group.descriptor.js.map +1 -0
  187. package/dist/cli/session.cli.descriptor.d.ts +4 -0
  188. package/dist/cli/session.cli.descriptor.d.ts.map +1 -0
  189. package/dist/cli/session.cli.descriptor.js +13 -0
  190. package/dist/cli/session.cli.descriptor.js.map +1 -0
  191. package/dist/cli/todo/session.todo.add.descriptor.d.ts +4 -0
  192. package/dist/cli/todo/session.todo.add.descriptor.d.ts.map +1 -0
  193. package/dist/cli/todo/session.todo.add.descriptor.js +23 -0
  194. package/dist/cli/todo/session.todo.add.descriptor.js.map +1 -0
  195. package/dist/cli/todo/session.todo.add.impl.d.ts +6 -0
  196. package/dist/cli/todo/session.todo.add.impl.d.ts.map +1 -0
  197. package/dist/cli/todo/session.todo.add.impl.js +22 -0
  198. package/dist/cli/todo/session.todo.add.impl.js.map +1 -0
  199. package/dist/cli/todo/session.todo.cli-group.descriptor.d.ts +4 -0
  200. package/dist/cli/todo/session.todo.cli-group.descriptor.d.ts.map +1 -0
  201. package/dist/cli/todo/session.todo.cli-group.descriptor.js +12 -0
  202. package/dist/cli/todo/session.todo.cli-group.descriptor.js.map +1 -0
  203. package/dist/cli/todo/session.todo.complete.descriptor.d.ts +4 -0
  204. package/dist/cli/todo/session.todo.complete.descriptor.d.ts.map +1 -0
  205. package/dist/cli/todo/session.todo.complete.descriptor.js +16 -0
  206. package/dist/cli/todo/session.todo.complete.descriptor.js.map +1 -0
  207. package/dist/cli/todo/session.todo.complete.impl.d.ts +6 -0
  208. package/dist/cli/todo/session.todo.complete.impl.d.ts.map +1 -0
  209. package/dist/cli/todo/session.todo.complete.impl.js +18 -0
  210. package/dist/cli/todo/session.todo.complete.impl.js.map +1 -0
  211. package/dist/cli/todo/session.todo.list.descriptor.d.ts +4 -0
  212. package/dist/cli/todo/session.todo.list.descriptor.d.ts.map +1 -0
  213. package/dist/cli/todo/session.todo.list.descriptor.js +19 -0
  214. package/dist/cli/todo/session.todo.list.descriptor.js.map +1 -0
  215. package/dist/cli/todo/session.todo.list.impl.d.ts +6 -0
  216. package/dist/cli/todo/session.todo.list.impl.d.ts.map +1 -0
  217. package/dist/cli/todo/session.todo.list.impl.js +34 -0
  218. package/dist/cli/todo/session.todo.list.impl.js.map +1 -0
  219. package/dist/cli/todo/session.todo.tree.descriptor.d.ts +4 -0
  220. package/dist/cli/todo/session.todo.tree.descriptor.d.ts.map +1 -0
  221. package/dist/cli/todo/session.todo.tree.descriptor.js +21 -0
  222. package/dist/cli/todo/session.todo.tree.descriptor.js.map +1 -0
  223. package/dist/cli/todo/session.todo.tree.impl.d.ts +6 -0
  224. package/dist/cli/todo/session.todo.tree.impl.d.ts.map +1 -0
  225. package/dist/cli/todo/session.todo.tree.impl.js +26 -0
  226. package/dist/cli/todo/session.todo.tree.impl.js.map +1 -0
  227. package/dist/cli/todo/session.todo.update.descriptor.d.ts +4 -0
  228. package/dist/cli/todo/session.todo.update.descriptor.d.ts.map +1 -0
  229. package/dist/cli/todo/session.todo.update.descriptor.js +21 -0
  230. package/dist/cli/todo/session.todo.update.descriptor.js.map +1 -0
  231. package/dist/cli/todo/session.todo.update.impl.d.ts +6 -0
  232. package/dist/cli/todo/session.todo.update.impl.d.ts.map +1 -0
  233. package/dist/cli/todo/session.todo.update.impl.js +18 -0
  234. package/dist/cli/todo/session.todo.update.impl.js.map +1 -0
  235. package/dist/client.d.ts +40 -0
  236. package/dist/client.d.ts.map +1 -0
  237. package/dist/client.js +114 -0
  238. package/dist/client.js.map +1 -0
  239. package/dist/cockpit/build-state.d.ts +104 -0
  240. package/dist/cockpit/build-state.d.ts.map +1 -0
  241. package/dist/cockpit/build-state.js +273 -0
  242. package/dist/cockpit/build-state.js.map +1 -0
  243. package/dist/cockpit/resolve-participant.d.ts +55 -0
  244. package/dist/cockpit/resolve-participant.d.ts.map +1 -0
  245. package/dist/cockpit/resolve-participant.js +116 -0
  246. package/dist/cockpit/resolve-participant.js.map +1 -0
  247. package/dist/docs/session.docs.descriptor.d.ts +24 -0
  248. package/dist/docs/session.docs.descriptor.d.ts.map +1 -0
  249. package/dist/docs/session.docs.descriptor.js +589 -0
  250. package/dist/docs/session.docs.descriptor.js.map +1 -0
  251. package/dist/events/session.bound.event.descriptor.d.ts +100 -0
  252. package/dist/events/session.bound.event.descriptor.d.ts.map +1 -0
  253. package/dist/events/session.bound.event.descriptor.js +121 -0
  254. package/dist/events/session.bound.event.descriptor.js.map +1 -0
  255. package/dist/events/session.unbound.event.descriptor.d.ts +58 -0
  256. package/dist/events/session.unbound.event.descriptor.d.ts.map +1 -0
  257. package/dist/events/session.unbound.event.descriptor.js +45 -0
  258. package/dist/events/session.unbound.event.descriptor.js.map +1 -0
  259. package/dist/helper.d.ts +15 -0
  260. package/dist/helper.d.ts.map +1 -0
  261. package/dist/helper.js +23 -0
  262. package/dist/helper.js.map +1 -0
  263. package/dist/index.d.ts +17 -0
  264. package/dist/index.d.ts.map +1 -0
  265. package/dist/index.js +15 -0
  266. package/dist/index.js.map +1 -0
  267. package/dist/knowledge-source/vibes.descriptor.d.ts +10 -0
  268. package/dist/knowledge-source/vibes.descriptor.d.ts.map +1 -0
  269. package/dist/knowledge-source/vibes.descriptor.js +10 -0
  270. package/dist/knowledge-source/vibes.descriptor.js.map +1 -0
  271. package/dist/knowledge-source/vibes.impl.consumer.d.ts +47 -0
  272. package/dist/knowledge-source/vibes.impl.consumer.d.ts.map +1 -0
  273. package/dist/knowledge-source/vibes.impl.consumer.js +457 -0
  274. package/dist/knowledge-source/vibes.impl.consumer.js.map +1 -0
  275. package/dist/manager/cli-body-connection.d.ts +4 -0
  276. package/dist/manager/cli-body-connection.d.ts.map +1 -0
  277. package/dist/manager/cli-body-connection.js +7 -0
  278. package/dist/manager/cli-body-connection.js.map +1 -0
  279. package/dist/manager/index.d.ts +3 -0
  280. package/dist/manager/index.d.ts.map +1 -0
  281. package/dist/manager/index.js +2 -0
  282. package/dist/manager/index.js.map +1 -0
  283. package/dist/manager/resolve-bound-session.d.ts +95 -0
  284. package/dist/manager/resolve-bound-session.d.ts.map +1 -0
  285. package/dist/manager/resolve-bound-session.js +163 -0
  286. package/dist/manager/resolve-bound-session.js.map +1 -0
  287. package/dist/manager.d.ts +630 -0
  288. package/dist/manager.d.ts.map +1 -0
  289. package/dist/manager.js +2669 -0
  290. package/dist/manager.js.map +1 -0
  291. package/dist/mcp/index.d.ts +14 -0
  292. package/dist/mcp/index.d.ts.map +1 -0
  293. package/dist/mcp/index.js +11 -0
  294. package/dist/mcp/index.js.map +1 -0
  295. package/dist/mcp/schemas/coordination.d.ts +20 -0
  296. package/dist/mcp/schemas/coordination.d.ts.map +1 -0
  297. package/dist/mcp/schemas/coordination.js +5 -0
  298. package/dist/mcp/schemas/coordination.js.map +1 -0
  299. package/dist/mcp/schemas/tools.d.ts +348 -0
  300. package/dist/mcp/schemas/tools.d.ts.map +1 -0
  301. package/dist/mcp/schemas/tools.js +266 -0
  302. package/dist/mcp/schemas/tools.js.map +1 -0
  303. package/dist/mcp/types.d.ts +3 -0
  304. package/dist/mcp/types.d.ts.map +1 -0
  305. package/dist/mcp/types.js +2 -0
  306. package/dist/mcp/types.js.map +1 -0
  307. package/dist/mcp/utils/coordination.d.ts +17 -0
  308. package/dist/mcp/utils/coordination.d.ts.map +1 -0
  309. package/dist/mcp/utils/coordination.js +56 -0
  310. package/dist/mcp/utils/coordination.js.map +1 -0
  311. package/dist/mcp/utils/knowledge.d.ts +90 -0
  312. package/dist/mcp/utils/knowledge.d.ts.map +1 -0
  313. package/dist/mcp/utils/knowledge.js +455 -0
  314. package/dist/mcp/utils/knowledge.js.map +1 -0
  315. package/dist/mcp/utils/mcp-session-resolver.d.ts +19 -0
  316. package/dist/mcp/utils/mcp-session-resolver.d.ts.map +1 -0
  317. package/dist/mcp/utils/mcp-session-resolver.js +28 -0
  318. package/dist/mcp/utils/mcp-session-resolver.js.map +1 -0
  319. package/dist/mcp/utils/participant.d.ts +409 -0
  320. package/dist/mcp/utils/participant.d.ts.map +1 -0
  321. package/dist/mcp/utils/participant.js +1429 -0
  322. package/dist/mcp/utils/participant.js.map +1 -0
  323. package/dist/mcp/utils/session-registry.d.ts +194 -0
  324. package/dist/mcp/utils/session-registry.d.ts.map +1 -0
  325. package/dist/mcp/utils/session-registry.js +479 -0
  326. package/dist/mcp/utils/session-registry.js.map +1 -0
  327. package/dist/mcp/utils/surface-sessions.d.ts +54 -0
  328. package/dist/mcp/utils/surface-sessions.d.ts.map +1 -0
  329. package/dist/mcp/utils/surface-sessions.js +118 -0
  330. package/dist/mcp/utils/surface-sessions.js.map +1 -0
  331. package/dist/mcp/utils/unbound-guidance.d.ts +97 -0
  332. package/dist/mcp/utils/unbound-guidance.d.ts.map +1 -0
  333. package/dist/mcp/utils/unbound-guidance.js +236 -0
  334. package/dist/mcp/utils/unbound-guidance.js.map +1 -0
  335. package/dist/schemas/chatter.d.ts +228 -0
  336. package/dist/schemas/chatter.d.ts.map +1 -0
  337. package/dist/schemas/chatter.js +130 -0
  338. package/dist/schemas/chatter.js.map +1 -0
  339. package/dist/schemas/cockpit.d.ts +332 -0
  340. package/dist/schemas/cockpit.d.ts.map +1 -0
  341. package/dist/schemas/cockpit.js +139 -0
  342. package/dist/schemas/cockpit.js.map +1 -0
  343. package/dist/schemas/harness-seat.d.ts +41 -0
  344. package/dist/schemas/harness-seat.d.ts.map +1 -0
  345. package/dist/schemas/harness-seat.js +61 -0
  346. package/dist/schemas/harness-seat.js.map +1 -0
  347. package/dist/schemas/index.d.ts +29 -0
  348. package/dist/schemas/index.d.ts.map +1 -0
  349. package/dist/schemas/index.js +29 -0
  350. package/dist/schemas/index.js.map +1 -0
  351. package/dist/schemas/job.d.ts +261 -0
  352. package/dist/schemas/job.d.ts.map +1 -0
  353. package/dist/schemas/job.js +144 -0
  354. package/dist/schemas/job.js.map +1 -0
  355. package/dist/schemas/json-text.d.ts +9 -0
  356. package/dist/schemas/json-text.d.ts.map +1 -0
  357. package/dist/schemas/json-text.js +129 -0
  358. package/dist/schemas/json-text.js.map +1 -0
  359. package/dist/schemas/knowledge.d.ts +109 -0
  360. package/dist/schemas/knowledge.d.ts.map +1 -0
  361. package/dist/schemas/knowledge.js +81 -0
  362. package/dist/schemas/knowledge.js.map +1 -0
  363. package/dist/schemas/mcp.d.ts +179 -0
  364. package/dist/schemas/mcp.d.ts.map +1 -0
  365. package/dist/schemas/mcp.js +46 -0
  366. package/dist/schemas/mcp.js.map +1 -0
  367. package/dist/schemas/participant.d.ts +377 -0
  368. package/dist/schemas/participant.d.ts.map +1 -0
  369. package/dist/schemas/participant.js +188 -0
  370. package/dist/schemas/participant.js.map +1 -0
  371. package/dist/schemas/presence-core.d.ts +93 -0
  372. package/dist/schemas/presence-core.d.ts.map +1 -0
  373. package/dist/schemas/presence-core.js +53 -0
  374. package/dist/schemas/presence-core.js.map +1 -0
  375. package/dist/schemas/presence.d.ts +69 -0
  376. package/dist/schemas/presence.d.ts.map +1 -0
  377. package/dist/schemas/presence.js +44 -0
  378. package/dist/schemas/presence.js.map +1 -0
  379. package/dist/schemas/registry-state.d.ts +64 -0
  380. package/dist/schemas/registry-state.d.ts.map +1 -0
  381. package/dist/schemas/registry-state.js +30 -0
  382. package/dist/schemas/registry-state.js.map +1 -0
  383. package/dist/schemas/scratchpad.d.ts +163 -0
  384. package/dist/schemas/scratchpad.d.ts.map +1 -0
  385. package/dist/schemas/scratchpad.js +119 -0
  386. package/dist/schemas/scratchpad.js.map +1 -0
  387. package/dist/schemas/session.d.ts +266 -0
  388. package/dist/schemas/session.d.ts.map +1 -0
  389. package/dist/schemas/session.js +179 -0
  390. package/dist/schemas/session.js.map +1 -0
  391. package/dist/schemas/todo-status-category.d.ts +21 -0
  392. package/dist/schemas/todo-status-category.d.ts.map +1 -0
  393. package/dist/schemas/todo-status-category.js +27 -0
  394. package/dist/schemas/todo-status-category.js.map +1 -0
  395. package/dist/schemas/todo.d.ts +293 -0
  396. package/dist/schemas/todo.d.ts.map +1 -0
  397. package/dist/schemas/todo.js +130 -0
  398. package/dist/schemas/todo.js.map +1 -0
  399. package/dist/schemas/types.d.ts +15 -0
  400. package/dist/schemas/types.d.ts.map +1 -0
  401. package/dist/schemas/types.js +9 -0
  402. package/dist/schemas/types.js.map +1 -0
  403. package/dist/schemas.d.ts +2 -0
  404. package/dist/schemas.d.ts.map +1 -0
  405. package/dist/schemas.js +2 -0
  406. package/dist/schemas.js.map +1 -0
  407. package/dist/search/providers/scratchpad.d.ts +9 -0
  408. package/dist/search/providers/scratchpad.d.ts.map +1 -0
  409. package/dist/search/providers/scratchpad.js +64 -0
  410. package/dist/search/providers/scratchpad.js.map +1 -0
  411. package/dist/search/providers/session.d.ts +13 -0
  412. package/dist/search/providers/session.d.ts.map +1 -0
  413. package/dist/search/providers/session.js +48 -0
  414. package/dist/search/providers/session.js.map +1 -0
  415. package/dist/session.client.descriptor.d.ts +27 -0
  416. package/dist/session.client.descriptor.d.ts.map +1 -0
  417. package/dist/session.client.descriptor.js +8 -0
  418. package/dist/session.client.descriptor.js.map +1 -0
  419. package/dist/session.client.impl.d.ts +7 -0
  420. package/dist/session.client.impl.d.ts.map +1 -0
  421. package/dist/session.client.impl.js +9 -0
  422. package/dist/session.client.impl.js.map +1 -0
  423. package/dist/session.cloud.plugin.d.ts +3 -0
  424. package/dist/session.cloud.plugin.d.ts.map +1 -0
  425. package/dist/session.cloud.plugin.js +16 -0
  426. package/dist/session.cloud.plugin.js.map +1 -0
  427. package/dist/session.consumer.plugin.d.ts +3 -0
  428. package/dist/session.consumer.plugin.d.ts.map +1 -0
  429. package/dist/session.consumer.plugin.js +28 -0
  430. package/dist/session.consumer.plugin.js.map +1 -0
  431. package/dist/session.context.descriptor.d.ts +33 -0
  432. package/dist/session.context.descriptor.d.ts.map +1 -0
  433. package/dist/session.context.descriptor.js +9 -0
  434. package/dist/session.context.descriptor.js.map +1 -0
  435. package/dist/session.context.impl.consumer.d.ts +3 -0
  436. package/dist/session.context.impl.consumer.d.ts.map +1 -0
  437. package/dist/session.context.impl.consumer.js +3 -0
  438. package/dist/session.context.impl.consumer.js.map +1 -0
  439. package/dist/session.context.impl.d.ts +8 -0
  440. package/dist/session.context.impl.d.ts.map +1 -0
  441. package/dist/session.context.impl.js +13 -0
  442. package/dist/session.context.impl.js.map +1 -0
  443. package/dist/session.plugin.d.ts +3 -0
  444. package/dist/session.plugin.d.ts.map +1 -0
  445. package/dist/session.plugin.js +129 -0
  446. package/dist/session.plugin.js.map +1 -0
  447. package/dist/storage/record-draft.d.ts +9 -0
  448. package/dist/storage/record-draft.d.ts.map +1 -0
  449. package/dist/storage/record-draft.js +2 -0
  450. package/dist/storage/record-draft.js.map +1 -0
  451. package/dist/storage/session-connections.record.descriptor.d.ts +37 -0
  452. package/dist/storage/session-connections.record.descriptor.d.ts.map +1 -0
  453. package/dist/storage/session-connections.record.descriptor.js +105 -0
  454. package/dist/storage/session-connections.record.descriptor.js.map +1 -0
  455. package/dist/storage/session-jobs.record.descriptor.d.ts +86 -0
  456. package/dist/storage/session-jobs.record.descriptor.d.ts.map +1 -0
  457. package/dist/storage/session-jobs.record.descriptor.js +135 -0
  458. package/dist/storage/session-jobs.record.descriptor.js.map +1 -0
  459. package/dist/storage/session-knowledge-refs.record.descriptor.d.ts +48 -0
  460. package/dist/storage/session-knowledge-refs.record.descriptor.d.ts.map +1 -0
  461. package/dist/storage/session-knowledge-refs.record.descriptor.js +103 -0
  462. package/dist/storage/session-knowledge-refs.record.descriptor.js.map +1 -0
  463. package/dist/storage/session-membership-leases.record.descriptor.d.ts +39 -0
  464. package/dist/storage/session-membership-leases.record.descriptor.d.ts.map +1 -0
  465. package/dist/storage/session-membership-leases.record.descriptor.js +185 -0
  466. package/dist/storage/session-membership-leases.record.descriptor.js.map +1 -0
  467. package/dist/storage/session-messages.record.descriptor.d.ts +79 -0
  468. package/dist/storage/session-messages.record.descriptor.d.ts.map +1 -0
  469. package/dist/storage/session-messages.record.descriptor.js +142 -0
  470. package/dist/storage/session-messages.record.descriptor.js.map +1 -0
  471. package/dist/storage/session-participants.record.descriptor.d.ts +78 -0
  472. package/dist/storage/session-participants.record.descriptor.d.ts.map +1 -0
  473. package/dist/storage/session-participants.record.descriptor.js +119 -0
  474. package/dist/storage/session-participants.record.descriptor.js.map +1 -0
  475. package/dist/storage/session-presence.record.descriptor.d.ts +43 -0
  476. package/dist/storage/session-presence.record.descriptor.d.ts.map +1 -0
  477. package/dist/storage/session-presence.record.descriptor.js +141 -0
  478. package/dist/storage/session-presence.record.descriptor.js.map +1 -0
  479. package/dist/storage/session-scratchpad.record.descriptor.d.ts +54 -0
  480. package/dist/storage/session-scratchpad.record.descriptor.d.ts.map +1 -0
  481. package/dist/storage/session-scratchpad.record.descriptor.js +134 -0
  482. package/dist/storage/session-scratchpad.record.descriptor.js.map +1 -0
  483. package/dist/storage/session-sessions.record.descriptor.d.ts +68 -0
  484. package/dist/storage/session-sessions.record.descriptor.d.ts.map +1 -0
  485. package/dist/storage/session-sessions.record.descriptor.js +260 -0
  486. package/dist/storage/session-sessions.record.descriptor.js.map +1 -0
  487. package/dist/storage/session-state.record.descriptor.d.ts +37 -0
  488. package/dist/storage/session-state.record.descriptor.d.ts.map +1 -0
  489. package/dist/storage/session-state.record.descriptor.js +84 -0
  490. package/dist/storage/session-state.record.descriptor.js.map +1 -0
  491. package/dist/storage/session-todo.record.descriptor.d.ts +84 -0
  492. package/dist/storage/session-todo.record.descriptor.d.ts.map +1 -0
  493. package/dist/storage/session-todo.record.descriptor.js +245 -0
  494. package/dist/storage/session-todo.record.descriptor.js.map +1 -0
  495. package/dist/subscriptions/session.bound.scope.descriptor.d.ts +21 -0
  496. package/dist/subscriptions/session.bound.scope.descriptor.d.ts.map +1 -0
  497. package/dist/subscriptions/session.bound.scope.descriptor.js +21 -0
  498. package/dist/subscriptions/session.bound.scope.descriptor.js.map +1 -0
  499. package/dist/subscriptions/session.bound.scope.impl.d.ts +29 -0
  500. package/dist/subscriptions/session.bound.scope.impl.d.ts.map +1 -0
  501. package/dist/subscriptions/session.bound.scope.impl.js +40 -0
  502. package/dist/subscriptions/session.bound.scope.impl.js.map +1 -0
  503. package/dist/svelte/components/SessionCockpit.svelte +360 -0
  504. package/dist/svelte/components/SessionCockpit.svelte.d.ts +17 -0
  505. package/dist/svelte/components/SessionCockpit.svelte.d.ts.map +1 -0
  506. package/dist/svelte/index.d.ts +18 -0
  507. package/dist/svelte/index.d.ts.map +1 -0
  508. package/dist/svelte/index.js +16 -0
  509. package/dist/svelte/session-svelte.plugin.d.ts +10 -0
  510. package/dist/svelte/session-svelte.plugin.d.ts.map +1 -0
  511. package/dist/svelte/session-svelte.plugin.js +54 -0
  512. package/dist/svelte/styles.css +6 -0
  513. package/dist/svelte/tool-widgets/SessionCollaborateToolCard.svelte +53 -0
  514. package/dist/svelte/tool-widgets/SessionCollaborateToolCard.svelte.d.ts +5 -0
  515. package/dist/svelte/tool-widgets/SessionCollaborateToolCard.svelte.d.ts.map +1 -0
  516. package/dist/svelte/tool-widgets/SessionScratchpadToolCard.svelte +47 -0
  517. package/dist/svelte/tool-widgets/SessionScratchpadToolCard.svelte.d.ts +5 -0
  518. package/dist/svelte/tool-widgets/SessionScratchpadToolCard.svelte.d.ts.map +1 -0
  519. package/dist/svelte/tool-widgets/SessionTodoToolCard.svelte +43 -0
  520. package/dist/svelte/tool-widgets/SessionTodoToolCard.svelte.d.ts +5 -0
  521. package/dist/svelte/tool-widgets/SessionTodoToolCard.svelte.d.ts.map +1 -0
  522. package/dist/svelte/tool-widgets/SessionToolCard.svelte +47 -0
  523. package/dist/svelte/tool-widgets/SessionToolCard.svelte.d.ts +5 -0
  524. package/dist/svelte/tool-widgets/SessionToolCard.svelte.d.ts.map +1 -0
  525. package/dist/svelte/tool-widgets/SessionWorkspaceToolCard.svelte +44 -0
  526. package/dist/svelte/tool-widgets/SessionWorkspaceToolCard.svelte.d.ts +5 -0
  527. package/dist/svelte/tool-widgets/SessionWorkspaceToolCard.svelte.d.ts.map +1 -0
  528. package/dist/svelte/tool-widgets/card-fields.d.ts +68 -0
  529. package/dist/svelte/tool-widgets/card-fields.d.ts.map +1 -0
  530. package/dist/svelte/tool-widgets/card-fields.js +274 -0
  531. package/dist/testing/harness/transport-mocks.d.ts +380 -0
  532. package/dist/testing/harness/transport-mocks.d.ts.map +1 -0
  533. package/dist/testing/harness/transport-mocks.js +809 -0
  534. package/dist/testing/harness/transport-mocks.js.map +1 -0
  535. package/dist/tools/best-effort.d.ts +27 -0
  536. package/dist/tools/best-effort.d.ts.map +1 -0
  537. package/dist/tools/best-effort.js +31 -0
  538. package/dist/tools/best-effort.js.map +1 -0
  539. package/dist/tools/lifecycle/archive/handler.d.ts +19 -0
  540. package/dist/tools/lifecycle/archive/handler.d.ts.map +1 -0
  541. package/dist/tools/lifecycle/archive/handler.js +31 -0
  542. package/dist/tools/lifecycle/archive/handler.js.map +1 -0
  543. package/dist/tools/lifecycle/archive/schemas.d.ts +24 -0
  544. package/dist/tools/lifecycle/archive/schemas.d.ts.map +1 -0
  545. package/dist/tools/lifecycle/archive/schemas.js +22 -0
  546. package/dist/tools/lifecycle/archive/schemas.js.map +1 -0
  547. package/dist/tools/lifecycle/context/handler.d.ts +115 -0
  548. package/dist/tools/lifecycle/context/handler.d.ts.map +1 -0
  549. package/dist/tools/lifecycle/context/handler.js +82 -0
  550. package/dist/tools/lifecycle/context/handler.js.map +1 -0
  551. package/dist/tools/lifecycle/context/schemas.d.ts +44 -0
  552. package/dist/tools/lifecycle/context/schemas.d.ts.map +1 -0
  553. package/dist/tools/lifecycle/context/schemas.js +42 -0
  554. package/dist/tools/lifecycle/context/schemas.js.map +1 -0
  555. package/dist/tools/lifecycle/delete/handler.d.ts +7 -0
  556. package/dist/tools/lifecycle/delete/handler.d.ts.map +1 -0
  557. package/dist/tools/lifecycle/delete/handler.js +29 -0
  558. package/dist/tools/lifecycle/delete/handler.js.map +1 -0
  559. package/dist/tools/lifecycle/delete/schemas.d.ts +16 -0
  560. package/dist/tools/lifecycle/delete/schemas.d.ts.map +1 -0
  561. package/dist/tools/lifecycle/delete/schemas.js +14 -0
  562. package/dist/tools/lifecycle/delete/schemas.js.map +1 -0
  563. package/dist/tools/lifecycle/get/handler.d.ts +18 -0
  564. package/dist/tools/lifecycle/get/handler.d.ts.map +1 -0
  565. package/dist/tools/lifecycle/get/handler.js +28 -0
  566. package/dist/tools/lifecycle/get/handler.js.map +1 -0
  567. package/dist/tools/lifecycle/get/schemas.d.ts +26 -0
  568. package/dist/tools/lifecycle/get/schemas.d.ts.map +1 -0
  569. package/dist/tools/lifecycle/get/schemas.js +24 -0
  570. package/dist/tools/lifecycle/get/schemas.js.map +1 -0
  571. package/dist/tools/lifecycle/init/atlas.consumer.d.ts +17 -0
  572. package/dist/tools/lifecycle/init/atlas.consumer.d.ts.map +1 -0
  573. package/dist/tools/lifecycle/init/atlas.consumer.js +180 -0
  574. package/dist/tools/lifecycle/init/atlas.consumer.js.map +1 -0
  575. package/dist/tools/lifecycle/init/handler.d.ts +4 -0
  576. package/dist/tools/lifecycle/init/handler.d.ts.map +1 -0
  577. package/dist/tools/lifecycle/init/handler.js +353 -0
  578. package/dist/tools/lifecycle/init/handler.js.map +1 -0
  579. package/dist/tools/lifecycle/init/knowledge.d.ts +18 -0
  580. package/dist/tools/lifecycle/init/knowledge.d.ts.map +1 -0
  581. package/dist/tools/lifecycle/init/knowledge.js +86 -0
  582. package/dist/tools/lifecycle/init/knowledge.js.map +1 -0
  583. package/dist/tools/lifecycle/init/language.d.ts +10 -0
  584. package/dist/tools/lifecycle/init/language.d.ts.map +1 -0
  585. package/dist/tools/lifecycle/init/language.js +26 -0
  586. package/dist/tools/lifecycle/init/language.js.map +1 -0
  587. package/dist/tools/lifecycle/init/participant.d.ts +58 -0
  588. package/dist/tools/lifecycle/init/participant.d.ts.map +1 -0
  589. package/dist/tools/lifecycle/init/participant.js +219 -0
  590. package/dist/tools/lifecycle/init/participant.js.map +1 -0
  591. package/dist/tools/lifecycle/init/schemas.d.ts +99 -0
  592. package/dist/tools/lifecycle/init/schemas.d.ts.map +1 -0
  593. package/dist/tools/lifecycle/init/schemas.js +81 -0
  594. package/dist/tools/lifecycle/init/schemas.js.map +1 -0
  595. package/dist/tools/lifecycle/init/types.d.ts +137 -0
  596. package/dist/tools/lifecycle/init/types.d.ts.map +1 -0
  597. package/dist/tools/lifecycle/init/types.js +3 -0
  598. package/dist/tools/lifecycle/init/types.js.map +1 -0
  599. package/dist/tools/lifecycle/init/workspace.consumer.d.ts +13 -0
  600. package/dist/tools/lifecycle/init/workspace.consumer.d.ts.map +1 -0
  601. package/dist/tools/lifecycle/init/workspace.consumer.js +58 -0
  602. package/dist/tools/lifecycle/init/workspace.consumer.js.map +1 -0
  603. package/dist/tools/lifecycle/list/handler.d.ts +47 -0
  604. package/dist/tools/lifecycle/list/handler.d.ts.map +1 -0
  605. package/dist/tools/lifecycle/list/handler.js +48 -0
  606. package/dist/tools/lifecycle/list/handler.js.map +1 -0
  607. package/dist/tools/lifecycle/list/schemas.d.ts +37 -0
  608. package/dist/tools/lifecycle/list/schemas.d.ts.map +1 -0
  609. package/dist/tools/lifecycle/list/schemas.js +30 -0
  610. package/dist/tools/lifecycle/list/schemas.js.map +1 -0
  611. package/dist/tools/lifecycle/packet/handler.d.ts +490 -0
  612. package/dist/tools/lifecycle/packet/handler.d.ts.map +1 -0
  613. package/dist/tools/lifecycle/packet/handler.js +428 -0
  614. package/dist/tools/lifecycle/packet/handler.js.map +1 -0
  615. package/dist/tools/lifecycle/packet/schemas.d.ts +100 -0
  616. package/dist/tools/lifecycle/packet/schemas.d.ts.map +1 -0
  617. package/dist/tools/lifecycle/packet/schemas.js +36 -0
  618. package/dist/tools/lifecycle/packet/schemas.js.map +1 -0
  619. package/dist/tools/lifecycle/packet/todos.d.ts +30 -0
  620. package/dist/tools/lifecycle/packet/todos.d.ts.map +1 -0
  621. package/dist/tools/lifecycle/packet/todos.js +47 -0
  622. package/dist/tools/lifecycle/packet/todos.js.map +1 -0
  623. package/dist/tools/lifecycle/search/handler.d.ts +36 -0
  624. package/dist/tools/lifecycle/search/handler.d.ts.map +1 -0
  625. package/dist/tools/lifecycle/search/handler.js +44 -0
  626. package/dist/tools/lifecycle/search/handler.js.map +1 -0
  627. package/dist/tools/lifecycle/search/schemas.d.ts +37 -0
  628. package/dist/tools/lifecycle/search/schemas.d.ts.map +1 -0
  629. package/dist/tools/lifecycle/search/schemas.js +30 -0
  630. package/dist/tools/lifecycle/search/schemas.js.map +1 -0
  631. package/dist/tools/lifecycle/snapshot/handler.d.ts +144 -0
  632. package/dist/tools/lifecycle/snapshot/handler.d.ts.map +1 -0
  633. package/dist/tools/lifecycle/snapshot/handler.js +31 -0
  634. package/dist/tools/lifecycle/snapshot/handler.js.map +1 -0
  635. package/dist/tools/lifecycle/snapshot/schemas.d.ts +27 -0
  636. package/dist/tools/lifecycle/snapshot/schemas.d.ts.map +1 -0
  637. package/dist/tools/lifecycle/snapshot/schemas.js +25 -0
  638. package/dist/tools/lifecycle/snapshot/schemas.js.map +1 -0
  639. package/dist/tools/lifecycle/state/pause/handler.d.ts +13 -0
  640. package/dist/tools/lifecycle/state/pause/handler.d.ts.map +1 -0
  641. package/dist/tools/lifecycle/state/pause/handler.js +33 -0
  642. package/dist/tools/lifecycle/state/pause/handler.js.map +1 -0
  643. package/dist/tools/lifecycle/state/pause/schemas.d.ts +21 -0
  644. package/dist/tools/lifecycle/state/pause/schemas.d.ts.map +1 -0
  645. package/dist/tools/lifecycle/state/pause/schemas.js +19 -0
  646. package/dist/tools/lifecycle/state/pause/schemas.js.map +1 -0
  647. package/dist/tools/lifecycle/state/resume/handler.d.ts +13 -0
  648. package/dist/tools/lifecycle/state/resume/handler.d.ts.map +1 -0
  649. package/dist/tools/lifecycle/state/resume/handler.js +31 -0
  650. package/dist/tools/lifecycle/state/resume/handler.js.map +1 -0
  651. package/dist/tools/lifecycle/state/resume/schemas.d.ts +21 -0
  652. package/dist/tools/lifecycle/state/resume/schemas.d.ts.map +1 -0
  653. package/dist/tools/lifecycle/state/resume/schemas.js +19 -0
  654. package/dist/tools/lifecycle/state/resume/schemas.js.map +1 -0
  655. package/dist/tools/lifecycle/state/status/handler.d.ts +133 -0
  656. package/dist/tools/lifecycle/state/status/handler.d.ts.map +1 -0
  657. package/dist/tools/lifecycle/state/status/handler.js +232 -0
  658. package/dist/tools/lifecycle/state/status/handler.js.map +1 -0
  659. package/dist/tools/lifecycle/state/status/schemas.d.ts +85 -0
  660. package/dist/tools/lifecycle/state/status/schemas.d.ts.map +1 -0
  661. package/dist/tools/lifecycle/state/status/schemas.js +84 -0
  662. package/dist/tools/lifecycle/state/status/schemas.js.map +1 -0
  663. package/dist/tools/lifecycle/tool/schemas/index.d.ts +130 -0
  664. package/dist/tools/lifecycle/tool/schemas/index.d.ts.map +1 -0
  665. package/dist/tools/lifecycle/tool/schemas/index.js +66 -0
  666. package/dist/tools/lifecycle/tool/schemas/index.js.map +1 -0
  667. package/dist/tools/lifecycle/tool/session.tool.descriptor.d.ts +8 -0
  668. package/dist/tools/lifecycle/tool/session.tool.descriptor.d.ts.map +1 -0
  669. package/dist/tools/lifecycle/tool/session.tool.descriptor.js +44 -0
  670. package/dist/tools/lifecycle/tool/session.tool.descriptor.js.map +1 -0
  671. package/dist/tools/lifecycle/tool/session.variant.archive.impl.d.ts +7 -0
  672. package/dist/tools/lifecycle/tool/session.variant.archive.impl.d.ts.map +1 -0
  673. package/dist/tools/lifecycle/tool/session.variant.archive.impl.js +4 -0
  674. package/dist/tools/lifecycle/tool/session.variant.archive.impl.js.map +1 -0
  675. package/dist/tools/lifecycle/tool/session.variant.context.impl.d.ts +6 -0
  676. package/dist/tools/lifecycle/tool/session.variant.context.impl.d.ts.map +1 -0
  677. package/dist/tools/lifecycle/tool/session.variant.context.impl.js +4 -0
  678. package/dist/tools/lifecycle/tool/session.variant.context.impl.js.map +1 -0
  679. package/dist/tools/lifecycle/tool/session.variant.delete.impl.d.ts +7 -0
  680. package/dist/tools/lifecycle/tool/session.variant.delete.impl.d.ts.map +1 -0
  681. package/dist/tools/lifecycle/tool/session.variant.delete.impl.js +4 -0
  682. package/dist/tools/lifecycle/tool/session.variant.delete.impl.js.map +1 -0
  683. package/dist/tools/lifecycle/tool/session.variant.get.impl.d.ts +6 -0
  684. package/dist/tools/lifecycle/tool/session.variant.get.impl.d.ts.map +1 -0
  685. package/dist/tools/lifecycle/tool/session.variant.get.impl.js +4 -0
  686. package/dist/tools/lifecycle/tool/session.variant.get.impl.js.map +1 -0
  687. package/dist/tools/lifecycle/tool/session.variant.init.impl.cloud.d.ts +32 -0
  688. package/dist/tools/lifecycle/tool/session.variant.init.impl.cloud.d.ts.map +1 -0
  689. package/dist/tools/lifecycle/tool/session.variant.init.impl.cloud.js +6 -0
  690. package/dist/tools/lifecycle/tool/session.variant.init.impl.cloud.js.map +1 -0
  691. package/dist/tools/lifecycle/tool/session.variant.init.impl.consumer.d.ts +32 -0
  692. package/dist/tools/lifecycle/tool/session.variant.init.impl.consumer.d.ts.map +1 -0
  693. package/dist/tools/lifecycle/tool/session.variant.init.impl.consumer.js +4 -0
  694. package/dist/tools/lifecycle/tool/session.variant.init.impl.consumer.js.map +1 -0
  695. package/dist/tools/lifecycle/tool/session.variant.list.impl.d.ts +8 -0
  696. package/dist/tools/lifecycle/tool/session.variant.list.impl.d.ts.map +1 -0
  697. package/dist/tools/lifecycle/tool/session.variant.list.impl.js +4 -0
  698. package/dist/tools/lifecycle/tool/session.variant.list.impl.js.map +1 -0
  699. package/dist/tools/lifecycle/tool/session.variant.packet.impl.d.ts +9 -0
  700. package/dist/tools/lifecycle/tool/session.variant.packet.impl.d.ts.map +1 -0
  701. package/dist/tools/lifecycle/tool/session.variant.packet.impl.js +4 -0
  702. package/dist/tools/lifecycle/tool/session.variant.packet.impl.js.map +1 -0
  703. package/dist/tools/lifecycle/tool/session.variant.pause.impl.d.ts +6 -0
  704. package/dist/tools/lifecycle/tool/session.variant.pause.impl.d.ts.map +1 -0
  705. package/dist/tools/lifecycle/tool/session.variant.pause.impl.js +4 -0
  706. package/dist/tools/lifecycle/tool/session.variant.pause.impl.js.map +1 -0
  707. package/dist/tools/lifecycle/tool/session.variant.resume.impl.d.ts +6 -0
  708. package/dist/tools/lifecycle/tool/session.variant.resume.impl.d.ts.map +1 -0
  709. package/dist/tools/lifecycle/tool/session.variant.resume.impl.js +4 -0
  710. package/dist/tools/lifecycle/tool/session.variant.resume.impl.js.map +1 -0
  711. package/dist/tools/lifecycle/tool/session.variant.search.impl.d.ts +8 -0
  712. package/dist/tools/lifecycle/tool/session.variant.search.impl.d.ts.map +1 -0
  713. package/dist/tools/lifecycle/tool/session.variant.search.impl.js +4 -0
  714. package/dist/tools/lifecycle/tool/session.variant.search.impl.js.map +1 -0
  715. package/dist/tools/lifecycle/tool/session.variant.snapshot.impl.d.ts +6 -0
  716. package/dist/tools/lifecycle/tool/session.variant.snapshot.impl.d.ts.map +1 -0
  717. package/dist/tools/lifecycle/tool/session.variant.snapshot.impl.js +4 -0
  718. package/dist/tools/lifecycle/tool/session.variant.snapshot.impl.js.map +1 -0
  719. package/dist/tools/lifecycle/tool/session.variant.status.impl.d.ts +8 -0
  720. package/dist/tools/lifecycle/tool/session.variant.status.impl.d.ts.map +1 -0
  721. package/dist/tools/lifecycle/tool/session.variant.status.impl.js +4 -0
  722. package/dist/tools/lifecycle/tool/session.variant.status.impl.js.map +1 -0
  723. package/dist/tools/lifecycle/tool/session.variant.update.impl.d.ts +9 -0
  724. package/dist/tools/lifecycle/tool/session.variant.update.impl.d.ts.map +1 -0
  725. package/dist/tools/lifecycle/tool/session.variant.update.impl.js +4 -0
  726. package/dist/tools/lifecycle/tool/session.variant.update.impl.js.map +1 -0
  727. package/dist/tools/lifecycle/update/handler.d.ts +47 -0
  728. package/dist/tools/lifecycle/update/handler.d.ts.map +1 -0
  729. package/dist/tools/lifecycle/update/handler.js +17 -0
  730. package/dist/tools/lifecycle/update/handler.js.map +1 -0
  731. package/dist/tools/lifecycle/update/schemas.d.ts +24 -0
  732. package/dist/tools/lifecycle/update/schemas.d.ts.map +1 -0
  733. package/dist/tools/lifecycle/update/schemas.js +22 -0
  734. package/dist/tools/lifecycle/update/schemas.js.map +1 -0
  735. package/dist/tools/lifecycle/workspace-capabilities.consumer.d.ts +3 -0
  736. package/dist/tools/lifecycle/workspace-capabilities.consumer.d.ts.map +1 -0
  737. package/dist/tools/lifecycle/workspace-capabilities.consumer.js +3 -0
  738. package/dist/tools/lifecycle/workspace-capabilities.consumer.js.map +1 -0
  739. package/dist/tools/lifecycle/workspace-capabilities.d.ts +14 -0
  740. package/dist/tools/lifecycle/workspace-capabilities.d.ts.map +1 -0
  741. package/dist/tools/lifecycle/workspace-capabilities.js +38 -0
  742. package/dist/tools/lifecycle/workspace-capabilities.js.map +1 -0
  743. package/dist/tools/messages/delete/schemas.d.ts +37 -0
  744. package/dist/tools/messages/delete/schemas.d.ts.map +1 -0
  745. package/dist/tools/messages/delete/schemas.js +50 -0
  746. package/dist/tools/messages/delete/schemas.js.map +1 -0
  747. package/dist/tools/messages/handlers.d.ts +143 -0
  748. package/dist/tools/messages/handlers.d.ts.map +1 -0
  749. package/dist/tools/messages/handlers.js +562 -0
  750. package/dist/tools/messages/handlers.js.map +1 -0
  751. package/dist/tools/messages/list/schemas.d.ts +38 -0
  752. package/dist/tools/messages/list/schemas.d.ts.map +1 -0
  753. package/dist/tools/messages/list/schemas.js +40 -0
  754. package/dist/tools/messages/list/schemas.js.map +1 -0
  755. package/dist/tools/messages/mark-read/schemas.d.ts +63 -0
  756. package/dist/tools/messages/mark-read/schemas.d.ts.map +1 -0
  757. package/dist/tools/messages/mark-read/schemas.js +60 -0
  758. package/dist/tools/messages/mark-read/schemas.js.map +1 -0
  759. package/dist/tools/messages/send/schemas.d.ts +50 -0
  760. package/dist/tools/messages/send/schemas.d.ts.map +1 -0
  761. package/dist/tools/messages/send/schemas.js +61 -0
  762. package/dist/tools/messages/send/schemas.js.map +1 -0
  763. package/dist/tools/messages/tool/schemas/index.d.ts +70 -0
  764. package/dist/tools/messages/tool/schemas/index.d.ts.map +1 -0
  765. package/dist/tools/messages/tool/schemas/index.js +22 -0
  766. package/dist/tools/messages/tool/schemas/index.js.map +1 -0
  767. package/dist/tools/messages/tool/session-collaborate.descriptor.d.ts +8 -0
  768. package/dist/tools/messages/tool/session-collaborate.descriptor.d.ts.map +1 -0
  769. package/dist/tools/messages/tool/session-collaborate.descriptor.js +32 -0
  770. package/dist/tools/messages/tool/session-collaborate.descriptor.js.map +1 -0
  771. package/dist/tools/messages/tool/session-collaborate.variant.delete.impl.d.ts +10 -0
  772. package/dist/tools/messages/tool/session-collaborate.variant.delete.impl.d.ts.map +1 -0
  773. package/dist/tools/messages/tool/session-collaborate.variant.delete.impl.js +4 -0
  774. package/dist/tools/messages/tool/session-collaborate.variant.delete.impl.js.map +1 -0
  775. package/dist/tools/messages/tool/session-collaborate.variant.list.impl.d.ts +12 -0
  776. package/dist/tools/messages/tool/session-collaborate.variant.list.impl.d.ts.map +1 -0
  777. package/dist/tools/messages/tool/session-collaborate.variant.list.impl.js +4 -0
  778. package/dist/tools/messages/tool/session-collaborate.variant.list.impl.js.map +1 -0
  779. package/dist/tools/messages/tool/session-collaborate.variant.mark-read.impl.d.ts +11 -0
  780. package/dist/tools/messages/tool/session-collaborate.variant.mark-read.impl.d.ts.map +1 -0
  781. package/dist/tools/messages/tool/session-collaborate.variant.mark-read.impl.js +4 -0
  782. package/dist/tools/messages/tool/session-collaborate.variant.mark-read.impl.js.map +1 -0
  783. package/dist/tools/messages/tool/session-collaborate.variant.send.impl.d.ts +18 -0
  784. package/dist/tools/messages/tool/session-collaborate.variant.send.impl.d.ts.map +1 -0
  785. package/dist/tools/messages/tool/session-collaborate.variant.send.impl.js +4 -0
  786. package/dist/tools/messages/tool/session-collaborate.variant.send.impl.js.map +1 -0
  787. package/dist/tools/scratchpad/delete/schemas.d.ts +18 -0
  788. package/dist/tools/scratchpad/delete/schemas.d.ts.map +1 -0
  789. package/dist/tools/scratchpad/delete/schemas.js +16 -0
  790. package/dist/tools/scratchpad/delete/schemas.js.map +1 -0
  791. package/dist/tools/scratchpad/handlers.d.ts +88 -0
  792. package/dist/tools/scratchpad/handlers.d.ts.map +1 -0
  793. package/dist/tools/scratchpad/handlers.js +189 -0
  794. package/dist/tools/scratchpad/handlers.js.map +1 -0
  795. package/dist/tools/scratchpad/list/schemas.d.ts +34 -0
  796. package/dist/tools/scratchpad/list/schemas.d.ts.map +1 -0
  797. package/dist/tools/scratchpad/list/schemas.js +27 -0
  798. package/dist/tools/scratchpad/list/schemas.js.map +1 -0
  799. package/dist/tools/scratchpad/read/schemas.d.ts +34 -0
  800. package/dist/tools/scratchpad/read/schemas.d.ts.map +1 -0
  801. package/dist/tools/scratchpad/read/schemas.js +34 -0
  802. package/dist/tools/scratchpad/read/schemas.js.map +1 -0
  803. package/dist/tools/scratchpad/search/schemas.d.ts +28 -0
  804. package/dist/tools/scratchpad/search/schemas.d.ts.map +1 -0
  805. package/dist/tools/scratchpad/search/schemas.js +26 -0
  806. package/dist/tools/scratchpad/search/schemas.js.map +1 -0
  807. package/dist/tools/scratchpad/tool/schemas/index.d.ts +102 -0
  808. package/dist/tools/scratchpad/tool/schemas/index.d.ts.map +1 -0
  809. package/dist/tools/scratchpad/tool/schemas/index.js +42 -0
  810. package/dist/tools/scratchpad/tool/schemas/index.js.map +1 -0
  811. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.d.ts +8 -0
  812. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.d.ts.map +1 -0
  813. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.js +36 -0
  814. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.js.map +1 -0
  815. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-delete.impl.d.ts +9 -0
  816. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-delete.impl.d.ts.map +1 -0
  817. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-delete.impl.js +4 -0
  818. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-delete.impl.js.map +1 -0
  819. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-read.impl.d.ts +8 -0
  820. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-read.impl.d.ts.map +1 -0
  821. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-read.impl.js +4 -0
  822. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-read.impl.js.map +1 -0
  823. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-write.impl.d.ts +14 -0
  824. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-write.impl.d.ts.map +1 -0
  825. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-write.impl.js +4 -0
  826. package/dist/tools/scratchpad/tool/session-scratchpad.variant.batch-write.impl.js.map +1 -0
  827. package/dist/tools/scratchpad/tool/session-scratchpad.variant.delete.impl.d.ts +9 -0
  828. package/dist/tools/scratchpad/tool/session-scratchpad.variant.delete.impl.d.ts.map +1 -0
  829. package/dist/tools/scratchpad/tool/session-scratchpad.variant.delete.impl.js +4 -0
  830. package/dist/tools/scratchpad/tool/session-scratchpad.variant.delete.impl.js.map +1 -0
  831. package/dist/tools/scratchpad/tool/session-scratchpad.variant.list.impl.d.ts +9 -0
  832. package/dist/tools/scratchpad/tool/session-scratchpad.variant.list.impl.d.ts.map +1 -0
  833. package/dist/tools/scratchpad/tool/session-scratchpad.variant.list.impl.js +4 -0
  834. package/dist/tools/scratchpad/tool/session-scratchpad.variant.list.impl.js.map +1 -0
  835. package/dist/tools/scratchpad/tool/session-scratchpad.variant.read.impl.d.ts +8 -0
  836. package/dist/tools/scratchpad/tool/session-scratchpad.variant.read.impl.d.ts.map +1 -0
  837. package/dist/tools/scratchpad/tool/session-scratchpad.variant.read.impl.js +4 -0
  838. package/dist/tools/scratchpad/tool/session-scratchpad.variant.read.impl.js.map +1 -0
  839. package/dist/tools/scratchpad/tool/session-scratchpad.variant.search.impl.d.ts +8 -0
  840. package/dist/tools/scratchpad/tool/session-scratchpad.variant.search.impl.d.ts.map +1 -0
  841. package/dist/tools/scratchpad/tool/session-scratchpad.variant.search.impl.js +4 -0
  842. package/dist/tools/scratchpad/tool/session-scratchpad.variant.search.impl.js.map +1 -0
  843. package/dist/tools/scratchpad/tool/session-scratchpad.variant.write.impl.d.ts +14 -0
  844. package/dist/tools/scratchpad/tool/session-scratchpad.variant.write.impl.d.ts.map +1 -0
  845. package/dist/tools/scratchpad/tool/session-scratchpad.variant.write.impl.js +4 -0
  846. package/dist/tools/scratchpad/tool/session-scratchpad.variant.write.impl.js.map +1 -0
  847. package/dist/tools/scratchpad/write/schemas/index.d.ts +47 -0
  848. package/dist/tools/scratchpad/write/schemas/index.d.ts.map +1 -0
  849. package/dist/tools/scratchpad/write/schemas/index.js +26 -0
  850. package/dist/tools/scratchpad/write/schemas/index.js.map +1 -0
  851. package/dist/tools/todos/add/handler.d.ts +37 -0
  852. package/dist/tools/todos/add/handler.d.ts.map +1 -0
  853. package/dist/tools/todos/add/handler.js +59 -0
  854. package/dist/tools/todos/add/handler.js.map +1 -0
  855. package/dist/tools/todos/add/schemas.d.ts +92 -0
  856. package/dist/tools/todos/add/schemas.d.ts.map +1 -0
  857. package/dist/tools/todos/add/schemas.js +59 -0
  858. package/dist/tools/todos/add/schemas.js.map +1 -0
  859. package/dist/tools/todos/batch-add/handler.d.ts +21 -0
  860. package/dist/tools/todos/batch-add/handler.d.ts.map +1 -0
  861. package/dist/tools/todos/batch-add/handler.js +46 -0
  862. package/dist/tools/todos/batch-add/handler.js.map +1 -0
  863. package/dist/tools/todos/batch-add/schemas.d.ts +65 -0
  864. package/dist/tools/todos/batch-add/schemas.d.ts.map +1 -0
  865. package/dist/tools/todos/batch-add/schemas.js +47 -0
  866. package/dist/tools/todos/batch-add/schemas.js.map +1 -0
  867. package/dist/tools/todos/batch-update/handler.d.ts +4 -0
  868. package/dist/tools/todos/batch-update/handler.d.ts.map +1 -0
  869. package/dist/tools/todos/batch-update/handler.js +25 -0
  870. package/dist/tools/todos/batch-update/handler.js.map +1 -0
  871. package/dist/tools/todos/batch-update/schemas.d.ts +35 -0
  872. package/dist/tools/todos/batch-update/schemas.d.ts.map +1 -0
  873. package/dist/tools/todos/batch-update/schemas.js +22 -0
  874. package/dist/tools/todos/batch-update/schemas.js.map +1 -0
  875. package/dist/tools/todos/complete/handler.d.ts +22 -0
  876. package/dist/tools/todos/complete/handler.d.ts.map +1 -0
  877. package/dist/tools/todos/complete/handler.js +76 -0
  878. package/dist/tools/todos/complete/handler.js.map +1 -0
  879. package/dist/tools/todos/complete/schemas.d.ts +27 -0
  880. package/dist/tools/todos/complete/schemas.d.ts.map +1 -0
  881. package/dist/tools/todos/complete/schemas.js +25 -0
  882. package/dist/tools/todos/complete/schemas.js.map +1 -0
  883. package/dist/tools/todos/delete/handler.d.ts +4 -0
  884. package/dist/tools/todos/delete/handler.d.ts.map +1 -0
  885. package/dist/tools/todos/delete/handler.js +58 -0
  886. package/dist/tools/todos/delete/handler.js.map +1 -0
  887. package/dist/tools/todos/delete/schemas.d.ts +25 -0
  888. package/dist/tools/todos/delete/schemas.d.ts.map +1 -0
  889. package/dist/tools/todos/delete/schemas.js +23 -0
  890. package/dist/tools/todos/delete/schemas.js.map +1 -0
  891. package/dist/tools/todos/get/handler.d.ts +45 -0
  892. package/dist/tools/todos/get/handler.d.ts.map +1 -0
  893. package/dist/tools/todos/get/handler.js +10 -0
  894. package/dist/tools/todos/get/handler.js.map +1 -0
  895. package/dist/tools/todos/get/schemas.d.ts +17 -0
  896. package/dist/tools/todos/get/schemas.d.ts.map +1 -0
  897. package/dist/tools/todos/get/schemas.js +15 -0
  898. package/dist/tools/todos/get/schemas.js.map +1 -0
  899. package/dist/tools/todos/list/handler.d.ts +58 -0
  900. package/dist/tools/todos/list/handler.d.ts.map +1 -0
  901. package/dist/tools/todos/list/handler.js +91 -0
  902. package/dist/tools/todos/list/handler.js.map +1 -0
  903. package/dist/tools/todos/list/schemas.d.ts +40 -0
  904. package/dist/tools/todos/list/schemas.d.ts.map +1 -0
  905. package/dist/tools/todos/list/schemas.js +34 -0
  906. package/dist/tools/todos/list/schemas.js.map +1 -0
  907. package/dist/tools/todos/project-ref.d.ts +19 -0
  908. package/dist/tools/todos/project-ref.d.ts.map +1 -0
  909. package/dist/tools/todos/project-ref.js +37 -0
  910. package/dist/tools/todos/project-ref.js.map +1 -0
  911. package/dist/tools/todos/tool/schemas/index.d.ts +219 -0
  912. package/dist/tools/todos/tool/schemas/index.d.ts.map +1 -0
  913. package/dist/tools/todos/tool/schemas/index.js +56 -0
  914. package/dist/tools/todos/tool/schemas/index.js.map +1 -0
  915. package/dist/tools/todos/tool/session-todo.descriptor.d.ts +8 -0
  916. package/dist/tools/todos/tool/session-todo.descriptor.d.ts.map +1 -0
  917. package/dist/tools/todos/tool/session-todo.descriptor.js +39 -0
  918. package/dist/tools/todos/tool/session-todo.descriptor.js.map +1 -0
  919. package/dist/tools/todos/tool/session-todo.variant.add.impl.d.ts +40 -0
  920. package/dist/tools/todos/tool/session-todo.variant.add.impl.d.ts.map +1 -0
  921. package/dist/tools/todos/tool/session-todo.variant.add.impl.js +4 -0
  922. package/dist/tools/todos/tool/session-todo.variant.add.impl.js.map +1 -0
  923. package/dist/tools/todos/tool/session-todo.variant.batch-add.impl.d.ts +24 -0
  924. package/dist/tools/todos/tool/session-todo.variant.batch-add.impl.d.ts.map +1 -0
  925. package/dist/tools/todos/tool/session-todo.variant.batch-add.impl.js +4 -0
  926. package/dist/tools/todos/tool/session-todo.variant.batch-add.impl.js.map +1 -0
  927. package/dist/tools/todos/tool/session-todo.variant.batch-complete.impl.d.ts +9 -0
  928. package/dist/tools/todos/tool/session-todo.variant.batch-complete.impl.d.ts.map +1 -0
  929. package/dist/tools/todos/tool/session-todo.variant.batch-complete.impl.js +4 -0
  930. package/dist/tools/todos/tool/session-todo.variant.batch-complete.impl.js.map +1 -0
  931. package/dist/tools/todos/tool/session-todo.variant.batch-delete.impl.d.ts +10 -0
  932. package/dist/tools/todos/tool/session-todo.variant.batch-delete.impl.d.ts.map +1 -0
  933. package/dist/tools/todos/tool/session-todo.variant.batch-delete.impl.js +4 -0
  934. package/dist/tools/todos/tool/session-todo.variant.batch-delete.impl.js.map +1 -0
  935. package/dist/tools/todos/tool/session-todo.variant.batch-update.impl.d.ts +9 -0
  936. package/dist/tools/todos/tool/session-todo.variant.batch-update.impl.d.ts.map +1 -0
  937. package/dist/tools/todos/tool/session-todo.variant.batch-update.impl.js +4 -0
  938. package/dist/tools/todos/tool/session-todo.variant.batch-update.impl.js.map +1 -0
  939. package/dist/tools/todos/tool/session-todo.variant.complete.impl.d.ts +9 -0
  940. package/dist/tools/todos/tool/session-todo.variant.complete.impl.d.ts.map +1 -0
  941. package/dist/tools/todos/tool/session-todo.variant.complete.impl.js +4 -0
  942. package/dist/tools/todos/tool/session-todo.variant.complete.impl.js.map +1 -0
  943. package/dist/tools/todos/tool/session-todo.variant.delete.impl.d.ts +10 -0
  944. package/dist/tools/todos/tool/session-todo.variant.delete.impl.d.ts.map +1 -0
  945. package/dist/tools/todos/tool/session-todo.variant.delete.impl.js +4 -0
  946. package/dist/tools/todos/tool/session-todo.variant.delete.impl.js.map +1 -0
  947. package/dist/tools/todos/tool/session-todo.variant.get.impl.d.ts +6 -0
  948. package/dist/tools/todos/tool/session-todo.variant.get.impl.d.ts.map +1 -0
  949. package/dist/tools/todos/tool/session-todo.variant.get.impl.js +4 -0
  950. package/dist/tools/todos/tool/session-todo.variant.get.impl.js.map +1 -0
  951. package/dist/tools/todos/tool/session-todo.variant.list.impl.d.ts +14 -0
  952. package/dist/tools/todos/tool/session-todo.variant.list.impl.d.ts.map +1 -0
  953. package/dist/tools/todos/tool/session-todo.variant.list.impl.js +4 -0
  954. package/dist/tools/todos/tool/session-todo.variant.list.impl.js.map +1 -0
  955. package/dist/tools/todos/tool/session-todo.variant.tree.impl.d.ts +7 -0
  956. package/dist/tools/todos/tool/session-todo.variant.tree.impl.d.ts.map +1 -0
  957. package/dist/tools/todos/tool/session-todo.variant.tree.impl.js +4 -0
  958. package/dist/tools/todos/tool/session-todo.variant.tree.impl.js.map +1 -0
  959. package/dist/tools/todos/tool/session-todo.variant.update.impl.d.ts +21 -0
  960. package/dist/tools/todos/tool/session-todo.variant.update.impl.d.ts.map +1 -0
  961. package/dist/tools/todos/tool/session-todo.variant.update.impl.js +4 -0
  962. package/dist/tools/todos/tool/session-todo.variant.update.impl.js.map +1 -0
  963. package/dist/tools/todos/tree/handler.d.ts +20 -0
  964. package/dist/tools/todos/tree/handler.d.ts.map +1 -0
  965. package/dist/tools/todos/tree/handler.js +41 -0
  966. package/dist/tools/todos/tree/handler.js.map +1 -0
  967. package/dist/tools/todos/tree/schemas.d.ts +21 -0
  968. package/dist/tools/todos/tree/schemas.d.ts.map +1 -0
  969. package/dist/tools/todos/tree/schemas.js +19 -0
  970. package/dist/tools/todos/tree/schemas.js.map +1 -0
  971. package/dist/tools/todos/update/handler.d.ts +54 -0
  972. package/dist/tools/todos/update/handler.d.ts.map +1 -0
  973. package/dist/tools/todos/update/handler.js +60 -0
  974. package/dist/tools/todos/update/handler.js.map +1 -0
  975. package/dist/tools/todos/update/schemas.d.ts +57 -0
  976. package/dist/tools/todos/update/schemas.d.ts.map +1 -0
  977. package/dist/tools/todos/update/schemas.js +35 -0
  978. package/dist/tools/todos/update/schemas.js.map +1 -0
  979. package/dist/tools/tool-runtime.d.ts +17 -0
  980. package/dist/tools/tool-runtime.d.ts.map +1 -0
  981. package/dist/tools/tool-runtime.js +39 -0
  982. package/dist/tools/tool-runtime.js.map +1 -0
  983. package/dist/tools/workspace/handlers.d.ts +75 -0
  984. package/dist/tools/workspace/handlers.d.ts.map +1 -0
  985. package/dist/tools/workspace/handlers.js +154 -0
  986. package/dist/tools/workspace/handlers.js.map +1 -0
  987. package/dist/tools/workspace/tool/schemas/index.d.ts +47 -0
  988. package/dist/tools/workspace/tool/schemas/index.d.ts.map +1 -0
  989. package/dist/tools/workspace/tool/schemas/index.js +33 -0
  990. package/dist/tools/workspace/tool/schemas/index.js.map +1 -0
  991. package/dist/tools/workspace/tool/session-workspace.descriptor.d.ts +8 -0
  992. package/dist/tools/workspace/tool/session-workspace.descriptor.d.ts.map +1 -0
  993. package/dist/tools/workspace/tool/session-workspace.descriptor.js +31 -0
  994. package/dist/tools/workspace/tool/session-workspace.descriptor.js.map +1 -0
  995. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.d.ts +5 -0
  996. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.d.ts.map +1 -0
  997. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.js +7 -0
  998. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.js.map +1 -0
  999. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.d.ts +8 -0
  1000. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.d.ts.map +1 -0
  1001. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.js +4 -0
  1002. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.js.map +1 -0
  1003. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.d.ts +6 -0
  1004. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.d.ts.map +1 -0
  1005. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.js +4 -0
  1006. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.js.map +1 -0
  1007. package/dist/tui/components/SessionDetailView.d.ts +17 -0
  1008. package/dist/tui/components/SessionDetailView.d.ts.map +1 -0
  1009. package/dist/tui/components/SessionDetailView.js +229 -0
  1010. package/dist/tui/components/SessionDetailView.js.map +1 -0
  1011. package/dist/tui/index.d.ts +7 -0
  1012. package/dist/tui/index.d.ts.map +1 -0
  1013. package/dist/tui/index.js +7 -0
  1014. package/dist/tui/index.js.map +1 -0
  1015. package/dist/tui/session-actions.d.ts +20 -0
  1016. package/dist/tui/session-actions.d.ts.map +1 -0
  1017. package/dist/tui/session-actions.js +27 -0
  1018. package/dist/tui/session-actions.js.map +1 -0
  1019. package/dist/tui/session-tui.plugin.d.ts +10 -0
  1020. package/dist/tui/session-tui.plugin.d.ts.map +1 -0
  1021. package/dist/tui/session-tui.plugin.js +29 -0
  1022. package/dist/tui/session-tui.plugin.js.map +1 -0
  1023. package/dist/tui/sessions-open.descriptor.d.ts +9 -0
  1024. package/dist/tui/sessions-open.descriptor.d.ts.map +1 -0
  1025. package/dist/tui/sessions-open.descriptor.js +18 -0
  1026. package/dist/tui/sessions-open.descriptor.js.map +1 -0
  1027. package/dist/tui/sessions-open.impl.consumer.d.ts +9 -0
  1028. package/dist/tui/sessions-open.impl.consumer.d.ts.map +1 -0
  1029. package/dist/tui/sessions-open.impl.consumer.js +13 -0
  1030. package/dist/tui/sessions-open.impl.consumer.js.map +1 -0
  1031. package/dist/tui/sessions-recovery.d.ts +14 -0
  1032. package/dist/tui/sessions-recovery.d.ts.map +1 -0
  1033. package/dist/tui/sessions-recovery.js +23 -0
  1034. package/dist/tui/sessions-recovery.js.map +1 -0
  1035. package/dist/tui/sessions-registry.descriptor.d.ts +11 -0
  1036. package/dist/tui/sessions-registry.descriptor.d.ts.map +1 -0
  1037. package/dist/tui/sessions-registry.descriptor.js +17 -0
  1038. package/dist/tui/sessions-registry.descriptor.js.map +1 -0
  1039. package/dist/tui/sessions-registry.impl.consumer.d.ts +15 -0
  1040. package/dist/tui/sessions-registry.impl.consumer.d.ts.map +1 -0
  1041. package/dist/tui/sessions-registry.impl.consumer.js +79 -0
  1042. package/dist/tui/sessions-registry.impl.consumer.js.map +1 -0
  1043. package/dist/tui/sessions-registry.state-key.d.ts +32 -0
  1044. package/dist/tui/sessions-registry.state-key.d.ts.map +1 -0
  1045. package/dist/tui/sessions-registry.state-key.js +8 -0
  1046. package/dist/tui/sessions-registry.state-key.js.map +1 -0
  1047. package/dist/tui/sessions.descriptor.d.ts +10 -0
  1048. package/dist/tui/sessions.descriptor.d.ts.map +1 -0
  1049. package/dist/tui/sessions.descriptor.js +25 -0
  1050. package/dist/tui/sessions.descriptor.js.map +1 -0
  1051. package/dist/tui/sessions.impl.consumer.d.ts +4 -0
  1052. package/dist/tui/sessions.impl.consumer.d.ts.map +1 -0
  1053. package/dist/tui/sessions.impl.consumer.js +284 -0
  1054. package/dist/tui/sessions.impl.consumer.js.map +1 -0
  1055. package/dist/tui/sessions.logic.d.ts +33 -0
  1056. package/dist/tui/sessions.logic.d.ts.map +1 -0
  1057. package/dist/tui/sessions.logic.js +94 -0
  1058. package/dist/tui/sessions.logic.js.map +1 -0
  1059. package/package.json +273 -4
@@ -0,0 +1,2669 @@
1
+ /**
2
+ * Session Manager
3
+ * Core business logic for session management
4
+ *
5
+ * Responsibilities:
6
+ * - Session lifecycle management (create, resume, pause, archive)
7
+ * - TODO tracking with dependencies and hierarchies
8
+ * - Scratchpad operations with expiration
9
+ * - Job reference tracking
10
+ * - Participant messaging
11
+ * - Knowledge context integration
12
+ * - Participant identification and connection tracking
13
+ */
14
+ import * as z from 'zod/v4';
15
+ import { sha256Hex } from '@vibesdotdev/shared/hashing';
16
+ import { CapabilityError, CaughtFailureSchema, valueFreeFailure } from '@vibesdotdev/kernel';
17
+ import { getVibesClient } from '@vibesdotdev/kernel/client';
18
+ import { JsonValueSchema as JsonSchema } from '@vibesdotdev/kernel/schemas/json';
19
+ import { getLogger } from '@vibesdotdev/logging';
20
+ import { StorageRecord } from '@vibesdotdev/storage/record';
21
+ import { storageFailureCondition } from '@vibesdotdev/storage/record/failure-condition';
22
+ import { ScratchpadContentTypeSchema } from "./schemas/index.js";
23
+ import { HarnessSeatRegistrationSchema, HarnessSeatSchema, harnessSeatParticipantId, harnessSeatSlug } from "./schemas/harness-seat.js";
24
+ import { ensureSessionContextWithManager, getOrCreateMCPParticipant, resolveParticipantReference } from "./mcp/utils/participant.js";
25
+ import { emitSessionBound } from "./events/session.bound.event.descriptor.js";
26
+ const SESSION_STATE_LEGACY_KEY = 'session-state';
27
+ const SESSION_STATE_DEFAULT_KEY = 'default';
28
+ const SESSION_STATE_PARTICIPANT_PREFIX = 'participant:';
29
+ const SESSION_STATE_MCP_PREFIX = 'mcp:';
30
+ /** A participant seen within this window is active (the CLI roster uses the same window). */
31
+ const PARTICIPANT_ACTIVE_WINDOW_MS = 5 * 60 * 1000;
32
+ /** The runtime client id notifications register their delivery client under. */
33
+ const NOTIFICATIONS_CLIENT_ID = 'notifications.delivery';
34
+ /**
35
+ * Whether a storage write lost a unique-index race. LocalDB's driver classifies
36
+ * the collision on its typed refusal (`unique-constraint`); the adapter's text
37
+ * is never read. A conflict another adapter reports untyped is not recognised
38
+ * here and propagates (cross-lane: storage has no adapter-neutral conflict code).
39
+ */
40
+ function isUniqueConstraintError(failure) {
41
+ return storageFailureCondition(failure) === 'unique-constraint';
42
+ }
43
+ /**
44
+ * Run `write`; when it fails with a failure `recoverable` admits, run
45
+ * `recover` instead. Any other failure of `write`, and a recovery that finds
46
+ * nothing (`null`), rethrows the write's own failure; a recovery that throws
47
+ * carries its own failure. The write's failure is never dropped.
48
+ */
49
+ async function writeOrRecover(write, recover, recoverable) {
50
+ let lost;
51
+ const steps = [
52
+ write,
53
+ async () => {
54
+ if (!recoverable(CaughtFailureSchema.parse(lost)))
55
+ throw lost;
56
+ const recovered = await recover();
57
+ if (recovered === null)
58
+ throw lost;
59
+ return recovered;
60
+ }
61
+ ];
62
+ for (const step of steps) {
63
+ try {
64
+ return await step();
65
+ }
66
+ catch (error) {
67
+ lost = error;
68
+ }
69
+ }
70
+ throw lost;
71
+ }
72
+ /** Any write failure admits a recovery read (the recovery itself confirms the race). */
73
+ function anyFailure() {
74
+ return true;
75
+ }
76
+ /** Decode JSON text; a parse failure is returned as a value with its cause, never thrown. */
77
+ function decodeJson(text) {
78
+ try {
79
+ return { ok: true, value: JsonSchema.parse(JSON.parse(text)) };
80
+ }
81
+ catch (error) {
82
+ return { ok: false, cause: CaughtFailureSchema.parse(error) };
83
+ }
84
+ }
85
+ /** A persisted routing row's value: each field read independently; a non-string id is no id. */
86
+ const RoutingValueSchema = z.object({
87
+ sessionId: z.string().nullable().optional().catch(undefined),
88
+ participantId: z.string().nullable().optional().catch(undefined)
89
+ });
90
+ /** The retired aggregate `session-state` row: each map entry is kept only when both sides are strings. */
91
+ const StringMapSchema = z
92
+ .record(z.string(), z.unknown())
93
+ .optional()
94
+ .catch(undefined)
95
+ .transform((entries) => {
96
+ const kept = new Map();
97
+ for (const [key, value] of Object.entries(entries ?? {})) {
98
+ const parsed = z.string().safeParse(value);
99
+ if (parsed.success)
100
+ kept.set(key, parsed.data);
101
+ }
102
+ return kept;
103
+ });
104
+ const LegacySessionStateSchema = z.object({
105
+ participantSessions: StringMapSchema,
106
+ mcpSessions: StringMapSchema,
107
+ defaultSessionId: z.string().nullable().optional().catch(undefined),
108
+ defaultParticipantId: z.string().nullable().optional().catch(undefined)
109
+ });
110
+ /** Whether a decoded JSON value is an object (not an array or a scalar). */
111
+ function isJsonObject(value) {
112
+ return Object(value) === value && !Array.isArray(value);
113
+ }
114
+ /** The list/search projection of one scratchpad entry. */
115
+ function scratchpadSummaryOf(entry) {
116
+ return {
117
+ id: entry.id,
118
+ key: entry.key,
119
+ tags: entry.tags ?? [],
120
+ contentType: entry.contentType,
121
+ contentLength: entry.content?.length ?? 0,
122
+ preview: entry.content?.substring(0, 100) ?? '',
123
+ createdAt: entry.createdAt,
124
+ updatedAt: entry.updatedAt,
125
+ language: entry.language,
126
+ expiresAt: entry.expiresAt
127
+ };
128
+ }
129
+ /** A typed `NOT_FOUND session-not-found` for a session id. */
130
+ function sessionNotFound(sessionId) {
131
+ return new CapabilityError(`Session ${sessionId} not found`, 'NOT_FOUND', {
132
+ reason: 'session-not-found',
133
+ details: { kind: 'session', id: sessionId }
134
+ });
135
+ }
136
+ /** A typed `NOT_FOUND todo-not-found` for a todo id. */
137
+ function todoNotFound(todoId) {
138
+ return new CapabilityError(`TODO ${todoId} not found`, 'NOT_FOUND', {
139
+ reason: 'todo-not-found',
140
+ details: { kind: 'session.todo', id: todoId }
141
+ });
142
+ }
143
+ /** A typed `CONFLICT session-not-writable` for a session that is paused or archived. */
144
+ function sessionNotWritable(sessionId, status) {
145
+ return new CapabilityError(`Session ${sessionId} is ${status}; resume it explicitly before writing to it.`, 'CONFLICT', { reason: 'session-not-writable', details: { sessionId, status } });
146
+ }
147
+ /** Default todo order; the id tiebreak keeps the pages of an exhaustive scan stable. */
148
+ const TODO_ORDER = { priority: 'desc', createdAt: 'asc', id: 'asc' };
149
+ /** Page size of an exhaustive todo scan. */
150
+ const TODO_SCAN_PAGE = 1000;
151
+ /** A work item id as the todo record stores it. */
152
+ const WorkItemIdSchema = z.string().min(1);
153
+ /** Whether the filters carry a dependency predicate, which storage cannot evaluate. */
154
+ function hasDependencyFilter(filters) {
155
+ return filters?.hasDependencies !== undefined || filters?.isBlocked !== undefined;
156
+ }
157
+ /** A storage search total: a finite count, or absent. */
158
+ const SearchTotalSchema = z.number().int().nonnegative();
159
+ /** The display fields a session summary reads from metadata; a non-string value is no value. */
160
+ const SessionDisplayMetadataSchema = z.object({
161
+ workspacePath: z.string().optional().catch(undefined),
162
+ workItemTitle: z.string().optional().catch(undefined),
163
+ gitBranch: z.string().optional().catch(undefined)
164
+ });
165
+ /**
166
+ * Key-order-independent JSON so metadata comparison can't flake on insertion
167
+ * order: every object's keys are emitted sorted, arrays keep their order, and
168
+ * `undefined` encodes as `null`.
169
+ */
170
+ export function canonicalJson(value) {
171
+ return JSON.stringify(value ?? null, (_key, nested) => sortedObjectKeys(nested));
172
+ }
173
+ /** An object (not an array) re-created with its own keys in sorted order; any other value unchanged. */
174
+ function sortedObjectKeys(nested) {
175
+ if (Object(nested) !== nested || Array.isArray(nested))
176
+ return nested;
177
+ const entries = Object.entries(Object(nested));
178
+ entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
179
+ return Object.fromEntries(entries);
180
+ }
181
+ /**
182
+ * Keep the indexed Work item id and its typed execution edge inseparable.
183
+ * A Work-linked todo always carries exactly one canonical work-item edge.
184
+ */
185
+ export function normalizeTodoWorkLink(todo, options = {}) {
186
+ const workLinks = todo.links?.filter((link) => link.kind === 'work-item') ?? [];
187
+ if (todo.workItemId === undefined) {
188
+ if (workLinks.length > 0)
189
+ throw new Error('Todo work-item links require workItemId.');
190
+ return todo;
191
+ }
192
+ if (!options.rewriteExisting && workLinks.some((link) => link.id !== todo.workItemId)) {
193
+ throw new Error('Todo workItemId must match every work-item link.');
194
+ }
195
+ return {
196
+ ...todo,
197
+ links: [
198
+ ...(todo.links ?? []).filter((link) => link.kind !== 'work-item'),
199
+ { kind: 'work-item', id: todo.workItemId, source: 'session', role: 'executes' }
200
+ ]
201
+ };
202
+ }
203
+ /** Attach one authoritative checkout project edge to every Work-linked todo. */
204
+ export function scopeTodoWorkLinks(todo, projectId) {
205
+ const normalized = normalizeTodoWorkLink(todo);
206
+ const workLinked = normalized.workItemId !== undefined ||
207
+ (normalized.links ?? []).some((link) => link.kind === 'work-item');
208
+ if (!workLinked)
209
+ return normalized;
210
+ if (!projectId) {
211
+ throw new CapabilityError('PROJECT_REF_UNBOUND: Work-linked session todos require a project-bound session', 'CONFLICT', {
212
+ reason: 'project-ref-unbound',
213
+ recoveryHint: 'Bind the session to a project or update the todo without Work links.',
214
+ details: { sessionProjectId: null, sessionCode: 'PROJECT_REF_UNBOUND' }
215
+ });
216
+ }
217
+ const projectLinks = (normalized.links ?? []).filter((link) => link.kind === 'project' && link.role === 'belongs-to');
218
+ const conflict = projectLinks.find((link) => link.id.toLowerCase() !== projectId.toLowerCase());
219
+ if (conflict) {
220
+ throw new CapabilityError(`PROJECT_REF_CONFLICT: todo is linked to ${conflict.id}, not session project ${projectId}`, 'CONFLICT', {
221
+ reason: 'project-ref-conflict',
222
+ recoveryHint: 'Ensure the todo project link matches the session project.',
223
+ details: {
224
+ sessionProjectId: projectId,
225
+ linkedProjectId: conflict.id,
226
+ sessionCode: 'PROJECT_REF_CONFLICT'
227
+ }
228
+ });
229
+ }
230
+ return {
231
+ ...normalized,
232
+ links: [
233
+ ...(normalized.links ?? []).filter((link) => !(link.kind === 'project' && link.role === 'belongs-to')),
234
+ {
235
+ kind: 'project',
236
+ id: projectId.toLowerCase(),
237
+ source: 'session.project-ref',
238
+ role: 'belongs-to'
239
+ }
240
+ ]
241
+ };
242
+ }
243
+ /**
244
+ * Does a durable row match the send a caller intended under the same
245
+ * deterministic id? Compares every caller-controlled field. A mismatch means
246
+ * the caller reused a batch identity for DIFFERENT content — that must
247
+ * surface as a conflict, never replay as a false "delivered".
248
+ */
249
+ function messagePayloadMatches(existing, sessionId, intended) {
250
+ return (existing.sessionId === sessionId &&
251
+ existing.fromParticipant === intended.fromParticipant &&
252
+ existing.toParticipant === intended.toParticipant &&
253
+ existing.messageType === intended.messageType &&
254
+ existing.content === intended.content &&
255
+ canonicalJson(existing.metadata ?? {}) === canonicalJson(intended.metadata ?? {}));
256
+ }
257
+ const FIXTURE_PARTICIPANT_NAMES = new Set([
258
+ 'senderx',
259
+ 'betax',
260
+ 'alphx',
261
+ 'alpha',
262
+ 'beta',
263
+ 'ownerx',
264
+ 'all',
265
+ 'Durable owner',
266
+ 'solox',
267
+ 'everyone',
268
+ 'realmember',
269
+ 'stats-sender',
270
+ 'stats-seat-b',
271
+ 'stats-seat-a',
272
+ 'activity-sender',
273
+ 'ghostx',
274
+ 'joinerx'
275
+ ]);
276
+ const UUID_LIKE_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
277
+ function isFixtureProfile(profile) {
278
+ if (FIXTURE_PARTICIPANT_NAMES.has(profile.name))
279
+ return true;
280
+ if (profile.participantId.startsWith('participant-') && profile.participantId.length > 12)
281
+ return true;
282
+ if (profile.metadata?.autoRegistered === true && UUID_LIKE_PATTERN.test(profile.name))
283
+ return true;
284
+ if (profile.name === 'MCP Participant')
285
+ return true;
286
+ return false;
287
+ }
288
+ /**
289
+ * SessionManager
290
+ * High-level API for session operations with business logic
291
+ *
292
+ * Uses storage descriptors for adapter-agnostic storage operations.
293
+ * Supports any compliant storage adapter (localdb, drizzle, etc.)
294
+ */
295
+ export class SessionManager {
296
+ hostRuntime;
297
+ logger;
298
+ /** A stable manager keeps state while dependencies follow its caller's view. */
299
+ get runtime() { return this.hostRuntime.invocationRuntime; }
300
+ /** The runtime this manager serves (its caller's view): per-root transport state keys on its root. */
301
+ get servingRuntime() { return this.runtime; }
302
+ bindRuntime(runtime) {
303
+ this.hostRuntime = runtime;
304
+ this.logger = getLogger('session:manager', runtime);
305
+ }
306
+ initialized = false;
307
+ // Storage references (lazy initialized via ensureStorage)
308
+ sessionsStorage;
309
+ todosStorage;
310
+ scratchpadStorage;
311
+ jobsStorage;
312
+ messagesStorage;
313
+ participantsStorage;
314
+ presenceStorage;
315
+ knowledgeRefsStorage;
316
+ connectionsStorage;
317
+ membershipLeasesStorage;
318
+ // In-memory state
319
+ participantSessions = new Map();
320
+ mcpSessions = new Map(); // mcpSessionId -> vibesSessionId
321
+ defaultSessionId = null;
322
+ defaultParticipantId = null;
323
+ persistMappingsTail = Promise.resolve();
324
+ constructor(runtime) {
325
+ this.hostRuntime = runtime;
326
+ this.logger = getLogger('session:manager', runtime);
327
+ }
328
+ /**
329
+ * Resolve Session's participant/session policy through the registered
330
+ * manager context. Consumers do not import MCP participant internals.
331
+ */
332
+ async resolveContext(sessionId, participantId, mcpSessionId, options = {}) {
333
+ return ensureSessionContextWithManager(this, this.runtime, sessionId, participantId, mcpSessionId, options);
334
+ }
335
+ /** Identify this caller by UUID or slug, reusing Session's self-registration policy. */
336
+ async identifyParticipant(reference) {
337
+ const resolved = await resolveParticipantReference(reference, this, undefined, {
338
+ registerIfMissing: true
339
+ });
340
+ return resolved.participantId;
341
+ }
342
+ /** Resolve or create the caller's MCP participant under Session policy. */
343
+ async resolveMcpParticipant(options = {}, mcpSessionId, caller) {
344
+ return getOrCreateMCPParticipant(undefined, options, mcpSessionId, caller, this);
345
+ }
346
+ /** Commit and publish Session's canonical bound-session lifecycle event. */
347
+ async announceBound(sessionId, participantId, surface) {
348
+ await emitSessionBound({
349
+ runtime: this.runtime,
350
+ sessionId,
351
+ participantId,
352
+ surface
353
+ });
354
+ }
355
+ /**
356
+ * Ambient consumers need one stable session identity, not a private map
357
+ * guess. The durable default is the process' current binding and is cleared
358
+ * when that session is paused, archived, or deleted.
359
+ */
360
+ get activeSessionId() {
361
+ return this.defaultSessionId ?? undefined;
362
+ }
363
+ /**
364
+ * Resolve one `session.*` storage record through the runtime. A declared
365
+ * record whose implementation is not registered (the descriptor-only
366
+ * registration path) is bound directly to its descriptor; a record that is
367
+ * not declared at all is a typed NOT_FOUND. Every other resolution failure
368
+ * propagates unchanged.
369
+ */
370
+ async resolveStorageRecord(id) {
371
+ const selected = await this.runtime
372
+ .query('storage/record')
373
+ .withId(id)
374
+ .enabled()
375
+ .select();
376
+ if (selected)
377
+ return selected.impl;
378
+ const descriptor = this.runtime
379
+ .query('storage/record')
380
+ .withId(id)
381
+ .firstDescriptor();
382
+ if (!descriptor) {
383
+ throw new CapabilityError(`Storage record ${id} is not registered.`, 'NOT_FOUND', {
384
+ reason: 'not-registered',
385
+ details: { kind: 'storage/record', id }
386
+ });
387
+ }
388
+ return new StorageRecord(descriptor, { runtime: this.runtime });
389
+ }
390
+ async ensureStorage() {
391
+ if (this.sessionsStorage)
392
+ return; // Already initialized
393
+ const [sessions, todos, scratchpad, jobs, messages, participants, presence, knowledgeRefs, connections, membershipLeases] = await Promise.all([
394
+ this.resolveStorageRecord('session.sessions'),
395
+ this.resolveStorageRecord('session.todos'),
396
+ this.resolveStorageRecord('session.scratchpad'),
397
+ this.resolveStorageRecord('session.jobs'),
398
+ this.resolveStorageRecord('session.messages'),
399
+ this.resolveStorageRecord('session.participants'),
400
+ this.resolveStorageRecord('session.presence'),
401
+ this.resolveStorageRecord('session.knowledge-refs'),
402
+ this.resolveStorageRecord('session.connections'),
403
+ this.resolveStorageRecord('session.membership-leases')
404
+ ]);
405
+ this.sessionsStorage = sessions;
406
+ this.todosStorage = todos;
407
+ this.scratchpadStorage = scratchpad;
408
+ this.jobsStorage = jobs;
409
+ this.messagesStorage = messages;
410
+ this.participantsStorage = participants;
411
+ this.presenceStorage = presence;
412
+ this.knowledgeRefsStorage = knowledgeRefs;
413
+ this.connectionsStorage = connections;
414
+ this.membershipLeasesStorage = membershipLeases;
415
+ }
416
+ /**
417
+ * The notifications delivery client, or `undefined` when the composition
418
+ * does not provide one. Notifications are runtime-soft (SPEC): absence is
419
+ * confirmed by asking the runtime whether the client is declared, never by
420
+ * catching a failure. A declared client that fails to load propagates.
421
+ */
422
+ async resolveNotifications() {
423
+ const runtime = this.runtime;
424
+ if (!runtime.hasKind('runtime/client'))
425
+ return undefined;
426
+ const declared = await runtime
427
+ .query('runtime/client')
428
+ .withId(NOTIFICATIONS_CLIENT_ID)
429
+ .existsAsync();
430
+ if (!declared)
431
+ return undefined;
432
+ return getVibesClient(NOTIFICATIONS_CLIENT_ID, runtime);
433
+ }
434
+ /** A true COUNT over `where` — never a loaded page's length (ledger L-11). */
435
+ countRecords(storage, where) {
436
+ return storage.count(where);
437
+ }
438
+ async buildSessionSummary(session) {
439
+ await this.ensureStorage();
440
+ const [todoCount, completedTodoCount, scratchpadEntryCount, jobCount, messageCount, owner, assigned] = await Promise.all([
441
+ this.countRecords(this.todosStorage, { sessionId: session.id }),
442
+ this.countRecords(this.todosStorage, { sessionId: session.id, status: 'completed' }),
443
+ this.countRecords(this.scratchpadStorage, { sessionId: session.id }),
444
+ this.countRecords(this.jobsStorage, { sessionId: session.id }),
445
+ this.countRecords(this.messagesStorage, { sessionId: session.id }),
446
+ this.projectParticipant(session.ownerParticipant),
447
+ session.assignedParticipant
448
+ ? this.projectParticipant(session.assignedParticipant)
449
+ : Promise.resolve(undefined)
450
+ ]);
451
+ const workspaceRoot = this.nonEmptyMetadata(session, 'workspacePath');
452
+ const workItemTitle = this.nonEmptyMetadata(session, 'workItemTitle');
453
+ const gitBranch = this.nonEmptyMetadata(session, 'gitBranch');
454
+ return {
455
+ id: session.id,
456
+ title: session.title,
457
+ tags: session.tags ?? [],
458
+ status: session.status,
459
+ ownerParticipant: session.ownerParticipant,
460
+ assignedParticipant: session.assignedParticipant,
461
+ ownerParticipantId: session.ownerParticipant,
462
+ ownerParticipantSlug: owner?.slug,
463
+ ownerParticipantName: owner?.name,
464
+ assignedParticipantId: session.assignedParticipant,
465
+ assignedParticipantSlug: assigned?.slug,
466
+ assignedParticipantName: assigned?.name,
467
+ workItemId: session.workItemId,
468
+ workItemTitle,
469
+ workspaceRoot,
470
+ gitBranch,
471
+ worktree: workspaceRoot,
472
+ createdAt: session.createdAt,
473
+ lastActivityAt: session.lastActivityAt,
474
+ todoCount,
475
+ completedTodoCount,
476
+ scratchpadEntryCount,
477
+ jobCount,
478
+ messageCount
479
+ };
480
+ }
481
+ nonEmptyMetadata(session, key) {
482
+ const value = SessionDisplayMetadataSchema.parse(session.metadata ?? {})[key]?.trim();
483
+ return value ? value : undefined;
484
+ }
485
+ async projectParticipant(participantId) {
486
+ const profile = await this.getParticipant(participantId);
487
+ if (!profile)
488
+ return undefined;
489
+ const storedSlug = profile.metadata?.slug?.trim();
490
+ const slug = storedSlug
491
+ ? storedSlug.toLowerCase()
492
+ : profile.name
493
+ .trim()
494
+ .toLowerCase()
495
+ .replace(/[^a-z0-9-]+/g, '-')
496
+ .replace(/-+/g, '-')
497
+ .replace(/^-+|-+$/g, '')
498
+ .slice(0, 64);
499
+ return { slug: slug || undefined, name: profile.name };
500
+ }
501
+ // LIFECYCLE
502
+ async initialize() {
503
+ if (this.initialized)
504
+ return;
505
+ // Storage descriptors handle their own initialization
506
+ // No need to call storage.initialize()
507
+ await this.ensureStorage();
508
+ this.initialized = true;
509
+ await this.recoverActiveSession();
510
+ }
511
+ async close() {
512
+ await this.persistMappingsTail;
513
+ // Storage descriptors handle their own cleanup
514
+ this.initialized = false;
515
+ this.participantSessions.clear();
516
+ this.mcpSessions.clear();
517
+ this.defaultSessionId = null;
518
+ this.defaultParticipantId = null;
519
+ // Clear storage references
520
+ this.sessionsStorage = undefined;
521
+ this.todosStorage = undefined;
522
+ this.scratchpadStorage = undefined;
523
+ this.jobsStorage = undefined;
524
+ this.messagesStorage = undefined;
525
+ this.participantsStorage = undefined;
526
+ this.presenceStorage = undefined;
527
+ this.knowledgeRefsStorage = undefined;
528
+ this.membershipLeasesStorage = undefined;
529
+ this.connectionsStorage = undefined;
530
+ }
531
+ /**
532
+ * Resolves `true` when session storage answers a query. Storage that cannot
533
+ * answer is a typed `UNAVAILABLE session-storage-unavailable` refusal
534
+ * carrying the storage failure, never a silent `false`.
535
+ */
536
+ async healthCheck() {
537
+ await this.ensureStorage();
538
+ try {
539
+ await this.sessionsStorage.list({ limit: 0 });
540
+ }
541
+ catch (error) {
542
+ throw new CapabilityError('Session storage is unavailable.', 'UNAVAILABLE', {
543
+ reason: 'session-storage-unavailable',
544
+ cause: error
545
+ });
546
+ }
547
+ return true;
548
+ }
549
+ // SESSIONS
550
+ /**
551
+ * Create a session. An active creation becomes the workspace's durable
552
+ * default unless `adoptAsDefault: false` — a per-native-session continuity
553
+ * row minted by an external-tool hook is not "the process' current binding"
554
+ * and must not retarget every later anonymous bind onto itself.
555
+ */
556
+ async createSession(session, options = {}) {
557
+ await this.ensureStorage();
558
+ const now = new Date();
559
+ const id = options.id === undefined ? undefined : z.uuid().parse(options.id);
560
+ if (id && (session.metadata?.source !== 'external-hook' || !session.metadata.nativeSessionId || !session.metadata.externalToolId)) {
561
+ throw new CapabilityError('An explicit continuity session id requires native identity.', 'INVALID_INPUT', { reason: 'session-native-identity-required' });
562
+ }
563
+ const write = () => this.sessionsStorage.create({
564
+ ...session,
565
+ id,
566
+ status: session.status ?? 'active',
567
+ scope: session.scope ?? 'project',
568
+ tags: session.tags ?? [],
569
+ metadata: session.metadata ?? {},
570
+ lastActivityAt: now
571
+ });
572
+ const created = id ? await writeOrRecover(write, async () => {
573
+ const existing = await this.sessionsStorage.get(id);
574
+ if (!existing || existing.ownerParticipant !== session.ownerParticipant ||
575
+ existing.metadata?.source !== session.metadata?.source ||
576
+ existing.metadata?.nativeSessionId !== session.metadata?.nativeSessionId ||
577
+ existing.metadata?.externalToolId !== session.metadata?.externalToolId ||
578
+ existing.metadata?.workspacePath !== session.metadata?.workspacePath)
579
+ return null;
580
+ return existing;
581
+ }, isUniqueConstraintError) : await write();
582
+ if (created.status === 'active') {
583
+ if (options.adoptAsDefault !== false) {
584
+ await this.setDefaultActiveSession(created.id);
585
+ }
586
+ await this.setParticipantActiveSession(created.ownerParticipant, created.id);
587
+ if (created.assignedParticipant) {
588
+ await this.setParticipantActiveSession(created.assignedParticipant, created.id);
589
+ }
590
+ }
591
+ return created;
592
+ }
593
+ async getSession(id) {
594
+ await this.ensureStorage();
595
+ return this.sessionsStorage.get(id);
596
+ }
597
+ async updateSession(id, updates) {
598
+ await this.ensureStorage();
599
+ await this.sessionsStorage.update(id, {
600
+ ...updates,
601
+ updatedAt: new Date()
602
+ });
603
+ }
604
+ /**
605
+ * Bump a session's activity clock to reflect real traffic (messages), so
606
+ * `lastActivityAt` tracks when the session was last USED — not merely when its
607
+ * record was created. Before this, `lastActivityAt` was stamped only at
608
+ * createSession, so a live session reads hours-stale while agents are actively
609
+ * messaging on it — a false "dark/idle" signal that mislead liveness reads.
610
+ * Best-effort: an activity touch must NEVER fail the underlying operation.
611
+ */
612
+ async touchSessionActivity(sessionId, at) {
613
+ const logger = getLogger('session:manager', this.runtime);
614
+ try {
615
+ await this.sessionsStorage.update(sessionId, { lastActivityAt: at });
616
+ }
617
+ catch (error) {
618
+ // Activity tracking is advisory; never break the caller on it — but a
619
+ // swallowed failure here leaves lastActivityAt silently stale, which
620
+ // reads as a false "idle" liveness signal. Report the value-free facts
621
+ // so the failure is observable without repeating storage text.
622
+ logger.warn('Failed to bump session lastActivityAt', {
623
+ sessionId,
624
+ error: valueFreeFailure(CaughtFailureSchema.parse(error))
625
+ });
626
+ }
627
+ }
628
+ async listSessions(filters) {
629
+ await this.ensureStorage();
630
+ const where = {};
631
+ if (filters?.status)
632
+ where.status = filters.status;
633
+ if (filters?.ownerParticipant)
634
+ where.ownerParticipant = filters.ownerParticipant;
635
+ const result = await this.sessionsStorage.list({
636
+ where,
637
+ orderBy: { lastActivityAt: 'desc' },
638
+ limit: filters?.limit ?? 100,
639
+ offset: filters?.offset ?? 0
640
+ });
641
+ return Promise.all(result.items.map((session) => this.buildSessionSummary(session)));
642
+ }
643
+ /**
644
+ * Count sessions matching the filter without pagination.
645
+ *
646
+ * Routines need a way to distinguish "this page is empty" from
647
+ * "the underlying set is empty," so this exposes the unfiltered
648
+ * count even when callers paginate via `listSessions`.
649
+ */
650
+ async countSessions(filters) {
651
+ await this.ensureStorage();
652
+ const where = {};
653
+ if (filters?.status)
654
+ where.status = filters.status;
655
+ if (filters?.ownerParticipant)
656
+ where.ownerParticipant = filters.ownerParticipant;
657
+ // True COUNT(*) — never a loaded page's length (ledger L-11).
658
+ return this.countRecords(this.sessionsStorage, where);
659
+ }
660
+ async getActiveSession() {
661
+ await this.ensureStorage();
662
+ const result = await this.sessionsStorage.list({
663
+ where: { status: 'active' },
664
+ orderBy: { lastActivityAt: 'desc' },
665
+ limit: 1
666
+ });
667
+ return result.items[0] ?? null;
668
+ }
669
+ /**
670
+ * Pause a specific session by id. Prefer this over pauseActiveSession when
671
+ * the caller has a concrete target (e.g. a UI selection): with more than one
672
+ * active session, "the active session" is ambiguous and pausing the wrong
673
+ * one is a real hazard.
674
+ */
675
+ async pauseSession(id) {
676
+ await this.ensureStorage();
677
+ await this.updateSession(id, { status: 'paused' });
678
+ // Keep participant and MCP mappings. Concurrent lanes share one manager;
679
+ // wiping bindings on pause is what made the next send unresolvable and
680
+ // forced a mint. Archive still clears mappings.
681
+ if (this.defaultSessionId === id) {
682
+ await this.setDefaultActiveSession(null);
683
+ }
684
+ }
685
+ /**
686
+ * Pause the active session and optionally create a new one
687
+ */
688
+ async pauseActiveSession() {
689
+ const active = await this.getActiveSession();
690
+ if (active) {
691
+ await this.pauseSession(active.id);
692
+ }
693
+ }
694
+ /**
695
+ * Resume a paused session (sets it to active)
696
+ */
697
+ async resumeSession(id) {
698
+ await this.ensureStorage();
699
+ if (!(await this.getSession(id)))
700
+ throw sessionNotFound(id);
701
+ // Several collaborations can be active at once. Pausing "the" current
702
+ // session here is what severed every other bound lane tonight.
703
+ await this.updateSession(id, { status: 'active' });
704
+ const resumed = await this.getSession(id);
705
+ if (!resumed) {
706
+ throw sessionNotFound(id);
707
+ }
708
+ await this.setDefaultActiveSession(resumed.id);
709
+ await this.setParticipantActiveSession(resumed.ownerParticipant, resumed.id);
710
+ if (resumed.assignedParticipant) {
711
+ await this.setParticipantActiveSession(resumed.assignedParticipant, resumed.id);
712
+ }
713
+ return resumed;
714
+ }
715
+ /**
716
+ * Open a session for bound work. Paused sessions resume in place.
717
+ * Archived sessions stay closed until the explicit resume action.
718
+ */
719
+ async openSessionForWork(sessionId) {
720
+ await this.ensureStorage();
721
+ const session = await this.getSession(sessionId);
722
+ if (!session) {
723
+ throw sessionNotFound(sessionId);
724
+ }
725
+ if (session.status === 'archived') {
726
+ throw sessionNotWritable(sessionId, session.status);
727
+ }
728
+ if (session.status === 'paused') {
729
+ return this.resumeSession(sessionId);
730
+ }
731
+ return session;
732
+ }
733
+ /** Participant ids currently mapped to this session. */
734
+ async listBoundParticipantIds(sessionId) {
735
+ await this.ensureStorage();
736
+ const ids = [];
737
+ for (const [participantId, boundId] of this.participantSessions) {
738
+ if (boundId === sessionId)
739
+ ids.push(participantId);
740
+ }
741
+ return ids;
742
+ }
743
+ async releaseAllMembershipLeases(sessionId) {
744
+ const pageSize = 500;
745
+ const where = {
746
+ sessionId,
747
+ releasedAt: { isNull: true }
748
+ };
749
+ for (;;) {
750
+ const result = await this.membershipLeasesStorage.list({
751
+ where,
752
+ limit: pageSize,
753
+ offset: 0
754
+ });
755
+ if (result.items.length === 0)
756
+ return;
757
+ const releasedAt = new Date().toISOString();
758
+ await Promise.all(result.items.map((lease) => this.membershipLeasesStorage.update(lease.id, {
759
+ releasedAt,
760
+ activeConnectionKey: null
761
+ })));
762
+ }
763
+ }
764
+ async archiveSession(id) {
765
+ await this.ensureStorage();
766
+ await this.releaseAllMembershipLeases(id);
767
+ await this.sessionsStorage.update(id, {
768
+ status: 'archived',
769
+ archivedAt: new Date()
770
+ });
771
+ await this.clearMappingsForSession(id);
772
+ if (this.defaultSessionId === id) {
773
+ await this.setDefaultActiveSession(null);
774
+ }
775
+ }
776
+ async deleteSession(id) {
777
+ await this.ensureStorage();
778
+ await this.sessionsStorage.delete(id);
779
+ await this.clearMappingsForSession(id);
780
+ if (this.defaultSessionId === id) {
781
+ await this.setDefaultActiveSession(null);
782
+ }
783
+ }
784
+ async searchSessions(query, filters) {
785
+ const { results } = await this.searchSessionsPaged(query, filters);
786
+ return results;
787
+ }
788
+ /**
789
+ * Session search with true match total (L-11 class).
790
+ * Storage search already returns COUNT over MATCH+where before limit;
791
+ * discard-only wrappers were re-introducing page-as-total at CLI/MCP.
792
+ */
793
+ async searchSessionsPaged(query, filters) {
794
+ await this.ensureStorage();
795
+ const where = {};
796
+ if (filters?.status)
797
+ where.status = filters.status;
798
+ const result = await this.sessionsStorage.search(query, {
799
+ where,
800
+ limit: filters?.limit ?? 100,
801
+ offset: filters?.offset ?? 0
802
+ });
803
+ const results = await Promise.all(result.items.map((session) => this.buildSessionSummary(session)));
804
+ // Prefer storage COUNT; never silently fall back to page length (L-31 advisory).
805
+ const total = SearchTotalSchema.safeParse(result.total);
806
+ if (!total.success) {
807
+ throw new CapabilityError('Session search returned no match total; a page length is not a match count.', 'INTERNAL', { reason: 'search-total-missing' });
808
+ }
809
+ return { results, total: total.data };
810
+ }
811
+ // SESSION HIERARCHY (Parent/Child Sessions)
812
+ /**
813
+ * Get child sessions for a parent session
814
+ */
815
+ async getChildSessions(parentSessionId) {
816
+ await this.ensureStorage();
817
+ const result = await this.sessionsStorage.list({
818
+ where: { parentSessionId },
819
+ limit: 1000
820
+ });
821
+ return result.items;
822
+ }
823
+ /**
824
+ * Get parent session if one exists
825
+ */
826
+ async getParentSession(childSessionId) {
827
+ await this.ensureStorage();
828
+ const child = await this.sessionsStorage.get(childSessionId);
829
+ if (!child || !child.parentSessionId) {
830
+ return null;
831
+ }
832
+ return this.sessionsStorage.get(child.parentSessionId);
833
+ }
834
+ /**
835
+ * Create a child session linked to a parent
836
+ */
837
+ async createChildSession(parentSessionId, session) {
838
+ await this.ensureStorage();
839
+ return this.createSession({
840
+ ...session,
841
+ parentSessionId
842
+ });
843
+ }
844
+ async createTodo(sessionId, todo) {
845
+ await this.ensureStorage();
846
+ const session = await this.sessionsStorage.get(sessionId);
847
+ if (!session)
848
+ throw sessionNotFound(sessionId);
849
+ const scopedTodo = scopeTodoWorkLinks(todo, session.projectId);
850
+ return this.todosStorage.create({
851
+ ...scopedTodo,
852
+ status: scopedTodo.status ?? 'pending',
853
+ priority: scopedTodo.priority ?? 'medium',
854
+ dependencies: scopedTodo.dependencies ?? [],
855
+ tags: scopedTodo.tags ?? [],
856
+ metadata: scopedTodo.metadata ?? {},
857
+ sessionId
858
+ });
859
+ }
860
+ /** Create once for one native todo identity; never overwrite another record. */
861
+ async ensureNativeTodo(sessionId, native, todo) {
862
+ await this.ensureStorage();
863
+ const identity = z.object({ tool: z.string().min(1), session: z.string().min(1), id: z.string().min(1) }).parse(native);
864
+ const session = await this.sessionsStorage.get(sessionId);
865
+ if (!session)
866
+ throw sessionNotFound(sessionId);
867
+ const scoped = scopeTodoWorkLinks(todo, session.projectId);
868
+ const hex = sha256Hex(JSON.stringify(['native-todo', sessionId, identity.tool, identity.session, identity.id])).slice(0, 32);
869
+ const variant = ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
870
+ const id = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
871
+ const metadata = { ...scoped.metadata, nativeTool: identity.tool, nativeSession: identity.session, nativeTodo: identity.id };
872
+ return writeOrRecover(() => this.todosStorage.create({
873
+ ...scoped, id, sessionId, metadata, status: scoped.status ?? 'pending', priority: scoped.priority ?? 'medium',
874
+ dependencies: scoped.dependencies ?? [], tags: scoped.tags ?? []
875
+ }), async () => {
876
+ const existing = await this.todosStorage.get(id);
877
+ return existing && existing.sessionId === sessionId && existing.metadata.nativeTool === identity.tool &&
878
+ existing.metadata.nativeSession === identity.session && existing.metadata.nativeTodo === identity.id ? existing : null;
879
+ }, isUniqueConstraintError);
880
+ }
881
+ /**
882
+ * Legacy alias retained for CLI compatibility.
883
+ */
884
+ addTodo(sessionId, todo) {
885
+ return this.createTodo(sessionId, todo);
886
+ }
887
+ async getTodo(id) {
888
+ await this.ensureStorage();
889
+ return this.todosStorage.get(id);
890
+ }
891
+ async updateTodo(id, updates) {
892
+ await this.ensureStorage();
893
+ const current = await this.todosStorage.get(id);
894
+ if (!current)
895
+ throw todoNotFound(id);
896
+ const session = await this.sessionsStorage.get(current.sessionId);
897
+ if (!session)
898
+ throw sessionNotFound(current.sessionId);
899
+ const merged = normalizeTodoWorkLink({ ...current, ...updates }, { rewriteExisting: updates.workItemId !== undefined });
900
+ const scoped = scopeTodoWorkLinks(merged, session.projectId);
901
+ const carriesWorkScope = scoped.workItemId !== undefined ||
902
+ (scoped.links ?? []).some((link) => link.kind === 'work-item' || (link.kind === 'project' && link.role === 'belongs-to'));
903
+ const persistedUpdates = { ...updates, updatedAt: new Date() };
904
+ if (carriesWorkScope) {
905
+ persistedUpdates.workItemId = scoped.workItemId;
906
+ persistedUpdates.links = scoped.links;
907
+ }
908
+ await this.todosStorage.update(id, persistedUpdates);
909
+ }
910
+ /**
911
+ * Legacy alias retained for CLI compatibility.
912
+ */
913
+ async completeTodo(id, updates = {}) {
914
+ await this.updateTodo(id, {
915
+ ...updates,
916
+ status: 'completed',
917
+ completedAt: new Date()
918
+ });
919
+ const todo = await this.todosStorage.get(id);
920
+ if (!todo)
921
+ throw todoNotFound(id);
922
+ return todo;
923
+ }
924
+ /**
925
+ * The storage filter for a session's todos. `tags` is exact all-of
926
+ * membership; `workItemId` is indexed. `hasDependencies`/`isBlocked` are
927
+ * not storage predicates: {@link scanTodosByDependency} applies them.
928
+ */
929
+ buildTodoWhere(sessionId, filters) {
930
+ const where = { sessionId };
931
+ if (filters?.status)
932
+ where.status = filters.status;
933
+ if (filters?.priority)
934
+ where.priority = filters.priority;
935
+ if (filters?.assignedParticipant)
936
+ where.assignedParticipant = filters.assignedParticipant;
937
+ if (filters?.workItemId)
938
+ where.workItemId = filters.workItemId;
939
+ if (filters?.tags !== undefined)
940
+ where.tags = { hasAll: filters.tags };
941
+ if (filters?.parentId !== undefined) {
942
+ where.parentId = filters.parentId === null ? { isNull: true } : filters.parentId;
943
+ }
944
+ return where;
945
+ }
946
+ async countTodos(sessionId, filters) {
947
+ await this.ensureStorage();
948
+ if (hasDependencyFilter(filters))
949
+ return (await this.scanTodosByDependency(sessionId, filters)).length;
950
+ return this.countRecords(this.todosStorage, this.buildTodoWhere(sessionId, filters));
951
+ }
952
+ async listTodos(sessionId, filters) {
953
+ await this.ensureStorage();
954
+ const limit = filters?.limit ?? 100;
955
+ const offset = filters?.offset ?? 0;
956
+ if (hasDependencyFilter(filters)) {
957
+ return (await this.scanTodosByDependency(sessionId, filters)).slice(offset, offset + limit);
958
+ }
959
+ return this.listTodoPage(this.buildTodoWhere(sessionId, filters), limit, offset);
960
+ }
961
+ /** One page of todos in the default order; the id tiebreak keeps successive pages stable. */
962
+ async listTodoPage(where, limit, offset) {
963
+ const result = await this.todosStorage.list({ where, orderBy: TODO_ORDER, limit, offset });
964
+ return result.items;
965
+ }
966
+ /** Every todo matching `where`, paged exhaustively (no row ceiling). */
967
+ async scanTodos(where) {
968
+ const all = [];
969
+ for (let offset = 0;; offset += TODO_SCAN_PAGE) {
970
+ const page = await this.listTodoPage(where, TODO_SCAN_PAGE, offset);
971
+ all.push(...page);
972
+ if (page.length < TODO_SCAN_PAGE)
973
+ return all;
974
+ }
975
+ }
976
+ /**
977
+ * The session's todos matching the storage filters and the dependency
978
+ * predicates, in the default order. Exhaustive, so callers page and count
979
+ * the full match set: `isBlocked` follows {@link computeTodo}.
980
+ */
981
+ async scanTodosByDependency(sessionId, filters) {
982
+ const scanned = await this.scanTodos(this.buildTodoWhere(sessionId, filters));
983
+ const known = new Map(scanned.map((todo) => [todo.id, todo.status]));
984
+ const matches = [];
985
+ for (const todo of scanned) {
986
+ if (filters.hasDependencies !== undefined && (todo.dependencies ?? []).length > 0 !== filters.hasDependencies)
987
+ continue;
988
+ if (filters.isBlocked !== undefined && (await this.incompleteDependencies(todo, known)).length > 0 !== filters.isBlocked)
989
+ continue;
990
+ matches.push(todo);
991
+ }
992
+ return matches;
993
+ }
994
+ /**
995
+ * The dependencies of `todo` not yet completed. A missing dependency row
996
+ * does not block; one absent from `known` (another session's todo)
997
+ * resolves through storage. The one blocked-dependency predicate.
998
+ */
999
+ async incompleteDependencies(todo, known) {
1000
+ const incomplete = [];
1001
+ for (const dependencyId of todo.dependencies ?? []) {
1002
+ const status = known.has(dependencyId)
1003
+ ? known.get(dependencyId)
1004
+ : (await this.todosStorage.get(dependencyId))?.status;
1005
+ if (status !== undefined && status !== 'completed')
1006
+ incomplete.push(dependencyId);
1007
+ }
1008
+ return incomplete;
1009
+ }
1010
+ /**
1011
+ * Every todo linked to a work item, across all sessions, most recently
1012
+ * updated first. Exhaustive: the work-item trace reads it whole.
1013
+ */
1014
+ async listTodosForWorkItem(workItemId) {
1015
+ await this.ensureStorage();
1016
+ const parsed = WorkItemIdSchema.safeParse(workItemId);
1017
+ if (!parsed.success) {
1018
+ throw new CapabilityError('A work item id must be a non-empty string.', 'INVALID_INPUT', {
1019
+ reason: 'work-item-id-invalid',
1020
+ retryable: false
1021
+ });
1022
+ }
1023
+ const id = parsed.data;
1024
+ const all = [];
1025
+ for (let offset = 0;; offset += TODO_SCAN_PAGE) {
1026
+ const result = await this.todosStorage.list({
1027
+ where: { workItemId: id },
1028
+ orderBy: { updatedAt: 'desc', id: 'asc' },
1029
+ limit: TODO_SCAN_PAGE,
1030
+ offset
1031
+ });
1032
+ all.push(...result.items);
1033
+ if (result.items.length < TODO_SCAN_PAGE)
1034
+ return all;
1035
+ }
1036
+ }
1037
+ async getTodoWithComputed(id) {
1038
+ await this.ensureStorage();
1039
+ const todo = await this.todosStorage.get(id);
1040
+ return todo ? this.computeTodo(todo) : null;
1041
+ }
1042
+ async listTodosWithComputed(sessionId, filters) {
1043
+ await this.ensureStorage();
1044
+ const todos = await this.listTodos(sessionId, filters);
1045
+ return Promise.all(todos.map((todo) => this.computeTodo(todo)));
1046
+ }
1047
+ /**
1048
+ * A todo with its display fields computed from storage: the dependencies
1049
+ * not yet completed (`blockedBy`; a missing dependency row does not block),
1050
+ * its direct subtask counts, and progress (completed share of subtasks, or
1051
+ * 0/100 by its own status when it has none).
1052
+ */
1053
+ async computeTodo(todo) {
1054
+ const blockedBy = await this.incompleteDependencies(todo, new Map());
1055
+ const [subtaskCount, completedSubtaskCount] = await Promise.all([
1056
+ this.countRecords(this.todosStorage, { parentId: todo.id }),
1057
+ this.countRecords(this.todosStorage, { parentId: todo.id, status: 'completed' })
1058
+ ]);
1059
+ const progress = subtaskCount > 0
1060
+ ? Math.round((completedSubtaskCount / subtaskCount) * 100)
1061
+ : todo.status === 'completed'
1062
+ ? 100
1063
+ : 0;
1064
+ return {
1065
+ ...todo,
1066
+ isBlocked: blockedBy.length > 0,
1067
+ blockedBy,
1068
+ subtaskCount,
1069
+ completedSubtaskCount,
1070
+ progress
1071
+ };
1072
+ }
1073
+ async deleteTodo(id) {
1074
+ await this.ensureStorage();
1075
+ await this.todosStorage.delete(id);
1076
+ }
1077
+ async getTodoHierarchy(parentId) {
1078
+ await this.ensureStorage();
1079
+ const result = await this.todosStorage.list({
1080
+ where: { parentId },
1081
+ limit: 100
1082
+ });
1083
+ return result.items;
1084
+ }
1085
+ /**
1086
+ * Blocked means the todo's own explicit status — `status === 'blocked'`.
1087
+ * A todo waiting on incomplete dependencies is a different, truthful state;
1088
+ * ask {@link hasIncompleteDependencies} for that.
1089
+ */
1090
+ async isTodoBlocked(id) {
1091
+ await this.ensureStorage();
1092
+ const todo = await this.todosStorage.get(id);
1093
+ return todo?.status === 'blocked';
1094
+ }
1095
+ /**
1096
+ * True when the todo declares at least one dependency that is not yet
1097
+ * completed. Reports dependency state only — it says nothing about the
1098
+ * todo's own status (a completed todo with a stale incomplete dependency
1099
+ * still answers true here).
1100
+ */
1101
+ async hasIncompleteDependencies(id) {
1102
+ await this.ensureStorage();
1103
+ const todo = await this.todosStorage.get(id);
1104
+ return todo ? (await this.incompleteDependencies(todo, new Map())).length > 0 : false;
1105
+ }
1106
+ /**
1107
+ * Get all root-level TODOs (no parent)
1108
+ */
1109
+ async getRootTodos(sessionId, filters) {
1110
+ await this.ensureStorage();
1111
+ return this.listTodos(sessionId, {
1112
+ ...filters,
1113
+ parentId: null
1114
+ });
1115
+ }
1116
+ /**
1117
+ * Get subtasks for a parent TODO
1118
+ */
1119
+ async getSubtasks(parentId) {
1120
+ await this.ensureStorage();
1121
+ const todo = await this.getTodo(parentId);
1122
+ if (!todo)
1123
+ return [];
1124
+ return this.listTodos(todo.sessionId, {
1125
+ parentId,
1126
+ limit: 100,
1127
+ offset: 0
1128
+ });
1129
+ }
1130
+ // SCRATCHPAD
1131
+ async writeScratchpad(sessionId, entry) {
1132
+ await this.ensureStorage();
1133
+ // (sessionId, key) is unique — a second write to the same key must replace
1134
+ // the entry in place, not collide on the index.
1135
+ const existing = await this.readScratchpad(sessionId, entry.key);
1136
+ if (existing) {
1137
+ return this.replaceScratchpadEntry(existing, entry);
1138
+ }
1139
+ // TOCTOU: a concurrent first write can land between the read above and
1140
+ // this create. On a unique-index collision, replace the entry that won.
1141
+ return writeOrRecover(() => this.scratchpadStorage.create({
1142
+ ...entry,
1143
+ tags: entry.tags ?? [],
1144
+ metadata: entry.metadata ?? {},
1145
+ sessionId
1146
+ }), async () => {
1147
+ const winner = await this.readScratchpad(sessionId, entry.key);
1148
+ return winner ? this.replaceScratchpadEntry(winner, entry) : null;
1149
+ }, isUniqueConstraintError);
1150
+ }
1151
+ /**
1152
+ * Replace an existing scratchpad entry with a fresh write.
1153
+ *
1154
+ * A write is a full replace, not a merge: every ScratchpadEntryCreate field
1155
+ * is passed explicitly with the create-branch defaults, so omitted optional
1156
+ * fields (tags/metadata/expiresAt/language) reset instead of silently
1157
+ * inheriting the previous entry's values. Only id and createdAt survive.
1158
+ */
1159
+ async replaceScratchpadEntry(existing, entry) {
1160
+ await this.scratchpadStorage.update(existing.id, {
1161
+ content: entry.content,
1162
+ contentType: entry.contentType,
1163
+ language: entry.language,
1164
+ tags: entry.tags ?? [],
1165
+ metadata: entry.metadata ?? {},
1166
+ expiresAt: entry.expiresAt,
1167
+ updatedAt: new Date()
1168
+ });
1169
+ return (await this.scratchpadStorage.get(existing.id)) ?? existing;
1170
+ }
1171
+ async readScratchpad(sessionId, key) {
1172
+ await this.ensureStorage();
1173
+ const result = await this.scratchpadStorage.list({
1174
+ where: { sessionId, key },
1175
+ limit: 1
1176
+ });
1177
+ return result.items[0] ?? null;
1178
+ }
1179
+ async getScratchpadEntry(id) {
1180
+ await this.ensureStorage();
1181
+ return this.scratchpadStorage.get(id);
1182
+ }
1183
+ async updateScratchpadEntry(id, updates) {
1184
+ await this.ensureStorage();
1185
+ await this.scratchpadStorage.update(id, {
1186
+ ...updates,
1187
+ updatedAt: new Date()
1188
+ });
1189
+ }
1190
+ buildScratchpadWhere(sessionId, filters) {
1191
+ const where = { sessionId };
1192
+ if (filters?.contentType)
1193
+ where.contentType = filters.contentType;
1194
+ return where;
1195
+ }
1196
+ async countScratchpad(sessionId, filters) {
1197
+ await this.ensureStorage();
1198
+ return this.countRecords(this.scratchpadStorage, this.buildScratchpadWhere(sessionId, filters));
1199
+ }
1200
+ async listScratchpad(sessionId, filters) {
1201
+ await this.ensureStorage();
1202
+ const where = this.buildScratchpadWhere(sessionId, filters);
1203
+ const result = await this.scratchpadStorage.list({
1204
+ where,
1205
+ orderBy: { createdAt: 'desc' },
1206
+ limit: filters?.limit ?? 100,
1207
+ offset: filters?.offset ?? 0
1208
+ });
1209
+ return result.items.map(scratchpadSummaryOf);
1210
+ }
1211
+ async deleteScratchpadEntry(id) {
1212
+ await this.ensureStorage();
1213
+ await this.scratchpadStorage.delete(id);
1214
+ }
1215
+ async cleanupExpiredScratchpad() {
1216
+ await this.ensureStorage();
1217
+ // expiresAt is persisted as an ISO string; storage encodes a Date filter
1218
+ // operand as ISO text (ISO strings order lexicographically = temporally).
1219
+ const result = await this.scratchpadStorage.list({
1220
+ where: { expiresAt: { lt: new Date() } },
1221
+ limit: 1000
1222
+ });
1223
+ // Delete them
1224
+ for (const entry of result.items) {
1225
+ await this.scratchpadStorage.delete(entry.id);
1226
+ }
1227
+ return result.items.length;
1228
+ }
1229
+ async searchScratchpad(sessionId, query, filters) {
1230
+ await this.ensureStorage();
1231
+ const where = this.buildScratchpadWhere(sessionId, filters);
1232
+ const result = await this.scratchpadStorage.search(query, {
1233
+ where,
1234
+ limit: filters?.limit ?? 100,
1235
+ offset: filters?.offset ?? 0
1236
+ });
1237
+ // Convert to summary format; total is the true FTS match count from the
1238
+ // storage layer, not the returned page length (ledger L-11).
1239
+ return {
1240
+ items: result.items.map(scratchpadSummaryOf),
1241
+ total: result.total ?? result.items.length
1242
+ };
1243
+ }
1244
+ // JOBS
1245
+ async addJobRef(sessionId, job) {
1246
+ await this.ensureStorage();
1247
+ return this.jobsStorage.create({
1248
+ ...job,
1249
+ status: job.status ?? 'pending',
1250
+ metadata: job.metadata ?? {},
1251
+ sessionId
1252
+ });
1253
+ }
1254
+ async getJobRef(id) {
1255
+ await this.ensureStorage();
1256
+ return this.jobsStorage.get(id);
1257
+ }
1258
+ async getJobRefByJobId(jobId) {
1259
+ await this.ensureStorage();
1260
+ const result = await this.jobsStorage.list({
1261
+ where: { jobId },
1262
+ limit: 1
1263
+ });
1264
+ return result.items[0] ?? null;
1265
+ }
1266
+ async updateJobRef(id, updates) {
1267
+ await this.ensureStorage();
1268
+ await this.jobsStorage.update(id, updates);
1269
+ }
1270
+ buildJobWhere(sessionId, filters) {
1271
+ const where = { sessionId };
1272
+ if (filters?.status)
1273
+ where.status = filters.status;
1274
+ if (filters?.jobType)
1275
+ where.jobType = filters.jobType;
1276
+ return where;
1277
+ }
1278
+ async countJobRefs(sessionId, filters) {
1279
+ await this.ensureStorage();
1280
+ return this.countRecords(this.jobsStorage, this.buildJobWhere(sessionId, filters));
1281
+ }
1282
+ async getJobRefs(sessionId, filters) {
1283
+ await this.ensureStorage();
1284
+ // Shared with countJobRefs so list and count cannot drift.
1285
+ const where = this.buildJobWhere(sessionId, filters);
1286
+ const result = await this.jobsStorage.list({
1287
+ where,
1288
+ orderBy: { createdAt: 'desc' },
1289
+ limit: filters?.limit ?? 100,
1290
+ offset: filters?.offset ?? 0
1291
+ });
1292
+ // Convert to summary format
1293
+ return result.items.map((job) => ({
1294
+ id: job.id,
1295
+ jobId: job.jobId,
1296
+ jobType: job.jobType,
1297
+ queueName: job.queueName,
1298
+ description: job.description,
1299
+ status: job.status,
1300
+ progress: job.progress,
1301
+ createdAt: job.createdAt,
1302
+ completedAt: job.completedAt
1303
+ }));
1304
+ }
1305
+ async deleteJobRef(id) {
1306
+ await this.ensureStorage();
1307
+ await this.jobsStorage.delete(id);
1308
+ }
1309
+ // MESSAGES
1310
+ async sendMessage(sessionId, message) {
1311
+ await this.ensureStorage();
1312
+ const now = new Date();
1313
+ const saved = await this.messagesStorage.create({
1314
+ ...message,
1315
+ attachments: message.attachments ?? [],
1316
+ metadata: message.metadata ?? {},
1317
+ sessionId,
1318
+ isRead: false
1319
+ });
1320
+ await this.touchSessionActivity(sessionId, now);
1321
+ if (message.fromParticipant)
1322
+ await this.touchParticipantLastSeen(message.fromParticipant);
1323
+ if (message.toParticipant)
1324
+ await this.notifyUnread(sessionId, message.toParticipant);
1325
+ return saved;
1326
+ }
1327
+ /**
1328
+ * Advisory: bump the sender's lastSeen on a send. A failure never fails
1329
+ * the send; it is reported with value-free facts.
1330
+ */
1331
+ async touchParticipantLastSeen(participantId) {
1332
+ const logger = getLogger('session:manager', this.runtime);
1333
+ try {
1334
+ await this.updateParticipantLastSeen(participantId);
1335
+ }
1336
+ catch (error) {
1337
+ logger.warn('Failed to update participant lastSeen on message send', {
1338
+ participantId,
1339
+ error: valueFreeFailure(CaughtFailureSchema.parse(error))
1340
+ });
1341
+ }
1342
+ }
1343
+ /**
1344
+ * Runtime-soft (SPEC): fan an unread-count notice out through the
1345
+ * notifications client at the 1st, 5th and 10th unread message. A
1346
+ * composition without notifications is confirmed absent and skipped; any
1347
+ * other failure never fails the send and is reported with value-free facts.
1348
+ */
1349
+ async notifyUnread(sessionId, toParticipant) {
1350
+ const logger = getLogger('session:manager', this.runtime);
1351
+ try {
1352
+ const notifications = await this.resolveNotifications();
1353
+ if (!notifications)
1354
+ return;
1355
+ const unreadCount = await this.getUnreadCount(sessionId, toParticipant);
1356
+ if (unreadCount !== 1 && unreadCount !== 5 && unreadCount !== 10)
1357
+ return;
1358
+ // ParticipantProfile is keyed by participantId — route notifications to
1359
+ // that identity; consumers map participantId → user id out-of-band.
1360
+ const participant = await this.getParticipant(toParticipant);
1361
+ if (!participant)
1362
+ return;
1363
+ const plural = unreadCount > 1 ? 's' : '';
1364
+ await notifications.send({
1365
+ userId: participant.participantId,
1366
+ channel: 'in-app',
1367
+ priority: unreadCount >= 10 ? 'high' : 'normal',
1368
+ title: `${unreadCount} unread message${plural} in session`,
1369
+ body: `You have ${unreadCount} unread message${plural} waiting.`,
1370
+ actionUrl: `/sessions/${sessionId}`
1371
+ });
1372
+ }
1373
+ catch (error) {
1374
+ logger.warn('Unread-message notification failed; the message was delivered', {
1375
+ sessionId,
1376
+ error: valueFreeFailure(CaughtFailureSchema.parse(error))
1377
+ });
1378
+ }
1379
+ }
1380
+ /**
1381
+ * Exactly-once send keyed on a caller-supplied deterministic id.
1382
+ *
1383
+ * A batch retry after a mid-batch failure re-sends entries whose first
1384
+ * attempt may or may not have persisted. With a stable per-entry id the
1385
+ * replayed create collides on the primary key instead of duplicating;
1386
+ * the existing row is returned with `replay: true` and the side effects
1387
+ * (activity bump, notification) are not re-fired.
1388
+ *
1389
+ * On ANY failure — not only a unique-constraint collision — durable state
1390
+ * is consulted by the deterministic id: a row can persist and a LATER
1391
+ * effect inside sendMessage throw, and classifying that schedule as
1392
+ * "failed" would make the receipt deny a delivery that exists. And a
1393
+ * found row only counts as a replay when its payload MATCHES the intended
1394
+ * send: the same id with different content/recipient is a caller bug that
1395
+ * must surface as a CONFLICT, never as a silent old-row "delivery".
1396
+ */
1397
+ async sendMessageIdempotent(sessionId, message) {
1398
+ await this.ensureStorage();
1399
+ return writeOrRecover(async () => ({ message: await this.sendMessage(sessionId, message), replay: false }), async () => {
1400
+ const existing = await this.messagesStorage.get(message.id);
1401
+ if (!existing)
1402
+ return null;
1403
+ if (!messagePayloadMatches(existing, sessionId, message)) {
1404
+ throw new CapabilityError(`Idempotency conflict: message ${message.id} already exists with a different payload — a retry must not change recipient, type, content, or metadata under the same batch identity.`, 'CONFLICT', {
1405
+ reason: 'batch-idempotency-conflict',
1406
+ retryable: false,
1407
+ recoveryHint: 'Retry a failed batch with the SAME entries, or use a new batchId for changed content.',
1408
+ details: {
1409
+ kind: 'BATCH_IDEMPOTENCY_CONFLICT',
1410
+ messageId: message.id,
1411
+ storedToParticipant: existing.toParticipant,
1412
+ intendedToParticipant: message.toParticipant
1413
+ }
1414
+ });
1415
+ }
1416
+ return { message: existing, replay: true };
1417
+ }, anyFailure);
1418
+ }
1419
+ async getMessage(id) {
1420
+ await this.ensureStorage();
1421
+ return this.messagesStorage.get(id);
1422
+ }
1423
+ buildMessageWhere(sessionId, filters) {
1424
+ const where = { sessionId };
1425
+ if (filters?.fromParticipant)
1426
+ where.fromParticipant = filters.fromParticipant;
1427
+ if (filters?.toParticipant)
1428
+ where.toParticipant = filters.toParticipant;
1429
+ if (filters?.isRead !== undefined)
1430
+ where.isRead = filters.isRead;
1431
+ return where;
1432
+ }
1433
+ async countMessages(sessionId, filters) {
1434
+ await this.ensureStorage();
1435
+ return this.countRecords(this.messagesStorage, this.buildMessageWhere(sessionId, filters));
1436
+ }
1437
+ async getMessages(sessionId, filters) {
1438
+ await this.ensureStorage();
1439
+ // Shared with countMessages so list and count cannot drift.
1440
+ const where = this.buildMessageWhere(sessionId, filters);
1441
+ const result = await this.messagesStorage.list({
1442
+ where,
1443
+ orderBy: { createdAt: 'desc' },
1444
+ limit: filters?.limit ?? 100,
1445
+ offset: filters?.offset ?? 0
1446
+ });
1447
+ return result.items;
1448
+ }
1449
+ async markMessageRead(id) {
1450
+ await this.ensureStorage();
1451
+ await this.messagesStorage.update(id, {
1452
+ isRead: true,
1453
+ readAt: new Date()
1454
+ });
1455
+ }
1456
+ async markAllMessagesRead(sessionId, toParticipant) {
1457
+ await this.ensureStorage();
1458
+ // Drain the WHOLE unread backlog in pages. A single capped list silently
1459
+ // stopped at 1000 and reported the cap as the full count (ledger L-11);
1460
+ // marking rows read removes them from the isRead:false filter, so
1461
+ // re-listing page 0 walks the backlog to a true zero.
1462
+ const PAGE_SIZE = 1000;
1463
+ let marked = 0;
1464
+ while (true) {
1465
+ const result = await this.messagesStorage.list({
1466
+ where: { sessionId, toParticipant, isRead: false },
1467
+ limit: PAGE_SIZE
1468
+ });
1469
+ if (result.items.length === 0)
1470
+ break;
1471
+ const now = new Date();
1472
+ for (const message of result.items) {
1473
+ await this.messagesStorage.update(message.id, {
1474
+ isRead: true,
1475
+ readAt: now
1476
+ });
1477
+ }
1478
+ marked += result.items.length;
1479
+ if (result.items.length < PAGE_SIZE)
1480
+ break;
1481
+ }
1482
+ return marked;
1483
+ }
1484
+ async deleteMessage(id) {
1485
+ await this.ensureStorage();
1486
+ await this.messagesStorage.delete(id);
1487
+ }
1488
+ async getUnreadCount(sessionId, toParticipant) {
1489
+ // A true count — not the length of a capped page. A busy inbox past any
1490
+ // page ceiling must still report its real backlog.
1491
+ return this.countMessages(sessionId, { toParticipant, isRead: false });
1492
+ }
1493
+ // PARTICIPANTS
1494
+ async registerParticipant(profile) {
1495
+ await this.ensureStorage();
1496
+ const now = new Date();
1497
+ return this.participantsStorage.create({
1498
+ ...profile,
1499
+ capabilities: profile.capabilities ?? [],
1500
+ preferences: profile.preferences ?? {},
1501
+ metadata: profile.metadata ?? {},
1502
+ lastSeenAt: now
1503
+ });
1504
+ }
1505
+ async getParticipant(participantId) {
1506
+ await this.ensureStorage();
1507
+ return this.participantsStorage.get(participantId);
1508
+ }
1509
+ async getParticipantByConnection(connectionId) {
1510
+ await this.ensureStorage();
1511
+ // Find connection and get associated participant
1512
+ const connection = await this.connectionsStorage.get(connectionId);
1513
+ if (!connection)
1514
+ return null;
1515
+ if (!connection.participantId)
1516
+ return null;
1517
+ return this.participantsStorage.get(connection.participantId);
1518
+ }
1519
+ async updateParticipant(participantId, updates) {
1520
+ await this.ensureStorage();
1521
+ await this.participantsStorage.update(participantId, updates);
1522
+ }
1523
+ /**
1524
+ * The seat for one harness conversation, read-only. See
1525
+ * `schemas/harness-seat.ts`: the id is derived, so this is a point read.
1526
+ */
1527
+ async findHarnessSeat(seat) {
1528
+ return this.getParticipant(harnessSeatParticipantId(HarnessSeatSchema.parse(seat)));
1529
+ }
1530
+ /**
1531
+ * The seat for one harness conversation, registered on first sight. The
1532
+ * harness hook and the MCP connection both call this, so they converge on
1533
+ * one participant whichever arrives first. The seat's identity (agent,
1534
+ * harness session, slug) is written onto rows registered before it was.
1535
+ */
1536
+ async ensureHarnessSeat(input) {
1537
+ const registration = HarnessSeatRegistrationSchema.parse(input);
1538
+ const seat = { agentId: registration.agentId, harnessSessionId: registration.harnessSessionId };
1539
+ const participantId = harnessSeatParticipantId(seat);
1540
+ const slug = harnessSeatSlug(seat);
1541
+ const identity = { agentId: seat.agentId, harnessSessionId: seat.harnessSessionId };
1542
+ let existing = await this.getParticipant(participantId);
1543
+ if (!existing) {
1544
+ // The hook and the MCP connection of one conversation can race the
1545
+ // create; the re-read confirms the race — the derived row must exist.
1546
+ const outcome = await writeOrRecover(async () => ({
1547
+ created: true,
1548
+ profile: await this.registerParticipant({
1549
+ participantId,
1550
+ name: registration.name ?? seat.agentId,
1551
+ role: 'assistant',
1552
+ capabilities: registration.capabilities ?? ['session-management'],
1553
+ metadata: {
1554
+ source: registration.source ?? 'harness',
1555
+ slug,
1556
+ clientInfo: { name: registration.name ?? seat.agentId, ...identity }
1557
+ }
1558
+ })
1559
+ }), async () => {
1560
+ const raced = await this.getParticipant(participantId);
1561
+ return raced ? { created: false, profile: raced } : null;
1562
+ }, anyFailure);
1563
+ if (outcome.created)
1564
+ return outcome.profile;
1565
+ existing = outcome.profile;
1566
+ }
1567
+ const metadata = existing.metadata ?? {};
1568
+ const clientInfo = metadata.clientInfo;
1569
+ if (metadata.slug === slug &&
1570
+ clientInfo?.agentId === identity.agentId &&
1571
+ clientInfo?.harnessSessionId === identity.harnessSessionId) {
1572
+ return existing;
1573
+ }
1574
+ const updated = {
1575
+ ...metadata,
1576
+ slug,
1577
+ clientInfo: { name: existing.name, ...clientInfo, ...identity }
1578
+ };
1579
+ await this.updateParticipant(participantId, { metadata: updated });
1580
+ return { ...existing, metadata: updated };
1581
+ }
1582
+ async updateParticipantLastSeen(participantId) {
1583
+ await this.ensureStorage();
1584
+ await this.participantsStorage.update(participantId, {
1585
+ lastSeenAt: new Date()
1586
+ });
1587
+ }
1588
+ async listParticipants(filters) {
1589
+ await this.ensureStorage();
1590
+ const where = {};
1591
+ // `active` (legacy alias `isActive`): last seen within the activity window.
1592
+ const active = filters?.active ?? filters?.isActive;
1593
+ if (active !== undefined) {
1594
+ const threshold = new Date(Date.now() - PARTICIPANT_ACTIVE_WINDOW_MS);
1595
+ where.lastSeenAt = active ? { gte: threshold } : { lt: threshold };
1596
+ }
1597
+ if (filters?.role !== undefined) {
1598
+ where.role = filters.role;
1599
+ }
1600
+ if (filters?.search !== undefined && filters.search.trim().length > 0) {
1601
+ where.name = { contains: filters.search.trim() };
1602
+ }
1603
+ if (filters?.after !== undefined) {
1604
+ if (filters.orderBy !== 'stable') {
1605
+ throw new CapabilityError('A participant keyset cursor pages only the stable order.', 'INVALID_INPUT', {
1606
+ reason: 'participant-cursor-unordered',
1607
+ retryable: false
1608
+ });
1609
+ }
1610
+ where.participantId = { gt: filters.after };
1611
+ }
1612
+ const result = await this.participantsStorage.list({
1613
+ where,
1614
+ // 'stable' = participantId asc (immutable, unique) so paged
1615
+ // enumerations cannot skip/double-visit rows when lastSeenAt moves
1616
+ // mid-scan (ledger L-19). Default stays recency for display callers.
1617
+ orderBy: filters?.orderBy === 'stable' ? { participantId: 'asc' } : { lastSeenAt: 'desc' },
1618
+ limit: filters?.limit ?? 100,
1619
+ offset: filters?.offset ?? 0
1620
+ });
1621
+ return result.items;
1622
+ }
1623
+ async deleteParticipant(participantId) {
1624
+ await this.ensureStorage();
1625
+ await this.participantsStorage.delete(participantId);
1626
+ }
1627
+ async pruneStaleSessionMappings() {
1628
+ await this.ensureStorage();
1629
+ const staleKeys = [];
1630
+ // A storage failure reading a session propagates: an unreadable store
1631
+ // is not proof the session is gone, and pruning on it would drop live
1632
+ // mappings wholesale.
1633
+ for (const [participantId, sessionId] of this.participantSessions) {
1634
+ const session = await this.sessionsStorage.get(sessionId);
1635
+ if (!session || session.status === 'archived') {
1636
+ staleKeys.push(participantId);
1637
+ }
1638
+ }
1639
+ for (const key of staleKeys) {
1640
+ this.participantSessions.delete(key);
1641
+ }
1642
+ if (staleKeys.length > 0) {
1643
+ await Promise.all(staleKeys.map((key) => this.deleteRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${key}`)));
1644
+ }
1645
+ return {
1646
+ prunedEntries: staleKeys.length,
1647
+ remainingEntries: this.participantSessions.size,
1648
+ prunedKeys: staleKeys
1649
+ };
1650
+ }
1651
+ async pruneOrphanedParticipantProfiles() {
1652
+ await this.ensureStorage();
1653
+ const activeSessionIds = new Set();
1654
+ const activeSessions = await this.listSessions({ status: 'active', limit: 10000 });
1655
+ for (const s of activeSessions)
1656
+ activeSessionIds.add(s.id);
1657
+ const pausedSessions = await this.listSessions({ status: 'paused', limit: 10000 });
1658
+ for (const s of pausedSessions)
1659
+ activeSessionIds.add(s.id);
1660
+ const protectedIds = new Set();
1661
+ for (const s of activeSessions) {
1662
+ if (s.ownerParticipant)
1663
+ protectedIds.add(s.ownerParticipant);
1664
+ if (s.assignedParticipant)
1665
+ protectedIds.add(s.assignedParticipant);
1666
+ }
1667
+ for (const s of pausedSessions) {
1668
+ if (s.ownerParticipant)
1669
+ protectedIds.add(s.ownerParticipant);
1670
+ if (s.assignedParticipant)
1671
+ protectedIds.add(s.assignedParticipant);
1672
+ }
1673
+ for (const [participantId, sessionId] of this.participantSessions) {
1674
+ if (activeSessionIds.has(sessionId))
1675
+ protectedIds.add(participantId);
1676
+ }
1677
+ const messageParticipants = new Set();
1678
+ try {
1679
+ const PAGE = 500;
1680
+ let msgOffset = 0;
1681
+ for (;;) {
1682
+ const msgPage = await this.messagesStorage.list({ limit: PAGE, offset: msgOffset });
1683
+ for (const msg of msgPage.items) {
1684
+ if (activeSessionIds.has(msg.sessionId)) {
1685
+ if (msg.fromParticipant)
1686
+ messageParticipants.add(msg.fromParticipant);
1687
+ if (msg.toParticipant)
1688
+ messageParticipants.add(msg.toParticipant);
1689
+ }
1690
+ }
1691
+ if (msgPage.items.length < PAGE)
1692
+ break;
1693
+ msgOffset += msgPage.items.length;
1694
+ }
1695
+ }
1696
+ catch (error) {
1697
+ throw new CapabilityError('Cannot safely prune participant profiles: message scan failed. A prune without full protection data is unsafe.', 'UNAVAILABLE', { reason: 'prune-protection-scan-failed', cause: error });
1698
+ }
1699
+ for (const pid of messageParticipants)
1700
+ protectedIds.add(pid);
1701
+ const prunedIds = [];
1702
+ let totalProcessed = 0;
1703
+ let offset = 0;
1704
+ const PAGE_SIZE = 100;
1705
+ while (true) {
1706
+ const result = await this.participantsStorage.list({
1707
+ orderBy: { participantId: 'asc' },
1708
+ limit: PAGE_SIZE,
1709
+ offset
1710
+ });
1711
+ if (result.items.length === 0)
1712
+ break;
1713
+ for (const profile of result.items) {
1714
+ totalProcessed++;
1715
+ if (!isFixtureProfile(profile))
1716
+ continue;
1717
+ if (protectedIds.has(profile.participantId))
1718
+ continue;
1719
+ prunedIds.push(profile.participantId);
1720
+ }
1721
+ offset += result.items.length;
1722
+ if (result.items.length < PAGE_SIZE)
1723
+ break;
1724
+ }
1725
+ // A failed delete propagates: reporting it as pruned would be false.
1726
+ for (const id of prunedIds) {
1727
+ await this.participantsStorage.delete(id);
1728
+ }
1729
+ return {
1730
+ prunedProfiles: prunedIds.length,
1731
+ remainingProfiles: totalProcessed - prunedIds.length,
1732
+ protectedCount: protectedIds.size,
1733
+ prunedIds
1734
+ };
1735
+ }
1736
+ async recordConnection(connection) {
1737
+ await this.ensureStorage();
1738
+ const now = new Date();
1739
+ const existing = await this.connectionsStorage.get(connection.connectionId);
1740
+ if (existing) {
1741
+ const updated = {
1742
+ ...connection,
1743
+ connectedAt: now,
1744
+ disconnectedAt: null
1745
+ };
1746
+ await this.connectionsStorage.update(connection.connectionId, updated);
1747
+ return { ...existing, ...updated };
1748
+ }
1749
+ return writeOrRecover(() => this.connectionsStorage.create({
1750
+ ...connection,
1751
+ connectedAt: now,
1752
+ disconnectedAt: undefined
1753
+ }), async () => {
1754
+ await this.connectionsStorage.update(connection.connectionId, {
1755
+ ...connection,
1756
+ connectedAt: now,
1757
+ disconnectedAt: null
1758
+ });
1759
+ return this.connectionsStorage.get(connection.connectionId);
1760
+ }, isUniqueConstraintError);
1761
+ }
1762
+ async closeConnection(connectionId) {
1763
+ await this.ensureStorage();
1764
+ await this.connectionsStorage.update(connectionId, {
1765
+ disconnectedAt: new Date()
1766
+ });
1767
+ }
1768
+ async getConnectionHistory(participantId, limit) {
1769
+ await this.ensureStorage();
1770
+ const where = {};
1771
+ if (participantId)
1772
+ where.participantId = participantId;
1773
+ const result = await this.connectionsStorage.list({
1774
+ where,
1775
+ orderBy: { connectedAt: 'desc' },
1776
+ limit: limit ?? 100
1777
+ });
1778
+ return result.items;
1779
+ }
1780
+ // KNOWLEDGE
1781
+ async addKnowledgeRef(sessionId, ref) {
1782
+ await this.ensureStorage();
1783
+ return this.knowledgeRefsStorage.create({
1784
+ ...ref,
1785
+ metadata: ref.metadata ?? {},
1786
+ sessionId
1787
+ });
1788
+ }
1789
+ async getKnowledgeRef(id) {
1790
+ await this.ensureStorage();
1791
+ return this.knowledgeRefsStorage.get(id);
1792
+ }
1793
+ async getKnowledgeRefs(sessionId, filters) {
1794
+ await this.ensureStorage();
1795
+ const where = { sessionId };
1796
+ if (filters?.reason)
1797
+ where.reason = filters.reason;
1798
+ const result = await this.knowledgeRefsStorage.list({
1799
+ where,
1800
+ orderBy: { createdAt: 'desc' },
1801
+ limit: filters?.limit ?? 100,
1802
+ offset: filters?.offset ?? 0
1803
+ });
1804
+ return result.items;
1805
+ }
1806
+ async deleteKnowledgeRef(id) {
1807
+ await this.ensureStorage();
1808
+ await this.knowledgeRefsStorage.delete(id);
1809
+ }
1810
+ async deleteSessionKnowledgeRefs(sessionId) {
1811
+ await this.ensureStorage();
1812
+ // Get all knowledge refs for session
1813
+ const result = await this.knowledgeRefsStorage.list({
1814
+ where: { sessionId },
1815
+ limit: 1000
1816
+ });
1817
+ // Delete them
1818
+ for (const ref of result.items) {
1819
+ await this.knowledgeRefsStorage.delete(ref.id);
1820
+ }
1821
+ return result.items.length;
1822
+ }
1823
+ // SESSION STATE (PERSISTED PER PARTICIPANT)
1824
+ async setParticipantActiveSession(participantId, sessionId) {
1825
+ await this.ensureStorage();
1826
+ if (!participantId)
1827
+ return;
1828
+ const previous = this.participantSessions.get(participantId);
1829
+ this.participantSessions.set(participantId, sessionId);
1830
+ try {
1831
+ await this.persistRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`, {
1832
+ sessionId
1833
+ });
1834
+ }
1835
+ catch (error) {
1836
+ if (previous)
1837
+ this.participantSessions.set(participantId, previous);
1838
+ else
1839
+ this.participantSessions.delete(participantId);
1840
+ throw error;
1841
+ }
1842
+ }
1843
+ async getParticipantActiveSession(participantId) {
1844
+ await this.ensureStorage();
1845
+ if (!participantId)
1846
+ return null;
1847
+ return this.participantSessions.get(participantId) ?? null;
1848
+ }
1849
+ /**
1850
+ * Read one participant binding from durable authority for a custody decision.
1851
+ *
1852
+ * The ordinary getter intentionally serves the manager's hydrated read model.
1853
+ * MCP authorization cannot use that cache: another process may have rebound
1854
+ * the participant since this manager started. Read the body-scoped row on
1855
+ * every call, reconcile only this cache entry, and let storage/parse failures
1856
+ * propagate so the caller fails closed.
1857
+ */
1858
+ async getAuthoritativeParticipantActiveSession(participantId) {
1859
+ await this.ensureStorage();
1860
+ if (!participantId)
1861
+ return null;
1862
+ const routing = await this.readParticipantRouting(participantId);
1863
+ if (routing.state === 'malformed') {
1864
+ throw new CapabilityError(routing.problem === 'invalid-json'
1865
+ ? `Participant ${participantId} routing row contains invalid JSON.`
1866
+ : `Participant ${participantId} routing row has no valid sessionId.`, 'INVALID_INPUT', { reason: 'routing-row-malformed', details: { participantId, problem: routing.problem } });
1867
+ }
1868
+ return routing.state === 'bound' ? routing.sessionId : null;
1869
+ }
1870
+ /**
1871
+ * Read one participant's body-scoped routing row from durable storage and
1872
+ * reconcile this manager's cache entry with it. A storage failure
1873
+ * propagates; a row that cannot be decoded is reported as `malformed`
1874
+ * (never as unbound) so each caller decides whether to fail closed.
1875
+ */
1876
+ async readParticipantRouting(participantId) {
1877
+ await this.persistMappingsTail;
1878
+ const stateStorage = await this.resolveStorageRecord('session.state');
1879
+ const row = await stateStorage.get(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`);
1880
+ if (!row) {
1881
+ this.participantSessions.delete(participantId);
1882
+ return { state: 'unbound' };
1883
+ }
1884
+ const decoded = decodeJson(row.value);
1885
+ if (!decoded.ok)
1886
+ return { state: 'malformed', problem: 'invalid-json' };
1887
+ const parsed = RoutingValueSchema.safeParse(decoded.value);
1888
+ if (!parsed.success)
1889
+ return { state: 'malformed', problem: 'not-an-object' };
1890
+ if (parsed.data.sessionId === null) {
1891
+ this.participantSessions.delete(participantId);
1892
+ return { state: 'unbound' };
1893
+ }
1894
+ const sessionId = parsed.data.sessionId;
1895
+ if (!sessionId || sessionId.trim().length === 0)
1896
+ return { state: 'malformed', problem: 'no-session-id' };
1897
+ this.participantSessions.set(participantId, sessionId);
1898
+ return { state: 'bound', sessionId };
1899
+ }
1900
+ async clearParticipantActiveSession(participantId) {
1901
+ await this.ensureStorage();
1902
+ if (!participantId)
1903
+ return;
1904
+ const previous = this.participantSessions.get(participantId);
1905
+ if (!previous)
1906
+ return;
1907
+ this.participantSessions.delete(participantId);
1908
+ try {
1909
+ await this.deleteRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`);
1910
+ }
1911
+ catch (error) {
1912
+ this.participantSessions.set(participantId, previous);
1913
+ throw error;
1914
+ }
1915
+ }
1916
+ async ensureParticipantSession(participantId) {
1917
+ await this.ensureStorage();
1918
+ if (!participantId)
1919
+ return null;
1920
+ // Participant resolution is part of MCP custody stamping. A manager can
1921
+ // outlive another process rebinding this participant, so its hydrated map
1922
+ // is not authority here. Read the exact durable routing row before any
1923
+ // session-open side effect; failures must propagate rather than falling
1924
+ // through to a stale/default session.
1925
+ const routing = await this.readParticipantRouting(participantId);
1926
+ if (routing.state === 'malformed') {
1927
+ // A routing row that cannot even be decoded names nothing bindable;
1928
+ // every reconciliation would refuse on it forever. Tombstone it and
1929
+ // treat the body as unbound rather than wedged. A storage failure
1930
+ // (the read above, or this tombstone write) propagates instead.
1931
+ this.logger.warn('Participant routing row malformed; clearing it', {
1932
+ participantId,
1933
+ problem: routing.problem
1934
+ });
1935
+ await this.deleteRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`);
1936
+ this.participantSessions.delete(participantId);
1937
+ return null;
1938
+ }
1939
+ const sessionId = routing.state === 'bound' ? routing.sessionId : null;
1940
+ if (!sessionId) {
1941
+ return null;
1942
+ }
1943
+ // This resolver reconciles identity; it does not own participant routing.
1944
+ // Repersisting or clearing the row after the read creates a TOCTOU window
1945
+ // where another process' newer binding can be overwritten or tombstoned —
1946
+ // so the heal below re-reads the row and tombstones it only when it still
1947
+ // names the SAME dead session. The mcp: transport mapping already self-
1948
+ // heals this way in getSessionForMcpConnection; a participant row whose
1949
+ // session row is gone used to be the one mapping that could never shed it,
1950
+ // wedging every host reconciliation (and the relay's bind replay) behind a
1951
+ // fail-closed error until storage was hand-edited.
1952
+ const session = await this.getSession(sessionId);
1953
+ if (!session) {
1954
+ // The mapped session row is GONE — not archived (that stays fail-closed
1955
+ // below, because reopening is an explicit lifecycle action) but deleted
1956
+ // out from under the mapping, possibly by another process. Heal it.
1957
+ const stillStale = await this.getAuthoritativeParticipantActiveSession(participantId);
1958
+ if (stillStale === sessionId) {
1959
+ await this.deleteRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`);
1960
+ this.participantSessions.delete(participantId);
1961
+ this.logger.warn('Cleared orphaned participant→session mapping (session row missing)', {
1962
+ participantId,
1963
+ sessionId
1964
+ });
1965
+ }
1966
+ return null;
1967
+ }
1968
+ if (session.status === 'archived')
1969
+ return null;
1970
+ if (session.status !== 'paused')
1971
+ return session;
1972
+ // Preserve the historical "open paused work" behavior without calling
1973
+ // resumeSession(), whose broader default/participant writes are forbidden
1974
+ // on this custody-resolution path.
1975
+ await this.updateSession(sessionId, { status: 'active' });
1976
+ return this.getSession(sessionId);
1977
+ }
1978
+ async setDefaultActiveSession(sessionId) {
1979
+ await this.ensureStorage();
1980
+ this.defaultSessionId = sessionId;
1981
+ if (!sessionId ||
1982
+ (this.defaultParticipantId && this.participantSessions.get(this.defaultParticipantId) !== sessionId)) {
1983
+ this.defaultParticipantId = null;
1984
+ }
1985
+ await this.persistRoutingRow(SESSION_STATE_DEFAULT_KEY, {
1986
+ sessionId,
1987
+ participantId: this.defaultParticipantId
1988
+ });
1989
+ }
1990
+ async getDefaultActiveSession() {
1991
+ await this.ensureStorage();
1992
+ return this.defaultSessionId;
1993
+ }
1994
+ /** Persist the default session together with the CLI participant that selected it. */
1995
+ async setDefaultActiveBinding(sessionId, participantId) {
1996
+ await this.ensureStorage();
1997
+ this.defaultSessionId = sessionId;
1998
+ this.defaultParticipantId = participantId;
1999
+ this.participantSessions.set(participantId, sessionId);
2000
+ // Participant binding is the authority. Publish the global/default pointer
2001
+ // only after that exact body row is durable.
2002
+ await this.persistRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${participantId}`, {
2003
+ sessionId
2004
+ });
2005
+ await this.persistRoutingRow(SESSION_STATE_DEFAULT_KEY, { sessionId, participantId });
2006
+ }
2007
+ /** Resolve the durable CLI caller without falling back to a participant-null session. */
2008
+ async getDefaultActiveBinding() {
2009
+ await this.ensureStorage();
2010
+ if (!this.defaultSessionId || !this.defaultParticipantId)
2011
+ return null;
2012
+ if (this.participantSessions.get(this.defaultParticipantId) !== this.defaultSessionId)
2013
+ return null;
2014
+ return {
2015
+ sessionId: this.defaultSessionId,
2016
+ participantId: this.defaultParticipantId
2017
+ };
2018
+ }
2019
+ // UTILITIES
2020
+ /**
2021
+ * Get or create active session for a participant
2022
+ */
2023
+ async getOrCreateActiveSession(participantId, title) {
2024
+ await this.ensureStorage();
2025
+ // Check for existing active session
2026
+ const active = await this.getActiveSession();
2027
+ if (active)
2028
+ return active;
2029
+ // Create new session
2030
+ return this.createSession({
2031
+ title: title || `Session ${new Date().toISOString()}`,
2032
+ status: 'active',
2033
+ ownerParticipant: participantId
2034
+ });
2035
+ }
2036
+ /**
2037
+ * Get session statistics.
2038
+ *
2039
+ * Every figure is a TRUE count — countRecords over the exact where-filters
2040
+ * the corresponding lists use. These totals previously came from capped
2041
+ * list pages (todos/scratchpad/messages at 10000 rows, jobs at the 100-row
2042
+ * default page) and silently froze at the ceiling on busy sessions.
2043
+ */
2044
+ async getSessionStats(sessionId, participantId) {
2045
+ await this.ensureStorage();
2046
+ const session = await this.getSession(sessionId);
2047
+ if (!session)
2048
+ return null;
2049
+ // When the caller names a participant, scope the message view to THAT
2050
+ // seat's inbox so `status {participantId}` reports the seat's own mail —
2051
+ // not a session-wide count that no per-seat mark-read can ever drain.
2052
+ // No participant → preserve the original ambient session-wide view.
2053
+ const messageScope = participantId ? { toParticipant: participantId } : {};
2054
+ const [todosTotal, todosCompleted, todosActive,
2055
+ // blocked = the todo's OWN explicit status ('blocked'). A todo waiting
2056
+ // on incomplete dependencies is a different state, reported separately
2057
+ // as awaitingDependencies below.
2058
+ todosBlocked, todosPending, awaitingDependencies, scratchpadTotal, scratchpadByType, jobsTotal, jobsCompleted, jobsPending, jobsProcessing, jobsFailed, messagesTotal, messagesUnread] = await Promise.all([
2059
+ this.countTodos(sessionId),
2060
+ this.countTodos(sessionId, { status: 'completed' }),
2061
+ this.countTodos(sessionId, { status: 'in_progress' }),
2062
+ this.countTodos(sessionId, { status: 'blocked' }),
2063
+ this.countTodos(sessionId, { status: 'pending' }),
2064
+ this.countTodosAwaitingDependencies(sessionId),
2065
+ this.countScratchpad(sessionId),
2066
+ this.countScratchpadByType(sessionId),
2067
+ this.countJobRefs(sessionId),
2068
+ this.countJobRefs(sessionId, { status: 'completed' }),
2069
+ this.countJobRefs(sessionId, { status: 'pending' }),
2070
+ this.countJobRefs(sessionId, { status: 'processing' }),
2071
+ this.countJobRefs(sessionId, { status: 'failed' }),
2072
+ this.countMessages(sessionId, messageScope),
2073
+ this.countMessages(sessionId, { ...messageScope, isRead: false })
2074
+ ]);
2075
+ return {
2076
+ session,
2077
+ todos: {
2078
+ total: todosTotal,
2079
+ completed: todosCompleted,
2080
+ active: todosActive,
2081
+ blocked: todosBlocked,
2082
+ awaitingDependencies,
2083
+ pending: todosPending
2084
+ },
2085
+ scratchpad: {
2086
+ total: scratchpadTotal,
2087
+ byType: scratchpadByType
2088
+ },
2089
+ jobs: {
2090
+ total: jobsTotal,
2091
+ completed: jobsCompleted,
2092
+ pending: jobsPending,
2093
+ processing: jobsProcessing,
2094
+ failed: jobsFailed
2095
+ },
2096
+ messages: {
2097
+ total: messagesTotal,
2098
+ unread: messagesUnread,
2099
+ // Label WHOSE mailbox these figures describe: seat-scoped when a
2100
+ // participant was named, session-wide otherwise. An unlabeled
2101
+ // ambient unread count reads as personal backlog when it is the
2102
+ // whole bus (ledger L-11).
2103
+ scope: participantId ? 'participant' : 'session',
2104
+ participantId
2105
+ }
2106
+ };
2107
+ }
2108
+ /**
2109
+ * True counts per scratchpad content type. Enumerates the closed content
2110
+ * type set and counts each — never derived from a capped list page. Types
2111
+ * with zero entries are omitted, matching the historical byType shape.
2112
+ */
2113
+ async countScratchpadByType(sessionId) {
2114
+ const byType = {};
2115
+ await Promise.all(ScratchpadContentTypeSchema.options.map(async (contentType) => {
2116
+ const count = await this.countScratchpad(sessionId, { contentType });
2117
+ if (count > 0)
2118
+ byType[contentType] = count;
2119
+ }));
2120
+ return byType;
2121
+ }
2122
+ /**
2123
+ * Count non-terminal todos waiting on at least one incomplete dependency.
2124
+ * Exhaustive paged scan — no row ceiling. Distinct from the `blocked`
2125
+ * bucket, which is the todo's own explicit status; a completed or
2126
+ * cancelled todo is not awaiting anything, so terminal rows never count.
2127
+ */
2128
+ async countTodosAwaitingDependencies(sessionId) {
2129
+ const blocked = await this.scanTodosByDependency(sessionId, { isBlocked: true });
2130
+ return blocked.filter((todo) => todo.status !== 'completed' && todo.status !== 'cancelled').length;
2131
+ }
2132
+ // PRIVATE
2133
+ ensureInitialized() {
2134
+ if (!this.initialized) {
2135
+ throw new Error('SessionManager not initialized. Call initialize() first.');
2136
+ }
2137
+ }
2138
+ async loadSessionMappings() {
2139
+ this.participantSessions.clear();
2140
+ this.mcpSessions.clear();
2141
+ this.defaultSessionId = null;
2142
+ this.defaultParticipantId = null;
2143
+ const stateStorage = await this.resolveStorageRecord('session.state');
2144
+ const items = [];
2145
+ const pageSize = 1000;
2146
+ let offset = 0;
2147
+ for (;;) {
2148
+ const page = await stateStorage.list({
2149
+ includeTotal: true,
2150
+ limit: pageSize,
2151
+ offset,
2152
+ orderBy: { key: 'asc' }
2153
+ });
2154
+ items.push(...page.items);
2155
+ if (page.items.length === 0) {
2156
+ if (page.hasMore === true || (page.total !== undefined && offset < page.total)) {
2157
+ throw new CapabilityError('session.state pagination made no progress while rows remain.', 'INTERNAL', { reason: 'routing-pagination-stalled' });
2158
+ }
2159
+ break;
2160
+ }
2161
+ offset += page.items.length;
2162
+ const hasMore = page.hasMore ??
2163
+ (page.total !== undefined ? offset < page.total : page.items.length === pageSize);
2164
+ if (!hasMore)
2165
+ break;
2166
+ }
2167
+ const legacy = items.find((item) => item.key === SESSION_STATE_LEGACY_KEY);
2168
+ if (legacy) {
2169
+ const decoded = decodeJson(legacy.value);
2170
+ if (!decoded.ok) {
2171
+ throw new CapabilityError('Persisted session.state record contains invalid JSON.', 'INVALID_INPUT', {
2172
+ reason: 'session-state-malformed',
2173
+ cause: decoded.cause
2174
+ });
2175
+ }
2176
+ this.applySessionState(LegacySessionStateSchema.parse(isJsonObject(decoded.value) ? decoded.value : {}));
2177
+ }
2178
+ // Body-scoped rows override the legacy aggregate. One participant write
2179
+ // cannot overwrite another participant simply because its manager is
2180
+ // stale. One undecodable row is skipped and reported; it never fails the
2181
+ // rest of the routing state.
2182
+ for (const item of items) {
2183
+ if (item.key === SESSION_STATE_LEGACY_KEY)
2184
+ continue;
2185
+ const decoded = decodeJson(item.value);
2186
+ if (!decoded.ok) {
2187
+ this.logger.warn('Skipping persisted session.state routing row with invalid JSON', {
2188
+ key: item.key,
2189
+ error: valueFreeFailure(decoded.cause)
2190
+ });
2191
+ continue;
2192
+ }
2193
+ const parsed = RoutingValueSchema.safeParse(decoded.value);
2194
+ if (!parsed.success) {
2195
+ this.logger.warn('Skipping persisted session.state routing row that is not a JSON object', {
2196
+ key: item.key
2197
+ });
2198
+ continue;
2199
+ }
2200
+ const value = parsed.data;
2201
+ const sessionId = value.sessionId ?? null;
2202
+ if (item.key.startsWith(SESSION_STATE_PARTICIPANT_PREFIX)) {
2203
+ const participantId = item.key.slice(SESSION_STATE_PARTICIPANT_PREFIX.length);
2204
+ if (participantId && sessionId)
2205
+ this.participantSessions.set(participantId, sessionId);
2206
+ else if (participantId)
2207
+ this.participantSessions.delete(participantId);
2208
+ }
2209
+ else if (item.key.startsWith(SESSION_STATE_MCP_PREFIX)) {
2210
+ const mcpSessionId = item.key.slice(SESSION_STATE_MCP_PREFIX.length);
2211
+ if (mcpSessionId && sessionId)
2212
+ this.mcpSessions.set(mcpSessionId, sessionId);
2213
+ else if (mcpSessionId)
2214
+ this.mcpSessions.delete(mcpSessionId);
2215
+ }
2216
+ else if (item.key === SESSION_STATE_DEFAULT_KEY) {
2217
+ this.defaultSessionId = sessionId;
2218
+ this.defaultParticipantId = value.participantId ?? null;
2219
+ }
2220
+ }
2221
+ }
2222
+ /**
2223
+ * Serialize one routing write behind every earlier one. Its failure is
2224
+ * returned to the caller that queued it (the returned promise rejects);
2225
+ * the tail only orders writes, so one failed write never blocks the next.
2226
+ */
2227
+ queueRoutingMutation(operation) {
2228
+ const queued = this.persistMappingsTail.then(operation);
2229
+ this.persistMappingsTail = Promise.allSettled([queued]).then(() => undefined);
2230
+ return queued;
2231
+ }
2232
+ persistRoutingRow(key, value) {
2233
+ return this.queueRoutingMutation(async () => {
2234
+ const stateStorage = await this.resolveStorageRecord('session.state');
2235
+ const encoded = JSON.stringify(value);
2236
+ const existing = await stateStorage.get(key);
2237
+ if (existing) {
2238
+ await stateStorage.update(key, { value: encoded });
2239
+ return;
2240
+ }
2241
+ await writeOrRecover(() => stateStorage.create({ key, value: encoded }), () => stateStorage.update(key, { value: encoded }), isUniqueConstraintError);
2242
+ });
2243
+ }
2244
+ deleteRoutingRow(key) {
2245
+ // A null tombstone wins over an old aggregate snapshot written by a stale
2246
+ // process. Physical deletion would let that legacy row resurrect custody.
2247
+ return this.persistRoutingRow(key, { sessionId: null });
2248
+ }
2249
+ async clearMappingsForSession(sessionId) {
2250
+ const participantIds = [];
2251
+ const mcpIds = [];
2252
+ for (const [participantId, mappedSessionId] of this.participantSessions.entries()) {
2253
+ if (mappedSessionId === sessionId) {
2254
+ this.participantSessions.delete(participantId);
2255
+ participantIds.push(participantId);
2256
+ }
2257
+ }
2258
+ for (const [mcpSessionId, mappedSessionId] of this.mcpSessions.entries()) {
2259
+ if (mappedSessionId === sessionId) {
2260
+ this.mcpSessions.delete(mcpSessionId);
2261
+ mcpIds.push(mcpSessionId);
2262
+ }
2263
+ }
2264
+ if (participantIds.length > 0 || mcpIds.length > 0) {
2265
+ await Promise.all([
2266
+ ...participantIds.map((id) => this.deleteRoutingRow(`${SESSION_STATE_PARTICIPANT_PREFIX}${id}`)),
2267
+ ...mcpIds.map((id) => this.deleteRoutingRow(`${SESSION_STATE_MCP_PREFIX}${id}`))
2268
+ ]);
2269
+ }
2270
+ }
2271
+ applySessionState(state) {
2272
+ this.participantSessions = new Map(state.participantSessions);
2273
+ this.mcpSessions = new Map(state.mcpSessions);
2274
+ this.defaultSessionId = state.defaultSessionId ?? null;
2275
+ this.defaultParticipantId = state.defaultParticipantId ?? null;
2276
+ }
2277
+ async recoverActiveSession() {
2278
+ await this.loadSessionMappings();
2279
+ if (!this.defaultSessionId)
2280
+ return;
2281
+ const existing = await this.getSession(this.defaultSessionId);
2282
+ if (!existing || existing.status !== 'active')
2283
+ await this.setDefaultActiveSession(null);
2284
+ }
2285
+ async ensureSessionActive(sessionId) {
2286
+ await this.ensureStorage();
2287
+ const session = await this.getSession(sessionId);
2288
+ if (!session) {
2289
+ throw sessionNotFound(sessionId);
2290
+ }
2291
+ if (session.status !== 'active') {
2292
+ throw sessionNotWritable(sessionId, session.status);
2293
+ }
2294
+ return session;
2295
+ }
2296
+ // MCP SESSION MAPPING
2297
+ /**
2298
+ * Link MCP HTTP session to Vibes session
2299
+ *
2300
+ * MCP HTTP transport maintains connection-based sessions.
2301
+ * This maps those to active Vibes sessions for tool execution context.
2302
+ *
2303
+ * @param mcpSessionId - MCP HTTP session identifier
2304
+ * @param vibesSessionId - Vibes session UUID
2305
+ */
2306
+ async linkMcpSession(mcpSessionId, vibesSessionId) {
2307
+ await this.ensureStorage();
2308
+ const session = await this.getSession(vibesSessionId);
2309
+ if (!session || session.status !== 'active') {
2310
+ throw new CapabilityError(`Cannot bind MCP connection to inactive session ${vibesSessionId}`, session ? 'CONFLICT' : 'NOT_FOUND', { reason: session ? 'session-not-writable' : 'session-not-found', details: { sessionId: vibesSessionId } });
2311
+ }
2312
+ this.mcpSessions.set(mcpSessionId, vibesSessionId);
2313
+ await this.persistRoutingRow(`${SESSION_STATE_MCP_PREFIX}${mcpSessionId}`, {
2314
+ sessionId: vibesSessionId
2315
+ });
2316
+ }
2317
+ /**
2318
+ * Get Vibes session for MCP connection
2319
+ *
2320
+ * @param mcpSessionId - MCP HTTP session identifier
2321
+ * @returns Session if mapped and exists, null otherwise
2322
+ */
2323
+ async getSessionForMcpConnection(mcpSessionId) {
2324
+ await this.ensureStorage();
2325
+ const vibesSessionId = this.mcpSessions.get(mcpSessionId);
2326
+ if (!vibesSessionId) {
2327
+ return null;
2328
+ }
2329
+ const session = await this.getSession(vibesSessionId);
2330
+ if (!session || session.status === 'archived') {
2331
+ this.mcpSessions.delete(mcpSessionId);
2332
+ await this.deleteRoutingRow(`${SESSION_STATE_MCP_PREFIX}${mcpSessionId}`);
2333
+ return null;
2334
+ }
2335
+ return session;
2336
+ }
2337
+ /**
2338
+ * Unlink MCP HTTP session from Vibes session
2339
+ *
2340
+ * Called when MCP session closes or Vibes session is archived.
2341
+ *
2342
+ * @param mcpSessionId - MCP HTTP session identifier
2343
+ */
2344
+ async unlinkMcpSession(mcpSessionId) {
2345
+ await this.ensureStorage();
2346
+ if (this.mcpSessions.delete(mcpSessionId)) {
2347
+ await this.deleteRoutingRow(`${SESSION_STATE_MCP_PREFIX}${mcpSessionId}`);
2348
+ }
2349
+ }
2350
+ /**
2351
+ * Clear all MCP session mappings for a given Vibes session
2352
+ *
2353
+ * Called when a session is archived or deleted.
2354
+ *
2355
+ * @param vibesSessionId - Vibes session UUID
2356
+ */
2357
+ async clearMcpSessionsForVibesSession(vibesSessionId) {
2358
+ await this.ensureStorage();
2359
+ const removed = [];
2360
+ for (const [mcpId, vibesId] of this.mcpSessions.entries()) {
2361
+ if (vibesId === vibesSessionId) {
2362
+ this.mcpSessions.delete(mcpId);
2363
+ removed.push(mcpId);
2364
+ }
2365
+ }
2366
+ await Promise.all(removed.map((mcpId) => this.deleteRoutingRow(`${SESSION_STATE_MCP_PREFIX}${mcpId}`)));
2367
+ }
2368
+ // PRESENCE TRACKING
2369
+ /**
2370
+ * Record presence for a connection to a session
2371
+ */
2372
+ async recordPresence(input) {
2373
+ await this.ensureStorage();
2374
+ const now = new Date();
2375
+ const presence = await this.presenceStorage.create({
2376
+ contextType: 'session',
2377
+ contextId: input.sessionId,
2378
+ participantId: input.participantId,
2379
+ connectionId: input.connectionId,
2380
+ metadata: input.metadata,
2381
+ lastHeartbeat: now.toISOString(),
2382
+ status: 'active',
2383
+ connectedAt: now.toISOString()
2384
+ });
2385
+ if (input.participantId) {
2386
+ // Lease authority must not be swallowed: presence without a lease
2387
+ // creates a ghost connection absent from the membership roster.
2388
+ try {
2389
+ await this.acquireMembershipLease({
2390
+ sessionId: input.sessionId,
2391
+ participantId: input.participantId,
2392
+ connectionKey: input.connectionId,
2393
+ role: 'member',
2394
+ metadata: input.metadata
2395
+ });
2396
+ }
2397
+ catch (error) {
2398
+ // Presence without a lease is a ghost roster entry — roll back. The
2399
+ // lease failure is what surfaces; a failed rollback is reported too.
2400
+ const [rollback] = await Promise.allSettled([this.presenceStorage.delete(presence.id)]);
2401
+ if (rollback.status === 'rejected') {
2402
+ this.logger.warn('Failed to roll back presence after lease failure', {
2403
+ presenceId: presence.id,
2404
+ error: valueFreeFailure(CaughtFailureSchema.parse(rollback.reason))
2405
+ });
2406
+ }
2407
+ throw error;
2408
+ }
2409
+ }
2410
+ return presence;
2411
+ }
2412
+ /**
2413
+ * Update heartbeat for a connection
2414
+ */
2415
+ async updateHeartbeat(connectionId) {
2416
+ await this.ensureStorage();
2417
+ const now = new Date();
2418
+ // Find presence by connectionId
2419
+ const result = await this.presenceStorage.list({
2420
+ where: { connectionId },
2421
+ limit: 1
2422
+ });
2423
+ const presence = result.items[0];
2424
+ if (!presence)
2425
+ return;
2426
+ await this.presenceStorage.update(presence.id, {
2427
+ lastHeartbeat: now.toISOString()
2428
+ });
2429
+ // Renew the membership lease on the same cadence as presence heartbeat
2430
+ // so the roster cannot expire while the connection is still live.
2431
+ if (presence.participantId) {
2432
+ await this.acquireMembershipLease({
2433
+ sessionId: presence.contextId,
2434
+ participantId: presence.participantId,
2435
+ connectionKey: presence.connectionId ?? connectionId
2436
+ });
2437
+ }
2438
+ }
2439
+ /**
2440
+ * Get active presence records for a session
2441
+ */
2442
+ async getActivePresence(sessionId) {
2443
+ await this.ensureStorage();
2444
+ // Get recent presence records (last 5 minutes)
2445
+ const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
2446
+ const result = await this.presenceStorage.list({
2447
+ where: { contextId: sessionId, status: 'active' },
2448
+ limit: 100
2449
+ });
2450
+ // Filter by heartbeat
2451
+ return result.items.filter((p) => p.lastHeartbeat >= fiveMinutesAgo);
2452
+ }
2453
+ /**
2454
+ * Get presence summary for a session
2455
+ */
2456
+ async getPresenceSummary(sessionId) {
2457
+ await this.ensureStorage();
2458
+ const active = await this.listActiveMembershipLeases(sessionId);
2459
+ const asIso = (value) => value instanceof Date ? value.toISOString() : value;
2460
+ return {
2461
+ sessionId,
2462
+ activeCount: active.length,
2463
+ idleCount: 0,
2464
+ staleCount: 0,
2465
+ totalCount: active.length,
2466
+ isOccupied: active.length > 0,
2467
+ connections: active.map((lease) => ({
2468
+ id: lease.id,
2469
+ status: 'active',
2470
+ lastHeartbeat: asIso(lease.lastHeartbeatAt),
2471
+ connectionId: lease.connectionKey,
2472
+ participantId: lease.participantId
2473
+ }))
2474
+ };
2475
+ }
2476
+ /**
2477
+ * Check if session is occupied (has active/idle connections)
2478
+ */
2479
+ async isSessionOccupied(sessionId) {
2480
+ await this.ensureStorage();
2481
+ const active = await this.listActiveMembershipLeases(sessionId);
2482
+ return active.length > 0;
2483
+ }
2484
+ // MEMBERSHIP LEASES
2485
+ static MEMBERSHIP_LEASE_TTL_MS = 10 * 60 * 1000;
2486
+ normalizeConnectionKey(connectionKey) {
2487
+ const key = connectionKey?.trim() ?? '';
2488
+ return key.length > 0 ? key : 'local';
2489
+ }
2490
+ /**
2491
+ * Acquire or renew a membership lease. Idempotent on
2492
+ * (sessionId, participantId, connectionKey).
2493
+ */
2494
+ async acquireMembershipLease(input) {
2495
+ await this.ensureStorage();
2496
+ const connectionKey = this.normalizeConnectionKey(input.connectionKey);
2497
+ const now = Date.now();
2498
+ const ttl = Math.max(30_000, input.ttlMs ?? SessionManager.MEMBERSHIP_LEASE_TTL_MS);
2499
+ const nowIso = new Date(now).toISOString();
2500
+ const expiresAt = new Date(now + ttl).toISOString();
2501
+ const existing = await this.membershipLeasesStorage.list({
2502
+ where: {
2503
+ sessionId: input.sessionId,
2504
+ participantId: input.participantId,
2505
+ activeConnectionKey: connectionKey
2506
+ },
2507
+ limit: 1
2508
+ });
2509
+ const row = existing.items[0];
2510
+ if (row) {
2511
+ return this.membershipLeasesStorage.update(row.id, {
2512
+ role: input.role ?? row.role ?? 'member',
2513
+ lastHeartbeatAt: nowIso,
2514
+ expiresAt,
2515
+ releasedAt: null,
2516
+ activeConnectionKey: connectionKey,
2517
+ claimedTodoIds: input.claimedTodoIds,
2518
+ metadata: input.metadata ? { ...row.metadata, ...input.metadata } : undefined
2519
+ });
2520
+ }
2521
+ return writeOrRecover(() => this.membershipLeasesStorage.create({
2522
+ sessionId: input.sessionId,
2523
+ participantId: input.participantId,
2524
+ connectionKey,
2525
+ activeConnectionKey: connectionKey,
2526
+ role: input.role ?? 'member',
2527
+ joinedAt: nowIso,
2528
+ lastHeartbeatAt: nowIso,
2529
+ expiresAt,
2530
+ releasedAt: null,
2531
+ claimedTodoIds: input.claimedTodoIds ?? [],
2532
+ metadata: input.metadata ?? {}
2533
+ }), async () => {
2534
+ const raced = await this.membershipLeasesStorage.list({
2535
+ where: {
2536
+ sessionId: input.sessionId,
2537
+ participantId: input.participantId,
2538
+ activeConnectionKey: connectionKey
2539
+ },
2540
+ limit: 1
2541
+ });
2542
+ const racedRow = raced.items[0];
2543
+ if (!racedRow)
2544
+ return null;
2545
+ return this.membershipLeasesStorage.update(racedRow.id, {
2546
+ lastHeartbeatAt: nowIso,
2547
+ expiresAt,
2548
+ releasedAt: null,
2549
+ activeConnectionKey: connectionKey
2550
+ });
2551
+ }, isUniqueConstraintError);
2552
+ }
2553
+ async releaseMembershipLease(input) {
2554
+ await this.ensureStorage();
2555
+ const connectionKey = this.normalizeConnectionKey(input.connectionKey);
2556
+ const existing = await this.membershipLeasesStorage.list({
2557
+ where: {
2558
+ sessionId: input.sessionId,
2559
+ participantId: input.participantId,
2560
+ activeConnectionKey: connectionKey
2561
+ },
2562
+ limit: 1
2563
+ });
2564
+ const row = existing.items[0];
2565
+ if (!row || row.releasedAt)
2566
+ return false;
2567
+ await this.membershipLeasesStorage.update(row.id, {
2568
+ releasedAt: new Date().toISOString(),
2569
+ activeConnectionKey: null
2570
+ });
2571
+ return true;
2572
+ }
2573
+ /** Live roster: unreleased leases whose expiry is still in the future. */
2574
+ async listActiveMembershipLeases(sessionId) {
2575
+ await this.ensureStorage();
2576
+ const nowIso = new Date().toISOString();
2577
+ const leases = [];
2578
+ const pageSize = 500;
2579
+ const where = {
2580
+ sessionId,
2581
+ releasedAt: { isNull: true }
2582
+ };
2583
+ for (let offset = 0;; offset += pageSize) {
2584
+ const result = await this.membershipLeasesStorage.list({
2585
+ where,
2586
+ limit: pageSize,
2587
+ offset
2588
+ });
2589
+ leases.push(...result.items);
2590
+ if (result.items.length < pageSize)
2591
+ break;
2592
+ }
2593
+ return leases
2594
+ .filter((lease) => !lease.releasedAt && String(lease.expiresAt) >= nowIso)
2595
+ .sort((a, b) => {
2596
+ const roleRank = (role) => (role === 'owner' ? 0 : 1);
2597
+ const byRole = roleRank(a.role) - roleRank(b.role);
2598
+ if (byRole !== 0)
2599
+ return byRole;
2600
+ return String(a.participantId).localeCompare(String(b.participantId));
2601
+ });
2602
+ }
2603
+ /**
2604
+ * Remove presence record (on disconnect) and release its membership lease.
2605
+ */
2606
+ async removePresence(connectionId) {
2607
+ await this.ensureStorage();
2608
+ // Find presence by connectionId
2609
+ const result = await this.presenceStorage.list({
2610
+ where: { connectionId },
2611
+ limit: 1
2612
+ });
2613
+ const presence = result.items[0];
2614
+ if (!presence)
2615
+ return;
2616
+ // Release before delete so a lease-authority failure cannot leave a
2617
+ // ghost roster entry after presence is gone.
2618
+ if (presence.participantId) {
2619
+ await this.releaseMembershipLease({
2620
+ sessionId: presence.contextId,
2621
+ participantId: presence.participantId,
2622
+ connectionKey: presence.connectionId ?? connectionId
2623
+ });
2624
+ }
2625
+ await this.presenceStorage.delete(presence.id);
2626
+ }
2627
+ /**
2628
+ * Cleanup stale presence records and release any associated leases.
2629
+ */
2630
+ async cleanupStalePresence(thresholdMs) {
2631
+ await this.ensureStorage();
2632
+ const threshold = new Date(Date.now() - (thresholdMs ?? 5 * 60 * 1000)).toISOString();
2633
+ // Get stale records
2634
+ const result = await this.presenceStorage.list({
2635
+ limit: 1000
2636
+ });
2637
+ const stale = result.items.filter((p) => p.lastHeartbeat < threshold);
2638
+ // Delete them (and release leases — same contract as removePresence)
2639
+ for (const presence of stale) {
2640
+ if (presence.participantId) {
2641
+ await this.releaseMembershipLease({
2642
+ sessionId: presence.contextId,
2643
+ participantId: presence.participantId,
2644
+ connectionKey: presence.connectionId ?? undefined
2645
+ });
2646
+ }
2647
+ await this.presenceStorage.delete(presence.id);
2648
+ }
2649
+ return stale.length;
2650
+ }
2651
+ /**
2652
+ * Resolve session with occupation awareness
2653
+ * Returns the first unoccupied session from candidates, or null if all occupied
2654
+ */
2655
+ async resolveUnoccupiedSession(candidateIds) {
2656
+ await this.ensureStorage();
2657
+ for (const sessionId of candidateIds) {
2658
+ const session = await this.getSession(sessionId);
2659
+ if (!session || session.status === 'archived')
2660
+ continue;
2661
+ const occupied = await this.isSessionOccupied(sessionId);
2662
+ if (!occupied) {
2663
+ return session;
2664
+ }
2665
+ }
2666
+ return null;
2667
+ }
2668
+ }
2669
+ //# sourceMappingURL=manager.js.map