zidane 7.2.4 → 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 -27242
  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-zVSyo6K1.js +0 -331
  342. package/dist/ipc-zVSyo6K1.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-h5czzllR.js +0 -1549
  363. package/dist/shell-path-h5czzllR.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
@@ -1,8 +1,90 @@
1
1
  # Zidane Architecture
2
2
 
3
- Internal execution model of the agent loop: how a run boots, how a turn streams, how a tool call is gated/executed/transformed, and the hook contracts that hold it together. Read this when you're modifying `src/agent.ts` / `src/loop.ts`, writing a hook that depends on firing order, or diagnosing a prompt-cache break. For the public API surface (createAgent, presets, hooks, sessions) see `docs/SKILL.md`.
3
+ Two parts. **System architecture** (first): the layers from the SDK core up to the TUI/GUI, where code goes, and the limits lint enforces. Read it before adding a feature or moving code between folders. **Agent loop internals** (the rest): how a run boots, how a turn streams, how a tool call is gated/executed/transformed, and the hook contracts that hold it together. Read that part when you're modifying `src/agent.ts` / `src/loop.ts`, writing a hook that depends on firing order, or diagnosing a prompt-cache break. For the public API surface (createAgent, presets, hooks, sessions) see `docs/SKILL.md`.
4
4
 
5
- Contents: [Agent Lifecycle](#agent-lifecycle) · [Turn Loop](#turn-loop) · [Single Turn](#single-turn) · [Tool Execution](#tool-execution) · [Tool ergonomics defaults](#tool-ergonomics-defaults) · [Prompt caching](#prompt-caching) · [Tool aliasing](#tool-aliasing-wire-vs-canonical) · [Skills activation lifecycle](#skills-activation-lifecycle) · [Progressive tool disclosure](#progressive-tool-disclosure) · [Hook Firing Order](#hook-firing-order) · [Dependency Graph](#dependency-graph) · [Presets](#presets) · [Option Precedence](#option-precedence)
5
+ System: [Layers](#layers) · [Feature folders](#feature-folders) · [Adding a feature](#adding-a-feature) · [Enforced limits](#enforced-limits) · [Testing contracts](#testing-contracts) · [Opt-in features](#opt-in-features)
6
+
7
+ ## Layers
8
+
9
+ Dependencies point one way, down this list. Each layer may import the ones above it, never the ones below.
10
+
11
+ | Layer | Where | Composition root | Details |
12
+ |---|---|---|---|
13
+ | SDK core | `src/*.ts`, `src/<folder>/` except `chat`, `ipc`, `tui`, `server` | `createAgent` (`src/agent.ts`) | this doc, below; `docs/SKILL.md` |
14
+ | Chat engine | `src/chat/` (engine in `src/chat/host/`) | `createChatHost` (`src/chat/host/engine.ts`) | `docs/CHAT.md` |
15
+ | Dispatcher + protocol | `src/ipc/protocol.ts`, `src/ipc/daemon/` | `src/ipc/daemon/dispatcher.ts`, one handler module per feature in `handlers/` (request params shape-checked first, `handlers/params.ts`) | `docs/IPC.md` |
16
+ | Transports | unix socket (`src/ipc/daemon/server.ts` + `src/ipc/client.ts`), in-memory (`src/ipc/memory.ts`), WebSocket (`src/ipc/transports/ws-server.ts` + `ws-client.ts`, token carriers `ws-auth.ts`, per-connection rate limit `rate-limit.ts`); shared by the wire transports in `src/ipc/transports/`: the host side of a connection (`peer-rpc.ts`), its send budget (`send-budget.ts`: signals backlog bound, reply size), the client side (`client-core.ts`) | `startIpcDaemon` / `startInProcessHost` / `startWsServer` | `docs/IPC.md` |
17
+ | Remote host (zidane-server) | `src/server/` (`server.ts`, device tokens `tokens.ts`, `config.ts`, origin / proxy policy and the `private` trusted-proxies keyword `security.ts`, the upgrade checks before the token `upgrade-guard.ts`, `audit.ts`, the `zidane server` CLI `cli.ts` (health probes, bootstrap token), standalone entry `main.ts`; the host both front-ends share comes from `createServerHostDispatcher` in `ws-server.ts`, which also serves `/healthz`); clients' side of several hosts in `src/chat/hosts/` (registry, host index, presence merge) and `src/ipc/hosts/` (probe, connection loop, saved servers; `zidane/ipc/remote`, entry `src/ipc/remote.ts`); packaging in `server/` (workspace package, Dockerfile, compose files incl. Coolify's), binary built by `scripts/build-server.ts` | `startZidaneServer` (one host behind the WebSocket transport with token auth, and a unix socket) | `docs/IPC.md` § Remote server; running it: `docs/SERVER.md` |
18
+ | Machines | engine side `src/chat/host/machines/` (registry, remote `ExecutionContext`, offline wait, `local@`/`worktree@<machine>` providers; wire types in the leaf `src/chat/machine-protocol.ts`); the WS side `src/ipc/daemon/machine-gateway.ts`; the machine agent, its confined executor and `zidane machine share` in `src/ipc/machine/` | `createMachines` (engine), `startMachineAgent` | `docs/MACHINES.md` |
19
+ | Clients | TUI (`src/tui/`, CLI in `tui/`), GUI (`gui/`, separate package), `zidane attach` (`src/ipc/attach.ts`) | `src/tui/app.tsx`; `gui/src/main/chat/ipc.ts` | `docs/TUI.md`, `docs/CHAT.md` § GUI checklist |
20
+
21
+ The engine owns all session state and behavior; the dispatcher is a transport-free JSON-RPC method table around one `ChatHost`; transports only frame messages. A client sees the same protocol whether the host runs in a shared daemon (socket) or in its own process (in-memory), which is why TUI local mode and daemon mode share every code path above the transport.
22
+
23
+ The GUI consumes the root package (`zidane`, `zidane/chat`, `zidane/chat/pure`, `zidane/ipc`, and on demand `zidane/ipc/remote`) like any external client; it never reaches into `src/`. `zidane/ipc/remote` (the WebSocket client and the saved-hosts registry) is its own entry, out of the `zidane/ipc` barrel, so an opt-in client loads none of it until it imports that entry.
24
+
25
+ ## Feature folders
26
+
27
+ - **Colocate by feature within a layer; keep layers separate.** Everything the engine does for, say, todos lives in `src/chat/host/todos/`; the TUI's todos UI lives in `src/tui/todos/`. They are two folders, not one, because they sit in different layers.
28
+ - **One entry per feature.** Other code imports a feature folder through its `index.ts` only. Files inside the folder import each other freely.
29
+ - **Composition roots stay thin.** `engine.ts`, `dispatcher.ts`, `app.tsx` and the GUI's `ipc.ts` wire features together; they don't implement them. If a root grows logic, that logic is a feature folder waiting to be extracted.
30
+ - Types shared between a root and its features live beside the root (e.g. `src/chat/host/types.ts`) so features never import the root.
31
+
32
+ ## Adding a feature
33
+
34
+ A feature that spans the stack touches one place per layer:
35
+
36
+ 1. **Engine:** `src/chat/host/<feature>/index.ts`, wired into `createChatHost`.
37
+ 2. **Protocol:** request/response/signal types in `src/ipc/protocol.ts`.
38
+ 3. **Dispatcher:** `src/ipc/daemon/handlers/<feature>.ts`, registered in `dispatcher.ts`.
39
+ 4. **TUI:** `src/tui/<feature>/` (hooks, components), mounted from the app shell.
40
+ 5. **GUI:** `gui/src/main/chat/channels/<feature>.ts` (registers through the injected `IpcRegistrar`), plus renderer UI.
41
+
42
+ Features that are pure SDK (a tool, a provider, a hook) stay in the core and need none of this.
43
+
44
+ ## Enforced limits
45
+
46
+ Both run in `bun run lint` as errors (`eslint.config.ts`).
47
+
48
+ - **Size** (`zidane/size-ratchet`): over all of `src/**/*.{ts,tsx}`, functions max 200 lines, modules max 1000, blank lines and comments not counted. Tests are exempt (a long spec is a catalogue). Fix a violation by extraction, not a disable.
49
+ - **Boundaries** (`zidane/boundaries`, a local rule in the config):
50
+ - `src/chat` must not import `src/ipc`, `src/tui`, `src/server` or `gui/`; `src/ipc` must not import `src/tui`, `src/server` or `gui/`.
51
+ - `src/server` (zidane-server, a host process) may import `src/ipc` and `src/chat`, never `src/tui` or `gui/`; `src/tui` and the SDK core must not import it.
52
+ - The SDK core must not import `src/tui` or `src/ipc` (sole exception: `src/daemon-cli.ts`, the daemon binary's entry point).
53
+ - The SDK core may import from `src/chat` only the leaf modules in `CORE_CHAT_ALLOWLIST` (formatting, XDG paths, provider tables, the context-breakdown model, extension-facing registries) and never `src/chat/host`. That list records existing inversions; shrink it, don't grow it.
54
+ - `src/chat/host/<feature>/<file>` may only be imported from inside `<feature>/`; everyone else (tests included) goes through the index.
55
+
56
+ ## Testing contracts
57
+
58
+ | Suite | Pins |
59
+ |---|---|
60
+ | `test/ipc-transport-conformance.test.ts` | Socket, in-memory and WebSocket `IpcClient`s behave identically against the same host (host identity, signal numbering and `sessions.resume` included). |
61
+ | `test/ipc-ws-*.test.ts`, `test/server-*.test.ts` | WebSocket specifics (heartbeat, version skew against a server, reconnect + resume, slow consumers) and zidane-server: device tokens and auth, configuration, exposure rules and limits, the audit log, one host on both front-ends, shutdown, the CLI, `zidane attach --host`. |
62
+ | `test/tui/app-modes.test.tsx` (+ `app-modes-harness.tsx`) | The real `<App>`, every scenario in local, daemon and remote (zidane-server over WebSocket) mode: same screen, same stored session. The remote server has its own store, so the window must get session data from its host. |
63
+ | `test/tui-runtime-parity.test.ts` | Local vs daemon: normalized event streams, persisted turns, and what each mode sends to the model. |
64
+ | `test/helpers/host-harness.ts` | A real `createChatHost` over an in-memory store and a mock provider; the base for `test/chat/host-*` specs. |
65
+
66
+ A new engine feature gets a host-harness spec; a new protocol method is covered by conformance if both transports must agree on it.
67
+
68
+ ## Opt-in features
69
+
70
+ An experimental feature is **one gate**: a boolean setting, off by default, plus a kill-switch env var that wins over it. `defineFeatureGate` (`src/chat/feature-gate.ts`, exported from `zidane/chat` and `zidane/chat/pure`) defines both together so every client reads the same pair:
71
+
72
+ ```ts
73
+ export const daemonGate = defineFeatureGate({ setting: 'useDaemon', killSwitchEnv: 'ZIDANE_NO_DAEMON' })
74
+
75
+ daemonGate.isEnabled(settings) // settings.useDaemon === true && !killed
76
+ daemonGate.isKilled() // process.env.ZIDANE_NO_DAEMON === '1'
77
+ ```
78
+
79
+ The daemon runtime is the reference: the TUI checks it via `resolveHostTarget` (`src/tui/host-target.ts`, read through `isMultiProcess` in `src/tui/live-settings.ts`), the GUI main process via `daemonGate` directly, and `zidane --no-daemon` exports the env var. The kill switch exists so a bug in the gated path can never lock a user out, even when the settings UI is unreachable.
80
+
81
+ Remote hosts have their gate too: `remoteHostsGate` (`remoteHosts` setting, kill switch `ZIDANE_NO_REMOTE`). Its settings row (Settings → Sessions in the TUI, General › Experimental in the GUI, "Remote hosts (experimental)") gates the saved servers (the Hosts settings, the per-host session groups, picking a host for a new session); an explicit `zidane --host` / `ZIDANE_HOST_URL` is its own opt-in, and the kill switch wins over it (see `docs/IPC.md` § TUI remote mode). Both clients load the saved-hosts code on demand behind the gate (the TUI's `src/tui/host/remote-hosts.ts`, the GUI main's `gui/src/main/chat/hosts/remote-hosts.ts`), each with a containment test that boots the app with the setting off and saved servers on disk and checks nothing loads or dials. Which settings a server owns when a window edits it is one classification for both: `isWindowOnlySetting` (`src/chat/host-scoped-settings.ts`).
82
+
83
+ A settings row names its gate's kill switch with `killSwitchEnv` (`SETTINGS_TOGGLES`; every gate is in `FEATURE_GATES`). While the switch is engaged the row is inert and says `Turned off by <VAR>=1`: the TUI reads its environment, the GUI renderer asks its main process (`settings:killSwitches` → `engagedKillSwitches()`). `useDaemon` doesn't declare one yet, so its row stays live under `ZIDANE_NO_DAEMON`. Which switches a window keeps in its own `state.json` while a host owns the settings file: `WINDOW_LOCAL_SETTINGS` (`useDaemon`, `remoteHosts`).
84
+
85
+ ---
86
+
87
+ Loop internals: [Agent Lifecycle](#agent-lifecycle) · [Turn Loop](#turn-loop) · [Single Turn](#single-turn) · [Tool Execution](#tool-execution) · [Tool ergonomics defaults](#tool-ergonomics-defaults) · [Prompt caching](#prompt-caching) · [Tool aliasing](#tool-aliasing-wire-vs-canonical) · [Skills activation lifecycle](#skills-activation-lifecycle) · [Progressive tool disclosure](#progressive-tool-disclosure) · [Hook Firing Order](#hook-firing-order) · [Dependency Graph](#dependency-graph) · [Presets](#presets) · [Option Precedence](#option-precedence)
6
88
 
7
89
  ## Agent Lifecycle
8
90
 
@@ -308,10 +390,11 @@ A prefix cache breaks at the first differing byte — the wire must be append-on
308
390
 
309
391
  | Knob | Cache-aware setting | Why |
310
392
  |---|---|---|
311
- | `elideStaleReads` | `'notice'` | Append-only: a `<system-reminder>` rides the mutating tool's own result, zero prefix rewrites. `'stub'` reclaims stale bytes at one full-prefix re-bill per read→edit transition. |
393
+ | `elideStaleReads` | `false` or `'notice'` | Neither rewrites the prefix. `'notice'` is append-only: a `<system-reminder>` rides the mutating tool's own result, zero prefix rewrites. `'stub'` reclaims stale bytes at one full-prefix re-bill per read→edit transition. |
312
394
  | `thinkingDecay` | Step form `{ afterTurn, floor }` | Every distinct budget is one cache loss; step emits one change per run, geometric emits one per halving, a per-turn function disables caching for the rest of the run. |
313
395
  | `cacheTtl` | `'1h'` for interactive hosts, `'5m'` for headless back-to-back runs | The 5-minute default TTL is the dominant cache cost for a human-paced session. |
314
- | `compactStrategy` / `mediaKeepTurns` | Keep windows equal, chunk-aligned (`COMPACT_CHUNK_TURNS`) | A window then costs one invalidation, not two. |
396
+ | `compactKeepTurns` | Chunk-aligned (`COMPACT_CHUNK_TURNS`) | The tail-compaction frontier then moves in discrete jumps, not per turn. |
397
+ | `mediaBudget` over `mediaKeepTurns` | `{ maxItems: 80, maxBytes: 20 MiB }` + `maxImageEdge: 2000` | A positional window rewrites an earlier message per aged image and re-bills the conversation each time (replayed on a 470k-token GUI session: ~340 rewrites, 11 under the budget). The budget only ages media out near the provider's caps, in half-budget batches. |
315
398
  | `compactThresholdWindowFraction` | `0.25` for interactive hosts | Each tail-compaction frontier step rewrites an early message and re-bills the whole suffix (~50x the cost of keeping the bytes cached). Scaling the byte threshold with the model's window removes the steps on large-context models; ≤128k models keep the byte rule. Live A/B in `test-inference/cache-stability.inference.test.ts` § tail compaction vs context window. |
316
399
  | `thinking` level | Constant for the whole run | The thinking config is part of the cache key like the budget; toggling it mid-run forfeits the prefix. |
317
400
  | `system:transform` handlers | Byte-stable output only | Per-turn counters/timestamps belong in the transcript, not the system prompt. |
@@ -588,11 +671,14 @@ skills:activate ← during run, when a skill file is re
588
671
  Fires when a parent invokes the `spawn` tool. Children inherit the parent's preset (tools, system, aliases, mcpServers, skills, behavior) by default.
589
672
 
590
673
  ```
674
+ spawn:queued ← a top-level call found every slot taken (maxConcurrent); it waits, first come first served
591
675
  spawn:before ← child agent created, before run
592
676
  spawn:complete ← child finished successfully, includes AgentStats
593
677
  spawn:error ← child run threw
594
678
  ```
595
679
 
680
+ Past `maxConcurrent`, the top-level agent's spawn calls wait for a slot (aborting the run drops the waiters, and no child starts). A subagent's are refused, since waiting on slots its own ancestors hold could never end; a refused call (that cap, `maxDepth`, an unknown model, a bad `resume`) fails as a tool error. A child's `SessionRun` records the call that started it (`parentCallId`), and its `spawn-start` marker carries it (`spawn.callId`), so a client pairs every worker with its own call, live and on replay.
681
+
596
682
  The child's lifecycle also bubbles to the parent hook surface with `childId` + `depth`:
597
683
 
598
684
  ```
@@ -601,9 +687,18 @@ child:tool:gate / child:mcp:tool:gate ← share the child's ctx — par
601
687
  child:tool:cancelled ← bubbled per-call cancel from a subagent's tool
602
688
  child:tool:transform ← share the child's ctx — parent mutations propagate
603
689
  child:tool:before / child:tool:after / child:tool:error
690
+ child:validation:reject / child:repeat-guard:exceeded
604
691
  child:turn:after
605
692
  ```
606
693
 
694
+ A spawn run BY a subagent (depth ≥ 2) fires `spawn:*` on that subagent's bus; it reaches the parent (and chains up to the root) as
695
+
696
+ ```
697
+ child:spawn:before / child:spawn:complete / child:spawn:error
698
+ ```
699
+
700
+ carrying the spawned agent's own record (`id`, `depth`, `task`, `callId`, …) plus `parentChildId` — the subagent that ran the `spawn` call. No `childId` (the spawned agent's `depth` is already the payload's `depth`), and no `tracingContext`: propagation into a nested child belongs to its spawner's own tracer. The root's `spawn:*` stays direct-children-only.
701
+
607
702
  `BUBBLED_MUTABLE_EVENTS` (`src/tools/spawn.ts`) lists `tool:gate`, `mcp:tool:gate`, `tool:transform` — the bubbled ctx is the same reference the child loop awaits on, so a parent listener can refuse/substitute/annotate a subagent's tool call without registering on the child.
608
703
 
609
704
  - **Subagent type registry** (`createSpawnTool({ subagents })`) adds a `subagent_type` enum to the schema; the matching `SubagentDef` overlays `system`, `model`, `tools` (allow-list), `readonly` (shorthand for `['read_file','grep','glob','list_files']`), and `description` for that one dispatch. Unknown types route to `'general-purpose'`.
@@ -684,10 +779,12 @@ Use `agent.hooks.hook(event, fn)` for dynamic post-creation registration (return
684
779
 
685
780
  - **`toolOutputBudget`** (off). Sums every result's `outputBytes` after `tool:transform`. On overshoot, appends a synthetic user message (`[Tool output budget exceeded: N bytes … cap: M]`) and fires `budget:exceeded` — the model self-compacts next turn instead of being cut.
686
781
  - **`compactStrategy: 'tail' | CompactFunction'`** — client-side, wire-only trim for non-Anthropic providers. `'tail'` stubs `tool_result` blocks older than `compactKeepTurns` past a byte threshold (`compactThreshold`, raised to `compactThresholdWindowFraction × contextWindow × 4 B` when the run knows its window — `autoCompact.contextWindow`, the active `ModelTarget`, or `provider.meta.contextWindow`); `session.turns` is untouched. Anthropic should prefer server-side `context-management-2025-06-27` (token-accurate). A custom function must preserve `tool_use ↔ tool_result` adjacency and the trailing user message.
687
- - **`mediaKeepTurns`** — wire-only media age-out. Inline `image`/`document` blocks older than N messages become an `attachment://<hash>` marker on the wire; `session.turns` keeps the bytes, `read_file path="attachment://<hash>"` re-inlines. Chunk-snapped for cache stability; requires `read_file` registered to be actionable.
782
+ - **`mediaBudget`** — wire-only, budget-triggered media age-out (`applyMediaBudget`). Inline `image`/`document` blocks stay on the wire until their count passes `maxItems` (lowered to `provider.meta.capabilities.maxMediaPerRequest`, e.g. Bedrock's 20) or their base64 passes `maxBytes`; then the oldest become `attachment://<hash>` markers in steps of half the limit. Stateless and monotone within a run, so the prefix is rewritten once per step. Images still over 2000 px age out past the last 8 messages (the many-image rule). `session.turns` keeps the bytes; `read_file path="attachment://<hash>"` re-inlines.
783
+ - **`mediaKeepTurns`** — positional media age-out: blocks older than N messages become markers, chunk-snapped. Takes precedence over `mediaBudget`; the two resolve as a pair from one layer (a run's `behavior` replaces the agent's media policy whole). Requires `read_file` registered.
784
+ - **`maxImageEdge`** — shrinks inline PNGs whose longest edge exceeds N px, once, as they enter the session (`emitToolResult` for tool results, the seed prompt for pasted images), so the persisted turn and every later request carry the same bytes. Pure `node:zlib` codec in `src/media/png-downscale.ts`; other formats and unsupported PNGs (interlaced, 16-bit) pass through.
688
785
  - **`autoCompact`** (`boolean | AutoCompactConfig`, off). At each turn boundary, once effective input (`input + cacheRead + cacheCreation`) crosses `threshold` (default `0.85`) of the context window, summarizes older turns and continues. Window source: `autoCompact.contextWindow` → `provider.meta.contextWindow`, else inert. `minGrowthFraction` (default `0.1`) stops post-compact thrashing; `maxFailures` (default `2`) disables it after consecutive summary failures. Chat profiles enable at `{ threshold: 0.85 }`.
689
786
  - **`modelCatalog`** — override model metadata keyed `"<provider>:<modelId>"` or via `{ get, list }`. Resolution: run → agent `behavior.modelCatalog` → process-wide `setModelCatalog()` → bundled registry → local fallback tables, merged field-level. `list` returning `[]` empties a provider's picker; `get` returning `undefined` falls through. Unset by default.
690
- - **`dedupReads` + `requireReadBeforeEdit`** share a per-session map keyed by canonical absolute path. `dedupReads` gates the short-circuit "unchanged since last read" reply; `requireReadBeforeEdit` rejects `edit`/`multi_edit` when no entry exists (shell reads don't register). Hash drift is anchor-tolerant — a still-verbatim `old_string` proceeds with a disclosure note, a fuzzy-only match is refused with "re-read the file", and consecutive refusals escalate to name the likely external writer. A `tool:gate` hook pre-hydrates the map from `session.turns` on resume.
787
+ - **`dedupReads` + `requireReadBeforeEdit`** share a per-session map keyed by canonical absolute path. `dedupReads` gates the short-circuit "unchanged since last read" reply; `requireReadBeforeEdit` rejects `edit`/`multi_edit` when no entry exists (shell reads register only with `trackShellReads`). Hash drift is anchor-tolerant — a still-verbatim `old_string` proceeds with a disclosure note, a fuzzy-only match is refused with "re-read the file", and consecutive refusals escalate to name the likely external writer. A `tool:gate` hook pre-hydrates the map from `session.turns` on resume.
691
788
  - **`dedupTools`** generalizes the pattern via a per-tool hasher keeping only the most recent hash. Hasher-only form replays the prior result on hit (fires `tool:transform`+`tool:after`). `{ hasher, mode: 'block-after', threshold, reason?, scope? }` replays the first `threshold − 1` calls then blocks via `tool:gate` (`threshold` default `4`, clamps at `2`); `scope: 'run'` keeps parent/subagent counters separate. Never list tools with side effects or non-deterministic output.
692
789
  - **`ToolContext.state`** (`src/tool-state.ts`) — durable key/value state that survives a durable-runtime replay where a journaled tool body doesn't re-execute. Reads are synchronous against a per-agent snapshot; writes stage as ops committed in call-index order. `AgentOptions.toolState` supplies the backing (unset ⇒ in-memory). A rejecting commit fires `tool-state:commit-failed` rather than failing the call; values cap at `maxValueBytes` (default 64 KiB JSON). See `docs/RESTATE.md#durable-tool-state`.
693
790
  - **`toolBudgets`** caps per-tool calls per run. At `runToolCounts[name] >= max`, `onExceed: 'block'` refuses (`Blocked: <message>`), `'steer'` enqueues a deduped synthetic user message. Fires `tool-budget:exceeded` (distinct from byte-level `budget:exceeded`). Counts include dedup substitutes.
package/docs/ARTIFACTS.md CHANGED
@@ -5,8 +5,8 @@ An **artifact** is a durable, typed pointer to something a session produced outs
5
5
  - **Model / store:** `src/session/artifacts.ts`, `SessionStore` artifact methods in `src/session/index.ts`
6
6
  - **Producers:** the `metadata.artifacts` tool-result namespace, lifted by `src/loop.ts`
7
7
  - **Kinds:** `src/extensions/builtin-artifact-kinds.ts` + `ctx.registerArtifactKind`
8
- - **Surfaces:** TUI rail + `▢` title-bar chip · GUI rail + header chip · sessions-list badge · `#pr` completion
9
- - **IPC:** `sessions.artifacts`, `artifacts.list` / `delete` / `upsert`; signals `artifact:recorded`, `artifact:changed`
8
+ - **Surfaces:** TUI rail + `▢` title-bar chip · GUI rail + header chip · session badges (GUI sidebar, TUI sessions list) · `#pr` completion
9
+ - **IPC:** `sessions.artifacts`, `artifacts.list` / `delete` / `upsert`; signals `artifact:recorded`, `artifact:changed`; GUI-local `artifacts:badges`
10
10
 
11
11
  Contents: [Model](#model) · [Producers](#producers) · [Storage](#storage) · [Extension API](#extension-api) · [Surfaces](#surfaces) · [IPC](#ipc) · [Lazy refresh](#lazy-refresh) · [Disabling](#disabling) · [Known gaps](#known-gaps)
12
12
 
@@ -33,7 +33,7 @@ interface SessionArtifact {
33
33
  | Identity | `(sessionId, kind, uri)`. Re-emitting upserts: keeps `id` / `runId` / `turnId` / `createdAt`, refreshes `label` / `meta` / `projectRoot` / `updatedAt`. |
34
34
  | Cap | 200 per session. On overflow, `file` artifacts are evicted first, oldest first. |
35
35
  | Bounds | `kind` `[\w.:-]+` ≤ 64 chars · `uri` ≤ 2048 · `label` ≤ 512 (clipped) · `meta` ≤ 16 KB (dropped, artifact kept). |
36
- | `projectRoot` | Defaults to the session's root. `file` artifacts get the git root of the landed path when the execution runs on the host (`process` handle); docker/remote paths keep the session default. |
36
+ | `projectRoot` | Defaults to the session's root. `file` artifacts get the project root of the landed path (`findProjectRoot` — a linked worktree's main checkout) when the execution runs on the host (`process` handle); docker/remote paths keep the session default. |
37
37
  | Subagents | Land on the shared parent session under the child's `runId`. |
38
38
  | Unknown kinds | Always render as `label` + `uri`. Rows are never dropped because a kind's extension is gone. |
39
39
 
@@ -76,6 +76,7 @@ ctx.registerArtifactKind({
76
76
  validateMeta?: (meta) => boolean, // failing → meta dropped, artifact kept (own detections only)
77
77
  render?: (artifact) => { title, subtitle?, href?, status? },
78
78
  resolve?: async (artifact) => ({ label?, meta? }), // lazy refresh, see below
79
+ badge?: (artifacts) => ExtensionSessionBadge | undefined, // the badge under a session's title
79
80
  })
80
81
  ctx.tui.registerArtifactRenderer({ kind, renderDetail: artifact => <Pane /> }) // TUI detail pane
81
82
  ctx.artifacts.list({ scope: 'session' | 'project' | 'all', kind?, since?, limit? })
@@ -88,6 +89,8 @@ ctx.registerHook('artifact:recorded', ({ artifact, runId, turnId }) => …)
88
89
  - `detect` rides `tool:transform` and `child:tool:transform` but can only append to `metadata.artifacts`; it never changes what the model sees. A throwing detector emits nothing.
89
90
  - `ctx.artifacts` is late-bound (like `ctx.sessions`): reads answer `[]`, `record` answers `undefined`, when detached or the store lacks artifact support. `record` never throws for unusable input either.
90
91
  - Row presentation goes through `renderArtifactRow(artifact, registry)`: registry kind → built-in kind → `label` + `uri` fallback. A throwing `render` falls back.
92
+ - Session badges go through `sessionBadges(artifacts, registry)`: one per kind with a `badge`, given that session's artifacts of the kind newest first, ordered by `badge.order` (pull requests 0, commits 10, default 100). JSON-safe; a throwing or empty `badge` contributes none. Example: [EXTENSIONS.md → Session badges](./EXTENSIONS.md#session-badges).
93
+ - The GUI main process renders rows, refreshes and badges with the extension registry of the project the artifact landed in (`projectRoot`; user-level when untagged), so extension kinds' `render` / `resolve` / `badge` run there too. Sidebar badges only use a registry already built (by a live runtime): until then a project's rows show the built-in kinds' badges only, and pick up extension kinds on the next re-read (`artifact:*`, `extensions:invalidated`).
91
94
 
92
95
  Authoring example: [EXTENSIONS.md → Session artifacts](./EXTENSIONS.md#session-artifacts).
93
96
 
@@ -98,7 +101,8 @@ Authoring example: [EXTENSIONS.md → Session artifacts](./EXTENSIONS.md#session
98
101
  | TUI rail (`ctrl+shift+a`) | Read-only column docked right of the chat screen, 34 cells: kind headers, bullet rows (PR state tints the bullet), path tails. Click a row to open its href. Hides itself under 110 columns; the toggle survives. Live via the same change ticks as the modal. |
99
102
  | TUI actions modal | Opened from the rail's `⋯ actions` footer: `↵` open href · `tab` session ↔ project · `c` copy locator · `d` remove. Grouped by kind, built-ins first; extension `renderDetail` mounts under the list in an error boundary. |
100
103
  | GUI rail (`⌃⇧A`, header chip, `⋯` menu) | 288 px column right of transcript + composer, under the header; session scope only (the project-wide view belongs to the project page). No controls of its own — the chip / shortcut open and close it. Hover → copy / remove, PR rows lazily refresh on first hover. Header chip shows the live count. Rows pre-rendered main-side (`artifacts:list` IPC). |
101
- | Sessions list | `N artifacts` badge from `artifactCount` (hidden at 0 or when the store doesn't report it). |
104
+ | GUI sidebar row | Line 2 under the title: one badge per kind with a `badge` (commits, pull requests, extension kinds), each with a hover card listing its artifacts. A click selects the chat and opens the badge's `panel` (default Artifacts, scrolled to the kind; a card row also focuses its artifact via `uri`); ⌘-click opens a lone badge's `href`; right-click opens the badge's actions (copy, add to prompt, open, extension items: [EXTENSIONS.md → Badge actions](./EXTENSIONS.md#badge-actions)). A kind whose count grew pops and sweeps the row. Follows Settings › Chat › Sidebar run details and Artifacts. Read via `artifacts:badges`, re-read on `artifact:*` and `extensions:invalidated`. |
105
+ | TUI sessions list | `N artifacts` stat from `artifactCount`, plus a badge line (icon in the badge's tone, text, `+N`). A click opens the chat with the rail on the badge's `panel` (Artifacts: the kind's group first); ctrl-click opens a lone badge's `href`; right-click (badges and rail rows) opens the badge actions modal. Only sessions with artifacts are asked, again when their count or activity changes. |
102
106
  | `#` completion | Shares the trigger with chats. `#pr` / `#br` / `#file` rank by kind alias; selecting inserts the locator as plain text (no reference grammar). |
103
107
 
104
108
  ### Opening artifacts
package/docs/CHAT.md CHANGED
@@ -52,6 +52,7 @@ A non-Bun host (Electron, Tauri, Next.js) imports `zidane/chat` only — build-e
52
52
  - [Session export](#session-export)
53
53
  - [Session artifacts](#session-artifacts)
54
54
  - [Commands](#commands)
55
+ - [Markdown commands and subagents](#markdown-commands-and-subagents)
55
56
  - [Title generation](#title-generation)
56
57
  - [Turn operations](#turn-operations)
57
58
  - [IPC contracts](#ipc-contracts)
@@ -149,13 +150,63 @@ Zero-config local models (Ollama, LM Studio, etc.) are discovered by probing wel
149
150
  ## Storage-slot layout
150
151
 
151
152
  ```ts
152
- resolveStorageDirs({ prefix: 'zidane', cwd, home }): StorageDirs
153
- // → { userDir, projectDir, mode: 'xdg' | 'legacy' }
153
+ resolveStorageDirs({ prefix: '.zidane', storageDir?, home?, platform?, env? }): StorageDirs
154
+ // → { configDir, dataDir, stateDir, cacheDir, mode: 'legacy-explicit' | 'legacy-existing' | 'xdg' | 'default' }
154
155
  ```
155
156
 
156
- - **`userDir`** — `$XDG_DATA_HOME/zidane` when set, else `~/.zidane` (legacy, still the common case).
157
- - **`projectDir`** — `<project root>/.zidane`, used for session exports and project-scoped skills/MCP config.
158
- - Every path in `ResolvedPaths` derives from these two roots; hosts overriding `ChatOptions.paths` should override both together to keep sessions and credentials colocated.
157
+ Four slots: **config** (credentials, keybindings, `config.json`, `projects.json`), **data** (`sessions.db`), **state** (`state.json`), **cache** (session artifacts, task logs, update cache). First match wins (`src/chat/xdg.ts`):
158
+
159
+ | Mode | When | Layout |
160
+ |---|---|---|
161
+ | `legacy-explicit` | `ChatOptions.storageDir` or `ZIDANE_STORAGE_DIR` set | All slots in `<storageDir>/<prefix>` |
162
+ | `legacy-existing` | `~/<prefix>/` holds storage | All slots in `~/<prefix>` |
163
+ | `xdg` | Linux, any `XDG_*_HOME`, or any `ZIDANE_{CONFIG,DATA,STATE,CACHE}_DIR` | `~/.config/zidane`, `~/.local/share/zidane`, `~/.local/state/zidane`, `~/.cache/zidane` (each overridable) |
164
+ | `default` | Otherwise (macOS, Windows) | All slots in `~/<prefix>` |
165
+
166
+ `ResolvedPaths.userDir` is an alias of `configDir`. Authored content stays home-anchored in every mode: `~/<prefix>/{extensions,skills,mcps.json,SYSTEM.md,AGENTS.md}` (with the project's `<checkout>/<prefix>/` counterparts). A `~/<prefix>/` holding only those entries (or `.DS_Store`) is not a legacy install, so scaffolding an extension or a skill never flips an XDG layout. Hosts writing storage must go through the resolved slots, never a literal `~/<prefix>`: one stray file there turns the next launch into `legacy-existing`. The desktop app resolves the same slots (`gui/src/main/paths.ts`).
167
+
168
+ An install that was flipped to `legacy-existing` by an older build stays there; nothing is moved automatically. To get back to XDG, move `credentials.json`, `*-credentials.json`, `keybindings.json`, `config.json` and `projects.json` to the config dir, `sessions.db` to the data dir, `state.json` to the state dir, and drop the rest (cache) — leaving only authored entries in `~/<prefix>/`.
169
+
170
+ ### Checkout root vs project root
171
+
172
+ `src/chat/project-root.ts` answers two questions that differ only inside a linked git worktree:
173
+
174
+ | | Function | Meaning | Used for |
175
+ |---|---|---|---|
176
+ | Checkout root | `findGitRoot(cwd)` | The working tree `cwd` sits in (a worktree's own dir) | Discovery (`AGENTS.md`, `SYSTEM.md`, skills, MCPs, plugins, extensions), file completion, path display, the model's environment section |
177
+ | Project root | `findProjectRoot(cwd)` / `projectRootOf(cwd)` | The project it belongs to — a linked worktree's **main checkout** | `SessionData.projectRoot`, session list scope + grouping, the safelist (`getSafelistForCwd`), artifact scope, the project-scoped DB |
178
+
179
+ So every worktree of a repo is one project, while each reads its own branch's files. Submodules and worktrees of bare / `--separate-git-dir` repos stay their own project. The TUI exposes both as `ProjectLocation.{checkoutRoot, projectRoot}`, the GUI as `folder.locationOf(cwd)`. Sessions stamped with a worktree's checkout root by older builds are moved to the main checkout when the SQLite store opens.
180
+
181
+ ### Session environments
182
+
183
+ Where a session's work happens is chosen at creation and stored as `metadata.environment` (`src/chat/environment.ts`). Absent means `{ kind: 'local', state: 'ready' }`, which is what every older session reads as — and what a create request without `environment` gets (workers, forks and programmatic creates stay local; the pickers turn the configured default into an explicit request). Each kind is an `EnvironmentProvider`, registered on `ChatHostEnvironment.environments` (default: `builtinEnvironmentProviders({ dataDir })` — `local` and `worktree`, `src/chat/environments/`):
184
+
185
+ | Hook | Role |
186
+ |---|---|
187
+ | `configSchema` | Option fields (`boolean` / `string` / `number` / `choice` / `path` / `secret`) rendered by both settings UIs. `setting` binds a field to an existing `Settings` key (Local's OS sandbox → `sandbox`, `sandboxNetwork`, `sandboxAllowWrite`); `env` names an env-var fallback; `when` hides a field unless another has a value. |
188
+ | `takesBranch` / `suggestBranch(config)` | The kind works on its own branch: pickers ask for one, pre-filled with the suggestion. |
189
+ | `available({ cwd, config })` | Whether it can be offered (credentials, binaries, a git repo). Creation of an unavailable or unknown kind throws before anything is written. |
190
+ | `prepare(input)` | Makes the environment exist. Present → the session starts `provisioning` and the host provisions in the background; its result can move `metadata.cwd` (e.g. to a worktree) and set `ref` / `execCwd`. Must be idempotent — `ChatHost.retryEnvironment` re-runs it after a failure or a restart. |
191
+ | `execution(input)` | `{ key, create }` for the session's context. `resolveExecution` is the only caller; `execution-cache.ts` reuses the context while `key` holds. |
192
+ | `teardown(env)` | Runs when the OWNING session is deleted, after its processes are gone. A fork shares its parent's environment (`owned: false`) and never tears it down. May keep it and say why. |
193
+
194
+ States: `provisioning` → `ready` \| `failed` (with `error`), each persisted transition announced as `sessions:changed`. A prompt queued (`queue.add` / `chat.pushQueued`) while this host provisions is parked and delivered once the environment is ready; after a failure it stays queued until a retry succeeds. Every agent build (host `buildActiveRun`) passes `assertEnvironmentReady`, so a session never runs before its environment exists. `metadata.cwd` stays the host directory config and discovery read from; `environment.execCwd` is set only when commands run somewhere else (inside a sandbox). `zidane run --context …` remains a one-launch override in the TUI (the host runs that session on the launch context).
195
+
196
+ Built-in kinds:
197
+
198
+ | Kind | What it does |
199
+ |---|---|
200
+ | `local` | This machine, in the session's folder. Its options are the OS sandbox settings, so Settings → Environments and the Safety settings edit the same keys. Can't be disabled. |
201
+ | `worktree` | A git worktree on its own branch — under `<dataDir>/worktrees/` by default, or next to the repo / in a git-excluded `.zidane/worktrees/` (the `location` option); `metadata.cwd` moves there, the session stays in the main checkout's project. Deleting the session removes the worktree unless it has uncommitted changes (kept, with the reason). |
202
+ | `docker` | A container on this machine (`src/chat/environments/docker.ts`), running as the host `uid:gid` with `HOME=/tmp`. Workspace: *this checkout* (bind mount, edits your working tree) or *a new worktree* (provisioned like `worktree`, then mounted). Provisioning pulls the image; the setup command runs in every new container; after `idleMinutes` the container is removed (files stay on the host) and the next prompt starts a fresh one. Off until turned on. |
203
+ | `e2b` / `daytona` | A cloud sandbox per session holding a **clone** of the repo on its own branch (`remote-sandbox.ts` + a vendor driver). Provisioning creates the sandbox, clones `origin` over https with the git token (secret → `GH_TOKEN`/`GITHUB_TOKEN` → `gh auth token`), checks out the branch from the base (the current branch if pushed, else the default branch), and runs the setup command; `execCwd` is the clone, `metadata.cwd` stays the host checkout (AGENTS.md, skills, MCPs load from it). Idle past `idleMinutes` → paused (e2b `pause`, Daytona `stop`) and marked `suspended`; the next prompt resumes it. e2b sandboxes are created with `onTimeout: 'pause'` and kept alive while used. Actions: **Push branch**, **Open pull request** (GitHub, via the REST API with the same token). Deleting the session deletes the sandbox. The token is stored in the sandbox's git credential store, so the agent can read it. Off until turned on. |
204
+
205
+ Beyond `configSchema`, a provider can declare `offByDefault` (hidden until turned on — stored in `Settings.environments.enabled`), `verify` ("Test connection"), `note` (what creating one here will do, shown in pickers), `actions` (per-session commands: `ChatHost.listEnvironmentActions` / `runEnvironmentAction`, run on the session's own cached execution through `runSessionEnvironmentAction`), and `idleSuspendMinutes` (a once-a-minute host sweep releases idle executions past it and calls `suspend`).
206
+
207
+ **Settings → Environments** (GUI tab, TUI Agent-tab "Environments" row) edit `Settings.environments` — `{ defaultKind?, disabled?, config?: { [kind]: { [key]: value } } }` in `state.json` — through `applyEnvironmentSettingsChange` (`src/chat/environment-settings.ts`), which validates every write against the kind's schema. `secret` fields go to `<dataDir>/environment-credentials.json` (0600) and never reach a client: descriptors (`describeEnvironments`) only say `configured` / `fromEnv`. A disabled kind is hidden from pickers and refused at creation.
208
+
209
+ **Picking one.** The GUI composer shows an environment pill beside the folder toggle for a new chat (when more than Local is enabled), with a branch field for kinds that take one; rows show the kind / branch, a spinner while provisioning, and the failure with a Retry action. The TUI's `newSession` asks where the session runs, creates and provisions it before opening it (`src/tui/environment-session.ts`, inline locally, via `sessions.create` under the daemon), then moves the window into the prepared directory; `ctrl+r` on a failed row retries.
159
210
 
160
211
  ## Agents
161
212
 
@@ -171,11 +222,18 @@ const SHARED_BEHAVIOR: AgentBehavior = {
171
222
  // runs never trip tail compaction on
172
223
  // ordinary sessions; ≤128k stays on bytes
173
224
  compactKeepTurns: 8, // floor, not a cap — see below
174
- elideStaleReads: 'notice', // append-only staleness note; never rewrites
175
- // cached prefix bytes ('stub' re-bills the
176
- // conversation per newly-stale read)
177
- mediaKeepTurns: 8, // matched to compactKeepTurns: one prompt-
178
- // cache invalidation per window, not two
225
+ elideStaleReads: false, // no reminder after the agent's own edits;
226
+ // 'stub' would re-bill the conversation
227
+ // per newly-stale read
228
+ mediaBudget: DEFAULT_MEDIA_BUDGET, // { maxItems: 80, maxBytes: 20 MiB }: media
229
+ // stays inline until near the provider's
230
+ // caps, then ages out in half-budget
231
+ // batches (no per-image cache reset)
232
+ maxImageEdge: DEFAULT_MAX_IMAGE_EDGE, // 2000: shrink PNGs once at entry; past
233
+ // it, >20-image requests are rejected
234
+ cacheTtl: '1h', // a human pause past 5 min no longer
235
+ // re-bills the conversation (API-key
236
+ // auth too; OAuth already defaulted to 1h)
179
237
  toolDisclosure: 'lazy',
180
238
  toolOutputBudget: 64 * 1024,
181
239
  toolOutputBudgetExcludeTools: DEFAULT_BUDGET_EXCLUDE_TOOLS,
@@ -188,7 +246,8 @@ const SHARED_BEHAVIOR: AgentBehavior = {
188
246
  autoCompact: { threshold: 0.85 },
189
247
 
190
248
  // loop prevention (ON in the chat profiles; OFF in the SDK core)
191
- requireReadBeforeEdit: true,
249
+ requireReadBeforeEdit: false, // old_string anchors already guard edits
250
+ shellToolNudges: true, // fading pointer to read_file/grep/edit
192
251
  dedupTools: { mode: 'chain', excludeTools: [...] },
193
252
  repeatGuard: { mode: 'both', windowSize: 40 },
194
253
  thinkingDecay: { afterTurn: 10, floor: 1024 },
@@ -270,6 +329,7 @@ Both join `[...doctrine, userInstructions]` as the **static** half and `[envSect
270
329
  | `fileLogging` | `boolean` | `false` | The in-TUI Logs panel works regardless (in-memory). |
271
330
  | `showTodoIndicator`, `showUsageIndicator`, `showThrobber` | `boolean` | `true`, `true`, `false` | See **Todos**, **Subscription usage**. |
272
331
  | `artifacts` | `boolean` | `true` | Master switch for session artifacts (detection, `record_artifact`, every surface). See [ARTIFACTS.md § Disabling](./ARTIFACTS.md#disabling). |
332
+ | `terminal`, `terminalAgent`, `terminalAgentTasks` | `boolean` | `true`, `true`, `true` | Terminals (the GUI drawer and tab; run by the daemon), the agent's `terminal_*` tools, and background `shell` tasks in terminals. Enforced by the host; a change rebuilds agents without aborting runs. See [TERMINAL.md § Settings](./TERMINAL.md#settings). |
273
333
  | `checkForUpdates` | `boolean` | `true` | |
274
334
  | `subscriptionUsage` | `boolean` | `true` | Gates *automatic* usage probes; see **Subscription usage**. |
275
335
  | `uiMode` | `'full'\|'minimal'` | `'full'` | |
@@ -758,11 +818,11 @@ The marker turn carries a `compact-summary` content block; the loop scans for it
758
818
 
759
819
  ```ts
760
820
  const text = renderSession(sessionData, { format: 'markdown' }) // or 'json' — render only
761
- const target = await writeSessionExport({ session: sessionData, format: 'markdown', cwd, prefix })
821
+ const target = await writeSessionExport({ session: sessionData, format: 'markdown', cwd, prefix, dataDir })
762
822
  // → { filepath, dir, anchor: 'project' | 'home' }
763
823
  ```
764
824
 
765
- Anchor walks upward from `cwd` looking for `.git`; the first hit anchors to that repo (`project`), otherwise `~/.{prefix}/sessions/` (`home`). `resolveSessionExportTarget` computes the path without writing.
825
+ Anchor walks upward from `cwd` looking for `.git`; the first hit anchors to that repo (`<repo>/.{prefix}/sessions/`, `project`), otherwise `<dataDir>/sessions/` (`home`; `dataDir` defaults to the resolved storage data dir, i.e. `~/.{prefix}` outside an XDG layout). `resolveSessionExportTarget` computes the path without writing.
766
826
 
767
827
  ## Session artifacts
768
828
 
@@ -776,11 +836,102 @@ Anchor walks upward from `cwd` looking for `.git`; the first hit anchors to that
776
836
 
777
837
  | Command | Requires | Does |
778
838
  |---|---|---|
779
- | `/help` | — | Lists `opts.getCommands()` (built-ins + extensions), read live so it stays accurate after `/reload`. |
839
+ | `/help` | — | Lists `opts.getCommands()` (built-ins, extensions, and markdown file commands), read live so it stays accurate after `/reload`. |
780
840
  | `/reload` | `opts.reloadExtensions` | Re-discovers, re-runs `setup`, tears down the old extension instances. Omitted from the list on hosts without one. |
781
841
 
782
842
  `createCommandsCompletionProvider` (trigger `/`, shared with skills) suggests from the live merged command list. Authoring a new command — the full `Command` shape, `CommandInteraction`, `generateData`/`spawnAgents` — is documented in `docs/EXTENSIONS.md`; this module is the runtime that executes whatever a `Command` describes.
783
843
 
844
+ ## Markdown commands and subagents
845
+
846
+ Drop a `.md` file in a commands or agents root and it becomes a slash command or a subagent, Claude Code style. `src/markdown-defs/` loads both; the loader never throws, and a bad file is dropped with a diagnostic.
847
+
848
+ **Subagents, not agent profiles.** Files in `agents/` folders are *subagents*: entries of the `spawn` tool's `subagent_type`, run as child agents. They are not the agent profiles in the picker (`Build`, `Plan`, Codemode; see `src/chat/agents.ts`), which pick the session's own preset. The folder keeps Claude Code's name for compatibility. The builtin `Plan` subagent is likewise unrelated to the `plan` profile: it is a read-only child a model can spawn from any profile.
849
+
850
+ | Root | Scope | Loaded |
851
+ |---|---|---|
852
+ | `.agents/commands`, `.agents/agents` | project | always |
853
+ | `.zidane/commands`, `.zidane/agents` | project | always |
854
+ | `~/.agents/commands`, `~/.agents/agents` | user | always |
855
+ | `~/.zidane/commands`, `~/.zidane/agents` | user | always |
856
+ | `.claude/commands`, `.claude/agents` (project and `~`) | foreign | only with `Settings.importForeignConfigs` |
857
+
858
+ Roots are scanned in order: native project, native user, foreign project, foreign user. The first file for a name wins, so project beats user and native beats foreign. File-derived names are lowercased. Files in nested directories get colon names (`review/pr.md` is `/review:pr`); the scan stops at depth 3 and skips every entry starting with `.` (dot-dirs and dotfiles) plus `node_modules` (`build/` and `dist/` are valid namespaces). Names that collide with object keys (`__proto__`, `constructor`, `prototype`) are dropped with a `reserved ... name` diagnostic.
859
+
860
+ **Where roots anchor.** Project roots anchor at the git root (the repo top, not the current subdirectory); outside git they use the cwd. `.zidane` follows the configured storage prefix (a custom prefix reads `.<prefix>/commands`). Headless always uses `.zidane`, and loads project agent roots only with `--project-agents` (user roots and builtins otherwise; see [HEADLESS.md](./HEADLESS.md)).
861
+
862
+ **When files are re-read.** The chat/daemon host re-reads markdown files on every command list and dispatch. The TUI and GUI list commands through the session's host (`commands.list`); the TUI re-asks when the session, extensions, or the foreign toggle change, and the GUI on those toggles or after its 30-second cache. Before a session opens, the TUI scans the checkout root itself. Subagents load at each agent build, so an edited agent file applies from the next agent build, not mid-run.
863
+
864
+ An agent's frontmatter `name:` keeps its case, and agent names match case-sensitively. To override builtin `Explore` (user folders only), set `name: Explore`; an `explore.md` without `name:` registers a separate `explore`. Commands match case-insensitively.
865
+
866
+ An agent's `tools:` key controls its allowlist. A missing `tools` key inherits all parent tools. An explicitly empty `tools: []` drops the agent with a diagnostic, since spawn would treat an empty list as inherit-all. A `tools` value that is not a string or list (a YAML map, for example) drops the agent with `unreadable tools value; skipped`. Restriction keys fail closed: a value the loader cannot read drops the agent rather than widening its access. A blank key (`tools:`, `disallowedTools:`, or `readonly:` with nothing after it) drops the agent, and so does any of `tools`, `disallowedTools`, `readonly`, or `permissionMode` written twice.
867
+
868
+ **Precedence across sources.** Commands: builtins (`/help`, `/reload`) > extension commands > file commands, first registration wins, case-insensitive. A file named like a builtin or an extension command is dropped (`mergeCommands` in `src/chat/commands.ts`). Skills also answer `/<name>`; as in Claude Code, a skill beats a file command of the same name, and the file command is dropped with a diagnostic. The check (`createSkillNameProbe`) looks for `<skills dir>/<name>/SKILL.md` in the session's skill paths plus extension skills, so a skill in a nested category folder or renamed by frontmatter is not detected. File commands carry no `group`: that field names a contributing extension, and a namespaced name (`git:commit`) already matches `/git`. Agents: builtin `Explore` / `Plan` < file agents < extension and profile agents. Under the TUI daemon, non-builtin commands (extension or file) run on the daemon.
869
+
870
+ **Command format.**
871
+
872
+ ```md
873
+ ---
874
+ description: Review a PR
875
+ argument-hint: <pr-number>
876
+ ---
877
+ Review PR $1 and focus on $ARGUMENTS.
878
+ ```
879
+
880
+ - `description` falls back to the first body line; `argument-hint` is appended to it as `(args: ...)`.
881
+ - `$ARGUMENTS` is the raw argument string; `$1`..`$9` are the whitespace-split arguments (a missing one becomes empty). Both expand in a single pass, so a `$` sequence the user types stays verbatim. A `$1`..`$9` followed by another digit stays literal (`$10` is not an argument), and `\$` produces a literal `$`.
882
+ - With no placeholder in the body (escaped and multi-digit `$` sequences do not count), the arguments are appended after a blank line.
883
+ - `model` and `allowed-tools` are ignored with a diagnostic. `!`cmd`` shell injection and `@file` inclusion are not supported. An empty body is dropped.
884
+
885
+ **Agent format.**
886
+
887
+ ```md
888
+ ---
889
+ name: reviewer
890
+ description: Reviews diffs for bugs
891
+ tools: Read, Grep, Bash(git:*)
892
+ model: sonnet
893
+ ---
894
+ You review diffs. Report bugs with file:line.
895
+ ```
896
+
897
+ - `name` defaults to the file name. `description` is required: a file with no frontmatter or no non-empty `description` is dropped. The body is the system prompt.
898
+ - `tools` takes a YAML list, `[a, b]`, `a, b`, or `a b`. Names map to zidane tools as below; `mcp__*` and native zidane names pass through. An unknown name is dropped. A pattern such as `Bash(git:*)` grants the full tool (here, unrestricted `shell`) with no argument restriction, so do not rely on patterns to sandbox an agent. An agent whose tools are all unmapped is dropped.
899
+ - `readonly: true` (or the string `true`) makes the agent read-only; `false` leaves it unset. Any other value drops the agent. `permissionMode: plan` also sets `readonly`; other `permissionMode` values are ignored with a diagnostic.
900
+ - `disallowedTools` takes the same shapes as `tools`. With `tools` set, the listed tools are subtracted from it. Without `tools`, the agent gets the native tool set minus the listed tools; MCP and extension tools are excluded (a diagnostic says so). An agent left with no tools is dropped. An unmappable name is noted, not fatal.
901
+ - Only user-scope files can override builtin `Explore` or `Plan`. A project file with either name is dropped with a diagnostic, so a cloned repo cannot make them writable. A user override that sets no `tools` stays read-only unless it writes `readonly: false`; otherwise it would inherit every parent tool.
902
+
903
+ | Claude Code | zidane |
904
+ |---|---|
905
+ | `Read` | `read_file` |
906
+ | `Write` | `write_file` |
907
+ | `Edit` | `edit` |
908
+ | `MultiEdit` | `multi_edit` |
909
+ | `Bash` | `shell` |
910
+ | `Grep` | `grep` |
911
+ | `Glob` | `glob` |
912
+ | `LS` | `list_files` |
913
+ | `WebFetch` | `fetch_url` |
914
+ | `WebSearch` | `web_search` |
915
+ | `Task`, `Agent` | `spawn` |
916
+
917
+ - `model` accepts `inherit` (or nothing) for the parent model, a raw model id, or an alias:
918
+
919
+ | Alias | Model |
920
+ |---|---|
921
+ | `haiku` | `claude-haiku-4-5-20251001` |
922
+ | `sonnet` | `claude-sonnet-5` |
923
+ | `opus` | `claude-opus-5-5` |
924
+ | `fable` | `claude-fable-5-1` |
925
+
926
+ Aliases resolve only under an Anthropic parent (hosts do not pass a model catalog yet). Under any other provider the agent inherits the parent model and a diagnostic is logged.
927
+ - `color`, `isolation`, `memory`, and `hooks` (and any `permissionMode` other than `plan`) are ignored with a diagnostic.
928
+
929
+ **Trust.** Project files load without a prompt, like project skills and `AGENTS.md`: each is text the model reads, not code zidane runs. A command body is sent as a user turn through the normal approval gate. A subagent file sets a child's system prompt and narrows its tools; it cannot add one (spawn filters to a subset of the parent's tools), and it cannot replace builtin `Explore` or `Plan`. Project extensions differ because they execute code, hence their opt-in setting. Headless, which often runs unattended on a repo it did not write, loads project subagents only with `--project-agents`. SDK hosts can pass `includeProject: false` to `loadMarkdownCommands` / `loadMarkdownAgents`.
930
+
931
+ **Agent Plugins.** Plugins in `.agents/plugins` contribute skills and MCP servers only: the Agent Plugins 1.0 spec defines no commands or subagents (see [PLUGINS.md](./PLUGINS.md)). Claude Code plugins (`.claude-plugin/plugin.json`) are a different format zidane does not load.
932
+
933
+ **Diagnostics.** Dropped files, ignored keys, and unenforced patterns go to the debug log only (no UI surface). Run with `ZIDANE_DEBUG=1` to see them; headless writes them to stderr only when `ZIDANE_DEBUG` is set.
934
+
784
935
  ## Title generation
785
936
 
786
937
  ```ts
@@ -29,6 +29,8 @@ context-specific flags.
29
29
 
30
30
  Contents: [Flags at a glance](#flags-at-a-glance) · [The `e2b` context](#the-e2b-context) · [The `daytona` context](#the-daytona-context) · [The `docker` context](#the-docker-context) · [The `process` context](#the-process-context-default) · [Programmatic use](#programmatic-use) · [See also](#see-also)
31
31
 
32
+ > Chat sessions (TUI, GUI, daemon) pick these per session instead — Settings → Environments turns Docker, e2b and Daytona on, configures them and tests the connection. See [CHAT.md § Session environments](./CHAT.md#session-environments). The flags below are for one-off `zidane run` launches.
33
+
32
34
  ## Flags at a glance
33
35
 
34
36
  | Flag | `process` | `docker` | `e2b` | `daytona` | Meaning |
@@ -356,6 +358,12 @@ in time by the command timeout, but not in bytes.
356
358
 
357
359
  ---
358
360
 
361
+ ## Terminals
362
+
363
+ `openTerminal(handle, { cols, rows, cwd?, env?, unsetEnv?, onData })` opens an interactive login shell in the context and returns a `TerminalProcess` (`write`, `resize`, `kill`, `detach`, `exited`). `process` and `docker` implement it under Bun (a local PTY; Docker through `docker exec -it`), `e2b` and `daytona` through their SDKs' PTY APIs, and those two also implement `reconnectTerminal(handle, id, …)` because their PTYs outlive the host. `execBackground` takes a `terminal` option that runs the task in a PTY (the `process` context honours it; `TaskHandle.terminal` says whether it did). See [TERMINAL.md](./TERMINAL.md).
364
+
365
+ ---
366
+
359
367
  ## Programmatic use
360
368
 
361
369
  The CLI flags map onto context factories you can call directly: