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,4941 @@
1
+ import { Mn as createExtensionBus, Nn as serializedPayloadBytes, Tt as createExtensionHostRuntimeBridge, at as ExtensionInstallError, bt as filterRegistryPlugins, dt as removeInstalledExtension, ft as updateInstalledExtension, mt as discoverAndBuildExtensionRegistry, pt as createExtensionHud, st as installExtensionFromSpec } from "./agent-DSLNmBp0.js";
2
+ import { d as abortableInstallIo, o as installSkillFromSpec } from "./skills-DKqZJm4X.js";
3
+ import { l as normalizeArtifactInput } from "./artifacts-CV2hUKnL.js";
4
+ import { t as projectUserPaths } from "./project-user-paths-BSgtrRxl.js";
5
+ import { i as writeJsonFileAtomic, n as writeFileAtomic, t as OWNER_ONLY } from "./atomic-write-Ijgf2Mhc.js";
6
+ import { c as setExtensionSessionState, o as loadSession, r as deleteExtensionSessionState } from "./session-xh9JXu6i.js";
7
+ import { a as loginMcpServer, c as resolveInlineSuggestion, s as createPathAutocompleteProvider } from "./src-BRMYRJib.js";
8
+ import { a as canSignInRemotely } from "./xai-CvJTeLrI.js";
9
+ import { a as errorResponse, r as createJsonRpcConnection } from "./json-rpc--HXQti1E.js";
10
+ import { M as selectActiveTodos, N as setTodosForRun, c as sessionModelFallbacks, i as describeForkFailure } from "./session-factory-BUGY5P40.js";
11
+ import { $t as MachineErrorCode, X as enabledMcpsWith, Yt as remoteHostsGate, Z as enabledMcpsWithout, in as toMachineName } from "./tool-formatters-IQFDrdrs.js";
12
+ import { c as isMainSession } from "./orchestration-BCGmB8Ps.js";
13
+ import { a as KNOWN_TOKEN, c as looksLikeSecret, o as isSecretName, s as isSecretPathSegment, u as DEFAULT_SETTINGS } from "./url-D8HCl65b.js";
14
+ import { $ as getSessionMeta, B as parseMcpsFile, Ot as readProjects, P as userSystemMdPath, Q as getSessionHeader, R as discoverProjectMcps, X as deriveSessionTitle, Z as eventsFromTurns, _ as enforceSettingsInvariants, dt as sumRunCosts, et as headerFromData, jt as writeProjects, kt as safelistProjectKey, lt as sessionEnvironmentSummary, mt as toolResultText, nt as listSessionMeta, ot as replayTurns, p as SETTINGS_TOGGLES, s as isWindowOnlySetting, tt as lastContextSizeFromTurns } from "./registry-D6ShBWJU.js";
15
+ import { Jt as createChatHost, Qt as createFileMcpCredentialStore, Tr as createMemoryApprovalsStore, X as createExtensionMcpHost, Xn as insideDeclaredRoots, Yn as machineDiscoveryCwd, _n as pinnedSkillsValue, _t as removeMcpServer, bn as toTodosSnapshot, fr as scopeCwd, gn as PINNED_SKILLS_META_KEY, gt as mcpsWritePath, hn as normalizeLaunchOverrides, j as runOAuthLogin, mr as discoverProjectPlugins, nn as applyEnvironmentSettingsChange, ot as resolveConfig, pn as isInside, pr as buildPluginContributions, vn as readPinnedSkills, vt as upsertMcpServer, zn as discoverProjectSkills } from "./chat-JPJJ8uu5.js";
16
+ import { c as searchSessionHistoryIndexAsync, n as buildSessionSearchIndexAsync, u as searchSessionIndexAsync } from "./history-search-CcYzF8-0.js";
17
+ import { c as removeProviderCredential, f as setProviderCredential, g as BUILTIN_PROVIDERS, l as removeStoredCredential, n as credentialsPath, s as readProviderCredentials, t as applyApiKeyEnv } from "./credentials-JttEuRON.js";
18
+ import { r as detectAuth } from "./cli-shared-C-YCaglL.js";
19
+ import { c as settingField, h as METHOD_NOT_FOUND, n as SYNC_CATEGORIES, o as SYSTEM_MD_FIELD } from "./types-CH5XpIHw.js";
20
+ import { C as IPC_BUILD_ID, E as ensureDaemonRunning, O as probeSocket, S as compareVersions, a as createSignalListeners, c as IpcErrorCode, f as REDACTED, i as IpcVersionMismatchError, l as LOAD_EVENTS_MAX_PAGE_TURNS, n as hashValue, o as openIpcClient, p as SESSION_TURNS_MAX_PAGE, r as isRecord, t as isSyncableSetting, x as ZIDANE_VERSION } from "./settings-keys-Ckf29NeM.js";
21
+ import { Buffer } from "node:buffer";
22
+ import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
23
+ import { basename, delimiter, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
24
+ import { homedir, hostname } from "node:os";
25
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
26
+ import { connect } from "node:net";
27
+ import process$1 from "node:process";
28
+ //#region src/ipc/transports/paced-writer.ts
29
+ /** Largest slice handed to the socket at once. */
30
+ const SLICE_BYTES = 65536;
31
+ function createPacedWriter(stream) {
32
+ const queue = [];
33
+ let queuedBytes = 0;
34
+ let waiting = false;
35
+ const pump = () => {
36
+ waiting = false;
37
+ while (queue.length > 0) {
38
+ if (stream.destroyed || stream.writableEnded) {
39
+ queue.length = 0;
40
+ queuedBytes = 0;
41
+ return;
42
+ }
43
+ const head = queue[0];
44
+ const slice = head.length > SLICE_BYTES ? head.subarray(0, SLICE_BYTES) : head;
45
+ if (slice === head) queue.shift();
46
+ else queue[0] = head.subarray(SLICE_BYTES);
47
+ queuedBytes -= slice.length;
48
+ if (!stream.write(slice)) {
49
+ waiting = true;
50
+ stream.once("drain", pump);
51
+ return;
52
+ }
53
+ }
54
+ };
55
+ return {
56
+ write(text) {
57
+ const bytes = Buffer.from(text);
58
+ queue.push(bytes);
59
+ queuedBytes += bytes.length;
60
+ if (!waiting) pump();
61
+ },
62
+ buffered: () => queuedBytes + stream.writableLength
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/ipc/client.ts
67
+ async function connectIpcClient(options) {
68
+ const socket = await new Promise((resolvePromise, reject) => {
69
+ const s = connect(options.socketPath);
70
+ s.once("connect", () => {
71
+ s.off("error", reject);
72
+ resolvePromise(s);
73
+ });
74
+ s.once("error", reject);
75
+ });
76
+ const signals = createSignalListeners();
77
+ let closed = false;
78
+ const writer = createPacedWriter(socket);
79
+ const connection = createJsonRpcConnection({
80
+ input: socket,
81
+ output: socket,
82
+ send: (json) => writer.write(`${json}\n`),
83
+ onRequest: (message) => errorResponse(message.id, METHOD_NOT_FOUND, "Clients do not accept requests."),
84
+ onNotification: signals.onNotification
85
+ });
86
+ connection.start();
87
+ const close = () => {
88
+ if (closed) return;
89
+ closed = true;
90
+ connection.close();
91
+ socket.destroy();
92
+ };
93
+ socket.on("close", () => {
94
+ if (closed) return;
95
+ closed = true;
96
+ connection.close();
97
+ options.onClose?.();
98
+ });
99
+ socket.on("error", () => {});
100
+ const passive = options.drainOnMismatch === false;
101
+ return openIpcClient({
102
+ rpc: connection,
103
+ close,
104
+ abort: () => socket.destroy(),
105
+ where: options.socketPath
106
+ }, signals.listeners, {
107
+ ...options.appVersion ? { appVersion: options.appVersion } : {},
108
+ drain: passive ? "stale-only" : "always",
109
+ buildFence: !passive
110
+ });
111
+ }
112
+ /**
113
+ * One-call convenience for windows: make sure a daemon is up (spawning if
114
+ * needed), connect, and — when the running daemon speaks an older protocol
115
+ * — drain it and spawn a fresh one before retrying once.
116
+ */
117
+ async function connectOrSpawnDaemon(options = {}) {
118
+ const socketPath = await ensureDaemonRunning(options);
119
+ try {
120
+ return await connectIpcClient({
121
+ ...options,
122
+ socketPath
123
+ });
124
+ } catch (err) {
125
+ if (!(err instanceof IpcVersionMismatchError)) throw err;
126
+ const drainDeadline = Date.now() + 2e3;
127
+ while (await probeSocket(socketPath)) {
128
+ if (Date.now() > drainDeadline) break;
129
+ await new Promise((r) => setTimeout(r, 100));
130
+ }
131
+ const retryPath = await ensureDaemonRunning(options);
132
+ return connectIpcClient({
133
+ ...options,
134
+ socketPath: retryPath
135
+ });
136
+ }
137
+ }
138
+ //#endregion
139
+ //#region src/ipc/daemon/environment.ts
140
+ /**
141
+ * File-backed pending-approvals persistence (`pending-approvals.json` next
142
+ * to `state.json`). Small, rewritten atomically enough for its purpose —
143
+ * the queue holds at most a handful of rows.
144
+ */
145
+ function createFileApprovalsStore(path) {
146
+ function read() {
147
+ if (!existsSync(path)) return [];
148
+ try {
149
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
150
+ return Array.isArray(parsed) ? parsed : [];
151
+ } catch {
152
+ return [];
153
+ }
154
+ }
155
+ function write(rows) {
156
+ if (rows.length === 0) {
157
+ try {
158
+ unlinkSync(path);
159
+ } catch {}
160
+ return;
161
+ }
162
+ writeJsonFileAtomic(path, rows, OWNER_ONLY);
163
+ }
164
+ return {
165
+ insert(request) {
166
+ const rows = read().filter((r) => r.id !== request.id);
167
+ rows.push(request);
168
+ write(rows);
169
+ },
170
+ delete(id) {
171
+ write(read().filter((r) => r.id !== id));
172
+ },
173
+ clear() {
174
+ write([]);
175
+ },
176
+ list() {
177
+ return read();
178
+ }
179
+ };
180
+ }
181
+ /** The daemon's environment: its own config, per-cwd extension discovery, multi-process. */
182
+ function createDaemonEnvironment(options) {
183
+ return createHostEnvironment(options);
184
+ }
185
+ /**
186
+ * Disk-backed host environment: the daemon's, or (with `shared`) a host
187
+ * sharing the window's process and setup.
188
+ */
189
+ async function createHostEnvironment(options) {
190
+ const { shared } = options;
191
+ const config = shared?.config ?? await resolveOwnConfig(options.cwd);
192
+ const store = config.store;
193
+ if (!shared && config.paths.projectDir) process.stderr.write(`[zidane-daemon] warning: project-DB mode is on, so this daemon uses ${config.paths.db}. Windows opened in other projects will share this project's sessions. Run a per-project daemon (ZIDANE_DAEMON_SOCKET) to isolate them.\n`);
194
+ const readSettings = () => ({
195
+ ...DEFAULT_SETTINGS,
196
+ ...config.initialSettings,
197
+ ...config.stateStore.load().settings ?? {}
198
+ });
199
+ /**
200
+ * Fields the invariant forced (only ever `false`), so the stored file records
201
+ * them too — otherwise a later read of `state.settings` would resurrect the
202
+ * value the invariant just rejected.
203
+ */
204
+ const pickInvariantFields = (merged, patch) => {
205
+ const out = {};
206
+ for (const row of SETTINGS_TOGGLES) if (row.requires && merged[row.key] === false && patch[row.key] !== false) out[row.key] = false;
207
+ return out;
208
+ };
209
+ const writeSettings = (patch) => {
210
+ const state = config.stateStore.load();
211
+ const merged = enforceSettingsInvariants({
212
+ ...DEFAULT_SETTINGS,
213
+ ...config.initialSettings,
214
+ ...state.settings ?? {},
215
+ ...patch
216
+ });
217
+ const next = {
218
+ ...state.settings ?? {},
219
+ ...patch,
220
+ ...pickInvariantFields(merged, patch)
221
+ };
222
+ config.stateStore.save({
223
+ ...state,
224
+ settings: next
225
+ });
226
+ return merged;
227
+ };
228
+ const userDataDir = () => config.paths.userDir;
229
+ const applyAuthEnv = () => {
230
+ const dir = userDataDir();
231
+ process.env.ZIDANE_CREDENTIALS_PATH = credentialsPath(dir);
232
+ applyApiKeyEnv(dir, shared ? config.providers : BUILTIN_PROVIDERS);
233
+ };
234
+ const base = {
235
+ multiProcess: shared ? shared.multiProcess === true : true,
236
+ broadcast: options.broadcast,
237
+ store,
238
+ readSettings,
239
+ writeSettings,
240
+ userDataDir,
241
+ applyAuthEnv,
242
+ mcpCredentialStore: () => createFileMcpCredentialStore(userDataDir()),
243
+ approvalsPersistence: shared ? createMemoryApprovalsStore() : createFileApprovalsStore(join(dirname(config.paths.state), "pending-approvals.json")),
244
+ ...options.debugStream !== void 0 ? { debugStream: options.debugStream } : {},
245
+ config
246
+ };
247
+ if (shared) {
248
+ const { resolve, invalidate, forget } = shared.runtimeConfig;
249
+ return {
250
+ ...base,
251
+ cacheDir: () => config.paths.cacheDir,
252
+ runtimeConfig: resolve,
253
+ ...shared.hostRuntime ? { hostRuntime: shared.hostRuntime } : {},
254
+ ...shared.extensionBus ? { extensionBus: shared.extensionBus } : {},
255
+ ...shared.instrumentAgent ? { instrumentAgent: shared.instrumentAgent } : {},
256
+ ...shared.terminals !== void 0 ? { terminals: shared.terminals } : {},
257
+ invalidateRuntime: () => invalidate?.(),
258
+ forgetRuntime: () => forget?.() ?? (() => void 0)
259
+ };
260
+ }
261
+ const hud = createExtensionHud();
262
+ hud.subscribe(() => {
263
+ const state = hud.getState();
264
+ options.broadcast({
265
+ kind: "hud:state",
266
+ statuses: Object.fromEntries(Object.entries(state.statuses).map(([name, s]) => [name, {
267
+ text: s.text,
268
+ loader: s.loader
269
+ }])),
270
+ banners: Object.fromEntries(Object.entries(state.banners).map(([name, lines]) => [name, [...lines]]))
271
+ });
272
+ });
273
+ hud.onNotify((n) => {
274
+ options.broadcast({
275
+ kind: "hud:notify",
276
+ extension: n.extension,
277
+ text: n.text,
278
+ tone: n.tone
279
+ });
280
+ });
281
+ const extensionBus = createExtensionBus();
282
+ extensionBus.onEmit((envelope) => {
283
+ options.broadcast({
284
+ kind: "extension:signal",
285
+ ...envelope
286
+ });
287
+ });
288
+ const RUNTIME_CACHE_MAX = 16;
289
+ const runtimeCache = /* @__PURE__ */ new Map();
290
+ const hostRuntime = createExtensionHostRuntimeBridge();
291
+ const mcpHost = createExtensionMcpHost({
292
+ prefix: config.prefix,
293
+ readSettings,
294
+ credentialStore: () => createFileMcpCredentialStore(userDataDir())
295
+ });
296
+ /**
297
+ * Best-effort release of an evicted entry's extension registry
298
+ * (teardown hooks, HUD writers — `registry.dispose()` is idempotent).
299
+ * Fire-and-forget: eviction must never block or throw on a broken
300
+ * extension teardown, and a rejected resolve has nothing to release.
301
+ */
302
+ const disposeRuntime = (promise) => {
303
+ promise.then((config) => config.extensions.dispose()).catch(() => void 0);
304
+ };
305
+ const runtimeConfig = (cwd) => {
306
+ const settings = readSettings();
307
+ const key = [
308
+ cwd,
309
+ JSON.stringify(settings.enabledExtensions ?? null),
310
+ String(settings.loadProjectExtensions === true)
311
+ ].join("\0");
312
+ const existing = runtimeCache.get(key);
313
+ if (existing) {
314
+ runtimeCache.delete(key);
315
+ runtimeCache.set(key, existing);
316
+ return existing;
317
+ }
318
+ const promise = (async () => {
319
+ const extensionRegistry = await discoverAndBuildExtensionRegistry({
320
+ cwd,
321
+ hud,
322
+ bus: extensionBus,
323
+ hostRuntime,
324
+ mcp: mcpHost,
325
+ prefix: config.prefix,
326
+ ...settings.enabledExtensions !== void 0 ? { enabledAllowlist: [...settings.enabledExtensions] } : {},
327
+ trustProjectExtensions: settings.loadProjectExtensions === true,
328
+ readSettings
329
+ });
330
+ return resolveConfig({
331
+ cwd,
332
+ extensionRegistry,
333
+ store: () => store
334
+ });
335
+ })();
336
+ runtimeCache.set(key, promise);
337
+ while (runtimeCache.size > RUNTIME_CACHE_MAX) {
338
+ const oldest = runtimeCache.keys().next().value;
339
+ if (oldest === void 0) break;
340
+ const evicted = runtimeCache.get(oldest);
341
+ runtimeCache.delete(oldest);
342
+ if (evicted) disposeRuntime(evicted);
343
+ }
344
+ promise.catch(() => {
345
+ if (runtimeCache.get(key) === promise) runtimeCache.delete(key);
346
+ });
347
+ return promise;
348
+ };
349
+ return {
350
+ ...base,
351
+ runtimeConfig,
352
+ hostRuntime,
353
+ invalidateRuntime: () => {
354
+ for (const entry of runtimeCache.values()) disposeRuntime(entry);
355
+ runtimeCache.clear();
356
+ },
357
+ forgetRuntime: () => {
358
+ const dropped = [...runtimeCache.values()];
359
+ runtimeCache.clear();
360
+ return () => {
361
+ for (const entry of dropped) disposeRuntime(entry);
362
+ };
363
+ },
364
+ extensionBus
365
+ };
366
+ }
367
+ /** The daemon's own boot config: paths + store + state, from `cwd`. */
368
+ async function resolveOwnConfig(cwd) {
369
+ const { createTuiStore } = await import("./session/sqlite.js");
370
+ return resolveConfig({
371
+ ...cwd !== void 0 ? { cwd } : {},
372
+ store: (resolved) => createTuiStore(resolved.db)
373
+ });
374
+ }
375
+ //#endregion
376
+ //#region src/ipc/daemon/handlers/params.ts
377
+ /**
378
+ * Shape checks for request params: they arrive as untrusted JSON (the
379
+ * `IpcMethods` types are not a runtime guarantee). The dispatcher runs
380
+ * {@link checkParams} on every request and notification before its handler;
381
+ * handlers use the field checks for what they require. A failed check is an
382
+ * {@link InvalidParamsError}, answered as JSON-RPC "invalid params".
383
+ */
384
+ var InvalidParamsError = class extends Error {
385
+ constructor(message) {
386
+ super(message);
387
+ this.name = "InvalidParamsError";
388
+ }
389
+ };
390
+ /** Longest session id accepted (minted ids are under 64 characters). */
391
+ const MAX_SESSION_ID_LENGTH = 1024;
392
+ /**
393
+ * A session id: a non-empty string that is one plain path segment. Ids name
394
+ * directories on disk (`<data dir>/sessions/<id>/`, removed recursively on
395
+ * delete), so `..`, separators and NUL are refused.
396
+ */
397
+ function checkSessionId(value, name = "sessionId") {
398
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_SESSION_ID_LENGTH) throw new InvalidParamsError(`${name} must be a non-empty string of at most ${MAX_SESSION_ID_LENGTH} characters.`);
399
+ if (value === "." || value === ".." || /[/\\\0]/.test(value)) throw new InvalidParamsError(`${name} must not be a path (no "/", "\\", "." or "..").`);
400
+ return value;
401
+ }
402
+ /** The required `sessionId` of `params`. */
403
+ function sessionIdOf(params) {
404
+ return checkSessionId(params?.sessionId);
405
+ }
406
+ /** An optional non-negative integer; `undefined` when absent. */
407
+ function nonNegativeInt(value, name) {
408
+ if (value === void 0) return void 0;
409
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new InvalidParamsError(`${name} must be a non-negative integer.`);
410
+ return value;
411
+ }
412
+ /** An optional string; `undefined` when absent. */
413
+ function optionalString(value, name) {
414
+ if (value !== void 0 && typeof value !== "string") throw new InvalidParamsError(`${name} must be a string.`);
415
+ return value;
416
+ }
417
+ /**
418
+ * What every method's params must be: absent, `null`, or an object (never an
419
+ * array or a scalar), whose `sessionId` / `sessionIds`, when present, name
420
+ * sessions ({@link checkSessionId}).
421
+ */
422
+ function checkParams(params) {
423
+ if (params === void 0 || params === null) return;
424
+ if (typeof params !== "object" || Array.isArray(params)) throw new InvalidParamsError("params must be an object.");
425
+ const { sessionId, sessionIds } = params;
426
+ if (sessionId !== void 0) checkSessionId(sessionId);
427
+ if (sessionIds !== void 0) {
428
+ if (!Array.isArray(sessionIds)) throw new InvalidParamsError("sessionIds must be an array.");
429
+ for (const id of sessionIds) checkSessionId(id, "sessionIds[]");
430
+ }
431
+ }
432
+ //#endregion
433
+ //#region src/ipc/daemon/handlers/machine-links.ts
434
+ const GATE_POLL_MS = 2e3;
435
+ const EMPTY = {
436
+ shares: [],
437
+ uses: []
438
+ };
439
+ /** A server's: no links, nothing loaded or resumed. */
440
+ function serverLinkHandlers() {
441
+ const refuse = () => {
442
+ throw new Error("A zidane-server has no machine links: share a computer with it from that computer (zidane machine share, or its Hosts page).");
443
+ };
444
+ return {
445
+ "machines.share.start": refuse,
446
+ "machines.share.stop": refuse,
447
+ "machines.useServer": refuse,
448
+ "machines.links.status": refuse
449
+ };
450
+ }
451
+ function createMachineLinkHandlers(options) {
452
+ if (options.server) return serverLinkHandlers();
453
+ const { host, env, broadcast } = options;
454
+ let links;
455
+ let gateTimer;
456
+ const gateOn = () => remoteHostsGate.isEnabled(env.readSettings());
457
+ const stop = () => {
458
+ const current = links;
459
+ links = void 0;
460
+ clearInterval(gateTimer);
461
+ gateTimer = void 0;
462
+ current?.then((l) => l.close()).catch(() => void 0);
463
+ broadcast({
464
+ kind: "machine:links",
465
+ links: EMPTY
466
+ });
467
+ };
468
+ const load = () => {
469
+ links ??= import("./links-j8rED1sR.js").then(({ startMachineLinks }) => startMachineLinks({
470
+ dataDir: env.userDataDir(),
471
+ machines: host.machines,
472
+ hostId: options.hostId,
473
+ appVersion: options.appVersion,
474
+ settings: () => env.readSettings(),
475
+ onChange: (status) => broadcast({
476
+ kind: "machine:links",
477
+ links: status
478
+ }),
479
+ ...options.pacing
480
+ }));
481
+ gateTimer ??= setInterval(() => {
482
+ if (!gateOn()) stop();
483
+ }, options.pacing?.pollMs ?? GATE_POLL_MS);
484
+ gateTimer.unref?.();
485
+ return links;
486
+ };
487
+ const required = () => {
488
+ if (!gateOn()) throw new Error(`Remote hosts are off${remoteHostsGate.isKilled() ? ` (${remoteHostsGate.killSwitchEnv}=1)` : ""}: machines are part of them.`);
489
+ return load();
490
+ };
491
+ const str = (value, name) => {
492
+ if (typeof value !== "string" || value === "") throw new InvalidParamsError(`"${name}" must be a non-empty string.`);
493
+ return value;
494
+ };
495
+ if (gateOn() && existsSync(join(env.userDataDir(), "machine-links.json"))) load().catch(() => void 0);
496
+ host.machines.onClose(() => {
497
+ const current = links;
498
+ links = void 0;
499
+ clearInterval(gateTimer);
500
+ current?.then((l) => l.close()).catch(() => void 0);
501
+ });
502
+ return {
503
+ "machines.share.start": async (p) => {
504
+ if (!Array.isArray(p.roots)) throw new InvalidParamsError("\"roots\" must be a list of folders.");
505
+ if (host.workspace.confined) for (const root of p.roots) host.workspace.resolveFolder(String(root));
506
+ return (await required()).startShare({
507
+ hostId: str(p.hostId, "hostId"),
508
+ name: str(p.name, "name"),
509
+ roots: p.roots,
510
+ ...typeof p.token === "string" ? { token: p.token } : {}
511
+ });
512
+ },
513
+ "machines.share.stop": async (p) => (await required()).stopShare(str(p.hostId, "hostId")),
514
+ "machines.useServer": async (p) => {
515
+ if (typeof p.enabled !== "boolean") throw new InvalidParamsError("\"enabled\" must be a boolean.");
516
+ return (await required()).setUse(str(p.hostId, "hostId"), p.enabled);
517
+ },
518
+ "machines.links.status": async () => gateOn() ? (await load()).status() : EMPTY
519
+ };
520
+ }
521
+ //#endregion
522
+ //#region src/ipc/daemon/handlers/machines.ts
523
+ /** Folders listed per call at most. */
524
+ const MAX_DIRS = 2e3;
525
+ function createMachineHandlers({ host, broadcast }) {
526
+ return {
527
+ "machines.list": () => host.machines.list(),
528
+ "machines.forget": (p) => {
529
+ if (typeof p.machineId !== "string") throw new InvalidParamsError("machines.forget: \"machineId\" must be a string.");
530
+ const machine = host.machines.get(p.machineId);
531
+ const forgotten = host.machines.forget(p.machineId);
532
+ if (forgotten && machine) broadcast({
533
+ kind: "machine:status",
534
+ machineId: machine.id,
535
+ name: machine.name,
536
+ status: "offline"
537
+ });
538
+ return { forgotten };
539
+ },
540
+ "machines.listDirs": async (p) => {
541
+ if (typeof p.machineId !== "string") throw new InvalidParamsError("machines.listDirs: \"machineId\" must be a string.");
542
+ const machine = host.machines.get(p.machineId);
543
+ const link = host.machines.link(p.machineId);
544
+ if (!machine || !link) throw new Error(`Unknown machine "${p.machineId}".`);
545
+ const roots = machine.roots.map((root) => root.path);
546
+ const asked = p.path === void 0 || p.path === "" ? roots[0] : p.path;
547
+ if (typeof asked !== "string" || !insideDeclaredRoots(machine.roots, asked)) throw new Error(`${String(asked)} is outside ${machine.name}'s roots.`);
548
+ const path = posix.normalize(asked);
549
+ const root = roots.find((r) => insideDeclaredRoots([{ path: r }], path));
550
+ const { entries } = await link.call("fs.list", {
551
+ handle: {
552
+ id: "host-list-dirs",
553
+ cwd: root
554
+ },
555
+ path
556
+ }, { wait: false });
557
+ return {
558
+ path,
559
+ dirs: entries.map((entry) => typeof entry === "string" ? entry : entry.path).filter((name) => name.endsWith("/")).map((name) => posix.join(path, name.slice(0, -1))).sort().slice(0, MAX_DIRS),
560
+ roots
561
+ };
562
+ }
563
+ };
564
+ }
565
+ //#endregion
566
+ //#region src/ipc/daemon/handlers/transcript-page.ts
567
+ /**
568
+ * A run longer than this many pages is split (by `limit`, so the client's
569
+ * halving retry on a reply too large still converges).
570
+ */
571
+ const WHOLE_RUN_PAGES = 5;
572
+ function isPagedLoad(p) {
573
+ return p?.before !== void 0 || p?.limit !== void 0;
574
+ }
575
+ /** Per turn: does a run start there (a parent-run user turn carrying no tool result)? */
576
+ function exchangeBoundaries(turns, runs) {
577
+ const childRuns = new Set(runs.filter((run) => (run.depth ?? 0) > 0 || run.parentRunId !== void 0).map((run) => run.id));
578
+ return turns.map((turn) => turn.role === "user" && !(turn.runId !== void 0 && childRuns.has(turn.runId)) && !turn.content.some((block) => block.type === "tool_result"));
579
+ }
580
+ /** Per turn: can a page start there without leaving a tool call's result behind (not a tool-result turn)? */
581
+ function turnCutPoints(turns) {
582
+ return turns.map((turn) => !(turn.role === "user" && turn.content.some((block) => block.type === "tool_result")));
583
+ }
584
+ /**
585
+ * `[from, to)` of the page, and the cursor of the one before it, over the
586
+ * turns' {@link exchangeBoundaries} (run starts) and {@link turnCutPoints}
587
+ * (where a run too long to send whole may be split; run starts when absent).
588
+ */
589
+ function transcriptPage(boundaries, paging, cuts = boundaries) {
590
+ const before = nonNegativeInt(paging.before, "before");
591
+ const limit = Math.min(Math.max(nonNegativeInt(paging.limit, "limit") ?? 200, 1), LOAD_EVENTS_MAX_PAGE_TURNS);
592
+ const to = Math.min(before ?? boundaries.length, boundaries.length);
593
+ if (to === 0) return {
594
+ from: 0,
595
+ to: 0
596
+ };
597
+ /** Where the run holding turn `end - 1` starts (0 when no run start precedes it). */
598
+ const runStart = (end) => {
599
+ let i = end - 1;
600
+ while (i > 0 && !boundaries[i]) i--;
601
+ return i;
602
+ };
603
+ let from = runStart(to);
604
+ const ceiling = limit * 5;
605
+ if (to - from > ceiling) {
606
+ from = to - ceiling;
607
+ while (from < to - 1 && !cuts[from]) from++;
608
+ } else while (from > 0) {
609
+ const previous = runStart(from);
610
+ if (to - previous > limit) break;
611
+ from = previous;
612
+ }
613
+ return {
614
+ from,
615
+ to,
616
+ ...from > 0 ? { cursor: from } : {}
617
+ };
618
+ }
619
+ //#endregion
620
+ //#region src/ipc/daemon/handlers/artifacts.ts
621
+ /** Wire-supplied limit → sane positive integer capped at 1000, else `undefined` (no limit). */
622
+ function boundedLimit(raw) {
623
+ return Number.isInteger(raw) && raw > 0 ? Math.min(raw, 1e3) : void 0;
624
+ }
625
+ function createArtifactHandlers({ store, broadcast }) {
626
+ return {
627
+ "sessions.artifacts": async (p) => {
628
+ if (!store.listArtifacts) return [];
629
+ return store.listArtifacts({
630
+ sessionId: p.sessionId,
631
+ ...typeof p.kind === "string" ? { kind: p.kind } : {},
632
+ ...boundedLimit(p.limit) !== void 0 ? { limit: boundedLimit(p.limit) } : {}
633
+ });
634
+ },
635
+ "artifacts.list": async (p) => {
636
+ if (!store.listArtifacts) return [];
637
+ return store.listArtifacts({
638
+ ...p.projectRoot === null || typeof p.projectRoot === "string" ? { projectRoot: p.projectRoot } : {},
639
+ ...typeof p.kind === "string" ? { kind: p.kind } : {},
640
+ ...typeof p.uri === "string" ? { uri: p.uri } : {},
641
+ ...typeof p.since === "number" && Number.isFinite(p.since) ? { since: p.since } : {},
642
+ ...boundedLimit(p.limit) !== void 0 ? { limit: boundedLimit(p.limit) } : {}
643
+ });
644
+ },
645
+ "artifacts.delete": async (p) => {
646
+ if (!store.deleteArtifact) return false;
647
+ await store.deleteArtifact(p.sessionId, p.artifactId);
648
+ broadcast({
649
+ kind: "artifact:changed",
650
+ sessionId: p.sessionId,
651
+ op: "delete",
652
+ artifactId: p.artifactId
653
+ });
654
+ return true;
655
+ },
656
+ "artifacts.upsert": async (p) => {
657
+ if (!store.upsertArtifact) return null;
658
+ const normalized = normalizeArtifactInput(p.artifact);
659
+ if (!normalized || typeof p.artifact?.sessionId !== "string" || typeof p.artifact?.id !== "string") return null;
660
+ const now = Date.now();
661
+ const stored = await store.upsertArtifact({
662
+ ...normalized,
663
+ id: p.artifact.id,
664
+ sessionId: p.artifact.sessionId,
665
+ ...typeof p.artifact.runId === "string" ? { runId: p.artifact.runId } : {},
666
+ ...typeof p.artifact.turnId === "string" ? { turnId: p.artifact.turnId } : {},
667
+ createdAt: typeof p.artifact.createdAt === "number" ? p.artifact.createdAt : now,
668
+ updatedAt: now
669
+ });
670
+ if (stored) broadcast({
671
+ kind: "artifact:changed",
672
+ sessionId: stored.sessionId,
673
+ op: "upsert",
674
+ artifactId: stored.id,
675
+ artifact: stored
676
+ });
677
+ return stored;
678
+ }
679
+ };
680
+ }
681
+ //#endregion
682
+ //#region src/ipc/daemon/handlers/auth.ts
683
+ /** Longest API key accepted (keys are well under 1 KiB; this bounds a mistaken paste). */
684
+ const MAX_KEY_LENGTH = 8192;
685
+ /** The host's providers and their credentials: lookups, statuses, and applying a change. */
686
+ function createProviderAuth({ host, env }) {
687
+ const registry = () => env.config.providers;
688
+ const dataDir = () => env.userDataDir();
689
+ const descriptorOf = (provider) => {
690
+ const descriptor = typeof provider === "string" ? registry()[provider] : void 0;
691
+ if (!descriptor) throw new Error(`Unknown provider "${String(provider)}".`);
692
+ return descriptor;
693
+ };
694
+ const statusOf = (key) => {
695
+ const auth = detectAuth(dataDir(), { [key]: registry()[key] })[0];
696
+ return {
697
+ key: auth.key,
698
+ label: auth.label,
699
+ configured: auth.available,
700
+ oauth: registry()[key]?.oauthProvider !== void 0,
701
+ ...auth.envKey ? { envKey: auth.envKey } : {},
702
+ methods: auth.methods.map((m) => ({
703
+ source: m.source,
704
+ active: m.active === true,
705
+ ...m.key !== void 0 ? { key: m.key } : {},
706
+ ...m.label !== void 0 ? { label: m.label } : {},
707
+ detail: m.detail
708
+ }))
709
+ };
710
+ };
711
+ /** The stored API key values of a provider (to clear them from the environment once removed). */
712
+ const storedKeys = (descriptor) => readProviderCredentials(dataDir(), descriptor).flatMap((s) => s.credential.kind === "apikey" && s.credential.value ? [s.credential.value] : []);
713
+ /** Make the change take effect: env re-applied, a removed key out of it, agents rebuilt. */
714
+ const applied = (descriptor, removedKeys = []) => {
715
+ if (env.applyAuthEnv) env.applyAuthEnv();
716
+ else applyApiKeyEnv(dataDir(), registry(), { log: () => void 0 });
717
+ const envKey = descriptor.envKey;
718
+ if (envKey && removedKeys.includes(process.env[envKey] ?? "") && !storedKeys(descriptor).includes(process.env[envKey])) delete process.env[envKey];
719
+ host.rebuildAllAgents().catch(() => void 0);
720
+ };
721
+ return {
722
+ registry,
723
+ dataDir,
724
+ descriptorOf,
725
+ statusOf,
726
+ storedKeys,
727
+ applied
728
+ };
729
+ }
730
+ function createAuthHandlers({ auth }) {
731
+ const { registry, dataDir, descriptorOf, statusOf, storedKeys, applied } = auth;
732
+ return {
733
+ "auth.status": () => ({ providers: Object.keys(registry()).map(statusOf) }),
734
+ "auth.setApiKey": (p) => {
735
+ const descriptor = descriptorOf(p.provider);
736
+ const key = typeof p.key === "string" ? p.key.trim() : "";
737
+ if (!key) throw new Error("The API key is empty.");
738
+ if (key.length > MAX_KEY_LENGTH) throw new Error(`The API key is longer than ${MAX_KEY_LENGTH} characters.`);
739
+ setProviderCredential(dataDir(), descriptor, {
740
+ kind: "apikey",
741
+ value: key
742
+ });
743
+ applied(descriptor);
744
+ return statusOf(descriptor.key);
745
+ },
746
+ "auth.remove": (p) => {
747
+ const descriptor = descriptorOf(p.provider);
748
+ const before = storedKeys(descriptor);
749
+ let removed;
750
+ if (typeof p.account === "string") removed = removeStoredCredential(dataDir(), descriptor, p.account);
751
+ else {
752
+ removed = readProviderCredentials(dataDir(), descriptor).length > 0;
753
+ removeProviderCredential(dataDir(), descriptor);
754
+ }
755
+ if (removed) applied(descriptor, before.filter((k) => !storedKeys(descriptor).includes(k)));
756
+ return {
757
+ removed,
758
+ provider: statusOf(descriptor.key)
759
+ };
760
+ }
761
+ };
762
+ }
763
+ //#endregion
764
+ //#region src/ipc/daemon/handlers/browser.ts
765
+ function createBrowserHandlers({ host }) {
766
+ return {
767
+ methods: {
768
+ "browser.registerHost": (p, peer) => {
769
+ (peer.browserHosts ??= /* @__PURE__ */ new Set()).add(p.hostId);
770
+ host.browser.registerHost(p.hostId);
771
+ return { ok: true };
772
+ },
773
+ "browser.unregisterHost": (p, peer) => {
774
+ peer.browserHosts?.delete(p.hostId);
775
+ host.browser.unregisterHost(p.hostId);
776
+ return { ok: true };
777
+ },
778
+ "browser.respond": (p, peer) => host.browser.respond(p.requestId, p.response, peer.browserHosts ?? /* @__PURE__ */ new Set())
779
+ },
780
+ releasePeer(peer) {
781
+ for (const hostId of peer.browserHosts ?? []) host.browser.unregisterHost(hostId);
782
+ }
783
+ };
784
+ }
785
+ //#endregion
786
+ //#region src/ipc/daemon/handlers/catalog-install.ts
787
+ const DEFAULT_TIMEOUT_MS = 3e5;
788
+ const MAX_SPEC_LENGTH = 500;
789
+ const MAX_MCP_CONFIG_BYTES = 65536;
790
+ const NAME_RE$1 = /^[\w@.-]{1,100}$/;
791
+ const MCP_TRANSPORTS = /* @__PURE__ */ new Set([
792
+ "stdio",
793
+ "sse",
794
+ "streamable-http"
795
+ ]);
796
+ const OFF_MESSAGE = "Installs from clients are off on this server (allowRemoteInstalls is off).";
797
+ /** A spec a server may fetch: GitHub or npm. Anything path-like is refused before the parsers see it. */
798
+ function checkSpec(spec) {
799
+ if (typeof spec !== "string" || spec.trim() === "") throw new InvalidParamsError("spec must be a non-empty string.");
800
+ const value = spec.trim();
801
+ if (value.length > MAX_SPEC_LENGTH) throw new InvalidParamsError(`spec is longer than ${MAX_SPEC_LENGTH} characters.`);
802
+ const segments = value.split(/[/:#@]/);
803
+ if (/^(?:[./~\\]|file:)/i.test(value) || value.includes("\\") || segments.includes(".") || segments.includes("..")) throw new InvalidParamsError(`"${value}" is a path: a server installs from GitHub (owner/repo, a GitHub URL) or npm only.`);
804
+ return value;
805
+ }
806
+ function checkName(name, what) {
807
+ if (typeof name !== "string" || !NAME_RE$1.test(name) || name === "." || name === "..") throw new InvalidParamsError(`${what} must be a plain name (letters, digits, ., _, -, @).`);
808
+ return name;
809
+ }
810
+ function checkKind(kind) {
811
+ if (kind !== "skill" && kind !== "extension") throw new InvalidParamsError("kind must be \"skill\" or \"extension\".");
812
+ return kind;
813
+ }
814
+ function isStringRecord(value) {
815
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
816
+ }
817
+ function checkMcpConfig(config) {
818
+ if (typeof config !== "object" || config === null || Array.isArray(config)) throw new InvalidParamsError("config must be an MCP server object.");
819
+ if (JSON.stringify(config).length > MAX_MCP_CONFIG_BYTES) throw new InvalidParamsError(`config is larger than ${MAX_MCP_CONFIG_BYTES} bytes.`);
820
+ const c = config;
821
+ checkName(c.name, "name");
822
+ if (c.transport !== void 0 && !MCP_TRANSPORTS.has(c.transport)) throw new InvalidParamsError("transport must be stdio, sse or streamable-http.");
823
+ const hasCommand = typeof c.command === "string" && c.command.trim() !== "";
824
+ let hasUrl = false;
825
+ if (typeof c.url === "string") {
826
+ try {
827
+ hasUrl = ["http:", "https:"].includes(new URL(c.url).protocol);
828
+ } catch {}
829
+ if (!hasUrl) throw new InvalidParamsError("url must be an http(s) URL.");
830
+ }
831
+ if (!hasCommand && !hasUrl) throw new InvalidParamsError("An MCP server needs a command (stdio) or a url.");
832
+ if (c.args !== void 0 && (!Array.isArray(c.args) || !c.args.every((a) => typeof a === "string"))) throw new InvalidParamsError("args must be strings.");
833
+ for (const key of ["env", "headers"]) if (c[key] !== void 0 && !isStringRecord(c[key])) throw new InvalidParamsError(`${key} must map names to strings.`);
834
+ return c;
835
+ }
836
+ /** How long a stopped install may take to roll back before the refusal is answered anyway. */
837
+ const ROLLBACK_GRACE_MS = 1e4;
838
+ /**
839
+ * `run` bounded by `ms`: past it, its signal fires (children killed, no next
840
+ * step: `abortableInstallIo`) and the refusal waits for the installer's own
841
+ * rollback (staging dir, transactional target), up to {@link ROLLBACK_GRACE_MS}.
842
+ */
843
+ async function bounded(run, ms, what) {
844
+ const controller = new AbortController();
845
+ const work = run(controller.signal);
846
+ let timer;
847
+ try {
848
+ return await Promise.race([work, new Promise((_, reject) => {
849
+ timer = setTimeout(() => {
850
+ controller.abort();
851
+ reject(/* @__PURE__ */ new Error(`${what} took longer than ${Math.round(ms / 1e3)}s and was stopped.`));
852
+ }, ms);
853
+ })]);
854
+ } catch (err) {
855
+ if (controller.signal.aborted) {
856
+ let grace;
857
+ await Promise.race([work.catch(() => void 0), new Promise((resolve) => {
858
+ grace = setTimeout(resolve, ROLLBACK_GRACE_MS);
859
+ })]);
860
+ clearTimeout(grace);
861
+ }
862
+ throw err;
863
+ } finally {
864
+ clearTimeout(timer);
865
+ }
866
+ }
867
+ function createCatalogInstallHandlers({ host, env, broadcast, setSettings, options }) {
868
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
869
+ const io = options?.io;
870
+ const prefix = () => env.config.prefix;
871
+ /** The host user's roots for `subPath` (`~/.agents/<sub>`, `~/.<prefix>/<sub>`), read at call time. */
872
+ const userRoots = (subPath) => projectUserPaths({
873
+ subPath,
874
+ cwd: homedir(),
875
+ home: homedir(),
876
+ prefix: prefix()
877
+ }).filter((p) => p.source === "user").map((p) => p.path);
878
+ /** Gate, run, audit: every attempt leaves one line, refusals and failures included. */
879
+ async function guarded(peer, event, fields, run) {
880
+ if (!options) throw new Error("This host takes no installs from clients (a zidane-server does, with allowRemoteInstalls on).");
881
+ if (!options.allowed()) {
882
+ options.audit?.(peer, {
883
+ event,
884
+ ...fields,
885
+ ok: false,
886
+ refused: "allowRemoteInstalls is off"
887
+ });
888
+ throw new Error(OFF_MESSAGE);
889
+ }
890
+ try {
891
+ const result = await run();
892
+ options.audit?.(peer, {
893
+ event,
894
+ ...fields,
895
+ ok: true
896
+ });
897
+ return result;
898
+ } catch (err) {
899
+ options.audit?.(peer, {
900
+ event,
901
+ ...fields,
902
+ ok: false,
903
+ error: err instanceof Error ? err.message : String(err)
904
+ });
905
+ throw err;
906
+ }
907
+ }
908
+ const changed = (catalog) => broadcast({
909
+ kind: "catalog:changed",
910
+ catalog
911
+ });
912
+ /** Skills and MCP servers are read when an agent is built. */
913
+ const rebuild = () => {
914
+ host.rebuildAllAgents({ rediscover: true }).catch(() => void 0);
915
+ };
916
+ /** Extensions live in the runtime registry: forget it, rebuild, dispose the old one once idle (as `runtime.reload`). */
917
+ const reloadExtensions = () => {
918
+ const disposeOld = env.forgetRuntime();
919
+ host.rebuildAllAgents({ rediscover: true }).then(disposeOld, disposeOld);
920
+ };
921
+ /** `name` dropped from (or added to) an explicit allowlist; an absent list means "all", left alone. */
922
+ const allowlist = (key, name, present) => {
923
+ const list = env.readSettings()[key];
924
+ if (list === void 0 || list.includes(name) === present) return false;
925
+ setSettings({ [key]: present ? [...list, name] : list.filter((n) => n !== name) });
926
+ return true;
927
+ };
928
+ /** The user-scope install directory named `name` under `subPath`, if any. */
929
+ const userDir = (subPath, name, marker) => userRoots(subPath).map((root) => join(root, name)).find((dir) => existsSync(marker ? join(dir, marker) : dir));
930
+ return {
931
+ "catalog.install": (p, peer) => guarded(peer, "catalog.install", {
932
+ kind: p.kind,
933
+ spec: typeof p.spec === "string" ? p.spec.slice(0, MAX_SPEC_LENGTH) : void 0
934
+ }, async () => {
935
+ const kind = checkKind(p.kind);
936
+ const spec = checkSpec(p.spec);
937
+ if (p.scope !== void 0 && p.scope !== "user") throw new InvalidParamsError("A server installs in its user scope only (scope: \"user\").");
938
+ const progress = [];
939
+ const onProgress = (line) => {
940
+ if (progress.length < 200) progress.push(line);
941
+ };
942
+ const home = homedir();
943
+ if (kind === "skill") {
944
+ const result = await bounded((signal) => installSkillFromSpec({
945
+ source: spec,
946
+ scope: "user",
947
+ cwd: home,
948
+ home,
949
+ prefix: prefix(),
950
+ onProgress,
951
+ io: abortableInstallIo(io, signal),
952
+ ...Array.isArray(p.skills) ? { skills: p.skills.filter((s) => typeof s === "string") } : {},
953
+ ...p.all === true ? { all: true } : {},
954
+ ...p.overwrite === true ? { overwrite: true } : {}
955
+ }), timeoutMs, `Installing ${spec}`);
956
+ rebuild();
957
+ changed("skills");
958
+ const skipped = result.skipped?.length ? `. Skipped existing: ${result.skipped.join(", ")}` : "";
959
+ return {
960
+ installed: [...result.installed],
961
+ message: `installed ${result.installed.join(", ")} (${result.provenance ?? result.engine})${skipped}`,
962
+ progress
963
+ };
964
+ }
965
+ let result;
966
+ try {
967
+ result = await bounded((signal) => installExtensionFromSpec({
968
+ source: spec,
969
+ scope: "user",
970
+ cwd: home,
971
+ home,
972
+ prefix: prefix(),
973
+ onProgress,
974
+ io: abortableInstallIo(io, signal),
975
+ ...typeof p.extension === "string" ? { extension: p.extension } : {},
976
+ ...p.overwrite === true ? { overwrite: true } : {}
977
+ }), timeoutMs, `Installing ${spec}`);
978
+ } catch (err) {
979
+ if (err instanceof ExtensionInstallError && err.code === "exists") throw new Error("already installed on the server: update or remove it first.");
980
+ throw err;
981
+ }
982
+ if (!allowlist("enabledExtensions", result.name, true)) reloadExtensions();
983
+ changed("extensions");
984
+ const warn = result.warnings.length > 0 ? `. ${result.warnings.join(". ")}` : "";
985
+ return {
986
+ installed: [result.name],
987
+ message: `installed "${result.name}" (${result.provenance})${warn}`,
988
+ progress
989
+ };
990
+ }),
991
+ "catalog.remove": (p, peer) => guarded(peer, "catalog.remove", {
992
+ kind: p.kind,
993
+ name: p.name
994
+ }, async () => {
995
+ const kind = checkKind(p.kind);
996
+ const name = checkName(p.name, "name");
997
+ if (kind === "skill") {
998
+ const dir = userDir("skills", name, "SKILL.md");
999
+ if (!dir) throw new Error(`No user-scope skill "${name}" on this server (project skills are files in their folder).`);
1000
+ rmSync(dir, {
1001
+ recursive: true,
1002
+ force: true
1003
+ });
1004
+ allowlist("enabledSkills", name, false);
1005
+ rebuild();
1006
+ changed("skills");
1007
+ return {
1008
+ removed: name,
1009
+ message: `deleted "${name}"`
1010
+ };
1011
+ }
1012
+ const dir = userDir("extensions", name);
1013
+ if (!dir) throw new Error(`No user-scope extension "${name}" on this server.`);
1014
+ removeInstalledExtension({
1015
+ dir,
1016
+ cwd: homedir(),
1017
+ home: homedir(),
1018
+ prefix: prefix()
1019
+ });
1020
+ if (!allowlist("enabledExtensions", name, false)) reloadExtensions();
1021
+ changed("extensions");
1022
+ return {
1023
+ removed: name,
1024
+ message: `removed "${name}"`
1025
+ };
1026
+ }),
1027
+ "catalog.update": (p, peer) => guarded(peer, "catalog.update", {
1028
+ kind: p.kind,
1029
+ name: p.name
1030
+ }, async () => {
1031
+ const kind = checkKind(p.kind);
1032
+ const name = checkName(p.name, "name");
1033
+ if (kind === "skill") throw new Error("Skills are not updated in place: install again with overwrite.");
1034
+ const dir = userDir("extensions", name);
1035
+ if (!dir) throw new Error(`No user-scope extension "${name}" on this server.`);
1036
+ const result = await bounded((signal) => updateInstalledExtension({
1037
+ dir,
1038
+ cwd: homedir(),
1039
+ home: homedir(),
1040
+ prefix: prefix(),
1041
+ io: abortableInstallIo(io, signal)
1042
+ }), timeoutMs, `Updating ${name}`);
1043
+ if (result.upToDate) return {
1044
+ updated: result.name,
1045
+ upToDate: true,
1046
+ message: `"${result.name}" is already up to date${result.commit ? ` (${result.commit.slice(0, 7)})` : ""}`
1047
+ };
1048
+ reloadExtensions();
1049
+ changed("extensions");
1050
+ return {
1051
+ updated: result.name,
1052
+ upToDate: false,
1053
+ message: `updated "${result.name}" → ${result.provenance}`
1054
+ };
1055
+ }),
1056
+ "mcp.upsert": (p, peer) => guarded(peer, "mcp.upsert", { name: p.config?.name }, () => {
1057
+ const config = checkMcpConfig(p.config);
1058
+ const result = upsertMcpServer(mcpsWritePath("user", {
1059
+ home: homedir(),
1060
+ cwd: homedir(),
1061
+ prefix: prefix()
1062
+ }), config);
1063
+ const enabled = result.action === "added" ? enabledMcpsWith(env.readSettings().enabledMcps, config.name) : void 0;
1064
+ if (enabled) setSettings({ enabledMcps: enabled });
1065
+ rebuild();
1066
+ changed("mcps");
1067
+ return {
1068
+ name: config.name,
1069
+ created: result.action === "added"
1070
+ };
1071
+ }),
1072
+ "mcp.remove": (p, peer) => guarded(peer, "mcp.remove", { name: p.name }, () => {
1073
+ const name = checkName(p.name, "name");
1074
+ const path = mcpsWritePath("user", {
1075
+ home: homedir(),
1076
+ cwd: homedir(),
1077
+ prefix: prefix()
1078
+ });
1079
+ try {
1080
+ removeMcpServer(path, name);
1081
+ } catch (err) {
1082
+ if (!existsSync(path) || /no entry named/.test(err instanceof Error ? err.message : "")) return { removed: false };
1083
+ throw err;
1084
+ }
1085
+ const enabled = enabledMcpsWithout(env.readSettings().enabledMcps, name);
1086
+ if (enabled) setSettings({ enabledMcps: enabled });
1087
+ rebuild();
1088
+ changed("mcps");
1089
+ return { removed: true };
1090
+ })
1091
+ };
1092
+ }
1093
+ /** For `ipc.hello`: installs are offered when the server allows them. */
1094
+ function remoteInstallsAllowed(options) {
1095
+ return options?.allowed() === true;
1096
+ }
1097
+ //#endregion
1098
+ //#region src/ipc/daemon/redact.ts
1099
+ /**
1100
+ * Secret redaction for what a host shows about its MCP servers
1101
+ * (`catalog.mcps`): command-line arguments and URLs, which config files
1102
+ * routinely fill with tokens. Over-redacting an ordinary argument is a
1103
+ * cosmetic loss; under-redacting leaks a credential to every client, so the
1104
+ * rules lean wide, but stay off file paths, package names and plain words.
1105
+ *
1106
+ * - URLs (`scheme://…`, as an argument or inside one): userinfo, query values,
1107
+ * fragment values, and secret-looking path segments.
1108
+ * - Values of secret-looking flags (`--token x`, `--api-key=x`), of header
1109
+ * flags (`-H "Name: value"`, `--header=Name: value`), of secret-looking
1110
+ * headers anywhere (`Authorization: Bearer …`), and of secret-looking
1111
+ * `KEY=VALUE` pairs (`-e GITHUB_TOKEN=…`, `env API_KEY=…`, `--pass x`).
1112
+ * - JSON arguments (`--config '{"apiKey":"…"}'`): the value of every
1113
+ * secret-named key at any depth; the other strings as values.
1114
+ * - Connection strings and form bodies (`Server=x;Password=…`,
1115
+ * `a=1&token=…`): each secret-named pair's value.
1116
+ * - Known token shapes anywhere in an argument (`sk-…`, `ghp_…`, `xoxb-…`,
1117
+ * `AKIA…`, `glpat-…`, `zds_…`), and whole high-entropy arguments.
1118
+ *
1119
+ * What counts as secret-looking is shared (`src/chat/secret-shapes`): the
1120
+ * remote host URL check refuses the same shapes in a URL's path.
1121
+ */
1122
+ const HEADER_FLAGS = /* @__PURE__ */ new Set([
1123
+ "-H",
1124
+ "--header",
1125
+ "--headers"
1126
+ ]);
1127
+ const URL_SHAPED = /^[a-z][a-z0-9+.-]*:\/\//i;
1128
+ const HEADER = /^([\w-]+):(\s*)\S.*$/s;
1129
+ const KEY_VALUE = /^([A-Z_][\w.-]*)=(.*)$/is;
1130
+ /** `k=v` pairs joined by `;` or `&` (a connection string, a form body): `Server=x;Password=y`, `a=1&token=x`. */
1131
+ const DELIMITED_PAIRS = /^[a-z_][\w .-]*=[^;&]*(?:[;&][a-z_][\w .-]*=[^;&]*)+[;&]?$/i;
1132
+ /** A JSON object or array argument (`--config '{"apiKey":"…"}'`). */
1133
+ const JSON_SHAPED = /^\s*[{[][\s\S]*[}\]]\s*$/;
1134
+ const scrubKnownTokens = (value) => value.replace(KNOWN_TOKEN, REDACTED);
1135
+ function redactPairs(text) {
1136
+ return text.split("&").map((pair) => {
1137
+ const eq = pair.indexOf("=");
1138
+ return eq < 0 ? pair : `${pair.slice(0, eq)}=${REDACTED}`;
1139
+ }).join("&");
1140
+ }
1141
+ /**
1142
+ * `https://user:pw@host/p/<key>/sse?k=v#t=x` → `https://<redacted>@host/p/<redacted>/sse?k=<redacted>#t=<redacted>`;
1143
+ * an unparsable URL is redacted whole.
1144
+ */
1145
+ function redactUrl(raw) {
1146
+ let url;
1147
+ try {
1148
+ url = new URL(raw);
1149
+ } catch {
1150
+ return REDACTED;
1151
+ }
1152
+ const credentials = url.username !== "" || url.password !== "" ? `${REDACTED}@` : "";
1153
+ const path = url.pathname.split("/").map((segment) => isSecretPathSegment(segment) ? REDACTED : segment).join("/");
1154
+ const keys = [...url.searchParams.keys()];
1155
+ const query = keys.length > 0 ? `?${keys.map((key) => `${encodeURIComponent(key)}=${REDACTED}`).join("&")}` : "";
1156
+ const fragment = url.hash.slice(1);
1157
+ const hash = fragment === "" ? "" : fragment.includes("=") ? `#${redactPairs(fragment)}` : looksLikeSecret(fragment) ? `#${REDACTED}` : `#${fragment}`;
1158
+ const authority = url.host !== "" || raw.includes("//") ? `//${credentials}${url.host}` : "";
1159
+ return scrubKnownTokens(`${url.protocol}${authority}${path}${query}${hash}`);
1160
+ }
1161
+ function redactHeader(value) {
1162
+ const match = HEADER.exec(value);
1163
+ return match ? `${match[1]}:${match[2]}${REDACTED}` : REDACTED;
1164
+ }
1165
+ /** One value standing on its own (a positional argument, or what follows `--flag=`). */
1166
+ function redactValue(value) {
1167
+ if (URL_SHAPED.test(value)) return redactUrl(value);
1168
+ const header = HEADER.exec(value);
1169
+ if (header && isSecretName(header[1])) return redactHeader(value);
1170
+ const json = JSON_SHAPED.test(value) ? redactJson(value) : null;
1171
+ if (json !== null) return json;
1172
+ if (DELIMITED_PAIRS.test(value)) return redactDelimitedPairs(value);
1173
+ const pair = KEY_VALUE.exec(value);
1174
+ if (pair) return `${pair[1]}=${isSecretName(pair[1]) ? REDACTED : redactValue(pair[2])}`;
1175
+ if (looksLikeSecret(value)) return REDACTED;
1176
+ return scrubKnownTokens(value);
1177
+ }
1178
+ /** `Server=x;Password=y` → `Server=x;Password=<redacted>`: each secret-named pair's value, the others' as values. */
1179
+ function redactDelimitedPairs(value) {
1180
+ return value.split(/([;&])/).map((part) => {
1181
+ const eq = part.indexOf("=");
1182
+ if (eq < 0) return part;
1183
+ const name = part.slice(0, eq);
1184
+ return `${name}=${isSecretName(name.trim()) ? REDACTED : redactValue(part.slice(eq + 1))}`;
1185
+ }).join("");
1186
+ }
1187
+ /** A JSON argument with every secret-named key's value redacted (any depth), the other strings as values; `null` when not JSON. */
1188
+ function redactJson(value) {
1189
+ let parsed;
1190
+ try {
1191
+ parsed = JSON.parse(value);
1192
+ } catch {
1193
+ return null;
1194
+ }
1195
+ if (parsed === null || typeof parsed !== "object") return null;
1196
+ const walk = (node) => {
1197
+ if (typeof node === "string") return redactValue(node);
1198
+ if (Array.isArray(node)) return node.map(walk);
1199
+ if (node !== null && typeof node === "object") return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, isSecretName(key) ? REDACTED : walk(child)]));
1200
+ return node;
1201
+ };
1202
+ return JSON.stringify(walk(parsed));
1203
+ }
1204
+ /** An MCP server's arguments with every secret-looking value replaced by {@link REDACTED}. */
1205
+ function redactArgs(args) {
1206
+ const out = [];
1207
+ for (let i = 0; i < args.length; i++) {
1208
+ const arg = args[i];
1209
+ if (!arg.startsWith("-")) {
1210
+ out.push(redactValue(arg));
1211
+ continue;
1212
+ }
1213
+ const eq = arg.indexOf("=");
1214
+ const flag = eq > 0 ? arg.slice(0, eq) : arg;
1215
+ if (eq > 0) {
1216
+ const value = arg.slice(eq + 1);
1217
+ out.push(`${flag}=${HEADER_FLAGS.has(flag) ? redactHeader(value) : isSecretName(flag) ? REDACTED : redactValue(value)}`);
1218
+ continue;
1219
+ }
1220
+ out.push(scrubKnownTokens(arg));
1221
+ const next = args[i + 1];
1222
+ if (next === void 0 || next.startsWith("-")) continue;
1223
+ if (HEADER_FLAGS.has(flag)) {
1224
+ out.push(redactHeader(next));
1225
+ i++;
1226
+ } else if (isSecretName(flag)) {
1227
+ out.push(REDACTED);
1228
+ i++;
1229
+ }
1230
+ }
1231
+ return out;
1232
+ }
1233
+ //#endregion
1234
+ //#region src/ipc/daemon/catalog-entries.ts
1235
+ const allows = (list, name) => list === void 0 || list.includes(name);
1236
+ /**
1237
+ * Disk skills (gated by `enabledSkills`), then plugin and extension skills
1238
+ * (gated by their plugin / extension, so always on here), later ones winning
1239
+ * a name like the agent's own merge (`mergeSkillCatalog`).
1240
+ */
1241
+ function skillEntries(input) {
1242
+ const byName = /* @__PURE__ */ new Map();
1243
+ const add = (skill, origin) => {
1244
+ const contributedBy = origin === "plugin" ? skill.metadata?.["zidane.plugin"] : origin === "extension" ? skill.metadata?.["zidane.extension"] : void 0;
1245
+ byName.set(skill.name, {
1246
+ name: skill.name,
1247
+ description: skill.description,
1248
+ ...skill.source ? { source: skill.source } : {},
1249
+ ...skill.location ? { location: skill.location } : {},
1250
+ origin,
1251
+ ...contributedBy ? { contributedBy } : {},
1252
+ enabled: origin === "disk" ? allows(input.settings.enabledSkills, skill.name) : true,
1253
+ toggleable: origin === "disk"
1254
+ });
1255
+ };
1256
+ for (const skill of input.disk) add(skill, "disk");
1257
+ for (const skill of input.plugin) add(skill, "plugin");
1258
+ for (const skill of input.extension) add(skill, "extension");
1259
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
1260
+ }
1261
+ function redactValues(record) {
1262
+ if (!record) return void 0;
1263
+ return Object.fromEntries(Object.keys(record).map((key) => [key, REDACTED]));
1264
+ }
1265
+ function mcpEntries(servers, errors, settings) {
1266
+ return {
1267
+ servers: servers.map(({ config, source, path }) => {
1268
+ const env = redactValues(config.env);
1269
+ const headers = redactValues(config.headers);
1270
+ return {
1271
+ name: config.name,
1272
+ transport: config.transport,
1273
+ source,
1274
+ path,
1275
+ enabled: allows(settings.enabledMcps, config.name),
1276
+ toggleable: true,
1277
+ ...config.command !== void 0 ? { command: config.command } : {},
1278
+ ...config.args ? { args: redactArgs(config.args) } : {},
1279
+ ...config.url !== void 0 ? { url: redactUrl(config.url) } : {},
1280
+ ...config.cwd !== void 0 ? { cwd: config.cwd } : {},
1281
+ ...config.scope !== void 0 ? { scope: config.scope } : {},
1282
+ ...config.auth !== void 0 ? { auth: config.auth } : {},
1283
+ ...env ? { env } : {},
1284
+ ...headers ? { headers } : {}
1285
+ };
1286
+ }),
1287
+ errors: errors.map((e) => ({
1288
+ path: e.path,
1289
+ source: e.source,
1290
+ message: e.message,
1291
+ ...e.informational ? { informational: true } : {}
1292
+ }))
1293
+ };
1294
+ }
1295
+ function capabilities(contributions) {
1296
+ const labels = [];
1297
+ const add = (label, present) => {
1298
+ if (present) labels.push(label);
1299
+ };
1300
+ add("agents", Object.keys(contributions.agents).length > 0);
1301
+ add("providers", Object.keys(contributions.providers).length > 0);
1302
+ add("tools", Object.keys(contributions.tools).length > 0);
1303
+ add("hooks", Object.values(contributions.hooks).some((handlers) => handlers !== void 0 && handlers.length > 0));
1304
+ add("mcps", contributions.mcpServers.length > 0);
1305
+ add("skills", contributions.skills.length > 0);
1306
+ add("commands", contributions.commands.length > 0);
1307
+ add("completions", contributions.completionProviders.length > 0);
1308
+ add("themes", Object.keys(contributions.themes).length > 0);
1309
+ add("system", contributions.systemFragments.length > 0);
1310
+ return labels;
1311
+ }
1312
+ /** Loaded, then inactive (disabled or broken), first one of a name wins. */
1313
+ function extensionEntries(loaded, inactive, settings) {
1314
+ const rows = /* @__PURE__ */ new Map();
1315
+ for (const ext of [...loaded, ...inactive]) {
1316
+ if (rows.has(ext.name)) continue;
1317
+ rows.set(ext.name, {
1318
+ name: ext.name,
1319
+ source: ext.source,
1320
+ enabled: !ext.error && allows(settings.enabledExtensions, ext.name),
1321
+ toggleable: !ext.error,
1322
+ ...ext.definition?.version ? { version: ext.definition.version } : {},
1323
+ ...ext.definition?.description ? { description: ext.definition.description } : {},
1324
+ ...ext.dir ? { dir: ext.dir } : {},
1325
+ ...ext.error ? { error: ext.error } : {},
1326
+ ...ext.contributions ? { capabilities: capabilities(ext.contributions) } : {}
1327
+ });
1328
+ }
1329
+ return [...rows.values()].sort((a, b) => a.name.localeCompare(b.name));
1330
+ }
1331
+ /** Disk plugins, then the ones extensions loaded (a name on disk wins, as the agent's gate decides). */
1332
+ function pluginEntries(disk, fromExtensions, settings) {
1333
+ const sources = [...disk.map((p) => ({
1334
+ plugin: p.plugin,
1335
+ name: p.name,
1336
+ origin: {
1337
+ kind: "disk",
1338
+ scope: p.source,
1339
+ dir: p.path
1340
+ }
1341
+ })), ...fromExtensions.map((r) => ({
1342
+ plugin: r.plugin,
1343
+ name: r.name,
1344
+ origin: {
1345
+ kind: "extension",
1346
+ extensionName: r.extensionName,
1347
+ dir: r.dir
1348
+ }
1349
+ }))];
1350
+ const rows = /* @__PURE__ */ new Map();
1351
+ for (const { plugin, name, origin } of sources) {
1352
+ if (rows.has(name)) continue;
1353
+ const error = plugin.ok ? void 0 : plugin.diagnostics.find((d) => d.severity === "error")?.message;
1354
+ rows.set(name, {
1355
+ name,
1356
+ origin,
1357
+ ok: plugin.ok,
1358
+ enabled: plugin.ok && allows(settings.enabledPlugins, name),
1359
+ toggleable: plugin.ok,
1360
+ ...plugin.manifest?.version !== void 0 ? { version: plugin.manifest.version } : {},
1361
+ ...plugin.manifest?.description !== void 0 ? { description: plugin.manifest.description } : {},
1362
+ skills: plugin.skills.map((s) => s.name),
1363
+ mcpServers: plugin.mcpServers.map((s) => s.name),
1364
+ ...error ? { error } : {}
1365
+ });
1366
+ }
1367
+ return [...rows.values()].sort((a, b) => a.name.localeCompare(b.name));
1368
+ }
1369
+ //#endregion
1370
+ //#region src/ipc/daemon/handlers/catalogs.ts
1371
+ const kindOf = (kind) => typeof kind === "string" ? kind : void 0;
1372
+ /**
1373
+ * The folder a catalog scans: `cwd` (inside the roots on a server), else the
1374
+ * host user's home. A machine's folder (`docs/MACHINES.md` § Folders) is
1375
+ * never read here: a folder of this host's stands in for the user scope.
1376
+ */
1377
+ function catalogFolder({ env, host }, cwd, kind) {
1378
+ if (cwd !== void 0 && typeof cwd !== "string") throw new Error("cwd must be a string.");
1379
+ if (!cwd) return scopeCwd("");
1380
+ const folder = host.resolveFolder(cwd, kindOf(kind));
1381
+ return folder.machine ? machineDiscoveryCwd(env.userDataDir()) : scopeCwd(folder.cwd);
1382
+ }
1383
+ function createCatalogHandlers({ env, host }) {
1384
+ const folderOf = (cwd, kind) => catalogFolder({
1385
+ env,
1386
+ host
1387
+ }, cwd, kind);
1388
+ /** User scope here, project scope read on the machine for a machine's folder. */
1389
+ const catalogOf = async (cwd, kind, prefix) => {
1390
+ if (typeof cwd !== "string" || !cwd || !host.resolveFolder(cwd, kindOf(kind)).machine) return { dir: folderOf(cwd, kind) };
1391
+ const { discoveryCwd, project } = await host.folderCatalog(cwd, kindOf(kind), prefix);
1392
+ return {
1393
+ dir: discoveryCwd,
1394
+ ...project ? { project } : {}
1395
+ };
1396
+ };
1397
+ const scanFlags = () => {
1398
+ const settings = env.readSettings();
1399
+ return {
1400
+ settings,
1401
+ foreignAgents: settings.importForeignConfigs === true,
1402
+ ruler: settings.importRulerConfigs !== false
1403
+ };
1404
+ };
1405
+ const plugins = async (dir, config) => {
1406
+ const { plugins: found } = await discoverProjectPlugins({
1407
+ cwd: dir,
1408
+ prefix: config.prefix,
1409
+ ...config.pluginScanPaths.length > 0 ? { extraRoots: config.pluginScanPaths } : {}
1410
+ });
1411
+ return found;
1412
+ };
1413
+ return {
1414
+ "catalog.skills": async (p) => {
1415
+ const { dir, project } = await catalogOf(p?.cwd, p?.kind, env.config.prefix);
1416
+ const config = await env.runtimeConfig(dir);
1417
+ const { settings, foreignAgents, ruler } = scanFlags();
1418
+ const [found, discovered] = await Promise.all([discoverProjectSkills({
1419
+ cwd: dir,
1420
+ prefix: config.prefix,
1421
+ foreignAgents,
1422
+ ruler
1423
+ }), plugins(dir, config)]);
1424
+ const machineNames = new Set((project?.skills ?? []).map((skill) => skill.name));
1425
+ const disk = [...project?.skills ?? [], ...found.filter((skill) => !machineNames.has(skill.name))];
1426
+ const pluginSkills = buildPluginContributions({
1427
+ discovered,
1428
+ ...settings.enabledPlugins !== void 0 ? { enabled: settings.enabledPlugins } : {}
1429
+ }).skills;
1430
+ return { skills: skillEntries({
1431
+ disk,
1432
+ plugin: pluginSkills,
1433
+ extension: filterRegistryPlugins(config.extensions, settings.enabledPlugins, new Set(discovered.map((d) => d.name))).skills,
1434
+ settings
1435
+ }) };
1436
+ },
1437
+ "catalog.mcps": async (p) => {
1438
+ const { dir, project } = await catalogOf(p?.cwd, p?.kind, env.config.prefix);
1439
+ const config = await env.runtimeConfig(dir);
1440
+ const { settings, foreignAgents, ruler } = scanFlags();
1441
+ const { servers: found, errors } = discoverProjectMcps({
1442
+ cwd: dir,
1443
+ prefix: config.prefix,
1444
+ foreignAgents,
1445
+ ruler
1446
+ });
1447
+ const machineNames = new Set((project?.mcps ?? []).map((server) => server.config.name));
1448
+ return mcpEntries([...project?.mcps ?? [], ...found.filter((server) => !machineNames.has(server.config.name))], errors, settings);
1449
+ },
1450
+ "catalog.extensions": async (p) => {
1451
+ const config = await env.runtimeConfig(folderOf(p?.cwd, p?.kind));
1452
+ return { extensions: extensionEntries(config.extensions.loaded, config.extensions.inactive, env.readSettings()) };
1453
+ },
1454
+ "catalog.plugins": async (p) => {
1455
+ const dir = folderOf(p?.cwd, p?.kind);
1456
+ const config = await env.runtimeConfig(dir);
1457
+ return { plugins: pluginEntries(await plugins(dir, config), config.extensions.plugins, env.readSettings()) };
1458
+ },
1459
+ "models.list": async (p) => {
1460
+ const config = p?.cwd ? await env.runtimeConfig(folderOf(p.cwd, p.kind)) : env.config;
1461
+ return { providers: detectAuth(env.userDataDir(), config.providers).filter((auth) => auth.available).map((auth) => ({
1462
+ key: auth.key,
1463
+ label: config.providers[auth.key]?.label ?? auth.label,
1464
+ models: JSON.parse(JSON.stringify(config.modelsFor(auth.key)))
1465
+ })) };
1466
+ }
1467
+ };
1468
+ }
1469
+ //#endregion
1470
+ //#region src/ipc/daemon/handlers/chat.ts
1471
+ function createChatHandlers({ host, clearDraft }) {
1472
+ return {
1473
+ "chat.send": async (p, peer) => {
1474
+ const { wake: _stripped, keepDraft, ...params } = p;
1475
+ const result = await host.sendPrompt(params);
1476
+ if (!keepDraft) clearDraft(p.sessionId, peer);
1477
+ return result;
1478
+ },
1479
+ "chat.interrupt": (p) => host.interrupt(p.sessionId),
1480
+ "chat.steer": (p) => host.steer(p.sessionId, p.message),
1481
+ "chat.followUp": (p) => host.followUp(p.sessionId, p.message),
1482
+ "chat.pushQueued": async (p, peer) => {
1483
+ const result = await host.pushQueued(p.sessionId, p.message, p.activatedSkills);
1484
+ if (!p.keepDraft) clearDraft(p.sessionId, peer);
1485
+ return result;
1486
+ },
1487
+ "chat.isRunning": (p) => host.isSessionRunning(p.sessionId),
1488
+ "chat.rebuildAgent": (p) => host.disposeAgentForRebuild(p.sessionId),
1489
+ "chat.listWork": (p) => host.listRunningWork(p.sessionId),
1490
+ "chat.cancelWork": (p) => host.cancelRunningWork(p.sessionId, p.id, p.reason),
1491
+ "commands.list": (p) => host.listCommands(p.cwd, typeof p.kind === "string" ? p.kind : void 0),
1492
+ "commands.completeArgs": (p) => host.completeCommandArgs(p.cwd, p.name, p.argsPrefix, typeof p.kind === "string" ? p.kind : void 0)
1493
+ };
1494
+ }
1495
+ //#endregion
1496
+ //#region src/ipc/daemon/handlers/context.ts
1497
+ function createContextHandlers({ host }) {
1498
+ return {
1499
+ "sessions.systemMd": (p) => host.sessionSystemMd(p.sessionId),
1500
+ "sessions.contextSegments": (p) => host.getContextSegments(p.sessionId),
1501
+ "sessions.createSystemOverride": (p) => host.createSystemOverride(p.sessionId),
1502
+ "sessions.contextBreakdown": (p) => host.getContextBreakdown(p.sessionId)
1503
+ };
1504
+ }
1505
+ //#endregion
1506
+ //#region src/ipc/daemon/handlers/daemon.ts
1507
+ function createDaemonHandlers({ host, appVersion, startedAt, lifecycle, identity }) {
1508
+ return {
1509
+ "daemon.info": () => ({
1510
+ protocolVersion: 13,
1511
+ appVersion,
1512
+ pid: process.pid,
1513
+ startedAt,
1514
+ ...identity
1515
+ }),
1516
+ "daemon.shutdown": async (p) => {
1517
+ if (p?.whenIdle === true && host.hasActiveWork()) {
1518
+ lifecycle.retireWhenIdle();
1519
+ return { deferred: true };
1520
+ }
1521
+ setImmediate(lifecycle.shutdown);
1522
+ }
1523
+ };
1524
+ }
1525
+ //#endregion
1526
+ //#region src/ipc/daemon/handlers/drafts.ts
1527
+ /**
1528
+ * Bound a peer-supplied attachment list: shape-guarded entries only,
1529
+ * 24 MiB of base64 per list. Shared by `queue.add` and
1530
+ * `drafts.setAttachments`.
1531
+ */
1532
+ function boundAttachments(list) {
1533
+ let budget = 25165824;
1534
+ const out = [];
1535
+ for (const entry of list) {
1536
+ const a = entry;
1537
+ if (typeof a?.mediaType !== "string" || typeof a?.data !== "string") continue;
1538
+ budget -= a.data.length;
1539
+ if (budget < 0) break;
1540
+ out.push({
1541
+ mediaType: a.mediaType,
1542
+ data: a.data,
1543
+ ...typeof a.name === "string" ? { name: a.name.slice(0, 256) } : {}
1544
+ });
1545
+ }
1546
+ return out;
1547
+ }
1548
+ function createDraftHandlers({ broadcastExcept }) {
1549
+ /**
1550
+ * Shared per-session prompt drafts. Ephemeral by design — in-memory
1551
+ * only, cleared when any window submits, gone on daemon restart.
1552
+ */
1553
+ const drafts = /* @__PURE__ */ new Map();
1554
+ /** Total base64 bytes currently pinned by ALL draft attachments. */
1555
+ function draftAttachmentBytes() {
1556
+ let total = 0;
1557
+ for (const entry of drafts.values()) for (const attachment of entry.attachments ?? []) total += attachment.data.length;
1558
+ return total;
1559
+ }
1560
+ function clearDraft(sessionId, origin) {
1561
+ const entry = drafts.get(sessionId);
1562
+ if (!entry) return;
1563
+ drafts.delete(sessionId);
1564
+ if (entry.text) broadcastExcept(origin, {
1565
+ kind: "draft:changed",
1566
+ sessionId,
1567
+ text: ""
1568
+ });
1569
+ if (entry.attachments?.length) broadcastExcept(origin, {
1570
+ kind: "draft:attachments",
1571
+ sessionId,
1572
+ attachments: []
1573
+ });
1574
+ }
1575
+ /** Drop empty draft entries so the map only holds live composing state. */
1576
+ function compactDraft(sessionId, entry) {
1577
+ if (!entry.text && !entry.shell && !entry.attachments?.length && !entry.search?.active) drafts.delete(sessionId);
1578
+ else drafts.set(sessionId, entry);
1579
+ }
1580
+ return {
1581
+ methods: {
1582
+ "drafts.get": (p) => {
1583
+ const entry = drafts.get(p.sessionId);
1584
+ return {
1585
+ text: entry?.text ?? "",
1586
+ ...entry?.shell ? { shell: true } : {},
1587
+ ...entry?.attachments?.length ? { attachments: entry.attachments } : {},
1588
+ ...entry?.search?.active ? { search: entry.search } : {}
1589
+ };
1590
+ },
1591
+ "drafts.set": (p, peer) => {
1592
+ const text = typeof p.text === "string" ? p.text.slice(0, 65536) : "";
1593
+ const truncated = typeof p.text === "string" && p.text.length > text.length;
1594
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
1595
+ const entry = drafts.get(p.sessionId) ?? {};
1596
+ entry.text = text.length > 0 ? text : void 0;
1597
+ entry.shell = p.shell === true ? true : void 0;
1598
+ compactDraft(p.sessionId, entry);
1599
+ broadcastExcept(truncated ? null : peer, {
1600
+ kind: "draft:changed",
1601
+ sessionId: p.sessionId,
1602
+ text,
1603
+ ...p.shell === true ? { shell: true } : {}
1604
+ });
1605
+ },
1606
+ "search.set": (p, peer) => {
1607
+ const active = p.active === true;
1608
+ const query = typeof p.query === "string" ? p.query.slice(0, 4096) : "";
1609
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
1610
+ const entry = drafts.get(p.sessionId) ?? {};
1611
+ entry.search = active || query.length > 0 ? {
1612
+ active,
1613
+ query
1614
+ } : void 0;
1615
+ compactDraft(p.sessionId, entry);
1616
+ broadcastExcept(peer, {
1617
+ kind: "search:changed",
1618
+ sessionId: p.sessionId,
1619
+ active,
1620
+ query
1621
+ });
1622
+ },
1623
+ "drafts.setAttachments": (p, peer) => {
1624
+ const bounded = Array.isArray(p.attachments) ? boundAttachments(p.attachments) : [];
1625
+ if (!drafts.has(p.sessionId) && drafts.size >= 256) return;
1626
+ const currentBytes = (drafts.get(p.sessionId)?.attachments ?? []).reduce((sum, a) => sum + a.data.length, 0);
1627
+ const incomingBytes = bounded.reduce((sum, a) => sum + a.data.length, 0);
1628
+ if (draftAttachmentBytes() - currentBytes + incomingBytes > 100663296) return;
1629
+ const entry = drafts.get(p.sessionId) ?? {};
1630
+ entry.attachments = bounded.length > 0 ? bounded : void 0;
1631
+ compactDraft(p.sessionId, entry);
1632
+ broadcastExcept(peer, {
1633
+ kind: "draft:attachments",
1634
+ sessionId: p.sessionId,
1635
+ attachments: bounded
1636
+ });
1637
+ }
1638
+ },
1639
+ clearDraft,
1640
+ forget: (sessionId) => {
1641
+ drafts.delete(sessionId);
1642
+ }
1643
+ };
1644
+ }
1645
+ //#endregion
1646
+ //#region src/ipc/daemon/handlers/sessions.ts
1647
+ async function loadOrThrow(store, sessionId) {
1648
+ const data = await store.load(sessionId);
1649
+ if (!data) throw new Error(`Session "${sessionId}" not found.`);
1650
+ return data;
1651
+ }
1652
+ function createSessionHandlers({ host, store, broadcast, forgetSession, hostId, workspace }) {
1653
+ const stamp = (row) => hostId ? {
1654
+ ...row,
1655
+ hostId
1656
+ } : row;
1657
+ /**
1658
+ * Apply a metadata-only patch. Prefers the host's live-session path (safe
1659
+ * mid-run — routes through the in-memory `Session` whose turns are the
1660
+ * source of truth); only falls back to a store-level full-document
1661
+ * `save()` when the session is idle, where no run is appending turns so
1662
+ * `load → mutate → save` can't drop concurrently-persisted turns.
1663
+ *
1664
+ * `bumpUpdatedAt: false` skips the recency bump so a rename doesn't yank
1665
+ * the row to the top of recency-sorted lists (mirrors the GUI).
1666
+ */
1667
+ async function patchSessionMeta(sessionId, patch, opts = {}) {
1668
+ await host.patchSessionMeta(sessionId, patch, opts);
1669
+ broadcast({
1670
+ kind: "sessions:changed",
1671
+ op: "update",
1672
+ sessionId
1673
+ });
1674
+ }
1675
+ async function patchSession(sessionId, mutate) {
1676
+ const data = await loadOrThrow(store, sessionId);
1677
+ mutate(data);
1678
+ data.updatedAt = Date.now();
1679
+ await store.save(data);
1680
+ broadcast({
1681
+ kind: "sessions:changed",
1682
+ op: "update",
1683
+ sessionId
1684
+ });
1685
+ }
1686
+ async function createSessionRow(params) {
1687
+ const metadata = creationMetadata(params);
1688
+ const onMachine = params.environment?.kind ? host.environments.get(params.environment.kind)?.checkFolder !== void 0 : false;
1689
+ const cwd = workspace?.describe().confined && !onMachine ? workspace.sessionFolder(params.cwd ?? "") : params.cwd;
1690
+ const id = await host.createSession({
1691
+ cwd,
1692
+ ...metadata ? { metadata } : {},
1693
+ ...params.title ? { title: params.title } : {},
1694
+ ...params.agentId ? { agentId: params.agentId } : {},
1695
+ ...params.providerKey && params.modelId ? {
1696
+ providerKey: params.providerKey,
1697
+ modelId: params.modelId
1698
+ } : {},
1699
+ ...params.fallbacks ? { fallbacks: params.fallbacks } : {},
1700
+ ...params.extensionEdit ? { extensionEdit: true } : {},
1701
+ ...params.skillEdit?.name ? { skillEdit: { name: params.skillEdit.name } } : {},
1702
+ ...params.environment?.kind ? { environment: {
1703
+ kind: params.environment.kind,
1704
+ ...params.environment.branch ? { branch: params.environment.branch } : {},
1705
+ ...params.environment.base ? { base: params.environment.base } : {}
1706
+ } } : {}
1707
+ });
1708
+ const data = await loadOrThrow(store, id);
1709
+ broadcast({
1710
+ kind: "sessions:changed",
1711
+ op: "create",
1712
+ sessionId: id
1713
+ });
1714
+ return stamp(toRow(host, data));
1715
+ }
1716
+ return {
1717
+ "sessions.list": async (p) => (await listSessionRows(host, store, p)).map(stamp),
1718
+ "sessions.create": (p) => createSessionRow(p),
1719
+ "sessions.delete": async (p) => {
1720
+ forgetSession(p.sessionId);
1721
+ const { environment } = await host.deleteSession(p.sessionId);
1722
+ broadcast({
1723
+ kind: "sessions:changed",
1724
+ op: "delete",
1725
+ sessionId: p.sessionId
1726
+ });
1727
+ return environment.removed && !environment.reason ? {} : { environment };
1728
+ },
1729
+ "sessions.setTitle": (p) => patchSessionMeta(p.sessionId, { title: p.title }, { bumpUpdatedAt: false }),
1730
+ "sessions.setArchived": (p) => patchSessionMeta(p.sessionId, { archived: p.archived }),
1731
+ "sessions.setAgentId": async (p) => {
1732
+ if (await host.setLiveSessionAgentId(p.sessionId, p.agentId)) broadcast({
1733
+ kind: "sessions:changed",
1734
+ op: "update",
1735
+ sessionId: p.sessionId
1736
+ });
1737
+ else await patchSession(p.sessionId, (d) => {
1738
+ d.agentId = p.agentId;
1739
+ });
1740
+ host.disposeAgentForRebuild(p.sessionId);
1741
+ },
1742
+ "sessions.setEffort": (p) => patchSessionMeta(p.sessionId, { effort: p.effort }),
1743
+ "sessions.setModel": async (p) => {
1744
+ await host.setSessionModel(p.sessionId, {
1745
+ providerKey: p.providerKey,
1746
+ modelId: p.modelId,
1747
+ ...p.fallbacks ? { fallbacks: p.fallbacks } : {},
1748
+ ...p.effort ? { effort: p.effort } : {},
1749
+ ...p.modelOptions ? { modelOptions: p.modelOptions } : {}
1750
+ });
1751
+ broadcast({
1752
+ kind: "sessions:changed",
1753
+ op: "update",
1754
+ sessionId: p.sessionId
1755
+ });
1756
+ },
1757
+ "sessions.setModelOptions": (p) => patchSessionMeta(p.sessionId, { modelOptions: p.options }),
1758
+ "sessions.setFolder": (p) => {
1759
+ const cwd = typeof p.cwd === "string" ? p.cwd : "";
1760
+ return host.setSessionFolder(p.sessionId, cwd);
1761
+ }
1762
+ };
1763
+ }
1764
+ /** The creation-time metadata a client may stamp: the pick's effort and options, pinned skills. */
1765
+ function creationMetadata(params) {
1766
+ const metadata = {};
1767
+ if (typeof params.effort === "string") metadata.effort = params.effort;
1768
+ if (params.modelOptions && typeof params.modelOptions === "object") {
1769
+ const options = Object.fromEntries(Object.entries(params.modelOptions).filter(([, on]) => typeof on === "boolean"));
1770
+ if (Object.keys(options).length > 0) metadata.modelOptions = options;
1771
+ }
1772
+ if (Array.isArray(params.pinnedSkills)) {
1773
+ const pins = readPinnedSkills(params.pinnedSkills.slice(0, 64));
1774
+ if (pins.size > 0) metadata[PINNED_SKILLS_META_KEY] = pinnedSkillsValue(pins);
1775
+ }
1776
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
1777
+ }
1778
+ async function listSessionRows(host, store, p) {
1779
+ const filter = p?.projectRoot !== void 0 ? { projectRoot: p.projectRoot } : void 0;
1780
+ const applyArchive = (rows) => {
1781
+ const filtered = rows.filter((row) => {
1782
+ if (p?.archive === "active") return !row.archived;
1783
+ if (p?.archive === "archived") return row.archived;
1784
+ return true;
1785
+ });
1786
+ filtered.sort((a, b) => b.updatedAt - a.updatedAt);
1787
+ return filtered;
1788
+ };
1789
+ if (store.listMeta) return applyArchive((await store.listMeta(filter)).map((meta) => {
1790
+ const md = meta.metadata;
1791
+ const title = deriveSessionTitle(meta.firstUserTurn ? [meta.firstUserTurn] : [], md);
1792
+ return {
1793
+ id: meta.id,
1794
+ title,
1795
+ projectRoot: meta.projectRoot ?? null,
1796
+ cwd: typeof md.cwd === "string" ? md.cwd : meta.projectRoot ?? "",
1797
+ ...meta.agentId ? { agentId: meta.agentId } : {},
1798
+ status: host.isSessionRunning(meta.id) ? "running" : meta.status === "running" && Date.now() - meta.updatedAt >= 3e5 ? "idle" : meta.status ?? "idle",
1799
+ createdAt: meta.createdAt ?? 0,
1800
+ updatedAt: meta.updatedAt,
1801
+ archived: md.archived === true,
1802
+ ...(() => {
1803
+ const environment = sessionEnvironmentSummary(md);
1804
+ return environment ? { environment } : {};
1805
+ })(),
1806
+ ...typeof md.providerKey === "string" ? { providerKey: md.providerKey } : {},
1807
+ ...typeof md.modelId === "string" ? { modelId: md.modelId } : {},
1808
+ ...typeof md.effort === "string" ? { effort: md.effort } : {},
1809
+ ...md.modelOptions && typeof md.modelOptions === "object" ? { modelOptions: md.modelOptions } : {},
1810
+ ...(() => {
1811
+ const fallbacks = sessionModelFallbacks(md);
1812
+ return fallbacks ? { modelFallbacks: fallbacks } : {};
1813
+ })()
1814
+ };
1815
+ }));
1816
+ const ids = await store.list(filter);
1817
+ const rows = [];
1818
+ for (const id of ids) {
1819
+ const data = await store.load(id);
1820
+ if (!data) continue;
1821
+ rows.push(toRow(host, data));
1822
+ }
1823
+ return applyArchive(rows);
1824
+ }
1825
+ function toRow(host, data) {
1826
+ const meta = data.metadata ?? {};
1827
+ const title = deriveSessionTitle(data.turns, meta);
1828
+ return {
1829
+ id: data.id,
1830
+ title,
1831
+ ...isMainSession(meta) ? { isMain: true } : {},
1832
+ projectRoot: data.projectRoot ?? null,
1833
+ cwd: typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "",
1834
+ ...typeof data.agentId === "string" ? { agentId: data.agentId } : {},
1835
+ status: host.isSessionRunning(data.id) ? "running" : data.status === "running" && Date.now() - data.updatedAt >= 3e5 ? "idle" : data.status,
1836
+ createdAt: data.createdAt,
1837
+ updatedAt: data.updatedAt,
1838
+ archived: meta.archived === true,
1839
+ ...(() => {
1840
+ const environment = sessionEnvironmentSummary(meta);
1841
+ return environment ? { environment } : {};
1842
+ })(),
1843
+ ...typeof meta.providerKey === "string" ? { providerKey: meta.providerKey } : {},
1844
+ ...typeof meta.modelId === "string" ? { modelId: meta.modelId } : {},
1845
+ ...typeof meta.effort === "string" ? { effort: meta.effort } : {},
1846
+ ...meta.modelOptions && typeof meta.modelOptions === "object" ? { modelOptions: meta.modelOptions } : {},
1847
+ ...(() => {
1848
+ const fallbacks = sessionModelFallbacks(meta);
1849
+ return fallbacks ? { modelFallbacks: fallbacks } : {};
1850
+ })()
1851
+ };
1852
+ }
1853
+ //#endregion
1854
+ //#region src/ipc/daemon/handlers/environments.ts
1855
+ function createEnvironmentHandlers({ host, env, setSettings }) {
1856
+ return {
1857
+ "sessions.retryEnvironment": async (p) => {
1858
+ await loadOrThrow(env.store, p.sessionId);
1859
+ await host.retryEnvironment(p.sessionId, { background: true });
1860
+ },
1861
+ "sessions.environmentActions": (p) => host.listEnvironmentActions(p.sessionId),
1862
+ "sessions.runEnvironmentAction": (p) => host.runEnvironmentAction(p.sessionId, p.actionId),
1863
+ "environments.list": (p) => host.listEnvironments(p.cwd ?? "", typeof p.kind === "string" ? p.kind : void 0),
1864
+ "environments.verify": (p) => host.verifyEnvironment(p.kind),
1865
+ "environments.update": async (p, peer) => {
1866
+ const write = applyEnvironmentSettingsChange(host.environments, env.readSettings(), p);
1867
+ for (const secret of write.secrets) host.environmentSecrets.set(p.kind, secret.key, secret.value);
1868
+ await setSettings({ patch: write.settingsPatch }, peer);
1869
+ }
1870
+ };
1871
+ }
1872
+ //#endregion
1873
+ //#region src/ipc/daemon/handlers/extensions.ts
1874
+ function createExtensionHandlers({ env, broadcastExcept }) {
1875
+ return { "extension.emit": async (p, peer) => {
1876
+ const extensionId = typeof p.extensionId === "string" ? p.extensionId : "";
1877
+ if (extensionId.length === 0 || extensionId.length > 256) return;
1878
+ if (typeof p.seq !== "number" || !Number.isFinite(p.seq)) return;
1879
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : void 0;
1880
+ const bytes = serializedPayloadBytes(p.payload);
1881
+ if (bytes === null || bytes > 65536) return;
1882
+ let registry = env.config.extensions;
1883
+ if (sessionId !== void 0) try {
1884
+ const data = await loadOrThrow(env.store, sessionId);
1885
+ const meta = data.metadata ?? {};
1886
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
1887
+ registry = (await env.runtimeConfig(cwd)).extensions;
1888
+ } catch {
1889
+ return;
1890
+ }
1891
+ if (!registry.loaded.some((ext) => !ext.error && ext.name === extensionId && ext.definition?.permissions?.includes("ipc") === true)) return;
1892
+ const nonce = typeof p.nonce === "string" ? p.nonce : void 0;
1893
+ const envelope = {
1894
+ extensionId,
1895
+ ...sessionId !== void 0 ? { sessionId } : {},
1896
+ payload: p.payload,
1897
+ seq: p.seq,
1898
+ ...nonce !== void 0 ? { nonce } : {}
1899
+ };
1900
+ env.extensionBus?.deliver(envelope);
1901
+ broadcastExcept(peer, {
1902
+ kind: "extension:signal",
1903
+ ...envelope
1904
+ });
1905
+ } };
1906
+ }
1907
+ //#endregion
1908
+ //#region src/ipc/daemon/handlers/fs.ts
1909
+ function createFsHandlers({ workspace }) {
1910
+ return {
1911
+ "fs.roots": () => workspace.describe(),
1912
+ "fs.listDirs": (p) => workspace.list(optionalString(p?.path, "path"))
1913
+ };
1914
+ }
1915
+ //#endregion
1916
+ //#region src/ipc/daemon/handlers/history.ts
1917
+ const SEARCH_INDEX_CACHE_MAX = 64;
1918
+ function createHistoryHandlers({ host, store }) {
1919
+ /** Latest `history.searchAll` generation per peer — see the handler. */
1920
+ const searchGenerations = /* @__PURE__ */ new WeakMap();
1921
+ const searchIndexCache = /* @__PURE__ */ new Map();
1922
+ async function sessionSearchIndex(sessionId) {
1923
+ const liveTurns = host.getLiveTurns(sessionId);
1924
+ let turns;
1925
+ let key;
1926
+ if (liveTurns) {
1927
+ turns = [...liveTurns];
1928
+ key = `live:${turns.length}:${turns.at(-1)?.id ?? ""}`;
1929
+ } else {
1930
+ const data = await store.load(sessionId);
1931
+ if (!data) return null;
1932
+ turns = data.turns;
1933
+ key = `store:${data.updatedAt}:${data.turns.length}`;
1934
+ }
1935
+ const cached = searchIndexCache.get(sessionId);
1936
+ if (cached?.key === key) {
1937
+ searchIndexCache.delete(sessionId);
1938
+ searchIndexCache.set(sessionId, cached);
1939
+ return cached.index;
1940
+ }
1941
+ const index = await buildSessionSearchIndexAsync(turns);
1942
+ searchIndexCache.set(sessionId, {
1943
+ key,
1944
+ index
1945
+ });
1946
+ while (searchIndexCache.size > SEARCH_INDEX_CACHE_MAX) {
1947
+ const oldest = searchIndexCache.keys().next().value;
1948
+ if (oldest === void 0) break;
1949
+ searchIndexCache.delete(oldest);
1950
+ }
1951
+ return index;
1952
+ }
1953
+ return {
1954
+ "history.searchSession": async (p) => {
1955
+ const index = await sessionSearchIndex(p.sessionId);
1956
+ if (!index) return [];
1957
+ const limit = Number.isInteger(p.limit) && p.limit > 0 ? Math.min(p.limit, index.turns.length) : index.turns.length;
1958
+ const matches = await searchSessionIndexAsync(index, p.query, { limit });
1959
+ const order = new Map(index.turns.map((entry, i) => [entry.turn.id, i]));
1960
+ matches.sort((a, b) => (order.get(a.turn.id) ?? 0) - (order.get(b.turn.id) ?? 0));
1961
+ return matches;
1962
+ },
1963
+ "history.searchAll": async (p, peer) => {
1964
+ const generation = (searchGenerations.get(peer) ?? 0) + 1;
1965
+ searchGenerations.set(peer, generation);
1966
+ const results = {};
1967
+ const ids = Array.isArray(p.sessionIds) ? p.sessionIds.slice(0, 500) : [];
1968
+ for (const sessionId of ids) {
1969
+ if (searchGenerations.get(peer) !== generation) break;
1970
+ try {
1971
+ const index = await sessionSearchIndex(sessionId);
1972
+ if (!index) continue;
1973
+ const match = await searchSessionHistoryIndexAsync(index, p.query, p.excerptLimit ?? 3);
1974
+ if (match) results[sessionId] = match;
1975
+ } catch {}
1976
+ }
1977
+ return results;
1978
+ }
1979
+ };
1980
+ }
1981
+ //#endregion
1982
+ //#region src/ipc/daemon/handlers/hitl.ts
1983
+ function createHitlHandlers({ host }) {
1984
+ return {
1985
+ "interactions.list": (p) => host.interactions.list(p?.sessionId),
1986
+ "interactions.respond": (p) => host.interactions.respond(p.requestId, p.response),
1987
+ "interactions.cancel": (p) => host.interactions.cancel(p.requestId, p.reason),
1988
+ "interactions.rehydrate": (p) => host.interactions.rehydrate(p.sessionId),
1989
+ "approvals.resolve": (p) => host.approvals.resolve(p.requestId, p.decision),
1990
+ "approvals.denyAll": (p) => host.approvals.denyAll(p?.sessionId),
1991
+ "approvals.listPending": () => host.approvals.listPending()
1992
+ };
1993
+ }
1994
+ //#endregion
1995
+ //#region src/ipc/daemon/handlers/launch-overrides.ts
1996
+ /**
1997
+ * A confined host (a server) drops a `cwd` outside its workspace roots
1998
+ * itself (`ChatHost.setLaunchOverrides`): a window's own folder is a path on
1999
+ * ITS machine, which the session must not move to.
2000
+ */
2001
+ function createLaunchOverrideHandlers({ host }) {
2002
+ /**
2003
+ * Each client's launch overrides per session, oldest setter first. The
2004
+ * session runs the most recent connected setter's; a client clearing, or
2005
+ * disconnecting, drops only its own, and the previous setter's apply again.
2006
+ */
2007
+ const launchOverridesByPeer = /* @__PURE__ */ new Map();
2008
+ /** Run the latest connected setter's overrides (see `launchOverridesByPeer`). */
2009
+ const applyLaunchOverrides = (sessionId) => {
2010
+ const setters = launchOverridesByPeer.get(sessionId);
2011
+ const latest = setters ? [...setters.values()].at(-1) : void 0;
2012
+ if (!latest) launchOverridesByPeer.delete(sessionId);
2013
+ host.setLaunchOverrides(sessionId, latest ?? null);
2014
+ };
2015
+ return {
2016
+ methods: { "sessions.setLaunchOverrides": (p, peer) => {
2017
+ const { sessionId, ...rest } = p;
2018
+ const next = normalizeLaunchOverrides(rest);
2019
+ const setters = launchOverridesByPeer.get(sessionId) ?? /* @__PURE__ */ new Map();
2020
+ setters.delete(peer);
2021
+ if (next) setters.set(peer, next);
2022
+ launchOverridesByPeer.set(sessionId, setters);
2023
+ applyLaunchOverrides(sessionId);
2024
+ } },
2025
+ releasePeer(peer) {
2026
+ for (const [sessionId, setters] of launchOverridesByPeer) if (setters.delete(peer)) applyLaunchOverrides(sessionId);
2027
+ },
2028
+ forget(sessionId) {
2029
+ launchOverridesByPeer.delete(sessionId);
2030
+ }
2031
+ };
2032
+ }
2033
+ //#endregion
2034
+ //#region src/ipc/daemon/handlers/machine-serve.ts
2035
+ /**
2036
+ * Executors kept between their connections (tasks, terminals), keyed by the
2037
+ * token that attached them and the host id it named: a host id is the
2038
+ * client's word, so another token naming it gets an executor of its own,
2039
+ * never the first one's tasks, terminals or exits. Each token holds at most
2040
+ * {@link MAX_PER_PRINCIPAL} and evicts only its own idle ones.
2041
+ */
2042
+ const MAX_PER_PRINCIPAL = 4;
2043
+ const MAX_ATTACHED_HOSTS = 32;
2044
+ var MachineServeError = class extends Error {
2045
+ code = MachineErrorCode.MethodNotAllowed;
2046
+ };
2047
+ /** A capacity limit: the client retries later (`../../machine/reverse.ts`). */
2048
+ var MachineServeBusyError = class extends MachineServeError {
2049
+ code = MachineErrorCode.Busy;
2050
+ };
2051
+ /** Who attached: the token's id (`''` on a host without auth, a loopback test server). */
2052
+ const principalOf = (peer) => peer.principal ?? "";
2053
+ function createMachineServeHandlers(input) {
2054
+ const { host } = input;
2055
+ const allowed = () => input.options?.allowed() === true;
2056
+ const served = /* @__PURE__ */ new Map();
2057
+ const byPeer = /* @__PURE__ */ new Map();
2058
+ const roots = () => {
2059
+ const { roots: list, confined } = host.workspace;
2060
+ if (!confined || list.length === 0) throw new MachineServeError("This host has no workspace roots to lend.");
2061
+ return [...list];
2062
+ };
2063
+ /** Make room for one more of `principal`'s: its own idle executor goes, never another token's. */
2064
+ const makeRoom = (principal) => {
2065
+ const own = [...served].filter(([, entry]) => entry.principal === principal);
2066
+ if (own.length < MAX_PER_PRINCIPAL && served.size < MAX_ATTACHED_HOSTS) return;
2067
+ const idle = own.find(([, entry]) => entry.peer === null);
2068
+ if (!idle) throw new MachineServeBusyError(own.length >= MAX_PER_PRINCIPAL ? `This token already has ${MAX_PER_PRINCIPAL} hosts attached here.` : `This server already lends itself to ${MAX_ATTACHED_HOSTS} hosts.`);
2069
+ served.delete(idle[0]);
2070
+ idle[1].executor.close();
2071
+ };
2072
+ async function executorFor(hostId, peer) {
2073
+ const principal = principalOf(peer);
2074
+ const key = `${principal}:${hostId}`;
2075
+ const existing = served.get(key);
2076
+ if (existing) {
2077
+ const previous = existing.peer;
2078
+ if (previous && previous !== peer) byPeer.delete(previous);
2079
+ existing.peer = peer;
2080
+ return existing.executor;
2081
+ }
2082
+ makeRoom(principal);
2083
+ const { createMachineExecutor } = await import("./executor-Cx5-8yqi.js");
2084
+ const entry = {
2085
+ peer,
2086
+ principal,
2087
+ executor: void 0
2088
+ };
2089
+ entry.executor = createMachineExecutor({
2090
+ roots: roots().map((path) => ({ path })),
2091
+ dataDir: join(input.dataDir(), "machine-self"),
2092
+ sandbox: input.settings(),
2093
+ credentialsDataDir: input.dataDir(),
2094
+ notify: (method, params) => {
2095
+ const target = entry.peer;
2096
+ if (!target || !input.isConnected(target)) return false;
2097
+ target.send({
2098
+ kind: "machine:notification",
2099
+ method,
2100
+ params
2101
+ });
2102
+ return true;
2103
+ }
2104
+ });
2105
+ served.set(key, entry);
2106
+ return entry.executor;
2107
+ }
2108
+ const attachedOf = (peer) => {
2109
+ const key = byPeer.get(peer);
2110
+ const entry = key ? served.get(key) : void 0;
2111
+ if (!entry || entry.peer !== peer) throw new MachineServeError("Send machine.attach first on this connection.");
2112
+ return entry;
2113
+ };
2114
+ return {
2115
+ allowed,
2116
+ methods: {
2117
+ "machine.attach": async (p, peer) => {
2118
+ if (!allowed()) throw new MachineServeError("This server does not lend itself as a machine (allowAsMachine is off).");
2119
+ if (typeof p.hostId !== "string" || !/^[\w-]{1,64}$/.test(p.hostId)) throw new MachineServeError("machine.attach: \"hostId\" must be 1 to 64 letters, digits, \"-\" or \"_\".");
2120
+ const executor = await executorFor(p.hostId, peer);
2121
+ byPeer.set(peer, `${principalOf(peer)}:${p.hostId}`);
2122
+ executor.linkUp();
2123
+ const { machineIdentity } = await import("./identity-CwfO8K-t.js").then((n) => n.t);
2124
+ return {
2125
+ machineId: machineIdentity(join(input.dataDir(), "machine-self")),
2126
+ name: toMachineName(input.hostName),
2127
+ roots: roots().map((path) => ({ path })),
2128
+ capabilities: {
2129
+ shell: true,
2130
+ terminals: typeof globalThis.Bun !== "undefined",
2131
+ background: true,
2132
+ worktrees: true,
2133
+ stream: true,
2134
+ sandboxed: executor.sandbox.on
2135
+ },
2136
+ version: ZIDANE_VERSION,
2137
+ protocolVersion: 1
2138
+ };
2139
+ },
2140
+ "machine.call": async (p, peer) => {
2141
+ if (!allowed()) throw new MachineServeError("This server does not lend itself as a machine (allowAsMachine is off).");
2142
+ if (typeof p.method !== "string") throw new MachineServeError("machine.call: \"method\" must be a string.");
2143
+ return await attachedOf(peer).executor.handle(p.method, p.params ?? {});
2144
+ },
2145
+ "machine.detach": (_p, peer) => {
2146
+ const key = byPeer.get(peer);
2147
+ if (!key) return { detached: false };
2148
+ byPeer.delete(peer);
2149
+ const entry = served.get(key);
2150
+ if (entry?.peer === peer) {
2151
+ entry.peer = null;
2152
+ entry.executor.linkLost();
2153
+ }
2154
+ return { detached: true };
2155
+ }
2156
+ },
2157
+ releasePeer(peer) {
2158
+ const key = byPeer.get(peer);
2159
+ if (!key) return;
2160
+ byPeer.delete(peer);
2161
+ const entry = served.get(key);
2162
+ if (entry?.peer === peer) {
2163
+ entry.peer = null;
2164
+ entry.executor.linkLost();
2165
+ }
2166
+ }
2167
+ };
2168
+ }
2169
+ //#endregion
2170
+ //#region src/ipc/daemon/handlers/oauth-flows.ts
2171
+ /** How long a flow waits for its code. */
2172
+ const OAUTH_FLOW_TTL_MS = 6e5;
2173
+ /** Open flows per connection. */
2174
+ const MAX_FLOWS_PER_PEER = 4;
2175
+ /** How long a login may take to produce its URL. */
2176
+ const BEGIN_TIMEOUT_MS = 3e4;
2177
+ function stepChannel(signal) {
2178
+ let current;
2179
+ const waiters = [];
2180
+ return {
2181
+ ask: (step) => new Promise((resolve, reject) => {
2182
+ if (signal.aborted) return reject(/* @__PURE__ */ new Error("The sign-in was cancelled."));
2183
+ const full = {
2184
+ ...step,
2185
+ id: randomBytes(9).toString("base64url")
2186
+ };
2187
+ current = {
2188
+ step: full,
2189
+ resolve
2190
+ };
2191
+ signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("The sign-in was cancelled.")), { once: true });
2192
+ for (const waiter of waiters.splice(0)) waiter(full);
2193
+ }),
2194
+ pending: () => current?.step,
2195
+ next: () => current ? Promise.resolve(current.step) : new Promise((resolve) => waiters.push(resolve)),
2196
+ answer(stepId, value) {
2197
+ const step = current?.step;
2198
+ if (!step || step.id !== stepId) throw new Error("That sign-in is not waiting on this question (answered already, or another one).");
2199
+ if (step.kind === "select" && value !== void 0 && !step.options.some((option) => option.id === value)) throw new InvalidParamsError(`"${value}" is not one of the choices.`);
2200
+ if (step.kind === "prompt" && (value === void 0 || value.trim() === "" && !step.allowEmpty)) throw new InvalidParamsError("That question needs an answer.");
2201
+ const { resolve } = current;
2202
+ current = void 0;
2203
+ resolve(value);
2204
+ }
2205
+ };
2206
+ }
2207
+ function deferred() {
2208
+ let resolve;
2209
+ return {
2210
+ promise: new Promise((r) => {
2211
+ resolve = r;
2212
+ }),
2213
+ resolve
2214
+ };
2215
+ }
2216
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
2217
+ "127.0.0.1",
2218
+ "localhost",
2219
+ "[::1]",
2220
+ "::1"
2221
+ ]);
2222
+ /** `http://<loopback>:<port>/<path>`, nothing else: where an MCP sign-in may send the browser. */
2223
+ function loopbackRedirect(raw) {
2224
+ let url;
2225
+ try {
2226
+ url = typeof raw === "string" ? new URL(raw) : void 0;
2227
+ } catch {}
2228
+ if (!url || url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port || url.username || url.password || url.search || url.hash) throw new InvalidParamsError("redirectUri must be a loopback URL on the client (http://127.0.0.1:<port>/callback).");
2229
+ return url.toString();
2230
+ }
2231
+ /** `state` in what a user pasted: a callback URL, a `code=…&state=…` query, or Anthropic's `code#state`. */
2232
+ function stateIn(input) {
2233
+ const value = input.trim();
2234
+ try {
2235
+ return new URL(value).searchParams.get("state") ?? void 0;
2236
+ } catch {}
2237
+ if (value.includes("#")) return value.split("#", 2)[1];
2238
+ if (value.includes("state=")) return new URLSearchParams(value).get("state") ?? void 0;
2239
+ }
2240
+ /** The code in a pasted callback URL / query / `code#state`. */
2241
+ function codeIn(input) {
2242
+ const value = input.trim();
2243
+ try {
2244
+ return new URL(value).searchParams.get("code") ?? void 0;
2245
+ } catch {}
2246
+ if (value.includes("#")) return value.split("#", 2)[0];
2247
+ if (value.includes("code=")) return new URLSearchParams(value).get("code") ?? void 0;
2248
+ return value || void 0;
2249
+ }
2250
+ function isLoopbackUrl(raw) {
2251
+ if (!raw) return false;
2252
+ try {
2253
+ const url = new URL(raw);
2254
+ return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
2255
+ } catch {
2256
+ return false;
2257
+ }
2258
+ }
2259
+ /** What a provider's manual-input path reads: the pasted text, or the callback URL rebuilt from a code + state. */
2260
+ function manualInput(delivery, redirectUri) {
2261
+ if ("input" in delivery) return delivery.input;
2262
+ const query = new URLSearchParams({
2263
+ code: delivery.code,
2264
+ ...delivery.state !== void 0 ? { state: delivery.state } : {}
2265
+ }).toString();
2266
+ return redirectUri ? `${redirectUri}?${query}` : query;
2267
+ }
2268
+ /** First of: what the login produced, its early end, a timeout, the abort. */
2269
+ async function firstOf(started, login, signal) {
2270
+ let timer;
2271
+ try {
2272
+ return await Promise.race([
2273
+ started,
2274
+ login.then(() => {
2275
+ throw new Error("The sign-in finished without a browser step (already signed in?).");
2276
+ }),
2277
+ new Promise((_, reject) => {
2278
+ timer = setTimeout(reject, BEGIN_TIMEOUT_MS, /* @__PURE__ */ new Error(`The sign-in did not produce an authorization URL within ${BEGIN_TIMEOUT_MS / 1e3}s.`));
2279
+ signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("The sign-in was cancelled.")), { once: true });
2280
+ })
2281
+ ]);
2282
+ } finally {
2283
+ clearTimeout(timer);
2284
+ }
2285
+ }
2286
+ /**
2287
+ * A provider's login, running here for a client: its URL (once known), its
2288
+ * questions, the code the client delivers, its stored result. Refused for a
2289
+ * provider that would listen on this machine (see the module header).
2290
+ */
2291
+ function startProviderLogin(auth, provider) {
2292
+ const descriptor = auth.descriptorOf(provider);
2293
+ if (!descriptor.oauthProvider) throw new Error(`OAuth not supported for ${descriptor.label} (${descriptor.key}). Use an API key instead.`);
2294
+ if (!canSignInRemotely(descriptor.oauthProvider)) throw new Error(`${descriptor.label}'s sign-in can't run from another computer: it waits for the browser on a port of the server's own machine. Sign in on the server machine or use an API key.`);
2295
+ const abort = new AbortController();
2296
+ const code = deferred();
2297
+ const steps = stepChannel(abort.signal);
2298
+ const url = deferred();
2299
+ const login = runOAuthLogin(descriptor, {
2300
+ onUrl: (u, instructions) => url.resolve({
2301
+ url: u,
2302
+ ...instructions ? { instructions } : {}
2303
+ }),
2304
+ onDeviceCode: (info) => url.resolve({
2305
+ url: info.verificationUri,
2306
+ userCode: info.userCode,
2307
+ device: true
2308
+ }),
2309
+ onManualCodeInput: () => code.promise,
2310
+ onPrompt: (prompt) => steps.ask({
2311
+ kind: "prompt",
2312
+ message: prompt.message,
2313
+ ...prompt.placeholder ? { placeholder: prompt.placeholder } : {},
2314
+ ...prompt.allowEmpty ? { allowEmpty: true } : {}
2315
+ }).then((value) => value ?? ""),
2316
+ onSelect: (prompt) => steps.ask({
2317
+ kind: "select",
2318
+ message: prompt.message,
2319
+ options: prompt.options.map((option) => ({
2320
+ id: option.id,
2321
+ label: option.label
2322
+ }))
2323
+ }),
2324
+ signal: abort.signal,
2325
+ callbackServer: false,
2326
+ openBrowser: false
2327
+ });
2328
+ login.catch(() => void 0);
2329
+ let redirectUri;
2330
+ const started = url.promise.then((first) => {
2331
+ if (!first.device) {
2332
+ const redirect = new URL(first.url).searchParams.get("redirect_uri");
2333
+ if (isLoopbackUrl(redirect)) redirectUri = redirect;
2334
+ }
2335
+ return {
2336
+ url: first.url,
2337
+ mode: first.device ? "device" : redirectUri ? "loopback" : "browser",
2338
+ ...redirectUri ? { redirectUri } : {},
2339
+ ...first.userCode ? { userCode: first.userCode } : {},
2340
+ ...first.instructions ? { instructions: first.instructions } : {}
2341
+ };
2342
+ });
2343
+ started.catch(() => void 0);
2344
+ return {
2345
+ login,
2346
+ abort,
2347
+ result: login.then((credentials) => {
2348
+ setProviderCredential(auth.dataDir(), descriptor, {
2349
+ kind: "oauth",
2350
+ ...credentials
2351
+ });
2352
+ auth.applied(descriptor);
2353
+ return { provider: auth.statusOf(descriptor.key) };
2354
+ }),
2355
+ steps,
2356
+ started,
2357
+ deliver: (delivery) => code.resolve(manualInput(delivery, redirectUri))
2358
+ };
2359
+ }
2360
+ /** The flow `flowId` names, if it exists and belongs to this connection. */
2361
+ function flowOf(flows, flowId, peer) {
2362
+ const flow = typeof flowId === "string" ? flows.get(flowId) : void 0;
2363
+ if (!flow) throw new Error("No sign-in with that id: it finished, expired or was cancelled. Start it again.");
2364
+ if (flow.peer !== peer) throw new Error("That sign-in belongs to another connection.");
2365
+ return flow;
2366
+ }
2367
+ /** Hand the flow its code (from the client's loopback) or pasted input, once, after checking its `state`. */
2368
+ function deliverCode(flow, p, end) {
2369
+ const code = typeof p.code === "string" && p.code !== "" ? p.code : void 0;
2370
+ const input = typeof p.input === "string" && p.input.trim() !== "" ? p.input : void 0;
2371
+ if (code === void 0 && input === void 0) return;
2372
+ if (flow.delivered) throw new Error("That sign-in already received its code.");
2373
+ const state = code !== void 0 ? typeof p.state === "string" ? p.state : void 0 : stateIn(input);
2374
+ if (flow.expectedState !== void 0 && (state !== void 0 || code !== void 0) && state !== flow.expectedState) {
2375
+ end(flow, "OAuth state mismatch.");
2376
+ throw new Error("OAuth state mismatch: the sign-in was refused. Start it again.");
2377
+ }
2378
+ flow.delivered = true;
2379
+ flow.deliver(code !== void 0 ? {
2380
+ code,
2381
+ ...state !== void 0 ? { state } : {}
2382
+ } : { input });
2383
+ }
2384
+ function createOAuthFlowHandlers({ host, env, auth, isConnected, ttlMs = OAUTH_FLOW_TTL_MS }) {
2385
+ const flows = /* @__PURE__ */ new Map();
2386
+ const end = (flow, reason) => {
2387
+ clearTimeout(flow.timer);
2388
+ flows.delete(flow.id);
2389
+ if (!flow.abort.signal.aborted) flow.abort.abort(new Error(reason));
2390
+ };
2391
+ /** Register a flow whose login is already running; it ends at expiry, on cancel, or once settled and collected. */
2392
+ function register(peer, parts) {
2393
+ const id = randomBytes(24).toString("base64url");
2394
+ const flow = {
2395
+ id,
2396
+ peer,
2397
+ expiresAt: Date.now() + ttlMs,
2398
+ delivered: false,
2399
+ ...parts,
2400
+ timer: void 0
2401
+ };
2402
+ flow.timer = setTimeout(end, ttlMs, flow, "The sign-in expired.");
2403
+ flow.timer.unref?.();
2404
+ flow.result.catch(() => void 0);
2405
+ flows.set(id, flow);
2406
+ return flow;
2407
+ }
2408
+ const ownFlows = (peer) => [...flows.values()].filter((flow) => flow.peer === peer);
2409
+ /** MCP begins in flight per connection: counted against the cap before their flow registers. */
2410
+ const startingMcp = /* @__PURE__ */ new Map();
2411
+ async function beginProvider(peer, provider) {
2412
+ const { login, ...parts } = startProviderLogin(auth, provider);
2413
+ const flow = register(peer, parts);
2414
+ try {
2415
+ return await stateOf(flow, login);
2416
+ } catch (err) {
2417
+ end(flow, "The sign-in did not start.");
2418
+ throw err;
2419
+ }
2420
+ }
2421
+ /** The flow's URL, or the question it waits on first; its `state` and redirect recorded once the URL is known. */
2422
+ async function stateOf(flow, login) {
2423
+ const steps = flow.steps;
2424
+ const next = await firstOf(Promise.race([flow.started.then((begin) => ({ begin })), steps.next().then((step) => ({ step }))]), login, flow.abort.signal);
2425
+ if ("step" in next) return {
2426
+ flowId: flow.id,
2427
+ url: "",
2428
+ mode: "step",
2429
+ step: next.step,
2430
+ expiresAt: flow.expiresAt
2431
+ };
2432
+ const begin = next.begin;
2433
+ flow.begun = begin;
2434
+ if (begin.mode !== "device") {
2435
+ flow.expectedState = new URL(begin.url).searchParams.get("state") ?? void 0;
2436
+ if (begin.redirectUri) flow.redirectUri = begin.redirectUri;
2437
+ }
2438
+ return {
2439
+ flowId: flow.id,
2440
+ ...begin,
2441
+ expiresAt: flow.expiresAt
2442
+ };
2443
+ }
2444
+ async function beginMcp(peer, p) {
2445
+ const redirectUri = loopbackRedirect(p.redirectUri);
2446
+ const dir = catalogFolder({
2447
+ env,
2448
+ host
2449
+ }, p.cwd, p.kind);
2450
+ const config = await env.runtimeConfig(dir);
2451
+ const settings = env.readSettings();
2452
+ const { servers } = discoverProjectMcps({
2453
+ cwd: dir,
2454
+ prefix: config.prefix,
2455
+ foreignAgents: settings.importForeignConfigs === true,
2456
+ ruler: settings.importRulerConfigs !== false
2457
+ });
2458
+ const entry = servers.find((server) => server.config.name === p.mcpServer)?.config;
2459
+ if (!entry) throw new Error(`No MCP server "${p.mcpServer}" in that folder's catalog.`);
2460
+ const abort = new AbortController();
2461
+ const state = randomBytes(24).toString("base64url");
2462
+ const callbackCode = deferred();
2463
+ const started = deferred();
2464
+ const login = loginMcpServer(entry, {
2465
+ store: env.mcpCredentialStore?.() ?? createFileMcpCredentialStore(env.userDataDir()),
2466
+ signal: abort.signal,
2467
+ state,
2468
+ timeoutMs: ttlMs,
2469
+ callback: {
2470
+ redirectUri,
2471
+ promise: callbackCode.promise,
2472
+ close: async () => void 0
2473
+ },
2474
+ onAuthorizationUrl: (url) => started.resolve({ url: url.toString() })
2475
+ });
2476
+ login.catch(() => void 0);
2477
+ const first = await firstOf(started.promise, login, abort.signal).catch((err) => {
2478
+ abort.abort();
2479
+ throw err;
2480
+ });
2481
+ const flow = register(peer, {
2482
+ abort,
2483
+ result: login.then(() => {
2484
+ host.rebuildAllAgents({ rediscover: true }).catch(() => void 0);
2485
+ return { mcpServer: entry.name };
2486
+ }),
2487
+ expectedState: state,
2488
+ redirectUri,
2489
+ deliver: (delivery) => {
2490
+ if ("input" in delivery) {
2491
+ const code = codeIn(delivery.input);
2492
+ const pastedState = stateIn(delivery.input);
2493
+ if (code) callbackCode.resolve({
2494
+ code,
2495
+ ...pastedState !== void 0 ? { state: pastedState } : {}
2496
+ });
2497
+ return;
2498
+ }
2499
+ callbackCode.resolve(delivery);
2500
+ }
2501
+ });
2502
+ return {
2503
+ flowId: flow.id,
2504
+ url: first.url,
2505
+ mode: "loopback",
2506
+ redirectUri,
2507
+ expiresAt: flow.expiresAt
2508
+ };
2509
+ }
2510
+ return {
2511
+ methods: {
2512
+ "auth.oauth.begin": async (p, peer) => {
2513
+ const target = [p.provider, p.mcpServer].filter((value) => value !== void 0);
2514
+ if (target.length !== 1 || typeof target[0] !== "string" || target[0] === "") throw new InvalidParamsError("auth.oauth.begin needs exactly one of \"provider\" or \"mcpServer\".");
2515
+ if (ownFlows(peer).length + (startingMcp.get(peer) ?? 0) >= MAX_FLOWS_PER_PEER) throw new Error(`This connection already has ${MAX_FLOWS_PER_PEER} sign-ins open: finish or cancel one first.`);
2516
+ let begun;
2517
+ if (p.provider !== void 0) begun = await beginProvider(peer, p.provider);
2518
+ else {
2519
+ startingMcp.set(peer, (startingMcp.get(peer) ?? 0) + 1);
2520
+ try {
2521
+ begun = await beginMcp(peer, {
2522
+ mcpServer: p.mcpServer,
2523
+ ...p.cwd !== void 0 ? { cwd: p.cwd } : {},
2524
+ ...p.kind !== void 0 ? { kind: p.kind } : {},
2525
+ ...p.redirectUri !== void 0 ? { redirectUri: p.redirectUri } : {}
2526
+ });
2527
+ } finally {
2528
+ const left = (startingMcp.get(peer) ?? 1) - 1;
2529
+ if (left > 0) startingMcp.set(peer, left);
2530
+ else startingMcp.delete(peer);
2531
+ }
2532
+ }
2533
+ if (!isConnected(peer)) {
2534
+ const flow = flows.get(begun.flowId);
2535
+ if (flow) end(flow, "The connection closed.");
2536
+ }
2537
+ return begun;
2538
+ },
2539
+ "auth.oauth.complete": async (p, peer) => {
2540
+ const flow = flowOf(flows, p.flowId, peer);
2541
+ deliverCode(flow, p, end);
2542
+ let asking = false;
2543
+ try {
2544
+ const outcome = await (flow.steps ? Promise.race([flow.result, flow.steps.next().then((step) => ({ step }))]) : flow.result);
2545
+ asking = outcome.step !== void 0;
2546
+ return outcome;
2547
+ } catch (err) {
2548
+ throw new Error(flow.abort.signal.aborted && flow.abort.signal.reason instanceof Error ? flow.abort.signal.reason.message : err instanceof Error ? err.message : String(err));
2549
+ } finally {
2550
+ if (!asking) end(flow, "The sign-in finished.");
2551
+ }
2552
+ },
2553
+ "auth.oauth.respond": async (p, peer) => {
2554
+ const flow = flowOf(flows, p.flowId, peer);
2555
+ if (!flow.steps || typeof p.stepId !== "string") throw new InvalidParamsError("auth.oauth.respond needs the flow's \"stepId\".");
2556
+ if (p.value !== void 0 && typeof p.value !== "string") throw new InvalidParamsError("\"value\" must be a string.");
2557
+ const cancelled = p.value === void 0 && flow.steps.pending()?.kind === "select";
2558
+ flow.steps.answer(p.stepId, p.value);
2559
+ if (cancelled) {
2560
+ end(flow, "The sign-in was cancelled.");
2561
+ throw new Error("The sign-in was cancelled (no choice made).");
2562
+ }
2563
+ return flow.begun ? {
2564
+ flowId: flow.id,
2565
+ ...flow.begun,
2566
+ expiresAt: flow.expiresAt
2567
+ } : stateOf(flow, flow.result);
2568
+ },
2569
+ "auth.oauth.cancel": (p, peer) => {
2570
+ const flow = typeof p.flowId === "string" ? flows.get(p.flowId) : void 0;
2571
+ if (!flow || flow.peer !== peer) return { cancelled: false };
2572
+ end(flow, "The sign-in was cancelled.");
2573
+ return { cancelled: true };
2574
+ }
2575
+ },
2576
+ releasePeer(peer) {
2577
+ for (const flow of ownFlows(peer)) end(flow, "The connection closed.");
2578
+ }
2579
+ };
2580
+ }
2581
+ //#endregion
2582
+ //#region src/ipc/daemon/handlers/orchestration.ts
2583
+ function createOrchestrationHandlers({ host }) {
2584
+ return {
2585
+ "orchestration.main": () => host.orchestration.ensureMain(),
2586
+ "orchestration.createWorker": ({ mainId, ...params }) => host.orchestration.createWorker(mainId, params),
2587
+ "orchestration.list": (p) => host.orchestration.list(p.mainId, p.limit),
2588
+ "orchestration.read": async (p) => await host.orchestration.canOrchestrate(p.mainId, p.sessionId) ? host.orchestration.read(p.sessionId) : null,
2589
+ "orchestration.send": (p) => host.orchestration.send(p.mainId, p.sessionId, p.prompt, p.wake),
2590
+ "orchestration.steer": (p) => host.orchestration.steer(p.mainId, p.sessionId, p.message),
2591
+ "orchestration.cancel": (p) => host.orchestration.cancel(p.mainId, p.sessionId),
2592
+ "orchestration.transcript": (p) => host.orchestration.transcript(p.mainId, p.sessionId, p.limit)
2593
+ };
2594
+ }
2595
+ //#endregion
2596
+ //#region src/ipc/daemon/handlers/presence.ts
2597
+ function createPresenceHandlers({ env, peers }) {
2598
+ return {
2599
+ "presence.list": () => peers.computePresence(),
2600
+ "presence.watch": (p, peer) => {
2601
+ peer.watching = p.sessionId;
2602
+ peers.broadcastPresence();
2603
+ env.store.load(p.sessionId).then((data) => {
2604
+ if (!data) return;
2605
+ const meta = data.metadata ?? {};
2606
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
2607
+ return env.runtimeConfig(cwd);
2608
+ }).catch(() => void 0);
2609
+ }
2610
+ };
2611
+ }
2612
+ //#endregion
2613
+ //#region src/ipc/daemon/handlers/queue.ts
2614
+ function createQueueHandlers({ host, clearDraft }) {
2615
+ return {
2616
+ "queue.list": (p) => host.queueList(p.sessionId),
2617
+ "queue.add": async (p, peer) => {
2618
+ const text = typeof p.text === "string" ? p.text.slice(0, 65536) : "";
2619
+ const activatedSkills = Array.isArray(p.activatedSkills) ? p.activatedSkills.filter((name) => typeof name === "string") : void 0;
2620
+ const attachments = Array.isArray(p.attachments) ? boundAttachments(p.attachments) : void 0;
2621
+ const asCommand = typeof p.asCommand === "string" ? p.asCommand.slice(0, 128) : void 0;
2622
+ const result = await host.queueAdd(p.sessionId, text, activatedSkills, attachments, asCommand);
2623
+ if (p.keepDraft !== true) clearDraft(p.sessionId, peer);
2624
+ return result;
2625
+ },
2626
+ "queue.remove": (p) => host.queueRemove(p.sessionId, p.id),
2627
+ "queue.update": (p) => host.queueUpdate(p.sessionId, p.id, typeof p.text === "string" ? p.text.slice(0, 65536) : "", updatedAttachments(p.attachments)),
2628
+ "queue.attachments": (p) => host.queueAttachments(p.sessionId, p.id),
2629
+ "queue.pushNow": (p) => host.queuePushNow(p.sessionId, p.id)
2630
+ };
2631
+ }
2632
+ /**
2633
+ * The list `queue.update` stores in place of an entry's attachments. An entry
2634
+ * that is malformed or over the budget is refused, not dropped: the list
2635
+ * REPLACES what is stored, so a quietly shorter list would delete the rest.
2636
+ */
2637
+ function updatedAttachments(list) {
2638
+ if (!Array.isArray(list)) return void 0;
2639
+ const bounded = boundAttachments(list);
2640
+ if (bounded.length !== list.length) throw new InvalidParamsError("attachments: an entry is malformed, or the list is over 24 MiB.");
2641
+ return bounded;
2642
+ }
2643
+ //#endregion
2644
+ //#region src/ipc/daemon/handlers/resume.ts
2645
+ function createResumeHandlers({ log }) {
2646
+ return { "sessions.resume": (p) => {
2647
+ if (p.epoch !== log.epoch) return {
2648
+ ok: false,
2649
+ reason: "epoch"
2650
+ };
2651
+ const signals = typeof p.afterSeq === "number" ? log.replay(p.sessionId, p.afterSeq) : null;
2652
+ return signals ? {
2653
+ ok: true,
2654
+ signals
2655
+ } : {
2656
+ ok: false,
2657
+ reason: "gap"
2658
+ };
2659
+ } };
2660
+ }
2661
+ //#endregion
2662
+ //#region src/ipc/daemon/handlers/session-reads.ts
2663
+ function pageBound(value, name, fallback, max) {
2664
+ return Math.min(nonNegativeInt(value, name) ?? fallback, max);
2665
+ }
2666
+ function createSessionReadHandlers({ host, store, snapshots }) {
2667
+ /** The document as the host holds it: the live copy (ahead of the store mid-run, and holding the todos), else the stored one. */
2668
+ const documentOf = async (sessionId) => {
2669
+ const live = host.getLiveSession(sessionId);
2670
+ if (live) return live.toJSON();
2671
+ const loaded = await loadSession(store, sessionId, { persistHeal: false });
2672
+ return loaded ? loaded.toJSON() : null;
2673
+ };
2674
+ return {
2675
+ "sessions.listMeta": (p) => {
2676
+ const projectRoot = p?.projectRoot;
2677
+ if (projectRoot !== void 0 && projectRoot !== null && typeof projectRoot !== "string") throw new InvalidParamsError("projectRoot must be a string or null.");
2678
+ return listSessionMeta(store, projectRoot !== void 0 ? { projectRoot } : void 0);
2679
+ },
2680
+ "sessions.getMeta": (p) => getSessionMeta(store, sessionIdOf(p)),
2681
+ "sessions.get": async (p) => {
2682
+ const sessionId = sessionIdOf(p);
2683
+ if (p.turns !== false) return documentOf(sessionId);
2684
+ const snapshot = await snapshots.document(store, sessionId, host.getLiveSession(sessionId));
2685
+ return snapshot && {
2686
+ ...snapshot.data,
2687
+ turns: [],
2688
+ version: snapshot.version
2689
+ };
2690
+ },
2691
+ "sessions.getTurns": async (p) => {
2692
+ const sessionId = sessionIdOf(p);
2693
+ const snapshot = await snapshots.document(store, sessionId, host.getLiveSession(sessionId));
2694
+ if (!snapshot) return null;
2695
+ const from = pageBound(p.from, "from", 0, Number.MAX_SAFE_INTEGER);
2696
+ const limit = Math.max(1, pageBound(p.limit, "limit", 200, SESSION_TURNS_MAX_PAGE));
2697
+ const { turns } = snapshot.data;
2698
+ return {
2699
+ turns: turns.slice(from, from + limit),
2700
+ total: turns.length,
2701
+ version: snapshot.version
2702
+ };
2703
+ },
2704
+ "sessions.getToolResult": async (p) => {
2705
+ const sessionId = sessionIdOf(p);
2706
+ if (typeof p.callId !== "string" || p.callId.length === 0) throw new InvalidParamsError("callId must be a non-empty string.");
2707
+ const data = await documentOf(sessionId);
2708
+ if (!data) return null;
2709
+ const named = typeof p.turnId === "string" ? data.turns.filter((turn) => turn.id === p.turnId) : [];
2710
+ for (const turn of [...named, ...[...data.turns].reverse()]) for (const block of turn.content) if (block.type === "tool_result" && block.callId === p.callId) return toolResultText(block.output);
2711
+ return null;
2712
+ },
2713
+ "sessions.getHeader": async (p) => {
2714
+ const sessionId = sessionIdOf(p);
2715
+ const live = host.getLiveSession(sessionId);
2716
+ if (p.usage !== true) return live ? headerFromData(live.toJSON()) : getSessionHeader(store, sessionId);
2717
+ const data = live ? live.toJSON() : await store.load(sessionId);
2718
+ if (!data) return null;
2719
+ return {
2720
+ ...live ? headerFromData(data) : await getSessionHeader(store, sessionId) ?? headerFromData({
2721
+ ...data,
2722
+ id: sessionId
2723
+ }),
2724
+ contextTokens: lastContextSizeFromTurns(data.turns, data.runs),
2725
+ costUsd: sumRunCosts(data.runs)
2726
+ };
2727
+ },
2728
+ "sessions.setExtensionState": async (p) => {
2729
+ const sessionId = sessionIdOf(p);
2730
+ if (typeof p.extension !== "string" || typeof p.key !== "string") throw new InvalidParamsError("extension and key must be strings.");
2731
+ const session = host.getLiveSession(sessionId) ?? await loadSession(store, sessionId, { persistHeal: false });
2732
+ if (!session) throw new Error(`Session "${sessionId}" not found.`);
2733
+ if (p.value === void 0) await deleteExtensionSessionState(session, p.extension, p.key);
2734
+ else await setExtensionSessionState(session, p.extension, p.key, p.value);
2735
+ }
2736
+ };
2737
+ }
2738
+ //#endregion
2739
+ //#region src/ipc/daemon/handlers/settings.ts
2740
+ /**
2741
+ * Settings the host reads when it BUILDS an agent (not per call): changing
2742
+ * one rebuilds the live agents. `enabledPlugins` applies the plugin gate;
2743
+ * a live agent would otherwise keep a disabled plugin's skills and stdio MCP
2744
+ * servers until something unrelated rebuilt it.
2745
+ */
2746
+ const AGENT_BUILD_KEYS = [
2747
+ "enabledMcps",
2748
+ "disabledMcpTools",
2749
+ "enabledSkills",
2750
+ "enabledPlugins",
2751
+ "importForeignConfigs",
2752
+ "importRulerConfigs",
2753
+ "artifacts"
2754
+ ];
2755
+ function createSettingsHandlers({ host, env, broadcast }) {
2756
+ return {
2757
+ "runtime.reload": () => {
2758
+ const disposeOld = env.forgetRuntime();
2759
+ host.rebuildAllAgents().then(disposeOld, disposeOld);
2760
+ },
2761
+ "settings.get": () => env.readSettings(),
2762
+ "settings.set": (p) => {
2763
+ const patch = p?.patch;
2764
+ if (typeof patch !== "object" || patch === null || Array.isArray(patch)) throw new InvalidParamsError("patch must be an object of settings.");
2765
+ const before = env.readSettings();
2766
+ const next = env.writeSettings(p.patch);
2767
+ const changed = (key) => JSON.stringify(before[key]) !== JSON.stringify(next[key]);
2768
+ if (changed("enabledExtensions") || changed("loadProjectExtensions")) {
2769
+ const disposeOld = env.forgetRuntime();
2770
+ host.rebuildAllAgents({ rediscover: true }).then(disposeOld, disposeOld);
2771
+ } else if (AGENT_BUILD_KEYS.some(changed)) host.rebuildAllAgents({ rediscover: true }).catch(() => void 0);
2772
+ if (changed("interactionMode") && next.interactionMode === "skip") host.interactions.resolveAutomatically();
2773
+ host.applyAgentSettings(before, next);
2774
+ broadcast({
2775
+ kind: "settings:changed",
2776
+ settings: next
2777
+ });
2778
+ return next;
2779
+ }
2780
+ };
2781
+ }
2782
+ //#endregion
2783
+ //#region src/ipc/daemon/handlers/suggest.ts
2784
+ function createSuggestHandlers({ env, broadcastExcept }) {
2785
+ /**
2786
+ * Shared inline-autocomplete state — per-session latest-wins
2787
+ * coalescing for `suggest.resolve`. `seq` is the newest sequence seen
2788
+ * (stale requests return without computing); `controller` aborts an
2789
+ * in-flight compute when a newer request supersedes it (the GUI's
2790
+ * controller-map pattern, daemon-side). Entries outlive their compute
2791
+ * (the seq high-water mark), so the map is an LRU of 256 sessions:
2792
+ * refusing new keys at the cap instead would switch suggestions off for
2793
+ * every session past the 256th until the daemon restarts.
2794
+ */
2795
+ const suggests = /* @__PURE__ */ new Map();
2796
+ return {
2797
+ methods: { "suggest.resolve": async (p, peer) => {
2798
+ const sessionId = String(p.sessionId);
2799
+ const seq = typeof p.seq === "number" ? p.seq : 0;
2800
+ const text = typeof p.text === "string" ? p.text.slice(0, 65536) : "";
2801
+ const caret = typeof p.caret === "number" ? Math.max(0, Math.min(Math.floor(p.caret), text.length)) : text.length;
2802
+ const prev = suggests.get(sessionId);
2803
+ if (prev && seq < prev.seq) return {
2804
+ suggestion: null,
2805
+ seq
2806
+ };
2807
+ prev?.controller?.abort();
2808
+ const controller = new AbortController();
2809
+ const entry = {
2810
+ seq,
2811
+ controller
2812
+ };
2813
+ suggests.delete(sessionId);
2814
+ suggests.set(sessionId, entry);
2815
+ if (suggests.size > 256) {
2816
+ const [oldest, evicted] = suggests.entries().next().value;
2817
+ evicted.controller?.abort();
2818
+ suggests.delete(oldest);
2819
+ }
2820
+ try {
2821
+ const data = await loadOrThrow(env.store, sessionId);
2822
+ const meta = data.metadata ?? {};
2823
+ const cwd = typeof meta.cwd === "string" ? meta.cwd : data.projectRoot ?? "";
2824
+ const config = await env.runtimeConfig(cwd);
2825
+ const providers = [createPathAutocompleteProvider({ cwd: () => cwd }), ...config.extensions.inlineAutocompleteProviders];
2826
+ const result = await resolveInlineSuggestion(providers, {
2827
+ text,
2828
+ cursor: caret,
2829
+ cwd,
2830
+ signal: controller.signal
2831
+ });
2832
+ if (controller.signal.aborted || suggests.get(sessionId) !== entry) return {
2833
+ suggestion: null,
2834
+ seq
2835
+ };
2836
+ const candidates = result ? [...result.suffixes] : [];
2837
+ const suggestion = candidates[0] ?? null;
2838
+ broadcastExcept(peer, {
2839
+ kind: "suggest:changed",
2840
+ sessionId,
2841
+ seq,
2842
+ basis: text,
2843
+ caret,
2844
+ suggestion,
2845
+ ...candidates.length > 0 ? { candidates } : {}
2846
+ });
2847
+ return {
2848
+ suggestion,
2849
+ seq,
2850
+ ...candidates.length > 0 ? { candidates } : {}
2851
+ };
2852
+ } finally {
2853
+ if (suggests.get(sessionId) === entry) entry.controller = null;
2854
+ }
2855
+ } },
2856
+ forget: (sessionId) => {
2857
+ suggests.get(sessionId)?.controller?.abort();
2858
+ suggests.delete(sessionId);
2859
+ }
2860
+ };
2861
+ }
2862
+ //#endregion
2863
+ //#region src/ipc/daemon/sync-apply.ts
2864
+ const MAX_SETTINGS_BYTES = 262144;
2865
+ const MAX_SYSTEM_MD_BYTES = 262144;
2866
+ const MAX_FILE_BYTES = 1048576;
2867
+ const MAX_SKILL_BYTES = 4194304;
2868
+ const MAX_FILES_BYTES = 16777216;
2869
+ const MAX_SKILL_FILES = 200;
2870
+ const MAX_SKILLS = 500;
2871
+ const MAX_PATH_DEPTH = 8;
2872
+ const MAX_SAFELIST_ENTRIES = 1e3;
2873
+ const MAX_ENTRY_LENGTH = 1e3;
2874
+ const NAME_RE = /^\w[\w@.-]{0,99}$/;
2875
+ const SEGMENT_RE = /^[^/\\\0]{1,255}$/;
2876
+ /** A skill file's relative path: plain segments, no `..`, bounded depth. */
2877
+ function checkRelativePath(path) {
2878
+ if (typeof path !== "string" || path === "" || isAbsolute(path) || path.includes("\\")) return null;
2879
+ const segments = path.split("/");
2880
+ if (segments.length > MAX_PATH_DEPTH || segments.some((s) => s === "." || s === ".." || !SEGMENT_RE.test(s))) return null;
2881
+ return segments.join("/");
2882
+ }
2883
+ /** Is `command` runnable here (an absolute path, or found on PATH)? Looks, never runs. */
2884
+ function commandAvailable(command) {
2885
+ const executable = (path) => {
2886
+ try {
2887
+ accessSync(path, constants.X_OK);
2888
+ return statSync(path).isFile();
2889
+ } catch {
2890
+ return false;
2891
+ }
2892
+ };
2893
+ if (command.includes("/")) return isAbsolute(command) && executable(command);
2894
+ return (process$1.env.PATH ?? "").split(delimiter).filter(Boolean).some((dir) => executable(join(dir, command)));
2895
+ }
2896
+ /** A folder's files, sorted, as one hash (symlinks hashed by target, never followed). Null when absent. */
2897
+ function hashDir(dir) {
2898
+ let stat;
2899
+ try {
2900
+ stat = lstatSync(dir);
2901
+ } catch {
2902
+ return null;
2903
+ }
2904
+ if (!stat.isDirectory()) return `not-a-folder:${stat.mode}`;
2905
+ const hash = createHash("sha256");
2906
+ const walk = (at, depth) => {
2907
+ if (depth > 9) return;
2908
+ for (const name of readdirSync(at).sort()) {
2909
+ const path = join(at, name);
2910
+ const rel = relative(dir, path).split(sep).join("/");
2911
+ const s = lstatSync(path);
2912
+ if (s.isDirectory()) walk(path, depth + 1);
2913
+ else if (s.isFile()) {
2914
+ hash.update(`f:${rel}\0`);
2915
+ hash.update(readFileSync(path));
2916
+ hash.update("\0");
2917
+ } else hash.update(`o:${rel}:${s.mode}\0`);
2918
+ }
2919
+ };
2920
+ walk(dir, 0);
2921
+ return hash.digest("hex");
2922
+ }
2923
+ /** The hash `hashDir` gives the folder these files make. */
2924
+ function hashFiles(files) {
2925
+ const hash = createHash("sha256");
2926
+ const sorted = [...files].sort((a, b) => compareWalkOrder(a.path, b.path));
2927
+ for (const file of sorted) {
2928
+ hash.update(`f:${file.path}\0`);
2929
+ hash.update(file.bytes);
2930
+ hash.update("\0");
2931
+ }
2932
+ return hash.digest("hex");
2933
+ }
2934
+ /** The order a sorted depth-first walk visits two relative paths. */
2935
+ function compareWalkOrder(a, b) {
2936
+ const as = a.split("/");
2937
+ const bs = b.split("/");
2938
+ for (let i = 0; i < Math.min(as.length, bs.length); i++) if (as[i] !== bs[i]) return as[i] < bs[i] ? -1 : 1;
2939
+ return as.length - bs.length;
2940
+ }
2941
+ function userSkillsRoot(ctx) {
2942
+ return projectUserPaths({
2943
+ subPath: "skills",
2944
+ cwd: ctx.home(),
2945
+ home: ctx.home(),
2946
+ prefix: ctx.prefix()
2947
+ }).find((p) => p.source === "user" && p.path.includes(`.${ctx.prefix().replace(/^\./, "")}`)).path;
2948
+ }
2949
+ function userMcpsPath(ctx) {
2950
+ return mcpsWritePath("user", {
2951
+ home: ctx.home(),
2952
+ cwd: ctx.home(),
2953
+ prefix: ctx.prefix()
2954
+ });
2955
+ }
2956
+ function userExtensionDirs(ctx, name) {
2957
+ return projectUserPaths({
2958
+ subPath: "extensions",
2959
+ cwd: ctx.home(),
2960
+ home: ctx.home(),
2961
+ prefix: ctx.prefix()
2962
+ }).filter((p) => p.source === "user").map((p) => join(p.path, name));
2963
+ }
2964
+ /** Is `field` free to write? An override (now or found by a changed value) is not. */
2965
+ function writable(record, field, currentHash, result) {
2966
+ if (record.overrides[field]) {
2967
+ result.skipped.push({
2968
+ field,
2969
+ reason: "override"
2970
+ });
2971
+ return false;
2972
+ }
2973
+ const known = record.fields[field];
2974
+ if (known?.hash !== void 0 && known.hash !== (currentHash ?? "absent")) {
2975
+ record.overrides[field] = { at: Date.now() };
2976
+ result.skipped.push({
2977
+ field,
2978
+ reason: "override"
2979
+ });
2980
+ return false;
2981
+ }
2982
+ return true;
2983
+ }
2984
+ function mark(record, field, category, fields) {
2985
+ record.fields[field] = {
2986
+ category,
2987
+ syncedAt: Date.now(),
2988
+ ...fields
2989
+ };
2990
+ }
2991
+ function pushSettings(ctx, record, payload, result) {
2992
+ if (!isRecord(payload?.values)) throw new InvalidParamsError("settings: payload.values must be an object.");
2993
+ if (JSON.stringify(payload.values).length > MAX_SETTINGS_BYTES) throw new InvalidParamsError(`settings: larger than ${MAX_SETTINGS_BYTES} bytes.`);
2994
+ const current = ctx.env.readSettings();
2995
+ const patch = {};
2996
+ for (const [key, value] of Object.entries(payload.values)) {
2997
+ const field = settingField(key);
2998
+ if (!Object.hasOwn(DEFAULT_SETTINGS, key)) {
2999
+ result.skipped.push({
3000
+ field,
3001
+ reason: "not a setting"
3002
+ });
3003
+ continue;
3004
+ }
3005
+ if (!isSyncableSetting(key)) {
3006
+ result.skipped.push({
3007
+ field,
3008
+ reason: isWindowOnlySetting(key) ? "window-only setting" : "not synced"
3009
+ });
3010
+ continue;
3011
+ }
3012
+ const currentHash = hashValue(current[key]);
3013
+ if (!writable(record, field, currentHash, result)) continue;
3014
+ if (hashValue(value) === currentHash) {
3015
+ mark(record, field, "settings", {
3016
+ hash: currentHash,
3017
+ sent: currentHash
3018
+ });
3019
+ result.unchanged.push(field);
3020
+ continue;
3021
+ }
3022
+ patch[key] = value;
3023
+ }
3024
+ if (Object.keys(patch).length === 0) return;
3025
+ const next = ctx.setters.setSettings(patch);
3026
+ for (const key of Object.keys(patch)) {
3027
+ mark(record, settingField(key), "settings", {
3028
+ hash: hashValue(next[key]),
3029
+ sent: hashValue(patch[key])
3030
+ });
3031
+ result.applied.push(settingField(key));
3032
+ }
3033
+ }
3034
+ /** The project here a client's path names: the same folder inside a workspace root, else one folder of that name in a root. */
3035
+ function projectHere(ctx, clientPath) {
3036
+ const roots = (ctx.env.workspaceRoots ?? []).map((root) => {
3037
+ try {
3038
+ return realpathSync(root);
3039
+ } catch {
3040
+ return null;
3041
+ }
3042
+ }).filter((r) => r !== null);
3043
+ const inRoot = (path) => roots.some((root) => path === root || path.startsWith(root + sep));
3044
+ const isDir = (path) => statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
3045
+ if (isAbsolute(clientPath) && isDir(clientPath)) {
3046
+ const real = realpathSync(clientPath);
3047
+ if (inRoot(real)) return safelistProjectKey(real);
3048
+ }
3049
+ const name = basename(clientPath);
3050
+ if (!name || name === "." || name === "..") return null;
3051
+ const found = [...new Set(roots.map((root) => join(root, name)).filter(isDir).map((path) => realpathSync(path)))];
3052
+ return found.length === 1 ? safelistProjectKey(found[0]) : null;
3053
+ }
3054
+ function pushSafelists(ctx, record, payload, result) {
3055
+ if (!isRecord(payload?.projects)) throw new InvalidParamsError("safelists: payload.projects must map paths to entries.");
3056
+ const dataDir = ctx.env.userDataDir();
3057
+ const file = readProjects(dataDir);
3058
+ let dirty = false;
3059
+ for (const [clientPath, entries] of Object.entries(payload.projects)) {
3060
+ const field = `safelist:${clientPath}`;
3061
+ if (!Array.isArray(entries) || entries.length > MAX_SAFELIST_ENTRIES || !entries.every((e) => typeof e === "string" && e.length <= MAX_ENTRY_LENGTH)) {
3062
+ result.skipped.push({
3063
+ field,
3064
+ reason: "invalid entries"
3065
+ });
3066
+ continue;
3067
+ }
3068
+ const target = projectHere(ctx, clientPath);
3069
+ if (!target) {
3070
+ result.skipped.push({
3071
+ field,
3072
+ reason: "no project of that path or name on this server"
3073
+ });
3074
+ continue;
3075
+ }
3076
+ const previous = record.fields[field]?.target === target ? record.fields[field]?.entries ?? [] : [];
3077
+ const current = file[target]?.safelist ?? [];
3078
+ const next = current.filter((entry) => !previous.includes(entry) || entries.includes(entry));
3079
+ for (const entry of entries) if (!next.includes(entry)) next.push(entry);
3080
+ mark(record, field, "safelists", {
3081
+ target,
3082
+ entries: [...entries]
3083
+ });
3084
+ if (JSON.stringify(next) === JSON.stringify(current)) {
3085
+ result.unchanged.push(field);
3086
+ continue;
3087
+ }
3088
+ file[target] = {
3089
+ ...file[target],
3090
+ safelist: next
3091
+ };
3092
+ dirty = true;
3093
+ result.applied.push(field);
3094
+ }
3095
+ if (dirty) writeProjects(dataDir, file);
3096
+ }
3097
+ /** A skill as checked bytes, or why it is refused. */
3098
+ function checkSkill(skill) {
3099
+ const name = isRecord(skill) && typeof skill.name === "string" ? skill.name : "";
3100
+ const field = `skill:${name}`;
3101
+ if (!NAME_RE.test(name)) return {
3102
+ field,
3103
+ reason: "not a plain skill name"
3104
+ };
3105
+ const files = isRecord(skill) && Array.isArray(skill.files) ? skill.files : null;
3106
+ if (!files || files.length === 0 || files.length > MAX_SKILL_FILES) return {
3107
+ field,
3108
+ reason: `needs 1 to ${MAX_SKILL_FILES} files`
3109
+ };
3110
+ const out = [];
3111
+ let total = 0;
3112
+ for (const file of files) {
3113
+ const path = checkRelativePath(isRecord(file) ? file.path : void 0);
3114
+ if (!path) return {
3115
+ field,
3116
+ reason: "a file path is not a plain relative path"
3117
+ };
3118
+ if (typeof file.data !== "string") return {
3119
+ field,
3120
+ reason: `${path}: no data`
3121
+ };
3122
+ const bytes = Buffer.from(file.data, "base64");
3123
+ if (bytes.length > MAX_FILE_BYTES) return {
3124
+ field,
3125
+ reason: `${path} is larger than ${MAX_FILE_BYTES} bytes`
3126
+ };
3127
+ total += bytes.length;
3128
+ if (out.some((f) => f.path === path || f.path.startsWith(`${path}/`) || path.startsWith(`${f.path}/`))) return {
3129
+ field,
3130
+ reason: `${path} appears twice`
3131
+ };
3132
+ out.push({
3133
+ path,
3134
+ bytes
3135
+ });
3136
+ }
3137
+ if (total > MAX_SKILL_BYTES) return {
3138
+ field,
3139
+ reason: `larger than ${MAX_SKILL_BYTES} bytes`
3140
+ };
3141
+ return {
3142
+ name,
3143
+ files: out
3144
+ };
3145
+ }
3146
+ /** Replace `dir` with exactly these files: staged next to it, then swapped in. */
3147
+ function writeSkillDir(root, name, files) {
3148
+ mkdirSync(root, { recursive: true });
3149
+ const dir = join(root, name);
3150
+ const stage = join(root, `.${name}.sync-${process$1.pid}-${Date.now()}`);
3151
+ mkdirSync(stage);
3152
+ try {
3153
+ for (const file of files) {
3154
+ const target = resolve(stage, file.path);
3155
+ if (!target.startsWith(stage + sep)) throw new Error(`${file.path} is outside the skill folder.`);
3156
+ mkdirSync(dirname(target), { recursive: true });
3157
+ writeFileSync(target, file.bytes);
3158
+ }
3159
+ const backup = existsSync(dir) ? `${stage}.old` : null;
3160
+ if (backup) renameSync(dir, backup);
3161
+ renameSync(stage, dir);
3162
+ if (backup) rmSync(backup, {
3163
+ recursive: true,
3164
+ force: true
3165
+ });
3166
+ } finally {
3167
+ rmSync(stage, {
3168
+ recursive: true,
3169
+ force: true
3170
+ });
3171
+ }
3172
+ }
3173
+ function pushFiles(ctx, record, payload, result) {
3174
+ if (!isRecord(payload) || !Array.isArray(payload.skills) || payload.skills.length > MAX_SKILLS || payload.systemMd !== null && typeof payload.systemMd !== "string") throw new InvalidParamsError("files: payload must be { systemMd: string | null, skills: [...] }.");
3175
+ let touched = false;
3176
+ let total = 0;
3177
+ const mdPath = userSystemMdPath({
3178
+ home: ctx.home(),
3179
+ prefix: ctx.prefix()
3180
+ });
3181
+ const currentMd = existsSync(mdPath) ? readFileSync(mdPath, "utf8") : null;
3182
+ const currentMdHash = currentMd === null ? null : hashValue(currentMd);
3183
+ if (payload.systemMd !== null && Buffer.byteLength(payload.systemMd) > MAX_SYSTEM_MD_BYTES) result.skipped.push({
3184
+ field: SYSTEM_MD_FIELD,
3185
+ reason: `larger than ${MAX_SYSTEM_MD_BYTES} bytes`
3186
+ });
3187
+ else if (payload.systemMd !== null || record.fields["system-md"]) {
3188
+ if (writable(record, "system-md", currentMdHash, result)) {
3189
+ if (payload.systemMd === null) {
3190
+ rmSync(mdPath, { force: true });
3191
+ delete record.fields[SYSTEM_MD_FIELD];
3192
+ result.applied.push(SYSTEM_MD_FIELD);
3193
+ touched = true;
3194
+ } else if (hashValue(payload.systemMd) === currentMdHash) {
3195
+ mark(record, SYSTEM_MD_FIELD, "files", { hash: currentMdHash });
3196
+ result.unchanged.push(SYSTEM_MD_FIELD);
3197
+ } else {
3198
+ total += Buffer.byteLength(payload.systemMd);
3199
+ writeFileAtomic(mdPath, payload.systemMd, { ensureDir: true });
3200
+ mark(record, SYSTEM_MD_FIELD, "files", { hash: hashValue(payload.systemMd) });
3201
+ result.applied.push(SYSTEM_MD_FIELD);
3202
+ touched = true;
3203
+ }
3204
+ }
3205
+ }
3206
+ const root = userSkillsRoot(ctx);
3207
+ const sent = /* @__PURE__ */ new Set();
3208
+ for (const raw of payload.skills) {
3209
+ const skill = checkSkill(raw);
3210
+ if ("reason" in skill) {
3211
+ result.skipped.push(skill);
3212
+ continue;
3213
+ }
3214
+ const field = `skill:${skill.name}`;
3215
+ sent.add(field);
3216
+ const bytes = skill.files.reduce((n, f) => n + f.bytes.length, 0);
3217
+ if (total + bytes > MAX_FILES_BYTES) {
3218
+ result.skipped.push({
3219
+ field,
3220
+ reason: `over the ${MAX_FILES_BYTES}-byte bound of one push`
3221
+ });
3222
+ continue;
3223
+ }
3224
+ const dir = join(root, skill.name);
3225
+ if (existsSync(dir) && lstatSync(dir).isSymbolicLink()) {
3226
+ result.skipped.push({
3227
+ field,
3228
+ reason: "a link on this server, left alone"
3229
+ });
3230
+ continue;
3231
+ }
3232
+ const currentHash = hashDir(dir);
3233
+ if (!writable(record, field, currentHash, result)) continue;
3234
+ const wanted = hashFiles(skill.files);
3235
+ if (wanted === currentHash) {
3236
+ mark(record, field, "files", { hash: wanted });
3237
+ result.unchanged.push(field);
3238
+ continue;
3239
+ }
3240
+ total += bytes;
3241
+ writeSkillDir(root, skill.name, skill.files);
3242
+ mark(record, field, "files", { hash: hashDir(dir) ?? wanted });
3243
+ result.applied.push(field);
3244
+ touched = true;
3245
+ }
3246
+ for (const [field, known] of Object.entries(record.fields)) {
3247
+ if (known.category !== "files" || !field.startsWith("skill:") || sent.has(field)) continue;
3248
+ const dir = join(root, field.slice(6));
3249
+ if (!writable(record, field, hashDir(dir), result)) continue;
3250
+ rmSync(dir, {
3251
+ recursive: true,
3252
+ force: true
3253
+ });
3254
+ delete record.fields[field];
3255
+ result.applied.push(field);
3256
+ touched = true;
3257
+ }
3258
+ return touched;
3259
+ }
3260
+ function readUserMcps(ctx) {
3261
+ const path = userMcpsPath(ctx);
3262
+ if (!existsSync(path)) return [];
3263
+ try {
3264
+ return parseMcpsFile(readFileSync(path, "utf8"));
3265
+ } catch {
3266
+ return [];
3267
+ }
3268
+ }
3269
+ async function pushMcps(ctx, record, payload, peer, result) {
3270
+ if (!isRecord(payload) || !Array.isArray(payload.servers)) throw new InvalidParamsError("mcps: payload.servers must be a list.");
3271
+ const hashOf = (name) => {
3272
+ const found = readUserMcps(ctx).find((s) => s.name === name);
3273
+ return found ? hashValue(found) : null;
3274
+ };
3275
+ const sent = /* @__PURE__ */ new Set();
3276
+ for (const config of payload.servers) {
3277
+ const name = isRecord(config) && typeof config.name === "string" ? config.name : "";
3278
+ const field = `mcp:${name}`;
3279
+ if (!NAME_RE.test(name)) {
3280
+ result.skipped.push({
3281
+ field,
3282
+ reason: "not a plain server name"
3283
+ });
3284
+ continue;
3285
+ }
3286
+ sent.add(field);
3287
+ const currentHash = hashOf(name);
3288
+ if (!writable(record, field, currentHash, result)) continue;
3289
+ const wanted = hashValue(config);
3290
+ if (record.fields[field]?.sent === wanted && currentHash !== null) {
3291
+ result.unchanged.push(field);
3292
+ continue;
3293
+ }
3294
+ try {
3295
+ await ctx.setters.mcpUpsert({ config }, peer);
3296
+ } catch (err) {
3297
+ result.skipped.push({
3298
+ field,
3299
+ reason: err instanceof Error ? err.message : String(err)
3300
+ });
3301
+ continue;
3302
+ }
3303
+ mark(record, field, "mcps", {
3304
+ hash: hashOf(name) ?? wanted,
3305
+ sent: wanted
3306
+ });
3307
+ result.applied.push(field);
3308
+ if (typeof config.command === "string" && config.command.trim() !== "" && !commandAvailable(config.command.trim())) result.warnings.push({
3309
+ field,
3310
+ message: `"${config.command}" is not on this server's PATH: the server may not run there.`
3311
+ });
3312
+ }
3313
+ for (const [field, known] of Object.entries(record.fields)) {
3314
+ if (known.category !== "mcps" || sent.has(field)) continue;
3315
+ const name = field.slice(4);
3316
+ if (!writable(record, field, hashOf(name), result)) continue;
3317
+ try {
3318
+ await ctx.setters.mcpRemove({ name }, peer);
3319
+ } catch (err) {
3320
+ result.skipped.push({
3321
+ field,
3322
+ reason: err instanceof Error ? err.message : String(err)
3323
+ });
3324
+ continue;
3325
+ }
3326
+ delete record.fields[field];
3327
+ result.applied.push(field);
3328
+ }
3329
+ }
3330
+ async function pushExtensions(ctx, record, payload, peer, result) {
3331
+ if (!isRecord(payload) || !Array.isArray(payload.extensions)) throw new InvalidParamsError("extensions: payload.extensions must be a list.");
3332
+ for (const entry of payload.extensions) {
3333
+ const name = isRecord(entry) && typeof entry.name === "string" ? entry.name : "";
3334
+ const field = `extension:${name}`;
3335
+ if (!NAME_RE.test(name) || typeof entry.spec !== "string") {
3336
+ result.skipped.push({
3337
+ field,
3338
+ reason: "needs a plain name and a source spec"
3339
+ });
3340
+ continue;
3341
+ }
3342
+ if (record.overrides[field]) {
3343
+ result.skipped.push({
3344
+ field,
3345
+ reason: "override"
3346
+ });
3347
+ continue;
3348
+ }
3349
+ if (userExtensionDirs(ctx, name).some((dir) => existsSync(dir))) {
3350
+ if (!record.fields[field]) mark(record, field, "extensions", {});
3351
+ result.unchanged.push(field);
3352
+ continue;
3353
+ }
3354
+ try {
3355
+ await ctx.setters.install({
3356
+ kind: "extension",
3357
+ spec: entry.spec,
3358
+ scope: "user",
3359
+ ...typeof entry.extension === "string" ? { extension: entry.extension } : {}
3360
+ }, peer);
3361
+ } catch (err) {
3362
+ result.skipped.push({
3363
+ field,
3364
+ reason: err instanceof Error ? err.message : String(err)
3365
+ });
3366
+ continue;
3367
+ }
3368
+ mark(record, field, "extensions", {});
3369
+ result.applied.push(field);
3370
+ }
3371
+ }
3372
+ function pushKeys(ctx, record, payload, result) {
3373
+ if (!isRecord(payload) || !Array.isArray(payload.keys)) throw new InvalidParamsError("keys: payload.keys must be a list.");
3374
+ for (const entry of payload.keys) {
3375
+ const provider = isRecord(entry) && typeof entry.provider === "string" ? entry.provider : "";
3376
+ const field = `key:${provider}`;
3377
+ if (!record.keys.includes(provider)) {
3378
+ result.skipped.push({
3379
+ field,
3380
+ reason: "not opted in for this key"
3381
+ });
3382
+ continue;
3383
+ }
3384
+ if (record.overrides[field]) {
3385
+ result.skipped.push({
3386
+ field,
3387
+ reason: "override"
3388
+ });
3389
+ continue;
3390
+ }
3391
+ try {
3392
+ if (typeof entry.key !== "string" || entry.key.trim() === "") throw new Error("empty key");
3393
+ if (ctx.auth.storedKeys(ctx.auth.descriptorOf(provider)).includes(entry.key.trim())) {
3394
+ if (!record.fields[field]) mark(record, field, "keys", {});
3395
+ result.unchanged.push(field);
3396
+ continue;
3397
+ }
3398
+ ctx.setters.setApiKey({
3399
+ provider,
3400
+ key: entry.key
3401
+ });
3402
+ } catch (err) {
3403
+ result.skipped.push({
3404
+ field,
3405
+ reason: err instanceof Error ? err.message : String(err)
3406
+ });
3407
+ continue;
3408
+ }
3409
+ mark(record, field, "keys", {});
3410
+ result.applied.push(field);
3411
+ }
3412
+ }
3413
+ //#endregion
3414
+ //#region src/ipc/daemon/sync-store.ts
3415
+ const SYNC_FILE = "sync.json";
3416
+ function emptySyncRecord() {
3417
+ return {
3418
+ version: 1,
3419
+ source: null,
3420
+ categories: [],
3421
+ keys: [],
3422
+ fields: {},
3423
+ overrides: {}
3424
+ };
3425
+ }
3426
+ function createSyncStore(dataDir) {
3427
+ const file = join(dataDir, SYNC_FILE);
3428
+ return {
3429
+ file,
3430
+ read: () => {
3431
+ if (!existsSync(file)) return emptySyncRecord();
3432
+ try {
3433
+ const raw = JSON.parse(readFileSync(file, "utf8"));
3434
+ if (!isRecord(raw)) return emptySyncRecord();
3435
+ return {
3436
+ version: 1,
3437
+ source: isRecord(raw.source) && typeof raw.source.deviceId === "string" && typeof raw.source.deviceName === "string" ? raw.source : null,
3438
+ categories: Array.isArray(raw.categories) ? raw.categories.filter((c) => SYNC_CATEGORIES.includes(c)) : [],
3439
+ keys: Array.isArray(raw.keys) ? raw.keys.filter((k) => typeof k === "string") : [],
3440
+ fields: isRecord(raw.fields) ? raw.fields : {},
3441
+ overrides: isRecord(raw.overrides) ? raw.overrides : {},
3442
+ ...typeof raw.lastPushAt === "number" ? { lastPushAt: raw.lastPushAt } : {}
3443
+ };
3444
+ } catch {
3445
+ return emptySyncRecord();
3446
+ }
3447
+ },
3448
+ write: (record) => writeJsonFileAtomic(file, record, OWNER_ONLY)
3449
+ };
3450
+ }
3451
+ /** What `sync.status` says: the record without hashes or safelist entries. */
3452
+ function syncStatusOf(record) {
3453
+ return {
3454
+ source: record.source,
3455
+ categories: [...record.categories],
3456
+ keys: [...record.keys],
3457
+ fields: Object.fromEntries(Object.entries(record.fields).map(([field, f]) => [field, {
3458
+ category: f.category,
3459
+ syncedAt: f.syncedAt
3460
+ }])),
3461
+ overrides: { ...record.overrides },
3462
+ ...record.lastPushAt !== void 0 ? { lastPushAt: record.lastPushAt } : {}
3463
+ };
3464
+ }
3465
+ //#endregion
3466
+ //#region src/ipc/daemon/handlers/sync.ts
3467
+ const MAX_DEVICE_FIELD = 200;
3468
+ const NOT_A_SERVER = "This host takes no settings sync (a zidane-server does).";
3469
+ function checkDevice(p, withName) {
3470
+ if (typeof p.deviceId !== "string" || p.deviceId.trim() === "" || p.deviceId.length > MAX_DEVICE_FIELD) throw new InvalidParamsError("deviceId must be a non-empty string.");
3471
+ if (withName && (typeof p.deviceName !== "string" || p.deviceName.trim() === "" || p.deviceName.length > MAX_DEVICE_FIELD)) throw new InvalidParamsError("deviceName must be a non-empty string.");
3472
+ return {
3473
+ deviceId: p.deviceId,
3474
+ deviceName: typeof p.deviceName === "string" ? p.deviceName.trim() : ""
3475
+ };
3476
+ }
3477
+ function checkCategories(value) {
3478
+ if (value === void 0) return void 0;
3479
+ if (!Array.isArray(value) || !value.every((c) => SYNC_CATEGORIES.includes(c))) throw new InvalidParamsError(`categories must be some of: ${SYNC_CATEGORIES.join(", ")}.`);
3480
+ return SYNC_CATEGORIES.filter((c) => value.includes(c));
3481
+ }
3482
+ function createSyncHandlers({ host, env, broadcast, setters, auth, options }) {
3483
+ const store = createSyncStore(env.userDataDir());
3484
+ const ctx = {
3485
+ env,
3486
+ setters,
3487
+ auth,
3488
+ home: () => homedir(),
3489
+ prefix: () => env.config.prefix
3490
+ };
3491
+ /** Sync calls (and override marks) one at a time: each reads the record, works, writes it. */
3492
+ let queue = Promise.resolve();
3493
+ const exclusive = (work) => {
3494
+ const next = queue.then(work, work);
3495
+ queue = next.catch(() => void 0);
3496
+ return next;
3497
+ };
3498
+ const changed = () => broadcast({ kind: "sync:changed" });
3499
+ const audit = (peer, entry) => options?.audit?.(peer, entry);
3500
+ function requireOptions() {
3501
+ if (!options) throw new Error(NOT_A_SERVER);
3502
+ }
3503
+ /** Mark fields the source set as edited here; nothing for fields nobody synced. */
3504
+ const markOverrides = (fields) => {
3505
+ if (!options || fields.length === 0) return Promise.resolve();
3506
+ return exclusive(() => {
3507
+ const record = store.read();
3508
+ const marked = fields.filter((field) => record.fields[field] && !record.overrides[field]);
3509
+ if (marked.length === 0) return;
3510
+ const at = Date.now();
3511
+ for (const field of marked) record.overrides[field] = { at };
3512
+ store.write(record);
3513
+ changed();
3514
+ });
3515
+ };
3516
+ /** Whether `deviceId` over `peer`'s token is the recorded source. */
3517
+ const isSource = (record, deviceId, peer) => record.source !== null && record.source.deviceId === deviceId && record.source.tokenId === peer.principal;
3518
+ /** The source's own call, or a refusal naming the device that syncs. */
3519
+ function requireSource(record, deviceId, peer) {
3520
+ if (!record.source) throw new Error("No device syncs to this server: claim it first (sync.claim).");
3521
+ if (!isSource(record, deviceId, peer)) throw new Error(`This server is synced from ${record.source.deviceName}${record.source.deviceId === deviceId ? " with another token" : ""}: take over from that device first.`);
3522
+ }
3523
+ const rebuild = () => {
3524
+ host.rebuildAllAgents({ rediscover: true }).catch(() => void 0);
3525
+ };
3526
+ return {
3527
+ markOverrides,
3528
+ "sync.status": () => {
3529
+ requireOptions();
3530
+ return syncStatusOf(store.read());
3531
+ },
3532
+ "sync.claim": (p, peer) => exclusive(() => {
3533
+ requireOptions();
3534
+ const { deviceId, deviceName } = checkDevice(p, true);
3535
+ const categories = checkCategories(p.categories);
3536
+ if (p.keys !== void 0 && (!Array.isArray(p.keys) || !p.keys.every((k) => typeof k === "string"))) throw new InvalidParamsError("keys must be provider names.");
3537
+ const takeover = p.takeover === true;
3538
+ const record = store.read();
3539
+ const other = record.source !== null && !isSource(record, deviceId, peer);
3540
+ if (other && !takeover) {
3541
+ audit(peer, {
3542
+ event: "sync.claim",
3543
+ deviceId,
3544
+ deviceName,
3545
+ takeover,
3546
+ claimed: false,
3547
+ ok: true
3548
+ });
3549
+ return {
3550
+ claimed: false,
3551
+ status: syncStatusOf(record)
3552
+ };
3553
+ }
3554
+ const next = other ? {
3555
+ version: 1,
3556
+ source: null,
3557
+ categories: [],
3558
+ keys: [],
3559
+ fields: {},
3560
+ overrides: {}
3561
+ } : record;
3562
+ next.source = {
3563
+ deviceId,
3564
+ deviceName,
3565
+ claimedAt: other || !record.source ? Date.now() : record.source.claimedAt,
3566
+ ...peer.principal ? { tokenId: peer.principal } : {}
3567
+ };
3568
+ if (categories) next.categories = categories;
3569
+ if (p.keys !== void 0) next.keys = [...new Set(p.keys)];
3570
+ store.write(next);
3571
+ changed();
3572
+ audit(peer, {
3573
+ event: "sync.claim",
3574
+ deviceId,
3575
+ deviceName,
3576
+ takeover,
3577
+ claimed: true,
3578
+ categories: next.categories,
3579
+ keys: next.keys,
3580
+ ok: true
3581
+ });
3582
+ return {
3583
+ claimed: true,
3584
+ status: syncStatusOf(next)
3585
+ };
3586
+ }),
3587
+ "sync.push": (p, peer) => exclusive(async () => {
3588
+ requireOptions();
3589
+ const { deviceId } = checkDevice(p, false);
3590
+ const category = p.category;
3591
+ const fields = {
3592
+ event: "sync.push",
3593
+ category,
3594
+ deviceId
3595
+ };
3596
+ try {
3597
+ if (!SYNC_CATEGORIES.includes(category)) throw new InvalidParamsError(`category must be one of: ${SYNC_CATEGORIES.join(", ")}.`);
3598
+ const record = store.read();
3599
+ requireSource(record, deviceId, peer);
3600
+ if (!record.categories.includes(category)) throw new Error(`"${category}" is not synced to this server (claim it with that category).`);
3601
+ const result = {
3602
+ applied: [],
3603
+ unchanged: [],
3604
+ skipped: [],
3605
+ warnings: []
3606
+ };
3607
+ const payload = p.payload;
3608
+ if (category === "settings") pushSettings(ctx, record, payload, result);
3609
+ else if (category === "safelists") pushSafelists(ctx, record, payload, result);
3610
+ else if (category === "files") {
3611
+ if (pushFiles(ctx, record, payload, result)) {
3612
+ rebuild();
3613
+ broadcast({
3614
+ kind: "catalog:changed",
3615
+ catalog: "skills"
3616
+ });
3617
+ }
3618
+ } else if (category === "mcps") await pushMcps(ctx, record, payload, peer, result);
3619
+ else if (category === "extensions") await pushExtensions(ctx, record, payload, peer, result);
3620
+ else pushKeys(ctx, record, payload, result);
3621
+ record.lastPushAt = Date.now();
3622
+ store.write(record);
3623
+ changed();
3624
+ audit(peer, {
3625
+ ...fields,
3626
+ applied: result.applied.length,
3627
+ unchanged: result.unchanged.length,
3628
+ skipped: result.skipped.length,
3629
+ ok: true
3630
+ });
3631
+ return result;
3632
+ } catch (err) {
3633
+ audit(peer, {
3634
+ ...fields,
3635
+ ok: false,
3636
+ error: err instanceof Error ? err.message : String(err)
3637
+ });
3638
+ throw err;
3639
+ }
3640
+ }),
3641
+ "sync.reset": (p, peer) => exclusive(() => {
3642
+ requireOptions();
3643
+ if (typeof p.field !== "string" || p.field === "") throw new InvalidParamsError("field must be a sync field name.");
3644
+ const record = store.read();
3645
+ const reset = Boolean(record.overrides[p.field]);
3646
+ if (reset) {
3647
+ delete record.overrides[p.field];
3648
+ const known = record.fields[p.field];
3649
+ if (known) {
3650
+ delete known.hash;
3651
+ delete known.sent;
3652
+ }
3653
+ store.write(record);
3654
+ changed();
3655
+ }
3656
+ audit(peer, {
3657
+ event: "sync.reset",
3658
+ field: p.field,
3659
+ reset,
3660
+ ok: true
3661
+ });
3662
+ return { reset };
3663
+ }),
3664
+ "sync.release": (p, peer) => exclusive(() => {
3665
+ requireOptions();
3666
+ const { deviceId } = checkDevice(p, false);
3667
+ const record = store.read();
3668
+ if (!record.source) return { released: false };
3669
+ requireSource(record, deviceId, peer);
3670
+ store.write({
3671
+ version: 1,
3672
+ source: null,
3673
+ categories: [],
3674
+ keys: [],
3675
+ fields: {},
3676
+ overrides: {}
3677
+ });
3678
+ changed();
3679
+ audit(peer, {
3680
+ event: "sync.release",
3681
+ deviceId,
3682
+ ok: true
3683
+ });
3684
+ return { released: true };
3685
+ })
3686
+ };
3687
+ }
3688
+ /**
3689
+ * The window-facing setters, marking what they change as an override when
3690
+ * the field came from sync (the sync handlers call the unwrapped ones).
3691
+ */
3692
+ function withSyncOverrides(handlers, { readSettings, markOverrides }) {
3693
+ const after = async (work, fields) => {
3694
+ const result = await work();
3695
+ await markOverrides(fields(result));
3696
+ return result;
3697
+ };
3698
+ return {
3699
+ "settings.set": async (p, peer) => {
3700
+ const before = readSettings();
3701
+ return after(() => handlers["settings.set"](p, peer), (next) => Object.keys(isRecord(p?.patch) ? p.patch : {}).filter((key) => JSON.stringify(before[key]) !== JSON.stringify(next[key])).map(settingField));
3702
+ },
3703
+ "mcp.upsert": (p, peer) => after(() => handlers["mcp.upsert"](p, peer), (r) => [`mcp:${r.name}`]),
3704
+ "mcp.remove": (p, peer) => after(() => handlers["mcp.remove"](p, peer), (r) => r.removed ? [`mcp:${p.name}`] : []),
3705
+ "catalog.remove": (p, peer) => after(() => handlers["catalog.remove"](p, peer), () => [`${p.kind}:${p.name}`]),
3706
+ "catalog.update": (p, peer) => after(() => handlers["catalog.update"](p, peer), () => [`${p.kind}:${p.name}`]),
3707
+ "auth.setApiKey": (p, peer) => after(() => handlers["auth.setApiKey"](p, peer), () => [`key:${p.provider}`]),
3708
+ "auth.remove": (p, peer) => after(() => handlers["auth.remove"](p, peer), (r) => r.removed ? [`key:${p.provider}`] : [])
3709
+ };
3710
+ }
3711
+ //#endregion
3712
+ //#region src/ipc/daemon/handlers/terminal.ts
3713
+ function createTerminalHandlers({ host, isConnected }) {
3714
+ return {
3715
+ methods: {
3716
+ "terminal.state": (p) => host.terminals.state(p.sessionId),
3717
+ "terminal.open": (p) => host.terminals.open(p),
3718
+ "terminal.attach": async (p, peer) => {
3719
+ const result = await host.terminals.attach(p.terminalId, p.clientId);
3720
+ if (!isConnected(peer)) {
3721
+ host.terminals.detach(p.terminalId, p.clientId);
3722
+ return result;
3723
+ }
3724
+ const views = peer.terminalViews ??= /* @__PURE__ */ new Map();
3725
+ views.set(p.terminalId, (views.get(p.terminalId) ?? /* @__PURE__ */ new Set()).add(p.clientId));
3726
+ return result;
3727
+ },
3728
+ "terminal.detach": (p, peer) => {
3729
+ const views = peer.terminalViews?.get(p.terminalId);
3730
+ views?.delete(p.clientId);
3731
+ if (views?.size === 0) peer.terminalViews?.delete(p.terminalId);
3732
+ host.terminals.detach(p.terminalId, p.clientId);
3733
+ },
3734
+ "terminal.write": (p) => host.terminals.write(p.terminalId, p.data, p.clientId ? {
3735
+ clientId: p.clientId,
3736
+ ...p.response ? { response: true } : {}
3737
+ } : void 0),
3738
+ "terminal.resize": (p) => host.terminals.resize(p.terminalId, p.clientId, p.cols, p.rows, p.claim),
3739
+ "terminal.ack": (p) => host.terminals.ack(p.terminalId, p.clientId, p.offset),
3740
+ "terminal.restart": (p) => host.terminals.restart(p.terminalId),
3741
+ "terminal.close": (p) => host.terminals.close(p.terminalId),
3742
+ "terminal.setHidden": (p) => host.terminals.setHidden(p.terminalId, p.hidden),
3743
+ "terminal.registerViewer": (p, peer) => {
3744
+ (peer.terminalViewers ??= /* @__PURE__ */ new Set()).add(p.viewerId);
3745
+ host.terminals.registerViewer(p.viewerId);
3746
+ return { ok: true };
3747
+ },
3748
+ "terminal.unregisterViewer": (p, peer) => {
3749
+ peer.terminalViewers?.delete(p.viewerId);
3750
+ host.terminals.unregisterViewer(p.viewerId);
3751
+ return { ok: true };
3752
+ }
3753
+ },
3754
+ releasePeer(peer) {
3755
+ for (const viewerId of peer.terminalViewers ?? []) host.terminals.unregisterViewer(viewerId);
3756
+ for (const [terminalId, clients] of peer.terminalViews ?? []) for (const clientId of clients) host.terminals.detach(terminalId, clientId);
3757
+ }
3758
+ };
3759
+ }
3760
+ //#endregion
3761
+ //#region src/ipc/daemon/handlers/titles.ts
3762
+ function createTitleHandlers({ host, broadcast }) {
3763
+ return { "sessions.generateTitle": async (p) => {
3764
+ const sessionId = sessionIdOf(p);
3765
+ const providerKey = optionalString(p.providerKey, "providerKey");
3766
+ const model = optionalString(p.model, "model");
3767
+ const title = await host.generateSessionTitle(sessionId, {
3768
+ ...providerKey !== void 0 ? { providerKey } : {},
3769
+ ...model !== void 0 ? { model } : {}
3770
+ });
3771
+ broadcast({
3772
+ kind: "sessions:changed",
3773
+ op: "update",
3774
+ sessionId
3775
+ });
3776
+ return { title };
3777
+ } };
3778
+ }
3779
+ //#endregion
3780
+ //#region src/ipc/daemon/handlers/todos.ts
3781
+ function createTodoHandlers({ host, store }) {
3782
+ return {
3783
+ "todos.get": (p) => {
3784
+ const state = host.getTodosSnapshot(p.sessionId);
3785
+ return state ? toTodosSnapshot(state) : null;
3786
+ },
3787
+ "todos.set": async (p) => {
3788
+ const state = await host.setTodos(p.sessionId, p.items);
3789
+ if (state) return toTodosSnapshot(state);
3790
+ if (p.persistIdle !== true || host.getLiveSession(p.sessionId)) return null;
3791
+ return persistIdleTodos(store, p.sessionId, Array.isArray(p.items) ? p.items : []);
3792
+ }
3793
+ };
3794
+ }
3795
+ /** The idle-session todo edit, as a window applied it to its own copy: the active run's list, archive cleared. */
3796
+ async function persistIdleTodos(store, sessionId, items) {
3797
+ const session = await loadSession(store, sessionId, { persistHeal: false });
3798
+ const runId = session ? selectActiveTodos(session).runId : null;
3799
+ if (!session || !runId) return null;
3800
+ setTodosForRun(session, runId, items, { clearArchive: true });
3801
+ await session.save();
3802
+ return toTodosSnapshot(selectActiveTodos(session));
3803
+ }
3804
+ //#endregion
3805
+ //#region src/ipc/daemon/handlers/transcript.ts
3806
+ function createTranscriptHandlers({ host, store, broadcast, replays }) {
3807
+ return {
3808
+ "sessions.loadEvents": async (p) => {
3809
+ const paging = p;
3810
+ if (isPagedLoad(paging)) return await replays.page(store, p.sessionId, paging);
3811
+ const data = await loadOrThrow(store, p.sessionId);
3812
+ return eventsFromTurns(data.turns, data.runs);
3813
+ },
3814
+ "sessions.getTurn": async (p) => {
3815
+ return (await store.load(p.sessionId))?.turns.find((t) => t.id === p.turnId) ?? null;
3816
+ },
3817
+ "sessions.touch": (p) => {
3818
+ broadcast({
3819
+ kind: "sessions:changed",
3820
+ op: "update",
3821
+ sessionId: p.sessionId
3822
+ });
3823
+ },
3824
+ "sessions.fork": async (p) => {
3825
+ const result = await host.forkSession({
3826
+ source: p.sessionId,
3827
+ turnId: p.turnId,
3828
+ ...typeof p.title === "string" && p.title ? { title: p.title.slice(0, 256) } : {}
3829
+ });
3830
+ if (!result.ok) throw new Error(describeForkFailure(result.reason, p.sessionId, p.turnId));
3831
+ broadcast({
3832
+ kind: "sessions:changed",
3833
+ op: "create",
3834
+ sessionId: result.id
3835
+ });
3836
+ return { id: result.id };
3837
+ },
3838
+ "sessions.deleteTurn": (p) => host.deleteTurn(p.sessionId, p.turnId),
3839
+ "sessions.editTurnText": (p) => host.editTurnText(p.sessionId, p.turnId, typeof p.text === "string" ? p.text.slice(0, 65536) : ""),
3840
+ "sessions.appendTurns": (p) => host.appendTurns(p.sessionId, Array.isArray(p.turns) ? p.turns : []),
3841
+ "sessions.appendNote": (p) => host.appendNote(p.sessionId, Array.isArray(p.blocks) ? p.blocks.filter((block) => typeof block?.text === "string") : []),
3842
+ "sessions.compact": (p) => host.compactSessionNow(p.sessionId)
3843
+ };
3844
+ }
3845
+ //#endregion
3846
+ //#region src/ipc/daemon/handlers/transcript-cache.ts
3847
+ const DEFAULT_MAX_SESSIONS = 4;
3848
+ const DEFAULT_MAX_BYTES = 67108864;
3849
+ /** Signals that follow a change to a session's turns or runs. */
3850
+ const CHANGE_SIGNALS = /* @__PURE__ */ new Set([
3851
+ "sessions:changed",
3852
+ "run:start",
3853
+ "run:end",
3854
+ "turn:end",
3855
+ "compacting:end",
3856
+ "prompt-turns"
3857
+ ]);
3858
+ /** One kind of entry (replays, documents): LRU by insertion order, bounded by count and bytes. */
3859
+ function createLru(maxSessions, maxBytes) {
3860
+ const entries = /* @__PURE__ */ new Map();
3861
+ let totalBytes = 0;
3862
+ const drop = (sessionId) => {
3863
+ const entry = entries.get(sessionId);
3864
+ if (entry) {
3865
+ totalBytes -= entry.bytes;
3866
+ entries.delete(sessionId);
3867
+ }
3868
+ };
3869
+ return {
3870
+ size: () => entries.size,
3871
+ hit(sessionId, version) {
3872
+ const entry = entries.get(sessionId);
3873
+ if (!entry || entry.version !== version) return void 0;
3874
+ entries.delete(sessionId);
3875
+ entries.set(sessionId, entry);
3876
+ return entry;
3877
+ },
3878
+ drop,
3879
+ keep(sessionId, entry) {
3880
+ if (entry.bytes > maxBytes) return;
3881
+ drop(sessionId);
3882
+ entries.set(sessionId, entry);
3883
+ totalBytes += entry.bytes;
3884
+ for (const [id, oldest] of entries) {
3885
+ if (entries.size <= maxSessions && totalBytes <= maxBytes) break;
3886
+ entries.delete(id);
3887
+ totalBytes -= oldest.bytes;
3888
+ }
3889
+ }
3890
+ };
3891
+ }
3892
+ function createTranscriptReplayCache(options = {}) {
3893
+ const maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;
3894
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
3895
+ const replay = options.replay ?? replayTurns;
3896
+ const replays = createLru(maxSessions, maxBytes);
3897
+ const documents = createLru(maxSessions, maxBytes);
3898
+ const inFlight = /* @__PURE__ */ new Map();
3899
+ /** Labels a read of no known version: never equal to another read's. */
3900
+ let unversioned = 0;
3901
+ function drop(sessionId) {
3902
+ for (const read of inFlight.get(sessionId) ?? []) read.dropped = true;
3903
+ replays.drop(sessionId);
3904
+ documents.drop(sessionId);
3905
+ }
3906
+ /** Run `fn` as an in-flight read of `sessionId`, tracked only while it runs. */
3907
+ async function tracked(sessionId, fn) {
3908
+ const read = { dropped: false };
3909
+ const reads = inFlight.get(sessionId) ?? /* @__PURE__ */ new Set();
3910
+ inFlight.set(sessionId, reads.add(read));
3911
+ try {
3912
+ return await fn(read);
3913
+ } finally {
3914
+ reads.delete(read);
3915
+ if (reads.size === 0 && inFlight.get(sessionId) === reads) inFlight.delete(sessionId);
3916
+ }
3917
+ }
3918
+ /**
3919
+ * A stored session's entry of `lru`, built from `load` on a miss. The
3920
+ * version comes from `getMeta` when the store has it (see the header),
3921
+ * else from the loaded data (`fingerprintOf`).
3922
+ */
3923
+ function stored(lru, store, sessionId, load, build, fingerprintOf) {
3924
+ return tracked(sessionId, async (read) => {
3925
+ if (store.getMeta) {
3926
+ const readAt = Date.now();
3927
+ const meta = await store.getMeta(sessionId);
3928
+ if (!meta) return null;
3929
+ const version = storeVersion(meta);
3930
+ const cached = lru.hit(sessionId, version);
3931
+ if (cached) return cached;
3932
+ const data = await load();
3933
+ if (data === null) return null;
3934
+ let settled;
3935
+ if (meta.rev !== void 0) {
3936
+ const after = await store.getMeta(sessionId);
3937
+ settled = after !== null && storeVersion(after) === version;
3938
+ } else settled = meta.updatedAt < readAt;
3939
+ const entry = build(data, settled ? version : `${version}~${++unversioned}`);
3940
+ if (settled && !read.dropped) lru.keep(sessionId, entry);
3941
+ return entry;
3942
+ }
3943
+ const data = await load();
3944
+ if (data === null) return null;
3945
+ const version = fingerprintOf(data);
3946
+ const cached = lru.hit(sessionId, version);
3947
+ if (cached) return cached;
3948
+ const entry = build(data, version);
3949
+ if (!read.dropped) lru.keep(sessionId, entry);
3950
+ return entry;
3951
+ });
3952
+ }
3953
+ function buildReplay(data, version) {
3954
+ const { events, turnStarts } = replay(data.turns, data.runs);
3955
+ return {
3956
+ version,
3957
+ events,
3958
+ turnStarts,
3959
+ boundaries: exchangeBoundaries(data.turns, data.runs),
3960
+ cuts: turnCutPoints(data.turns),
3961
+ bytes: JSON.stringify(events).length
3962
+ };
3963
+ }
3964
+ const snapshotOf = (data, version) => ({
3965
+ version,
3966
+ data,
3967
+ bytes: JSON.stringify(data).length
3968
+ });
3969
+ return {
3970
+ async page(store, sessionId, paging) {
3971
+ const entry = await stored(replays, store, sessionId, () => loadOrThrow(store, sessionId), buildReplay, (data) => fingerprint(data.updatedAt, data.turns, data.runs));
3972
+ if (!entry) throw new Error(`Session "${sessionId}" not found.`);
3973
+ const { from, to, cursor } = transcriptPage(entry.boundaries, paging, entry.cuts);
3974
+ return {
3975
+ events: entry.events.slice(entry.turnStarts[from], entry.turnStarts[to]),
3976
+ exact: true,
3977
+ start: entry.turnStarts[from],
3978
+ ...cursor !== void 0 ? { cursor } : {}
3979
+ };
3980
+ },
3981
+ async document(store, sessionId, live) {
3982
+ if (live) {
3983
+ const version = `live:${fingerprint(0, live.turns, live.runs)}:${hashText(JSON.stringify([live.status, live.metadata]))}`;
3984
+ return tracked(sessionId, async (read) => {
3985
+ const cached = documents.hit(sessionId, version);
3986
+ if (cached) return cached;
3987
+ const entry = snapshotOf(live.toJSON(), version);
3988
+ if (!read.dropped) documents.keep(sessionId, entry);
3989
+ return entry;
3990
+ });
3991
+ }
3992
+ const load = async () => (await loadSession(store, sessionId, { persistHeal: false }))?.toJSON() ?? null;
3993
+ return stored(documents, store, sessionId, load, snapshotOf, (data) => fingerprint(data.updatedAt, data.turns, data.runs));
3994
+ },
3995
+ observe(signal) {
3996
+ if (CHANGE_SIGNALS.has(signal.kind) && "sessionId" in signal && typeof signal.sessionId === "string") drop(signal.sessionId);
3997
+ },
3998
+ forget: drop,
3999
+ sizes: () => ({
4000
+ entries: replays.size() + documents.size(),
4001
+ inFlight: inFlight.size
4002
+ })
4003
+ };
4004
+ }
4005
+ function storeVersion(meta) {
4006
+ return `meta:${meta.rev ?? "-"}:${meta.updatedAt}:${meta.turnCount}:${meta.runCount}`;
4007
+ }
4008
+ /** A 32-bit FNV-1a: `mix` values in, `digest` (base 36) out. */
4009
+ function fnv1a() {
4010
+ let hash = 2166136261;
4011
+ const add = (text) => {
4012
+ for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
4013
+ };
4014
+ return {
4015
+ add,
4016
+ /** One value, then a separator: `ab` + `c` never hashes as `a` + `bc`. */
4017
+ mix: (value) => {
4018
+ add(String(value));
4019
+ hash = Math.imul(hash ^ 31, 16777619);
4020
+ },
4021
+ digest: () => (hash >>> 0).toString(36)
4022
+ };
4023
+ }
4024
+ function hashText(text) {
4025
+ const hash = fnv1a();
4026
+ hash.add(text);
4027
+ return hash.digest();
4028
+ }
4029
+ /** FNV-1a over the document's shape: what changes when a turn or run is added, removed, or rewritten. */
4030
+ function fingerprint(updatedAt, turns, runs) {
4031
+ const { mix, digest } = fnv1a();
4032
+ for (const turn of turns) {
4033
+ mix(turn.id);
4034
+ mix(turn.role);
4035
+ mix(turn.runId);
4036
+ mix(typeof turn.display === "string" ? turn.display : "");
4037
+ mix(turn.content.length);
4038
+ for (const block of turn.content) {
4039
+ mix(block.type);
4040
+ const fields = block;
4041
+ for (const field of [
4042
+ fields.text,
4043
+ fields.output,
4044
+ fields.summary
4045
+ ]) mix(typeof field === "string" ? field.length : Array.isArray(field) ? field.length : "");
4046
+ }
4047
+ }
4048
+ mix(JSON.stringify(runs));
4049
+ return `doc:${updatedAt}:${turns.length}:${runs.length}:${digest()}`;
4050
+ }
4051
+ //#endregion
4052
+ //#region src/ipc/daemon/handlers/workspace.ts
4053
+ function createWorkspaceHandlers({ host }) {
4054
+ return {
4055
+ "sessions.workspaceExec": (p) => host.workspaceExec(p.sessionId, p.command, {
4056
+ ...p.cwd !== void 0 ? { cwd: p.cwd } : {},
4057
+ ...p.env !== void 0 ? { env: p.env } : {},
4058
+ ...p.timeoutSeconds !== void 0 ? { timeoutSeconds: p.timeoutSeconds } : {}
4059
+ }),
4060
+ "sessions.workspaceReadFile": (p) => host.workspaceReadFile(p.sessionId, p.path),
4061
+ "sessions.workspaceWriteFile": (p) => host.workspaceWriteFile(p.sessionId, p.path, p.content),
4062
+ "sessions.workspaceListFiles": (p) => host.workspaceListFiles(p.sessionId, p.path, p.options),
4063
+ "sessions.workspaceFileSize": (p) => host.workspaceFileSize(p.sessionId, p.path)
4064
+ };
4065
+ }
4066
+ //#endregion
4067
+ //#region src/ipc/daemon/handlers/workspace-exec-stream.ts
4068
+ const WORKSPACE_EXEC_STREAM_LIMITS = {
4069
+ /** Streamed commands one connection may run at once. */
4070
+ maxConcurrent: 4,
4071
+ /** Largest `chunk` of one data signal, in bytes. */
4072
+ chunkBytes: 65536,
4073
+ /** Output past this (stdout + stderr, bytes) stops the process: the same ceiling as a local `!`. */
4074
+ maxOutputBytes: 1048576,
4075
+ /** Longest command text. */
4076
+ maxCommandLength: 65536,
4077
+ /** The buffered fallback's bound (seconds), as the buffered `!` had. */
4078
+ fallbackTimeoutSeconds: 600
4079
+ };
4080
+ /** Cut `text` into pieces of at most `max` UTF-8 bytes, never inside a code point. */
4081
+ function utf8Pieces(text, max) {
4082
+ if (Buffer.byteLength(text) <= max) return [text];
4083
+ const pieces = [];
4084
+ let current = "";
4085
+ let bytes = 0;
4086
+ for (const char of text) {
4087
+ const size = Buffer.byteLength(char);
4088
+ if (bytes + size > max) {
4089
+ pieces.push(current);
4090
+ current = "";
4091
+ bytes = 0;
4092
+ }
4093
+ current += char;
4094
+ bytes += size;
4095
+ }
4096
+ if (current) pieces.push(current);
4097
+ return pieces;
4098
+ }
4099
+ /** The longest prefix of `text` within `max` UTF-8 bytes. */
4100
+ function utf8Prefix(text, max) {
4101
+ let bytes = 0;
4102
+ let end = 0;
4103
+ for (const char of text) {
4104
+ const size = Buffer.byteLength(char);
4105
+ if (bytes + size > max) break;
4106
+ bytes += size;
4107
+ end += char.length;
4108
+ }
4109
+ return text.slice(0, end);
4110
+ }
4111
+ function createWorkspaceExecStreamHandlers({ host, isConnected, limits = WORKSPACE_EXEC_STREAM_LIMITS }) {
4112
+ const running = /* @__PURE__ */ new Map();
4113
+ const perPeer = /* @__PURE__ */ new WeakMap();
4114
+ const send = (peer, signal) => {
4115
+ if (isConnected(peer)) peer.send(signal);
4116
+ };
4117
+ /** Output accounting for one exec: emits what fits, reports when the cap is reached. */
4118
+ function outputSink(execId, peer, onCap) {
4119
+ let total = 0;
4120
+ let capped = false;
4121
+ return {
4122
+ capped: () => capped,
4123
+ emit(stream, text) {
4124
+ if (capped || text === "") return;
4125
+ let piece = text;
4126
+ if (total + Buffer.byteLength(piece) > limits.maxOutputBytes) {
4127
+ piece = utf8Prefix(piece, limits.maxOutputBytes - total);
4128
+ capped = true;
4129
+ }
4130
+ total += Buffer.byteLength(piece);
4131
+ for (const chunk of piece ? utf8Pieces(piece, limits.chunkBytes) : []) send(peer, {
4132
+ kind: "workspace:exec:data",
4133
+ execId,
4134
+ stream,
4135
+ chunk
4136
+ });
4137
+ if (capped) onCap();
4138
+ }
4139
+ };
4140
+ }
4141
+ async function pump(execId, peer, stream, entry) {
4142
+ const sink = outputSink(execId, peer, () => entry.kill());
4143
+ const drain = async (source, name) => {
4144
+ if (!source) return;
4145
+ const decoder = new TextDecoder();
4146
+ try {
4147
+ for await (const bytes of source) sink.emit(name, decoder.decode(bytes, { stream: true }));
4148
+ sink.emit(name, decoder.decode());
4149
+ } catch {}
4150
+ };
4151
+ const [code] = await Promise.all([
4152
+ stream.exited,
4153
+ drain(stream.stdout, "stdout"),
4154
+ drain(stream.stderr, "stderr")
4155
+ ]);
4156
+ const killed = entry.killedLive === true;
4157
+ const exit = {
4158
+ kind: "workspace:exec:exit",
4159
+ execId,
4160
+ code: killed ? null : code,
4161
+ signal: killed ? "SIGTERM" : null,
4162
+ ...sink.capped() ? { truncated: true } : {},
4163
+ ...entry.cancelled && killed ? { cancelled: true } : {}
4164
+ };
4165
+ send(peer, exit);
4166
+ }
4167
+ /** A context without `execStream`: the buffered exec, reported as one stdout and one stderr chunk. */
4168
+ async function buffered(execId, peer, entry, run) {
4169
+ let stopped = false;
4170
+ entry.kill = () => {
4171
+ stopped = true;
4172
+ };
4173
+ const sink = outputSink(execId, peer, () => void 0);
4174
+ try {
4175
+ const result = await run();
4176
+ if (!stopped) {
4177
+ sink.emit("stdout", result.stdout);
4178
+ sink.emit("stderr", result.stderr);
4179
+ }
4180
+ send(peer, {
4181
+ kind: "workspace:exec:exit",
4182
+ execId,
4183
+ code: stopped ? null : result.exitCode,
4184
+ signal: stopped ? "SIGTERM" : null,
4185
+ ...sink.capped() ? { truncated: true } : {},
4186
+ ...entry.cancelled ? { cancelled: true } : {}
4187
+ });
4188
+ } catch (err) {
4189
+ sink.emit("stderr", err instanceof Error ? err.message : String(err));
4190
+ send(peer, {
4191
+ kind: "workspace:exec:exit",
4192
+ execId,
4193
+ code: null,
4194
+ signal: null,
4195
+ ...entry.cancelled ? { cancelled: true } : {}
4196
+ });
4197
+ }
4198
+ }
4199
+ return {
4200
+ methods: {
4201
+ "sessions.workspaceExecStream": async (p, peer) => {
4202
+ if (typeof p.sessionId !== "string" || p.sessionId === "") throw new InvalidParamsError("sessionId must be a non-empty string.");
4203
+ if (typeof p.command !== "string" || p.command.trim() === "") throw new InvalidParamsError("command must be a non-empty string.");
4204
+ if (p.command.length > limits.maxCommandLength) throw new InvalidParamsError(`command is longer than ${limits.maxCommandLength} characters.`);
4205
+ if (p.cwd !== void 0 && typeof p.cwd !== "string") throw new InvalidParamsError("cwd must be a string.");
4206
+ const own = perPeer.get(peer) ?? /* @__PURE__ */ new Set();
4207
+ perPeer.set(peer, own);
4208
+ if (own.size >= limits.maxConcurrent) throw new Error(`This connection already runs ${own.size} shell commands (at most ${limits.maxConcurrent}): stop one first.`);
4209
+ const execId = randomUUID();
4210
+ own.add(execId);
4211
+ const entry = {
4212
+ peer,
4213
+ kill: () => void 0,
4214
+ cancelled: false
4215
+ };
4216
+ running.set(execId, entry);
4217
+ const done = () => {
4218
+ own.delete(execId);
4219
+ running.delete(execId);
4220
+ };
4221
+ const options = p.cwd !== void 0 ? { cwd: p.cwd } : {};
4222
+ let stream;
4223
+ try {
4224
+ stream = await host.workspaceExecStream(p.sessionId, p.command, options);
4225
+ } catch (err) {
4226
+ done();
4227
+ throw err;
4228
+ }
4229
+ if (stream) {
4230
+ const live = stream;
4231
+ let exited = false;
4232
+ live.exited.then(() => {
4233
+ exited = true;
4234
+ }, () => {
4235
+ exited = true;
4236
+ });
4237
+ entry.kill = () => {
4238
+ if (!exited) entry.killedLive = true;
4239
+ live.kill();
4240
+ };
4241
+ if (!isConnected(peer)) entry.kill();
4242
+ pump(execId, peer, live, entry).finally(done);
4243
+ } else buffered(execId, peer, entry, () => host.workspaceExec(p.sessionId, p.command, {
4244
+ ...options,
4245
+ timeoutSeconds: limits.fallbackTimeoutSeconds
4246
+ })).finally(done);
4247
+ return { execId };
4248
+ },
4249
+ "sessions.workspaceExecCancel": (p, peer) => {
4250
+ const entry = typeof p.execId === "string" ? running.get(p.execId) : void 0;
4251
+ if (!entry || entry.peer !== peer) return { cancelled: false };
4252
+ entry.cancelled = true;
4253
+ entry.kill();
4254
+ return { cancelled: true };
4255
+ }
4256
+ },
4257
+ releasePeer(peer) {
4258
+ for (const execId of perPeer.get(peer) ?? []) {
4259
+ const entry = running.get(execId);
4260
+ if (entry) {
4261
+ entry.cancelled = true;
4262
+ entry.kill();
4263
+ }
4264
+ }
4265
+ }
4266
+ };
4267
+ }
4268
+ //#endregion
4269
+ //#region src/ipc/daemon/handshake.ts
4270
+ function evaluateServerHello(params) {
4271
+ if (params.protocolVersion === 13) return {
4272
+ accept: true,
4273
+ retire: false
4274
+ };
4275
+ const client = typeof params.protocolVersion === "number" ? params.protocolVersion : void 0;
4276
+ return {
4277
+ accept: false,
4278
+ message: `Protocol version mismatch: the server speaks v13, the client v${client ?? "?"}. Upgrade the ${client !== void 0 && client > 13 ? "server" : "client"}.`
4279
+ };
4280
+ }
4281
+ function evaluateHello(params, ctx) {
4282
+ if (ctx.hostKind === "server") return evaluateServerHello(params);
4283
+ const clientBuild = Number(params.buildId);
4284
+ const daemonBuild = Number(ctx.buildId);
4285
+ const staleBuild = params.buildId !== void 0 && params.buildId !== "unknown" && ctx.buildId !== "unknown" && Number.isFinite(clientBuild) && Number.isFinite(daemonBuild) && clientBuild > daemonBuild;
4286
+ const staleRelease = typeof params.codeVersion === "string" && compareVersions(params.codeVersion, ctx.codeVersion) > 0;
4287
+ const protocolMatches = params.protocolVersion === 13;
4288
+ if (protocolMatches && (staleBuild || staleRelease) && ctx.hasActiveWork) return {
4289
+ accept: true,
4290
+ retire: true
4291
+ };
4292
+ if (!protocolMatches || staleBuild || staleRelease) return {
4293
+ accept: false,
4294
+ message: staleRelease ? `Stale daemon: it runs zidane ${ctx.codeVersion} but the client runs ${params.codeVersion}. Draining for respawn.` : staleBuild ? `Build mismatch: the daemon was started from an older build (daemon ${ctx.buildId}, client ${params.buildId}). Draining for respawn.` : `Protocol version mismatch: daemon speaks v13, client sent v${params.protocolVersion ?? "?"}.`
4295
+ };
4296
+ return {
4297
+ accept: true,
4298
+ retire: false
4299
+ };
4300
+ }
4301
+ //#endregion
4302
+ //#region src/ipc/daemon/identity.ts
4303
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4304
+ const FILE = "host-id";
4305
+ function readId(path) {
4306
+ try {
4307
+ const id = readFileSync(path, "utf8").trim();
4308
+ return UUID.test(id) ? id : void 0;
4309
+ } catch {
4310
+ return;
4311
+ }
4312
+ }
4313
+ /**
4314
+ * The id persisted in `dataDir`, created on first use. Two processes starting
4315
+ * at once agree: the file is created exclusively (`wx`) and the loser reads
4316
+ * the winner's id. A missing or corrupt file gets a fresh id.
4317
+ */
4318
+ function readOrCreateHostId(dataDir) {
4319
+ const path = join(dataDir, FILE);
4320
+ const existing = readId(path);
4321
+ if (existing) return existing;
4322
+ const id = randomUUID();
4323
+ try {
4324
+ mkdirSync(dataDir, { recursive: true });
4325
+ writeFileSync(path, `${id}\n`, {
4326
+ flag: "wx",
4327
+ mode: 384
4328
+ });
4329
+ return id;
4330
+ } catch (err) {
4331
+ if (err.code === "EEXIST") {
4332
+ const raced = readId(path);
4333
+ if (raced) return raced;
4334
+ try {
4335
+ writeFileSync(path, `${id}\n`, { mode: 384 });
4336
+ return readId(path) ?? id;
4337
+ } catch {
4338
+ return;
4339
+ }
4340
+ }
4341
+ return;
4342
+ }
4343
+ }
4344
+ function resolveHostIdentity(dataDir, options) {
4345
+ return {
4346
+ id: readOrCreateHostId(dataDir),
4347
+ kind: options.kind,
4348
+ name: options.name ?? hostname()
4349
+ };
4350
+ }
4351
+ //#endregion
4352
+ //#region src/ipc/daemon/peers.ts
4353
+ function createPeerRegistry(options = {}) {
4354
+ const peers = /* @__PURE__ */ new Set();
4355
+ let boundHost = null;
4356
+ const closeCleanups = [];
4357
+ /** Sessions with a run in flight, tracked from the run signals themselves. */
4358
+ const runningSessions = /* @__PURE__ */ new Set();
4359
+ const broadcast = (signal) => {
4360
+ if (signal.kind === "terminal:output" || signal.kind === "terminal:reset") {
4361
+ for (const peer of peers) if (peer.ready && peer.terminalViews?.has(signal.terminalId)) peer.send(signal);
4362
+ return;
4363
+ }
4364
+ fanOut(null, signal, options.sequence?.(signal));
4365
+ switch (signal.kind) {
4366
+ case "run:start":
4367
+ runningSessions.add(signal.sessionId);
4368
+ broadcastPresence();
4369
+ break;
4370
+ case "run:end":
4371
+ runningSessions.delete(signal.sessionId);
4372
+ broadcastPresence();
4373
+ break;
4374
+ case "approval:request":
4375
+ case "approval:resolved":
4376
+ case "interaction:request":
4377
+ case "interaction:resolved": broadcastPresence();
4378
+ }
4379
+ };
4380
+ function fanOut(origin, signal, seq) {
4381
+ for (const peer of peers) {
4382
+ if (!peer.ready || peer === origin) continue;
4383
+ peer.send(signal, seq);
4384
+ }
4385
+ }
4386
+ function broadcastExcept(origin, signal) {
4387
+ fanOut(origin, signal, void 0);
4388
+ }
4389
+ /**
4390
+ * Live presence: which sessions have attached windows, and their
4391
+ * run / awaiting-input state. Union of watched, live-agent, and
4392
+ * pending-HITL sessions — small by construction.
4393
+ */
4394
+ function computePresence() {
4395
+ const host = boundHost;
4396
+ if (!host) throw new Error("Presence read before the host was bound.");
4397
+ const windowCounts = /* @__PURE__ */ new Map();
4398
+ for (const peer of peers) if (peer.ready && peer.watching) windowCounts.set(peer.watching, (windowCounts.get(peer.watching) ?? 0) + 1);
4399
+ const attention = /* @__PURE__ */ new Set();
4400
+ for (const request of host.approvals.listPending()) attention.add(request.sessionId);
4401
+ for (const pending of host.interactions.list()) attention.add(pending.sessionId);
4402
+ const live = new Map(host.listLiveSessions().map((entry) => [entry.sessionId, entry.running]));
4403
+ return [.../* @__PURE__ */ new Set([
4404
+ ...windowCounts.keys(),
4405
+ ...attention,
4406
+ ...live.keys(),
4407
+ ...runningSessions
4408
+ ])].map((sessionId) => ({
4409
+ sessionId,
4410
+ windows: windowCounts.get(sessionId) ?? 0,
4411
+ running: runningSessions.has(sessionId) || live.get(sessionId) === true,
4412
+ attention: attention.has(sessionId)
4413
+ }));
4414
+ }
4415
+ function broadcastPresence() {
4416
+ broadcastExcept(null, {
4417
+ kind: "presence:changed",
4418
+ sessions: computePresence()
4419
+ });
4420
+ }
4421
+ return {
4422
+ add(peer) {
4423
+ peers.add(peer);
4424
+ },
4425
+ has: (peer) => peers.has(peer),
4426
+ remove(peer) {
4427
+ if (!peers.delete(peer)) return;
4428
+ for (const cleanup of closeCleanups) cleanup(peer);
4429
+ if (peer.watching) broadcastPresence();
4430
+ },
4431
+ onClose(cleanup) {
4432
+ closeCleanups.push(cleanup);
4433
+ },
4434
+ clear() {
4435
+ peers.clear();
4436
+ },
4437
+ size: () => peers.size,
4438
+ broadcast,
4439
+ broadcastExcept,
4440
+ computePresence,
4441
+ broadcastPresence,
4442
+ bindHost(host) {
4443
+ boundHost = host;
4444
+ }
4445
+ };
4446
+ }
4447
+ //#endregion
4448
+ //#region src/ipc/daemon/signal-log.ts
4449
+ function sessionOf(signal) {
4450
+ const id = signal.sessionId;
4451
+ return typeof id === "string" ? id : void 0;
4452
+ }
4453
+ function createSignalLog(options = {}) {
4454
+ const maxSignals = options.maxSignals ?? 2e3;
4455
+ const maxBytes = options.maxBytes ?? 4194304;
4456
+ const maxTotalBytes = options.maxTotalBytes ?? 33554432;
4457
+ const maxForgotten = options.maxForgotten ?? 1e4;
4458
+ const epoch = randomUUID();
4459
+ /** Insertion order = least recently written first. */
4460
+ const logs = /* @__PURE__ */ new Map();
4461
+ /** Next number of forgotten sessions; insertion order = least recently forgotten first. */
4462
+ const forgotten = /* @__PURE__ */ new Map();
4463
+ /** Every number below this may have been given out by a session whose counter is gone. */
4464
+ let floor = 1;
4465
+ let totalBytes = 0;
4466
+ const dropOldest = (log) => {
4467
+ totalBytes -= log.entries[log.head].bytes;
4468
+ log.bytes -= log.entries[log.head].bytes;
4469
+ log.head++;
4470
+ if (log.head > 1024 && log.head * 2 > log.entries.length) {
4471
+ log.entries = log.entries.slice(log.head);
4472
+ log.head = 0;
4473
+ }
4474
+ };
4475
+ const clear = (log) => {
4476
+ totalBytes -= log.bytes;
4477
+ log.entries = [];
4478
+ log.head = 0;
4479
+ log.bytes = 0;
4480
+ };
4481
+ return {
4482
+ epoch,
4483
+ record(signal) {
4484
+ const sessionId = sessionOf(signal);
4485
+ if (sessionId === void 0) return void 0;
4486
+ let log = logs.get(sessionId);
4487
+ if (log) logs.delete(sessionId);
4488
+ else {
4489
+ log = {
4490
+ next: forgotten.get(sessionId) ?? floor,
4491
+ entries: [],
4492
+ head: 0,
4493
+ bytes: 0
4494
+ };
4495
+ forgotten.delete(sessionId);
4496
+ }
4497
+ logs.set(sessionId, log);
4498
+ const seq = log.next++;
4499
+ let json;
4500
+ try {
4501
+ json = JSON.stringify(signal);
4502
+ } catch {
4503
+ clear(log);
4504
+ return seq;
4505
+ }
4506
+ const bytes = Buffer.byteLength(json);
4507
+ log.entries.push({
4508
+ seq,
4509
+ json,
4510
+ bytes
4511
+ });
4512
+ log.bytes += bytes;
4513
+ totalBytes += bytes;
4514
+ while (log.entries.length - log.head > maxSignals || log.bytes > maxBytes && log.entries.length - log.head > 0) dropOldest(log);
4515
+ for (const [id, other] of logs) {
4516
+ if (totalBytes <= maxTotalBytes || id === sessionId) break;
4517
+ clear(other);
4518
+ }
4519
+ return seq;
4520
+ },
4521
+ replay(sessionId, afterSeq) {
4522
+ const log = logs.get(sessionId);
4523
+ if (!log || !Number.isSafeInteger(afterSeq) || afterSeq < 0) return null;
4524
+ const last = log.next - 1;
4525
+ if (afterSeq > last || afterSeq < 0) return null;
4526
+ if (afterSeq === last) return [];
4527
+ const first = log.entries[log.head]?.seq;
4528
+ if (first === void 0 || first > afterSeq + 1) return null;
4529
+ const out = [];
4530
+ for (let i = log.head; i < log.entries.length; i++) {
4531
+ const entry = log.entries[i];
4532
+ if (entry.seq > afterSeq) out.push({
4533
+ seq: entry.seq,
4534
+ signal: JSON.parse(entry.json)
4535
+ });
4536
+ }
4537
+ return out;
4538
+ },
4539
+ forget(sessionId) {
4540
+ const log = logs.get(sessionId);
4541
+ if (!log) return;
4542
+ clear(log);
4543
+ logs.delete(sessionId);
4544
+ forgotten.set(sessionId, log.next);
4545
+ while (forgotten.size > maxForgotten) {
4546
+ const [oldest, next] = forgotten.entries().next().value;
4547
+ forgotten.delete(oldest);
4548
+ floor = Math.max(floor, next);
4549
+ }
4550
+ },
4551
+ sessionCount: () => logs.size + forgotten.size
4552
+ };
4553
+ }
4554
+ //#endregion
4555
+ //#region src/ipc/daemon/workspace-roots.ts
4556
+ /**
4557
+ * The folders a host lets sessions use, as `fs.*` lists them: the host's own
4558
+ * confinement (`ChatHost.workspace`, `src/chat/workspace-confinement.ts`,
4559
+ * which the host enforces at its entry points), plus the folder listing. A
4560
+ * server is confined to its workspace roots; a local host is not, and its
4561
+ * listing root is the user's home, like the window's own folder picker.
4562
+ */
4563
+ /** Most folders one listing returns. */
4564
+ const MAX_DIR_ENTRIES = 2e3;
4565
+ function createWorkspaceRoots(confinement) {
4566
+ const { confined, roots, resolveFolder } = confinement;
4567
+ return {
4568
+ describe: () => ({
4569
+ roots: [...roots],
4570
+ confined
4571
+ }),
4572
+ resolveFolder,
4573
+ checkFolder: confinement.checkFolder,
4574
+ sessionFolder: confinement.sessionFolder,
4575
+ list(path) {
4576
+ const dir = resolveFolder(path ?? roots[0]);
4577
+ const entries = [];
4578
+ let truncated = false;
4579
+ const names = readdirSync(dir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && (e.isDirectory() || e.isSymbolicLink())).map((e) => e.name).sort((a, b) => a.localeCompare(b));
4580
+ for (const name of names) {
4581
+ let real;
4582
+ try {
4583
+ real = realpathSync.native(join(dir, name));
4584
+ if (!statSync(real).isDirectory()) continue;
4585
+ } catch {
4586
+ continue;
4587
+ }
4588
+ if (!roots.some((root) => isInside(root, real))) continue;
4589
+ if (entries.length >= MAX_DIR_ENTRIES) {
4590
+ truncated = true;
4591
+ break;
4592
+ }
4593
+ entries.push({
4594
+ name,
4595
+ path: real,
4596
+ isGitRepo: existsSync(join(real, ".git"))
4597
+ });
4598
+ }
4599
+ const atRoot = roots.includes(dir);
4600
+ return {
4601
+ roots: [...roots],
4602
+ path: dir,
4603
+ parent: atRoot ? null : dirname(dir),
4604
+ entries,
4605
+ ...truncated ? { truncated } : {}
4606
+ };
4607
+ }
4608
+ };
4609
+ }
4610
+ //#endregion
4611
+ //#region src/ipc/daemon/dispatcher.ts
4612
+ /** Codes a machine error keeps over the wire (`MachineErrorCode`). */
4613
+ const MACHINE_ERROR_CODES = new Set(Object.values(MachineErrorCode));
4614
+ async function createHostDispatcher(options) {
4615
+ const { appVersion, startedAt, lifecycle } = options;
4616
+ const signalLog = createSignalLog(options.replay);
4617
+ const replays = createTranscriptReplayCache();
4618
+ const peers = createPeerRegistry({ sequence: (signal) => {
4619
+ replays.observe(signal);
4620
+ return signalLog.record(signal);
4621
+ } });
4622
+ const { broadcast, broadcastExcept } = peers;
4623
+ const env = await (options.createEnvironment ? options.createEnvironment(broadcast) : createDaemonEnvironment({ broadcast }));
4624
+ const host = createChatHost(env);
4625
+ peers.bindHost(host);
4626
+ host.approvals.rehydrate();
4627
+ const store = env.store;
4628
+ const identity = resolveHostIdentity(env.userDataDir(), options.identity ?? { kind: "daemon" });
4629
+ const identityFields = {
4630
+ ...identity.id ? { hostId: identity.id } : {},
4631
+ hostKind: identity.kind,
4632
+ hostName: identity.name
4633
+ };
4634
+ const workspace = createWorkspaceRoots(host.workspace);
4635
+ const drafts = createDraftHandlers({ broadcastExcept });
4636
+ const suggest = createSuggestHandlers({
4637
+ env,
4638
+ broadcastExcept
4639
+ });
4640
+ const launchOverrides = createLaunchOverrideHandlers({ host });
4641
+ const browser = createBrowserHandlers({ host });
4642
+ const terminal = createTerminalHandlers({
4643
+ host,
4644
+ isConnected: peers.has
4645
+ });
4646
+ const execStreams = createWorkspaceExecStreamHandlers({
4647
+ host,
4648
+ isConnected: peers.has
4649
+ });
4650
+ const auth = createProviderAuth({
4651
+ host,
4652
+ env
4653
+ });
4654
+ const oauth = createOAuthFlowHandlers({
4655
+ host,
4656
+ env,
4657
+ auth,
4658
+ isConnected: peers.has,
4659
+ ...options.oauthFlowTtlMs !== void 0 ? { ttlMs: options.oauthFlowTtlMs } : {}
4660
+ });
4661
+ const settings = createSettingsHandlers({
4662
+ host,
4663
+ env,
4664
+ broadcast
4665
+ });
4666
+ const installs = createCatalogInstallHandlers({
4667
+ host,
4668
+ env,
4669
+ broadcast,
4670
+ setSettings: (patch) => settings["settings.set"]({ patch }, null),
4671
+ ...options.remoteInstalls ? { options: options.remoteInstalls } : {}
4672
+ });
4673
+ const authHandlers = createAuthHandlers({ auth });
4674
+ const { markOverrides, ...syncMethods } = createSyncHandlers({
4675
+ host,
4676
+ env,
4677
+ broadcast,
4678
+ auth,
4679
+ setters: {
4680
+ setSettings: (patch) => settings["settings.set"]({ patch }, null),
4681
+ install: (p, peer) => installs["catalog.install"](p, peer),
4682
+ mcpUpsert: (p, peer) => installs["mcp.upsert"](p, peer),
4683
+ mcpRemove: (p, peer) => installs["mcp.remove"](p, peer),
4684
+ setApiKey: (p) => authHandlers["auth.setApiKey"](p, null)
4685
+ },
4686
+ ...options.sync ? { options: options.sync } : {}
4687
+ });
4688
+ const machineServe = createMachineServeHandlers({
4689
+ host,
4690
+ dataDir: () => env.userDataDir(),
4691
+ hostName: identity.name,
4692
+ settings: () => env.readSettings(),
4693
+ isConnected: peers.has,
4694
+ ...options.machineServe ? { options: options.machineServe } : {}
4695
+ });
4696
+ peers.onClose(browser.releasePeer);
4697
+ peers.onClose(terminal.releasePeer);
4698
+ peers.onClose(launchOverrides.releasePeer);
4699
+ peers.onClose(machineServe.releasePeer);
4700
+ peers.onClose(execStreams.releasePeer);
4701
+ peers.onClose(oauth.releasePeer);
4702
+ const forgetSession = (sessionId) => {
4703
+ drafts.forget(sessionId);
4704
+ launchOverrides.forget(sessionId);
4705
+ suggest.forget(sessionId);
4706
+ signalLog.forget(sessionId);
4707
+ replays.forget(sessionId);
4708
+ };
4709
+ const methods = {
4710
+ ...createChatHandlers({
4711
+ host,
4712
+ clearDraft: drafts.clearDraft
4713
+ }),
4714
+ ...createQueueHandlers({
4715
+ host,
4716
+ clearDraft: drafts.clearDraft
4717
+ }),
4718
+ ...drafts.methods,
4719
+ ...suggest.methods,
4720
+ ...createExtensionHandlers({
4721
+ env,
4722
+ broadcastExcept
4723
+ }),
4724
+ ...launchOverrides.methods,
4725
+ ...createSessionHandlers({
4726
+ host,
4727
+ store,
4728
+ broadcast,
4729
+ forgetSession,
4730
+ hostId: identity.id,
4731
+ workspace
4732
+ }),
4733
+ ...createTitleHandlers({
4734
+ host,
4735
+ broadcast
4736
+ }),
4737
+ ...createSessionReadHandlers({
4738
+ host,
4739
+ store,
4740
+ snapshots: replays
4741
+ }),
4742
+ ...createTranscriptHandlers({
4743
+ host,
4744
+ store,
4745
+ broadcast,
4746
+ replays
4747
+ }),
4748
+ ...createResumeHandlers({ log: signalLog }),
4749
+ ...createContextHandlers({ host }),
4750
+ ...createEnvironmentHandlers({
4751
+ host,
4752
+ env,
4753
+ setSettings: settings["settings.set"]
4754
+ }),
4755
+ ...createWorkspaceHandlers({ host }),
4756
+ ...execStreams.methods,
4757
+ ...createFsHandlers({ workspace }),
4758
+ ...authHandlers,
4759
+ ...oauth.methods,
4760
+ ...installs,
4761
+ ...createCatalogHandlers({
4762
+ env,
4763
+ host
4764
+ }),
4765
+ ...createArtifactHandlers({
4766
+ store,
4767
+ broadcast
4768
+ }),
4769
+ ...createHistoryHandlers({
4770
+ host,
4771
+ store
4772
+ }),
4773
+ ...createTodoHandlers({
4774
+ host,
4775
+ store
4776
+ }),
4777
+ ...createOrchestrationHandlers({ host }),
4778
+ ...createHitlHandlers({ host }),
4779
+ ...browser.methods,
4780
+ ...terminal.methods,
4781
+ ...settings,
4782
+ ...createPresenceHandlers({
4783
+ env,
4784
+ peers
4785
+ }),
4786
+ ...createMachineHandlers({
4787
+ host,
4788
+ broadcast
4789
+ }),
4790
+ ...machineServe.methods,
4791
+ ...createMachineLinkHandlers({
4792
+ host,
4793
+ env,
4794
+ broadcast,
4795
+ hostId: identity.id,
4796
+ server: identity.kind === "server",
4797
+ appVersion,
4798
+ ...options.machineLinks ? { pacing: options.machineLinks } : {}
4799
+ }),
4800
+ "tokens.createMachineToken": (p, peer) => {
4801
+ if (!options.tokens) throw new Error("This host has no tokens to mint (only a zidane-server does).");
4802
+ if (typeof p.name !== "string" || p.name.trim() === "") throw new InvalidParamsError("tokens.createMachineToken: \"name\" must be a non-empty string.");
4803
+ return options.tokens.createMachineToken(p.name.trim().slice(0, 100), peer.principal);
4804
+ },
4805
+ ...createDaemonHandlers({
4806
+ host,
4807
+ appVersion,
4808
+ startedAt,
4809
+ lifecycle,
4810
+ identity: identityFields
4811
+ }),
4812
+ ...syncMethods,
4813
+ ...options.sync ? withSyncOverrides({
4814
+ "settings.set": settings["settings.set"],
4815
+ "mcp.upsert": installs["mcp.upsert"],
4816
+ "mcp.remove": installs["mcp.remove"],
4817
+ "catalog.remove": installs["catalog.remove"],
4818
+ "catalog.update": installs["catalog.update"],
4819
+ "auth.setApiKey": authHandlers["auth.setApiKey"],
4820
+ "auth.remove": authHandlers["auth.remove"]
4821
+ }, {
4822
+ readSettings: () => env.readSettings(),
4823
+ markOverrides
4824
+ }) : {}
4825
+ };
4826
+ /**
4827
+ * Widened view for the string-keyed lookup: the table is typed exhaustively
4828
+ * above; unknown wire methods fall out here. Own keys only: a wire method
4829
+ * named `constructor` / `__proto__` / `toString` is unknown, not a call into
4830
+ * `Object.prototype`.
4831
+ */
4832
+ const handlerFor = (method) => Object.hasOwn(methods, method) ? methods[method] : void 0;
4833
+ function hello(params, peer) {
4834
+ const verdict = evaluateHello(params, {
4835
+ buildId: IPC_BUILD_ID,
4836
+ codeVersion: ZIDANE_VERSION,
4837
+ hasActiveWork: host.hasActiveWork(),
4838
+ hostKind: identity.kind
4839
+ });
4840
+ if (!verdict.accept) return {
4841
+ ok: false,
4842
+ code: IpcErrorCode.VersionMismatch,
4843
+ message: verdict.message,
4844
+ data: { daemon: {
4845
+ protocolVersion: 13,
4846
+ appVersion,
4847
+ pid: process.pid,
4848
+ startedAt,
4849
+ buildId: IPC_BUILD_ID,
4850
+ codeVersion: ZIDANE_VERSION,
4851
+ ...identityFields
4852
+ } }
4853
+ };
4854
+ if (verdict.retire) lifecycle.retireWhenIdle();
4855
+ peer.ready = true;
4856
+ return {
4857
+ ok: true,
4858
+ result: {
4859
+ protocolVersion: 13,
4860
+ appVersion,
4861
+ pid: process.pid,
4862
+ startedAt,
4863
+ buildId: IPC_BUILD_ID,
4864
+ codeVersion: ZIDANE_VERSION,
4865
+ ...identityFields,
4866
+ signalEpoch: signalLog.epoch,
4867
+ ...lifecycle.isRetiring() ? { retiring: true } : {},
4868
+ ...machineServe.allowed() ? { machineServe: true } : {},
4869
+ ...remoteInstallsAllowed(options.remoteInstalls) ? { remoteInstalls: true } : {},
4870
+ ...options.sync ? { sync: true } : {}
4871
+ }
4872
+ };
4873
+ }
4874
+ return {
4875
+ host,
4876
+ addPeer: peers.add,
4877
+ removePeer: peers.remove,
4878
+ detachAll: peers.clear,
4879
+ peerCount: peers.size,
4880
+ async handleRequest(method, params, peer) {
4881
+ if (method === "$/ping") return {
4882
+ ok: true,
4883
+ result: {}
4884
+ };
4885
+ if (method === "ipc.hello") return hello(params ?? {}, peer);
4886
+ if (!peer.ready && method !== "daemon.shutdown" && method !== "daemon.info") return {
4887
+ ok: false,
4888
+ code: IpcErrorCode.NotHandshaken,
4889
+ message: "Handshake required: send ipc.hello first."
4890
+ };
4891
+ const handler = handlerFor(method);
4892
+ if (!handler && method.startsWith("machine.")) return {
4893
+ ok: false,
4894
+ code: MachineErrorCode.MethodNotAllowed,
4895
+ message: `"${method}" needs a machine connection (a machine-scoped token); this is a client connection.`
4896
+ };
4897
+ if (!handler) return {
4898
+ ok: false,
4899
+ code: -32601,
4900
+ message: `Unknown method "${method}".`
4901
+ };
4902
+ try {
4903
+ checkParams(params);
4904
+ return {
4905
+ ok: true,
4906
+ result: await handler(params, peer) ?? null
4907
+ };
4908
+ } catch (err) {
4909
+ if (err instanceof InvalidParamsError) return {
4910
+ ok: false,
4911
+ code: -32602,
4912
+ message: err.message
4913
+ };
4914
+ const code = err?.code;
4915
+ if (typeof code === "number" && MACHINE_ERROR_CODES.has(code)) return {
4916
+ ok: false,
4917
+ code,
4918
+ message: err.message
4919
+ };
4920
+ return {
4921
+ ok: false,
4922
+ code: IpcErrorCode.HandlerFailed,
4923
+ message: err instanceof Error ? err.message : String(err)
4924
+ };
4925
+ }
4926
+ },
4927
+ async handleNotification(method, params, peer) {
4928
+ if (!peer.ready) return;
4929
+ const handler = handlerFor(method);
4930
+ if (!handler) return;
4931
+ try {
4932
+ checkParams(params);
4933
+ await handler(params, peer);
4934
+ } catch {}
4935
+ }
4936
+ };
4937
+ }
4938
+ //#endregion
4939
+ export { turnCutPoints as a, createDaemonEnvironment as c, connectIpcClient as d, connectOrSpawnDaemon as f, transcriptPage as i, createFileApprovalsStore as l, WHOLE_RUN_PAGES as n, createMachineHandlers as o, createPacedWriter as p, exchangeBoundaries as r, createMachineLinkHandlers as s, createHostDispatcher as t, createHostEnvironment as u };
4940
+
4941
+ //# sourceMappingURL=dispatcher-BMlu2BmZ.js.map