@maudecode/talaria-web 1.14.0 → 1.16.1

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 (348) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-router.d.ts +123 -7
  3. package/dist/api/automation-router.js +5 -0
  4. package/dist/api/automation-router.js.map +1 -1
  5. package/dist/api/chat-router.d.ts +728 -55
  6. package/dist/api/chat-router.js +122 -126
  7. package/dist/api/chat-router.js.map +1 -1
  8. package/dist/api/git-router.js +44 -32
  9. package/dist/api/git-router.js.map +1 -1
  10. package/dist/api/raw-routes.js +5 -58
  11. package/dist/api/raw-routes.js.map +1 -1
  12. package/dist/api/router.d.ts +6 -3
  13. package/dist/api/router.js +10 -8
  14. package/dist/api/router.js.map +1 -1
  15. package/dist/api/sessions-router.d.ts +2583 -297
  16. package/dist/api/sessions-router.js +92 -47
  17. package/dist/api/sessions-router.js.map +1 -1
  18. package/dist/api/settings-router.d.ts +188 -17
  19. package/dist/api/settings-router.js +75 -26
  20. package/dist/api/settings-router.js.map +1 -1
  21. package/dist/api/sse-routes.js +1 -1
  22. package/dist/api/sse-routes.js.map +1 -1
  23. package/dist/api/tools-router.d.ts +13 -1
  24. package/dist/api/tools-router.js +10 -8
  25. package/dist/api/tools-router.js.map +1 -1
  26. package/dist/app.d.ts +3620 -343
  27. package/dist/auth/gate.js +8 -4
  28. package/dist/auth/gate.js.map +1 -1
  29. package/dist/auth/oidc.d.ts +6 -2
  30. package/dist/auth/oidc.js +16 -11
  31. package/dist/auth/oidc.js.map +1 -1
  32. package/dist/auth/store.d.ts +14 -2
  33. package/dist/auth/store.js +26 -1
  34. package/dist/auth/store.js.map +1 -1
  35. package/dist/cli/dotenv.d.ts +1 -1
  36. package/dist/cli/dotenv.js +36 -3
  37. package/dist/cli/dotenv.js.map +1 -1
  38. package/dist/config/agent-config.js +20 -15
  39. package/dist/config/agent-config.js.map +1 -1
  40. package/dist/http/addresses.d.ts +6 -0
  41. package/dist/http/addresses.js +26 -0
  42. package/dist/http/addresses.js.map +1 -1
  43. package/dist/http/context.d.ts +7 -5
  44. package/dist/http/context.js.map +1 -1
  45. package/dist/links.d.ts +26 -0
  46. package/dist/links.js +60 -0
  47. package/dist/links.js.map +1 -0
  48. package/dist/onboarding.d.ts +1 -1
  49. package/dist/onboarding.js +28 -8
  50. package/dist/onboarding.js.map +1 -1
  51. package/dist/profiles/profiles.js +1 -1
  52. package/dist/profiles/profiles.js.map +1 -1
  53. package/dist/providers/catalog.d.ts +85 -1
  54. package/dist/providers/catalog.js +280 -43
  55. package/dist/providers/catalog.js.map +1 -1
  56. package/dist/providers/tables.d.ts +3 -1
  57. package/dist/providers/tables.js +9 -4
  58. package/dist/providers/tables.js.map +1 -1
  59. package/dist/redact.js +493 -13
  60. package/dist/redact.js.map +1 -1
  61. package/dist/runtime.js +104 -42
  62. package/dist/runtime.js.map +1 -1
  63. package/dist/sessions/anchor.d.ts +22 -3
  64. package/dist/sessions/anchor.js +87 -42
  65. package/dist/sessions/anchor.js.map +1 -1
  66. package/dist/sessions/background-tasks.d.ts +140 -0
  67. package/dist/sessions/background-tasks.js +352 -0
  68. package/dist/sessions/background-tasks.js.map +1 -0
  69. package/dist/sessions/background-updates.d.ts +56 -0
  70. package/dist/sessions/background-updates.js +152 -0
  71. package/dist/sessions/background-updates.js.map +1 -0
  72. package/dist/sessions/cli-sessions.d.ts +6 -0
  73. package/dist/sessions/cli-sessions.js +20 -7
  74. package/dist/sessions/cli-sessions.js.map +1 -1
  75. package/dist/sessions/completions.d.ts +17 -1
  76. package/dist/sessions/completions.js +84 -13
  77. package/dist/sessions/completions.js.map +1 -1
  78. package/dist/sessions/compress.d.ts +33 -0
  79. package/dist/sessions/compress.js +66 -0
  80. package/dist/sessions/compress.js.map +1 -0
  81. package/dist/sessions/list.d.ts +22 -4
  82. package/dist/sessions/list.js +113 -33
  83. package/dist/sessions/list.js.map +1 -1
  84. package/dist/sessions/merge.d.ts +81 -7
  85. package/dist/sessions/merge.js +391 -26
  86. package/dist/sessions/merge.js.map +1 -1
  87. package/dist/sessions/pending.d.ts +5 -0
  88. package/dist/sessions/pending.js +30 -10
  89. package/dist/sessions/pending.js.map +1 -1
  90. package/dist/sessions/relay.d.ts +7 -1
  91. package/dist/sessions/relay.js +17 -4
  92. package/dist/sessions/relay.js.map +1 -1
  93. package/dist/sessions/service.d.ts +118 -6
  94. package/dist/sessions/service.js +564 -65
  95. package/dist/sessions/service.js.map +1 -1
  96. package/dist/sessions/session.d.ts +22 -1
  97. package/dist/sessions/session.js +29 -2
  98. package/dist/sessions/session.js.map +1 -1
  99. package/dist/sessions/shares.js +11 -2
  100. package/dist/sessions/shares.js.map +1 -1
  101. package/dist/sessions/source-kind.d.ts +22 -0
  102. package/dist/sessions/source-kind.js +54 -0
  103. package/dist/sessions/source-kind.js.map +1 -0
  104. package/dist/sessions/state-db.d.ts +10 -1
  105. package/dist/sessions/state-db.js +48 -17
  106. package/dist/sessions/state-db.js.map +1 -1
  107. package/dist/sessions/store.d.ts +2 -0
  108. package/dist/sessions/store.js +24 -5
  109. package/dist/sessions/store.js.map +1 -1
  110. package/dist/sessions/streams.d.ts +3 -0
  111. package/dist/sessions/streams.js +0 -5
  112. package/dist/sessions/streams.js.map +1 -1
  113. package/dist/sessions/titles.d.ts +4 -0
  114. package/dist/sessions/titles.js +16 -0
  115. package/dist/sessions/titles.js.map +1 -1
  116. package/dist/sessions/turn-context.d.ts +1 -0
  117. package/dist/sessions/turn-context.js +3 -0
  118. package/dist/sessions/turn-context.js.map +1 -1
  119. package/dist/sessions/turn.d.ts +85 -7
  120. package/dist/sessions/turn.js +518 -170
  121. package/dist/sessions/turn.js.map +1 -1
  122. package/dist/settings.d.ts +2 -0
  123. package/dist/settings.js +13 -0
  124. package/dist/settings.js.map +1 -1
  125. package/dist/sidecar/fake.js +2 -0
  126. package/dist/sidecar/fake.js.map +1 -1
  127. package/dist/tools/commands.d.ts +15 -0
  128. package/dist/tools/commands.js +52 -0
  129. package/dist/tools/commands.js.map +1 -0
  130. package/dist/tools/crons.d.ts +38 -4
  131. package/dist/tools/crons.js +323 -36
  132. package/dist/tools/crons.js.map +1 -1
  133. package/dist/tools/mcp.js +5 -1
  134. package/dist/tools/mcp.js.map +1 -1
  135. package/dist/tools/update-notifications.d.ts +1 -1
  136. package/dist/tools/update-notifications.js +1 -1
  137. package/dist/tools/update-notifications.js.map +1 -1
  138. package/dist/tools/updates.d.ts +8 -3
  139. package/dist/tools/updates.js +133 -34
  140. package/dist/tools/updates.js.map +1 -1
  141. package/dist/workspace/git.d.ts +57 -10
  142. package/dist/workspace/git.js +302 -195
  143. package/dist/workspace/git.js.map +1 -1
  144. package/dist/workspace/media-refs.d.ts +13 -0
  145. package/dist/workspace/media-refs.js +165 -0
  146. package/dist/workspace/media-refs.js.map +1 -0
  147. package/dist/workspace/media.d.ts +19 -0
  148. package/dist/workspace/media.js +61 -2
  149. package/dist/workspace/media.js.map +1 -1
  150. package/dist/workspace/workspaces.d.ts +7 -1
  151. package/dist/workspace/workspaces.js +30 -21
  152. package/dist/workspace/workspaces.js.map +1 -1
  153. package/dist/workspace/worktrees.d.ts +2 -4
  154. package/dist/workspace/worktrees.js +28 -33
  155. package/dist/workspace/worktrees.js.map +1 -1
  156. package/package.json +4 -2
  157. package/sidecar/scripts/replay_sidecar.py +3 -1
  158. package/sidecar/talaria_sidecar/__init__.py +1 -1
  159. package/sidecar/talaria_sidecar/home.py +59 -2
  160. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  161. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  162. package/sidecar/talaria_sidecar/methods/chat.py +360 -27
  163. package/sidecar/talaria_sidecar/methods/commands.py +46 -31
  164. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  165. package/sidecar/talaria_sidecar/methods/cron.py +22 -14
  166. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  167. package/sidecar/talaria_sidecar/methods/process.py +318 -0
  168. package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
  169. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  170. package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
  171. package/sidecar/talaria_sidecar/methods/text.py +1 -20
  172. package/sidecar/talaria_sidecar/methods/usage.py +7 -1
  173. package/static/dist/FILES.txt +124 -119
  174. package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
  175. package/static/dist/assets/Button-nx37f4u1.js +1 -0
  176. package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
  177. package/static/dist/assets/Dialog-mec42hvb.js +1 -0
  178. package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
  179. package/static/dist/assets/{Markdown-emii55o0.js → Markdown-jqrx5es0.js} +125 -125
  180. package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
  181. package/static/dist/assets/Select-h4ivl9qf.js +1 -0
  182. package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
  183. package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
  184. package/static/dist/assets/States-ehsb6lzo.js +1 -0
  185. package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
  186. package/static/dist/assets/_app-m8d961ef.js +1 -0
  187. package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
  188. package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
  189. package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
  190. package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
  191. package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
  192. package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
  193. package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
  194. package/static/dist/assets/{_app.session._sessionId-m6nx9g8t.js → _app.session._sessionId-hh27wwu2.js} +1 -1
  195. package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
  196. package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
  197. package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
  198. package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
  199. package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
  200. package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
  201. package/static/dist/assets/{abnfDiagram-VCTEODGH-kejgxpil.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
  202. package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
  203. package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
  204. package/static/dist/assets/back-eu64w89w.js +1 -0
  205. package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
  206. package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
  207. package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
  208. package/static/dist/assets/channel-mbvmf2vv.js +1 -0
  209. package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
  210. package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
  211. package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
  212. package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-erassib3.js} +1 -1
  213. package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
  214. package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
  215. package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
  216. package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
  217. package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
  218. package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
  219. package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
  220. package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
  221. package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
  222. package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
  223. package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
  224. package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
  225. package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
  226. package/static/dist/assets/{copied-gpchexqu.js → copied-h65fc3cx.js} +1 -1
  227. package/static/dist/assets/copy-e6977uwq.js +1 -0
  228. package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
  229. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
  230. package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
  231. package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
  232. package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
  233. package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
  234. package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
  235. package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
  236. package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
  237. package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
  238. package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
  239. package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
  240. package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
  241. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-geqx7zgl.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
  242. package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
  243. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
  244. package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
  245. package/static/dist/assets/{index-f9bzglkn.js → index-h00usru2.js} +3 -3
  246. package/static/dist/assets/index-n73ijkv2.css +2 -0
  247. package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
  248. package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
  249. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
  250. package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
  251. package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
  252. package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
  253. package/static/dist/assets/{line-laaa8h99.js → line-dmzlfe0u.js} +1 -1
  254. package/static/dist/assets/link-budeg5t9.js +1 -0
  255. package/static/dist/assets/login-kjhasvp8.js +1 -0
  256. package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
  257. package/static/dist/assets/{mermaid-parser.core-ix4junux.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
  258. package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
  259. package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
  260. package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-gpv06065.js} +1 -1
  261. package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
  262. package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
  263. package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
  264. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
  265. package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
  266. package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
  267. package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
  268. package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
  269. package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
  270. package/static/dist/assets/{railroadDiagram-O6MQD6OU-jgroj2ew.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
  271. package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-kbcje1uy.js} +1 -1
  272. package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
  273. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
  274. package/static/dist/assets/{saved-brdfjjnw.js → saved-bazmcup9.js} +1 -1
  275. package/static/dist/assets/search-jen74z4u.js +1 -0
  276. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
  277. package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
  278. package/static/dist/assets/share._token-dan9hgvj.js +2 -0
  279. package/static/dist/assets/{sliders-horizontal-gcgscv8r.js → sliders-horizontal-f9cb3lh3.js} +1 -1
  280. package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
  281. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
  282. package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
  283. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
  284. package/static/dist/assets/{timeline-definition-24CTP7MA-olvknr62.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
  285. package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
  286. package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
  287. package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
  288. package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-htelhc68.js} +1 -1
  289. package/static/dist/assets/useForm-h2t0u14e.js +1 -0
  290. package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
  291. package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
  292. package/static/dist/assets/{useParams-katwvzil.js → useParams-dqd64jv7.js} +1 -1
  293. package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-j30pvsy7.js} +1 -1
  294. package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
  295. package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
  296. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
  297. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
  298. package/static/dist/index.html +1 -1
  299. package/static/dist/sw.js +1 -1
  300. package/static/dist/assets/AppShell-haqi9qzu.js +0 -1
  301. package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
  302. package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
  303. package/static/dist/assets/RightPanel-o73y98dn.js +0 -1
  304. package/static/dist/assets/Select-jld3yeni.js +0 -1
  305. package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
  306. package/static/dist/assets/SettingsLayout-lnn2wo2s.js +0 -1
  307. package/static/dist/assets/States-e5hv6gdh.js +0 -1
  308. package/static/dist/assets/Toaster-c4jq1i26.js +0 -1
  309. package/static/dist/assets/_app-bnip2pjg.js +0 -1
  310. package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
  311. package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
  312. package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
  313. package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
  314. package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
  315. package/static/dist/assets/_app.memory-i00paisv.js +0 -1
  316. package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
  317. package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
  318. package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
  319. package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
  320. package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
  321. package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
  322. package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
  323. package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
  324. package/static/dist/assets/architectureDiagram-5GKGNRK7-hqnf84fc.js +0 -36
  325. package/static/dist/assets/channel-o77ioktg.js +0 -1
  326. package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
  327. package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
  328. package/static/dist/assets/endpoints-e58saih3.js +0 -9
  329. package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
  330. package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
  331. package/static/dist/assets/index-gkh7kum4.css +0 -2
  332. package/static/dist/assets/infoDiagram-27XIBGKW-oo7kdyte.js +0 -2
  333. package/static/dist/assets/lazyRouteComponent-b1h2ykmb.js +0 -1
  334. package/static/dist/assets/link-d8f0788e.js +0 -1
  335. package/static/dist/assets/login-llkohrcg.js +0 -1
  336. package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
  337. package/static/dist/assets/onboarding-lx0d6b38.js +0 -1
  338. package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
  339. package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
  340. package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
  341. package/static/dist/assets/search-gt2ueyvg.js +0 -1
  342. package/static/dist/assets/share._token-oyjv046r.js +0 -2
  343. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
  344. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
  345. package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
  346. package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
  347. package/static/dist/assets/useForm-kd759sjk.js +0 -1
  348. package/static/dist/assets/useLocale-hds4xmja.js +0 -1
@@ -6,7 +6,7 @@
6
6
  * pathspecs, and keeps every Git subprocess shell-free, env-scrubbed, hardened
7
7
  * against repo-local config, and bounded by a timeout.
8
8
  */
9
- import { spawnSync } from 'node:child_process';
9
+ import { execFile } from 'node:child_process';
10
10
  import { closeSync, existsSync, fstatSync, lstatSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { dirname, join, relative, resolve, sep } from 'node:path';
@@ -21,6 +21,8 @@ export const STATUS_CACHE_LIMIT = 32;
21
21
  export const DIFF_SIZE_LIMIT = 512 * 1024;
22
22
  export const COMMIT_MESSAGE_DIFF_LIMIT = 64 * 1024;
23
23
  export const WORKSPACE_GIT_DESTRUCTIVE_ENV = 'HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE';
24
+ export const WORKSPACE_BUSY_MESSAGE = 'A Git operation is running in this workspace.';
25
+ export const WORKSPACE_WRITE_BUSY_MESSAGE = 'A file operation is running in this workspace.';
24
26
  const GIT_ENV_SCRUB_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_SYSTEM', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', 'GIT_ASKPASS', 'SSH_ASKPASS', 'GIT_SSH', 'GIT_SSH_COMMAND'];
25
27
  const GIT_ENV_SCRUB_PREFIXES = ['GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_'];
26
28
  const BRANCH_SWITCH_STASH_PREFIX = 'hermes-webui branch switch';
@@ -51,6 +53,67 @@ export class GitWorkspaceError extends Error {
51
53
  this.name = 'GitWorkspaceError';
52
54
  }
53
55
  }
56
+ /**
57
+ * Run `git` without blocking the event loop, resolving like `spawnSync`: the exit status (null when a signal ended it),
58
+ * plus the `error` that prevented a normal run (`ETIMEDOUT` when the timeout killed it). Without `env` the child
59
+ * inherits the server's environment.
60
+ */
61
+ export function spawnGit(cwd, argv, timeoutMs, env) {
62
+ return new Promise((done) => {
63
+ execFile('git', argv, { cwd, ...(env ? { env } : {}), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
64
+ if (!error)
65
+ done({ status: 0, stdout, stderr });
66
+ else if (typeof error.code === 'number')
67
+ done({ status: error.code, stdout, stderr });
68
+ else if (error.code == null)
69
+ done({ status: null, stdout, stderr, ...(error.killed ? { error: Object.assign(new Error('git timed out'), { code: 'ETIMEDOUT' }) } : {}) });
70
+ else
71
+ done({ status: null, stdout, stderr, error: error });
72
+ });
73
+ });
74
+ }
75
+ /**
76
+ * The nearest directory at or above `path` holding a `.git` entry (a repository, linked worktree, or submodule root), the
77
+ * root Git itself would discover, found synchronously so a caller can hold it before its first await.
78
+ */
79
+ export function enclosingRepoRoot(path) {
80
+ let dir = resolvePathLikePython(path);
81
+ for (;;) {
82
+ if (existsSync(join(dir, '.git')))
83
+ return dir;
84
+ const parent = dirname(dir);
85
+ if (parent === dir)
86
+ return null;
87
+ dir = parent;
88
+ }
89
+ }
90
+ /** Whether two paths are the same directory or one contains the other. */
91
+ export function pathsOverlap(a, b) {
92
+ const x = resolvePathLikePython(a);
93
+ const y = resolvePathLikePython(b);
94
+ return x === y || isWithin(x, y) || isWithin(y, x);
95
+ }
96
+ function overlapsAny(held, path) {
97
+ for (const p of held.keys())
98
+ if (pathsOverlap(p, path))
99
+ return true;
100
+ return false;
101
+ }
102
+ /** Count `path` in `held` until `fn` settles; the count is taken before `fn` runs. */
103
+ async function holding(held, path, fn) {
104
+ const key = resolvePathLikePython(path);
105
+ held.set(key, (held.get(key) ?? 0) + 1);
106
+ try {
107
+ return await fn();
108
+ }
109
+ finally {
110
+ const left = (held.get(key) ?? 1) - 1;
111
+ if (left)
112
+ held.set(key, left);
113
+ else
114
+ held.delete(key);
115
+ }
116
+ }
54
117
  export function classifyGitError(message, args = []) {
55
118
  const text = message.toLowerCase();
56
119
  const joined = args.join(' ').toLowerCase();
@@ -112,9 +175,38 @@ export class GitRunner {
112
175
  locks = new Map();
113
176
  statusCache = new Map();
114
177
  generations = new Map();
178
+ /** Workspaces a working-tree mutation or worktree removal is changing; runs, terminals, and file writes stay out. */
179
+ busy = new Map();
180
+ /** Workspaces an asynchronous writer (worktree creation) is changing; working-tree mutations stay out. */
181
+ writing = new Map();
115
182
  constructor(deps) {
116
183
  this.deps = deps;
117
184
  }
185
+ /**
186
+ * Hold `path` busy until `fn` settles. The hold is taken before `fn` runs, so checks `fn` makes synchronously (no run
187
+ * active) and admission's `workspaceBusy` refusal leave no gap for a chat run, terminal, or file write in between.
188
+ */
189
+ holdWorkspace(path, fn) {
190
+ return holding(this.busy, path, fn);
191
+ }
192
+ /** Whether `path` overlaps a held workspace (the same directory, inside it, or containing it). */
193
+ workspaceBusy(path) {
194
+ return overlapsAny(this.busy, path);
195
+ }
196
+ /** Run an asynchronous workspace writer at `path`: refused while Git holds it, and Git mutations refuse meanwhile. */
197
+ holdWrite(path, fn) {
198
+ if (this.workspaceBusy(path))
199
+ throw new GitWorkspaceError(WORKSPACE_BUSY_MESSAGE, 'operation_in_progress');
200
+ return holding(this.writing, path, fn);
201
+ }
202
+ /**
203
+ * Worktree removal: serialized with Git mutations in the main checkout and in the worktree (the same per-repo locks),
204
+ * and held busy so no run, terminal, or file write starts there. `fn`'s synchronous lock checks run inside the hold.
205
+ */
206
+ async removeWorktreeExclusive(worktree, repoRoot, fn) {
207
+ const inWorktree = () => this.withLock(worktree, () => this.holdWorkspace(worktree, fn));
208
+ return repoRoot ? await this.withLock(repoRoot, inWorktree) : await inWorktree();
209
+ }
118
210
  destructiveEnabled() {
119
211
  return ['1', 'true', 'yes', 'on'].includes((this.deps.env[WORKSPACE_GIT_DESTRUCTIVE_ENV] ?? '').trim().toLowerCase());
120
212
  }
@@ -136,11 +228,8 @@ export class GitRunner {
136
228
  return this.deps.now ? this.deps.now() * 1000 : Date.now();
137
229
  }
138
230
  // ── subprocess ───────────────────────────────────────────────────────────
139
- rawGit(cwd, argv, env, timeoutMs, input) {
140
- return spawnSync('git', argv, { cwd, env, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true, ...(input !== undefined ? { input } : {}) });
141
- }
142
- configNamesForScope(scope, cwd, env, pattern, nameRe, ignoreUnsupported = false) {
143
- const result = this.rawGit(cwd, ['config', '--includes', scope, '--name-only', '--get-regexp', pattern], env, GIT_TIMEOUT_MS);
231
+ async configNamesForScope(scope, cwd, env, pattern, nameRe, ignoreUnsupported = false) {
232
+ const result = await spawnGit(cwd, ['config', '--includes', scope, '--name-only', '--get-regexp', pattern], GIT_TIMEOUT_MS, env);
144
233
  const names = new Set();
145
234
  if (result.status !== 0 && result.status !== 1) {
146
235
  if (ignoreUnsupported)
@@ -155,28 +244,28 @@ export class GitRunner {
155
244
  }
156
245
  return names;
157
246
  }
158
- namesLocalAndWorktree(cwd, env, pattern, nameRe) {
159
- const names = this.configNamesForScope('--local', cwd, env, pattern, nameRe);
160
- for (const n of this.configNamesForScope('--worktree', cwd, env, pattern, nameRe, true))
247
+ async namesLocalAndWorktree(cwd, env, pattern, nameRe) {
248
+ const names = await this.configNamesForScope('--local', cwd, env, pattern, nameRe);
249
+ for (const n of await this.configNamesForScope('--worktree', cwd, env, pattern, nameRe, true))
161
250
  names.add(n);
162
251
  return names;
163
252
  }
164
- filterNames(cwd, env) {
165
- return this.namesLocalAndWorktree(cwd, env, String.raw `^filter\..*\.(clean|smudge|process|required)$`, /^filter\.(.+)\.(clean|smudge|process|required)$/);
253
+ async filterNames(cwd, env) {
254
+ return await this.namesLocalAndWorktree(cwd, env, String.raw `^filter\..*\.(clean|smudge|process|required)$`, /^filter\.(.+)\.(clean|smudge|process|required)$/);
166
255
  }
167
- mergeDriverNames(cwd, env) {
168
- return this.namesLocalAndWorktree(cwd, env, String.raw `^merge\..*\.driver$`, /^merge\.(.+)\.driver$/);
256
+ async mergeDriverNames(cwd, env) {
257
+ return await this.namesLocalAndWorktree(cwd, env, String.raw `^merge\..*\.driver$`, /^merge\.(.+)\.driver$/);
169
258
  }
170
- remoteHelperNames(cwd, env) {
171
- return this.namesLocalAndWorktree(cwd, env, String.raw `^remote\..*\.(uploadpack|receivepack)$`, /^remote\.(.+)\.(uploadpack|receivepack)$/);
259
+ async remoteHelperNames(cwd, env) {
260
+ return await this.namesLocalAndWorktree(cwd, env, String.raw `^remote\..*\.(uploadpack|receivepack)$`, /^remote\.(.+)\.(uploadpack|receivepack)$/);
172
261
  }
173
262
  static legal(name) {
174
263
  return !name.includes('\n') && !name.includes('\0');
175
264
  }
176
- hasRepoLocalFilters(cwd) {
177
- return this.filterNames(cwd, this.cleanEnv()).size > 0;
265
+ async hasRepoLocalFilters(cwd) {
266
+ return (await this.filterNames(cwd, this.cleanEnv())).size > 0;
178
267
  }
179
- run(ctxOrCwd, args, opts = {}) {
268
+ async run(ctxOrCwd, args, opts = {}) {
180
269
  const cwd = typeof ctxOrCwd === 'string' ? ctxOrCwd : ctxOrCwd.repoRoot;
181
270
  const runEnv = this.cleanEnv(opts.env);
182
271
  const effectiveDestructive = Boolean(opts.destructive) && this.destructiveEnabled();
@@ -191,21 +280,21 @@ export class GitRunner {
191
280
  writeFileSync(attributesFile, '');
192
281
  }
193
282
  if (opts.disableFilterAttributes || opts.neutralizeFilterPrograms) {
194
- for (const name of [...this.filterNames(cwd, runEnv)].sort()) {
283
+ for (const name of [...await this.filterNames(cwd, runEnv)].sort()) {
195
284
  if (!GitRunner.legal(name))
196
285
  continue;
197
286
  extraConfigs.push([`filter.${name}.clean`, 'cat'], [`filter.${name}.smudge`, 'cat'], [`filter.${name}.process`, ''], [`filter.${name}.required`, 'false']);
198
287
  }
199
288
  }
200
289
  if (effectiveDestructive) {
201
- for (const name of [...this.mergeDriverNames(cwd, runEnv)].sort()) {
290
+ for (const name of [...await this.mergeDriverNames(cwd, runEnv)].sort()) {
202
291
  if (!GitRunner.legal(name))
203
292
  continue;
204
293
  extraConfigs.push([`merge.${name}.driver`, 'git merge-file "%A" "%O" "%B"']);
205
294
  }
206
295
  }
207
296
  if (effectiveDestructive || opts.neutralizeRemoteHelpers) {
208
- const names = [...this.remoteHelperNames(cwd, runEnv)].sort();
297
+ const names = [...await this.remoteHelperNames(cwd, runEnv)].sort();
209
298
  for (const name of names) {
210
299
  if (!GitRunner.legal(name))
211
300
  continue;
@@ -240,7 +329,7 @@ export class GitRunner {
240
329
  if (attributesFile)
241
330
  full.push('-c', `core.attributesFile=${attributesFile}`);
242
331
  full.push(...argv);
243
- const result = this.rawGit(cwd, full, runEnv, opts.timeoutMs ?? GIT_TIMEOUT_MS, opts.input);
332
+ const result = await spawnGit(cwd, full, opts.timeoutMs ?? GIT_TIMEOUT_MS, runEnv);
244
333
  if (result.error) {
245
334
  const err = result.error;
246
335
  if (err.code === 'ETIMEDOUT')
@@ -249,7 +338,7 @@ export class GitRunner {
249
338
  throw new GitWorkspaceError('Git is not installed or not available on PATH', 'missing_git');
250
339
  throw new GitWorkspaceError(err.message, classifyGitError(err.message, args));
251
340
  }
252
- const out = { status: result.status ?? 1, stdout: result.stdout || '', stderr: result.stderr || '' };
341
+ const out = { status: result.status ?? 1, stdout: result.stdout, stderr: result.stderr };
253
342
  if (opts.check && out.status !== 0) {
254
343
  const message = (out.stderr || out.stdout || 'Git command failed').trim();
255
344
  throw new GitWorkspaceError(message, classifyGitError(message, args));
@@ -264,7 +353,7 @@ export class GitRunner {
264
353
  }
265
354
  }
266
355
  // ── context and paths ────────────────────────────────────────────────────
267
- resolveContext(workspace) {
356
+ async resolveContext(workspace) {
268
357
  const ws = resolvePathLikePython(workspace);
269
358
  // Python ran `git rev-parse` with `cwd=<workspace>`; a vanished directory raised `FileNotFoundError`, which `_run_git`
270
359
  // reported as `missing_git` — every route answered 400 rather than "not a repo".
@@ -272,7 +361,7 @@ export class GitRunner {
272
361
  throw new GitWorkspaceError('Git is not installed or not available on PATH', 'missing_git');
273
362
  let result;
274
363
  try {
275
- result = this.run(ws, ['rev-parse', '--show-toplevel']);
364
+ result = await this.run(ws, ['rev-parse', '--show-toplevel']);
276
365
  }
277
366
  catch (error) {
278
367
  if (error instanceof GitWorkspaceError && (error.code === 'missing_git' || error.code === 'timeout'))
@@ -317,28 +406,46 @@ export class GitRunner {
317
406
  return null;
318
407
  }
319
408
  // ── mutation lock ────────────────────────────────────────────────────────
320
- async withMutationLock(ctx, fn) {
321
- const key = ctx.repoRoot;
409
+ /**
410
+ * The per-repo mutation lock. `holdRepo` (working-tree mutations) also holds the whole repository busy and, in the same
411
+ * tick, refuses when a run or an asynchronous writer is already active anywhere in it.
412
+ */
413
+ async withMutationLock(ctx, fn, { holdRepo = true } = {}) {
414
+ try {
415
+ return await this.withLock(ctx.repoRoot, () => (holdRepo ? this.holdWorkspace(ctx.repoRoot, () => { this.assertRepoFree(ctx.repoRoot); return fn(); }) : fn()));
416
+ }
417
+ finally {
418
+ this.invalidateStatusCache(ctx.repoRoot);
419
+ }
420
+ }
421
+ assertRepoFree(repoRoot) {
422
+ if (this.deps.activeRunIn?.(repoRoot))
423
+ throw new GitWorkspaceError('A session run is active. Wait for it to finish before running this Git operation.', 'active_stream');
424
+ if (overlapsAny(this.writing, repoRoot))
425
+ throw new GitWorkspaceError(WORKSPACE_WRITE_BUSY_MESSAGE, 'operation_in_progress');
426
+ }
427
+ async withLock(path, fn) {
428
+ const key = resolvePathLikePython(path);
322
429
  const previous = this.locks.get(key) ?? Promise.resolve();
323
430
  let release;
324
431
  const current = new Promise((r) => { release = r; });
325
432
  const chained = previous.then(() => current);
326
433
  this.locks.set(key, chained);
434
+ // Drop the entry once this link settles and nothing queued behind it, whether it ran or timed out waiting.
435
+ void chained.then(() => { if (this.locks.get(key) === chained)
436
+ this.locks.delete(key); });
327
437
  let timer;
328
- const acquired = await Promise.race([previous.then(() => true), new Promise((r) => { timer = setTimeout(() => { r(false); }, GIT_REMOTE_TIMEOUT_MS); })]);
438
+ const acquired = await Promise.race([previous.then(() => true), new Promise((r) => { timer = setTimeout(() => { r(false); }, this.deps.mutationLockTimeoutMs ?? GIT_REMOTE_TIMEOUT_MS); })]);
329
439
  clearTimeout(timer);
330
440
  if (!acquired) {
331
441
  release();
332
442
  throw new GitWorkspaceError('Another Git operation is still running', 'operation_in_progress');
333
443
  }
334
444
  try {
335
- return fn();
445
+ return await fn();
336
446
  }
337
447
  finally {
338
- this.invalidateStatusCache(ctx.repoRoot);
339
448
  release();
340
- if (this.locks.get(key) === chained)
341
- this.locks.delete(key);
342
449
  }
343
450
  }
344
451
  // ── status cache ─────────────────────────────────────────────────────────
@@ -441,12 +548,12 @@ export class GitRunner {
441
548
  }
442
549
  return stats;
443
550
  }
444
- collectNumstat(ctx, cached) {
551
+ async collectNumstat(ctx, cached) {
445
552
  const args = ['diff', '--numstat', '-z', '--no-textconv', '--ignore-cr-at-eol'];
446
553
  if (cached)
447
554
  args.push('--cached');
448
555
  args.push('--', GitRunner.workspacePathspec(ctx));
449
- const result = this.run(ctx, args, { disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
556
+ const result = await this.run(ctx, args, { disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
450
557
  if (result.status !== 0)
451
558
  return null;
452
559
  return this.parseNumstat(result.stdout, ctx);
@@ -502,20 +609,20 @@ export class GitRunner {
502
609
  return [0, 0, true];
503
610
  return [splitLines(text).length || (text ? 1 : 0), 0, false];
504
611
  }
505
- status(workspace, opts = {}) {
612
+ async status(workspace, opts = {}) {
506
613
  const resolved = resolvePathLikePython(workspace);
507
614
  if (opts.useCache) {
508
615
  const cached = this.cachedStatus(resolved);
509
616
  if (cached)
510
617
  return cached;
511
618
  }
512
- const ctx = this.resolveContext(resolved);
619
+ const ctx = await this.resolveContext(resolved);
513
620
  if (!ctx)
514
621
  return { is_git: false };
515
622
  const generation = this.generations.get(ctx.repoRoot) ?? 0;
516
- const result = this.run(ctx, ['status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--', GitRunner.workspacePathspec(ctx)], { check: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
517
- const stagedStats = this.collectNumstat(ctx, true);
518
- const unstagedStats = this.collectNumstat(ctx, false);
623
+ const result = await this.run(ctx, ['status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--', GitRunner.workspacePathspec(ctx)], { check: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
624
+ const stagedStats = await this.collectNumstat(ctx, true);
625
+ const unstagedStats = await this.collectNumstat(ctx, false);
519
626
  const statSources = [stagedStats ?? new Map(), unstagedStats ?? new Map()];
520
627
  let branch = '';
521
628
  let upstream = '';
@@ -660,7 +767,7 @@ export class GitRunner {
660
767
  totals.conflicts += 1;
661
768
  }
662
769
  if (!branch)
663
- branch = this.run(ctx, ['rev-parse', '--short', 'HEAD']).stdout.trim();
770
+ branch = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'])).stdout.trim();
664
771
  const payload = {
665
772
  is_git: true, branch: branch || 'HEAD', upstream, ahead, behind, totals, files: fileList, truncated,
666
773
  noise_filtering: { ...noise, active: noise.filemode_only > 0 || noise.crlf_only > 0 },
@@ -669,10 +776,10 @@ export class GitRunner {
669
776
  return payload;
670
777
  }
671
778
  // ── branches ─────────────────────────────────────────────────────────────
672
- branchAheadBehind(ctx, branch, upstream) {
779
+ async branchAheadBehind(ctx, branch, upstream) {
673
780
  if (!upstream)
674
781
  return [0, 0];
675
- const result = this.run(ctx, ['rev-list', '--left-right', '--count', `${branch}...${upstream}`]);
782
+ const result = await this.run(ctx, ['rev-list', '--left-right', '--count', `${branch}...${upstream}`]);
676
783
  if (result.status !== 0)
677
784
  return [0, 0];
678
785
  const parts = result.stdout.trim().split(/\s+/);
@@ -682,9 +789,9 @@ export class GitRunner {
682
789
  const b = Number.parseInt(parts[1] ?? '', 10);
683
790
  return Number.isFinite(a) && Number.isFinite(b) ? [a, b] : [0, 0];
684
791
  }
685
- forEachRef(ctx, refPrefix) {
792
+ async forEachRef(ctx, refPrefix) {
686
793
  const fmt = '%(refname)%00%(refname:short)%00%(upstream:short)%00%(objectname:short)%00%(committerdate:unix)%00%(committerdate:relative)%00%(authorname)%00%(subject)';
687
- const result = this.run(ctx, ['for-each-ref', `--format=${fmt}`, refPrefix], { check: true });
794
+ const result = await this.run(ctx, ['for-each-ref', `--format=${fmt}`, refPrefix], { check: true });
688
795
  const refs = [];
689
796
  for (const line of result.stdout.split('\n')) {
690
797
  const [fullName = '', name = '', upstream = '', sha = '', updated = '', updatedRelative = '', author = '', subject = ''] = line.split('\0');
@@ -694,65 +801,65 @@ export class GitRunner {
694
801
  continue;
695
802
  const item = { name, sha, updated: /^\d+$/.test(updated) ? Number.parseInt(updated, 10) : 0, updated_relative: updatedRelative, author, subject, upstream: '', ahead: 0, behind: 0 };
696
803
  if (upstream) {
697
- const [ahead, behind] = this.branchAheadBehind(ctx, name, upstream);
804
+ const [ahead, behind] = await this.branchAheadBehind(ctx, name, upstream);
698
805
  Object.assign(item, { upstream, ahead, behind });
699
806
  }
700
807
  refs.push(item);
701
808
  }
702
809
  return refs.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0));
703
810
  }
704
- branches(workspace) {
705
- const ctx = this.resolveContext(workspace);
811
+ async branches(workspace) {
812
+ const ctx = await this.resolveContext(workspace);
706
813
  if (!ctx)
707
814
  throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
708
- const headName = this.run(ctx, ['branch', '--show-current'], { check: true }).stdout.trim();
709
- const headSha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
710
- const status = this.status(workspace);
815
+ const headName = (await this.run(ctx, ['branch', '--show-current'], { check: true })).stdout.trim();
816
+ const headSha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
817
+ const status = await this.status(workspace);
711
818
  return {
712
819
  is_git: true, current: headName || headSha || 'HEAD', detached: !headName, head: headSha,
713
- local: this.forEachRef(ctx, 'refs/heads'), remote: this.forEachRef(ctx, 'refs/remotes'),
820
+ local: await this.forEachRef(ctx, 'refs/heads'), remote: await this.forEachRef(ctx, 'refs/remotes'),
714
821
  upstream: status.upstream ?? '', ahead: status.ahead ?? 0, behind: status.behind ?? 0,
715
822
  };
716
823
  }
717
824
  // ── checkout ─────────────────────────────────────────────────────────────
718
- validateLocalBranch(ctx, ref) {
825
+ async validateLocalBranch(ctx, ref) {
719
826
  const r = ref.trim();
720
827
  if (!r)
721
828
  throw new GitWorkspaceError('Branch name is required', 'invalid_ref');
722
- this.run(ctx, ['show-ref', '--verify', `refs/heads/${r}`], { check: true });
829
+ await this.run(ctx, ['show-ref', '--verify', `refs/heads/${r}`], { check: true });
723
830
  return r;
724
831
  }
725
- validateRemoteBranch(ctx, ref) {
832
+ async validateRemoteBranch(ctx, ref) {
726
833
  const r = ref.trim();
727
834
  if (!r)
728
835
  throw new GitWorkspaceError('Remote branch name is required', 'invalid_ref');
729
- this.run(ctx, ['show-ref', '--verify', `refs/remotes/${r}`], { check: true });
836
+ await this.run(ctx, ['show-ref', '--verify', `refs/remotes/${r}`], { check: true });
730
837
  return r;
731
838
  }
732
- validateCheckoutStart(ctx, ref) {
839
+ async validateCheckoutStart(ctx, ref) {
733
840
  const r = (ref || 'HEAD').trim() || 'HEAD';
734
- if (this.run(ctx, ['rev-parse', '--verify', `${r}^{commit}`]).status !== 0)
841
+ if ((await this.run(ctx, ['rev-parse', '--verify', `${r}^{commit}`])).status !== 0)
735
842
  throw new GitWorkspaceError('Invalid checkout reference', 'invalid_ref');
736
843
  return r;
737
844
  }
738
- validateNewBranchName(ctx, name) {
845
+ async validateNewBranchName(ctx, name) {
739
846
  const n = name.trim();
740
847
  if (!n)
741
848
  throw new GitWorkspaceError('New branch name is required', 'invalid_ref');
742
- if (this.run(ctx, ['check-ref-format', '--branch', n]).status !== 0)
849
+ if ((await this.run(ctx, ['check-ref-format', '--branch', n])).status !== 0)
743
850
  throw new GitWorkspaceError('Invalid branch name', 'invalid_ref');
744
- if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${n}`]).status === 0)
851
+ if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${n}`])).status === 0)
745
852
  throw new GitWorkspaceError('A local branch with that name already exists', 'invalid_ref');
746
853
  return n;
747
854
  }
748
- dirtyWorktree(ctx) {
749
- return this.run(ctx, ['status', '--porcelain=v2', '--untracked-files=all'], { check: true, neutralizeFilterPrograms: true }).stdout.trim() !== '';
855
+ async dirtyWorktree(ctx) {
856
+ return (await this.run(ctx, ['status', '--porcelain=v2', '--untracked-files=all'], { check: true, neutralizeFilterPrograms: true })).stdout.trim() !== '';
750
857
  }
751
- currentCheckoutLabel(ctx) {
752
- const branch = this.run(ctx, ['branch', '--show-current']).stdout.trim();
858
+ async currentCheckoutLabel(ctx) {
859
+ const branch = (await this.run(ctx, ['branch', '--show-current'])).stdout.trim();
753
860
  if (branch)
754
861
  return branch;
755
- return this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim() || 'HEAD';
862
+ return (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim() || 'HEAD';
756
863
  }
757
864
  static stashSubjectParts(subject) {
758
865
  const s = subject.trim();
@@ -765,8 +872,8 @@ export class GitRunner {
765
872
  return null;
766
873
  return [branch, message];
767
874
  }
768
- branchSwitchStashes(ctx) {
769
- const result = this.run(ctx, ['stash', 'list', '--format=%gd%x00%gs']);
875
+ async branchSwitchStashes(ctx) {
876
+ const result = await this.run(ctx, ['stash', 'list', '--format=%gd%x00%gs']);
770
877
  if (result.status !== 0)
771
878
  return [];
772
879
  const out = [];
@@ -781,129 +888,127 @@ export class GitRunner {
781
888
  }
782
889
  return out;
783
890
  }
784
- restoreBranchSwitchStashLocked(ctx, branch) {
785
- if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot))
891
+ async restoreBranchSwitchStashLocked(ctx, branch) {
892
+ if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot))
786
893
  return { restore_blocked: true, restore_reason: 'Repository defines local filter programs' };
787
- if (this.dirtyWorktree(ctx))
894
+ if (await this.dirtyWorktree(ctx))
788
895
  return {};
789
- for (const item of this.branchSwitchStashes(ctx)) {
896
+ for (const item of await this.branchSwitchStashes(ctx)) {
790
897
  if (item.branch !== branch)
791
898
  continue;
792
- const result = this.run(ctx, ['stash', 'pop', '--index', item.ref], { destructive: true, disableFilterAttributes: true });
899
+ const result = await this.run(ctx, ['stash', 'pop', '--index', item.ref], { destructive: true, disableFilterAttributes: true });
793
900
  if (result.status === 0)
794
901
  return { restored_stash: item };
795
902
  return { restore_failed: true, restore_error: (result.stderr || result.stdout || 'Git stash restore failed').trim(), restore_stash: item };
796
903
  }
797
904
  return {};
798
905
  }
799
- validateCheckoutRequestLocked(ctx, ref, mode, newBranch) {
906
+ async validateCheckoutRequestLocked(ctx, ref, mode, newBranch) {
800
907
  if (mode === 'local') {
801
- this.validateLocalBranch(ctx, ref);
908
+ await this.validateLocalBranch(ctx, ref);
802
909
  return;
803
910
  }
804
911
  if (mode === 'new' || mode === 'create') {
805
- this.validateNewBranchName(ctx, newBranch || ref);
806
- this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
912
+ await this.validateNewBranchName(ctx, newBranch || ref);
913
+ await this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
807
914
  return;
808
915
  }
809
916
  if (mode === 'remote') {
810
- const remoteRef = this.validateRemoteBranch(ctx, ref);
917
+ const remoteRef = await this.validateRemoteBranch(ctx, ref);
811
918
  const branchName = (newBranch || remoteRef.split('/').slice(1).join('/')).trim();
812
- if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`]).status !== 0)
813
- this.validateNewBranchName(ctx, branchName);
919
+ if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`])).status !== 0)
920
+ await this.validateNewBranchName(ctx, branchName);
814
921
  return;
815
922
  }
816
923
  if (mode === 'detached' || mode === 'detach') {
817
- this.validateCheckoutStart(ctx, ref);
924
+ await this.validateCheckoutStart(ctx, ref);
818
925
  return;
819
926
  }
820
927
  throw new GitWorkspaceError('Unsupported checkout mode', 'invalid_ref');
821
928
  }
822
- performCheckoutLocked(ctx, ref, mode, newBranch, track) {
823
- if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot)) {
929
+ async performCheckoutLocked(ctx, ref, mode, newBranch, track) {
930
+ if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot)) {
824
931
  throw new GitWorkspaceError('Cannot checkout: repository defines local filter programs that would alter file content', 'filtered_path');
825
932
  }
826
933
  const destructive = { check: true, destructive: true, disableFilterAttributes: true };
827
934
  if (mode === 'local')
828
- return this.run(ctx, ['switch', '--recurse-submodules=no', this.validateLocalBranch(ctx, ref)], destructive);
935
+ return await this.run(ctx, ['switch', '--recurse-submodules=no', await this.validateLocalBranch(ctx, ref)], destructive);
829
936
  if (mode === 'new' || mode === 'create') {
830
- const branch = this.validateNewBranchName(ctx, newBranch || ref);
831
- const startRef = this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
832
- return this.run(ctx, ['switch', '--recurse-submodules=no', '-c', branch, startRef], destructive);
937
+ const branch = await this.validateNewBranchName(ctx, newBranch || ref);
938
+ const startRef = await this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
939
+ return await this.run(ctx, ['switch', '--recurse-submodules=no', '-c', branch, startRef], destructive);
833
940
  }
834
941
  if (mode === 'remote') {
835
- const remoteRef = this.validateRemoteBranch(ctx, ref);
942
+ const remoteRef = await this.validateRemoteBranch(ctx, ref);
836
943
  const branchName = (newBranch || remoteRef.split('/').slice(1).join('/')).trim();
837
- if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`]).status === 0) {
838
- const result = this.run(ctx, ['switch', '--recurse-submodules=no', branchName], destructive);
944
+ if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`])).status === 0) {
945
+ const result = await this.run(ctx, ['switch', '--recurse-submodules=no', branchName], destructive);
839
946
  if (track)
840
- this.run(ctx, ['branch', '--set-upstream-to', remoteRef, branchName]);
947
+ await this.run(ctx, ['branch', '--set-upstream-to', remoteRef, branchName]);
841
948
  return result;
842
949
  }
843
- const branch = this.validateNewBranchName(ctx, branchName);
950
+ const branch = await this.validateNewBranchName(ctx, branchName);
844
951
  const args = ['switch', '--recurse-submodules=no', '-c', branch];
845
952
  if (track)
846
953
  args.push('--track');
847
954
  args.push(remoteRef);
848
- return this.run(ctx, args, destructive);
955
+ return await this.run(ctx, args, destructive);
849
956
  }
850
957
  if (mode === 'detached' || mode === 'detach')
851
- return this.run(ctx, ['switch', '--recurse-submodules=no', '--detach', this.validateCheckoutStart(ctx, ref)], destructive);
958
+ return await this.run(ctx, ['switch', '--recurse-submodules=no', '--detach', await this.validateCheckoutStart(ctx, ref)], destructive);
852
959
  throw new GitWorkspaceError('Unsupported checkout mode', 'invalid_ref');
853
960
  }
854
961
  static remoteMessage(result) {
855
962
  return (result.stdout || result.stderr || '').trim();
856
963
  }
857
964
  async checkout(workspace, ref, mode, opts = {}) {
858
- const ctx = this.resolveContext(workspace);
965
+ const ctx = await this.resolveContext(workspace);
859
966
  if (!ctx)
860
967
  throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
861
968
  const m = (mode || 'local').trim().toLowerCase();
862
969
  const dirtyMode = (opts.dirtyMode || 'block').trim().toLowerCase();
863
970
  if (dirtyMode !== 'block')
864
971
  throw new GitWorkspaceError('Only dirty_mode=block is supported for branch checkout', 'dirty_worktree');
865
- const result = await this.withMutationLock(ctx, () => {
866
- this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
867
- if (this.dirtyWorktree(ctx))
972
+ const result = await this.withMutationLock(ctx, async () => {
973
+ await this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
974
+ if (await this.dirtyWorktree(ctx))
868
975
  throw new GitWorkspaceError('Checkout blocked because the Git worktree has uncommitted changes', 'dirty_worktree');
869
- return this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
976
+ const checkedOut = await this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
977
+ return { checkedOut, status: await this.status(workspace), branches: await this.branches(workspace) };
870
978
  });
871
- const status = this.status(workspace);
872
- const branches = this.branches(workspace);
873
- return { ok: true, message: GitRunner.remoteMessage(result), current_branch: branches.current, status, branches };
979
+ const { status, branches } = result;
980
+ return { ok: true, message: GitRunner.remoteMessage(result.checkedOut), current_branch: branches.current, status, branches };
874
981
  }
875
982
  async stashAndCheckout(workspace, ref, mode, opts = {}) {
876
- const ctx = this.resolveContext(workspace);
983
+ const ctx = await this.resolveContext(workspace);
877
984
  if (!ctx)
878
985
  throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
879
986
  const m = (mode || 'local').trim().toLowerCase();
880
987
  const targetLabel = (opts.newBranch || ref || 'HEAD').trim() || 'HEAD';
881
988
  const stashName = `${BRANCH_SWITCH_STASH_PREFIX} to ${targetLabel}`.trim();
882
- const outcome = await this.withMutationLock(ctx, () => {
883
- this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
884
- if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot)) {
989
+ const outcome = await this.withMutationLock(ctx, async () => {
990
+ await this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
991
+ if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot)) {
885
992
  throw new GitWorkspaceError('Cannot stash: repository defines local filter programs that would alter file content', 'filtered_path');
886
993
  }
887
994
  let stashed = false;
888
- if (this.dirtyWorktree(ctx)) {
889
- const stashResult = this.run(ctx, ['stash', 'push', '-u', '-m', stashName], { check: true, destructive: true, disableFilterAttributes: true });
995
+ if (await this.dirtyWorktree(ctx)) {
996
+ const stashResult = await this.run(ctx, ['stash', 'push', '-u', '-m', stashName], { check: true, destructive: true, disableFilterAttributes: true });
890
997
  stashed = !GitRunner.remoteMessage(stashResult).includes('No local changes to save');
891
998
  }
892
999
  let result;
893
1000
  try {
894
- result = this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
1001
+ result = await this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
895
1002
  }
896
1003
  catch (error) {
897
1004
  if (stashed)
898
- this.run(ctx, ['stash', 'pop', '--index', 'stash@{0}'], { destructive: true, disableFilterAttributes: true });
1005
+ await this.run(ctx, ['stash', 'pop', '--index', 'stash@{0}'], { destructive: true, disableFilterAttributes: true });
899
1006
  throw error;
900
1007
  }
901
- const restored = this.restoreBranchSwitchStashLocked(ctx, this.currentCheckoutLabel(ctx));
902
- return { result, stashed, restored };
1008
+ const restored = await this.restoreBranchSwitchStashLocked(ctx, await this.currentCheckoutLabel(ctx));
1009
+ return { result, stashed, restored, status: await this.status(workspace), branches: await this.branches(workspace) };
903
1010
  });
904
- const status = this.status(workspace);
905
- const branches = this.branches(workspace);
906
- const { restored } = outcome;
1011
+ const { restored, status, branches } = outcome;
907
1012
  return {
908
1013
  ok: true, message: GitRunner.remoteMessage(outcome.result), stash_name: outcome.stashed ? stashName : '', stashed: outcome.stashed,
909
1014
  restored_stash: restored.restored_stash ?? null, restore_failed: Boolean(restored.restore_failed), restore_error: str(restored.restore_error),
@@ -929,15 +1034,15 @@ export class GitRunner {
929
1034
  const [additions, deletions] = diffStats(diff);
930
1035
  return { binary: false, too_large: tooLarge, diff, additions, deletions };
931
1036
  }
932
- diff(workspace, path, kind = 'unstaged') {
933
- const ctx = this.resolveContext(workspace);
1037
+ async diff(workspace, path, kind = 'unstaged') {
1038
+ const ctx = await this.resolveContext(workspace);
934
1039
  if (!ctx)
935
1040
  throw new GitWorkspaceError('Workspace is not a Git repository');
936
1041
  if (kind !== 'unstaged' && kind !== 'staged')
937
1042
  throw new GitWorkspaceError('kind must be staged or unstaged');
938
1043
  const repoRel = GitRunner.repoRel(ctx, path);
939
1044
  const workspaceRel = GitRunner.workspaceRel(ctx, repoRel) ?? path;
940
- const status = this.status(workspace);
1045
+ const status = await this.status(workspace);
941
1046
  const fileState = (status.files ?? []).find((f) => f.path === workspaceRel);
942
1047
  if (kind === 'unstaged' && fileState?.untracked)
943
1048
  return { path: workspaceRel, kind, ...GitRunner.syntheticUntrackedDiff(ctx.workspace, join(ctx.workspace, workspaceRel), workspaceRel) };
@@ -945,7 +1050,7 @@ export class GitRunner {
945
1050
  if (kind === 'staged')
946
1051
  args.push('--cached');
947
1052
  args.push('--', repoRel);
948
- let diff = this.run(ctx, args, { check: true, neutralizeFilterPrograms: true }).stdout;
1053
+ let diff = (await this.run(ctx, args, { check: true, neutralizeFilterPrograms: true })).stdout;
949
1054
  const binary = diff.includes('Binary files ') || diff.includes('GIT binary patch');
950
1055
  const tooLarge = Buffer.byteLength(diff, 'utf8') > DIFF_SIZE_LIMIT;
951
1056
  if (tooLarge)
@@ -968,39 +1073,39 @@ export class GitRunner {
968
1073
  static pathspecs(ctx, paths) {
969
1074
  return GitRunner.cleanPaths(paths).map((p) => GitRunner.repoRel(ctx, p));
970
1075
  }
971
- blockFilteredDestructiveWrite(ctx, message) {
972
- if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot))
1076
+ async blockFilteredDestructiveWrite(ctx, message) {
1077
+ if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot))
973
1078
  throw new GitWorkspaceError(message, 'filtered_path');
974
1079
  }
975
- requireContext(workspace) {
976
- const ctx = this.resolveContext(workspace);
1080
+ async requireContext(workspace) {
1081
+ const ctx = await this.resolveContext(workspace);
977
1082
  if (!ctx)
978
1083
  throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
979
1084
  return ctx;
980
1085
  }
981
1086
  async stage(workspace, paths) {
982
- const ctx = this.requireContext(workspace);
983
- await this.withMutationLock(ctx, () => {
984
- this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; stage may corrupt index content. Use the terminal to stage manually.');
985
- this.run(ctx, ['add', '--', ...GitRunner.pathspecs(ctx, paths)], { check: true, destructive: true, disableFilterAttributes: true });
1087
+ const ctx = await this.requireContext(workspace);
1088
+ return await this.withMutationLock(ctx, async () => {
1089
+ await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; stage may corrupt index content. Use the terminal to stage manually.');
1090
+ await this.run(ctx, ['add', '--', ...GitRunner.pathspecs(ctx, paths)], { check: true, destructive: true, disableFilterAttributes: true });
1091
+ return await this.status(workspace);
986
1092
  });
987
- return this.status(workspace);
988
1093
  }
989
1094
  async unstage(workspace, paths) {
990
- const ctx = this.requireContext(workspace);
1095
+ const ctx = await this.requireContext(workspace);
991
1096
  const specs = GitRunner.pathspecs(ctx, paths);
992
- await this.withMutationLock(ctx, () => {
993
- const result = this.run(ctx, ['restore', '--staged', '--', ...specs], { destructive: true });
1097
+ return await this.withMutationLock(ctx, async () => {
1098
+ const result = await this.run(ctx, ['restore', '--staged', '--', ...specs], { destructive: true });
994
1099
  if (result.status !== 0)
995
- this.run(ctx, ['reset', 'HEAD', '--', ...specs], { check: true, destructive: true });
1100
+ await this.run(ctx, ['reset', 'HEAD', '--', ...specs], { check: true, destructive: true });
1101
+ return await this.status(workspace);
996
1102
  });
997
- return this.status(workspace);
998
1103
  }
999
1104
  async discard(workspace, paths, opts = {}) {
1000
- const ctx = this.requireContext(workspace);
1001
- await this.withMutationLock(ctx, () => {
1002
- this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; discard may corrupt working-tree content. Use the terminal to discard manually.');
1003
- const status = this.status(workspace);
1105
+ const ctx = await this.requireContext(workspace);
1106
+ return await this.withMutationLock(ctx, async () => {
1107
+ await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; discard may corrupt working-tree content. Use the terminal to discard manually.');
1108
+ const status = await this.status(workspace);
1004
1109
  const byPath = new Map((status.files ?? []).map((f) => [f.path, f]));
1005
1110
  for (const path of GitRunner.cleanPaths(paths)) {
1006
1111
  const repoRel = GitRunner.repoRel(ctx, path);
@@ -1032,14 +1137,14 @@ export class GitRunner {
1032
1137
  }
1033
1138
  continue;
1034
1139
  }
1035
- this.run(ctx, ['restore', '--worktree', '--', repoRel], { check: true, destructive: true, disableFilterAttributes: true });
1140
+ await this.run(ctx, ['restore', '--worktree', '--', repoRel], { check: true, destructive: true, disableFilterAttributes: true });
1036
1141
  }
1142
+ return await this.status(workspace);
1037
1143
  });
1038
- return this.status(workspace);
1039
1144
  }
1040
1145
  // ── commit messages ──────────────────────────────────────────────────────
1041
- stagedDiffText(ctx) {
1042
- const diff = this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', GitRunner.workspacePathspec(ctx)], { check: true, neutralizeFilterPrograms: true }).stdout;
1146
+ async stagedDiffText(ctx) {
1147
+ const diff = (await this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', GitRunner.workspacePathspec(ctx)], { check: true, neutralizeFilterPrograms: true })).stdout;
1043
1148
  return GitRunner.capDiff(diff);
1044
1149
  }
1045
1150
  static capDiff(diff) {
@@ -1048,18 +1153,18 @@ export class GitRunner {
1048
1153
  return [diff, false];
1049
1154
  return [encoded.subarray(0, COMMIT_MESSAGE_DIFF_LIMIT).toString('utf8'), true];
1050
1155
  }
1051
- selectedTempIndexEnv(ctx, specs) {
1052
- this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; selected commit staging may corrupt index content. Use the terminal to commit manually.');
1156
+ async selectedTempIndexEnv(ctx, specs) {
1157
+ await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; selected commit staging may corrupt index content. Use the terminal to commit manually.');
1053
1158
  const dir = mkdtempSync(join(tmpdir(), 'hermes-webui-git-index-'));
1054
1159
  const indexPath = join(dir, 'index');
1055
1160
  const env = { GIT_INDEX_FILE: indexPath };
1056
1161
  try {
1057
- const head = this.run(ctx, ['rev-parse', '--verify', 'HEAD'], { env, destructive: true });
1162
+ const head = await this.run(ctx, ['rev-parse', '--verify', 'HEAD'], { env, destructive: true });
1058
1163
  if (head.status === 0)
1059
- this.run(ctx, ['read-tree', 'HEAD'], { check: true, env, destructive: true });
1164
+ await this.run(ctx, ['read-tree', 'HEAD'], { check: true, env, destructive: true });
1060
1165
  else
1061
- this.run(ctx, ['read-tree', '--empty'], { check: true, env, destructive: true });
1062
- this.run(ctx, ['add', '-A', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true });
1166
+ await this.run(ctx, ['read-tree', '--empty'], { check: true, env, destructive: true });
1167
+ await this.run(ctx, ['add', '-A', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true });
1063
1168
  return [env, dir];
1064
1169
  }
1065
1170
  catch (error) {
@@ -1067,11 +1172,11 @@ export class GitRunner {
1067
1172
  throw error;
1068
1173
  }
1069
1174
  }
1070
- selectedFiles(ctx, paths) {
1175
+ async selectedFiles(ctx, paths) {
1071
1176
  const requested = GitRunner.cleanPaths(paths);
1072
1177
  const requestedSpecs = requested.map((p) => GitRunner.repoRel(ctx, p));
1073
1178
  const workspacePaths = requestedSpecs.map((spec, i) => GitRunner.workspaceRel(ctx, spec) ?? requested[i] ?? spec);
1074
- const status = this.status(ctx.workspace);
1179
+ const status = await this.status(ctx.workspace);
1075
1180
  const byPath = new Map((status.files ?? []).map((f) => [f.path, f]));
1076
1181
  const specs = [];
1077
1182
  const selected = [];
@@ -1092,10 +1197,10 @@ export class GitRunner {
1092
1197
  throw new GitWorkspaceError('Selected paths have no committable changes');
1093
1198
  return [specs, workspacePaths, selected];
1094
1199
  }
1095
- selectedDiffText(ctx, specs) {
1096
- const [env, dir] = this.selectedTempIndexEnv(ctx, specs);
1200
+ async selectedDiffText(ctx, specs) {
1201
+ const [env, dir] = await this.selectedTempIndexEnv(ctx, specs);
1097
1202
  try {
1098
- return GitRunner.capDiff(this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true }).stdout);
1203
+ return GitRunner.capDiff((await this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true })).stdout);
1099
1204
  }
1100
1205
  finally {
1101
1206
  rmSync(dir, { recursive: true, force: true });
@@ -1107,24 +1212,24 @@ export class GitRunner {
1107
1212
  lines.push(`- ... ${files.length - 80} more ${label} file(s)`);
1108
1213
  return lines;
1109
1214
  }
1110
- selectedCommitMessagePrompt(workspace, paths) {
1111
- const ctx = this.requireContext(workspace);
1112
- const [specs, , selected] = this.selectedFiles(ctx, paths);
1113
- const [diff, truncated] = this.selectedDiffText(ctx, specs);
1215
+ async selectedCommitMessagePrompt(workspace, paths) {
1216
+ const ctx = await this.requireContext(workspace);
1217
+ const [specs, , selected] = await this.selectedFiles(ctx, paths);
1218
+ const [diff, truncated] = await this.selectedDiffText(ctx, specs);
1114
1219
  if (!diff.trim())
1115
1220
  throw new GitWorkspaceError('No selected diff is available');
1116
- const status = this.status(workspace);
1221
+ const status = await this.status(workspace);
1117
1222
  const userPrompt = `Write a commit message for the selected Git diff below.\n\nBranch: ${status.branch || 'HEAD'}\nSelected files (${selected.length}):\n${GitRunner.fileLines(selected, 'selected').join('\n')}${truncated ? '\n\nDiff was truncated for size; summarize only what is visible.\n' : '\n'}\nSelected diff:\n\`\`\`diff\n${diff}\n\`\`\``;
1118
1223
  return { system_prompt: COMMIT_MESSAGE_SYSTEM_PROMPT, user_prompt: userPrompt, truncated, status };
1119
1224
  }
1120
- stagedCommitMessagePrompt(workspace) {
1121
- const ctx = this.resolveContext(workspace);
1225
+ async stagedCommitMessagePrompt(workspace) {
1226
+ const ctx = await this.resolveContext(workspace);
1122
1227
  if (!ctx)
1123
1228
  throw new GitWorkspaceError('Workspace is not a Git repository');
1124
- const status = this.status(workspace);
1229
+ const status = await this.status(workspace);
1125
1230
  if ((status.totals?.staged ?? 0) <= 0)
1126
1231
  throw new GitWorkspaceError('Stage changes before generating a commit message');
1127
- const [diff, truncated] = this.stagedDiffText(ctx);
1232
+ const [diff, truncated] = await this.stagedDiffText(ctx);
1128
1233
  if (!diff.trim())
1129
1234
  throw new GitWorkspaceError('No staged diff is available');
1130
1235
  const stagedFiles = (status.files ?? []).filter((f) => f.staged);
@@ -1136,72 +1241,74 @@ export class GitRunner {
1136
1241
  const msg = str(message).trim();
1137
1242
  if (!msg)
1138
1243
  throw new GitWorkspaceError('Commit message is required');
1139
- const ctx = this.requireContext(workspace);
1140
- await this.withMutationLock(ctx, () => {
1141
- this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, destructive: true, disableFilterAttributes: true });
1244
+ const ctx = await this.requireContext(workspace);
1245
+ // The SHA and status are read under the lock so a later mutation's state cannot be reported as this commit's.
1246
+ return await this.withMutationLock(ctx, async () => {
1247
+ await this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, destructive: true, disableFilterAttributes: true });
1248
+ const sha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
1249
+ return { ok: true, commit: sha, status: await this.status(workspace) };
1142
1250
  });
1143
- const sha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
1144
- return { ok: true, commit: sha, status: this.status(workspace) };
1145
1251
  }
1146
1252
  async commitSelected(workspace, message, paths) {
1147
1253
  const msg = str(message).trim();
1148
1254
  if (!msg)
1149
1255
  throw new GitWorkspaceError('Commit message is required');
1150
- const ctx = this.requireContext(workspace);
1151
- const workspacePaths = await this.withMutationLock(ctx, () => {
1152
- const [specs, wsPaths] = this.selectedFiles(ctx, paths);
1153
- const [env, dir] = this.selectedTempIndexEnv(ctx, specs);
1256
+ const ctx = await this.requireContext(workspace);
1257
+ return await this.withMutationLock(ctx, async () => {
1258
+ const [specs, wsPaths] = await this.selectedFiles(ctx, paths);
1259
+ const [env, dir] = await this.selectedTempIndexEnv(ctx, specs);
1154
1260
  try {
1155
- const quiet = this.run(ctx, ['diff', '--cached', '--quiet', '--no-textconv', '--', ...specs], { env, destructive: true, disableFilterAttributes: true });
1261
+ const quiet = await this.run(ctx, ['diff', '--cached', '--quiet', '--no-textconv', '--', ...specs], { env, destructive: true, disableFilterAttributes: true });
1156
1262
  if (quiet.status === 0)
1157
1263
  throw new GitWorkspaceError('Selected paths have no committable changes');
1158
- this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, env, destructive: true, disableFilterAttributes: true });
1159
- this.run(ctx, ['reset', '-q', 'HEAD', '--', ...specs], { check: true, destructive: true });
1264
+ await this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, env, destructive: true, disableFilterAttributes: true });
1265
+ await this.run(ctx, ['reset', '-q', 'HEAD', '--', ...specs], { check: true, destructive: true });
1160
1266
  }
1161
1267
  finally {
1162
1268
  rmSync(dir, { recursive: true, force: true });
1163
1269
  }
1164
- return wsPaths;
1270
+ const sha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
1271
+ return { ok: true, commit: sha, paths: wsPaths, status: await this.status(workspace) };
1165
1272
  });
1166
- const sha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
1167
- return { ok: true, commit: sha, paths: workspacePaths, status: this.status(workspace) };
1168
1273
  }
1169
- branchName(ctx) {
1170
- const branch = this.run(ctx, ['branch', '--show-current'], { check: true }).stdout.trim();
1274
+ async branchName(ctx) {
1275
+ const branch = (await this.run(ctx, ['branch', '--show-current'], { check: true })).stdout.trim();
1171
1276
  if (!branch)
1172
1277
  throw new GitWorkspaceError('Cannot push from a detached HEAD');
1173
1278
  return branch;
1174
1279
  }
1175
1280
  async fetch(workspace) {
1176
- const ctx = this.requireContext(workspace);
1177
- const result = await this.withMutationLock(ctx, () => this.run(ctx, ['fetch', '--prune', '--no-recurse-submodules'], {
1178
- timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, forceDestructiveHardening: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true,
1179
- }));
1180
- return { ok: true, message: GitRunner.remoteMessage(result), status: this.status(workspace) };
1281
+ const ctx = await this.requireContext(workspace);
1282
+ return await this.withMutationLock(ctx, async () => {
1283
+ const result = await this.run(ctx, ['fetch', '--prune', '--no-recurse-submodules'], {
1284
+ timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, forceDestructiveHardening: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true,
1285
+ });
1286
+ return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
1287
+ }, { holdRepo: false });
1181
1288
  }
1182
1289
  async pull(workspace) {
1183
- const ctx = this.requireContext(workspace);
1184
- const result = await this.withMutationLock(ctx, () => {
1185
- this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; pull may corrupt working-tree content. Use the terminal to pull manually.');
1186
- return this.run(ctx, ['pull', '--ff-only', '--no-recurse-submodules'], { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
1290
+ const ctx = await this.requireContext(workspace);
1291
+ return await this.withMutationLock(ctx, async () => {
1292
+ await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; pull may corrupt working-tree content. Use the terminal to pull manually.');
1293
+ const result = await this.run(ctx, ['pull', '--ff-only', '--no-recurse-submodules'], { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
1294
+ return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
1187
1295
  });
1188
- return { ok: true, message: GitRunner.remoteMessage(result), status: this.status(workspace) };
1189
1296
  }
1190
1297
  async push(workspace) {
1191
- const ctx = this.requireContext(workspace);
1192
- const result = await this.withMutationLock(ctx, () => {
1193
- const status = this.status(workspace);
1298
+ const ctx = await this.requireContext(workspace);
1299
+ return await this.withMutationLock(ctx, async () => {
1300
+ const status = await this.status(workspace);
1194
1301
  const args = ['push'];
1195
1302
  if (!status.upstream) {
1196
- const branch = this.branchName(ctx);
1197
- const remotes = this.run(ctx, ['remote'], { check: true }).stdout.split(/\s+/).filter(Boolean);
1303
+ const branch = await this.branchName(ctx);
1304
+ const remotes = (await this.run(ctx, ['remote'], { check: true })).stdout.split(/\s+/).filter(Boolean);
1198
1305
  if (!remotes.includes('origin'))
1199
1306
  throw new GitWorkspaceError('No upstream branch or origin remote is configured', 'no_upstream');
1200
1307
  args.push('-u', 'origin', branch);
1201
1308
  }
1202
- return this.run(ctx, args, { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
1203
- });
1204
- return { ok: true, message: GitRunner.remoteMessage(result), status: this.status(workspace) };
1309
+ const result = await this.run(ctx, args, { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
1310
+ return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
1311
+ }, { holdRepo: false });
1205
1312
  }
1206
1313
  }
1207
1314
  export const COMMIT_MESSAGE_SYSTEM_PROMPT = `When writing commit messages, PR titles, or PR descriptions: