@maudecode/talaria-web 1.17.2 → 1.19.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 (623) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.d.ts +1 -1
  3. package/dist/api/auth-raw.js +20 -20
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +7 -1
  6. package/dist/api/automation-raw.js +74 -27
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +128 -23
  9. package/dist/api/automation-router.js +29 -13
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +1845 -40
  12. package/dist/api/chat-router.js +196 -114
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +37 -3
  15. package/dist/api/git-router.js +47 -48
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +9 -1
  18. package/dist/api/raw-routes.js +286 -73
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +16 -6
  21. package/dist/api/router.js +28 -26
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/session-visibility.d.ts +4 -4
  24. package/dist/api/session-visibility.js +26 -16
  25. package/dist/api/session-visibility.js.map +1 -1
  26. package/dist/api/sessions-router.d.ts +3312 -131
  27. package/dist/api/sessions-router.js +287 -157
  28. package/dist/api/sessions-router.js.map +1 -1
  29. package/dist/api/settings-router.d.ts +457 -8
  30. package/dist/api/settings-router.js +54 -33
  31. package/dist/api/settings-router.js.map +1 -1
  32. package/dist/api/sse-routes.d.ts +7 -2
  33. package/dist/api/sse-routes.js +109 -62
  34. package/dist/api/sse-routes.js.map +1 -1
  35. package/dist/api/tools-raw.d.ts +5 -0
  36. package/dist/api/tools-raw.js +63 -55
  37. package/dist/api/tools-raw.js.map +1 -1
  38. package/dist/api/tools-router.d.ts +998 -85
  39. package/dist/api/tools-router.js +447 -61
  40. package/dist/api/tools-router.js.map +1 -1
  41. package/dist/api/transcribe-stream.d.ts +16 -0
  42. package/dist/api/transcribe-stream.js +172 -0
  43. package/dist/api/transcribe-stream.js.map +1 -0
  44. package/dist/app.d.ts +4 -1
  45. package/dist/app.js +98 -59
  46. package/dist/app.js.map +1 -1
  47. package/dist/auth/gate.js +15 -11
  48. package/dist/auth/gate.js.map +1 -1
  49. package/dist/auth/oidc.d.ts +5 -2
  50. package/dist/auth/oidc.js +8 -7
  51. package/dist/auth/oidc.js.map +1 -1
  52. package/dist/auth/passkeys.d.ts +15 -14
  53. package/dist/auth/passkeys.js +89 -91
  54. package/dist/auth/passkeys.js.map +1 -1
  55. package/dist/auth/store.d.ts +17 -7
  56. package/dist/auth/store.js +93 -52
  57. package/dist/auth/store.js.map +1 -1
  58. package/dist/bin/talaria-web.js +12 -81
  59. package/dist/bin/talaria-web.js.map +1 -1
  60. package/dist/cli/ctl.js +2 -1
  61. package/dist/cli/ctl.js.map +1 -1
  62. package/dist/cli/dotenv.d.ts +5 -0
  63. package/dist/cli/dotenv.js +15 -2
  64. package/dist/cli/dotenv.js.map +1 -1
  65. package/dist/cli/launcher.js +5 -5
  66. package/dist/cli/launcher.js.map +1 -1
  67. package/dist/cli/main.d.ts +1 -0
  68. package/dist/cli/main.js +85 -0
  69. package/dist/cli/main.js.map +1 -0
  70. package/dist/cli/supervise.d.ts +2 -0
  71. package/dist/cli/supervise.js +67 -4
  72. package/dist/cli/supervise.js.map +1 -1
  73. package/dist/config/agent-config.d.ts +34 -11
  74. package/dist/config/agent-config.js +116 -60
  75. package/dist/config/agent-config.js.map +1 -1
  76. package/dist/config.d.ts +4 -0
  77. package/dist/config.js +19 -6
  78. package/dist/config.js.map +1 -1
  79. package/dist/fs/atomic.d.ts +1 -0
  80. package/dist/fs/atomic.js +4 -1
  81. package/dist/fs/atomic.js.map +1 -1
  82. package/dist/fs/stat.d.ts +1 -0
  83. package/dist/fs/stat.js +11 -0
  84. package/dist/fs/stat.js.map +1 -0
  85. package/dist/http/context.d.ts +31 -12
  86. package/dist/http/context.js +60 -24
  87. package/dist/http/context.js.map +1 -1
  88. package/dist/http/csp.d.ts +2 -1
  89. package/dist/http/csp.js +3 -2
  90. package/dist/http/csp.js.map +1 -1
  91. package/dist/onboarding.d.ts +2 -2
  92. package/dist/onboarding.js +10 -8
  93. package/dist/onboarding.js.map +1 -1
  94. package/dist/profiles/profiles.d.ts +20 -4
  95. package/dist/profiles/profiles.js +115 -30
  96. package/dist/profiles/profiles.js.map +1 -1
  97. package/dist/projects.d.ts +15 -6
  98. package/dist/projects.js +55 -47
  99. package/dist/projects.js.map +1 -1
  100. package/dist/providers/catalog.d.ts +84 -14
  101. package/dist/providers/catalog.js +474 -151
  102. package/dist/providers/catalog.js.map +1 -1
  103. package/dist/providers/env-file.d.ts +10 -3
  104. package/dist/providers/env-file.js +24 -75
  105. package/dist/providers/env-file.js.map +1 -1
  106. package/dist/providers/runtime-env.d.ts +4 -2
  107. package/dist/providers/runtime-env.js +9 -6
  108. package/dist/providers/runtime-env.js.map +1 -1
  109. package/dist/providers/tables.d.ts +3 -1
  110. package/dist/providers/tables.js +4 -2
  111. package/dist/providers/tables.js.map +1 -1
  112. package/dist/raster-image.d.ts +7 -0
  113. package/dist/raster-image.js +267 -0
  114. package/dist/raster-image.js.map +1 -0
  115. package/dist/redact.js +342 -46
  116. package/dist/redact.js.map +1 -1
  117. package/dist/release.d.ts +7 -0
  118. package/dist/release.js +14 -0
  119. package/dist/release.js.map +1 -1
  120. package/dist/runtime.d.ts +5 -1
  121. package/dist/runtime.js +305 -96
  122. package/dist/runtime.js.map +1 -1
  123. package/dist/server.js +15 -0
  124. package/dist/server.js.map +1 -1
  125. package/dist/sessions/anchor.d.ts +9 -3
  126. package/dist/sessions/anchor.js +28 -36
  127. package/dist/sessions/anchor.js.map +1 -1
  128. package/dist/sessions/background-events.d.ts +2 -0
  129. package/dist/sessions/background-events.js +10 -0
  130. package/dist/sessions/background-events.js.map +1 -0
  131. package/dist/sessions/background-tasks.d.ts +26 -27
  132. package/dist/sessions/background-tasks.js +89 -97
  133. package/dist/sessions/background-tasks.js.map +1 -1
  134. package/dist/sessions/background-updates.d.ts +15 -8
  135. package/dist/sessions/background-updates.js +101 -25
  136. package/dist/sessions/background-updates.js.map +1 -1
  137. package/dist/sessions/cli-sessions.d.ts +27 -6
  138. package/dist/sessions/cli-sessions.js +152 -42
  139. package/dist/sessions/cli-sessions.js.map +1 -1
  140. package/dist/sessions/completions.d.ts +77 -9
  141. package/dist/sessions/completions.js +482 -45
  142. package/dist/sessions/completions.js.map +1 -1
  143. package/dist/sessions/compress.d.ts +8 -0
  144. package/dist/sessions/compress.js +2 -2
  145. package/dist/sessions/compress.js.map +1 -1
  146. package/dist/sessions/drafts.d.ts +14 -10
  147. package/dist/sessions/drafts.js +38 -43
  148. package/dist/sessions/drafts.js.map +1 -1
  149. package/dist/sessions/export.d.ts +5 -0
  150. package/dist/sessions/export.js +30 -0
  151. package/dist/sessions/export.js.map +1 -1
  152. package/dist/sessions/gateway-sessions.d.ts +46 -0
  153. package/dist/sessions/gateway-sessions.js +378 -0
  154. package/dist/sessions/gateway-sessions.js.map +1 -0
  155. package/dist/sessions/gateway-watcher.d.ts +7 -3
  156. package/dist/sessions/gateway-watcher.js +31 -19
  157. package/dist/sessions/gateway-watcher.js.map +1 -1
  158. package/dist/sessions/goals.d.ts +31 -0
  159. package/dist/sessions/goals.js +75 -0
  160. package/dist/sessions/goals.js.map +1 -0
  161. package/dist/sessions/handoff.d.ts +32 -0
  162. package/dist/sessions/handoff.js +114 -0
  163. package/dist/sessions/handoff.js.map +1 -0
  164. package/dist/sessions/journal.d.ts +5 -79
  165. package/dist/sessions/journal.js +15 -606
  166. package/dist/sessions/journal.js.map +1 -1
  167. package/dist/sessions/list.d.ts +31 -16
  168. package/dist/sessions/list.js +139 -129
  169. package/dist/sessions/list.js.map +1 -1
  170. package/dist/sessions/merge.d.ts +69 -13
  171. package/dist/sessions/merge.js +265 -64
  172. package/dist/sessions/merge.js.map +1 -1
  173. package/dist/sessions/notices.d.ts +22 -0
  174. package/dist/sessions/notices.js +72 -0
  175. package/dist/sessions/notices.js.map +1 -0
  176. package/dist/sessions/pending.d.ts +10 -4
  177. package/dist/sessions/pending.js +83 -7
  178. package/dist/sessions/pending.js.map +1 -1
  179. package/dist/sessions/prefix-index.d.ts +82 -0
  180. package/dist/sessions/prefix-index.js +232 -0
  181. package/dist/sessions/prefix-index.js.map +1 -0
  182. package/dist/sessions/recovery.d.ts +19 -0
  183. package/dist/sessions/recovery.js +322 -0
  184. package/dist/sessions/recovery.js.map +1 -0
  185. package/dist/sessions/relay.d.ts +38 -19
  186. package/dist/sessions/relay.js +302 -79
  187. package/dist/sessions/relay.js.map +1 -1
  188. package/dist/sessions/service.d.ts +260 -55
  189. package/dist/sessions/service.js +1448 -462
  190. package/dist/sessions/service.js.map +1 -1
  191. package/dist/sessions/session.d.ts +24 -2
  192. package/dist/sessions/session.js +69 -7
  193. package/dist/sessions/session.js.map +1 -1
  194. package/dist/sessions/shares.d.ts +7 -6
  195. package/dist/sessions/shares.js +22 -45
  196. package/dist/sessions/shares.js.map +1 -1
  197. package/dist/sessions/source-kind.js +1 -0
  198. package/dist/sessions/source-kind.js.map +1 -1
  199. package/dist/sessions/state-db-identity.d.ts +18 -0
  200. package/dist/sessions/state-db-identity.js +67 -0
  201. package/dist/sessions/state-db-identity.js.map +1 -0
  202. package/dist/sessions/state-db-worker.d.ts +1 -0
  203. package/dist/sessions/state-db-worker.js +35 -0
  204. package/dist/sessions/state-db-worker.js.map +1 -0
  205. package/dist/sessions/state-db.d.ts +113 -7
  206. package/dist/sessions/state-db.js +861 -44
  207. package/dist/sessions/state-db.js.map +1 -1
  208. package/dist/sessions/storage/backend.d.ts +162 -0
  209. package/dist/sessions/storage/backend.js +19 -0
  210. package/dist/sessions/storage/backend.js.map +1 -0
  211. package/dist/sessions/storage/conformance.d.ts +20 -0
  212. package/dist/sessions/storage/conformance.js +237 -0
  213. package/dist/sessions/storage/conformance.js.map +1 -0
  214. package/dist/sessions/storage/document.d.ts +16 -0
  215. package/dist/sessions/storage/document.js +44 -0
  216. package/dist/sessions/storage/document.js.map +1 -0
  217. package/dist/sessions/storage/json-documents.d.ts +23 -0
  218. package/dist/sessions/storage/json-documents.js +233 -0
  219. package/dist/sessions/storage/json-documents.js.map +1 -0
  220. package/dist/sessions/storage/json-files.d.ts +12 -0
  221. package/dist/sessions/storage/json-files.js +124 -0
  222. package/dist/sessions/storage/json-files.js.map +1 -0
  223. package/dist/sessions/storage/json-installation.d.ts +3 -0
  224. package/dist/sessions/storage/json-installation.js +31 -0
  225. package/dist/sessions/storage/json-installation.js.map +1 -0
  226. package/dist/sessions/storage/json-journal.d.ts +114 -0
  227. package/dist/sessions/storage/json-journal.js +686 -0
  228. package/dist/sessions/storage/json-journal.js.map +1 -0
  229. package/dist/sessions/storage/json-recovery.d.ts +4 -0
  230. package/dist/sessions/storage/json-recovery.js +195 -0
  231. package/dist/sessions/storage/json-recovery.js.map +1 -0
  232. package/dist/sessions/storage/json.d.ts +68 -0
  233. package/dist/sessions/storage/json.js +258 -0
  234. package/dist/sessions/storage/json.js.map +1 -0
  235. package/dist/sessions/storage/row.d.ts +6 -0
  236. package/dist/sessions/storage/row.js +49 -0
  237. package/dist/sessions/storage/row.js.map +1 -0
  238. package/dist/sessions/storage/search-text.d.ts +4 -0
  239. package/dist/sessions/storage/search-text.js +16 -0
  240. package/dist/sessions/storage/search-text.js.map +1 -0
  241. package/dist/sessions/store.d.ts +65 -68
  242. package/dist/sessions/store.js +305 -602
  243. package/dist/sessions/store.js.map +1 -1
  244. package/dist/sessions/streams.d.ts +4 -0
  245. package/dist/sessions/streams.js +9 -1
  246. package/dist/sessions/streams.js.map +1 -1
  247. package/dist/sessions/titles.d.ts +2 -0
  248. package/dist/sessions/titles.js +5 -1
  249. package/dist/sessions/titles.js.map +1 -1
  250. package/dist/sessions/todo.d.ts +5 -0
  251. package/dist/sessions/todo.js +28 -21
  252. package/dist/sessions/todo.js.map +1 -1
  253. package/dist/sessions/tool-display.d.ts +5 -4
  254. package/dist/sessions/tool-display.js +30 -50
  255. package/dist/sessions/tool-display.js.map +1 -1
  256. package/dist/sessions/turn-context.d.ts +11 -2
  257. package/dist/sessions/turn-context.js +18 -11
  258. package/dist/sessions/turn-context.js.map +1 -1
  259. package/dist/sessions/turn.d.ts +122 -17
  260. package/dist/sessions/turn.js +939 -358
  261. package/dist/sessions/turn.js.map +1 -1
  262. package/dist/sessions/wakeup-pause.d.ts +54 -0
  263. package/dist/sessions/wakeup-pause.js +163 -0
  264. package/dist/sessions/wakeup-pause.js.map +1 -0
  265. package/dist/sessions/window.js +13 -12
  266. package/dist/sessions/window.js.map +1 -1
  267. package/dist/settings.d.ts +19 -7
  268. package/dist/settings.js +178 -184
  269. package/dist/settings.js.map +1 -1
  270. package/dist/sidecar/client.d.ts +17 -3
  271. package/dist/sidecar/client.js +78 -8
  272. package/dist/sidecar/client.js.map +1 -1
  273. package/dist/sidecar/discover.d.ts +2 -1
  274. package/dist/sidecar/discover.js +93 -10
  275. package/dist/sidecar/discover.js.map +1 -1
  276. package/dist/sidecar/env.d.ts +4 -0
  277. package/dist/sidecar/env.js +3 -0
  278. package/dist/sidecar/env.js.map +1 -0
  279. package/dist/sidecar/fake.d.ts +2 -2
  280. package/dist/sidecar/fake.js +19 -2
  281. package/dist/sidecar/fake.js.map +1 -1
  282. package/dist/startup.d.ts +13 -5
  283. package/dist/startup.js +48 -0
  284. package/dist/startup.js.map +1 -1
  285. package/dist/test/env-file.d.ts +5 -0
  286. package/dist/test/env-file.js +88 -0
  287. package/dist/test/env-file.js.map +1 -0
  288. package/dist/test/harness.d.ts +1 -0
  289. package/dist/test/harness.js +1 -1
  290. package/dist/test/harness.js.map +1 -1
  291. package/dist/test/passkeys.d.ts +22 -0
  292. package/dist/test/passkeys.js +62 -0
  293. package/dist/test/passkeys.js.map +1 -0
  294. package/dist/test/storage.d.ts +15 -0
  295. package/dist/test/storage.js +26 -0
  296. package/dist/test/storage.js.map +1 -0
  297. package/dist/test/ts-worker-hooks.d.ts +1 -0
  298. package/dist/test/ts-worker-hooks.js +24 -0
  299. package/dist/test/ts-worker-hooks.js.map +1 -0
  300. package/dist/text/diff.d.ts +9 -14
  301. package/dist/text/diff.js +74 -108
  302. package/dist/text/diff.js.map +1 -1
  303. package/dist/tools/commands.d.ts +6 -1
  304. package/dist/tools/commands.js +8 -2
  305. package/dist/tools/commands.js.map +1 -1
  306. package/dist/tools/connectors.d.ts +33 -0
  307. package/dist/tools/connectors.js +121 -0
  308. package/dist/tools/connectors.js.map +1 -0
  309. package/dist/tools/crons.d.ts +38 -1
  310. package/dist/tools/crons.js +122 -18
  311. package/dist/tools/crons.js.map +1 -1
  312. package/dist/tools/dashboard-plugins.d.ts +53 -0
  313. package/dist/tools/dashboard-plugins.js +188 -0
  314. package/dist/tools/dashboard-plugins.js.map +1 -0
  315. package/dist/tools/extensions.d.ts +18 -5
  316. package/dist/tools/extensions.js +344 -266
  317. package/dist/tools/extensions.js.map +1 -1
  318. package/dist/tools/gateway-restart.d.ts +62 -0
  319. package/dist/tools/gateway-restart.js +142 -0
  320. package/dist/tools/gateway-restart.js.map +1 -0
  321. package/dist/tools/health.d.ts +44 -0
  322. package/dist/tools/health.js +201 -46
  323. package/dist/tools/health.js.map +1 -1
  324. package/dist/tools/hygiene.d.ts +4 -3
  325. package/dist/tools/hygiene.js +23 -9
  326. package/dist/tools/hygiene.js.map +1 -1
  327. package/dist/tools/kanban.d.ts +4 -2
  328. package/dist/tools/kanban.js +28 -19
  329. package/dist/tools/kanban.js.map +1 -1
  330. package/dist/tools/mcp.d.ts +13 -0
  331. package/dist/tools/mcp.js +88 -0
  332. package/dist/tools/mcp.js.map +1 -1
  333. package/dist/tools/memory-masks.d.ts +5 -0
  334. package/dist/tools/memory-masks.js +256 -0
  335. package/dist/tools/memory-masks.js.map +1 -0
  336. package/dist/tools/memory.d.ts +29 -3
  337. package/dist/tools/memory.js +69 -27
  338. package/dist/tools/memory.js.map +1 -1
  339. package/dist/tools/plugin-settings.d.ts +15 -0
  340. package/dist/tools/plugin-settings.js +90 -0
  341. package/dist/tools/plugin-settings.js.map +1 -0
  342. package/dist/tools/project-os.d.ts +11 -0
  343. package/dist/tools/project-os.js +267 -0
  344. package/dist/tools/project-os.js.map +1 -0
  345. package/dist/tools/skills.d.ts +14 -5
  346. package/dist/tools/skills.js +49 -16
  347. package/dist/tools/skills.js.map +1 -1
  348. package/dist/tools/update-notifications.d.ts +34 -15
  349. package/dist/tools/update-notifications.js +141 -57
  350. package/dist/tools/update-notifications.js.map +1 -1
  351. package/dist/tools/update-rollback.d.ts +11 -0
  352. package/dist/tools/update-rollback.js +28 -0
  353. package/dist/tools/update-rollback.js.map +1 -0
  354. package/dist/tools/updates.d.ts +110 -32
  355. package/dist/tools/updates.js +1112 -294
  356. package/dist/tools/updates.js.map +1 -1
  357. package/dist/tools/wiki.d.ts +68 -0
  358. package/dist/tools/wiki.js +413 -0
  359. package/dist/tools/wiki.js.map +1 -0
  360. package/dist/util.d.ts +2 -0
  361. package/dist/util.js +5 -0
  362. package/dist/util.js.map +1 -1
  363. package/dist/workspace/escape.d.ts +61 -0
  364. package/dist/workspace/escape.js +149 -0
  365. package/dist/workspace/escape.js.map +1 -0
  366. package/dist/workspace/extract.d.ts +22 -0
  367. package/dist/workspace/extract.js +308 -0
  368. package/dist/workspace/extract.js.map +1 -0
  369. package/dist/workspace/fs.d.ts +12 -0
  370. package/dist/workspace/fs.js +92 -23
  371. package/dist/workspace/fs.js.map +1 -1
  372. package/dist/workspace/git.d.ts +34 -1
  373. package/dist/workspace/git.js +328 -35
  374. package/dist/workspace/git.js.map +1 -1
  375. package/dist/workspace/media.d.ts +16 -13
  376. package/dist/workspace/media.js +43 -61
  377. package/dist/workspace/media.js.map +1 -1
  378. package/dist/workspace/rollback.d.ts +36 -22
  379. package/dist/workspace/rollback.js +178 -272
  380. package/dist/workspace/rollback.js.map +1 -1
  381. package/dist/workspace/unzip.js +4 -1
  382. package/dist/workspace/unzip.js.map +1 -1
  383. package/dist/workspace/upload.d.ts +7 -4
  384. package/dist/workspace/upload.js +5 -2
  385. package/dist/workspace/upload.js.map +1 -1
  386. package/dist/workspace/workspaces.d.ts +15 -19
  387. package/dist/workspace/workspaces.js +55 -127
  388. package/dist/workspace/workspaces.js.map +1 -1
  389. package/dist/workspace/worktrees.d.ts +2 -1
  390. package/dist/workspace/worktrees.js +179 -42
  391. package/dist/workspace/worktrees.js.map +1 -1
  392. package/package.json +7 -3
  393. package/sidecar/agent_dependency.json +4 -4
  394. package/sidecar/agent_support.json +26 -0
  395. package/sidecar/scripts/record-fixtures.py +12 -3
  396. package/sidecar/scripts/replay_sidecar.py +32 -0
  397. package/sidecar/scripts/suite_runner.py +302 -0
  398. package/sidecar/scripts/test.sh +12 -4
  399. package/sidecar/talaria_sidecar/__init__.py +6 -1
  400. package/sidecar/talaria_sidecar/__main__.py +4 -2
  401. package/sidecar/talaria_sidecar/agent_mutation.py +975 -0
  402. package/sidecar/talaria_sidecar/agent_services.py +1053 -0
  403. package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
  404. package/sidecar/talaria_sidecar/home.py +56 -3
  405. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  406. package/sidecar/talaria_sidecar/methods/aux.py +166 -24
  407. package/sidecar/talaria_sidecar/methods/chat.py +852 -135
  408. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  409. package/sidecar/talaria_sidecar/methods/commands.py +294 -4
  410. package/sidecar/talaria_sidecar/methods/config.py +123 -4
  411. package/sidecar/talaria_sidecar/methods/connectors.py +343 -0
  412. package/sidecar/talaria_sidecar/methods/cron.py +395 -36
  413. package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
  414. package/sidecar/talaria_sidecar/methods/gateway.py +212 -55
  415. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +234 -0
  416. package/sidecar/talaria_sidecar/methods/goals.py +81 -41
  417. package/sidecar/talaria_sidecar/methods/kanban.py +108 -34
  418. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  419. package/sidecar/talaria_sidecar/methods/memory.py +84 -0
  420. package/sidecar/talaria_sidecar/methods/oauth.py +5 -2
  421. package/sidecar/talaria_sidecar/methods/process.py +218 -15
  422. package/sidecar/talaria_sidecar/methods/profiles.py +121 -13
  423. package/sidecar/talaria_sidecar/methods/providers.py +217 -28
  424. package/sidecar/talaria_sidecar/methods/runtime.py +69 -1
  425. package/sidecar/talaria_sidecar/methods/skills.py +67 -4
  426. package/sidecar/talaria_sidecar/methods/state_db.py +316 -75
  427. package/sidecar/talaria_sidecar/methods/stt.py +159 -78
  428. package/sidecar/talaria_sidecar/methods/text.py +14 -1
  429. package/sidecar/talaria_sidecar/methods/toolsets.py +49 -0
  430. package/sidecar/talaria_sidecar/methods/usage.py +113 -14
  431. package/sidecar/talaria_sidecar/methods/worktree.py +26 -19
  432. package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
  433. package/sidecar/talaria_sidecar/rpc.py +45 -4
  434. package/sidecar/talaria_sidecar/runtime.py +98 -13
  435. package/sidecar/talaria_sidecar/speed.py +35 -0
  436. package/static/dist/FILES.txt +126 -126
  437. package/static/dist/assets/AppShell-cey33v9s.js +8 -0
  438. package/static/dist/assets/ChatPage-i8wnugr7.js +18 -0
  439. package/static/dist/assets/ConnectTargets-hg19553x.js +1 -0
  440. package/static/dist/assets/ExtensionRoute-cly4apm5.js +1 -0
  441. package/static/dist/assets/Field-j8uqoigz.js +9 -0
  442. package/static/dist/assets/HubRoute-e4bgz41j.js +2 -0
  443. package/static/dist/assets/Markdown-lahuichm.js +598 -0
  444. package/static/dist/assets/RightPanel-lbo4i0fn.js +1 -0
  445. package/static/dist/assets/Select-gqatm2e0.js +1 -0
  446. package/static/dist/assets/SessionListPanel-mfqayfjh.js +1 -0
  447. package/static/dist/assets/SettingsLayout-b8980u7k.js +1 -0
  448. package/static/dist/assets/States-e7dkgtxf.js +1 -0
  449. package/static/dist/assets/Toaster-bfqqy786.js +1 -0
  450. package/static/dist/assets/_app-n1n6pfx1.js +1 -0
  451. package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-buxjxav3.js} +1 -1
  452. package/static/dist/assets/_app.index-jt8zwiu2.js +1 -0
  453. package/static/dist/assets/_app.insights-cv76ml71.js +1 -0
  454. package/static/dist/assets/_app.kanban-h6ts53j7.js +1 -0
  455. package/static/dist/assets/_app.logs-dm873z90.js +1 -0
  456. package/static/dist/assets/_app.memory-glk5y6m8.js +1 -0
  457. package/static/dist/assets/_app.profiles-ka6zpstq.js +1 -0
  458. package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-g2vzghvc.js} +1 -1
  459. package/static/dist/assets/_app.settings-hgjdhzcd.js +1 -0
  460. package/static/dist/assets/_app.settings._section-jf2kmhae.js +3 -0
  461. package/static/dist/assets/_app.skills-cii852yq.js +1 -0
  462. package/static/dist/assets/_app.tasks-fp9q7atm.js +1 -0
  463. package/static/dist/assets/_app.todos-dw94qzue.js +1 -0
  464. package/static/dist/assets/_app.workspaces-inyygzze.js +1 -0
  465. package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-gjayq3ra.js} +1 -1
  466. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +1 -0
  467. package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-nwb3xovn.js} +1 -1
  468. package/static/dist/assets/archive-b1j004vn.js +1 -0
  469. package/static/dist/assets/back-g74gkozu.js +1 -0
  470. package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-joedghi3.js} +1 -1
  471. package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-hyrjrohe.js} +1 -1
  472. package/static/dist/assets/channel-es95o4y8.js +1 -0
  473. package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-koouqbut.js} +1 -1
  474. package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-bl4bhdnt.js} +1 -1
  475. package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-gpedttsp.js} +3 -3
  476. package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-d8xxi0zi.js} +1 -1
  477. package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-jro0hl73.js} +1 -1
  478. package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-djn6gbyu.js} +1 -1
  479. package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-vw4p5p7o.js} +1 -1
  480. package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-n1hj77q8.js} +1 -1
  481. package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-oy0gwn2n.js} +1 -1
  482. package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-onf1hz6c.js} +1 -1
  483. package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-m8sqn7cs.js} +1 -1
  484. package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-ek4smc8x.js} +1 -1
  485. package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-g9gfvefo.js} +3 -3
  486. package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-f9nfv2cu.js} +1 -1
  487. package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-bnk3m1mq.js} +1 -1
  488. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +1 -0
  489. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +1 -0
  490. package/static/dist/assets/copied-bli9tbcx.js +1 -0
  491. package/static/dist/assets/{copy-fw5b1pc6.js → copy-jamtd1pf.js} +1 -1
  492. package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-f8xfdd3f.js} +1 -1
  493. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-n9lb99h9.js} +1 -1
  494. package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-dgailddi.js} +4 -4
  495. package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-m6aeoe7c.js} +1 -1
  496. package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-jall3j0x.js} +1 -1
  497. package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-hbawi7hn.js} +1 -1
  498. package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-hendl2tt.js} +1 -1
  499. package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-igwc35p3.js} +1 -1
  500. package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c64knz08.js} +1 -1
  501. package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-brdu9lcm.js} +1 -1
  502. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +1 -0
  503. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +1 -0
  504. package/static/dist/assets/folder-plus-lvt9kudb.js +1 -0
  505. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ma6jxj5i.js} +1 -1
  506. package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-naa9w6xa.js} +1 -1
  507. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-hzltvsod.js} +1 -1
  508. package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-ghoavj8d.js} +1 -1
  509. package/static/dist/assets/host-hzm8n85n.js +1 -0
  510. package/static/dist/assets/index-drdcaqeq.css +2 -0
  511. package/static/dist/assets/index-jzm844he.js +11 -0
  512. package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-mn2ut5ko.js} +1 -1
  513. package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-h6kuqvsb.js} +1 -1
  514. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-e7bnwq7h.js} +1 -1
  515. package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-etd1bi6p.js} +1 -1
  516. package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-emb1g9dz.js} +4 -4
  517. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +1 -0
  518. package/static/dist/assets/{line-osxq6fq7.js → line-h0ojzsvs.js} +1 -1
  519. package/static/dist/assets/link-k01ryaf9.js +1 -0
  520. package/static/dist/assets/login-bsae49ew.js +1 -0
  521. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +1 -0
  522. package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-bnre27u8.js} +3 -3
  523. package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-o1vathns.js} +1 -1
  524. package/static/dist/assets/onboarding-c65tysya.js +1 -0
  525. package/static/dist/assets/onboarding_model_label-gpn28uix.js +1 -0
  526. package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-hatq7m2e.js} +1 -1
  527. package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
  528. package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-gwisz8jv.js} +1 -1
  529. package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-fpftf0mk.js} +1 -1
  530. package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-khm1o9a6.js} +1 -1
  531. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-owqc4uqn.js} +1 -1
  532. package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-cntfwjpq.js} +1 -1
  533. package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-a7t5eibp.js} +1 -1
  534. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +1 -0
  535. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +1 -0
  536. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +1 -0
  537. package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-etuh0qa0.js} +1 -1
  538. package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-mqbkv2a3.js} +1 -1
  539. package/static/dist/assets/refresh-l29ljp76.js +1 -0
  540. package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-oq2ulk22.js} +1 -1
  541. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-iqc02emv.js} +1 -1
  542. package/static/dist/assets/saved-ndd4t2xb.js +1 -0
  543. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-i0v116q0.js} +2 -2
  544. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +1 -0
  545. package/static/dist/assets/share._token-c1ri0hy1.js +2 -0
  546. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +1 -0
  547. package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-dnpt1ato.js} +1 -1
  548. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +1 -0
  549. package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-loqbdazb.js} +1 -1
  550. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +8 -0
  551. package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-ol2hnvma.js} +1 -1
  552. package/static/dist/assets/toast-i3h34v6o.js +64 -0
  553. package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-idrj6iox.js} +1 -1
  554. package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-lzr49vv5.js} +1 -1
  555. package/static/dist/assets/untitled-oaoya4qj.js +1 -0
  556. package/static/dist/assets/useForm-nfaxgmsk.js +1 -0
  557. package/static/dist/assets/{useParams-lhg566ew.js → useParams-gjc70qru.js} +1 -1
  558. package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-e6ae539u.js} +1 -1
  559. package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-bvwr9o2z.js} +1 -1
  560. package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-31ui8tma.js} +1 -1
  561. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-k5wqtx4b.js} +1 -1
  562. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-aqbgvujg.js} +1 -1
  563. package/static/dist/index.html +1 -1
  564. package/static/dist/sw.js +1 -1
  565. package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
  566. package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
  567. package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
  568. package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
  569. package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
  570. package/static/dist/assets/Markdown-mdwlvj3w.js +0 -598
  571. package/static/dist/assets/RightPanel-ftvrjlcp.js +0 -1
  572. package/static/dist/assets/Select-bilaypej.js +0 -1
  573. package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
  574. package/static/dist/assets/SettingsLayout-jonjefmj.js +0 -1
  575. package/static/dist/assets/States-nmejrwrm.js +0 -1
  576. package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
  577. package/static/dist/assets/_app-gdidzd94.js +0 -1
  578. package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
  579. package/static/dist/assets/_app.insights-luywlarz.js +0 -1
  580. package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
  581. package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
  582. package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
  583. package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
  584. package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
  585. package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
  586. package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
  587. package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
  588. package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
  589. package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
  590. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
  591. package/static/dist/assets/back-bemaer09.js +0 -1
  592. package/static/dist/assets/cancel-drd0g6d9.js +0 -1
  593. package/static/dist/assets/channel-cpdgf867.js +0 -1
  594. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
  595. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
  596. package/static/dist/assets/copied-ima3pysb.js +0 -1
  597. package/static/dist/assets/endpoints-d33siokx.js +0 -64
  598. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
  599. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
  600. package/static/dist/assets/index-cxq58xb1.js +0 -11
  601. package/static/dist/assets/index-hgtx08n5.css +0 -2
  602. package/static/dist/assets/lazyRouteComponent-af0lvgjx.js +0 -1
  603. package/static/dist/assets/link-jyxicdhg.js +0 -1
  604. package/static/dist/assets/login-ec21t39x.js +0 -1
  605. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
  606. package/static/dist/assets/onboarding-btnltvyz.js +0 -1
  607. package/static/dist/assets/onboarding_model_label-dp7xffoh.js +0 -1
  608. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
  609. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
  610. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
  611. package/static/dist/assets/refresh-b4uams1i.js +0 -1
  612. package/static/dist/assets/saved-kg52csy7.js +0 -1
  613. package/static/dist/assets/settings_section_extensions_title-ej0iapxe.js +0 -1
  614. package/static/dist/assets/share._token-f97e94r0.js +0 -2
  615. package/static/dist/assets/sliders-horizontal-fj18it47.js +0 -1
  616. package/static/dist/assets/sse-fwpqtzbg.js +0 -1
  617. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
  618. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
  619. package/static/dist/assets/todoStore-eginf9h4.js +0 -1
  620. package/static/dist/assets/tts-lxdxnmjd.js +0 -1
  621. package/static/dist/assets/untitled-ovpfthg4.js +0 -1
  622. package/static/dist/assets/useForm-bcblv1rx.js +0 -1
  623. package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
@@ -1,21 +1,28 @@
1
+ import { filterAsync } from '../util.js';
1
2
  /**
2
3
  * Talaria Relay: publisher pairing, profile enrollment, browser presence
3
4
  * leases, and the best-effort snapshot publisher (Python
4
5
  * `api/talaria_relay.py`). Snapshots are Ed25519-signed complete states per
5
- * enrolled profile; failures back off and never block a turn.
6
+ * enrolled profile; failures back off and never block a turn. Each session that
7
+ * first appears in an enrolled profile is announced once with a signed
8
+ * session-started event (contracts/fixtures/publisher-session-started.json).
6
9
  */
7
10
  import { readCapped } from '../http/capped.js';
8
11
  import { createHash, createPrivateKey, generateKeyPairSync, randomUUID, sign as cryptoSign } from 'node:crypto';
9
- import { existsSync, mkdirSync, readFileSync } from 'node:fs';
10
- import { dirname, join } from 'node:path';
11
- import { atomicWriteText } from '../fs/atomic.js';
12
+ import { readFileSync, statSync } from 'node:fs';
13
+ import { z } from 'zod';
14
+ import { updateDocument } from './storage/document.js';
12
15
  import { str } from '../util.js';
13
16
  export const PRESENCE_LEASE_SECONDS = 90;
14
- const PRESENCE_MAX_LEASES = 256;
17
+ export const PRESENCE_MAX_LEASES = 256;
18
+ /** How long a tab's sequence fence outlives its lease, so a reloaded tab whose clock moved back recovers. */
19
+ export const PRESENCE_FENCE_SECONDS = 15 * 60;
15
20
  const PRESENCE_TAB_RE = /^[A-Za-z0-9_-]{8,64}$/;
16
21
  const DEFAULT_RELAY_URL = 'https://relay.talaria.kil.dev';
17
22
  const TERMINAL_RETENTION_S = 15 * 60;
18
23
  const REQUEST_TIMEOUT_MS = 10_000;
24
+ // Sessions started longer ago than this are never announced, which bounds the started ledger.
25
+ const STARTED_WINDOW_MS = 24 * 60 * 60 * 1000;
19
26
  const b64u = (data) => data.toString('base64url');
20
27
  export class RelayPairingError extends Error {
21
28
  status;
@@ -49,13 +56,12 @@ export function validatedOrigin(raw, httpsOnly = false) {
49
56
  }
50
57
  return value;
51
58
  }
52
- export function relayStatePath(stateDir) { return join(stateDir, 'talaria-relay.json'); }
53
59
  /** Python `RelayConfig.from_state`: null when absent or a v1 file (registration is required again); throws on corruption. */
54
- export function loadRelayConfig(stateDir) {
55
- const path = relayStatePath(stateDir);
56
- if (!existsSync(path))
60
+ export async function loadRelayConfig(documents) {
61
+ const record = await documents.get('relay', 'config');
62
+ if (!record)
57
63
  return null;
58
- const values = JSON.parse(readFileSync(path, 'utf8'));
64
+ const values = record.value;
59
65
  if (!values || typeof values !== 'object' || Array.isArray(values))
60
66
  throw new Error('saved relay configuration is not an object');
61
67
  const v = values;
@@ -71,32 +77,54 @@ export function loadRelayConfig(stateDir) {
71
77
  throw new Error('saved relay profiles are invalid');
72
78
  normalized[name] = { identity: p.identity, profile_id: p.profile_id };
73
79
  }
74
- return { url: validatedOrigin(str(v.url)), publisher_id: validatedOrigin(str(v.publisher_id)), key_id: str(v.key_id), private_key_path: str(v.private_key_path), profiles: normalized };
80
+ return { url: validatedOrigin(str(v.url)), publisher_id: validatedOrigin(str(v.publisher_id)), key_id: str(v.key_id), private_key_path: str(v.private_key_path), ...(typeof v.private_key_document === 'string' ? { private_key_document: v.private_key_document } : {}), profiles: normalized };
75
81
  }
76
- export function saveRelayConfig(stateDir, config) {
82
+ export async function saveRelayConfig(documents, config) {
77
83
  const payload = { key_id: config.key_id, private_key_path: config.private_key_path, profiles: sortedProfiles(config.profiles), publisher_id: config.publisher_id, url: config.url, version: 2 };
78
- mkdirSync(stateDir, { recursive: true });
79
- atomicWriteText(relayStatePath(stateDir), JSON.stringify(payload, null, 2) + '\n');
84
+ const value = { ...payload, ...(config.private_key_document ? { private_key_document: config.private_key_document } : {}) };
85
+ await updateDocument(documents, 'relay', 'config', () => ({ value, result: undefined }), { replaceInvalid: true });
80
86
  }
81
87
  function sortedProfiles(profiles) {
82
88
  return Object.fromEntries(Object.keys(profiles).sort().map((k) => [k, { identity: profiles[k].identity, profile_id: profiles[k].profile_id }]));
83
89
  }
84
- /** Python `_profile_identity`: a persistent `pfi_<32 hex>` per profile home, minted on first use (0600). */
85
- export function profileIdentity(profileHome) {
86
- const path = join(profileHome, '.talaria-relay-profile-id');
87
- let identity;
88
- if (existsSync(path))
89
- identity = readFileSync(path, 'utf8').trim();
90
- else {
91
- identity = `pfi_${randomUUID().replace(/-/g, '')}`;
92
- atomicWriteText(path, identity + '\n', { mode: 0o600 });
93
- }
94
- if (!/^pfi_[0-9a-f]{32}$/.test(identity))
95
- throw new Error('invalid Talaria Relay profile identity');
96
- return identity;
90
+ // The started ledger records announcements; deletions need a separate, non-expiring delivery outbox.
91
+ const DeletedViewSchema = z.object({
92
+ id: z.string().min(1), sessionId: z.string().min(1), profile: z.string().min(1),
93
+ through: z.number().int().nonnegative(), identity: z.string().nullable(),
94
+ scope: z.object({ url: z.string(), publisherId: z.string(), profileId: z.string() }).nullable(),
95
+ });
96
+ const DeletedViewsSchema = z.object({ version: z.literal(1), views: z.array(DeletedViewSchema) });
97
+ // A staged deletion cannot clear a session that survived a failed removal or a restart before removal.
98
+ async function hasSessionData(store, sid) {
99
+ if (store.sessions.has(sid))
100
+ return true;
101
+ return (await store.exists(sid));
97
102
  }
98
- function loadKey(path) {
99
- const key = createPrivateKey(readFileSync(path));
103
+ function parseDeletedViews(raw) { return raw === undefined ? [] : DeletedViewsSchema.parse(raw).views; }
104
+ async function loadDeletedViews(documents) { return parseDeletedViews((await documents.get('relay', 'deleted'))?.value); }
105
+ async function mutateDeletedViews(documents, edit) {
106
+ return (await updateDocument(documents, 'relay', 'deleted', async (raw) => {
107
+ const views = parseDeletedViews(raw);
108
+ const result = await edit(views);
109
+ return { value: { version: 1, views }, result };
110
+ }));
111
+ }
112
+ /** The profile identity lives in the selected backend, with the JSON adapter retaining its old private file. */
113
+ export async function profileIdentity(deps, profile) {
114
+ if (!statSync(deps.profileHome(profile)).isDirectory())
115
+ throw new Error('Hermes profile is unavailable');
116
+ return (await updateDocument(deps.documents, 'relay-identities', profile, (raw) => {
117
+ const identity = typeof raw === 'string' ? raw.trim() : `pfi_${randomUUID().replace(/-/g, '')}`;
118
+ if (!/^pfi_[0-9a-f]{32}$/.test(identity))
119
+ throw new Error('invalid Talaria Relay profile identity');
120
+ return { value: typeof raw === 'string' ? raw : identity + '\n', result: identity };
121
+ }));
122
+ }
123
+ async function loadKey(documents, config) {
124
+ const value = config.private_key_document ? (await documents.get('relay-keys', config.private_key_document))?.value : readFileSync(config.private_key_path, 'utf8');
125
+ if (typeof value !== 'string')
126
+ throw new Error('Talaria publisher key is missing');
127
+ const key = createPrivateKey(value);
100
128
  if (key.asymmetricKeyType !== 'ed25519')
101
129
  throw new Error('Talaria publisher key must be an Ed25519 private key');
102
130
  return key;
@@ -108,21 +136,23 @@ function signedHeaders(key, keyId, method, path, body, now) {
108
136
  return { 'Content-Type': 'application/json', 'X-Talaria-Key-Id': keyId, 'X-Talaria-Timestamp': timestamp, 'X-Talaria-Nonce': nonce, 'X-Talaria-Signature': b64u(cryptoSign(null, Buffer.from(signed, 'utf8'), key)) };
109
137
  }
110
138
  // ── presence leases ───────────────────────────────────────────────────────
111
- /** Profile-scoped browser activity leases; a fresh active lease mutes alerts for that profile. */
139
+ /**
140
+ * Profile-scoped browser activity leases; a fresh active lease mutes alerts for that profile.
141
+ * Each tab's entry also fences its sequence number for PRESENCE_FENCE_SECONDS past lease expiry,
142
+ * so a delayed lower sequence stays rejected. At most PRESENCE_MAX_LEASES tabs are kept; the least
143
+ * recently updated tab is evicted first, which drops its fence too.
144
+ */
112
145
  export class PresenceLeases {
113
146
  now;
114
147
  leases = new Map();
115
148
  constructor(now) {
116
149
  this.now = now;
117
150
  }
118
- prune(now) {
119
- for (const [key, entry] of this.leases)
120
- if (entry.expires <= now)
121
- this.leases.delete(key);
122
- }
123
151
  apply(profile, tabId, seq, active) {
124
152
  const now = this.now();
125
- this.prune(now);
153
+ for (const [key, entry] of this.leases)
154
+ if (entry.expires + PRESENCE_FENCE_SECONDS <= now)
155
+ this.leases.delete(key);
126
156
  const key = `${profile}\n${tabId}`;
127
157
  const current = this.leases.get(key);
128
158
  if (current && seq <= current.seq)
@@ -135,9 +165,9 @@ export class PresenceLeases {
135
165
  renew(profile, tabId, seq = 1) { this.apply(profile, tabId, seq, true); }
136
166
  revoke(profile, tabId, seq = 1) { this.apply(profile, tabId, seq, false); }
137
167
  has(profile) {
138
- this.prune(this.now());
168
+ const now = this.now();
139
169
  for (const [key, entry] of this.leases)
140
- if (entry.active && key.startsWith(`${profile}\n`))
170
+ if (entry.active && entry.expires > now && key.startsWith(`${profile}\n`))
141
171
  return true;
142
172
  return false;
143
173
  }
@@ -164,7 +194,7 @@ export class RelayPublisher {
164
194
  config;
165
195
  deps;
166
196
  key;
167
- revisionPath;
197
+ startedUnsupportedLogged = false;
168
198
  lastRevision = 0;
169
199
  terminal = new Map();
170
200
  disabledProfiles = new Set();
@@ -180,10 +210,15 @@ export class RelayPublisher {
180
210
  constructor(config, deps) {
181
211
  this.config = config;
182
212
  this.deps = deps;
183
- this.key = loadKey(config.private_key_path);
184
- this.revisionPath = join(dirname(config.private_key_path), 'talaria-relay-revision');
213
+ }
214
+ async initialize() {
215
+ this.key = await loadKey(this.deps.documents, this.config);
216
+ try {
217
+ await this.mutateStarted((ledger) => { this.trackProfiles(ledger); });
218
+ }
219
+ catch { /* the first pass retries */ }
185
220
  try {
186
- this.lastRevision = Number.parseInt(readFileSync(this.revisionPath, 'utf8'), 10) || 0;
221
+ this.lastRevision = Number.parseInt(str((await this.deps.documents.get('relay', 'revision'))?.value), 10) || 0;
187
222
  }
188
223
  catch {
189
224
  this.lastRevision = 0;
@@ -197,11 +232,11 @@ export class RelayPublisher {
197
232
  this.views.set(sid, view);
198
233
  this.lastRevision = Math.max(this.lastRevision, previous.lastRevision);
199
234
  }
200
- start(publishInitial = true) {
235
+ async start(publishInitial = true) {
201
236
  if (this.loop)
202
237
  return;
203
238
  this.loop = this.run();
204
- if (publishInitial)
239
+ if (publishInitial || (await loadDeletedViews(this.deps.documents)).length)
205
240
  this.changed();
206
241
  }
207
242
  stop() {
@@ -229,15 +264,37 @@ export class RelayPublisher {
229
264
  this.views.set(sid, { profile, through: Math.floor(this.deps.now() * 1000) });
230
265
  this.changed();
231
266
  }
267
+ /** Bind an unconfigured deletion once, before sending, so retries cannot cross relay or profile identities. */
268
+ async pendingDeleted() {
269
+ const views = await mutateDeletedViews(this.deps.documents, async (views) => {
270
+ for (const view of views) {
271
+ if (view.scope)
272
+ continue;
273
+ const cfg = this.config.profiles[view.profile];
274
+ if (!cfg || (view.identity !== null && cfg.identity !== view.identity) || !await this.publishes(view.profile, cfg))
275
+ continue;
276
+ view.identity = cfg.identity;
277
+ view.scope = { url: this.config.url, publisherId: this.config.publisher_id, profileId: cfg.profile_id };
278
+ }
279
+ return views;
280
+ });
281
+ return filterAsync(views, async (view) => !await hasSessionData(this.deps.store, view.sessionId));
282
+ }
232
283
  /** Send the views queued before the snapshot just published, so the relay already holds every outcome they saw. */
233
284
  async flushViews(ready) {
234
285
  for (const [sid, view] of ready) {
286
+ if (view.deleted && (await hasSessionData(this.deps.store, sid)))
287
+ continue;
288
+ let accepted = false;
235
289
  for (const [profile, cfg] of Object.entries(this.config.profiles)) {
236
- if (!this.deps.profilesMatch(view.profile, profile) || !this.publishes(profile, cfg))
290
+ if (!this.deps.profilesMatch(view.profile, profile) || !(await this.publishes(profile, cfg)))
291
+ continue;
292
+ if (view.deleted && (view.deleted.identity !== cfg.identity || view.deleted.scope?.url !== this.config.url || view.deleted.scope.publisherId !== this.config.publisher_id || view.deleted.scope.profileId !== cfg.profile_id))
237
293
  continue;
238
294
  const path = `/v1/publishers/${encodeURIComponent(this.config.publisher_id)}/profiles/${encodeURIComponent(cfg.profile_id)}/sessions/${encodeURIComponent(sid)}/viewed`;
239
295
  try {
240
296
  await this.put(path, JSON.stringify({ through: view.through }));
297
+ accepted = true;
241
298
  }
242
299
  catch (error) {
243
300
  if (!(error instanceof RelayHttpError) || error.retryable)
@@ -250,7 +307,12 @@ export class RelayPublisher {
250
307
  this.deps.log(`[relay] Talaria relay rejected a viewed acknowledgement (HTTP ${String(error.status)})`);
251
308
  }
252
309
  }
253
- if (this.views.get(sid) === view)
310
+ if (view.deleted) {
311
+ if (accepted)
312
+ await mutateDeletedViews(this.deps.documents, (views) => { const index = views.findIndex((entry) => entry.id === view.deleted.id && entry.through === view.through); if (index >= 0)
313
+ views.splice(index, 1); });
314
+ }
315
+ else if (this.views.get(sid) === view)
254
316
  this.views.delete(sid);
255
317
  }
256
318
  }
@@ -274,9 +336,10 @@ export class RelayPublisher {
274
336
  return;
275
337
  this.dirty = false;
276
338
  try {
277
- const ready = new Map(this.views);
339
+ const ready = [...this.views, ...(await this.pendingDeleted()).map((view) => [view.sessionId, { profile: view.profile, through: view.through, deleted: view }])];
278
340
  await this.publishSnapshot(true);
279
341
  await this.flushViews(ready);
342
+ await this.publishStarted();
280
343
  if (this.failures)
281
344
  this.deps.log('[relay] Talaria relay snapshot recovered');
282
345
  this.failures = 0;
@@ -296,7 +359,7 @@ export class RelayPublisher {
296
359
  }
297
360
  }
298
361
  /** Python `build_states`: one row per session with a live or recently terminal run in this profile. */
299
- buildStates(profile) {
362
+ async buildStates(profile) {
300
363
  const cutoff = this.deps.now() - TERMINAL_RETENTION_S;
301
364
  for (const [sid, run] of this.terminal)
302
365
  if (run.terminal_at < cutoff)
@@ -320,7 +383,7 @@ export class RelayPublisher {
320
383
  for (const [sid, run] of bySession) {
321
384
  let session;
322
385
  try {
323
- session = this.deps.store.get(sid, { metadataOnly: true });
386
+ session = (await this.deps.store.get(sid, { metadataOnly: true }));
324
387
  }
325
388
  catch {
326
389
  continue;
@@ -343,13 +406,22 @@ export class RelayPublisher {
343
406
  states.push(state);
344
407
  }
345
408
  if (states.length)
346
- atomicWriteText(this.revisionPath, `${String(this.lastRevision)}\n`);
409
+ await updateDocument(this.deps.documents, 'relay', 'revision', (raw) => {
410
+ let floor = Number.parseInt(str(raw), 10) || 0;
411
+ for (const state of states) {
412
+ state.revision = Math.max(state.revision, floor + 1);
413
+ state.eventId = `snapshot:${String(state.revision)}:${state.sessionId}`;
414
+ floor = state.revision;
415
+ }
416
+ this.lastRevision = Math.max(this.lastRevision, floor);
417
+ return { value: `${String(this.lastRevision)}\n`, result: undefined };
418
+ });
347
419
  return states;
348
420
  }
349
421
  async publishSnapshot(isolatePermanent = false) {
350
422
  let retryable = null;
351
423
  for (const [profile, cfg] of Object.entries(this.config.profiles)) {
352
- if (!this.publishes(profile, cfg))
424
+ if (!(await this.publishes(profile, cfg)))
353
425
  continue;
354
426
  try {
355
427
  await this.publishProfileSnapshot(profile, cfg.profile_id);
@@ -368,10 +440,10 @@ export class RelayPublisher {
368
440
  if (retryable)
369
441
  throw retryable;
370
442
  }
371
- publishes(profile, cfg) {
443
+ async publishes(profile, cfg) {
372
444
  if (cfg.identity) {
373
445
  try {
374
- if (profileIdentity(this.deps.profileHome(profile)) !== cfg.identity)
446
+ if ((await profileIdentity(this.deps, profile)) !== cfg.identity)
375
447
  return false;
376
448
  }
377
449
  catch {
@@ -385,15 +457,136 @@ export class RelayPublisher {
385
457
  for (const [profile, cfg] of Object.entries(this.config.profiles)) {
386
458
  if (cfg.profile_id !== profileId)
387
459
  continue;
388
- if (cfg.identity !== expectedIdentity || profileIdentity(this.deps.profileHome(profile)) !== expectedIdentity)
460
+ if (cfg.identity !== expectedIdentity || (await profileIdentity(this.deps, profile)) !== expectedIdentity)
389
461
  throw new RelayPairingError('Hermes profile changed during relay enrollment', 409);
390
462
  await this.publishProfileSnapshot(profile, profileId);
391
463
  return;
392
464
  }
393
465
  throw new RelayPairingError('Talaria Relay profile enrollment is unavailable', 502);
394
466
  }
467
+ // The ledger is reread for every change, so a restarted or replacement publisher resends an unsent event under its ID.
468
+ parseStarted(raw) {
469
+ const ledger = new Map();
470
+ const profiles = raw?.profiles;
471
+ if (!Array.isArray(profiles))
472
+ return ledger;
473
+ for (const p of profiles) {
474
+ if (typeof p?.profileId !== 'string' || typeof p.floor !== 'number' || !Array.isArray(p.sessions))
475
+ continue;
476
+ const sessions = new Map();
477
+ for (const e of p.sessions) {
478
+ if (typeof e?.sessionId === 'string' && typeof e.startedAt === 'number')
479
+ sessions.set(e.sessionId, { startedAt: e.startedAt, sent: e.sent === true });
480
+ }
481
+ ledger.set(p.profileId, { floor: p.floor, sessions });
482
+ }
483
+ return ledger;
484
+ }
485
+ startedDocument(ledger) {
486
+ const profiles = [...ledger].map(([profileId, p]) => ({ profileId, floor: p.floor, sessions: [...p.sessions].map(([sessionId, e]) => ({ sessionId, startedAt: e.startedAt, sent: e.sent })) }));
487
+ return { version: 1, profiles };
488
+ }
489
+ async mutateStarted(edit) {
490
+ return (await updateDocument(this.deps.documents, 'relay', 'started', async (raw) => {
491
+ const ledger = this.parseStarted(raw);
492
+ const result = await edit(ledger);
493
+ return { value: this.startedDocument(ledger), result };
494
+ }, { replaceInvalid: true }));
495
+ }
496
+ /** Give each enrolled profile without a ledger entry a floor of now; true when the ledger changed. */
497
+ trackProfiles(ledger) {
498
+ const nowMs = Math.floor(this.deps.now() * 1000);
499
+ let changed = false;
500
+ for (const cfg of Object.values(this.config.profiles)) {
501
+ if (ledger.has(cfg.profile_id))
502
+ continue;
503
+ ledger.set(cfg.profile_id, { floor: nowMs, sessions: new Map() });
504
+ changed = true;
505
+ }
506
+ return changed;
507
+ }
508
+ /** Record every messageful Web or Agent session that started in an enrolled profile since tracking began. */
509
+ async observeStarted() {
510
+ // Agent rows come from the listing worker first, so the ledger's load-to-save stays synchronous.
511
+ const agentRows = new Map();
512
+ for (const [profile, cfg] of Object.entries(this.config.profiles)) {
513
+ if (!(await this.publishes(profile, cfg)))
514
+ continue;
515
+ try {
516
+ agentRows.set(profile, await this.deps.agentSessions(profile));
517
+ }
518
+ catch {
519
+ agentRows.set(profile, []);
520
+ }
521
+ }
522
+ let index = [];
523
+ try {
524
+ index = await this.deps.store.readIndexEntries();
525
+ }
526
+ catch { /* no resident index */ }
527
+ const eligible = new Set();
528
+ for (const [profile, cfg] of Object.entries(this.config.profiles))
529
+ if (await this.publishes(profile, cfg))
530
+ eligible.add(profile);
531
+ return (await this.mutateStarted((ledger) => {
532
+ const nowMs = Math.floor(this.deps.now() * 1000);
533
+ this.trackProfiles(ledger);
534
+ for (const [profile, cfg] of Object.entries(this.config.profiles)) {
535
+ if (!eligible.has(profile))
536
+ continue;
537
+ const entry = ledger.get(cfg.profile_id);
538
+ const floor = Math.max(entry.floor, nowMs - STARTED_WINDOW_MS);
539
+ for (const [sid, e] of entry.sessions)
540
+ if (e.startedAt < floor) {
541
+ entry.sessions.delete(sid);
542
+ }
543
+ // A compressed Agent chat keeps its lineage root's id and start, so later segments are not new sessions.
544
+ const agent = agentRows.get(profile) ?? [];
545
+ const seen = [
546
+ ...index.filter((row) => Number(row.message_count) > 0 && this.deps.profilesMatch(row.profile, profile)).map((row) => [row.session_id, row.created_at]),
547
+ ...agent.map((row) => [row._lineage_root_id || row.id, row.started_at]),
548
+ ];
549
+ for (const [rawSid, created] of seen) {
550
+ const sid = str(rawSid).trim();
551
+ const startedAt = Math.floor(Number(created) * 1000);
552
+ if (!sid || sid.length > 191 || !Number.isSafeInteger(startedAt) || startedAt < floor || entry.sessions.has(sid))
553
+ continue;
554
+ entry.sessions.set(sid, { startedAt, sent: false });
555
+ }
556
+ }
557
+ return ledger;
558
+ }));
559
+ }
560
+ /** Announce each newly observed session once, oldest first, to the relay scope of the profile that owns it. */
561
+ async publishStarted() {
562
+ const ledger = await this.observeStarted();
563
+ for (const [profile, cfg] of Object.entries(this.config.profiles)) {
564
+ if (!(await this.publishes(profile, cfg)))
565
+ continue;
566
+ const unsent = [...(ledger.get(cfg.profile_id)?.sessions ?? [])].filter(([, e]) => !e.sent).sort((a, b) => a[1].startedAt - b[1].startedAt);
567
+ for (const [sid, e] of unsent) {
568
+ const path = `/v1/publishers/${encodeURIComponent(this.config.publisher_id)}/profiles/${encodeURIComponent(cfg.profile_id)}/sessions/${encodeURIComponent(sid)}/started`;
569
+ const body = JSON.stringify({ version: 1, eventId: `started:${sid}`, publisherId: this.config.publisher_id, profileId: cfg.profile_id, sessionId: sid, startedAt: e.startedAt });
570
+ try {
571
+ await this.put(path, body);
572
+ }
573
+ catch (error) {
574
+ if (!(error instanceof RelayHttpError) || error.retryable)
575
+ throw error;
576
+ if (error.status !== 404)
577
+ this.deps.log(`[relay] Talaria relay rejected a session-started event (HTTP ${String(error.status)})`);
578
+ else if (!this.startedUnsupportedLogged) {
579
+ this.startedUnsupportedLogged = true;
580
+ this.deps.log('[relay] Talaria relay does not support session-started events (HTTP 404); devices refresh on their own schedule');
581
+ }
582
+ }
583
+ await this.mutateStarted((latest) => { const sent = latest.get(cfg.profile_id)?.sessions.get(sid); if (sent)
584
+ sent.sent = true; });
585
+ }
586
+ }
587
+ }
395
588
  async publishProfileSnapshot(profile, profileId) {
396
- const states = this.buildStates(profile);
589
+ const states = (await this.buildStates(profile));
397
590
  try {
398
591
  await this.putSnapshot(profileId, states);
399
592
  }
@@ -439,18 +632,19 @@ export class RelayService {
439
632
  }
440
633
  current() { return this.publisher; }
441
634
  /** Python `start_talaria_relay_publisher`: resume from saved state; invalid state disables the publisher. */
442
- start() {
635
+ async start() {
443
636
  if (this.publisher)
444
637
  return true;
445
638
  let candidate = null;
446
639
  try {
447
- const config = loadRelayConfig(this.deps.stateDir);
640
+ const config = (await loadRelayConfig(this.deps.documents));
448
641
  if (!config)
449
642
  return false;
450
643
  candidate = new RelayPublisher(config, this.deps);
644
+ await candidate.initialize();
451
645
  this.candidate = candidate;
452
646
  this.adopt(candidate, null);
453
- candidate.start(false);
647
+ await candidate.start(false);
454
648
  return true;
455
649
  }
456
650
  catch (error) {
@@ -480,21 +674,50 @@ export class RelayService {
480
674
  }
481
675
  }
482
676
  /** A viewed session clears its finished runs on the relay; without a publisher there is nothing to clear. */
483
- markViewed(sid) {
677
+ async markViewed(sid) {
484
678
  const publisher = this.publisher ?? this.candidate;
485
- publisher?.markViewed(sid, this.deps.store.get(sid, { metadataOnly: true }).profile);
679
+ publisher?.markViewed(sid, (await this.deps.store.get(sid, { metadataOnly: true })).profile);
486
680
  }
487
- /**
488
- * A deleted session can never be viewed, so its finished runs are cleared once, under the profile it had (the
489
- * store no longer knows it). Later lookups of the same id do not resend.
490
- */
491
- clearDeleted(sid, profile) {
681
+ /** Persist before reporting successful deletion, even when publication is unavailable. */
682
+ async clearDeleted(sid, profile) {
492
683
  const publisher = this.publisher ?? this.candidate;
493
- if (!publisher || this.clearedDeleted.has(sid))
684
+ const name = this.deps.canonicalProfile(profile ?? 'default');
685
+ const key = JSON.stringify([name, sid]);
686
+ const live = (await hasSessionData(this.deps.store, sid));
687
+ if (!live && this.clearedDeleted.has(key))
494
688
  return;
495
- // ponytail: grows by one id per deleted session per process; reset on restart is fine because the relay ack is idempotent.
496
- this.clearedDeleted.add(sid);
497
- publisher.markViewed(sid, profile);
689
+ await mutateDeletedViews(this.deps.documents, async (views) => {
690
+ const queued = views.find((view) => view.sessionId === sid && view.profile === name);
691
+ if (queued && live) {
692
+ const through = Math.max(queued.through, Math.floor(this.deps.now() * 1000));
693
+ if (through !== queued.through) {
694
+ queued.through = through;
695
+ }
696
+ }
697
+ else if (!queued) {
698
+ let config = publisher?.config ?? null;
699
+ if (!config) {
700
+ try {
701
+ config = (await loadRelayConfig(this.deps.documents));
702
+ }
703
+ catch { /* preserve unconfigured cleanup */ }
704
+ }
705
+ const cfg = config?.profiles[name];
706
+ let identity = cfg?.identity ?? null;
707
+ if (identity === null) {
708
+ try {
709
+ identity = (await profileIdentity(this.deps, name));
710
+ }
711
+ catch { /* bind when this profile becomes available */ }
712
+ }
713
+ views.push({
714
+ id: randomUUID(), sessionId: sid, profile: name, through: Math.floor(this.deps.now() * 1000), identity,
715
+ scope: config && cfg ? { url: config.url, publisherId: config.publisher_id, profileId: cfg.profile_id } : null,
716
+ });
717
+ }
718
+ });
719
+ this.clearedDeleted.add(key);
720
+ publisher?.changed();
498
721
  }
499
722
  /** Terminal turn events reach the relay even mid-swap (Python `note_talaria_terminal`). */
500
723
  noteTerminal(streamId, phase) {
@@ -509,6 +732,7 @@ export class RelayService {
509
732
  /** Python `configure_talaria_relay_publisher`: validate with a real snapshot before swapping publishers. */
510
733
  async configure(config, validate) {
511
734
  const candidate = new RelayPublisher(config, this.deps);
735
+ await candidate.initialize();
512
736
  const previous = this.publisher;
513
737
  this.candidate = candidate;
514
738
  if (previous)
@@ -525,7 +749,7 @@ export class RelayService {
525
749
  throw new RelayPairingError(error instanceof RelayPairingError ? error.message : 'Could not publish the initial Talaria Relay snapshot', error instanceof RelayPairingError ? error.status : 502);
526
750
  }
527
751
  this.adopt(candidate, previous);
528
- candidate.start(false);
752
+ await candidate.start(false);
529
753
  candidate.changed();
530
754
  }
531
755
  /** Python `pair_talaria_relay`, serialised so concurrent enrollments preserve both mappings. */
@@ -585,12 +809,12 @@ export class RelayService {
585
809
  let identity;
586
810
  try {
587
811
  canonical = this.deps.canonicalProfile(profile);
588
- identity = profileIdentity(this.deps.profileHome(canonical));
812
+ identity = (await profileIdentity(this.deps, canonical));
589
813
  }
590
814
  catch {
591
815
  throw new RelayPairingError('Hermes profile is unavailable', 403);
592
816
  }
593
- const existing = loadRelayConfig(this.deps.stateDir);
817
+ const existing = (await loadRelayConfig(this.deps.documents));
594
818
  if (existing) {
595
819
  if (existing.url !== relayUrl || existing.publisher_id !== publisherId)
596
820
  throw new RelayPairingError('This Hermes server is registered to a different relay', 409);
@@ -598,11 +822,11 @@ export class RelayService {
598
822
  const profileId = known?.identity === identity ? known.profile_id : `prf_${randomUUID().replace(/-/g, '')}`;
599
823
  const requestBody = JSON.stringify({ invitation, publisherId, profileId });
600
824
  const path = '/v1/pairings/profile/redeem';
601
- const payload = await this.relayPost(existing.url + path, requestBody, signedHeaders(loadKey(existing.private_key_path), existing.key_id, 'POST', path, requestBody, this.deps.now), 'Talaria Relay rejected the profile enrollment');
825
+ const payload = await this.relayPost(existing.url + path, requestBody, signedHeaders((await loadKey(this.deps.documents, existing)), existing.key_id, 'POST', path, requestBody, this.deps.now), 'Talaria Relay rejected the profile enrollment');
602
826
  if (payload.protocolVersion !== 2 || payload.publisherId !== publisherId || payload.profileId !== profileId)
603
827
  throw new RelayPairingError('Talaria Relay returned an invalid response', 502);
604
828
  const config = { ...existing, profiles: { ...existing.profiles, [canonical]: { identity, profile_id: profileId } } };
605
- saveRelayConfig(this.deps.stateDir, config);
829
+ await saveRelayConfig(this.deps.documents, config);
606
830
  await this.configure(config, { profileId, identity });
607
831
  return { ok: true, publisher_id: publisherId };
608
832
  }
@@ -626,11 +850,10 @@ export class RelayService {
626
850
  catch {
627
851
  throw new RelayPairingError('Talaria Relay returned an invalid response', 502);
628
852
  }
629
- const keyPath = join(this.deps.stateDir, `talaria-relay-publisher-${createHash('sha256').update(keyId, 'utf8').digest('hex').slice(0, 16)}.pem`);
630
- mkdirSync(this.deps.stateDir, { recursive: true });
631
- atomicWriteText(keyPath, privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(), { mode: 0o600 });
632
- const config = { url: relayUrl, publisher_id: finalPublisher, key_id: keyId, private_key_path: keyPath, profiles: { [canonical]: { identity, profile_id: pairedProfile } } };
633
- saveRelayConfig(this.deps.stateDir, config);
853
+ const keyDocument = createHash('sha256').update(keyId, 'utf8').digest('hex').slice(0, 16);
854
+ await updateDocument(this.deps.documents, 'relay-keys', keyDocument, () => ({ value: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(), result: undefined }));
855
+ const config = { url: relayUrl, publisher_id: finalPublisher, key_id: keyId, private_key_path: '', private_key_document: keyDocument, profiles: { [canonical]: { identity, profile_id: pairedProfile } } };
856
+ await saveRelayConfig(this.deps.documents, config);
634
857
  await this.configure(config, { profileId: pairedProfile, identity });
635
858
  return { ok: true, publisher_id: finalPublisher };
636
859
  }