@iowarp/clio-coder 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (581) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +116 -16
  3. package/dist/{acp-G5WJBNCT.js → acp-ZILU3AUO.js} +14 -14
  4. package/dist/{agents-FMV2Q5G4.js → agents-HYWGBGQR.js} +40 -29
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-3IDSJEIK.js → auth-N3QT7CBO.js} +21 -19
  7. package/dist/{builtins-XCZWXSC7.js → builtins-UJLMOVOV.js} +6 -6
  8. package/dist/{chunk-PQEFIJ36.js → chunk-2LEAKD4C.js} +1 -2
  9. package/dist/{chunk-3BT2XMV4.js → chunk-2NM363SV.js} +50 -57
  10. package/dist/{chunk-J4W7KFM7.js → chunk-2VG7KLYV.js} +24 -279
  11. package/dist/{chunk-SST6Z5JA.js → chunk-336NSSKI.js} +1 -1
  12. package/dist/{chunk-K5C3NCBD.js → chunk-3I5NY75V.js} +5 -5
  13. package/dist/{chunk-VAZSBTKF.js → chunk-3QSOM6PA.js} +2 -2
  14. package/dist/{chunk-TZ3SGWZZ.js → chunk-5QIAJV2D.js} +2 -2
  15. package/dist/{chunk-OQBA45DZ.js → chunk-5YHDIDBP.js} +2 -2
  16. package/dist/{chunk-NHLBIGRH.js → chunk-6CCS4G3W.js} +40 -17
  17. package/dist/{chunk-2JDWVJND.js → chunk-6DWBAZ5U.js} +2 -2
  18. package/dist/{chunk-LDQ2ZF2M.js → chunk-6FN3E6KX.js} +5 -5
  19. package/dist/{chunk-X3YGUTOB.js → chunk-6HMJX2VU.js} +59 -59
  20. package/dist/{chunk-VEO4AP2K.js → chunk-6I5ILFOF.js} +3 -2
  21. package/dist/{chunk-B7OBL7PK.js → chunk-6NJQITNH.js} +18 -11
  22. package/dist/{chunk-IQ7KR472.js → chunk-6VC4OV3Z.js} +6 -3
  23. package/dist/{chunk-P3FOHJT4.js → chunk-72GZI5EV.js} +2 -2
  24. package/dist/{chunk-AB44T6BB.js → chunk-74YWRRU5.js} +8 -7
  25. package/dist/{chunk-SJ5ZKQ4S.js → chunk-7EPLI7VL.js} +2 -2
  26. package/dist/{chunk-HWHKMHUA.js → chunk-7OBGU7UB.js} +3 -3
  27. package/dist/{chunk-2OQE55CK.js → chunk-7PWAODYW.js} +2 -2
  28. package/dist/{chunk-IBEBSCYA.js → chunk-7RY5VZPH.js} +42 -33
  29. package/dist/{chunk-LQ3DZAMX.js → chunk-ABLSQ6JX.js} +2 -2
  30. package/dist/{chunk-QLFS5GO2.js → chunk-AFKWHWXF.js} +12 -22
  31. package/dist/{chunk-FWDFM5ZU.js → chunk-AK5XEFVZ.js} +3 -3
  32. package/dist/{chunk-O6TL7WWY.js → chunk-AKB4GYDL.js} +4 -4
  33. package/dist/chunk-AP73CFDC.js +308 -0
  34. package/dist/{chunk-JDG2WCRO.js → chunk-BBTJOK6Y.js} +5 -5
  35. package/dist/{chunk-SP2RXXYO.js → chunk-BDPT6GTK.js} +10 -10
  36. package/dist/{chunk-NQQH3YT7.js → chunk-BJGUKIG4.js} +3 -3
  37. package/dist/{chunk-O4XIVISU.js → chunk-BTGG6BG2.js} +79 -38
  38. package/dist/chunk-BTK3SSSK.js +67 -0
  39. package/dist/{chunk-M6L6IDJG.js → chunk-BWH6N36D.js} +1 -1
  40. package/dist/chunk-BWW4HLO4.js +301 -0
  41. package/dist/{chunk-I5VEOC6I.js → chunk-C537JADH.js} +10 -10
  42. package/dist/{chunk-HI63TFOG.js → chunk-CDNVLKUX.js} +3 -3
  43. package/dist/{chunk-UM7N4G5A.js → chunk-CWVRRIEI.js} +2 -2
  44. package/dist/{chunk-BKFM6EJV.js → chunk-D4CJV46V.js} +2 -2
  45. package/dist/chunk-D7E7WCDF.js +27 -0
  46. package/dist/{chunk-WZR7K7ZX.js → chunk-E67WX76H.js} +6 -6
  47. package/dist/{chunk-FQ4SKYE4.js → chunk-EBY63B7G.js} +2 -5
  48. package/dist/chunk-EIMVLWB3.js +1842 -0
  49. package/dist/{chunk-RAPCMZL4.js → chunk-FSP7CMNU.js} +9 -1
  50. package/dist/{chunk-MLKNTWH2.js → chunk-FYUN5KZ3.js} +21 -21
  51. package/dist/{chunk-YJX4SHTD.js → chunk-GIZNH63R.js} +1 -1
  52. package/dist/{chunk-X75E3D2N.js → chunk-GPPB3JBE.js} +18 -16
  53. package/dist/{chunk-GR5G2PVF.js → chunk-GVQJ5CCZ.js} +15 -10
  54. package/dist/chunk-HJWWJ6IL.js +184 -0
  55. package/dist/{chunk-VO67MWHC.js → chunk-HRQMFLJP.js} +3 -3
  56. package/dist/{chunk-VYMXRQI6.js → chunk-HUAS7ITX.js} +89 -93
  57. package/dist/{chunk-BKFJHQCA.js → chunk-I66EAJFY.js} +2 -2
  58. package/dist/{chunk-DYJP44XW.js → chunk-IG7BCQBA.js} +1 -1
  59. package/dist/{chunk-32KWKNSF.js → chunk-IXJT6DCX.js} +3 -3
  60. package/dist/{chunk-C2LTL2W6.js → chunk-J4HBWF6Y.js} +360 -172
  61. package/dist/chunk-J5LZHVIT.js +284 -0
  62. package/dist/{chunk-QLL7ILRG.js → chunk-JA5QWE4Z.js} +510 -202
  63. package/dist/{chunk-BBVJUZHB.js → chunk-JIEGK6UF.js} +5 -173
  64. package/dist/{chunk-36EJLSQQ.js → chunk-JZWT5J3Y.js} +4 -4
  65. package/dist/{chunk-HZHHCK24.js → chunk-K6BF4U2H.js} +476 -322
  66. package/dist/{chunk-Z2RR6MAK.js → chunk-MA3H6DM5.js} +6 -6
  67. package/dist/{chunk-CODPRO7Q.js → chunk-MOPSG2X7.js} +6 -6
  68. package/dist/{chunk-76ONBSIA.js → chunk-MQXIVJ35.js} +14 -5
  69. package/dist/{chunk-YDFRH54B.js → chunk-N5UK64DP.js} +4 -4
  70. package/dist/chunk-NDDGJUXD.js +81 -0
  71. package/dist/{chunk-4ZG3XFUR.js → chunk-NLFAQR7Z.js} +12 -1
  72. package/dist/{chunk-VO2LKSTM.js → chunk-O42A54GG.js} +2 -2
  73. package/dist/{chunk-W5VSYASO.js → chunk-OJTRZGR3.js} +46 -87
  74. package/dist/{chunk-NHCZP4K7.js → chunk-PBP4B7XR.js} +2 -2
  75. package/dist/{chunk-3URVFKWK.js → chunk-PJJ6MY27.js} +5 -5
  76. package/dist/{chunk-ZGVHUX3M.js → chunk-PMZVFWSR.js} +2 -2
  77. package/dist/{chunk-GKF55TAZ.js → chunk-PVAMAVBB.js} +3 -2
  78. package/dist/{chunk-6DB53AJS.js → chunk-QKIFBZKT.js} +7 -7
  79. package/dist/{chunk-HJJTYUHX.js → chunk-QMXC4JB7.js} +20 -20
  80. package/dist/chunk-QTFGO774.js +644 -0
  81. package/dist/{chunk-CY6FY24N.js → chunk-R23Z6K6I.js} +2 -2
  82. package/dist/{chunk-ULC6OTWO.js → chunk-R32CLGZ6.js} +13 -13
  83. package/dist/{chunk-KMVISBZR.js → chunk-RKSR6VSF.js} +1 -1
  84. package/dist/{chunk-F4CKPOEQ.js → chunk-SCYB3HA4.js} +72 -26
  85. package/dist/{chunk-LY4S7GJC.js → chunk-SINK3QR6.js} +18 -16
  86. package/dist/{chunk-U77AMWDL.js → chunk-TD3PGPQA.js} +43 -22
  87. package/dist/{chunk-QBJA7R7N.js → chunk-THYWACCR.js} +2 -2
  88. package/dist/{chunk-QREDIESB.js → chunk-TVH4ONAM.js} +14 -8
  89. package/dist/chunk-U2WB7TZS.js +271 -0
  90. package/dist/{chunk-KFZI4NIL.js → chunk-UAPGZHYC.js} +145 -31
  91. package/dist/{chunk-DQITNCXG.js → chunk-UEDMSP56.js} +29 -14
  92. package/dist/{chunk-3XML7CDN.js → chunk-UXN6JT4W.js} +2 -2
  93. package/dist/{chunk-CMZWFGD2.js → chunk-VAVR4IJY.js} +19 -15
  94. package/dist/{chunk-BPKCPIL7.js → chunk-VEGN6WIQ.js} +2 -2
  95. package/dist/chunk-VKFQTNDV.js +66 -0
  96. package/dist/{chunk-7MCTRUCE.js → chunk-VKRH2TCS.js} +2 -2
  97. package/dist/{chunk-5LXZXPKX.js → chunk-VN3SHNBN.js} +2 -2
  98. package/dist/{chunk-UOSL25KY.js → chunk-VXMFAE2W.js} +1 -1
  99. package/dist/{chunk-BBVYXMFO.js → chunk-WNP7O5WZ.js} +2 -2
  100. package/dist/{chunk-SUCTJL45.js → chunk-WR6U3OVP.js} +3 -3
  101. package/dist/{chunk-BUMFYQFY.js → chunk-XKA2ICR3.js} +124 -113
  102. package/dist/{chunk-UXMFQ54G.js → chunk-Y45G3AXC.js} +39 -39
  103. package/dist/{chunk-J5Q24KAG.js → chunk-Y4CAGMM6.js} +2 -2
  104. package/dist/{chunk-NYS75XW5.js → chunk-YAKGTXEV.js} +5 -3
  105. package/dist/{chunk-V5DHCITQ.js → chunk-ZAZB4JMW.js} +23 -12
  106. package/dist/{chunk-Z4TXYIEG.js → chunk-ZJLUDYFY.js} +9 -9
  107. package/dist/{chunk-HKIYEGME.js → chunk-ZTJUVLTU.js} +2 -2
  108. package/dist/{chunk-2ANTL7MR.js → chunk-ZW55JB7N.js} +2 -2
  109. package/dist/cli/index.js +36 -36
  110. package/dist/{clio-2JXHBBY5.js → clio-IT3G3VQH.js} +12 -13
  111. package/dist/{code-nav-3YYRMYNF.js → code-nav-RK6S7F6E.js} +5 -5
  112. package/dist/codewiki/build-worker.js +1 -1
  113. package/dist/{compile-cache-7FPE6PS3.js → compile-cache-OQAVB7Y5.js} +2 -4
  114. package/dist/{components-RYZV4JGP.js → components-UBWCQSRW.js} +4 -4
  115. package/dist/{config-QZPCMYSO.js → config-3QZRWZJF.js} +53 -49
  116. package/dist/{configure-TEGEBYCA.js → configure-FL7Y3KJF.js} +25 -22
  117. package/dist/{context-E6H5RNMC.js → context-5HE7ODYK.js} +47 -43
  118. package/dist/{context-AV7OEZ4D.js → context-KYQFRVDC.js} +11 -11
  119. package/dist/{context-GSXUE4CT.js → context-XNHL75JV.js} +32 -19
  120. package/dist/{context-clear-SHIBYK6T.js → context-clear-N545L53A.js} +47 -43
  121. package/dist/{context-working-set-5ZGKPGZQ.js → context-working-set-QHKXSV2F.js} +10 -9
  122. package/dist/{dispatch-runner-EFMJT4LD.js → dispatch-runner-RGIE5PCT.js} +56 -47
  123. package/dist/{docs-23KQS3XK.js → docs-5NAF6AU7.js} +5 -11
  124. package/dist/doctor-ZGPEGHIP.js +772 -0
  125. package/dist/{eval-TFBYQH4H.js → eval-GXLL44RD.js} +63 -45
  126. package/dist/{eval-inventory-SXH7PDKX.js → eval-inventory-HBWSWQOK.js} +7 -8
  127. package/dist/{evidence-ERGESKGN.js → evidence-HWLBRH3Q.js} +44 -40
  128. package/dist/{evolve-VDXTSYCJ.js → evolve-FTZBMNVW.js} +43 -39
  129. package/dist/{extensions-7BGBHN57.js → extensions-VHRBEID7.js} +4 -4
  130. package/dist/{fleet-2RRVDF2V.js → fleet-CKZHJWZJ.js} +86 -73
  131. package/dist/{fleet-commands-VJ726XIA.js → fleet-commands-EXDXBMV6.js} +10 -9
  132. package/dist/{fleet-decisions-EPAPM3XJ.js → fleet-decisions-OTHB6KRL.js} +5 -5
  133. package/dist/{fleet-graph-JF5QOATM.js → fleet-graph-YTEZUCUT.js} +13 -11
  134. package/dist/{fleet-inspect-VLY4S7QM.js → fleet-inspect-SS6YMDCK.js} +44 -41
  135. package/dist/{fleet-preflight-AIZUEJOY.js → fleet-preflight-PBY4VYOM.js} +5 -9
  136. package/dist/{fleet-validate-AJRPDMDV.js → fleet-validate-KMEM5L3S.js} +16 -13
  137. package/dist/{fleet-verify-JFEL2L3H.js → fleet-verify-QD5M7E7Q.js} +42 -39
  138. package/dist/{fleet-view-ZCON35AG.js → fleet-view-WAMJYNDT.js} +44 -52
  139. package/dist/{init-DN2WWLFE.js → init-5XQRBOFV.js} +63 -58
  140. package/dist/{install-XGLBQY5E.js → install-NKNG7KD4.js} +2 -2
  141. package/dist/{interop-OZBKXAYL.js → interop-34TVO25M.js} +13 -11
  142. package/dist/{library-YWZG7IMW.js → library-3QY6KF57.js} +17 -14
  143. package/dist/{memory-I4C4HMLW.js → memory-L4UTIIIW.js} +45 -41
  144. package/dist/{models-CEYXJBO6.js → models-ZVX3QOWE.js} +39 -31
  145. package/dist/{monitor-NZ6GCI3P.js → monitor-CEKVSYTS.js} +50 -46
  146. package/dist/{orchestrator-GCGQ4N5I.js → orchestrator-77BAP6BC.js} +1807 -1039
  147. package/dist/{panes-KY6W3V2E.js → panes-7STHOAUJ.js} +4 -4
  148. package/dist/{panes-HMABYVO4.js → panes-SHAUIRXY.js} +10 -8
  149. package/dist/{paths-II4K7DNR.js → paths-L7LGY6RN.js} +4 -4
  150. package/dist/{reset-DQ6FGCSH.js → reset-EOLM7GVE.js} +7 -8
  151. package/dist/{resources-BB3MVJMD.js → resources-74GKTLSF.js} +17 -13
  152. package/dist/{run-H2GQDUER.js → run-HBAUJNNZ.js} +86 -76
  153. package/dist/{share-GTJN6A5O.js → share-G3APVLVP.js} +17 -14
  154. package/dist/{skills-L55TEW6R.js → skills-35HHUKCR.js} +19 -16
  155. package/dist/{skills-eval-XVXPH2JI.js → skills-eval-QN4HSHDC.js} +55 -57
  156. package/dist/{skills-inventory-S4MXPJFV.js → skills-inventory-J357J34F.js} +15 -12
  157. package/dist/{slash-commands-ZSGASKJC.js → slash-commands-JZZCQA32.js} +31 -29
  158. package/dist/{steer-RZGSCY4R.js → steer-XAVHJM22.js} +3 -3
  159. package/dist/{support-PKEUNNQL.js → support-U7QOWY26.js} +6 -6
  160. package/dist/{targets-NCPZ644J.js → targets-DSM6CY3M.js} +59 -53
  161. package/dist/{terminal-lease-44SV3YCN.js → terminal-lease-JOPFUVEM.js} +14 -9
  162. package/dist/{tools-DAF3DI3C.js → tools-MKNWVPBH.js} +10 -8
  163. package/dist/{trace-FYVW2MQA.js → trace-ECQ7TIYZ.js} +48 -3
  164. package/dist/{tui-primitives-2AKXQNZK.js → tui-primitives-K5PASC3O.js} +8 -4
  165. package/dist/{uninstall-DW2PNOIC.js → uninstall-N34PCTGJ.js} +4 -6
  166. package/dist/{upgrade-3XPP6OQL.js → upgrade-H7TOM7YL.js} +35 -62
  167. package/dist/{usage-3NLHGTU2.js → usage-X52N3IDJ.js} +51 -48
  168. package/dist/{verifiers-SSQONKRT.js → verifiers-EJTVVSMA.js} +10 -9
  169. package/dist/{verify-3U6J7FZI.js → verify-YJL6XET2.js} +9 -8
  170. package/dist/{web-fetch-S7RR6GZ7.js → web-fetch-MPIFL3LL.js} +1 -2
  171. package/dist/{wiki-generate-CEHYGPGQ.js → wiki-generate-4NDZTQ4B.js} +66 -63
  172. package/dist/{with-panes-MKB46MPQ.js → with-panes-OBOBFIIR.js} +62 -26
  173. package/dist/worker/entry.js +57 -52
  174. package/docs/acp.md +1 -1
  175. package/docs/built-in-agents.md +40 -32
  176. package/docs/commands-and-modes.md +9 -3
  177. package/docs/config-knobs-audit.md +3 -0
  178. package/docs/configuration-and-targets.md +105 -164
  179. package/docs/environment-variables.md +2 -1
  180. package/docs/exit-codes-and-output.md +2 -0
  181. package/docs/fleet-demo-runbook.md +6 -3
  182. package/docs/fleet-dispatch.md +8 -6
  183. package/docs/installation-and-lifecycle.md +1 -1
  184. package/docs/release-cut-checklist.md +164 -177
  185. package/docs/trace-store.md +15 -10
  186. package/docs/troubleshooting.md +1 -1
  187. package/evals/README.md +5 -0
  188. package/evals/behavioral-corpus-grader.mjs +103 -0
  189. package/evals/behavioral-machinery-baseline.json +2276 -0
  190. package/evals/behavioral-machinery-driver.ts +250 -0
  191. package/evals/behavioral-machinery-support.ts +253 -0
  192. package/evals/behavioral-machinery.yaml +614 -0
  193. package/evals/behavioral-model-negative-control.yaml +68 -0
  194. package/evals/behavioral-model.yaml +216 -0
  195. package/evals/fixtures/behavioral-facts.txt +3 -0
  196. package/evals/fixtures/behavioral-main.ts +7 -0
  197. package/evals/fixtures/decoy/note.txt +2 -0
  198. package/evals/fixtures/private/credentials.txt +1 -0
  199. package/evals/tracked-metrics-baseline.yaml +229 -0
  200. package/evals/tracked-metrics-grader.mjs +129 -0
  201. package/package.json +5 -15
  202. package/skills/README.md +6 -6
  203. package/skills/coding/ast-grep/SKILL.md +8 -2
  204. package/skills/coding/coding-standards/SKILL.md +8 -2
  205. package/skills/coding/prototype/SKILL.md +8 -2
  206. package/skills/coding/tdd/SKILL.md +8 -2
  207. package/skills/context/context-handoff/SKILL.md +12 -3
  208. package/skills/context/context-handoff/evals.md +3 -3
  209. package/skills/context/context-prime/SKILL.md +8 -2
  210. package/skills/context/context-prime/evals.md +2 -2
  211. package/skills/git/file-ticket/SKILL.md +9 -4
  212. package/skills/git/file-ticket/evals.md +2 -2
  213. package/skills/git/fix-issue/SKILL.md +12 -5
  214. package/skills/git/fix-issue/evals.md +4 -4
  215. package/skills/git/resolve-merge-conflicts/SKILL.md +8 -2
  216. package/skills/git/resolve-merge-conflicts/evals.md +2 -2
  217. package/skills/git/ship/SKILL.md +9 -3
  218. package/skills/git/ship/evals.md +4 -4
  219. package/skills/git/worktree-create/SKILL.md +7 -2
  220. package/skills/git/worktree-create/evals.md +6 -6
  221. package/skills/git/worktree-merge/SKILL.md +7 -2
  222. package/skills/git/worktree-merge/evals.md +6 -6
  223. package/skills/meta/{clio-dev → clio-coder-dev}/SKILL.md +14 -8
  224. package/skills/meta/{clio-dev → clio-coder-dev}/evals.md +3 -3
  225. package/skills/meta/{clio-test → clio-coder-test}/SKILL.md +11 -5
  226. package/skills/meta/{clio-test → clio-coder-test}/evals.md +1 -1
  227. package/skills/meta/credentials/SKILL.md +9 -2
  228. package/skills/meta/find-skills/SKILL.md +8 -2
  229. package/skills/meta/herdr/SKILL.md +8 -2
  230. package/skills/meta/skill-craft/SKILL.md +21 -4
  231. package/skills/planning/architecture/SKILL.md +9 -3
  232. package/skills/planning/backlog/SKILL.md +8 -2
  233. package/skills/planning/prd/SKILL.md +8 -2
  234. package/skills/planning/product-intent/SKILL.md +8 -2
  235. package/skills/planning/tech-spec/SKILL.md +9 -3
  236. package/skills/registry.yaml +66 -66
  237. package/skills/research/arxiv-literature/SKILL.md +8 -2
  238. package/skills/research/experiment-protocol/SKILL.md +9 -2
  239. package/skills/research/scientific-debugging/SKILL.md +9 -2
  240. package/skills/research/scientific-debugging/evals.md +2 -2
  241. package/skills/research/scientific-modernization/SKILL.md +9 -2
  242. package/skills/skill-marketplace.json +257 -38
  243. package/skills/workflow/cut-it/SKILL.md +10 -2
  244. package/skills/workflow/design-council/SKILL.md +11 -2
  245. package/skills/workflow/grill-me/SKILL.md +8 -2
  246. package/skills/workflow/workflow-distiller/SKILL.md +8 -2
  247. package/src/cli/acp.ts +2 -2
  248. package/src/cli/auth.ts +2 -2
  249. package/src/cli/bootstrap-generate.ts +5 -5
  250. package/src/cli/clio.ts +6 -1
  251. package/src/cli/config-inspect.ts +3 -3
  252. package/src/cli/configure-interop.ts +1 -1
  253. package/src/cli/configure.ts +27 -27
  254. package/src/cli/default-target.ts +1 -1
  255. package/src/cli/docs.ts +6 -6
  256. package/src/cli/doctor-naming.ts +323 -0
  257. package/src/cli/doctor-panes.ts +46 -14
  258. package/src/cli/doctor-toolchain.ts +29 -8
  259. package/src/cli/doctor.ts +15 -1
  260. package/src/cli/eval-inventory.ts +8 -8
  261. package/src/cli/evidence-detail.ts +1 -1
  262. package/src/cli/evidence-inventory.ts +1 -1
  263. package/src/cli/fleet-decisions.ts +1 -1
  264. package/src/cli/fleet-inspect.ts +1 -1
  265. package/src/cli/fleet-preflight.ts +1 -1
  266. package/src/cli/fleet-verify.ts +1 -1
  267. package/src/cli/fleet-view.ts +40 -23
  268. package/src/cli/fleet.ts +8 -5
  269. package/src/cli/interop-inspect.ts +2 -2
  270. package/src/cli/models.ts +3 -3
  271. package/src/cli/modes/print.ts +1 -1
  272. package/src/cli/reset.ts +1 -1
  273. package/src/cli/run.ts +11 -15
  274. package/src/cli/share.ts +1 -1
  275. package/src/cli/skills-eval.ts +11 -11
  276. package/src/cli/targets.ts +23 -24
  277. package/src/cli/trace-inspect.ts +1 -1
  278. package/src/cli/trace.ts +41 -0
  279. package/src/cli/uninstall.ts +2 -2
  280. package/src/cli/upgrade.ts +29 -34
  281. package/src/cli/usage.ts +2 -2
  282. package/src/cli/validate-model.ts +1 -1
  283. package/src/cli/verifiers-inspect.ts +1 -1
  284. package/src/cli/wiki-generate.ts +5 -5
  285. package/src/core/bash-exec.ts +1 -1
  286. package/src/core/boot-trace.ts +3 -3
  287. package/src/core/bus-trace.ts +2 -2
  288. package/src/core/compile-cache.ts +1 -1
  289. package/src/core/config.ts +1062 -886
  290. package/src/core/defaults.ts +321 -528
  291. package/src/core/domain-loader.ts +6 -4
  292. package/src/core/event-bus.ts +1 -1
  293. package/src/core/file-references.ts +78 -88
  294. package/src/core/git-commit-attribution.ts +3 -9
  295. package/src/core/guardrails.ts +24 -4
  296. package/src/core/naming-compat.ts +45 -0
  297. package/src/core/naming-events.ts +62 -0
  298. package/src/core/recent-models.ts +1 -7
  299. package/src/core/residency-protection.ts +4 -8
  300. package/src/core/resolve-config-value.ts +1 -1
  301. package/src/core/response-model-id.ts +0 -14
  302. package/src/core/session-routing.ts +84 -74
  303. package/src/core/skill-activation.ts +18 -0
  304. package/src/core/termination.ts +4 -4
  305. package/src/core/timers.ts +2 -12
  306. package/src/domains/agents/catalog.ts +2 -12
  307. package/src/domains/agents/extension.ts +3 -3
  308. package/src/domains/agents/registry.ts +7 -7
  309. package/src/domains/agents/result-contract.ts +1 -1
  310. package/src/domains/agents/spec.ts +0 -11
  311. package/src/domains/config/classify.ts +35 -47
  312. package/src/domains/config/extension.ts +5 -5
  313. package/src/domains/config/keybindings.ts +48 -47
  314. package/src/domains/config/watcher.ts +1 -1
  315. package/src/domains/context/codewiki/indexer.ts +1 -1
  316. package/src/domains/context/extension.ts +3 -3
  317. package/src/domains/context/prompt-context.ts +1 -1
  318. package/src/domains/context/wiki/assemble.ts +2 -2
  319. package/src/domains/context/working-set/replay/metrics.ts +0 -4
  320. package/src/domains/context/working-set/replay/report.ts +1 -1
  321. package/src/domains/context/working-set/replay/synthetic.ts +2 -2
  322. package/src/domains/dispatch/active-route-planner.ts +2 -2
  323. package/src/domains/dispatch/agent-candidates.ts +1 -1
  324. package/src/domains/dispatch/agent-ledger-store.ts +1 -1
  325. package/src/domains/dispatch/agent-ledger.ts +1 -1
  326. package/src/domains/dispatch/assignment.ts +5 -5
  327. package/src/domains/dispatch/capacity-lease.ts +2 -5
  328. package/src/domains/dispatch/code-step.ts +1 -1
  329. package/src/domains/dispatch/contract.ts +2 -2
  330. package/src/domains/dispatch/extension.ts +65 -61
  331. package/src/domains/dispatch/fleet-node-prompt.ts +1 -1
  332. package/src/domains/dispatch/fleet-preflight.ts +8 -17
  333. package/src/domains/dispatch/fleet-run.ts +0 -7
  334. package/src/domains/dispatch/gate-decisions.ts +25 -14
  335. package/src/domains/dispatch/joint-route-resolver.ts +1 -1
  336. package/src/domains/dispatch/path-scope.ts +0 -61
  337. package/src/domains/dispatch/placement.ts +2 -2
  338. package/src/domains/dispatch/receipt-findings.ts +1 -1
  339. package/src/domains/dispatch/receipt-integrity.ts +20 -8
  340. package/src/domains/dispatch/reservation-store.ts +0 -23
  341. package/src/domains/dispatch/route-decision.ts +1 -1
  342. package/src/domains/dispatch/route-history.ts +1 -1
  343. package/src/domains/dispatch/route-policy.ts +1 -1
  344. package/src/domains/dispatch/route-readiness.ts +0 -28
  345. package/src/domains/dispatch/run-event-journal.ts +6 -5
  346. package/src/domains/dispatch/tool-stats.ts +1 -1
  347. package/src/domains/dispatch/transport.ts +2 -19
  348. package/src/domains/dispatch/types.ts +3 -3
  349. package/src/domains/dispatch/worker-protocol.ts +2 -2
  350. package/src/domains/eval/artifacts/store.ts +5 -4
  351. package/src/domains/eval/compare/thresholds.ts +0 -8
  352. package/src/domains/eval/execution-provenance.ts +1 -1
  353. package/src/domains/eval/metrics/token-stream.ts +0 -7
  354. package/src/domains/eval/report.ts +1 -1
  355. package/src/domains/eval/runner.ts +1 -1
  356. package/src/domains/eval/runners/clio-run.ts +7 -6
  357. package/src/domains/eval/runners/external-command.ts +4 -3
  358. package/src/domains/eval/schema/artifact.ts +1 -1
  359. package/src/domains/eval/schema/behavioral-metrics.ts +3 -2
  360. package/src/domains/eval/schema/behavioral.ts +7 -6
  361. package/src/domains/eval/schema/execution-envelope.ts +4 -2
  362. package/src/domains/eval/schema/naming.ts +10 -0
  363. package/src/domains/eval/schema/suite.ts +2 -2
  364. package/src/domains/eval/schema/validate.ts +9 -5
  365. package/src/domains/eval/schema/verdict.ts +4 -2
  366. package/src/domains/eval/store.ts +12 -2
  367. package/src/domains/eval/suites/run.ts +12 -6
  368. package/src/domains/eval/types.ts +1 -1
  369. package/src/domains/eval/workspaces/git.ts +1 -1
  370. package/src/domains/eval/workspaces/temp-copy.ts +1 -1
  371. package/src/domains/evidence/build.ts +13 -5
  372. package/src/domains/interop/consent.ts +7 -7
  373. package/src/domains/interop/extension.ts +1 -1
  374. package/src/domains/lifecycle/doctor.ts +27 -21
  375. package/src/domains/lifecycle/migrations/2026-09-01-clio-coder-naming.ts +155 -0
  376. package/src/domains/lifecycle/migrations/2026-09-01-retire-panes-knobs.ts +59 -0
  377. package/src/domains/lifecycle/migrations/2026-09-01-settings-v2.ts +338 -0
  378. package/src/domains/lifecycle/migrations/index.ts +20 -1
  379. package/src/domains/lifecycle/naming-history.ts +153 -0
  380. package/src/domains/lifecycle/naming-mutable-state.ts +104 -0
  381. package/src/domains/lifecycle/naming-resources.ts +261 -0
  382. package/src/domains/lifecycle/naming-tool-markers.ts +139 -0
  383. package/src/domains/lifecycle/naming-yazi.ts +81 -0
  384. package/src/domains/lifecycle/upgrade-notice.ts +1 -1
  385. package/src/domains/memory/task-memory-handoff.ts +6 -3
  386. package/src/domains/middleware/index.ts +7 -0
  387. package/src/domains/middleware/marketplace-offer.ts +339 -0
  388. package/src/domains/middleware/runtime.ts +2 -2
  389. package/src/domains/mux/contract.ts +157 -25
  390. package/src/domains/mux/detect.ts +29 -5
  391. package/src/domains/mux/dock-controller.ts +365 -0
  392. package/src/domains/mux/extension.ts +7 -1
  393. package/src/domains/mux/index.ts +20 -0
  394. package/src/domains/mux/operations.ts +28 -0
  395. package/src/domains/mux/protocol.ts +19 -6
  396. package/src/domains/mux/socket-client.ts +167 -19
  397. package/src/domains/mux/types.ts +49 -6
  398. package/src/domains/mux/yazi/event-stream.ts +6 -4
  399. package/src/domains/mux/yazi/profile.ts +1 -1
  400. package/src/domains/mux/yazi/session.ts +26 -3
  401. package/src/domains/observability/extension.ts +1 -1
  402. package/src/domains/observability/out-of-turn-usage.ts +2 -2
  403. package/src/domains/observability/projection.ts +1 -1
  404. package/src/domains/observability/trace-store.ts +22 -12
  405. package/src/domains/prompts/compiler.ts +2 -2
  406. package/src/domains/prompts/extension.ts +3 -3
  407. package/src/domains/prompts/fragments/identity/clio-worker.md +1 -1
  408. package/src/domains/prompts/fragments/operating/skills.md +25 -18
  409. package/src/domains/providers/extension.ts +5 -5
  410. package/src/domains/providers/knowledge-base-path.ts +1 -1
  411. package/src/domains/providers/model-runtime-capabilities.ts +14 -2
  412. package/src/domains/providers/models/cloud-models/alcf.yaml +1 -1
  413. package/src/domains/providers/models/local-models/{clio-local-coding-targets.yaml → clio-coder-local-coding-targets.yaml} +4 -4
  414. package/src/domains/providers/plugins.ts +1 -1
  415. package/src/domains/providers/resolver.ts +1 -1
  416. package/src/domains/providers/runtimes/cloud/alcf.ts +4 -4
  417. package/src/domains/providers/runtimes/common/lmstudio-http.ts +1 -5
  418. package/src/domains/providers/runtimes/common/local-synth.ts +3 -3
  419. package/src/domains/providers/runtimes/common/probe-helpers.ts +2 -2
  420. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  421. package/src/domains/providers/runtimes/local-native/lmstudio.ts +2 -9
  422. package/src/domains/providers/runtimes/protocol/litellm.ts +1 -91
  423. package/src/domains/providers/types/knowledge-base.ts +13 -0
  424. package/src/domains/providers/types/local-model-quirks.ts +2 -2
  425. package/src/domains/providers/types/target-descriptor.ts +3 -3
  426. package/src/domains/resources/extension.ts +1 -1
  427. package/src/domains/resources/library.ts +6 -6
  428. package/src/domains/resources/prompts/loader.ts +1 -1
  429. package/src/domains/resources/skills/content-hash.ts +7 -6
  430. package/src/domains/resources/skills/evals.ts +1 -1
  431. package/src/domains/resources/skills/install.ts +36 -12
  432. package/src/domains/resources/skills/loader.ts +39 -13
  433. package/src/domains/resources/skills/marketplace.ts +21 -2
  434. package/src/domains/resources/skills/promotion.ts +214 -0
  435. package/src/domains/safety/audit.ts +2 -2
  436. package/src/domains/safety/finish-contract.ts +2 -1
  437. package/src/domains/safety/run-effects.ts +1 -1
  438. package/src/domains/scheduling/extension.ts +4 -4
  439. package/src/domains/session/archive-readers.ts +9 -1
  440. package/src/domains/session/compaction/branch-summary.ts +4 -0
  441. package/src/domains/session/compaction/compact.ts +1 -1
  442. package/src/domains/session/compaction/tokens.ts +4 -0
  443. package/src/domains/session/context-accounting.ts +1 -1
  444. package/src/domains/session/decision-board.ts +1 -1
  445. package/src/domains/session/extension.ts +1 -1
  446. package/src/domains/session/handoff.ts +2 -2
  447. package/src/domains/session/prompt-manifest.ts +0 -5
  448. package/src/domains/session/protected-artifact-journal.ts +12 -6
  449. package/src/domains/session/retry.ts +1 -1
  450. package/src/domains/share/archive.ts +85 -36
  451. package/src/domains/toolchain/install.ts +1 -1
  452. package/src/engine/acp/adapter.ts +3 -3
  453. package/src/engine/acp/event-mapper.ts +3 -3
  454. package/src/engine/acp/server.ts +27 -26
  455. package/src/engine/acp/tool-mediator.ts +2 -2
  456. package/src/engine/acp/types.ts +1 -1
  457. package/src/engine/alcf-oauth.ts +5 -5
  458. package/src/engine/antigravity/subprocess-runtime.ts +2 -2
  459. package/src/engine/api-registry.ts +1 -1
  460. package/src/engine/apis/llamacpp-residency.ts +1 -19
  461. package/src/engine/apis/lmstudio.ts +1 -6
  462. package/src/engine/apis/ollama-native.ts +3 -3
  463. package/src/engine/apis/openai-completions.ts +2 -2
  464. package/src/engine/apis/residency.ts +3 -9
  465. package/src/engine/claude/sdk-module.ts +1 -12
  466. package/src/engine/claude/sdk-runtime.ts +6 -6
  467. package/src/engine/claude/subprocess-runtime.ts +1 -1
  468. package/src/engine/claude/tool-safety.ts +4 -4
  469. package/src/engine/loop-guard.ts +0 -5
  470. package/src/engine/session.ts +14 -7
  471. package/src/engine/tui-primitives.ts +1 -1
  472. package/src/engine/worker-events.ts +6 -6
  473. package/src/engine/worker-runtime.ts +14 -13
  474. package/src/entry/orchestrator.ts +107 -88
  475. package/src/interactive/application-controller.ts +21 -21
  476. package/src/interactive/bus-notices.ts +2 -2
  477. package/src/interactive/chat-loop.ts +8 -8
  478. package/src/interactive/chat-panel.ts +1 -1
  479. package/src/interactive/clio-editor.ts +105 -1
  480. package/src/interactive/context-activity.ts +8 -6
  481. package/src/interactive/context-meter.ts +3 -6
  482. package/src/interactive/context-overlay.ts +1 -1
  483. package/src/interactive/cost-overlay.ts +1 -1
  484. package/src/interactive/council-grid.ts +2 -2
  485. package/src/interactive/dispatch-board.ts +30 -33
  486. package/src/interactive/editor-bash.ts +21 -0
  487. package/src/interactive/editor-steer.ts +9 -4
  488. package/src/interactive/editor-submit.ts +24 -18
  489. package/src/interactive/external-editor.ts +1 -1
  490. package/src/interactive/file-reference-completion.ts +360 -0
  491. package/src/interactive/footer/dashboard.ts +15 -17
  492. package/src/interactive/footer/notifications.ts +14 -8
  493. package/src/interactive/footer/widgets.ts +2 -44
  494. package/src/interactive/interactive-application.ts +79 -59
  495. package/src/interactive/interactive-event-projection.ts +1 -1
  496. package/src/interactive/interactive-input-runtime.ts +2 -2
  497. package/src/interactive/interactive-presentation.ts +21 -16
  498. package/src/interactive/interactive-shell.ts +2 -2
  499. package/src/interactive/interactive-slash-runtime.ts +63 -17
  500. package/src/interactive/interactive-subscriptions.ts +1 -1
  501. package/src/interactive/interactive-tickers.ts +1 -1
  502. package/src/interactive/keybinding-manager.ts +4 -4
  503. package/src/interactive/layout.ts +1 -1
  504. package/src/interactive/memory-overlay.ts +2 -2
  505. package/src/interactive/modal-marker.ts +2 -21
  506. package/src/interactive/mutation-preview.ts +1 -1
  507. package/src/interactive/overlay-frame.ts +4 -12
  508. package/src/interactive/overlay-general-openers.ts +6 -2
  509. package/src/interactive/overlay-key-routing.ts +7 -7
  510. package/src/interactive/overlay-lifecycle.ts +1 -1
  511. package/src/interactive/overlay-model-selectors.ts +3 -3
  512. package/src/interactive/overlays/agents.ts +2 -2
  513. package/src/interactive/overlays/ask-user.ts +25 -11
  514. package/src/interactive/overlays/context-reset.ts +28 -4
  515. package/src/interactive/overlays/cwd-fallback.ts +28 -4
  516. package/src/interactive/overlays/decisions.ts +4 -2
  517. package/src/interactive/overlays/fleet-run-approval.ts +3 -3
  518. package/src/interactive/overlays/handoff-review.ts +2 -2
  519. package/src/interactive/overlays/interop.ts +1 -1
  520. package/src/interactive/overlays/library-install-confirm.ts +1 -1
  521. package/src/interactive/overlays/message-picker.ts +1 -1
  522. package/src/interactive/overlays/model-selector.ts +39 -31
  523. package/src/interactive/overlays/prompts.ts +10 -4
  524. package/src/interactive/overlays/session-selector.ts +2 -2
  525. package/src/interactive/overlays/settings.ts +339 -293
  526. package/src/interactive/overlays/side-question.ts +2 -2
  527. package/src/interactive/overlays/skills-hub.ts +8 -8
  528. package/src/interactive/overlays/tree-selector.ts +2 -2
  529. package/src/interactive/panes-runtime.ts +36 -10
  530. package/src/interactive/permission-overlay.ts +2 -2
  531. package/src/interactive/renderers/retry-status.ts +12 -4
  532. package/src/interactive/renderers/tool-execution.ts +4 -4
  533. package/src/interactive/session-transcript.ts +3 -3
  534. package/src/interactive/slash-autocomplete.ts +454 -165
  535. package/src/interactive/slash-commands.ts +279 -183
  536. package/src/interactive/slash-spec.ts +160 -17
  537. package/src/interactive/status/controller.ts +2 -2
  538. package/src/interactive/stream-pacing-policy.ts +1 -1
  539. package/src/interactive/tasks-overlay.ts +20 -13
  540. package/src/interactive/terminal-lease.ts +6 -7
  541. package/src/interactive/turn-context.ts +11 -11
  542. package/src/interactive/turn-persistence.ts +2 -2
  543. package/src/interactive/turn-prewarm.ts +2 -2
  544. package/src/interactive/turn-runtime.ts +8 -8
  545. package/src/interactive/turn-state.ts +1 -1
  546. package/src/interactive/view/artifacts.ts +12 -9
  547. package/src/interactive/view/view-overlay.ts +13 -10
  548. package/src/interactive/watch-pane.ts +38 -10
  549. package/src/interactive/welcome-dashboard.ts +15 -15
  550. package/src/interactive/worker-progress.ts +4 -4
  551. package/src/interactive/worker-receipts.ts +1 -1
  552. package/src/interactive/worker-stream.ts +1 -4
  553. package/src/interactive/yazi-bridge.ts +12 -5
  554. package/src/tools/ask-user.ts +3 -3
  555. package/src/tools/bash.ts +1 -1
  556. package/src/tools/builtin-tool-catalog.ts +1 -1
  557. package/src/tools/compete-worktrees.ts +27 -17
  558. package/src/tools/context/index.ts +15 -1
  559. package/src/tools/dispatch-admission.ts +4 -2
  560. package/src/tools/dispatch-arguments.ts +1 -1
  561. package/src/tools/dispatch-plan.ts +0 -11
  562. package/src/tools/dispatch-run-events.ts +12 -10
  563. package/src/tools/dispatch-runner.ts +11 -6
  564. package/src/tools/dispatch.ts +1 -1
  565. package/src/tools/find.ts +1 -6
  566. package/src/tools/monitor.ts +4 -4
  567. package/src/tools/observers.ts +0 -33
  568. package/src/tools/task-worktree.ts +28 -7
  569. package/src/tools/verify/authoring.ts +3 -6
  570. package/src/tools/verify/frontend.ts +1 -1
  571. package/src/tools/web-fetch.ts +1 -1
  572. package/src/worker/entry.ts +4 -3
  573. package/src/worker/protocol.ts +16 -14
  574. package/src/worker/spec-contract.ts +2 -2
  575. package/src/worker/stdin-demux.ts +2 -2
  576. package/dist/chunk-GXNLGKAB.js +0 -571
  577. package/dist/chunk-VFA6GDY5.js +0 -1625
  578. package/dist/doctor-QOA5FNY5.js +0 -313
  579. package/src/engine/apis/degraded-inference.ts +0 -95
  580. /package/skills/meta/{clio-test → clio-coder-test}/references/harness.md +0 -0
  581. /package/skills/meta/{clio-test → clio-coder-test}/references/test-map.md +0 -0
@@ -0,0 +1,339 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ SKILL_INSTALL_OFFER_OPTION_NEVER,
4
+ SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
5
+ SKILL_INSTALL_OFFER_OPTION_PROJECT,
6
+ SKILL_INSTALL_OFFER_OPTION_USER,
7
+ } from "../../core/skill-activation.js";
8
+ import { ToolNames } from "../../core/tool-names.js";
9
+ import type { MarketplaceSkill } from "../resources/skills/marketplace.js";
10
+ import {
11
+ assertPromotionInstallSource,
12
+ declineKey,
13
+ matchMarketplaceSkills,
14
+ type PromotionMatch,
15
+ readPromotionDeclines,
16
+ recordPromotionNeverDecline,
17
+ } from "../resources/skills/promotion.js";
18
+ import type { AutonomyLevel } from "../safety/autonomy.js";
19
+ import type { MiddlewareHookRegistration } from "./runtime.js";
20
+ import { isSubstantiveUserTurn } from "./skills-reminder.js";
21
+ import { type MiddlewareEffect, type MiddlewareHookInput, metadataNumber } from "./types.js";
22
+
23
+ /**
24
+ * Marketplace self-promotion offer. When the operator's request needs
25
+ * expertise no installed skill covers and the local marketplace has a match,
26
+ * this registration teaches the model to put one tight ask_user question to
27
+ * the operator; the after_tool observer then acts on the answer — a consented
28
+ * install runs harness-side through the injected installer, "Not now" is a
29
+ * session-only decline, and "Never offer this skill" persists.
30
+ *
31
+ * Firing is need-driven, not count-budgeted: any substantive turn whose text
32
+ * matches an uninstalled, undeclined marketplace skill may fire, but each
33
+ * skill is offered at most once per session so a repeated request never
34
+ * becomes a nag. Detection is the local lexical matcher in
35
+ * resources/skills/promotion.ts — no request text leaves the machine.
36
+ *
37
+ * At full-auto the offer collapses into an autonomous install, but ONLY from
38
+ * Clio's own marketplace: every promotion-flow install (consented or
39
+ * autonomous) passes `assertPromotionInstallSource`, the hard runtime gate
40
+ * that rejects public registries in code rather than prompt text.
41
+ *
42
+ * Coordinator-only by wiring: this registration is created in the
43
+ * orchestrator entry and never in a dispatch worker's loop, so workers can
44
+ * neither see nor trigger offers.
45
+ */
46
+
47
+ export const MARKETPLACE_OFFER_REGISTRATION_ID = "observer.marketplace-offer";
48
+
49
+ export interface MarketplaceOfferInstallResult {
50
+ path: string;
51
+ /** The source the skill was fetched from; surfaced so an autonomous install stays auditable. */
52
+ sourceUrl: string;
53
+ /** Normalized content hash of what was written; the integrity record the consent overlay would have shown. */
54
+ installedHash: string;
55
+ }
56
+
57
+ export interface MarketplaceOfferDeps {
58
+ /** Installed skill names the matcher must exclude. */
59
+ listInstalledSkillNames(): ReadonlyArray<string>;
60
+ /** The local marketplace lookup (catalog + index); already local-only. */
61
+ listMarketplaceEntries(): ReadonlyArray<MarketplaceSkill>;
62
+ /** The session's effective autonomy at evaluation time. */
63
+ getAutonomy(): AutonomyLevel;
64
+ /** Perform the install; the registration gates the source before calling. */
65
+ installEntry(entry: MarketplaceSkill, scope: "user" | "project"): MarketplaceOfferInstallResult;
66
+ /** Decline persistence; defaults to the promotion module's config-dir store. Keyed by name+version. */
67
+ declines?: {
68
+ readNever(): Readonly<Record<string, string>>;
69
+ recordNever(name: string, version?: string): void;
70
+ };
71
+ /** Per-offer binding tag source; injectable for deterministic tests. Defaults to a random id. */
72
+ newOfferTag?: () => string;
73
+ }
74
+
75
+ const NO_EFFECTS: ReadonlyArray<MiddlewareEffect> = [];
76
+
77
+ /**
78
+ * The tag the model must echo verbatim in its ask_user question so the harness
79
+ * binds the answer to this specific offer and nothing else. The tag lives only
80
+ * in a system reminder (a trusted channel), so prompt-injected user or file
81
+ * content cannot forge it onto an unrelated question.
82
+ */
83
+ export function offerBindingTag(offerTag: string): string {
84
+ return `[clio-install:${offerTag}]`;
85
+ }
86
+
87
+ export function marketplaceOfferReminder(entry: MarketplaceSkill, offerTag: string): string {
88
+ return (
89
+ `[Marketplace] This request may need expertise no installed skill covers. The local marketplace has ` +
90
+ `"${entry.name}" (not installed): ${entry.description} ` +
91
+ `First check the installed side with context(scope="skills"); if ${entry.name} is genuinely the right fit ` +
92
+ `and nothing installed serves, ask the operator with ask_user (mode=single_question, header "Install skill") ` +
93
+ `whether to install ${entry.name}, offering exactly these options in this order: ` +
94
+ `"${SKILL_INSTALL_OFFER_OPTION_PROJECT}", "${SKILL_INSTALL_OFFER_OPTION_USER}", ` +
95
+ `"${SKILL_INSTALL_OFFER_OPTION_NOT_NOW}", "${SKILL_INSTALL_OFFER_OPTION_NEVER}". ` +
96
+ `Include the exact tag ${offerBindingTag(offerTag)} verbatim in that question's text so the harness can bind ` +
97
+ `the answer to this offer; the harness acts only on an answer carrying this tag. ` +
98
+ `Then continue the task in the same turn. The harness handles the answer; never install or load a skill yourself. ` +
99
+ `If it is not actually needed, do not mention it.`
100
+ );
101
+ }
102
+
103
+ export function marketplaceAutoInstallReminder(entry: MarketplaceSkill, result: MarketplaceOfferInstallResult): string {
104
+ // Full-auto skips the operator's yes/no, never the integrity record. The
105
+ // consent overlay states the source and the SHA-256 of what it writes; the
106
+ // autonomous path computes the same hash and must surface it here, so an
107
+ // install nobody was asked about is still traceable to its source and bytes.
108
+ return (
109
+ `[Marketplace] Installed skill "${entry.name}" from Clio's own local marketplace (full-auto) to ${result.path}. ` +
110
+ `Source ${result.sourceUrl}, sha256 ${result.installedHash}. ${entry.description} ` +
111
+ `It is installed but not active — activation stays operator-gated. If it fits this task, ` +
112
+ `suggest /skill ${entry.name} to the operator; never load it yourself.`
113
+ );
114
+ }
115
+
116
+ interface AnswerLike {
117
+ question: string;
118
+ answer: string;
119
+ options?: ReadonlyArray<string>;
120
+ }
121
+
122
+ function answersFromDetails(details: Readonly<Record<string, unknown>> | undefined): AnswerLike[] {
123
+ const raw = details?.answers;
124
+ if (!Array.isArray(raw)) return [];
125
+ return raw.filter(
126
+ (entry): entry is AnswerLike =>
127
+ typeof entry === "object" &&
128
+ entry !== null &&
129
+ typeof (entry as AnswerLike).question === "string" &&
130
+ typeof (entry as AnswerLike).answer === "string",
131
+ );
132
+ }
133
+
134
+ /** The offer option this answer chose, matched by exact label in options or by label substring in the one-line answer. */
135
+ function chosenOfferOption(answer: AnswerLike): string | null {
136
+ const labels = [
137
+ SKILL_INSTALL_OFFER_OPTION_PROJECT,
138
+ SKILL_INSTALL_OFFER_OPTION_USER,
139
+ SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
140
+ SKILL_INSTALL_OFFER_OPTION_NEVER,
141
+ ];
142
+ for (const label of labels) {
143
+ if (answer.options?.includes(label)) return label;
144
+ }
145
+ for (const label of labels) {
146
+ if (answer.answer.includes(label)) return label;
147
+ }
148
+ return null;
149
+ }
150
+
151
+ export function createMarketplaceOfferRegistration(deps: MarketplaceOfferDeps): MiddlewareHookRegistration {
152
+ const declines = deps.declines ?? {
153
+ readNever: () => readPromotionDeclines().never,
154
+ recordNever: (name: string, version?: string) => recordPromotionNeverDecline(name, version),
155
+ };
156
+ const newOfferTag = deps.newOfferTag ?? (() => randomUUID());
157
+ // Per-session state, reset on a session id change (same adoption rule as
158
+ // the skills reminder: a fresh session's opening turn fires with a null id
159
+ // that the created session then fills in).
160
+ let lastSeenSessionId: string | null | undefined;
161
+ let offeredNames = new Set<string>();
162
+ let sessionDeclines = new Set<string>();
163
+ // Discovery parses and hashes every installed/catalog SKILL.md. On a real
164
+ // home with 31 entries per side that cost 36-92ms on every turn_start, while
165
+ // the lexical match itself stayed below 2ms. Inventory is stable within a
166
+ // session except for installs, so keep one session snapshot and revalidate
167
+ // installed names only when cached data is about to produce an offer.
168
+ let installedNamesCache: Set<string> | null = null;
169
+ let marketplaceEntriesCache: ReadonlyArray<MarketplaceSkill> | null = null;
170
+ // The offer put to the operator, consumed by the ask_user after_tool
171
+ // observer. Each offer carries a unique binding tag the model must echo in
172
+ // its question; the observer acts only on an answer whose question carries
173
+ // that tag, so an unrelated (or prompt-injected) ask_user answer can never
174
+ // bind the pending offer.
175
+ let pendingOffer: { entry: MarketplaceSkill; tag: string } | null = null;
176
+
177
+ const trackSession = (input: MiddlewareHookInput): void => {
178
+ const sessionId = input.sessionId ?? null;
179
+ if (lastSeenSessionId === undefined || (lastSeenSessionId === null && sessionId !== null)) {
180
+ lastSeenSessionId = sessionId;
181
+ return;
182
+ }
183
+ if (sessionId !== lastSeenSessionId) {
184
+ lastSeenSessionId = sessionId;
185
+ offeredNames = new Set<string>();
186
+ sessionDeclines = new Set<string>();
187
+ pendingOffer = null;
188
+ installedNamesCache = null;
189
+ marketplaceEntriesCache = null;
190
+ }
191
+ };
192
+
193
+ const bestMatch = (text: string): PromotionMatch | null => {
194
+ const installedWasCached = installedNamesCache !== null;
195
+ let never: Readonly<Record<string, string>>;
196
+ try {
197
+ installedNamesCache ??= new Set(deps.listInstalledSkillNames());
198
+ marketplaceEntriesCache ??= deps.listMarketplaceEntries();
199
+ never = declines.readNever();
200
+ } catch {
201
+ return null;
202
+ }
203
+ // offeredNames caps to one offer per skill per session, regardless of
204
+ // version. Declines are version-scoped: a "never"/"not now" on one catalog
205
+ // version does not suppress a later version of the same skill.
206
+ const declinedKeys = new Set<string>([...Object.keys(never), ...sessionDeclines]);
207
+ const findMatch = (): PromotionMatch | null =>
208
+ matchMarketplaceSkills(
209
+ text,
210
+ marketplaceEntriesCache ?? [],
211
+ new Set<string>([...(installedNamesCache ?? []), ...offeredNames]),
212
+ ).find((candidate) => !declinedKeys.has(declineKey(candidate.entry.name, candidate.entry.version))) ?? null;
213
+ let match = findMatch();
214
+ if (match && installedWasCached) {
215
+ // A skills-hub or external install can happen after the session snapshot.
216
+ // Pay the installed-tree refresh only on the rare path that would surface
217
+ // an offer, then match again so an already-installed skill is never nagged.
218
+ try {
219
+ installedNamesCache = new Set(deps.listInstalledSkillNames());
220
+ } catch {
221
+ return null;
222
+ }
223
+ match = findMatch();
224
+ }
225
+ return match;
226
+ };
227
+
228
+ const installGated = (entry: MarketplaceSkill, scope: "user" | "project"): MarketplaceOfferInstallResult => {
229
+ assertPromotionInstallSource(entry);
230
+ const result = deps.installEntry(entry, scope);
231
+ installedNamesCache?.add(entry.name);
232
+ return result;
233
+ };
234
+
235
+ const evaluateAskUserAnswers = (input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> => {
236
+ const offer = pendingOffer;
237
+ if (!offer) return NO_EFFECTS;
238
+ const marker = offerBindingTag(offer.tag);
239
+ if (input.toolResultDetails?.cancelled === true) {
240
+ // A cancelled interview carries no question text, so it cannot be
241
+ // attributed to this offer. Do not bind it: an unrelated cancel must
242
+ // not decline our offer, and the offer stays armed for a later answer.
243
+ return NO_EFFECTS;
244
+ }
245
+ for (const answer of answersFromDetails(input.toolResultDetails)) {
246
+ // The answer binds only when its question carries this offer's tag.
247
+ if (!answer.question.includes(marker)) continue;
248
+ const option = chosenOfferOption(answer);
249
+ if (option === null) continue;
250
+ pendingOffer = null;
251
+ const offerKey = declineKey(offer.entry.name, offer.entry.version);
252
+ if (option === SKILL_INSTALL_OFFER_OPTION_NOT_NOW) {
253
+ sessionDeclines.add(offerKey);
254
+ return NO_EFFECTS;
255
+ }
256
+ if (option === SKILL_INSTALL_OFFER_OPTION_NEVER) {
257
+ sessionDeclines.add(offerKey);
258
+ try {
259
+ declines.recordNever(offer.entry.name, offer.entry.version);
260
+ } catch {
261
+ // Persistence failure degrades to a session decline.
262
+ }
263
+ return NO_EFFECTS;
264
+ }
265
+ const scope = option === SKILL_INSTALL_OFFER_OPTION_USER ? "user" : "project";
266
+ try {
267
+ const result = installGated(offer.entry, scope);
268
+ return [
269
+ {
270
+ kind: "annotate_tool_result",
271
+ severity: "info",
272
+ message:
273
+ `[Marketplace] Installed skill "${offer.entry.name}" (${scope} scope) to ${result.path}. ` +
274
+ `It is installed but not active; the operator activates it with /skill ${offer.entry.name}.`,
275
+ },
276
+ ];
277
+ } catch (error) {
278
+ sessionDeclines.add(offerKey);
279
+ return [
280
+ {
281
+ kind: "annotate_tool_result",
282
+ severity: "warn",
283
+ message: `[Marketplace] Install of "${offer.entry.name}" failed: ${
284
+ error instanceof Error ? error.message : String(error)
285
+ }. Continue the task without it.`,
286
+ },
287
+ ];
288
+ }
289
+ }
290
+ return NO_EFFECTS;
291
+ };
292
+
293
+ return {
294
+ id: MARKETPLACE_OFFER_REGISTRATION_ID,
295
+ description:
296
+ "offers an uninstalled marketplace skill via ask_user when the request matches it; consented installs run gated to Clio's own marketplace, declines are remembered",
297
+ hooks: ["turn_start", "after_tool"],
298
+ evaluate(input): ReadonlyArray<MiddlewareEffect> {
299
+ trackSession(input);
300
+ // The offer is deliberately NOT cleared at turn_end. The reminder asks
301
+ // the model to put the ask_user question in the same turn, but it often
302
+ // defers a turn; clearing here dropped that deferred answer on the floor,
303
+ // so the operator's "Install" installed nothing and the model got no
304
+ // signal to correct a likely success claim. The offer now stays armed
305
+ // until a tag-carrying answer binds it, a newer offer replaces it, or the
306
+ // session ends — safe because only a tagged answer can bind (see H4).
307
+ if (input.hook === "after_tool") {
308
+ return input.toolName === ToolNames.AskUser ? evaluateAskUserAnswers(input) : NO_EFFECTS;
309
+ }
310
+ if (input.hook !== "turn_start") return NO_EFFECTS;
311
+ if (!isSubstantiveUserTurn(input.text)) return NO_EFFECTS;
312
+ // The operator already chose a skill for this turn; an offer on top of
313
+ // that choice is noise.
314
+ const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
315
+ if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS;
316
+ const match = bestMatch(input.text ?? "");
317
+ if (!match) return NO_EFFECTS;
318
+ offeredNames.add(match.entry.name);
319
+ if (deps.getAutonomy() === "full-auto") {
320
+ try {
321
+ const result = installGated(match.entry, "project");
322
+ return [
323
+ {
324
+ kind: "inject_reminder",
325
+ severity: "info",
326
+ message: marketplaceAutoInstallReminder(match.entry, result),
327
+ },
328
+ ];
329
+ } catch {
330
+ // Gate refusal or install failure: fall back to the consent offer
331
+ // so the operator still hears about the match.
332
+ }
333
+ }
334
+ const tag = newOfferTag();
335
+ pendingOffer = { entry: match.entry, tag };
336
+ return [{ kind: "inject_reminder", severity: "info", message: marketplaceOfferReminder(match.entry, tag) }];
337
+ },
338
+ };
339
+ }
@@ -102,7 +102,7 @@ export type MiddlewareDiagnosticSink = (diagnostic: MiddlewareDiagnostic) => voi
102
102
  export function writeMiddlewareDiagnosticToStderr(diagnostic: MiddlewareDiagnostic): void {
103
103
  if (diagnostic.kind === "hook_failed") {
104
104
  process.stderr.write(
105
- `[clio:middleware] registration '${diagnostic.registrationId}' failed on '${diagnostic.hook}': ${diagnostic.message}\n`,
105
+ `[clio-coder:middleware] registration '${diagnostic.registrationId}' failed on '${diagnostic.hook}': ${diagnostic.message}\n`,
106
106
  );
107
107
  return;
108
108
  }
@@ -115,7 +115,7 @@ export function writeMiddlewareDiagnosticToStderr(diagnostic: MiddlewareDiagnost
115
115
  ? ` (slow on ${stats.overCount}/${stats.window} recent ${diagnostic.hook} calls, p95 ${stats.p95Ms.toFixed(1)}ms)`
116
116
  : "";
117
117
  process.stderr.write(
118
- `[clio:middleware] registration '${diagnostic.registrationId}' exceeded budget on '${diagnostic.hook}': ` +
118
+ `[clio-coder:middleware] registration '${diagnostic.registrationId}' exceeded budget on '${diagnostic.hook}': ` +
119
119
  `${diagnostic.elapsedMs.toFixed(1)}ms > ${diagnostic.budgetMs}ms${trend}\n`,
120
120
  );
121
121
  }
@@ -8,7 +8,7 @@
8
8
  * fallback value rather than rejecting.
9
9
  *
10
10
  * Two ownership rules run through the whole file. Clio acts only on panes it
11
- * created, tracked in `pane-registry.ts` and tagged with the `clio_owner`
11
+ * created, tracked in `pane-registry.ts` and tagged with the `clio_coder_owner`
12
12
  * metadata token so `clio doctor` can find orphans. The one documented
13
13
  * exception is Clio's own hosting pane in guest mode, which `reportSelf`
14
14
  * writes to under SA-3 of the v0.4.0 cycle plan.
@@ -25,6 +25,7 @@
25
25
 
26
26
  import type { DomainContract } from "../../core/domain-loader.js";
27
27
  import type { MuxDetection } from "./detect.js";
28
+ import { createDockController, type DockController, type DockSlot, type DockState } from "./dock-controller.js";
28
29
  import { createPaneRegistry, type MuxPaneRegistry, paneRecord } from "./pane-registry.js";
29
30
  import { muxSupportsMethod } from "./protocol.js";
30
31
  import type {
@@ -46,12 +47,15 @@ import {
46
47
  } from "./types.js";
47
48
 
48
49
  /** Metadata source for everything Clio's pane layer writes. */
49
- const METADATA_SOURCE = "clio:mux";
50
+ const METADATA_SOURCE = "clio-coder:mux";
50
51
  /** Agent-authority source for Clio's own hosting pane, per SA-3. */
51
- const SELF_AGENT_SOURCE = "clio:coder";
52
+ const SELF_AGENT_SOURCE = "clio-coder:coder";
52
53
  /** Token every Clio-created pane carries so orphans are findable. */
53
- const OWNER_TOKEN_KEY = "clio_owner";
54
- const OWNER_TOKEN_VALUE = "clio:mux";
54
+ const OWNER_TOKEN_KEY = "clio_coder_owner";
55
+ const OWNER_TOKEN_VALUE = "clio-coder:mux";
56
+ /** Old-host cleanup bridge; ownership readers retain this indefinitely. */
57
+ const LEGACY_OWNER_TOKEN_KEY = "clio_owner";
58
+ const LEGACY_OWNER_TOKEN_VALUE = "clio:mux";
55
59
  /** herdr caps a metadata token value at 80 characters. */
56
60
  const TOKEN_VALUE_MAX = 80;
57
61
  /** How long `available()` stays false after a transport failure before probing again. */
@@ -71,6 +75,13 @@ export interface MuxOpenUtilityPaneRequest {
71
75
  * such as Yazi reopen `/dev/tty` for their interface when stdout is not a tty.
72
76
  */
73
77
  stdoutPath?: string;
78
+ /**
79
+ * Managed placement: the pane becomes this slot's dock, split from the
80
+ * anchor with the slot's direction and sized to `share` of the axis
81
+ * (clamped, cell-floored). Requires the layout tier (protocol 17); below
82
+ * the floor the request degrades to a plain split and `direction` applies.
83
+ */
84
+ dock?: { slot: DockSlot; share?: number };
74
85
  }
75
86
 
76
87
  export interface MuxNotifyRequest {
@@ -99,7 +110,19 @@ export interface MuxContract extends DomainContract {
99
110
  * This is what stops a restarted session's Enter-in-the-workers-view from
100
111
  * opening a second watch pane beside the surviving one.
101
112
  */
102
- adoptPane(request: { purpose: MuxPanePurpose; label: string }): Promise<MuxPaneRef | null>;
113
+ adoptPane(request: { purpose: MuxPanePurpose; label: string; dock?: DockSlot }): Promise<MuxPaneRef | null>;
114
+ /**
115
+ * Focus one Clio-created pane, switching the user's view to it (pane.focus
116
+ * moves the focused tab too). Explicit-request only; refuses foreign panes
117
+ * and servers below the pane.focus floor.
118
+ */
119
+ focusPane(paneId: string): Promise<boolean>;
120
+ /** Zoom one Clio-created pane on, off, or toggled. Zooming steals focus; same rule as focusPane. */
121
+ zoomPane(paneId: string, mode: "on" | "off" | "toggle"): Promise<boolean>;
122
+ /** Set a dock's share of its axis. False when the slot has no dock or the tier is absent. */
123
+ resizeDock(slot: DockSlot, share: number): Promise<boolean>;
124
+ /** Live dock geometry states, for `/panes` status. */
125
+ docks(): ReadonlyArray<DockState>;
103
126
  notify(request: MuxNotifyRequest): Promise<void>;
104
127
  /** Optional compete storage route. Protocol-gated with a native Git fallback at the caller. */
105
128
  worktreeCreate(request: MuxWorktreeCreateRequest): Promise<MuxWorktreeCreatedResult | null>;
@@ -147,11 +170,25 @@ function token(value: string | undefined | null): string | null {
147
170
  return value.length > TOKEN_VALUE_MAX ? value.slice(0, TOKEN_VALUE_MAX) : value;
148
171
  }
149
172
 
173
+ function hasOwnedPaneToken(tokens: Readonly<Record<string, string>>): boolean {
174
+ return tokens[OWNER_TOKEN_KEY] === OWNER_TOKEN_VALUE || tokens[LEGACY_OWNER_TOKEN_KEY] === LEGACY_OWNER_TOKEN_VALUE;
175
+ }
176
+
150
177
  export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
151
178
  const { detection, client } = options;
152
179
  const log = options.log ?? ((): void => undefined);
153
180
  const now = options.now ?? Date.now;
154
181
  const registry = createPaneRegistry();
182
+ const anchorPaneId = detection.self.paneId;
183
+ /**
184
+ * The managed dock tier needs an anchor to split from and the protocol-17
185
+ * layout methods to converge with. Absent either, dock requests degrade to
186
+ * plain utility splits and the rest of the tier answers false/empty.
187
+ */
188
+ const docks: DockController | null =
189
+ client !== null && anchorPaneId !== null && muxSupportsMethod(detection.server, "layout.export")
190
+ ? createDockController({ client, anchorPaneId, log })
191
+ : null;
155
192
 
156
193
  let healthy = client !== null;
157
194
  let probeHealthAt = 0;
@@ -201,11 +238,30 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
201
238
  paneId,
202
239
  source: METADATA_SOURCE,
203
240
  ...(title === undefined ? {} : { title }),
204
- tokens: { [OWNER_TOKEN_KEY]: OWNER_TOKEN_VALUE, ...tokens },
241
+ tokens: {
242
+ [OWNER_TOKEN_KEY]: OWNER_TOKEN_VALUE,
243
+ [LEGACY_OWNER_TOKEN_KEY]: LEGACY_OWNER_TOKEN_VALUE,
244
+ ...tokens,
245
+ },
205
246
  });
206
247
  };
207
248
 
208
249
  const onEvent = (event: MuxEvent): void => {
250
+ if (event.kind === "layout.updated") {
251
+ docks?.noteLayoutUpdated(event.geometry);
252
+ return;
253
+ }
254
+ if (event.kind === "pane.moved") {
255
+ // herdr rewrites the pane id on a move; the registry and dock state
256
+ // follow it so ownership is not lost to the user reorganizing.
257
+ const held = registry.forget(event.previousPaneId);
258
+ if (held) {
259
+ registry.record({ ...held, ref: { paneId: event.paneId, tabId: event.tabId, workspaceId: event.workspaceId } });
260
+ docks?.notePaneMoved(event.previousPaneId, event.paneId, event.tabId);
261
+ }
262
+ return;
263
+ }
264
+ docks?.notePaneGone(event.paneId);
209
265
  const dropped = registry.forget(event.paneId);
210
266
  if (dropped) {
211
267
  log("debug", `mux pane ${event.paneId} left on ${event.kind}; dropped from the registry`);
@@ -231,38 +287,57 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
231
287
  },
232
288
 
233
289
  async openUtilityPane(request: MuxOpenUtilityPaneRequest): Promise<MuxPaneRef | null> {
290
+ // Idempotence for docks: a slot that already has a pane answers with it.
291
+ const dockSlot = request.dock?.slot;
292
+ if (dockSlot && docks) {
293
+ const existing = docks.stateFor(dockSlot);
294
+ const held = existing ? registry.byPaneId(existing.paneId) : null;
295
+ if (held) return held.ref;
296
+ }
234
297
  return await attempt(
235
298
  "openUtilityPane",
236
299
  async (live) => {
237
- const anchor = detection.self.paneId ?? (await live.paneCurrent()).paneId;
238
- const pane = await live.paneSplit({
239
- direction: request.direction ?? "right",
240
- targetPaneId: anchor,
241
- cwd: request.cwd,
242
- focus: false,
243
- ...(request.env ? { env: request.env } : {}),
244
- });
245
- const ref: MuxPaneRef = { paneId: pane.paneId, tabId: pane.tabId, workspaceId: pane.workspaceId };
300
+ let ref: MuxPaneRef;
301
+ if (request.dock && docks) {
302
+ const placed = await docks.open(request.dock.slot, {
303
+ ...(request.dock.share === undefined ? {} : { share: request.dock.share }),
304
+ cwd: request.cwd,
305
+ ...(request.env ? { env: request.env } : {}),
306
+ });
307
+ // A refusal (anchor too small) is an answer, not a failure.
308
+ if (placed === null) return null;
309
+ ref = placed;
310
+ } else {
311
+ const anchor = anchorPaneId ?? (await live.paneCurrent()).paneId;
312
+ const pane = await live.paneSplit({
313
+ direction: request.direction ?? "right",
314
+ targetPaneId: anchor,
315
+ cwd: request.cwd,
316
+ focus: false,
317
+ ...(request.env ? { env: request.env } : {}),
318
+ });
319
+ ref = { paneId: pane.paneId, tabId: pane.tabId, workspaceId: pane.workspaceId };
320
+ }
246
321
  const purpose = request.purpose ?? "utility";
247
322
  registry.record(paneRecord(ref, { purpose, label: request.label, openedAt: now() }));
248
323
  const title = request.title;
249
324
  const titleSupported = title !== undefined && muxSupportsMethod(detection.server, "pane.rename");
250
325
  if (titleSupported) {
251
326
  try {
252
- await live.paneRename(pane.paneId, title);
327
+ await live.paneRename(ref.paneId, title);
253
328
  } catch {
254
329
  // Presentation is optional. A server that advertises the floor but
255
330
  // lacks or refuses rename must not strand an otherwise healthy pane.
256
331
  }
257
332
  }
258
333
  // The `role` token is what adoptPane finds again after a restart.
259
- await tagOwner(live, pane.paneId, { role: token(purpose) }, titleSupported ? title : undefined);
334
+ await tagOwner(live, ref.paneId, { role: token(purpose) }, titleSupported ? title : undefined);
260
335
  if (request.argv.length > 0) {
261
336
  // herdr has no argv parameter on pane.split, so the command goes in
262
337
  // through the pane's shell. `exec` replaces the shell so the pane
263
338
  // exits with the program and emits pane.exited for reconciliation.
264
339
  const redirect = request.stdoutPath ? ` > ${shellQuote([request.stdoutPath])}` : "";
265
- await live.paneSendText(pane.paneId, `exec ${shellQuote(request.argv)}${redirect}\n`);
340
+ await live.paneSendText(ref.paneId, `exec ${shellQuote(request.argv)}${redirect}\n`);
266
341
  }
267
342
  return ref;
268
343
  },
@@ -280,13 +355,18 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
280
355
  async (live) => {
281
356
  await live.paneClose(paneId);
282
357
  registry.forget(paneId);
358
+ // Dock state must fall with the registry entry, not ride on the async
359
+ // pane.closed push: if the event subscription failed at start(), no
360
+ // push ever comes, and a slot pointing at a dead pane makes every
361
+ // later dock open return the corpse.
362
+ docks?.notePaneGone(paneId);
283
363
  return true;
284
364
  },
285
365
  false,
286
366
  );
287
367
  },
288
368
 
289
- async adoptPane(request: { purpose: MuxPanePurpose; label: string }): Promise<MuxPaneRef | null> {
369
+ async adoptPane(request: { purpose: MuxPanePurpose; label: string; dock?: DockSlot }): Promise<MuxPaneRef | null> {
290
370
  const existing = registry.byPurpose(request.purpose);
291
371
  if (existing) return existing.ref;
292
372
  return await attempt(
@@ -294,7 +374,7 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
294
374
  async (live) => {
295
375
  const snapshot = await live.snapshot();
296
376
  for (const pane of snapshot.panes) {
297
- if (pane.tokens[OWNER_TOKEN_KEY] !== OWNER_TOKEN_VALUE) continue;
377
+ if (!hasOwnedPaneToken(pane.tokens)) continue;
298
378
  if (pane.tokens.role !== request.purpose) continue;
299
379
  if (registry.owns(pane.paneId)) continue;
300
380
  // Another workspace's pane belongs to another session's screen;
@@ -304,6 +384,9 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
304
384
  registry.record(
305
385
  paneRecord(ref, { purpose: request.purpose, label: request.label, openedAt: now(), adopted: true }),
306
386
  );
387
+ // Crash recovery for a dock: the surviving pane takes the slot back so
388
+ // geometry management resumes instead of a second dock opening.
389
+ if (request.dock && docks) docks.adopt(request.dock, ref);
307
390
  log("info", `mux adopted a ${request.purpose} pane left open by a previous session`);
308
391
  return ref;
309
392
  }
@@ -313,6 +396,44 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
313
396
  );
314
397
  },
315
398
 
399
+ async focusPane(paneId: string): Promise<boolean> {
400
+ // Ownership first: focusing the user's own panes stays off the table
401
+ // even though the wire allows it.
402
+ if (!registry.owns(paneId)) return false;
403
+ if (!muxSupportsMethod(detection.server, "pane.focus")) return false;
404
+ return await attempt(
405
+ "focusPane",
406
+ async (live) => {
407
+ await live.paneFocus(paneId);
408
+ return true;
409
+ },
410
+ false,
411
+ );
412
+ },
413
+
414
+ async zoomPane(paneId: string, mode: "on" | "off" | "toggle"): Promise<boolean> {
415
+ if (!registry.owns(paneId)) return false;
416
+ if (!muxSupportsMethod(detection.server, "pane.zoom")) return false;
417
+ return await attempt(
418
+ "zoomPane",
419
+ async (live) => {
420
+ const result = await live.paneZoom(paneId, mode);
421
+ // A toggle always changes state; on/off report false when already there.
422
+ return result.changed;
423
+ },
424
+ false,
425
+ );
426
+ },
427
+
428
+ async resizeDock(slot: DockSlot, share: number): Promise<boolean> {
429
+ if (!docks) return false;
430
+ return await attempt("resizeDock", () => docks.resize(slot, share), false);
431
+ },
432
+
433
+ docks(): ReadonlyArray<DockState> {
434
+ return docks?.states() ?? [];
435
+ },
436
+
316
437
  async notify(request: MuxNotifyRequest): Promise<void> {
317
438
  if (!muxSupportsMethod(detection.server, "notification.show")) {
318
439
  log("debug", `mux notify skipped, server protocol is below the notification.show floor: ${request.title}`);
@@ -404,9 +525,17 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
404
525
  subscription?.close();
405
526
  subscription = null;
406
527
  paneGoneHandlers.clear();
407
- // Pane ownership is server-side and intentionally survives process
408
- // shutdown. Keep the local snapshot too so the contract itself proves
409
- // shutdown tears down sockets, never panes.
528
+ // Docks close with the session: a native application takes its windows
529
+ // with it. A crash never reaches this method, which is exactly what
530
+ // leaves dock panes behind for the next session's adoption path.
531
+ // Unmanaged utility panes are the operator's and stay open.
532
+ if (client && docks) {
533
+ for (const paneId of docks.paneIds()) {
534
+ await client.paneClose(paneId).catch(() => undefined);
535
+ registry.forget(paneId);
536
+ }
537
+ docks.clear();
538
+ }
410
539
  if (client) await client.close().catch(() => undefined);
411
540
  },
412
541
  };
@@ -414,12 +543,15 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
414
543
  const start = async (): Promise<void> => {
415
544
  if (detection.mode === "none" || client === null) return;
416
545
  try {
417
- subscription = await client.subscribe(["pane.closed", "pane.exited"], onEvent, {
546
+ const kinds: ("pane.closed" | "pane.exited" | "pane.moved" | "layout.updated")[] = ["pane.closed", "pane.exited"];
547
+ if (docks) kinds.push("pane.moved", "layout.updated");
548
+ subscription = await client.subscribe(kinds, onEvent, {
418
549
  onResync: (snapshot) => {
419
550
  // A reconnect means events were missed. The snapshot is the authority
420
551
  // on which panes still exist, so anything Clio thinks it owns that is
421
552
  // absent from it is gone.
422
553
  const dropped = registry.reconcile(snapshot.panes.map((pane) => pane.paneId));
554
+ for (const record of dropped) docks?.notePaneGone(record.ref.paneId);
423
555
  if (dropped.length > 0) {
424
556
  log("info", `mux reconciled ${dropped.length} pane(s) closed while the socket was down`);
425
557
  }