@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
@@ -14,9 +14,13 @@ import importlib
14
14
  import logging
15
15
  import multiprocessing
16
16
  import queue
17
+ import os
17
18
  import re
19
+ import signal
18
20
  import threading
19
21
  import time
22
+ from datetime import datetime
23
+ from functools import wraps
20
24
  from pathlib import Path
21
25
 
22
26
  from ..errors import InvalidParams, RpcError
@@ -31,6 +35,47 @@ _PASSTHROUGH_FIELDS = ("script", "no_agent", "context_from", "reasoning_effort",
31
35
  _RUNNING: dict[str, float] = {}
32
36
  _RUNNING_LOCK = threading.Lock()
33
37
  _SNAPSHOT_LOCK = threading.Lock()
38
+ # The Agent's fire claim lives FIRE_CLAIM_TTL_SECONDS (300); a run refreshes it well inside that, re-checks a missed
39
+ # refresh once before treating the claim as lost (as the Agent's own runs do), and gives up once no refresh has landed
40
+ # for most of the lease.
41
+ _CLAIM_HEARTBEAT_SECONDS = 60.0
42
+ _CLAIM_MISS_CONFIRM_SECONDS = 1.0
43
+ _CLAIM_HEARTBEAT_GRACE_SECONDS = 240.0
44
+ _CLAIM_LOST = "Another process took over this task's run claim, so this run stopped before saving or delivering anything"
45
+
46
+
47
+ class _ClaimLost(RuntimeError):
48
+ def __init__(self) -> None:
49
+ super().__init__(_CLAIM_LOST)
50
+
51
+
52
+ def _main_model_pin() -> tuple[str | None, str | None]:
53
+ """``(provider, model)`` the scoped profile's main agent runs on: the Agent's own ``cron.jobs._main_model_pin``.
54
+
55
+ Agent 0.21.3 predates that helper, so it is rebuilt from the readers 0.21.4 composes: the home's effective
56
+ ``config.yaml`` model and the provider the runtime resolves for it. ``(None, None)`` when no model is configured.
57
+ """
58
+ from cron import jobs
59
+
60
+ if hasattr(jobs, "_main_model_pin"):
61
+ return jobs._main_model_pin()
62
+ from hermes_cli.config_effective import load_user_config_effective
63
+ from hermes_constants import get_hermes_home
64
+
65
+ cfg_path = get_hermes_home() / "config.yaml"
66
+ cfg = load_user_config_effective(cfg_path) if cfg_path.exists() else {}
67
+ model_cfg = cfg.get("model") or {}
68
+ model = model_cfg.get("default") or model_cfg.get("model") if isinstance(model_cfg, dict) else model_cfg
69
+ model = model.strip() or None if isinstance(model, str) else None
70
+ if not model:
71
+ return None, None
72
+ provider = None
73
+ with contextlib.suppress(Exception):
74
+ from hermes_cli.runtime_provider import resolve_runtime_provider
75
+
76
+ resolved = resolve_runtime_provider(requested=None).get("provider")
77
+ provider = resolved.strip().lower() or None if isinstance(resolved, str) else None
78
+ return provider, model
34
79
 
35
80
 
36
81
  class CronUnavailable(RpcError):
@@ -68,6 +113,25 @@ def _job_ref(params: dict, key: str = "job_id") -> str:
68
113
  return ref
69
114
 
70
115
 
116
+ def _settled_mutation(method):
117
+ """Fence schedule edits against manual execution, including its retained recovery obligation."""
118
+ @wraps(method)
119
+ def guarded(ctx, params):
120
+ from .cron_execution import ManualExecution, Unverified
121
+ home, jobs = profile_home_param(params), _jobs()
122
+ with _store(home):
123
+ job = getattr(jobs, "resolve_job_ref", jobs.get_job)(_job_ref(params))
124
+ if job is None:
125
+ return method(ctx, params)
126
+ job_id = _job_id({"job_id": job["id"]})
127
+ execution = ManualExecution(home, home, {"id": job_id}, None)
128
+ with execution.acquire():
129
+ if execution.file.exists():
130
+ raise Unverified("Schedule edits and deletion are blocked until the manual execution is reconciled.")
131
+ return method(ctx, dict(params, job_id=job_id))
132
+ return guarded
133
+
134
+
71
135
  # ── job payload shaping ───────────────────────────────────────────────────
72
136
 
73
137
  def _monitor_storage(monitor) -> dict:
@@ -115,6 +179,87 @@ def job_for_api(job: dict) -> dict:
115
179
  return payload
116
180
 
117
181
 
182
+ # ── store health and foreign runs (TAL-810) ─────────────────────────────
183
+
184
+ def _store_health(cron_dir: Path) -> dict | None:
185
+ """The Agent's cross-process write probe: ``None`` when the store accepts writes or the Agent predates it."""
186
+ try:
187
+ from cron.store_health import probe_report
188
+ except ImportError:
189
+ return None
190
+ return probe_report(cron_dir)
191
+
192
+
193
+ def _live_executions(home: Path, job_ids: list[str], *, strict: bool = False) -> dict[str, dict]:
194
+ """Jobs whose latest attempt is claimed or running under a live owner: the gateway scheduler or another process.
195
+
196
+ The executions ledger lives under ``get_hermes_home()``, not the cron store override, so it is read in the
197
+ profile's scope. A store without a ledger never ran anything, and is not created by this read. An unreadable
198
+ ledger reads idle for a listing; ``strict`` (run admission) refuses instead, since it cannot prove the job idle."""
199
+ try:
200
+ from cron.executions import live_inflight_execution
201
+ except ImportError:
202
+ return {}
203
+ if not job_ids or not (Path(home).expanduser() / "cron" / "executions.db").is_file():
204
+ return {}
205
+ try:
206
+ with scoped_home(home):
207
+ return {job_id: record for job_id in job_ids if job_id and (record := live_inflight_execution(job_id))}
208
+ except Exception as exc: # noqa: BLE001 - an unreadable ledger cannot prove a run; the list still loads
209
+ if strict:
210
+ raise RpcError("Cannot tell whether this task is already running: its executions ledger is unreadable", condition="cron_ledger_unreadable") from exc
211
+ log.warning("Cron executions ledger unreadable in %s", home, exc_info=True)
212
+ return {}
213
+
214
+
215
+ def _claim_fire(store_home: Path, job: dict) -> dict | bool | None:
216
+ """The Agent's own run-now lease (``claim_job_for_fire(manual=True)``), which a gateway tick must also win.
217
+
218
+ The exact record the claim took (the one to run), ``False`` when another process holds the job's claim, or
219
+ ``None`` for a job the Agent does not claim (paused, disabled, or terminal), which no tick fires. ``mark_job_run``
220
+ releases a claim when the run ends; the run heartbeats it meanwhile."""
221
+ jobs = _jobs()
222
+ with _store(store_home):
223
+ if not jobs.is_job_runnable(job):
224
+ return None
225
+ claimed = jobs.claim_job_for_fire(job["id"], manual=True, return_job=True)
226
+ if not isinstance(claimed, dict):
227
+ # The claim only loses to a live owner's claim; any other refusal is a record the Agent never claims.
228
+ current = jobs.get_job(job["id"]) or {}
229
+ return False if isinstance(current.get("fire_claim"), dict) else None
230
+ return {key: value for key, value in claimed.items() if key != "_scheduled_instant"}
231
+
232
+
233
+ @contextlib.contextmanager
234
+ def _owner_fence(jobs, job_id: str, owner: str | None, *, protect_unclaimed=False):
235
+ """Hold the Agent fire fence across side effects; canonical unclaimed runs also exclude a successor claim."""
236
+ if owner is None and protect_unclaimed:
237
+ with jobs._fire_job_lock(job_id) as locked:
238
+ current = jobs.get_job(job_id) if locked else None
239
+ yield bool(current) and not current.get("fire_claim")
240
+ else:
241
+ with jobs.fire_claim_fence(job_id, expected_owner=owner) if owner else contextlib.nullcontext(True) as owns:
242
+ yield owns
243
+
244
+
245
+ def _mark_run(jobs, job_id: str, success: bool, error, owner: str | None, delivery_error=None) -> None:
246
+ """``mark_job_run`` fenced by the run's fire claim: a run that lost its claim records nothing over the new owner's."""
247
+ fence = {"expected_fire_owner": owner} if owner else {}
248
+ try:
249
+ jobs.mark_job_run(job_id, success, error, delivery_error=delivery_error, **fence)
250
+ except TypeError:
251
+ if owner:
252
+ raise
253
+ jobs.mark_job_run(job_id, success, error)
254
+
255
+
256
+ def _elapsed(record: dict) -> float:
257
+ try:
258
+ return round(max(0.0, time.time() - datetime.fromisoformat(str(record.get("claimed_at"))).timestamp()), 1)
259
+ except ValueError:
260
+ return 0.0
261
+
262
+
118
263
  # ── output files ──────────────────────────────────────────────────────────
119
264
 
120
265
  def _response_marker_index(text: str) -> int:
@@ -199,12 +344,20 @@ def _snippet(text: str, limit: int = 600) -> str:
199
344
 
200
345
  # ── manual runs ───────────────────────────────────────────────────────────
201
346
 
202
- def _child_main(job, execution_home, result_queue):
347
+ def _child_main(job, execution_home, result_queue, cancel_event=None, execution_id=None, recovering=False):
203
348
  try:
204
349
  from cron.scheduler import run_job
205
350
 
351
+ def execute():
352
+ if recovering:
353
+ from .cron_execution import recover
354
+ return recover(job, execution_id, cancel_event)
355
+ if execution_id is not None:
356
+ return run_job(job, cancel_event=cancel_event, execution_id=execution_id)
357
+ return run_job(job)
358
+
206
359
  if execution_home is None:
207
- result = run_job(job)
360
+ result = execute()
208
361
  else:
209
362
  from talaria_sidecar.home import scoped_home as _scoped
210
363
 
@@ -212,12 +365,16 @@ def _child_main(job, execution_home, result_queue):
212
365
 
213
366
  os.environ["HERMES_HOME"] = str(execution_home)
214
367
  with _scoped(Path(execution_home)):
215
- result = run_job(job)
216
- result_queue.put(("ok", result))
368
+ result = execute()
369
+ result_queue.put(("ok", result, {key: job[key] for key in ("_model_unreachable", "_quota_hold_seconds") if key in job}))
217
370
  except BaseException as exc: # noqa: BLE001 - surfaced in the parent
218
371
  import traceback
372
+ from .cron_execution import Unverified
219
373
 
220
- result_queue.put(("error", f"{type(exc).__name__}: {exc}", traceback.format_exc()))
374
+ if isinstance(exc, Unverified):
375
+ result_queue.put(("unverified", exc.detail))
376
+ else:
377
+ result_queue.put(("error", f"{type(exc).__name__}: {exc}", traceback.format_exc()))
221
378
 
222
379
 
223
380
  def _result_timeout(job) -> float:
@@ -234,44 +391,137 @@ def _result_timeout(job) -> float:
234
391
  return 6 * 60 * 60.0
235
392
 
236
393
 
237
- def _run_in_child(job, execution_home, ctx: CallContext):
394
+ def _worker(main, *args) -> None:
395
+ """A run's worker leads its own process group, so stopping the run also reaches every process it started."""
396
+ if hasattr(os, "setsid"):
397
+ os.setsid()
398
+ main(*args)
399
+
400
+
401
+ def _signal_group(process, sig) -> None:
402
+ with contextlib.suppress(ProcessLookupError, PermissionError):
403
+ os.killpg(process.pid, sig)
404
+
405
+
406
+ def _stop(process) -> None:
407
+ """End a run's worker and everything it started, returning only once the worker has exited: SIGTERM to the
408
+ worker's process group, then SIGKILL to whatever is left, so nothing keeps running after the run gives up its
409
+ claim or its running guard. Without process groups (Windows) only the worker itself is stopped."""
410
+ if not hasattr(os, "killpg"):
411
+ process.terminate()
412
+ process.join(timeout=5)
413
+ if process.is_alive():
414
+ process.kill()
415
+ process.join()
416
+ return
417
+ _signal_group(process, signal.SIGTERM)
418
+ process.join(timeout=5)
419
+ _signal_group(process, signal.SIGKILL)
420
+ process.join()
421
+
422
+
423
+ def _run_in_child(job, execution_home, ctx: CallContext, heartbeat=None, execution=None):
424
+ from .cron_execution import SETTLEMENT_SECONDS, Unverified
425
+
238
426
  ctx_mp = multiprocessing.get_context("spawn")
239
427
  result_queue = ctx_mp.Queue(maxsize=1)
240
- process = ctx_mp.Process(target=_child_main, args=(job, str(execution_home) if execution_home else None, result_queue))
241
- process.start()
428
+ cancel_event = ctx_mp.Event() if execution is not None else None
429
+
430
+ def start(recovering=False):
431
+ args = (job, str(execution_home) if execution_home else None, result_queue)
432
+ if execution is not None:
433
+ args += (cancel_event, execution.execution_id, recovering)
434
+ child = ctx_mp.Process(target=_worker, args=(_child_main, *args))
435
+ child.start()
436
+ return child
437
+
438
+ process = start(execution.recovering if execution else False)
242
439
  deadline = time.monotonic() + _result_timeout(job)
440
+ next_beat = time.monotonic() + _CLAIM_HEARTBEAT_SECONDS
243
441
  status, payload = "error", ["cron run subprocess failed before producing a result", ""]
442
+ settling = None
443
+ recovery_started = bool(execution and execution.recovering)
444
+
445
+ def cancel(reason):
446
+ nonlocal settling
447
+ if execution is None:
448
+ return
449
+ execution.cancel(reason)
450
+ cancel_event.set()
451
+ if settling is None:
452
+ settling = time.monotonic() + SETTLEMENT_SECONDS
453
+
454
+ def recover_child(detail):
455
+ nonlocal process, recovery_started, result_queue
456
+ process.join(timeout=5)
457
+ if process.is_alive():
458
+ _stop(process)
459
+ if recovery_started:
460
+ raise Unverified(detail)
461
+ recovery_started = True
462
+ cancel("submitter interrupted")
463
+ # A killed queue writer can leave a partial frame. Recovery gets a fresh pipe.
464
+ result_queue.close()
465
+ result_queue.join_thread()
466
+ result_queue = ctx_mp.Queue(maxsize=1)
467
+ process = start(True)
468
+
469
+ if execution and execution.record["cancel_reason"]:
470
+ cancel(execution.record["cancel_reason"])
244
471
  try:
245
472
  while True:
246
473
  try:
247
474
  status, *payload = result_queue.get(timeout=1.0)
248
- break
475
+ if execution is not None and status == "unverified":
476
+ raise Unverified(payload[0])
477
+ if execution is None or status == "ok":
478
+ break
479
+ # A transport failure is not a failed task. Recover the original identity once.
480
+ recover_child(payload[0])
249
481
  except queue.Empty:
482
+ if heartbeat is not None and time.monotonic() >= next_beat:
483
+ next_beat = time.monotonic() + _CLAIM_HEARTBEAT_SECONDS
484
+ if not heartbeat():
485
+ if execution is None:
486
+ _stop(process)
487
+ raise _ClaimLost()
488
+ cancel("claim_lost")
489
+ heartbeat = None
250
490
  if ctx.cancelled:
251
- process.terminate()
252
- process.join(timeout=5)
253
- status, payload = "cancelled", ["cancelled", ""]
254
- break
491
+ if execution is None:
492
+ _stop(process)
493
+ status, payload = "cancelled", ["cancelled", ""]
494
+ break
495
+ cancel("cancelled")
255
496
  if not process.is_alive():
256
497
  # The child may have put its result just before exiting; otherwise it died (OOM, signal) without one.
257
498
  try:
258
499
  status, *payload = result_queue.get_nowait()
259
500
  except queue.Empty:
260
501
  payload = [f"cron run subprocess exited with code {process.exitcode} without producing a result", ""]
502
+ if execution is not None and status != "ok":
503
+ recover_child(payload[0])
504
+ continue
261
505
  break
262
506
  if time.monotonic() >= deadline:
263
- process.terminate()
264
- process.join(timeout=5)
265
- payload = [f"cron run subprocess produced no result within {_result_timeout(job):g}s and was terminated", ""]
266
- break
507
+ if execution is None:
508
+ _stop(process)
509
+ payload = [f"cron run subprocess produced no result within {_result_timeout(job):g}s and was terminated", ""]
510
+ break
511
+ cancel("Manual task timed out")
512
+ if settling is not None and time.monotonic() >= settling:
513
+ raise Unverified("The owner has not provided a terminal receipt.")
267
514
  process.join(timeout=5)
268
515
  if process.is_alive():
269
- process.terminate()
270
- process.join(timeout=5)
516
+ _stop(process)
271
517
  finally:
518
+ if process.is_alive():
519
+ _stop(process)
272
520
  result_queue.close()
273
521
  result_queue.join_thread()
274
522
  if status == "ok":
523
+ if execution is not None and len(payload) > 1:
524
+ execution.job.update(payload[1])
275
525
  return payload[0]
276
526
  if status == "cancelled":
277
527
  raise RpcError("cancelled", condition="cancelled")
@@ -302,7 +552,32 @@ def _delivery_home(scheduler, home: Path):
302
552
  pass
303
553
 
304
554
 
305
- def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: CallContext) -> dict:
555
+ def _heartbeat(store_home, job_id, claim_owner):
556
+ jobs = _jobs()
557
+ last_beat = time.monotonic()
558
+
559
+ def heartbeat() -> bool:
560
+ """Refresh only this fire's claim, giving transient store errors less than a lease to recover."""
561
+ nonlocal last_beat
562
+ try:
563
+ with _store(store_home):
564
+ owned = jobs.heartbeat_fire_claim(job_id, expected_owner=claim_owner) is not False
565
+ if not owned:
566
+ time.sleep(_CLAIM_MISS_CONFIRM_SECONDS)
567
+ owned = jobs.heartbeat_fire_claim(job_id, expected_owner=claim_owner) is not False
568
+ except Exception: # noqa: BLE001
569
+ log.warning("Fire claim heartbeat failed for job %s", job_id, exc_info=True)
570
+ return time.monotonic() - last_beat < _CLAIM_HEARTBEAT_GRACE_SECONDS
571
+ if owned:
572
+ last_beat = time.monotonic()
573
+ return owned
574
+ return heartbeat
575
+
576
+
577
+ def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: CallContext, claim_owner: str | None = None, *, recovery_only=False) -> dict:
578
+ from . import cron_execution
579
+ if recovery_only or cron_execution.path(store_home, job["id"]).exists() or cron_execution.supported(job):
580
+ return cron_execution.run(job, store_home, execution_home, ctx, claim_owner, recovery_only=recovery_only)
306
581
  jobs = _jobs()
307
582
  scheduler = importlib.import_module("cron.scheduler")
308
583
  silent_marker = getattr(scheduler, "SILENT_MARKER", "[SILENT]")
@@ -311,8 +586,12 @@ def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: C
311
586
  execution_home = execution_home or store_home
312
587
  outcome: dict = {"job_id": job_id, "status": "completed"}
313
588
  try:
314
- success, output, final_response, error = _run_in_child(job, execution_home, ctx)
315
- with _store(store_home), _delivery_home(scheduler, store_home):
589
+ heartbeat = _heartbeat(store_home, job_id, claim_owner)
590
+ success, output, final_response, error = _run_in_child(job, execution_home, ctx, heartbeat if claim_owner else None)
591
+ with _store(store_home), _owner_fence(jobs, job_id, claim_owner) as owns, contextlib.ExitStack() as delivery:
592
+ if not owns:
593
+ raise _ClaimLost()
594
+ delivery.enter_context(_delivery_home(scheduler, store_home))
316
595
  jobs.save_job_output(job_id, output)
317
596
  content = final_response if success else f"⚠️ Cron job '{job.get('name', job_id)}' failed:\n{error}"
318
597
  should_deliver = bool(content) and not (success and silent_marker in content.strip().upper())
@@ -324,15 +603,12 @@ def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: C
324
603
  delivery_error = str(exc)
325
604
  if success and not final_response:
326
605
  success, error = False, "Agent completed but produced empty response (model error, timeout, or misconfiguration)"
327
- try:
328
- jobs.mark_job_run(job_id, success, error, delivery_error=delivery_error)
329
- except TypeError:
330
- jobs.mark_job_run(job_id, success, error)
606
+ _mark_run(jobs, job_id, success, error, claim_owner, delivery_error)
331
607
  outcome.update(success=bool(success), error=error, delivery_error=delivery_error)
332
608
  except RpcError:
333
609
  with _store(store_home):
334
610
  try:
335
- jobs.mark_job_run(job_id, False, "cancelled")
611
+ _mark_run(jobs, job_id, False, "cancelled", claim_owner)
336
612
  except Exception: # noqa: BLE001
337
613
  pass
338
614
  raise
@@ -340,7 +616,7 @@ def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: C
340
616
  log.exception("Manual cron run failed for job %s", job_id)
341
617
  with _store(store_home):
342
618
  try:
343
- jobs.mark_job_run(job_id, False, str(exc))
619
+ _mark_run(jobs, job_id, False, str(exc), claim_owner)
344
620
  except Exception: # noqa: BLE001
345
621
  pass
346
622
  outcome.update(status="failed", success=False, error=str(exc))
@@ -353,17 +629,79 @@ def run_tracked(job: dict, store_home: Path, execution_home: Path | None, ctx: C
353
629
  # ── methods ───────────────────────────────────────────────────────────────
354
630
 
355
631
  def register(registry) -> None:
632
+ @registry.method("cron.blueprints")
633
+ def blueprints(ctx: CallContext, params: dict) -> dict:
634
+ try:
635
+ catalog = importlib.import_module("cron.blueprint_catalog")
636
+ except ModuleNotFoundError as exc:
637
+ if exc.name != "cron.blueprint_catalog":
638
+ raise
639
+ return {"blueprints": []}
640
+ with scoped_home(profile_home_param(params)):
641
+ rows = []
642
+ for blueprint in catalog.list_blueprints():
643
+ entry = catalog.blueprint_catalog_entry(blueprint)
644
+ schema = catalog.blueprint_form_schema(blueprint)
645
+ slots = [{**slot, "default": str(slot["default"]) if slot.get("default") is not None else None,
646
+ "options": [str(value) for value in slot.get("options", [])]} for slot in schema["fields"]]
647
+ rows.append({key: entry[key] for key in ("key", "title", "description", "category", "source", "plugin")})
648
+ rows[-1].update(schedule_label=entry["scheduleHuman"], slots=slots)
649
+ return {"blueprints": rows}
650
+
651
+ @registry.method("cron.blueprint_fill")
652
+ def blueprint_fill(ctx: CallContext, params: dict) -> dict:
653
+ key, values = params.get("key"), params.get("values")
654
+ if not isinstance(key, str) or not key.strip() or not isinstance(values, dict) or not all(isinstance(v, str) for v in values.values()):
655
+ raise InvalidParams("key and string slot values are required")
656
+ try:
657
+ catalog = importlib.import_module("cron.blueprint_catalog")
658
+ except ModuleNotFoundError as exc:
659
+ if exc.name != "cron.blueprint_catalog":
660
+ raise
661
+ raise InvalidParams("Automation blueprints are unavailable") from exc
662
+ with scoped_home(profile_home_param(params)):
663
+ blueprint = catalog.get_blueprint(key)
664
+ if blueprint is None:
665
+ raise InvalidParams("Unknown automation blueprint")
666
+ try:
667
+ draft = catalog.fill_blueprint(blueprint, values)
668
+ except catalog.BlueprintFillError as exc:
669
+ raise InvalidParams(str(exc)) from exc
670
+ draft.setdefault("skills", [])
671
+ return {"draft": draft}
672
+
356
673
  @registry.method("cron.list")
357
674
  def list_(ctx: CallContext, params: dict) -> dict:
358
- """Jobs of one store, shaped for the API; the server merges profiles."""
675
+ """Jobs of one store, shaped for the API; the server merges profiles. ``probe_store`` adds the store's write health."""
359
676
  jobs = _jobs()
360
- with _store(profile_home_param(params)):
361
- return {"jobs": [job_for_api(job) for job in jobs.list_jobs(include_disabled=True)]}
677
+ home = profile_home_param(params)
678
+ with _store(home):
679
+ from .cron_execution import pending_jobs
680
+ pending = {job["id"]: job for job in pending_jobs(home)}
681
+ stored = jobs.list_jobs(include_disabled=True)
682
+ ids = {job["id"] for job in stored}
683
+ stored.extend(job for jid, job in pending.items() if jid not in ids)
684
+ listed = [job_for_api(job) for job in stored]
685
+ result: dict = {"jobs": listed}
686
+ if params.get("probe_store"):
687
+ result["store_health"] = _store_health(jobs.get_cron_output_dir().parent)
688
+ live = _live_executions(home, [str(job.get("id") or "") for job in listed])
689
+ with _RUNNING_LOCK:
690
+ active = set(_RUNNING)
691
+ for index, job in enumerate(listed):
692
+ jid = str(job.get("id") or "")
693
+ if jid in pending and jid not in live and jid not in active:
694
+ # Web preflight must use the accepted fire's profile, even after external job edits.
695
+ job = listed[index] = job_for_api(pending[jid])
696
+ job["running_elsewhere"] = jid in live
697
+ return result
362
698
 
363
699
  @registry.method("cron.get")
364
700
  def get(ctx: CallContext, params: dict) -> dict:
365
- with _store(profile_home_param(params)):
366
- job = _jobs().get_job(_job_id(params))
701
+ from .cron_execution import pending_job
702
+ home, job_id = profile_home_param(params), _job_id(params)
703
+ with _store(home):
704
+ job = pending_job(home, job_id) or _jobs().get_job(job_id)
367
705
  return {"job": job_for_api(job) if job else None}
368
706
 
369
707
  @registry.method("cron.create")
@@ -396,8 +734,6 @@ def register(registry) -> None:
396
734
  # to the store home; owner_profile only preserves management from the creating Web profile.
397
735
  if post.get("profile") and execution_home and not (model and provider) and not body.get("no_agent"):
398
736
  try:
399
- from cron.jobs import _main_model_pin
400
-
401
737
  with _SNAPSHOT_LOCK, scoped_home(Path(execution_home)):
402
738
  profile_provider, profile_model = _main_model_pin()
403
739
  if not profile_model:
@@ -417,6 +753,7 @@ def register(registry) -> None:
417
753
  return {"job": job_for_api(job)}
418
754
 
419
755
  @registry.method("cron.update")
756
+ @_settled_mutation
420
757
  def update(ctx: CallContext, params: dict) -> dict:
421
758
  jobs = _jobs()
422
759
  job_id = _job_ref(params)
@@ -445,6 +782,7 @@ def register(registry) -> None:
445
782
  return {"job": job_for_api(job)}
446
783
 
447
784
  @registry.method("cron.delete")
785
+ @_settled_mutation
448
786
  def delete(ctx: CallContext, params: dict) -> dict:
449
787
  job_id = _job_ref(params)
450
788
  with _store(profile_home_param(params)):
@@ -462,6 +800,7 @@ def register(registry) -> None:
462
800
  return {"job": dict(job)}
463
801
 
464
802
  @registry.method("cron.resume")
803
+ @_settled_mutation
465
804
  def resume(ctx: CallContext, params: dict) -> dict:
466
805
  with _store(profile_home_param(params)):
467
806
  try:
@@ -478,17 +817,37 @@ def register(registry) -> None:
478
817
  store_home = profile_home_param(params)
479
818
  execution_home = Path(params["execution_home"]).expanduser() if params.get("execution_home") else None
480
819
  job_id = _job_id(params)
820
+ from .cron_execution import path
821
+ if path(store_home, job_id).exists():
822
+ return run_tracked({"id": job_id}, store_home, execution_home, ctx, recovery_only=True)
481
823
  with _store(store_home):
482
824
  job = _jobs().get_job(job_id)
483
825
  if not job:
484
826
  raise RpcError("Job not found", condition="not_found")
827
+ foreign = _live_executions(store_home, [job_id], strict=True).get(job_id)
828
+ if foreign is not None:
829
+ return {"job_id": job_id, "status": "already_running", "elapsed": _elapsed(foreign)}
485
830
  with _RUNNING_LOCK:
486
831
  started = _RUNNING.get(job_id)
487
832
  if started is not None:
488
833
  return {"job_id": job_id, "status": "already_running", "elapsed": round(time.time() - started, 1)}
489
834
  _RUNNING[job_id] = time.time()
835
+ try:
836
+ claimed = _claim_fire(store_home, job)
837
+ except BaseException:
838
+ with _RUNNING_LOCK:
839
+ _RUNNING.pop(job_id, None)
840
+ raise
841
+ if claimed is False:
842
+ with _RUNNING_LOCK:
843
+ _RUNNING.pop(job_id, None)
844
+ return {"job_id": job_id, "status": "already_running", "elapsed": 0.0}
845
+ owner = None
846
+ if claimed:
847
+ job = claimed
848
+ owner = str((claimed.get("fire_claim") or {}).get("by") or "") or None
490
849
  ctx.emit("started", {"job_id": job_id})
491
- return run_tracked(job, store_home, execution_home, ctx)
850
+ return run_tracked(job, store_home, execution_home, ctx, owner)
492
851
 
493
852
  @registry.method("cron.status", requires_agent=False)
494
853
  def status(ctx: CallContext, params: dict) -> dict: