@iowarp/clio-coder 0.3.0

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 (226) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +224 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +9 -0
  6. package/README.md +798 -0
  7. package/SECURITY.md +72 -0
  8. package/assets/clio-coder-logo-128.webp +0 -0
  9. package/damage-control-rules.yaml +419 -0
  10. package/dist/acp-UMLFVA3F.js +92 -0
  11. package/dist/agents-Q4MYPMUW.js +91 -0
  12. package/dist/auth-O6HYIJ6J.js +521 -0
  13. package/dist/chunk-262G75JS.js +35 -0
  14. package/dist/chunk-26BZQOAD.js +1281 -0
  15. package/dist/chunk-2J63S4SF.js +508 -0
  16. package/dist/chunk-3DANZDGR.js +717 -0
  17. package/dist/chunk-4UQA7NCT.js +29 -0
  18. package/dist/chunk-527KG6XR.js +497 -0
  19. package/dist/chunk-5LDRNKX2.js +1063 -0
  20. package/dist/chunk-5N2FG33Q.js +25 -0
  21. package/dist/chunk-67MTHP2E.js +135 -0
  22. package/dist/chunk-6CWDTGUC.js +20 -0
  23. package/dist/chunk-7BHLZB3A.js +2115 -0
  24. package/dist/chunk-7RBKDI66.js +348 -0
  25. package/dist/chunk-AMFR5YA3.js +541 -0
  26. package/dist/chunk-BBUH4VAA.js +1224 -0
  27. package/dist/chunk-BYEU76JP.js +899 -0
  28. package/dist/chunk-CLJ5HLUD.js +458 -0
  29. package/dist/chunk-D5YD55AR.js +116 -0
  30. package/dist/chunk-DXQNI4PC.js +61 -0
  31. package/dist/chunk-E3NYWENM.js +1004 -0
  32. package/dist/chunk-GNGDQYDU.js +34688 -0
  33. package/dist/chunk-GOTUR54M.js +9 -0
  34. package/dist/chunk-HBU5MTAM.js +41 -0
  35. package/dist/chunk-HMYNFFY4.js +28 -0
  36. package/dist/chunk-JPOWPFCU.js +1010 -0
  37. package/dist/chunk-JWHCJDCI.js +1215 -0
  38. package/dist/chunk-KBR4MZZR.js +41 -0
  39. package/dist/chunk-KKKPTZLM.js +93 -0
  40. package/dist/chunk-ME6DNWIU.js +66 -0
  41. package/dist/chunk-NI4DEJMC.js +88 -0
  42. package/dist/chunk-O4EJEDHO.js +659 -0
  43. package/dist/chunk-PIDUD6M2.js +31 -0
  44. package/dist/chunk-PS4PFJQP.js +29459 -0
  45. package/dist/chunk-QV47YRF4.js +48 -0
  46. package/dist/chunk-RQDWMVRB.js +279 -0
  47. package/dist/chunk-TFSSEXL6.js +136 -0
  48. package/dist/chunk-TKHQ4DGZ.js +8290 -0
  49. package/dist/chunk-TPOCL34A.js +2876 -0
  50. package/dist/chunk-UGYAX5YI.js +565 -0
  51. package/dist/chunk-UHTSULZS.js +461 -0
  52. package/dist/chunk-UU3R62TT.js +128 -0
  53. package/dist/chunk-UWIJNAOB.js +3906 -0
  54. package/dist/chunk-VOO7NYPP.js +914 -0
  55. package/dist/chunk-VPAWTYLY.js +117 -0
  56. package/dist/chunk-WD6AJM35.js +1216 -0
  57. package/dist/chunk-X3BR7HWV.js +115 -0
  58. package/dist/chunk-X3NE4WVW.js +120 -0
  59. package/dist/chunk-XNISANGE.js +1395 -0
  60. package/dist/chunk-XV4ZJ6ZM.js +3177 -0
  61. package/dist/cli/index.js +236 -0
  62. package/dist/clio-KIQ5SNDS.js +53 -0
  63. package/dist/components-JVHMUBEB.js +653 -0
  64. package/dist/config-ZFCDBMDC.js +372 -0
  65. package/dist/configure-G4E3A2PG.js +27 -0
  66. package/dist/context-CDXTP2MP.js +293 -0
  67. package/dist/context-E3KIFVXI.js +185 -0
  68. package/dist/context-clear-3F4PLXOS.js +102 -0
  69. package/dist/context-index-Q7YSYTR3.js +106 -0
  70. package/dist/docs-YIETIWZI.js +280 -0
  71. package/dist/doctor-M5HJJZOL.js +61 -0
  72. package/dist/domains/agents/builtins/architect.md +33 -0
  73. package/dist/domains/agents/builtins/coder.md +31 -0
  74. package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
  75. package/dist/domains/agents/builtins/debugger.md +30 -0
  76. package/dist/domains/agents/builtins/documenter.md +31 -0
  77. package/dist/domains/agents/builtins/git-master.md +30 -0
  78. package/dist/domains/agents/builtins/provenance.md +30 -0
  79. package/dist/domains/agents/builtins/researcher.md +71 -0
  80. package/dist/domains/agents/builtins/scout.md +42 -0
  81. package/dist/domains/agents/builtins/tester.md +31 -0
  82. package/dist/domains/agents/builtins/verifier.md +30 -0
  83. package/dist/domains/agents/builtins/wiki-writer.md +41 -0
  84. package/dist/eval-B3KZZESM.js +2674 -0
  85. package/dist/evidence-V67CHM35.js +233 -0
  86. package/dist/evolve-YDZSUQYA.js +518 -0
  87. package/dist/extensions-SRG7XCAH.js +207 -0
  88. package/dist/fleet-CA2CRTVG.js +760 -0
  89. package/dist/fleet-preflight-CLIAX7YR.js +21 -0
  90. package/dist/init-2OZDJE2D.js +227 -0
  91. package/dist/memory-3PIQQAKX.js +207 -0
  92. package/dist/models-DY35XI7Y.js +237 -0
  93. package/dist/paths-5OMXW7Z4.js +57 -0
  94. package/dist/preload-KZVHET2B.js +11 -0
  95. package/dist/reset-PIFYNOS3.js +216 -0
  96. package/dist/run-3VSPP24F.js +735 -0
  97. package/dist/share-D36RQCXM.js +241 -0
  98. package/dist/skills-F2MRLELY.js +445 -0
  99. package/dist/skills-eval-E2ZTW4PL.js +932 -0
  100. package/dist/targets-DZMEZAH4.js +977 -0
  101. package/dist/trace-7NYCUI2J.js +250 -0
  102. package/dist/uninstall-AD3JWHBB.js +322 -0
  103. package/dist/upgrade-WYYBKGDY.js +301 -0
  104. package/dist/usage-ULIDAGFF.js +755 -0
  105. package/dist/version-ROZ6CZKH.js +16 -0
  106. package/dist/wiki-generate-PKFIX6OB.js +377 -0
  107. package/dist/worker/entry.js +1739 -0
  108. package/docs/README.md +93 -0
  109. package/docs/acp.md +120 -0
  110. package/docs/alcf-provider.md +72 -0
  111. package/docs/architecture.md +172 -0
  112. package/docs/artifact-versions.md +54 -0
  113. package/docs/built-in-agents.md +265 -0
  114. package/docs/capacity-and-scheduling.md +97 -0
  115. package/docs/commands-and-modes.md +554 -0
  116. package/docs/config-knobs-audit.md +115 -0
  117. package/docs/configuration-and-targets.md +812 -0
  118. package/docs/context-engine.md +236 -0
  119. package/docs/dispatch-architecture-rationale.md +126 -0
  120. package/docs/documentation-coverage.md +46 -0
  121. package/docs/documentation-guide.md +166 -0
  122. package/docs/environment-variables.md +105 -0
  123. package/docs/eval-runner.md +205 -0
  124. package/docs/evals-internal.md +298 -0
  125. package/docs/evidence-and-memory.md +243 -0
  126. package/docs/evolution.md +143 -0
  127. package/docs/exit-codes-and-output.md +74 -0
  128. package/docs/extensions-and-sharing.md +306 -0
  129. package/docs/fleet-demo-runbook.md +179 -0
  130. package/docs/fleet-dispatch.md +591 -0
  131. package/docs/glossary.md +75 -0
  132. package/docs/html/agents_blueprint.html +936 -0
  133. package/docs/html/alcf_blueprint.html +324 -0
  134. package/docs/html/architecture_blueprint.html +850 -0
  135. package/docs/html/commands_blueprint.html +794 -0
  136. package/docs/html/config_knobs_audit_blueprint.html +178 -0
  137. package/docs/html/configuration_blueprint.html +1080 -0
  138. package/docs/html/context_blueprint.html +603 -0
  139. package/docs/html/documentation_blueprint.html +832 -0
  140. package/docs/html/environment_blueprint.html +404 -0
  141. package/docs/html/eval_blueprint.html +743 -0
  142. package/docs/html/evals_internal_blueprint.html +190 -0
  143. package/docs/html/evolution_blueprint.html +674 -0
  144. package/docs/html/extensions_blueprint.html +2065 -0
  145. package/docs/html/fleet_dispatch_blueprint.html +286 -0
  146. package/docs/html/index.html +919 -0
  147. package/docs/html/lifecycle_blueprint.html +723 -0
  148. package/docs/html/memory_blueprint.html +699 -0
  149. package/docs/html/middleware_blueprint.html +664 -0
  150. package/docs/html/models_blueprint.html +2366 -0
  151. package/docs/html/observability_blueprint.html +683 -0
  152. package/docs/html/provider_adapter_blueprint.html +245 -0
  153. package/docs/html/safety_blueprint.html +1386 -0
  154. package/docs/html/shared.css +571 -0
  155. package/docs/html/shared.js +143 -0
  156. package/docs/html/skills_blueprint.html +671 -0
  157. package/docs/html/soak_blueprint.html +182 -0
  158. package/docs/html/tool_usage_blueprint.html +350 -0
  159. package/docs/html/tools_blueprint.html +2249 -0
  160. package/docs/html/trace_blueprint.html +235 -0
  161. package/docs/html/tui_design_blueprint.html +314 -0
  162. package/docs/html/validation_blueprint.html +961 -0
  163. package/docs/html/worker_dispatch_blueprint.html +231 -0
  164. package/docs/installation-and-lifecycle.md +308 -0
  165. package/docs/middleware-and-components.md +148 -0
  166. package/docs/model-catalog.md +189 -0
  167. package/docs/observability.md +233 -0
  168. package/docs/proactive-memory.md +452 -0
  169. package/docs/prompt-envelope-and-tools.md +142 -0
  170. package/docs/provider-adapter-cookbook.md +148 -0
  171. package/docs/release-cut-checklist.md +138 -0
  172. package/docs/safety-model.md +357 -0
  173. package/docs/scientific-validation.md +105 -0
  174. package/docs/session-lifecycle.md +156 -0
  175. package/docs/skills-marketplace.md +46 -0
  176. package/docs/tool-usage.md +527 -0
  177. package/docs/trace-store.md +132 -0
  178. package/docs/troubleshooting.md +33 -0
  179. package/docs/tui-design.md +239 -0
  180. package/docs/worker-dispatch-mechanics.md +242 -0
  181. package/package.json +132 -0
  182. package/skills/README.md +408 -0
  183. package/skills/git/commit-crafting/SKILL.md +79 -0
  184. package/skills/git/commit-crafting/evals.md +92 -0
  185. package/skills/git/create-pr/SKILL.md +116 -0
  186. package/skills/git/create-pr/evals.md +114 -0
  187. package/skills/git/investigate-issue/SKILL.md +139 -0
  188. package/skills/git/investigate-issue/evals.md +94 -0
  189. package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
  190. package/skills/git/resolve-merge-conflicts/evals.md +58 -0
  191. package/skills/git/review-changes/SKILL.md +103 -0
  192. package/skills/git/review-changes/evals.md +85 -0
  193. package/skills/git/worktree-create/SKILL.md +92 -0
  194. package/skills/git/worktree-create/evals.md +97 -0
  195. package/skills/git/worktree-create/references/worktree-setup.md +66 -0
  196. package/skills/git/worktree-merge/SKILL.md +95 -0
  197. package/skills/git/worktree-merge/evals.md +114 -0
  198. package/skills/skill-marketplace.json +261 -0
  199. package/skills/workflow/cut-it/SKILL.md +86 -0
  200. package/skills/workflow/cut-it/evals.md +42 -0
  201. package/src/domains/agents/builtins/architect.md +33 -0
  202. package/src/domains/agents/builtins/coder.md +31 -0
  203. package/src/domains/agents/builtins/context-bootstrap.md +38 -0
  204. package/src/domains/agents/builtins/debugger.md +30 -0
  205. package/src/domains/agents/builtins/documenter.md +31 -0
  206. package/src/domains/agents/builtins/git-master.md +30 -0
  207. package/src/domains/agents/builtins/provenance.md +30 -0
  208. package/src/domains/agents/builtins/researcher.md +71 -0
  209. package/src/domains/agents/builtins/scout.md +42 -0
  210. package/src/domains/agents/builtins/tester.md +31 -0
  211. package/src/domains/agents/builtins/verifier.md +30 -0
  212. package/src/domains/agents/builtins/wiki-writer.md +41 -0
  213. package/src/domains/agents/fleets/build-review.md +34 -0
  214. package/src/domains/agents/fleets/build-test.md +35 -0
  215. package/src/domains/agents/fleets/sdlc.md +86 -0
  216. package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
  217. package/src/domains/prompts/fragments/identity/clio.md +26 -0
  218. package/src/domains/prompts/fragments/operating/contract.md +64 -0
  219. package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
  220. package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
  221. package/src/domains/prompts/fragments/safety/read-only.md +13 -0
  222. package/src/domains/prompts/fragments/safety/suggest.md +13 -0
  223. package/src/domains/prompts/fragments/wiki/page.md +75 -0
  224. package/src/domains/prompts/fragments/wiki/plan.md +48 -0
  225. package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
  226. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
@@ -0,0 +1,1224 @@
1
+ import {
2
+ shellQuote
3
+ } from "./chunk-GOTUR54M.js";
4
+ import {
5
+ atomicWrite
6
+ } from "./chunk-UGYAX5YI.js";
7
+ import {
8
+ clioStateDir
9
+ } from "./chunk-X3NE4WVW.js";
10
+ import {
11
+ readClioVersion,
12
+ resolvePackageRoot
13
+ } from "./chunk-DXQNI4PC.js";
14
+
15
+ // src/domains/dispatch/fleet-preflight.ts
16
+ import { execFile } from "node:child_process";
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { join as join2 } from "node:path";
19
+
20
+ // src/domains/dispatch/route-facts.ts
21
+ var ROUTE_FACT_FRESHNESS_MS = 15 * 60 * 1e3;
22
+ function isFresh(probedAt, now, freshnessMs) {
23
+ const at = Date.parse(probedAt);
24
+ if (!Number.isFinite(at)) return false;
25
+ return at <= now && now - at <= freshnessMs;
26
+ }
27
+ function evaluateRouteFacts(facts, resources, requirement, options) {
28
+ const now = options?.now ?? Date.now();
29
+ const freshnessMs = options?.freshnessMs ?? ROUTE_FACT_FRESHNESS_MS;
30
+ const unknowns = [];
31
+ const fact = facts.find((entry) => entry.nodeId === requirement.nodeId && entry.targetId === requirement.targetId);
32
+ if (!fact) {
33
+ const unknowns2 = ["reachable", "runtimeCompatible", "modelAvailable"];
34
+ if (requirement.mode === "shadow") return { ok: true, unknowns: unknowns2 };
35
+ return {
36
+ ok: false,
37
+ reason: `no probe fact for target '${requirement.targetId}' on node '${requirement.nodeId}'; run 'clio-coder doctor'`,
38
+ unknowns: unknowns2
39
+ };
40
+ }
41
+ const fresh = isFresh(fact.probedAt, now, freshnessMs);
42
+ if (fact.endpointIdentityHash !== requirement.endpointIdentityHash) {
43
+ return {
44
+ ok: false,
45
+ reason: `node '${requirement.nodeId}' endpoint identity for target '${requirement.targetId}' does not match settings`,
46
+ unknowns
47
+ };
48
+ }
49
+ const checks = [
50
+ ["reachable", requirement.requireReachable, fact.reachable],
51
+ ["runtimeCompatible", requirement.requireRuntimeCompatible, fact.runtimeCompatible],
52
+ ["modelAvailable", requirement.requireModelAvailable, fact.modelAvailable]
53
+ ];
54
+ for (const [name, required, state] of checks) {
55
+ if (state === "unknown") unknowns.push(name);
56
+ if (!required) continue;
57
+ if (state === "false") {
58
+ return {
59
+ ok: false,
60
+ reason: `node '${requirement.nodeId}' proved ${name}=false for target '${requirement.targetId}'`,
61
+ unknowns
62
+ };
63
+ }
64
+ if (state === "unknown" && requirement.mode === "active") {
65
+ return {
66
+ ok: false,
67
+ reason: `node '${requirement.nodeId}' has no ${name} evidence for target '${requirement.targetId}'`,
68
+ unknowns
69
+ };
70
+ }
71
+ if (!fresh && requirement.mode === "active") {
72
+ return {
73
+ ok: false,
74
+ reason: `node '${requirement.nodeId}' ${name} evidence for target '${requirement.targetId}' is stale (probed ${fact.probedAt})`,
75
+ unknowns
76
+ };
77
+ }
78
+ }
79
+ if (requirement.requireModelAvailable && requirement.wireModelId !== null && fact.wireModelId !== null && fact.wireModelId !== requirement.wireModelId) {
80
+ return {
81
+ ok: false,
82
+ reason: `node '${requirement.nodeId}' model evidence names '${fact.wireModelId}', not '${requirement.wireModelId}'`,
83
+ unknowns
84
+ };
85
+ }
86
+ const resource = resources.find((entry) => entry.nodeId === requirement.nodeId);
87
+ const resourceChecks = [
88
+ ["gpuCount", requirement.requireGpuCount, resource?.gpuCount],
89
+ ["vramBytes", requirement.requireVramBytes, resource?.vramBytes]
90
+ ];
91
+ for (const [name, required, observed] of resourceChecks) {
92
+ if (observed === null || observed === void 0) {
93
+ if (required !== null) unknowns.push(name);
94
+ if (required !== null && requirement.mode === "active") {
95
+ return {
96
+ ok: false,
97
+ reason: `node '${requirement.nodeId}' reports unknown ${name}; a declared fit requirement cannot be satisfied by unknown`,
98
+ unknowns
99
+ };
100
+ }
101
+ continue;
102
+ }
103
+ if (required !== null && observed < required) {
104
+ return {
105
+ ok: false,
106
+ reason: `node '${requirement.nodeId}' ${name} ${observed} is below the declared requirement ${required}`,
107
+ unknowns
108
+ };
109
+ }
110
+ }
111
+ return { ok: true, unknowns };
112
+ }
113
+
114
+ // src/domains/dispatch/transport.ts
115
+ import { spawn as spawn2 } from "node:child_process";
116
+
117
+ // src/domains/dispatch/worker-spawn.ts
118
+ import { spawn } from "node:child_process";
119
+ import { join } from "node:path";
120
+
121
+ // src/domains/dispatch/worker-protocol.ts
122
+ import { createHash as createHash2 } from "node:crypto";
123
+
124
+ // src/worker/protocol.ts
125
+ import { createHash } from "node:crypto";
126
+ var WORKER_PROTOCOL_VERSION = 1;
127
+ var CONTROL_FRAME_PREFIX = "@clio-control/1 ";
128
+ var WORKER_CONTROL_FRAME_MAX_BYTES = 16 * 1024;
129
+ var WORKER_BULK_FRAME_MAX_BYTES = 4 * 1024 * 1024;
130
+ var WORKER_STDIN_FRAME_MAX_BYTES = 1024 * 1024;
131
+ var WORKER_STDIN_QUEUE_MAX_BYTES = 4 * 1024 * 1024;
132
+ var WORKER_EVENT_QUEUE_MAX_FRAMES = 4096;
133
+ function knownResource(value) {
134
+ return { known: true, value };
135
+ }
136
+ var UNKNOWN_RESOURCE = { known: false };
137
+ var WORKER_RESOURCE_LABEL_MAX = 32;
138
+ var WORKER_RESIDENT_MODEL_MAX = 64;
139
+ var AGENT_LEDGER_SCOPE_MAX_ENTRIES = 8;
140
+ var AGENT_LEDGER_SCOPE_ENTRY_MAX_CHARS = 200;
141
+ var AGENT_LEDGER_INTENT_MAX_CHARS = 200;
142
+ var AGENT_LEDGER_CLAIM_MAX_CHARS = 400;
143
+ var AGENT_LEDGER_EVIDENCE_MAX_CHARS = 400;
144
+ var AGENT_LEDGER_PATH_MAX_CHARS = 400;
145
+ var AGENT_LEDGER_ENTRY_ID = /^e[1-9][0-9]*$/;
146
+ function isAgentLedgerEntryId(value) {
147
+ return typeof value === "string" && AGENT_LEDGER_ENTRY_ID.test(value);
148
+ }
149
+ function boundedString(value, max) {
150
+ return typeof value === "string" && value.length > 0 && value.length <= max;
151
+ }
152
+ function parseAgentLedgerBody(value) {
153
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
154
+ return { ok: false, reason: "ledger body is not an object" };
155
+ }
156
+ const record = value;
157
+ switch (record.kind) {
158
+ case "claim": {
159
+ const scope = record.scope;
160
+ if (!Array.isArray(scope) || scope.length === 0) {
161
+ return { ok: false, reason: "claim scope must be a non-empty array of path prefixes" };
162
+ }
163
+ if (scope.length > AGENT_LEDGER_SCOPE_MAX_ENTRIES) {
164
+ return { ok: false, reason: `claim scope exceeds ${AGENT_LEDGER_SCOPE_MAX_ENTRIES} entries` };
165
+ }
166
+ for (const entry of scope) {
167
+ if (!boundedString(entry, AGENT_LEDGER_SCOPE_ENTRY_MAX_CHARS)) {
168
+ return {
169
+ ok: false,
170
+ reason: `claim scope entries must be 1..${AGENT_LEDGER_SCOPE_ENTRY_MAX_CHARS} characters`
171
+ };
172
+ }
173
+ }
174
+ if (!boundedString(record.intent, AGENT_LEDGER_INTENT_MAX_CHARS)) {
175
+ return { ok: false, reason: `claim intent must be 1..${AGENT_LEDGER_INTENT_MAX_CHARS} characters` };
176
+ }
177
+ return { ok: true, body: { kind: "claim", scope: scope.map(String), intent: record.intent } };
178
+ }
179
+ case "finding": {
180
+ if (!boundedString(record.claim, AGENT_LEDGER_CLAIM_MAX_CHARS)) {
181
+ return { ok: false, reason: `finding claim must be 1..${AGENT_LEDGER_CLAIM_MAX_CHARS} characters` };
182
+ }
183
+ const body = {
184
+ kind: "finding",
185
+ claim: record.claim
186
+ };
187
+ if (record.path !== void 0) {
188
+ if (!boundedString(record.path, AGENT_LEDGER_PATH_MAX_CHARS)) {
189
+ return { ok: false, reason: `finding path must be 1..${AGENT_LEDGER_PATH_MAX_CHARS} characters` };
190
+ }
191
+ body.path = record.path;
192
+ }
193
+ if (record.line !== void 0) {
194
+ const line = record.line;
195
+ if (typeof line !== "number" || !Number.isSafeInteger(line) || line <= 0) {
196
+ return { ok: false, reason: "finding line must be a positive safe integer" };
197
+ }
198
+ body.line = line;
199
+ }
200
+ return { ok: true, body };
201
+ }
202
+ case "review": {
203
+ if (!isAgentLedgerEntryId(record.target)) {
204
+ return { ok: false, reason: "review target must be a ledger entry id such as e3" };
205
+ }
206
+ if (typeof record.passed !== "boolean") {
207
+ return { ok: false, reason: "review passed must be a boolean" };
208
+ }
209
+ if (!boundedString(record.evidence, AGENT_LEDGER_EVIDENCE_MAX_CHARS)) {
210
+ return { ok: false, reason: `review evidence must be 1..${AGENT_LEDGER_EVIDENCE_MAX_CHARS} characters` };
211
+ }
212
+ return {
213
+ ok: true,
214
+ body: { kind: "review", target: record.target, passed: record.passed, evidence: record.evidence }
215
+ };
216
+ }
217
+ default:
218
+ return { ok: false, reason: `unknown ledger entry kind ${String(record.kind)}` };
219
+ }
220
+ }
221
+ function parseAgentLedgerEntry(value) {
222
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
223
+ const record = value;
224
+ const sequence = record.sequence;
225
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence <= 0) return null;
226
+ if (!isAgentLedgerEntryId(record.id)) return null;
227
+ for (const key of ["at", "runId", "assignmentId", "agentId", "nodeId"]) {
228
+ if (typeof record[key] !== "string" || record[key].length === 0) return null;
229
+ }
230
+ const body = parseAgentLedgerBody(record.body);
231
+ if (!body.ok) return null;
232
+ const conflicts = record.conflictsWith;
233
+ if (conflicts !== void 0 && !(Array.isArray(conflicts) && conflicts.every(isAgentLedgerEntryId))) return null;
234
+ return {
235
+ id: record.id,
236
+ sequence,
237
+ at: record.at,
238
+ runId: record.runId,
239
+ assignmentId: record.assignmentId,
240
+ agentId: record.agentId,
241
+ nodeId: record.nodeId,
242
+ body: body.body,
243
+ ...conflicts === void 0 ? {} : { conflictsWith: [...conflicts] }
244
+ };
245
+ }
246
+ function sha256Hex(input) {
247
+ return createHash("sha256").update(input, "utf8").digest("hex");
248
+ }
249
+ var HEX_64 = /^[0-9a-f]{64}$/;
250
+ function endpointIdentityHash(url) {
251
+ if (url === void 0 || url.trim().length === 0) return sha256Hex("clio.endpoint:none");
252
+ const raw = url.trim();
253
+ let canonical;
254
+ try {
255
+ const parsed = new URL(raw);
256
+ const path = parsed.pathname.replace(/\/+$/u, "");
257
+ canonical = `${parsed.protocol}//${parsed.hostname}:${parsed.port}${path}`;
258
+ } catch {
259
+ canonical = raw.replace(/\/+$/u, "");
260
+ }
261
+ return sha256Hex(`clio.endpoint:${canonical}`);
262
+ }
263
+ function workerSpecDigest(spec) {
264
+ return sha256Hex(`clio.workerSpec:${canonicalJson(spec)}`);
265
+ }
266
+ function toolSignatureOf(names) {
267
+ return sha256Hex(`clio.tools:${[...names].sort().join(",")}`);
268
+ }
269
+ function canonicalJson(value) {
270
+ if (value === null) return "null";
271
+ if (typeof value === "number") return Number.isFinite(value) ? JSON.stringify(value) : "null";
272
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
273
+ if (Array.isArray(value))
274
+ return `[${value.map((entry) => entry === void 0 ? "null" : canonicalJson(entry)).join(",")}]`;
275
+ if (typeof value === "object") {
276
+ const record = value;
277
+ const parts = [];
278
+ for (const key of Object.keys(record).sort()) {
279
+ const child = record[key];
280
+ if (child === void 0) continue;
281
+ parts.push(`${JSON.stringify(key)}:${canonicalJson(child)}`);
282
+ }
283
+ return `{${parts.join(",")}}`;
284
+ }
285
+ return "null";
286
+ }
287
+ function withinFrameBudget(line, maxBytes) {
288
+ return Buffer.byteLength(line, "utf8") <= maxBytes;
289
+ }
290
+ function parseJsonObject(line, maxBytes, lane) {
291
+ if (!withinFrameBudget(line, maxBytes)) {
292
+ return { ok: false, reason: `${lane} frame exceeds ${maxBytes} bytes` };
293
+ }
294
+ let value;
295
+ try {
296
+ value = JSON.parse(line);
297
+ } catch {
298
+ return { ok: false, reason: `${lane} frame is not JSON` };
299
+ }
300
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
301
+ return { ok: false, reason: `${lane} frame is not a JSON object` };
302
+ }
303
+ return { ok: true, value };
304
+ }
305
+ function parseBulkFrame(line) {
306
+ return parseJsonObject(line, WORKER_BULK_FRAME_MAX_BYTES, "bulk");
307
+ }
308
+ function isControlLine(line) {
309
+ return line.startsWith(CONTROL_FRAME_PREFIX);
310
+ }
311
+ function encodeControlFrame(frame) {
312
+ return `${CONTROL_FRAME_PREFIX}${JSON.stringify(frame)}
313
+ `;
314
+ }
315
+ function readFiniteNumber(record, key) {
316
+ const value = record[key];
317
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
318
+ }
319
+ function readResourceValue(value, validate) {
320
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
321
+ const record = value;
322
+ if (record.known === false) return { known: false };
323
+ if (record.known !== true || !validate(record.value)) return null;
324
+ return { known: true, value: record.value };
325
+ }
326
+ function isFiniteNumber(value) {
327
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
328
+ }
329
+ function isBoundedStringArray(value, max) {
330
+ return Array.isArray(value) && value.length <= max && value.every((entry) => typeof entry === "string");
331
+ }
332
+ function parseResourceFacts(value) {
333
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
334
+ const record = value;
335
+ if (!isBoundedStringArray(record.labels, WORKER_RESOURCE_LABEL_MAX)) return null;
336
+ const numeric = ["cpuCount", "totalMemoryBytes", "freeMemoryBytes", "gpuCount", "vramBytes"];
337
+ const parsed = { labels: [...record.labels] };
338
+ for (const key of numeric) {
339
+ const fact = readResourceValue(record[key], isFiniteNumber);
340
+ if (fact === null) return null;
341
+ parsed[key] = fact;
342
+ }
343
+ const residentModels = readResourceValue(
344
+ record.residentModels,
345
+ (raw) => isBoundedStringArray(raw, WORKER_RESIDENT_MODEL_MAX)
346
+ );
347
+ if (residentModels === null) return null;
348
+ parsed.residentModels = residentModels;
349
+ return parsed;
350
+ }
351
+ function parseAttestation(value) {
352
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
353
+ return { ok: false, reason: "announce attestation is not an object" };
354
+ }
355
+ const record = value;
356
+ if (record.protocolVersion !== WORKER_PROTOCOL_VERSION) {
357
+ return { ok: false, reason: `announce protocol version ${String(record.protocolVersion)} is unsupported` };
358
+ }
359
+ const specVersion = readFiniteNumber(record, "specVersion");
360
+ if (specVersion === null) return { ok: false, reason: "announce specVersion must be a finite number" };
361
+ const pid = readFiniteNumber(record, "pid");
362
+ if (pid === null) return { ok: false, reason: "announce pid must be a finite number" };
363
+ const rawPgid = record.processGroupId;
364
+ if (rawPgid !== null && !(typeof rawPgid === "number" && Number.isFinite(rawPgid))) {
365
+ return { ok: false, reason: "announce processGroupId must be a finite number or null" };
366
+ }
367
+ for (const key of ["host", "runtimeId", "targetId", "wireModelId"]) {
368
+ if (typeof record[key] !== "string" || record[key].length === 0) {
369
+ return { ok: false, reason: `announce ${key} must be a non-empty string` };
370
+ }
371
+ }
372
+ for (const key of ["settingsFingerprint", "specDigest", "endpointIdentityHash", "toolSignature"]) {
373
+ if (typeof record[key] !== "string" || !HEX_64.test(record[key])) {
374
+ return { ok: false, reason: `announce ${key} must be a sha256 hex digest` };
375
+ }
376
+ }
377
+ const resources = parseResourceFacts(record.resources);
378
+ if (resources === null) return { ok: false, reason: "announce resources are missing or malformed" };
379
+ return {
380
+ ok: true,
381
+ value: {
382
+ protocolVersion: WORKER_PROTOCOL_VERSION,
383
+ specVersion,
384
+ pid,
385
+ processGroupId: typeof rawPgid === "number" ? rawPgid : null,
386
+ host: record.host,
387
+ settingsFingerprint: record.settingsFingerprint,
388
+ specDigest: record.specDigest,
389
+ runtimeId: record.runtimeId,
390
+ targetId: record.targetId,
391
+ endpointIdentityHash: record.endpointIdentityHash,
392
+ wireModelId: record.wireModelId,
393
+ toolSignature: record.toolSignature,
394
+ resources
395
+ }
396
+ };
397
+ }
398
+ function parseControlFrame(line) {
399
+ if (!isControlLine(line)) return { ok: false, reason: "control frame is missing its lane marker" };
400
+ const body = line.slice(CONTROL_FRAME_PREFIX.length);
401
+ const parsed = parseJsonObject(body, WORKER_CONTROL_FRAME_MAX_BYTES, "control");
402
+ if (!parsed.ok) return parsed;
403
+ const record = parsed.value;
404
+ switch (record.kind) {
405
+ case "announce": {
406
+ const attestation = parseAttestation(record.attestation);
407
+ if (!attestation.ok) return attestation;
408
+ return { ok: true, value: { kind: "announce", attestation: attestation.value } };
409
+ }
410
+ case "heartbeat": {
411
+ const at = readFiniteNumber(record, "at");
412
+ if (at === null) return { ok: false, reason: "heartbeat frame requires a finite at" };
413
+ return { ok: true, value: { kind: "heartbeat", at } };
414
+ }
415
+ case "cancel_ack": {
416
+ const at = readFiniteNumber(record, "at");
417
+ if (at === null) return { ok: false, reason: "cancel_ack frame requires a finite at" };
418
+ return { ok: true, value: { kind: "cancel_ack", at } };
419
+ }
420
+ case "ledger_post": {
421
+ const body2 = parseAgentLedgerBody(record.body);
422
+ if (!body2.ok) return { ok: false, reason: `ledger_post frame rejected: ${body2.reason}` };
423
+ return { ok: true, value: { kind: "ledger_post", body: body2.body } };
424
+ }
425
+ default:
426
+ return { ok: false, reason: `unknown control frame kind ${String(record.kind)}` };
427
+ }
428
+ }
429
+ var RECEIPT_BEARING_BULK_TYPES = /* @__PURE__ */ new Set([
430
+ "message_end",
431
+ "clio_run_outcome",
432
+ "clio_permission_escalated",
433
+ "clio_permission_resolved",
434
+ "clio_steer_received",
435
+ "clio_tool_activity",
436
+ "clio_skill_activation",
437
+ "clio_safety_decision",
438
+ "clio_verification",
439
+ "clio_usage",
440
+ "tool_execution_end",
441
+ "spawn_error"
442
+ ]);
443
+ function isReceiptBearingFrame(value) {
444
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return true;
445
+ const type = value.type;
446
+ if (typeof type !== "string") return true;
447
+ return RECEIPT_BEARING_BULK_TYPES.has(type);
448
+ }
449
+
450
+ // src/domains/dispatch/worker-protocol.ts
451
+ function computeSettingsFingerprint(settings) {
452
+ return createHash2("sha256").update(`clio.settings:${canonicalJson(settings)}`, "utf8").digest("hex");
453
+ }
454
+ function verifyWorkerAttestation(attestation, approved) {
455
+ if (attestation.protocolVersion !== WORKER_PROTOCOL_VERSION) {
456
+ return {
457
+ ok: false,
458
+ reason: `worker protocol mismatch: approved ${WORKER_PROTOCOL_VERSION}, worker announced ${attestation.protocolVersion}`
459
+ };
460
+ }
461
+ const fields = [
462
+ ["WorkerSpec version", approved.specVersion, attestation.specVersion],
463
+ ["settings fingerprint", approved.settingsFingerprint, attestation.settingsFingerprint],
464
+ ["WorkerSpec digest", approved.specDigest, attestation.specDigest],
465
+ ["runtime", approved.runtimeId, attestation.runtimeId],
466
+ ["target", approved.targetId, attestation.targetId],
467
+ ["endpoint identity", approved.endpointIdentityHash, attestation.endpointIdentityHash],
468
+ ["wire model", approved.wireModelId, attestation.wireModelId],
469
+ ["tool surface", approved.toolSignature, attestation.toolSignature]
470
+ ];
471
+ for (const [label, expected, announced] of fields) {
472
+ if (!Object.is(expected, announced)) {
473
+ return {
474
+ ok: false,
475
+ reason: `${label} drift: dispatched ${String(expected)}, worker announced ${String(announced)}`
476
+ };
477
+ }
478
+ }
479
+ return { ok: true };
480
+ }
481
+ function approvedIdentityForSpec(spec) {
482
+ return {
483
+ specVersion: spec.specVersion,
484
+ settingsFingerprint: spec.settingsFingerprint,
485
+ specDigest: workerSpecDigestOf(spec),
486
+ runtimeId: spec.runtimeId,
487
+ targetId: spec.target.id,
488
+ endpointIdentityHash: endpointIdentityHash(spec.target.url),
489
+ wireModelId: spec.wireModelId,
490
+ // The spec carries the final runtime-narrowed admission surface. The
491
+ // worker fingerprints its independently resolved registry intersection,
492
+ // so a missing, added, or differently filtered tool fails admission.
493
+ toolSignature: toolSignatureOf(spec.allowedTools)
494
+ };
495
+ }
496
+ function workerSpecDigestOf(spec) {
497
+ return createHash2("sha256").update(`clio.workerSpec:${canonicalJson(spec)}`, "utf8").digest("hex");
498
+ }
499
+ function resourceOrNull(value) {
500
+ return value.known ? value.value : null;
501
+ }
502
+ function receiptAttestationFields(attestation) {
503
+ if (attestation === null) return {};
504
+ return {
505
+ attestation: {
506
+ protocolVersion: attestation.protocolVersion,
507
+ host: attestation.host,
508
+ pid: attestation.pid,
509
+ processGroupId: attestation.processGroupId,
510
+ settingsFingerprint: attestation.settingsFingerprint,
511
+ specDigest: attestation.specDigest,
512
+ targetId: attestation.targetId,
513
+ endpointIdentityHash: attestation.endpointIdentityHash,
514
+ wireModelId: attestation.wireModelId,
515
+ runtimeId: attestation.runtimeId,
516
+ toolSignature: attestation.toolSignature,
517
+ resources: {
518
+ labels: [...attestation.resources.labels],
519
+ cpuCount: resourceOrNull(attestation.resources.cpuCount),
520
+ totalMemoryBytes: resourceOrNull(attestation.resources.totalMemoryBytes),
521
+ gpuCount: resourceOrNull(attestation.resources.gpuCount),
522
+ vramBytes: resourceOrNull(attestation.resources.vramBytes)
523
+ }
524
+ }
525
+ };
526
+ }
527
+ var WorkerChannelFailure = class extends Error {
528
+ failureClass = "node-channel";
529
+ operation;
530
+ constructor(operation, detail) {
531
+ super(`worker control channel failure during ${operation}: ${detail}`);
532
+ this.name = "WorkerChannelFailure";
533
+ this.operation = operation;
534
+ }
535
+ };
536
+ function createBoundedEventQueue(maxFrames = WORKER_EVENT_QUEUE_MAX_FRAMES) {
537
+ const items = [];
538
+ let droppedDisplayFrames = 0;
539
+ let peakDepth = 0;
540
+ function dropOneDisplayFrame() {
541
+ for (let index = 0; index < items.length; index += 1) {
542
+ if (!isReceiptBearingFrame(items[index])) {
543
+ items.splice(index, 1);
544
+ droppedDisplayFrames += 1;
545
+ return true;
546
+ }
547
+ }
548
+ return false;
549
+ }
550
+ return {
551
+ push(value) {
552
+ if (items.length >= maxFrames && !dropOneDisplayFrame()) {
553
+ if (isReceiptBearingFrame(value)) {
554
+ items.push(value);
555
+ peakDepth = Math.max(peakDepth, items.length);
556
+ return true;
557
+ }
558
+ droppedDisplayFrames += 1;
559
+ return false;
560
+ }
561
+ items.push(value);
562
+ peakDepth = Math.max(peakDepth, items.length);
563
+ return true;
564
+ },
565
+ shift() {
566
+ return items.shift();
567
+ },
568
+ get size() {
569
+ return items.length;
570
+ },
571
+ stats() {
572
+ return { droppedDisplayFrames, peakDepth };
573
+ }
574
+ };
575
+ }
576
+
577
+ // src/domains/dispatch/worker-spawn.ts
578
+ var DEFAULT_SHUTDOWN_GRACE_MS = 500;
579
+ var DEFAULT_ATTESTATION_GRACE_MS = 2e3;
580
+ var STDERR_TAIL_BYTES = 4096;
581
+ function signalProcessGroup(child, signal) {
582
+ const pid = child.pid;
583
+ if (pid !== void 0 && pid !== null) {
584
+ try {
585
+ process.kill(-pid, signal);
586
+ return;
587
+ } catch {
588
+ }
589
+ }
590
+ try {
591
+ child.kill(signal);
592
+ } catch {
593
+ }
594
+ }
595
+ function spawnWorkerProcess(command, args, spec, opts) {
596
+ const shutdownGraceMs = opts?.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;
597
+ const attestationGraceMs = opts?.attestationGraceMs ?? DEFAULT_ATTESTATION_GRACE_MS;
598
+ const approved = opts?.approvedIdentity ?? approvedIdentityForSpec(spec);
599
+ const child = spawn(command, [...args], {
600
+ stdio: ["pipe", "pipe", "pipe"],
601
+ cwd: opts?.cwd,
602
+ env: opts?.env ?? process.env,
603
+ // The worker leads its own process group so abort escalation covers the
604
+ // descendants a runtime spawned, not only the immediate child.
605
+ detached: true
606
+ });
607
+ const pid = child.pid ?? null;
608
+ const heartbeatAt = { current: Date.now() };
609
+ const queue = createBoundedEventQueue();
610
+ const waiters = [];
611
+ let finished = false;
612
+ let malformedStdoutLines = 0;
613
+ let stderrTail = Buffer.alloc(0);
614
+ let channelFailure = null;
615
+ let attestation = null;
616
+ function appendStderr(chunk) {
617
+ const next = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
618
+ if (next.length === 0) return;
619
+ const combined = Buffer.concat([stderrTail, next]);
620
+ stderrTail = combined.length > STDERR_TAIL_BYTES ? combined.subarray(combined.length - STDERR_TAIL_BYTES) : combined;
621
+ }
622
+ function diagnostics() {
623
+ const stderrText = stderrTail.toString("utf8").trim();
624
+ const dropped = queue.stats().droppedDisplayFrames + preAnnounce.stats().droppedDisplayFrames;
625
+ return {
626
+ ...stderrText.length > 0 ? { stderrTail: stderrText } : {},
627
+ ...malformedStdoutLines > 0 ? { malformedStdoutLines } : {},
628
+ ...dropped > 0 ? { droppedDisplayFrames: dropped } : {},
629
+ ...channelFailure !== null ? { channelFailure: channelFailure.operation } : {}
630
+ };
631
+ }
632
+ function push(value) {
633
+ heartbeatAt.current = Date.now();
634
+ const w = waiters.shift();
635
+ if (w) {
636
+ w({ value, done: false });
637
+ return;
638
+ }
639
+ queue.push(value);
640
+ }
641
+ function end() {
642
+ if (finished) return;
643
+ finished = true;
644
+ while (waiters.length > 0) {
645
+ const w = waiters.shift();
646
+ if (w) w({ value: void 0, done: true });
647
+ }
648
+ }
649
+ let sawSpawnError = false;
650
+ let announceAccepted = false;
651
+ let announceFailed = false;
652
+ function failAttestation(message) {
653
+ if (announceFailed) return;
654
+ announceFailed = true;
655
+ appendStderr(`[worker] ${message}
656
+ `);
657
+ signalProcessGroup(child, "SIGKILL");
658
+ }
659
+ child.once("error", (err) => {
660
+ sawSpawnError = true;
661
+ push({
662
+ type: "spawn_error",
663
+ error: err instanceof Error ? err.message : String(err)
664
+ });
665
+ signalProcessGroup(child, "SIGKILL");
666
+ });
667
+ if (pid !== null && child.stdin) {
668
+ child.stdin.on("error", (err) => {
669
+ channelFailure = new WorkerChannelFailure("spec", err instanceof Error ? err.message : String(err));
670
+ appendStderr(`[worker] ${channelFailure.message}
671
+ `);
672
+ });
673
+ child.stdin.write(`${JSON.stringify(spec)}
674
+ `);
675
+ }
676
+ function createLineReader(maxBytes, onLine, onOversize) {
677
+ let buffer = "";
678
+ let discarding = false;
679
+ return (chunk) => {
680
+ buffer += chunk.toString("utf8");
681
+ let idx = buffer.indexOf("\n");
682
+ while (idx >= 0) {
683
+ const line = buffer.slice(0, idx);
684
+ buffer = buffer.slice(idx + 1);
685
+ if (discarding) {
686
+ discarding = false;
687
+ onOversize();
688
+ } else if (Buffer.byteLength(line, "utf8") > maxBytes) {
689
+ onOversize();
690
+ } else {
691
+ onLine(line);
692
+ }
693
+ idx = buffer.indexOf("\n");
694
+ }
695
+ if (Buffer.byteLength(buffer, "utf8") > maxBytes) {
696
+ buffer = "";
697
+ discarding = true;
698
+ }
699
+ };
700
+ }
701
+ const preAnnounce = createBoundedEventQueue();
702
+ let attestationGraceTimer = null;
703
+ function releaseHeldFrames() {
704
+ if (attestationGraceTimer !== null) {
705
+ clearTimeout(attestationGraceTimer);
706
+ attestationGraceTimer = null;
707
+ }
708
+ while (preAnnounce.size > 0) push(preAnnounce.shift());
709
+ }
710
+ function startAttestationGrace() {
711
+ if (attestationGraceTimer !== null || announceAccepted || announceFailed) return;
712
+ attestationGraceTimer = setTimeout(() => {
713
+ attestationGraceTimer = null;
714
+ if (announceAccepted || announceFailed) return;
715
+ failAttestation("Missing worker attestation: the peer produced output without announcing its route identity");
716
+ }, attestationGraceMs);
717
+ attestationGraceTimer.unref?.();
718
+ }
719
+ function handleBulkLine(line) {
720
+ const trimmed = line.trim();
721
+ if (trimmed.length === 0) return;
722
+ const frame = parseBulkFrame(trimmed);
723
+ if (!frame.ok) {
724
+ malformedStdoutLines += 1;
725
+ return;
726
+ }
727
+ if (announceFailed) return;
728
+ if (!announceAccepted) {
729
+ preAnnounce.push(frame.value);
730
+ startAttestationGrace();
731
+ return;
732
+ }
733
+ push(frame.value);
734
+ }
735
+ function handleControlLine(line) {
736
+ const frame = parseControlFrame(line);
737
+ if (!frame.ok) {
738
+ appendStderr(`[worker] rejected control frame: ${frame.reason}
739
+ `);
740
+ if (!announceAccepted) failAttestation(`Invalid worker attestation: ${frame.reason}`);
741
+ return;
742
+ }
743
+ heartbeatAt.current = Date.now();
744
+ if (frame.value.kind === "announce") {
745
+ if (announceAccepted || announceFailed) return;
746
+ const verdict = verifyWorkerAttestation(frame.value.attestation, approved);
747
+ if (!verdict.ok) {
748
+ failAttestation(`Worker attestation rejected: ${verdict.reason}`);
749
+ return;
750
+ }
751
+ announceAccepted = true;
752
+ attestation = frame.value.attestation;
753
+ opts?.onAnnounce?.(frame.value.attestation);
754
+ releaseHeldFrames();
755
+ return;
756
+ }
757
+ if (frame.value.kind === "ledger_post") {
758
+ if (!announceAccepted) {
759
+ appendStderr("[worker] dropped a ledger post that arrived before attestation was accepted\n");
760
+ return;
761
+ }
762
+ opts?.onLedgerPost?.(frame.value.body);
763
+ return;
764
+ }
765
+ opts?.onControl?.({ kind: frame.value.kind });
766
+ }
767
+ if (child.stdout) {
768
+ child.stdout.on(
769
+ "data",
770
+ createLineReader(WORKER_BULK_FRAME_MAX_BYTES, handleBulkLine, () => {
771
+ malformedStdoutLines += 1;
772
+ appendStderr(`[worker] dropped a bulk frame over the ${WORKER_BULK_FRAME_MAX_BYTES} byte lane limit
773
+ `);
774
+ })
775
+ );
776
+ }
777
+ if (child.stderr) {
778
+ child.stderr.on(
779
+ "data",
780
+ createLineReader(
781
+ WORKER_BULK_FRAME_MAX_BYTES,
782
+ (line) => {
783
+ if (isControlLine(line)) {
784
+ handleControlLine(line);
785
+ return;
786
+ }
787
+ appendStderr(`${line}
788
+ `);
789
+ },
790
+ () => appendStderr("[worker] dropped an oversized stderr line\n")
791
+ )
792
+ );
793
+ }
794
+ const events = {
795
+ next() {
796
+ if (queue.size > 0) {
797
+ return Promise.resolve({ value: queue.shift(), done: false });
798
+ }
799
+ if (finished) return Promise.resolve({ value: void 0, done: true });
800
+ return new Promise((resolve) => {
801
+ waiters.push(resolve);
802
+ });
803
+ },
804
+ return() {
805
+ end();
806
+ return Promise.resolve({ value: void 0, done: true });
807
+ },
808
+ [Symbol.asyncIterator]() {
809
+ return this;
810
+ }
811
+ };
812
+ const promise = new Promise((resolve) => {
813
+ child.on("close", (code, signal) => {
814
+ try {
815
+ child.stdin?.end();
816
+ } catch {
817
+ }
818
+ end();
819
+ if (sawSpawnError) {
820
+ resolve({ exitCode: null, signal: null, ...diagnostics() });
821
+ return;
822
+ }
823
+ if (!announceAccepted && !announceFailed) {
824
+ announceFailed = true;
825
+ appendStderr("[worker] Missing worker attestation: peer exited before announcing its route identity\n");
826
+ }
827
+ if (announceFailed) {
828
+ resolve({ exitCode: code !== null && code !== 0 ? code : 1, signal: signal ?? null, ...diagnostics() });
829
+ return;
830
+ }
831
+ resolve({ exitCode: code ?? 0, signal: signal ?? null, ...diagnostics() });
832
+ });
833
+ });
834
+ function isAlive() {
835
+ return child.exitCode === null && child.signalCode === null;
836
+ }
837
+ let queuedStdinBytes = 0;
838
+ function failChannel(operation, detail) {
839
+ channelFailure = new WorkerChannelFailure(operation, detail);
840
+ appendStderr(`[worker] ${channelFailure.message}
841
+ `);
842
+ return false;
843
+ }
844
+ const send = (value) => {
845
+ const frameType = typeof value === "object" && value !== null ? value.type : void 0;
846
+ const operation = frameType === "permission_decision" ? "permission_decision" : frameType === "ledger_delta" ? "ledger_delta" : "steer";
847
+ if (!isAlive()) return failChannel(operation, "worker has exited");
848
+ const stdin = child.stdin;
849
+ if (!stdin || stdin.destroyed || !stdin.writable) return failChannel(operation, "worker stdin is not writable");
850
+ const line = `${JSON.stringify(value)}
851
+ `;
852
+ const bytes = Buffer.byteLength(line, "utf8");
853
+ if (bytes > WORKER_STDIN_FRAME_MAX_BYTES) {
854
+ return failChannel(operation, `frame of ${bytes} bytes exceeds the ${WORKER_STDIN_FRAME_MAX_BYTES} byte limit`);
855
+ }
856
+ if (queuedStdinBytes + bytes > WORKER_STDIN_QUEUE_MAX_BYTES) {
857
+ return failChannel(
858
+ operation,
859
+ `stdin queue is full (${queuedStdinBytes} of ${WORKER_STDIN_QUEUE_MAX_BYTES} bytes pending)`
860
+ );
861
+ }
862
+ try {
863
+ queuedStdinBytes += bytes;
864
+ stdin.write(line, (error) => {
865
+ queuedStdinBytes -= bytes;
866
+ if (error) failChannel(operation, error.message);
867
+ });
868
+ return true;
869
+ } catch (error) {
870
+ queuedStdinBytes -= bytes;
871
+ return failChannel(operation, error instanceof Error ? error.message : String(error));
872
+ }
873
+ };
874
+ const abort = () => {
875
+ if (!isAlive()) return;
876
+ try {
877
+ child.stdin?.end();
878
+ } catch {
879
+ }
880
+ signalProcessGroup(child, "SIGTERM");
881
+ const killTimer = setTimeout(() => {
882
+ if (isAlive()) {
883
+ signalProcessGroup(child, "SIGKILL");
884
+ try {
885
+ opts?.onForcedKill?.();
886
+ } catch {
887
+ }
888
+ }
889
+ }, shutdownGraceMs);
890
+ killTimer.unref?.();
891
+ };
892
+ return {
893
+ pid,
894
+ processCommand: JSON.stringify([command, ...args]),
895
+ promise,
896
+ events,
897
+ abort,
898
+ heartbeatAt,
899
+ send,
900
+ lastChannelFailure: () => channelFailure,
901
+ attestation: () => attestation
902
+ };
903
+ }
904
+ function spawnNativeWorker(spec, opts) {
905
+ const workerEntry = opts?.workerEntryPath ?? join(resolvePackageRoot(), "dist/worker/entry.js");
906
+ return spawnWorkerProcess(process.execPath, ["--disable-warning=ExperimentalWarning", workerEntry], spec, {
907
+ ...opts?.cwd !== void 0 ? { cwd: opts.cwd } : {},
908
+ env: opts?.env ?? process.env,
909
+ ...opts?.shutdownGraceMs !== void 0 ? { shutdownGraceMs: opts.shutdownGraceMs } : {}
910
+ });
911
+ }
912
+
913
+ // src/domains/dispatch/transport.ts
914
+ var LOCAL_NODE_ID = "local";
915
+ function localNodeIdentity() {
916
+ return { id: LOCAL_NODE_ID, kind: "local" };
917
+ }
918
+ var DEFAULT_SSH_CONNECT_TIMEOUT_SEC = 10;
919
+ var DEFAULT_REMOTE_CLIO_ENTRY = "clio-coder worker";
920
+ function remoteEnvAssignments(node) {
921
+ const residency = node.residency ?? "observe";
922
+ const labels = (node.labels ?? []).map((label) => label.trim()).filter((label) => label.length > 0);
923
+ const labelAssignment = labels.length > 0 ? ` CLIO_CODER_WORKER_LABELS=${shellQuote(labels.join(","))}` : "";
924
+ return `CLIO_CODER_RESIDENCY=${residency} CLIO_CODER_WORKER_PGID=$$${labelAssignment}`;
925
+ }
926
+ function buildRemoteWorkerCommand(node, cwd) {
927
+ const entry = node.clioEntry !== void 0 && node.clioEntry.trim().length > 0 ? node.clioEntry.trim() : DEFAULT_REMOTE_CLIO_ENTRY;
928
+ return `cd ${shellQuote(cwd)} && exec env ${remoteEnvAssignments(node)} ${entry}`;
929
+ }
930
+ function buildSshArgs(node, remoteCommand) {
931
+ const connectTimeout = node.connectTimeoutSec ?? DEFAULT_SSH_CONNECT_TIMEOUT_SEC;
932
+ const args = [
933
+ "-o",
934
+ "BatchMode=yes",
935
+ "-o",
936
+ `ConnectTimeout=${connectTimeout}`,
937
+ // The channel is a byte pipe, never a terminal: a pty would translate
938
+ // newlines and inject control sequences into the NDJSON stream.
939
+ "-T"
940
+ ];
941
+ if (node.port !== void 0) args.push("-p", String(node.port));
942
+ if (node.identityFile !== void 0 && node.identityFile.length > 0) args.push("-i", node.identityFile);
943
+ if (node.user !== void 0 && node.user.length > 0) args.push("-l", node.user);
944
+ args.push(node.host, "--", remoteCommand);
945
+ return args;
946
+ }
947
+ function createSshWorkerTransport(node, opts) {
948
+ const sshBinary = opts?.sshBinary ?? "ssh";
949
+ const identity = { id: node.id, kind: "ssh", host: node.host };
950
+ return {
951
+ kind: "ssh",
952
+ node: identity,
953
+ spawn(spec, spawnOpts) {
954
+ const cwd = spawnOpts?.cwd ?? process.cwd();
955
+ const remote = { pid: null, processGroupId: null };
956
+ const killRemote = () => {
957
+ const group = remote.processGroupId;
958
+ const command = group !== null ? `kill -TERM -${group} 2>/dev/null || kill -TERM ${remote.pid ?? group} 2>/dev/null || true` : remote.pid !== null ? `kill -TERM ${remote.pid} 2>/dev/null || true` : null;
959
+ if (command === null) return;
960
+ try {
961
+ const fallback = spawn2(sshBinary, buildSshArgs(node, command), {
962
+ stdio: "ignore",
963
+ detached: true
964
+ });
965
+ fallback.unref();
966
+ } catch {
967
+ }
968
+ };
969
+ return spawnWorkerProcess(sshBinary, buildSshArgs(node, buildRemoteWorkerCommand(node, cwd)), spec, {
970
+ // No cwd for the local ssh client; the remote command cds itself.
971
+ env: process.env,
972
+ ...opts?.shutdownGraceMs !== void 0 ? { shutdownGraceMs: opts.shutdownGraceMs } : {},
973
+ onAnnounce: (attestation) => {
974
+ remote.pid = attestation.pid;
975
+ remote.processGroupId = attestation.processGroupId;
976
+ },
977
+ onForcedKill: killRemote
978
+ });
979
+ }
980
+ };
981
+ }
982
+
983
+ // src/domains/dispatch/fleet-preflight.ts
984
+ function storePath() {
985
+ return join2(clioStateDir(), "fleet-preflight.json");
986
+ }
987
+ function readFleetPreflightRecords() {
988
+ const path = storePath();
989
+ if (!existsSync(path)) return [];
990
+ try {
991
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
992
+ if (parsed?.version !== 2 || !Array.isArray(parsed.records)) return [];
993
+ return parsed.records;
994
+ } catch {
995
+ return [];
996
+ }
997
+ }
998
+ function recordFleetPreflight(records) {
999
+ const existing = readFleetPreflightRecords();
1000
+ const merged = /* @__PURE__ */ new Map();
1001
+ for (const record of existing) merged.set(`${record.nodeId}\0${record.projectRoot}`, record);
1002
+ for (const record of records) merged.set(`${record.nodeId}\0${record.projectRoot}`, record);
1003
+ const file = { version: 2, records: [...merged.values()] };
1004
+ atomicWrite(storePath(), JSON.stringify(file, null, 2));
1005
+ }
1006
+ function fleetPreflightVerdict(node, projectRoot, records = readFleetPreflightRecords()) {
1007
+ const record = records.find((entry) => entry.nodeId === node.id && entry.projectRoot === projectRoot);
1008
+ if (!record) {
1009
+ return {
1010
+ ok: false,
1011
+ reason: `node '${node.id}' has not passed the fleet preflight for ${projectRoot}; run 'clio-coder doctor'`
1012
+ };
1013
+ }
1014
+ if (record.host !== node.host) {
1015
+ return {
1016
+ ok: false,
1017
+ reason: `node '${node.id}' preflight was recorded for host '${record.host}' but the node now points at '${node.host}'; run 'clio-coder doctor'`
1018
+ };
1019
+ }
1020
+ if (record.localVersion !== readClioVersion()) {
1021
+ return {
1022
+ ok: false,
1023
+ reason: `node '${node.id}' preflight predates a local clio-coder upgrade (${record.localVersion} -> ${readClioVersion()}); run 'clio-coder doctor'`
1024
+ };
1025
+ }
1026
+ if (!record.ok) {
1027
+ return {
1028
+ ok: false,
1029
+ reason: `node '${node.id}' failed its last fleet preflight: ${record.detail ?? "see clio-coder doctor"}`
1030
+ };
1031
+ }
1032
+ return { ok: true, reason: null };
1033
+ }
1034
+ function routeFactVerdict(requirement, records = readFleetPreflightRecords(), options) {
1035
+ const targets = [];
1036
+ const resources = [];
1037
+ for (const record of records) {
1038
+ targets.push(...record.targets);
1039
+ if (record.resources !== null) resources.push(record.resources);
1040
+ }
1041
+ return evaluateRouteFacts(targets, resources, requirement, options);
1042
+ }
1043
+ var PREFLIGHT_MARKER = "clio-preflight/1";
1044
+ var DEFAULT_PREFLIGHT_TIMEOUT_MS = 2e4;
1045
+ function buildPreflightScript(node, projectRoot, targets = []) {
1046
+ const entry = node.clioEntry !== void 0 && node.clioEntry.trim().length > 0 ? node.clioEntry.trim() : "clio-coder worker";
1047
+ const cliBase = entry.endsWith(" worker") ? entry.slice(0, -" worker".length) : null;
1048
+ const versionProbe = cliBase !== null ? `v=$(${cliBase} --version 2>/dev/null | head -n 1); if [ -n "$v" ]; then echo "clio=$v"; else echo clio=missing; fi` : `echo clio=custom-entry`;
1049
+ const lines = [
1050
+ `echo ${shellQuote(PREFLIGHT_MARKER)}`,
1051
+ `if cd ${shellQuote(projectRoot)} 2>/dev/null; then echo cwd=ok; else echo cwd=missing; fi`,
1052
+ versionProbe,
1053
+ `d="\${XDG_STATE_HOME:-$HOME/.local/state}/clio-coder"; if mkdir -p "$d" 2>/dev/null && [ -w "$d" ]; then echo state=ok; else echo state=fail; fi`,
1054
+ // Resource observation runs on the node. A node without nvidia-smi reports
1055
+ // unknown; it never reports zero GPUs, which a fit requirement would read
1056
+ // as a proven absence rather than an absence of evidence.
1057
+ `echo "cpu=$(nproc 2>/dev/null || echo unknown)"`,
1058
+ `echo "memkb=$(awk '/MemTotal/{print $2}' /proc/meminfo 2>/dev/null || echo unknown)"`,
1059
+ `g=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null); if [ -n "$g" ]; then echo "gpu=$(echo "$g" | wc -l)"; echo "vrammb=$(echo "$g" | paste -sd+ - | bc 2>/dev/null || echo unknown)"; else echo gpu=unknown; echo vrammb=unknown; fi`
1060
+ ];
1061
+ for (const target of targets) {
1062
+ if (target.url === void 0 || target.url.trim().length === 0) {
1063
+ lines.push(`echo ${shellQuote(`target=${target.id}:noendpoint`)}`);
1064
+ continue;
1065
+ }
1066
+ const modelsUrl = `${target.url.replace(/\/+$/u, "")}/v1/models`;
1067
+ lines.push(
1068
+ `b=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' ${shellQuote(modelsUrl)} 2>/dev/null || echo 000); m=$(curl -sS -m 5 ${shellQuote(modelsUrl)} 2>/dev/null || echo ""); echo ${shellQuote(`target=${target.id}:`)}"$b"; ` + (target.wireModelId !== void 0 ? `case "$m" in *${shellQuote(target.wireModelId).slice(1, -1)}*) echo ${shellQuote(`model=${target.id}:true`)};; *) echo ${shellQuote(`model=${target.id}:false`)};; esac` : `echo ${shellQuote(`model=${target.id}:unknown`)}`)
1069
+ );
1070
+ }
1071
+ return lines.join("; ");
1072
+ }
1073
+ function parseSemver(text) {
1074
+ const match = text.match(/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
1075
+ return match?.[1] ?? null;
1076
+ }
1077
+ function parseUnknownableNumber(lines, prefix, scale = 1) {
1078
+ const line = lines.find((entry) => entry.startsWith(prefix));
1079
+ if (line === void 0) return null;
1080
+ const raw = line.slice(prefix.length).trim();
1081
+ const value = Number.parseInt(raw, 10);
1082
+ return Number.isFinite(value) && value >= 0 ? value * scale : null;
1083
+ }
1084
+ function parseTargetFacts(lines, targets, probedAt, probeDurationMs) {
1085
+ return targets.map((target) => {
1086
+ const statusLine = lines.find((entry) => entry.startsWith(`target=${target.id}:`));
1087
+ const status = statusLine?.slice(`target=${target.id}:`.length).trim() ?? "";
1088
+ const code = Number.parseInt(status, 10);
1089
+ const reachable = status === "" ? "unknown" : status === "noendpoint" ? "unknown" : Number.isFinite(code) && code >= 200 && code < 500 ? "true" : "false";
1090
+ const modelLine = lines.find((entry) => entry.startsWith(`model=${target.id}:`));
1091
+ const modelValue = modelLine?.slice(`model=${target.id}:`.length).trim();
1092
+ const modelAvailable = modelValue === "true" ? "true" : modelValue === "false" ? "false" : "unknown";
1093
+ return {
1094
+ nodeId: "",
1095
+ targetId: target.id,
1096
+ reachable,
1097
+ // The runtime the target names is registered in this same release on
1098
+ // every node, and the version check already proved release parity.
1099
+ runtimeCompatible: reachable === "true" ? "true" : "unknown",
1100
+ modelAvailable,
1101
+ // A worker observes only the models it loads itself, so residency is
1102
+ // never inferred from an endpoint listing.
1103
+ modelResident: "unknown",
1104
+ endpointIdentityHash: endpointIdentityHash(target.url),
1105
+ wireModelId: target.wireModelId ?? null,
1106
+ probedAt,
1107
+ probeDurationMs
1108
+ };
1109
+ });
1110
+ }
1111
+ function runSsh(sshBinary, args, timeoutMs) {
1112
+ return new Promise((resolve) => {
1113
+ execFile(sshBinary, [...args], { timeout: timeoutMs, encoding: "utf8" }, (error, stdout, stderr) => {
1114
+ const code = error === null ? 0 : typeof error.code === "number" ? error.code : 255;
1115
+ resolve({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
1116
+ });
1117
+ });
1118
+ }
1119
+ async function runFleetNodePreflight(node, projectRoot, options) {
1120
+ const sshBinary = options?.sshBinary ?? "ssh";
1121
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_PREFLIGHT_TIMEOUT_MS;
1122
+ const localVersion = readClioVersion();
1123
+ const checkedAt = (options?.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
1124
+ const checks = {
1125
+ reachable: false,
1126
+ clioPresent: false,
1127
+ versionMatch: false,
1128
+ pathParity: false,
1129
+ stateDirWritable: false
1130
+ };
1131
+ const targets = options?.targets ?? [];
1132
+ const record = {
1133
+ nodeId: node.id,
1134
+ host: node.host,
1135
+ projectRoot,
1136
+ ok: false,
1137
+ checkedAt,
1138
+ localVersion,
1139
+ remoteVersion: null,
1140
+ detail: null,
1141
+ checks,
1142
+ targets: [],
1143
+ resources: null
1144
+ };
1145
+ const script = buildPreflightScript(node, projectRoot, targets);
1146
+ const probeStartedAt = Date.now();
1147
+ const result = await runSsh(sshBinary, buildSshArgs(node, script), timeoutMs);
1148
+ const probeDurationMs = Date.now() - probeStartedAt;
1149
+ if (!result.stdout.includes(PREFLIGHT_MARKER)) {
1150
+ const stderr = result.stderr.trim().split("\n").slice(-1)[0] ?? "";
1151
+ record.detail = `unreachable (ssh exit ${result.code}${stderr.length > 0 ? `: ${stderr}` : ""})`;
1152
+ return record;
1153
+ }
1154
+ checks.reachable = true;
1155
+ const lines = result.stdout.split("\n").map((line) => line.trim());
1156
+ record.targets = parseTargetFacts(lines, targets, checkedAt, probeDurationMs).map((fact) => ({
1157
+ ...fact,
1158
+ nodeId: node.id
1159
+ }));
1160
+ record.resources = {
1161
+ nodeId: node.id,
1162
+ labels: [...node.labels ?? []],
1163
+ cpuCount: parseUnknownableNumber(lines, "cpu="),
1164
+ totalMemoryBytes: parseUnknownableNumber(lines, "memkb=", 1024),
1165
+ gpuCount: parseUnknownableNumber(lines, "gpu="),
1166
+ vramBytes: parseUnknownableNumber(lines, "vrammb=", 1024 * 1024),
1167
+ observedAt: checkedAt
1168
+ };
1169
+ checks.pathParity = lines.includes("cwd=ok");
1170
+ checks.stateDirWritable = lines.includes("state=ok");
1171
+ const clioLine = lines.find((line) => line.startsWith("clio="));
1172
+ const clioValue = clioLine?.slice("clio=".length) ?? "missing";
1173
+ if (clioValue === "custom-entry") {
1174
+ checks.clioPresent = true;
1175
+ checks.versionMatch = true;
1176
+ record.remoteVersion = null;
1177
+ } else if (clioValue !== "missing") {
1178
+ checks.clioPresent = true;
1179
+ record.remoteVersion = parseSemver(clioValue);
1180
+ checks.versionMatch = record.remoteVersion !== null && record.remoteVersion === parseSemver(localVersion);
1181
+ }
1182
+ const failures = [];
1183
+ if (!checks.pathParity)
1184
+ failures.push(`project root ${projectRoot} missing on node (disjoint filesystems are unsupported)`);
1185
+ if (!checks.clioPresent)
1186
+ failures.push("clio-coder not found on remote PATH (set fleet.nodes[].clioEntry or install clio-coder)");
1187
+ else if (!checks.versionMatch) {
1188
+ failures.push(
1189
+ `clio-coder version mismatch (local ${parseSemver(localVersion) ?? localVersion}, remote ${record.remoteVersion ?? "unknown"})`
1190
+ );
1191
+ }
1192
+ if (!checks.stateDirWritable) failures.push("remote clio-coder state dir is not writable");
1193
+ record.ok = failures.length === 0;
1194
+ record.detail = failures.length > 0 ? failures.join("; ") : null;
1195
+ return record;
1196
+ }
1197
+
1198
+ export {
1199
+ WORKER_PROTOCOL_VERSION,
1200
+ WORKER_STDIN_FRAME_MAX_BYTES,
1201
+ knownResource,
1202
+ UNKNOWN_RESOURCE,
1203
+ WORKER_RESOURCE_LABEL_MAX,
1204
+ parseAgentLedgerBody,
1205
+ parseAgentLedgerEntry,
1206
+ endpointIdentityHash,
1207
+ workerSpecDigest,
1208
+ toolSignatureOf,
1209
+ canonicalJson,
1210
+ withinFrameBudget,
1211
+ encodeControlFrame,
1212
+ computeSettingsFingerprint,
1213
+ receiptAttestationFields,
1214
+ spawnNativeWorker,
1215
+ LOCAL_NODE_ID,
1216
+ localNodeIdentity,
1217
+ createSshWorkerTransport,
1218
+ readFleetPreflightRecords,
1219
+ recordFleetPreflight,
1220
+ fleetPreflightVerdict,
1221
+ routeFactVerdict,
1222
+ runFleetNodePreflight
1223
+ };
1224
+ //# sourceMappingURL=chunk-BBUH4VAA.js.map