@iowarp/clio-coder 0.3.4 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/CONTRIBUTING.md +6 -6
  3. package/README.md +16 -5
  4. package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
  5. package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
  8. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  9. package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
  10. package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
  11. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  12. package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
  13. package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
  14. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  15. package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
  16. package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
  17. package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
  18. package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
  19. package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
  20. package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
  21. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  22. package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
  23. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  24. package/dist/chunk-DJNLUABN.js +843 -0
  25. package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
  26. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  27. package/dist/chunk-DQA7QLMD.js +123 -0
  28. package/dist/chunk-DR52UMZW.js +21 -0
  29. package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
  30. package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
  31. package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
  32. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  33. package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
  34. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  35. package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
  36. package/dist/chunk-GWS3VEIW.js +195 -0
  37. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  38. package/dist/chunk-HWUFFB6L.js +83 -0
  39. package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
  40. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  41. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  42. package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
  43. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  44. package/dist/chunk-KZ2H5X4G.js +1026 -0
  45. package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
  46. package/dist/chunk-LCGCVYZ4.js +57 -0
  47. package/dist/chunk-M4AKACEO.js +382 -0
  48. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  49. package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
  50. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  51. package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
  52. package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
  53. package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
  54. package/dist/chunk-PD3MESLB.js +242 -0
  55. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  56. package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
  57. package/dist/chunk-RVG5JXAL.js +41 -0
  58. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  59. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  60. package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
  61. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  62. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  63. package/dist/chunk-UND3GU2L.js +103 -0
  64. package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
  65. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  66. package/dist/chunk-UVDSQ6LW.js +472 -0
  67. package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
  68. package/dist/chunk-VREKEFLL.js +37 -0
  69. package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
  70. package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
  71. package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
  72. package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
  73. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  74. package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
  75. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  76. package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
  77. package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
  78. package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
  79. package/dist/cli/index.js +34 -32
  80. package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
  81. package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
  82. package/dist/codewiki/build-worker.js +4 -4
  83. package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
  84. package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
  85. package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
  86. package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
  87. package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
  88. package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
  89. package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
  90. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  91. package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
  92. package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
  93. package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
  94. package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
  95. package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
  96. package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
  97. package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
  98. package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
  99. package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
  100. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  101. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  102. package/dist/fleet-new-RDVJLHHH.js +48 -0
  103. package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
  104. package/dist/fleet-validate-BIYREGIK.js +79 -0
  105. package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
  106. package/dist/library-NJAHIGG4.js +217 -0
  107. package/dist/memory-OG6HOYKM.js +472 -0
  108. package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
  109. package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
  110. package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
  111. package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
  112. package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
  113. package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
  114. package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
  115. package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
  116. package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
  117. package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
  118. package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
  119. package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
  120. package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
  121. package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
  122. package/dist/verifiers-RLAHT27O.js +336 -0
  123. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  124. package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
  125. package/dist/worker/entry.js +98 -84
  126. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  127. package/docs/README.md +4 -3
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-placement.md +1 -2
  132. package/docs/artifact-versions.md +10 -6
  133. package/docs/built-in-agents.md +26 -2
  134. package/docs/capacity-and-scheduling.md +1 -1
  135. package/docs/commands-and-modes.md +90 -8
  136. package/docs/configuration-and-targets.md +90 -2
  137. package/docs/context-engine.md +4 -2
  138. package/docs/context-working-set.md +4 -4
  139. package/docs/development-pipeline.md +1 -1
  140. package/docs/dispatch-architecture-rationale.md +1 -1
  141. package/docs/documentation-coverage.md +4 -4
  142. package/docs/documentation-guide.md +4 -4
  143. package/docs/eval-runner.md +1 -1
  144. package/docs/evals-internal.md +4 -45
  145. package/docs/evidence-and-memory.md +70 -10
  146. package/docs/evolution.md +1 -1
  147. package/docs/exit-codes-and-output.md +4 -1
  148. package/docs/extensions-and-sharing.md +6 -2
  149. package/docs/fleet-demo-runbook.md +2 -2
  150. package/docs/fleet-dispatch.md +224 -11
  151. package/docs/git-commit-provenance.md +2 -2
  152. package/docs/glossary.md +1 -1
  153. package/docs/installation-and-lifecycle.md +2 -2
  154. package/docs/middleware-and-components.md +20 -2
  155. package/docs/model-catalog.md +1 -1
  156. package/docs/observability.md +55 -8
  157. package/docs/proactive-memory.md +26 -16
  158. package/docs/prompt-envelope-and-tools.md +4 -2
  159. package/docs/provider-adapter-cookbook.md +1 -1
  160. package/docs/release-cut-checklist.md +83 -65
  161. package/docs/resource-library.md +59 -0
  162. package/docs/safety-model.md +29 -7
  163. package/docs/scientific-validation.md +3 -3
  164. package/docs/session-lifecycle.md +37 -1
  165. package/docs/skills-marketplace.md +16 -3
  166. package/docs/tool-usage.md +14 -7
  167. package/docs/trace-store.md +1 -1
  168. package/docs/troubleshooting.md +1 -1
  169. package/docs/tui-design.md +38 -4
  170. package/docs/worker-dispatch-mechanics.md +3 -3
  171. package/package.json +7 -4
  172. package/src/cli/agents.ts +2 -3
  173. package/src/cli/argv.ts +14 -1
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +121 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +188 -335
  180. package/src/cli/index.ts +4 -2
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/memory.ts +272 -10
  183. package/src/cli/modes/json-stream.ts +2 -2
  184. package/src/cli/modes/print.ts +12 -1
  185. package/src/cli/run.ts +22 -2
  186. package/src/cli/share.ts +13 -1
  187. package/src/cli/targets.ts +12 -3
  188. package/src/cli/usage.ts +160 -20
  189. package/src/core/bus-events.ts +7 -0
  190. package/src/core/commit-attribution.ts +4 -4
  191. package/src/core/config.ts +130 -0
  192. package/src/core/defaults.ts +81 -0
  193. package/src/core/response-model-id.ts +134 -0
  194. package/src/core/toml.ts +62 -0
  195. package/src/core/workspace-files.ts +0 -1
  196. package/src/domains/agents/builtins/architect.md +2 -1
  197. package/src/domains/agents/builtins/oracle.md +33 -0
  198. package/src/domains/agents/catalog.ts +18 -5
  199. package/src/domains/agents/fleet-contract.ts +278 -16
  200. package/src/domains/agents/index.ts +14 -0
  201. package/src/domains/agents/recipe.ts +54 -14
  202. package/src/domains/agents/result-contract.ts +242 -5
  203. package/src/domains/config/classify.ts +4 -0
  204. package/src/domains/context/bootstrap.ts +36 -27
  205. package/src/domains/context/project-metadata.ts +19 -63
  206. package/src/domains/context/prompt-context.ts +8 -0
  207. package/src/domains/context/working-set/policies/index.ts +3 -4
  208. package/src/domains/dispatch/active-route-planner.ts +14 -0
  209. package/src/domains/dispatch/backoff.ts +2 -1
  210. package/src/domains/dispatch/budget-envelope.ts +396 -0
  211. package/src/domains/dispatch/capability-match.ts +1 -0
  212. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  213. package/src/domains/dispatch/contract.ts +36 -0
  214. package/src/domains/dispatch/delegation-plan.ts +167 -0
  215. package/src/domains/dispatch/execution-plan.ts +76 -5
  216. package/src/domains/dispatch/execution-role.ts +3 -1
  217. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  218. package/src/domains/dispatch/extension.ts +339 -36
  219. package/src/domains/dispatch/fleet-gate.ts +14 -0
  220. package/src/domains/dispatch/fleet-plan.ts +63 -3
  221. package/src/domains/dispatch/fleet-run.ts +737 -0
  222. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  223. package/src/domains/dispatch/host-verification.ts +178 -0
  224. package/src/domains/dispatch/index.ts +38 -0
  225. package/src/domains/dispatch/intent.ts +159 -0
  226. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  227. package/src/domains/dispatch/receipt-integrity.ts +12 -4
  228. package/src/domains/dispatch/state.ts +37 -3
  229. package/src/domains/dispatch/types.ts +61 -9
  230. package/src/domains/dispatch/validation.ts +80 -6
  231. package/src/domains/dispatch/worker-spawn.ts +14 -3
  232. package/src/domains/eval/metrics/evidence.ts +0 -116
  233. package/src/domains/eval/metrics/invariants.ts +1 -1
  234. package/src/domains/eval/runners/clio-run.ts +1 -10
  235. package/src/domains/eval/runners/external-command.ts +2 -29
  236. package/src/domains/eval/schema/suite.ts +0 -7
  237. package/src/domains/eval/suites/run.ts +1 -7
  238. package/src/domains/evidence/trust-status.ts +10 -1
  239. package/src/domains/memory/index.ts +22 -0
  240. package/src/domains/memory/operations.ts +58 -1
  241. package/src/domains/memory/promotion.ts +281 -0
  242. package/src/domains/memory/prompt-section.ts +25 -5
  243. package/src/domains/memory/proposal.ts +51 -7
  244. package/src/domains/memory/task-bank.ts +3 -2
  245. package/src/domains/memory/task-memory-handoff.ts +181 -24
  246. package/src/domains/memory/task-memory-policy.ts +3 -1
  247. package/src/domains/memory/types.ts +37 -0
  248. package/src/domains/memory/validate.ts +178 -0
  249. package/src/domains/middleware/index.ts +15 -0
  250. package/src/domains/middleware/memory-intervention.ts +35 -25
  251. package/src/domains/middleware/runtime.ts +6 -0
  252. package/src/domains/middleware/skills-reminder.ts +19 -4
  253. package/src/domains/middleware/stalled-turn.ts +43 -1
  254. package/src/domains/middleware/types.ts +10 -0
  255. package/src/domains/middleware/watchdog.ts +281 -0
  256. package/src/domains/observability/contract.ts +9 -2
  257. package/src/domains/observability/cost.ts +31 -4
  258. package/src/domains/observability/extension.ts +2 -2
  259. package/src/domains/observability/index.ts +10 -0
  260. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  261. package/src/domains/providers/index.ts +3 -0
  262. package/src/domains/providers/model-discovery.ts +9 -0
  263. package/src/domains/providers/runtime-resolution.ts +38 -1
  264. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  265. package/src/domains/providers/types/context-window-slots.ts +18 -0
  266. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  267. package/src/domains/resources/index.ts +20 -0
  268. package/src/domains/resources/library.ts +326 -0
  269. package/src/domains/resources/skills/marketplace.ts +37 -12
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/redaction.ts +73 -0
  273. package/src/domains/session/context-ledger.ts +10 -1
  274. package/src/domains/session/decision-board.ts +4 -0
  275. package/src/domains/session/entries.ts +3 -0
  276. package/src/domains/session/handoff.ts +629 -0
  277. package/src/domains/session/history.ts +68 -19
  278. package/src/domains/session/usage.ts +24 -7
  279. package/src/domains/share/archive.ts +67 -2
  280. package/src/engine/acp/event-mapper.ts +7 -0
  281. package/src/engine/acp/server.ts +29 -2
  282. package/src/engine/apis/lmstudio.ts +25 -4
  283. package/src/engine/apis/openai-completions.ts +147 -22
  284. package/src/engine/claude/sdk-runtime.ts +8 -2
  285. package/src/engine/claude/tool-safety.ts +13 -0
  286. package/src/engine/loop-guard.ts +27 -3
  287. package/src/engine/worker-events.ts +4 -3
  288. package/src/engine/worker-runtime.ts +59 -54
  289. package/src/entry/orchestrator.ts +55 -1
  290. package/src/interactive/bus-notices.ts +26 -0
  291. package/src/interactive/chat-loop-messages.ts +22 -0
  292. package/src/interactive/chat-loop.ts +248 -1
  293. package/src/interactive/chat-renderer.ts +41 -3
  294. package/src/interactive/clio-editor.ts +44 -7
  295. package/src/interactive/context-overlay.ts +43 -5
  296. package/src/interactive/cost-overlay.ts +70 -11
  297. package/src/interactive/council-dispatch.ts +30 -0
  298. package/src/interactive/council-grid.ts +213 -0
  299. package/src/interactive/council.ts +99 -0
  300. package/src/interactive/dispatch-board.ts +471 -50
  301. package/src/interactive/fleet-run-preview.ts +307 -0
  302. package/src/interactive/footer/notifications.ts +219 -0
  303. package/src/interactive/footer/widgets.ts +13 -0
  304. package/src/interactive/handoff-round.ts +56 -0
  305. package/src/interactive/interactive-application.ts +49 -2
  306. package/src/interactive/interactive-event-projection.ts +9 -1
  307. package/src/interactive/interactive-input-runtime.ts +11 -1
  308. package/src/interactive/interactive-presentation.ts +11 -1
  309. package/src/interactive/interactive-slash-runtime.ts +52 -2
  310. package/src/interactive/interactive-subscriptions.ts +14 -2
  311. package/src/interactive/memory-overlay.ts +89 -4
  312. package/src/interactive/oracle.ts +179 -0
  313. package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
  314. package/src/interactive/overlay-frame.ts +5 -2
  315. package/src/interactive/overlay-general-openers.ts +230 -2
  316. package/src/interactive/overlay-key-routing.ts +58 -2
  317. package/src/interactive/overlay-lifecycle.ts +52 -5
  318. package/src/interactive/overlay-permission-lifecycle.ts +33 -8
  319. package/src/interactive/overlay-resource-openers.ts +11 -3
  320. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  321. package/src/interactive/overlay-transitions.ts +11 -0
  322. package/src/interactive/overlays/ask-user.ts +74 -30
  323. package/src/interactive/overlays/decisions.ts +3 -1
  324. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  325. package/src/interactive/overlays/handoff-review.ts +185 -0
  326. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  327. package/src/interactive/overlays/list-overlay.ts +168 -2
  328. package/src/interactive/overlays/settings.ts +101 -4
  329. package/src/interactive/overlays/side-question.ts +139 -0
  330. package/src/interactive/overlays/skills-hub.ts +401 -15
  331. package/src/interactive/permission-hint.ts +35 -0
  332. package/src/interactive/permission-overlay.ts +95 -45
  333. package/src/interactive/renderers/tool-execution.ts +19 -49
  334. package/src/interactive/session-last-turn.ts +8 -1
  335. package/src/interactive/session-usage-reseed.ts +36 -10
  336. package/src/interactive/side-question.ts +171 -0
  337. package/src/interactive/slash-commands.ts +434 -7
  338. package/src/interactive/slash-spec.ts +19 -6
  339. package/src/interactive/status/summary.ts +5 -0
  340. package/src/interactive/status/types.ts +5 -0
  341. package/src/interactive/terminal-lease.ts +1 -0
  342. package/src/interactive/theme/tokens.ts +30 -0
  343. package/src/interactive/turn-context.ts +96 -23
  344. package/src/interactive/turn-middleware.ts +16 -1
  345. package/src/interactive/turn-runtime.ts +37 -8
  346. package/src/interactive/turn-state.ts +3 -0
  347. package/src/interactive/watchdog-run.ts +75 -0
  348. package/src/interactive/worker-progress.ts +440 -0
  349. package/src/interactive/worker-share.ts +56 -1
  350. package/src/interactive/worker-stream.ts +58 -110
  351. package/src/tools/agent-tools.ts +28 -3
  352. package/src/tools/ask-user.ts +21 -1
  353. package/src/tools/bootstrap.ts +3 -0
  354. package/src/tools/compete-worktrees.ts +13 -79
  355. package/src/tools/context/index.ts +2 -2
  356. package/src/tools/dispatch-admission.ts +242 -8
  357. package/src/tools/dispatch-arguments.ts +65 -1
  358. package/src/tools/dispatch-event-text.ts +19 -0
  359. package/src/tools/dispatch-plan.ts +136 -6
  360. package/src/tools/dispatch-runner.ts +319 -13
  361. package/src/tools/dispatch-types.ts +20 -1
  362. package/src/tools/dispatch.ts +96 -3
  363. package/src/tools/monitor.ts +31 -0
  364. package/src/tools/profiles.ts +18 -4
  365. package/src/tools/registry.ts +15 -5
  366. package/src/tools/result-disposition.ts +156 -0
  367. package/src/tools/result-shaping.ts +59 -1
  368. package/src/tools/task-worktree.ts +238 -0
  369. package/src/tools/verify/authoring.ts +116 -55
  370. package/src/tools/verify/scripts.ts +62 -0
  371. package/src/tools/worker-evidence.ts +21 -1
  372. package/src/worker/spec-contract.ts +44 -3
  373. package/dist/chunk-EFADSJET.js +0 -18
  374. package/dist/chunk-HC4CLZ2Y.js +0 -68
  375. package/dist/memory-4ALKDJ4Q.js +0 -246
  376. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -48,21 +48,15 @@ function collapseWhitespace(text: string): string {
48
48
  }
49
49
 
50
50
  /**
51
- * Pull the first non-empty piece of user-authored text out of a payload.
52
- * Structured message payloads may contain a bare string, a text property,
53
- * or pi-ai content blocks.
51
+ * Pull the first non-empty piece of text out of a message payload. Structured
52
+ * message payloads may contain a bare string, a text property, or pi-ai
53
+ * content blocks.
54
54
  */
55
- function extractUserText(payload: unknown): string | null {
56
- if (typeof payload === "string") {
57
- const collapsed = collapseWhitespace(payload);
58
- return collapsed.length > 0 ? collapsed : null;
59
- }
55
+ function rawMessageText(payload: unknown): string | null {
56
+ if (typeof payload === "string") return payload.trim().length > 0 ? payload : null;
60
57
  if (!payload || typeof payload !== "object") return null;
61
58
  const obj = payload as Record<string, unknown>;
62
- if (typeof obj.text === "string") {
63
- const collapsed = collapseWhitespace(obj.text);
64
- if (collapsed.length > 0) return collapsed;
65
- }
59
+ if (typeof obj.text === "string" && obj.text.trim().length > 0) return obj.text;
66
60
  if (Array.isArray(obj.content)) {
67
61
  const parts: string[] = [];
68
62
  for (const part of obj.content) {
@@ -75,14 +69,65 @@ function extractUserText(payload: unknown): string | null {
75
69
  parts.push((part as { text: string }).text);
76
70
  }
77
71
  }
78
- const joined = collapseWhitespace(parts.join(" "));
79
- if (joined.length > 0) return joined;
72
+ const joined = parts.join("\n");
73
+ if (joined.trim().length > 0) return joined;
80
74
  }
81
75
  return null;
82
76
  }
83
77
 
78
+ function extractMessageText(payload: unknown): string | null {
79
+ const raw = rawMessageText(payload);
80
+ if (raw === null) return null;
81
+ const collapsed = collapseWhitespace(raw);
82
+ return collapsed.length > 0 ? collapsed : null;
83
+ }
84
+
85
+ /**
86
+ * What the operator typed for a user turn, for the picker preview.
87
+ *
88
+ * The persisted text is the composed prompt: a `<system-reminder>` block and
89
+ * any `[Skill request]` preamble ride ahead of the operator's words as visible
90
+ * text the model receives. Reading that verbatim made every row of the
91
+ * `/resume` picker read `<system-reminder> [Skills] 9 installed…`, so sessions
92
+ * were indistinguishable and the type-to-filter matched them all (issue #188).
93
+ * Entries written since the operator text was persisted carry it as
94
+ * `operatorText`; older ones drop the leading scaffolding. Null when nothing
95
+ * operator-authored remains, so the caller moves on to the next turn.
96
+ */
97
+ function operatorTextOfUserPayload(payload: unknown): string | null {
98
+ if (
99
+ payload &&
100
+ typeof payload === "object" &&
101
+ typeof (payload as { operatorText?: unknown }).operatorText === "string"
102
+ ) {
103
+ const collapsed = collapseWhitespace((payload as { operatorText: string }).operatorText);
104
+ return collapsed.length > 0 ? collapsed : null;
105
+ }
106
+ // Strip before collapsing: the preamble's line structure is what the
107
+ // patterns anchor on.
108
+ const text = rawMessageText(payload);
109
+ if (text === null) return null;
110
+ const stripped = collapseWhitespace(stripInjectedPreamble(text));
111
+ return stripped.length > 0 ? stripped : null;
112
+ }
113
+
114
+ const LEADING_SYSTEM_REMINDER = /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*/u;
115
+ const LEADING_SKILL_REQUEST =
116
+ /^\s*\[Skill request\][\s\S]*?Only these pending skill names are allowed this turn\.[^\n]*\s*/u;
117
+
118
+ function stripInjectedPreamble(text: string): string {
119
+ let current = text;
120
+ for (;;) {
121
+ const next = current.replace(LEADING_SYSTEM_REMINDER, "").replace(LEADING_SKILL_REQUEST, "");
122
+ if (next === current) return current;
123
+ current = next;
124
+ }
125
+ }
126
+
84
127
  interface ScanResult {
85
128
  firstUserMessage: string | null;
129
+ /** The first assistant text, the preview when no turn carries operator words. */
130
+ firstAssistantMessage: string | null;
86
131
  messageCount: number;
87
132
  lastTimestamp: string | null;
88
133
  name: string | null;
@@ -92,6 +137,7 @@ interface ScanResult {
92
137
  function scanCurrentJsonl(currentPath: string): ScanResult {
93
138
  const result: ScanResult = {
94
139
  firstUserMessage: null,
140
+ firstAssistantMessage: null,
95
141
  messageCount: 0,
96
142
  lastTimestamp: null,
97
143
  name: null,
@@ -106,10 +152,14 @@ function scanCurrentJsonl(currentPath: string): ScanResult {
106
152
  if (parsed.kind === "message" && parsed.role === "user") {
107
153
  result.messageCount += 1;
108
154
  if (result.firstUserMessage === null) {
109
- const text = extractUserText(parsed.payload);
155
+ const text = operatorTextOfUserPayload(parsed.payload);
110
156
  if (text !== null) result.firstUserMessage = text;
111
157
  }
112
158
  }
159
+ if (parsed.kind === "message" && parsed.role === "assistant" && result.firstAssistantMessage === null) {
160
+ const text = extractMessageText(parsed.payload);
161
+ if (text !== null) result.firstAssistantMessage = text;
162
+ }
113
163
  if (parsed.kind === "sessionInfo") {
114
164
  const info = parsed as SessionInfoEntry;
115
165
  if (info.name !== undefined) {
@@ -139,11 +189,10 @@ function enrichMetaForListing(meta: SessionMeta, currentPath: string): void {
139
189
  if (scan.name) meta.name = scan.name;
140
190
  const labelValues = [...scan.labels.values()].map((entry) => entry.label.trim()).filter((label) => label.length > 0);
141
191
  if (labelValues.length > 0) meta.labels = labelValues;
142
- if (scan.firstUserMessage) {
192
+ const preview = scan.firstUserMessage ?? scan.firstAssistantMessage;
193
+ if (preview) {
143
194
  meta.firstMessagePreview =
144
- scan.firstUserMessage.length > PREVIEW_MAX_CHARS
145
- ? `${scan.firstUserMessage.slice(0, PREVIEW_MAX_CHARS - 1)}…`
146
- : scan.firstUserMessage;
195
+ preview.length > PREVIEW_MAX_CHARS ? `${preview.slice(0, PREVIEW_MAX_CHARS - 1)}…` : preview;
147
196
  }
148
197
  if (scan.messageCount > 0) meta.messageCount = scan.messageCount;
149
198
  const fallbackMtime = readMtimeIso(currentPath);
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Fold a session ledger's assistant turns into the per-call usage the rest of
3
3
  * Clio reports: tokens in and out, cache reads and writes, reasoning tokens,
4
- * and provider-reported cost, attributed to the target and wire model that
5
- * served each call.
4
+ * and provider-reported cost, attributed to the target and observed model id
5
+ * for each call.
6
6
  *
7
7
  * This lives in the session domain because the ledger is the only durable
8
8
  * record of what a session spent, and three surfaces read it: the `/cost`
@@ -19,12 +19,22 @@
19
19
  * counting it would add an API call worth nothing.
20
20
  */
21
21
 
22
+ import {
23
+ attributedModelId,
24
+ type ResponseModelIdObservation,
25
+ responseModelIdObservationFromRecord,
26
+ } from "../../core/response-model-id.js";
22
27
  import type { SessionEntry } from "./entries.js";
23
28
 
24
29
  /** One completed assistant API call, as the ledger recorded it. */
25
30
  export interface LedgerUsageCall {
26
31
  providerId: string;
27
- modelId: string;
32
+ /** Model id the accounting row uses, or `unknown` when an observed response omitted it. */
33
+ attributedModelId: string;
34
+ /** The id the session asked for (the configured model, or the message's own `model`). */
35
+ requestedModelId: string;
36
+ /** Direct presence observation, with the pre-#193 difference-only shape labeled separately. */
37
+ responseModelIdObservation: ResponseModelIdObservation;
28
38
  input: number;
29
39
  output: number;
30
40
  cacheRead: number;
@@ -71,8 +81,8 @@ function stringAt(source: Record<string, unknown>, ...keys: string[]): string |
71
81
  * payload instead split one endpoint into two blocks in `/cost`, so a single
72
82
  * `node-a` target on `llamacpp` rendered as two providers whose turn counts
73
83
  * diverged with every resume. `modelChange` rows are replayed in order so a
74
- * session that switched targets mid-way attributes each call to the one that
75
- * served it.
84
+ * session that switched targets mid-way attributes each call to the active
85
+ * target.
76
86
  */
77
87
  export function ledgerUsageCalls(
78
88
  entries: ReadonlyArray<SessionEntry>,
@@ -92,7 +102,9 @@ export function ledgerUsageCalls(
92
102
  if (!usage || usage.totalTokens <= 0) continue;
93
103
  calls.push({
94
104
  providerId: currentTarget ?? "unknown",
95
- modelId: currentModel ?? "unknown",
105
+ attributedModelId: currentModel ?? "unknown",
106
+ requestedModelId: currentModel ?? "unknown",
107
+ responseModelIdObservation: { state: "not-observed" },
96
108
  input: usage.input,
97
109
  output: usage.output,
98
110
  cacheRead: usage.cacheRead,
@@ -132,9 +144,14 @@ export function ledgerUsageCalls(
132
144
  if (totalTokens === 0) continue;
133
145
  const cost = usage.cost;
134
146
  const costUsd = cost && typeof cost === "object" ? numberAt(cost as Record<string, unknown>, "total") : 0;
147
+ const requestedModelId = currentModel ?? stringAt(record, "model") ?? "unknown";
148
+ const differingResponseModelId = stringAt(record, "responseModel");
149
+ const responseModelIdObservation = responseModelIdObservationFromRecord(record, "legacy-difference-only");
135
150
  calls.push({
136
151
  providerId: currentTarget ?? stringAt(record, "provider", "api") ?? "unknown",
137
- modelId: currentModel ?? stringAt(record, "responseModel", "model") ?? "unknown",
152
+ attributedModelId: attributedModelId(responseModelIdObservation, requestedModelId, differingResponseModelId),
153
+ requestedModelId,
154
+ responseModelIdObservation,
138
155
  input,
139
156
  output,
140
157
  cacheRead,
@@ -11,9 +11,16 @@ import {
11
11
  safeResourceWrite,
12
12
  } from "../../core/safe-resource-write.js";
13
13
  import { clioConfigDir, resolveClioDirs } from "../../core/xdg.js";
14
+ import { parseFrontmatter } from "../agents/frontmatter.js";
15
+ import {
16
+ assertAgentSpecPolicy,
17
+ normalizeAgentSpec,
18
+ parseAgentRecipeSchema,
19
+ parseFleetContract,
20
+ } from "../agents/index.js";
14
21
 
15
22
  export type ShareScope = "project" | "user";
16
- export type ShareEntryType = "project-context" | "prompt" | "skill" | "settings" | "extension";
23
+ export type ShareEntryType = "project-context" | "prompt" | "skill" | "agent" | "fleet" | "settings" | "extension";
17
24
 
18
25
  export interface ShareArchiveFile {
19
26
  type: ShareEntryType;
@@ -61,6 +68,8 @@ export interface ShareExportOptions {
61
68
  includeContext?: boolean;
62
69
  includePrompts?: boolean;
63
70
  includeSkills?: boolean;
71
+ includeAgents?: boolean;
72
+ includeFleets?: boolean;
64
73
  includeSettings?: boolean;
65
74
  includeExtensions?: boolean;
66
75
  }
@@ -206,7 +215,13 @@ function defaultedIncludes(
206
215
  ): Required<
207
216
  Pick<
208
217
  ShareExportOptions,
209
- "includeContext" | "includePrompts" | "includeSkills" | "includeSettings" | "includeExtensions"
218
+ | "includeContext"
219
+ | "includePrompts"
220
+ | "includeSkills"
221
+ | "includeAgents"
222
+ | "includeFleets"
223
+ | "includeSettings"
224
+ | "includeExtensions"
210
225
  >
211
226
  > {
212
227
  // True when the caller named at least one include flag explicitly. In that
@@ -216,12 +231,16 @@ function defaultedIncludes(
216
231
  options.includeContext !== undefined ||
217
232
  options.includePrompts !== undefined ||
218
233
  options.includeSkills !== undefined ||
234
+ options.includeAgents !== undefined ||
235
+ options.includeFleets !== undefined ||
219
236
  options.includeSettings !== undefined ||
220
237
  options.includeExtensions !== undefined;
221
238
  return {
222
239
  includeContext: hasExplicitInclude ? options.includeContext === true : true,
223
240
  includePrompts: hasExplicitInclude ? options.includePrompts === true : true,
224
241
  includeSkills: hasExplicitInclude ? options.includeSkills === true : true,
242
+ includeAgents: hasExplicitInclude ? options.includeAgents === true : true,
243
+ includeFleets: hasExplicitInclude ? options.includeFleets === true : true,
225
244
  includeSettings: hasExplicitInclude ? options.includeSettings === true : true,
226
245
  includeExtensions: hasExplicitInclude ? options.includeExtensions === true : true,
227
246
  };
@@ -263,6 +282,18 @@ export function createShareArchive(options: ShareExportOptions = {}): ClioShareA
263
282
  addTree(files, "skill", scope, root, `${scope}/skills`);
264
283
  }
265
284
  }
285
+ if (includes.includeAgents) {
286
+ for (const scope of scopes) {
287
+ const root = scope === "user" ? path.join(clioConfigDir(), "agents") : path.join(cwd, ".clio-coder", "agents");
288
+ addTree(files, "agent", scope, root, `${scope}/agents`);
289
+ }
290
+ }
291
+ if (includes.includeFleets) {
292
+ for (const scope of scopes) {
293
+ const root = scope === "user" ? path.join(clioConfigDir(), "fleets") : path.join(cwd, ".clio-coder", "fleets");
294
+ addTree(files, "fleet", scope, root, `${scope}/fleets`);
295
+ }
296
+ }
266
297
  if (includes.includeExtensions) {
267
298
  for (const scope of scopes) {
268
299
  const root =
@@ -313,9 +344,19 @@ function parseArchive(raw: unknown): ClioShareArchive {
313
344
  throw new Error("share archive manifest is malformed");
314
345
  }
315
346
  const archive = raw as unknown as ClioShareArchive;
347
+ const allowedTypes = new Set<ShareEntryType>([
348
+ "project-context",
349
+ "prompt",
350
+ "skill",
351
+ "agent",
352
+ "fleet",
353
+ "settings",
354
+ "extension",
355
+ ]);
316
356
  for (const file of archive.files) {
317
357
  if (
318
358
  !isRecord(file) ||
359
+ !allowedTypes.has(file.type as ShareEntryType) ||
319
360
  typeof file.archivePath !== "string" ||
320
361
  typeof file.relativePath !== "string" ||
321
362
  typeof file.sha256 !== "string" ||
@@ -392,6 +433,10 @@ function targetRootForFile(entry: ShareArchiveFile, options: ShareImportOptions)
392
433
  return scope === "user"
393
434
  ? { root: path.join(config, "skills"), containmentRoot: config, scope }
394
435
  : { root: path.join(cwd, ".clio-coder", "skills"), containmentRoot: cwd, scope };
436
+ case "agent":
437
+ return { root: path.join(config, "agents"), containmentRoot: config, scope: "user" };
438
+ case "fleet":
439
+ return { root: path.join(config, "fleets"), containmentRoot: config, scope: "user" };
395
440
  case "extension":
396
441
  return scope === "user"
397
442
  ? { root: path.join(config, "extensions"), containmentRoot: config, scope }
@@ -563,6 +608,26 @@ function prepareShareImport(filePath: string, options: ShareImportOptions = {}):
563
608
  }
564
609
  for (const targetInfo of preflight.targets) {
565
610
  const { entry, target, scope, buffer } = targetInfo;
611
+ if (entry.type === "fleet") {
612
+ try {
613
+ parseFleetContract(buffer.toString("utf8"), target);
614
+ } catch (error) {
615
+ diagnostics.push({ type: "error", message: error instanceof Error ? error.message : String(error), path: target });
616
+ continue;
617
+ }
618
+ }
619
+ if (entry.type === "agent") {
620
+ try {
621
+ const parsed = parseFrontmatter(buffer.toString("utf8"), target);
622
+ const id = path.basename(target, ".md");
623
+ assertAgentSpecPolicy(
624
+ normalizeAgentSpec(parseAgentRecipeSchema({ id, source: "user", filepath: target, ...parsed })),
625
+ );
626
+ } catch (error) {
627
+ diagnostics.push({ type: "error", message: error instanceof Error ? error.message : String(error), path: target });
628
+ continue;
629
+ }
630
+ }
566
631
  if (entry.type === "settings") {
567
632
  diagnostics.push(...settingsPlan(buffer, options));
568
633
  actions.push({ action: "settings", type: entry.type, scope, path: target });
@@ -1,4 +1,5 @@
1
1
  import { performance } from "node:perf_hooks";
2
+ import { describeCallAction } from "../../domains/safety/call-target.js";
2
3
  import type { AgentEvent, AgentMessage } from "../types.js";
3
4
  import type { ClioWorkerEvent } from "../worker-events.js";
4
5
  import type { AcpPromptResponse, AcpSessionUpdateParams, AcpToolCallUpdate } from "./types.js";
@@ -111,12 +112,17 @@ export class AcpEventMapper {
111
112
  // keeps the monotonic twin, which is what the duration below spans.
112
113
  const startedAt = Date.now();
113
114
  this.toolStarts.set(toolCallId, performance.now());
115
+ // A peer's `rawInput` is its own argument object and never reaches an
116
+ // operator surface. The descriptor composed from it here does.
117
+ const action = describeCallAction(update.kind ?? title, update.rawInput);
114
118
  out.push({
115
119
  type: "clio_tool_start",
116
120
  payload: {
117
121
  tool: update.kind ?? title,
122
+ toolCallId,
118
123
  posture: "operating",
119
124
  startedAt,
125
+ ...(action !== null ? { action } : {}),
120
126
  },
121
127
  });
122
128
  out.push({
@@ -142,6 +148,7 @@ export class AcpEventMapper {
142
148
  type: "clio_tool_finish",
143
149
  payload: {
144
150
  tool: update.kind ?? title,
151
+ toolCallId,
145
152
  posture: "operating",
146
153
  durationMs: Math.round(performance.now() - startedAtClock),
147
154
  outcome: isError ? "error" : "ok",
@@ -5,12 +5,18 @@ import { BusChannels, type LoopBlockedPayload } from "../../core/bus-events.js";
5
5
  import { DEFAULT_DELEGATION_PERMISSION_TIMEOUT_MS } from "../../core/defaults.js";
6
6
  import type { SafeEventBus } from "../../core/event-bus.js";
7
7
  import { MAX_TIMER_DELAY_MS } from "../../core/timers.js";
8
+ import { ToolNames } from "../../core/tool-names.js";
8
9
  import type { ProvidersContract } from "../../domains/providers/contract.js";
9
10
  import { isOrchestratorEligibleRuntime } from "../../domains/providers/eligibility.js";
10
11
  import { type AutonomyLevel, DEFAULT_AUTONOMY_LEVEL } from "../../domains/safety/autonomy.js";
12
+ import {
13
+ classifyDecisionPresentation,
14
+ decisionFactsForPermission,
15
+ } from "../../domains/safety/decision-presentation.js";
11
16
  import type { SessionContract, SessionMeta } from "../../domains/session/contract.js";
12
17
  import type { MessageEntry, SessionEntry } from "../../domains/session/entries.js";
13
18
  import { filterEntriesToActivePath } from "../../domains/session/tree/active-path.js";
19
+ import { askUserExposure } from "../../tools/ask-user.js";
14
20
  import type { ToolRegistry } from "../../tools/registry.js";
15
21
  import { toolResultPresentationText } from "../../tools/result-disposition.js";
16
22
  import type { AgentMessage } from "../types.js";
@@ -1265,6 +1271,19 @@ function installPermissionBridge(input: {
1265
1271
  const queuedRequestDetails = new Map<string, { tool: string; actionClass: string }>();
1266
1272
  const unregister = input.toolRegistry.onPermissionRequired((call, decision, meta) => {
1267
1273
  if (queuedRequestIds.has(meta.requestId)) return;
1274
+ const presentation = classifyDecisionPresentation(
1275
+ decisionFactsForPermission({
1276
+ tool: call.tool,
1277
+ actionClass: decision.classification.actionClass,
1278
+ axis: meta.axis.startsWith("net:")
1279
+ ? { kind: "safety-net", ruleId: meta.axis.slice("net:".length) || "unknown" }
1280
+ : { kind: "autonomy", level: meta.axis.slice("autonomy:".length) || DEFAULT_AUTONOMY_LEVEL },
1281
+ origin: { kind: "main" },
1282
+ ...(call.tool === ToolNames.AskUser ? { exposure: askUserExposure(call.args) } : {}),
1283
+ }),
1284
+ );
1285
+ const approveAction = presentation.requiredActions.find((action) => action.id === "approve-once");
1286
+ const denyAction = presentation.requiredActions.find((action) => action.id === "deny");
1268
1287
  queuedRequestIds.add(meta.requestId);
1269
1288
  queuedRequestDetails.set(meta.requestId, {
1270
1289
  tool: call.tool,
@@ -1381,8 +1400,16 @@ function installPermissionBridge(input: {
1381
1400
  ...(snapshot.locations !== undefined ? { locations: snapshot.locations } : {}),
1382
1401
  },
1383
1402
  options: [
1384
- { optionId: "allow-once", name: "Allow once", kind: "allow_once" },
1385
- { optionId: "reject-once", name: "Reject", kind: "reject_once" },
1403
+ {
1404
+ optionId: "allow-once",
1405
+ name: approveAction?.label ?? "Approve once",
1406
+ kind: "allow_once",
1407
+ },
1408
+ {
1409
+ optionId: "reject-once",
1410
+ name: denyAction?.label ?? "Deny this request",
1411
+ kind: "reject_once",
1412
+ },
1386
1413
  ],
1387
1414
  },
1388
1415
  input.permissionTimeoutMs,
@@ -130,6 +130,25 @@ async function loadOwnedInstance(
130
130
  return instanceId;
131
131
  }
132
132
 
133
+ /**
134
+ * Residency runs before every request, and the same co-residency facts held
135
+ * every turn, so the peer warning printed once per turn (issue #185). Each
136
+ * distinct fact is said once per process; a new target, model, or peer set is
137
+ * a new fact.
138
+ */
139
+ const announcedResidencyFacts = new Set<string>();
140
+
141
+ function emitResidencyNoticeOnce(key: string, notice: Parameters<typeof emitResidencyNotice>[0]): void {
142
+ if (announcedResidencyFacts.has(key)) return;
143
+ announcedResidencyFacts.add(key);
144
+ emitResidencyNotice(notice);
145
+ }
146
+
147
+ /** Test seam: forget which residency facts were announced. */
148
+ export function resetAnnouncedResidencyFacts(): void {
149
+ announcedResidencyFacts.clear();
150
+ }
151
+
133
152
  export async function ensureLmStudioResidency(
134
153
  model: Model<"openai-completions">,
135
154
  options: { apiKey?: string; signal?: AbortSignal } = {},
@@ -155,7 +174,7 @@ export async function ensureLmStudioResidency(
155
174
  );
156
175
  }
157
176
  if (resolution.wireModelId !== model.id) {
158
- emitResidencyNotice({
177
+ emitResidencyNoticeOnce(`resolved|${info.targetId}|${model.id}|${resolution.wireModelId}`, {
159
178
  kind: "co-resident",
160
179
  level: "info",
161
180
  targetId: info.targetId,
@@ -166,14 +185,16 @@ export async function ensureLmStudioResidency(
166
185
  });
167
186
  }
168
187
  if (resolution.peerTargets.length > 0) {
169
- emitResidencyNotice({
188
+ const peers = resolution.peerTargets.join(", ");
189
+ const requested = resolution.wireModelId === model.id ? "" : ` (requested '${model.id}')`;
190
+ emitResidencyNoticeOnce(`peer|${info.targetId}|${model.id}|${resolution.wireModelId}|${peers}`, {
170
191
  kind: "co-resident",
171
192
  level: "warning",
172
193
  targetId: info.targetId,
173
194
  runtimeId: "lmstudio",
174
195
  model: resolution.wireModelId,
175
- message: `LM Studio instance '${resolution.wireModelId}' is also loaded on ${resolution.peerTargets.join(", ")}; this request may be served by that LM Link peer.`,
176
- detail: { peerTargets: resolution.peerTargets.join(", ") },
196
+ message: `LM Studio instance '${resolution.wireModelId}'${requested} is also loaded on ${peers}; a request may be served by that LM Link peer, and the footer and usage ledger name the id that answered when it differs.`,
197
+ detail: { requestedModel: model.id, wireModel: resolution.wireModelId, peerTargets: peers },
177
198
  });
178
199
  }
179
200
  if (!load || Object.keys(load).length === 0 || resolution.instance) return resolution.wireModelId;