@iowarp/clio-coder 0.3.7 → 0.3.8

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 (223) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +7 -2
  3. package/dist/{acp-SK4MD6MM.js → acp-U67UHUK2.js} +13 -13
  4. package/dist/{agents-2FN2K6ME.js → agents-YU6SGALZ.js} +35 -33
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-QIYZWM5I.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-YD734TPH.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-WJHBC77E.js → chunk-2HFZQUHL.js} +5 -5
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-EOOQZZDE.js → chunk-4SPRNWDE.js} +18 -18
  13. package/dist/{chunk-EBEFWSGL.js → chunk-5DHKRSMQ.js} +7 -7
  14. package/dist/{chunk-OBMAI2DP.js → chunk-5H3GB5BO.js} +11 -9
  15. package/dist/{chunk-JTSEDYVQ.js → chunk-5Q2VVUKB.js} +7 -7
  16. package/dist/{chunk-VQNODYQ4.js → chunk-7RFXX52T.js} +294 -3704
  17. package/dist/{chunk-XEGB6BCN.js → chunk-7RGZWPB6.js} +5 -5
  18. package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
  19. package/dist/{chunk-CEYBNUGC.js → chunk-A3WNZD3P.js} +323 -27
  20. package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
  21. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  22. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  23. package/dist/{chunk-KCMKRQX4.js → chunk-E77JEWSD.js} +42 -49
  24. package/dist/{chunk-J7PIKKWC.js → chunk-EMYUUSFG.js} +7 -7
  25. package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
  26. package/dist/{chunk-UVDSQ6LW.js → chunk-FBVTI2TJ.js} +74 -28
  27. package/dist/{chunk-BMWK7ZIZ.js → chunk-FHJEP5SW.js} +13 -13
  28. package/dist/{chunk-FOT2FX5J.js → chunk-GN57SG4G.js} +7 -7
  29. package/dist/{chunk-SROCI7ZU.js → chunk-GPIEI3LY.js} +5 -5
  30. package/dist/{chunk-UFQ3F4FW.js → chunk-GU2UIAFZ.js} +4 -4
  31. package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
  32. package/dist/{chunk-VREKEFLL.js → chunk-HLE42MG7.js} +3 -3
  33. package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
  34. package/dist/{chunk-LADCF22A.js → chunk-IGWKHNIQ.js} +113 -54
  35. package/dist/chunk-IIZWH4XA.js +172 -0
  36. package/dist/{chunk-UHXRNZ2J.js → chunk-IJ7RPIYJ.js} +4 -4
  37. package/dist/{chunk-M4AKACEO.js → chunk-J3YUBZWY.js} +2 -2
  38. package/dist/{chunk-AB4XIIVB.js → chunk-JOZYP4GM.js} +6 -6
  39. package/dist/{chunk-ZGH7FGS5.js → chunk-K4XHGFR5.js} +742 -12
  40. package/dist/{chunk-EELBMBT6.js → chunk-KTYTFRMB.js} +62 -5
  41. package/dist/chunk-LU7P4LHA.js +33 -0
  42. package/dist/{chunk-DQA7QLMD.js → chunk-ME6CCNFO.js} +9 -24
  43. package/dist/{chunk-KZ2H5X4G.js → chunk-MXKJU4JB.js} +88 -14
  44. package/dist/{chunk-DMD2AGVS.js → chunk-N22QMJKY.js} +20 -18
  45. package/dist/chunk-NMPKI6XL.js +3006 -0
  46. package/dist/chunk-NUGM5KR6.js +165 -0
  47. package/dist/{chunk-5C77SEEY.js → chunk-P43ETTHK.js} +2 -2
  48. package/dist/{chunk-JNXPYBB4.js → chunk-PMDBGQSJ.js} +2 -2
  49. package/dist/chunk-PT7HYKEM.js +165 -0
  50. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  51. package/dist/{chunk-C4JBQ5SR.js → chunk-TANS5ZJS.js} +6 -6
  52. package/dist/{chunk-YTYFXUI3.js → chunk-TB5666IT.js} +9 -9
  53. package/dist/{chunk-UUANF5CR.js → chunk-TLQJPP24.js} +375 -687
  54. package/dist/{chunk-OB5HIGJY.js → chunk-TT36MB5S.js} +2 -1
  55. package/dist/{chunk-DJNLUABN.js → chunk-TTHACPOM.js} +135 -17
  56. package/dist/{chunk-6M7VS3J3.js → chunk-TYPGUK6W.js} +4 -4
  57. package/dist/{chunk-THKY7CD7.js → chunk-U6MBIEMB.js} +108 -24
  58. package/dist/{chunk-PD3MESLB.js → chunk-VAWNZU7Z.js} +4 -4
  59. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  60. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  61. package/dist/{chunk-MXI6J5JF.js → chunk-VWZOAB7K.js} +10 -10
  62. package/dist/{chunk-QCTRSGHQ.js → chunk-WLFILSD5.js} +36 -36
  63. package/dist/{chunk-5UJ6ECTS.js → chunk-WNIJTQQK.js} +6 -6
  64. package/dist/{chunk-4DWFMQDR.js → chunk-WSB3FPX7.js} +68 -20
  65. package/dist/{chunk-3HAPLH5M.js → chunk-WWCZ5F23.js} +112 -10
  66. package/dist/{chunk-X2KV5FXT.js → chunk-WXY7KU3G.js} +2 -2
  67. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  68. package/dist/{chunk-DOOEX22V.js → chunk-XWSF374K.js} +4 -4
  69. package/dist/{chunk-6TUKSZVF.js → chunk-YS5VLNH5.js} +8 -8
  70. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  71. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  72. package/dist/cli/index.js +31 -31
  73. package/dist/{clio-WBVQEBKO.js → clio-QVTYJ57A.js} +8 -8
  74. package/dist/code-nav-FGGFIE7L.js +2 -2
  75. package/dist/codewiki/build-worker.js +4 -4
  76. package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
  77. package/dist/{config-TRBL3RCF.js → config-LW5IJFQN.js} +58 -53
  78. package/dist/{configure-OLCVPHNM.js → configure-7XIZCOU4.js} +26 -21
  79. package/dist/{context-XEWE3MOJ.js → context-L3WL3X7K.js} +47 -41
  80. package/dist/{context-WFPKQSM6.js → context-N52ZA626.js} +22 -22
  81. package/dist/{context-MJIJ6GOX.js → context-Y6Y7QPR6.js} +12 -12
  82. package/dist/{context-clear-KNOS2JPB.js → context-clear-MBQRLSDQ.js} +47 -41
  83. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  84. package/dist/{context-working-set-EUXAZI6N.js → context-working-set-GS6DSO7F.js} +14 -14
  85. package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-22ZCNOM3.js} +83 -55
  86. package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
  87. package/dist/{doctor-RN4YKO2X.js → doctor-M7YEDGAE.js} +24 -20
  88. package/dist/{eval-RUBJVSNQ.js → eval-BEC2WHDA.js} +71 -19
  89. package/dist/{evidence-JZNBUOQZ.js → evidence-REJUMSKM.js} +62 -56
  90. package/dist/{evolve-FJVC4KKI.js → evolve-PY5ZBA5K.js} +41 -35
  91. package/dist/{extensions-IQL36S7K.js → extensions-HVKU65YU.js} +6 -6
  92. package/dist/{fleet-BDKYJFCP.js → fleet-7WZEWRFA.js} +72 -67
  93. package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-UVHWM76J.js} +10 -10
  94. package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-6ULH7PES.js} +13 -13
  95. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  96. package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-72PC4SLA.js} +17 -17
  97. package/dist/{init-LQUB5COQ.js → init-OG3TPGQG.js} +61 -56
  98. package/dist/{library-NJAHIGG4.js → library-CNTMPLRF.js} +18 -18
  99. package/dist/{memory-OG6HOYKM.js → memory-6IS7F275.js} +43 -37
  100. package/dist/{models-5ZG5XY7J.js → models-ENRJDA5W.js} +33 -28
  101. package/dist/{monitor-TJ7AMTGB.js → monitor-XLDVO7TN.js} +56 -38
  102. package/dist/{orchestrator-WZYB54DM.js → orchestrator-6KSPYRHA.js} +421 -303
  103. package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
  104. package/dist/registry-LG64LTF4.js +11 -0
  105. package/dist/{reset-PXQT45IY.js → reset-RZ4ER727.js} +10 -10
  106. package/dist/{run-FQ74YF62.js → run-Y2CNK5RU.js} +79 -74
  107. package/dist/{share-FW7SVCL3.js → share-A55GYP6Z.js} +16 -16
  108. package/dist/{skills-7E7IRB3R.js → skills-ALC5J6AT.js} +19 -19
  109. package/dist/{skills-eval-LI75W6OK.js → skills-eval-JPBEBYQU.js} +52 -47
  110. package/dist/support-MIETYA5E.js +38 -0
  111. package/dist/{targets-4CIFKCTW.js → targets-VGNXIR3S.js} +42 -33
  112. package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-WOBR64YA.js} +3 -3
  113. package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
  114. package/dist/{upgrade-K2HVIVMQ.js → upgrade-FUSUAGHR.js} +27 -25
  115. package/dist/{usage-GTZELZQX.js → usage-N4MKVHKD.js} +51 -46
  116. package/dist/{verifiers-RLAHT27O.js → verifiers-YAWOJ3H2.js} +13 -13
  117. package/dist/{verify-BX3BRKH5.js → verify-LTDHYBGY.js} +9 -9
  118. package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-6M7GHTBJ.js} +66 -61
  119. package/dist/worker/entry.js +68 -61
  120. package/docs/alcf-provider.md +1 -1
  121. package/docs/architecture.md +1 -1
  122. package/docs/artifact-versions.md +6 -5
  123. package/docs/built-in-agents.md +1 -1
  124. package/docs/commands-and-modes.md +2 -2
  125. package/docs/configuration-and-targets.md +8 -3
  126. package/docs/context-engine.md +1 -1
  127. package/docs/documentation-guide.md +1 -1
  128. package/docs/eval-runner.md +1 -1
  129. package/docs/evals-internal.md +1 -1
  130. package/docs/evidence-and-memory.md +70 -6
  131. package/docs/evolution.md +1 -1
  132. package/docs/extensions-and-sharing.md +1 -1
  133. package/docs/fleet-dispatch.md +34 -9
  134. package/docs/glossary.md +21 -1
  135. package/docs/installation-and-lifecycle.md +1 -1
  136. package/docs/middleware-and-components.md +1 -1
  137. package/docs/model-catalog.md +1 -1
  138. package/docs/observability.md +2 -2
  139. package/docs/proactive-memory.md +1 -1
  140. package/docs/prompt-envelope-and-tools.md +4 -2
  141. package/docs/provider-adapter-cookbook.md +1 -1
  142. package/docs/release-cut-checklist.md +41 -38
  143. package/docs/safety-model.md +1 -1
  144. package/docs/scientific-validation.md +1 -1
  145. package/docs/skills-marketplace.md +1 -1
  146. package/docs/tool-usage.md +1 -1
  147. package/docs/trace-store.md +1 -1
  148. package/docs/tui-design.md +1 -1
  149. package/docs/worker-dispatch-mechanics.md +1 -1
  150. package/package.json +1 -2
  151. package/src/cli/argv.ts +5 -0
  152. package/src/cli/configure.ts +107 -23
  153. package/src/cli/doctor.ts +5 -1
  154. package/src/cli/evidence.ts +30 -25
  155. package/src/cli/fleet-preflight.ts +2 -12
  156. package/src/cli/shared.ts +1 -0
  157. package/src/cli/targets.ts +4 -1
  158. package/src/cli/validate-model.ts +60 -5
  159. package/src/core/bus-events.ts +25 -0
  160. package/src/core/commit-attribution.ts +4 -4
  161. package/src/core/path-boundary.ts +100 -0
  162. package/src/domains/agents/extension.ts +2 -11
  163. package/src/domains/agents/fleet-contract.ts +30 -12
  164. package/src/domains/agents/recipe.ts +7 -1
  165. package/src/domains/agents/registry.ts +73 -5
  166. package/src/domains/agents/result-contract.ts +128 -17
  167. package/src/domains/agents/write-boundary.ts +15 -50
  168. package/src/domains/context/project-rules.ts +51 -1
  169. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  170. package/src/domains/dispatch/assignment-store.ts +151 -14
  171. package/src/domains/dispatch/contract.ts +15 -1
  172. package/src/domains/dispatch/delegation-plan.ts +2 -5
  173. package/src/domains/dispatch/execution-role.ts +9 -1
  174. package/src/domains/dispatch/extension.ts +156 -67
  175. package/src/domains/dispatch/fleet-run.ts +57 -3
  176. package/src/domains/dispatch/gate-role-prompts.ts +38 -0
  177. package/src/domains/dispatch/index.ts +3 -1
  178. package/src/domains/dispatch/intent-requirements.ts +40 -0
  179. package/src/domains/dispatch/intent.ts +84 -8
  180. package/src/domains/dispatch/path-scope.ts +370 -0
  181. package/src/domains/dispatch/receipt-integrity.ts +2 -1
  182. package/src/domains/dispatch/types.ts +14 -7
  183. package/src/domains/dispatch/validation.ts +6 -3
  184. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  185. package/src/domains/dispatch/write-boundary.ts +201 -22
  186. package/src/domains/eval/metrics/evidence.ts +79 -2
  187. package/src/domains/eval/runners/clio-run.ts +12 -2
  188. package/src/domains/evidence/build.ts +69 -11
  189. package/src/domains/evidence/index.ts +21 -0
  190. package/src/domains/evidence/provenance.ts +46 -11
  191. package/src/domains/evidence/trust-projection.ts +274 -0
  192. package/src/domains/evidence/trust-status.ts +145 -17
  193. package/src/domains/evidence/types.ts +4 -0
  194. package/src/domains/extensions/discovery.ts +88 -1
  195. package/src/domains/extensions/resources.ts +20 -8
  196. package/src/domains/extensions/state.ts +6 -2
  197. package/src/domains/extensions/types.ts +4 -1
  198. package/src/domains/lifecycle/doctor.ts +140 -1
  199. package/src/domains/prompts/contract.ts +3 -5
  200. package/src/domains/providers/extension.ts +30 -2
  201. package/src/domains/resources/common-loader.ts +3 -0
  202. package/src/domains/resources/prompts/loader.ts +119 -14
  203. package/src/domains/safety/policy-engine.ts +5 -5
  204. package/src/domains/safety/run-effects.ts +64 -1
  205. package/src/domains/safety/scope.ts +7 -12
  206. package/src/engine/acp/server.ts +4 -1
  207. package/src/engine/prompt-templates.ts +18 -1
  208. package/src/engine/worker-runtime.ts +6 -3
  209. package/src/interactive/dispatch-board.ts +53 -3
  210. package/src/interactive/interactive-event-projection.ts +14 -0
  211. package/src/interactive/overlays/settings.ts +141 -47
  212. package/src/interactive/slash-commands.ts +7 -2
  213. package/src/interactive/view/artifacts.ts +42 -9
  214. package/src/interactive/view/view-overlay.ts +15 -3
  215. package/src/interactive/worker-receipts.ts +14 -2
  216. package/src/interactive/worker-stream.ts +8 -0
  217. package/src/tools/dispatch-admission.ts +12 -13
  218. package/src/tools/dispatch-arguments.ts +27 -0
  219. package/src/tools/dispatch-plan.ts +29 -0
  220. package/src/tools/dispatch-runner.ts +48 -13
  221. package/src/tools/monitor.ts +13 -0
  222. package/src/tools/worker-evidence.ts +19 -13
  223. package/src/worker/spec-contract.ts +2 -1
@@ -1,7 +1,7 @@
1
1
  # Worker Dispatch Mechanics
2
2
 
3
3
  > [!TIP]
4
- > **Interactive Spec Available:** An interactive NDJSON protocol timeline stream and heartbeat watchdog simulator is located at [docs/html/worker_dispatch_blueprint.html](html/worker_dispatch_blueprint.html) (Version: 0.3.7).
4
+ > **Interactive Spec Available:** An interactive NDJSON protocol timeline stream and heartbeat watchdog simulator is located at [docs/html/worker_dispatch_blueprint.html](html/worker_dispatch_blueprint.html) (Version: 0.3.8).
5
5
 
6
6
  This document describes the design and lifecycle of Clio Coder dispatched workers, focusing on the spawning sequence, execution isolation, the standard input/output NDJSON communication loop, and permission escalation routing.
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iowarp/clio-coder",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Coding agent for HPC and scientific-software developers, part of IOWarp's CLIO ecosystem of agentic science.",
5
5
  "keywords": [
6
6
  "ai",
@@ -107,7 +107,6 @@
107
107
  },
108
108
  "overrides": {
109
109
  "@anthropic-ai/sdk": "0.105.0",
110
- "ip-address": "10.2.0",
111
110
  "esbuild": "0.28.1"
112
111
  },
113
112
  "devDependencies": {
package/src/cli/argv.ts CHANGED
@@ -15,6 +15,11 @@ export function printError(message: string, detail?: string): void {
15
15
  if (detail) process.stderr.write(` ${detail}\n`);
16
16
  }
17
17
 
18
+ /** An expected condition worth a line on stderr that is neither an error nor a success. */
19
+ export function printNote(message: string): void {
20
+ process.stderr.write(`${chalk.yellow("note:")} ${message}\n`);
21
+ }
22
+
18
23
  export function printOk(message: string): void {
19
24
  process.stdout.write(`${chalk.green("ok:")} ${message}\n`);
20
25
  }
@@ -27,7 +27,12 @@ import { fingerprintNativeRuntime } from "../domains/providers/probe/fingerprint
27
27
  import { getRuntimeRegistry } from "../domains/providers/registry.js";
28
28
  import { registerBuiltinRuntimes } from "../domains/providers/runtimes/builtins.js";
29
29
  import { greetLmStudio } from "../domains/providers/runtimes/common/lmstudio-http.js";
30
- import type { ProbeContext, ProbeResult, RuntimeDescriptor } from "../domains/providers/types/runtime-descriptor.js";
30
+ import type {
31
+ ProbeContext,
32
+ ProbeModelStatus,
33
+ ProbeResult,
34
+ RuntimeDescriptor,
35
+ } from "../domains/providers/types/runtime-descriptor.js";
31
36
  import type { TargetDescriptor } from "../domains/providers/types/target-descriptor.js";
32
37
  import { registerClioOAuthProviders } from "../engine/oauth.js";
33
38
  import { ask, askYesNo } from "./ask.js";
@@ -44,7 +49,7 @@ import { createDelayedManualCodeInput } from "./oauth-manual-input.js";
44
49
  import { promptOAuthSelection } from "./oauth-select.js";
45
50
  import { credentialWriteFailed, printError, printOk, printPlaintextCredentialWarning } from "./shared.js";
46
51
  import { terminalColumns, wrapPlain } from "./text-layout.js";
47
- import { validateModelChoice } from "./validate-model.js";
52
+ import { type LiveModelInventory, validateModelChoice } from "./validate-model.js";
48
53
 
49
54
  const HELP = `clio-coder configure
50
55
 
@@ -70,7 +75,8 @@ Non-interactive flags:
70
75
  --agent-profile-model <id> model to use for --agent-profile
71
76
  --api-key-env <VAR> read API key from this env var at call time
72
77
  --api-key <literal> store API key in credentials.yaml
73
- --force allow a model outside the runtime catalog
78
+ --force save a model outside the runtime catalog, or one the
79
+ target does not advertise, without refusing
74
80
  --gateway mark the target as a gateway
75
81
  --lifecycle <user-managed|clio-managed>
76
82
  resident model lifecycle policy
@@ -378,19 +384,35 @@ function catalogContextWindowFor(runtime: RuntimeDescriptor, modelId: string): n
378
384
  return null;
379
385
  }
380
386
 
381
- function validateResolvedModel(runtimeId: string, modelId: string | undefined, force: boolean): boolean {
387
+ /**
388
+ * Check the resolved model against the catalog where there is one and against
389
+ * the target's own list where there is not. A runtime with no catalog used to
390
+ * pass any string here while the same command wrote the live list that ruled
391
+ * it out into `wireModels`, so the target saved as `ok` and failed on its first
392
+ * turn. Without a catalog or a live list there is nothing to check against, and
393
+ * the note says so rather than implying the id was verified.
394
+ */
395
+ function validateResolvedModel(
396
+ runtime: RuntimeDescriptor,
397
+ target: Pick<TargetDescriptor, "id" | "url">,
398
+ modelId: string | undefined,
399
+ force: boolean,
400
+ inventory: WireModelInventory,
401
+ ): boolean {
382
402
  if (!modelId) return true;
383
- const validation = validateModelChoice({
384
- runtimeId,
385
- modelId,
386
- knownModels: runtimeKnownModelsFor(runtimeId),
387
- force,
388
- });
403
+ const knownModels = runtimeKnownModelsFor(runtime.id);
404
+ const live = inventory.source === "probe" ? liveInventoryFor(target, inventory) : undefined;
405
+ const validation = validateModelChoice({ runtimeId: runtime.id, modelId, knownModels, live, force });
389
406
  if (!validation.ok) {
390
407
  process.stderr.write(`error: ${validation.reason}\n`);
391
408
  return false;
392
409
  }
393
410
  if (validation.warning) process.stderr.write(`warning: ${validation.warning}\n`);
411
+ if (knownModels.length === 0 && live === undefined && runtime.kind === "http") {
412
+ process.stderr.write(
413
+ `warning: could not verify model '${modelId}': ${target.url ?? `target '${target.id}'`} returned no model list; it is saved as written\n`,
414
+ );
415
+ }
394
416
  return true;
395
417
  }
396
418
 
@@ -412,10 +434,15 @@ function validateContextWindowOverride(
412
434
  return true;
413
435
  }
414
436
 
415
- async function runtimeProbe(runtime: RuntimeDescriptor, target: TargetDescriptor): Promise<ProbeResult | null> {
437
+ async function runtimeProbe(
438
+ runtime: RuntimeDescriptor,
439
+ target: TargetDescriptor,
440
+ authToken?: string,
441
+ ): Promise<ProbeResult | null> {
416
442
  if (typeof runtime.probe !== "function") return null;
417
443
  try {
418
- return await runtime.probe(target, await buildProbeContext(runtime, target));
444
+ const context = await buildProbeContext(runtime, target);
445
+ return await runtime.probe(target, authToken === undefined ? context : { ...context, authToken });
419
446
  } catch (err) {
420
447
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
421
448
  }
@@ -679,17 +706,69 @@ async function loginOAuthRuntime(rl: ReturnType<typeof createInterface>, runtime
679
706
  }
680
707
  }
681
708
 
709
+ /**
710
+ * The model ids a target can be configured with, and where they came from.
711
+ * `probe` is the only source that says anything about the server in front of
712
+ * us right now; `catalog` is static provider knowledge, and `existing` is the
713
+ * list a previous configure recorded, which the server may no longer match.
714
+ */
715
+ interface WireModelInventory {
716
+ models: string[];
717
+ source: "catalog" | "probe" | "existing" | "none";
718
+ /** Per-model load state when the probe reported it. */
719
+ modelStates?: Readonly<Record<string, ProbeModelStatus>> | undefined;
720
+ }
721
+
722
+ /**
723
+ * Every id the runtime would resolve. LM Studio lists a loaded model under its
724
+ * instance id and keeps the model key only in the state map, and the request
725
+ * path accepts either, so both count as advertised.
726
+ */
727
+ function advertisedModelIds(inventory: Pick<WireModelInventory, "models" | "modelStates">): string[] {
728
+ const ids = [...inventory.models];
729
+ for (const id of Object.keys(inventory.modelStates ?? {})) if (!ids.includes(id)) ids.push(id);
730
+ return ids;
731
+ }
732
+
733
+ function residentModelIds(modelStates: WireModelInventory["modelStates"]): string[] {
734
+ return Object.entries(modelStates ?? {})
735
+ .filter(([, status]) => status.state === "loaded" || status.state === "loading")
736
+ .map(([id]) => id);
737
+ }
738
+
739
+ function liveInventoryFor(
740
+ target: Pick<TargetDescriptor, "id" | "url">,
741
+ inventory: WireModelInventory,
742
+ ): LiveModelInventory {
743
+ return {
744
+ targetId: target.id,
745
+ url: target.url,
746
+ models: advertisedModelIds(inventory),
747
+ resident: residentModelIds(inventory.modelStates),
748
+ };
749
+ }
750
+
682
751
  async function resolveSupportedWireModels(
683
752
  runtime: RuntimeDescriptor,
684
753
  target: TargetDescriptor,
685
754
  existing?: TargetDescriptor,
686
755
  authToken?: string,
687
- ): Promise<string[]> {
756
+ ): Promise<WireModelInventory> {
688
757
  const known = listKnownModelsForRuntime(runtime.id);
689
- if (known.length > 0) return known;
690
- const discovered = runtime.kind === "http" ? await runtimeProbeModels(runtime, target, authToken) : [];
691
- if (discovered.length > 0) return discovered;
692
- return existing?.wireModels ? [...existing.wireModels] : [];
758
+ if (known.length > 0) return { models: known, source: "catalog" };
759
+ if (runtime.kind === "http") {
760
+ // The full probe carries load state alongside the ids; a runtime that
761
+ // lists models only through probeModels still gets its ids checked.
762
+ const probe = await runtimeProbe(runtime, target, authToken);
763
+ if (probe?.ok && probe.models && probe.models.length > 0) {
764
+ return { models: [...probe.models], source: "probe", modelStates: probe.modelStates };
765
+ }
766
+ const discovered = await runtimeProbeModels(runtime, target, authToken);
767
+ if (discovered.length > 0) return { models: discovered, source: "probe" };
768
+ }
769
+ if (existing?.wireModels && existing.wireModels.length > 0)
770
+ return { models: [...existing.wireModels], source: "existing" };
771
+ return { models: [], source: "none" };
693
772
  }
694
773
 
695
774
  function resolveModelChoice(
@@ -810,7 +889,8 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
810
889
  ...(args.reasoning !== undefined ? { reasoning: args.reasoning } : {}),
811
890
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
812
891
  });
813
- const wireModels = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
892
+ const inventory = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
893
+ const wireModels = inventory.models;
814
894
  const model =
815
895
  args.model ??
816
896
  existing?.defaultModel ??
@@ -820,7 +900,7 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
820
900
  refuseCatalogSeededModel(runtime, support);
821
901
  return 2;
822
902
  }
823
- if (!validateResolvedModel(runtime.id, model, args.force)) return 2;
903
+ if (!validateResolvedModel(runtime, seed, model, args.force, inventory)) return 2;
824
904
  if (!validateContextWindowOverride(runtime, model, args.contextWindow, args.force)) return 2;
825
905
  const descriptor = buildDescriptor(runtime, args.id, {
826
906
  ...(url !== undefined ? { url } : {}),
@@ -1192,7 +1272,10 @@ async function runInteractive(
1192
1272
  }
1193
1273
 
1194
1274
  let model: string | undefined = defaults.model;
1195
- let wireModels: string[] = existing?.wireModels ? [...existing.wireModels] : [];
1275
+ let inventory: WireModelInventory =
1276
+ existing?.wireModels && existing.wireModels.length > 0
1277
+ ? { models: [...existing.wireModels], source: "existing" }
1278
+ : { models: [], source: "none" };
1196
1279
  const tentative = buildDescriptor(runtime, targetId, {
1197
1280
  ...(url !== undefined ? { url } : {}),
1198
1281
  ...(apiKeyEnv !== undefined ? { apiKeyEnv } : {}),
@@ -1211,8 +1294,9 @@ async function runInteractive(
1211
1294
  });
1212
1295
 
1213
1296
  if (runtime.kind === "http") {
1214
- wireModels = await resolveSupportedWireModels(runtime, tentative, existing ?? undefined);
1297
+ inventory = await resolveSupportedWireModels(runtime, tentative, existing ?? undefined);
1215
1298
  }
1299
+ const wireModels = inventory.models;
1216
1300
  // The wizard shows the whole list, so a catalog-ordered runtime does not need
1217
1301
  // --model here the way the non-interactive path does. It does need to stop
1218
1302
  // offering the alphabetically first id as though it were the recommended one.
@@ -1234,11 +1318,11 @@ async function runInteractive(
1234
1318
  process.stdout.write(" a model is required: enter a number from the list or a model id.\n");
1235
1319
  continue;
1236
1320
  }
1237
- if (validateResolvedModel(runtime.id, model, defaults.force)) break;
1321
+ if (validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
1238
1322
  }
1239
1323
  } else {
1240
1324
  for (;;) {
1241
- if (model && validateResolvedModel(runtime.id, model, defaults.force)) break;
1325
+ if (model && validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
1242
1326
  const manual = await ask(rl, "Default model id (blank to leave empty)", model ?? "");
1243
1327
  if (manual === null) return 0;
1244
1328
  model = manual.length > 0 ? manual : undefined;
package/src/cli/doctor.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  runDoctor,
5
5
  runDoctorFleetChecks,
6
6
  runDoctorInteropChecks,
7
+ runDoctorModelChecks,
7
8
  runDoctorRuntimeChecks,
8
9
  } from "../domains/lifecycle/doctor.js";
9
10
  import { printError } from "./shared.js";
@@ -35,6 +36,9 @@ export async function runDoctorCommand(args: ReadonlyArray<string> = []): Promis
35
36
  const untouched = !fix && isUninitializedHome();
36
37
  const findings = runDoctor({ fix });
37
38
  const runtimeChecks = await runDoctorRuntimeChecks();
39
+ // Every model pointer is checked against what its target advertises, so a
40
+ // placeholder id saved by configure is reported here and not on the first turn.
41
+ const modelChecks = await runDoctorModelChecks();
38
42
  // The interop and fleet sweeps read the state and config roots through the
39
43
  // ensuring accessors, which create them, and there is no fleet or interop
40
44
  // state to inspect before Clio has ever written anything. On a home Clio has
@@ -43,7 +47,7 @@ export async function runDoctorCommand(args: ReadonlyArray<string> = []): Promis
43
47
  // Fleet preflight probes each configured node over SSH and persists the
44
48
  // per-node eligibility verdicts dispatch placement enforces.
45
49
  const fleetChecks = untouched ? [] : await runDoctorFleetChecks();
46
- const all = [...findings, ...runtimeChecks, ...interopChecks, ...fleetChecks];
50
+ const all = [...findings, ...runtimeChecks, ...modelChecks, ...interopChecks, ...fleetChecks];
47
51
  const ok = all.every((f) => f.ok);
48
52
  if (json) {
49
53
  process.stdout.write(`${JSON.stringify({ ok, fix, findings: all }, null, 2)}\n`);
@@ -1,16 +1,18 @@
1
1
  import { InvalidIdError } from "../core/safe-id.js";
2
2
  import { clioDataDir, clioStateDir } from "../core/xdg.js";
3
- import type { CanonicalTrustStatus, EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
3
+ import type { EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
4
4
  import {
5
5
  buildEvalEvidence,
6
6
  buildEvidence,
7
7
  EvidenceNotFoundError,
8
+ formatTrustAxes,
9
+ formatTrustSummary,
8
10
  inspectEvidence,
9
11
  listEvidenceOverviews,
10
12
  loadEvidenceRunProvenance,
11
13
  provenanceTranscriptLines,
12
14
  } from "../domains/evidence/index.js";
13
- import { printError, printOk } from "./shared.js";
15
+ import { printError, printNote, printOk } from "./shared.js";
14
16
 
15
17
  const HELP = `clio-coder evidence build --run <runId>
16
18
  clio-coder evidence build --session <sessionId>
@@ -137,15 +139,19 @@ export async function runEvidenceCommand(args: ReadonlyArray<string>): Promise<n
137
139
  // but the verdict line and exit code must not say ok over a receipt
138
140
  // that failed integrity verification.
139
141
  const integrityFailures = result.findings.filter((entry) => entry.tag === "receipt-integrity");
140
- if (integrityFailures.length > 0) {
141
- process.stdout.write(`wrote ${result.evidenceId} ${result.directory}\n`);
142
- for (const failure of integrityFailures) {
143
- printError(`${failure.runId !== null ? `run ${failure.runId}: ` : ""}${failure.message}`);
144
- }
145
- return 1;
142
+ // A retired seal is the expected state of every run that predates an
143
+ // integrity bump: the receipt is set aside unread, as a missing one
144
+ // is, so it is a note beside the verdict and leaves the exit code alone.
145
+ const retiredSeals = result.findings.filter((entry) => entry.tag === "receipt-retired");
146
+ if (integrityFailures.length > 0) process.stdout.write(`wrote ${result.evidenceId} ${result.directory}\n`);
147
+ else printOk(`wrote ${result.evidenceId} ${result.directory}`);
148
+ for (const entry of retiredSeals) {
149
+ printNote(`${entry.runId !== null ? `run ${entry.runId}: ` : ""}${entry.message}`);
146
150
  }
147
- printOk(`wrote ${result.evidenceId} ${result.directory}`);
148
- return 0;
151
+ for (const failure of integrityFailures) {
152
+ printError(`${failure.runId !== null ? `run ${failure.runId}: ` : ""}${failure.message}`);
153
+ }
154
+ return integrityFailures.length > 0 ? 1 : 0;
149
155
  }
150
156
  if (parsed.command === "inspect") {
151
157
  const evidenceId = parsed.evidenceId;
@@ -191,30 +197,29 @@ function renderEvidence(
191
197
  if (trustStatus.projection === "historical_format") {
192
198
  process.stdout.write("trust status: unavailable (historical bundle without canonical projection)\n");
193
199
  } else {
200
+ // The compact projection answers the trust questions; the canonical axis
201
+ // line beneath it is the drill-down, in the same form the dispatch tool
202
+ // and monitor print.
194
203
  for (const run of trustStatus.runs) {
195
- process.stdout.write(`trust ${run.runId}: ${formatTrustStatus(run.status)}\n`);
204
+ process.stdout.write(`trust ${run.runId}: ${formatTrustSummary(run.status)}\n`);
205
+ process.stdout.write(` ${formatTrustAxes(run.status)}\n`);
196
206
  }
197
207
  }
198
- // Provenance is printed only for runs whose receipts carry it, so a legacy
199
- // bundle prints nothing new here.
208
+ // Provenance is the detail behind the projection, never a second reading
209
+ // of it: each run's block is gated on that run's canonical status, so a
210
+ // receipt the projection withheld (a broken or retired seal) prints no
211
+ // autonomy value here, and a run whose block would be empty prints no
212
+ // header. A historical bundle has no projection and admits no axis detail.
213
+ const statusByRun = new Map(trustStatus.runs.map((entry) => [entry.runId, entry.status]));
200
214
  for (const { runId, view } of provenance) {
215
+ const lines = provenanceTranscriptLines(view, statusByRun.get(runId));
216
+ if (lines.length === 0) continue;
201
217
  process.stdout.write(`provenance ${runId}:\n`);
202
- for (const line of provenanceTranscriptLines(view)) process.stdout.write(` ${line}\n`);
218
+ for (const line of lines) process.stdout.write(` ${line}\n`);
203
219
  }
204
220
  process.stdout.write(`files: ${formatList(overview.files)}\n`);
205
221
  }
206
222
 
207
- function formatTrustStatus(status: CanonicalTrustStatus): string {
208
- return [
209
- `artifactIntegrity=${status.artifactIntegrity.state}`,
210
- `validationGrounding=${status.validationGrounding.state}`,
211
- `independentReview=${status.independentReview.state}`,
212
- `contextProvenance=${status.contextProvenance.state}`,
213
- `autonomyEnforcement=${status.autonomyEnforcement.state}`,
214
- `completionEvidence=${status.completionEvidence.state}`,
215
- ].join(" ");
216
- }
217
-
218
223
  function renderEvidenceList(overviews: ReadonlyArray<EvidenceOverview>): void {
219
224
  process.stdout.write(`${overviews.length} evidence artifacts\n`);
220
225
  if (overviews.length === 0) return;
@@ -1,7 +1,4 @@
1
- import { join } from "node:path";
2
1
  import { readSettings } from "../core/config.js";
3
- import { resolvePackageRoot } from "../core/package-root.js";
4
- import { clioConfigDir } from "../core/xdg.js";
5
2
  import {
6
3
  type FleetCommandRegistry,
7
4
  type FleetContract,
@@ -9,7 +6,7 @@ import {
9
6
  loadFleetContract,
10
7
  renderFleetPrompt,
11
8
  } from "../domains/agents/index.js";
12
- import { type AgentRecipeDiagnostic, loadRecipesFromDir, mergeRecipes } from "../domains/agents/registry.js";
9
+ import { discoverAgentRecipes } from "../domains/agents/registry.js";
13
10
  import { type AgentSpec, normalizeAgentSpec } from "../domains/agents/spec.js";
14
11
  import type { ExecutionPlan } from "../domains/dispatch/execution-plan.js";
15
12
  import { requestExecutionRole, withAttemptRole } from "../domains/dispatch/execution-role.js";
@@ -23,14 +20,7 @@ export interface FleetPreflightResult {
23
20
  }
24
21
 
25
22
  function discoverSpecs(cwd: string): AgentSpec[] {
26
- const diagnostics: AgentRecipeDiagnostic[] = [];
27
- const builtin = loadRecipesFromDir(
28
- { dir: join(resolvePackageRoot(), "src", "domains", "agents", "builtins"), source: "builtin" },
29
- diagnostics,
30
- );
31
- const user = loadRecipesFromDir({ dir: join(clioConfigDir(), "agents"), source: "user" }, diagnostics);
32
- const project = loadRecipesFromDir({ dir: join(cwd, ".clio-coder", "agents"), source: "project" }, diagnostics);
33
- return mergeRecipes(builtin, user, project).map(normalizeAgentSpec);
23
+ return discoverAgentRecipes(cwd).map(normalizeAgentSpec);
34
24
  }
35
25
 
36
26
  export function inspectFleet(name: string, vars?: Readonly<Record<string, string>>): FleetPreflightResult {
package/src/cli/shared.ts CHANGED
@@ -15,6 +15,7 @@ export {
15
15
  parseFlags,
16
16
  printError,
17
17
  printHeader,
18
+ printNote,
18
19
  printOk,
19
20
  } from "./argv.js";
20
21
 
@@ -953,7 +953,7 @@ function renderTable(providers: ProvidersContract, entries: ReadonlyArray<Target
953
953
  }
954
954
  }
955
955
 
956
- function targetTableRow(providers: ProvidersContract, status: TargetStatus): TargetTableRow {
956
+ export function targetTableRow(providers: ProvidersContract, status: TargetStatus): TargetTableRow {
957
957
  return {
958
958
  id: status.target.id,
959
959
  tier: statusTier(status),
@@ -1051,6 +1051,9 @@ function formatNotes(status: TargetStatus): string {
1051
1051
  if (status.runtime?.auth === "claude-cli") parts.push("claude-cli");
1052
1052
  if (status.capabilities.contextWindow > 0) parts.push(formatContextWindow(status));
1053
1053
  if (!status.available && status.reason) parts.push(status.reason);
1054
+ // A reachable target that cannot serve its own default is degraded, and the
1055
+ // row has to say why or the model column reads as a model that works.
1056
+ if (status.health.status === "degraded" && status.health.lastError) parts.push(status.health.lastError);
1054
1057
  const residency = residentModelsSummary(status.discoveredModelStates);
1055
1058
  if (residency) parts.push(residency);
1056
1059
  if (status.probeNotes && status.probeNotes.length > 0) parts.push(`note: ${status.probeNotes.join("; ")}`);
@@ -1,7 +1,25 @@
1
+ /**
2
+ * What a target advertised when it was asked, for runtimes with no static
3
+ * catalog. `models` is the id list the probe returned and `resident` the
4
+ * subset the server reports as loaded, when it reports load state at all.
5
+ */
6
+ export interface LiveModelInventory {
7
+ targetId: string;
8
+ url?: string | undefined;
9
+ models: ReadonlyArray<string>;
10
+ resident: ReadonlyArray<string>;
11
+ }
12
+
1
13
  export interface ValidateModelInput {
2
14
  runtimeId: string;
3
15
  modelId: string;
4
16
  knownModels: ReadonlyArray<string>;
17
+ /**
18
+ * The live list, when one was fetched. It is consulted only when the static
19
+ * catalog is empty, so the catalog stays the authority for cloud runtimes
20
+ * and the probe becomes the authority where there is nothing else.
21
+ */
22
+ live?: LiveModelInventory | undefined;
5
23
  force: boolean;
6
24
  }
7
25
 
@@ -9,9 +27,30 @@ export type ValidateModelResult =
9
27
  | { ok: true; warning?: string }
10
28
  | { ok: false; reason: string; knownModels: ReadonlyArray<string> };
11
29
 
30
+ const SAMPLE_SIZE = 10;
31
+
32
+ function sample(models: ReadonlyArray<string>): string {
33
+ const shown = models.slice(0, SAMPLE_SIZE).join(", ");
34
+ return models.length > SAMPLE_SIZE ? `${shown}, …` : shown;
35
+ }
36
+
37
+ function describeLiveTarget(live: LiveModelInventory): string {
38
+ return live.url ? `target '${live.targetId}' at ${live.url}` : `target '${live.targetId}'`;
39
+ }
40
+
41
+ /** The refusal `configure` prints for a model the live target does not advertise. */
42
+ export function formatUnadvertisedModelReason(modelId: string, live: LiveModelInventory): string {
43
+ const resident = live.resident.length > 0 ? live.resident.join(", ") : "none";
44
+ return (
45
+ `${describeLiveTarget(live)} does not advertise model '${modelId}'. ` +
46
+ `Advertised (${live.models.length}): ${sample(live.models)}. Resident instances: ${resident}. ` +
47
+ "Pass one of the advertised ids with --model, or --force to save a model the target cannot serve."
48
+ );
49
+ }
50
+
12
51
  export function validateModelChoice(input: ValidateModelInput): ValidateModelResult {
13
- const { runtimeId, modelId, knownModels, force } = input;
14
- if (knownModels.length === 0) return { ok: true };
52
+ const { runtimeId, modelId, knownModels, live, force } = input;
53
+ if (knownModels.length === 0) return validateAgainstLive(modelId, live, force);
15
54
  if (knownModels.includes(modelId)) return { ok: true };
16
55
  if (force) {
17
56
  return {
@@ -19,10 +58,26 @@ export function validateModelChoice(input: ValidateModelInput): ValidateModelRes
19
58
  warning: `model '${modelId}' not in ${runtimeId} catalog (use without --force to reject)`,
20
59
  };
21
60
  }
22
- const sample = knownModels.slice(0, 10).join(", ");
23
61
  return {
24
62
  ok: false,
25
- reason: `model '${modelId}' not in ${runtimeId} catalog. Known: ${sample}. Use --force to skip validation.`,
26
- knownModels: knownModels.slice(0, 10),
63
+ reason: `model '${modelId}' not in ${runtimeId} catalog. Known: ${sample(knownModels)}. Use --force to skip validation.`,
64
+ knownModels: knownModels.slice(0, SAMPLE_SIZE),
27
65
  };
28
66
  }
67
+
68
+ /**
69
+ * With no catalog, the live list is the only evidence there is. An empty
70
+ * catalog used to be an early `ok` on its own, which is how a placeholder id
71
+ * was written next to the very list that ruled it out.
72
+ */
73
+ function validateAgainstLive(
74
+ modelId: string,
75
+ live: LiveModelInventory | undefined,
76
+ force: boolean,
77
+ ): ValidateModelResult {
78
+ if (!live || live.models.length === 0) return { ok: true };
79
+ if (live.models.includes(modelId)) return { ok: true };
80
+ const reason = formatUnadvertisedModelReason(modelId, live);
81
+ if (force) return { ok: true, warning: `${reason.replace(/ Pass one of.*$/u, "")} Saved anyway because of --force.` };
82
+ return { ok: false, reason, knownModels: live.models.slice(0, SAMPLE_SIZE) };
83
+ }
@@ -51,6 +51,7 @@ export const BusChannels = {
51
51
  ToolBudgetExceeded: "safety.toolBudgetExceeded",
52
52
  ProviderHealth: "provider.health",
53
53
  RuntimeNotice: "runtime.notice",
54
+ DispatchScopeNotice: "dispatch.scopeNotice",
54
55
  DispatchEnqueued: "dispatch.enqueued",
55
56
  DispatchStarted: "dispatch.started",
56
57
  DispatchProgress: "dispatch.progress",
@@ -108,6 +109,29 @@ export interface ContextWarningPayload {
108
109
  warning: string | null;
109
110
  }
110
111
 
112
+ /** A dispatch path-scope decision that must remain visible in the operator transcript. */
113
+ export type DispatchScopeNoticePayload =
114
+ | {
115
+ code: "typed_scope_replaced_inferred_paths";
116
+ level: "warning";
117
+ agentId: string;
118
+ omittedPaths: ReadonlyArray<string>;
119
+ message: string;
120
+ }
121
+ | {
122
+ code: "legacy_scope_inferred" | "legacy_scope_empty";
123
+ level: "warning";
124
+ agentId: string;
125
+ paths: ReadonlyArray<{
126
+ path: string;
127
+ policy: "working-context" | "write-boundary";
128
+ provenance: "declared" | "derived" | "inferred";
129
+ source: "intent.read_roots" | "intent.write_roots" | "intent.relevant_paths" | "writeRoots" | "task" | "briefing";
130
+ confidence: "certain" | "high" | "medium" | "low";
131
+ }>;
132
+ message: string;
133
+ };
134
+
111
135
  export type ContextActivityKind = "context-init" | "context-clear" | "context-refresh" | "context-wiki" | "compaction";
112
136
  export type ContextActivityPhase = "scan" | "codewiki" | "generate" | "clio-md" | "state" | "done";
113
137
  export type ContextActivityStatus = "started" | "running" | "completed" | "failed";
@@ -737,6 +761,7 @@ export type BusPayloadMap = {
737
761
  [BusChannels.ToolBudgetExceeded]: ToolBudgetExceededPayload;
738
762
  [BusChannels.ProviderHealth]: ProviderHealthPayload;
739
763
  [BusChannels.RuntimeNotice]: RuntimeNoticePayload;
764
+ [BusChannels.DispatchScopeNotice]: DispatchScopeNoticePayload;
740
765
  [BusChannels.DispatchEnqueued]: DispatchEnqueuedPayload;
741
766
  [BusChannels.DispatchStarted]: DispatchStartedPayload;
742
767
  [BusChannels.DispatchProgress]: DispatchProgressPayload;
@@ -16,7 +16,7 @@ export const CLIO_COMMIT_TRAILERS = {
16
16
  } as const;
17
17
 
18
18
  export interface CommitReceiptEvidence {
19
- version: 19;
19
+ version: 20;
20
20
  algorithm: "sha256";
21
21
  digest: string;
22
22
  /** The receipt was checked against its run-ledger envelope. */
@@ -58,7 +58,7 @@ const KNOWN_CLIO_TRAILERS = new Set(
58
58
  );
59
59
 
60
60
  function isClioTrailer(key: string): boolean {
61
- return KNOWN_CLIO_TRAILERS.has(key) || key.startsWith("clio-evidence:receipt-v19/sha256:");
61
+ return KNOWN_CLIO_TRAILERS.has(key) || key.startsWith("clio-evidence:receipt-v20/sha256:");
62
62
  }
63
63
 
64
64
  /**
@@ -87,7 +87,7 @@ function trailerBlockStart(lines: ReadonlyArray<string>): number | null {
87
87
 
88
88
  function receiptTrailer(receipt: CommitReceiptEvidence | undefined): string | null {
89
89
  if (
90
- receipt?.version !== 19 ||
90
+ receipt?.version !== 20 ||
91
91
  receipt.algorithm !== "sha256" ||
92
92
  receipt.integrityValid !== true ||
93
93
  receipt.directlyRelevant !== true ||
@@ -95,7 +95,7 @@ function receiptTrailer(receipt: CommitReceiptEvidence | undefined): string | nu
95
95
  ) {
96
96
  return null;
97
97
  }
98
- return `Clio-Evidence: receipt-v19/sha256:${receipt.digest}`;
98
+ return `Clio-Evidence: receipt-v20/sha256:${receipt.digest}`;
99
99
  }
100
100
 
101
101
  /**