@maudecode/talaria-web 1.18.0 → 1.20.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 (774) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.js +16 -16
  3. package/dist/api/auth-raw.js.map +1 -1
  4. package/dist/api/automation-raw.d.ts +2 -2
  5. package/dist/api/automation-raw.js +93 -38
  6. package/dist/api/automation-raw.js.map +1 -1
  7. package/dist/api/automation-router.d.ts +99 -17
  8. package/dist/api/automation-router.js +22 -14
  9. package/dist/api/automation-router.js.map +1 -1
  10. package/dist/api/chat-router.d.ts +1433 -37
  11. package/dist/api/chat-router.js +205 -90
  12. package/dist/api/chat-router.js.map +1 -1
  13. package/dist/api/git-router.d.ts +6 -2
  14. package/dist/api/git-router.js +30 -30
  15. package/dist/api/git-router.js.map +1 -1
  16. package/dist/api/raw-routes.d.ts +3 -3
  17. package/dist/api/raw-routes.js +76 -62
  18. package/dist/api/raw-routes.js.map +1 -1
  19. package/dist/api/router.d.ts +5 -5
  20. package/dist/api/router.js +22 -22
  21. package/dist/api/router.js.map +1 -1
  22. package/dist/api/session-visibility.d.ts +4 -4
  23. package/dist/api/session-visibility.js +26 -16
  24. package/dist/api/session-visibility.js.map +1 -1
  25. package/dist/api/sessions-router.d.ts +2635 -151
  26. package/dist/api/sessions-router.js +193 -177
  27. package/dist/api/sessions-router.js.map +1 -1
  28. package/dist/api/settings-router.d.ts +450 -11
  29. package/dist/api/settings-router.js +102 -49
  30. package/dist/api/settings-router.js.map +1 -1
  31. package/dist/api/sse-routes.d.ts +2 -2
  32. package/dist/api/sse-routes.js +102 -80
  33. package/dist/api/sse-routes.js.map +1 -1
  34. package/dist/api/tools-raw.d.ts +5 -0
  35. package/dist/api/tools-raw.js +58 -33
  36. package/dist/api/tools-raw.js.map +1 -1
  37. package/dist/api/tools-router.d.ts +963 -109
  38. package/dist/api/tools-router.js +426 -72
  39. package/dist/api/tools-router.js.map +1 -1
  40. package/dist/api/transcribe-stream.d.ts +16 -0
  41. package/dist/api/transcribe-stream.js +172 -0
  42. package/dist/api/transcribe-stream.js.map +1 -0
  43. package/dist/app.d.ts +4 -1
  44. package/dist/app.js +80 -57
  45. package/dist/app.js.map +1 -1
  46. package/dist/auth/crypto.d.ts +3 -0
  47. package/dist/auth/crypto.js +18 -0
  48. package/dist/auth/crypto.js.map +1 -1
  49. package/dist/auth/gate.js +14 -9
  50. package/dist/auth/gate.js.map +1 -1
  51. package/dist/auth/passkeys.d.ts +15 -14
  52. package/dist/auth/passkeys.js +89 -91
  53. package/dist/auth/passkeys.js.map +1 -1
  54. package/dist/auth/store.d.ts +17 -7
  55. package/dist/auth/store.js +93 -52
  56. package/dist/auth/store.js.map +1 -1
  57. package/dist/bin/talaria-web.js +12 -81
  58. package/dist/bin/talaria-web.js.map +1 -1
  59. package/dist/cli/ctl.js +8 -2
  60. package/dist/cli/ctl.js.map +1 -1
  61. package/dist/cli/dotenv.d.ts +5 -0
  62. package/dist/cli/dotenv.js +14 -2
  63. package/dist/cli/dotenv.js.map +1 -1
  64. package/dist/cli/launcher.js +5 -5
  65. package/dist/cli/launcher.js.map +1 -1
  66. package/dist/cli/main.d.ts +1 -0
  67. package/dist/cli/main.js +90 -0
  68. package/dist/cli/main.js.map +1 -0
  69. package/dist/cli/supervise.d.ts +24 -0
  70. package/dist/cli/supervise.js +147 -6
  71. package/dist/cli/supervise.js.map +1 -1
  72. package/dist/config/agent-config.d.ts +37 -11
  73. package/dist/config/agent-config.js +146 -64
  74. package/dist/config/agent-config.js.map +1 -1
  75. package/dist/config.d.ts +2 -0
  76. package/dist/config.js +14 -5
  77. package/dist/config.js.map +1 -1
  78. package/dist/fs/atomic.d.ts +4 -0
  79. package/dist/fs/atomic.js +19 -2
  80. package/dist/fs/atomic.js.map +1 -1
  81. package/dist/fs/stat.d.ts +7 -0
  82. package/dist/fs/stat.js +27 -0
  83. package/dist/fs/stat.js.map +1 -0
  84. package/dist/http/context.d.ts +28 -9
  85. package/dist/http/context.js +77 -25
  86. package/dist/http/context.js.map +1 -1
  87. package/dist/onboarding.d.ts +2 -2
  88. package/dist/onboarding.js +10 -8
  89. package/dist/onboarding.js.map +1 -1
  90. package/dist/profiles/profiles.d.ts +12 -4
  91. package/dist/profiles/profiles.js +78 -30
  92. package/dist/profiles/profiles.js.map +1 -1
  93. package/dist/projects.d.ts +15 -6
  94. package/dist/projects.js +55 -47
  95. package/dist/projects.js.map +1 -1
  96. package/dist/providers/catalog.d.ts +85 -14
  97. package/dist/providers/catalog.js +502 -146
  98. package/dist/providers/catalog.js.map +1 -1
  99. package/dist/providers/env-file.d.ts +10 -3
  100. package/dist/providers/env-file.js +24 -75
  101. package/dist/providers/env-file.js.map +1 -1
  102. package/dist/providers/runtime-env.d.ts +4 -2
  103. package/dist/providers/runtime-env.js +9 -6
  104. package/dist/providers/runtime-env.js.map +1 -1
  105. package/dist/providers/tables.d.ts +3 -1
  106. package/dist/providers/tables.js +4 -2
  107. package/dist/providers/tables.js.map +1 -1
  108. package/dist/redact.js +56 -23
  109. package/dist/redact.js.map +1 -1
  110. package/dist/release.d.ts +7 -0
  111. package/dist/release.js +19 -3
  112. package/dist/release.js.map +1 -1
  113. package/dist/runtime.d.ts +5 -1
  114. package/dist/runtime.js +303 -96
  115. package/dist/runtime.js.map +1 -1
  116. package/dist/server.js +31 -18
  117. package/dist/server.js.map +1 -1
  118. package/dist/sessions/anchor.d.ts +11 -4
  119. package/dist/sessions/anchor.js +33 -37
  120. package/dist/sessions/anchor.js.map +1 -1
  121. package/dist/sessions/background-events.d.ts +2 -0
  122. package/dist/sessions/background-events.js +10 -0
  123. package/dist/sessions/background-events.js.map +1 -0
  124. package/dist/sessions/background-tasks.d.ts +49 -23
  125. package/dist/sessions/background-tasks.js +161 -89
  126. package/dist/sessions/background-tasks.js.map +1 -1
  127. package/dist/sessions/background-updates.d.ts +15 -8
  128. package/dist/sessions/background-updates.js +101 -25
  129. package/dist/sessions/background-updates.js.map +1 -1
  130. package/dist/sessions/cli-sessions.d.ts +23 -6
  131. package/dist/sessions/cli-sessions.js +84 -84
  132. package/dist/sessions/cli-sessions.js.map +1 -1
  133. package/dist/sessions/completions.d.ts +38 -2
  134. package/dist/sessions/completions.js +348 -77
  135. package/dist/sessions/completions.js.map +1 -1
  136. package/dist/sessions/compress.d.ts +8 -0
  137. package/dist/sessions/compress.js +2 -2
  138. package/dist/sessions/compress.js.map +1 -1
  139. package/dist/sessions/drafts.d.ts +14 -10
  140. package/dist/sessions/drafts.js +38 -43
  141. package/dist/sessions/drafts.js.map +1 -1
  142. package/dist/sessions/export.d.ts +2 -0
  143. package/dist/sessions/export.js +6 -2
  144. package/dist/sessions/export.js.map +1 -1
  145. package/dist/sessions/gateway-sessions.d.ts +86 -0
  146. package/dist/sessions/gateway-sessions.js +914 -0
  147. package/dist/sessions/gateway-sessions.js.map +1 -0
  148. package/dist/sessions/gateway-watcher.d.ts +10 -11
  149. package/dist/sessions/gateway-watcher.js +25 -21
  150. package/dist/sessions/gateway-watcher.js.map +1 -1
  151. package/dist/sessions/goals.d.ts +31 -0
  152. package/dist/sessions/goals.js +75 -0
  153. package/dist/sessions/goals.js.map +1 -0
  154. package/dist/sessions/journal.d.ts +5 -79
  155. package/dist/sessions/journal.js +15 -606
  156. package/dist/sessions/journal.js.map +1 -1
  157. package/dist/sessions/list.d.ts +34 -18
  158. package/dist/sessions/list.js +161 -147
  159. package/dist/sessions/list.js.map +1 -1
  160. package/dist/sessions/merge.d.ts +77 -13
  161. package/dist/sessions/merge.js +315 -65
  162. package/dist/sessions/merge.js.map +1 -1
  163. package/dist/sessions/notices.d.ts +22 -0
  164. package/dist/sessions/notices.js +72 -0
  165. package/dist/sessions/notices.js.map +1 -0
  166. package/dist/sessions/pending.d.ts +10 -4
  167. package/dist/sessions/pending.js +83 -7
  168. package/dist/sessions/pending.js.map +1 -1
  169. package/dist/sessions/prefix-index.d.ts +85 -0
  170. package/dist/sessions/prefix-index.js +243 -0
  171. package/dist/sessions/prefix-index.js.map +1 -0
  172. package/dist/sessions/recovery.d.ts +4 -7
  173. package/dist/sessions/recovery.js +100 -283
  174. package/dist/sessions/recovery.js.map +1 -1
  175. package/dist/sessions/relay.d.ts +20 -20
  176. package/dist/sessions/relay.js +205 -127
  177. package/dist/sessions/relay.js.map +1 -1
  178. package/dist/sessions/service.d.ts +251 -78
  179. package/dist/sessions/service.js +1510 -564
  180. package/dist/sessions/service.js.map +1 -1
  181. package/dist/sessions/session.d.ts +20 -2
  182. package/dist/sessions/session.js +33 -5
  183. package/dist/sessions/session.js.map +1 -1
  184. package/dist/sessions/shares.d.ts +7 -6
  185. package/dist/sessions/shares.js +22 -45
  186. package/dist/sessions/shares.js.map +1 -1
  187. package/dist/sessions/source-kind.js +1 -0
  188. package/dist/sessions/source-kind.js.map +1 -1
  189. package/dist/sessions/state-db-identity.d.ts +18 -0
  190. package/dist/sessions/state-db-identity.js +67 -0
  191. package/dist/sessions/state-db-identity.js.map +1 -0
  192. package/dist/sessions/state-db-worker.js +24 -5
  193. package/dist/sessions/state-db-worker.js.map +1 -1
  194. package/dist/sessions/state-db.d.ts +157 -21
  195. package/dist/sessions/state-db.js +1145 -204
  196. package/dist/sessions/state-db.js.map +1 -1
  197. package/dist/sessions/state-saved.d.ts +1 -1
  198. package/dist/sessions/state-saved.js +35 -11
  199. package/dist/sessions/state-saved.js.map +1 -1
  200. package/dist/sessions/storage/backend.d.ts +171 -0
  201. package/dist/sessions/storage/backend.js +19 -0
  202. package/dist/sessions/storage/backend.js.map +1 -0
  203. package/dist/sessions/storage/conformance.d.ts +20 -0
  204. package/dist/sessions/storage/conformance.js +237 -0
  205. package/dist/sessions/storage/conformance.js.map +1 -0
  206. package/dist/sessions/storage/document.d.ts +16 -0
  207. package/dist/sessions/storage/document.js +44 -0
  208. package/dist/sessions/storage/document.js.map +1 -0
  209. package/dist/sessions/storage/json-documents.d.ts +25 -0
  210. package/dist/sessions/storage/json-documents.js +277 -0
  211. package/dist/sessions/storage/json-documents.js.map +1 -0
  212. package/dist/sessions/storage/json-files.d.ts +23 -0
  213. package/dist/sessions/storage/json-files.js +244 -0
  214. package/dist/sessions/storage/json-files.js.map +1 -0
  215. package/dist/sessions/storage/json-installation.d.ts +3 -0
  216. package/dist/sessions/storage/json-installation.js +31 -0
  217. package/dist/sessions/storage/json-installation.js.map +1 -0
  218. package/dist/sessions/storage/json-journal.d.ts +113 -0
  219. package/dist/sessions/storage/json-journal.js +756 -0
  220. package/dist/sessions/storage/json-journal.js.map +1 -0
  221. package/dist/sessions/storage/json-recovery.d.ts +4 -0
  222. package/dist/sessions/storage/json-recovery.js +195 -0
  223. package/dist/sessions/storage/json-recovery.js.map +1 -0
  224. package/dist/sessions/storage/json.d.ts +82 -0
  225. package/dist/sessions/storage/json.js +350 -0
  226. package/dist/sessions/storage/json.js.map +1 -0
  227. package/dist/sessions/storage/row.d.ts +6 -0
  228. package/dist/sessions/storage/row.js +49 -0
  229. package/dist/sessions/storage/row.js.map +1 -0
  230. package/dist/sessions/storage/search-text.d.ts +4 -0
  231. package/dist/sessions/storage/search-text.js +16 -0
  232. package/dist/sessions/storage/search-text.js.map +1 -0
  233. package/dist/sessions/store.d.ts +67 -73
  234. package/dist/sessions/store.js +341 -607
  235. package/dist/sessions/store.js.map +1 -1
  236. package/dist/sessions/streams.d.ts +12 -7
  237. package/dist/sessions/streams.js +58 -15
  238. package/dist/sessions/streams.js.map +1 -1
  239. package/dist/sessions/titles.js +1 -1
  240. package/dist/sessions/titles.js.map +1 -1
  241. package/dist/sessions/todo.d.ts +5 -0
  242. package/dist/sessions/todo.js +28 -21
  243. package/dist/sessions/todo.js.map +1 -1
  244. package/dist/sessions/tool-display.d.ts +8 -5
  245. package/dist/sessions/tool-display.js +28 -16
  246. package/dist/sessions/tool-display.js.map +1 -1
  247. package/dist/sessions/turn-context.d.ts +10 -1
  248. package/dist/sessions/turn-context.js +13 -6
  249. package/dist/sessions/turn-context.js.map +1 -1
  250. package/dist/sessions/turn.d.ts +110 -22
  251. package/dist/sessions/turn.js +918 -380
  252. package/dist/sessions/turn.js.map +1 -1
  253. package/dist/sessions/wakeup-pause.d.ts +8 -13
  254. package/dist/sessions/wakeup-pause.js +31 -49
  255. package/dist/sessions/wakeup-pause.js.map +1 -1
  256. package/dist/sessions/window.js +13 -12
  257. package/dist/sessions/window.js.map +1 -1
  258. package/dist/settings.d.ts +19 -7
  259. package/dist/settings.js +179 -183
  260. package/dist/settings.js.map +1 -1
  261. package/dist/sidecar/client.d.ts +41 -4
  262. package/dist/sidecar/client.js +192 -42
  263. package/dist/sidecar/client.js.map +1 -1
  264. package/dist/sidecar/discover.d.ts +2 -1
  265. package/dist/sidecar/discover.js +93 -10
  266. package/dist/sidecar/discover.js.map +1 -1
  267. package/dist/sidecar/env.d.ts +4 -0
  268. package/dist/sidecar/env.js +3 -0
  269. package/dist/sidecar/env.js.map +1 -0
  270. package/dist/sidecar/fake.d.ts +6 -2
  271. package/dist/sidecar/fake.js +34 -4
  272. package/dist/sidecar/fake.js.map +1 -1
  273. package/dist/spa.d.ts +9 -4
  274. package/dist/spa.js +36 -12
  275. package/dist/spa.js.map +1 -1
  276. package/dist/startup-timing.d.ts +66 -0
  277. package/dist/startup-timing.js +129 -0
  278. package/dist/startup-timing.js.map +1 -0
  279. package/dist/test/env-file.d.ts +5 -0
  280. package/dist/test/env-file.js +88 -0
  281. package/dist/test/env-file.js.map +1 -0
  282. package/dist/test/fs-handles.d.ts +11 -0
  283. package/dist/test/fs-handles.js +31 -0
  284. package/dist/test/fs-handles.js.map +1 -0
  285. package/dist/test/harness.d.ts +1 -0
  286. package/dist/test/harness.js +1 -1
  287. package/dist/test/harness.js.map +1 -1
  288. package/dist/test/passkeys.d.ts +22 -0
  289. package/dist/test/passkeys.js +62 -0
  290. package/dist/test/passkeys.js.map +1 -0
  291. package/dist/test/storage.d.ts +15 -0
  292. package/dist/test/storage.js +26 -0
  293. package/dist/test/storage.js.map +1 -0
  294. package/dist/tools/commands.d.ts +6 -1
  295. package/dist/tools/commands.js +8 -2
  296. package/dist/tools/commands.js.map +1 -1
  297. package/dist/tools/connectors.d.ts +33 -0
  298. package/dist/tools/connectors.js +121 -0
  299. package/dist/tools/connectors.js.map +1 -0
  300. package/dist/tools/crons.d.ts +46 -5
  301. package/dist/tools/crons.js +100 -26
  302. package/dist/tools/crons.js.map +1 -1
  303. package/dist/tools/dashboard-plugins.d.ts +34 -5
  304. package/dist/tools/dashboard-plugins.js +146 -14
  305. package/dist/tools/dashboard-plugins.js.map +1 -1
  306. package/dist/tools/extensions.d.ts +5 -4
  307. package/dist/tools/extensions.js +213 -207
  308. package/dist/tools/extensions.js.map +1 -1
  309. package/dist/tools/gateway-restart.d.ts +82 -0
  310. package/dist/tools/gateway-restart.js +222 -0
  311. package/dist/tools/gateway-restart.js.map +1 -0
  312. package/dist/tools/health.d.ts +28 -4
  313. package/dist/tools/health.js +171 -50
  314. package/dist/tools/health.js.map +1 -1
  315. package/dist/tools/hygiene.d.ts +4 -3
  316. package/dist/tools/hygiene.js +23 -9
  317. package/dist/tools/hygiene.js.map +1 -1
  318. package/dist/tools/insights.d.ts +1 -1
  319. package/dist/tools/insights.js +2 -2
  320. package/dist/tools/insights.js.map +1 -1
  321. package/dist/tools/kanban.d.ts +4 -2
  322. package/dist/tools/kanban.js +28 -19
  323. package/dist/tools/kanban.js.map +1 -1
  324. package/dist/tools/mcp-health.js +74 -24
  325. package/dist/tools/mcp-health.js.map +1 -1
  326. package/dist/tools/mcp.js +19 -1
  327. package/dist/tools/mcp.js.map +1 -1
  328. package/dist/tools/memory-masks.d.ts +5 -0
  329. package/dist/tools/memory-masks.js +256 -0
  330. package/dist/tools/memory-masks.js.map +1 -0
  331. package/dist/tools/memory.d.ts +29 -3
  332. package/dist/tools/memory.js +69 -27
  333. package/dist/tools/memory.js.map +1 -1
  334. package/dist/tools/plugin-oauth.d.ts +14 -0
  335. package/dist/tools/plugin-oauth.js +53 -0
  336. package/dist/tools/plugin-oauth.js.map +1 -0
  337. package/dist/tools/plugin-settings.d.ts +15 -0
  338. package/dist/tools/plugin-settings.js +90 -0
  339. package/dist/tools/plugin-settings.js.map +1 -0
  340. package/dist/tools/prune-advice.d.ts +50 -0
  341. package/dist/tools/prune-advice.js +114 -0
  342. package/dist/tools/prune-advice.js.map +1 -0
  343. package/dist/tools/skills.d.ts +13 -5
  344. package/dist/tools/skills.js +53 -19
  345. package/dist/tools/skills.js.map +1 -1
  346. package/dist/tools/update-notifications.d.ts +102 -17
  347. package/dist/tools/update-notifications.js +400 -82
  348. package/dist/tools/update-notifications.js.map +1 -1
  349. package/dist/tools/update-rollback.d.ts +11 -0
  350. package/dist/tools/update-rollback.js +28 -0
  351. package/dist/tools/update-rollback.js.map +1 -0
  352. package/dist/tools/updates.d.ts +100 -33
  353. package/dist/tools/updates.js +1085 -345
  354. package/dist/tools/updates.js.map +1 -1
  355. package/dist/util.d.ts +2 -0
  356. package/dist/util.js +5 -0
  357. package/dist/util.js.map +1 -1
  358. package/dist/workspace/git.d.ts +16 -1
  359. package/dist/workspace/git.js +291 -11
  360. package/dist/workspace/git.js.map +1 -1
  361. package/dist/workspace/media.d.ts +12 -9
  362. package/dist/workspace/media.js +98 -38
  363. package/dist/workspace/media.js.map +1 -1
  364. package/dist/workspace/preview.js +4 -14
  365. package/dist/workspace/preview.js.map +1 -1
  366. package/dist/workspace/rollback.d.ts +8 -4
  367. package/dist/workspace/rollback.js +22 -12
  368. package/dist/workspace/rollback.js.map +1 -1
  369. package/dist/workspace/workspaces.d.ts +15 -19
  370. package/dist/workspace/workspaces.js +55 -127
  371. package/dist/workspace/workspaces.js.map +1 -1
  372. package/dist/workspace/worktrees.js +170 -32
  373. package/dist/workspace/worktrees.js.map +1 -1
  374. package/package.json +6 -4
  375. package/sidecar/agent_dependency.json +4 -4
  376. package/sidecar/agent_support.json +29 -0
  377. package/sidecar/scripts/record-fixtures.py +12 -3
  378. package/sidecar/scripts/replay_sidecar.py +8 -0
  379. package/sidecar/scripts/suite_runner.py +458 -0
  380. package/sidecar/scripts/test.sh +11 -5
  381. package/sidecar/talaria_sidecar/__init__.py +6 -1
  382. package/sidecar/talaria_sidecar/__main__.py +13 -4
  383. package/sidecar/talaria_sidecar/agent_mutation.py +1111 -0
  384. package/sidecar/talaria_sidecar/agent_services.py +1116 -0
  385. package/sidecar/talaria_sidecar/context_detail.py +59 -0
  386. package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
  387. package/sidecar/talaria_sidecar/home.py +133 -14
  388. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  389. package/sidecar/talaria_sidecar/methods/aux.py +211 -41
  390. package/sidecar/talaria_sidecar/methods/chat.py +1378 -200
  391. package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
  392. package/sidecar/talaria_sidecar/methods/commands.py +483 -13
  393. package/sidecar/talaria_sidecar/methods/config.py +195 -6
  394. package/sidecar/talaria_sidecar/methods/connectors.py +375 -0
  395. package/sidecar/talaria_sidecar/methods/cron.py +374 -36
  396. package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
  397. package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
  398. package/sidecar/talaria_sidecar/methods/gateway.py +215 -55
  399. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +418 -0
  400. package/sidecar/talaria_sidecar/methods/goals.py +81 -41
  401. package/sidecar/talaria_sidecar/methods/kanban.py +82 -30
  402. package/sidecar/talaria_sidecar/methods/memory.py +84 -0
  403. package/sidecar/talaria_sidecar/methods/oauth.py +144 -13
  404. package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
  405. package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
  406. package/sidecar/talaria_sidecar/methods/process.py +229 -16
  407. package/sidecar/talaria_sidecar/methods/profiles.py +48 -9
  408. package/sidecar/talaria_sidecar/methods/providers.py +196 -26
  409. package/sidecar/talaria_sidecar/methods/runtime.py +71 -1
  410. package/sidecar/talaria_sidecar/methods/skills.py +83 -5
  411. package/sidecar/talaria_sidecar/methods/state_db.py +405 -112
  412. package/sidecar/talaria_sidecar/methods/stt.py +159 -78
  413. package/sidecar/talaria_sidecar/methods/text.py +14 -1
  414. package/sidecar/talaria_sidecar/methods/toolsets.py +51 -0
  415. package/sidecar/talaria_sidecar/methods/usage.py +113 -14
  416. package/sidecar/talaria_sidecar/methods/worktree.py +145 -19
  417. package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
  418. package/sidecar/talaria_sidecar/rpc.py +70 -7
  419. package/sidecar/talaria_sidecar/runtime.py +122 -14
  420. package/sidecar/talaria_sidecar/speed.py +47 -0
  421. package/sidecar/talaria_sidecar/startup_timing.py +119 -0
  422. package/static/dist/FILES.txt +242 -215
  423. package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
  424. package/static/dist/assets/AppShell-3isgvoro.js +8 -0
  425. package/static/dist/assets/Button-f4n6dapf.js +1 -0
  426. package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
  427. package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
  428. package/static/dist/assets/Dialog-hta5utr3.js +1 -0
  429. package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
  430. package/static/dist/assets/Field-b6ez48oz.js +1 -0
  431. package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
  432. package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
  433. package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
  434. package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
  435. package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
  436. package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
  437. package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
  438. package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
  439. package/static/dist/assets/Select-kr07928w.js +1 -0
  440. package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
  441. package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
  442. package/static/dist/assets/States-icab19yg.js +1 -0
  443. package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
  444. package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
  445. package/static/dist/assets/_app-ga1moszl.js +1 -0
  446. package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
  447. package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
  448. package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
  449. package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
  450. package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
  451. package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
  452. package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
  453. package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
  454. package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
  455. package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
  456. package/static/dist/assets/_app.skills-n016rihd.js +1 -0
  457. package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
  458. package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
  459. package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
  460. package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
  461. package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
  462. package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
  463. package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
  464. package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
  465. package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
  466. package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
  467. package/static/dist/assets/archive-il7c6053.js +1 -0
  468. package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
  469. package/static/dist/assets/back-gu3nu279.js +1 -0
  470. package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
  471. package/static/dist/assets/{blockDiagram-I7D4REHJ-jzthp1wa.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
  472. package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
  473. package/static/dist/assets/{c4Diagram-7LVT6UL2-mwr67mt8.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
  474. package/static/dist/assets/cancel-bsnfo36u.js +1 -0
  475. package/static/dist/assets/channel-dsbtk01y.js +1 -0
  476. package/static/dist/assets/{chunk-4HAMMTFA-gpq1y228.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
  477. package/static/dist/assets/{chunk-75Z2AOVW-ktje58c4.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
  478. package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
  479. package/static/dist/assets/{chunk-F27PBJKO-cmev85ti.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
  480. package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
  481. package/static/dist/assets/{chunk-GMAD6QVW-htjgt22y.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
  482. package/static/dist/assets/{chunk-GVQU2GXP-qndvoxl7.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
  483. package/static/dist/assets/{chunk-IMKFNOWR-e5bkd5q6.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
  484. package/static/dist/assets/{chunk-L3NEJ4N5-mj1155r6.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
  485. package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
  486. package/static/dist/assets/{chunk-P2QGCYS3-f7t54xfm.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
  487. package/static/dist/assets/{chunk-POPQ4Y6H-i927v9oa.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
  488. package/static/dist/assets/{chunk-PWAF6VOD-m84lmnyt.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
  489. package/static/dist/assets/{chunk-SHT3W25Y-mi0tlcu9.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
  490. package/static/dist/assets/{chunk-SVP7TREG-dnau702j.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
  491. package/static/dist/assets/{chunk-TICWLB2K-edem1gkg.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
  492. package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
  493. package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
  494. package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
  495. package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
  496. package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
  497. package/static/dist/assets/copied-bz3gkazh.js +1 -0
  498. package/static/dist/assets/copy-jo006coq.js +1 -0
  499. package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
  500. package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
  501. package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
  502. package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
  503. package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
  504. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
  505. package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
  506. package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
  507. package/static/dist/assets/dagre-o5jmjz46.js +1 -0
  508. package/static/dist/assets/{diagram-S7CK7UJ4-l8abxaes.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
  509. package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
  510. package/static/dist/assets/{diagram-VSXAHHWV-fnky6lvc.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
  511. package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
  512. package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
  513. package/static/dist/assets/dist-crc46di9.js +7 -0
  514. package/static/dist/assets/dist-etpoyscq.js +44 -0
  515. package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
  516. package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
  517. package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
  518. package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
  519. package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
  520. package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
  521. package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
  522. package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
  523. package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
  524. package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
  525. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ngbsyp37.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
  526. package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
  527. package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
  528. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-eravdti4.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
  529. package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
  530. package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
  531. package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
  532. package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
  533. package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
  534. package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
  535. package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
  536. package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
  537. package/static/dist/assets/host-fr6lni1p.js +1 -0
  538. package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
  539. package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
  540. package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
  541. package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
  542. package/static/dist/assets/index-73649w7o.js +10 -0
  543. package/static/dist/assets/index-lhf1fsdf.css +2 -0
  544. package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
  545. package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
  546. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-i6ypq351.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
  547. package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
  548. package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
  549. package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
  550. package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
  551. package/static/dist/assets/{journeyDiagram-3NMN7TZE-lduf1z95.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
  552. package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
  553. package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
  554. package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
  555. package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
  556. package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
  557. package/static/dist/assets/{kanban-definition-UXKFOSKX-e18bu4pg.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
  558. package/static/dist/assets/katex-cbdd1ohw.css +1 -0
  559. package/static/dist/assets/katex-pbij2oo6.js +257 -0
  560. package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
  561. package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
  562. package/static/dist/assets/{line-meizqsvn.js → line-mtr5k6il.js} +1 -1
  563. package/static/dist/assets/linear-jlwrvvy6.js +1 -0
  564. package/static/dist/assets/link-iadw4ghw.js +2 -0
  565. package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
  566. package/static/dist/assets/locale-cs-n41hptse.js +5 -0
  567. package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
  568. package/static/dist/assets/locale-es-ih078lco.js +5 -0
  569. package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
  570. package/static/dist/assets/locale-it-kb96jufy.js +5 -0
  571. package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
  572. package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
  573. package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
  574. package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
  575. package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
  576. package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
  577. package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
  578. package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
  579. package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
  580. package/static/dist/assets/login-lkhtzfcl.js +1 -0
  581. package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
  582. package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
  583. package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
  584. package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
  585. package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
  586. package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
  587. package/static/dist/assets/{mindmap-definition-YA3MSWOX-27b26eok.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
  588. package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
  589. package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
  590. package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
  591. package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
  592. package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
  593. package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
  594. package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
  595. package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
  596. package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
  597. package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
  598. package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
  599. package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
  600. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-hhue7d8o.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
  601. package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
  602. package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
  603. package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
  604. package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
  605. package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
  606. package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
  607. package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
  608. package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
  609. package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
  610. package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
  611. package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
  612. package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
  613. package/static/dist/assets/registry-g7w3353u.js +1 -0
  614. package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
  615. package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
  616. package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
  617. package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
  618. package/static/dist/assets/runtime-nlxpnegr.js +1 -0
  619. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-fqb8xcjf.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
  620. package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
  621. package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
  622. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-dlcz6qjz.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
  623. package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
  624. package/static/dist/assets/share._token-o76ki0ly.js +2 -0
  625. package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
  626. package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
  627. package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
  628. package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
  629. package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
  630. package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
  631. package/static/dist/assets/src-h89s0hs4.js +1 -0
  632. package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
  633. package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
  634. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
  635. package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
  636. package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
  637. package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
  638. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
  639. package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
  640. package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
  641. package/static/dist/assets/{timeline-definition-24CTP7MA-b1vl2zil.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
  642. package/static/dist/assets/toast-liparz2j.js +56 -0
  643. package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
  644. package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
  645. package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
  646. package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
  647. package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
  648. package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
  649. package/static/dist/assets/untitled-mj86r4gz.js +1 -0
  650. package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
  651. package/static/dist/assets/useForm-o4uu74mx.js +1 -0
  652. package/static/dist/assets/{useParams-b4dq6n4q.js → useParams-ftsveu84.js} +1 -1
  653. package/static/dist/assets/{useSearch-ipfmv3he.js → useSearch-crqueqga.js} +1 -1
  654. package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
  655. package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
  656. package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
  657. package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
  658. package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
  659. package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
  660. package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
  661. package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
  662. package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
  663. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-e49523ab.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
  664. package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
  665. package/static/dist/extension-sdk.js +1 -1
  666. package/static/dist/index.html +1 -1
  667. package/static/dist/sw.js +180 -136
  668. package/static/dist/assets/AppShell-g3bkgnvt.js +0 -1
  669. package/static/dist/assets/ChatPage-oo7bdan6.js +0 -25
  670. package/static/dist/assets/Dialog-g355qxhp.js +0 -1
  671. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +0 -1
  672. package/static/dist/assets/HubRoute-lmkrbw47.js +0 -2
  673. package/static/dist/assets/Markdown-j4kut4j2.js +0 -598
  674. package/static/dist/assets/Markdown-lwviulmg.css +0 -1
  675. package/static/dist/assets/RightPanel-ohlsdzfl.js +0 -1
  676. package/static/dist/assets/Select-kddq75qc.js +0 -1
  677. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +0 -1
  678. package/static/dist/assets/SettingsLayout-o9bv4mgb.js +0 -1
  679. package/static/dist/assets/States-eq6fe29m.js +0 -1
  680. package/static/dist/assets/Toaster-pc247u9f.js +0 -1
  681. package/static/dist/assets/_app-f8n1i0m1.js +0 -1
  682. package/static/dist/assets/_app.ext._extensionId-gfbzpoou.js +0 -1
  683. package/static/dist/assets/_app.index-bzzv80hk.js +0 -1
  684. package/static/dist/assets/_app.insights-dzixbryi.js +0 -1
  685. package/static/dist/assets/_app.kanban-c6hep2us.js +0 -1
  686. package/static/dist/assets/_app.logs-i69qds98.js +0 -1
  687. package/static/dist/assets/_app.memory-kl4vf0gd.js +0 -1
  688. package/static/dist/assets/_app.profiles-b8zinmyp.js +0 -1
  689. package/static/dist/assets/_app.session._sessionId-fn1bhde6.js +0 -1
  690. package/static/dist/assets/_app.settings-cg8plwmf.js +0 -1
  691. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +0 -3
  692. package/static/dist/assets/_app.skills-cs47sbvs.js +0 -1
  693. package/static/dist/assets/_app.tasks-dwvk64tc.js +0 -1
  694. package/static/dist/assets/_app.todos-c8xjm8p3.js +0 -1
  695. package/static/dist/assets/_app.workspaces-ehvd1gds.js +0 -1
  696. package/static/dist/assets/abnfDiagram-VCTEODGH-c8kdixke.js +0 -1
  697. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +0 -1
  698. package/static/dist/assets/architectureDiagram-5GKGNRK7-cku06x1p.js +0 -36
  699. package/static/dist/assets/back-h7aun7ew.js +0 -1
  700. package/static/dist/assets/cancel-fhi0vcnu.js +0 -1
  701. package/static/dist/assets/channel-b2zo73z1.js +0 -1
  702. package/static/dist/assets/chunk-DU6HZSFF-ofajdixh.js +0 -128
  703. package/static/dist/assets/chunk-OSK3NFVY-e7gkikcw.js +0 -10
  704. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +0 -1
  705. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +0 -1
  706. package/static/dist/assets/copied-hcplv46p.js +0 -1
  707. package/static/dist/assets/copy-magtofyp.js +0 -1
  708. package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
  709. package/static/dist/assets/cynefin-OW5HDTMX-siqlulkl.js +0 -1
  710. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-i648tah9.js +0 -62
  711. package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
  712. package/static/dist/assets/dagre-GXQ25YYZ-mwn6uc17.js +0 -4
  713. package/static/dist/assets/dagre-lkoknbjn.js +0 -1
  714. package/static/dist/assets/diagram-UQ7AKVKN-mekq7zgq.js +0 -41
  715. package/static/dist/assets/diagram-VX7I27RA-xe6r5qfw.js +0 -24
  716. package/static/dist/assets/diagram-Z3DM3KII-nq07dg7k.js +0 -24
  717. package/static/dist/assets/ebnfDiagram-PWID7BFC-c62ipm3g.js +0 -1
  718. package/static/dist/assets/endpoints-klbxod4t.js +0 -64
  719. package/static/dist/assets/erDiagram-RLTQ6QDP-h39kmnzy.js +0 -99
  720. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +0 -1
  721. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +0 -1
  722. package/static/dist/assets/gitGraph-4MIJSDKK-fr2i7caa.js +0 -1
  723. package/static/dist/assets/highlighted-body-KPVGNVTW-jwpop9p8.js +0 -1
  724. package/static/dist/assets/host-i828xuo7.js +0 -1
  725. package/static/dist/assets/index-eh8zilnv.css +0 -2
  726. package/static/dist/assets/index-r648f4o1.js +0 -11
  727. package/static/dist/assets/info-A6RAGUB7-h11x0dxo.js +0 -1
  728. package/static/dist/assets/infoDiagram-27XIBGKW-dfh47dw9.js +0 -2
  729. package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +0 -1
  730. package/static/dist/assets/linear-becwlnhk.js +0 -1
  731. package/static/dist/assets/link-oot2yn73.js +0 -1
  732. package/static/dist/assets/login-gu28l9ou.js +0 -1
  733. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +0 -1
  734. package/static/dist/assets/mermaid-parser.core-ntne45i9.js +0 -7
  735. package/static/dist/assets/not-found-ccnnac39.js +0 -1
  736. package/static/dist/assets/onboarding-lkphzu1s.js +0 -1
  737. package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +0 -1
  738. package/static/dist/assets/packet-AYTQ26CC-l6jdka8p.js +0 -1
  739. package/static/dist/assets/pegDiagram-XKGWAZYB-g1er3bxj.js +0 -1
  740. package/static/dist/assets/pie-WAS4IAKB-i0ylcrt9.js +0 -1
  741. package/static/dist/assets/pieDiagram-E7YTZNPT-fbz1e55z.js +0 -39
  742. package/static/dist/assets/radar-RG4KPBEZ-f9b70aa4.js +0 -1
  743. package/static/dist/assets/railroad-74A4TZTK-lgsuoeda.js +0 -1
  744. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +0 -1
  745. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +0 -1
  746. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +0 -1
  747. package/static/dist/assets/railroadDiagram-O6MQD6OU-eqpy9fnz.js +0 -1
  748. package/static/dist/assets/refresh-abtu93ke.js +0 -1
  749. package/static/dist/assets/refresh-cw-hlwb02qv.js +0 -1
  750. package/static/dist/assets/requirementDiagram-BXWQKSXE-ogf6puhi.js +0 -84
  751. package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
  752. package/static/dist/assets/root-mbklpuv4.js +0 -1
  753. package/static/dist/assets/saved-girnabkf.js +0 -1
  754. package/static/dist/assets/settings_section_extensions_title-h14cwk6f.js +0 -1
  755. package/static/dist/assets/share._token-kl0r7ido.js +0 -2
  756. package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
  757. package/static/dist/assets/sliders-horizontal-n1zm34qq.js +0 -1
  758. package/static/dist/assets/src-hjyib4mu.js +0 -1
  759. package/static/dist/assets/sse-iwku0sjo.js +0 -1
  760. package/static/dist/assets/stateDiagram-D77RDMKH-fyoznpcs.js +0 -1
  761. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +0 -1
  762. package/static/dist/assets/swimlanes-42K2YHIH-oefldhfy.js +0 -1
  763. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +0 -8
  764. package/static/dist/assets/todoStore-jjr5yr78.js +0 -1
  765. package/static/dist/assets/treeView-Q6P3EWNA-f7hcci5o.js +0 -1
  766. package/static/dist/assets/treemap-WGGIJYW6-nl85071a.js +0 -1
  767. package/static/dist/assets/units-kgxzxfhi.js +0 -1
  768. package/static/dist/assets/untitled-cikfjntn.js +0 -1
  769. package/static/dist/assets/useForm-kqoqg0k9.js +0 -1
  770. package/static/dist/assets/useLocale-o59tbiea.js +0 -1
  771. package/static/dist/assets/vennDiagram-4TSXK5OY-h5hzbk0n.js +0 -34
  772. package/static/dist/assets/wardley-WFR3VGLG-jftvpm3e.js +0 -1
  773. package/static/dist/assets/wardleyDiagram-VM6X3IG4-hedh85ge.js +0 -78
  774. /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
@@ -0,0 +1,1111 @@
1
+ """A lifetime-leased Agent update supervisor, extending dependency_sync's child custody.
2
+
3
+ Agent code is loaded only in the separate worker, before source changes. The native
4
+ checkout lock and gateway journal also fence the backend recovery companion.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import ast
9
+ import contextlib
10
+ import hashlib
11
+ import importlib
12
+ import json
13
+ import os
14
+ from pathlib import Path
15
+ import queue
16
+ import re
17
+ import subprocess
18
+ import sys
19
+ import tempfile
20
+ import threading
21
+ import time
22
+ import uuid
23
+
24
+ from .dependency_sync import _stop_tree
25
+ from .errors import InvalidParams, RpcError
26
+ from . import agent_services as services
27
+
28
+ _SESSIONS = {}
29
+ _SESSIONS_LOCK = threading.Lock()
30
+ _CONSENTS = {}
31
+ CONSENT_SECONDS = 300
32
+ # One lease exchange, including the worker's unwind after a refused drain.
33
+ LEASE_SECONDS = 330
34
+
35
+
36
+ def _close(session):
37
+ with session["close_lock"]:
38
+ if session["closed"]:
39
+ return
40
+ session["closed"] = True
41
+ with contextlib.suppress(OSError):
42
+ session["proc"].stdin.close()
43
+ try:
44
+ session["proc"].wait(timeout=330)
45
+ except subprocess.TimeoutExpired:
46
+ # The native pause journal still owns recovery; never fabricate a successful resume.
47
+ session["proc"].kill()
48
+ session["proc"].wait()
49
+ session["stderr"].close()
50
+ session["proc"].stdout.close()
51
+ session["gateway_lock"].release()
52
+
53
+
54
+ def close_all():
55
+ with _SESSIONS_LOCK:
56
+ sessions = list(_SESSIONS.values())
57
+ _SESSIONS.clear()
58
+ for session in sessions:
59
+ _close(session)
60
+
61
+
62
+ def _reap_sessions():
63
+ with _SESSIONS_LOCK:
64
+ dead = [token for token, session in _SESSIONS.items() if session['proc'].poll() is not None]
65
+ sessions = [_SESSIONS.pop(token) for token in dead]
66
+ for session in sessions:
67
+ _close(session)
68
+
69
+
70
+ def _git_directory(root):
71
+ """Locate receipts before importing possibly torn Agent modules."""
72
+ gitdir = root / '.git'
73
+ if gitdir.is_file():
74
+ value = gitdir.read_text().strip()
75
+ if not value.startswith('gitdir: '):
76
+ raise OSError('The Agent checkout git directory is unreadable.')
77
+ gitdir = (root / value[8:]).resolve()
78
+ return gitdir
79
+
80
+
81
+ def _native_source_repair(root):
82
+ marker = _git_directory(root) / 'hermes-update-pull'
83
+ if not marker.exists():
84
+ return
85
+ with marker.open(encoding='utf-8-sig') as handle:
86
+ raw = handle.read(65537)
87
+ fields = dict(line.partition('=')[::2] for line in raw.splitlines())
88
+ if len(raw) > 65536 or any(not re.fullmatch(r'[a-f0-9]{40}', fields.get(key, '')) for key in ('pre', 'target')):
89
+ raise RuntimeError('Native source recovery receipt is malformed; its evidence is retained.')
90
+ executable = fields.get('git', '')
91
+ executable = executable if os.path.isabs(executable) and Path(executable).is_file() else 'git'
92
+ head = subprocess.run([executable, '--no-replace-objects', '-C', str(root), 'rev-parse', 'HEAD'], capture_output=True, text=True, timeout=15, check=True).stdout.strip()
93
+ if head not in {fields['pre'], fields['target']} or not fields.get('pid', '').isdecimal():
94
+ raise RuntimeError('Native source recovery no longer matches the recorded checkout identity; its receipt is retained.')
95
+ # Read the native launch-repair procedure from the immutable baseline, never the damaged tree.
96
+ def constants(path, names):
97
+ found = subprocess.run([executable, '--no-replace-objects', '-C', str(root), 'show', fields['pre'] + ':' + path],
98
+ capture_output=True, text=True, timeout=15, check=True)
99
+ values = {}
100
+ for node in ast.parse(found.stdout).body:
101
+ if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].id in names:
102
+ values[node.targets[0].id] = ast.literal_eval(node.value)
103
+ if values.keys() != names:
104
+ raise RuntimeError('The recorded Agent baseline has no complete native launch-repair procedure.')
105
+ return values
106
+ template = constants('hermes_cli/_launchers.py', {'_CLOSURE_REPAIR'})['_CLOSURE_REPAIR']
107
+ keys = {'INTERRUPTED_PULL_MARKER', 'RECOVERY_CLOSURE_DIR', 'RECOVERY_CLOSURE_MANIFEST', 'RECOVERY_CLOSURE_INIT', 'RECOVERY_CLOSURE'}
108
+ values = constants('hermes_cli/_early_recovery.py', keys)
109
+ for placeholder, key in (('__MARKER__', 'INTERRUPTED_PULL_MARKER'), ('__CLOSURE_DIR__', 'RECOVERY_CLOSURE_DIR'),
110
+ ('__MANIFEST__', 'RECOVERY_CLOSURE_MANIFEST'), ('__INIT__', 'RECOVERY_CLOSURE_INIT'), ('__CLOSURE__', 'RECOVERY_CLOSURE')):
111
+ template = template.replace(placeholder, repr(values[key]))
112
+ template = template.replace('__ROOT__', repr(str(root)))
113
+ namespace = {'os': os, 're': re, 'sys': sys}
114
+ exec(compile(template, '<native recorded launch repair>', 'exec'), namespace)
115
+ try:
116
+ namespace['_hermes_closure_repair']() # Native verification, checkout custody and re-exec remain authoritative.
117
+ except SystemExit:
118
+ if not marker.exists():
119
+ # A proved no-op removes the receipt without changing files; discard the closure imports just as after a restore.
120
+ sys.modules['hermes_cli._early_recovery'].relaunch_after_restore()
121
+ raise
122
+
123
+
124
+ def _receipt_candidates(path, pattern, cache):
125
+ """Cache discovery, never receipt presence; missing paths watch their nearest existing ancestor."""
126
+ key = (path, pattern)
127
+ if cache is not None:
128
+ ancestor = path
129
+ while True:
130
+ try:
131
+ stat = ancestor.stat()
132
+ break
133
+ except FileNotFoundError:
134
+ if ancestor == ancestor.parent:
135
+ raise
136
+ ancestor = ancestor.parent
137
+ stamp = (ancestor, stat.st_dev, stat.st_ino, stat.st_mode, stat.st_mtime_ns, stat.st_ctime_ns, stat.st_size)
138
+ now = time.monotonic()
139
+ previous = cache.get(key)
140
+ if previous is not None and previous[0] == stamp and 0 <= now - previous[1] < 1:
141
+ return previous[2]
142
+ if pattern is not None:
143
+ try:
144
+ # glob suppresses enumeration errors, which cannot authorize Agent admission.
145
+ with os.scandir(path) as children:
146
+ found = tuple(path / child.name for child in children if Path(child.name).match(pattern))
147
+ except FileNotFoundError:
148
+ found = ()
149
+ else:
150
+ try:
151
+ found = (path,) if any(' refs/talaria/autostash/pending/' in line for line in path.read_text().splitlines()) else ()
152
+ except FileNotFoundError:
153
+ found = ()
154
+ if cache is not None:
155
+ if len(cache) >= 16:
156
+ cache.clear() # Bound retained paths if an installer repeatedly redirects Git metadata.
157
+ cache[key] = (stamp, now, found)
158
+ return found
159
+
160
+
161
+ def _process_presence(pid):
162
+ """A stdlib rearm trigger only; native identity verification alone decides recovery."""
163
+ if os.name == 'nt' or pid <= 0:
164
+ return 'unknown'
165
+ try:
166
+ os.kill(pid, 0)
167
+ except ProcessLookupError:
168
+ return 'exited'
169
+ except PermissionError:
170
+ pass
171
+ except (OSError, OverflowError):
172
+ return 'unknown'
173
+ return 'present'
174
+
175
+
176
+ def recovery_receipts(runtime, *, watch=False, discovery_cache=None):
177
+ """Read only the configured checkout's receipts without importing its possibly torn code."""
178
+ root, home = runtime.agent_dir, runtime.hermes_home
179
+ if root is None:
180
+ return [], None
181
+ root = root.resolve()
182
+ state = os.environ.get('TALARIA_SIDECAR_WEB_STATE_DIR') or os.environ.get('HERMES_WEBUI_STATE_DIR', '').strip() or str(home / 'webui')
183
+ marker = Path(state).expanduser() / 'agent-dependency-sync' / hashlib.sha256(str(root).encode()).hexdigest()
184
+ key = hashlib.sha256(os.path.normcase(str(root)).encode()).hexdigest()[:12]
185
+ native = home / ('.hermes-update-paused-gateways.' + key + '.json')
186
+ gitdir = _git_directory(root)
187
+ paths = [services.recovery_path(root, home), marker, native,
188
+ root / '.update-incomplete', root / '.lazy-refresh-incomplete',
189
+ gitdir / 'hermes-update-pull', root / '.hermes-update-zip-swap']
190
+ paths.extend(_receipt_candidates(home, native.name + '.*.claim', discovery_cache))
191
+ common = gitdir
192
+ try:
193
+ common = (gitdir / (gitdir / 'commondir').read_text().strip()).resolve()
194
+ except FileNotFoundError:
195
+ pass
196
+ paths.extend(_receipt_candidates(common / 'refs/talaria/autostash/pending', '*', discovery_cache))
197
+ packed = common / 'packed-refs'
198
+ paths.extend(_receipt_candidates(packed, None, discovery_cache))
199
+ def stamps(candidates):
200
+ result = []
201
+ for path in sorted(set(candidates)):
202
+ try:
203
+ stat = path.lstat()
204
+ result.append((str(path), stat.st_ino, stat.st_mtime_ns, stat.st_size))
205
+ except FileNotFoundError:
206
+ pass
207
+ return result
208
+ found = stamps(paths)
209
+ if watch and found:
210
+ # A repaired source/index or interpreter selection can make previously blocked debt provable again.
211
+ watched = [gitdir / 'HEAD', gitdir / 'index', root / '.venv/pyvenv.cfg', root / 'venv/pyvenv.cfg',
212
+ root / '.venv/bin/python', root / 'venv/bin/python',
213
+ home / 'installs' / hashlib.sha256(str(root).encode()).hexdigest()[:16] / 'facts.json']
214
+ try:
215
+ head = (gitdir / 'HEAD').read_text().strip()
216
+ if head.startswith('ref: '):
217
+ ref = Path(head[5:])
218
+ if not ref.is_absolute() and '..' not in ref.parts:
219
+ watched.append(common / ref)
220
+ except (OSError, UnicodeError):
221
+ pass
222
+ lifecycle = []
223
+ try:
224
+ body = json.loads(services.recovery_path(root, home).read_text())
225
+ for baseline in body.get('tree', {}).get('baselines', []):
226
+ changed = [*baseline.get('dirty_at_pause', []), *baseline.get('move_paths', [])]
227
+ changed.extend(path for paths in baseline.get('landed', {}).values() for path in paths)
228
+ watched.extend(root / path for path in changed if isinstance(path, str) and not Path(path).is_absolute() and '..' not in Path(path).parts)
229
+ # A retained gateway's exit or successor rearms exhausted recovery without changing any receipt.
230
+ for entry in body.get('entries', []):
231
+ if entry.get('purpose') == 'gateway' and isinstance(entry.get('home'), str):
232
+ watched.append(Path(entry['home']) / 'gateway.pid')
233
+ pids = {**entry.get('retired_processes', {}), **entry.get('processes', {})}
234
+ lifecycle.extend(('process', int(pid), _process_presence(int(pid))) for pid in sorted(pids, key=int))
235
+ except (OSError, ValueError, TypeError, AttributeError):
236
+ pass # The receipt's own stamp still rearms corrected evidence; the worker reports its exact refusal.
237
+ found.extend(stamps(watched))
238
+ found.extend(lifecycle)
239
+ return found, marker
240
+
241
+
242
+ def recover_retained(runtime, ctx, *, probe=False, complete_revision=None):
243
+ """One leased continuation. UpdateService owns the bounded retry sequence."""
244
+ receipts, _ = recovery_receipts(runtime, watch=True)
245
+ fingerprint = hashlib.sha256(json.dumps(receipts).encode()).hexdigest() if receipts else ''
246
+ result = {'ok': not receipts, 'owed': bool(receipts), 'fingerprint': fingerprint, 'detail': ''}
247
+ if complete_revision is not None:
248
+ if not isinstance(complete_revision, str) or not re.fullmatch(r'[a-f0-9]{40}', complete_revision):
249
+ raise InvalidParams('A verified loaded revision is required to complete recovery.')
250
+ if not runtime.loaded or runtime.revision != complete_revision or runtime.is_stale():
251
+ return {**result, 'ok': False, 'detail': 'The loaded Agent runtime does not match the recovered revision.'}
252
+ if probe or not receipts:
253
+ return result
254
+ _reap_sessions()
255
+ from .methods.gateway import _ACTION_LOCK
256
+ if not _ACTION_LOCK.acquire(blocking=False):
257
+ return {**result, 'detail': 'A gateway lifecycle action or Agent update still owns recovery.'}
258
+ from .home import _frozen_launch_env
259
+ root = runtime.agent_dir.resolve()
260
+ env = _frozen_launch_env()
261
+ env.update(HERMES_HOME=str(runtime.hermes_home), HERMES_DISABLE_LAZY_INSTALLS='1',
262
+ PYTHONPATH=str(Path(__file__).parents[1]) + os.pathsep + str(root))
263
+ try:
264
+ if complete_revision is None:
265
+ runtime.retire()
266
+ with tempfile.TemporaryFile() as stdout, tempfile.TemporaryFile() as stderr:
267
+ child = subprocess.Popen([sys.executable, '-m', 'talaria_sidecar.agent_mutation', '--recover-current', str(root), complete_revision or ''],
268
+ cwd=root, env=env, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
269
+ try:
270
+ deadline = time.monotonic() + 660
271
+ while child.poll() is None:
272
+ ctx.check_cancelled()
273
+ if time.monotonic() >= deadline:
274
+ raise RuntimeError('Agent recovery timed out; durable recovery remains owed.')
275
+ ctx.cancel_event.wait(.05)
276
+ stdout.seek(0)
277
+ reply = json.loads(stdout.read(1024 * 1024))
278
+ if not isinstance(reply, dict) or not isinstance(reply.get('ok'), bool):
279
+ raise ValueError('Invalid Agent recovery result.')
280
+ if not reply['ok']:
281
+ stderr.seek(0, os.SEEK_END)
282
+ stderr.seek(max(0, stderr.tell() - 4000))
283
+ diagnostic = stderr.read().decode('utf-8', errors='replace').strip()
284
+ if diagnostic:
285
+ reply['detail'] = str(reply.get('detail') or '') + ': ' + diagnostic
286
+ finally:
287
+ child.stdin.close()
288
+ try:
289
+ child.wait(timeout=330)
290
+ except subprocess.TimeoutExpired:
291
+ child.kill()
292
+ child.wait()
293
+ remaining, _ = recovery_receipts(runtime, watch=True)
294
+ return {**result, **reply, 'owed': bool(remaining),
295
+ 'fingerprint': hashlib.sha256(json.dumps(remaining).encode()).hexdigest() if remaining else ''}
296
+ finally:
297
+ _ACTION_LOCK.release()
298
+
299
+
300
+ def _drain_progress(result):
301
+ progress = result.get("progress") if isinstance(result, dict) and result.keys() == {"progress"} else None
302
+ if (not isinstance(progress, dict) or progress.get("phase") != "gateway_drain"
303
+ or not isinstance(progress.get("deadline_seconds"), (int, float)) or isinstance(progress["deadline_seconds"], bool)
304
+ or not 0 <= progress["deadline_seconds"] <= services.GATEWAY_DRAIN_CEILING
305
+ or not isinstance(progress.get("services"), list) or not all(isinstance(name, str) for name in progress["services"])):
306
+ return None
307
+ return {"phase": "gateway_drain", "deadline_seconds": progress["deadline_seconds"], "services": progress["services"]}
308
+
309
+
310
+ def _exchange(session, request, ctx):
311
+ with session["lock"]:
312
+ if request is not None:
313
+ session["proc"].stdin.write(json.dumps(request) + "\n")
314
+ session["proc"].stdin.flush()
315
+ deadline = time.monotonic() + LEASE_SECONDS
316
+ while True:
317
+ ctx.check_cancelled()
318
+ if time.monotonic() >= deadline:
319
+ raise RpcError("Agent writer lease timed out; recovery remains recorded.", condition="agent_update_unsafe")
320
+ try:
321
+ line = session["output"].get(timeout=.1)
322
+ except queue.Empty:
323
+ continue
324
+ if line is None:
325
+ raise RpcError("Agent writer lease exited; its pause journal retains recovery.", condition="agent_update_unsafe")
326
+ result = json.loads(line)
327
+ if isinstance(result, dict) and "progress" in result:
328
+ progress = _drain_progress(result)
329
+ if progress is None:
330
+ raise RpcError("Invalid Agent writer lease response.", condition="agent_update_unsafe")
331
+ # The worker's own drain deadline is bounded; unwind keeps the ordinary lease margin.
332
+ deadline = max(deadline, time.monotonic() + progress["deadline_seconds"] + LEASE_SECONDS)
333
+ ctx.emit("progress", progress)
334
+ continue
335
+ if not isinstance(result, dict) or not isinstance(result.get("ok"), bool):
336
+ raise RpcError("Invalid Agent writer lease response.", condition="agent_update_unsafe")
337
+ return {"ok": result["ok"], "skipped": result.get("skipped") is True, "detail": str(result.get("detail") or ""),
338
+ **({"refresh_confirmation": result["refresh_confirmation"]} if "refresh_confirmation" in result else {})}
339
+
340
+
341
+ def call(runtime, ctx, params):
342
+ root = runtime.agent_dir
343
+ expected = params.get("agent_dir")
344
+ if root is None or not isinstance(expected, str) or not expected or Path(expected).resolve() != root.resolve():
345
+ raise InvalidParams("agent_dir must match the sidecar's configured Agent checkout")
346
+ action = params.get("action")
347
+ if action not in {"begin", "git", "sync", "finish"}:
348
+ raise InvalidParams("invalid Agent mutation action")
349
+ if action == "begin":
350
+ _reap_sessions()
351
+ revision = params.get("revision")
352
+ homes = params.get("homes")
353
+ if not isinstance(revision, str) or not re.fullmatch(r"[a-f0-9]{40}", revision):
354
+ raise InvalidParams("an immutable target revision is required")
355
+ if not isinstance(homes, list) or not homes or not all(isinstance(home, str) and Path(home).is_absolute() for home in homes):
356
+ raise InvalidParams("resolved profile homes are required")
357
+ if any(Path(home).resolve().is_relative_to(root.resolve()) for home in homes):
358
+ raise InvalidParams("Agent profile homes must stay outside the source checkout to survive update cleanup")
359
+ intent = params.get('intent', '')
360
+ refresh_token = params.get('refresh_token')
361
+ if not isinstance(intent, str) or len(intent) > 1024:
362
+ raise InvalidParams('invalid update intent')
363
+ binding = None
364
+ with _SESSIONS_LOCK:
365
+ for key, value in list(_CONSENTS.items()):
366
+ if value['expires'] <= time.monotonic():
367
+ _CONSENTS.pop(key)
368
+ if refresh_token is not None:
369
+ consent = _CONSENTS.pop(refresh_token, None) if isinstance(refresh_token, str) else None
370
+ if (consent is None or not intent or consent['intent'] != intent or consent['root'] != root.resolve()
371
+ or consent['revision'] != revision or consent['homes'] != sorted({str(Path(h).resolve()) for h in homes})):
372
+ return {'ok': False, 'skipped': False, 'detail': 'Legacy refresh confirmation expired, changed or was already used. Check for updates and confirm again.'}
373
+ binding = consent['binding']
374
+ if any(s["root"] == root.resolve() for s in _SESSIONS.values()):
375
+ return {"ok": False, "skipped": False, "detail": "Another Agent update owns the writer lease."}
376
+ from .home import _frozen_launch_env
377
+ env = _frozen_launch_env()
378
+ extra_pythonpath = (os.pathsep + env["PYTHONPATH"]) if env.get("PYTHONPATH") else ""
379
+ env.update(HERMES_HOME=str(runtime.hermes_home), HERMES_DISABLE_LAZY_INSTALLS="1",
380
+ PYTHONPATH=str(Path(__file__).parents[1]) + os.pathsep + str(root) + extra_pythonpath)
381
+ stderr = tempfile.TemporaryFile()
382
+ from .methods.gateway import _ACTION_LOCK
383
+ if not _ACTION_LOCK.acquire(blocking=False):
384
+ stderr.close()
385
+ return {"ok": False, "skipped": False, "detail": "A gateway lifecycle action is still running."}
386
+ try:
387
+ proc = subprocess.Popen([sys.executable, "-m", "talaria_sidecar.agent_mutation", str(root), revision, json.dumps(homes), binding or ""],
388
+ cwd=root, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr, text=True)
389
+ except BaseException:
390
+ stderr.close()
391
+ _ACTION_LOCK.release()
392
+ raise
393
+ token = uuid.uuid4().hex
394
+ session = {"intent": intent, "revision": revision, "homes": sorted({str(Path(h).resolve()) for h in homes}), "root": root.resolve(), "proc": proc, "stderr": stderr, "lock": threading.RLock(),
395
+ "close_lock": threading.Lock(), "closed": False, "output": queue.Queue(), "gateway_lock": _ACTION_LOCK}
396
+ _SESSIONS[token] = session
397
+ def read():
398
+ try:
399
+ for line in proc.stdout:
400
+ if len(line) > 16 * 1024 * 1024:
401
+ break
402
+ session["output"].put(line)
403
+ finally:
404
+ session["output"].put(None)
405
+ threading.Thread(target=read, daemon=True).start()
406
+ else:
407
+ token = params.get("token")
408
+ if not isinstance(token, str) or not re.fullmatch(r"[a-f0-9]{32}", token):
409
+ raise InvalidParams("invalid Agent writer lease token")
410
+ with _SESSIONS_LOCK:
411
+ session = _SESSIONS.get(token)
412
+ if session is None or session["root"] != root.resolve():
413
+ raise RpcError("Agent writer lease is unavailable; retry recovery before updating.", condition="agent_update_unsafe")
414
+ if action == "git":
415
+ args = params.get("args")
416
+ if (not isinstance(args, list) or not args or len(args) > 1000
417
+ or not all(isinstance(arg, str) for arg in args) or sum(map(len, args)) > 1024 * 1024
418
+ or args[0] not in {"rev-parse", "ls-tree", "show", "tag", "for-each-ref", "status", "diff-index", "diff", "stash",
419
+ "merge", "apply", "checkout", "clean", "reset", "update-ref", "describe", "log", "merge-base", "rev-list", "ls-files"}
420
+ or "--unsafe-paths" in args):
421
+ raise InvalidParams("invalid leased git command")
422
+ timeout = params.get("timeout_ms")
423
+ if not isinstance(timeout, int) or not 100 <= timeout <= 300000:
424
+ raise InvalidParams("invalid leased git timeout")
425
+ try:
426
+ result = _exchange(session, None if action == "begin" else params, ctx)
427
+ if action == 'begin' and 'refresh_confirmation' in result:
428
+ confirmation = result['refresh_confirmation']
429
+ if not session['intent']:
430
+ return {'ok': False, 'skipped': False, 'detail': 'Legacy dashboard refresh requires an interactive operator confirmation.'}
431
+ nonce = uuid.uuid4().hex
432
+ with _SESSIONS_LOCK:
433
+ if len(_CONSENTS) >= 64:
434
+ _CONSENTS.pop(next(iter(_CONSENTS)))
435
+ _CONSENTS[nonce] = {key: session[key] for key in ('root', 'revision', 'homes', 'intent')}
436
+ _CONSENTS[nonce].update(binding=confirmation['binding'], expires=time.monotonic() + CONSENT_SECONDS)
437
+ result['refresh_confirmation'] = {'token': nonce, 'services': confirmation['services']}
438
+ if action == "begin" and result["ok"]:
439
+ runtime.retire()
440
+ return {**result, "token": token}
441
+ if action != "finish" and result["ok"]:
442
+ return result
443
+ # A failed Git/sync command leaves the lease held so the caller can restore its stash and finish.
444
+ if action in {"git", "sync"}:
445
+ return result
446
+ return result
447
+ except BaseException:
448
+ with _SESSIONS_LOCK:
449
+ _SESSIONS.pop(token, None)
450
+ _close(session)
451
+ raise
452
+ finally:
453
+ if action == "finish" or (action == "begin" and 'result' in locals() and not result["ok"]):
454
+ with _SESSIONS_LOCK:
455
+ _SESSIONS.pop(token, None)
456
+ _close(session)
457
+
458
+
459
+ def _owners(root, homes, pause, identity, windows=None):
460
+ homes = {Path(home).resolve() for home in homes}
461
+ if windows is None:
462
+ units, jobs = pause._discover_systemd(), pause._discover_launchd()
463
+ units, jobs, notices = pause._left_running(units, jobs)
464
+ supervised = services.gateway_entries(units + jobs, homes, pause)
465
+ bare, more = pause._discover_bare({pid for e in supervised for pid in (e["pid"], e["service_pid"])})
466
+ if notices or more:
467
+ raise RuntimeError("A gateway cannot be safely quiesced: " + "; ".join(notices + more))
468
+ entries = supervised + bare
469
+ else:
470
+ from hermes_cli.gateway import _capture_gateway_argv
471
+ _, native_services, _, running = windows._discover_windows_gateways()
472
+ by_pid = {int(service.gateway_pid): service for service in native_services}
473
+ entries = [{"pid": int(pid), "home": pause._home_of(int(pid)), "argv": _capture_gateway_argv(int(pid))} for pid in running]
474
+ for entry in entries:
475
+ service = by_pid.get(entry["pid"])
476
+ if service is not None:
477
+ entry.update(kind="windows-service", service=str(service.name), profile=str(service.profile),
478
+ service_pid=int(service.service_pid), service_ct=float(service.service_create_time),
479
+ descendants=list(getattr(service, "descendant_identities", ())))
480
+ if entry["service_pid"] <= 0 or entry["service_ct"] <= 0:
481
+ raise RuntimeError("A Windows gateway service's process identity could not be proved.")
482
+ if any(not entry["argv"] and not entry.get("kind") for entry in entries):
483
+ raise RuntimeError("A Windows gateway's resume command could not be proved.")
484
+ if any(holder["kind"] != "gateway" or holder["pid"] not in {e["pid"] for e in entries} for holder in windows.list_venv_holders()):
485
+ raise RuntimeError("An Agent environment writer cannot be cooperatively quiesced.")
486
+ pids = {entry["pid"] for entry in entries}
487
+ for entry, live in _writer_ledger(root, identity):
488
+ if live is not True:
489
+ raise RuntimeError(f"Agent writer PID {entry.get('pid')} has no proven live identity; source was not changed.")
490
+ if entry.get('purpose') in {'serve', 'dashboard'} and windows is None:
491
+ backend = services.backend_entry(entry, homes, pause, identity)
492
+ existing = next((e for e in entries if e['pid'] == backend['pid']), None)
493
+ if existing is not None and (existing.get('purpose') != backend['purpose'] or existing.get('home') != backend['home']):
494
+ raise RuntimeError(f"Agent writer PID {backend['pid']} has conflicting native ownership identities.")
495
+ if backend['pid'] not in pids:
496
+ if any(e.get('purpose') in {'serve', 'dashboard'} and services._service_key(e) == services._service_key(backend) for e in entries):
497
+ raise RuntimeError('More than one backend claims the same supervised service.')
498
+ entries.append(backend)
499
+ pids.add(backend['pid'])
500
+ elif entry.get("pid") not in pids:
501
+ raise RuntimeError(f"Agent writer PID {entry.get('pid')} has no proven cooperative pause; stop it through its owner before updating.")
502
+ if any(not entry.get("home") or Path(entry["home"]).resolve() not in homes for entry in entries):
503
+ raise RuntimeError("A gateway's owning profile is unknown or outside this Web instance's scope.")
504
+ return entries
505
+
506
+
507
+ def _writer_ledger(root, identity):
508
+ ledger = identity._read_ledger(identity._ledger_path())
509
+ if ledger is None:
510
+ raise RuntimeError("Agent process ownership evidence is unreadable.")
511
+ try:
512
+ raw = identity._ledger_path().read_text(encoding="utf-8-sig")
513
+ except FileNotFoundError:
514
+ raw = "[]"
515
+ if raw.strip():
516
+ complete = json.loads(raw)
517
+ if not isinstance(complete, list) or not all(isinstance(row, dict) for row in complete):
518
+ raise RuntimeError("Agent process ownership evidence is malformed.")
519
+ found = []
520
+ for entry in ledger:
521
+ if entry.get("purpose") in {"gateway", "serve", "dashboard"} and not entry.get("install"):
522
+ raise RuntimeError("Agent process ownership evidence is incomplete.")
523
+ if entry.get("install") != identity.install_id(root) or entry.get("purpose") not in {"gateway", "serve", "dashboard"}:
524
+ continue
525
+ live = identity._pid_alive_matches(entry.get("pid"), entry.get("create_time"))
526
+ if live is not False:
527
+ found.append((entry, live))
528
+ return found
529
+
530
+
531
+ def _worker(root, revision, homes, requests, stopped, send, refresh_binding=None):
532
+ if os.name not in {"posix", "nt"}:
533
+ raise RuntimeError("A cooperative Agent writer lease is unavailable on this platform; source was not changed.")
534
+ sys.path.insert(0, str(root))
535
+ # Keep the installed updater's resume closure loaded across replacement of its source files.
536
+ modules = {name: importlib.import_module("hermes_cli." + name) for name in (
537
+ "update_lock", "update_cmd_posix_pause", "update_pause_record", "update_cmd_pause_gate",
538
+ "update_cmd_windows", "update_custody", "process_identity", "update_cmd_commit")}
539
+ if any(not Path(module.__file__).resolve().is_relative_to(root) for module in modules.values()):
540
+ raise RuntimeError("Agent writer lease imported a different checkout.")
541
+ pause, records, identity = (modules[name] for name in ("update_cmd_posix_pause", "update_pause_record", "process_identity"))
542
+ windows = modules["update_cmd_windows"] if os.name == "nt" else None
543
+ lock = modules["update_lock"].UpdateLock(install_root=root)
544
+ if not lock.acquire():
545
+ raise RuntimeError("Another Agent update owns the native checkout lock.")
546
+ token = None
547
+ initial = None
548
+ initial_tree = None
549
+ handed_off = False
550
+ sync_failed = False
551
+ error = None
552
+ recovery = None
553
+ gateway_deadline = None
554
+ try:
555
+ recovery = services.ServiceRecovery(root, Path(os.environ['HERMES_HOME']), records, identity, pause)
556
+ found = _command(modules["update_custody"].git_argv(["git"], ["rev-parse", "HEAD"]), root, 10, stopped, modules["update_custody"])
557
+ if not found["ok"]:
558
+ raise RuntimeError("The installed Agent revision could not be verified before quiescence.")
559
+ initial = found["detail"]
560
+ initial_tree = records.tree_state(root)
561
+ if initial_tree is None or initial_tree.get("pre_sha") != initial or initial_tree.get("dirty_at_pause") is None:
562
+ raise RuntimeError("The Agent source baseline could not be proved before quiescence.")
563
+ # Recovery must remain readable after a crash on the target tree. Inspect it without importing it.
564
+ found = _command(modules["update_custody"].git_argv(["git"], ["show", f"{revision}:hermes_cli/update_pause_record.py"]), root, 15, stopped, modules["update_custody"])
565
+ if not found["ok"]:
566
+ raise RuntimeError("The target Agent's durable gateway resume protocol could not be read.")
567
+ reader = found["detail"]
568
+ if not all(re.search(r"^def " + name + r"\(", reader, re.M) for name in ("recover", "tree_is_whole", "adopt_orphans")):
569
+ raise RuntimeError("The target Agent lacks the durable gateway resume protocol.")
570
+ found = _command(modules["update_custody"].git_argv(["git"], ["ls-tree", "-r", "--name-only", revision, "--", "hermes_cli"]), root, 15, stopped, modules["update_custody"])
571
+ target_files = set(found["detail"].splitlines()) if found["ok"] else set()
572
+ if not {f"hermes_cli/{name}.py" for name in (*modules, "main")} <= target_files:
573
+ raise RuntimeError("The target Agent is missing its native update-recovery modules.")
574
+ needs_sync = "hermes_cli/venv_sync.py" in target_files
575
+ adopted, claims = records.adopt_orphans()
576
+ bootstrap_repair = False
577
+ try:
578
+ main_module = importlib.import_module("hermes_cli.main")
579
+ if not Path(main_module.__file__).resolve().is_relative_to(root):
580
+ raise RuntimeError("The Agent update loaded another checkout's runtime.")
581
+ except Exception:
582
+ if (not (adopted or recovery.state['entries']) or (initial != revision and needs_sync) or _writer_ledger(root, identity)
583
+ or any(records.identity_is_live({"pid": int(pid), "ct": ct}) for pid, ct in adopted.get("identities", {}).items())):
584
+ raise RuntimeError("The Agent environment cannot import and its writers cannot be proved quiesced; use native recovery first.")
585
+ bootstrap_repair = True
586
+ if any(e.get('legacy_refresh') for e in recovery.state['entries']):
587
+ recovery.resume(_writer_ledger)
588
+ entries = [] if bootstrap_repair else _owners(root, homes, pause, identity, windows)
589
+ recovery_only = ((bootstrap_repair and needs_sync) or bool(adopted and not records.tree_is_whole(adopted, root)[0])
590
+ or bool(recovery.state['entries'] and (recovery.state.get('sync_failed') or not records.tree_is_whole(recovery.state['tree'], root)[0])))
591
+ # Native gateway birth stamps and journal identities use different units.
592
+ # Bracket and repeat both probes, binding them to the same incarnation before recording it.
593
+ from gateway.status import get_process_start_time
594
+ owner_identities = {}
595
+ legacy = []
596
+ for entry in entries:
597
+ services.bind_processes(entry, records)
598
+ canonical = records.identity(entry["pid"])["ct"]
599
+ birth = get_process_start_time(entry["pid"])
600
+ if canonical is None or birth is None:
601
+ raise RuntimeError("Gateway process identity could not be proved.")
602
+ if (records.identity(entry["pid"])["ct"] != canonical
603
+ or get_process_start_time(entry["pid"]) != birth
604
+ or (entry.get("ct") is not None and entry["ct"] != birth)):
605
+ raise RuntimeError("Gateway process identity changed during quiescence; source was not changed.")
606
+ entry["ct"] = birth
607
+ if entry.get('purpose') in {'dashboard', 'serve'}:
608
+ try:
609
+ services.backend_request(entry)
610
+ except services.BackendLifecycleUnsupported:
611
+ services.verify_listener(entry)
612
+ entry['config_stamp'] = services._config_stamp(entry)
613
+ legacy.append(entry)
614
+ services.verify_processes(entry, records)
615
+ if not entry.get('purpose'):
616
+ owner_identities[str(entry["pid"])] = canonical
617
+ owner_identities.update({pid: p['ct'] for pid, p in entry.get('processes', {}).items()})
618
+ binding = hashlib.sha256(json.dumps({'root': str(root), 'revision': revision, 'tree': initial_tree,
619
+ 'homes': sorted(map(str, homes)), 'entries': sorted(legacy, key=services._name)},
620
+ sort_keys=True).encode()).hexdigest()
621
+ if refresh_binding and (not legacy or refresh_binding != binding):
622
+ raise RuntimeError('Legacy refresh target or process identity changed. Check for updates and confirm again.')
623
+ if legacy and not refresh_binding:
624
+ send({'ok': False, 'detail': 'Active dashboard work may be interrupted by a graceful refresh.',
625
+ 'refresh_confirmation': {'binding': binding, 'services': [services._name(e) for e in legacy]}})
626
+ return
627
+ if legacy and (recovery_only or bootstrap_repair or initial_tree['dirty_at_pause']
628
+ or (root / '.interrupted').exists()):
629
+ raise RuntimeError('The installed Agent source/dependencies are not safe for a legacy refresh; repair remains owed.')
630
+ gateway_entries = [e for e in entries if not e.get('purpose')]
631
+ backend_entries = [e for e in entries if e.get('purpose') in {'serve', 'dashboard'}]
632
+ token = pause._empty_token()
633
+ if recovery.state['entries']:
634
+ token['baselines'] = recovery.state['tree'].get('baselines', [])
635
+ if windows is not None:
636
+ token.pop("platform", None)
637
+ native_services = [e for e in entries if e.get("kind") == "windows-service"]
638
+ token.update(services=[e["service"] for e in native_services], expected_services=[e["service"] for e in native_services], restarted_services=[],
639
+ service_profiles={e["service"]: e["profile"] for e in native_services})
640
+ token.update(posix_units=[], unmapped=[e for e in gateway_entries if not e.get("kind")],
641
+ unmapped_pids=[e["pid"] for e in gateway_entries if not e.get("kind")],
642
+ identities=owner_identities, resume_needed=bool(gateway_entries))
643
+ records.record_pause(token, adopted, claims)
644
+ recovery.state.setdefault('transaction', {'revision': revision, 'homes': sorted(map(str, homes))})
645
+ recovery.save(token)
646
+ if legacy:
647
+ whole, why = records.tree_is_whole(token, root)
648
+ if not whole:
649
+ raise RuntimeError('Legacy refresh requires verified current source/dependencies: ' + why)
650
+ for entry in legacy:
651
+ recovery.prepare([entry], token)
652
+ recovery.refresh(entry, stopped, _writer_ledger)
653
+ # Bootstrap is bounded to the consented incarnations; every successor now uses the normal fence.
654
+ entries = _owners(root, homes, pause, identity, windows)
655
+ for entry in entries:
656
+ services.bind_processes(entry, records)
657
+ if entry.get('purpose') in {'dashboard', 'serve'}:
658
+ services.backend_request(entry)
659
+ gateway_entries = [e for e in entries if not e.get('purpose')]
660
+ backend_entries = [e for e in entries if e.get('purpose') in {'serve', 'dashboard'}]
661
+ recovery.prepare(backend_entries + [{**e, 'purpose': 'gateway'} for e in gateway_entries if e.get('processes')], token)
662
+ records.mark_stop_requested(token, sorted({pid for e in gateway_entries for pid in (e['pid'], e.get('service_pid', e['pid']))}))
663
+ for entry in backend_entries:
664
+ recovery.stop(entry, stopped)
665
+ # Every profile shares the longest native drain; the lease reports it so its RPC keeps custody.
666
+ gateway_deadline = recovery.gateway_deadline(homes)
667
+ if gateway_entries:
668
+ send({'progress': {'phase': 'gateway_drain', 'deadline_seconds': round(max(gateway_deadline - time.monotonic(), 0), 3),
669
+ 'services': sorted({services._name(e) for e in gateway_entries})}})
670
+ for entry in gateway_entries:
671
+ if stopped.is_set() or time.monotonic() >= gateway_deadline:
672
+ raise RuntimeError('Gateway drain has not completed; its durable resume obligation is retained.')
673
+ if entry.get('processes'):
674
+ services.verify_processes(entry, records)
675
+ recovery.gateway_requested(entry)
676
+ for pid in {entry['pid'], entry.get('service_pid', entry['pid'])}:
677
+ records.mark_stop_sent(token, pid)
678
+ # mark_stop_sent is best effort upstream; this producer requires a durable request before sending it.
679
+ records.write(token)
680
+ if not pause._ask_to_drain(entry):
681
+ raise RuntimeError("A gateway did not acknowledge cooperative quiescence; source was not changed.")
682
+ recovery.gateway_requested(entry, acknowledged=True)
683
+ waiting = list(gateway_entries)
684
+ while waiting:
685
+ for entry in list(waiting):
686
+ if not pause._alive(entry["pid"], entry["ct"]):
687
+ if entry.get("kind") == "windows-service":
688
+ _, service = windows._win_service(entry["service"])
689
+ if service.status() != "stopped":
690
+ windows._stop_windows_gateway_service(entry["service"], expected_processes=tuple(map(tuple, entry["descendants"])),
691
+ expected_service_identity=(entry["service_pid"], entry["service_ct"]))
692
+ else:
693
+ if entry.get('processes'):
694
+ recovery.settle_gateway(entry, stopped, deadline=gateway_deadline)
695
+ else:
696
+ pause._disarm(services.service_entry(entry))
697
+ waiting.remove(entry)
698
+ if waiting and (stopped.wait(.05) or time.monotonic() >= gateway_deadline):
699
+ raise RuntimeError("Gateway drain has not completed; its durable resume obligation is retained.")
700
+ if (_writer_ledger(root, identity) if bootstrap_repair else _owners(root, homes, pause, identity, windows)):
701
+ raise RuntimeError("A gateway appeared during quiescence; source was not changed.")
702
+ send({"ok": True})
703
+ last_request = time.monotonic()
704
+ while not stopped.is_set():
705
+ try:
706
+ request = requests.get(timeout=.1)
707
+ except queue.Empty:
708
+ if time.monotonic() - last_request > 60:
709
+ raise RuntimeError("The idle Agent writer lease expired; no further commands are admitted.")
710
+ continue
711
+ last_request = time.monotonic()
712
+ if request["action"] == "finish":
713
+ break
714
+ if (_writer_ledger(root, identity) if bootstrap_repair else _owners(root, homes, pause, identity, windows)):
715
+ raise RuntimeError('An Agent writer appeared during the held lease; source/dependency mutation was refused.')
716
+ if request["action"] == "sync":
717
+ argv = [sys.executable, "-m", "hermes_cli.venv_sync", "--project-root", str(root), "--json"]
718
+ if not (root / "hermes_cli/venv_sync.py").is_file():
719
+ sync_failed = needs_sync
720
+ recovery.state['sync_failed'] = needs_sync
721
+ recovery.save(token)
722
+ send({"ok": not needs_sync, "skipped": True, "detail": "The target's dependency repair module is missing." if needs_sync else ""})
723
+ continue
724
+ sync_failed = True
725
+ recovery.state['sync_failed'] = True
726
+ recovery.save(token)
727
+ result = _command(argv, root, 300, stopped, modules["update_custody"])
728
+ try:
729
+ response = json.loads(result["detail"]) if result["ok"] else {}
730
+ result["ok"] = result["ok"] and response.get("ok") is True
731
+ result["detail"] = str(response.get("detail") or result["detail"])
732
+ except ValueError:
733
+ result = {"ok": False, "detail": "Agent dependency sync returned an invalid result."}
734
+ sync_failed = not result["ok"]
735
+ recovery.state['sync_failed'] = sync_failed
736
+ recovery.state['transaction']['dependencies_verified'] = not sync_failed
737
+ recovery.save(token)
738
+ if not bootstrap_repair and not sync_failed and records.tree_is_whole(token, root)[0]:
739
+ recovery_only = False
740
+ else:
741
+ args = request.get("args")
742
+ if not isinstance(args, list) or not args or not all(isinstance(arg, str) for arg in args):
743
+ raise ValueError("invalid git arguments")
744
+ if recovery_only and args[0] in {"stash", "merge", "apply", "checkout", "clean", "reset"}:
745
+ send({"ok": False, "detail": "A prior Agent update requires source/dependency recovery before another checkout mutation."})
746
+ continue
747
+ seconds = min(max(float(request.get("timeout_ms", 10000)) / 1000, .1), 300)
748
+ applying = args[0] == "apply" and "--check" not in args
749
+ paths = _patch_paths(root, args[-1], stopped, modules["update_custody"]) if applying else []
750
+ before = records.tree_state(root) if applying else None
751
+ whole_before = records.tree_is_whole(token, root)[0] if applying else False
752
+ mutating = args[0] in {"merge", "reset", "checkout", "apply", "clean", "stash"} and "--check" not in args
753
+ gate = modules["update_cmd_pause_gate"]._checkout_move(token, revision, paths=paths, revert=True) if mutating else contextlib.nullcontext({})
754
+ if mutating:
755
+ recovery.state['transaction']['dependencies_verified'] = False
756
+ move_marker = None
757
+ if args[0] == 'merge' and '--ff-only' in args or args[0] == 'reset' and '--hard' in args:
758
+ commit = modules['update_cmd_commit']
759
+ current = records.tree_state(root)
760
+ if current is None or current.get('dirty_at_pause'):
761
+ raise RuntimeError('A source move requires the recorded clean tree; local edits remain retained.')
762
+ # The native rollback receipt is immutable before Git can tear any source file.
763
+ move_marker = commit.arm_tree_move(['git'], root, pre=current['pre_sha'], target=revision, stash=None, rollback='branch')
764
+ with gate as step:
765
+ recovery.save(token)
766
+ argv = modules["update_custody"].git_argv(["git"], args)
767
+ result = _command(argv, root, seconds, stopped, modules["update_custody"])
768
+ if result["ok"] and applying:
769
+ step["whole"] = True
770
+ if move_marker is not None and result['ok']:
771
+ installed = records.tree_state(root)
772
+ if installed and installed['pre_sha'] == revision and installed['dirty_at_pause'] == []:
773
+ move_marker.unlink(missing_ok=True)
774
+ if not needs_sync:
775
+ recovery.state['sync_failed'] = False
776
+ recovery.state['transaction']['dependencies_verified'] = True
777
+ else:
778
+ result = {'ok': False, 'detail': 'The source move did not leave a verified whole target; native rollback remains owed.'}
779
+ if result["ok"] and applying and whole_before:
780
+ after = records.tree_state(root)
781
+ if before is None or after is None or before["pre_sha"] != after["pre_sha"]:
782
+ raise RuntimeError("The completed local restore could not be verified; gateway resume remains owed.")
783
+ # Every carried pause verified this HEAD before the atomic restore; retain the intended edits for all of them.
784
+ for baseline in token.get("baselines", []):
785
+ if baseline.get("pre_sha") == after["pre_sha"]:
786
+ baseline.update(after)
787
+ records.write(token)
788
+ recovery.save(token)
789
+ send(result)
790
+ except BaseException as exc:
791
+ error = str(exc)
792
+ finally:
793
+ try:
794
+ # Cancellation or a lost lease pipe ends the drain wait; the retained debt keeps the gateway owed.
795
+ unwind_deadline = time.monotonic() if stopped.is_set() else gateway_deadline
796
+ if token:
797
+ live = any(records.identity_is_live({"pid": int(pid), "ct": ct}) for pid, ct in token.get("identities", {}).items())
798
+ found = _command(modules["update_custody"].git_argv(["git"], ["rev-parse", "HEAD"]), root, 10, threading.Event(), modules["update_custody"])
799
+ if not found["ok"]:
800
+ raise RuntimeError("Gateway resume remains owed because the Agent revision is unverified.")
801
+ final_tree = records.tree_state(root)
802
+ if final_tree is None or final_tree.get("dirty_at_pause") is None:
803
+ raise RuntimeError("Gateway resume remains owed because the source tree is unverified.")
804
+ changed = found["detail"] != initial or final_tree != initial_tree
805
+ if live and not changed and not sync_failed and records.tree_is_whole(token, root)[0]:
806
+ # An untouched gateway must not strand a backend stopped earlier in a partial pause.
807
+ recovery.save(token)
808
+ recovery.resume(_writer_ledger, gateway_deadline=unwind_deadline)
809
+ if live or sync_failed or (changed and needs_sync and not locals().get("response", {}).get("ok")):
810
+ raise RuntimeError("Gateway resume remains owed until writer drain and dependency repair complete.")
811
+ whole, why = records.tree_is_whole(token, root)
812
+ if not whole:
813
+ raise RuntimeError("Gateway resume remains owed: " + why)
814
+ pending = _command(modules['update_custody'].git_argv(['git'], ['for-each-ref', '--format=%(objectname)', 'refs/talaria/autostash/pending/']), root, 10, threading.Event(), modules['update_custody'])
815
+ if not pending['ok'] or pending['detail']:
816
+ raise RuntimeError('Gateway resume remains owed until retained local Agent edits are restored.')
817
+ if token.get("resume_needed"):
818
+ if bootstrap_repair:
819
+ records.write({**token, "resume_needed": True}, owner=records.UNOWNED)
820
+ handed_off = True
821
+ resumed = _resume_current(root, modules["update_custody"])
822
+ if not resumed["ok"]:
823
+ raise RuntimeError(resumed["detail"])
824
+ token["resume_needed"] = False
825
+ else:
826
+ modules["update_cmd_windows"]._resume_windows_gateways_after_update(token)
827
+ if token.get("resume_needed") or token.get("resume_deferred"):
828
+ raise RuntimeError("Gateway resume could not be verified; its obligation is retained.")
829
+ recovery.save(token)
830
+ recovery.resume(_writer_ledger, gateway_deadline=unwind_deadline)
831
+ if token and not handed_off:
832
+ records.discharge(token)
833
+ from .runtime import AgentRuntime
834
+ _, marker = recovery_receipts(AgentRuntime(Path(os.environ['HERMES_HOME']), root))
835
+ if marker and marker.exists():
836
+ recovery.state['transaction']['runtime_revision'] = found['detail']
837
+ recovery.save(token)
838
+ else:
839
+ recovery.state.pop('transaction', None)
840
+ recovery.save()
841
+ except BaseException as exc:
842
+ error = (error + "; " if error else "") + str(exc)
843
+ if token and not handed_off:
844
+ records.write({**token, "resume_needed": True}, owner=records.UNOWNED)
845
+ finally:
846
+ lock.release()
847
+ send({"ok": error is None, "detail": error or ""})
848
+
849
+
850
+ def _resume_current(root, custody, stopped=None, complete_revision=None):
851
+ # Resolve target PM code in a fresh stdlib process, after any source/dependency change.
852
+ try:
853
+ launchers = importlib.import_module('hermes_cli._launchers')
854
+ if not Path(launchers.__file__).resolve().is_relative_to(root):
855
+ raise RuntimeError('Recovery loaded another Agent bootstrap selector.')
856
+ bootstrap = launchers.resolve_store_python(root)
857
+ except ModuleNotFoundError as exc:
858
+ if exc.name != 'hermes_cli._launchers':
859
+ raise
860
+ bootstrap = None
861
+ # A copied macOS venv interpreter can be its own _base_executable and later be retired.
862
+ bootstrap = bootstrap or getattr(sys, '_base_executable', None) or sys.executable
863
+ result = _command([str(bootstrap), "-m", "talaria_sidecar.agent_mutation", "--select-resume", str(root), complete_revision or "", sys.executable], root, 660, stopped or threading.Event(), custody, cooperative=True)
864
+ try:
865
+ reply = json.loads(result["detail"]) if result["ok"] else {}
866
+ return {**reply, "ok": result["ok"] and reply.get("ok") is True, "detail": str(reply.get("detail") or result["detail"])}
867
+ except ValueError:
868
+ return {"ok": False, "detail": "The current Agent could not verify its retained gateway resume obligation."}
869
+
870
+
871
+ def _recovery_environment(root):
872
+ """Reuse PM's selector and packaged launch policy without copying its facts/path rules."""
873
+ sys.path.insert(0, str(root))
874
+ environments = importlib.import_module('pm.environments')
875
+ if any(not Path(sys.modules[name].__file__).resolve().is_relative_to(root)
876
+ for name in ('pm', 'pm.environments', 'hermes_constants')):
877
+ raise RuntimeError('Recovery loaded another Agent environment selector.')
878
+ selected = environments.committed_venv(root)
879
+ if selected is None:
880
+ if environments.install_state_dir(root).exists():
881
+ raise RuntimeError('No dependency environment is committed for this Agent install; recovery remains owed.')
882
+ selected = environments.selected_venv(root)
883
+ return environments, selected
884
+
885
+
886
+ def _select_resume(root, complete_revision, legacy_python):
887
+ if (root / 'pm').exists():
888
+ environments, selected = _recovery_environment(root)
889
+ command = environments.venv_command(root, selected)
890
+ environment = str(selected)
891
+ else:
892
+ python = next((path for venv in ('.venv', 'venv')
893
+ for path in (root / venv / 'bin/python', root / venv / 'Scripts/python.exe') if path.is_file()), Path(legacy_python))
894
+ command, environment = [str(python)], ''
895
+ # Keep bin/python intact: realpath would turn a POSIX venv into its bare tool.
896
+ argv = [*command, '-m', 'talaria_sidecar.agent_mutation', '--resume-leased', str(root), complete_revision or '', environment]
897
+ os.execv(argv[0], argv)
898
+
899
+
900
+ def _verify_recovery_environment(root, expected):
901
+ from .runtime import _check_site_packages_python
902
+ environments, selected = _recovery_environment(root)
903
+ if str(selected) != expected or environments.venv_python_version(selected) != sys.version_info[:2]:
904
+ raise RuntimeError('Agent recovery dependency environment changed or has an incompatible Python ABI.')
905
+ site = environments.site_packages(selected)
906
+ if not site.is_dir() or site.resolve() not in {Path(entry).resolve() for entry in sys.path if entry}:
907
+ raise RuntimeError('Agent recovery did not enter its selected dependency environment.')
908
+ _check_site_packages_python()
909
+
910
+
911
+ def _resume_worker(root, stopped=None, complete_revision=None):
912
+ sys.path.insert(0, str(root))
913
+ from hermes_cli import update_lock, update_pause_record as records, process_identity, update_cmd_posix_pause
914
+ if any(not Path(module.__file__).resolve().is_relative_to(root) for module in (update_lock, records, process_identity, update_cmd_posix_pause)):
915
+ raise RuntimeError("Gateway recovery loaded another Agent checkout.")
916
+ lock = update_lock.UpdateLock(install_root=root)
917
+ if not lock.acquire():
918
+ raise RuntimeError("The gateway recovery child could not join the native checkout lease.")
919
+ try:
920
+ recovery = services.ServiceRecovery(root, Path(os.environ['HERMES_HOME']), records, process_identity, update_cmd_posix_pause)
921
+ adopted = None
922
+ for _, body in records.orphans():
923
+ adopted = records.merge_into(adopted, body['token'])
924
+ tree = records.merge_into(adopted, recovery.state['tree']) if recovery.state['tree'] else adopted or {}
925
+ from .runtime import AgentRuntime
926
+ runtime = AgentRuntime(Path(os.environ['HERMES_HOME']), root)
927
+ receipts, marker = recovery_receipts(runtime)
928
+ custody = importlib.import_module('hermes_cli.update_custody')
929
+ stopped = stopped or threading.Event()
930
+ from hermes_cli._early_recovery import interrupted_pull_marker
931
+ if interrupted_pull_marker(root).exists():
932
+ return {'ok': False, 'detail': 'Native source recovery remains incomplete; its receipt is retained.'}
933
+ if (root / '.hermes-update-zip-swap').exists():
934
+ from hermes_cli._early_recovery import restore_interrupted_pull
935
+ if restore_interrupted_pull(root):
936
+ return _resume_current(root, custody, stopped, complete_revision)
937
+ if (root / '.update-incomplete').exists() or (root / '.lazy-refresh-incomplete').exists():
938
+ return {'ok': False, 'detail': 'Native source recovery remains incomplete; its receipt is preserved.'}
939
+ if receipts and not tree.get('baselines'):
940
+ return {'ok': False, 'detail': 'Recovery has no verified transaction baseline; its receipt is preserved.'}
941
+ whole, why = records.tree_is_whole(tree, root)
942
+ if not whole:
943
+ return {'ok': False, 'detail': 'Source recovery remains owed: ' + why}
944
+ head = _command(custody.git_argv(['git'], ['rev-parse', 'HEAD']), root, 10, stopped, custody)
945
+ if not head['ok'] or not re.fullmatch(r'[a-f0-9]{40}', head['detail']):
946
+ return {'ok': False, 'detail': 'Recovery cannot verify the installed revision.'}
947
+ revision = head['detail']
948
+ if revision not in {r for b in tree.get('baselines', []) for r in [b.get('pre_sha'), *b.get('move_targets', []), *b.get('landed', {})]}:
949
+ return {'ok': False, 'detail': 'Installed revision is outside the retained transaction; recovery was refused.'}
950
+ moved = any(b.get('pre_sha') != revision for b in tree.get('baselines', []))
951
+ transaction = recovery.state.get('transaction') or {}
952
+ if recovery.state.get('sync_failed') or (moved and not transaction.get('dependencies_verified')) or (marker and marker.exists() and not transaction):
953
+ if _writer_ledger(root, process_identity):
954
+ return {'ok': False, 'detail': 'Recorded dependency repair is waiting for the existing writer drain; no additional stop was sent.'}
955
+ sync = root / 'hermes_cli/venv_sync.py'
956
+ if sync.is_file():
957
+ recovery.state['sync_failed'] = True
958
+ recovery.save(tree)
959
+ synced = _command([sys.executable, '-m', 'hermes_cli.venv_sync', '--project-root', str(root), '--json'], root, 300, stopped, custody)
960
+ response = json.loads(synced['detail']) if synced['ok'] else {}
961
+ if response.get('ok') is not True:
962
+ return {'ok': False, 'detail': 'Recorded dependency repair failed: ' + str(response.get('detail') or synced['detail'])}
963
+ recovery.state['sync_failed'] = False
964
+ transaction = recovery.state.setdefault('transaction', {'revision': revision, 'homes': [str(runtime.hermes_home)]})
965
+ transaction['dependencies_verified'] = True
966
+ recovery.save(tree)
967
+ return _resume_current(root, custody, stopped, complete_revision)
968
+ elif recovery.state.get('sync_failed'):
969
+ return {'ok': False, 'detail': 'The recorded dependency repair module is missing.'}
970
+ whole, why = records.tree_is_whole(tree, root)
971
+ if not whole:
972
+ return {'ok': False, 'detail': 'Recorded source/dependency repair remains unverified: ' + why}
973
+ pending = _command(custody.git_argv(['git'], ['for-each-ref', '--format=%(objectname)', 'refs/talaria/autostash/pending/']), root, 10, stopped, custody)
974
+ if not pending['ok']:
975
+ return {'ok': False, 'detail': 'Retained local edits could not be inspected.'}
976
+ if pending['detail']:
977
+ return {'ok': False, 'local_restore': True, 'revision': revision, 'detail': 'Retained local edits require the existing leased restore path.'}
978
+ main_module = importlib.import_module("hermes_cli.main")
979
+ if not Path(main_module.__file__).resolve().is_relative_to(root):
980
+ raise RuntimeError("Gateway recovery loaded another Agent runtime.")
981
+ records.recover(argv=[])
982
+ try:
983
+ recovery.resume(_writer_ledger)
984
+ except services.GatewayDraining as exc:
985
+ return {'ok': False, 'draining': True, 'detail': str(exc)}
986
+ owed = records.record_path().exists() or bool(records._claims(records.record_path())) or bool(recovery.state['entries'])
987
+ runtime_pending = bool((marker and marker.exists()) or (recovery.state.get('transaction') or {}).get('runtime_revision'))
988
+ if not owed and runtime_pending:
989
+ transaction = recovery.state.setdefault('transaction', {'revision': revision, 'homes': [str(runtime.hermes_home)]})
990
+ if complete_revision == revision and transaction.get('runtime_revision') == revision:
991
+ runtime_pending = False
992
+ else:
993
+ transaction['runtime_revision'] = revision
994
+ recovery.save(tree)
995
+ if not owed and not runtime_pending:
996
+ recovery.state.pop('transaction', None)
997
+ recovery.state['sync_failed'] = False
998
+ recovery.save()
999
+ if marker:
1000
+ marker.unlink(missing_ok=True)
1001
+ return {"ok": not owed, "revision": revision, "runtime_pending": runtime_pending, "detail": "Gateway recovery remains durably recorded." if owed else ""}
1002
+ finally:
1003
+ lock.release()
1004
+
1005
+
1006
+ def _patch_paths(root, patch, stopped, custody):
1007
+ result = _command(custody.git_argv(["git"], ["apply", "--numstat", "-z", patch]), root, 10, stopped, custody)
1008
+ if not result["ok"]:
1009
+ raise RuntimeError("Local restore paths could not be bound to the native recovery journal.")
1010
+ rows, paths, index = result["detail"].split("\0"), [], 0
1011
+ while index < len(rows) and rows[index]:
1012
+ fields = rows[index].split("\t", 2)
1013
+ if len(fields) != 3:
1014
+ raise RuntimeError("Invalid local restore path evidence.")
1015
+ if fields[2]:
1016
+ paths.append(fields[2])
1017
+ index += 1
1018
+ else:
1019
+ if index + 2 >= len(rows) or not rows[index + 1] or not rows[index + 2]:
1020
+ raise RuntimeError("Invalid renamed local restore path evidence.")
1021
+ paths.extend(rows[index + 1:index + 3])
1022
+ index += 3
1023
+ return paths
1024
+
1025
+
1026
+ def _command(argv, root, timeout, stopped, custody, *, cooperative=False):
1027
+ with tempfile.TemporaryFile() as stdout, tempfile.TemporaryFile() as stderr:
1028
+ child = custody.popen(argv, inherit_lock=True, cwd=root, stdin=subprocess.PIPE if cooperative else subprocess.DEVNULL, stdout=stdout, stderr=stderr,
1029
+ start_new_session=os.name != "nt")
1030
+ deadline = time.monotonic() + timeout
1031
+ try:
1032
+ while child.poll() is None:
1033
+ if stopped.wait(.05) or time.monotonic() >= deadline:
1034
+ if cooperative:
1035
+ child.stdin.close()
1036
+ try:
1037
+ child.wait(timeout=330)
1038
+ except subprocess.TimeoutExpired:
1039
+ child.kill() # The service journal still owns unverified resume.
1040
+ child.wait()
1041
+ else:
1042
+ _stop_tree(child)
1043
+ return {"ok": False, "detail": "Agent update command cancelled or timed out."}
1044
+ out = stdout if child.returncode == 0 else stderr
1045
+ out.seek(0, os.SEEK_END)
1046
+ size = out.tell()
1047
+ if size > 16 * 1024 * 1024:
1048
+ return {"ok": False, "detail": "Agent update command output exceeds limit."}
1049
+ out.seek(0)
1050
+ return {"ok": child.returncode == 0, "detail": out.read().decode("utf-8", errors="replace").strip()}
1051
+ finally:
1052
+ if cooperative:
1053
+ child.stdin.close()
1054
+ if child.poll() is None:
1055
+ _stop_tree(child)
1056
+
1057
+
1058
+ def main():
1059
+ for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"):
1060
+ os.environ.pop(key, None)
1061
+ if len(sys.argv) > 2 and sys.argv[1] in {'--recover-current', '--resume', '--resume-leased', '--select-resume'}:
1062
+ try:
1063
+ _native_source_repair(Path(sys.argv[2]).resolve())
1064
+ if sys.argv[1] == '--select-resume':
1065
+ _select_resume(Path(sys.argv[2]).resolve(), sys.argv[3], sys.argv[4])
1066
+ except BaseException as exc:
1067
+ print(json.dumps({'ok': False, 'detail': str(exc) if str(exc) != '1' else 'Native recorded source repair could not complete; its receipt remains owed.'}), flush=True)
1068
+ return
1069
+ from .__main__ import _claim_stdout
1070
+ output = _claim_stdout()
1071
+ stopped, requests = threading.Event(), queue.Queue()
1072
+ def read():
1073
+ try:
1074
+ for line in sys.stdin:
1075
+ requests.put(json.loads(line))
1076
+ finally:
1077
+ stopped.set()
1078
+ threading.Thread(target=read, daemon=True).start()
1079
+ def send(result):
1080
+ output.write((json.dumps(result) + "\n").encode())
1081
+ try:
1082
+ if sys.argv[1] == "--recover-current":
1083
+ root = Path(sys.argv[2]).resolve()
1084
+ sys.path.insert(0, str(root))
1085
+ custody = importlib.import_module('hermes_cli.update_custody')
1086
+ update_lock = importlib.import_module('hermes_cli.update_lock')
1087
+ if any(not Path(module.__file__).resolve().is_relative_to(root) for module in (custody, update_lock)):
1088
+ raise RuntimeError('Recovery loaded another Agent checkout.')
1089
+ lock = update_lock.UpdateLock(install_root=root)
1090
+ if not lock.acquire():
1091
+ raise RuntimeError('Another Agent update owns the native checkout lock.')
1092
+ try:
1093
+ send(_resume_current(root, custody, stopped, sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None))
1094
+ finally:
1095
+ lock.release()
1096
+ return
1097
+ if sys.argv[1] in {"--resume", "--resume-leased"}:
1098
+ with contextlib.redirect_stdout(sys.stderr):
1099
+ if len(sys.argv) > 4 and sys.argv[4]:
1100
+ _verify_recovery_environment(Path(sys.argv[2]).resolve(), sys.argv[4])
1101
+ reply = _resume_worker(Path(sys.argv[2]).resolve(), stopped if sys.argv[1] == "--resume-leased" else None, sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None)
1102
+ send(reply)
1103
+ return
1104
+ with contextlib.redirect_stdout(sys.stderr):
1105
+ _worker(Path(sys.argv[1]).resolve(), sys.argv[2], {Path(h).resolve() for h in json.loads(sys.argv[3])}, requests, stopped, send, sys.argv[4] if len(sys.argv) > 4 else None)
1106
+ except BaseException as exc:
1107
+ send({"ok": False, "detail": str(exc)})
1108
+
1109
+
1110
+ if __name__ == "__main__":
1111
+ main()