@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
@@ -7,14 +7,30 @@
7
7
  * redelivered at turn teardown with a bounded retry budget).
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
+ import { readFileSync, realpathSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
12
+ import { SessionNotFound } from './store.js';
10
13
  import { str } from '../util.js';
11
- import { batchUpdate, recordBackgroundUpdate } from './background-updates.js';
14
+ import { expandDotenv, parseDotenv } from '../cli/dotenv.js';
15
+ import { isDict, messageText } from './merge.js';
16
+ import { batchUpdate, recordBackgroundUpdate, completionAccepted } from './background-updates.js';
17
+ import { backgroundEventKind } from './background-events.js';
18
+ import { HeldWakeups, deliveredPerProcess, heldKey, mergeWakeups, poolRecovery, wakeupPaused } from './wakeup-pause.js';
12
19
  export const COMPLETION_POLL_MS = 1000;
13
20
  const EMIT_COALESCE_WINDOW_S = 1;
14
21
  const WAKEUP_RETRY_SECONDS = 30;
15
22
  const WAKEUP_RETRY_MAX_ATTEMPTS = 5;
16
23
  const WAKEUP_BATCH_MAX_CHARS = 24_000;
24
+ /** A session with held wakeups rechecks its pause at least this often (a credential edit has no deadline). */
25
+ const HELD_RECHECK_SECONDS = 300;
17
26
  const DELIVERY_CONSUMER = 'webui';
27
+ const canonicalHome = (home) => { try {
28
+ return realpathSync(home);
29
+ }
30
+ catch {
31
+ return resolve(home);
32
+ } };
33
+ const completionKey = (home, event) => event.type === 'async_delegation' && event.delegation_id && !event.task_failure_notice ? JSON.stringify([canonicalHome(home), str(event.delegation_id)]) : '';
18
34
  /** Python `format_wakeup_prompt` for plain completion events; async delegations use the sidecar formatter. */
19
35
  export function formatWakeupPrompt(evt) {
20
36
  const type = str(evt.type ?? 'completion');
@@ -31,12 +47,22 @@ export function formatWakeupPrompt(evt) {
31
47
  body += `\n(${str(evt.suppressed)} earlier matches were suppressed by rate limit)`;
32
48
  return body + ']';
33
49
  }
34
- if (type !== 'completion')
50
+ if (backgroundEventKind(evt) !== 'completion')
35
51
  return null;
36
52
  if (!(sid || cmd || 'exit_code' in evt || evt.output))
37
53
  return null;
38
54
  return `[IMPORTANT: Background process ${sid} completed (exit_code=${str(evt.exit_code ?? '?')}).\nCommand: ${cmd}\nOutput:\n${truncate(evt.output, 4000)}]`;
39
55
  }
56
+ /**
57
+ * TAL-724: an event's delivery identity, the `process_id` of its wakeup entry. A completion (an event with no type is one) or
58
+ * an async delegation is its process: one exit can be enqueued twice when a kill races the reader thread. The Agent enqueues
59
+ * each watch event once, and one process raises several, so each is its own delivery; its event keeps the process id.
60
+ */
61
+ export function deliveryKey(evt, processId) {
62
+ return !processId || deliveredPerProcess(evt) ? processId : `${processId}:${str(evt.type)}:${randomUUID()}`;
63
+ }
64
+ /** Only a process's exit is consumed by the agent's own wait or poll (TAL-532); its watch events and delegations are not. */
65
+ const isCompletion = (e) => Boolean(e.process_id) && str(e.event?.type ?? 'completion') === 'completion';
40
66
  export class CompletionDrain {
41
67
  deps;
42
68
  timer = null;
@@ -52,8 +78,28 @@ export class CompletionDrain {
52
78
  recoveredFor = null;
53
79
  recoveryWarnedFor = null;
54
80
  lastDrainError = null;
81
+ heldResumed = false;
82
+ drains = new Map();
83
+ /** Admitted wakeups whose release could not be written: never delivered again, and the release is retried. */
84
+ unreleased = new Map();
85
+ /** TAL-576: wakeups held while their session is paused. */
86
+ heldWakeups;
87
+ /** Waiting requires an unadmitted result, not an admitted turn's outstanding ledger acknowledgement. */
88
+ async pendingDelegations(sid) {
89
+ try {
90
+ const session = (await this.deps.store.get(sid)), home = this.ownerHome(session);
91
+ return new Set((await this.heldWakeups.get(sid)).filter((entry) => {
92
+ const key = completionKey(home, entry.event ?? {});
93
+ return Boolean(key) && !completionAccepted(session, key);
94
+ }).map((entry) => str(entry.event?.delegation_id)).filter(Boolean));
95
+ }
96
+ catch {
97
+ return new Set();
98
+ }
99
+ }
55
100
  constructor(deps) {
56
101
  this.deps = deps;
102
+ this.heldWakeups = new HeldWakeups(deps.documents);
57
103
  }
58
104
  start() {
59
105
  if (this.timer || this.stopped)
@@ -107,7 +153,19 @@ export class CompletionDrain {
107
153
  this.recoveryWarnedFor = describe;
108
154
  }
109
155
  }
110
- const { events } = await sidecar.call('process.drain', { profile_home: this.deps.profileHome(this.deps.activeProfile()), max_events: 256 });
156
+ // TAL-576: whether or not every profile recovered, the sessions left holding wakeups resume once a sidecar answers.
157
+ if (!this.heldResumed)
158
+ (await this.resumeHeld());
159
+ // The drain is sidecar-wide; an active profile whose home escapes the profiles root (`profileHome` throws) scopes it
160
+ // to the base home instead of stalling every other profile's completions.
161
+ let scope;
162
+ try {
163
+ scope = this.deps.profileHome(this.deps.activeProfile());
164
+ }
165
+ catch {
166
+ scope = this.deps.baseHome;
167
+ }
168
+ const { events } = await sidecar.call('process.drain', { profile_home: scope, max_events: 256 });
111
169
  let routed = 0;
112
170
  const unrouted = [];
113
171
  for (const evt of events) {
@@ -130,19 +188,19 @@ export class CompletionDrain {
130
188
  }
131
189
  return routed;
132
190
  }
133
- resolveTarget(evt) {
191
+ async resolveTarget(evt) {
134
192
  const sessionKey = str(evt.session_key).trim();
135
193
  const origin = str(evt.origin_ui_session_id).trim();
136
- const live = (sid) => { if (!sid)
194
+ const live = async (sid) => { if (!sid)
137
195
  return ''; try {
138
- this.deps.store.get(sid, { metadataOnly: true, promote: false, cacheOnMiss: false });
196
+ (await this.deps.store.get(sid, { metadataOnly: true, promote: false, cacheOnMiss: false }));
139
197
  return sid;
140
198
  }
141
199
  catch {
142
200
  return '';
143
201
  } };
144
- const resolved = live(sessionKey);
145
- const owner = live(origin);
202
+ const resolved = (await live(sessionKey));
203
+ const owner = (await live(origin));
146
204
  if (!owner)
147
205
  return resolved;
148
206
  if (resolved && resolved !== owner)
@@ -156,7 +214,8 @@ export class CompletionDrain {
156
214
  return false;
157
215
  }
158
216
  async wakeupPrompt(evt) {
159
- if (evt.type === 'async_delegation') {
217
+ const kind = backgroundEventKind(evt);
218
+ if (kind === 'delegation' || kind === 'progress') {
160
219
  const sidecar = this.deps.sidecar();
161
220
  if (!sidecar)
162
221
  return '';
@@ -169,30 +228,178 @@ export class CompletionDrain {
169
228
  }
170
229
  return (formatWakeupPrompt(evt) ?? '').trim();
171
230
  }
231
+ /** The session's owning profile home; a session without a profile belongs to the root profile (`profilesMatch`). */
232
+ ownerHome(session) { return this.deps.profileHome(session.profile ?? 'default'); }
233
+ /** A profile's config; null when it cannot be read. */
234
+ async profileConfig(home) {
235
+ try {
236
+ return (await this.deps.readConfig?.(home)) ?? {};
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ /** The owning profile's `delegation.surface_child_process_notifications`; off, the Agent's default, when it cannot be read. */
243
+ async surfacesChildProcesses(sid) {
244
+ let config;
245
+ try {
246
+ config = await this.profileConfig(this.ownerHome((await this.deps.store.get(sid, { metadataOnly: true, promote: false, cacheOnMiss: false }))));
247
+ }
248
+ catch {
249
+ return false;
250
+ }
251
+ return isDict(config?.delegation) && config.delegation.surface_child_process_notifications === true;
252
+ }
253
+ /**
254
+ * TAL-907 (Agent `_load_background_notifications_mode`): `HERMES_BACKGROUND_NOTIFICATIONS` from the profile's `.env`
255
+ * (which overrides the shell, as in the Agent) or the server environment, else `display.background_process_notifications`
256
+ * (YAML `false` is off). Only `off` changes Web delivery; null when the setting cannot be read.
257
+ */
258
+ async notificationsOff(home) {
259
+ let mode;
260
+ try {
261
+ mode = expandDotenv(parseDotenv(readFileSync(join(home, '.env'), 'utf8')), this.deps.env ?? {}).HERMES_BACKGROUND_NOTIFICATIONS?.trim();
262
+ }
263
+ catch (error) {
264
+ if (error.code !== 'ENOENT')
265
+ return null;
266
+ }
267
+ mode ||= this.deps.env?.HERMES_BACKGROUND_NOTIFICATIONS?.trim();
268
+ if (!mode) {
269
+ const config = await this.profileConfig(home);
270
+ if (!config)
271
+ return null;
272
+ mode = isDict(config.display) ? config.display.background_process_notifications : undefined;
273
+ }
274
+ return mode === false || str(mode).trim().toLowerCase() === 'off';
275
+ }
276
+ async isReadOnly(session) { return await this.deps.isReadOnly?.(session) ?? session.read_only; }
277
+ /** Hand durable results back without acknowledging delivery or mutating the canonical session. */
278
+ async relinquish(sid, session, entries, claims = []) {
279
+ try {
280
+ if (!(await this.settleClaims(this.ownerHome(session), 'process.defer_delivery', claims)))
281
+ throw new Error('delivery claim could not be returned');
282
+ const events = entries.flatMap((e) => e.event?.type === 'async_delegation' ? [e.event] : []);
283
+ if (events.length) {
284
+ const sidecar = this.deps.sidecar();
285
+ if (!sidecar)
286
+ throw new Error('sidecar unavailable');
287
+ await sidecar.call('process.requeue', { events });
288
+ }
289
+ this.deferred.delete(sid);
290
+ this.pendingSessions.delete(sid);
291
+ this.retryAttempts.delete(sid);
292
+ const timer = this.retryTimers.get(sid);
293
+ if (timer)
294
+ clearTimeout(timer);
295
+ this.retryTimers.delete(sid);
296
+ await this.release(sid);
297
+ }
298
+ catch (error) {
299
+ for (const entry of entries)
300
+ this.defer(sid, entry);
301
+ this.scheduleRetry(sid);
302
+ this.deps.log(`[webui] WARNING: completion hand-back failed for ${sid}: ${error.message}`);
303
+ }
304
+ }
172
305
  /** Python `_process_one`; returns false when the event has no owning WebUI session. */
173
306
  async processOne(evt) {
174
307
  const processId = str(evt.process_id).trim();
175
- const sid = this.resolveTarget(evt);
308
+ const sid = (await this.resolveTarget(evt));
176
309
  if (!sid)
177
310
  return false;
311
+ let owner;
312
+ let home;
313
+ let homeError = '';
314
+ try {
315
+ owner = (await this.deps.store.get(sid));
316
+ }
317
+ catch {
318
+ return false;
319
+ }
320
+ try {
321
+ home = this.ownerHome(owner);
322
+ }
323
+ catch (error) {
324
+ home = '';
325
+ homeError = error.message;
326
+ }
327
+ if (home && (await this.isReadOnly(owner)))
328
+ return false;
329
+ // Reconciliation needs positive profile proof; live events retain the existing admission deferral on home errors.
330
+ if (evt.reconciliation_profile_home && (!home || canonicalHome(str(evt.reconciliation_profile_home)) !== canonicalHome(home)))
331
+ return false;
332
+ if (evt.reconciliation_profile_home && completionAccepted(owner, completionKey(home, evt)))
333
+ return true;
178
334
  if (evt.consumed === true)
179
335
  return true;
336
+ // TAL-933 (Agent `should_surface_notification`): a subagent's own process notices stay out of the parent chat unless
337
+ // the session's profile opts in. Dropped, not consumed, as in the Agent: an exit nobody read still reaches the parent
338
+ // as the child's `unread_completions`.
339
+ if (evt.child_process === true && !(await this.surfacesChildProcesses(sid)))
340
+ return true;
180
341
  const seen = this.seen.get(sid) ?? new Set();
181
342
  this.seen.set(sid, seen);
182
- if (processId && seen.has(processId))
343
+ // Only a per-process delivery can repeat: a watch event's key is unique, so remembering it would only grow `seen`.
344
+ if (processId && deliveredPerProcess(evt)) {
345
+ if (seen.has(processId))
346
+ return true;
347
+ }
348
+ const prompt = await this.wakeupPrompt(evt);
349
+ if ((await this.isReadOnly(owner))) {
350
+ if (home)
351
+ return false;
352
+ this.defer(sid, { process_id: deliveryKey(evt, processId), wakeup_prompt: prompt, event: evt });
353
+ this.scheduleRetry(sid);
354
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: ${homeError || 'ownership home unavailable'}; retrying`);
355
+ return true;
356
+ }
357
+ if (evt.reconciliation_profile_home && evt.delegation_id && prompt.includes(str(evt.delegation_id))) {
358
+ // Upgrade a proven pre-receipt Web turn; typed Agent bookkeeping alone never qualifies.
359
+ const previous = owner.messages.find((m) => m._source === 'process_wakeup' && m._turn_id && messageText(m.content) === prompt);
360
+ const turnId = str(previous?._turn_id) || (owner.pending_user_source === 'process_wakeup' && owner.pending_user_message === prompt ? str(owner.active_stream_id) : '');
361
+ if (turnId) {
362
+ const receipts = isDict(owner.extra.completion_acceptances) ? owner.extra.completion_acceptances : {};
363
+ owner.extra.completion_acceptances = { ...receipts, [completionKey(home, evt)]: turnId };
364
+ await this.deps.store.save(owner, { touchUpdatedAt: false });
365
+ return true;
366
+ }
367
+ }
368
+ // The formatter awaits the sidecar; another drain may have retained this event meanwhile.
369
+ if (processId && deliveredPerProcess(evt) && seen.has(processId))
183
370
  return true;
184
- if (processId)
371
+ const entry = { process_id: deliveryKey(evt, processId), wakeup_prompt: prompt, event: evt };
372
+ if (prompt && evt.type === 'async_delegation')
373
+ this.defer(sid, entry);
374
+ if (prompt && evt.type === 'async_delegation' && !evt.task_failure_notice) {
375
+ try {
376
+ // Busy, Stop, sealed and provider-paused entries survive either process restarting.
377
+ await this.heldWakeups.hold(sid, [entry]);
378
+ recordBackgroundUpdate(owner, `delegation:${str(evt.delegation_id)}`, batchUpdate([{ event: evt, prompt }]));
379
+ await this.deps.store.save(owner, { touchUpdatedAt: false });
380
+ }
381
+ catch (error) {
382
+ this.scheduleRetry(sid);
383
+ this.deps.log(`[webui] WARNING: failed to persist delegation wakeup for session ${sid}; retained in memory for retry: ${error.message}`);
384
+ return true;
385
+ }
386
+ }
387
+ if (processId && deliveredPerProcess(evt))
185
388
  seen.add(processId);
186
389
  this.pendingSessions.add(sid);
187
- const prompt = await this.wakeupPrompt(evt);
188
- this.deps.background?.recordEvent(sid, evt, prompt);
189
- this.emitCoalesced(sid, this.buildPayload(evt, sid, prompt));
390
+ await this.deps.background?.recordEvent(sid, evt, prompt);
391
+ if (backgroundEventKind(evt) !== 'progress')
392
+ this.emitCoalesced(sid, this.buildPayload(evt, sid, prompt));
190
393
  if (!prompt)
191
394
  return true;
192
- if (this.hasActiveTurn(sid))
193
- this.recordDeferred(sid, processId, prompt, evt);
395
+ if (evt.type === 'async_delegation') {
396
+ if (!this.hasActiveTurn(sid))
397
+ await this.drainDeferred(sid);
398
+ }
399
+ else if (this.hasActiveTurn(sid))
400
+ this.defer(sid, entry);
194
401
  else
195
- await this.startWakeup(sid, [{ process_id: processId, wakeup_prompt: prompt, event: evt }]);
402
+ await this.startWakeup(sid, [entry]);
196
403
  return true;
197
404
  }
198
405
  buildPayload(evt, sid, prompt) {
@@ -243,21 +450,26 @@ export class CompletionDrain {
243
450
  this.pendingEmit.set(sid, { payload, timer });
244
451
  }
245
452
  recordDeferred(sid, processId, prompt, event) {
246
- if (!sid || !prompt)
453
+ this.defer(sid, { process_id: deliveryKey(event ?? {}, processId), wakeup_prompt: prompt, ...(event ? { event } : {}) });
454
+ }
455
+ /** Re-deferring keeps an entry's delivery identity, so its held copy and this one stay one wakeup. */
456
+ defer(sid, entry) {
457
+ if (!sid || !entry.wakeup_prompt)
247
458
  return;
248
459
  const entries = this.deferred.get(sid) ?? [];
249
- if (processId && entries.some((e) => e.process_id === processId))
460
+ if (entries.some((e) => heldKey(e) === heldKey(entry)))
250
461
  return;
251
- entries.push({ process_id: processId, wakeup_prompt: prompt, ...(event ? { event } : {}) });
462
+ entries.push(entry);
252
463
  this.deferred.set(sid, entries);
253
464
  this.pendingSessions.add(sid);
254
465
  }
255
466
  deferredCount(sid) { return this.deferred.get(sid)?.length ?? 0; }
256
- async markConsumed(processIds) {
467
+ /** Delivering a process's exit consumes it; a watch event leaves the exit to be delivered (TAL-724). */
468
+ async markConsumed(entries) {
257
469
  const sidecar = this.deps.sidecar();
258
470
  if (!sidecar)
259
471
  return;
260
- for (const pid of processIds.filter(Boolean)) {
472
+ for (const pid of new Set(entries.filter(isCompletion).map((e) => e.process_id))) {
261
473
  try {
262
474
  await sidecar.call('process.mark_consumed', { process_id: pid });
263
475
  }
@@ -269,10 +481,12 @@ export class CompletionDrain {
269
481
  * another consumer delivered (or holds) it, so it leaves the batch; a sidecar failure returns null so the caller keeps
270
482
  * every entry pending. Claims are taken at delivery, never while deferred, so a long turn cannot outlive the lease.
271
483
  */
272
- async claimBatch(home, batched) {
484
+ async claimBatch(home, batched, session) {
273
485
  const deliver = [];
274
486
  const held = [];
275
487
  for (const entry of batched) {
488
+ if ((await this.isReadOnly(session)))
489
+ break;
276
490
  if (entry.event?.type !== 'async_delegation') {
277
491
  deliver.push(entry);
278
492
  continue;
@@ -282,7 +496,10 @@ export class CompletionDrain {
282
496
  try {
283
497
  if (!sidecar)
284
498
  throw new Error('sidecar unavailable');
285
- claimId = (await sidecar.call('process.claim_delivery', { profile_home: home, event: entry.event, consumer: DELIVERY_CONSUMER })).claim_id;
499
+ const claim = await sidecar.call('process.claim_delivery', { profile_home: home, event: entry.event, consumer: DELIVERY_CONSUMER });
500
+ if (claim.retryable)
501
+ throw new Error('another consumer holds the pending result');
502
+ claimId = claim.claim_id;
286
503
  }
287
504
  catch (error) {
288
505
  await this.settleClaims(home, 'process.release_delivery', held);
@@ -305,15 +522,18 @@ export class CompletionDrain {
305
522
  async settleClaims(home, method, held) {
306
523
  const sidecar = this.deps.sidecar();
307
524
  if (!sidecar)
308
- return;
525
+ return held.length === 0;
526
+ let ok = true;
309
527
  for (const claim of held) {
310
528
  try {
311
529
  await sidecar.call(method, { profile_home: home, event: claim.event, claim_id: claim.claim_id });
312
530
  }
313
531
  catch (error) {
532
+ ok = false;
314
533
  this.deps.log(`[webui] WARNING: ${method} failed for ${str(claim.event.delegation_id)}: ${error.message}`);
315
534
  }
316
535
  }
536
+ return ok;
317
537
  }
318
538
  scheduleRetry(sid) {
319
539
  const attempts = (this.retryAttempts.get(sid) ?? 0) + 1;
@@ -322,24 +542,159 @@ export class CompletionDrain {
322
542
  this.deps.log(`[webui] WARNING: server-side wakeup retry budget exhausted for session ${sid}; deferred entries wait for the next turn teardown`);
323
543
  return;
324
544
  }
545
+ this.scheduleDrain(sid, WAKEUP_RETRY_SECONDS);
546
+ }
547
+ scheduleDrain(sid, delaySeconds) {
325
548
  const existing = this.retryTimers.get(sid);
326
549
  if (existing)
327
550
  clearTimeout(existing);
328
- const timer = setTimeout(() => { this.retryTimers.delete(sid); void this.drainDeferred(sid); }, WAKEUP_RETRY_SECONDS * 1000);
551
+ const timer = setTimeout(() => { this.retryTimers.delete(sid); void this.drainDeferred(sid); }, Math.max(0, delaySeconds) * 1000);
329
552
  timer.unref();
330
553
  this.retryTimers.set(sid, timer);
331
554
  }
555
+ /** TAL-576: `usage.pool` for the provider whose pool ran dry, through `poolRecovery`; null when the sidecar cannot say. */
556
+ async poolRecovery(home, session) {
557
+ const sidecar = this.deps.sidecar();
558
+ const pause = session.process_wakeup_pause;
559
+ const provider = str(isDict(pause) ? pause.pool_provider : '').trim() || str(session.model_provider).trim();
560
+ if (!sidecar || !provider)
561
+ return null;
562
+ try {
563
+ return poolRecovery((await sidecar.call('usage.pool', { profile_home: home, provider })).entries, this.deps.now());
564
+ }
565
+ catch {
566
+ return null;
567
+ }
568
+ }
569
+ /** A restarted server resumes every session it left holding wakeups (`startWakeup` drops a deleted session's). */
570
+ async resumeHeld() {
571
+ let sids;
572
+ try {
573
+ sids = (await this.heldWakeups.sessions());
574
+ }
575
+ catch (error) {
576
+ this.deps.log(`[webui] WARNING: held wakeups unreadable; retrying: ${error.message}`);
577
+ return;
578
+ }
579
+ this.heldResumed = true;
580
+ for (const sid of sids)
581
+ void this.drainDeferred(sid);
582
+ }
583
+ async held(sid) {
584
+ if (this.unreleased.has(sid))
585
+ (await this.release(sid, []));
586
+ const done = new Set((this.unreleased.get(sid) ?? []).map(heldKey));
587
+ try {
588
+ return (await this.heldWakeups.get(sid)).filter((e) => !done.has(heldKey(e)));
589
+ }
590
+ catch (error) {
591
+ // An unreadable store is unknown, not empty: recheck later rather than leave the session dormant.
592
+ this.scheduleDrain(sid, HELD_RECHECK_SECONDS);
593
+ this.deps.log(`[webui] WARNING: held wakeups unreadable for session ${sid}; retrying: ${error.message}`);
594
+ return [];
595
+ }
596
+ }
597
+ /** A session whose file is gone was deleted; any other load failure keeps its wakeups. */
598
+ async deleted(sid, error) {
599
+ return error instanceof SessionNotFound && !await this.deps.store.exists(sid);
600
+ }
601
+ /** Admitted or settled wakeups (every one, with none given) leave the held file. */
602
+ async release(sid, entries) {
603
+ // Earlier releases that could not be written go with this one.
604
+ const batch = entries ? mergeWakeups([...(this.unreleased.get(sid) ?? [])], entries) : undefined;
605
+ try {
606
+ await this.heldWakeups.release(sid, batch);
607
+ this.unreleased.delete(sid);
608
+ }
609
+ catch (error) {
610
+ if (batch)
611
+ this.unreleased.set(sid, batch);
612
+ this.deps.log(`[webui] WARNING: failed to release held wakeups for session ${sid}; they are not delivered again: ${error.message}`);
613
+ }
614
+ }
615
+ async saveSession(sid, session) {
616
+ try {
617
+ (await this.deps.store.save(session, { touchUpdatedAt: false }));
618
+ }
619
+ catch (error) {
620
+ this.deps.log(`[webui] WARNING: failed to save wakeup pause for session ${sid}: ${error.message}`);
621
+ }
622
+ }
623
+ /**
624
+ * TAL-576: while the provider has no usable credentials no wakeup turn starts. The entries are held until a turn
625
+ * admits them, and a timer rechecks the pause at the pool's earliest retry deadline, at least every five minutes.
626
+ * Returns whether the wakeup is handled here (held, or dropped with its deleted session).
627
+ */
628
+ async holdWhilePaused(sid, session, home, batched) {
629
+ if ((await this.isReadOnly(session))) {
630
+ await this.relinquish(sid, session, batched);
631
+ return true;
632
+ }
633
+ const pause = session.process_wakeup_pause;
634
+ if (!wakeupPaused(session, home, this.deps.configPath?.(home))) {
635
+ if (pause !== session.process_wakeup_pause)
636
+ (await this.saveSession(sid, session));
637
+ return false;
638
+ }
639
+ const recovery = await this.poolRecovery(home, session);
640
+ // A turn that succeeded or a provider switched during the lookup lifted the pause already.
641
+ // A deleted session owns no turn; a transient load failure holds the wakeups for the next recheck.
642
+ let current = null;
643
+ try {
644
+ current = (await this.deps.store.get(sid));
645
+ }
646
+ catch (error) {
647
+ if ((await this.deleted(sid, error))) {
648
+ (await this.release(sid));
649
+ return true;
650
+ }
651
+ }
652
+ if (current) {
653
+ if ((await this.isReadOnly(current))) {
654
+ await this.relinquish(sid, current, batched);
655
+ return true;
656
+ }
657
+ const before = current.process_wakeup_pause;
658
+ if (recovery === true)
659
+ current.process_wakeup_pause = null;
660
+ if (recovery === true || !wakeupPaused(current, home, this.deps.configPath?.(home))) {
661
+ if (before !== current.process_wakeup_pause)
662
+ (await this.saveSession(sid, current));
663
+ return false;
664
+ }
665
+ }
666
+ try {
667
+ (await this.heldWakeups.hold(sid, batched));
668
+ }
669
+ catch (error) {
670
+ for (const e of batched)
671
+ this.defer(sid, e);
672
+ this.deps.log(`[webui] WARNING: failed to persist held wakeups for session ${sid}; kept in memory: ${error.message}`);
673
+ }
674
+ this.retryAttempts.delete(sid);
675
+ this.scheduleDrain(sid, typeof recovery === 'number' ? Math.min(recovery - this.deps.now(), HELD_RECHECK_SECONDS) : HELD_RECHECK_SECONDS);
676
+ this.deps.log(`[webui] server-side wakeup paused for session ${sid}: provider credential pool is empty`);
677
+ return true;
678
+ }
332
679
  /** Python `_start_server_side_wakeup_turn`: one batched turn; 409 re-defers, other failures re-defer with a timed retry. */
333
- async startWakeup(sid, batched) {
680
+ async startWakeup(sid, wakeups) {
681
+ let batched = wakeups;
334
682
  const redefer = (entries) => { for (const e of entries)
335
- this.recordDeferred(sid, e.process_id, e.wakeup_prompt, e.event); };
683
+ this.defer(sid, e); };
336
684
  let session;
337
685
  try {
338
- session = this.deps.store.get(sid);
686
+ session = (await this.deps.store.get(sid));
339
687
  }
340
- catch {
688
+ catch (error) {
689
+ // A deleted session owns no turn: its wakeups, held ones included, go with it.
690
+ if ((await this.deleted(sid, error))) {
691
+ (await this.release(sid));
692
+ this.deps.log(`[webui] server-side wakeup dropped for deleted session ${sid}`);
693
+ return false;
694
+ }
341
695
  redefer(batched);
342
- this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: session unavailable`);
696
+ this.scheduleRetry(sid);
697
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: session unavailable; retrying`);
343
698
  return false;
344
699
  }
345
700
  if (session.pre_compression_snapshot) {
@@ -348,17 +703,75 @@ export class CompletionDrain {
348
703
  return false;
349
704
  }
350
705
  // TAL-534: the delegation's ledger row lives in the session's own profile, whichever profile is active now.
351
- const home = this.deps.profileHome(session.profile ?? this.deps.activeProfile());
352
- const claimed = await this.claimBatch(home, batched);
706
+ let home;
707
+ try {
708
+ home = this.ownerHome(session);
709
+ }
710
+ catch (error) {
711
+ redefer(batched);
712
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: ${error.message}`);
713
+ return false;
714
+ }
715
+ if ((await this.isReadOnly(session))) {
716
+ await this.relinquish(sid, session, batched);
717
+ return false;
718
+ }
719
+ const accepted = batched.filter((e) => completionAccepted(session, completionKey(home, e.event ?? {})));
720
+ if (accepted.length) {
721
+ // A crash may have saved admission before acknowledging the Agent. Finish that handshake without a new turn.
722
+ const claims = await this.claimBatch(home, accepted, session);
723
+ if (claims && (await this.isReadOnly(session))) {
724
+ await this.relinquish(sid, session, batched, claims.held);
725
+ return false;
726
+ }
727
+ if (!claims || !(await this.settleClaims(home, 'process.complete_delivery', claims.held))) {
728
+ redefer(batched);
729
+ this.scheduleRetry(sid);
730
+ return false;
731
+ }
732
+ await this.release(sid, accepted);
733
+ batched = batched.filter((e) => !accepted.includes(e));
734
+ }
735
+ if (!batched.length)
736
+ return false;
737
+ // TAL-907: `off` mutes process-driven wakeups at delivery, so a deferred or held wakeup follows the current setting; the
738
+ // drained event's record and status row are its whole delivery. A subagent result is not a process notification and
739
+ // still lands, as in the Agent. A setting that cannot be read retains the wakeups rather than start a turn.
740
+ const muted = batched.filter((e) => e.event?.type !== 'async_delegation');
741
+ if (muted.length) {
742
+ const off = await this.notificationsOff(home);
743
+ if (off === null) {
744
+ redefer(batched);
745
+ this.scheduleRetry(sid);
746
+ this.deps.log(`[webui] WARNING: server-side wakeup retained for session ${sid}: background_process_notifications is unreadable; retrying`);
747
+ return false;
748
+ }
749
+ if (off) {
750
+ await this.release(sid, muted);
751
+ batched = batched.filter((e) => !muted.includes(e));
752
+ if (!batched.length) {
753
+ this.retryAttempts.delete(sid);
754
+ return false;
755
+ }
756
+ }
757
+ }
758
+ if (await this.holdWhilePaused(sid, session, home, batched))
759
+ return false;
760
+ const claimed = await this.claimBatch(home, batched, session);
353
761
  if (!claimed) {
354
762
  redefer(batched);
355
763
  this.scheduleRetry(sid);
356
764
  return false;
357
765
  }
358
766
  const { deliver, held } = claimed;
767
+ if ((await this.isReadOnly(session))) {
768
+ await this.relinquish(sid, session, batched, held);
769
+ return false;
770
+ }
359
771
  // Entries another consumer already delivered are done here too.
360
772
  if (!deliver.length) {
361
- await this.markConsumed(batched.map((e) => e.process_id));
773
+ (await this.release(sid, batched));
774
+ await this.markConsumed(batched);
362
775
  return true;
363
776
  }
364
777
  // A wakeup that does not start hands its claims back, so those rows stay pending for the retry or the next restart.
@@ -366,7 +779,7 @@ export class CompletionDrain {
366
779
  const prompt = deliver.length === 1 ? deliver[0].wakeup_prompt : deliver.map((e) => e.wakeup_prompt).join('\n\n');
367
780
  let resp;
368
781
  try {
369
- resp = this.deps.startTurn(session, prompt);
782
+ resp = await this.deps.startTurn(session, prompt, deliver.map((e) => completionKey(home, e.event ?? {})).filter(Boolean), batchUpdate(deliver.map((e) => ({ event: e.event ?? {}, prompt: e.wakeup_prompt }))));
370
783
  }
371
784
  catch (error) {
372
785
  await giveBack();
@@ -374,9 +787,16 @@ export class CompletionDrain {
374
787
  this.deps.log(`[webui] WARNING: server-side wakeup turn raised for session ${sid}: ${error.message}`);
375
788
  return false;
376
789
  }
790
+ if ((await this.isReadOnly(session))) {
791
+ await this.relinquish(sid, session, batched, held);
792
+ return false;
793
+ }
377
794
  const status = resp._status ?? (resp.stream_id ? 200 : 500);
795
+ // A busy session redelivers at its turn teardown; a stale runtime admits no turn, so it is retried on a timer (TAL-577).
378
796
  if (status === 409) {
379
797
  await giveBack('process.defer_delivery');
798
+ if (resp.retryable)
799
+ this.scheduleRetry(sid);
380
800
  return false;
381
801
  }
382
802
  if (status >= 400) {
@@ -387,8 +807,14 @@ export class CompletionDrain {
387
807
  }
388
808
  this.retryAttempts.delete(sid);
389
809
  recordBackgroundUpdate(session, str(resp.stream_id), batchUpdate(deliver.map((e) => ({ event: e.event ?? {}, prompt: e.wakeup_prompt }))));
390
- await this.settleClaims(home, 'process.complete_delivery', held);
391
- await this.markConsumed(batched.map((e) => e.process_id));
810
+ await this.saveSession(sid, session);
811
+ if (await this.settleClaims(home, 'process.complete_delivery', held))
812
+ (await this.release(sid, batched));
813
+ else {
814
+ redefer(deliver);
815
+ this.scheduleRetry(sid);
816
+ }
817
+ await this.markConsumed(batched);
392
818
  this.deps.log(`[webui] server-side wakeup turn started for session ${sid} (stream_id=${str(resp.stream_id)})`);
393
819
  return true;
394
820
  }
@@ -399,7 +825,7 @@ export class CompletionDrain {
399
825
  * A failed check delivers everything rather than lose a result.
400
826
  */
401
827
  async alreadyConsumed(entries) {
402
- const completions = entries.filter((e) => e.process_id && str(e.event?.type ?? 'completion') === 'completion');
828
+ const completions = entries.filter(isCompletion);
403
829
  const sidecar = this.deps.sidecar();
404
830
  if (!completions.length || !sidecar)
405
831
  return new Set();
@@ -411,20 +837,31 @@ export class CompletionDrain {
411
837
  return new Set();
412
838
  }
413
839
  }
414
- /** Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires. */
415
- async drainDeferred(sid) {
840
+ /**
841
+ * Python `drain_deferred_wakeups_for_session`: turn-teardown idle hook; only the last active stream's teardown fires.
842
+ * TAL-576: one drain per session at a time, so two never copy the same held wakeups before either admits them.
843
+ */
844
+ drainDeferred(sid) {
845
+ const run = (this.drains.get(sid) ?? Promise.resolve(0)).catch(() => 0).then(async () => (await this.drainDeferredNow(sid)));
846
+ this.drains.set(sid, run);
847
+ void run.finally(() => { if (this.drains.get(sid) === run)
848
+ this.drains.delete(sid); }).catch(() => undefined);
849
+ return run;
850
+ }
851
+ async drainDeferredNow(sid) {
416
852
  if (!sid || this.hasActiveTurn(sid))
417
853
  return 0;
418
- const entries = this.deferred.get(sid);
419
- if (!entries?.length)
854
+ const entries = mergeWakeups([...(this.deferred.get(sid) ?? [])], (await this.held(sid)));
855
+ if (!entries.length)
420
856
  return 0;
421
857
  this.deferred.delete(sid);
422
858
  this.pendingSessions.delete(sid);
423
859
  const prompted = entries.filter((e) => e.wakeup_prompt.trim());
424
860
  const held = await this.alreadyConsumed(prompted);
425
861
  if (held.size) {
862
+ await this.release(sid, [...held]);
426
863
  const ids = [...held].map((e) => e.process_id);
427
- await this.markConsumed(ids);
864
+ await this.markConsumed([...held]);
428
865
  this.deps.log(`[webui] dropped ${String(held.size)} deferred wakeup(s) already consumed by the agent's own turn for session ${sid}: ${ids.join(', ')}`);
429
866
  }
430
867
  const pending = prompted.filter((e) => !held.has(e));
@@ -437,7 +874,7 @@ export class CompletionDrain {
437
874
  for (const entry of pending) {
438
875
  const size = entry.wakeup_prompt.length;
439
876
  if (batch.length && total + size > WAKEUP_BATCH_MAX_CHARS) {
440
- this.recordDeferred(sid, entry.process_id, entry.wakeup_prompt, entry.event);
877
+ this.defer(sid, entry);
441
878
  continue;
442
879
  }
443
880
  batch.push(entry);