@iowarp/clio-coder 0.3.1 → 0.3.3

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 (601) hide show
  1. package/CHANGELOG.md +233 -373
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +284 -613
  4. package/dist/{acp-FPR54DGL.js → acp-P2AQILE2.js} +43 -53
  5. package/dist/{agents-OGPIHPJH.js → agents-72W3BI7I.js} +43 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-IC3K6NIZ.js → auth-5TWEIYDN.js} +20 -12
  8. package/dist/chunk-2DJ2KNFG.js +2095 -0
  9. package/dist/{chunk-IS3ONKU3.js → chunk-2IR2NMPA.js} +6 -4
  10. package/dist/chunk-2SFS6XQE.js +122 -0
  11. package/dist/{chunk-PV4JUBVJ.js → chunk-2TLUCQVG.js} +40 -21
  12. package/dist/chunk-2VTFPG5O.js +48 -0
  13. package/dist/chunk-4BJ5BYCE.js +61 -0
  14. package/dist/{chunk-474KN5II.js → chunk-4BPJXDWC.js} +111 -181
  15. package/dist/chunk-4VP4KH3K.js +962 -0
  16. package/dist/chunk-4XUGQOHA.js +797 -0
  17. package/dist/chunk-4ZG3XFUR.js +77 -0
  18. package/dist/chunk-5B2AEOW5.js +5407 -0
  19. package/dist/{chunk-K2ITRMHZ.js → chunk-5TSRNF4G.js} +6 -138
  20. package/dist/{chunk-4QKXUHSR.js → chunk-5UFT4SUX.js} +70 -20
  21. package/dist/{chunk-OLBBMFRD.js → chunk-5UUP6MWO.js} +24 -62
  22. package/dist/chunk-65DEGPJ6.js +52 -0
  23. package/dist/chunk-6EJMN2Y3.js +17 -0
  24. package/dist/chunk-6N5PTWMY.js +136 -0
  25. package/dist/chunk-6SGHMWE3.js +277 -0
  26. package/dist/chunk-6XLNIQDB.js +27 -0
  27. package/dist/chunk-7CR24IG7.js +242 -0
  28. package/dist/chunk-7MNJORFF.js +22 -0
  29. package/dist/{chunk-KY56HMHH.js → chunk-A3CYT5EX.js} +125 -31
  30. package/dist/chunk-AGYYIBLL.js +1069 -0
  31. package/dist/{chunk-GB6QRBXN.js → chunk-APJ265NV.js} +54 -1187
  32. package/dist/{chunk-673JJUWJ.js → chunk-BMEMKKIT.js} +2 -2
  33. package/dist/chunk-CBCAPZAA.js +229 -0
  34. package/dist/chunk-CMZWFGD2.js +352 -0
  35. package/dist/chunk-COU2UHX6.js +400 -0
  36. package/dist/chunk-DSELYM6W.js +1077 -0
  37. package/dist/chunk-DUYJ5IO6.js +644 -0
  38. package/dist/chunk-ECH6PKUQ.js +39 -0
  39. package/dist/chunk-ED4KHGC3.js +143 -0
  40. package/dist/chunk-EKMEHE4H.js +340 -0
  41. package/dist/chunk-FCSXB6T2.js +338 -0
  42. package/dist/chunk-FJ3H4MN5.js +48 -0
  43. package/dist/{chunk-RPTR2H26.js → chunk-FNTMWMX5.js} +21 -15
  44. package/dist/chunk-FQ4SKYE4.js +29 -0
  45. package/dist/chunk-G4BMMOKF.js +182 -0
  46. package/dist/{chunk-ZPY3JZ5E.js → chunk-GGXXDWE4.js} +183 -1233
  47. package/dist/chunk-HC4CLZ2Y.js +68 -0
  48. package/dist/{chunk-LU4TK2PR.js → chunk-HFSBBKSQ.js} +5 -56
  49. package/dist/{chunk-PIUMUEMV.js → chunk-HKIYEGME.js} +10 -6
  50. package/dist/chunk-I4HZDVNP.js +73 -0
  51. package/dist/chunk-IKCO5N3L.js +162 -0
  52. package/dist/chunk-IR4CFBFN.js +56 -0
  53. package/dist/{chunk-R5KLMSBV.js → chunk-J5Q24KAG.js} +2 -2
  54. package/dist/chunk-J7CWMCQD.js +255 -0
  55. package/dist/{chunk-K5XEMXTI.js → chunk-JVCV3ICN.js} +1 -1
  56. package/dist/chunk-KZWTDYJF.js +217 -0
  57. package/dist/chunk-LBMZMYH2.js +285 -0
  58. package/dist/{chunk-G34LV2PF.js → chunk-LM5TQCJZ.js} +84 -170
  59. package/dist/chunk-LW6DSM3M.js +5135 -0
  60. package/dist/chunk-LWLEKMDQ.js +3482 -0
  61. package/dist/{chunk-H6F6BYOH.js → chunk-LZSJBIVT.js} +7003 -7434
  62. package/dist/{chunk-HQQID6OA.js → chunk-M6SHUN7Q.js} +5 -5
  63. package/dist/{chunk-FST4FYJB.js → chunk-MFFY33HR.js} +99 -140
  64. package/dist/{chunk-BSU2YIWB.js → chunk-MVVUPGPW.js} +131 -136
  65. package/dist/chunk-OAO4GE4M.js +619 -0
  66. package/dist/{chunk-Q3RUPKEJ.js → chunk-OC7FIQPC.js} +58 -189
  67. package/dist/chunk-OKGUZO2U.js +34 -0
  68. package/dist/{chunk-GAEBEQVI.js → chunk-OOJYHWRB.js} +32 -346
  69. package/dist/{chunk-Q5WJOSJ7.js → chunk-OQ33BKR3.js} +2 -1
  70. package/dist/chunk-OQE5J4C6.js +73 -0
  71. package/dist/{chunk-KKNLWXI6.js → chunk-ORBHGJC5.js} +8 -8
  72. package/dist/{chunk-MAR7Y6HW.js → chunk-PAJK6MAQ.js} +23 -16
  73. package/dist/{chunk-M5T5VO65.js → chunk-PIWWS5BL.js} +837 -635
  74. package/dist/chunk-POHLU5DW.js +1186 -0
  75. package/dist/chunk-QKMUKYO7.js +4961 -0
  76. package/dist/chunk-SRDMMSEP.js +16405 -0
  77. package/dist/chunk-SST6Z5JA.js +80 -0
  78. package/dist/chunk-STBPMHSX.js +2456 -0
  79. package/dist/chunk-T6YILFSB.js +80 -0
  80. package/dist/chunk-TZK7PACC.js +174 -0
  81. package/dist/chunk-TZTZS7QK.js +227 -0
  82. package/dist/{chunk-ASND7OZK.js → chunk-UFIIWP2H.js} +13 -13
  83. package/dist/chunk-UOV2BYIW.js +107 -0
  84. package/dist/{chunk-PFEFKVGL.js → chunk-V6RTAOC2.js} +13 -11
  85. package/dist/chunk-VAKQQHWR.js +434 -0
  86. package/dist/chunk-VG7TBQIY.js +128 -0
  87. package/dist/chunk-VJWL6YS5.js +244 -0
  88. package/dist/{chunk-EYOKLTMF.js → chunk-VPAYEGVX.js} +17 -3
  89. package/dist/chunk-WEH5XRJQ.js +32 -0
  90. package/dist/chunk-X4RCMKVQ.js +641 -0
  91. package/dist/{chunk-TEKV33Q5.js → chunk-X6IAEBZR.js} +65 -33
  92. package/dist/chunk-XBXAASKX.js +18 -0
  93. package/dist/chunk-XN3L4EYL.js +46 -0
  94. package/dist/{chunk-RDLVBZEO.js → chunk-YCWGATWI.js} +6 -4
  95. package/dist/chunk-YHZX5GEU.js +193 -0
  96. package/dist/chunk-YXLYO42X.js +91 -0
  97. package/dist/{chunk-NMOX6HFD.js → chunk-ZDOOVTXZ.js} +29 -77
  98. package/dist/chunk-ZI647VB5.js +37 -0
  99. package/dist/{chunk-C4PTHK7P.js → chunk-ZWLZP4ZT.js} +5 -5
  100. package/dist/chunk-ZWMF7253.js +1882 -0
  101. package/dist/cli/index.js +62 -54
  102. package/dist/clio-JOU4FXVA.js +25 -0
  103. package/dist/code-nav-7AX6FYE6.js +600 -0
  104. package/dist/codewiki/build-worker.js +66 -0
  105. package/dist/compile-cache-CVJMMODC.js +18 -0
  106. package/dist/{components-DMAOEKFB.js → components-KELWS457.js} +11 -6
  107. package/dist/{config-IRUQ7SE4.js → config-XCDVKR23.js} +92 -55
  108. package/dist/configure-4GAP54ZW.js +42 -0
  109. package/dist/{context-5RADCKTR.js → context-4UOGGLQ5.js} +71 -35
  110. package/dist/context-5VKGUVJJ.js +866 -0
  111. package/dist/{context-3KWFLHJG.js → context-77FM5DV5.js} +15 -13
  112. package/dist/{context-clear-7TSNPAAI.js → context-clear-XXJRLCJJ.js} +54 -28
  113. package/dist/{context-index-W4RLWOQH.js → context-index-BZ4UYMTC.js} +30 -24
  114. package/dist/dispatch-runner-QPRDDBDX.js +1997 -0
  115. package/dist/{docs-5AWSPS37.js → docs-2C2LTVT2.js} +23 -10
  116. package/dist/{doctor-UC5NAJYQ.js → doctor-HR46URBJ.js} +27 -17
  117. package/dist/{eval-U6TJHRLX.js → eval-XSSNATB4.js} +29 -16
  118. package/dist/{evidence-YEGUW4L3.js → evidence-6HG2PY2B.js} +46 -26
  119. package/dist/{evolve-TXARCTPG.js → evolve-K7YU3NCY.js} +45 -25
  120. package/dist/{extensions-OZFJ3A3G.js → extensions-QVDOHDGJ.js} +16 -7
  121. package/dist/{fleet-6G3DHNYE.js → fleet-VY3HHKN6.js} +163 -54
  122. package/dist/{fleet-preflight-DSNT37JK.js → fleet-preflight-DDN536IT.js} +7 -4
  123. package/dist/{init-KZ5QTF6M.js → init-JYGXI3FK.js} +69 -32
  124. package/dist/{memory-73ESV5YC.js → memory-WFZMGYHX.js} +48 -27
  125. package/dist/{models-A4PVNWJK.js → models-I5QWSEOM.js} +39 -25
  126. package/dist/monitor-GE4ID3IA.js +661 -0
  127. package/dist/{chunk-FCIH3BIZ.js → orchestrator-EM5MC3HM.js} +15979 -12407
  128. package/dist/{paths-C4H6IV77.js → paths-UXLN5YYZ.js} +10 -5
  129. package/dist/{preload-6WVMHX3A.js → preload-P6DGH2PZ.js} +2 -2
  130. package/dist/{reset-BGW6OGMV.js → reset-L2FQEE3E.js} +16 -10
  131. package/dist/{run-YTPEYQOH.js → run-ZU3QMZPZ.js} +101 -61
  132. package/dist/{share-YIFFV4NQ.js → share-S5BZQC5I.js} +15 -8
  133. package/dist/{skills-2V6RA3OQ.js → skills-X5VXCRNQ.js} +34 -14
  134. package/dist/{skills-eval-S2TVJO4F.js → skills-eval-WKIHWTHR.js} +70 -34
  135. package/dist/steer-GGWFUJUD.js +77 -0
  136. package/dist/{targets-TYXLPB23.js → targets-SNCPI2NR.js} +43 -27
  137. package/dist/terminal-lease-BNAHVHBS.js +395 -0
  138. package/dist/{trace-GGOJ6Q6Z.js → trace-PNCASAXC.js} +41 -16
  139. package/dist/{chunk-N6F52NLF.js → tree-sitter-HGKH6LG4.js} +28 -2306
  140. package/dist/{uninstall-LLLT4F4W.js → uninstall-FZCQCDKC.js} +10 -5
  141. package/dist/{upgrade-33G2LMM5.js → upgrade-JQHHPQ4K.js} +45 -25
  142. package/dist/{usage-ZAFSXKKG.js → usage-OR4O5SMZ.js} +62 -31
  143. package/dist/verify-375KUB3Y.js +716 -0
  144. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  145. package/dist/{wiki-generate-NUQCVOQ3.js → wiki-generate-UEXP2ARI.js} +74 -34
  146. package/dist/worker/entry.js +221 -36
  147. package/dist/workspace-G4ZWUIPR.js +22 -0
  148. package/docs/README.md +22 -17
  149. package/docs/acp.md +168 -16
  150. package/docs/alcf-provider.md +1 -1
  151. package/docs/architecture.md +136 -7
  152. package/docs/artifact-versions.md +1 -1
  153. package/docs/built-in-agents.md +1 -1
  154. package/docs/capacity-and-scheduling.md +1 -1
  155. package/docs/commands-and-modes.md +114 -71
  156. package/docs/config-knobs-audit.md +1 -3
  157. package/docs/configuration-and-targets.md +174 -46
  158. package/docs/context-engine.md +29 -6
  159. package/docs/development-pipeline.md +26 -1
  160. package/docs/dispatch-architecture-rationale.md +1 -1
  161. package/docs/documentation-coverage.md +2 -2
  162. package/docs/documentation-guide.md +1 -1
  163. package/docs/environment-variables.md +13 -5
  164. package/docs/eval-runner.md +1 -1
  165. package/docs/evals-internal.md +1 -1
  166. package/docs/evidence-and-memory.md +6 -2
  167. package/docs/evolution.md +2 -2
  168. package/docs/exit-codes-and-output.md +15 -9
  169. package/docs/extensions-and-sharing.md +9 -9
  170. package/docs/fleet-dispatch.md +7 -5
  171. package/docs/git-commit-provenance.md +120 -0
  172. package/docs/glossary.md +1 -1
  173. package/docs/installation-and-lifecycle.md +34 -27
  174. package/docs/middleware-and-components.md +1 -1
  175. package/docs/model-catalog.md +45 -14
  176. package/docs/observability.md +8 -5
  177. package/docs/performance-methodology.md +491 -0
  178. package/docs/pi-boundary.md +72 -0
  179. package/docs/proactive-memory.md +3 -3
  180. package/docs/prompt-envelope-and-tools.md +24 -3
  181. package/docs/provider-adapter-cookbook.md +57 -4
  182. package/docs/release-cut-checklist.md +129 -115
  183. package/docs/safety-model.md +9 -5
  184. package/docs/scientific-validation.md +3 -3
  185. package/docs/session-lifecycle.md +55 -12
  186. package/docs/skills-marketplace.md +12 -8
  187. package/docs/time-conventions.md +1 -1
  188. package/docs/tool-usage.md +3 -3
  189. package/docs/trace-store.md +1 -1
  190. package/docs/troubleshooting.md +10 -7
  191. package/docs/tui-design.md +47 -10
  192. package/docs/worker-dispatch-mechanics.md +1 -1
  193. package/package.json +19 -22
  194. package/skills/coding/ast-grep/SKILL.md +136 -0
  195. package/skills/coding/ast-grep/evals.md +56 -0
  196. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  197. package/skills/coding/coding-standards/SKILL.md +113 -0
  198. package/skills/coding/coding-standards/evals.md +34 -0
  199. package/skills/coding/prototype/SKILL.md +86 -0
  200. package/skills/coding/prototype/evals.md +42 -0
  201. package/skills/coding/prototype/references/LOGIC.md +67 -0
  202. package/skills/coding/prototype/references/UI.md +112 -0
  203. package/skills/coding/tdd/SKILL.md +101 -0
  204. package/skills/coding/tdd/evals.md +41 -0
  205. package/skills/coding/tdd/references/mocking.md +59 -0
  206. package/skills/coding/tdd/references/tests.md +77 -0
  207. package/skills/context/context-handoff/SKILL.md +126 -0
  208. package/skills/context/context-handoff/evals.md +57 -0
  209. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  210. package/skills/context/context-prime/SKILL.md +95 -0
  211. package/skills/context/context-prime/evals.md +54 -0
  212. package/skills/meta/clio-dev/SKILL.md +91 -0
  213. package/skills/meta/clio-dev/evals.md +45 -0
  214. package/skills/meta/clio-test/SKILL.md +130 -0
  215. package/skills/meta/clio-test/evals.md +43 -0
  216. package/skills/meta/clio-test/references/harness.md +97 -0
  217. package/skills/meta/clio-test/references/test-map.md +59 -0
  218. package/skills/meta/credentials/SKILL.md +125 -0
  219. package/skills/meta/credentials/evals.md +104 -0
  220. package/skills/meta/find-skills/SKILL.md +72 -0
  221. package/skills/meta/find-skills/evals.md +47 -0
  222. package/skills/meta/herdr/SKILL.md +127 -0
  223. package/skills/meta/herdr/evals.md +38 -0
  224. package/skills/meta/skill-craft/SKILL.md +102 -0
  225. package/skills/meta/skill-craft/evals.md +41 -0
  226. package/skills/planning/architecture/SKILL.md +129 -0
  227. package/skills/planning/architecture/evals.md +36 -0
  228. package/skills/planning/backlog/SKILL.md +90 -0
  229. package/skills/planning/backlog/evals.md +43 -0
  230. package/skills/planning/prd/SKILL.md +82 -0
  231. package/skills/planning/prd/evals.md +49 -0
  232. package/skills/planning/product-intent/SKILL.md +112 -0
  233. package/skills/planning/product-intent/evals.md +36 -0
  234. package/skills/planning/tech-spec/SKILL.md +115 -0
  235. package/skills/planning/tech-spec/evals.md +47 -0
  236. package/skills/registry.yaml +136 -0
  237. package/skills/research/arxiv-literature/SKILL.md +104 -0
  238. package/skills/research/arxiv-literature/evals.md +58 -0
  239. package/skills/research/experiment-protocol/SKILL.md +122 -0
  240. package/skills/research/experiment-protocol/evals.md +91 -0
  241. package/skills/research/scientific-debugging/SKILL.md +119 -0
  242. package/skills/research/scientific-debugging/evals.md +138 -0
  243. package/skills/research/scientific-modernization/SKILL.md +138 -0
  244. package/skills/research/scientific-modernization/evals.md +84 -0
  245. package/skills/workflow/design-council/SKILL.md +139 -0
  246. package/skills/workflow/design-council/evals.md +97 -0
  247. package/skills/workflow/grill-me/SKILL.md +186 -0
  248. package/skills/workflow/grill-me/evals.md +78 -0
  249. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  250. package/skills/workflow/workflow-distiller/evals.md +107 -0
  251. package/src/cli/acp.ts +31 -4
  252. package/src/cli/clio.ts +68 -6
  253. package/src/cli/config-inspect.ts +28 -22
  254. package/src/cli/configure.ts +47 -9
  255. package/src/cli/context-clear.ts +2 -2
  256. package/src/cli/context-index.ts +21 -23
  257. package/src/cli/context.ts +13 -8
  258. package/src/cli/default-target.ts +9 -17
  259. package/src/cli/docs.ts +11 -5
  260. package/src/cli/evidence.ts +4 -1
  261. package/src/cli/extensions.ts +10 -1
  262. package/src/cli/fleet.ts +47 -6
  263. package/src/cli/index.ts +55 -26
  264. package/src/cli/memory.ts +3 -1
  265. package/src/cli/models.ts +1 -1
  266. package/src/cli/modes/json-stream.ts +37 -1
  267. package/src/cli/modes/print.ts +24 -9
  268. package/src/cli/run.ts +2 -2
  269. package/src/cli/skills-eval.ts +23 -8
  270. package/src/cli/skills.ts +19 -4
  271. package/src/cli/targets.ts +4 -0
  272. package/src/cli/text-layout.ts +15 -5
  273. package/src/cli/trace.ts +62 -14
  274. package/src/cli/upgrade.ts +18 -2
  275. package/src/cli/usage.ts +10 -3
  276. package/src/cli/wiki-generate.ts +2 -1
  277. package/src/core/agent-environment.ts +7 -0
  278. package/src/core/bash-exec.ts +72 -1
  279. package/src/core/boot-trace.ts +9 -4
  280. package/src/core/bus-events.ts +20 -4
  281. package/src/core/commit-attribution.ts +157 -0
  282. package/src/core/compile-cache.ts +159 -0
  283. package/src/core/config.ts +131 -2
  284. package/src/core/defaults.ts +39 -5
  285. package/src/core/domain-loader.ts +12 -5
  286. package/src/core/git-commit-attribution.ts +387 -0
  287. package/src/core/incomplete-installation.ts +45 -0
  288. package/src/core/response-schema.ts +1 -1
  289. package/src/core/safe-exec.ts +13 -1
  290. package/src/core/settings-layers.ts +155 -21
  291. package/src/core/skill-activation.ts +1 -1
  292. package/src/core/startup-timer.ts +3 -3
  293. package/src/core/state-file-lock.ts +13 -1
  294. package/src/core/termination.ts +78 -5
  295. package/src/domains/config/classify.ts +15 -3
  296. package/src/domains/config/extension.ts +19 -13
  297. package/src/domains/config/index.ts +10 -0
  298. package/src/domains/config/keybindings.ts +45 -9
  299. package/src/domains/context/bootstrap-prompt.ts +1 -1
  300. package/src/domains/context/bootstrap.ts +111 -18
  301. package/src/domains/context/clear.ts +16 -11
  302. package/src/domains/context/clio-md.ts +111 -9
  303. package/src/domains/context/codewiki/artifact.ts +400 -0
  304. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  305. package/src/domains/context/codewiki/build-worker.ts +54 -0
  306. package/src/domains/context/codewiki/coordinator.ts +182 -0
  307. package/src/domains/context/codewiki/indexer.ts +59 -144
  308. package/src/domains/context/codewiki/paths.ts +67 -0
  309. package/src/domains/context/codewiki/schema.ts +80 -0
  310. package/src/domains/context/codewiki/tree-sitter.ts +1 -1
  311. package/src/domains/context/contract.ts +11 -5
  312. package/src/domains/context/extension.ts +94 -143
  313. package/src/domains/context/fingerprint.ts +3 -1
  314. package/src/domains/context/index.ts +12 -22
  315. package/src/domains/context/project-metadata.ts +19 -0
  316. package/src/domains/context/prompt-context.ts +9 -10
  317. package/src/domains/context/refresh.ts +29 -21
  318. package/src/domains/context/runtime.ts +17 -0
  319. package/src/domains/context/wiki/generate.ts +39 -34
  320. package/src/domains/context/wiki/plan.ts +1 -1
  321. package/src/domains/context/wiki/prompts.ts +21 -8
  322. package/src/domains/dispatch/code-step.ts +20 -1
  323. package/src/domains/dispatch/extension.ts +158 -21
  324. package/src/domains/dispatch/failure-classification.ts +6 -0
  325. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  326. package/src/domains/dispatch/orphan-recovery.ts +50 -8
  327. package/src/domains/dispatch/receipt-integrity.ts +5 -0
  328. package/src/domains/dispatch/state.ts +31 -6
  329. package/src/domains/dispatch/transport.ts +2 -1
  330. package/src/domains/dispatch/types.ts +14 -0
  331. package/src/domains/dispatch/worker-spawn.ts +21 -2
  332. package/src/domains/eval/metrics/context.ts +1 -1
  333. package/src/domains/eval/types.ts +0 -1
  334. package/src/domains/evidence/build.ts +41 -1
  335. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  336. package/src/domains/lifecycle/migrations/index.ts +24 -4
  337. package/src/domains/middleware/hooks-io.ts +12 -0
  338. package/src/domains/middleware/skills-reminder.ts +30 -15
  339. package/src/domains/prompts/compiler.ts +142 -84
  340. package/src/domains/prompts/contract.ts +18 -2
  341. package/src/domains/prompts/extension.ts +39 -7
  342. package/src/domains/prompts/fragment-loader.ts +0 -1
  343. package/src/domains/prompts/fragments/identity/clio.md +2 -4
  344. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  345. package/src/domains/prompts/fragments/identity/self-awareness.md +1 -45
  346. package/src/domains/prompts/fragments/operating/contract.md +4 -50
  347. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  348. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  349. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  350. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  351. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  352. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  353. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  354. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  355. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  356. package/src/domains/prompts/preload.ts +3 -3
  357. package/src/domains/providers/auth/api-key.ts +1 -1
  358. package/src/domains/providers/auth/backend-file.ts +20 -10
  359. package/src/domains/providers/auth/backend-memory.ts +59 -4
  360. package/src/domains/providers/auth/boot-status.ts +65 -0
  361. package/src/domains/providers/auth/oauth.ts +2 -1
  362. package/src/domains/providers/auth/storage.ts +97 -38
  363. package/src/domains/providers/capabilities.ts +12 -4
  364. package/src/domains/providers/contract.ts +15 -4
  365. package/src/domains/providers/extension.ts +18 -6
  366. package/src/domains/providers/model-runtime-capabilities.ts +15 -4
  367. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +118 -35
  368. package/src/domains/providers/plugins.ts +5 -3
  369. package/src/domains/providers/probe/fingerprint.ts +25 -5
  370. package/src/domains/providers/registry.ts +31 -10
  371. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  372. package/src/domains/providers/runtimes/builtins.ts +2 -2
  373. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  374. package/src/domains/providers/runtimes/common/local-synth.ts +6 -7
  375. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  376. package/src/domains/providers/support.ts +6 -3
  377. package/src/domains/providers/types/local-model-quirks.ts +7 -9
  378. package/src/domains/providers/types/runtime-descriptor.ts +12 -1
  379. package/src/domains/providers/types/target-descriptor.ts +22 -0
  380. package/src/domains/resources/contract.ts +0 -1
  381. package/src/domains/resources/extension.ts +1 -3
  382. package/src/domains/resources/loader.ts +3 -4
  383. package/src/domains/resources/prompts/loader.ts +16 -2
  384. package/src/domains/resources/prompts/substitute.ts +1 -65
  385. package/src/domains/resources/skills/content-hash.ts +2 -0
  386. package/src/domains/resources/skills/install.ts +17 -0
  387. package/src/domains/resources/skills/loader.ts +17 -10
  388. package/src/domains/resources/skills/marketplace.ts +55 -9
  389. package/src/domains/safety/action-classifier.ts +4 -2
  390. package/src/domains/safety/audit.ts +8 -2
  391. package/src/domains/safety/extension.ts +1 -1
  392. package/src/domains/session/compaction/branch-summary.ts +3 -2
  393. package/src/domains/session/compaction/cut-point.ts +2 -1
  394. package/src/domains/session/compaction/tokens.ts +2 -1
  395. package/src/domains/session/context-ledger.ts +14 -0
  396. package/src/domains/session/contract.ts +15 -0
  397. package/src/domains/session/decision-board.ts +190 -0
  398. package/src/domains/session/entries.ts +66 -3
  399. package/src/domains/session/extension.ts +93 -12
  400. package/src/domains/session/retry.ts +10 -18
  401. package/src/domains/session/session-artifacts.ts +107 -0
  402. package/src/domains/session/task-board.ts +207 -13
  403. package/src/domains/session/tree/active-path.ts +44 -5
  404. package/src/domains/session/tree/fork.ts +26 -27
  405. package/src/domains/session/tree/preview.ts +2 -2
  406. package/src/domains/session/workspace/git-probe.ts +17 -11
  407. package/src/domains/user-tasks/store.ts +297 -0
  408. package/src/engine/acp/errors.ts +96 -0
  409. package/src/engine/acp/server.ts +1728 -146
  410. package/src/engine/acp/transport.ts +135 -14
  411. package/src/engine/acp/types.ts +26 -0
  412. package/src/engine/agent.ts +3 -3
  413. package/src/engine/ai.ts +32 -27
  414. package/src/engine/alcf-oauth.ts +26 -19
  415. package/src/engine/api-registry.ts +223 -0
  416. package/src/engine/apis/index.ts +3 -7
  417. package/src/engine/apis/llamacpp-residency.ts +49 -9
  418. package/src/engine/apis/lmstudio-residency.ts +5 -21
  419. package/src/engine/apis/lmstudio.ts +243 -0
  420. package/src/engine/apis/ollama-native.ts +24 -3
  421. package/src/engine/apis/openai-completions.ts +170 -91
  422. package/src/engine/apis/residency.ts +139 -3
  423. package/src/engine/apis/types.ts +16 -0
  424. package/src/engine/env-api-keys.ts +98 -0
  425. package/src/engine/gemma-channel-filter.ts +223 -0
  426. package/src/engine/instrumented-tui.ts +192 -0
  427. package/src/engine/messages.ts +14 -0
  428. package/src/engine/models.ts +42 -0
  429. package/src/engine/oauth.ts +16 -12
  430. package/src/engine/prompt-templates.ts +1 -0
  431. package/src/engine/provider-payload.ts +16 -59
  432. package/src/engine/strip-tokenizer-sentinels.ts +1 -1
  433. package/src/engine/truncate.ts +9 -0
  434. package/src/engine/tui.ts +17 -9
  435. package/src/engine/types.ts +3 -6
  436. package/src/engine/worker-runtime-capabilities.ts +5 -0
  437. package/src/engine/worker-runtime.ts +1 -1
  438. package/src/engine/worker-tools.ts +9 -4
  439. package/src/entry/boot-options.ts +50 -0
  440. package/src/entry/orchestrator.ts +288 -150
  441. package/src/interactive/application-controller.ts +89 -2
  442. package/src/interactive/chat-loop.ts +266 -41
  443. package/src/interactive/chat-panel.ts +715 -278
  444. package/src/interactive/chat-renderer.ts +306 -85
  445. package/src/interactive/clio-editor.ts +3 -8
  446. package/src/interactive/command-fallbacks.ts +2 -2
  447. package/src/interactive/context-overlay.ts +27 -1
  448. package/src/interactive/editor-submit.ts +253 -24
  449. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  450. package/src/interactive/export-html/index.ts +51 -0
  451. package/src/interactive/export-html/template.ts +45 -0
  452. package/src/interactive/export-html/tool-renderer.ts +54 -0
  453. package/src/interactive/footer/dashboard.ts +4 -0
  454. package/src/interactive/footer/notifications.ts +1 -1
  455. package/src/interactive/footer/widgets.ts +42 -22
  456. package/src/interactive/footer-panel.ts +8 -3
  457. package/src/interactive/format-time.ts +14 -2
  458. package/src/interactive/interactive-application.ts +203 -17
  459. package/src/interactive/interactive-event-projection.ts +18 -1
  460. package/src/interactive/interactive-input-runtime.ts +50 -4
  461. package/src/interactive/interactive-presentation.ts +151 -19
  462. package/src/interactive/interactive-shell.ts +268 -14
  463. package/src/interactive/interactive-slash-runtime.ts +184 -117
  464. package/src/interactive/interactive-tickers.ts +38 -7
  465. package/src/interactive/keybinding-manager.ts +1 -1
  466. package/src/interactive/layout.ts +40 -3
  467. package/src/interactive/overlay-frame.ts +1 -1
  468. package/src/interactive/overlay-general-openers.ts +58 -1
  469. package/src/interactive/overlay-key-routing.ts +3 -0
  470. package/src/interactive/overlay-lifecycle.ts +13 -0
  471. package/src/interactive/overlay-permission-lifecycle.ts +2 -1
  472. package/src/interactive/overlay-session-lifecycle.ts +69 -12
  473. package/src/interactive/overlays/ask-user.ts +146 -24
  474. package/src/interactive/overlays/decisions.ts +300 -0
  475. package/src/interactive/overlays/help-reference.ts +15 -10
  476. package/src/interactive/overlays/model-selector.ts +34 -16
  477. package/src/interactive/overlays/session-selector.ts +18 -0
  478. package/src/interactive/overlays/settings.ts +105 -17
  479. package/src/interactive/overlays/skills-hub.ts +4 -4
  480. package/src/interactive/overlays/tree-selector.ts +41 -6
  481. package/src/interactive/render-trace.ts +499 -90
  482. package/src/interactive/renderers/compaction-summary.ts +2 -2
  483. package/src/interactive/renderers/diff.ts +115 -104
  484. package/src/interactive/renderers/mermaid.ts +53 -0
  485. package/src/interactive/renderers/tool-execution.ts +516 -168
  486. package/src/interactive/renderers/worker-entry.ts +20 -4
  487. package/src/interactive/session-switch-settlement.ts +10 -0
  488. package/src/interactive/slash-autocomplete.ts +6 -114
  489. package/src/interactive/slash-commands.ts +135 -47
  490. package/src/interactive/slash-spec.ts +9 -38
  491. package/src/interactive/status/controller.ts +5 -1
  492. package/src/interactive/status/index.ts +12 -1
  493. package/src/interactive/status/reasoning.ts +87 -0
  494. package/src/interactive/status/summary.ts +13 -2
  495. package/src/interactive/stdout-backpressure.ts +99 -0
  496. package/src/interactive/stream-pacer.ts +530 -0
  497. package/src/interactive/stream-pacing-policy.ts +66 -0
  498. package/src/interactive/tasks-overlay.ts +368 -14
  499. package/src/interactive/terminal-lease.ts +485 -0
  500. package/src/interactive/theme/tokens.ts +1 -1
  501. package/src/interactive/transcript-detail.ts +120 -0
  502. package/src/interactive/turn-context.ts +4 -3
  503. package/src/interactive/turn-persistence.ts +30 -13
  504. package/src/interactive/turn-queues.ts +12 -0
  505. package/src/interactive/turn-recovery.ts +25 -8
  506. package/src/interactive/turn-runtime.ts +79 -12
  507. package/src/interactive/turn-state.ts +10 -0
  508. package/src/interactive/view/artifacts.ts +114 -4
  509. package/src/interactive/view/view-overlay.ts +3 -0
  510. package/src/interactive/welcome-dashboard.ts +17 -16
  511. package/src/interactive/worker-receipts.ts +52 -3
  512. package/src/interactive/worker-stream.ts +5 -1
  513. package/src/tools/agent-tools.ts +23 -3
  514. package/src/tools/artifact.ts +2 -2
  515. package/src/tools/ask-user.ts +23 -13
  516. package/src/tools/bash.ts +30 -2
  517. package/src/tools/bootstrap.ts +34 -431
  518. package/src/tools/builtin-tool-catalog.ts +271 -0
  519. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  520. package/src/tools/codewiki/code-nav.ts +8 -22
  521. package/src/tools/codewiki/shared.ts +41 -38
  522. package/src/tools/context/docs-engine.ts +14 -3
  523. package/src/tools/context/index.ts +107 -28
  524. package/src/tools/context/surface.ts +19 -0
  525. package/src/tools/core-bootstrap.ts +168 -0
  526. package/src/tools/credential-present.ts +5 -5
  527. package/src/tools/dispatch-admission.ts +533 -0
  528. package/src/tools/dispatch-background.ts +54 -0
  529. package/src/tools/dispatch-event-text.ts +6 -0
  530. package/src/tools/dispatch-plan.ts +9 -4
  531. package/src/tools/dispatch-run-events.ts +238 -0
  532. package/src/tools/dispatch-runner.ts +2370 -0
  533. package/src/tools/dispatch-scout-admission.ts +295 -0
  534. package/src/tools/dispatch-types.ts +77 -0
  535. package/src/tools/dispatch.ts +67 -3161
  536. package/src/tools/find.ts +4 -2
  537. package/src/tools/grep.ts +2 -2
  538. package/src/tools/lazy-tool.ts +60 -0
  539. package/src/tools/ledger.ts +3 -3
  540. package/src/tools/monitor-surface.ts +36 -0
  541. package/src/tools/monitor.ts +2 -32
  542. package/src/tools/observers.ts +2 -2
  543. package/src/tools/presentation.ts +107 -0
  544. package/src/tools/registry.ts +45 -27
  545. package/src/tools/safe-exec.ts +2 -2
  546. package/src/tools/steer-surface.ts +17 -0
  547. package/src/tools/steer.ts +2 -13
  548. package/src/tools/tasks.ts +108 -11
  549. package/src/tools/truncate.ts +25 -184
  550. package/src/tools/verify/frontend.ts +3 -1
  551. package/src/tools/verify/index.ts +3 -38
  552. package/src/tools/verify/surface.ts +46 -0
  553. package/src/tools/web-fetch-surface.ts +23 -0
  554. package/src/tools/web-fetch.ts +2 -20
  555. package/src/tools/write.ts +7 -2
  556. package/src/worker/entry.ts +39 -2
  557. package/src/worker/spec-contract.ts +26 -5
  558. package/dist/chunk-7SS2CTV2.js +0 -61361
  559. package/dist/chunk-DKGKUHFA.js +0 -924
  560. package/dist/chunk-GEP36Y4X.js +0 -12796
  561. package/dist/chunk-XYWBQRDM.js +0 -137
  562. package/dist/clio-BZVGEUFJ.js +0 -58
  563. package/dist/configure-S7S6F6CL.js +0 -32
  564. package/docs/html/agents_blueprint.html +0 -936
  565. package/docs/html/alcf_blueprint.html +0 -324
  566. package/docs/html/architecture_blueprint.html +0 -850
  567. package/docs/html/commands_blueprint.html +0 -939
  568. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  569. package/docs/html/configuration_blueprint.html +0 -1080
  570. package/docs/html/context_blueprint.html +0 -603
  571. package/docs/html/documentation_blueprint.html +0 -832
  572. package/docs/html/environment_blueprint.html +0 -404
  573. package/docs/html/eval_blueprint.html +0 -743
  574. package/docs/html/evals_internal_blueprint.html +0 -190
  575. package/docs/html/evolution_blueprint.html +0 -674
  576. package/docs/html/extensions_blueprint.html +0 -2065
  577. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  578. package/docs/html/index.html +0 -919
  579. package/docs/html/lifecycle_blueprint.html +0 -723
  580. package/docs/html/memory_blueprint.html +0 -699
  581. package/docs/html/middleware_blueprint.html +0 -664
  582. package/docs/html/models_blueprint.html +0 -2366
  583. package/docs/html/observability_blueprint.html +0 -683
  584. package/docs/html/provider_adapter_blueprint.html +0 -245
  585. package/docs/html/safety_blueprint.html +0 -1386
  586. package/docs/html/shared.css +0 -571
  587. package/docs/html/shared.js +0 -143
  588. package/docs/html/skills_blueprint.html +0 -671
  589. package/docs/html/soak_blueprint.html +0 -182
  590. package/docs/html/tool_usage_blueprint.html +0 -350
  591. package/docs/html/tools_blueprint.html +0 -2249
  592. package/docs/html/trace_blueprint.html +0 -235
  593. package/docs/html/tui_design_blueprint.html +0 -374
  594. package/docs/html/validation_blueprint.html +0 -961
  595. package/docs/html/worker_dispatch_blueprint.html +0 -231
  596. package/src/core/release.ts +0 -2
  597. package/src/domains/providers/runtimes/common/lmstudio-logger.ts +0 -32
  598. package/src/domains/providers/runtimes/local-native/lmstudio-native.ts +0 -491
  599. package/src/engine/apis/lmstudio-native.ts +0 -1438
  600. package/src/engine/apis/thinking-replay.ts +0 -11
  601. package/src/tools/string-enum.ts +0 -15
@@ -7,8 +7,9 @@ import {
7
7
  type TaskBoardTask,
8
8
  taskBoardCounts,
9
9
  } from "../domains/session/task-board.js";
10
+ import type { UserTask, UserTasksStore } from "../domains/user-tasks/store.js";
11
+ import { StringEnum } from "../engine/ai.js";
10
12
  import type { ToolResult, ToolSpec } from "./registry.js";
11
- import { stringEnum } from "./string-enum.js";
12
13
 
13
14
  /**
14
15
  * The tasks tool: the session task board. The agent declares what it is
@@ -23,7 +24,7 @@ import { stringEnum } from "./string-enum.js";
23
24
  * track it across turns.
24
25
  */
25
26
 
26
- const TASKS_ACTIONS = ["plan", "add", "start", "done", "block", "drop", "list"] as const;
27
+ const TASKS_ACTIONS = ["plan", "add", "pick", "start", "done", "block", "drop", "list"] as const;
27
28
  type TasksAction = (typeof TASKS_ACTIONS)[number];
28
29
 
29
30
  /** Plain-text status markers for model-facing output; the TUI owns glyphs. */
@@ -37,9 +38,20 @@ const STATUS_MARK: Record<TaskBoardTask["status"], string> = {
37
38
 
38
39
  export interface TasksToolDeps {
39
40
  board: TaskBoardStore;
41
+ userTasks?: UserTasksStore;
42
+ getSessionId?: () => string | null;
40
43
  }
41
44
 
42
- function renderTaskBoardText(board: TaskBoardSnapshot): string {
45
+ function waitingTrailer(tasks: ReadonlyArray<UserTask>): string[] {
46
+ return tasks
47
+ .filter((task) => task.status === "open" || task.status === "handed")
48
+ .map(
49
+ (task) =>
50
+ `operator tasks waiting: ${task.id} ${JSON.stringify(task.title)} — pick up with action="pick" id="${task.id}"`,
51
+ );
52
+ }
53
+
54
+ function renderTaskBoardText(board: TaskBoardSnapshot, userTasks: ReadonlyArray<UserTask> = []): string {
43
55
  const counts = taskBoardCounts(board);
44
56
  const lines: string[] = [`board "${board.title}" ${counts.completed}/${counts.total} done`];
45
57
  for (const task of board.tasks) {
@@ -52,6 +64,7 @@ function renderTaskBoardText(board: TaskBoardSnapshot): string {
52
64
  if (counts.open > 0 && counts.active === 0) {
53
65
  lines.push(`next: start a task with action="start" before working it`);
54
66
  }
67
+ lines.push(...waitingTrailer(userTasks));
55
68
  return lines.join("\n");
56
69
  }
57
70
 
@@ -60,6 +73,7 @@ function boardDetails(action: TasksAction, board: TaskBoardSnapshot | null): Rec
60
73
  const counts = taskBoardCounts(board);
61
74
  return {
62
75
  action,
76
+ boardId: board.boardId,
63
77
  title: board.title,
64
78
  counts,
65
79
  tasks: board.tasks.map((task) => ({ ...task })),
@@ -87,6 +101,8 @@ function mutationFromArgs(action: TasksAction, args: Record<string, unknown>): T
87
101
  }
88
102
  case "add":
89
103
  return { op: "add", tasks: taskTitlesArg(args) };
104
+ case "pick":
105
+ return { error: "pick is resolved from the durable operator task inbox" };
90
106
  case "start":
91
107
  case "done":
92
108
  case "block":
@@ -105,18 +121,32 @@ function mutationFromArgs(action: TasksAction, args: Record<string, unknown>): T
105
121
  }
106
122
 
107
123
  export function createTasksTool(deps: TasksToolDeps): ToolSpec {
124
+ const reconcileUserTasks = (): ReadonlyArray<UserTask> => {
125
+ if (!deps.userTasks) return [];
126
+ const sessionId = deps.getSessionId?.();
127
+ if (!sessionId) return deps.userTasks.snapshot();
128
+ const links =
129
+ deps.board
130
+ .snapshot()
131
+ ?.tasks.flatMap((task) =>
132
+ task.userTaskId ? [{ userTaskId: task.userTaskId, boardTaskId: task.id, status: task.status }] : [],
133
+ ) ?? [];
134
+ return deps.userTasks.reconcile(links, sessionId);
135
+ };
136
+
108
137
  return {
109
138
  name: ToolNames.Tasks,
110
139
  description:
111
140
  "Session task board. plan declares a titled board (replaces any prior board); add appends tasks; " +
112
- "start marks one task active (the current focus); done completes a started task and requires note as the " +
141
+ "pick moves one operator task uN onto the board; start marks one task active (the current focus); " +
142
+ "done completes a started task and requires note as the " +
113
143
  "evidence the work actually finished; block parks it with a required reason; drop cancels it; list shows the board. " +
114
144
  "Work that did not happen is blocked or dropped, never done.",
115
145
  parameters: Type.Object({
116
- action: stringEnum(TASKS_ACTIONS, "Board action."),
146
+ action: StringEnum(TASKS_ACTIONS, { description: "Board action." }),
117
147
  title: Type.Optional(Type.String({ description: "Board title (plan)." })),
118
148
  tasks: Type.Optional(Type.Array(Type.String(), { description: "Task titles (plan, add)." })),
119
- id: Type.Optional(Type.String({ description: 'Task id like "t2" (start, done, block, drop).' })),
149
+ id: Type.Optional(Type.String({ description: 'Task id like "t2", or operator id "u2" for pick.' })),
120
150
  note: Type.Optional(
121
151
  Type.String({ description: "Evidence of completion (required for done) or the reason (block, drop)." }),
122
152
  ),
@@ -127,8 +157,11 @@ export function createTasksTool(deps: TasksToolDeps): ToolSpec {
127
157
  const prepared = { ...args };
128
158
  // Weak-model shapes: a numeric or bare-number id becomes "tN", and a
129
159
  // JSON-string tasks array is parsed back into a real array.
130
- if (typeof prepared.id === "number" && Number.isFinite(prepared.id)) prepared.id = `t${prepared.id}`;
131
- if (typeof prepared.id === "string" && /^\d+$/.test(prepared.id.trim())) prepared.id = `t${prepared.id.trim()}`;
160
+ const idPrefix = prepared.action === "pick" ? "u" : "t";
161
+ if (typeof prepared.id === "number" && Number.isFinite(prepared.id)) prepared.id = `${idPrefix}${prepared.id}`;
162
+ if (typeof prepared.id === "string" && /^\d+$/.test(prepared.id.trim())) {
163
+ prepared.id = `${idPrefix}${prepared.id.trim()}`;
164
+ }
132
165
  if (typeof prepared.tasks === "string" && prepared.tasks.trim().startsWith("[")) {
133
166
  try {
134
167
  const parsed = JSON.parse(prepared.tasks);
@@ -145,23 +178,87 @@ export function createTasksTool(deps: TasksToolDeps): ToolSpec {
145
178
  return { kind: "error", message: `tasks: action must be one of ${TASKS_ACTIONS.join(", ")}; got '${action}'` };
146
179
  }
147
180
  const typedAction = action as TasksAction;
181
+ let userTasks: ReadonlyArray<UserTask>;
182
+ try {
183
+ userTasks = reconcileUserTasks();
184
+ } catch (error) {
185
+ const message = error instanceof Error ? error.message : String(error);
186
+ return { kind: "error", message: `tasks: could not reconcile the operator task inbox: ${message}` };
187
+ }
148
188
  if (typedAction === "list") {
149
189
  const board = deps.board.snapshot();
150
190
  if (!board) {
191
+ const output = ['no task board yet; declare one with action="plan"', ...waitingTrailer(userTasks)].join("\n");
151
192
  return {
152
193
  kind: "ok",
153
- output: 'no task board yet; declare one with action="plan"',
194
+ output,
154
195
  details: boardDetails("list", null),
155
196
  };
156
197
  }
157
- return { kind: "ok", output: renderTaskBoardText(board), details: boardDetails("list", board) };
198
+ return { kind: "ok", output: renderTaskBoardText(board, userTasks), details: boardDetails("list", board) };
158
199
  }
200
+ if (typedAction === "pick") {
201
+ if (!deps.userTasks) return { kind: "error", message: "tasks: operator task inbox is unavailable" };
202
+ const sessionId = deps.getSessionId?.();
203
+ if (!sessionId) return { kind: "error", message: "tasks: pick requires an active session" };
204
+ const id = stringArg(args, "id");
205
+ if (!id || !/^u[1-9]\d*$/.test(id)) {
206
+ return { kind: "error", message: 'tasks: pick requires an operator task id (e.g. id="u3")' };
207
+ }
208
+ const userTask = userTasks.find((task) => task.id === id);
209
+ if (!userTask) return { kind: "error", message: `tasks: operator task ${id} was not found` };
210
+ if (userTask.status !== "open" && userTask.status !== "handed") {
211
+ return { kind: "error", message: `tasks: operator task ${id} is ${userTask.status}; it cannot be picked` };
212
+ }
213
+ const result = deps.board.apply({ op: "pick", title: userTask.title, userTaskId: userTask.id });
214
+ if (!result.ok) return { kind: "error", message: `tasks: ${result.message}` };
215
+ const picked = result.board.tasks.find((task) => task.userTaskId === userTask.id && task.status === "pending");
216
+ if (!picked) return { kind: "error", message: `tasks: durable pickup for ${id} did not produce a board row` };
217
+ try {
218
+ deps.userTasks.recordPicked(userTask.id, sessionId, picked.id);
219
+ userTasks = deps.userTasks.snapshot();
220
+ } catch (error) {
221
+ const message = error instanceof Error ? error.message : String(error);
222
+ return {
223
+ kind: "error",
224
+ message: `tasks: ${picked.id} durably picked up ${id}, but the operator inbox update failed: ${message}`,
225
+ details: boardDetails("pick", result.board),
226
+ };
227
+ }
228
+ return {
229
+ kind: "ok",
230
+ output: renderTaskBoardText(result.board, userTasks),
231
+ details: boardDetails("pick", result.board),
232
+ };
233
+ }
234
+ const before = deps.board.snapshot();
159
235
  const mutation = mutationFromArgs(typedAction, args);
160
236
  if ("error" in mutation) return { kind: "error", message: mutation.error };
237
+ const completedUserTask =
238
+ typedAction === "done" && mutation.op === "done"
239
+ ? before?.tasks.find((task) => task.id === mutation.id && task.userTaskId)
240
+ : undefined;
241
+ const completionSessionId = completedUserTask?.userTaskId && deps.userTasks ? deps.getSessionId?.() : undefined;
242
+ if (completedUserTask?.userTaskId && deps.userTasks && !completionSessionId) {
243
+ return { kind: "error", message: "tasks: durable completion requires an active session" };
244
+ }
161
245
  const result = deps.board.apply(mutation);
162
246
  if (!result.ok) return { kind: "error", message: `tasks: ${result.message}` };
247
+ if (completedUserTask?.userTaskId && deps.userTasks && completionSessionId) {
248
+ try {
249
+ deps.userTasks.recordDone(completedUserTask.userTaskId, completionSessionId, completedUserTask.id);
250
+ userTasks = deps.userTasks.snapshot();
251
+ } catch (error) {
252
+ const message = error instanceof Error ? error.message : String(error);
253
+ return {
254
+ kind: "error",
255
+ message: `tasks: ${completedUserTask.id} completed durably, but operator task ${completedUserTask.userTaskId} could not be updated: ${message}`,
256
+ details: boardDetails("done", result.board),
257
+ };
258
+ }
259
+ }
163
260
  const noteLines = result.notes.map((note) => `note: ${note}`);
164
- const output = [...noteLines, renderTaskBoardText(result.board)].join("\n");
261
+ const output = [...noteLines, renderTaskBoardText(result.board, userTasks)].join("\n");
165
262
  return { kind: "ok", output, details: boardDetails(typedAction, result.board) };
166
263
  },
167
264
  };
@@ -1,38 +1,25 @@
1
- // Per-observation source cap. One tool result must stay proportional to the
2
- // question asked: ~16k chars ≈ 4k tokens, enough to read a typical source file
3
- // or doc section in one call while a local backend still prefills it quickly.
4
- // Truncated output always says how to continue (offset/limit or a narrower
5
- // query), so larger requests cost extra calls, not a blown context. The
6
- // per-turn observation budget (see read.ts) bounds how much these calls add up
7
- // to within a single turn.
1
+ import {
2
+ formatSize,
3
+ GREP_MAX_LINE_LENGTH,
4
+ type TruncationOptions,
5
+ type TruncationResult,
6
+ truncateLine,
7
+ truncateHead as truncatePiHead,
8
+ truncateTail as truncatePiTail,
9
+ } from "../engine/truncate.js";
10
+
11
+ // Per-observation source cap. Pi defaults to 50 KiB; Clio deliberately keeps
12
+ // one result proportional to its per-turn observation budget at 16 KiB. The
13
+ // wrappers below retain only that product-level delta while Pi owns UTF-8-safe
14
+ // head/tail truncation, line limits, grep-line clipping, and size formatting.
8
15
  export const DEFAULT_MAX_LINES = 2000;
9
16
  export const DEFAULT_MAX_BYTES = 16 * 1024;
10
- export const GREP_MAX_LINE_LENGTH = 500;
17
+ export type { TruncationResult };
18
+ export { formatSize, GREP_MAX_LINE_LENGTH, truncateLine };
11
19
 
12
- export interface TruncationResult {
13
- content: string;
14
- truncated: boolean;
15
- truncatedBy: "lines" | "bytes" | null;
16
- totalLines: number;
17
- totalBytes: number;
18
- outputLines: number;
19
- outputBytes: number;
20
- lastLinePartial: boolean;
21
- firstLineExceedsLimit: boolean;
22
- maxLines: number;
23
- maxBytes: number;
24
- }
25
-
26
- export interface TruncationOptions {
27
- maxLines?: number;
28
- maxBytes?: number;
29
- }
30
-
31
- // Count lines without a trailing-newline phantom entry. `"a\nb\n".split("\n")`
32
- // yields `["a","b",""]` (3 entries) for a 2-line file; dropping the final empty
33
- // element restores an honest count so continuation notices ("N more lines") do
34
- // not over-report by one on newline-terminated files. Ported from pi's
35
- // truncate.ts:47.
20
+ // Count lines without a trailing-newline phantom entry. Pi uses the same
21
+ // counting rule internally but does not export it; Clio's read continuation
22
+ // notices need the total before selecting a slice.
36
23
  export function splitLinesForCounting(content: string): string[] {
37
24
  if (content.length === 0) return [];
38
25
  const lines = content.split("\n");
@@ -40,163 +27,17 @@ export function splitLinesForCounting(content: string): string[] {
40
27
  return lines;
41
28
  }
42
29
 
43
- export function formatSize(bytes: number): string {
44
- if (bytes < 1024) return `${bytes}B`;
45
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
46
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
47
- }
48
-
49
- export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
50
- const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
51
- const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
52
- const totalBytes = Buffer.byteLength(content, "utf8");
53
- const lines = splitLinesForCounting(content);
54
- const totalLines = lines.length;
55
-
56
- if (totalLines <= maxLines && totalBytes <= maxBytes) {
57
- return {
58
- content,
59
- truncated: false,
60
- truncatedBy: null,
61
- totalLines,
62
- totalBytes,
63
- outputLines: totalLines,
64
- outputBytes: totalBytes,
65
- lastLinePartial: false,
66
- firstLineExceedsLimit: false,
67
- maxLines,
68
- maxBytes,
69
- };
70
- }
71
-
72
- const firstLineBytes = Buffer.byteLength(lines[0] ?? "", "utf8");
73
- if (firstLineBytes > maxBytes) {
74
- return {
75
- content: "",
76
- truncated: true,
77
- truncatedBy: "bytes",
78
- totalLines,
79
- totalBytes,
80
- outputLines: 0,
81
- outputBytes: 0,
82
- lastLinePartial: false,
83
- firstLineExceedsLimit: true,
84
- maxLines,
85
- maxBytes,
86
- };
87
- }
88
-
89
- const out: string[] = [];
90
- let outputBytes = 0;
91
- let truncatedBy: "lines" | "bytes" = "lines";
92
- for (let i = 0; i < lines.length && i < maxLines; i += 1) {
93
- const line = lines[i] ?? "";
94
- const lineBytes = Buffer.byteLength(line, "utf8") + (i > 0 ? 1 : 0);
95
- if (outputBytes + lineBytes > maxBytes) {
96
- truncatedBy = "bytes";
97
- break;
98
- }
99
- out.push(line);
100
- outputBytes += lineBytes;
101
- }
102
- if (out.length >= maxLines && outputBytes <= maxBytes) truncatedBy = "lines";
103
- const output = out.join("\n");
30
+ function withClioDefaults(options: TruncationOptions): Required<TruncationOptions> {
104
31
  return {
105
- content: output,
106
- truncated: true,
107
- truncatedBy,
108
- totalLines,
109
- totalBytes,
110
- outputLines: out.length,
111
- outputBytes: Buffer.byteLength(output, "utf8"),
112
- lastLinePartial: false,
113
- firstLineExceedsLimit: false,
114
- maxLines,
115
- maxBytes,
32
+ maxLines: options.maxLines ?? DEFAULT_MAX_LINES,
33
+ maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES,
116
34
  };
117
35
  }
118
36
 
119
- // Keep the tail of a UTF-8 string within a byte budget, cutting at a valid
120
- // character boundary. Used when the last line alone exceeds the byte cap.
121
- function truncateStringToBytesFromEnd(str: string, maxBytes: number): string {
122
- const buf = Buffer.from(str, "utf8");
123
- if (buf.length <= maxBytes) return str;
124
- let start = buf.length - maxBytes;
125
- while (start < buf.length && ((buf[start] ?? 0) & 0xc0) === 0x80) start += 1;
126
- return buf.subarray(start).toString("utf8");
37
+ export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
38
+ return truncatePiHead(content, withClioDefaults(options));
127
39
  }
128
40
 
129
- // Keep the LAST N lines / bytes (whichever cap is hit first). Suitable for
130
- // shell/build/test output where the failing assertion, compiler error, and
131
- // exit summary live at the end. Ported from pi's truncate.ts:168, including the
132
- // partial-last-line edge case: when the final line alone exceeds maxBytes, the
133
- // end of that line is kept and lastLinePartial is set.
134
41
  export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
135
- const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
136
- const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
137
- const totalBytes = Buffer.byteLength(content, "utf8");
138
- const lines = splitLinesForCounting(content);
139
- const totalLines = lines.length;
140
-
141
- if (totalLines <= maxLines && totalBytes <= maxBytes) {
142
- return {
143
- content,
144
- truncated: false,
145
- truncatedBy: null,
146
- totalLines,
147
- totalBytes,
148
- outputLines: totalLines,
149
- outputBytes: totalBytes,
150
- lastLinePartial: false,
151
- firstLineExceedsLimit: false,
152
- maxLines,
153
- maxBytes,
154
- };
155
- }
156
-
157
- const out: string[] = [];
158
- let outputBytes = 0;
159
- let truncatedBy: "lines" | "bytes" = "lines";
160
- let lastLinePartial = false;
161
- for (let i = lines.length - 1; i >= 0 && out.length < maxLines; i -= 1) {
162
- const line = lines[i] ?? "";
163
- const lineBytes = Buffer.byteLength(line, "utf8") + (out.length > 0 ? 1 : 0);
164
- if (outputBytes + lineBytes > maxBytes) {
165
- truncatedBy = "bytes";
166
- // The final line alone overflows the budget: keep its tail so the
167
- // last bytes (the exit summary) are still shown, flagged partial.
168
- if (out.length === 0) {
169
- const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
170
- out.unshift(truncatedLine);
171
- outputBytes = Buffer.byteLength(truncatedLine, "utf8");
172
- lastLinePartial = true;
173
- }
174
- break;
175
- }
176
- out.unshift(line);
177
- outputBytes += lineBytes;
178
- }
179
- if (out.length >= maxLines && outputBytes <= maxBytes) truncatedBy = "lines";
180
- const output = out.join("\n");
181
- return {
182
- content: output,
183
- truncated: true,
184
- truncatedBy,
185
- totalLines,
186
- totalBytes,
187
- outputLines: out.length,
188
- outputBytes: Buffer.byteLength(output, "utf8"),
189
- lastLinePartial,
190
- firstLineExceedsLimit: false,
191
- maxLines,
192
- maxBytes,
193
- };
194
- }
195
-
196
- export function truncateLine(
197
- line: string,
198
- maxChars: number = GREP_MAX_LINE_LENGTH,
199
- ): { text: string; wasTruncated: boolean } {
200
- if (line.length <= maxChars) return { text: line, wasTruncated: false };
201
- return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
42
+ return truncatePiTail(content, withClioDefaults(options));
202
43
  }
@@ -9,8 +9,10 @@ import { resolveReadPath } from "../path-utils.js";
9
9
  import type { ToolResult, ToolResultDetails } from "../registry.js";
10
10
  import { maxOutputArg, timeoutArg } from "../safe-exec.js";
11
11
  import { truncateUtf8 } from "../truncate-utf8.js";
12
+ import { BROWSER_MODES } from "./surface.js";
13
+
14
+ export { BROWSER_MODES } from "./surface.js";
12
15
 
13
- export const BROWSER_MODES = ["auto", "required", "off"] as const;
14
16
  type BrowserMode = (typeof BROWSER_MODES)[number];
15
17
 
16
18
  type CheckStatus = "pass" | "warn" | "fail" | "skip";
@@ -1,10 +1,8 @@
1
- import { Type } from "typebox";
2
- import { ToolNames } from "../../core/tool-names.js";
3
1
  import { isVerificationScriptName } from "../../core/verification-scripts.js";
4
2
  import type { ToolResult, ToolSpec } from "../registry.js";
5
- import { stringEnum } from "../string-enum.js";
6
- import { BROWSER_MODES, runFrontendCheck } from "./frontend.js";
3
+ import { runFrontendCheck } from "./frontend.js";
7
4
  import { listChecks, runScriptCheck, VERIFICATION_SCRIPT_FAMILY_HINT } from "./scripts.js";
5
+ import { prepareVerifyArguments, verifyToolSurface } from "./surface.js";
8
6
 
9
7
  /**
10
8
  * The verify tool: one EXECUTE entry point for declared verification.
@@ -13,41 +11,8 @@ import { listChecks, runScriptCheck, VERIFICATION_SCRIPT_FAMILY_HINT } from "./s
13
11
  * path=<file>) validates an HTML/CSS/JS artifact without shell access.
14
12
  */
15
13
 
16
- /** Tolerate the weak-model shape of `args` sent as a JSON string. */
17
- function prepareVerifyArguments(args: Record<string, unknown>): Record<string, unknown> {
18
- if (!args || typeof args !== "object" || Array.isArray(args)) return args;
19
- const next: Record<string, unknown> = { ...args };
20
- if (typeof next.args === "string") {
21
- try {
22
- const parsed = JSON.parse(next.args) as unknown;
23
- if (Array.isArray(parsed)) next.args = parsed;
24
- } catch {
25
- // Leave the malformed string; runScriptCheck ignores non-arrays.
26
- }
27
- }
28
- return next;
29
- }
30
-
31
14
  export const verifyTool: ToolSpec = {
32
- name: ToolNames.Verify,
33
- description:
34
- 'Run a declared verification check: no arguments lists checks, check=<package.json script> runs it, check="frontend" with path validates an HTML/CSS/JS artifact.',
35
- parameters: Type.Object({
36
- check: Type.Optional(
37
- Type.String({
38
- description: `Declared script name (${VERIFICATION_SCRIPT_FAMILY_HINT}) or "frontend". Omit to list available checks.`,
39
- }),
40
- ),
41
- path: Type.Optional(Type.String({ description: "check=frontend: artifact file under the workspace root." })),
42
- args: Type.Optional(Type.Array(Type.String(), { description: "Extra arguments passed after --." })),
43
- browser: Type.Optional(stringEnum(BROWSER_MODES, "check=frontend: headless browser mode (default auto).")),
44
- cwd: Type.Optional(Type.String({ description: "Working directory." })),
45
- timeout_ms: Type.Optional(Type.Number({ description: "Timeout in ms (default 120000)." })),
46
- max_output_bytes: Type.Optional(Type.Number({ description: "Output cap in bytes (default 600000)." })),
47
- }),
48
- baseActionClass: "execute",
49
- executionMode: "sequential",
50
- prepareArguments: prepareVerifyArguments,
15
+ ...verifyToolSurface,
51
16
  async run(rawArgs, options): Promise<ToolResult> {
52
17
  const args = prepareVerifyArguments(rawArgs);
53
18
  const check = typeof args.check === "string" ? args.check.trim() : "";
@@ -0,0 +1,46 @@
1
+ import { Type } from "typebox";
2
+ import { ToolNames } from "../../core/tool-names.js";
3
+ import { VERIFICATION_SCRIPT_FAMILY_HINT } from "../../core/verification-scripts.js";
4
+ import { StringEnum } from "../../engine/ai.js";
5
+ import type { ToolSurface } from "../lazy-tool.js";
6
+
7
+ export const BROWSER_MODES = ["auto", "required", "off"] as const;
8
+
9
+ /** Tolerate the weak-model shape of `args` sent as a JSON string. */
10
+ export function prepareVerifyArguments(args: Record<string, unknown>): Record<string, unknown> {
11
+ if (!args || typeof args !== "object" || Array.isArray(args)) return args;
12
+ const next: Record<string, unknown> = { ...args };
13
+ if (typeof next.args === "string") {
14
+ try {
15
+ const parsed = JSON.parse(next.args) as unknown;
16
+ if (Array.isArray(parsed)) next.args = parsed;
17
+ } catch {
18
+ // Leave the malformed string; runScriptCheck ignores non-arrays.
19
+ }
20
+ }
21
+ return next;
22
+ }
23
+
24
+ export const verifyToolSurface = {
25
+ name: ToolNames.Verify,
26
+ description:
27
+ 'Run a declared verification check: no arguments lists checks, check=<package.json script> runs it, check="frontend" with path validates an HTML/CSS/JS artifact.',
28
+ parameters: Type.Object({
29
+ check: Type.Optional(
30
+ Type.String({
31
+ description: `Declared script name (${VERIFICATION_SCRIPT_FAMILY_HINT}) or "frontend". Omit to list available checks.`,
32
+ }),
33
+ ),
34
+ path: Type.Optional(Type.String({ description: "check=frontend: artifact file under the workspace root." })),
35
+ args: Type.Optional(Type.Array(Type.String(), { description: "Extra arguments passed after --." })),
36
+ browser: Type.Optional(
37
+ StringEnum(BROWSER_MODES, { description: "check=frontend: headless browser mode (default auto)." }),
38
+ ),
39
+ cwd: Type.Optional(Type.String({ description: "Working directory." })),
40
+ timeout_ms: Type.Optional(Type.Number({ description: "Timeout in ms (default 120000)." })),
41
+ max_output_bytes: Type.Optional(Type.Number({ description: "Output cap in bytes (default 600000)." })),
42
+ }),
43
+ baseActionClass: "execute",
44
+ executionMode: "sequential",
45
+ prepareArguments: prepareVerifyArguments,
46
+ } satisfies ToolSurface;
@@ -0,0 +1,23 @@
1
+ import { Type } from "typebox";
2
+ import { ToolNames } from "../core/tool-names.js";
3
+ import type { ToolSurface } from "./lazy-tool.js";
4
+
5
+ export const webFetchToolSurface = {
6
+ name: ToolNames.WebFetch,
7
+ description: "Fetch an http(s) URL; HTML is cleaned and converted to Markdown. Non-2xx responses are errors.",
8
+ parameters: Type.Object({
9
+ url: Type.String({ description: "Fully-qualified http(s) URL." }),
10
+ method: Type.Optional(Type.String({ description: "HTTP method (default GET)." })),
11
+ headers: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Request headers." })),
12
+ body: Type.Optional(Type.String({ description: "Request body (POST/PUT)." })),
13
+ timeout_ms: Type.Optional(Type.Number({ description: "Timeout ms (default 30000)." })),
14
+ max_bytes: Type.Optional(Type.Number({ description: "Max bytes returned (default 600000)." })),
15
+ format: Type.Optional(
16
+ Type.String({
17
+ description: "auto (default: HTML is cleaned to Markdown, other content passes through) or raw (no conversion).",
18
+ }),
19
+ ),
20
+ }),
21
+ baseActionClass: "read",
22
+ executionMode: "parallel",
23
+ } satisfies ToolSurface;
@@ -1,8 +1,6 @@
1
- import { Type } from "typebox";
2
- import { fetch } from "undici";
3
- import { ToolNames } from "../core/tool-names.js";
4
1
  import type { ToolResult, ToolSpec } from "./registry.js";
5
2
  import { truncateUtf8 } from "./truncate-utf8.js";
3
+ import { webFetchToolSurface } from "./web-fetch-surface.js";
6
4
 
7
5
  const DEFAULT_TIMEOUT_MS = 30_000;
8
6
  const DEFAULT_MAX_BYTES = 600_000;
@@ -627,23 +625,7 @@ function formatOutput(args: {
627
625
  }
628
626
 
629
627
  export const webFetchTool: ToolSpec = {
630
- name: ToolNames.WebFetch,
631
- description: "Fetch an http(s) URL; HTML is cleaned and converted to Markdown. Non-2xx responses are errors.",
632
- parameters: Type.Object({
633
- url: Type.String({ description: "Fully-qualified http(s) URL." }),
634
- method: Type.Optional(Type.String({ description: "HTTP method (default GET)." })),
635
- headers: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Request headers." })),
636
- body: Type.Optional(Type.String({ description: "Request body (POST/PUT)." })),
637
- timeout_ms: Type.Optional(Type.Number({ description: "Timeout ms (default 30000)." })),
638
- max_bytes: Type.Optional(Type.Number({ description: "Max bytes returned (default 600000)." })),
639
- format: Type.Optional(
640
- Type.String({
641
- description: "auto (default: HTML is cleaned to Markdown, other content passes through) or raw (no conversion).",
642
- }),
643
- ),
644
- }),
645
- baseActionClass: "read",
646
- executionMode: "parallel",
628
+ ...webFetchToolSurface,
647
629
  async run(args, options): Promise<ToolResult> {
648
630
  const urlArg = typeof args.url === "string" ? args.url : null;
649
631
  if (!urlArg) return { kind: "error", message: "web_fetch: missing url argument" };
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { Type } from "typebox";
4
4
  import { ToolNames } from "../core/tool-names.js";
5
+ import { generateDiffString } from "./edit-diff.js";
5
6
  import { withFileMutationQueue } from "./file-mutation-queue.js";
6
7
  import { resolveToCwd } from "./path-utils.js";
7
8
  import type { ToolResult, ToolSpec } from "./registry.js";
@@ -25,16 +26,20 @@ export const writeTool: ToolSpec = {
25
26
  const filePath = resolveToCwd(pathArg);
26
27
  try {
27
28
  let previousEndedWithNewline = false;
29
+ let previousContent = "";
28
30
  await withFileMutationQueue(filePath, async () => {
29
31
  try {
30
- previousEndedWithNewline = readFileSync(filePath, "utf8").endsWith("\n");
32
+ previousContent = readFileSync(filePath, "utf8");
33
+ previousEndedWithNewline = previousContent.endsWith("\n");
31
34
  } catch {
35
+ previousContent = "";
32
36
  previousEndedWithNewline = false;
33
37
  }
34
38
  mkdirSync(dirname(filePath), { recursive: true });
35
39
  writeFileSync(filePath, content, "utf8");
36
40
  });
37
41
  const bytes = Buffer.byteLength(content, "utf8");
42
+ const diff = generateDiffString(previousContent, content).diff;
38
43
  let output = `wrote ${bytes}B to ${pathArg}`;
39
44
  if (previousEndedWithNewline && !content.endsWith("\n")) {
40
45
  output += `\nnote: ${pathArg} no longer ends with a newline; the previous content did`;
@@ -43,7 +48,7 @@ export const writeTool: ToolSpec = {
43
48
  // before it falls back to the length of the returned text. A write's
44
49
  // text is a confirmation sentence, so without this the ledger printed
45
50
  // the sentence's length as the file size.
46
- return { kind: "ok", output, details: { paths: [filePath], observation: { shownBytes: bytes } } };
51
+ return { kind: "ok", output, details: { diff, paths: [filePath], observation: { shownBytes: bytes } } };
47
52
  } catch (err) {
48
53
  const msg = err instanceof Error ? err.message : String(err);
49
54
  return { kind: "error", message: `write: ${msg}` };