@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
@@ -0,0 +1,284 @@
1
+ """Exact manual-fire recovery. The Agent owns admission, delivery and bookkeeping formats.
2
+
3
+ Its admission journal lacks the Web caller's cancellation intent and tail ownership.
4
+ This additive record retains those two obligations across a lost submitter or sidecar.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import contextlib
10
+ import importlib
11
+ import json
12
+ import os
13
+ import time
14
+ from pathlib import Path
15
+
16
+ from ..dependency_sync import _lock
17
+ from ..errors import RpcError
18
+ from ..home import scoped_home
19
+
20
+ SETTLEMENT_SECONDS = 30.0
21
+
22
+
23
+ class Unverified(RpcError):
24
+ def __init__(self, detail):
25
+ self.detail = str(detail)
26
+ super().__init__(f"Task outcome is unverified. {detail}",
27
+ condition="cron_execution_unverified")
28
+
29
+
30
+ def path(home, job_id):
31
+ return Path(home) / "cron" / "manual_runs" / (job_id + ".json")
32
+
33
+
34
+ def _read_record(home, job_id):
35
+ try:
36
+ record = json.loads(path(home, job_id).read_text())
37
+ except FileNotFoundError:
38
+ return None
39
+ except (OSError, ValueError) as exc:
40
+ raise Unverified("The retained task record could not be read.") from exc
41
+ job = record.get("job") if isinstance(record, dict) else None
42
+ if not isinstance(job, dict) or job.get("id") != job_id:
43
+ raise Unverified("Recovery identity does not match this task.")
44
+ return record
45
+
46
+
47
+ def pending_job(home, job_id):
48
+ """The frozen job keeps an externally deleted execution reachable through Web's list/get preflight."""
49
+ record = _read_record(home, job_id)
50
+ if record is None:
51
+ return None
52
+ job = record["job"]
53
+ return dict(job, recovery_only=True, state="paused", enabled=False, last_run_at=None, last_status="error",
54
+ last_error="Task outcome is unverified. Use Run now to check it.")
55
+
56
+
57
+ def pending_jobs(home):
58
+ from .cron import _job_id
59
+ result = []
60
+ for file in sorted((Path(home) / "cron" / "manual_runs").glob("*.json")):
61
+ job = pending_job(home, _job_id({"job_id": file.stem}))
62
+ if job is not None:
63
+ result.append(job)
64
+ return result
65
+
66
+
67
+ def supported(job, *, recovery=False):
68
+ if job.get("no_agent"):
69
+ return False
70
+ try:
71
+ authority = importlib.import_module("cron.scheduler_authority")
72
+ if recovery:
73
+ surfaces = ((authority, ("journal_path", "_lift_pause")),
74
+ (importlib.import_module("cron.scheduler_bookkeeping"), ("fail_empty_response", "finish_completed_run")),
75
+ (importlib.import_module("cron.executions"), ("give_up_execution", "recover_receipted_execution")),
76
+ (importlib.import_module("hermes_cli.gateway_client"), ("connect_gateway",)),
77
+ (importlib.import_module("hermes_cli.gateway_runtime"), ("discover_gateway_endpoint",)))
78
+ return all(callable(getattr(module, name, None)) for module, names in surfaces for name in names)
79
+ except ImportError as exc:
80
+ if recovery or (isinstance(exc, ModuleNotFoundError) and exc.name in {"cron.scheduler_authority", "cron"}):
81
+ return False
82
+ raise
83
+ return True
84
+
85
+
86
+ def recover(job, execution_id, cancel_event):
87
+ """Observe only. A lost submission reply never authorizes another submit."""
88
+ from hermes_cli.gateway_client import connect_gateway
89
+
90
+ async def observe():
91
+ endpoint = None
92
+ if not os.environ.get("HERMES_TUI_GATEWAY_URL", "").strip():
93
+ from hermes_cli.gateway_runtime import discover_gateway_endpoint
94
+ from hermes_constants import get_hermes_home
95
+ discovery = await asyncio.to_thread(discover_gateway_endpoint, get_hermes_home())
96
+ if discovery.state != "ready" or discovery.endpoint is None:
97
+ raise Unverified("The owning gateway is unavailable.")
98
+ endpoint = discovery.endpoint
99
+ async with connect_gateway(endpoint=endpoint) as client:
100
+ state = await client.rpc("cron.recover", job_id=job["id"], request_id=execution_id, extra_prompt=None)
101
+ if state["status"] == "missing":
102
+ return False, "", "", "Cron admission missing: the owner never admitted this fire"
103
+ if state["status"] == "unknown":
104
+ raise Unverified("The owner cannot prove this admission's outcome.")
105
+ receipt = {key: state[key] for key in ("session_id", "admission_id")}
106
+ while True:
107
+ if state["status"] == "terminal":
108
+ job.update(state.get("job_flags") or {})
109
+ return tuple(state["result"])
110
+ if state["status"] not in {"queued", "started"}:
111
+ raise Unverified(f"Owner status: {state['status']}")
112
+ if cancel_event.is_set():
113
+ await client.rpc("cron.cancel", **receipt)
114
+ await asyncio.sleep(.2)
115
+ state = await client.rpc("cron.status", **receipt)
116
+ return asyncio.run(observe())
117
+
118
+
119
+ class ManualExecution:
120
+ def __init__(self, home, execution_home, job, owner):
121
+ self.home = Path(home)
122
+ self.file = path(home, job["id"])
123
+ self.execution_home = Path(execution_home or home)
124
+ self.job, self.owner = dict(job), owner
125
+ self.record = None
126
+ self.recovering = False
127
+ self.settled = False
128
+
129
+ def save(self):
130
+ from utils import atomic_json_write
131
+ atomic_json_write(self.file, self.record, mode=0o600, fsync_dir=True)
132
+
133
+ @contextlib.contextmanager
134
+ def acquire(self):
135
+ self.file.parent.mkdir(parents=True, exist_ok=True)
136
+ with self.file.with_suffix(".lock").open("a+b") as lease:
137
+ try:
138
+ _lock(lease)
139
+ except OSError as exc:
140
+ raise RpcError("This manual task is still being reconciled", condition="cron_execution_unverified") from exc
141
+ yield
142
+
143
+ def prepare(self, *, recovery_only=False):
144
+ from cron import executions, jobs
145
+ self.record = _read_record(self.home, self.job["id"])
146
+ if self.record is not None:
147
+ self.job, self.owner = self.record["job"], self.record["owner"]
148
+ self.execution_home = Path(self.record["execution_home"])
149
+ self.recovering = True
150
+ else:
151
+ if recovery_only:
152
+ raise Unverified("The retained task record is no longer available. Refresh its status.")
153
+ with scoped_home(self.execution_home):
154
+ row = executions.create_execution(self.job["id"], source="web-manual")
155
+ executions.mark_execution_running(row["id"])
156
+ self.record = {"job": self.job, "owner": self.owner, "execution_home": str(self.execution_home),
157
+ "execution_id": row["id"], "cancel_reason": None, "tail_started": False}
158
+ self.execution_id = row["id"]
159
+ self.save()
160
+ self.execution_id = self.record["execution_id"]
161
+ if self.recovering:
162
+ self.cancel("submitter interrupted")
163
+ # Pause before dispatch: a dead sidecar must not let claim expiry re-fire an accepted task.
164
+ # Only this exact pause is lifted. Existing operator pauses are never changed.
165
+ with jobs.use_cron_store(self.home):
166
+ from .cron import _owner_fence
167
+ with _owner_fence(jobs, self.job["id"], self.owner, protect_unclaimed=True) as owns:
168
+ current = jobs.get_job(self.job["id"])
169
+ if not owns and not self.recovering:
170
+ self.cancel("claim_lost")
171
+ self.record["result"] = [False, "", "", "Fire claim lost before submission"]
172
+ self.save()
173
+ if owns and current and jobs.is_job_runnable(current) and not self.recovering:
174
+ current = jobs.pause_job(self.job["id"], reason=f"Manual execution pending: {self.execution_id}")
175
+ # The job write may have committed before the submitter died saving its pause receipt.
176
+ if (current and current.get("state") == "paused" and not self.record.get("pause")
177
+ and current.get("paused_reason") == f"Manual execution pending: {self.execution_id}"):
178
+ self.record["pause"] = {"paused_at": current["paused_at"], "reason": current["paused_reason"]}
179
+ self.save()
180
+
181
+ def cancel(self, reason):
182
+ if self.record["cancel_reason"] is None:
183
+ self.record["cancel_reason"] = reason
184
+ self.save()
185
+
186
+ def finish(self, result):
187
+ from cron import executions, jobs, scheduler
188
+ from cron.scheduler_authority import journal_path
189
+ from .cron import _delivery_home, _owner_fence, _ClaimLost
190
+
191
+ success, output, answer, error = result
192
+ with scoped_home(self.execution_home), jobs.use_cron_store(self.home), _owner_fence(jobs, self.job["id"], self.owner, protect_unclaimed=True) as owns:
193
+ current = jobs.get_job(self.job["id"]) or {}
194
+ row = executions.get_execution(self.execution_id)
195
+ marked = self.execution_id in current.get("canonical_completions", [])
196
+ booked = marked or (row and row["status"] in {"completed", "failed"})
197
+ # Our own mark releases the claim before pause cleanup. A successor's claim never qualifies.
198
+ may_finish = bool(current) and (owns or (marked and not current.get("fire_claim")))
199
+ if not booked and self.record["tail_started"]:
200
+ raise Unverified("Bookkeeping was interrupted; its side effects will not be repeated.")
201
+ # Stop the Agent's orphan journal reader from replaying OUR tail after a crash.
202
+ # Our record now owns recovery, including the at-most-once tail barrier.
203
+ self.record["result"] = list(result)
204
+ self.save()
205
+ journal_path(self.job["id"], self.execution_id).unlink(missing_ok=True)
206
+ if not may_finish:
207
+ self._finish_ledger(False, "Fire claim ownership lost before terminal completion.")
208
+ elif booked:
209
+ self._finish_ledger(success, error)
210
+ else:
211
+ self.record["tail_started"] = True
212
+ self.save()
213
+ self.job["execution_id"] = self.execution_id
214
+ with _delivery_home(scheduler, self.home):
215
+ from cron.scheduler_bookkeeping import fail_empty_response, finish_completed_run
216
+ success, error, declared = scheduler._apply_agent_failure_marker(self.job, success, error, answer)
217
+ delivery = scheduler._RunDelivery(self.job, success, error, agent_declared=declared)
218
+ scheduler._save_compose_deliver(delivery, scheduler._FireOwnership(self.job), answer, output,
219
+ adapters=None, loop=None, verbose=False, execution_token=None)
220
+ fail_empty_response(delivery, answer)
221
+ if not finish_completed_run(delivery, self.owner, self.execution_id, recovered=self.recovering):
222
+ raise Unverified("A prior submitter still owns this execution's bookkeeping.")
223
+ success, error = delivery.success, delivery.error
224
+ pause = self.record.get("pause")
225
+ if may_finish and pause:
226
+ from cron.scheduler_authority import _lift_pause
227
+ _lift_pause(self.job["id"], pause)
228
+ self.file.unlink()
229
+ self.settled = True
230
+ reason = self.record["cancel_reason"]
231
+ if not may_finish or reason == "claim_lost":
232
+ return {"job_id": self.job["id"], "status": "failed", "success": False, "error": str(_ClaimLost())}
233
+ if reason == "cancelled" and not success:
234
+ raise RpcError("cancelled", condition="cancelled")
235
+ if reason and not success:
236
+ return {"job_id": self.job["id"], "status": "failed", "success": False, "error": reason}
237
+ return {"job_id": self.job["id"], "status": "completed", "success": bool(success), "error": error}
238
+
239
+ def _finish_ledger(self, success, error):
240
+ from cron import executions
241
+ finish = executions.recover_receipted_execution if self.recovering else executions.finish_execution
242
+ kwargs = {"job_id": self.job["id"]} if self.recovering else {}
243
+ finish(self.execution_id, success=success, error=error, **kwargs)
244
+
245
+
246
+ def run(job, home, execution_home, ctx, owner, *, recovery_only=False):
247
+ from . import cron
248
+ # Refuse before prepare/cancel can rewrite retained evidence or cleanup can enter a removed owner API.
249
+ if (recovery_only or path(home, job["id"]).exists()) and not supported({}, recovery=True):
250
+ raise Unverified("This Agent has an unsupported recovery protocol. Reconcile the retained manual task "
251
+ "with a capable Agent before retrying. No task was resubmitted or settled.")
252
+ execution = ManualExecution(home, execution_home, job, owner)
253
+ with execution.acquire():
254
+ with cron._RUNNING_LOCK:
255
+ cron._RUNNING.setdefault(job["id"], time.time())
256
+ try:
257
+ execution.prepare(recovery_only=recovery_only)
258
+ if recovery_only:
259
+ ctx.emit("started", {"job_id": job["id"]})
260
+ heartbeat = cron._heartbeat(home, execution.job["id"], execution.owner) if execution.owner else None
261
+ if execution.record.get("result") is not None:
262
+ result = execution.record["result"]
263
+ else:
264
+ result = cron._run_in_child(execution.job, execution.execution_home, ctx, heartbeat, execution)
265
+ return execution.finish(result)
266
+ except Exception as exc:
267
+ if execution.settled:
268
+ raise
269
+ if execution.record is not None and hasattr(execution, "execution_id"):
270
+ execution.cancel(execution.record["cancel_reason"] or "submitter interrupted")
271
+ # The Web record, not the Agent's generic orphan replay, now owns cancellation.
272
+ # Give up only our own audit row; this never releases or stamps a fire claim.
273
+ with scoped_home(execution.execution_home):
274
+ from cron.scheduler_authority import journal_path
275
+ from cron.executions import give_up_execution
276
+ journal_path(job["id"], execution.execution_id).unlink(missing_ok=True)
277
+ give_up_execution(execution.execution_id, reason=str(exc))
278
+ if isinstance(exc, Unverified):
279
+ raise
280
+ raise Unverified(str(exc)) from exc
281
+ finally:
282
+ # The durable record guards an unverified owner; this map guards only an active call.
283
+ with cron._RUNNING_LOCK:
284
+ cron._RUNNING.pop(job["id"], None)
@@ -1,9 +1,10 @@
1
- """``gateway.*``: Hermes gateway lifecycle through the ``hermes`` CLI (ported from api/gateway_restart.py)."""
1
+ """``gateway.*``: Hermes gateway lifecycle (start, stop, restart) through the ``hermes`` CLI."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  import shutil
8
9
  import subprocess
9
10
  import sys
@@ -12,11 +13,22 @@ import time
12
13
  from pathlib import Path
13
14
 
14
15
  from ..errors import InvalidParams
15
- from ..home import profile_home_param
16
+ from ..home import _is_named_profile, profile_home_param
16
17
  from ..rpc import CallContext
18
+ from .profiles import _bounded, gateway_mode
17
19
 
18
20
  log = logging.getLogger("talaria_sidecar.gateway")
19
- _RESTART_LOCK = threading.Lock()
21
+ # One lifecycle action at a time across every action: overlapping `hermes gateway` subprocesses race each other.
22
+ _ACTION_LOCK = threading.Lock()
23
+ ACTIONS = ("start", "stop", "restart")
24
+ _DONE = {"start": "started", "stop": "stopped", "restart": "restarted"}
25
+ # EX_CONFIG: Agent v0.21.6 refuses a named profile's own start/restart with it, since the host gateway serves every profile.
26
+ _REFUSED = 78
27
+ _SERVED_WAIT_SECONDS = 15.0
28
+ # A restart waits out the CLI's own exit budget plus this headroom; 240 s on an Agent without the budget helper.
29
+ _RESTART_HEADROOM_SECONDS = 30.0
30
+ _RESTART_FALLBACK_SECONDS = 240.0
31
+ _ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
20
32
 
21
33
 
22
34
  def _hermes_command() -> str:
@@ -39,68 +51,213 @@ def _drain(stream, sink: list[str]) -> None:
39
51
  pass
40
52
 
41
53
 
42
- def restart(home: Path, cli_profile: str | None, ctx: CallContext, *, quick_timeout: float, background_wait: float) -> dict:
43
- if not _RESTART_LOCK.acquire(blocking=False):
44
- return {"status": "busy", "message": "Restart already in progress. Please wait a moment and try again."}
54
+ def _reason(stdout: str, stderr: str) -> str | None:
55
+ """The Agent's first error line: its first ``✗`` line, else the first line it wrote; one bounded printable line without colour codes."""
56
+ def lines(text: str) -> list[str]:
57
+ return [line.strip() for line in _ANSI.sub("", text).splitlines() if line.strip()]
58
+
59
+ first = next((line for line in lines(f"{stdout}\n{stderr}") if line.startswith("✗")), None) or next(iter(lines(stderr or stdout)), "")
60
+ return _bounded(first.lstrip("✗ "), 300) or None
61
+
62
+
63
+ def _host_root(home: Path) -> Path | None:
64
+ """The Hermes root of a ``<root>/profiles/<name>`` home, as the Agent's ``profile_name_for_home`` reads a named profile."""
65
+ return home.parent.parent if home.parent.name == "profiles" and not home.name.startswith(".") and home.name != "default" else None
66
+
67
+
68
+ def _host_serves(home: Path, budget_deadline: float) -> bool:
69
+ """True once the host gateway serves the named profile at ``home`` (TAL-805 ``served``); the host may still be coming up."""
70
+ deadline = min(time.monotonic() + _SERVED_WAIT_SECONDS, budget_deadline)
71
+ while True:
72
+ try:
73
+ if gateway_mode(home, home.name, False) == "served":
74
+ return True
75
+ except Exception: # noqa: BLE001
76
+ log.debug("gateway mode probe failed for %s", home, exc_info=True)
77
+ return False
78
+ if time.monotonic() >= deadline:
79
+ return False
80
+ time.sleep(0.5)
81
+
82
+
83
+ def control(action: str, home: Path, cli_profile: str | None, ctx: CallContext, *, quick_timeout: float, background_wait: float) -> dict:
84
+ if action not in ACTIONS:
85
+ raise InvalidParams("unsupported gateway action")
86
+ if not _ACTION_LOCK.acquire(blocking=False):
87
+ return {"status": "busy", "message": "Another gateway action is already in progress. Please wait a moment and try again."}
45
88
  try:
89
+ # One budget for the whole action, host fallback included: each step gets only what is left of it.
90
+ deadline = time.monotonic() + quick_timeout + background_wait
91
+
92
+ def run(action: str, home: Path, cli_profile: str | None) -> dict:
93
+ left = deadline - time.monotonic()
94
+ if left <= 0:
95
+ return {"status": "failed", "message": f"Gateway {action} ran out of time before it could start", "timed_out": True}
96
+ quick = min(quick_timeout, left)
97
+ return _run(action, home, cli_profile, ctx, quick_timeout=quick, background_wait=left - quick)
98
+
99
+ result = run(action, home, cli_profile)
100
+ root = _host_root(home)
101
+ # TAL-884: Agent v0.21.6 gives a named profile no gateway of its own. Start the host gateway from the root profile
102
+ # (`--profile default`, so a sticky active profile cannot retarget it), then rerun the profile's action, which
103
+ # serves or unparks it there. Never `--force`: that grows the per-profile fleet member the Agent refuses.
104
+ if action == "stop" or root is None or result.get("returncode") != _REFUSED:
105
+ return result
106
+ log.info("Gateway %s refused for %s; starting the host gateway from %s", action, home, root)
107
+ host = run("start", root, "default")
108
+ if host["status"] != "completed":
109
+ return host
110
+ result = run(action, home, cli_profile)
111
+ if result.get("returncode") == _REFUSED and _host_serves(home, deadline):
112
+ return {"status": "completed", "message": f"Gateway service {_DONE[action]} successfully", "detail": result.get("detail", "")}
113
+ return result
114
+ finally:
115
+ _ACTION_LOCK.release()
116
+
117
+
118
+ def _profile_selector(home: Path) -> str | None:
119
+ """The profile the Agent names ``home`` (``default`` for the root; None for no profile home): a selector-less verb would
120
+ follow the sticky ``active_profile``. Raises ImportError on an Agent without ``profile_name_for_home``."""
121
+ from hermes_constants import profile_name_for_home
122
+
123
+ return profile_name_for_home(home)
124
+
125
+
126
+ def _child_env(home: Path, profile: str | None, action: str) -> dict:
127
+ """The CLI's environment. The sidecar's ``os.environ`` carries its launch profile's ``.env`` once chat ran, so only the
128
+ launch home's own root-profile action keeps it; any other profile starts scrubbed of it and loads its own ``.env``."""
129
+ if profile is None or (profile == "default" and not _is_named_profile(home)):
46
130
  env = dict(os.environ)
47
131
  env["HERMES_HOME"] = str(home)
48
- env.pop("HERMES_EXEC_ASK", None) # the sidecar's own approval posture, not the gateway's
49
- cmd = [_hermes_command()]
50
- if cli_profile:
51
- cmd.extend(["--profile", cli_profile])
52
- cmd.extend(["gateway", "restart"])
53
- log.info("Restarting gateway via %s (HERMES_HOME=%s)", " ".join(cmd), home)
132
+ else:
133
+ from hermes_cli.web_server_gateway import _profile_action_environment
134
+
135
+ env = _profile_action_environment(["--profile", profile, "gateway", action])
136
+ env.pop("HERMES_EXEC_ASK", None) # the sidecar's own approval posture, not the gateway's
137
+ return env
138
+
139
+
140
+ # The CLI's own restart budget, read as the CLI reads it: in the child's environment once its home's .env has loaded.
141
+ _BUDGET_SCRIPT = (
142
+ "import os, sys; from pathlib import Path; sys.path.insert(0, sys.argv[1]); "
143
+ "from hermes_cli.env_loader import load_hermes_dotenv; "
144
+ "load_hermes_dotenv(hermes_home=os.environ['HERMES_HOME'], project_env=Path(sys.argv[1]) / '.env'); "
145
+ "from hermes_cli.gateway import _get_restart_exit_wait_budget; print(_get_restart_exit_wait_budget())"
146
+ )
147
+
148
+
149
+ def _agent_restart_budget(env: dict) -> float:
150
+ import hermes_cli
151
+
152
+ agent_dir = str(Path(hermes_cli.__file__).resolve().parent.parent)
153
+ out = subprocess.run([sys.executable, "-c", _BUDGET_SCRIPT, agent_dir], env=env, capture_output=True, text=True, timeout=60, check=True).stdout
154
+ return float(out.split()[-1])
155
+
156
+
157
+ def _restart_wait(home: Path, cli_profile: str | None = None) -> float:
158
+ """The Agent CLI's own restart wait for ``home`` plus headroom, so Web never kills a restart the CLI still waits on."""
159
+ try:
160
+ profile = cli_profile or _profile_selector(home)
161
+ return _agent_restart_budget(_child_env(home, profile, "restart")) + _RESTART_HEADROOM_SECONDS
162
+ except Exception: # noqa: BLE001 - an Agent without the helper, or an unreadable config, gets the historical wait
163
+ log.warning("Could not read the Agent's restart budget for %s", home, exc_info=True)
164
+ return _RESTART_FALLBACK_SECONDS
165
+
166
+
167
+ def _run(action: str, home: Path, cli_profile: str | None, ctx: CallContext, *, quick_timeout: float, background_wait: float) -> dict:
168
+ """One ``hermes --profile <name> gateway <action>`` subprocess for ``home``; the caller holds ``_ACTION_LOCK``."""
169
+ try:
170
+ profile = cli_profile or _profile_selector(home)
171
+ except ImportError:
172
+ # Selector-less, the CLI would follow the sticky profile, and a named profile's would inherit the launch profile's credentials.
173
+ return {"status": "failed", "message": f"Gateway {action} refused: the installed Hermes Agent cannot name the profile at {home}"}
174
+ try:
175
+ env = _child_env(home, profile, action)
176
+ except Exception as exc: # noqa: BLE001 - never start a named profile's gateway with another profile's credentials
177
+ log.warning("Could not build the %s profile's gateway environment", profile, exc_info=True)
178
+ return {"status": "failed", "message": f"Could not isolate the {profile} profile's environment: {getattr(exc, 'detail', None) or exc}"}
179
+ cmd = [_hermes_command()]
180
+ if profile:
181
+ cmd.extend(["--profile", profile])
182
+ cmd.extend(["gateway", action])
183
+ log.info("Running gateway %s via %s (HERMES_HOME=%s)", action, " ".join(cmd), home)
184
+ try:
54
185
  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)
55
- # Drain both pipes from the start: a chatty restart must never block on a full pipe and be mistaken for a hang.
56
- out: list[str] = []
57
- err: list[str] = []
58
- drainers = [threading.Thread(target=_drain, args=(proc.stdout, out), daemon=True), threading.Thread(target=_drain, args=(proc.stderr, err), daemon=True)]
186
+ except FileNotFoundError:
187
+ return {"status": "failed", "message": "Hermes CLI not found"}
188
+ # Drain both pipes from the start: a chatty restart must never block on a full pipe and be mistaken for a hang.
189
+ out: list[str] = []
190
+ err: list[str] = []
191
+ drainers = [threading.Thread(target=_drain, args=(proc.stdout, out), daemon=True), threading.Thread(target=_drain, args=(proc.stderr, err), daemon=True)]
192
+ for t in drainers:
193
+ t.start()
194
+
195
+ def collected() -> tuple[str, str]:
59
196
  for t in drainers:
60
- t.start()
61
-
62
- def collected() -> tuple[str, str]:
63
- for t in drainers:
64
- t.join(timeout=5)
65
- return "".join(out).strip(), "".join(err).strip()
66
-
67
- def outcome() -> dict:
68
- stdout, stderr = collected()
69
- if proc.returncode == 0:
70
- return {"status": "completed", "message": "Gateway service restarted successfully", "detail": stdout or stderr}
71
- return {"status": "failed", "message": f"Restart failed: {stderr or stdout}", "detail": stdout or stderr, "returncode": proc.returncode}
72
-
73
- ctx.emit("progress", {"phase": "started"})
74
- deadline = time.monotonic() + quick_timeout
75
- while proc.poll() is None and time.monotonic() < deadline:
76
- time.sleep(0.1)
77
- if proc.poll() is not None:
78
- return outcome()
79
- ctx.emit("progress", {"phase": "draining"})
80
- deadline = time.monotonic() + background_wait
81
- while proc.poll() is None and time.monotonic() < deadline:
82
- if ctx.cancelled:
83
- break
84
- time.sleep(0.5)
85
- if proc.poll() is None:
86
- proc.terminate()
87
- try:
88
- proc.wait(timeout=5)
89
- except subprocess.TimeoutExpired:
90
- proc.kill()
91
- proc.wait(timeout=5)
92
- collected()
93
- return {"status": "failed", "message": f"Gateway restart process timed out after {background_wait:.0f}s and was terminated"}
197
+ t.join(timeout=5)
198
+ return "".join(out).strip(), "".join(err).strip()
199
+
200
+ def outcome() -> dict:
201
+ stdout, stderr = collected()
202
+ if proc.returncode == 0:
203
+ return {"status": "completed", "message": f"Gateway service {_DONE[action]} successfully", "detail": stdout or stderr}
204
+ failed = {"status": "failed", "message": f"{action.capitalize()} failed: {stderr or stdout}", "detail": stdout or stderr, "returncode": proc.returncode}
205
+ reason = _reason(stdout, stderr)
206
+ return {**failed, "reason": reason} if reason else failed
207
+
208
+ ctx.emit("progress", {"phase": "started"})
209
+ deadline = time.monotonic() + quick_timeout
210
+ while proc.poll() is None and time.monotonic() < deadline:
211
+ time.sleep(0.1)
212
+ if proc.poll() is not None:
94
213
  return outcome()
95
- finally:
96
- _RESTART_LOCK.release()
214
+ ctx.emit("progress", {"phase": "draining"})
215
+ deadline = time.monotonic() + background_wait
216
+ while proc.poll() is None and time.monotonic() < deadline:
217
+ if ctx.cancelled:
218
+ break
219
+ time.sleep(0.5)
220
+ if proc.poll() is None:
221
+ proc.terminate()
222
+ try:
223
+ proc.wait(timeout=5)
224
+ except subprocess.TimeoutExpired:
225
+ proc.kill()
226
+ proc.wait(timeout=5)
227
+ collected()
228
+ if ctx.cancelled:
229
+ return {"status": "failed", "message": f"Gateway {action} was cancelled and its process terminated", "timed_out": True}
230
+ return {"status": "failed", "message": f"Gateway {action} process timed out after {background_wait:.0f}s and was terminated", "timed_out": True}
231
+ return outcome()
97
232
 
98
233
 
99
234
  def register(registry) -> None:
100
- @registry.method("gateway.restart", requires_agent=False)
101
- def restart_(ctx: CallContext, params: dict) -> dict:
235
+ @registry.method("gateway.session")
236
+ def session_(ctx: CallContext, params: dict) -> dict:
237
+ from .gateway_sessions import session
238
+
239
+ return session(ctx, params)
240
+
241
+ def run(ctx: CallContext, params: dict, action: str) -> dict:
102
242
  home = profile_home_param(params)
103
243
  cli_profile = params.get("cli_profile")
104
244
  if cli_profile is not None and not isinstance(cli_profile, str):
105
245
  raise InvalidParams("cli_profile must be a string")
106
- return restart(home, cli_profile or None, ctx, quick_timeout=float(params.get("quick_timeout_seconds") or 2.0), background_wait=float(params.get("background_wait_seconds") or 240.0))
246
+ # A restart drains the old gateway first, for as long as the CLI's own budget allows (infinite: no deadline).
247
+ wait = params.get("background_wait_seconds") or (_restart_wait(home, cli_profile or None) if action == "restart" else 60.0)
248
+ return control(action, home, cli_profile or None, ctx, quick_timeout=float(params.get("quick_timeout_seconds") or 2.0), background_wait=float(wait))
249
+
250
+ @registry.method("gateway.control", requires_agent=False)
251
+ def control_(ctx: CallContext, params: dict) -> dict:
252
+ return run(ctx, params, str(params.get("action") or ""))
253
+
254
+ @registry.method("gateway.restart", requires_agent=False)
255
+ def restart_(ctx: CallContext, params: dict) -> dict:
256
+ return run(ctx, params, "restart")
257
+
258
+ @registry.method("gateway.mode")
259
+ def mode_(ctx: CallContext, params: dict) -> dict:
260
+ name = params.get("name")
261
+ if not isinstance(name, str) or not name.strip():
262
+ raise InvalidParams("name is required")
263
+ return {"gateway_mode": gateway_mode(profile_home_param(params), name, params.get("is_default") is True)}