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