@namzu/sdk 0.1.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (626) hide show
  1. package/CHANGELOG.md +69 -2
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +5 -2
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/RouterAgent.d.ts.map +1 -1
  6. package/dist/agents/RouterAgent.js +3 -0
  7. package/dist/agents/RouterAgent.js.map +1 -1
  8. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  9. package/dist/agents/SupervisorAgent.js +21 -5
  10. package/dist/agents/SupervisorAgent.js.map +1 -1
  11. package/dist/bridge/a2a/index.d.ts +1 -1
  12. package/dist/bridge/a2a/index.d.ts.map +1 -1
  13. package/dist/bridge/a2a/index.js +1 -1
  14. package/dist/bridge/a2a/index.js.map +1 -1
  15. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  16. package/dist/bridge/a2a/mapper.js +6 -0
  17. package/dist/bridge/a2a/mapper.js.map +1 -1
  18. package/dist/bridge/a2a/message.d.ts +0 -2
  19. package/dist/bridge/a2a/message.d.ts.map +1 -1
  20. package/dist/bridge/a2a/message.js +0 -26
  21. package/dist/bridge/a2a/message.js.map +1 -1
  22. package/dist/bridge/a2a/task.d.ts +5 -4
  23. package/dist/bridge/a2a/task.d.ts.map +1 -1
  24. package/dist/bridge/a2a/task.js +4 -4
  25. package/dist/bridge/a2a/task.js.map +1 -1
  26. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  27. package/dist/bridge/sse/mapper.js +6 -0
  28. package/dist/bridge/sse/mapper.js.map +1 -1
  29. package/dist/constants/a2a/index.d.ts +2 -2
  30. package/dist/constants/a2a/index.d.ts.map +1 -1
  31. package/dist/constants/a2a/index.js.map +1 -1
  32. package/dist/contracts/api.d.ts +14 -27
  33. package/dist/contracts/api.d.ts.map +1 -1
  34. package/dist/contracts/ids.d.ts +1 -1
  35. package/dist/contracts/ids.d.ts.map +1 -1
  36. package/dist/contracts/index.d.ts +3 -3
  37. package/dist/contracts/index.d.ts.map +1 -1
  38. package/dist/contracts/index.js +1 -1
  39. package/dist/contracts/index.js.map +1 -1
  40. package/dist/contracts/schemas.d.ts +1 -31
  41. package/dist/contracts/schemas.d.ts.map +1 -1
  42. package/dist/contracts/schemas.js +1 -7
  43. package/dist/contracts/schemas.js.map +1 -1
  44. package/dist/gateway/local.d.ts.map +1 -1
  45. package/dist/gateway/local.js +6 -0
  46. package/dist/gateway/local.js.map +1 -1
  47. package/dist/index.d.ts +6 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +6 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/manager/agent/__tests__/lifecycle.test.d.ts +2 -0
  52. package/dist/manager/agent/__tests__/lifecycle.test.d.ts.map +1 -0
  53. package/dist/manager/agent/__tests__/lifecycle.test.js +316 -0
  54. package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -0
  55. package/dist/manager/agent/lifecycle.d.ts +67 -3
  56. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  57. package/dist/manager/agent/lifecycle.js +375 -14
  58. package/dist/manager/agent/lifecycle.js.map +1 -1
  59. package/dist/manager/index.d.ts +2 -0
  60. package/dist/manager/index.d.ts.map +1 -1
  61. package/dist/manager/index.js +1 -0
  62. package/dist/manager/index.js.map +1 -1
  63. package/dist/manager/run/persistence.d.ts +10 -1
  64. package/dist/manager/run/persistence.d.ts.map +1 -1
  65. package/dist/manager/run/persistence.js +20 -0
  66. package/dist/manager/run/persistence.js.map +1 -1
  67. package/dist/manager/thread/__tests__/lifecycle.test.d.ts +2 -0
  68. package/dist/manager/thread/__tests__/lifecycle.test.d.ts.map +1 -0
  69. package/dist/manager/thread/__tests__/lifecycle.test.js +216 -0
  70. package/dist/manager/thread/__tests__/lifecycle.test.js.map +1 -0
  71. package/dist/manager/thread/lifecycle.d.ts +105 -0
  72. package/dist/manager/thread/lifecycle.d.ts.map +1 -0
  73. package/dist/manager/thread/lifecycle.js +186 -0
  74. package/dist/manager/thread/lifecycle.js.map +1 -0
  75. package/dist/rag/retriever.js +2 -2
  76. package/dist/run/reporter.d.ts.map +1 -1
  77. package/dist/run/reporter.js +25 -0
  78. package/dist/run/reporter.js.map +1 -1
  79. package/dist/runtime/query/__tests__/context.test.d.ts +2 -0
  80. package/dist/runtime/query/__tests__/context.test.d.ts.map +1 -0
  81. package/dist/runtime/query/__tests__/context.test.js +85 -0
  82. package/dist/runtime/query/__tests__/context.test.js.map +1 -0
  83. package/dist/runtime/query/context-cache.d.ts +3 -3
  84. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  85. package/dist/runtime/query/context-cache.js +2 -2
  86. package/dist/runtime/query/context-cache.js.map +1 -1
  87. package/dist/runtime/query/context.d.ts +45 -1
  88. package/dist/runtime/query/context.d.ts.map +1 -1
  89. package/dist/runtime/query/context.js +50 -8
  90. package/dist/runtime/query/context.js.map +1 -1
  91. package/dist/runtime/query/events.d.ts.map +1 -1
  92. package/dist/runtime/query/events.js +8 -0
  93. package/dist/runtime/query/events.js.map +1 -1
  94. package/dist/runtime/query/index.d.ts +22 -1
  95. package/dist/runtime/query/index.d.ts.map +1 -1
  96. package/dist/runtime/query/index.js +11 -0
  97. package/dist/runtime/query/index.js.map +1 -1
  98. package/dist/session/__tests__/integration/_fixtures.d.ts +122 -0
  99. package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -0
  100. package/dist/session/__tests__/integration/_fixtures.js +215 -0
  101. package/dist/session/__tests__/integration/_fixtures.js.map +1 -0
  102. package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
  103. package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
  104. package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
  105. package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
  106. package/dist/session/__tests__/integration/capacity-caps.test.d.ts +13 -0
  107. package/dist/session/__tests__/integration/capacity-caps.test.d.ts.map +1 -0
  108. package/dist/session/__tests__/integration/capacity-caps.test.js +123 -0
  109. package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -0
  110. package/dist/session/__tests__/integration/e2e-spawn.test.d.ts +18 -0
  111. package/dist/session/__tests__/integration/e2e-spawn.test.d.ts.map +1 -0
  112. package/dist/session/__tests__/integration/e2e-spawn.test.js +238 -0
  113. package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -0
  114. package/dist/session/__tests__/integration/event-stream-ordering.test.d.ts +15 -0
  115. package/dist/session/__tests__/integration/event-stream-ordering.test.d.ts.map +1 -0
  116. package/dist/session/__tests__/integration/event-stream-ordering.test.js +330 -0
  117. package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -0
  118. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.d.ts +12 -0
  119. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.d.ts.map +1 -0
  120. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +182 -0
  121. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -0
  122. package/dist/session/__tests__/integration/handoff-illegal-transition.test.d.ts +18 -0
  123. package/dist/session/__tests__/integration/handoff-illegal-transition.test.d.ts.map +1 -0
  124. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +156 -0
  125. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -0
  126. package/dist/session/__tests__/integration/handoff-single-e2e.test.d.ts +15 -0
  127. package/dist/session/__tests__/integration/handoff-single-e2e.test.d.ts.map +1 -0
  128. package/dist/session/__tests__/integration/handoff-single-e2e.test.js +179 -0
  129. package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -0
  130. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.d.ts +12 -0
  131. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.d.ts.map +1 -0
  132. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +158 -0
  133. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -0
  134. package/dist/session/__tests__/integration/migration-filesystem.test.d.ts +11 -0
  135. package/dist/session/__tests__/integration/migration-filesystem.test.d.ts.map +1 -0
  136. package/dist/session/__tests__/integration/migration-filesystem.test.js +140 -0
  137. package/dist/session/__tests__/integration/migration-filesystem.test.js.map +1 -0
  138. package/dist/session/__tests__/integration/migration-id-prefix.test.d.ts +13 -0
  139. package/dist/session/__tests__/integration/migration-id-prefix.test.d.ts.map +1 -0
  140. package/dist/session/__tests__/integration/migration-id-prefix.test.js +84 -0
  141. package/dist/session/__tests__/integration/migration-id-prefix.test.js.map +1 -0
  142. package/dist/session/__tests__/integration/prev-artifact-dag.test.d.ts +14 -0
  143. package/dist/session/__tests__/integration/prev-artifact-dag.test.d.ts.map +1 -0
  144. package/dist/session/__tests__/integration/prev-artifact-dag.test.js +242 -0
  145. package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -0
  146. package/dist/session/__tests__/integration/retention-archive.test.d.ts +12 -0
  147. package/dist/session/__tests__/integration/retention-archive.test.d.ts.map +1 -0
  148. package/dist/session/__tests__/integration/retention-archive.test.js +187 -0
  149. package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -0
  150. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
  151. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
  152. package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
  153. package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
  154. package/dist/session/__tests__/integration/summary-materialization-e2e.test.d.ts +18 -0
  155. package/dist/session/__tests__/integration/summary-materialization-e2e.test.d.ts.map +1 -0
  156. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +201 -0
  157. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -0
  158. package/dist/session/__tests__/integration/tenant-isolation.test.d.ts +14 -0
  159. package/dist/session/__tests__/integration/tenant-isolation.test.d.ts.map +1 -0
  160. package/dist/session/__tests__/integration/tenant-isolation.test.js +189 -0
  161. package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -0
  162. package/dist/session/errors.d.ts +139 -0
  163. package/dist/session/errors.d.ts.map +1 -0
  164. package/dist/session/errors.js +107 -0
  165. package/dist/session/errors.js.map +1 -0
  166. package/dist/session/events/index.d.ts +4 -0
  167. package/dist/session/events/index.d.ts.map +1 -0
  168. package/dist/session/events/index.js +8 -0
  169. package/dist/session/events/index.js.map +1 -0
  170. package/dist/session/events/schema-version.d.ts +13 -0
  171. package/dist/session/events/schema-version.d.ts.map +1 -0
  172. package/dist/session/events/schema-version.js +12 -0
  173. package/dist/session/events/schema-version.js.map +1 -0
  174. package/dist/session/events/types.d.ts +64 -0
  175. package/dist/session/events/types.d.ts.map +1 -0
  176. package/dist/session/events/types.js +2 -0
  177. package/dist/session/events/types.js.map +1 -0
  178. package/dist/session/handoff/__tests__/broadcast.test.d.ts +2 -0
  179. package/dist/session/handoff/__tests__/broadcast.test.d.ts.map +1 -0
  180. package/dist/session/handoff/__tests__/broadcast.test.js +261 -0
  181. package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -0
  182. package/dist/session/handoff/__tests__/capacity.test.d.ts +2 -0
  183. package/dist/session/handoff/__tests__/capacity.test.d.ts.map +1 -0
  184. package/dist/session/handoff/__tests__/capacity.test.js +103 -0
  185. package/dist/session/handoff/__tests__/capacity.test.js.map +1 -0
  186. package/dist/session/handoff/__tests__/single.test.d.ts +2 -0
  187. package/dist/session/handoff/__tests__/single.test.d.ts.map +1 -0
  188. package/dist/session/handoff/__tests__/single.test.js +239 -0
  189. package/dist/session/handoff/__tests__/single.test.js.map +1 -0
  190. package/dist/session/handoff/assignment.d.ts +71 -0
  191. package/dist/session/handoff/assignment.d.ts.map +1 -0
  192. package/dist/session/handoff/assignment.js +11 -0
  193. package/dist/session/handoff/assignment.js.map +1 -0
  194. package/dist/session/handoff/broadcast.d.ts +54 -0
  195. package/dist/session/handoff/broadcast.d.ts.map +1 -0
  196. package/dist/session/handoff/broadcast.js +311 -0
  197. package/dist/session/handoff/broadcast.js.map +1 -0
  198. package/dist/session/handoff/capacity.d.ts +66 -0
  199. package/dist/session/handoff/capacity.d.ts.map +1 -0
  200. package/dist/session/handoff/capacity.js +60 -0
  201. package/dist/session/handoff/capacity.js.map +1 -0
  202. package/dist/session/handoff/events.d.ts +66 -0
  203. package/dist/session/handoff/events.d.ts.map +1 -0
  204. package/dist/session/handoff/events.js +13 -0
  205. package/dist/session/handoff/events.js.map +1 -0
  206. package/dist/session/handoff/index.d.ts +12 -0
  207. package/dist/session/handoff/index.d.ts.map +1 -0
  208. package/dist/session/handoff/index.js +9 -0
  209. package/dist/session/handoff/index.js.map +1 -0
  210. package/dist/session/handoff/single.d.ts +69 -0
  211. package/dist/session/handoff/single.d.ts.map +1 -0
  212. package/dist/session/handoff/single.js +229 -0
  213. package/dist/session/handoff/single.js.map +1 -0
  214. package/dist/session/handoff/version.d.ts +52 -0
  215. package/dist/session/handoff/version.d.ts.map +1 -0
  216. package/dist/session/handoff/version.js +36 -0
  217. package/dist/session/handoff/version.js.map +1 -0
  218. package/dist/session/hierarchy/__tests__/session.test.d.ts +2 -0
  219. package/dist/session/hierarchy/__tests__/session.test.d.ts.map +1 -0
  220. package/dist/session/hierarchy/__tests__/session.test.js +69 -0
  221. package/dist/session/hierarchy/__tests__/session.test.js.map +1 -0
  222. package/dist/session/hierarchy/actor.d.ts +26 -0
  223. package/dist/session/hierarchy/actor.d.ts.map +1 -0
  224. package/dist/session/hierarchy/actor.js +2 -0
  225. package/dist/session/hierarchy/actor.js.map +1 -0
  226. package/dist/session/hierarchy/index.d.ts +9 -0
  227. package/dist/session/hierarchy/index.d.ts.map +1 -0
  228. package/dist/session/hierarchy/index.js +4 -0
  229. package/dist/session/hierarchy/index.js.map +1 -0
  230. package/dist/session/hierarchy/lineage.d.ts +15 -0
  231. package/dist/session/hierarchy/lineage.d.ts.map +1 -0
  232. package/dist/session/hierarchy/lineage.js +2 -0
  233. package/dist/session/hierarchy/lineage.js.map +1 -0
  234. package/dist/session/hierarchy/project.d.ts +40 -0
  235. package/dist/session/hierarchy/project.d.ts.map +1 -0
  236. package/dist/session/hierarchy/project.js +2 -0
  237. package/dist/session/hierarchy/project.js.map +1 -0
  238. package/dist/session/hierarchy/session.d.ts +71 -0
  239. package/dist/session/hierarchy/session.d.ts.map +1 -0
  240. package/dist/session/hierarchy/session.js +51 -0
  241. package/dist/session/hierarchy/session.js.map +1 -0
  242. package/dist/session/hierarchy/sub-session.d.ts +76 -0
  243. package/dist/session/hierarchy/sub-session.d.ts.map +1 -0
  244. package/dist/session/hierarchy/sub-session.js +2 -0
  245. package/dist/session/hierarchy/sub-session.js.map +1 -0
  246. package/dist/session/hierarchy/tenant.d.ts +13 -0
  247. package/dist/session/hierarchy/tenant.d.ts.map +1 -0
  248. package/dist/session/hierarchy/tenant.js +2 -0
  249. package/dist/session/hierarchy/tenant.js.map +1 -0
  250. package/dist/session/hierarchy/thread.d.ts +54 -0
  251. package/dist/session/hierarchy/thread.d.ts.map +1 -0
  252. package/dist/session/hierarchy/thread.js +2 -0
  253. package/dist/session/hierarchy/thread.js.map +1 -0
  254. package/dist/session/index.d.ts +10 -0
  255. package/dist/session/index.d.ts.map +1 -0
  256. package/dist/session/index.js +15 -0
  257. package/dist/session/index.js.map +1 -0
  258. package/dist/session/intervention/__tests__/prev-artifact.test.d.ts +2 -0
  259. package/dist/session/intervention/__tests__/prev-artifact.test.d.ts.map +1 -0
  260. package/dist/session/intervention/__tests__/prev-artifact.test.js +179 -0
  261. package/dist/session/intervention/__tests__/prev-artifact.test.js.map +1 -0
  262. package/dist/session/intervention/index.d.ts +3 -0
  263. package/dist/session/intervention/index.d.ts.map +1 -0
  264. package/dist/session/intervention/index.js +8 -0
  265. package/dist/session/intervention/index.js.map +1 -0
  266. package/dist/session/intervention/prev-artifact.d.ts +103 -0
  267. package/dist/session/intervention/prev-artifact.d.ts.map +1 -0
  268. package/dist/session/intervention/prev-artifact.js +112 -0
  269. package/dist/session/intervention/prev-artifact.js.map +1 -0
  270. package/dist/session/migration/__tests__/filesystem.test.d.ts +2 -0
  271. package/dist/session/migration/__tests__/filesystem.test.d.ts.map +1 -0
  272. package/dist/session/migration/__tests__/filesystem.test.js +188 -0
  273. package/dist/session/migration/__tests__/filesystem.test.js.map +1 -0
  274. package/dist/session/migration/__tests__/id-prefix.test.d.ts +2 -0
  275. package/dist/session/migration/__tests__/id-prefix.test.d.ts.map +1 -0
  276. package/dist/session/migration/__tests__/id-prefix.test.js +83 -0
  277. package/dist/session/migration/__tests__/id-prefix.test.js.map +1 -0
  278. package/dist/session/migration/__tests__/marker.test.d.ts +2 -0
  279. package/dist/session/migration/__tests__/marker.test.d.ts.map +1 -0
  280. package/dist/session/migration/__tests__/marker.test.js +75 -0
  281. package/dist/session/migration/__tests__/marker.test.js.map +1 -0
  282. package/dist/session/migration/errors.d.ts +26 -0
  283. package/dist/session/migration/errors.d.ts.map +1 -0
  284. package/dist/session/migration/errors.js +22 -0
  285. package/dist/session/migration/errors.js.map +1 -0
  286. package/dist/session/migration/filesystem.d.ts +94 -0
  287. package/dist/session/migration/filesystem.d.ts.map +1 -0
  288. package/dist/session/migration/filesystem.js +319 -0
  289. package/dist/session/migration/filesystem.js.map +1 -0
  290. package/dist/session/migration/id-prefix.d.ts +93 -0
  291. package/dist/session/migration/id-prefix.d.ts.map +1 -0
  292. package/dist/session/migration/id-prefix.js +111 -0
  293. package/dist/session/migration/id-prefix.js.map +1 -0
  294. package/dist/session/migration/index.d.ts +8 -0
  295. package/dist/session/migration/index.d.ts.map +1 -0
  296. package/dist/session/migration/index.js +8 -0
  297. package/dist/session/migration/index.js.map +1 -0
  298. package/dist/session/migration/marker.d.ts +57 -0
  299. package/dist/session/migration/marker.d.ts.map +1 -0
  300. package/dist/session/migration/marker.js +111 -0
  301. package/dist/session/migration/marker.js.map +1 -0
  302. package/dist/session/retention/__tests__/archive.test.d.ts +2 -0
  303. package/dist/session/retention/__tests__/archive.test.d.ts.map +1 -0
  304. package/dist/session/retention/__tests__/archive.test.js +253 -0
  305. package/dist/session/retention/__tests__/archive.test.js.map +1 -0
  306. package/dist/session/retention/__tests__/disk-backend.test.d.ts +2 -0
  307. package/dist/session/retention/__tests__/disk-backend.test.d.ts.map +1 -0
  308. package/dist/session/retention/__tests__/disk-backend.test.js +154 -0
  309. package/dist/session/retention/__tests__/disk-backend.test.js.map +1 -0
  310. package/dist/session/retention/archive-backend-ref.d.ts +18 -0
  311. package/dist/session/retention/archive-backend-ref.d.ts.map +1 -0
  312. package/dist/session/retention/archive-backend-ref.js +2 -0
  313. package/dist/session/retention/archive-backend-ref.js.map +1 -0
  314. package/dist/session/retention/archive.d.ts +130 -0
  315. package/dist/session/retention/archive.d.ts.map +1 -0
  316. package/dist/session/retention/archive.js +203 -0
  317. package/dist/session/retention/archive.js.map +1 -0
  318. package/dist/session/retention/backend.d.ts +101 -0
  319. package/dist/session/retention/backend.d.ts.map +1 -0
  320. package/dist/session/retention/backend.js +15 -0
  321. package/dist/session/retention/backend.js.map +1 -0
  322. package/dist/session/retention/disk-backend.d.ts +59 -0
  323. package/dist/session/retention/disk-backend.d.ts.map +1 -0
  324. package/dist/session/retention/disk-backend.js +236 -0
  325. package/dist/session/retention/disk-backend.js.map +1 -0
  326. package/dist/session/retention/index.d.ts +9 -0
  327. package/dist/session/retention/index.d.ts.map +1 -0
  328. package/dist/session/retention/index.js +6 -0
  329. package/dist/session/retention/index.js.map +1 -0
  330. package/dist/session/retention/policy.d.ts +49 -0
  331. package/dist/session/retention/policy.d.ts.map +1 -0
  332. package/dist/session/retention/policy.js +21 -0
  333. package/dist/session/retention/policy.js.map +1 -0
  334. package/dist/session/summary/__tests__/materialize.test.d.ts +2 -0
  335. package/dist/session/summary/__tests__/materialize.test.d.ts.map +1 -0
  336. package/dist/session/summary/__tests__/materialize.test.js +270 -0
  337. package/dist/session/summary/__tests__/materialize.test.js.map +1 -0
  338. package/dist/session/summary/deliverable.d.ts +74 -0
  339. package/dist/session/summary/deliverable.d.ts.map +1 -0
  340. package/dist/session/summary/deliverable.js +20 -0
  341. package/dist/session/summary/deliverable.js.map +1 -0
  342. package/dist/session/summary/index.d.ts +6 -0
  343. package/dist/session/summary/index.d.ts.map +1 -0
  344. package/dist/session/summary/index.js +9 -0
  345. package/dist/session/summary/index.js.map +1 -0
  346. package/dist/session/summary/materialize.d.ts +82 -0
  347. package/dist/session/summary/materialize.d.ts.map +1 -0
  348. package/dist/session/summary/materialize.js +117 -0
  349. package/dist/session/summary/materialize.js.map +1 -0
  350. package/dist/session/summary/ref.d.ts +91 -0
  351. package/dist/session/summary/ref.d.ts.map +1 -0
  352. package/dist/session/summary/ref.js +51 -0
  353. package/dist/session/summary/ref.js.map +1 -0
  354. package/dist/session/workspace/__tests__/git-worktree.test.d.ts +2 -0
  355. package/dist/session/workspace/__tests__/git-worktree.test.d.ts.map +1 -0
  356. package/dist/session/workspace/__tests__/git-worktree.test.js +244 -0
  357. package/dist/session/workspace/__tests__/git-worktree.test.js.map +1 -0
  358. package/dist/session/workspace/__tests__/path-builder.test.d.ts +2 -0
  359. package/dist/session/workspace/__tests__/path-builder.test.d.ts.map +1 -0
  360. package/dist/session/workspace/__tests__/path-builder.test.js +37 -0
  361. package/dist/session/workspace/__tests__/path-builder.test.js.map +1 -0
  362. package/dist/session/workspace/driver.d.ts +55 -0
  363. package/dist/session/workspace/driver.d.ts.map +1 -0
  364. package/dist/session/workspace/driver.js +12 -0
  365. package/dist/session/workspace/driver.js.map +1 -0
  366. package/dist/session/workspace/git-worktree.d.ts +65 -0
  367. package/dist/session/workspace/git-worktree.d.ts.map +1 -0
  368. package/dist/session/workspace/git-worktree.js +156 -0
  369. package/dist/session/workspace/git-worktree.js.map +1 -0
  370. package/dist/session/workspace/index.d.ts +8 -0
  371. package/dist/session/workspace/index.d.ts.map +1 -0
  372. package/dist/session/workspace/index.js +7 -0
  373. package/dist/session/workspace/index.js.map +1 -0
  374. package/dist/session/workspace/path-builder.d.ts +50 -0
  375. package/dist/session/workspace/path-builder.d.ts.map +1 -0
  376. package/dist/session/workspace/path-builder.js +50 -0
  377. package/dist/session/workspace/path-builder.js.map +1 -0
  378. package/dist/session/workspace/ref.d.ts +46 -0
  379. package/dist/session/workspace/ref.d.ts.map +1 -0
  380. package/dist/session/workspace/ref.js +11 -0
  381. package/dist/session/workspace/ref.js.map +1 -0
  382. package/dist/session/workspace/registry.d.ts +26 -0
  383. package/dist/session/workspace/registry.d.ts.map +1 -0
  384. package/dist/session/workspace/registry.js +35 -0
  385. package/dist/session/workspace/registry.js.map +1 -0
  386. package/dist/store/index.d.ts +0 -2
  387. package/dist/store/index.d.ts.map +1 -1
  388. package/dist/store/index.js +0 -1
  389. package/dist/store/index.js.map +1 -1
  390. package/dist/store/session/__tests__/disk.test.d.ts +2 -0
  391. package/dist/store/session/__tests__/disk.test.d.ts.map +1 -0
  392. package/dist/store/session/__tests__/disk.test.js +267 -0
  393. package/dist/store/session/__tests__/disk.test.js.map +1 -0
  394. package/dist/store/session/__tests__/memory.test.d.ts +2 -0
  395. package/dist/store/session/__tests__/memory.test.d.ts.map +1 -0
  396. package/dist/store/session/__tests__/memory.test.js +258 -0
  397. package/dist/store/session/__tests__/memory.test.js.map +1 -0
  398. package/dist/store/session/disk.d.ts +86 -0
  399. package/dist/store/session/disk.d.ts.map +1 -0
  400. package/dist/store/session/disk.js +818 -0
  401. package/dist/store/session/disk.js.map +1 -0
  402. package/dist/store/session/index.d.ts +7 -0
  403. package/dist/store/session/index.d.ts.map +1 -0
  404. package/dist/store/session/index.js +10 -0
  405. package/dist/store/session/index.js.map +1 -0
  406. package/dist/store/session/linkage.d.ts +38 -0
  407. package/dist/store/session/linkage.d.ts.map +1 -0
  408. package/dist/store/session/linkage.js +64 -0
  409. package/dist/store/session/linkage.js.map +1 -0
  410. package/dist/store/session/memory.d.ts +49 -0
  411. package/dist/store/session/memory.d.ts.map +1 -0
  412. package/dist/store/session/memory.js +335 -0
  413. package/dist/store/session/memory.js.map +1 -0
  414. package/dist/store/session/messages.d.ts +20 -0
  415. package/dist/store/session/messages.d.ts.map +1 -0
  416. package/dist/store/session/messages.js +12 -0
  417. package/dist/store/session/messages.js.map +1 -0
  418. package/dist/store/thread/disk.d.ts +41 -0
  419. package/dist/store/thread/disk.d.ts.map +1 -0
  420. package/dist/store/thread/disk.js +229 -0
  421. package/dist/store/thread/disk.js.map +1 -0
  422. package/dist/store/thread/index.d.ts +4 -0
  423. package/dist/store/thread/index.d.ts.map +1 -0
  424. package/dist/store/thread/index.js +6 -0
  425. package/dist/store/thread/index.js.map +1 -0
  426. package/dist/store/thread/memory.d.ts +23 -0
  427. package/dist/store/thread/memory.d.ts.map +1 -0
  428. package/dist/store/thread/memory.js +90 -0
  429. package/dist/store/thread/memory.js.map +1 -0
  430. package/dist/tools/builtins/__tests__/structuredOutput.example.d.ts +1 -1
  431. package/dist/types/agent/base.d.ts +24 -1
  432. package/dist/types/agent/base.d.ts.map +1 -1
  433. package/dist/types/agent/factory.d.ts +8 -2
  434. package/dist/types/agent/factory.d.ts.map +1 -1
  435. package/dist/types/agent/task.d.ts +57 -2
  436. package/dist/types/agent/task.d.ts.map +1 -1
  437. package/dist/types/agent/task.js.map +1 -1
  438. package/dist/types/ids/index.d.ts +22 -3
  439. package/dist/types/ids/index.d.ts.map +1 -1
  440. package/dist/types/ids/index.js +8 -1
  441. package/dist/types/ids/index.js.map +1 -1
  442. package/dist/types/invocation/__tests__/state.test.js +36 -29
  443. package/dist/types/invocation/__tests__/state.test.js.map +1 -1
  444. package/dist/types/invocation/index.d.ts +20 -4
  445. package/dist/types/invocation/index.d.ts.map +1 -1
  446. package/dist/types/invocation/index.js +10 -7
  447. package/dist/types/invocation/index.js.map +1 -1
  448. package/dist/types/rag/retrieval.d.ts +4 -3
  449. package/dist/types/rag/retrieval.d.ts.map +1 -1
  450. package/dist/types/run/config.d.ts +12 -1
  451. package/dist/types/run/config.d.ts.map +1 -1
  452. package/dist/types/run/events.d.ts +26 -1
  453. package/dist/types/run/events.d.ts.map +1 -1
  454. package/dist/types/run/index.d.ts.map +1 -1
  455. package/dist/types/run/index.js +8 -0
  456. package/dist/types/run/index.js.map +1 -1
  457. package/dist/types/run/metadata.d.ts +12 -2
  458. package/dist/types/run/metadata.d.ts.map +1 -1
  459. package/dist/types/run/status.d.ts +26 -0
  460. package/dist/types/run/status.d.ts.map +1 -0
  461. package/dist/types/run/status.js +2 -0
  462. package/dist/types/run/status.js.map +1 -0
  463. package/dist/types/session/ids.d.ts +9 -0
  464. package/dist/types/session/ids.d.ts.map +1 -0
  465. package/dist/types/session/ids.js +9 -0
  466. package/dist/types/session/ids.js.map +1 -0
  467. package/dist/types/session/index.d.ts +3 -0
  468. package/dist/types/session/index.d.ts.map +1 -0
  469. package/dist/types/session/index.js +5 -0
  470. package/dist/types/session/index.js.map +1 -0
  471. package/dist/types/session/store.d.ts +210 -0
  472. package/dist/types/session/store.d.ts.map +1 -0
  473. package/dist/types/session/store.js +9 -0
  474. package/dist/types/session/store.js.map +1 -0
  475. package/dist/types/thread/index.d.ts +2 -0
  476. package/dist/types/thread/index.d.ts.map +1 -0
  477. package/dist/types/thread/index.js +5 -0
  478. package/dist/types/thread/index.js.map +1 -0
  479. package/dist/types/thread/store.d.ts +86 -0
  480. package/dist/types/thread/store.d.ts.map +1 -0
  481. package/dist/types/thread/store.js +22 -0
  482. package/dist/types/thread/store.js.map +1 -0
  483. package/dist/utils/id.d.ts +8 -2
  484. package/dist/utils/id.d.ts.map +1 -1
  485. package/dist/utils/id.js +22 -4
  486. package/dist/utils/id.js.map +1 -1
  487. package/package.json +6 -11
  488. package/src/agents/ReactiveAgent.ts +7 -2
  489. package/src/agents/RouterAgent.ts +5 -0
  490. package/src/agents/SupervisorAgent.ts +29 -6
  491. package/src/bridge/a2a/index.ts +0 -1
  492. package/src/bridge/a2a/mapper.ts +7 -0
  493. package/src/bridge/a2a/message.ts +0 -32
  494. package/src/bridge/a2a/task.ts +9 -8
  495. package/src/bridge/sse/mapper.ts +8 -1
  496. package/src/constants/a2a/index.ts +2 -2
  497. package/src/contracts/api.ts +14 -30
  498. package/src/contracts/ids.ts +1 -1
  499. package/src/contracts/index.ts +3 -7
  500. package/src/contracts/schemas.ts +1 -8
  501. package/src/gateway/local.ts +6 -0
  502. package/src/index.ts +14 -4
  503. package/src/manager/agent/__tests__/lifecycle.test.ts +473 -0
  504. package/src/manager/agent/lifecycle.ts +515 -21
  505. package/src/manager/index.ts +3 -0
  506. package/src/manager/run/persistence.ts +26 -1
  507. package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
  508. package/src/manager/thread/lifecycle.ts +217 -0
  509. package/src/rag/retriever.ts +2 -2
  510. package/src/run/reporter.ts +28 -0
  511. package/src/runtime/query/__tests__/context.test.ts +102 -0
  512. package/src/runtime/query/context-cache.ts +4 -4
  513. package/src/runtime/query/context.ts +98 -9
  514. package/src/runtime/query/events.ts +8 -0
  515. package/src/runtime/query/index.ts +38 -1
  516. package/src/session/__tests__/integration/_fixtures.ts +310 -0
  517. package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
  518. package/src/session/__tests__/integration/capacity-caps.test.ts +171 -0
  519. package/src/session/__tests__/integration/e2e-spawn.test.ts +296 -0
  520. package/src/session/__tests__/integration/event-stream-ordering.test.ts +410 -0
  521. package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +271 -0
  522. package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +214 -0
  523. package/src/session/__tests__/integration/handoff-single-e2e.test.ts +251 -0
  524. package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +240 -0
  525. package/src/session/__tests__/integration/migration-filesystem.test.ts +209 -0
  526. package/src/session/__tests__/integration/migration-id-prefix.test.ts +101 -0
  527. package/src/session/__tests__/integration/prev-artifact-dag.test.ts +325 -0
  528. package/src/session/__tests__/integration/retention-archive.test.ts +233 -0
  529. package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
  530. package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +239 -0
  531. package/src/session/__tests__/integration/tenant-isolation.test.ts +292 -0
  532. package/src/session/errors.ts +159 -0
  533. package/src/session/events/index.ts +16 -0
  534. package/src/session/events/schema-version.ts +13 -0
  535. package/src/session/events/types.ts +71 -0
  536. package/src/session/handoff/__tests__/broadcast.test.ts +378 -0
  537. package/src/session/handoff/__tests__/capacity.test.ts +129 -0
  538. package/src/session/handoff/__tests__/single.test.ts +333 -0
  539. package/src/session/handoff/assignment.ts +74 -0
  540. package/src/session/handoff/broadcast.ts +406 -0
  541. package/src/session/handoff/capacity.ts +121 -0
  542. package/src/session/handoff/events.ts +72 -0
  543. package/src/session/handoff/index.ts +29 -0
  544. package/src/session/handoff/single.ts +310 -0
  545. package/src/session/handoff/version.ts +59 -0
  546. package/src/session/hierarchy/__tests__/session.test.ts +100 -0
  547. package/src/session/hierarchy/actor.ts +17 -0
  548. package/src/session/hierarchy/index.ts +18 -0
  549. package/src/session/hierarchy/lineage.ts +15 -0
  550. package/src/session/hierarchy/project.ts +41 -0
  551. package/src/session/hierarchy/session.ts +109 -0
  552. package/src/session/hierarchy/sub-session.ts +92 -0
  553. package/src/session/hierarchy/tenant.ts +13 -0
  554. package/src/session/hierarchy/thread.ts +55 -0
  555. package/src/session/index.ts +15 -0
  556. package/src/session/intervention/__tests__/prev-artifact.test.ts +234 -0
  557. package/src/session/intervention/index.ts +16 -0
  558. package/src/session/intervention/prev-artifact.ts +180 -0
  559. package/src/session/migration/__tests__/filesystem.test.ts +263 -0
  560. package/src/session/migration/__tests__/id-prefix.test.ts +101 -0
  561. package/src/session/migration/__tests__/marker.test.ts +84 -0
  562. package/src/session/migration/errors.ts +23 -0
  563. package/src/session/migration/filesystem.ts +401 -0
  564. package/src/session/migration/id-prefix.ts +141 -0
  565. package/src/session/migration/index.ts +38 -0
  566. package/src/session/migration/marker.ts +131 -0
  567. package/src/session/retention/__tests__/archive.test.ts +318 -0
  568. package/src/session/retention/__tests__/disk-backend.test.ts +180 -0
  569. package/src/session/retention/archive-backend-ref.ts +17 -0
  570. package/src/session/retention/archive.ts +281 -0
  571. package/src/session/retention/backend.ts +107 -0
  572. package/src/session/retention/disk-backend.ts +304 -0
  573. package/src/session/retention/index.ts +16 -0
  574. package/src/session/retention/policy.ts +53 -0
  575. package/src/session/summary/__tests__/materialize.test.ts +343 -0
  576. package/src/session/summary/deliverable.ts +84 -0
  577. package/src/session/summary/index.ts +31 -0
  578. package/src/session/summary/materialize.ts +169 -0
  579. package/src/session/summary/ref.ts +104 -0
  580. package/src/session/workspace/__tests__/git-worktree.test.ts +258 -0
  581. package/src/session/workspace/__tests__/path-builder.test.ts +51 -0
  582. package/src/session/workspace/driver.ts +60 -0
  583. package/src/session/workspace/git-worktree.ts +209 -0
  584. package/src/session/workspace/index.ts +25 -0
  585. package/src/session/workspace/path-builder.ts +71 -0
  586. package/src/session/workspace/ref.ts +50 -0
  587. package/src/session/workspace/registry.ts +42 -0
  588. package/src/store/index.ts +0 -3
  589. package/src/store/session/__tests__/disk.test.ts +397 -0
  590. package/src/store/session/__tests__/memory.test.ts +402 -0
  591. package/src/store/session/disk.ts +976 -0
  592. package/src/store/session/index.ts +13 -0
  593. package/src/store/session/linkage.ts +80 -0
  594. package/src/store/session/memory.ts +412 -0
  595. package/src/store/session/messages.ts +21 -0
  596. package/src/store/thread/disk.ts +261 -0
  597. package/src/store/thread/index.ts +7 -0
  598. package/src/store/thread/memory.ts +104 -0
  599. package/src/types/agent/base.ts +27 -1
  600. package/src/types/agent/factory.ts +8 -3
  601. package/src/types/agent/task.ts +66 -2
  602. package/src/types/ids/index.ts +34 -3
  603. package/src/types/invocation/__tests__/state.test.ts +37 -29
  604. package/src/types/invocation/index.ts +26 -10
  605. package/src/types/rag/retrieval.ts +4 -3
  606. package/src/types/run/config.ts +13 -1
  607. package/src/types/run/events.ts +36 -1
  608. package/src/types/run/index.ts +8 -0
  609. package/src/types/run/metadata.ts +12 -2
  610. package/src/types/run/status.ts +33 -0
  611. package/src/types/session/ids.ts +23 -0
  612. package/src/types/session/index.ts +27 -0
  613. package/src/types/session/store.ts +252 -0
  614. package/src/types/thread/index.ts +5 -0
  615. package/src/types/thread/store.ts +92 -0
  616. package/src/utils/id.ts +34 -4
  617. package/dist/store/conversation/memory.d.ts +0 -21
  618. package/dist/store/conversation/memory.d.ts.map +0 -1
  619. package/dist/store/conversation/memory.js +0 -86
  620. package/dist/store/conversation/memory.js.map +0 -1
  621. package/dist/types/conversation/index.d.ts +0 -7
  622. package/dist/types/conversation/index.d.ts.map +0 -1
  623. package/dist/types/conversation/index.js +0 -2
  624. package/dist/types/conversation/index.js.map +0 -1
  625. package/src/store/conversation/memory.ts +0 -121
  626. package/src/types/conversation/index.ts +0 -8
@@ -0,0 +1,976 @@
1
+ /**
2
+ * DiskSessionStore — filesystem-backed implementation of
3
+ * {@link SessionStore}.
4
+ *
5
+ * Every mutation is write-tmp-rename (Convention #8). Directory layout
6
+ * matches session-hierarchy.md §7 / §13.4:
7
+ *
8
+ * {rootDir}/projects/{projectId}/
9
+ * project.json
10
+ * sessions/{sessionId}/
11
+ * session.json
12
+ * messages.jsonl
13
+ * subsessions/{subSessionId}/
14
+ * subsession.json
15
+ *
16
+ * Tenant scoping is enforced through the JSON payload (`tenantId` field on
17
+ * every record) rather than the path layout; cross-tenant reads reject with
18
+ * {@link TenantIsolationError} (Convention #17, session-hierarchy.md §12.2).
19
+ *
20
+ * Constructor takes `rootDir`; migration to the canonical `.namzu/projects/`
21
+ * path lives in Phase 7 of the overall roadmap.
22
+ */
23
+
24
+ import {
25
+ appendFile,
26
+ mkdir,
27
+ readFile,
28
+ readdir,
29
+ rename,
30
+ rm,
31
+ unlink,
32
+ writeFile,
33
+ } from 'node:fs/promises'
34
+ import { join } from 'node:path'
35
+ import { TenantIsolationError } from '../../session/errors.js'
36
+ import type { Project } from '../../session/hierarchy/project.js'
37
+ import type { Session } from '../../session/hierarchy/session.js'
38
+ import type { SubSession } from '../../session/hierarchy/sub-session.js'
39
+ import type { DeliverableRef } from '../../session/summary/deliverable.js'
40
+ import { SessionAlreadySummarizedError } from '../../session/summary/ref.js'
41
+ import type {
42
+ SessionSummaryKeyDecision,
43
+ SessionSummaryOutcome,
44
+ SessionSummaryRef,
45
+ } from '../../session/summary/ref.js'
46
+ import type { MessageId, SessionId, TenantId } from '../../types/ids/index.js'
47
+ import type { Message } from '../../types/message/index.js'
48
+ import type { ProjectId, SubSessionId, SummaryId, ThreadId } from '../../types/session/ids.js'
49
+ import type {
50
+ CreateProjectParams,
51
+ CreateSessionParams,
52
+ CreateSubSessionParams,
53
+ SessionStore,
54
+ SessionView,
55
+ } from '../../types/session/store.js'
56
+ import {
57
+ generateMessageId,
58
+ generateProjectId,
59
+ generateSessionId,
60
+ generateSubSessionId,
61
+ } from '../../utils/id.js'
62
+ import { getAncestry, getChildren, orderChildren } from './linkage.js'
63
+ import type { LinkageView } from './linkage.js'
64
+ import type { SessionMessage } from './messages.js'
65
+
66
+ /**
67
+ * Config for {@link DiskSessionStore}. `rootDir` is absolute; all files live
68
+ * under it per the layout documented in the module header.
69
+ */
70
+ export interface DiskSessionStoreConfig {
71
+ rootDir: string
72
+ }
73
+
74
+ interface PersistedProject {
75
+ id: ProjectId
76
+ tenantId: TenantId
77
+ name: string
78
+ config: Project['config']
79
+ createdAt: string
80
+ updatedAt: string
81
+ }
82
+
83
+ interface PersistedSession {
84
+ id: SessionId
85
+ threadId: ThreadId
86
+ projectId: ProjectId
87
+ tenantId: TenantId
88
+ status: Session['status']
89
+ currentActor: Session['currentActor']
90
+ previousActors: Session['previousActors']
91
+ workspaceId: Session['workspaceId']
92
+ ownerVersion: number
93
+ createdAt: string
94
+ updatedAt: string
95
+ }
96
+
97
+ interface PersistedSubSession {
98
+ id: SubSessionId
99
+ parentSessionId: SessionId
100
+ childSessionId: SessionId
101
+ tenantId: TenantId
102
+ kind: SubSession['kind']
103
+ status: SubSession['status']
104
+ spawnedBy: SubSession['spawnedBy']
105
+ spawnedAt: string
106
+ failureMode: SubSession['failureMode']
107
+ completionMode: SubSession['completionMode']
108
+ workspaceId: SubSession['workspaceId']
109
+ broadcastGroupId?: string
110
+ summaryRef?: SubSession['summaryRef']
111
+ archiveRef?: SubSession['archiveRef']
112
+ archivedAt?: string
113
+ updatedAt: string
114
+ }
115
+
116
+ interface PersistedMessageLine {
117
+ id: MessageId
118
+ sessionId: SessionId
119
+ tenantId: TenantId
120
+ message: Message
121
+ at: string
122
+ }
123
+
124
+ interface PersistedSummary {
125
+ id: SummaryId
126
+ sessionRef: SessionId
127
+ tenantId: TenantId
128
+ outcome: SessionSummaryOutcome
129
+ deliverables: readonly DeliverableRef[]
130
+ agentSummary: string
131
+ keyDecisions: ReadonlyArray<{ at: string; summary: string }>
132
+ at: string
133
+ materializedBy: 'kernel'
134
+ }
135
+
136
+ /**
137
+ * Non-terminal statuses from which {@link DiskSessionStore.recordSummary}
138
+ * flips the owning session to `'idle'` as part of the atomic materialize +
139
+ * transition contract (session-hierarchy.md §8.1).
140
+ */
141
+ const SUMMARY_TERMINAL_FLIP_STATUSES: ReadonlySet<Session['status']> = new Set([
142
+ 'active',
143
+ 'locked',
144
+ 'awaiting_merge',
145
+ ])
146
+
147
+ /**
148
+ * Index of projectId → its directory path. Built lazily on lookup via
149
+ * {@link DiskSessionStore.resolveProjectDir}; populated by create / getProject.
150
+ */
151
+ interface ProjectIndexEntry {
152
+ projectId: ProjectId
153
+ path: string
154
+ }
155
+
156
+ /**
157
+ * Index of sessionId → (projectId, path). Populated lazily similarly.
158
+ */
159
+ interface SessionIndexEntry {
160
+ sessionId: SessionId
161
+ projectId: ProjectId
162
+ path: string
163
+ }
164
+
165
+ export class DiskSessionStore implements SessionStore {
166
+ private readonly rootDir: string
167
+ private readonly projectIndex = new Map<ProjectId, ProjectIndexEntry>()
168
+ private readonly sessionIndex = new Map<SessionId, SessionIndexEntry>()
169
+ private readonly subSessionIndex = new Map<
170
+ SubSessionId,
171
+ { subSessionId: SubSessionId; sessionId: SessionId; projectId: ProjectId; path: string }
172
+ >()
173
+
174
+ constructor(config: DiskSessionStoreConfig) {
175
+ this.rootDir = config.rootDir
176
+ }
177
+
178
+ // Project CRUD ------------------------------------------------------------
179
+
180
+ async createProject(params: CreateProjectParams, tenantId: TenantId): Promise<Project> {
181
+ if (params.tenantId !== tenantId) {
182
+ throw new TenantIsolationError({
183
+ requested: tenantId,
184
+ resource: `project(name=${params.name})`,
185
+ })
186
+ }
187
+ const now = new Date()
188
+ const project: Project = {
189
+ id: generateProjectId(),
190
+ tenantId,
191
+ name: params.name,
192
+ config: {
193
+ maxDelegationDepth: 4,
194
+ maxDelegationWidth: 8,
195
+ maxInterventionDepth: 10,
196
+ },
197
+ createdAt: now,
198
+ updatedAt: now,
199
+ }
200
+ const dir = join(this.rootDir, 'projects', project.id)
201
+ await mkdir(dir, { recursive: true })
202
+ await atomicWriteJson(join(dir, 'project.json'), serializeProject(project))
203
+ this.projectIndex.set(project.id, { projectId: project.id, path: dir })
204
+ return project
205
+ }
206
+
207
+ async getProject(projectId: ProjectId, tenantId: TenantId): Promise<Project | null> {
208
+ const dir = this.projectDir(projectId)
209
+ const raw = await readJson<PersistedProject>(join(dir, 'project.json'))
210
+ if (!raw) return null
211
+ this.assertTenant(raw.tenantId, tenantId, `project(${projectId})`)
212
+ return deserializeProject(raw)
213
+ }
214
+
215
+ // Session CRUD ------------------------------------------------------------
216
+
217
+ async createSession(params: CreateSessionParams, tenantId: TenantId): Promise<Session> {
218
+ const project = await this.getProject(params.projectId, tenantId)
219
+ if (!project) {
220
+ throw new Error(`Project ${params.projectId} not found`)
221
+ }
222
+ const now = new Date()
223
+ const session: Session = {
224
+ id: generateSessionId(),
225
+ threadId: params.threadId,
226
+ projectId: params.projectId,
227
+ tenantId,
228
+ status: 'idle',
229
+ currentActor: params.currentActor,
230
+ previousActors: [],
231
+ workspaceId: null,
232
+ ownerVersion: 0,
233
+ createdAt: now,
234
+ updatedAt: now,
235
+ }
236
+ const dir = join(this.projectDir(params.projectId), 'sessions', session.id)
237
+ await mkdir(dir, { recursive: true })
238
+ await atomicWriteJson(join(dir, 'session.json'), serializeSession(session))
239
+ this.sessionIndex.set(session.id, {
240
+ sessionId: session.id,
241
+ projectId: params.projectId,
242
+ path: dir,
243
+ })
244
+ return session
245
+ }
246
+
247
+ async getSession(sessionId: SessionId, tenantId: TenantId): Promise<Session | null> {
248
+ const located = await this.locateSession(sessionId)
249
+ if (!located) return null
250
+ const raw = await readJson<PersistedSession>(join(located.path, 'session.json'))
251
+ if (!raw) return null
252
+ this.assertTenant(raw.tenantId, tenantId, `session(${sessionId})`)
253
+ return deserializeSession(raw)
254
+ }
255
+
256
+ async listSessions(threadId: ThreadId, tenantId: TenantId): Promise<readonly Session[]> {
257
+ // Walk projects/*/sessions/* and filter on the persisted record. Sessions
258
+ // don't live under a thread-scoped path in the current layout — the
259
+ // denormalized `threadId` on every session.json is the authority. Matches
260
+ // DiskThreadStore.listThreads in scan semantics.
261
+ //
262
+ // Cost: O(all sessions across all projects in the root) per call. The
263
+ // MVP disk store prioritizes simplicity over index freshness, matching
264
+ // `buildLinkageView` / `locateSession` which use the same pattern. A
265
+ // production driver would maintain a threadId → sessionIds secondary
266
+ // index populated on createSession / deleteSession. Acceptable for
267
+ // ThreadManager archive/delete today because those operations are
268
+ // admin-initiated and infrequent.
269
+ const projectsDir = join(this.rootDir, 'projects')
270
+ let projectDirs: string[]
271
+ try {
272
+ projectDirs = await readdir(projectsDir)
273
+ } catch (err) {
274
+ const code = (err as NodeJS.ErrnoException).code
275
+ if (code === 'ENOENT') return []
276
+ throw err
277
+ }
278
+
279
+ const results: Session[] = []
280
+ for (const rawProject of projectDirs) {
281
+ if (!rawProject.startsWith('prj_')) continue
282
+ const sessionsRoot = join(projectsDir, rawProject, 'sessions')
283
+ let sessionDirs: string[]
284
+ try {
285
+ sessionDirs = await readdir(sessionsRoot)
286
+ } catch {
287
+ continue
288
+ }
289
+ for (const rawSessionId of sessionDirs) {
290
+ if (!rawSessionId.startsWith('ses_')) continue
291
+ const path = join(sessionsRoot, rawSessionId)
292
+ const raw = await readJson<PersistedSession>(join(path, 'session.json'))
293
+ if (!raw) continue
294
+ if (raw.tenantId !== tenantId) continue
295
+ if (raw.threadId !== threadId) continue
296
+ results.push(deserializeSession(raw))
297
+ this.sessionIndex.set(raw.id, {
298
+ sessionId: raw.id,
299
+ projectId: rawProject as ProjectId,
300
+ path,
301
+ })
302
+ }
303
+ }
304
+ results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
305
+ return results
306
+ }
307
+
308
+ async updateSession(session: Session, tenantId: TenantId): Promise<void> {
309
+ const located = await this.locateSession(session.id)
310
+ if (!located) {
311
+ throw new Error(`Session ${session.id} not found`)
312
+ }
313
+ if (session.tenantId !== tenantId) {
314
+ throw new TenantIsolationError({
315
+ requested: tenantId,
316
+ resource: `session(${session.id}) payload`,
317
+ })
318
+ }
319
+ const existing = await readJson<PersistedSession>(join(located.path, 'session.json'))
320
+ if (existing) {
321
+ this.assertTenant(existing.tenantId, tenantId, `session(${session.id})`)
322
+ }
323
+ const updated: Session = { ...session, updatedAt: new Date() }
324
+ await atomicWriteJson(join(located.path, 'session.json'), serializeSession(updated))
325
+ }
326
+
327
+ async deleteSession(sessionId: SessionId, tenantId: TenantId): Promise<void> {
328
+ const located = await this.locateSession(sessionId)
329
+ if (!located) return // Idempotent: missing = no-op.
330
+ const existing = await readJson<PersistedSession>(join(located.path, 'session.json'))
331
+ if (!existing) return
332
+ this.assertTenant(existing.tenantId, tenantId, `session(${sessionId})`)
333
+
334
+ // Policy: reject if sub-sessions are attached. Callers must delete
335
+ // children first — Convention #5 deny-by-default; no implicit cascade.
336
+ // We check BOTH directions (this session as parent, or as child) to
337
+ // match the in-memory semantics.
338
+ const subsDir = join(located.path, 'subsessions')
339
+ let subEntries: string[] = []
340
+ try {
341
+ subEntries = await readdir(subsDir)
342
+ } catch (err) {
343
+ const code = (err as NodeJS.ErrnoException).code
344
+ if (code !== 'ENOENT') throw err
345
+ }
346
+ if (subEntries.some((e) => e.startsWith('sub_'))) {
347
+ throw new Error(
348
+ `Session ${sessionId} has attached sub-sessions; delete them before deleting the session`,
349
+ )
350
+ }
351
+
352
+ // Also scan tree for sub-session records that reference this session as
353
+ // `childSessionId`. We need to walk siblings; acceptable cost for the
354
+ // MVP disk store since the broadcast rollback path always pairs a
355
+ // deleteSubSession + deleteSession call on the child (no orphans at
356
+ // steady state).
357
+ const projectsDir = join(this.rootDir, 'projects')
358
+ let projectDirs: string[]
359
+ try {
360
+ projectDirs = await readdir(projectsDir)
361
+ } catch (err) {
362
+ const code = (err as NodeJS.ErrnoException).code
363
+ if (code === 'ENOENT') projectDirs = []
364
+ else throw err
365
+ }
366
+ for (const rawProject of projectDirs) {
367
+ if (!rawProject.startsWith('prj_')) continue
368
+ const sessionsRoot = join(projectsDir, rawProject, 'sessions')
369
+ let siblingSessions: string[] = []
370
+ try {
371
+ siblingSessions = await readdir(sessionsRoot)
372
+ } catch {
373
+ continue
374
+ }
375
+ for (const rawSib of siblingSessions) {
376
+ if (!rawSib.startsWith('ses_')) continue
377
+ const sibSubsDir = join(sessionsRoot, rawSib, 'subsessions')
378
+ let sibSubs: string[] = []
379
+ try {
380
+ sibSubs = await readdir(sibSubsDir)
381
+ } catch {
382
+ continue
383
+ }
384
+ for (const rawSub of sibSubs) {
385
+ if (!rawSub.startsWith('sub_')) continue
386
+ const subRaw = await readJson<PersistedSubSession>(
387
+ join(sibSubsDir, rawSub, 'subsession.json'),
388
+ )
389
+ if (!subRaw) continue
390
+ if (subRaw.childSessionId === sessionId || subRaw.parentSessionId === sessionId) {
391
+ throw new Error(
392
+ `Session ${sessionId} has attached sub-sessions; delete them before deleting the session`,
393
+ )
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ // Recursive removal — `fs.rm` with `recursive: true` is the atomic
400
+ // primitive for bulk delete. No write-tmp-rename applies here (we're
401
+ // destroying state, not creating it).
402
+ await rm(located.path, { recursive: true, force: true })
403
+ this.sessionIndex.delete(sessionId)
404
+ }
405
+
406
+ // SubSession CRUD ---------------------------------------------------------
407
+
408
+ async createSubSession(params: CreateSubSessionParams, tenantId: TenantId): Promise<SubSession> {
409
+ const parent = await this.getSession(params.parentSessionId, tenantId)
410
+ if (!parent) throw new Error(`Parent session ${params.parentSessionId} not found`)
411
+
412
+ const child = await this.getSession(params.childSessionId, tenantId)
413
+ if (!child) throw new Error(`Child session ${params.childSessionId} not found`)
414
+
415
+ const parentLoc = this.sessionIndex.get(params.parentSessionId)
416
+ if (!parentLoc) throw new Error(`Parent session ${params.parentSessionId} missing from index`)
417
+
418
+ const now = new Date()
419
+ const subSession: SubSession = {
420
+ id: generateSubSessionId(),
421
+ parentSessionId: params.parentSessionId,
422
+ childSessionId: params.childSessionId,
423
+ kind: params.kind,
424
+ status: 'pending',
425
+ spawnedBy: params.spawnedBy,
426
+ spawnedAt: now,
427
+ failureMode: params.failureMode ?? 'delegate',
428
+ completionMode: params.completionMode ?? 'summary_ref',
429
+ workspaceId: null,
430
+ updatedAt: now,
431
+ }
432
+ const dir = join(parentLoc.path, 'subsessions', subSession.id)
433
+ await mkdir(dir, { recursive: true })
434
+ await atomicWriteJson(join(dir, 'subsession.json'), serializeSubSession(subSession, tenantId))
435
+ this.subSessionIndex.set(subSession.id, {
436
+ subSessionId: subSession.id,
437
+ sessionId: params.parentSessionId,
438
+ projectId: parentLoc.projectId,
439
+ path: dir,
440
+ })
441
+ return subSession
442
+ }
443
+
444
+ async getSubSession(subSessionId: SubSessionId, tenantId: TenantId): Promise<SubSession | null> {
445
+ const located = await this.locateSubSession(subSessionId)
446
+ if (!located) return null
447
+ const raw = await readJson<PersistedSubSession>(join(located.path, 'subsession.json'))
448
+ if (!raw) return null
449
+ this.assertTenant(raw.tenantId, tenantId, `sub-session(${subSessionId})`)
450
+ return deserializeSubSession(raw)
451
+ }
452
+
453
+ async updateSubSession(subSession: SubSession, tenantId: TenantId): Promise<void> {
454
+ const located = await this.locateSubSession(subSession.id)
455
+ if (!located) {
456
+ throw new Error(`SubSession ${subSession.id} not found`)
457
+ }
458
+ const existing = await readJson<PersistedSubSession>(join(located.path, 'subsession.json'))
459
+ if (existing) {
460
+ this.assertTenant(existing.tenantId, tenantId, `sub-session(${subSession.id})`)
461
+ }
462
+ const updated: SubSession = { ...subSession, updatedAt: new Date() }
463
+ await atomicWriteJson(
464
+ join(located.path, 'subsession.json'),
465
+ serializeSubSession(updated, tenantId),
466
+ )
467
+ }
468
+
469
+ async deleteSubSession(subSessionId: SubSessionId, tenantId: TenantId): Promise<void> {
470
+ const located = await this.locateSubSession(subSessionId)
471
+ if (!located) return // Idempotent: missing = no-op.
472
+ const existing = await readJson<PersistedSubSession>(join(located.path, 'subsession.json'))
473
+ if (!existing) {
474
+ // Record vanished between locate + read — treat as already deleted.
475
+ this.subSessionIndex.delete(subSessionId)
476
+ return
477
+ }
478
+ this.assertTenant(existing.tenantId, tenantId, `sub-session(${subSessionId})`)
479
+
480
+ await rm(located.path, { recursive: true, force: true })
481
+ this.subSessionIndex.delete(subSessionId)
482
+ }
483
+
484
+ // Messages ----------------------------------------------------------------
485
+
486
+ async appendMessage(
487
+ sessionId: SessionId,
488
+ message: Message,
489
+ tenantId: TenantId,
490
+ ): Promise<MessageId> {
491
+ const located = await this.locateSession(sessionId)
492
+ if (!located) throw new Error(`Session ${sessionId} not found`)
493
+
494
+ const session = await readJson<PersistedSession>(join(located.path, 'session.json'))
495
+ if (!session) throw new Error(`Session ${sessionId} not found on disk`)
496
+ this.assertTenant(session.tenantId, tenantId, `session(${sessionId})`)
497
+
498
+ const id = generateMessageId()
499
+ const entry: PersistedMessageLine = {
500
+ id,
501
+ sessionId,
502
+ tenantId,
503
+ message,
504
+ at: new Date().toISOString(),
505
+ }
506
+ await appendFile(join(located.path, 'messages.jsonl'), `${JSON.stringify(entry)}\n`, 'utf-8')
507
+ return id
508
+ }
509
+
510
+ async loadMessages(sessionId: SessionId, tenantId: TenantId): Promise<readonly Message[]> {
511
+ const rows = await this.loadSessionMessages(sessionId, tenantId)
512
+ return rows.map((r) => r.message)
513
+ }
514
+
515
+ async loadSessionMessages(
516
+ sessionId: SessionId,
517
+ tenantId: TenantId,
518
+ ): Promise<readonly SessionMessage[]> {
519
+ const located = await this.locateSession(sessionId)
520
+ if (!located) return []
521
+
522
+ const session = await readJson<PersistedSession>(join(located.path, 'session.json'))
523
+ if (!session) return []
524
+ this.assertTenant(session.tenantId, tenantId, `session(${sessionId})`)
525
+
526
+ const path = join(located.path, 'messages.jsonl')
527
+ let raw: string
528
+ try {
529
+ raw = await readFile(path, 'utf-8')
530
+ } catch (err) {
531
+ const code = (err as NodeJS.ErrnoException).code
532
+ if (code === 'ENOENT') return []
533
+ throw err
534
+ }
535
+ const lines = raw.split('\n').filter((l) => l.length > 0)
536
+ return lines.map((line) => {
537
+ const persisted = JSON.parse(line) as PersistedMessageLine
538
+ return {
539
+ id: persisted.id,
540
+ sessionId: persisted.sessionId,
541
+ tenantId: persisted.tenantId,
542
+ message: persisted.message,
543
+ at: new Date(persisted.at),
544
+ }
545
+ })
546
+ }
547
+
548
+ // Linkage -----------------------------------------------------------------
549
+
550
+ async getChildren(sessionId: SessionId, tenantId: TenantId): Promise<readonly SubSession[]> {
551
+ const session = await this.getSession(sessionId, tenantId)
552
+ if (!session) return []
553
+ const view = await this.buildLinkageView(tenantId)
554
+ return orderChildren(getChildren(view, sessionId))
555
+ }
556
+
557
+ async getAncestry(sessionId: SessionId, tenantId: TenantId): Promise<readonly SessionId[]> {
558
+ const session = await this.getSession(sessionId, tenantId)
559
+ if (!session) return []
560
+ const view = await this.buildLinkageView(tenantId)
561
+ return getAncestry(view, sessionId)
562
+ }
563
+
564
+ async drill(sessionId: SessionId, tenantId: TenantId): Promise<SessionView | null> {
565
+ const session = await this.getSession(sessionId, tenantId)
566
+ if (!session) return null
567
+ const view = await this.buildLinkageView(tenantId)
568
+ return {
569
+ session,
570
+ children: orderChildren(getChildren(view, sessionId)),
571
+ ancestry: getAncestry(view, sessionId),
572
+ }
573
+ }
574
+
575
+ // Summary (§4.7 / §8.1) ---------------------------------------------------
576
+
577
+ /**
578
+ * Atomic materialize-with-terminal-transition (§8.1). Two write-tmp-renames:
579
+ *
580
+ * 1. Persist `summary.json` under the session directory.
581
+ * 2. Flip `session.json#status` to `'idle'` if it's in a non-terminal
582
+ * state (`'active' | 'locked' | 'awaiting_merge'`).
583
+ *
584
+ * Each rename is atomic individually. A crash between step 1 and step 2
585
+ * leaves summary present + session still non-terminal — recovery replays
586
+ * the flip via {@link SessionSummaryMaterializer.recover}. Idempotent when
587
+ * the same summary is re-presented (recovery path); rejects a *different*
588
+ * summary for the same session as {@link SessionAlreadySummarizedError}.
589
+ */
590
+ async recordSummary(
591
+ summary: SessionSummaryRef & { materializedBy: 'kernel' },
592
+ tenantId: TenantId,
593
+ ): Promise<void> {
594
+ if (summary.tenantId !== tenantId) {
595
+ throw new TenantIsolationError({
596
+ requested: tenantId,
597
+ resource: `summary(${summary.id}) payload`,
598
+ })
599
+ }
600
+
601
+ const located = await this.locateSession(summary.sessionRef)
602
+ if (!located) {
603
+ throw new Error(`Session ${summary.sessionRef} not found`)
604
+ }
605
+ const sessionRaw = await readJson<PersistedSession>(join(located.path, 'session.json'))
606
+ if (!sessionRaw) {
607
+ throw new Error(`Session ${summary.sessionRef} not found on disk`)
608
+ }
609
+ this.assertTenant(sessionRaw.tenantId, tenantId, `session(${summary.sessionRef})`)
610
+
611
+ const summaryPath = join(located.path, 'summary.json')
612
+ const existingRaw = await readJson<PersistedSummary>(summaryPath)
613
+ if (existingRaw) {
614
+ this.assertTenant(existingRaw.tenantId, tenantId, `summary(${existingRaw.id})`)
615
+ if (existingRaw.id !== summary.id) {
616
+ throw new SessionAlreadySummarizedError({
617
+ sessionId: summary.sessionRef,
618
+ existingSummaryId: existingRaw.id,
619
+ })
620
+ }
621
+ // Same summary id — recovery replay. No duplicate write; fall through
622
+ // to the status flip so crash-between-writes is recovered.
623
+ } else {
624
+ // Step 1: persist summary.
625
+ await atomicWriteJson(summaryPath, serializeSummary(summary))
626
+ }
627
+
628
+ // Step 2: flip session status atomically if still non-terminal.
629
+ if (SUMMARY_TERMINAL_FLIP_STATUSES.has(sessionRaw.status)) {
630
+ const flipped: PersistedSession = {
631
+ ...sessionRaw,
632
+ status: 'idle',
633
+ updatedAt: new Date().toISOString(),
634
+ }
635
+ await atomicWriteJson(join(located.path, 'session.json'), flipped)
636
+ }
637
+ }
638
+
639
+ async getSummary(sessionId: SessionId, tenantId: TenantId): Promise<SessionSummaryRef | null> {
640
+ const located = await this.locateSession(sessionId)
641
+ if (!located) return null
642
+ const raw = await readJson<PersistedSummary>(join(located.path, 'summary.json'))
643
+ if (!raw) return null
644
+ this.assertTenant(raw.tenantId, tenantId, `summary(${raw.id})`)
645
+ return deserializeSummary(raw)
646
+ }
647
+
648
+ // Helpers -----------------------------------------------------------------
649
+
650
+ private assertTenant(actual: TenantId, requested: TenantId, resource: string): void {
651
+ if (actual !== requested) {
652
+ throw new TenantIsolationError({ requested, resource })
653
+ }
654
+ }
655
+
656
+ private projectDir(projectId: ProjectId): string {
657
+ const cached = this.projectIndex.get(projectId)
658
+ if (cached) return cached.path
659
+ const path = join(this.rootDir, 'projects', projectId)
660
+ this.projectIndex.set(projectId, { projectId, path })
661
+ return path
662
+ }
663
+
664
+ private async locateSession(sessionId: SessionId): Promise<SessionIndexEntry | null> {
665
+ const cached = this.sessionIndex.get(sessionId)
666
+ if (cached) return cached
667
+
668
+ const projectsDir = join(this.rootDir, 'projects')
669
+ let projectDirs: string[]
670
+ try {
671
+ projectDirs = await readdir(projectsDir)
672
+ } catch (err) {
673
+ const code = (err as NodeJS.ErrnoException).code
674
+ if (code === 'ENOENT') return null
675
+ throw err
676
+ }
677
+ for (const rawId of projectDirs) {
678
+ if (!rawId.startsWith('prj_')) continue
679
+ const projectId = rawId as ProjectId
680
+ const sessionsRoot = join(projectsDir, projectId, 'sessions')
681
+ let sessionDirs: string[]
682
+ try {
683
+ sessionDirs = await readdir(sessionsRoot)
684
+ } catch {
685
+ continue
686
+ }
687
+ for (const rawSessionId of sessionDirs) {
688
+ if (!rawSessionId.startsWith('ses_')) continue
689
+ if (rawSessionId === sessionId) {
690
+ const entry: SessionIndexEntry = {
691
+ sessionId,
692
+ projectId,
693
+ path: join(sessionsRoot, rawSessionId),
694
+ }
695
+ this.sessionIndex.set(sessionId, entry)
696
+ return entry
697
+ }
698
+ }
699
+ }
700
+ return null
701
+ }
702
+
703
+ private async locateSubSession(subSessionId: SubSessionId): Promise<{
704
+ subSessionId: SubSessionId
705
+ sessionId: SessionId
706
+ projectId: ProjectId
707
+ path: string
708
+ } | null> {
709
+ const cached = this.subSessionIndex.get(subSessionId)
710
+ if (cached) return cached
711
+
712
+ const projectsDir = join(this.rootDir, 'projects')
713
+ let projectDirs: string[]
714
+ try {
715
+ projectDirs = await readdir(projectsDir)
716
+ } catch (err) {
717
+ const code = (err as NodeJS.ErrnoException).code
718
+ if (code === 'ENOENT') return null
719
+ throw err
720
+ }
721
+ for (const rawProject of projectDirs) {
722
+ if (!rawProject.startsWith('prj_')) continue
723
+ const projectId = rawProject as ProjectId
724
+ const sessionsRoot = join(projectsDir, projectId, 'sessions')
725
+ let sessionDirs: string[]
726
+ try {
727
+ sessionDirs = await readdir(sessionsRoot)
728
+ } catch {
729
+ continue
730
+ }
731
+ for (const rawSession of sessionDirs) {
732
+ if (!rawSession.startsWith('ses_')) continue
733
+ const sessionId = rawSession as SessionId
734
+ const subsDir = join(sessionsRoot, sessionId, 'subsessions')
735
+ let subDirs: string[]
736
+ try {
737
+ subDirs = await readdir(subsDir)
738
+ } catch {
739
+ continue
740
+ }
741
+ for (const rawSub of subDirs) {
742
+ if (rawSub === subSessionId) {
743
+ const entry = {
744
+ subSessionId,
745
+ sessionId,
746
+ projectId,
747
+ path: join(subsDir, rawSub),
748
+ }
749
+ this.subSessionIndex.set(subSessionId, entry)
750
+ return entry
751
+ }
752
+ }
753
+ }
754
+ }
755
+ return null
756
+ }
757
+
758
+ private async buildLinkageView(tenantId: TenantId): Promise<LinkageView> {
759
+ // Walk the full projects → sessions → subsessions tree once per call.
760
+ // Acceptable for an MVP disk store; a production impl would cache.
761
+ const allSubs: SubSession[] = []
762
+ const projectsDir = join(this.rootDir, 'projects')
763
+ let projectDirs: string[]
764
+ try {
765
+ projectDirs = await readdir(projectsDir)
766
+ } catch (err) {
767
+ const code = (err as NodeJS.ErrnoException).code
768
+ if (code === 'ENOENT') return emptyLinkageView()
769
+ throw err
770
+ }
771
+
772
+ for (const rawProject of projectDirs) {
773
+ if (!rawProject.startsWith('prj_')) continue
774
+ const sessionsRoot = join(projectsDir, rawProject, 'sessions')
775
+ let sessionDirs: string[]
776
+ try {
777
+ sessionDirs = await readdir(sessionsRoot)
778
+ } catch {
779
+ continue
780
+ }
781
+ for (const rawSession of sessionDirs) {
782
+ if (!rawSession.startsWith('ses_')) continue
783
+ const subsRoot = join(sessionsRoot, rawSession, 'subsessions')
784
+ let subDirs: string[]
785
+ try {
786
+ subDirs = await readdir(subsRoot)
787
+ } catch {
788
+ continue
789
+ }
790
+ for (const rawSub of subDirs) {
791
+ const raw = await readJson<PersistedSubSession>(join(subsRoot, rawSub, 'subsession.json'))
792
+ if (!raw) continue
793
+ if (raw.tenantId !== tenantId) continue
794
+ allSubs.push(deserializeSubSession(raw))
795
+ }
796
+ }
797
+ }
798
+
799
+ return {
800
+ findChildSubSessions: (parentSessionId) =>
801
+ allSubs.filter((s) => s.parentSessionId === parentSessionId),
802
+ findParentSubSession: (childSessionId) =>
803
+ allSubs.find((s) => s.childSessionId === childSessionId) ?? null,
804
+ }
805
+ }
806
+ }
807
+
808
+ function emptyLinkageView(): LinkageView {
809
+ return {
810
+ findChildSubSessions: () => [],
811
+ findParentSubSession: () => null,
812
+ }
813
+ }
814
+
815
+ // Serialization helpers -----------------------------------------------------
816
+
817
+ function serializeProject(p: Project): PersistedProject {
818
+ return {
819
+ id: p.id,
820
+ tenantId: p.tenantId,
821
+ name: p.name,
822
+ config: p.config,
823
+ createdAt: p.createdAt.toISOString(),
824
+ updatedAt: p.updatedAt.toISOString(),
825
+ }
826
+ }
827
+
828
+ function deserializeProject(p: PersistedProject): Project {
829
+ return {
830
+ id: p.id,
831
+ tenantId: p.tenantId,
832
+ name: p.name,
833
+ config: p.config,
834
+ createdAt: new Date(p.createdAt),
835
+ updatedAt: new Date(p.updatedAt),
836
+ }
837
+ }
838
+
839
+ function serializeSession(s: Session): PersistedSession {
840
+ return {
841
+ id: s.id,
842
+ threadId: s.threadId,
843
+ projectId: s.projectId,
844
+ tenantId: s.tenantId,
845
+ status: s.status,
846
+ currentActor: s.currentActor,
847
+ previousActors: s.previousActors,
848
+ workspaceId: s.workspaceId,
849
+ ownerVersion: s.ownerVersion,
850
+ createdAt: s.createdAt.toISOString(),
851
+ updatedAt: s.updatedAt.toISOString(),
852
+ }
853
+ }
854
+
855
+ function deserializeSession(s: PersistedSession): Session {
856
+ return {
857
+ id: s.id,
858
+ threadId: s.threadId,
859
+ projectId: s.projectId,
860
+ tenantId: s.tenantId,
861
+ status: s.status,
862
+ currentActor: s.currentActor,
863
+ previousActors: s.previousActors,
864
+ workspaceId: s.workspaceId,
865
+ ownerVersion: s.ownerVersion,
866
+ createdAt: new Date(s.createdAt),
867
+ updatedAt: new Date(s.updatedAt),
868
+ }
869
+ }
870
+
871
+ function serializeSubSession(s: SubSession, tenantId: TenantId): PersistedSubSession {
872
+ return {
873
+ id: s.id,
874
+ parentSessionId: s.parentSessionId,
875
+ childSessionId: s.childSessionId,
876
+ tenantId,
877
+ kind: s.kind,
878
+ status: s.status,
879
+ spawnedBy: s.spawnedBy,
880
+ spawnedAt: s.spawnedAt.toISOString(),
881
+ failureMode: s.failureMode,
882
+ completionMode: s.completionMode,
883
+ workspaceId: s.workspaceId,
884
+ ...(s.broadcastGroupId !== undefined && { broadcastGroupId: s.broadcastGroupId }),
885
+ ...(s.summaryRef !== undefined && { summaryRef: s.summaryRef }),
886
+ ...(s.archiveRef !== undefined && { archiveRef: s.archiveRef }),
887
+ ...(s.archivedAt !== undefined && { archivedAt: s.archivedAt.toISOString() }),
888
+ updatedAt: s.updatedAt.toISOString(),
889
+ }
890
+ }
891
+
892
+ function deserializeSubSession(s: PersistedSubSession): SubSession {
893
+ return {
894
+ id: s.id,
895
+ parentSessionId: s.parentSessionId,
896
+ childSessionId: s.childSessionId,
897
+ kind: s.kind,
898
+ status: s.status,
899
+ spawnedBy: s.spawnedBy,
900
+ spawnedAt: new Date(s.spawnedAt),
901
+ failureMode: s.failureMode,
902
+ completionMode: s.completionMode,
903
+ workspaceId: s.workspaceId,
904
+ ...(s.broadcastGroupId !== undefined && { broadcastGroupId: s.broadcastGroupId }),
905
+ ...(s.summaryRef !== undefined && { summaryRef: s.summaryRef }),
906
+ ...(s.archiveRef !== undefined && { archiveRef: s.archiveRef }),
907
+ ...(s.archivedAt !== undefined && { archivedAt: new Date(s.archivedAt) }),
908
+ updatedAt: new Date(s.updatedAt),
909
+ }
910
+ }
911
+
912
+ function serializeSummary(s: SessionSummaryRef): PersistedSummary {
913
+ return {
914
+ id: s.id,
915
+ sessionRef: s.sessionRef,
916
+ tenantId: s.tenantId,
917
+ outcome: s.outcome,
918
+ deliverables: s.deliverables,
919
+ agentSummary: s.agentSummary,
920
+ keyDecisions: s.keyDecisions.map((k) => ({
921
+ at: k.at.toISOString(),
922
+ summary: k.summary,
923
+ })),
924
+ at: s.at.toISOString(),
925
+ materializedBy: 'kernel',
926
+ }
927
+ }
928
+
929
+ function deserializeSummary(s: PersistedSummary): SessionSummaryRef {
930
+ const decisions: SessionSummaryKeyDecision[] = s.keyDecisions.map((k) => ({
931
+ at: new Date(k.at),
932
+ summary: k.summary,
933
+ }))
934
+ return {
935
+ id: s.id,
936
+ sessionRef: s.sessionRef,
937
+ tenantId: s.tenantId,
938
+ outcome: s.outcome,
939
+ deliverables: s.deliverables,
940
+ agentSummary: s.agentSummary,
941
+ keyDecisions: decisions,
942
+ at: new Date(s.at),
943
+ materializedBy: 'kernel',
944
+ }
945
+ }
946
+
947
+ // FS helpers -----------------------------------------------------------------
948
+
949
+ async function readJson<T>(path: string): Promise<T | null> {
950
+ try {
951
+ const raw = await readFile(path, 'utf-8')
952
+ return JSON.parse(raw) as T
953
+ } catch (err) {
954
+ const code = (err as NodeJS.ErrnoException).code
955
+ if (code === 'ENOENT') return null
956
+ throw err
957
+ }
958
+ }
959
+
960
+ async function atomicWriteJson(filePath: string, value: unknown): Promise<void> {
961
+ const tempPath = `${filePath}.tmp`
962
+ try {
963
+ await writeFile(tempPath, JSON.stringify(value, null, 2), 'utf-8')
964
+ await rename(tempPath, filePath)
965
+ } catch (err) {
966
+ await unlink(tempPath).catch(() => undefined)
967
+ throw err
968
+ }
969
+ }
970
+
971
+ // Note: messages are append-only `messages.jsonl` (not write-tmp-rename).
972
+ // Append is the write-safety primitive for log-structured files; each
973
+ // line is a whole record. This matches pattern doc §13.4 persistence
974
+ // (`messages.json[l]` as append-only event log).
975
+
976
+ export type { SessionMessage } from './messages.js'