zidane 7.2.5 → 8.0.4

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 (376) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -6
  3. package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
  4. package/dist/acp-DQDhi2dj.js.map +1 -0
  5. package/dist/acp-cli.d.ts +1 -1
  6. package/dist/acp-cli.js +9 -66
  7. package/dist/acp-cli.js.map +1 -1
  8. package/dist/acp.d.ts +10 -590
  9. package/dist/acp.d.ts.map +1 -1
  10. package/dist/acp.js +3 -3
  11. package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
  12. package/dist/agent-DSLNmBp0.js.map +1 -0
  13. package/dist/agent.d.ts +1 -1
  14. package/dist/agent.js +3 -2
  15. package/dist/aliasing-lVFbm81n.js +217 -0
  16. package/dist/aliasing-lVFbm81n.js.map +1 -0
  17. package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
  18. package/dist/anthropic-CD00LhUP.js.map +1 -0
  19. package/dist/artifacts-CV2hUKnL.js.map +1 -1
  20. package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
  21. package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
  22. package/dist/call-tool-dHlbGOIY.js +70 -0
  23. package/dist/call-tool-dHlbGOIY.js.map +1 -0
  24. package/dist/chat/pure.d.ts +372 -6
  25. package/dist/chat/pure.d.ts.map +1 -1
  26. package/dist/chat/pure.js +35 -244
  27. package/dist/chat/pure.js.map +1 -1
  28. package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
  29. package/dist/chat-JPJJ8uu5.js.map +1 -0
  30. package/dist/chat.d.ts +4 -6
  31. package/dist/chat.js +23 -12
  32. package/dist/cli-shared-C-YCaglL.js +159 -0
  33. package/dist/cli-shared-C-YCaglL.js.map +1 -0
  34. package/dist/client-core-BQSyDALT.d.ts +2115 -0
  35. package/dist/client-core-BQSyDALT.d.ts.map +1 -0
  36. package/dist/codemode-worker.d.ts +1 -0
  37. package/dist/codemode-worker.js +155 -0
  38. package/dist/codemode-worker.js.map +1 -0
  39. package/dist/codemode.d.ts +352 -0
  40. package/dist/codemode.d.ts.map +1 -0
  41. package/dist/codemode.js +5 -0
  42. package/dist/connection-Dr-Js4yZ.d.ts +28 -0
  43. package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
  44. package/dist/connection-loop-gdh22LTI.js +112 -0
  45. package/dist/connection-loop-gdh22LTI.js.map +1 -0
  46. package/dist/context-segments-BHKlagL6.js +1016 -0
  47. package/dist/context-segments-BHKlagL6.js.map +1 -0
  48. package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
  49. package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
  50. package/dist/contexts/daytona.d.ts +19 -8
  51. package/dist/contexts/daytona.d.ts.map +1 -1
  52. package/dist/contexts/daytona.js +1 -440
  53. package/dist/contexts/docker.d.ts +36 -7
  54. package/dist/contexts/docker.d.ts.map +1 -1
  55. package/dist/contexts/docker.js +1 -456
  56. package/dist/contexts/e2b.d.ts +68 -13
  57. package/dist/contexts/e2b.d.ts.map +1 -1
  58. package/dist/contexts/e2b.js +1 -382
  59. package/dist/contexts/sandbox.d.ts +1 -1
  60. package/dist/contexts/sandbox.js +5 -0
  61. package/dist/contexts/sandbox.js.map +1 -1
  62. package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
  63. package/dist/contexts-d7UolYot.js.map +1 -0
  64. package/dist/contexts.d.ts +3 -3
  65. package/dist/contexts.js +1 -1
  66. package/dist/cred-keys-GpBg71cP.js +105 -0
  67. package/dist/cred-keys-GpBg71cP.js.map +1 -0
  68. package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
  69. package/dist/credentials-JttEuRON.js.map +1 -0
  70. package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
  71. package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
  72. package/dist/daemon-cli.d.ts +1 -1
  73. package/dist/daemon-cli.js +3 -2
  74. package/dist/daemon-cli.js.map +1 -1
  75. package/dist/daytona-DG3omipZ.js +513 -0
  76. package/dist/daytona-DG3omipZ.js.map +1 -0
  77. package/dist/defaults-Cx3zvDOV.js +23 -0
  78. package/dist/defaults-Cx3zvDOV.js.map +1 -0
  79. package/dist/dispatcher-BMlu2BmZ.js +4941 -0
  80. package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
  81. package/dist/docker-BZhMMJUp.js +727 -0
  82. package/dist/docker-BZhMMJUp.js.map +1 -0
  83. package/dist/e2b-CEVOdRib.js +480 -0
  84. package/dist/e2b-CEVOdRib.js.map +1 -0
  85. package/dist/edit-utils-EGosADZq.js +234 -0
  86. package/dist/edit-utils-EGosADZq.js.map +1 -0
  87. package/dist/environment-8wsKE2Ln.js +214 -0
  88. package/dist/environment-8wsKE2Ln.js.map +1 -0
  89. package/dist/eval-BNcvle_J.js +1595 -0
  90. package/dist/eval-BNcvle_J.js.map +1 -0
  91. package/dist/eval.d.ts +2 -2
  92. package/dist/eval.js +1 -1489
  93. package/dist/executor-Cx5-8yqi.js +621 -0
  94. package/dist/executor-Cx5-8yqi.js.map +1 -0
  95. package/dist/extension-authoring-DyGGmq9V.js +385 -0
  96. package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
  97. package/dist/extensions.d.ts +2 -2
  98. package/dist/extensions.js +3 -3
  99. package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
  100. package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
  101. package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
  102. package/dist/format-C2NQEVUH.js.map +1 -0
  103. package/dist/format-bytes-C7tfc3Zm.js +55 -0
  104. package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
  105. package/dist/headless-C_rNUuYC.js +960 -0
  106. package/dist/headless-C_rNUuYC.js.map +1 -0
  107. package/dist/headless.d.ts +1 -1
  108. package/dist/headless.js +1 -413
  109. package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
  110. package/dist/history-search-CcYzF8-0.js.map +1 -0
  111. package/dist/history-search-worker.d.ts +1 -1
  112. package/dist/history-search-worker.js +1 -1
  113. package/dist/host-D7NrI03V.js +378 -0
  114. package/dist/host-D7NrI03V.js.map +1 -0
  115. package/dist/host-signals-BcP5QKUD.js +118 -0
  116. package/dist/host-signals-BcP5QKUD.js.map +1 -0
  117. package/dist/identity-CwfO8K-t.js +26 -0
  118. package/dist/identity-CwfO8K-t.js.map +1 -0
  119. package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
  120. package/dist/index-BGe1fQT7.d.ts.map +1 -0
  121. package/dist/index-BVQsIKGn.d.ts +448 -0
  122. package/dist/index-BVQsIKGn.d.ts.map +1 -0
  123. package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
  124. package/dist/index-CHABJd-L.d.ts.map +1 -0
  125. package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
  126. package/dist/index-D6a7qXpg.d.ts.map +1 -0
  127. package/dist/index.d.ts +6 -6
  128. package/dist/index.js +27 -23
  129. package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
  130. package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
  131. package/dist/ipc/remote.d.ts +313 -0
  132. package/dist/ipc/remote.d.ts.map +1 -0
  133. package/dist/ipc/remote.js +4 -0
  134. package/dist/ipc.d.ts +6 -964
  135. package/dist/ipc.js +403 -4
  136. package/dist/ipc.js.map +1 -0
  137. package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
  138. package/dist/isolation-D9L650W5.d.ts.map +1 -0
  139. package/dist/json-rpc--HXQti1E.js +350 -0
  140. package/dist/json-rpc--HXQti1E.js.map +1 -0
  141. package/dist/links-j8rED1sR.js +603 -0
  142. package/dist/links-j8rED1sR.js.map +1 -0
  143. package/dist/local-terminal-D8QThwo_.js +252 -0
  144. package/dist/local-terminal-D8QThwo_.js.map +1 -0
  145. package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
  146. package/dist/logger-Gtqlayx8.js.map +1 -0
  147. package/dist/logs-view-CWG8aUAG.js.map +1 -1
  148. package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
  149. package/dist/mcp-_wecH9Qe.js.map +1 -0
  150. package/dist/mcp.d.ts +2 -2
  151. package/dist/mcp.js +2 -2
  152. package/dist/media-sniff-UKijEaiQ.js.map +1 -1
  153. package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
  154. package/dist/messages-BqiwcD2Q.js.map +1 -0
  155. package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
  156. package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
  157. package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
  158. package/dist/openai-compat-CFrpH7uq.js.map +1 -0
  159. package/dist/orchestration-BCGmB8Ps.js +511 -0
  160. package/dist/orchestration-BCGmB8Ps.js.map +1 -0
  161. package/dist/output/stream-json.d.ts +1 -2
  162. package/dist/output/stream-json.d.ts.map +1 -1
  163. package/dist/output/stream-json.js +1 -1
  164. package/dist/output/terminal.d.ts +1 -2
  165. package/dist/output/terminal.d.ts.map +1 -1
  166. package/dist/output/terminal.js +13 -7
  167. package/dist/output/terminal.js.map +1 -1
  168. package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
  169. package/dist/plugins-DS7mahzZ.js.map +1 -0
  170. package/dist/plugins.d.ts +1 -1
  171. package/dist/plugins.js +1 -2
  172. package/dist/prelude-Cxo4t74B.js +517 -0
  173. package/dist/prelude-Cxo4t74B.js.map +1 -0
  174. package/dist/presets.d.ts +1 -1
  175. package/dist/presets.js +1 -2
  176. package/dist/project-root-B8zADr4o.js +167 -0
  177. package/dist/project-root-B8zADr4o.js.map +1 -0
  178. package/dist/project-user-paths-BSgtrRxl.js +58 -0
  179. package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
  180. package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
  181. package/dist/prompts-DYcvqShf.d.ts.map +1 -0
  182. package/dist/protocol-DnhqqSmE.js +13 -0
  183. package/dist/protocol-DnhqqSmE.js.map +1 -0
  184. package/dist/providers/anthropic.d.ts +2 -2
  185. package/dist/providers/anthropic.js +1 -2
  186. package/dist/providers/arcee.d.ts +1 -1
  187. package/dist/providers/arcee.js +1 -1
  188. package/dist/providers/baseten.d.ts +1 -1
  189. package/dist/providers/baseten.js +1 -1
  190. package/dist/providers/cerebras.d.ts +1 -1
  191. package/dist/providers/cerebras.js +3 -2
  192. package/dist/providers/cerebras.js.map +1 -1
  193. package/dist/providers/local.d.ts +1 -1
  194. package/dist/providers/local.js +1 -1
  195. package/dist/providers/openai-compat.d.ts +1 -1
  196. package/dist/providers/openai-compat.js +1 -2
  197. package/dist/providers/openai.d.ts +1 -1
  198. package/dist/providers/openai.js +15 -12
  199. package/dist/providers/openai.js.map +1 -1
  200. package/dist/providers/openrouter.d.ts +1 -1
  201. package/dist/providers/openrouter.js +3 -2
  202. package/dist/providers/openrouter.js.map +1 -1
  203. package/dist/providers/xai.d.ts +1 -1
  204. package/dist/providers/xai.js +3 -3
  205. package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
  206. package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
  207. package/dist/providers.d.ts +1 -1
  208. package/dist/providers.js +4 -4
  209. package/dist/registry-D6ShBWJU.js +5234 -0
  210. package/dist/registry-D6ShBWJU.js.map +1 -0
  211. package/dist/remote-hosts-BtA_9ZGp.js +97 -0
  212. package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
  213. package/dist/restate.d.ts +2 -3
  214. package/dist/restate.d.ts.map +1 -1
  215. package/dist/restate.js +631 -572
  216. package/dist/restate.js.map +1 -1
  217. package/dist/run-summary-CklLiZ4y.js +288 -0
  218. package/dist/run-summary-CklLiZ4y.js.map +1 -0
  219. package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
  220. package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
  221. package/dist/session/sqlite.d.ts +1 -2
  222. package/dist/session/sqlite.d.ts.map +1 -1
  223. package/dist/session/sqlite.js +1 -616
  224. package/dist/session-factory-BUGY5P40.js +1576 -0
  225. package/dist/session-factory-BUGY5P40.js.map +1 -0
  226. package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
  227. package/dist/session-xh9JXu6i.js.map +1 -0
  228. package/dist/session.d.ts +1 -1
  229. package/dist/session.js +2 -2
  230. package/dist/settings-keys-Ckf29NeM.js +677 -0
  231. package/dist/settings-keys-Ckf29NeM.js.map +1 -0
  232. package/dist/shell-path-Dqaoow2J.js +412 -0
  233. package/dist/shell-path-Dqaoow2J.js.map +1 -0
  234. package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
  235. package/dist/skills-DKqZJm4X.js.map +1 -0
  236. package/dist/skills.d.ts +2 -2
  237. package/dist/skills.js +1 -2
  238. package/dist/source-mPExaUm8.js +381 -0
  239. package/dist/source-mPExaUm8.js.map +1 -0
  240. package/dist/sqlite-DV1DQzN2.js +698 -0
  241. package/dist/sqlite-DV1DQzN2.js.map +1 -0
  242. package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
  243. package/dist/src-BRMYRJib.js.map +1 -0
  244. package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
  245. package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
  246. package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
  247. package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
  248. package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
  249. package/dist/timeout-DEaqslLO.d.ts.map +1 -1
  250. package/dist/todo-edits-Dn251YUP.js +1790 -0
  251. package/dist/todo-edits-Dn251YUP.js.map +1 -0
  252. package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
  253. package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
  254. package/dist/tool-state-Dku6YqAB.js +989 -0
  255. package/dist/tool-state-Dku6YqAB.js.map +1 -0
  256. package/dist/tools/fetch-url.d.ts +1 -2
  257. package/dist/tools/fetch-url.d.ts.map +1 -1
  258. package/dist/tools/fetch-url.js +1 -2
  259. package/dist/tools/prompts.d.ts +1 -1
  260. package/dist/tools/prompts.js +7 -7
  261. package/dist/tools/prompts.js.map +1 -1
  262. package/dist/tools/web-search.d.ts +1 -2
  263. package/dist/tools/web-search.d.ts.map +1 -1
  264. package/dist/tools/web-search.js +2 -2
  265. package/dist/tools/web-search.js.map +1 -1
  266. package/dist/tools-_DnT3AGM.js +578 -0
  267. package/dist/tools-_DnT3AGM.js.map +1 -0
  268. package/dist/tools.d.ts +3 -3
  269. package/dist/tools.js +4 -2
  270. package/dist/tui.d.ts +534 -517
  271. package/dist/tui.d.ts.map +1 -1
  272. package/dist/tui.js +34933 -27243
  273. package/dist/tui.js.map +1 -1
  274. package/dist/turn-operations-B8sokwi5.js +333 -0
  275. package/dist/turn-operations-B8sokwi5.js.map +1 -0
  276. package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
  277. package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
  278. package/dist/types-C7ZFOztE.d.ts +132 -0
  279. package/dist/types-C7ZFOztE.d.ts.map +1 -0
  280. package/dist/types-CH5XpIHw.js +114 -0
  281. package/dist/types-CH5XpIHw.js.map +1 -0
  282. package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
  283. package/dist/types-Cu0ZGDu9.js.map +1 -0
  284. package/dist/types-IqegbJEE.d.ts +624 -0
  285. package/dist/types-IqegbJEE.d.ts.map +1 -0
  286. package/dist/types.d.ts +4 -4
  287. package/dist/types.js +1 -1
  288. package/dist/url-D8HCl65b.js +1259 -0
  289. package/dist/url-D8HCl65b.js.map +1 -0
  290. package/dist/wasm-BVT-j5cl.js +33 -0
  291. package/dist/wasm-BVT-j5cl.js.map +1 -0
  292. package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
  293. package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
  294. package/dist/ws-CIHMvlSh.js +952 -0
  295. package/dist/ws-CIHMvlSh.js.map +1 -0
  296. package/dist/ws-client-N7NxzvQp.js +230 -0
  297. package/dist/ws-client-N7NxzvQp.js.map +1 -0
  298. package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
  299. package/dist/xai-CvJTeLrI.js.map +1 -0
  300. package/dist/xdg-zweed0-t.js +117 -0
  301. package/dist/xdg-zweed0-t.js.map +1 -0
  302. package/docs/ACP.md +52 -0
  303. package/docs/ARCHITECTURE.md +103 -6
  304. package/docs/ARTIFACTS.md +8 -4
  305. package/docs/CHAT.md +165 -14
  306. package/docs/EXECUTION_CONTEXT.md +8 -0
  307. package/docs/EXTENSIONS.md +123 -2
  308. package/docs/HEADLESS.md +2 -0
  309. package/docs/IPC.md +775 -64
  310. package/docs/MACHINES.md +456 -0
  311. package/docs/PANELS.md +3 -0
  312. package/docs/README.md +4 -0
  313. package/docs/RESTATE.md +3 -2
  314. package/docs/RUN_IN_BACKGROUND.md +10 -2
  315. package/docs/SERVER.md +1085 -0
  316. package/docs/SKILL.md +6 -1
  317. package/docs/TERMINAL.md +75 -0
  318. package/docs/TUI.md +65 -14
  319. package/package.json +58 -26
  320. package/scripts/eval.ts +4 -0
  321. package/dist/acp-D-tVyUjw.js.map +0 -1
  322. package/dist/agent-D0zZnGbt.js.map +0 -1
  323. package/dist/anthropic-C2aole0G.js.map +0 -1
  324. package/dist/approvals-DKaJZYlm.js.map +0 -1
  325. package/dist/atomic-write-B7zr1ddF.js.map +0 -1
  326. package/dist/auth-BG2rVKSt.js.map +0 -1
  327. package/dist/completion-core-BRo86R_L.js.map +0 -1
  328. package/dist/contexts/daytona.js.map +0 -1
  329. package/dist/contexts/docker.js.map +0 -1
  330. package/dist/contexts/e2b.js.map +0 -1
  331. package/dist/contexts-CRE9pw07.js.map +0 -1
  332. package/dist/eval.js.map +0 -1
  333. package/dist/format-BF745pLL.js.map +0 -1
  334. package/dist/glob-DluQFSw1.js +0 -270
  335. package/dist/glob-DluQFSw1.js.map +0 -1
  336. package/dist/headless.js.map +0 -1
  337. package/dist/history-search-C4bk1Wz_.js.map +0 -1
  338. package/dist/index-BgN1T3lk.d.ts.map +0 -1
  339. package/dist/index-C37_ppWv.d.ts.map +0 -1
  340. package/dist/index-C6NEd5y_.d.ts.map +0 -1
  341. package/dist/ipc-BKVIIOqH.js +0 -331
  342. package/dist/ipc-BKVIIOqH.js.map +0 -1
  343. package/dist/ipc.d.ts.map +0 -1
  344. package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
  345. package/dist/json-rpc-rSWMUh9b.js +0 -192
  346. package/dist/json-rpc-rSWMUh9b.js.map +0 -1
  347. package/dist/logger--C1VQjwJ.js.map +0 -1
  348. package/dist/mcp-CgNd-f3F.js.map +0 -1
  349. package/dist/messages-2jcOA_5R.js.map +0 -1
  350. package/dist/openai-compat-Dy56weK8.js.map +0 -1
  351. package/dist/output-cap-C9zukcCj.js +0 -100
  352. package/dist/output-cap-C9zukcCj.js.map +0 -1
  353. package/dist/panels-B6gRk4bf.js +0 -789
  354. package/dist/panels-B6gRk4bf.js.map +0 -1
  355. package/dist/plugins-gbI7SBA6.js.map +0 -1
  356. package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
  357. package/dist/run-summary-BU_NgwUT.js +0 -269
  358. package/dist/run-summary-BU_NgwUT.js.map +0 -1
  359. package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
  360. package/dist/session/sqlite.js.map +0 -1
  361. package/dist/session-BqRP1hS7.js.map +0 -1
  362. package/dist/shell-path-DfdXrGg9.js +0 -1549
  363. package/dist/shell-path-DfdXrGg9.js.map +0 -1
  364. package/dist/skills-BC3dodWp.js.map +0 -1
  365. package/dist/src-CkNQKFKr.js.map +0 -1
  366. package/dist/todo-edits-UOwgxGKu.js +0 -528
  367. package/dist/todo-edits-UOwgxGKu.js.map +0 -1
  368. package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
  369. package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
  370. package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
  371. package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
  372. package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
  373. package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
  374. package/dist/turn-operations-DrbJlpq9.js.map +0 -1
  375. package/dist/types-CWgENVLY.js.map +0 -1
  376. package/dist/xai-CazjStOK.js.map +0 -1
@@ -0,0 +1,1790 @@
1
+ import { i as readFileInputPaths } from "./format-bytes-C7tfc3Zm.js";
2
+ import { rn as parseMachineKind } from "./tool-formatters-IQFDrdrs.js";
3
+ import { y as computeLineDiff } from "./url-D8HCl65b.js";
4
+ import { p as LOCAL_HOST_KEY } from "./types-CH5XpIHw.js";
5
+ //#region src/chat/hosts/host-index.ts
6
+ /**
7
+ * Which host an id lives on: a session (or a request a host handed out: an
8
+ * approval, a question, a terminal). A client learns it from what a host
9
+ * tells it (its lists, a create, an activation, its signals) and routes
10
+ * every later call on that id there.
11
+ *
12
+ * Never guessed: an id no host told about is an error
13
+ * ({@link UnknownHostIdError}), one several hosts list is an error unless the
14
+ * lookup names its host ({@link AmbiguousHostIdError}), and the ids of a host
15
+ * that was removed stay remembered as such ({@link HostRemovedError}) until
16
+ * a host claims them again, so nothing aimed at a removed server quietly
17
+ * lands on another host.
18
+ * Node-free, shared by the TUI and the GUI main process.
19
+ */
20
+ /** No host told this client about the id: nothing to route it to. */
21
+ var UnknownHostIdError = class extends Error {
22
+ id;
23
+ code = "unknown-host-id";
24
+ constructor(id, noun = "session") {
25
+ super(`No host is known for ${noun} "${id}": list or open it on its host first.`);
26
+ this.id = id;
27
+ this.name = "UnknownHostIdError";
28
+ }
29
+ };
30
+ /** The id lived on a host that was removed from this window. */
31
+ var HostRemovedError = class extends Error {
32
+ hostKey;
33
+ hostName;
34
+ code = "host-removed";
35
+ constructor(hostKey, hostName) {
36
+ super(`${hostName} was removed.`);
37
+ this.hostKey = hostKey;
38
+ this.hostName = hostName;
39
+ this.name = "HostRemovedError";
40
+ }
41
+ };
42
+ /**
43
+ * Several hosts list the id (two hosts can hold the same session id: a
44
+ * server sharing this machine's data dir, a copied store) and the lookup
45
+ * did not say which: refused, never guessed.
46
+ */
47
+ var AmbiguousHostIdError = class extends Error {
48
+ id;
49
+ hostKeys;
50
+ code = "ambiguous-host-id";
51
+ constructor(id, hostKeys, noun = "session") {
52
+ super(`${noun[0].toUpperCase()}${noun.slice(1)} "${id}" exists on several hosts (${hostKeys.join(", ")}): say which one.`);
53
+ this.id = id;
54
+ this.hostKeys = hostKeys;
55
+ this.name = "AmbiguousHostIdError";
56
+ }
57
+ };
58
+ function createHostIndex(noun = "session") {
59
+ /** id → live host keys, in the order they were noted. */
60
+ const live = /* @__PURE__ */ new Map();
61
+ /** id → removed host key → its name. */
62
+ const removed = /* @__PURE__ */ new Map();
63
+ const liveOf = (id) => [...live.get(id) ?? []];
64
+ const removedOf = (id, hostKey) => {
65
+ const marks = removed.get(id);
66
+ if (!marks) return void 0;
67
+ if (hostKey !== void 0) {
68
+ const name = marks.get(hostKey);
69
+ return name === void 0 ? void 0 : {
70
+ key: hostKey,
71
+ name
72
+ };
73
+ }
74
+ const first = [...marks][0];
75
+ return first ? {
76
+ key: first[0],
77
+ name: first[1]
78
+ } : void 0;
79
+ };
80
+ const index = {
81
+ note: (id, hostKey) => {
82
+ let hosts = live.get(id);
83
+ if (!hosts) live.set(id, hosts = /* @__PURE__ */ new Set());
84
+ hosts.add(hostKey);
85
+ const marks = removed.get(id);
86
+ marks?.delete(hostKey);
87
+ if (marks?.size === 0) removed.delete(id);
88
+ },
89
+ forget: (id, hostKey) => {
90
+ if (hostKey === void 0) {
91
+ live.delete(id);
92
+ removed.delete(id);
93
+ return;
94
+ }
95
+ live.get(id)?.delete(hostKey);
96
+ if (live.get(id)?.size === 0) live.delete(id);
97
+ removed.get(id)?.delete(hostKey);
98
+ if (removed.get(id)?.size === 0) removed.delete(id);
99
+ },
100
+ hostOf: (id, hostKey) => {
101
+ const hosts = liveOf(id);
102
+ if (hostKey !== void 0) return hosts.includes(hostKey) ? hostKey : void 0;
103
+ return hosts.length === 1 ? hosts[0] : void 0;
104
+ },
105
+ hostsOf: liveOf,
106
+ require: (id, hostKey) => {
107
+ const hosts = liveOf(id);
108
+ if (hostKey !== void 0) {
109
+ if (hosts.includes(hostKey)) return hostKey;
110
+ const gone = removedOf(id, hostKey);
111
+ if (gone) throw new HostRemovedError(gone.key, gone.name);
112
+ throw new UnknownHostIdError(id, noun);
113
+ }
114
+ if (hosts.length === 1) return hosts[0];
115
+ if (hosts.length > 1) throw new AmbiguousHostIdError(id, hosts, noun);
116
+ const gone = removedOf(id);
117
+ if (gone) throw new HostRemovedError(gone.key, gone.name);
118
+ throw new UnknownHostIdError(id, noun);
119
+ },
120
+ removedHostOf: removedOf,
121
+ idsOf: (hostKey) => [...live].filter(([, hosts]) => hosts.has(hostKey)).map(([id]) => id),
122
+ dropHost: (hostKey, hostName) => {
123
+ const ids = index.idsOf(hostKey);
124
+ for (const id of ids) {
125
+ index.forget(id, hostKey);
126
+ let marks = removed.get(id);
127
+ if (!marks) removed.set(id, marks = /* @__PURE__ */ new Map());
128
+ marks.set(hostKey, hostName);
129
+ }
130
+ return ids;
131
+ }
132
+ };
133
+ return index;
134
+ }
135
+ //#endregion
136
+ //#region src/chat/hosts/session-key.ts
137
+ /**
138
+ * A session's identity across hosts: the (host, session) pair, as one
139
+ * string every client keys rows, maps and persisted state by. Two hosts may
140
+ * hold the same session id (a server sharing this computer's data dir, a
141
+ * copied store), so a bare id never names a session on its own once several
142
+ * hosts are listed.
143
+ *
144
+ * The host is named by its HOST KEY, the key of a client's host directory:
145
+ * {@link LOCAL_HOST_KEY} for This computer (the in-process host or the
146
+ * daemon), a saved server's `hostId` (for a server, hostKey === hostId),
147
+ * or a client's own key for a host it never saves (the TUI's `--host`
148
+ * server: `'ad-hoc'`).
149
+ *
150
+ * The encoding, shared by the TUI and the GUI:
151
+ *
152
+ * - This computer: the bare id (`abc`). Local-only users see exactly the
153
+ * ids, and state persisted before keys existed still means the same
154
+ * session.
155
+ * - Any other host: `<hostKey>␟<id>` (U+001F, never in a host key).
156
+ *
157
+ * A local id that itself contains U+001F is written `local␟<id>`, so a key
158
+ * always parses back to the pair it was made from. Node-free.
159
+ */
160
+ const SESSION_KEY_SEPARATOR = "";
161
+ /** The key of `sessionId` on `hostKey` (`null` / absent / {@link LOCAL_HOST_KEY}: a local session). */
162
+ function sessionKey(hostKey, sessionId) {
163
+ if (!hostKey || hostKey === "local") return sessionId.includes("") ? `${LOCAL_HOST_KEY}${sessionId}` : sessionId;
164
+ if (hostKey.includes("")) throw new Error("A host key cannot contain U+001F.");
165
+ return `${hostKey}${sessionId}`;
166
+ }
167
+ /** The pair `key` names (a bare id: This computer's). */
168
+ function parseSessionKey(key) {
169
+ const at = key.indexOf("");
170
+ if (at < 0) return {
171
+ hostKey: LOCAL_HOST_KEY,
172
+ sessionId: key
173
+ };
174
+ return {
175
+ hostKey: key.slice(0, at),
176
+ sessionId: key.slice(at + 1)
177
+ };
178
+ }
179
+ //#endregion
180
+ //#region src/chat/auth-rows.ts
181
+ /**
182
+ * Stored credentials, i.e. the ones a user can actually choose between.
183
+ *
184
+ * `env` is excluded: it is observed rather than stored, and is usually just the
185
+ * mirror `applyApiKeyEnv` wrote from the stored API key, so counting it would
186
+ * advertise a choice that does not exist.
187
+ */
188
+ function selectableSources(provider) {
189
+ return provider.methods.filter((m) => m.source !== "env");
190
+ }
191
+ /** A provider earns a chevron only when there is a real choice to reveal. */
192
+ function isExpandableProvider(provider) {
193
+ return selectableSources(provider).length > 1;
194
+ }
195
+ /**
196
+ * Flatten the provider list plus the expanded set into the visible-row list.
197
+ *
198
+ * - `providers` — filtered / ordered provider list (post-search).
199
+ * - `expanded` — keys of providers whose sources are open. Keys for
200
+ * providers that are no longer expandable are ignored rather
201
+ * than cleaned up, so stale expansion state (a credential
202
+ * removed while the modal is open) can't desync the cursor.
203
+ */
204
+ function buildVisibleAuthRows(providers, expanded) {
205
+ const out = [];
206
+ for (const provider of providers) {
207
+ const expandable = isExpandableProvider(provider);
208
+ out.push({
209
+ kind: "provider",
210
+ provider,
211
+ expandable
212
+ });
213
+ if (!expandable || !expanded.has(provider.key)) continue;
214
+ for (const method of selectableSources(provider)) out.push({
215
+ kind: "source",
216
+ providerKey: provider.key,
217
+ method
218
+ });
219
+ if (provider.envKey) {
220
+ const envMethod = provider.methods.find((m) => m.source === "env");
221
+ out.push({
222
+ kind: "source",
223
+ providerKey: provider.key,
224
+ method: envMethod ?? {
225
+ source: "env",
226
+ detail: provider.envKey,
227
+ active: false
228
+ }
229
+ });
230
+ }
231
+ }
232
+ return out;
233
+ }
234
+ /**
235
+ * Provider key a row "belongs to" — its own for a provider header, the parent's
236
+ * for a source row.
237
+ */
238
+ function parentProviderKey(row) {
239
+ return row.kind === "provider" ? row.provider.key : row.providerKey;
240
+ }
241
+ /**
242
+ * Index in `rows` of the header for the given provider key, or `-1`.
243
+ *
244
+ * Collapse handlers snap the cursor up to the parent BEFORE the row list
245
+ * shrinks; without that the cursor keeps its flat index and silently lands on
246
+ * whichever sibling row slid into that slot.
247
+ */
248
+ function indexOfProviderRow(rows, providerKey) {
249
+ return rows.findIndex((r) => r.kind === "provider" && r.provider.key === providerKey);
250
+ }
251
+ //#endregion
252
+ //#region src/chat/fold-actions.ts
253
+ /** Programs whose first argument is the thing they do (`git status`, `bun test`). */
254
+ const SUBCOMMAND_PROGRAMS = /* @__PURE__ */ new Set([
255
+ "git",
256
+ "gh",
257
+ "bun",
258
+ "npm",
259
+ "pnpm",
260
+ "yarn",
261
+ "npx",
262
+ "bunx",
263
+ "cargo",
264
+ "go",
265
+ "docker",
266
+ "kubectl",
267
+ "make",
268
+ "uv",
269
+ "pip",
270
+ "pip3",
271
+ "brew",
272
+ "deno",
273
+ "node",
274
+ "playwright",
275
+ "vitest",
276
+ "jest",
277
+ "tsc",
278
+ "eslint",
279
+ "prettier"
280
+ ]);
281
+ /** Wrappers that only change how the next program runs. */
282
+ const WRAPPERS = /* @__PURE__ */ new Set([
283
+ "sudo",
284
+ "time",
285
+ "nice",
286
+ "nohup",
287
+ "exec",
288
+ "command",
289
+ "caffeinate"
290
+ ]);
291
+ /** Setup steps that carry no meaning of their own. */
292
+ const SETUP = /* @__PURE__ */ new Set([
293
+ "cd",
294
+ "pushd",
295
+ "popd",
296
+ "export",
297
+ "set",
298
+ "source",
299
+ ".",
300
+ "unset",
301
+ "true",
302
+ "mkdir"
303
+ ]);
304
+ /** Split on `&&`, `||`, `;`, `|` and newlines outside quotes. */
305
+ function splitSegments(command) {
306
+ const out = [];
307
+ let cur = "";
308
+ let quote = null;
309
+ for (let i = 0; i < command.length; i++) {
310
+ const ch = command[i];
311
+ if (quote) {
312
+ if (ch === quote) quote = null;
313
+ cur += ch;
314
+ continue;
315
+ }
316
+ if (ch === "\"" || ch === "'") {
317
+ quote = ch;
318
+ cur += ch;
319
+ continue;
320
+ }
321
+ const two = command.slice(i, i + 2);
322
+ if (two === "&&" || two === "||") {
323
+ out.push(cur);
324
+ cur = "";
325
+ i++;
326
+ continue;
327
+ }
328
+ if (ch === ";" || ch === "|" || ch === "\n") {
329
+ out.push(cur);
330
+ cur = "";
331
+ continue;
332
+ }
333
+ cur += ch;
334
+ }
335
+ out.push(cur);
336
+ return out.map((s) => s.trim()).filter(Boolean);
337
+ }
338
+ /** Words of a segment, quotes kept as one word (unquoted). */
339
+ function words(segment) {
340
+ const out = [];
341
+ for (const m of segment.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
342
+ return out;
343
+ }
344
+ /** The gist of one segment (`bun run build`, `python3`), or null for setup noise. */
345
+ function segmentGist(segment) {
346
+ let w = words(segment);
347
+ for (;;) {
348
+ if (w.length === 0) return null;
349
+ const head = w[0];
350
+ if (/^[A-Z_]\w*=/i.test(head)) w = w.slice(1);
351
+ else if (WRAPPERS.has(head)) w = w.slice(1);
352
+ else if (head === "env") {
353
+ w = w.slice(1);
354
+ while (w[0] && (w[0].startsWith("-") || /^[A-Z_]\w*=/i.test(w[0]))) {
355
+ const flag = w[0];
356
+ w = w.slice(1);
357
+ if (flag === "-u" && w[0]) w = w.slice(1);
358
+ }
359
+ } else if (head === "timeout" && w[1] && /^\d/.test(w[1])) w = w.slice(2);
360
+ else break;
361
+ }
362
+ const program = w[0].split("/").pop();
363
+ if (SETUP.has(program)) return null;
364
+ if (!SUBCOMMAND_PROGRAMS.has(program)) return program;
365
+ const rest = w.slice(1).filter((arg) => !arg.startsWith("-"));
366
+ let sub = rest[0];
367
+ if ((program === "npx" || program === "bunx") && sub) {
368
+ const tool = sub.split("/").pop();
369
+ const next = rest[1];
370
+ return SUBCOMMAND_PROGRAMS.has(tool) && next && !next.includes("/") && !next.includes(".") ? `${tool} ${next}` : tool;
371
+ }
372
+ if ((sub === "run" || sub === "exec") && rest[1] && [
373
+ "bun",
374
+ "npm",
375
+ "pnpm",
376
+ "yarn"
377
+ ].includes(program)) return `${program} ${sub} ${rest[1]}`;
378
+ if (sub && (sub.includes("/") || /\.[a-z]+$/i.test(sub))) sub = void 0;
379
+ return sub ? `${program} ${sub}` : program;
380
+ }
381
+ /**
382
+ * `Ran <gist>` for a shell command: heredoc bodies dropped, setup steps
383
+ * (`cd`, `export`, `set -e`) skipped, env and wrappers peeled off, program
384
+ * plus subcommand kept. Further meaningful steps read as `+N`.
385
+ */
386
+ function commandGist(command) {
387
+ const gists = splitSegments(command.replace(/<<-?\s*(['"]?)(\w+)\1[\s\S]*?(?:\n\2\b|$)/g, "")).map(segmentGist).filter((g) => g !== null);
388
+ if (gists.length === 0) return {
389
+ gist: "a command",
390
+ more: 0
391
+ };
392
+ return {
393
+ gist: gists[0],
394
+ more: gists.length - 1
395
+ };
396
+ }
397
+ function splitPath(path) {
398
+ const clean = path.replace(/\/+$/, "");
399
+ const i = clean.lastIndexOf("/");
400
+ return i < 0 ? {
401
+ name: clean,
402
+ dir: ""
403
+ } : {
404
+ name: clean.slice(i + 1),
405
+ dir: clean.slice(0, i)
406
+ };
407
+ }
408
+ function str$1(input, key) {
409
+ const v = input?.[key];
410
+ return typeof v === "string" && v.trim() !== "" ? v : void 0;
411
+ }
412
+ /** Line counts exactly as the diff in the details shows them. */
413
+ function editCounts(edit) {
414
+ if (!edit) return null;
415
+ let added = 0;
416
+ let removed = 0;
417
+ for (const h of edit.hunks) for (const d of computeLineDiff(h.oldString, h.newString)) if (d.op === "add") added++;
418
+ else if (d.op === "remove") removed++;
419
+ return {
420
+ added,
421
+ removed
422
+ };
423
+ }
424
+ function shortUrl(url) {
425
+ const short = url.replace(/^https?:\/\//, "").replace(/\/$/, "");
426
+ return short.length > 48 ? `${short.slice(0, 47)}…` : short;
427
+ }
428
+ /**
429
+ * The dev browser in words: where it went and what it did. Clicks and typing
430
+ * read their result's first line (`Clicked "Save"`), which names the element;
431
+ * the ref the model used (`e12`) means nothing to a person.
432
+ */
433
+ function browserOverview(tool) {
434
+ const input = tool.input;
435
+ const url = str$1(input, "url");
436
+ const head = typeof tool.result === "string" && tool.ok ? tool.result.split("\n")[0].replace(/\. No navigation, dialog or error\.$|\.$/, "") : "";
437
+ switch (tool.name) {
438
+ case "browser_open": return url ? {
439
+ label: `Opened ${shortUrl(url)}`,
440
+ title: url
441
+ } : { label: "Opened the browser" };
442
+ case "browser_navigate": {
443
+ const action = str$1(input, "action");
444
+ if (action === "back") return { label: "Went back" };
445
+ if (action === "forward") return { label: "Went forward" };
446
+ if (action === "reload") return { label: "Reloaded the page" };
447
+ return url ? {
448
+ label: `Went to ${shortUrl(url)}`,
449
+ title: url
450
+ } : { label: "Navigated" };
451
+ }
452
+ case "browser_snapshot": return { label: "Read the page" };
453
+ case "browser_screenshot": return { label: "Took a screenshot" };
454
+ case "browser_click": return { label: head.startsWith("Clicked") ? head : `Clicked ${tool.target ?? "the page"}` };
455
+ case "browser_type": return { label: head.startsWith("Typed") ? head : "Typed in the page" };
456
+ case "browser_press": return { label: head.startsWith("Pressed") ? head : `Pressed ${str$1(input, "key") ?? "a key"}` };
457
+ case "browser_scroll": return { label: "Scrolled the page" };
458
+ case "browser_wait_for": {
459
+ const what = str$1(input, "text") ?? str$1(input, "selector") ?? str$1(input, "urlIncludes");
460
+ return { label: what ? `Waited for “${what.length > 40 ? `${what.slice(0, 39)}…` : what}”` : "Waited" };
461
+ }
462
+ case "browser_evaluate": {
463
+ const expression = str$1(input, "expression") ?? "";
464
+ return {
465
+ label: "Ran a script in the page",
466
+ ...expression ? { title: expression.slice(0, 600) } : {}
467
+ };
468
+ }
469
+ case "browser_console": return { label: "Read the console" };
470
+ default: return null;
471
+ }
472
+ }
473
+ /** The overview line for one tool call. */
474
+ function toolOverview(tool) {
475
+ const input = tool.input;
476
+ if (tool.name.startsWith("browser_")) {
477
+ const browser = browserOverview(tool);
478
+ if (browser) return browser;
479
+ }
480
+ switch (tool.name) {
481
+ case "shell":
482
+ case "bash": {
483
+ const command = str$1(input, "command") ?? tool.target ?? "";
484
+ const description = str$1(input, "description")?.trim();
485
+ if (description) return {
486
+ label: description.length > 120 ? `${description.slice(0, 119)}…` : description,
487
+ title: command.slice(0, 600)
488
+ };
489
+ const { gist, more } = commandGist(command);
490
+ return {
491
+ label: `Ran ${gist}`,
492
+ ...more > 0 ? { hint: `+${more}` } : {},
493
+ title: command.slice(0, 600)
494
+ };
495
+ }
496
+ case "read_file": {
497
+ const batch = str$1(input, "path") ? [] : readFileInputPaths(input);
498
+ if (batch.length > 1) return {
499
+ label: `Read ${splitPath(batch[0]).name} +${batch.length - 1}`,
500
+ title: batch.join("\n")
501
+ };
502
+ const path = str$1(input, "path") ?? batch[0] ?? tool.target ?? "";
503
+ const { name, dir } = splitPath(path);
504
+ return {
505
+ label: `Read ${name || "a file"}`,
506
+ ...dir ? { hint: dir } : {},
507
+ title: path
508
+ };
509
+ }
510
+ case "edit":
511
+ case "multi_edit":
512
+ case "write_file": {
513
+ const path = str$1(input, "path") ?? tool.edit?.path ?? tool.target ?? "";
514
+ const { name, dir } = splitPath(path);
515
+ const counts = editCounts(tool.edit);
516
+ return {
517
+ label: `${tool.name === "write_file" ? "Wrote" : "Edited"} ${name || "a file"}`,
518
+ ...dir ? { hint: dir } : {},
519
+ ...counts && (counts.added > 0 || counts.removed > 0) ? counts : {},
520
+ title: path
521
+ };
522
+ }
523
+ case "grep": {
524
+ const pattern = str$1(input, "pattern") ?? tool.target ?? "";
525
+ const where = str$1(input, "path");
526
+ return {
527
+ label: pattern ? `Searched for “${pattern.length > 48 ? `${pattern.slice(0, 47)}…` : pattern}”` : "Searched",
528
+ ...where ? { hint: where } : {},
529
+ title: pattern
530
+ };
531
+ }
532
+ case "glob": {
533
+ const pattern = str$1(input, "pattern") ?? tool.target ?? "";
534
+ return {
535
+ label: pattern ? `Found files ${pattern}` : "Found files",
536
+ title: pattern
537
+ };
538
+ }
539
+ case "spawn": {
540
+ const task = str$1(input, "task") ?? tool.target ?? "";
541
+ const first = task.split("\n").find((line) => line.trim()) ?? "";
542
+ return {
543
+ label: "Agent",
544
+ ...first ? { hint: first.length > 80 ? `${first.slice(0, 79)}…` : first } : {},
545
+ ...task ? { title: task.slice(0, 600) } : {}
546
+ };
547
+ }
548
+ case "present_plan": {
549
+ const title = str$1(input, "title");
550
+ return {
551
+ label: "Presented a plan",
552
+ ...title ? {
553
+ hint: title,
554
+ title
555
+ } : {}
556
+ };
557
+ }
558
+ case "ask_user": {
559
+ const n = Array.isArray(input?.questions) ? input.questions.length : 0;
560
+ return { label: n > 0 ? `Asked ${n} question${n === 1 ? "" : "s"}` : "Asked a question" };
561
+ }
562
+ case "todowrite": {
563
+ const todos = Array.isArray(input?.todos) ? input.todos : [];
564
+ const done = todos.filter((t) => t.status === "completed").length;
565
+ return {
566
+ label: "Updated todos",
567
+ ...todos.length > 0 ? { hint: `${done} of ${todos.length} done` } : {}
568
+ };
569
+ }
570
+ case "list_files": {
571
+ const path = str$1(input, "path") ?? tool.target ?? ".";
572
+ return {
573
+ label: `Listed ${path}`,
574
+ title: path
575
+ };
576
+ }
577
+ default: {
578
+ const target = tool.target;
579
+ const short = target && target.length > 60 ? `${target.slice(0, 59)}…` : target;
580
+ return {
581
+ label: short ? `${tool.displayName} ${short}` : tool.displayName,
582
+ ...target ? { title: target } : {}
583
+ };
584
+ }
585
+ }
586
+ }
587
+ /**
588
+ * Verb phrase per canonical tool name for a batch summary. Unknown tools
589
+ * derive one from their display name, so MCP and host tools read cleanly.
590
+ */
591
+ const BATCH_VERB_PHRASE = {
592
+ shell: "ran a command",
593
+ read_file: "read a file",
594
+ grep: "searched",
595
+ glob: "searched for files",
596
+ list_files: "listed files",
597
+ edit: "edited a file",
598
+ multi_edit: "edited a file",
599
+ write_file: "created a file",
600
+ spawn: "spawned a subagent",
601
+ tool_search: "searched for tools",
602
+ skills_use: "activated a skill",
603
+ skills_read: "read a skill",
604
+ skills_run_script: "ran a skill script",
605
+ ask_user: "asked a question",
606
+ present_plan: "presented a plan",
607
+ browser_open: "opened the browser",
608
+ browser_navigate: "navigated",
609
+ browser_snapshot: "read the page",
610
+ browser_screenshot: "took a screenshot",
611
+ browser_click: "clicked",
612
+ browser_type: "typed",
613
+ browser_press: "pressed a key",
614
+ browser_scroll: "scrolled",
615
+ browser_wait_for: "waited",
616
+ browser_evaluate: "ran a script in the page",
617
+ browser_console: "read the console",
618
+ terminal_list: "listed terminals",
619
+ terminal_read: "read a terminal",
620
+ terminal_write: "typed in a terminal"
621
+ };
622
+ function toolPhrase(name, displayName) {
623
+ return BATCH_VERB_PHRASE[name] ?? `used ${displayName.toLowerCase()}`;
624
+ }
625
+ /**
626
+ * Cap on distinct phrases in a summary: a 40-call exchange reads "Ran a
627
+ * command, searched, read a file, edited a file +23", where +N counts the
628
+ * calls the shown phrases do not cover.
629
+ */
630
+ const MAX_SUMMARY_PHRASES = 4;
631
+ /**
632
+ * "Ran a command, read a file +2" for a run of calls. Adjacent identical
633
+ * phrases merge, so three reads in a row say "read a file" once.
634
+ */
635
+ function summarizeTools(tools) {
636
+ const phrases = [];
637
+ for (const t of tools) {
638
+ const phrase = toolPhrase(t.name, t.displayName);
639
+ const last = phrases[phrases.length - 1];
640
+ if (last && last.phrase === phrase) last.calls += 1;
641
+ else phrases.push({
642
+ phrase,
643
+ calls: 1
644
+ });
645
+ }
646
+ if (phrases.length === 0) return "Ran tools";
647
+ const shown = phrases.slice(0, MAX_SUMMARY_PHRASES);
648
+ const shownCalls = shown.reduce((n, p) => n + p.calls, 0);
649
+ const rest = tools.length - shownCalls;
650
+ const joined = shown.map((p) => p.phrase).join(", ");
651
+ const capped = rest > 0 ? `${joined} +${rest}` : joined;
652
+ return capped.charAt(0).toUpperCase() + capped.slice(1);
653
+ }
654
+ /**
655
+ * A message as a person skims it: its words, markdown syntax and code
656
+ * blocks dropped, whitespace folded. For the compact stacks of earlier
657
+ * messages.
658
+ */
659
+ function messagePreview(text) {
660
+ return text.replace(/```[\s\S]*?(?:```|$)/g, " ").replace(/`([^`]*)`/g, "$1").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/^\s{0,3}(?:#{1,6}\s+|>\s?|[-*+]\s+|\d+[.)]\s+)/gm, "").replace(/(\*\*|__|\*|_|~~)(\S(?:.*?\S)?)\1/g, "$2").replace(/\s+/g, " ").trim();
661
+ }
662
+ //#endregion
663
+ //#region src/chat/instruction-rows.ts
664
+ /**
665
+ * Upper bound on preview lines rendered for one expanded segment. Mirrors the
666
+ * Logs tab's render cap: the scrollbox can hold arbitrary height, but pasting a
667
+ * runaway 64KB instruction file into the layout pass is a waste of a frame.
668
+ * File-backed segments have `$EDITOR` as the escape hatch for the remainder.
669
+ */
670
+ const INSTRUCTION_PREVIEW_LINE_CAP = 400;
671
+ /**
672
+ * Filter segments by the active query and resolve each one's expanded state.
673
+ *
674
+ * Deliberately does NOT auto-expand matches the way the Extensions tab does:
675
+ * the search corpus includes each segment's full text, so a query typically
676
+ * matches deep inside a long document — auto-expanding every hit would dump
677
+ * thousands of lines into the viewport on a keystroke. The user expands the row
678
+ * they care about.
679
+ */
680
+ function buildVisibleInstructionRows(opts) {
681
+ const { segments, expanded, queryActive, matchSegment } = opts;
682
+ const out = [];
683
+ for (const segment of segments) {
684
+ if (queryActive && !matchSegment(segment)) continue;
685
+ out.push({
686
+ segment,
687
+ expanded: expanded.has(segment.id)
688
+ });
689
+ }
690
+ return out;
691
+ }
692
+ /**
693
+ * Split a segment's text into renderable preview lines, clamped to
694
+ * {@link INSTRUCTION_PREVIEW_LINE_CAP}.
695
+ *
696
+ * `hidden` is how many lines were dropped off the tail, so the view can render
697
+ * an honest "+N more lines" marker instead of silently truncating. Tabs are
698
+ * expanded to two spaces because the terminal text renderable doesn't
699
+ * interpret `\t`, and `\r` is stripped so CRLF files don't leave artefacts at
700
+ * every line end.
701
+ */
702
+ function instructionPreviewLines(text) {
703
+ const all = text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").split("\n");
704
+ while (all.length > 0 && all[all.length - 1].trim() === "") all.pop();
705
+ if (all.length <= 400) return {
706
+ lines: all,
707
+ hidden: 0
708
+ };
709
+ return {
710
+ lines: all.slice(0, 400),
711
+ hidden: all.length - 400
712
+ };
713
+ }
714
+ /**
715
+ * Short badge for a segment's origin — the "where does this come from" column.
716
+ * `'file'` is intentionally the only one that reads like a location, since it's
717
+ * the only origin the user can act on.
718
+ */
719
+ function instructionOriginBadge(segment) {
720
+ switch (segment.origin) {
721
+ case "file": return "file";
722
+ case "generated": return "generated";
723
+ case "mcp": return "mcp server";
724
+ case "built-in": return "built-in";
725
+ }
726
+ }
727
+ //#endregion
728
+ //#region src/chat/machines-view.ts
729
+ /** The `label` of the notices a host emits about a session's machine. */
730
+ const MACHINE_NOTICE_LABEL = "machine";
731
+ const WAITING_RE = /^waiting for (\S+?)…/;
732
+ const BACK_RE = /^\S+ is back\./;
733
+ const GAVE_UP_RE = /^\S+ stayed offline for /;
734
+ /**
735
+ * The machine a session's run is paused on, from its notices (`waiting for
736
+ * laptop…` until `laptop is back.` or `laptop stayed offline … run stopped`),
737
+ * `null` when none. `online`: machines known to be back since (a
738
+ * `machine:status` the window saw), which clear the wait at once.
739
+ */
740
+ function machineWaiting(events, online = /* @__PURE__ */ new Set()) {
741
+ for (let i = events.length - 1; i >= 0; i--) {
742
+ const event = events[i];
743
+ if (event.notice?.label !== "machine" || typeof event.text !== "string") continue;
744
+ if (BACK_RE.test(event.text) || GAVE_UP_RE.test(event.text)) return null;
745
+ const waiting = WAITING_RE.exec(event.text)?.[1];
746
+ if (waiting) return online.has(waiting) ? null : waiting;
747
+ }
748
+ return null;
749
+ }
750
+ function dirname(path) {
751
+ const trimmed = path.replace(/\/+$/, "");
752
+ const at = trimmed.lastIndexOf("/");
753
+ return at <= 0 ? "/" : trimmed.slice(0, at);
754
+ }
755
+ function basename(path) {
756
+ const trimmed = path.replace(/\/+$/, "");
757
+ return trimmed.slice(trimmed.lastIndexOf("/") + 1) || trimmed;
758
+ }
759
+ /** `machines.listDirs` as a folder listing: the parent stops at the root the folder is in. */
760
+ function machineDirListing(result) {
761
+ const { path } = result;
762
+ return {
763
+ roots: result.roots,
764
+ path,
765
+ parent: result.roots.includes(path) ? null : dirname(path),
766
+ entries: result.dirs.map((dir) => ({
767
+ path: dir,
768
+ name: basename(dir),
769
+ isGitRepo: false
770
+ }))
771
+ };
772
+ }
773
+ /**
774
+ * Environment kinds in picker order: this host's own first, then each
775
+ * machine's together (`local@` before `worktree@`), machines by name.
776
+ */
777
+ function orderByMachine(descriptors) {
778
+ const rank = (d) => {
779
+ const parsed = parseMachineKind(d.kind);
780
+ return parsed ? [
781
+ 1,
782
+ parsed.name,
783
+ parsed.base === "local" ? 0 : 1
784
+ ] : [
785
+ 0,
786
+ "",
787
+ 0
788
+ ];
789
+ };
790
+ return descriptors.map((d, i) => ({
791
+ d,
792
+ i,
793
+ r: rank(d)
794
+ })).sort((a, b) => a.r[0] - b.r[0] || a.r[1].localeCompare(b.r[1]) || a.r[2] - b.r[2] || a.i - b.i).map((entry) => entry.d);
795
+ }
796
+ /** Why a machine kind can't be picked now (its machine is offline), else `undefined`. */
797
+ function machineBlockedReason(descriptor) {
798
+ return descriptor.machine?.status === "offline" ? `${descriptor.machine.name} is offline` : void 0;
799
+ }
800
+ /** A session's environment as its header shows it: `local@laptop`, `worktree@laptop ⎇ feat`, `⎇ feat`, or the kind. */
801
+ function environmentLabel(environment) {
802
+ if (parseMachineKind(environment.kind)) return environment.branch ? `${environment.kind} ⎇ ${environment.branch}` : environment.kind;
803
+ return environment.branch ? `⎇ ${environment.branch}` : environment.kind;
804
+ }
805
+ const MACHINE_LINK_STATE_TEXT = {
806
+ "connecting": "connecting…",
807
+ "connected": "connected",
808
+ "reconnecting": "reconnecting…",
809
+ "offline": "offline",
810
+ "auth-refused": "refused",
811
+ "identity": "another server answers there",
812
+ "refused": "refused"
813
+ };
814
+ /** "seen 5 min ago" for a machine's `lastSeen`. */
815
+ function lastSeenText(lastSeen, now) {
816
+ if (lastSeen === void 0) return "never seen";
817
+ const minutes = Math.max(0, Math.round((now - lastSeen) / 6e4));
818
+ if (minutes < 1) return "seen just now";
819
+ if (minutes < 60) return `seen ${minutes} min ago`;
820
+ const hours = Math.round(minutes / 60);
821
+ return hours < 48 ? `seen ${hours} h ago` : `seen ${Math.round(hours / 24)} d ago`;
822
+ }
823
+ //#endregion
824
+ //#region src/chat/mcp-form.ts
825
+ const MCP_TRANSPORTS = [
826
+ "stdio",
827
+ "streamable-http",
828
+ "sse"
829
+ ];
830
+ /**
831
+ * Validate + normalize the raw field values into a config entry.
832
+ * Returns the first problem in row order so the dialog can surface it.
833
+ */
834
+ function validateMcpForm(values) {
835
+ const name = values.name.trim();
836
+ if (name.length === 0) return {
837
+ ok: false,
838
+ error: "name is required"
839
+ };
840
+ if (!/^[\w.-]+$/.test(name)) return {
841
+ ok: false,
842
+ error: "name must be alphanumerics, dots, hyphens, underscores"
843
+ };
844
+ if (values.transport === "stdio") {
845
+ const tokens = splitCommandLine(values.commandLine);
846
+ if (tokens.length === 0) return {
847
+ ok: false,
848
+ error: "command is required for stdio servers"
849
+ };
850
+ const env = parseKeyValueLines(values.env, {
851
+ label: "env",
852
+ separators: ["="]
853
+ });
854
+ if (!env.ok) return {
855
+ ok: false,
856
+ error: env.error
857
+ };
858
+ return {
859
+ ok: true,
860
+ result: {
861
+ entry: {
862
+ name,
863
+ transport: "stdio",
864
+ command: tokens[0],
865
+ ...tokens.length > 1 ? { args: tokens.slice(1) } : {},
866
+ ...Object.keys(env.pairs).length > 0 ? { env: env.pairs } : {}
867
+ },
868
+ scope: values.scope
869
+ }
870
+ };
871
+ }
872
+ const url = values.url.trim();
873
+ if (url.length === 0) return {
874
+ ok: false,
875
+ error: "url is required for http/sse servers"
876
+ };
877
+ if (!/^https?:\/\//.test(url)) return {
878
+ ok: false,
879
+ error: "url must start with http:// or https://"
880
+ };
881
+ const headers = parseKeyValueLines(values.headers, {
882
+ label: "headers",
883
+ separators: [":", "="]
884
+ });
885
+ if (!headers.ok) return {
886
+ ok: false,
887
+ error: headers.error
888
+ };
889
+ return {
890
+ ok: true,
891
+ result: {
892
+ entry: {
893
+ name,
894
+ transport: values.transport,
895
+ url,
896
+ ...Object.keys(headers.pairs).length > 0 ? { headers: headers.pairs } : {}
897
+ },
898
+ scope: values.scope
899
+ }
900
+ };
901
+ }
902
+ /**
903
+ * Split a command line into tokens, honoring single/double quotes so
904
+ * `npx -y my-server --root "/path with spaces"` round-trips. No escape
905
+ * processing beyond quote grouping — this is a form field, not a shell.
906
+ */
907
+ function splitCommandLine(line) {
908
+ const out = [];
909
+ const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
910
+ for (let m = re.exec(line); m !== null; m = re.exec(line)) out.push(m[1] ?? m[2] ?? m[3]);
911
+ return out;
912
+ }
913
+ /**
914
+ * Parse one key/value pair PER LINE. Line-based (not whitespace-split)
915
+ * because values legitimately contain spaces — `Authorization: Bearer
916
+ * abc123` is the canonical headers case, and env values like
917
+ * `GREETING=hello world` are equally valid. The first separator found
918
+ * (in `separators` priority order, `:` before `=` for headers) splits;
919
+ * the rest of the line is the value, trimmed. Blank lines skipped.
920
+ */
921
+ function parseKeyValueLines(raw, opts) {
922
+ const pairs = {};
923
+ for (const line of raw.split("\n")) {
924
+ const trimmed = line.trim();
925
+ if (trimmed.length === 0) continue;
926
+ let splitAt = -1;
927
+ for (const sep of opts.separators) {
928
+ const idx = trimmed.indexOf(sep);
929
+ if (idx > 0 && (splitAt === -1 || idx < splitAt)) splitAt = idx;
930
+ }
931
+ if (splitAt <= 0) {
932
+ const forms = opts.separators.map((s) => `KEY${s}VALUE`).join(" or ");
933
+ return {
934
+ ok: false,
935
+ error: `${opts.label} entries must be ${forms}, one per line (got "${trimmed}")`
936
+ };
937
+ }
938
+ const key = trimmed.slice(0, splitAt).trim();
939
+ const value = trimmed.slice(splitAt + 1).trim();
940
+ if (key.length === 0) return {
941
+ ok: false,
942
+ error: `${opts.label} entry has an empty key ("${trimmed}")`
943
+ };
944
+ pairs[key] = value;
945
+ }
946
+ return {
947
+ ok: true,
948
+ pairs
949
+ };
950
+ }
951
+ //#endregion
952
+ //#region src/chat/model-name.ts
953
+ /** Brand words a first-party provider's names start with. */
954
+ const OWN_BRAND = {
955
+ anthropic: /^claude[\s-]+/i,
956
+ openai: /^openai[\s:-]+/i
957
+ };
958
+ const ACRONYMS = /* @__PURE__ */ new Set([
959
+ "gpt",
960
+ "glm",
961
+ "oss",
962
+ "ai",
963
+ "r1",
964
+ "vl"
965
+ ]);
966
+ /** `claude-opus-4-7-20250722` → `Claude Opus 4.7`; `gpt-5.5` → `GPT 5.5`. */
967
+ function readableId(id) {
968
+ const parts = (id.split("/").pop() ?? id).replace(/-\d{8}$/, "").split(/[-_]/).filter(Boolean);
969
+ const out = [];
970
+ for (const part of parts) {
971
+ const prev = out[out.length - 1];
972
+ if (/^\d+$/.test(part) && prev && /^\d+(?:\.\d+)*$/.test(prev)) {
973
+ out[out.length - 1] = `${prev}.${part}`;
974
+ continue;
975
+ }
976
+ out.push(ACRONYMS.has(part.toLowerCase()) ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1));
977
+ }
978
+ return out.join(" ");
979
+ }
980
+ function modelDisplayName({ providerKey, modelId, modelName }) {
981
+ let name = (modelName && modelName !== modelId ? modelName : readableId(modelId)).trim();
982
+ let vendor;
983
+ name = name.replace(/\s*\(latest\)\s*$/i, "");
984
+ const prefixed = /^([^:]{2,40}):\s+(\S.*)$/.exec(name);
985
+ if (prefixed) {
986
+ vendor = prefixed[1].trim();
987
+ name = prefixed[2].trim();
988
+ }
989
+ const key = providerKey ?? (/^claude[-_]/i.test(modelId) ? "anthropic" : void 0);
990
+ const brand = key ? OWN_BRAND[key] : void 0;
991
+ if (brand && brand.test(name) && name.replace(brand, "").trim()) name = name.replace(brand, "").trim();
992
+ return vendor ? {
993
+ name,
994
+ vendor
995
+ } : { name };
996
+ }
997
+ //#endregion
998
+ //#region src/chat/notice-format.ts
999
+ /** Leading glyph per severity. `info` is deliberately quiet — a middle dot. */
1000
+ const NOTICE_GLYPH = {
1001
+ info: "·",
1002
+ warn: "!",
1003
+ error: "✗"
1004
+ };
1005
+ //#endregion
1006
+ //#region src/chat/palette.ts
1007
+ /** Group name the hosts use for the most-recently-used rows shown on an empty query. */
1008
+ const PALETTE_RECENT_GROUP = "Recent";
1009
+ const PALETTE_RECENTS_MAX = 12;
1010
+ /** Ranked rows returned for a query; the host virtualises anyway, this bounds the work. */
1011
+ const PALETTE_RESULT_MAX = 200;
1012
+ const WORD_BREAKS = /* @__PURE__ */ new Set([
1013
+ " ",
1014
+ "/",
1015
+ "-",
1016
+ "_",
1017
+ ":",
1018
+ "."
1019
+ ]);
1020
+ /** What a hit at `at` is worth: the very start, a word start, a camelCase hump, or anywhere. */
1021
+ function startBonus(text, lower, at) {
1022
+ if (at === 0) return 8;
1023
+ if (WORD_BREAKS.has(lower[at - 1])) return 6;
1024
+ if (text[at] !== lower[at] && text[at - 1] === lower[at - 1]) return 4;
1025
+ return 1;
1026
+ }
1027
+ /** Bonus per character for a term found whole: "set" in "settings" beats s·e·t across words. */
1028
+ const WHOLE_TERM_BONUS = 2;
1029
+ /**
1030
+ * Subsequence match of `query` in `text` with the usual bonuses: word
1031
+ * starts and the very beginning score higher, gaps cost, runs of adjacent
1032
+ * hits are rewarded, and the query found whole (as one run) beats the same
1033
+ * letters scattered. Returns null when not every query character appears
1034
+ * in order. Case-insensitive; `query` must already be lower-cased.
1035
+ */
1036
+ function fuzzyMatch(text, query) {
1037
+ return matchLowered(text, text.toLowerCase(), query);
1038
+ }
1039
+ const NO_INDICES = [];
1040
+ /** `indices` null: the score alone (a fallback field never highlights), without the allocations. */
1041
+ function matchLowered(text, lower, query, indices = []) {
1042
+ if (query.length === 0) return {
1043
+ score: 0,
1044
+ indices: [],
1045
+ whole: true
1046
+ };
1047
+ let score = 0;
1048
+ let from = 0;
1049
+ let prev = -2;
1050
+ for (let q = 0; q < query.length; q++) {
1051
+ const at = lower.indexOf(query[q], from);
1052
+ if (at === -1) return null;
1053
+ score += startBonus(text, lower, at);
1054
+ if (at === prev + 1) score += 3;
1055
+ else if (prev >= 0) score -= Math.min(3, at - prev - 1) * .5;
1056
+ indices?.push(at);
1057
+ prev = at;
1058
+ from = at + 1;
1059
+ }
1060
+ let whole = -Infinity;
1061
+ let wholeAt = -1;
1062
+ for (let at = lower.indexOf(query); at !== -1; at = lower.indexOf(query, at + 1)) {
1063
+ const value = startBonus(text, lower, at) + (query.length - 1) * 4 + query.length * WHOLE_TERM_BONUS;
1064
+ if (value > whole) {
1065
+ whole = value;
1066
+ wholeAt = at;
1067
+ }
1068
+ if (at === 0 || WORD_BREAKS.has(lower[at - 1])) break;
1069
+ }
1070
+ const found = whole > score;
1071
+ if (found) {
1072
+ score = whole;
1073
+ if (indices) for (let q = 0; q < query.length; q++) indices[q] = wholeAt + q;
1074
+ }
1075
+ score -= text.length * .02;
1076
+ return {
1077
+ score,
1078
+ indices: indices ?? NO_INDICES,
1079
+ whole: found
1080
+ };
1081
+ }
1082
+ const haystacks = /* @__PURE__ */ new WeakMap();
1083
+ function haystackOf(item) {
1084
+ let h = haystacks.get(item);
1085
+ if (!h) {
1086
+ const texts = [
1087
+ item.title,
1088
+ item.description,
1089
+ item.group,
1090
+ ...item.keywords ?? []
1091
+ ].filter((t) => !!t);
1092
+ h = {
1093
+ title: item.title.toLowerCase(),
1094
+ fields: texts.map((t) => [t, t.toLowerCase()])
1095
+ };
1096
+ haystacks.set(item, h);
1097
+ }
1098
+ return h;
1099
+ }
1100
+ /**
1101
+ * Every whitespace-separated term must match (AND); scores add. `whole`:
1102
+ * each term was found as one run (the words typed, not letters picked
1103
+ * across the text). `indices` collects the merged highlight when given;
1104
+ * ranking scores without it and highlights only the rows it keeps.
1105
+ */
1106
+ function matchTerms(text, lower, terms, indices) {
1107
+ let score = 0;
1108
+ let whole = true;
1109
+ for (const term of terms) {
1110
+ const m = matchLowered(text, lower, term, indices && []);
1111
+ if (!m) return null;
1112
+ score += m.score;
1113
+ whole &&= m.whole;
1114
+ indices?.push(...m.indices);
1115
+ }
1116
+ return {
1117
+ score,
1118
+ whole
1119
+ };
1120
+ }
1121
+ function titleIndices(item, terms) {
1122
+ const indices = [];
1123
+ matchTerms(item.title, haystackOf(item).title, terms, indices);
1124
+ return terms.length === 1 ? indices : [...new Set(indices)].sort((a, b) => a - b);
1125
+ }
1126
+ const collator = new Intl.Collator();
1127
+ function termsOf(query) {
1128
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1129
+ }
1130
+ /** On an empty query: recents first (most recent on top), then the catalog in provider order. */
1131
+ function listUnfiltered(items, recents) {
1132
+ const recentRank = new Map(recents.map((id, i) => [id, i]));
1133
+ const out = [];
1134
+ for (const item of items) {
1135
+ const r = recentRank.get(item.id);
1136
+ if (item.searchOnly && r === void 0) continue;
1137
+ out.push({
1138
+ item,
1139
+ score: r === void 0 ? 0 : 12 - r,
1140
+ titleMatches: []
1141
+ });
1142
+ }
1143
+ return out.sort((a, b) => b.score - a.score).slice(0, 200);
1144
+ }
1145
+ /** The ranked rows for `terms`, and every item that matched (the pool a longer query searches). */
1146
+ function rankTerms(items, terms) {
1147
+ const hits = [];
1148
+ for (const item of items) {
1149
+ const hay = haystackOf(item);
1150
+ const title = matchTerms(item.title, hay.title, terms, null);
1151
+ if (title?.whole) {
1152
+ hits.push({
1153
+ item,
1154
+ score: 100 + title.score,
1155
+ inTitle: true
1156
+ });
1157
+ continue;
1158
+ }
1159
+ let total = 0;
1160
+ let ok = true;
1161
+ for (const term of terms) {
1162
+ let best = -Infinity;
1163
+ for (const [text, lower] of hay.fields) {
1164
+ const m = matchLowered(text, lower, term, null);
1165
+ if (m && m.score > best) best = m.score;
1166
+ }
1167
+ if (best === -Infinity) {
1168
+ ok = false;
1169
+ break;
1170
+ }
1171
+ total += best;
1172
+ }
1173
+ if (title && title.score >= total) hits.push({
1174
+ item,
1175
+ score: title.score,
1176
+ inTitle: true
1177
+ });
1178
+ else if (ok) hits.push({
1179
+ item,
1180
+ score: total,
1181
+ inTitle: false
1182
+ });
1183
+ }
1184
+ const matched = hits.map((h) => h.item);
1185
+ hits.sort((a, b) => b.score - a.score || collator.compare(a.item.title, b.item.title));
1186
+ return {
1187
+ ranked: hits.slice(0, 200).map(({ item, score, inTitle }) => ({
1188
+ item,
1189
+ score,
1190
+ titleMatches: inTitle ? titleIndices(item, terms) : []
1191
+ })),
1192
+ matched
1193
+ };
1194
+ }
1195
+ /**
1196
+ * Score every item against `query`. Terms are AND-ed: `tog sid` needs both
1197
+ * `tog` and `sid` in order somewhere in the text. Title matches dominate;
1198
+ * description, keywords and group are fallbacks that never outrank a
1199
+ * title hit. On an empty query recents come first (most recent on top),
1200
+ * then the catalog in provider order.
1201
+ */
1202
+ function rankPaletteItems(items, query, recents = []) {
1203
+ const terms = termsOf(query);
1204
+ return terms.length === 0 ? listUnfiltered(items, recents) : rankTerms(items, terms).ranked;
1205
+ }
1206
+ /** `next` asks for at least what `prev` did: each earlier term grew or stayed, new ones came after. */
1207
+ function narrows(prev, next) {
1208
+ return next.length >= prev.length && prev.every((term, i) => next[i].startsWith(term));
1209
+ }
1210
+ /**
1211
+ * `rankPaletteItems` for a palette being typed into: it remembers the last
1212
+ * query, and when the next one only adds to it (one more character, one
1213
+ * more word) it searches just what matched before. A longer query never
1214
+ * matches an item the shorter one missed, so the result is the same.
1215
+ */
1216
+ function createPaletteRanker() {
1217
+ let last = null;
1218
+ return (items, query, recents = []) => {
1219
+ const terms = termsOf(query);
1220
+ if (terms.length === 0) {
1221
+ last = null;
1222
+ return listUnfiltered(items, recents);
1223
+ }
1224
+ const { ranked, matched } = rankTerms(last && last.items === items && narrows(last.terms, terms) ? last.matched : items, terms);
1225
+ last = {
1226
+ items,
1227
+ terms,
1228
+ matched
1229
+ };
1230
+ return ranked;
1231
+ };
1232
+ }
1233
+ /**
1234
+ * Sections for display. With a query the ranking already interleaves
1235
+ * providers, so groups follow the order of their best row. Without a
1236
+ * query the recents (ids in `recents`, present in `ranked`) form a
1237
+ * leading `Recent` section and the rest keeps provider order.
1238
+ */
1239
+ function groupPaletteRows(ranked, query, recents = []) {
1240
+ const groups = /* @__PURE__ */ new Map();
1241
+ const push = (title, row) => {
1242
+ const list = groups.get(title);
1243
+ if (list) list.push(row);
1244
+ else groups.set(title, [row]);
1245
+ };
1246
+ const isRecent = new Set(query.trim() ? [] : recents);
1247
+ for (const row of ranked) push(isRecent.has(row.item.id) ? PALETTE_RECENT_GROUP : row.item.group, row);
1248
+ return [...groups.entries()].map(([title, rows]) => ({
1249
+ title,
1250
+ rows
1251
+ }));
1252
+ }
1253
+ /** Most-recent-first list of ids, deduplicated and capped. */
1254
+ function pushPaletteRecent(recents, id) {
1255
+ return [id, ...recents.filter((r) => r !== id)].slice(0, 12);
1256
+ }
1257
+ const PALETTE_LIMITS = {
1258
+ items: 500,
1259
+ textChars: 500,
1260
+ keywords: 16
1261
+ };
1262
+ function str(v, max = PALETTE_LIMITS.textChars) {
1263
+ return typeof v === "string" && v.length > 0 ? v.length > max ? `${v.slice(0, max - 1)}…` : v : void 0;
1264
+ }
1265
+ /**
1266
+ * Coerce whatever a provider returned into well-formed items: entries
1267
+ * without an id or title are dropped, strings are capped, unknown kinds
1268
+ * fall back to `action`, and `source` is stamped with the provider id.
1269
+ * Never throws.
1270
+ */
1271
+ function normalizePaletteItems(input, source, defaultGroup) {
1272
+ if (!Array.isArray(input)) return [];
1273
+ const out = [];
1274
+ const seen = /* @__PURE__ */ new Set();
1275
+ for (const raw of input.slice(0, PALETTE_LIMITS.items)) {
1276
+ if (!raw || typeof raw !== "object") continue;
1277
+ const r = raw;
1278
+ const id = str(r.id, 200);
1279
+ const title = str(r.title, 200);
1280
+ if (!id || !title || seen.has(id)) continue;
1281
+ seen.add(id);
1282
+ const item = {
1283
+ id,
1284
+ title,
1285
+ group: str(r.group, 80) ?? defaultGroup,
1286
+ source
1287
+ };
1288
+ const description = str(r.description);
1289
+ if (description) item.description = description;
1290
+ if (Array.isArray(r.keywords)) {
1291
+ const kw = r.keywords.filter((k) => typeof k === "string" && k.length > 0).slice(0, PALETTE_LIMITS.keywords);
1292
+ if (kw.length > 0) item.keywords = kw.map((k) => k.slice(0, 60));
1293
+ }
1294
+ if (r.icon && typeof r.icon === "object") {
1295
+ const icon = r.icon;
1296
+ const gui = str(icon.gui, 120);
1297
+ const tui = str(icon.tui, 8);
1298
+ if (gui || tui) item.icon = {
1299
+ ...gui ? { gui } : {},
1300
+ ...tui ? { tui } : {}
1301
+ };
1302
+ }
1303
+ if (r.kind === "toggle" || r.kind === "picker") item.kind = r.kind;
1304
+ if (typeof r.checked === "boolean") item.checked = r.checked;
1305
+ const keybinding = str(r.keybinding, 60);
1306
+ if (keybinding) item.keybinding = keybinding;
1307
+ const disabled = str(r.disabled, 200);
1308
+ if (disabled) item.disabled = disabled;
1309
+ if (r.searchOnly === true) item.searchOnly = true;
1310
+ out.push(item);
1311
+ }
1312
+ return out;
1313
+ }
1314
+ /** Same guard for a `run` result: anything malformed becomes `undefined` (nothing to do). */
1315
+ function normalizePaletteRunResult(input, source, defaultGroup) {
1316
+ if (!input || typeof input !== "object") return void 0;
1317
+ const r = input;
1318
+ switch (r.kind) {
1319
+ case "items": {
1320
+ const out = {
1321
+ kind: "items",
1322
+ items: normalizePaletteItems(r.items, source, defaultGroup)
1323
+ };
1324
+ const title = str(r.title, 200);
1325
+ const placeholder = str(r.placeholder, 200);
1326
+ if (title) out.title = title;
1327
+ if (placeholder) out.placeholder = placeholder;
1328
+ return out;
1329
+ }
1330
+ case "prompt":
1331
+ case "insert": {
1332
+ const text = str(r.text, 2e4);
1333
+ return text ? {
1334
+ kind: r.kind,
1335
+ text
1336
+ } : void 0;
1337
+ }
1338
+ case "notify": {
1339
+ const text = str(r.text, 1e3);
1340
+ if (!text) return void 0;
1341
+ return {
1342
+ kind: "notify",
1343
+ text,
1344
+ tone: r.tone === "warn" || r.tone === "error" ? r.tone : "info"
1345
+ };
1346
+ }
1347
+ default: return;
1348
+ }
1349
+ }
1350
+ /** Type guard hosts use before forwarding a payload from an untrusted provider. */
1351
+ function isPaletteContext(v) {
1352
+ if (!v || typeof v !== "object") return false;
1353
+ const c = v;
1354
+ return (c.host === "gui" || c.host === "tui") && (c.cwd === void 0 || typeof c.cwd === "string") && (c.sessionId === void 0 || typeof c.sessionId === "string") && (c.busy === void 0 || typeof c.busy === "boolean");
1355
+ }
1356
+ //#endregion
1357
+ //#region src/chat/panels.ts
1358
+ const INBOX_PANEL_ID = "inbox";
1359
+ const TODOS_PANEL_ID = "todos";
1360
+ const ARTIFACTS_PANEL_ID = "artifacts";
1361
+ const DIFFS_PANEL_ID = "diffs";
1362
+ const FILES_PANEL_ID = "files";
1363
+ const BROWSER_PANEL_ID = "browser";
1364
+ const TERMINAL_PANEL_ID = "terminal";
1365
+ const BUILTIN_PANEL_TABS = [
1366
+ {
1367
+ id: INBOX_PANEL_ID,
1368
+ label: "Inbox",
1369
+ icon: {
1370
+ gui: "i-ph-tray",
1371
+ tui: "✉"
1372
+ },
1373
+ builtin: true
1374
+ },
1375
+ {
1376
+ id: TODOS_PANEL_ID,
1377
+ label: "Todos",
1378
+ icon: {
1379
+ gui: "i-ph-list-checks",
1380
+ tui: "▣"
1381
+ },
1382
+ builtin: true
1383
+ },
1384
+ {
1385
+ id: ARTIFACTS_PANEL_ID,
1386
+ label: "Artifacts",
1387
+ icon: {
1388
+ gui: "i-ph-cube",
1389
+ tui: "◆"
1390
+ },
1391
+ builtin: true
1392
+ },
1393
+ {
1394
+ id: DIFFS_PANEL_ID,
1395
+ label: "Diffs",
1396
+ icon: {
1397
+ gui: "i-ph-git-diff",
1398
+ tui: "±"
1399
+ },
1400
+ builtin: true
1401
+ },
1402
+ {
1403
+ id: FILES_PANEL_ID,
1404
+ label: "Files",
1405
+ icon: {
1406
+ gui: "i-ph-tree-structure",
1407
+ tui: "▤"
1408
+ },
1409
+ builtin: true
1410
+ },
1411
+ {
1412
+ id: BROWSER_PANEL_ID,
1413
+ label: "Browser",
1414
+ icon: {
1415
+ gui: "i-ph-globe-simple",
1416
+ tui: "◎"
1417
+ },
1418
+ builtin: true
1419
+ },
1420
+ {
1421
+ id: TERMINAL_PANEL_ID,
1422
+ label: "Terminal",
1423
+ icon: {
1424
+ gui: "i-ph-terminal-window",
1425
+ tui: "❯"
1426
+ },
1427
+ builtin: true
1428
+ }
1429
+ ];
1430
+ /** Built-ins first, then extension panels in registry (load) order. */
1431
+ function buildPanelCatalog(extensionPanels, options = {}) {
1432
+ const tabs = [];
1433
+ for (const tab of BUILTIN_PANEL_TABS) {
1434
+ if (tab.id === "artifacts" && options.showArtifacts === false) continue;
1435
+ if (options.omitBuiltins?.includes(tab.id)) continue;
1436
+ tabs.push(tab);
1437
+ }
1438
+ const seen = new Set(tabs.map((t) => t.id));
1439
+ for (const p of extensionPanels) {
1440
+ if (seen.has(p.id)) continue;
1441
+ seen.add(p.id);
1442
+ tabs.push({
1443
+ id: p.id,
1444
+ label: p.label,
1445
+ builtin: false,
1446
+ owner: p.owner,
1447
+ ...p.icon ? { icon: p.icon } : {}
1448
+ });
1449
+ }
1450
+ return tabs;
1451
+ }
1452
+ const INITIAL_PANEL_STATE = {
1453
+ open: false,
1454
+ activeId: null,
1455
+ userPicks: 0,
1456
+ proactiveAt: 0
1457
+ };
1458
+ /** Add a tab to the host's pinned strip (idempotent, keeps order). */
1459
+ function pinTab(pinned, id) {
1460
+ return pinned.includes(id) ? pinned : [...pinned, id];
1461
+ }
1462
+ /**
1463
+ * Apply a user reorder of the VISIBLE strip to the pinned list. `pinned` can
1464
+ * hold ids the catalog no longer shows (an extension disabled for this cwd);
1465
+ * they keep their slots, and the visible ids fill the remaining slots in the
1466
+ * new order, so re-enabling the extension brings its tab back where it was.
1467
+ * Ids in `visible` that aren't pinned are ignored. Returns `pinned` itself
1468
+ * when nothing moved.
1469
+ */
1470
+ function reorderTabs(pinned, visible) {
1471
+ const shown = new Set(visible.filter((id) => pinned.includes(id)));
1472
+ const order = visible.filter((id) => shown.has(id));
1473
+ let next = 0;
1474
+ const out = pinned.map((id) => shown.has(id) ? order[next++] : id);
1475
+ return out.every((id, i) => id === pinned[i]) ? pinned : out;
1476
+ }
1477
+ /**
1478
+ * Move one visible tab `delta` slots within the visible strip (keyboard
1479
+ * reorder), clamped at the ends. Hidden pinned ids keep their slots.
1480
+ */
1481
+ function moveTab(pinned, visible, id, delta) {
1482
+ const from = visible.indexOf(id);
1483
+ if (from < 0) return pinned;
1484
+ const to = Math.max(0, Math.min(visible.length - 1, from + delta));
1485
+ if (to === from) return pinned;
1486
+ const order = [...visible];
1487
+ order.splice(from, 1);
1488
+ order.splice(to, 0, id);
1489
+ return reorderTabs(pinned, order);
1490
+ }
1491
+ /** The user clicked a tab (or a keybinding cycled to it). Always honoured, always opens. */
1492
+ function pickTab(state, id) {
1493
+ return {
1494
+ ...state,
1495
+ open: true,
1496
+ activeId: id,
1497
+ userPicks: state.userPicks + 1
1498
+ };
1499
+ }
1500
+ function togglePanel(state) {
1501
+ return {
1502
+ ...state,
1503
+ open: !state.open
1504
+ };
1505
+ }
1506
+ function closePanel(state) {
1507
+ return {
1508
+ ...state,
1509
+ open: false
1510
+ };
1511
+ }
1512
+ /**
1513
+ * Agent / extension asked to show a tab. Honoured only until the user has
1514
+ * picked a tab themselves (a badge that flipped must never steal the tab);
1515
+ * otherwise the request is dropped and the state returned unchanged.
1516
+ */
1517
+ function openProactive(state, id) {
1518
+ if (state.userPicks !== state.proactiveAt) return state;
1519
+ return {
1520
+ ...state,
1521
+ open: true,
1522
+ activeId: id
1523
+ };
1524
+ }
1525
+ /**
1526
+ * The agent is blocked on the user (a plan to approve, a question to
1527
+ * answer): show the tab regardless of earlier picks. Costs at most a tab
1528
+ * switch, and nothing in that tab decides implicitly. Not a user pick, so
1529
+ * it does not lock out later proactive opens.
1530
+ */
1531
+ function openForAttention(state, id) {
1532
+ if (state.open && state.activeId === id) return state;
1533
+ return {
1534
+ ...state,
1535
+ open: true,
1536
+ activeId: id
1537
+ };
1538
+ }
1539
+ /**
1540
+ * Resolve the tab to render: the active one when it still exists, else the
1541
+ * first tab, else `null` (empty catalog). Keeps state stable when an
1542
+ * extension is disabled and its tab vanishes.
1543
+ */
1544
+ function resolveActiveTab(state, catalog) {
1545
+ if (catalog.length === 0) return null;
1546
+ return catalog.find((t) => t.id === state.activeId) ?? catalog[0];
1547
+ }
1548
+ /** Cycle to the next / previous tab (wraps). Counts as a user pick. */
1549
+ function cycleTab(state, catalog, direction) {
1550
+ if (catalog.length === 0) return state;
1551
+ const current = resolveActiveTab(state, catalog);
1552
+ const next = catalog[((current ? catalog.findIndex((t) => t.id === current.id) : 0) + direction + catalog.length) % catalog.length];
1553
+ return pickTab(state, next.id);
1554
+ }
1555
+ //#endregion
1556
+ //#region src/chat/prompt-text.ts
1557
+ function tidyPromptText(text, refs) {
1558
+ const trimmed = text.trimEnd();
1559
+ if (!/\n{3,}/.test(trimmed)) return {
1560
+ text: trimmed,
1561
+ refs: refs.filter((r) => r.end <= trimmed.length)
1562
+ };
1563
+ const map = Array.from({ length: trimmed.length + 1 });
1564
+ let out = "";
1565
+ for (let i = 0; i < trimmed.length;) {
1566
+ if (/^\n{3,}/.exec(trimmed.slice(i, i + 64)) && trimmed[i] === "\n") {
1567
+ let end = i;
1568
+ while (trimmed[end] === "\n") end++;
1569
+ for (let k = i; k < end; k++) map[k] = out.length + Math.min(k - i, 2);
1570
+ out += "\n\n";
1571
+ i = end;
1572
+ continue;
1573
+ }
1574
+ map[i] = out.length;
1575
+ out += trimmed[i];
1576
+ i++;
1577
+ }
1578
+ map[trimmed.length] = out.length;
1579
+ const remapped = refs.filter((r) => r.end <= trimmed.length).map((r) => ({
1580
+ ...r,
1581
+ start: map[r.start],
1582
+ end: map[r.end]
1583
+ }));
1584
+ return {
1585
+ text: out,
1586
+ refs: remapped
1587
+ };
1588
+ }
1589
+ //#endregion
1590
+ //#region src/chat/stream-failure.ts
1591
+ const FAILED = /(?:^|\n\n)\[✗ Streaming failed (before any output|mid-output)(?:: ([\s\S]*))?\.?\]\s*$/;
1592
+ const ABORTED = /(?:^|\n\n)\[⏹ Streaming was aborted\.\]\s*$/;
1593
+ /** Split a reply into what the model wrote and the stop marker at its end, if any. */
1594
+ function splitStreamFailure(text) {
1595
+ const aborted = ABORTED.exec(text);
1596
+ if (aborted) {
1597
+ const before = text.slice(0, aborted.index).trimEnd();
1598
+ return {
1599
+ text: before,
1600
+ failure: {
1601
+ message: "",
1602
+ midOutput: before !== "",
1603
+ aborted: true
1604
+ }
1605
+ };
1606
+ }
1607
+ const m = FAILED.exec(text);
1608
+ if (!m) return {
1609
+ text,
1610
+ failure: null
1611
+ };
1612
+ return {
1613
+ text: text.slice(0, m.index).trimEnd(),
1614
+ failure: {
1615
+ message: (m[2] ?? "").trim(),
1616
+ midOutput: m[1] === "mid-output",
1617
+ aborted: false
1618
+ }
1619
+ };
1620
+ }
1621
+ /** Cheap pre-check before splitting: the text may end in a stop marker. */
1622
+ function mayHaveStreamFailure(text) {
1623
+ return text.includes("[✗ Streaming failed") || text.includes("[⏹ Streaming was aborted");
1624
+ }
1625
+ /** What the "continue" action sends after a run that had done work. */
1626
+ const CONTINUE_PROMPT = "Continue from where you stopped.";
1627
+ //#endregion
1628
+ //#region src/chat/stream-rate.ts
1629
+ const EMPTY_RATE = {
1630
+ turnId: null,
1631
+ samples: [],
1632
+ estimated: null,
1633
+ rate: null
1634
+ };
1635
+ /** Trailing window the rate is measured over. */
1636
+ const WINDOW_MS = 3e3;
1637
+ /** Minimum span before a reading counts: shorter spans are one or two ticks of noise. */
1638
+ const MIN_SPAN_MS = 1200;
1639
+ /** Faster than any served model today (the fastest wafer-scale ones run ~3k): a reading above is a glitch. */
1640
+ const MAX_PLAUSIBLE_RATE = 6e3;
1641
+ /** Weight of a new reading in the smoothed rate. */
1642
+ const SMOOTHING = .35;
1643
+ function stepStreamRate(state, sample) {
1644
+ const point = {
1645
+ t: sample.elapsedMs,
1646
+ n: sample.outputTokens
1647
+ };
1648
+ if (state.turnId !== sample.turnId) return {
1649
+ turnId: sample.turnId,
1650
+ samples: [point],
1651
+ estimated: sample.estimated,
1652
+ rate: null
1653
+ };
1654
+ const last = state.samples[state.samples.length - 1];
1655
+ if (state.estimated === true && !sample.estimated || last !== void 0 && point.n < last.n || !last) return {
1656
+ ...state,
1657
+ samples: [point],
1658
+ estimated: sample.estimated
1659
+ };
1660
+ if (point.t <= last.t) return state;
1661
+ const kept = [...state.samples, point];
1662
+ let start = 0;
1663
+ while (start < kept.length - 2 && kept[start + 1].t <= point.t - WINDOW_MS) start++;
1664
+ const samples = kept.slice(start);
1665
+ const base = samples[0];
1666
+ const span = point.t - base.t;
1667
+ let rate = state.rate;
1668
+ if (span >= MIN_SPAN_MS) {
1669
+ const reading = (point.n - base.n) / span * 1e3;
1670
+ if (reading >= 0 && reading <= 6e3) rate = rate === null ? reading : rate + SMOOTHING * (reading - rate);
1671
+ }
1672
+ return {
1673
+ turnId: state.turnId,
1674
+ samples,
1675
+ estimated: sample.estimated,
1676
+ rate
1677
+ };
1678
+ }
1679
+ /** Whole tokens per second for display, or `null` when there is no honest reading. */
1680
+ function streamRateValue(state) {
1681
+ if (state.rate === null) return null;
1682
+ const rounded = Math.round(state.rate);
1683
+ return rounded > 0 ? rounded : null;
1684
+ }
1685
+ //#endregion
1686
+ //#region src/chat/todo-edits.ts
1687
+ /**
1688
+ * Prefix for ids minted here, distinguishing user-authored rows from the
1689
+ * model's own ids in a session file. Purely diagnostic — nothing branches on
1690
+ * it — but it makes "who added this" answerable after the fact.
1691
+ */
1692
+ const USER_TODO_ID_PREFIX = "user-";
1693
+ /**
1694
+ * Mint an id that cannot collide with an existing row.
1695
+ *
1696
+ * Counter-based rather than random: a stable, readable id keeps session diffs
1697
+ * legible, and the collision check makes it safe against ids the model already
1698
+ * used (including previous `user-*` ones after a reload).
1699
+ */
1700
+ function nextTodoId(items) {
1701
+ const taken = new Set(items.map((i) => i.id));
1702
+ for (let n = 1;; n++) {
1703
+ const id = `${USER_TODO_ID_PREFIX}${n}`;
1704
+ if (!taken.has(id)) return id;
1705
+ }
1706
+ }
1707
+ /**
1708
+ * Append a todo. Returns the list unchanged when `content` is blank — an empty
1709
+ * row is never what the user meant, and rejecting it here means every caller
1710
+ * (keyboard, click, future GUI) gets the same guard.
1711
+ */
1712
+ function addTodo(items, content) {
1713
+ const trimmed = content.trim();
1714
+ if (!trimmed) return [...items];
1715
+ return [...items, {
1716
+ id: nextTodoId(items),
1717
+ content: trimmed,
1718
+ status: "pending"
1719
+ }];
1720
+ }
1721
+ /**
1722
+ * Replace one todo's text. Blank content is rejected (see {@link addTodo}); an
1723
+ * unknown id is a no-op rather than an error, because the list can be replaced
1724
+ * by a `todowrite` between render and keypress.
1725
+ */
1726
+ function editTodo(items, id, content) {
1727
+ const trimmed = content.trim();
1728
+ if (!trimmed) return [...items];
1729
+ return items.map((item) => item.id === id ? {
1730
+ ...item,
1731
+ content: trimmed
1732
+ } : item);
1733
+ }
1734
+ /** Drop one todo. Unknown id is a no-op, same rationale as {@link editTodo}. */
1735
+ function removeTodo(items, id) {
1736
+ return items.filter((item) => item.id !== id);
1737
+ }
1738
+ /** Drop everything. Separate from `removeTodo` so the caller's intent is explicit at the call site. */
1739
+ function clearTodos() {
1740
+ return [];
1741
+ }
1742
+ /**
1743
+ * Advance a todo's status one step: pending → in_progress → completed →
1744
+ * pending. `cancelled` also lands on `pending`, so a dropped item can be
1745
+ * revived without a dedicated key.
1746
+ *
1747
+ * Cycling INTO `in_progress` demotes any other in-progress row to `pending`:
1748
+ * the list's whole purpose is "what is being worked on right now", and two
1749
+ * live rows make that unanswerable — `TodoIndicator`'s successor
1750
+ * (`todoProgressLabel`) reads the first one and would silently ignore the rest.
1751
+ */
1752
+ function cycleTodoStatus(items, id) {
1753
+ const target = items.find((item) => item.id === id);
1754
+ if (!target) return [...items];
1755
+ const next = nextStatus(target.status);
1756
+ return items.map((item) => {
1757
+ if (item.id === id) return {
1758
+ ...item,
1759
+ status: next
1760
+ };
1761
+ if (next === "in_progress" && item.status === "in_progress") return {
1762
+ ...item,
1763
+ status: "pending"
1764
+ };
1765
+ return item;
1766
+ });
1767
+ }
1768
+ function nextStatus(status) {
1769
+ switch (status) {
1770
+ case "pending": return "in_progress";
1771
+ case "in_progress": return "completed";
1772
+ default: return "pending";
1773
+ }
1774
+ }
1775
+ /**
1776
+ * Steering text for a "retry this todo" request.
1777
+ *
1778
+ * Quotes the content so a model reading it can match the row verbatim against
1779
+ * its own list, and names the status because "retry" means different things
1780
+ * for a row it believes is `completed` versus one it abandoned as `cancelled`.
1781
+ * Shared by the TUI and GUI modals so both surfaces steer with byte-identical
1782
+ * text; pinned by a test rather than living inside a JSX callback.
1783
+ */
1784
+ function buildTodoRetryMessage(item) {
1785
+ return `Retry this todo (currently ${item.status}): "${item.content}"`;
1786
+ }
1787
+ //#endregion
1788
+ export { validateMcpForm as $, openProactive as A, createPaletteRanker as B, TERMINAL_PANEL_ID as C, parseSessionKey as Ct, cycleTab as D, UnknownHostIdError as Dt, closePanel as E, HostRemovedError as Et, togglePanel as F, normalizePaletteRunResult as G, groupPaletteRows as H, PALETTE_LIMITS as I, NOTICE_GLYPH as J, pushPaletteRecent as K, PALETTE_RECENTS_MAX as L, pinTab as M, reorderTabs as N, moveTab as O, createHostIndex as Ot, resolveActiveTab as P, splitCommandLine as Q, PALETTE_RECENT_GROUP as R, INITIAL_PANEL_STATE as S, SESSION_KEY_SEPARATOR as St, buildPanelCatalog as T, AmbiguousHostIdError as Tt, isPaletteContext as U, fuzzyMatch as V, normalizePaletteItems as W, MCP_TRANSPORTS as X, modelDisplayName as Y, parseKeyValueLines as Z, BROWSER_PANEL_ID as _, buildVisibleAuthRows as _t, editTodo as a, machineDirListing as at, FILES_PANEL_ID as b, parentProviderKey as bt, EMPTY_RATE as c, INSTRUCTION_PREVIEW_LINE_CAP as ct, streamRateValue as d, instructionPreviewLines as dt, MACHINE_LINK_STATE_TEXT as et, CONTINUE_PROMPT as f, commandGist as ft, ARTIFACTS_PANEL_ID as g, toolPhrase as gt, tidyPromptText as h, toolOverview as ht, cycleTodoStatus as i, machineBlockedReason as it, pickTab as j, openForAttention as k, MAX_PLAUSIBLE_RATE as l, buildVisibleInstructionRows as lt, splitStreamFailure as m, summarizeTools as mt, buildTodoRetryMessage as n, environmentLabel as nt, nextTodoId as o, machineWaiting as ot, mayHaveStreamFailure as p, messagePreview as pt, rankPaletteItems as q, clearTodos as r, lastSeenText as rt, removeTodo as s, orderByMachine as st, addTodo as t, MACHINE_NOTICE_LABEL as tt, stepStreamRate as u, instructionOriginBadge as ut, BUILTIN_PANEL_TABS as v, indexOfProviderRow as vt, TODOS_PANEL_ID as w, sessionKey as wt, INBOX_PANEL_ID as x, selectableSources as xt, DIFFS_PANEL_ID as y, isExpandableProvider as yt, PALETTE_RESULT_MAX as z };
1789
+
1790
+ //# sourceMappingURL=todo-edits-Dn251YUP.js.map