@maudecode/talaria-web 1.16.1 → 1.18.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 (445) 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 +4 -4
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +6 -0
  6. package/dist/api/automation-raw.js +59 -9
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +163 -10
  9. package/dist/api/automation-router.js +25 -8
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +700 -15
  12. package/dist/api/chat-router.js +74 -55
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +47 -2
  15. package/dist/api/git-router.js +31 -22
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +14 -0
  18. package/dist/api/raw-routes.js +277 -36
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +10 -0
  21. package/dist/api/router.js +10 -8
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/sessions-router.d.ts +1710 -73
  24. package/dist/api/sessions-router.js +164 -10
  25. package/dist/api/sessions-router.js.map +1 -1
  26. package/dist/api/settings-router.d.ts +132 -2
  27. package/dist/api/settings-router.js +15 -5
  28. package/dist/api/settings-router.js.map +1 -1
  29. package/dist/api/sse-routes.d.ts +16 -3
  30. package/dist/api/sse-routes.js +194 -67
  31. package/dist/api/sse-routes.js.map +1 -1
  32. package/dist/api/tools-raw.js +12 -25
  33. package/dist/api/tools-raw.js.map +1 -1
  34. package/dist/api/tools-router.d.ts +425 -19
  35. package/dist/api/tools-router.js +157 -26
  36. package/dist/api/tools-router.js.map +1 -1
  37. package/dist/app.d.ts +0 -9377
  38. package/dist/app.js +42 -17
  39. package/dist/app.js.map +1 -1
  40. package/dist/auth/gate.js +8 -2
  41. package/dist/auth/gate.js.map +1 -1
  42. package/dist/auth/oidc.d.ts +5 -2
  43. package/dist/auth/oidc.js +8 -7
  44. package/dist/auth/oidc.js.map +1 -1
  45. package/dist/bin/talaria-web.js +1 -1
  46. package/dist/bin/talaria-web.js.map +1 -1
  47. package/dist/cli/dotenv.js +1 -0
  48. package/dist/cli/dotenv.js.map +1 -1
  49. package/dist/config/agent-config.d.ts +2 -0
  50. package/dist/config/agent-config.js +12 -2
  51. package/dist/config/agent-config.js.map +1 -1
  52. package/dist/config.d.ts +2 -0
  53. package/dist/config.js +5 -1
  54. package/dist/config.js.map +1 -1
  55. package/dist/http/context.d.ts +20 -3
  56. package/dist/http/context.js +41 -8
  57. package/dist/http/context.js.map +1 -1
  58. package/dist/http/csp.d.ts +2 -1
  59. package/dist/http/csp.js +3 -2
  60. package/dist/http/csp.js.map +1 -1
  61. package/dist/onboarding.js +4 -0
  62. package/dist/onboarding.js.map +1 -1
  63. package/dist/profiles/profiles.d.ts +10 -0
  64. package/dist/profiles/profiles.js +40 -2
  65. package/dist/profiles/profiles.js.map +1 -1
  66. package/dist/providers/catalog.d.ts +101 -8
  67. package/dist/providers/catalog.js +463 -123
  68. package/dist/providers/catalog.js.map +1 -1
  69. package/dist/raster-image.d.ts +7 -0
  70. package/dist/raster-image.js +267 -0
  71. package/dist/raster-image.js.map +1 -0
  72. package/dist/redact.d.ts +1 -0
  73. package/dist/redact.js +354 -44
  74. package/dist/redact.js.map +1 -1
  75. package/dist/runtime.js +111 -27
  76. package/dist/runtime.js.map +1 -1
  77. package/dist/server.js +5 -0
  78. package/dist/server.js.map +1 -1
  79. package/dist/sessions/anchor.d.ts +2 -0
  80. package/dist/sessions/anchor.js +8 -3
  81. package/dist/sessions/anchor.js.map +1 -1
  82. package/dist/sessions/background-tasks.d.ts +2 -9
  83. package/dist/sessions/background-tasks.js +2 -12
  84. package/dist/sessions/background-tasks.js.map +1 -1
  85. package/dist/sessions/claude-code.d.ts +19 -0
  86. package/dist/sessions/claude-code.js +250 -0
  87. package/dist/sessions/claude-code.js.map +1 -0
  88. package/dist/sessions/cli-sessions.d.ts +15 -4
  89. package/dist/sessions/cli-sessions.js +120 -29
  90. package/dist/sessions/cli-sessions.js.map +1 -1
  91. package/dist/sessions/completions.d.ts +61 -9
  92. package/dist/sessions/completions.js +282 -34
  93. package/dist/sessions/completions.js.map +1 -1
  94. package/dist/sessions/compress.d.ts +14 -1
  95. package/dist/sessions/compress.js +52 -11
  96. package/dist/sessions/compress.js.map +1 -1
  97. package/dist/sessions/export.d.ts +5 -0
  98. package/dist/sessions/export.js +30 -0
  99. package/dist/sessions/export.js.map +1 -1
  100. package/dist/sessions/gateway-watcher.d.ts +7 -3
  101. package/dist/sessions/gateway-watcher.js +31 -19
  102. package/dist/sessions/gateway-watcher.js.map +1 -1
  103. package/dist/sessions/handoff.d.ts +32 -0
  104. package/dist/sessions/handoff.js +114 -0
  105. package/dist/sessions/handoff.js.map +1 -0
  106. package/dist/sessions/journal.d.ts +8 -0
  107. package/dist/sessions/journal.js +44 -10
  108. package/dist/sessions/journal.js.map +1 -1
  109. package/dist/sessions/list.d.ts +13 -3
  110. package/dist/sessions/list.js +46 -15
  111. package/dist/sessions/list.js.map +1 -1
  112. package/dist/sessions/merge.d.ts +53 -5
  113. package/dist/sessions/merge.js +219 -23
  114. package/dist/sessions/merge.js.map +1 -1
  115. package/dist/sessions/recovery.d.ts +22 -0
  116. package/dist/sessions/recovery.js +506 -0
  117. package/dist/sessions/recovery.js.map +1 -0
  118. package/dist/sessions/relay.d.ts +21 -2
  119. package/dist/sessions/relay.js +156 -11
  120. package/dist/sessions/relay.js.map +1 -1
  121. package/dist/sessions/service.d.ts +152 -8
  122. package/dist/sessions/service.js +640 -72
  123. package/dist/sessions/service.js.map +1 -1
  124. package/dist/sessions/session.d.ts +17 -1
  125. package/dist/sessions/session.js +63 -4
  126. package/dist/sessions/session.js.map +1 -1
  127. package/dist/sessions/state-db-worker.d.ts +1 -0
  128. package/dist/sessions/state-db-worker.js +33 -0
  129. package/dist/sessions/state-db-worker.js.map +1 -0
  130. package/dist/sessions/state-db.d.ts +87 -4
  131. package/dist/sessions/state-db.js +417 -8
  132. package/dist/sessions/state-db.js.map +1 -1
  133. package/dist/sessions/store.d.ts +7 -0
  134. package/dist/sessions/store.js +36 -22
  135. package/dist/sessions/store.js.map +1 -1
  136. package/dist/sessions/streams.d.ts +10 -0
  137. package/dist/sessions/streams.js +14 -3
  138. package/dist/sessions/streams.js.map +1 -1
  139. package/dist/sessions/titles.d.ts +2 -0
  140. package/dist/sessions/titles.js +4 -0
  141. package/dist/sessions/titles.js.map +1 -1
  142. package/dist/sessions/todo.d.ts +6 -1
  143. package/dist/sessions/todo.js +11 -4
  144. package/dist/sessions/todo.js.map +1 -1
  145. package/dist/sessions/tool-display.d.ts +12 -1
  146. package/dist/sessions/tool-display.js +50 -1
  147. package/dist/sessions/tool-display.js.map +1 -1
  148. package/dist/sessions/turn-context.d.ts +1 -1
  149. package/dist/sessions/turn-context.js +11 -7
  150. package/dist/sessions/turn-context.js.map +1 -1
  151. package/dist/sessions/turn.d.ts +67 -11
  152. package/dist/sessions/turn.js +413 -111
  153. package/dist/sessions/turn.js.map +1 -1
  154. package/dist/sessions/wakeup-pause.d.ts +59 -0
  155. package/dist/sessions/wakeup-pause.js +181 -0
  156. package/dist/sessions/wakeup-pause.js.map +1 -0
  157. package/dist/settings.d.ts +20 -2
  158. package/dist/settings.js +45 -9
  159. package/dist/settings.js.map +1 -1
  160. package/dist/sidecar/fake.d.ts +4 -4
  161. package/dist/sidecar/fake.js +5 -2
  162. package/dist/sidecar/fake.js.map +1 -1
  163. package/dist/spa.js +2 -1
  164. package/dist/spa.js.map +1 -1
  165. package/dist/startup.d.ts +13 -5
  166. package/dist/startup.js +48 -0
  167. package/dist/startup.js.map +1 -1
  168. package/dist/test/harness.js +2 -0
  169. package/dist/test/harness.js.map +1 -1
  170. package/dist/test/ts-worker-hooks.d.ts +1 -0
  171. package/dist/test/ts-worker-hooks.js +24 -0
  172. package/dist/test/ts-worker-hooks.js.map +1 -0
  173. package/dist/text/diff.d.ts +9 -14
  174. package/dist/text/diff.js +74 -108
  175. package/dist/text/diff.js.map +1 -1
  176. package/dist/tools/crons.js +45 -8
  177. package/dist/tools/crons.js.map +1 -1
  178. package/dist/tools/dashboard-plugins.d.ts +53 -0
  179. package/dist/tools/dashboard-plugins.js +188 -0
  180. package/dist/tools/dashboard-plugins.js.map +1 -0
  181. package/dist/tools/extensions.d.ts +13 -1
  182. package/dist/tools/extensions.js +132 -60
  183. package/dist/tools/extensions.js.map +1 -1
  184. package/dist/tools/health.d.ts +32 -0
  185. package/dist/tools/health.js +111 -19
  186. package/dist/tools/health.js.map +1 -1
  187. package/dist/tools/insights.d.ts +8 -2
  188. package/dist/tools/insights.js +18 -2
  189. package/dist/tools/insights.js.map +1 -1
  190. package/dist/tools/kanban.d.ts +41 -1
  191. package/dist/tools/kanban.js +113 -2
  192. package/dist/tools/kanban.js.map +1 -1
  193. package/dist/tools/mcp.d.ts +15 -1
  194. package/dist/tools/mcp.js +99 -5
  195. package/dist/tools/mcp.js.map +1 -1
  196. package/dist/tools/project-os.d.ts +11 -0
  197. package/dist/tools/project-os.js +267 -0
  198. package/dist/tools/project-os.js.map +1 -0
  199. package/dist/tools/skills.d.ts +5 -1
  200. package/dist/tools/skills.js +5 -3
  201. package/dist/tools/skills.js.map +1 -1
  202. package/dist/tools/terminal.d.ts +1 -0
  203. package/dist/tools/terminal.js +1 -1
  204. package/dist/tools/terminal.js.map +1 -1
  205. package/dist/tools/updates.d.ts +33 -8
  206. package/dist/tools/updates.js +236 -57
  207. package/dist/tools/updates.js.map +1 -1
  208. package/dist/tools/wiki.d.ts +68 -0
  209. package/dist/tools/wiki.js +413 -0
  210. package/dist/tools/wiki.js.map +1 -0
  211. package/dist/workspace/escape.d.ts +61 -0
  212. package/dist/workspace/escape.js +149 -0
  213. package/dist/workspace/escape.js.map +1 -0
  214. package/dist/workspace/extract.d.ts +22 -0
  215. package/dist/workspace/extract.js +308 -0
  216. package/dist/workspace/extract.js.map +1 -0
  217. package/dist/workspace/fs.d.ts +19 -3
  218. package/dist/workspace/fs.js +96 -23
  219. package/dist/workspace/fs.js.map +1 -1
  220. package/dist/workspace/git.d.ts +18 -0
  221. package/dist/workspace/git.js +38 -25
  222. package/dist/workspace/git.js.map +1 -1
  223. package/dist/workspace/media.d.ts +4 -4
  224. package/dist/workspace/media.js +18 -45
  225. package/dist/workspace/media.js.map +1 -1
  226. package/dist/workspace/preview.d.ts +28 -0
  227. package/dist/workspace/preview.js +110 -0
  228. package/dist/workspace/preview.js.map +1 -0
  229. package/dist/workspace/rollback.d.ts +36 -21
  230. package/dist/workspace/rollback.js +179 -270
  231. package/dist/workspace/rollback.js.map +1 -1
  232. package/dist/workspace/unzip.js +4 -1
  233. package/dist/workspace/unzip.js.map +1 -1
  234. package/dist/workspace/upload.d.ts +9 -4
  235. package/dist/workspace/upload.js +9 -3
  236. package/dist/workspace/upload.js.map +1 -1
  237. package/dist/workspace/worktrees.d.ts +2 -1
  238. package/dist/workspace/worktrees.js +14 -15
  239. package/dist/workspace/worktrees.js.map +1 -1
  240. package/package.json +4 -2
  241. package/sidecar/agent_dependency.json +4 -4
  242. package/sidecar/scripts/replay_sidecar.py +25 -1
  243. package/sidecar/talaria_sidecar/__init__.py +1 -1
  244. package/sidecar/talaria_sidecar/__main__.py +7 -1
  245. package/sidecar/talaria_sidecar/home.py +52 -2
  246. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  247. package/sidecar/talaria_sidecar/methods/aux.py +39 -5
  248. package/sidecar/talaria_sidecar/methods/chat.py +431 -43
  249. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  250. package/sidecar/talaria_sidecar/methods/commands.py +96 -2
  251. package/sidecar/talaria_sidecar/methods/cron.py +39 -8
  252. package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
  253. package/sidecar/talaria_sidecar/methods/kanban.py +140 -19
  254. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  255. package/sidecar/talaria_sidecar/methods/process.py +115 -2
  256. package/sidecar/talaria_sidecar/methods/profiles.py +84 -8
  257. package/sidecar/talaria_sidecar/methods/providers.py +27 -8
  258. package/sidecar/talaria_sidecar/methods/runtime.py +3 -0
  259. package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
  260. package/sidecar/talaria_sidecar/methods/text.py +17 -1
  261. package/sidecar/talaria_sidecar/methods/usage.py +257 -4
  262. package/sidecar/talaria_sidecar/runtime.py +17 -1
  263. package/static/dist/FILES.txt +127 -125
  264. package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
  265. package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
  266. package/static/dist/assets/Dialog-g355qxhp.js +1 -0
  267. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
  268. package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
  269. package/static/dist/assets/{Markdown-jqrx5es0.js → Markdown-j4kut4j2.js} +125 -125
  270. package/static/dist/assets/RightPanel-ohlsdzfl.js +1 -0
  271. package/static/dist/assets/Select-kddq75qc.js +1 -0
  272. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
  273. package/static/dist/assets/SettingsLayout-o9bv4mgb.js +1 -0
  274. package/static/dist/assets/States-eq6fe29m.js +1 -0
  275. package/static/dist/assets/Toaster-pc247u9f.js +1 -0
  276. package/static/dist/assets/_app-f8n1i0m1.js +1 -0
  277. package/static/dist/assets/_app.ext._extensionId-gfbzpoou.js +1 -0
  278. package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
  279. package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
  280. package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
  281. package/static/dist/assets/_app.logs-i69qds98.js +1 -0
  282. package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
  283. package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
  284. package/static/dist/assets/{_app.session._sessionId-hh27wwu2.js → _app.session._sessionId-fn1bhde6.js} +1 -1
  285. package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
  286. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
  287. package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
  288. package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
  289. package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
  290. package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
  291. package/static/dist/assets/{abnfDiagram-VCTEODGH-oity9y4o.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
  292. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
  293. package/static/dist/assets/{architectureDiagram-5GKGNRK7-ceaepnyr.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
  294. package/static/dist/assets/{back-eu64w89w.js → back-h7aun7ew.js} +1 -1
  295. package/static/dist/assets/{blockDiagram-I7D4REHJ-n79st4pj.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
  296. package/static/dist/assets/{c4Diagram-7LVT6UL2-mi73akzk.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
  297. package/static/dist/assets/{cancel-mzvkdjg8.js → cancel-fhi0vcnu.js} +1 -1
  298. package/static/dist/assets/channel-b2zo73z1.js +1 -0
  299. package/static/dist/assets/{chunk-4HAMMTFA-laat0g1q.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
  300. package/static/dist/assets/{chunk-75Z2AOVW-0psy1tr2.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
  301. package/static/dist/assets/{chunk-DU6HZSFF-lw8uox8x.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
  302. package/static/dist/assets/{chunk-F27PBJKO-erassib3.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
  303. package/static/dist/assets/{chunk-GMAD6QVW-fz7rx619.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
  304. package/static/dist/assets/{chunk-GVQU2GXP-gdb0epb2.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
  305. package/static/dist/assets/{chunk-IMKFNOWR-ea8t26qw.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
  306. package/static/dist/assets/{chunk-L3NEJ4N5-mb1gu9kz.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
  307. package/static/dist/assets/{chunk-OSK3NFVY-eovmccvy.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
  308. package/static/dist/assets/{chunk-P2QGCYS3-bo55zjo5.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
  309. package/static/dist/assets/{chunk-POPQ4Y6H-ja7ohe4h.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
  310. package/static/dist/assets/{chunk-PWAF6VOD-e30g9nus.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
  311. package/static/dist/assets/{chunk-SHT3W25Y-gbb798f7.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
  312. package/static/dist/assets/{chunk-SVP7TREG-h2iw8urm.js → chunk-SVP7TREG-dnau702j.js} +1 -1
  313. package/static/dist/assets/{chunk-TICWLB2K-fd2cottz.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
  314. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
  315. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
  316. package/static/dist/assets/{copied-h65fc3cx.js → copied-hcplv46p.js} +1 -1
  317. package/static/dist/assets/copy-magtofyp.js +1 -0
  318. package/static/dist/assets/{cynefin-OW5HDTMX-lbltyl5j.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
  319. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-lxhsqq4a.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
  320. package/static/dist/assets/{dagre-GXQ25YYZ-gn4nnaln.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
  321. package/static/dist/assets/{diagram-S7CK7UJ4-pa3ij6q7.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
  322. package/static/dist/assets/{diagram-UQ7AKVKN-zbipyz57.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
  323. package/static/dist/assets/{diagram-VSXAHHWV-d1kbpzgf.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
  324. package/static/dist/assets/{diagram-VX7I27RA-i3ty14xl.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
  325. package/static/dist/assets/{diagram-Z3DM3KII-m8xefqa7.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
  326. package/static/dist/assets/{ebnfDiagram-PWID7BFC-nz7wj05l.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
  327. package/static/dist/assets/endpoints-klbxod4t.js +64 -0
  328. package/static/dist/assets/{erDiagram-RLTQ6QDP-fa41ib83.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
  329. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
  330. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
  331. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-n7zq01qu.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
  332. package/static/dist/assets/{gitGraph-4MIJSDKK-c89emb07.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
  333. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-cz7cgr6w.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
  334. package/static/dist/assets/{highlighted-body-KPVGNVTW-d9bapmni.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
  335. package/static/dist/assets/host-i828xuo7.js +1 -0
  336. package/static/dist/assets/index-eh8zilnv.css +2 -0
  337. package/static/dist/assets/index-r648f4o1.js +11 -0
  338. package/static/dist/assets/{info-A6RAGUB7-ndddhhhh.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
  339. package/static/dist/assets/{infoDiagram-27XIBGKW-ccxigcpf.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
  340. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-le0z9vtc.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
  341. package/static/dist/assets/{journeyDiagram-3NMN7TZE-ivsmb26o.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
  342. package/static/dist/assets/{kanban-definition-UXKFOSKX-h6n2awt0.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
  343. package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +1 -0
  344. package/static/dist/assets/{line-dmzlfe0u.js → line-meizqsvn.js} +1 -1
  345. package/static/dist/assets/link-oot2yn73.js +1 -0
  346. package/static/dist/assets/login-gu28l9ou.js +1 -0
  347. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
  348. package/static/dist/assets/{mermaid-parser.core-gwyd5bnq.js → mermaid-parser.core-ntne45i9.js} +3 -3
  349. package/static/dist/assets/{mindmap-definition-YA3MSWOX-l68gg5h7.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
  350. package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
  351. package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +1 -0
  352. package/static/dist/assets/{packet-AYTQ26CC-gpv06065.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
  353. package/static/dist/assets/{pegDiagram-XKGWAZYB-e52vs23u.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
  354. package/static/dist/assets/{pie-WAS4IAKB-l6m7nz5s.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
  355. package/static/dist/assets/{pieDiagram-E7YTZNPT-b4is36bq.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
  356. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-m8vt2788.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
  357. package/static/dist/assets/{radar-RG4KPBEZ-nxbrhiov.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
  358. package/static/dist/assets/{railroad-74A4TZTK-222mu9hi.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
  359. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
  360. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
  361. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
  362. package/static/dist/assets/{railroadDiagram-O6MQD6OU-o8lldfzv.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
  363. package/static/dist/assets/refresh-abtu93ke.js +1 -0
  364. package/static/dist/assets/refresh-cw-hlwb02qv.js +1 -0
  365. package/static/dist/assets/{requirementDiagram-BXWQKSXE-ovyq38kv.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
  366. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-ou7lznor.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
  367. package/static/dist/assets/{saved-bazmcup9.js → saved-girnabkf.js} +1 -1
  368. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-ovckdzf2.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
  369. package/static/dist/assets/{settings_section_extensions_title-ertwwhlx.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
  370. package/static/dist/assets/share._token-kl0r7ido.js +2 -0
  371. package/static/dist/assets/sliders-horizontal-n1zm34qq.js +1 -0
  372. package/static/dist/assets/sse-iwku0sjo.js +1 -0
  373. package/static/dist/assets/{stateDiagram-D77RDMKH-fcrexkkv.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
  374. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
  375. package/static/dist/assets/{swimlanes-42K2YHIH-f7usu8fw.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
  376. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
  377. package/static/dist/assets/{timeline-definition-24CTP7MA-gp1hvz9f.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
  378. package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
  379. package/static/dist/assets/{treeView-Q6P3EWNA-gb9gf11u.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
  380. package/static/dist/assets/{treemap-WGGIJYW6-nuvfnvvi.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
  381. package/static/dist/assets/units-kgxzxfhi.js +1 -0
  382. package/static/dist/assets/{untitled-htelhc68.js → untitled-cikfjntn.js} +1 -1
  383. package/static/dist/assets/{useForm-h2t0u14e.js → useForm-kqoqg0k9.js} +1 -1
  384. package/static/dist/assets/useLocale-o59tbiea.js +1 -0
  385. package/static/dist/assets/{useParams-dqd64jv7.js → useParams-b4dq6n4q.js} +1 -1
  386. package/static/dist/assets/{useSearch-j30pvsy7.js → useSearch-ipfmv3he.js} +1 -1
  387. package/static/dist/assets/{vennDiagram-4TSXK5OY-fzchbzaj.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
  388. package/static/dist/assets/{wardley-WFR3VGLG-m47mhfbh.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
  389. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-odg6ct1z.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
  390. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mrhms5um.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
  391. package/static/dist/index.html +1 -1
  392. package/static/dist/sw.js +2 -2
  393. package/static/dist/assets/AppShell-ef3bhu32.js +0 -1
  394. package/static/dist/assets/Button-nx37f4u1.js +0 -1
  395. package/static/dist/assets/ChatPage-cdcpykcg.js +0 -25
  396. package/static/dist/assets/Dialog-mec42hvb.js +0 -1
  397. package/static/dist/assets/HubRoute-ecb65zpr.js +0 -2
  398. package/static/dist/assets/RightPanel-d1lgvk4s.js +0 -1
  399. package/static/dist/assets/Select-h4ivl9qf.js +0 -1
  400. package/static/dist/assets/SessionListPanel-ewib046b.js +0 -1
  401. package/static/dist/assets/SettingsLayout-lx654cup.js +0 -1
  402. package/static/dist/assets/States-ehsb6lzo.js +0 -1
  403. package/static/dist/assets/Toaster-mch1pa3t.js +0 -1
  404. package/static/dist/assets/_app-m8d961ef.js +0 -1
  405. package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +0 -1
  406. package/static/dist/assets/_app.index-dk6zp7e0.js +0 -1
  407. package/static/dist/assets/_app.insights-jt2fk4i1.js +0 -1
  408. package/static/dist/assets/_app.kanban-if4pubeu.js +0 -1
  409. package/static/dist/assets/_app.logs-mfaw9m6g.js +0 -1
  410. package/static/dist/assets/_app.memory-h3eaarb6.js +0 -1
  411. package/static/dist/assets/_app.profiles-ienktovs.js +0 -1
  412. package/static/dist/assets/_app.settings-dpoe0bzf.js +0 -1
  413. package/static/dist/assets/_app.settings._section-l468ynag.js +0 -3
  414. package/static/dist/assets/_app.skills-kcbncc8i.js +0 -1
  415. package/static/dist/assets/_app.tasks-occdrqhw.js +0 -2
  416. package/static/dist/assets/_app.todos-nnke2rcc.js +0 -1
  417. package/static/dist/assets/_app.workspaces-cdlatvwj.js +0 -1
  418. package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +0 -1
  419. package/static/dist/assets/channel-mbvmf2vv.js +0 -1
  420. package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +0 -1
  421. package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +0 -1
  422. package/static/dist/assets/copy-e6977uwq.js +0 -1
  423. package/static/dist/assets/endpoints-k7yf25m0.js +0 -64
  424. package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +0 -1
  425. package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +0 -1
  426. package/static/dist/assets/index-h00usru2.js +0 -11
  427. package/static/dist/assets/index-n73ijkv2.css +0 -2
  428. package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +0 -1
  429. package/static/dist/assets/link-budeg5t9.js +0 -1
  430. package/static/dist/assets/login-kjhasvp8.js +0 -1
  431. package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
  432. package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +0 -1
  433. package/static/dist/assets/onboarding-cdfrk0pj.js +0 -1
  434. package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +0 -1
  435. package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +0 -1
  436. package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +0 -1
  437. package/static/dist/assets/refresh-cw-kbcje1uy.js +0 -1
  438. package/static/dist/assets/search-jen74z4u.js +0 -1
  439. package/static/dist/assets/share._token-dan9hgvj.js +0 -2
  440. package/static/dist/assets/sliders-horizontal-f9cb3lh3.js +0 -1
  441. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +0 -1
  442. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +0 -8
  443. package/static/dist/assets/todoStore-eqtid4r4.js +0 -1
  444. package/static/dist/assets/useLocale-hcsb84pj.js +0 -1
  445. package/static/dist/assets/useNavigate-nwy70ztj.js +0 -1
@@ -1,31 +1,43 @@
1
1
  import type { SidecarLike } from '../sidecar/client.js';
2
2
  import type { SessionChannels, StreamRegistry } from './streams.js';
3
- import type { SessionStore } from './store.js';
3
+ import { type SessionStore } from './store.js';
4
4
  import type { Session } from './session.js';
5
5
  import type { BackgroundActivity } from './background-tasks.js';
6
6
  type Dict = Record<string, unknown>;
7
7
  export declare const COMPLETION_POLL_MS = 1000;
8
+ interface StartedTurn {
9
+ _status?: number;
10
+ error?: string;
11
+ stream_id?: string;
12
+ retryable?: boolean;
13
+ }
8
14
  export interface CompletionDrainDeps {
9
15
  sidecar: () => SidecarLike | null;
16
+ /** The base Hermes home: every profile under it has its background processes recovered in a new sidecar (TAL-533). */
17
+ baseHome: string;
10
18
  profileHome: (profile: string | null) => string;
11
19
  activeProfile: () => string;
12
20
  store: SessionStore;
13
21
  channels: SessionChannels;
14
22
  registry: StreamRegistry;
15
- /** Start a server-side turn; `_status` 409 means a turn is already active. */
16
- startTurn: (session: Session, prompt: string) => {
17
- _status?: number;
18
- error?: string;
19
- stream_id?: string;
20
- };
23
+ /** Start a server-side turn; `_status` 409 means a turn is already active, or with `retryable` that the Agent runtime is stale. */
24
+ startTurn: (session: Session, prompt: string) => StartedTurn | Promise<StartedTurn>;
21
25
  now: () => number;
22
26
  log: (line: string) => void;
23
27
  pollMs?: number;
28
+ /** TAL-576: the profile's authoritative config file (`AgentConfig.path`); `<home>/config.yaml` when absent. */
29
+ configPath?: (profileHome: string) => string;
24
30
  /** TAL-372: each routed completion settles its background record. */
25
31
  background?: Pick<BackgroundActivity, 'recordEvent'>;
26
32
  }
27
33
  /** Python `format_wakeup_prompt` for plain completion events; async delegations use the sidecar formatter. */
28
34
  export declare function formatWakeupPrompt(evt: Dict): string | null;
35
+ /**
36
+ * TAL-724: an event's delivery identity, the `process_id` of its wakeup entry. A completion (an event with no type is one) or
37
+ * an async delegation is its process: one exit can be enqueued twice when a kill races the reader thread. The Agent enqueues
38
+ * each watch event once, and one process raises several, so each is its own delivery; its event keeps the process id.
39
+ */
40
+ export declare function deliveryKey(evt: Dict, processId: string): string;
29
41
  export declare class CompletionDrain {
30
42
  private readonly deps;
31
43
  private timer;
@@ -37,6 +49,16 @@ export declare class CompletionDrain {
37
49
  private readonly retryTimers;
38
50
  private readonly lastEmitAt;
39
51
  private readonly pendingEmit;
52
+ /** The handshake of the sidecar whose process registry already recovered every profile's checkpoint. */
53
+ private recoveredFor;
54
+ private recoveryWarnedFor;
55
+ private lastDrainError;
56
+ private heldResumed;
57
+ private readonly drains;
58
+ /** Admitted wakeups whose release could not be written: never delivered again, and the release is retried. */
59
+ private readonly unreleased;
60
+ /** TAL-576: wakeups held while their session is paused. */
61
+ private readonly heldWakeups;
40
62
  constructor(deps: CompletionDrainDeps);
41
63
  start(): void;
42
64
  stop(): void;
@@ -52,7 +74,10 @@ export declare class CompletionDrain {
52
74
  /** Python `_emit_bg_task_complete_events_coalesced`: first emit immediately, bursts flush the latest payload after 1 s of quiet. */
53
75
  private emitCoalesced;
54
76
  recordDeferred(sid: string, processId: string, prompt: string, event?: Dict): void;
77
+ /** Re-deferring keeps an entry's delivery identity, so its held copy and this one stay one wakeup. */
78
+ private defer;
55
79
  deferredCount(sid: string): number;
80
+ /** Delivering a process's exit consumes it; a watch event leaves the exit to be delivered (TAL-724). */
56
81
  private markConsumed;
57
82
  /**
58
83
  * TAL-459: claim each async delegation in the Agent's durable ledger right before delivering it. A refused claim means
@@ -66,12 +91,39 @@ export declare class CompletionDrain {
66
91
  * lease bounds a lost hand-back.
67
92
  */
68
93
  private settleClaims;
69
- private profileHome;
70
94
  private scheduleRetry;
95
+ private scheduleDrain;
96
+ /** TAL-576: `usage.pool` for the provider whose pool ran dry, through `poolRecovery`; null when the sidecar cannot say. */
97
+ private poolRecovery;
98
+ /** A restarted server resumes every session it left holding wakeups (`startWakeup` drops a deleted session's). */
99
+ private resumeHeld;
100
+ private held;
101
+ /** A session whose file is gone was deleted; any other load failure keeps its wakeups. */
102
+ private deleted;
103
+ /** Admitted or settled wakeups (every one, with none given) leave the held file. */
104
+ private release;
105
+ private saveSession;
106
+ /**
107
+ * TAL-576: while the provider has no usable credentials no wakeup turn starts. The entries are held until a turn
108
+ * admits them, and a timer rechecks the pause at the pool's earliest retry deadline, at least every five minutes.
109
+ * Returns whether the wakeup is handled here (held, or dropped with its deleted session).
110
+ */
111
+ private holdWhilePaused;
71
112
  /** Python `_start_server_side_wakeup_turn`: one batched turn; 409 re-defers, other failures re-defer with a timed retry. */
72
113
  private startWakeup;
73
- /** Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires. */
114
+ /**
115
+ * TAL-532: the deferred process completions the agent already holds from its own turn (wait/log or poll). Checked at
116
+ * delivery, not drain: the completion is usually drained seconds before the agent's wait returns. Only completions:
117
+ * consuming the exit says nothing about a watch match or notice, and async delegations settle in their delivery ledger.
118
+ * A failed check delivers everything rather than lose a result.
119
+ */
120
+ private alreadyConsumed;
121
+ /**
122
+ * Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires.
123
+ * TAL-576: one drain per session at a time, so two never copy the same held wakeups before either admits them.
124
+ */
74
125
  drainDeferred(sid: string): Promise<number>;
126
+ private drainDeferredNow;
75
127
  /** Reaper sweep: forget dedupe sets for sessions with nothing pending. */
76
128
  sweep(): void;
77
129
  }
@@ -7,13 +7,20 @@
7
7
  * redelivered at turn teardown with a bounded retry budget).
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
+ import { existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { SessionNotFound } from './store.js';
10
13
  import { str } from '../util.js';
14
+ import { isDict } from './merge.js';
11
15
  import { batchUpdate, recordBackgroundUpdate } from './background-updates.js';
16
+ import { HeldWakeups, deliveredPerProcess, heldKey, mergeWakeups, poolRecovery, wakeupPaused } from './wakeup-pause.js';
12
17
  export const COMPLETION_POLL_MS = 1000;
13
18
  const EMIT_COALESCE_WINDOW_S = 1;
14
19
  const WAKEUP_RETRY_SECONDS = 30;
15
20
  const WAKEUP_RETRY_MAX_ATTEMPTS = 5;
16
21
  const WAKEUP_BATCH_MAX_CHARS = 24_000;
22
+ /** A session with held wakeups rechecks its pause at least this often (a credential edit has no deadline). */
23
+ const HELD_RECHECK_SECONDS = 300;
17
24
  const DELIVERY_CONSUMER = 'webui';
18
25
  /** Python `format_wakeup_prompt` for plain completion events; async delegations use the sidecar formatter. */
19
26
  export function formatWakeupPrompt(evt) {
@@ -37,6 +44,16 @@ export function formatWakeupPrompt(evt) {
37
44
  return null;
38
45
  return `[IMPORTANT: Background process ${sid} completed (exit_code=${str(evt.exit_code ?? '?')}).\nCommand: ${cmd}\nOutput:\n${truncate(evt.output, 4000)}]`;
39
46
  }
47
+ /**
48
+ * TAL-724: an event's delivery identity, the `process_id` of its wakeup entry. A completion (an event with no type is one) or
49
+ * an async delegation is its process: one exit can be enqueued twice when a kill races the reader thread. The Agent enqueues
50
+ * each watch event once, and one process raises several, so each is its own delivery; its event keeps the process id.
51
+ */
52
+ export function deliveryKey(evt, processId) {
53
+ return !processId || deliveredPerProcess(evt) ? processId : `${processId}:${str(evt.type)}:${randomUUID()}`;
54
+ }
55
+ /** Only a process's exit is consumed by the agent's own wait or poll (TAL-532); its watch events and delegations are not. */
56
+ const isCompletion = (e) => Boolean(e.process_id) && str(e.event?.type ?? 'completion') === 'completion';
40
57
  export class CompletionDrain {
41
58
  deps;
42
59
  timer = null;
@@ -48,8 +65,19 @@ export class CompletionDrain {
48
65
  retryTimers = new Map();
49
66
  lastEmitAt = new Map();
50
67
  pendingEmit = new Map();
68
+ /** The handshake of the sidecar whose process registry already recovered every profile's checkpoint. */
69
+ recoveredFor = null;
70
+ recoveryWarnedFor = null;
71
+ lastDrainError = null;
72
+ heldResumed = false;
73
+ drains = new Map();
74
+ /** Admitted wakeups whose release could not be written: never delivered again, and the release is retried. */
75
+ unreleased = new Map();
76
+ /** TAL-576: wakeups held while their session is paused. */
77
+ heldWakeups;
51
78
  constructor(deps) {
52
79
  this.deps = deps;
80
+ this.heldWakeups = new HeldWakeups(join(deps.store.sessionDir, '_wakeup_held.json'));
53
81
  }
54
82
  start() {
55
83
  if (this.timer || this.stopped)
@@ -57,7 +85,13 @@ export class CompletionDrain {
57
85
  const tick = () => {
58
86
  if (this.stopped)
59
87
  return;
60
- void this.drainOnce().catch((error) => { this.deps.log(`[webui] WARNING: bg_task_complete drain failed: ${error.message}`); }).finally(() => {
88
+ // A lasting failure (an unrecoverable checkpoint fails every call in its home, TAL-533) is logged once, not every poll.
89
+ void this.drainOnce().then(() => { this.lastDrainError = null; }, (error) => {
90
+ const message = error.message;
91
+ if (message !== this.lastDrainError)
92
+ this.deps.log(`[webui] WARNING: bg_task_complete drain failed: ${message}`);
93
+ this.lastDrainError = message;
94
+ }).finally(() => {
61
95
  if (this.stopped)
62
96
  return;
63
97
  this.timer = setTimeout(tick, this.deps.pollMs ?? COMPLETION_POLL_MS);
@@ -82,7 +116,34 @@ export class CompletionDrain {
82
116
  const sidecar = this.deps.sidecar();
83
117
  if (!sidecar)
84
118
  return 0;
85
- const { events } = await sidecar.call('process.drain', { profile_home: this.deps.profileHome(this.deps.activeProfile()), max_events: 256 });
119
+ // Each sidecar handshake is a fresh process registry: re-adopt every profile's checkpointed processes before the first
120
+ // drain, so one that exits in a profile nobody has used since the restart still reports its completion.
121
+ const describe = sidecar.describe;
122
+ if (describe && describe !== this.recoveredFor) {
123
+ try {
124
+ await sidecar.call('process.recover', { base_home: this.deps.baseHome });
125
+ this.recoveredFor = describe;
126
+ }
127
+ catch (error) {
128
+ // Retried on every pass; reported once per sidecar so a lasting failure does not flood the log.
129
+ if (this.recoveryWarnedFor !== describe)
130
+ this.deps.log(`[webui] WARNING: background process recovery failed; retrying: ${error.message}`);
131
+ this.recoveryWarnedFor = describe;
132
+ }
133
+ }
134
+ // TAL-576: whether or not every profile recovered, the sessions left holding wakeups resume once a sidecar answers.
135
+ if (!this.heldResumed)
136
+ this.resumeHeld();
137
+ // The drain is sidecar-wide; an active profile whose home escapes the profiles root (`profileHome` throws) scopes it
138
+ // to the base home instead of stalling every other profile's completions.
139
+ let scope;
140
+ try {
141
+ scope = this.deps.profileHome(this.deps.activeProfile());
142
+ }
143
+ catch {
144
+ scope = this.deps.baseHome;
145
+ }
146
+ const { events } = await sidecar.call('process.drain', { profile_home: scope, max_events: 256 });
86
147
  let routed = 0;
87
148
  const unrouted = [];
88
149
  for (const evt of events) {
@@ -154,20 +215,23 @@ export class CompletionDrain {
154
215
  return true;
155
216
  const seen = this.seen.get(sid) ?? new Set();
156
217
  this.seen.set(sid, seen);
157
- if (processId && seen.has(processId))
158
- return true;
159
- if (processId)
218
+ // Only a per-process delivery can repeat: a watch event's key is unique, so remembering it would only grow `seen`.
219
+ if (processId && deliveredPerProcess(evt)) {
220
+ if (seen.has(processId))
221
+ return true;
160
222
  seen.add(processId);
223
+ }
161
224
  this.pendingSessions.add(sid);
162
225
  const prompt = await this.wakeupPrompt(evt);
163
226
  this.deps.background?.recordEvent(sid, evt, prompt);
164
227
  this.emitCoalesced(sid, this.buildPayload(evt, sid, prompt));
165
228
  if (!prompt)
166
229
  return true;
230
+ const entry = { process_id: deliveryKey(evt, processId), wakeup_prompt: prompt, event: evt };
167
231
  if (this.hasActiveTurn(sid))
168
- this.recordDeferred(sid, processId, prompt, evt);
232
+ this.defer(sid, entry);
169
233
  else
170
- await this.startWakeup(sid, [{ process_id: processId, wakeup_prompt: prompt, event: evt }]);
234
+ await this.startWakeup(sid, [entry]);
171
235
  return true;
172
236
  }
173
237
  buildPayload(evt, sid, prompt) {
@@ -218,21 +282,26 @@ export class CompletionDrain {
218
282
  this.pendingEmit.set(sid, { payload, timer });
219
283
  }
220
284
  recordDeferred(sid, processId, prompt, event) {
221
- if (!sid || !prompt)
285
+ this.defer(sid, { process_id: deliveryKey(event ?? {}, processId), wakeup_prompt: prompt, ...(event ? { event } : {}) });
286
+ }
287
+ /** Re-deferring keeps an entry's delivery identity, so its held copy and this one stay one wakeup. */
288
+ defer(sid, entry) {
289
+ if (!sid || !entry.wakeup_prompt)
222
290
  return;
223
291
  const entries = this.deferred.get(sid) ?? [];
224
- if (processId && entries.some((e) => e.process_id === processId))
292
+ if (entries.some((e) => heldKey(e) === heldKey(entry)))
225
293
  return;
226
- entries.push({ process_id: processId, wakeup_prompt: prompt, ...(event ? { event } : {}) });
294
+ entries.push(entry);
227
295
  this.deferred.set(sid, entries);
228
296
  this.pendingSessions.add(sid);
229
297
  }
230
298
  deferredCount(sid) { return this.deferred.get(sid)?.length ?? 0; }
231
- async markConsumed(processIds) {
299
+ /** Delivering a process's exit consumes it; a watch event leaves the exit to be delivered (TAL-724). */
300
+ async markConsumed(entries) {
232
301
  const sidecar = this.deps.sidecar();
233
302
  if (!sidecar)
234
303
  return;
235
- for (const pid of processIds.filter(Boolean)) {
304
+ for (const pid of new Set(entries.filter(isCompletion).map((e) => e.process_id))) {
236
305
  try {
237
306
  await sidecar.call('process.mark_consumed', { process_id: pid });
238
307
  }
@@ -244,7 +313,7 @@ export class CompletionDrain {
244
313
  * another consumer delivered (or holds) it, so it leaves the batch; a sidecar failure returns null so the caller keeps
245
314
  * every entry pending. Claims are taken at delivery, never while deferred, so a long turn cannot outlive the lease.
246
315
  */
247
- async claimBatch(batched) {
316
+ async claimBatch(home, batched) {
248
317
  const deliver = [];
249
318
  const held = [];
250
319
  for (const entry of batched) {
@@ -257,10 +326,10 @@ export class CompletionDrain {
257
326
  try {
258
327
  if (!sidecar)
259
328
  throw new Error('sidecar unavailable');
260
- claimId = (await sidecar.call('process.claim_delivery', { profile_home: this.profileHome(), event: entry.event, consumer: DELIVERY_CONSUMER })).claim_id;
329
+ claimId = (await sidecar.call('process.claim_delivery', { profile_home: home, event: entry.event, consumer: DELIVERY_CONSUMER })).claim_id;
261
330
  }
262
331
  catch (error) {
263
- await this.settleClaims('process.release_delivery', held);
332
+ await this.settleClaims(home, 'process.release_delivery', held);
264
333
  this.deps.log(`[webui] WARNING: async delegation delivery claim failed for ${entry.process_id}: ${error.message}; kept pending`);
265
334
  return null;
266
335
  }
@@ -277,20 +346,19 @@ export class CompletionDrain {
277
346
  * attempt spent), `release` when it failed (spends one; the Agent drops a row past its budget). Best effort: the Agent's
278
347
  * lease bounds a lost hand-back.
279
348
  */
280
- async settleClaims(method, held) {
349
+ async settleClaims(home, method, held) {
281
350
  const sidecar = this.deps.sidecar();
282
351
  if (!sidecar)
283
352
  return;
284
353
  for (const claim of held) {
285
354
  try {
286
- await sidecar.call(method, { profile_home: this.profileHome(), event: claim.event, claim_id: claim.claim_id });
355
+ await sidecar.call(method, { profile_home: home, event: claim.event, claim_id: claim.claim_id });
287
356
  }
288
357
  catch (error) {
289
358
  this.deps.log(`[webui] WARNING: ${method} failed for ${str(claim.event.delegation_id)}: ${error.message}`);
290
359
  }
291
360
  }
292
361
  }
293
- profileHome() { return this.deps.profileHome(this.deps.activeProfile()); }
294
362
  scheduleRetry(sid) {
295
363
  const attempts = (this.retryAttempts.get(sid) ?? 0) + 1;
296
364
  this.retryAttempts.set(sid, attempts);
@@ -298,24 +366,150 @@ export class CompletionDrain {
298
366
  this.deps.log(`[webui] WARNING: server-side wakeup retry budget exhausted for session ${sid}; deferred entries wait for the next turn teardown`);
299
367
  return;
300
368
  }
369
+ this.scheduleDrain(sid, WAKEUP_RETRY_SECONDS);
370
+ }
371
+ scheduleDrain(sid, delaySeconds) {
301
372
  const existing = this.retryTimers.get(sid);
302
373
  if (existing)
303
374
  clearTimeout(existing);
304
- const timer = setTimeout(() => { this.retryTimers.delete(sid); void this.drainDeferred(sid); }, WAKEUP_RETRY_SECONDS * 1000);
375
+ const timer = setTimeout(() => { this.retryTimers.delete(sid); void this.drainDeferred(sid); }, Math.max(0, delaySeconds) * 1000);
305
376
  timer.unref();
306
377
  this.retryTimers.set(sid, timer);
307
378
  }
379
+ /** TAL-576: `usage.pool` for the provider whose pool ran dry, through `poolRecovery`; null when the sidecar cannot say. */
380
+ async poolRecovery(home, session) {
381
+ const sidecar = this.deps.sidecar();
382
+ const pause = session.process_wakeup_pause;
383
+ const provider = str(isDict(pause) ? pause.pool_provider : '').trim() || str(session.model_provider).trim();
384
+ if (!sidecar || !provider)
385
+ return null;
386
+ try {
387
+ return poolRecovery((await sidecar.call('usage.pool', { profile_home: home, provider })).entries, this.deps.now());
388
+ }
389
+ catch {
390
+ return null;
391
+ }
392
+ }
393
+ /** A restarted server resumes every session it left holding wakeups (`startWakeup` drops a deleted session's). */
394
+ resumeHeld() {
395
+ let sids;
396
+ try {
397
+ sids = this.heldWakeups.sessions();
398
+ }
399
+ catch (error) {
400
+ this.deps.log(`[webui] WARNING: held wakeups unreadable; retrying: ${error.message}`);
401
+ return;
402
+ }
403
+ this.heldResumed = true;
404
+ for (const sid of sids)
405
+ void this.drainDeferred(sid);
406
+ }
407
+ held(sid) {
408
+ if (this.unreleased.has(sid))
409
+ this.release(sid, []);
410
+ const done = new Set((this.unreleased.get(sid) ?? []).map(heldKey));
411
+ try {
412
+ return this.heldWakeups.get(sid).filter((e) => !done.has(heldKey(e)));
413
+ }
414
+ catch (error) {
415
+ // An unreadable store is unknown, not empty: recheck later rather than leave the session dormant.
416
+ this.scheduleDrain(sid, HELD_RECHECK_SECONDS);
417
+ this.deps.log(`[webui] WARNING: held wakeups unreadable for session ${sid}; retrying: ${error.message}`);
418
+ return [];
419
+ }
420
+ }
421
+ /** A session whose file is gone was deleted; any other load failure keeps its wakeups. */
422
+ deleted(sid, error) {
423
+ return error instanceof SessionNotFound && !existsSync(this.deps.store.pathFor(sid));
424
+ }
425
+ /** Admitted or settled wakeups (every one, with none given) leave the held file. */
426
+ release(sid, entries) {
427
+ // Earlier releases that could not be written go with this one.
428
+ const batch = entries ? mergeWakeups([...(this.unreleased.get(sid) ?? [])], entries) : undefined;
429
+ try {
430
+ this.heldWakeups.release(sid, batch);
431
+ this.unreleased.delete(sid);
432
+ }
433
+ catch (error) {
434
+ if (batch)
435
+ this.unreleased.set(sid, batch);
436
+ this.deps.log(`[webui] WARNING: failed to release held wakeups for session ${sid}; they are not delivered again: ${error.message}`);
437
+ }
438
+ }
439
+ saveSession(sid, session) {
440
+ try {
441
+ this.deps.store.save(session, { touchUpdatedAt: false });
442
+ }
443
+ catch (error) {
444
+ this.deps.log(`[webui] WARNING: failed to save wakeup pause for session ${sid}: ${error.message}`);
445
+ }
446
+ }
447
+ /**
448
+ * TAL-576: while the provider has no usable credentials no wakeup turn starts. The entries are held until a turn
449
+ * admits them, and a timer rechecks the pause at the pool's earliest retry deadline, at least every five minutes.
450
+ * Returns whether the wakeup is handled here (held, or dropped with its deleted session).
451
+ */
452
+ async holdWhilePaused(sid, session, home, batched) {
453
+ const pause = session.process_wakeup_pause;
454
+ if (!wakeupPaused(session, home, this.deps.configPath?.(home))) {
455
+ if (pause !== session.process_wakeup_pause)
456
+ this.saveSession(sid, session);
457
+ return false;
458
+ }
459
+ const recovery = await this.poolRecovery(home, session);
460
+ // A turn that succeeded or a provider switched during the lookup lifted the pause already.
461
+ // A deleted session owns no turn; a transient load failure holds the wakeups for the next recheck.
462
+ let current = null;
463
+ try {
464
+ current = this.deps.store.get(sid);
465
+ }
466
+ catch (error) {
467
+ if (this.deleted(sid, error)) {
468
+ this.release(sid);
469
+ return true;
470
+ }
471
+ }
472
+ if (current) {
473
+ const before = current.process_wakeup_pause;
474
+ if (recovery === true)
475
+ current.process_wakeup_pause = null;
476
+ if (recovery === true || !wakeupPaused(current, home, this.deps.configPath?.(home))) {
477
+ if (before !== current.process_wakeup_pause)
478
+ this.saveSession(sid, current);
479
+ return false;
480
+ }
481
+ }
482
+ try {
483
+ this.heldWakeups.hold(sid, batched);
484
+ }
485
+ catch (error) {
486
+ for (const e of batched)
487
+ this.defer(sid, e);
488
+ this.deps.log(`[webui] WARNING: failed to persist held wakeups for session ${sid}; kept in memory: ${error.message}`);
489
+ }
490
+ this.retryAttempts.delete(sid);
491
+ this.scheduleDrain(sid, typeof recovery === 'number' ? Math.min(recovery - this.deps.now(), HELD_RECHECK_SECONDS) : HELD_RECHECK_SECONDS);
492
+ this.deps.log(`[webui] server-side wakeup paused for session ${sid}: provider credential pool is empty`);
493
+ return true;
494
+ }
308
495
  /** Python `_start_server_side_wakeup_turn`: one batched turn; 409 re-defers, other failures re-defer with a timed retry. */
309
496
  async startWakeup(sid, batched) {
310
497
  const redefer = (entries) => { for (const e of entries)
311
- this.recordDeferred(sid, e.process_id, e.wakeup_prompt, e.event); };
498
+ this.defer(sid, e); };
312
499
  let session;
313
500
  try {
314
501
  session = this.deps.store.get(sid);
315
502
  }
316
- catch {
503
+ catch (error) {
504
+ // A deleted session owns no turn: its wakeups, held ones included, go with it.
505
+ if (this.deleted(sid, error)) {
506
+ this.release(sid);
507
+ this.deps.log(`[webui] server-side wakeup dropped for deleted session ${sid}`);
508
+ return false;
509
+ }
317
510
  redefer(batched);
318
- this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: session unavailable`);
511
+ this.scheduleRetry(sid);
512
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: session unavailable; retrying`);
319
513
  return false;
320
514
  }
321
515
  if (session.pre_compression_snapshot) {
@@ -323,7 +517,19 @@ export class CompletionDrain {
323
517
  this.deps.log(`[webui] WARNING: automatic wakeup retained: sealed snapshot ${sid} cannot own a turn`);
324
518
  return false;
325
519
  }
326
- const claimed = await this.claimBatch(batched);
520
+ // TAL-534: the delegation's ledger row lives in the session's own profile, whichever profile is active now.
521
+ let home;
522
+ try {
523
+ home = this.deps.profileHome(session.profile ?? this.deps.activeProfile());
524
+ }
525
+ catch (error) {
526
+ redefer(batched);
527
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: ${error.message}`);
528
+ return false;
529
+ }
530
+ if (await this.holdWhilePaused(sid, session, home, batched))
531
+ return false;
532
+ const claimed = await this.claimBatch(home, batched);
327
533
  if (!claimed) {
328
534
  redefer(batched);
329
535
  this.scheduleRetry(sid);
@@ -332,15 +538,16 @@ export class CompletionDrain {
332
538
  const { deliver, held } = claimed;
333
539
  // Entries another consumer already delivered are done here too.
334
540
  if (!deliver.length) {
335
- await this.markConsumed(batched.map((e) => e.process_id));
541
+ this.release(sid, batched);
542
+ await this.markConsumed(batched);
336
543
  return true;
337
544
  }
338
545
  // A wakeup that does not start hands its claims back, so those rows stay pending for the retry or the next restart.
339
- const giveBack = async (method = 'process.release_delivery') => { await this.settleClaims(method, held); redefer(deliver); };
546
+ const giveBack = async (method = 'process.release_delivery') => { await this.settleClaims(home, method, held); redefer(deliver); };
340
547
  const prompt = deliver.length === 1 ? deliver[0].wakeup_prompt : deliver.map((e) => e.wakeup_prompt).join('\n\n');
341
548
  let resp;
342
549
  try {
343
- resp = this.deps.startTurn(session, prompt);
550
+ resp = await this.deps.startTurn(session, prompt);
344
551
  }
345
552
  catch (error) {
346
553
  await giveBack();
@@ -349,8 +556,11 @@ export class CompletionDrain {
349
556
  return false;
350
557
  }
351
558
  const status = resp._status ?? (resp.stream_id ? 200 : 500);
559
+ // A busy session redelivers at its turn teardown; a stale runtime admits no turn, so it is retried on a timer (TAL-577).
352
560
  if (status === 409) {
353
561
  await giveBack('process.defer_delivery');
562
+ if (resp.retryable)
563
+ this.scheduleRetry(sid);
354
564
  return false;
355
565
  }
356
566
  if (status >= 400) {
@@ -360,22 +570,60 @@ export class CompletionDrain {
360
570
  return false;
361
571
  }
362
572
  this.retryAttempts.delete(sid);
573
+ this.release(sid, batched);
363
574
  recordBackgroundUpdate(session, str(resp.stream_id), batchUpdate(deliver.map((e) => ({ event: e.event ?? {}, prompt: e.wakeup_prompt }))));
364
- await this.settleClaims('process.complete_delivery', held);
365
- await this.markConsumed(batched.map((e) => e.process_id));
575
+ await this.settleClaims(home, 'process.complete_delivery', held);
576
+ await this.markConsumed(batched);
366
577
  this.deps.log(`[webui] server-side wakeup turn started for session ${sid} (stream_id=${str(resp.stream_id)})`);
367
578
  return true;
368
579
  }
369
- /** Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires. */
370
- async drainDeferred(sid) {
580
+ /**
581
+ * TAL-532: the deferred process completions the agent already holds from its own turn (wait/log or poll). Checked at
582
+ * delivery, not drain: the completion is usually drained seconds before the agent's wait returns. Only completions:
583
+ * consuming the exit says nothing about a watch match or notice, and async delegations settle in their delivery ledger.
584
+ * A failed check delivers everything rather than lose a result.
585
+ */
586
+ async alreadyConsumed(entries) {
587
+ const completions = entries.filter(isCompletion);
588
+ const sidecar = this.deps.sidecar();
589
+ if (!completions.length || !sidecar)
590
+ return new Set();
591
+ try {
592
+ const { consumed } = await sidecar.call('process.consumed', { process_ids: completions.map((e) => e.process_id) });
593
+ return new Set(completions.filter((e) => consumed.includes(e.process_id)));
594
+ }
595
+ catch {
596
+ return new Set();
597
+ }
598
+ }
599
+ /**
600
+ * Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires.
601
+ * TAL-576: one drain per session at a time, so two never copy the same held wakeups before either admits them.
602
+ */
603
+ drainDeferred(sid) {
604
+ const run = (this.drains.get(sid) ?? Promise.resolve(0)).catch(() => 0).then(() => this.drainDeferredNow(sid));
605
+ this.drains.set(sid, run);
606
+ void run.finally(() => { if (this.drains.get(sid) === run)
607
+ this.drains.delete(sid); }).catch(() => undefined);
608
+ return run;
609
+ }
610
+ async drainDeferredNow(sid) {
371
611
  if (!sid || this.hasActiveTurn(sid))
372
612
  return 0;
373
- const entries = this.deferred.get(sid);
374
- if (!entries?.length)
613
+ const entries = mergeWakeups([...(this.deferred.get(sid) ?? [])], this.held(sid));
614
+ if (!entries.length)
375
615
  return 0;
376
616
  this.deferred.delete(sid);
377
617
  this.pendingSessions.delete(sid);
378
- const pending = entries.filter((e) => e.wakeup_prompt.trim());
618
+ const prompted = entries.filter((e) => e.wakeup_prompt.trim());
619
+ const held = await this.alreadyConsumed(prompted);
620
+ if (held.size) {
621
+ this.release(sid, [...held]);
622
+ const ids = [...held].map((e) => e.process_id);
623
+ await this.markConsumed([...held]);
624
+ this.deps.log(`[webui] dropped ${String(held.size)} deferred wakeup(s) already consumed by the agent's own turn for session ${sid}: ${ids.join(', ')}`);
625
+ }
626
+ const pending = prompted.filter((e) => !held.has(e));
379
627
  if (!pending.length) {
380
628
  this.retryAttempts.delete(sid);
381
629
  return 0;
@@ -385,7 +633,7 @@ export class CompletionDrain {
385
633
  for (const entry of pending) {
386
634
  const size = entry.wakeup_prompt.length;
387
635
  if (batch.length && total + size > WAKEUP_BATCH_MAX_CHARS) {
388
- this.recordDeferred(sid, entry.process_id, entry.wakeup_prompt, entry.event);
636
+ this.defer(sid, entry);
389
637
  continue;
390
638
  }
391
639
  batch.push(entry);