privateer-agent 0.15.4 → 0.15.6

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 (229) hide show
  1. package/README.md +11 -0
  2. package/bin/boot-bundle.d.mts +2 -0
  3. package/bin/boot-bundle.mjs +44 -0
  4. package/bin/fresh-supervisor.d.mts +5 -1
  5. package/bin/fresh-supervisor.mjs +47 -10
  6. package/bin/privateer-launch.mjs +27 -3
  7. package/bin/privateer-subagent.mjs +5 -2
  8. package/bin/privateer-verify.mjs +12 -0
  9. package/bin/session-recovery.d.mts +53 -0
  10. package/bin/session-recovery.mjs +240 -0
  11. package/bin/session-seal-preload.mjs +17 -0
  12. package/bin/session-seal.d.mts +25 -0
  13. package/bin/session-seal.mjs +242 -0
  14. package/dist-boot/boot.mjs +3 -0
  15. package/dist-boot/c-27JKSMWL.mjs +1460 -0
  16. package/dist-boot/c-2BMRFV24.mjs +269 -0
  17. package/dist-boot/c-2CTF6JA3.mjs +166 -0
  18. package/dist-boot/c-2G3BBHRJ.mjs +84 -0
  19. package/dist-boot/c-2IDLKXX6.mjs +250 -0
  20. package/dist-boot/c-2NJD5VYM.mjs +8 -0
  21. package/dist-boot/c-2S5EEQPW.mjs +1602 -0
  22. package/dist-boot/c-2WRDCUVI.mjs +25 -0
  23. package/dist-boot/c-2YN3M256.mjs +14 -0
  24. package/dist-boot/c-37GM3GMU.mjs +187 -0
  25. package/dist-boot/c-3STKRSVO.mjs +2480 -0
  26. package/dist-boot/c-3TPV6TQQ.mjs +2320 -0
  27. package/dist-boot/c-3UVHLQDJ.mjs +225 -0
  28. package/dist-boot/c-42PUUZYC.mjs +697 -0
  29. package/dist-boot/c-4D5UMWVW.mjs +683 -0
  30. package/dist-boot/c-4ETZRMTA.mjs +46 -0
  31. package/dist-boot/c-4QT25BD5.mjs +1482 -0
  32. package/dist-boot/c-4WVR32AP.mjs +2579 -0
  33. package/dist-boot/c-5EMM567G.mjs +152 -0
  34. package/dist-boot/c-5FA4CV63.mjs +145 -0
  35. package/dist-boot/c-5GV3X3RB.mjs +14 -0
  36. package/dist-boot/c-5JCEPRIY.mjs +52 -0
  37. package/dist-boot/c-5NDKIIWT.mjs +16 -0
  38. package/dist-boot/c-5QL3AR26.mjs +4665 -0
  39. package/dist-boot/c-5XY6MFKN.mjs +5142 -0
  40. package/dist-boot/c-65UZIZC7.mjs +2897 -0
  41. package/dist-boot/c-6FUXKHOK.mjs +1969 -0
  42. package/dist-boot/c-6JHWQE4V.mjs +18242 -0
  43. package/dist-boot/c-6LOBR4I6.mjs +5280 -0
  44. package/dist-boot/c-6QHOZXKQ.mjs +14394 -0
  45. package/dist-boot/c-6ZO2SADQ.mjs +178 -0
  46. package/dist-boot/c-6ZORVVZP.mjs +1449 -0
  47. package/dist-boot/c-76QH4UCP.mjs +2441 -0
  48. package/dist-boot/c-77GO5L5Y.mjs +20982 -0
  49. package/dist-boot/c-7M23XFTN.mjs +5052 -0
  50. package/dist-boot/c-7TC367E2.mjs +32 -0
  51. package/dist-boot/c-7UOG7POX.mjs +1524 -0
  52. package/dist-boot/c-7VHP3FLN.mjs +376 -0
  53. package/dist-boot/c-A73JHZP2.mjs +13207 -0
  54. package/dist-boot/c-ABXULAFL.mjs +71 -0
  55. package/dist-boot/c-ANDD2AQO.mjs +74 -0
  56. package/dist-boot/c-AOY77AZL.mjs +1430 -0
  57. package/dist-boot/c-AXEPZ22I.mjs +108 -0
  58. package/dist-boot/c-B2Y7VNHU.mjs +427 -0
  59. package/dist-boot/c-B7VJUH73.mjs +77 -0
  60. package/dist-boot/c-BFW6CDVL.mjs +24 -0
  61. package/dist-boot/c-BHNIF2AR.mjs +188 -0
  62. package/dist-boot/c-BKF2O6BL.mjs +23 -0
  63. package/dist-boot/c-BOZXFTZN.mjs +8 -0
  64. package/dist-boot/c-BRNYJWZL.mjs +28 -0
  65. package/dist-boot/c-C3N4Z2HO.mjs +93 -0
  66. package/dist-boot/c-C4IV7ZFG.mjs +717 -0
  67. package/dist-boot/c-C5NK6UYV.mjs +793 -0
  68. package/dist-boot/c-C67644MB.mjs +1659 -0
  69. package/dist-boot/c-C6S2YB53.mjs +620 -0
  70. package/dist-boot/c-CDBEJSBF.mjs +37 -0
  71. package/dist-boot/c-CG47HBKU.mjs +435 -0
  72. package/dist-boot/c-CKCTWCT5.mjs +51 -0
  73. package/dist-boot/c-CN77DCGF.mjs +60 -0
  74. package/dist-boot/c-CRBXNKFO.mjs +1795 -0
  75. package/dist-boot/c-CRIHASQ6.mjs +973 -0
  76. package/dist-boot/c-CU4NGAUO.mjs +3786 -0
  77. package/dist-boot/c-CWRFLGQU.mjs +1883 -0
  78. package/dist-boot/c-CYQB2USO.mjs +32 -0
  79. package/dist-boot/c-DEE67DJO.mjs +379 -0
  80. package/dist-boot/c-DIFDBYOP.mjs +1258 -0
  81. package/dist-boot/c-DOSGVXDZ.mjs +34879 -0
  82. package/dist-boot/c-DR7XQI32.mjs +381 -0
  83. package/dist-boot/c-DTGPSB46.mjs +14666 -0
  84. package/dist-boot/c-DTHDNA4P.mjs +320 -0
  85. package/dist-boot/c-E4Z3Y5B5.mjs +3864 -0
  86. package/dist-boot/c-E66Z2TI7.mjs +203 -0
  87. package/dist-boot/c-EBU6RLRV.mjs +1527 -0
  88. package/dist-boot/c-EBZZNGSF.mjs +432 -0
  89. package/dist-boot/c-EC3LJ2KF.mjs +1032 -0
  90. package/dist-boot/c-EQBKXWNX.mjs +15829 -0
  91. package/dist-boot/c-EUEPIW4S.mjs +574 -0
  92. package/dist-boot/c-EX4YVWAT.mjs +378 -0
  93. package/dist-boot/c-EYZGSAJU.mjs +98 -0
  94. package/dist-boot/c-F75VRKY4.mjs +172 -0
  95. package/dist-boot/c-FH4NXKHV.mjs +177 -0
  96. package/dist-boot/c-FHNUCTEM.mjs +140 -0
  97. package/dist-boot/c-FJ5T42DS.mjs +5349 -0
  98. package/dist-boot/c-FPCNRL7G.mjs +1728 -0
  99. package/dist-boot/c-G4DLN7Q4.mjs +8 -0
  100. package/dist-boot/c-G56GYQFZ.mjs +2507 -0
  101. package/dist-boot/c-GLIOERDQ.mjs +1200 -0
  102. package/dist-boot/c-GMJ5HUBM.mjs +14 -0
  103. package/dist-boot/c-H3HSGMAQ.mjs +461 -0
  104. package/dist-boot/c-HIKTO6P7.mjs +275 -0
  105. package/dist-boot/c-I2MCJ34O.mjs +464 -0
  106. package/dist-boot/c-I3C6LBW4.mjs +765 -0
  107. package/dist-boot/c-I54OKQHW.mjs +387 -0
  108. package/dist-boot/c-IFSP3R4D.mjs +80 -0
  109. package/dist-boot/c-IZK77VB7.mjs +171 -0
  110. package/dist-boot/c-J3Y25UGE.mjs +54 -0
  111. package/dist-boot/c-J5IESS7W.mjs +27750 -0
  112. package/dist-boot/c-JJWZBY7L.mjs +254 -0
  113. package/dist-boot/c-JKTFXTGF.mjs +463 -0
  114. package/dist-boot/c-JN57AXQ4.mjs +77 -0
  115. package/dist-boot/c-KFIO6LUW.mjs +1096 -0
  116. package/dist-boot/c-KIEMEFWW.mjs +478 -0
  117. package/dist-boot/c-KIVXWBYD.mjs +28 -0
  118. package/dist-boot/c-KP6CRIP4.mjs +580 -0
  119. package/dist-boot/c-L26VOJX4.mjs +11558 -0
  120. package/dist-boot/c-L5GHWG5G.mjs +5988 -0
  121. package/dist-boot/c-M7NSQAKK.mjs +353 -0
  122. package/dist-boot/c-MCEWOPBW.mjs +56 -0
  123. package/dist-boot/c-MGPIEG7I.mjs +20 -0
  124. package/dist-boot/c-NREXJV3M.mjs +393 -0
  125. package/dist-boot/c-O5PP6CTX.mjs +162 -0
  126. package/dist-boot/c-OBQDHSXM.mjs +1774 -0
  127. package/dist-boot/c-OLQHHDD4.mjs +21 -0
  128. package/dist-boot/c-OXW4JQQH.mjs +319 -0
  129. package/dist-boot/c-P3D7ZUWN.mjs +1288 -0
  130. package/dist-boot/c-P4PQZVV5.mjs +948 -0
  131. package/dist-boot/c-P63T3XL2.mjs +26373 -0
  132. package/dist-boot/c-P6N5DQGZ.mjs +194 -0
  133. package/dist-boot/c-P7MLKQFC.mjs +58 -0
  134. package/dist-boot/c-PB3PZJL5.mjs +1276 -0
  135. package/dist-boot/c-PTN4S6G2.mjs +1259 -0
  136. package/dist-boot/c-PYCE5J7U.mjs +1083 -0
  137. package/dist-boot/c-Q4HYCRLB.mjs +19410 -0
  138. package/dist-boot/c-QUXMETGG.mjs +68 -0
  139. package/dist-boot/c-RDMJZCOX.mjs +356 -0
  140. package/dist-boot/c-ROGJK76M.mjs +17 -0
  141. package/dist-boot/c-RQJJ26T4.mjs +708 -0
  142. package/dist-boot/c-RSKCUPJF.mjs +7559 -0
  143. package/dist-boot/c-RVJU4O6P.mjs +37 -0
  144. package/dist-boot/c-S2RVHR3M.mjs +165 -0
  145. package/dist-boot/c-SKMK3PEE.mjs +91120 -0
  146. package/dist-boot/c-TI7SM4EG.mjs +20 -0
  147. package/dist-boot/c-TILJ6GXD.mjs +1235 -0
  148. package/dist-boot/c-TT6BCUJD.mjs +114 -0
  149. package/dist-boot/c-U4ITOXAN.mjs +2107 -0
  150. package/dist-boot/c-UGERXVO2.mjs +7120 -0
  151. package/dist-boot/c-UPBH5AXY.mjs +1363 -0
  152. package/dist-boot/c-US2PUNZL.mjs +597 -0
  153. package/dist-boot/c-UU4WZPPE.mjs +20 -0
  154. package/dist-boot/c-UWVIYPQS.mjs +39 -0
  155. package/dist-boot/c-V4RHQBPQ.mjs +17683 -0
  156. package/dist-boot/c-V5MRVK47.mjs +351 -0
  157. package/dist-boot/c-V6CL266R.mjs +1755 -0
  158. package/dist-boot/c-VJTMFKYE.mjs +14975 -0
  159. package/dist-boot/c-VJTRUKIB.mjs +773 -0
  160. package/dist-boot/c-W4IFSHPW.mjs +43 -0
  161. package/dist-boot/c-WETGB2CF.mjs +113 -0
  162. package/dist-boot/c-WO3QSZUU.mjs +1337 -0
  163. package/dist-boot/c-WQLH73TY.mjs +242 -0
  164. package/dist-boot/c-XL735JW5.mjs +500 -0
  165. package/dist-boot/c-Y7HADL5B.mjs +1107 -0
  166. package/dist-boot/c-YGQHSVSK.mjs +41 -0
  167. package/dist-boot/c-YMP2AMEB.mjs +10350 -0
  168. package/dist-boot/c-YRDO2WEF.mjs +12012 -0
  169. package/dist-boot/c-YUSTRATJ.mjs +8 -0
  170. package/dist-boot/c-Z2262R22.mjs +2697 -0
  171. package/dist-boot/c-Z4ALQMJC.mjs +709 -0
  172. package/dist-boot/c-Z5ZUQVUY.mjs +62 -0
  173. package/dist-boot/c-ZDZUKXF7.mjs +874 -0
  174. package/dist-boot/c-ZJYLGIC3.mjs +32 -0
  175. package/dist-boot/c-ZNA5CDMI.mjs +77 -0
  176. package/dist-boot/c-ZSO6DI3M.mjs +204 -0
  177. package/dist-boot/c-ZWUVHKMK.mjs +100 -0
  178. package/dist-boot/c-ZY4MESEW.mjs +832 -0
  179. package/dist-boot/c-ZZ5REI4L.mjs +329 -0
  180. package/dist-boot/cli.mjs +51 -0
  181. package/dist-boot/ext-anthropic-attribution.mjs +207 -0
  182. package/dist-boot/ext-pi-background-tasks.mjs +7234 -0
  183. package/dist-boot/ext-pi-subagents.mjs +37 -0
  184. package/dist-boot/ext-privateer-account.mjs +4426 -0
  185. package/dist-boot/ext-privateer-brand.mjs +5274 -0
  186. package/dist-boot/ext-privateer-codemode.mjs +37 -0
  187. package/dist-boot/ext-privateer-computer.mjs +626 -0
  188. package/dist-boot/ext-privateer-connect.mjs +1862 -0
  189. package/dist-boot/ext-privateer-context.mjs +323 -0
  190. package/dist-boot/ext-privateer-desktop.mjs +92 -0
  191. package/dist-boot/ext-privateer-final-audit.mjs +48 -0
  192. package/dist-boot/ext-privateer-fresh.mjs +81 -0
  193. package/dist-boot/ext-privateer-gate.mjs +5959 -0
  194. package/dist-boot/ext-privateer-hints.mjs +236 -0
  195. package/dist-boot/ext-privateer-media.mjs +2626 -0
  196. package/dist-boot/ext-privateer-models.mjs +3397 -0
  197. package/dist-boot/ext-privateer-posture.mjs +3006 -0
  198. package/dist-boot/ext-privateer-privacy.mjs +4578 -0
  199. package/dist-boot/ext-privateer-recovery.mjs +287 -0
  200. package/dist-boot/ext-privateer-speak.mjs +4754 -0
  201. package/dist-boot/ext-privateer-terminals.mjs +725 -0
  202. package/dist-boot/ext-privateer-tools.mjs +528 -0
  203. package/dist-boot/ext-privateer-truncation.mjs +74 -0
  204. package/dist-boot/ext-privateer-update.mjs +210 -0
  205. package/dist-boot/ext-privateer-vision.mjs +148 -0
  206. package/dist-boot/ext-privateer-web.mjs +543 -0
  207. package/dist-boot/ext-rpiv-ask-user-question.mjs +710 -0
  208. package/dist-boot/manifest.json +9 -0
  209. package/extensions/privateer-brand.ts +3 -1
  210. package/extensions/privateer-context.ts +4 -2
  211. package/extensions/privateer-final-audit.ts +17 -0
  212. package/extensions/privateer-gate.ts +55 -9
  213. package/extensions/privateer-recovery.ts +175 -0
  214. package/extensions/privateer-web.ts +4 -4
  215. package/package.json +3 -1
  216. package/patches/@earendil-works+pi-coding-agent+1.3.0.patch +199 -2
  217. package/patches/pi-background-tasks+2.6.10.patch +91 -7
  218. package/src/cli/chat.ts +7 -1
  219. package/src/config/moatManifest.json +2 -0
  220. package/src/config/whatsNew.json +4 -4
  221. package/src/context.ts +33 -5
  222. package/src/engine/finalAudit.ts +38 -0
  223. package/src/engine/stallWatchdog.ts +8 -1
  224. package/src/providers/account.ts +34 -3
  225. package/src/remote/attentionPush.ts +40 -0
  226. package/src/remote/liveTaskSession.ts +7 -1
  227. package/src/remote/relayClient.ts +38 -4
  228. package/src/remote/remoteBridge.ts +17 -0
  229. package/src/remote/transcriptSnapshot.ts +104 -0
@@ -0,0 +1,4665 @@
1
+ import{createRequire as __pvCR}from"node:module";import{fileURLToPath as __pvF}from"node:url";import{dirname as __pvD}from"node:path";const require=__pvCR(import.meta.url);const __pvUrl=(r)=>new URL(r,import.meta.url).href;const __pvFile=(r)=>__pvF(new URL(r,import.meta.url));
2
+ import {
3
+ FusionArtifactStore,
4
+ buildFusionFailureSummary,
5
+ buildFusionRunProgress
6
+ } from "./c-Z4ALQMJC.mjs";
7
+ import {
8
+ FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
9
+ FUSION_DIAGNOSTICS_MAX_BYTES,
10
+ FUSION_EVALUATION_MAX_OUTPUT_BYTES,
11
+ FUSION_MERGE_MAX_OUTPUT_BYTES,
12
+ FUSION_REASON_WORKFLOW,
13
+ assertChildOutputWithinContract,
14
+ assertWorkflowCapability,
15
+ buildBlindEvaluationInput,
16
+ buildCandidatePrompt,
17
+ buildEvaluationPrompt,
18
+ buildEvaluationRepairPrompt,
19
+ buildMergeInput,
20
+ buildMergePrompt,
21
+ fusionJsonRenderedTextBytes,
22
+ fusionWorkflowProfile
23
+ } from "./c-EBZZNGSF.mjs";
24
+ import {
25
+ buildFusionSourcePolicy,
26
+ canonicalizeFusionPublicUrl,
27
+ readFusionSourcePolicyFile,
28
+ sourcePolicyCanonicalBytes
29
+ } from "./c-JJWZBY7L.mjs";
30
+ import {
31
+ FUSION_BUDGET_PLAN_SCHEMA_VERSION,
32
+ FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
33
+ FUSION_CANDIDATE_IDS,
34
+ FUSION_EVALUATION_SCHEMA_VERSION,
35
+ FUSION_FORBIDDEN_TOOLS,
36
+ FUSION_INPUT_SCHEMA_VERSION,
37
+ FUSION_INSPECT_TOOLS,
38
+ FUSION_NO_TOOLS_CAPABILITY,
39
+ FUSION_RESEARCH_TOOLS,
40
+ FUSION_RESULT_SCHEMA_VERSION,
41
+ FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
42
+ FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
43
+ FUSION_WEB_FETCH_TOOL_NAME,
44
+ FusionError,
45
+ addFusionUsage,
46
+ cloneFusionUsage,
47
+ createEmptyFusionUsage
48
+ } from "./c-37GM3GMU.mjs";
49
+ import {
50
+ TOKEN_BUDGET_AFFINE_F_TOKENS,
51
+ TOKEN_BUDGET_CALIBRATION_VERSION,
52
+ TOKEN_BUDGET_FAMILY_CALIBRATIONS,
53
+ TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
54
+ TOKEN_BUDGET_RATE_SCALE,
55
+ allowedInputTokens,
56
+ estimateInputTokens,
57
+ isUsableContextWindow,
58
+ knownTextSegment,
59
+ maxKnownTextBytesForTokens,
60
+ resolveTokenBudgetFamily,
61
+ unknownOutputContractSegment
62
+ } from "./c-US2PUNZL.mjs";
63
+ import {
64
+ canonicalJson
65
+ } from "./c-KIVXWBYD.mjs";
66
+ import {
67
+ assertWindowsCommandLineWithinLimit,
68
+ piLaunchArgv,
69
+ resolvePiLaunch
70
+ } from "./c-KIEMEFWW.mjs";
71
+ import {
72
+ resolveAnthropicAttributionExtensionPath
73
+ } from "./c-BFW6CDVL.mjs";
74
+ import "./c-DR7XQI32.mjs";
75
+ import {
76
+ isJsonObject,
77
+ parseJsonText
78
+ } from "./c-I3C6LBW4.mjs";
79
+ import "./c-B2Y7VNHU.mjs";
80
+ import "./c-SKMK3PEE.mjs";
81
+ import "./c-I54OKQHW.mjs";
82
+ import "./c-UPBH5AXY.mjs";
83
+ import "./c-7UOG7POX.mjs";
84
+ import "./c-E4Z3Y5B5.mjs";
85
+ import "./c-6ZO2SADQ.mjs";
86
+ import "./c-JKTFXTGF.mjs";
87
+ import "./c-CRBXNKFO.mjs";
88
+ import "./c-KP6CRIP4.mjs";
89
+ import "./c-RSKCUPJF.mjs";
90
+ import "./c-PTN4S6G2.mjs";
91
+ import "./c-P6N5DQGZ.mjs";
92
+ import "./c-6LOBR4I6.mjs";
93
+ import "./c-3STKRSVO.mjs";
94
+ import "./c-YRDO2WEF.mjs";
95
+ import "./c-BHNIF2AR.mjs";
96
+ import "./c-ABXULAFL.mjs";
97
+ import "./c-2G3BBHRJ.mjs";
98
+ import "./c-O5PP6CTX.mjs";
99
+ import "./c-CDBEJSBF.mjs";
100
+ import "./c-DEE67DJO.mjs";
101
+ import "./c-WO3QSZUU.mjs";
102
+ import "./c-S2RVHR3M.mjs";
103
+ import "./c-EQBKXWNX.mjs";
104
+ import "./c-RQJJ26T4.mjs";
105
+ import "./c-L5GHWG5G.mjs";
106
+ import {
107
+ __name
108
+ } from "./c-CN77DCGF.mjs";
109
+
110
+ // node_modules/pi-background-tasks/src/core/fusion/orchestrator.ts?ext=pi-background-tasks
111
+ import { createHash as createHash4, randomBytes as nodeRandomBytes } from "node:crypto";
112
+
113
+ // node_modules/pi-background-tasks/src/core/fusion/budget.ts?ext=pi-background-tasks
114
+ import { createHash } from "node:crypto";
115
+ var FUSION_CALIBRATED_BYTES_PER_TOKEN = TOKEN_BUDGET_FAMILY_CALIBRATIONS;
116
+ var FUSION_OUTPUT_RESERVE_RATE_X100 = 200;
117
+ var FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS = 8e3;
118
+ var BASIS_POINTS_DENOMINATOR = 1e4;
119
+ function ceilDiv(numerator, denominator) {
120
+ if (!Number.isSafeInteger(numerator) || numerator < 0) {
121
+ throw new TypeError("ceilDiv numerator must be a non-negative safe integer");
122
+ }
123
+ if (!Number.isSafeInteger(denominator) || denominator <= 0) {
124
+ throw new TypeError("ceilDiv denominator must be a positive safe integer");
125
+ }
126
+ if (numerator === 0) return 0;
127
+ return Math.floor((numerator - 1) / denominator) + 1;
128
+ }
129
+ __name(ceilDiv, "ceilDiv");
130
+ var FUSION_RESERVED_OUTPUT_TOKENS = ceilDiv(
131
+ FUSION_MERGE_MAX_OUTPUT_BYTES * TOKEN_BUDGET_RATE_SCALE,
132
+ FUSION_OUTPUT_RESERVE_RATE_X100
133
+ );
134
+ var FUSION_FRAMING_RESERVE_TOKENS = 0;
135
+ var FUSION_SAFETY_RESERVE_TOKENS = 4096;
136
+ var FUSION_MIN_CANONICAL_INPUT_TOKENS = 8192;
137
+ var FUSION_MIN_CONTEXT_WINDOW_TOKENS = FUSION_MIN_CANONICAL_INPUT_TOKENS + FUSION_RESERVED_OUTPUT_TOKENS + FUSION_FRAMING_RESERVE_TOKENS + FUSION_SAFETY_RESERVE_TOKENS;
138
+ var FUSION_BUDGET_POLICY = {
139
+ id: "fusion-budget-policy-v4",
140
+ route_output_reserve_strategy: "max_fusion_contract_or_model_max",
141
+ calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
142
+ calibration_table: FUSION_CALIBRATED_BYTES_PER_TOKEN,
143
+ reserved_output_tokens: FUSION_RESERVED_OUTPUT_TOKENS,
144
+ framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
145
+ safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
146
+ candidate_output_contract_bytes: FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
147
+ evaluation_output_contract_bytes: FUSION_EVALUATION_MAX_OUTPUT_BYTES,
148
+ merge_output_contract_bytes: FUSION_MERGE_MAX_OUTPUT_BYTES,
149
+ diagnostics_contract_bytes: FUSION_DIAGNOSTICS_MAX_BYTES,
150
+ utilization_warning_threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
151
+ };
152
+ var REASON_EMPTY_REMEDIATION = Object.freeze([
153
+ "Start a fresh Pi conversation, or run fusion_reason earlier in the session.",
154
+ "Raise the route's context window with a larger-context subscription model via /fusion-models.",
155
+ "Restate only the required prior findings in the fusion_reason prompt."
156
+ ]);
157
+ var REASON_REQUEST_REMEDIATION = Object.freeze([
158
+ "Provide a shorter fusion_reason prompt.",
159
+ "Start a fresh Pi conversation, or run fusion_reason earlier in the session.",
160
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
161
+ ]);
162
+ var INVESTIGATE_EMPTY_REMEDIATION = Object.freeze([
163
+ "Split the repository investigation into smaller independently complete path or subsystem scopes.",
164
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
165
+ ]);
166
+ var INVESTIGATE_REQUEST_REMEDIATION = Object.freeze([
167
+ "Narrow the fusion_investigate objective, repository scope, or required evidence.",
168
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
169
+ ]);
170
+ var RESEARCH_EMPTY_REMEDIATION = Object.freeze([
171
+ "Split the research into smaller independently complete source sets.",
172
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
173
+ ]);
174
+ var RESEARCH_REQUEST_REMEDIATION = Object.freeze([
175
+ "Narrow the fusion_research question or split large declared-source sets across independent runs.",
176
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
177
+ ]);
178
+ var VALIDATE_EMPTY_REMEDIATION = Object.freeze([
179
+ "Split validation into smaller independently complete change or acceptance-criterion scopes.",
180
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
181
+ ]);
182
+ var VALIDATE_REQUEST_REMEDIATION = Object.freeze([
183
+ "Narrow the fusion_validate scope, acceptance criteria, or supplied verification evidence.",
184
+ "Raise the route's context window with a larger-context subscription model via /fusion-models."
185
+ ]);
186
+ function cleanRemediation(profile, requestDeterminesFeasibility) {
187
+ if (profile.id === "investigate") {
188
+ return requestDeterminesFeasibility ? INVESTIGATE_REQUEST_REMEDIATION : INVESTIGATE_EMPTY_REMEDIATION;
189
+ }
190
+ if (profile.id === "research") {
191
+ return requestDeterminesFeasibility ? RESEARCH_REQUEST_REMEDIATION : RESEARCH_EMPTY_REMEDIATION;
192
+ }
193
+ if (profile.id === "validate") {
194
+ return requestDeterminesFeasibility ? VALIDATE_REQUEST_REMEDIATION : VALIDATE_EMPTY_REMEDIATION;
195
+ }
196
+ return requestDeterminesFeasibility ? REASON_REQUEST_REMEDIATION : REASON_EMPTY_REMEDIATION;
197
+ }
198
+ __name(cleanRemediation, "cleanRemediation");
199
+ var RESERVATION_REMEDIATION = Object.freeze([
200
+ "Route the blocking stage to a model with larger byte capacity.",
201
+ "Keep producer output contracts intact; do not shrink or truncate child answers.",
202
+ "Inspect budget-plan.json for the advisory reservation component before retrying."
203
+ ]);
204
+ var DENSE_REMEDIATION = Object.freeze([
205
+ "Remove or externalize low-whitespace dense ASCII payloads (base64, minified code, PEM/hex blocks), then retry.",
206
+ "The prompt is preserved; no content was clipped to fit."
207
+ ]);
208
+ var MULTIBYTE_REMEDIATION = Object.freeze([
209
+ "This rejection is dominated by multibyte UTF-8 content; use a larger-context route or split the non-Latin/CJK-heavy task.",
210
+ "The budget plan includes the stricter multibyte advisory ceiling separately from the fatal estimate.",
211
+ "The prompt is preserved; no content was clipped to fit."
212
+ ]);
213
+ var EMPTY_CANDIDATES = Object.freeze([
214
+ Object.freeze({ candidate_id: "A", response: "" }),
215
+ Object.freeze({ candidate_id: "B", response: "" }),
216
+ Object.freeze({ candidate_id: "C", response: "" })
217
+ ]);
218
+ var EMPTY_EVALUATION = Object.freeze({
219
+ schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
220
+ candidate_assessments: Object.freeze([
221
+ Object.freeze({
222
+ candidate_id: "A",
223
+ summary: "",
224
+ strengths: Object.freeze([]),
225
+ limitations: Object.freeze([]),
226
+ useful_contributions: Object.freeze([]),
227
+ risks: Object.freeze([])
228
+ }),
229
+ Object.freeze({
230
+ candidate_id: "B",
231
+ summary: "",
232
+ strengths: Object.freeze([]),
233
+ limitations: Object.freeze([]),
234
+ useful_contributions: Object.freeze([]),
235
+ risks: Object.freeze([])
236
+ }),
237
+ Object.freeze({
238
+ candidate_id: "C",
239
+ summary: "",
240
+ strengths: Object.freeze([]),
241
+ limitations: Object.freeze([]),
242
+ useful_contributions: Object.freeze([]),
243
+ risks: Object.freeze([])
244
+ })
245
+ ]),
246
+ agreements: Object.freeze([]),
247
+ conflicts: Object.freeze([]),
248
+ synthesis_plan: Object.freeze({
249
+ must_include: Object.freeze([]),
250
+ must_resolve: Object.freeze([]),
251
+ must_avoid: Object.freeze([])
252
+ })
253
+ });
254
+ function utf8Bytes(value) {
255
+ return Buffer.byteLength(value, "utf8");
256
+ }
257
+ __name(utf8Bytes, "utf8Bytes");
258
+ function sha256Hex(value) {
259
+ return createHash("sha256").update(value, "utf8").digest("hex");
260
+ }
261
+ __name(sha256Hex, "sha256Hex");
262
+ function requirePositiveContextWindow(model, role) {
263
+ const value = model.contextWindow;
264
+ if (!isUsableContextWindow(value)) {
265
+ throw new FusionError(
266
+ `fusion ${role} route ${model.qualifiedId} has no usable context window capacity`,
267
+ { code: "model_capacity_unknown", childCreated: false }
268
+ );
269
+ }
270
+ return value;
271
+ }
272
+ __name(requirePositiveContextWindow, "requirePositiveContextWindow");
273
+ function formatRateX100(value) {
274
+ const whole = Math.floor(value / TOKEN_BUDGET_RATE_SCALE);
275
+ const frac = String(value % TOKEN_BUDGET_RATE_SCALE).padStart(2, "0");
276
+ return `${String(whole)}.${frac}`;
277
+ }
278
+ __name(formatRateX100, "formatRateX100");
279
+ function routeCapacity(model, role) {
280
+ const contextWindow = requirePositiveContextWindow(model, role);
281
+ const reservedOutputTokens = Math.max(FUSION_RESERVED_OUTPUT_TOKENS, model.maxOutputTokens);
282
+ const allowed = allowedInputTokens(contextWindow, {
283
+ reservedOutputTokens,
284
+ framingReserveTokens: FUSION_FRAMING_RESERVE_TOKENS,
285
+ safetyReserveTokens: FUSION_SAFETY_RESERVE_TOKENS
286
+ });
287
+ if (allowed < FUSION_MIN_CANONICAL_INPUT_TOKENS) {
288
+ const minimumContextWindow = reservedOutputTokens + FUSION_FRAMING_RESERVE_TOKENS + FUSION_SAFETY_RESERVE_TOKENS + FUSION_MIN_CANONICAL_INPUT_TOKENS;
289
+ throw new FusionError(
290
+ `fusion ${role} route ${model.qualifiedId} has a ${String(contextWindow)}-token context window, but Fusion requires at least ${String(minimumContextWindow)} tokens: ${String(reservedOutputTokens)} reserved for the route's configured maximum output + ${String(FUSION_FRAMING_RESERVE_TOKENS)} framing + ${String(FUSION_SAFETY_RESERVE_TOKENS)} safety + ${String(FUSION_MIN_CANONICAL_INPUT_TOKENS)} usable input. Choose a larger-context or lower-max-output subscription model for this slot with /fusion-models.`,
291
+ { code: "model_capacity_unknown", childCreated: false }
292
+ );
293
+ }
294
+ const family = resolveTokenBudgetFamily({ provider: model.provider, model: model.model });
295
+ const rateSource = estimateInputTokens({
296
+ family: family.family,
297
+ calibrationBacked: family.backed,
298
+ familyResolution: family.resolution,
299
+ allowedInputTokens: allowed,
300
+ scope: "fusion",
301
+ segments: [
302
+ {
303
+ kind: "known_text",
304
+ bytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
305
+ multibyteBytes: 0,
306
+ denseBytes: 0,
307
+ asciiWhitespaceBytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES
308
+ }
309
+ ]
310
+ }).rateSource;
311
+ return {
312
+ role,
313
+ provider: model.provider,
314
+ model: model.model,
315
+ qualified_id: model.qualifiedId,
316
+ context_window_tokens: contextWindow,
317
+ reserved_output_tokens: reservedOutputTokens,
318
+ framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
319
+ safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
320
+ allowed_input_tokens: allowed,
321
+ family: family.family,
322
+ rate_source: rateSource,
323
+ byte_capacity_utf8_bytes: Math.floor(
324
+ allowed * rateSource.effective_rate_bytes_per_token_x100 / TOKEN_BUDGET_RATE_SCALE
325
+ )
326
+ };
327
+ }
328
+ __name(routeCapacity, "routeCapacity");
329
+ function fusionRouteCapacities(models) {
330
+ return [
331
+ routeCapacity(models.candidates[0], "candidate-1"),
332
+ routeCapacity(models.candidates[1], "candidate-2"),
333
+ routeCapacity(models.candidates[2], "candidate-3"),
334
+ routeCapacity(models.evaluator, "evaluator"),
335
+ routeCapacity(models.merger, "merger")
336
+ ];
337
+ }
338
+ __name(fusionRouteCapacities, "fusionRouteCapacities");
339
+ function fusionLimitingRoute(routes) {
340
+ let limiting;
341
+ for (const route of routes) {
342
+ if (limiting === void 0 || route.byte_capacity_utf8_bytes < limiting.byte_capacity_utf8_bytes || route.byte_capacity_utf8_bytes === limiting.byte_capacity_utf8_bytes && route.role.localeCompare(limiting.role) < 0) {
343
+ limiting = route;
344
+ }
345
+ }
346
+ if (limiting === void 0) {
347
+ throw new FusionError("fusion budget planning received no configured routes", {
348
+ code: "model_capacity_unknown",
349
+ childCreated: false
350
+ });
351
+ }
352
+ return limiting;
353
+ }
354
+ __name(fusionLimitingRoute, "fusionLimitingRoute");
355
+ function routeByRole(routes, role) {
356
+ const route = routes.find((item) => item.role === role);
357
+ if (route === void 0) {
358
+ throw new FusionError(`fusion budget route ${role} is missing`, {
359
+ code: "model_capacity_unknown",
360
+ childCreated: false
361
+ });
362
+ }
363
+ return route;
364
+ }
365
+ __name(routeByRole, "routeByRole");
366
+ function candidateRole(slot) {
367
+ if (slot === 1) return "candidate-1";
368
+ if (slot === 2) return "candidate-2";
369
+ return "candidate-3";
370
+ }
371
+ __name(candidateRole, "candidateRole");
372
+ function estimateRouteInput(route, segments) {
373
+ return estimateInputTokens({
374
+ family: route.family,
375
+ calibrationBacked: route.rate_source.backed,
376
+ familyResolution: route.rate_source.model_resolution,
377
+ allowedInputTokens: route.allowed_input_tokens,
378
+ scope: "fusion",
379
+ segments
380
+ });
381
+ }
382
+ __name(estimateRouteInput, "estimateRouteInput");
383
+ function utilizationBasisPoints(tokens, allowed) {
384
+ return ceilDiv(tokens * BASIS_POINTS_DENOMINATOR, allowed);
385
+ }
386
+ __name(utilizationBasisPoints, "utilizationBasisPoints");
387
+ function forecastEntry(draft) {
388
+ const inputSegments = [knownTextSegment(draft.system_prompt), knownTextSegment(draft.empty_user_prompt)];
389
+ const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
390
+ const inputOnly = estimateRouteInput(draft.route, inputSegments);
391
+ const reservationSegments = draft.upstream_output_contract_bytes === 0 ? inputSegments : [...inputSegments, unknownOutputContractSegment(draft.upstream_output_contract_bytes)];
392
+ const reservation = estimateRouteInput(draft.route, reservationSegments);
393
+ const forecastUtf8Bytes = inputBytes + draft.upstream_output_contract_bytes;
394
+ const entry = {
395
+ budget_stage: draft.budget_stage,
396
+ route: draft.route,
397
+ conditional: draft.conditional,
398
+ check_kind: "input_only_preflight",
399
+ input_utf8_bytes: inputBytes,
400
+ upstream_output_contract_bytes: draft.upstream_output_contract_bytes,
401
+ forecast_utf8_bytes: forecastUtf8Bytes,
402
+ input_only_input_tokens_upper_bound: inputOnly.tokens,
403
+ forecast_input_tokens_upper_bound: reservation.tokens,
404
+ allowed_input_tokens: draft.route.allowed_input_tokens,
405
+ input_only_signed_headroom_tokens: draft.route.allowed_input_tokens - inputOnly.tokens,
406
+ signed_headroom_tokens: draft.route.allowed_input_tokens - reservation.tokens,
407
+ input_only_utilization_basis_points: utilizationBasisPoints(
408
+ inputOnly.tokens,
409
+ draft.route.allowed_input_tokens
410
+ ),
411
+ utilization_basis_points: utilizationBasisPoints(
412
+ reservation.tokens,
413
+ draft.route.allowed_input_tokens
414
+ ),
415
+ input_only_estimate: inputOnly,
416
+ reservation_estimate: reservation,
417
+ fits: inputOnly.tokens <= draft.route.allowed_input_tokens,
418
+ reservation_fits: reservation.tokens <= draft.route.allowed_input_tokens
419
+ };
420
+ if (draft.slot !== void 0) entry.slot = draft.slot;
421
+ return entry;
422
+ }
423
+ __name(forecastEntry, "forecastEntry");
424
+ function maxKnownTextBytes(route) {
425
+ return maxKnownTextBytesForTokens({
426
+ family: route.family,
427
+ calibrationBacked: route.rate_source.backed,
428
+ familyResolution: route.rate_source.model_resolution,
429
+ allowedInputTokens: route.allowed_input_tokens,
430
+ scope: "fusion"
431
+ });
432
+ }
433
+ __name(maxKnownTextBytes, "maxKnownTextBytes");
434
+ function blockerFromEntry(entry) {
435
+ return {
436
+ ...entry,
437
+ overage_tokens: Math.max(0, entry.input_only_input_tokens_upper_bound - entry.allowed_input_tokens),
438
+ bytes_over: Math.max(0, entry.input_utf8_bytes - maxKnownTextBytes(entry.route))
439
+ };
440
+ }
441
+ __name(blockerFromEntry, "blockerFromEntry");
442
+ function blockerOrder(blocker) {
443
+ if (blocker.budget_stage === "candidate") return blocker.slot ?? 1;
444
+ if (blocker.budget_stage === "evaluation") return 4;
445
+ if (blocker.budget_stage === "merge") return 5;
446
+ return 6;
447
+ }
448
+ __name(blockerOrder, "blockerOrder");
449
+ function selectBlockers(entries) {
450
+ return entries.filter((entry) => !entry.fits).map(blockerFromEntry).sort((left, right) => {
451
+ const byOrder = blockerOrder(left) - blockerOrder(right);
452
+ if (byOrder !== 0) return byOrder;
453
+ return left.route.role.localeCompare(right.route.role);
454
+ });
455
+ }
456
+ __name(selectBlockers, "selectBlockers");
457
+ function selectPrimaryBlocker(blockers) {
458
+ const mandatory = blockers.find((blocker) => !blocker.conditional);
459
+ return mandatory ?? blockers[0];
460
+ }
461
+ __name(selectPrimaryBlocker, "selectPrimaryBlocker");
462
+ function replaceRequestText(input, text) {
463
+ return {
464
+ ...input,
465
+ request: {
466
+ ...input.request,
467
+ text,
468
+ sha256: sha256Hex(text)
469
+ }
470
+ };
471
+ }
472
+ __name(replaceRequestText, "replaceRequestText");
473
+ function visibleTextBytes(input) {
474
+ const projection = input.context?.kind === "session_projection" ? input.context.conversation_projection : "conversation_projection" in input ? input.conversation_projection : void 0;
475
+ if (projection === void 0) return 0;
476
+ let total = 0;
477
+ for (const entry of projection.entries) {
478
+ if (entry[0] === "t") total += utf8Bytes(JSON.stringify(entry[4]));
479
+ }
480
+ return total;
481
+ }
482
+ __name(visibleTextBytes, "visibleTextBytes");
483
+ function omissionReceiptBytes(input) {
484
+ const projection = input.context?.kind === "session_projection" ? input.context.conversation_projection : "conversation_projection" in input ? input.conversation_projection : void 0;
485
+ if (projection === void 0) return 0;
486
+ let total = 0;
487
+ for (const entry of projection.entries) {
488
+ if (entry[0] === "o") total += utf8Bytes(JSON.stringify(entry));
489
+ }
490
+ return total;
491
+ }
492
+ __name(omissionReceiptBytes, "omissionReceiptBytes");
493
+ function warningFromEntry(entry) {
494
+ if (!entry.fits) return void 0;
495
+ if (!entry.reservation_fits) {
496
+ return {
497
+ ...entry,
498
+ warning_kind: "worst_case_reservation",
499
+ threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
500
+ };
501
+ }
502
+ if (entry.utilization_basis_points >= FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS) {
503
+ return {
504
+ ...entry,
505
+ warning_kind: "input_utilization",
506
+ threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS
507
+ };
508
+ }
509
+ return void 0;
510
+ }
511
+ __name(warningFromEntry, "warningFromEntry");
512
+ function warningsFor(entries) {
513
+ return entries.flatMap((entry) => {
514
+ const warning = warningFromEntry(entry);
515
+ return warning === void 0 ? [] : [warning];
516
+ });
517
+ }
518
+ __name(warningsFor, "warningsFor");
519
+ function entryLabel(entry) {
520
+ const slot = entry.slot === void 0 ? "" : `-${String(entry.slot)}`;
521
+ const conditional = entry.conditional ? " (conditional)" : "";
522
+ return `${entry.budget_stage}${slot}${conditional}`;
523
+ }
524
+ __name(entryLabel, "entryLabel");
525
+ function blockingChildLabel(entry) {
526
+ if (entry.budget_stage === "candidate") return `candidate-${String(entry.slot ?? 1)}`;
527
+ if (entry.budget_stage === "evaluation_repair") return "evaluator-repair";
528
+ if (entry.budget_stage === "evaluation") return "evaluator";
529
+ return "merger";
530
+ }
531
+ __name(blockingChildLabel, "blockingChildLabel");
532
+ function formatTable(entries) {
533
+ const lines = entries.map(
534
+ (entry) => `${entryLabel(entry)} | route=${entry.route.qualified_id} | input=${String(entry.input_only_input_tokens_upper_bound)} | reserved=${String(entry.forecast_input_tokens_upper_bound)} | allowed=${String(entry.allowed_input_tokens)} | input_headroom=${String(entry.input_only_signed_headroom_tokens)} | reservation_headroom=${String(entry.signed_headroom_tokens)} | ${entry.fits ? "input-fits" : "input-over"} | ${entry.reservation_fits ? "reservation-fits" : "reservation-warn"}`
535
+ );
536
+ return lines.join("\n");
537
+ }
538
+ __name(formatTable, "formatTable");
539
+ function formatComposition(composition) {
540
+ return [
541
+ `visible text ${String(composition.visible_text_bytes)} B`,
542
+ `omission receipts ${String(composition.omission_receipt_bytes)} B`,
543
+ `projection metadata ${String(composition.projection_metadata_bytes)} B`,
544
+ `request ${String(composition.request_bytes)} B`,
545
+ `static stage framing ${String(composition.static_stage_framing_bytes)} B`,
546
+ `upstream output contracts ${String(composition.upstream_output_contract_bytes)} B`
547
+ ].join("; ");
548
+ }
549
+ __name(formatComposition, "formatComposition");
550
+ function dominantRemediation(verdict, composition, dominantByteClass, profile) {
551
+ if (dominantByteClass === "dense_ascii") return DENSE_REMEDIATION;
552
+ if (dominantByteClass === "multibyte") return MULTIBYTE_REMEDIATION;
553
+ if (composition === void 0) return remediationFor(verdict, profile);
554
+ const entries = [
555
+ { name: "visible", bytes: composition.visible_text_bytes },
556
+ { name: "request", bytes: composition.request_bytes },
557
+ { name: "reservation", bytes: composition.upstream_output_contract_bytes }
558
+ ].sort((left, right) => right.bytes - left.bytes);
559
+ const dominant = entries[0];
560
+ if (dominant?.name === "reservation") return RESERVATION_REMEDIATION;
561
+ if (dominant?.name === "request" && !verdict.still_fails_with_empty_request) {
562
+ return profile.contextKind === "clean_task" ? cleanRemediation(profile, true) : REASON_REQUEST_REMEDIATION;
563
+ }
564
+ if (profile.contextKind === "clean_task") return cleanRemediation(profile, false);
565
+ return REASON_EMPTY_REMEDIATION;
566
+ }
567
+ __name(dominantRemediation, "dominantRemediation");
568
+ function remediationFor(verdict, profile) {
569
+ if (profile.contextKind === "clean_task") {
570
+ return cleanRemediation(profile, !verdict.still_fails_with_empty_request);
571
+ }
572
+ return verdict.still_fails_with_empty_request ? REASON_EMPTY_REMEDIATION : REASON_REQUEST_REMEDIATION;
573
+ }
574
+ __name(remediationFor, "remediationFor");
575
+ function formatEmptyRequestVerdict(verdict) {
576
+ if (verdict.still_fails_with_empty_request) {
577
+ return `Empty-request counterfactual: still fails with ${String(verdict.blockers_with_empty_request.length)} blocking stage(s), so shortening the request cannot make this workflow fit.`;
578
+ }
579
+ if (verdict.minimum_request_byte_reduction === 0) {
580
+ return "Empty-request counterfactual: fits; no request reduction is required by the current plan.";
581
+ }
582
+ return `Empty-request counterfactual: fits, so request size determines feasibility; reduce the request by at least ${String(verdict.minimum_request_byte_reduction)} UTF-8 bytes, leaving at most ${String(verdict.maximum_safe_request_utf8_bytes)} UTF-8 bytes.`;
583
+ }
584
+ __name(formatEmptyRequestVerdict, "formatEmptyRequestVerdict");
585
+ function stageFromBudgetStage(stage) {
586
+ if (stage === "merge") return "merge";
587
+ if (stage === "candidate") return "candidate";
588
+ return "evaluation";
589
+ }
590
+ __name(stageFromBudgetStage, "stageFromBudgetStage");
591
+ function routeTable(routes) {
592
+ return [...routes].sort((left, right) => {
593
+ const byCapacity = left.byte_capacity_utf8_bytes - right.byte_capacity_utf8_bytes;
594
+ if (byCapacity !== 0) return byCapacity;
595
+ return left.role.localeCompare(right.role);
596
+ }).map((route) => ({
597
+ role: route.role,
598
+ qualified_id: route.qualified_id,
599
+ allowed_input_tokens: route.allowed_input_tokens,
600
+ family: route.family,
601
+ effective_rate_bytes_per_token_x100: route.rate_source.effective_rate_bytes_per_token_x100,
602
+ byte_capacity_utf8_bytes: route.byte_capacity_utf8_bytes,
603
+ backed: route.rate_source.backed
604
+ }));
605
+ }
606
+ __name(routeTable, "routeTable");
607
+ function segmentTokensForBytes(route, bytes) {
608
+ if (bytes <= 0) return 0;
609
+ const estimate = estimateRouteInput(route, [
610
+ { kind: "known_text", bytes, multibyteBytes: 0, denseBytes: 0 }
611
+ ]);
612
+ const segment = estimate.perSegment[0];
613
+ return segment === void 0 ? 0 : segment.tokens;
614
+ }
615
+ __name(segmentTokensForBytes, "segmentTokensForBytes");
616
+ function contractTokens(bytes) {
617
+ return bytes;
618
+ }
619
+ __name(contractTokens, "contractTokens");
620
+ function componentBreakdown(composition, route) {
621
+ const empty = {
622
+ visible_text_bytes: 0,
623
+ omission_receipt_bytes: 0,
624
+ projection_metadata_bytes: 0,
625
+ request_bytes: 0,
626
+ static_stage_framing_bytes: 0,
627
+ upstream_output_contract_bytes: 0
628
+ };
629
+ const item = composition ?? empty;
630
+ return {
631
+ visible_text: {
632
+ bytes: item.visible_text_bytes,
633
+ tokens: segmentTokensForBytes(route, item.visible_text_bytes)
634
+ },
635
+ omission_receipts: {
636
+ bytes: item.omission_receipt_bytes,
637
+ tokens: segmentTokensForBytes(route, item.omission_receipt_bytes)
638
+ },
639
+ projection_metadata: {
640
+ bytes: item.projection_metadata_bytes,
641
+ tokens: segmentTokensForBytes(route, item.projection_metadata_bytes)
642
+ },
643
+ request: {
644
+ bytes: item.request_bytes,
645
+ tokens: segmentTokensForBytes(route, item.request_bytes)
646
+ },
647
+ static_stage_framing: {
648
+ bytes: item.static_stage_framing_bytes,
649
+ tokens: segmentTokensForBytes(route, item.static_stage_framing_bytes)
650
+ },
651
+ upstream_output_contracts: {
652
+ bytes: item.upstream_output_contract_bytes,
653
+ tokens: contractTokens(item.upstream_output_contract_bytes)
654
+ }
655
+ };
656
+ }
657
+ __name(componentBreakdown, "componentBreakdown");
658
+ function medianCounterfactual(primary) {
659
+ if (!primary.route.rate_source.backed) {
660
+ return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
661
+ }
662
+ const median = primary.route.rate_source.provenance.median_bpt_x1000;
663
+ if (median === null) return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
664
+ const variableTokens = ceilDiv(primary.input_utf8_bytes * 1e3, median);
665
+ const tokens = variableTokens + TOKEN_BUDGET_AFFINE_F_TOKENS;
666
+ return {
667
+ forecast_input_tokens_upper_bound: tokens,
668
+ signed_headroom_tokens: primary.allowed_input_tokens - tokens,
669
+ fits: tokens <= primary.allowed_input_tokens
670
+ };
671
+ }
672
+ __name(medianCounterfactual, "medianCounterfactual");
673
+ function inputCounterfactuals(primary, plan) {
674
+ return {
675
+ empty_request: plan.empty_request,
676
+ without_reservation: {
677
+ forecast_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
678
+ signed_headroom_tokens: primary.input_only_signed_headroom_tokens,
679
+ fits: primary.fits
680
+ },
681
+ at_median_rate: medianCounterfactual(primary)
682
+ };
683
+ }
684
+ __name(inputCounterfactuals, "inputCounterfactuals");
685
+ function budgetErrorCode(checkKind) {
686
+ return checkKind === "rendered_prompt" ? "prompt_budget_exceeded_measured" : "prompt_budget_exceeded_forecast";
687
+ }
688
+ __name(budgetErrorCode, "budgetErrorCode");
689
+ var FusionBudget = class {
690
+ static {
691
+ __name(this, "FusionBudget");
692
+ }
693
+ routes;
694
+ limiting;
695
+ contextPolicyId;
696
+ candidateCapability;
697
+ profile;
698
+ constructor(models, contextPolicyId, candidateCapability = FUSION_REASON_WORKFLOW.candidateCapability, profile = FUSION_REASON_WORKFLOW) {
699
+ this.routes = fusionRouteCapacities(models);
700
+ this.limiting = fusionLimitingRoute(this.routes);
701
+ this.contextPolicyId = contextPolicyId;
702
+ this.candidateCapability = candidateCapability;
703
+ this.profile = profile;
704
+ }
705
+ get allowedInputTokens() {
706
+ return this.limiting.allowed_input_tokens;
707
+ }
708
+ get resultRateSources() {
709
+ return this.routes.map((route) => route.rate_source);
710
+ }
711
+ get unknownProviderWarnings() {
712
+ return this.routes.flatMap((route) => {
713
+ const warning = route.rate_source.warning;
714
+ return route.rate_source.backed || warning === null ? [] : [`${route.qualified_id}: ${warning}`];
715
+ });
716
+ }
717
+ routeForStage(stage, slot) {
718
+ if (stage === "candidate") return routeByRole(this.routes, candidateRole(slot ?? 1));
719
+ if (stage === "merge") return routeByRole(this.routes, "merger");
720
+ return routeByRole(this.routes, "evaluator");
721
+ }
722
+ drafts(input) {
723
+ const candidateSystemPrompt = this.profile.candidateSystemPrompt(this.candidateCapability);
724
+ const candidatePrompt = buildCandidatePrompt(input);
725
+ const blindInput = buildBlindEvaluationInput(input, EMPTY_CANDIDATES);
726
+ const evaluationPrompt = buildEvaluationPrompt(blindInput);
727
+ const repairPrompt = buildEvaluationRepairPrompt({
728
+ schema_version: "pi-background-tasks.fusion-evaluation-repair-input.v1",
729
+ original_blind_input: blindInput,
730
+ invalid_output: "",
731
+ validation_errors: []
732
+ });
733
+ const mergePrompt = buildMergePrompt(buildMergeInput(input, EMPTY_CANDIDATES, EMPTY_EVALUATION));
734
+ return [
735
+ {
736
+ budget_stage: "candidate",
737
+ slot: 1,
738
+ route: this.routeForStage("candidate", 1),
739
+ conditional: false,
740
+ system_prompt: candidateSystemPrompt,
741
+ empty_user_prompt: candidatePrompt,
742
+ upstream_output_contract_bytes: 0
743
+ },
744
+ {
745
+ budget_stage: "candidate",
746
+ slot: 2,
747
+ route: this.routeForStage("candidate", 2),
748
+ conditional: false,
749
+ system_prompt: candidateSystemPrompt,
750
+ empty_user_prompt: candidatePrompt,
751
+ upstream_output_contract_bytes: 0
752
+ },
753
+ {
754
+ budget_stage: "candidate",
755
+ slot: 3,
756
+ route: this.routeForStage("candidate", 3),
757
+ conditional: false,
758
+ system_prompt: candidateSystemPrompt,
759
+ empty_user_prompt: candidatePrompt,
760
+ upstream_output_contract_bytes: 0
761
+ },
762
+ {
763
+ budget_stage: "evaluation",
764
+ route: this.routeForStage("evaluation"),
765
+ conditional: false,
766
+ system_prompt: this.profile.evaluatorSystemPrompt,
767
+ empty_user_prompt: evaluationPrompt,
768
+ upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES
769
+ },
770
+ {
771
+ budget_stage: "merge",
772
+ route: this.routeForStage("merge"),
773
+ conditional: false,
774
+ system_prompt: this.profile.mergerSystemPrompt,
775
+ empty_user_prompt: mergePrompt,
776
+ upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES + FUSION_EVALUATION_MAX_OUTPUT_BYTES
777
+ },
778
+ {
779
+ budget_stage: "evaluation_repair",
780
+ route: this.routeForStage("evaluation_repair"),
781
+ conditional: true,
782
+ system_prompt: this.profile.evaluationRepairSystemPrompt,
783
+ empty_user_prompt: repairPrompt,
784
+ upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES + FUSION_EVALUATION_MAX_OUTPUT_BYTES + FUSION_DIAGNOSTICS_MAX_BYTES
785
+ }
786
+ ];
787
+ }
788
+ entries(input) {
789
+ return this.drafts(input).map(forecastEntry);
790
+ }
791
+ composition(input, blocker) {
792
+ const canonicalBytes = utf8Bytes(buildCandidatePrompt(input));
793
+ const emptyRequestCanonicalBytes = utf8Bytes(buildCandidatePrompt(replaceRequestText(input, "")));
794
+ const requestBytes = canonicalBytes - emptyRequestCanonicalBytes;
795
+ const visible = visibleTextBytes(input);
796
+ const omissions = omissionReceiptBytes(input);
797
+ const projectionMetadata = canonicalBytes - requestBytes - visible - omissions;
798
+ const draft = this.drafts(input).find(
799
+ (item) => item.budget_stage === blocker.budget_stage && item.slot === blocker.slot
800
+ );
801
+ if (draft === void 0) {
802
+ throw new FusionError("primary budget blocker disappeared during composition", {
803
+ code: "orchestration_failed",
804
+ childCreated: false
805
+ });
806
+ }
807
+ return {
808
+ visible_text_bytes: visible,
809
+ omission_receipt_bytes: omissions,
810
+ projection_metadata_bytes: projectionMetadata,
811
+ request_bytes: requestBytes,
812
+ static_stage_framing_bytes: utf8Bytes(draft.system_prompt) + utf8Bytes(draft.empty_user_prompt) - canonicalBytes,
813
+ upstream_output_contract_bytes: draft.upstream_output_contract_bytes
814
+ };
815
+ }
816
+ emptyRequestVerdict(input, entries) {
817
+ const emptyEntries = this.entries(replaceRequestText(input, ""));
818
+ const emptyBlockers = selectBlockers(emptyEntries);
819
+ let reduction = 0;
820
+ for (const entry of entries) {
821
+ reduction = Math.max(reduction, entry.input_utf8_bytes - maxKnownTextBytes(entry.route));
822
+ }
823
+ reduction = Math.max(0, reduction);
824
+ const requestBytes = utf8Bytes(input.request.text);
825
+ return {
826
+ request_utf8_bytes: requestBytes,
827
+ still_fails_with_empty_request: emptyBlockers.length > 0,
828
+ shortening_request_can_help: emptyBlockers.length === 0,
829
+ minimum_request_byte_reduction: reduction,
830
+ maximum_safe_request_utf8_bytes: Math.max(0, requestBytes - reduction),
831
+ blockers_with_empty_request: emptyBlockers
832
+ };
833
+ }
834
+ failure(primary, plan, artifactDir, measurementKind, checkKind) {
835
+ const composition = plan.primary_blocker_composition;
836
+ const dominantByteClass = primary.input_only_estimate.rateSource.dominant_byte_class;
837
+ const remediation = dominantRemediation(
838
+ plan.empty_request,
839
+ composition,
840
+ dominantByteClass,
841
+ this.profile
842
+ );
843
+ const tokensOver = primary.input_only_input_tokens_upper_bound - primary.allowed_input_tokens;
844
+ const budget = {
845
+ budget_stage: primary.budget_stage,
846
+ measurement_kind: measurementKind,
847
+ check_kind: checkKind,
848
+ measured_utf8_bytes: primary.input_utf8_bytes,
849
+ measured_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
850
+ allowed_input_tokens: primary.allowed_input_tokens,
851
+ limiting_model: {
852
+ provider: primary.route.provider,
853
+ model: primary.route.model,
854
+ qualified_id: primary.route.qualified_id,
855
+ context_window_tokens: primary.route.context_window_tokens
856
+ },
857
+ rate_source: primary.input_only_estimate.rateSource,
858
+ backed: primary.input_only_estimate.rateSource.backed,
859
+ dominant_byte_class: dominantByteClass,
860
+ component_breakdown: componentBreakdown(composition, primary.route),
861
+ byte_class_breakdown: primary.input_only_estimate.byte_class_breakdown,
862
+ dense_regions: [],
863
+ bytes_over: primary.bytes_over,
864
+ tokens_over: Math.max(0, tokensOver),
865
+ required_allowed_tokens: primary.input_only_input_tokens_upper_bound,
866
+ route_table: routeTable(this.routes),
867
+ counterfactuals: inputCounterfactuals(primary, plan),
868
+ stage_upstream_actuals: [],
869
+ policy_id: FUSION_BUDGET_POLICY.id,
870
+ calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
871
+ context_policy_id: this.contextPolicyId,
872
+ remediation,
873
+ blockers: plan.blockers,
874
+ artifact_dir: artifactDir
875
+ };
876
+ if (primary.slot !== void 0) budget.slot = primary.slot;
877
+ const compositionText = composition === void 0 ? "unavailable" : formatComposition(composition);
878
+ const additional = plan.blockers.filter((blocker) => blocker !== primary).map((blocker) => `${entryLabel(blocker)} route=${blocker.route.qualified_id}`).join("; ");
879
+ const rateText = `${primary.route.family} ${formatRateX100(primary.input_only_estimate.rateSource.effective_rate_bytes_per_token_x100)} B/tok + ${String(primary.input_only_estimate.rateSource.affine_f_tokens)} tokens (${primary.input_only_estimate.rateSource.source}, backed=${String(primary.input_only_estimate.rateSource.backed)}, dominant=${dominantByteClass})`;
880
+ const sourceWarning = primary.input_only_estimate.rateSource.warning;
881
+ const routeWarning = sourceWarning === null ? "" : ` Rate warning: ${sourceWarning}.`;
882
+ const checkText = checkKind === "input_only_preflight" ? "input-only preflight forecast" : "exact rendered prompt measurement";
883
+ const dominantText = dominantByteClass === "multibyte" ? " Dominant byte class is multibyte UTF-8; the fatal gate uses the conservative multibyte rate and the plan separately records the provable 1.00 B/tok advisory ceiling." : dominantByteClass === "dense_ascii" ? " Dominant byte class is dense ASCII/low-whitespace content; the whitespace gate is a heuristic token-density proxy, not a bound." : ` Dominant byte class is ${dominantByteClass}.`;
884
+ const message = `Fusion prompt budget exceeded by ${checkText} before ${blockingChildLabel(primary)} child creation. Primary blocking stage: ${entryLabel(primary)} on route ${primary.route.qualified_id}. Forecast ${String(primary.input_utf8_bytes)} UTF-8 bytes (<= ${String(primary.input_only_input_tokens_upper_bound)} input tokens) against ${String(primary.allowed_input_tokens)} allowed input tokens, over by ${String(Math.max(0, tokensOver))} tokens. Estimator: ${rateText}.${routeWarning}${dominantText} The ${blockingChildLabel(primary)} child was not created. Nothing was clipped, dropped, or substituted. Artifact directory: ${artifactDir}.
885
+ Per-stage forecast table:
886
+ ${formatTable(plan.stages)}
887
+ Primary blocker byte composition: ${compositionText}.
888
+ Additional blockers: ${additional.length === 0 ? "none" : additional}.
889
+ ${formatEmptyRequestVerdict(plan.empty_request)}
890
+ Route byte-capacity order: ${routeTable(this.routes).map((route) => `${route.qualified_id}=${String(route.byte_capacity_utf8_bytes)}B`).join(", ")}.
891
+ Remediation: ${remediation.join(" ")}`;
892
+ const details = {
893
+ code: budgetErrorCode(checkKind),
894
+ childCreated: false,
895
+ budget,
896
+ stage: stageFromBudgetStage(primary.budget_stage)
897
+ };
898
+ if (primary.slot !== void 0) return new FusionError(message, { ...details, slot: primary.slot });
899
+ return new FusionError(message, details);
900
+ }
901
+ planMetadata(input) {
902
+ return {
903
+ workflow: this.profile.id,
904
+ context: {
905
+ kind: this.profile.contextKind,
906
+ policy_id: input?.context?.policy_id ?? this.contextPolicyId
907
+ },
908
+ fixed_candidate_policy: {
909
+ capability: this.candidateCapability,
910
+ tools: this.profile.candidateTools
911
+ },
912
+ tool_policy: {
913
+ candidate_tools: this.profile.candidateTools,
914
+ evaluation_tools: [],
915
+ merge_tools: []
916
+ }
917
+ };
918
+ }
919
+ plan(input) {
920
+ const stages = this.entries(input);
921
+ const blockers = selectBlockers(stages);
922
+ const primary = selectPrimaryBlocker(blockers);
923
+ const emptyRequest = this.emptyRequestVerdict(input, stages);
924
+ const base = {
925
+ schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
926
+ ...this.planMetadata(input),
927
+ policy: FUSION_BUDGET_POLICY,
928
+ routes: this.routes,
929
+ stages,
930
+ blockers,
931
+ empty_request: emptyRequest,
932
+ warnings: warningsFor(stages)
933
+ };
934
+ if (primary === void 0) return base;
935
+ return {
936
+ ...base,
937
+ primary_blocker: primary,
938
+ primary_blocker_composition: this.composition(input, primary)
939
+ };
940
+ }
941
+ assertPlanFits(plan, artifactDir) {
942
+ if (plan.primary_blocker !== void 0) {
943
+ throw this.failure(
944
+ plan.primary_blocker,
945
+ plan,
946
+ artifactDir,
947
+ "stage_forecast",
948
+ "input_only_preflight"
949
+ );
950
+ }
951
+ }
952
+ assertStagePrompt(stage, systemPrompt, userPrompt, slot) {
953
+ const route = this.routeForStage(stage, slot);
954
+ const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
955
+ const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
956
+ const estimate = estimateRouteInput(route, inputSegments);
957
+ if (estimate.tokens <= route.allowed_input_tokens) return;
958
+ const entry = {
959
+ budget_stage: stage,
960
+ route,
961
+ conditional: stage === "evaluation_repair",
962
+ check_kind: "input_only_preflight",
963
+ input_utf8_bytes: inputBytes,
964
+ upstream_output_contract_bytes: 0,
965
+ forecast_utf8_bytes: inputBytes,
966
+ input_only_input_tokens_upper_bound: estimate.tokens,
967
+ forecast_input_tokens_upper_bound: estimate.tokens,
968
+ allowed_input_tokens: route.allowed_input_tokens,
969
+ input_only_signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
970
+ signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
971
+ input_only_utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
972
+ utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
973
+ input_only_estimate: estimate,
974
+ reservation_estimate: estimate,
975
+ fits: false,
976
+ reservation_fits: false
977
+ };
978
+ if (slot !== void 0) entry.slot = slot;
979
+ const blocker = blockerFromEntry(entry);
980
+ const plan = {
981
+ schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
982
+ ...this.planMetadata(),
983
+ policy: FUSION_BUDGET_POLICY,
984
+ routes: this.routes,
985
+ stages: [entry],
986
+ blockers: [blocker],
987
+ primary_blocker: blocker,
988
+ empty_request: {
989
+ request_utf8_bytes: 0,
990
+ still_fails_with_empty_request: true,
991
+ shortening_request_can_help: false,
992
+ minimum_request_byte_reduction: blocker.bytes_over,
993
+ maximum_safe_request_utf8_bytes: 0,
994
+ blockers_with_empty_request: [blocker]
995
+ },
996
+ warnings: []
997
+ };
998
+ throw this.failure(blocker, plan, "stage prompt re-measurement", "rendered_prompt", "rendered_prompt");
999
+ }
1000
+ calibrationViolationForCompletedChild(stage, systemPrompt, userPrompt, result, slot) {
1001
+ const route = this.routeForStage(stage, slot);
1002
+ const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
1003
+ const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
1004
+ const estimate = estimateRouteInput(route, inputSegments);
1005
+ const observedUsage = result.firstRequestUsage;
1006
+ if (observedUsage === void 0) return void 0;
1007
+ const billedInput = observedUsage.input + observedUsage.cacheRead + observedUsage.cacheWrite;
1008
+ if (billedInput <= estimate.tokens) return void 0;
1009
+ const violation = {
1010
+ schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
1011
+ stage,
1012
+ attempt: result.attempt,
1013
+ route: {
1014
+ provider: result.provider,
1015
+ model: result.model,
1016
+ qualified_id: result.qualifiedId
1017
+ },
1018
+ family: route.family,
1019
+ rate_source: estimate.rateSource,
1020
+ prompt_utf8_bytes: promptUtf8Bytes,
1021
+ prompt_sha256: sha256Hex(`${systemPrompt}\0${userPrompt}`),
1022
+ observation_scope: "first_provider_request",
1023
+ provider_request_count: result.providerRequestCount ?? 1,
1024
+ forecast_input_tokens: estimate.tokens,
1025
+ billed_input_tokens: billedInput,
1026
+ billed_input_breakdown: {
1027
+ input: observedUsage.input,
1028
+ cache_read: observedUsage.cacheRead,
1029
+ cache_write: observedUsage.cacheWrite
1030
+ },
1031
+ under_forecast_tokens: billedInput - estimate.tokens,
1032
+ byte_class_breakdown: estimate.byte_class_breakdown,
1033
+ dominant_byte_class: estimate.rateSource.dominant_byte_class
1034
+ };
1035
+ if (slot !== void 0) violation.slot = slot;
1036
+ return violation;
1037
+ }
1038
+ };
1039
+
1040
+ // node_modules/pi-background-tasks/src/core/fusion/evaluation.ts?ext=pi-background-tasks
1041
+ var MAX_REPAIR_ERROR_CHARS = 500;
1042
+ var MAX_REPAIR_ERROR_COUNT = 24;
1043
+ var MAX_REPAIR_ERROR_TOTAL_CHARS = 4e3;
1044
+ function isRecord(value) {
1045
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1046
+ }
1047
+ __name(isRecord, "isRecord");
1048
+ function errorText(error) {
1049
+ return error instanceof Error ? error.message : String(error);
1050
+ }
1051
+ __name(errorText, "errorText");
1052
+ function closed(record, keys, label, errors) {
1053
+ const expected = new Set(keys);
1054
+ for (const key of Object.keys(record)) {
1055
+ if (!expected.has(key)) errors.push(`${label} contains unknown key ${key}`);
1056
+ }
1057
+ for (const key of keys) {
1058
+ if (!Object.prototype.hasOwnProperty.call(record, key))
1059
+ errors.push(`${label} is missing key ${key}`);
1060
+ }
1061
+ }
1062
+ __name(closed, "closed");
1063
+ function nonBlankString(value, label, errors) {
1064
+ if (typeof value !== "string") {
1065
+ errors.push(`${label} must be a string`);
1066
+ return void 0;
1067
+ }
1068
+ if (value.trim().length === 0) {
1069
+ errors.push(`${label} must be non-blank`);
1070
+ return void 0;
1071
+ }
1072
+ return value;
1073
+ }
1074
+ __name(nonBlankString, "nonBlankString");
1075
+ function stringList(value, label, errors) {
1076
+ if (!Array.isArray(value)) {
1077
+ errors.push(`${label} must be an array`);
1078
+ return void 0;
1079
+ }
1080
+ const out = [];
1081
+ for (const [index, item] of value.entries()) {
1082
+ const parsed = nonBlankString(item, `${label}[${String(index)}]`, errors);
1083
+ if (parsed !== void 0) out.push(parsed);
1084
+ }
1085
+ return out;
1086
+ }
1087
+ __name(stringList, "stringList");
1088
+ function candidateId(value, label, errors) {
1089
+ if (value === "A" || value === "B" || value === "C") return value;
1090
+ errors.push(`${label} must be A, B, or C`);
1091
+ return void 0;
1092
+ }
1093
+ __name(candidateId, "candidateId");
1094
+ function tuple3(items, label, errors) {
1095
+ if (items.length !== 3) {
1096
+ errors.push(`${label} must contain exactly three entries`);
1097
+ return void 0;
1098
+ }
1099
+ const first = items[0];
1100
+ const second = items[1];
1101
+ const third = items[2];
1102
+ if (first === void 0 || second === void 0 || third === void 0) {
1103
+ errors.push(`${label} must not contain empty positions`);
1104
+ return void 0;
1105
+ }
1106
+ return [first, second, third];
1107
+ }
1108
+ __name(tuple3, "tuple3");
1109
+ function parseAssessment(value, label, errors) {
1110
+ if (!isRecord(value)) {
1111
+ errors.push(`${label} must be an object`);
1112
+ return void 0;
1113
+ }
1114
+ closed(
1115
+ value,
1116
+ ["candidate_id", "summary", "strengths", "limitations", "useful_contributions", "risks"],
1117
+ label,
1118
+ errors
1119
+ );
1120
+ const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
1121
+ const summary = nonBlankString(value["summary"], `${label}.summary`, errors);
1122
+ const strengths = stringList(value["strengths"], `${label}.strengths`, errors);
1123
+ const limitations = stringList(value["limitations"], `${label}.limitations`, errors);
1124
+ const useful = stringList(value["useful_contributions"], `${label}.useful_contributions`, errors);
1125
+ const risks = stringList(value["risks"], `${label}.risks`, errors);
1126
+ if (id === void 0 || summary === void 0 || strengths === void 0 || limitations === void 0 || useful === void 0 || risks === void 0) {
1127
+ return void 0;
1128
+ }
1129
+ return { candidate_id: id, summary, strengths, limitations, useful_contributions: useful, risks };
1130
+ }
1131
+ __name(parseAssessment, "parseAssessment");
1132
+ function parsePosition(value, label, errors) {
1133
+ if (!isRecord(value)) {
1134
+ errors.push(`${label} must be an object`);
1135
+ return void 0;
1136
+ }
1137
+ closed(value, ["candidate_id", "position"], label, errors);
1138
+ const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
1139
+ const position = nonBlankString(value["position"], `${label}.position`, errors);
1140
+ if (id === void 0 || position === void 0) return void 0;
1141
+ return { candidate_id: id, position };
1142
+ }
1143
+ __name(parsePosition, "parsePosition");
1144
+ function parseConflict(value, label, errors) {
1145
+ if (!isRecord(value)) {
1146
+ errors.push(`${label} must be an object`);
1147
+ return void 0;
1148
+ }
1149
+ closed(value, ["topic", "positions", "resolution"], label, errors);
1150
+ const topic = nonBlankString(value["topic"], `${label}.topic`, errors);
1151
+ const positionsRaw = value["positions"];
1152
+ const positions = [];
1153
+ if (!Array.isArray(positionsRaw)) {
1154
+ errors.push(`${label}.positions must be an array`);
1155
+ } else {
1156
+ for (const [index, item] of positionsRaw.entries()) {
1157
+ const parsed = parsePosition(item, `${label}.positions[${String(index)}]`, errors);
1158
+ if (parsed !== void 0) positions.push(parsed);
1159
+ }
1160
+ const distinctIds = new Set(positions.map((position) => position.candidate_id));
1161
+ if (distinctIds.size < 2)
1162
+ errors.push(`${label}.positions must include at least two distinct candidates`);
1163
+ if (distinctIds.size !== positions.length)
1164
+ errors.push(`${label}.positions candidate_id values must be unique`);
1165
+ }
1166
+ const resolution = nonBlankString(value["resolution"], `${label}.resolution`, errors);
1167
+ if (topic === void 0 || resolution === void 0 || !Array.isArray(positionsRaw))
1168
+ return void 0;
1169
+ return { topic, positions, resolution };
1170
+ }
1171
+ __name(parseConflict, "parseConflict");
1172
+ function parseContribution(value, label, errors) {
1173
+ if (!isRecord(value)) {
1174
+ errors.push(`${label} must be an object`);
1175
+ return void 0;
1176
+ }
1177
+ closed(value, ["candidate_id", "contribution"], label, errors);
1178
+ const id = candidateId(value["candidate_id"], `${label}.candidate_id`, errors);
1179
+ const contribution = nonBlankString(value["contribution"], `${label}.contribution`, errors);
1180
+ if (id === void 0 || contribution === void 0) return void 0;
1181
+ return { candidate_id: id, contribution };
1182
+ }
1183
+ __name(parseContribution, "parseContribution");
1184
+ function parseContributionList(value, label, errors) {
1185
+ if (!Array.isArray(value)) {
1186
+ errors.push(`${label} must be an array`);
1187
+ return void 0;
1188
+ }
1189
+ const out = [];
1190
+ for (const [index, item] of value.entries()) {
1191
+ const parsed = parseContribution(item, `${label}[${String(index)}]`, errors);
1192
+ if (parsed !== void 0) out.push(parsed);
1193
+ }
1194
+ return out;
1195
+ }
1196
+ __name(parseContributionList, "parseContributionList");
1197
+ function parseSynthesisPlan(value, label, errors) {
1198
+ if (!isRecord(value)) {
1199
+ errors.push(`${label} must be an object`);
1200
+ return void 0;
1201
+ }
1202
+ closed(value, ["must_include", "must_resolve", "must_avoid"], label, errors);
1203
+ const include = parseContributionList(value["must_include"], `${label}.must_include`, errors);
1204
+ const resolve2 = stringList(value["must_resolve"], `${label}.must_resolve`, errors);
1205
+ const avoid = stringList(value["must_avoid"], `${label}.must_avoid`, errors);
1206
+ if (include === void 0 || resolve2 === void 0 || avoid === void 0) return void 0;
1207
+ return { must_include: include, must_resolve: resolve2, must_avoid: avoid };
1208
+ }
1209
+ __name(parseSynthesisPlan, "parseSynthesisPlan");
1210
+ function parseAssessmentList(value, label, errors) {
1211
+ if (!Array.isArray(value)) {
1212
+ errors.push(`${label} must be an array`);
1213
+ return void 0;
1214
+ }
1215
+ const parsed = [];
1216
+ for (const [index, item] of value.entries()) {
1217
+ const assessment = parseAssessment(item, `${label}[${String(index)}]`, errors);
1218
+ if (assessment !== void 0) parsed.push(assessment);
1219
+ }
1220
+ const ids = new Set(parsed.map((assessment) => assessment.candidate_id));
1221
+ for (const id of FUSION_CANDIDATE_IDS) {
1222
+ if (!ids.has(id)) errors.push(`${label} must contain candidate ${id}`);
1223
+ }
1224
+ if (ids.size !== parsed.length) errors.push(`${label} candidate_id values must be unique`);
1225
+ return tuple3(parsed, label, errors);
1226
+ }
1227
+ __name(parseAssessmentList, "parseAssessmentList");
1228
+ function parseConflictList(value, label, errors) {
1229
+ if (!Array.isArray(value)) {
1230
+ errors.push(`${label} must be an array`);
1231
+ return void 0;
1232
+ }
1233
+ const out = [];
1234
+ for (const [index, item] of value.entries()) {
1235
+ const parsed = parseConflict(item, `${label}[${String(index)}]`, errors);
1236
+ if (parsed !== void 0) out.push(parsed);
1237
+ }
1238
+ return out;
1239
+ }
1240
+ __name(parseConflictList, "parseConflictList");
1241
+ function parseValidationGroups(value, label, errors) {
1242
+ if (!Array.isArray(value)) {
1243
+ errors.push(`${label} must be an array`);
1244
+ return [];
1245
+ }
1246
+ const groups = [];
1247
+ for (const [index, item] of value.entries()) {
1248
+ const itemLabel = `${label}[${String(index)}]`;
1249
+ if (!isRecord(item)) {
1250
+ errors.push(`${itemLabel} must be an object`);
1251
+ continue;
1252
+ }
1253
+ closed(
1254
+ item,
1255
+ ["group_id", "source_ids", "severity", "location", "evidence", "impact", "summary", "rationale"],
1256
+ itemLabel,
1257
+ errors
1258
+ );
1259
+ const groupId = nonBlankString(item["group_id"], `${itemLabel}.group_id`, errors);
1260
+ const sourceIds = stringList(item["source_ids"], `${itemLabel}.source_ids`, errors);
1261
+ const severity = nonBlankString(item["severity"], `${itemLabel}.severity`, errors);
1262
+ const location = nonBlankString(item["location"], `${itemLabel}.location`, errors);
1263
+ const evidence = nonBlankString(item["evidence"], `${itemLabel}.evidence`, errors);
1264
+ const impact = nonBlankString(item["impact"], `${itemLabel}.impact`, errors);
1265
+ const summary = nonBlankString(item["summary"], `${itemLabel}.summary`, errors);
1266
+ const rationale = nonBlankString(item["rationale"], `${itemLabel}.rationale`, errors);
1267
+ if (sourceIds !== void 0 && sourceIds.length === 0) {
1268
+ errors.push(`${itemLabel}.source_ids must not be empty`);
1269
+ }
1270
+ if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) {
1271
+ errors.push(`${itemLabel}.severity invalid`);
1272
+ }
1273
+ if (groupId !== void 0 && sourceIds !== void 0 && sourceIds.length > 0 && severity !== void 0 && location !== void 0 && evidence !== void 0 && impact !== void 0 && summary !== void 0 && rationale !== void 0) {
1274
+ groups.push({
1275
+ group_id: groupId,
1276
+ source_ids: sourceIds,
1277
+ severity,
1278
+ location,
1279
+ evidence,
1280
+ impact,
1281
+ summary,
1282
+ rationale
1283
+ });
1284
+ }
1285
+ }
1286
+ return groups;
1287
+ }
1288
+ __name(parseValidationGroups, "parseValidationGroups");
1289
+ function parseValidationAccounting(value, label, errors) {
1290
+ if (!isRecord(value)) {
1291
+ errors.push(`${label} must be an object`);
1292
+ return void 0;
1293
+ }
1294
+ closed(value, ["findings", "decisions", "groups"], label, errors);
1295
+ const findingsRaw = value["findings"];
1296
+ const decisionsRaw = value["decisions"];
1297
+ const groups = parseValidationGroups(value["groups"], `${label}.groups`, errors);
1298
+ const findings = [];
1299
+ if (!Array.isArray(findingsRaw)) errors.push(`${label}.findings must be an array`);
1300
+ else {
1301
+ for (const [index, item] of findingsRaw.entries()) {
1302
+ const itemLabel = `${label}.findings[${String(index)}]`;
1303
+ if (!isRecord(item)) {
1304
+ errors.push(`${itemLabel} must be an object`);
1305
+ continue;
1306
+ }
1307
+ closed(item, ["id", "candidate_id", "severity", "location", "evidence", "impact", "summary"], itemLabel, errors);
1308
+ const id = nonBlankString(item["id"], `${itemLabel}.id`, errors);
1309
+ const candidate = candidateId(item["candidate_id"], `${itemLabel}.candidate_id`, errors);
1310
+ const severity = nonBlankString(item["severity"], `${itemLabel}.severity`, errors);
1311
+ const location = nonBlankString(item["location"], `${itemLabel}.location`, errors);
1312
+ const evidence = nonBlankString(item["evidence"], `${itemLabel}.evidence`, errors);
1313
+ const impact = nonBlankString(item["impact"], `${itemLabel}.impact`, errors);
1314
+ const summary = nonBlankString(item["summary"], `${itemLabel}.summary`, errors);
1315
+ if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) errors.push(`${itemLabel}.severity invalid`);
1316
+ if (id !== void 0 && candidate !== void 0 && severity !== void 0 && location !== void 0 && evidence !== void 0 && impact !== void 0 && summary !== void 0) {
1317
+ findings.push({ id, candidate_id: candidate, severity, location, evidence, impact, summary });
1318
+ }
1319
+ }
1320
+ }
1321
+ const decisions = [];
1322
+ if (!Array.isArray(decisionsRaw)) errors.push(`${label}.decisions must be an array`);
1323
+ else {
1324
+ for (const [index, item] of decisionsRaw.entries()) {
1325
+ const itemLabel = `${label}.decisions[${String(index)}]`;
1326
+ if (!isRecord(item)) {
1327
+ errors.push(`${itemLabel} must be an object`);
1328
+ continue;
1329
+ }
1330
+ const allowedDecisionKeys = /* @__PURE__ */ new Set(["source_id", "disposition", "rationale", "group_id"]);
1331
+ for (const key of Object.keys(item)) {
1332
+ if (!allowedDecisionKeys.has(key)) errors.push(`${itemLabel} contains unknown key ${key}`);
1333
+ }
1334
+ for (const key of ["source_id", "disposition", "rationale"]) {
1335
+ if (!Object.prototype.hasOwnProperty.call(item, key)) errors.push(`${itemLabel} is missing key ${key}`);
1336
+ }
1337
+ const sourceId = nonBlankString(item["source_id"], `${itemLabel}.source_id`, errors);
1338
+ const disposition = nonBlankString(item["disposition"], `${itemLabel}.disposition`, errors);
1339
+ const rationale = nonBlankString(item["rationale"], `${itemLabel}.rationale`, errors);
1340
+ const group = item["group_id"] === void 0 ? void 0 : nonBlankString(item["group_id"], `${itemLabel}.group_id`, errors);
1341
+ if (disposition !== void 0 && disposition !== "include" && disposition !== "exclude") errors.push(`${itemLabel}.disposition invalid`);
1342
+ if (sourceId !== void 0 && (disposition === "include" || disposition === "exclude") && rationale !== void 0) {
1343
+ const decision = { source_id: sourceId, disposition, rationale };
1344
+ if (group !== void 0) decision.group_id = group;
1345
+ decisions.push(decision);
1346
+ }
1347
+ }
1348
+ }
1349
+ const accounting = { findings, decisions, groups };
1350
+ errors.push(...validateFusionFindingAccounting(accounting));
1351
+ return accounting;
1352
+ }
1353
+ __name(parseValidationAccounting, "parseValidationAccounting");
1354
+ function validateFusionEvaluation(value) {
1355
+ const errors = [];
1356
+ if (!isRecord(value)) return { ok: false, errors: ["evaluation must be a JSON object"] };
1357
+ const evaluationAllowed = /* @__PURE__ */ new Set(["schema_version", "candidate_assessments", "agreements", "conflicts", "synthesis_plan", "validation_accounting"]);
1358
+ for (const key of Object.keys(value)) {
1359
+ if (!evaluationAllowed.has(key)) errors.push(`evaluation contains unknown key ${key}`);
1360
+ }
1361
+ for (const key of ["schema_version", "candidate_assessments", "agreements", "conflicts", "synthesis_plan"]) {
1362
+ if (!Object.prototype.hasOwnProperty.call(value, key)) errors.push(`evaluation is missing key ${key}`);
1363
+ }
1364
+ if (value["schema_version"] !== FUSION_EVALUATION_SCHEMA_VERSION) {
1365
+ errors.push("evaluation.schema_version mismatch");
1366
+ }
1367
+ const assessments = parseAssessmentList(
1368
+ value["candidate_assessments"],
1369
+ "evaluation.candidate_assessments",
1370
+ errors
1371
+ );
1372
+ const agreements = stringList(value["agreements"], "evaluation.agreements", errors);
1373
+ const conflicts = parseConflictList(value["conflicts"], "evaluation.conflicts", errors);
1374
+ const plan = parseSynthesisPlan(value["synthesis_plan"], "evaluation.synthesis_plan", errors);
1375
+ const validationAccounting = Object.prototype.hasOwnProperty.call(value, "validation_accounting") ? parseValidationAccounting(value["validation_accounting"], "evaluation.validation_accounting", errors) : void 0;
1376
+ if (errors.length > 0 || assessments === void 0 || agreements === void 0 || conflicts === void 0 || plan === void 0) {
1377
+ return { ok: false, errors };
1378
+ }
1379
+ const parsedValue = {
1380
+ schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
1381
+ candidate_assessments: assessments,
1382
+ agreements,
1383
+ conflicts,
1384
+ synthesis_plan: plan
1385
+ };
1386
+ if (validationAccounting !== void 0) parsedValue.validation_accounting = validationAccounting;
1387
+ return { ok: true, value: parsedValue };
1388
+ }
1389
+ __name(validateFusionEvaluation, "validateFusionEvaluation");
1390
+ function boundedEvaluationErrors(errors) {
1391
+ const bounded = [];
1392
+ let total = 0;
1393
+ for (const error of errors) {
1394
+ if (bounded.length >= MAX_REPAIR_ERROR_COUNT) break;
1395
+ const perError = error.length <= MAX_REPAIR_ERROR_CHARS ? error : `${error.slice(0, MAX_REPAIR_ERROR_CHARS - 1)}\u2026`;
1396
+ const remaining = MAX_REPAIR_ERROR_TOTAL_CHARS - total;
1397
+ if (remaining <= 0) break;
1398
+ const next = perError.length <= remaining ? perError : `${perError.slice(0, Math.max(0, remaining - 1))}\u2026`;
1399
+ bounded.push(next);
1400
+ total += next.length;
1401
+ }
1402
+ if (errors.length > bounded.length)
1403
+ bounded.push(`\u2026 ${String(errors.length - bounded.length)} more validation errors omitted`);
1404
+ return bounded;
1405
+ }
1406
+ __name(boundedEvaluationErrors, "boundedEvaluationErrors");
1407
+ function formatEvaluationErrors(errors) {
1408
+ return boundedEvaluationErrors(errors).join("; ");
1409
+ }
1410
+ __name(formatEvaluationErrors, "formatEvaluationErrors");
1411
+ function parseValidationCandidateFinding(value, label, errors) {
1412
+ if (!isRecord(value)) {
1413
+ errors.push(`${label} must be an object`);
1414
+ return void 0;
1415
+ }
1416
+ closed(value, ["severity", "location", "evidence", "impact", "summary"], label, errors);
1417
+ const severity = nonBlankString(value["severity"], `${label}.severity`, errors);
1418
+ const location = nonBlankString(value["location"], `${label}.location`, errors);
1419
+ const evidence = nonBlankString(value["evidence"], `${label}.evidence`, errors);
1420
+ const impact = nonBlankString(value["impact"], `${label}.impact`, errors);
1421
+ const summary = nonBlankString(value["summary"], `${label}.summary`, errors);
1422
+ if (severity !== void 0 && !["critical", "high", "minor"].includes(severity)) errors.push(`${label}.severity invalid`);
1423
+ if (severity === void 0 || location === void 0 || evidence === void 0 || impact === void 0 || summary === void 0) return void 0;
1424
+ return { severity, location, evidence, impact, summary };
1425
+ }
1426
+ __name(parseValidationCandidateFinding, "parseValidationCandidateFinding");
1427
+ function fencedValidationCandidateJson(text) {
1428
+ const trimmed = text.trim();
1429
+ const openingPattern = /```json[ \t]*\r?\n/giu;
1430
+ const openings = [...trimmed.matchAll(openingPattern)];
1431
+ if (openings.length !== 1) return void 0;
1432
+ const opening = openings[0];
1433
+ if (opening === void 0) return void 0;
1434
+ const headerEnd = opening.index + opening[0].length;
1435
+ const closing = trimmed.indexOf("```", headerEnd);
1436
+ if (closing < 0 || trimmed.slice(closing + 3).includes("```")) return void 0;
1437
+ if (trimmed.slice(closing + 3).trim().length > 0) return void 0;
1438
+ const preamble = trimmed.slice(0, opening.index).trim();
1439
+ if (Buffer.byteLength(preamble, "utf8") > 2e3 || preamble.includes("```")) return void 0;
1440
+ const payload = trimmed.slice(headerEnd, closing).trim();
1441
+ if (payload.length === 0 || payload.includes("```")) return void 0;
1442
+ return {
1443
+ payload,
1444
+ normalization: preamble.length === 0 ? "markdown_json_fence" : "prose_then_markdown_json_fence"
1445
+ };
1446
+ }
1447
+ __name(fencedValidationCandidateJson, "fencedValidationCandidateJson");
1448
+ function recoverFencedFusionValidationCandidateReport(text, candidateId2) {
1449
+ const recovered = fencedValidationCandidateJson(text);
1450
+ if (recovered === void 0) return void 0;
1451
+ return {
1452
+ report: parseFusionValidationCandidateReport(recovered.payload, candidateId2),
1453
+ response: recovered.payload,
1454
+ normalization: recovered.normalization
1455
+ };
1456
+ }
1457
+ __name(recoverFencedFusionValidationCandidateReport, "recoverFencedFusionValidationCandidateReport");
1458
+ function parseFusionValidationCandidateReport(text, candidateId2) {
1459
+ let parsed;
1460
+ try {
1461
+ parsed = parseJsonText(text);
1462
+ } catch (error) {
1463
+ throw new FusionError(
1464
+ `validation candidate ${candidateId2} output must be structured JSON only: ${errorText(error)}`,
1465
+ { code: "evaluation_invalid", stage: "candidate" }
1466
+ );
1467
+ }
1468
+ const errors = [];
1469
+ if (!isRecord(parsed)) {
1470
+ errors.push("validation candidate report must be an object");
1471
+ } else {
1472
+ closed(parsed, ["schema_version", "findings", "verified", "limitations"], "validation candidate report", errors);
1473
+ }
1474
+ if (!isRecord(parsed)) {
1475
+ throw new FusionError(`validation candidate ${candidateId2} output failed schema validation: ${formatEvaluationErrors(errors)}`, {
1476
+ code: "evaluation_invalid",
1477
+ stage: "candidate"
1478
+ });
1479
+ }
1480
+ if (parsed["schema_version"] !== FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION) errors.push("validation candidate report.schema_version mismatch");
1481
+ const rawFindings = parsed["findings"];
1482
+ const findings = [];
1483
+ if (!Array.isArray(rawFindings)) errors.push("validation candidate report.findings must be an array");
1484
+ else {
1485
+ for (const [index, item] of rawFindings.entries()) {
1486
+ const finding = parseValidationCandidateFinding(item, `validation candidate report.findings[${String(index)}]`, errors);
1487
+ if (finding !== void 0) findings.push(finding);
1488
+ }
1489
+ }
1490
+ const verified = stringList(parsed["verified"], "validation candidate report.verified", errors);
1491
+ const limitations = stringList(parsed["limitations"], "validation candidate report.limitations", errors);
1492
+ if (errors.length > 0) {
1493
+ throw new FusionError(`validation candidate ${candidateId2} output failed schema validation: ${formatEvaluationErrors(errors)}`, {
1494
+ code: "evaluation_invalid",
1495
+ stage: "candidate"
1496
+ });
1497
+ }
1498
+ return {
1499
+ findings: findings.map((finding, index) => ({
1500
+ id: stableFusionFindingId(candidateId2, index + 1),
1501
+ candidate_id: candidateId2,
1502
+ ...finding
1503
+ })),
1504
+ verified: verified ?? [],
1505
+ limitations: limitations ?? []
1506
+ };
1507
+ }
1508
+ __name(parseFusionValidationCandidateReport, "parseFusionValidationCandidateReport");
1509
+ function stableFusionFindingId(candidateId2, ordinal) {
1510
+ if (!Number.isSafeInteger(ordinal) || ordinal <= 0) {
1511
+ throw new FusionError("validation finding ordinal must be a positive integer", {
1512
+ code: "evaluation_invalid",
1513
+ stage: "evaluation"
1514
+ });
1515
+ }
1516
+ return `${candidateId2}-F${String(ordinal).padStart(3, "0")}`;
1517
+ }
1518
+ __name(stableFusionFindingId, "stableFusionFindingId");
1519
+ function validateFusionFindingAccounting(accounting) {
1520
+ const errors = [];
1521
+ const sourceIds = /* @__PURE__ */ new Set();
1522
+ const perCandidateOrdinal = { A: 0, B: 0, C: 0 };
1523
+ for (const [index, finding] of accounting.findings.entries()) {
1524
+ const label = `finding[${String(index)}]`;
1525
+ perCandidateOrdinal[finding.candidate_id] += 1;
1526
+ if (finding.id !== stableFusionFindingId(finding.candidate_id, perCandidateOrdinal[finding.candidate_id])) {
1527
+ errors.push(`${label}.id must be the stable host id for its candidate and ordinal`);
1528
+ }
1529
+ if (!["critical", "high", "minor"].includes(finding.severity)) errors.push(`${label}.severity invalid`);
1530
+ for (const key of ["location", "evidence", "impact", "summary"]) {
1531
+ if (finding[key].trim().length === 0) errors.push(`${label}.${key} must be non-blank`);
1532
+ }
1533
+ if (sourceIds.has(finding.id)) errors.push(`${label}.id duplicate`);
1534
+ sourceIds.add(finding.id);
1535
+ }
1536
+ const accounted = /* @__PURE__ */ new Set();
1537
+ for (const [index, decision] of accounting.decisions.entries()) {
1538
+ const label = `decision[${String(index)}]`;
1539
+ if (!sourceIds.has(decision.source_id)) errors.push(`${label}.source_id does not name a candidate finding`);
1540
+ if (accounted.has(decision.source_id)) errors.push(`${label}.source_id accounted more than once`);
1541
+ accounted.add(decision.source_id);
1542
+ if (decision.disposition !== "include" && decision.disposition !== "exclude") errors.push(`${label}.disposition invalid`);
1543
+ if (decision.rationale.trim().length === 0) errors.push(`${label}.rationale must be non-blank`);
1544
+ if (decision.disposition === "include" && (decision.group_id === void 0 || decision.group_id.trim().length === 0)) {
1545
+ errors.push(`${label}.group_id required for included findings`);
1546
+ }
1547
+ if (decision.disposition === "exclude" && decision.group_id !== void 0) {
1548
+ errors.push(`${label}.group_id must be omitted for excluded findings`);
1549
+ }
1550
+ }
1551
+ for (const id of sourceIds) {
1552
+ if (!accounted.has(id)) errors.push(`source finding ${id} was not accounted exactly once`);
1553
+ }
1554
+ const groupsById = /* @__PURE__ */ new Map();
1555
+ for (const [index, group] of accounting.groups.entries()) {
1556
+ const label = `group[${String(index)}]`;
1557
+ if (groupsById.has(group.group_id)) errors.push(`${label}.group_id duplicate`);
1558
+ groupsById.set(group.group_id, group);
1559
+ if (!["critical", "high", "minor"].includes(group.severity)) errors.push(`${label}.severity invalid`);
1560
+ for (const key of ["location", "evidence", "impact", "summary", "rationale"]) {
1561
+ if (group[key].trim().length === 0) errors.push(`${label}.${key} must be non-blank`);
1562
+ }
1563
+ if (group.source_ids.length === 0) errors.push(`${label}.source_ids must not be empty`);
1564
+ const groupSourceIds = /* @__PURE__ */ new Set();
1565
+ for (const sourceId of group.source_ids) {
1566
+ if (!sourceIds.has(sourceId)) errors.push(`${label}.source_ids contains unknown finding ${sourceId}`);
1567
+ if (groupSourceIds.has(sourceId)) errors.push(`${label}.source_ids contains duplicate ${sourceId}`);
1568
+ groupSourceIds.add(sourceId);
1569
+ }
1570
+ }
1571
+ const includedByGroup = /* @__PURE__ */ new Map();
1572
+ for (const decision of accounting.decisions) {
1573
+ if (decision.disposition !== "include" || decision.group_id === void 0) continue;
1574
+ const group = groupsById.get(decision.group_id);
1575
+ if (group === void 0) {
1576
+ errors.push(`included source finding ${decision.source_id} references unknown group ${decision.group_id}`);
1577
+ continue;
1578
+ }
1579
+ const members = includedByGroup.get(decision.group_id) ?? /* @__PURE__ */ new Set();
1580
+ members.add(decision.source_id);
1581
+ includedByGroup.set(decision.group_id, members);
1582
+ }
1583
+ for (const group of accounting.groups) {
1584
+ const expected = [...group.source_ids].sort();
1585
+ const actual = [...includedByGroup.get(group.group_id) ?? /* @__PURE__ */ new Set()].sort();
1586
+ if (expected.length !== actual.length || expected.some((id, index) => id !== actual[index])) {
1587
+ errors.push(`group ${group.group_id} source_ids must exactly match included decisions assigned to that group`);
1588
+ }
1589
+ }
1590
+ return errors;
1591
+ }
1592
+ __name(validateFusionFindingAccounting, "validateFusionFindingAccounting");
1593
+ function sanitizeValidationRationale(value) {
1594
+ return value.replace(/\b[ABC]-F\d{3}\b/gu, "source finding").replace(/\bcandidate [ABC]\b/giu, "one reviewer").replace(/\b[ABC]:\s*/gu, "");
1595
+ }
1596
+ __name(sanitizeValidationRationale, "sanitizeValidationRationale");
1597
+ function renderValidatedFusionValidationReport(accounting, coverage) {
1598
+ const errors = validateFusionFindingAccounting(accounting);
1599
+ if (errors.length > 0) {
1600
+ throw new FusionError(`validation accounting invalid before render: ${formatEvaluationErrors(errors)}`, {
1601
+ code: "evaluation_invalid",
1602
+ stage: "merge"
1603
+ });
1604
+ }
1605
+ const severityOrder = { critical: 0, high: 1, minor: 2 };
1606
+ const renderedFindings = [...accounting.groups].sort(
1607
+ (left, right) => severityOrder[left.severity] - severityOrder[right.severity] || left.location.localeCompare(right.location) || left.group_id.localeCompare(right.group_id)
1608
+ );
1609
+ const lines = ["# Validation report", ""];
1610
+ if (renderedFindings.length === 0) {
1611
+ lines.push("No included findings were identified by the validated accounting.", "");
1612
+ } else {
1613
+ lines.push("## Findings", "");
1614
+ for (const finding of renderedFindings) {
1615
+ lines.push(`### ${finding.severity}: ${finding.summary}`, "");
1616
+ lines.push(`- Location: ${finding.location}`);
1617
+ lines.push(`- Evidence: ${finding.evidence}`);
1618
+ lines.push(`- Impact: ${finding.impact}`);
1619
+ lines.push(`- Inclusion rationale: ${sanitizeValidationRationale(finding.rationale)}`, "");
1620
+ }
1621
+ }
1622
+ const exclusions = accounting.decisions.filter((decision) => decision.disposition === "exclude").sort((left, right) => left.source_id.localeCompare(right.source_id));
1623
+ if (exclusions.length > 0) {
1624
+ lines.push("## Excluded source findings", "");
1625
+ for (const decision of exclusions) lines.push(`- ${sanitizeValidationRationale(decision.rationale)}`);
1626
+ lines.push("");
1627
+ }
1628
+ if (coverage !== void 0) {
1629
+ lines.push("## Verified", "");
1630
+ if (coverage.verified.length === 0) lines.push("- No verification statements were provided.");
1631
+ else for (const item of coverage.verified) lines.push(`- ${item}`);
1632
+ lines.push("", "## Limitations", "");
1633
+ if (coverage.limitations.length === 0) lines.push("- No limitations were provided.");
1634
+ else for (const item of coverage.limitations) lines.push(`- ${item}`);
1635
+ lines.push("");
1636
+ }
1637
+ return lines.join("\n").trimEnd();
1638
+ }
1639
+ __name(renderValidatedFusionValidationReport, "renderValidatedFusionValidationReport");
1640
+
1641
+ // node_modules/pi-background-tasks/src/core/fusion/pi-child.ts?ext=pi-background-tasks
1642
+ import { spawn as nodeSpawn } from "node:child_process";
1643
+ import { createHash as createHash3 } from "node:crypto";
1644
+ import { constants, existsSync } from "node:fs";
1645
+ import { open } from "node:fs/promises";
1646
+ import { dirname, resolve } from "node:path";
1647
+ import { fileURLToPath } from "node:url";
1648
+
1649
+ // node_modules/pi-background-tasks/src/core/fusion/child-protocol.ts?ext=pi-background-tasks
1650
+ import { createHash as createHash2 } from "node:crypto";
1651
+ var FUSION_CHILD_RESULT_SCHEMA_VERSION = "pi-background-tasks.fusion-child-result.v4";
1652
+ var FUSION_CHILD_RESULT_PREFIX = "PI_FUSION_CHILD_RESULT ";
1653
+ var FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION = "pi-background-tasks.fusion-child-settlement.v3";
1654
+ var FUSION_CHILD_SETTLEMENT_PREFIX = "PI_FUSION_CHILD_SETTLEMENT ";
1655
+ var FUSION_TOOL_CALL_LOG_PATH_ENV = "PI_FUSION_TOOL_CALL_LOG_PATH";
1656
+ var FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV = "PI_FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH";
1657
+ var FUSION_RESEARCH_ENABLED_ENV = "PI_FUSION_RESEARCH_ENABLED";
1658
+ var FUSION_SOURCE_POLICY_PATH_ENV = "PI_FUSION_SOURCE_POLICY_PATH";
1659
+ var FUSION_SOURCE_POLICY_SHA256_ENV = "PI_FUSION_SOURCE_POLICY_SHA256";
1660
+ var FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION = "pi-background-tasks.fusion-tool-call-seal.v1";
1661
+ var FUSION_TOOL_CALL_SEAL_SUFFIX = ".seal.json";
1662
+ var FUSION_RUNTIME_GUARD_SCHEMA_VERSION = "pi-background-tasks.fusion-runtime-guard.v2";
1663
+ var FUSION_RUNTIME_GUARD_PREFIX = "PI_FUSION_RUNTIME_GUARD ";
1664
+ var FUSION_CHILD_MAX_PROVIDER_REQUESTS = 550;
1665
+ var FUSION_CHILD_MAX_TOOL_CALLS = 600;
1666
+ var FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES = 32 * 1024 * 1024;
1667
+ function protocolSha256(value) {
1668
+ return createHash2("sha256").update(value).digest("hex");
1669
+ }
1670
+ __name(protocolSha256, "protocolSha256");
1671
+ function serializeFusionChildResultRecords(records) {
1672
+ return Buffer.from(
1673
+ records.length === 0 ? "" : `${records.map((record) => JSON.stringify(record)).join("\n")}
1674
+ `,
1675
+ "utf8"
1676
+ );
1677
+ }
1678
+ __name(serializeFusionChildResultRecords, "serializeFusionChildResultRecords");
1679
+ function hasZeroUsage(record) {
1680
+ const usage = record.usage;
1681
+ return usage.input === 0 && usage.output === 0 && usage.cacheRead === 0 && usage.cacheWrite === 0 && usage.totalTokens === 0 && usage.cost.input === 0 && usage.cost.output === 0 && usage.cost.cacheRead === 0 && usage.cost.cacheWrite === 0 && usage.cost.total === 0;
1682
+ }
1683
+ __name(hasZeroUsage, "hasZeroUsage");
1684
+ function isRecoverableFusionChildErrorRecord(record) {
1685
+ return record.stop_reason === "error" && record.text_blocks.length === 0 && record.text_sha256 === protocolSha256(Buffer.alloc(0)) && hasZeroUsage(record) && record.output_contract.recovery_role === "none";
1686
+ }
1687
+ __name(isRecoverableFusionChildErrorRecord, "isRecoverableFusionChildErrorRecord");
1688
+ function isOversizedOriginal(record) {
1689
+ const output = record.output_contract;
1690
+ return output.recovery_role === "oversized_original" && output.candidate_limit_bytes === FUSION_CANDIDATE_MAX_OUTPUT_BYTES && output.json_rendered_bytes > FUSION_CANDIDATE_MAX_OUTPUT_BYTES && record.stop_reason === "stop";
1691
+ }
1692
+ __name(isOversizedOriginal, "isOversizedOriginal");
1693
+ function outputRecoveryProtocolInvalid(records) {
1694
+ const originals = records.flatMap(
1695
+ (record, ordinal) => record.output_contract.recovery_role === "oversized_original" ? [ordinal] : []
1696
+ );
1697
+ const replacements = records.flatMap(
1698
+ (record, ordinal) => record.output_contract.recovery_role === "replacement" ? [ordinal] : []
1699
+ );
1700
+ for (const record of records) {
1701
+ const output = record.output_contract;
1702
+ if (output.candidate_limit_bytes !== null && output.candidate_limit_bytes !== FUSION_CANDIDATE_MAX_OUTPUT_BYTES) {
1703
+ return true;
1704
+ }
1705
+ if (output.recovery_role !== "none" && output.candidate_limit_bytes === null) return true;
1706
+ if (output.recovery_role === "oversized_original" && !isOversizedOriginal(record)) return true;
1707
+ }
1708
+ if (originals.length === 0 && replacements.length === 0) return false;
1709
+ if (originals.length !== 1 || replacements.length !== 1) return true;
1710
+ const original = originals[0];
1711
+ const replacement = replacements[0];
1712
+ return original === void 0 || replacement === void 0 || original !== records.length - 2 || replacement !== records.length - 1;
1713
+ }
1714
+ __name(outputRecoveryProtocolInvalid, "outputRecoveryProtocolInvalid");
1715
+ function finalCandidateOutputExceedsContract(records) {
1716
+ const final = records.at(-1);
1717
+ if (final === void 0) return false;
1718
+ const output = final.output_contract;
1719
+ return output.candidate_limit_bytes === FUSION_CANDIDATE_MAX_OUTPUT_BYTES && output.json_rendered_bytes > FUSION_CANDIDATE_MAX_OUTPUT_BYTES;
1720
+ }
1721
+ __name(finalCandidateOutputExceedsContract, "finalCandidateOutputExceedsContract");
1722
+ function buildFusionChildSettlement(records, runtimeGuardFailed = false, cacheObservationFailed = false, outputRecoveryFailed = false) {
1723
+ const finalRecordIndex = records.length === 0 ? null : records.length - 1;
1724
+ const final = records.at(-1);
1725
+ const recoveredErrorOrdinals = records.flatMap(
1726
+ (record, ordinal) => ordinal < records.length - 1 && isRecoverableFusionChildErrorRecord(record) ? [ordinal] : []
1727
+ );
1728
+ const recoveredOutputCapOrdinals = records.flatMap(
1729
+ (record, ordinal) => ordinal < records.length - 1 && isOversizedOriginal(record) ? [ordinal] : []
1730
+ );
1731
+ const invalidRecovery = outputRecoveryProtocolInvalid(records);
1732
+ const invalidNonFinal = records.some(
1733
+ (record, ordinal) => ordinal < records.length - 1 && record.stop_reason !== "toolUse" && !isRecoverableFusionChildErrorRecord(record) && !isOversizedOriginal(record)
1734
+ );
1735
+ let failureReason = null;
1736
+ if (runtimeGuardFailed) failureReason = "runtime_guard";
1737
+ else if (cacheObservationFailed) failureReason = "cache_observation";
1738
+ else if (final === void 0) failureReason = "no_records";
1739
+ else if (final.stop_reason !== "stop") failureReason = "final_not_stop";
1740
+ else if (outputRecoveryFailed || invalidRecovery || finalCandidateOutputExceedsContract(records)) {
1741
+ failureReason = "output_recovery";
1742
+ } else if (invalidNonFinal) failureReason = "invalid_non_final";
1743
+ return {
1744
+ schema_version: FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
1745
+ status: failureReason === null ? "complete" : "failed",
1746
+ record_count: records.length,
1747
+ records_sha256: protocolSha256(serializeFusionChildResultRecords(records)),
1748
+ final_record_index: finalRecordIndex,
1749
+ final_text_sha256: final?.text_sha256 ?? null,
1750
+ recovered_error_ordinals: recoveredErrorOrdinals,
1751
+ recovered_output_cap_ordinals: recoveredOutputCapOrdinals,
1752
+ failure_reason: failureReason
1753
+ };
1754
+ }
1755
+ __name(buildFusionChildSettlement, "buildFusionChildSettlement");
1756
+
1757
+ // node_modules/pi-background-tasks/src/core/fusion/claude-cache.ts?ext=pi-background-tasks
1758
+ var FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION = "pi-background-tasks.fusion-claude-cache-observation.v1";
1759
+ var FUSION_CLAUDE_CACHE_RETENTION_ENV = "PI_CACHE_RETENTION";
1760
+ var FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT = 4;
1761
+
1762
+ // node_modules/pi-background-tasks/src/core/fusion/pi-child.ts?ext=pi-background-tasks
1763
+ var __pvMetaUrl = __pvUrl("../node_modules/pi-background-tasks/src/core/fusion/pi-child.ts");
1764
+ var FUSION_CHILD_STDOUT_LIMIT_BYTES = 32 * 1024 * 1024;
1765
+ var FUSION_CHILD_STDERR_LIMIT_BYTES = 16 * 1024 * 1024;
1766
+ var FUSION_CHILD_TIMEOUT_MS = 50 * 60 * 1e3;
1767
+ var FUSION_CHILD_IDLE_TIMEOUT_MS = 35 * 60 * 1e3;
1768
+ var FUSION_CHILD_KILL_GRACE_MS = 3e3;
1769
+ var FUSION_CHILD_SIGKILL_WAIT_MS = 5e3;
1770
+ var FUSION_PI_CHILD_O_NOFOLLOW = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
1771
+ var FUSION_CHILD_REMOVED_ENV_KEYS = [
1772
+ "PI_SESSION_ID",
1773
+ "PI_SESSION_FILE",
1774
+ "PI_PROVIDER",
1775
+ "PI_MODEL",
1776
+ "PI_REASONING_LEVEL",
1777
+ "OPENROUTER_API_KEY",
1778
+ "OPENROUTER_BASE_URL",
1779
+ "OPENAI_API_KEY",
1780
+ "OPENAI_BASE_URL",
1781
+ "ANTHROPIC_API_KEY",
1782
+ "ANTHROPIC_AUTH_TOKEN",
1783
+ "ANTHROPIC_BASE_URL",
1784
+ "AZURE_OPENAI_API_KEY",
1785
+ "AZURE_OPENAI_BASE_URL",
1786
+ "AZURE_OPENAI_ENDPOINT",
1787
+ "AZURE_OPENAI_RESOURCE_NAME",
1788
+ "AZURE_OPENAI_API_VERSION",
1789
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP",
1790
+ "AZURE_OPENAI_AD_TOKEN",
1791
+ "PI_API_KEY",
1792
+ "PI_API_BASE_URL",
1793
+ "PI_AUTH_FILE",
1794
+ FUSION_TOOL_CALL_LOG_PATH_ENV,
1795
+ FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV,
1796
+ FUSION_RESEARCH_ENABLED_ENV,
1797
+ FUSION_SOURCE_POLICY_PATH_ENV,
1798
+ FUSION_SOURCE_POLICY_SHA256_ENV
1799
+ ];
1800
+ var FusionChildRunError = class extends FusionError {
1801
+ static {
1802
+ __name(this, "FusionChildRunError");
1803
+ }
1804
+ events;
1805
+ response;
1806
+ stderr;
1807
+ exitCode;
1808
+ signalName;
1809
+ usage;
1810
+ provider;
1811
+ modelName;
1812
+ qualifiedId;
1813
+ outputRecovery;
1814
+ constructor(error, events, response, stderr, close, observed, outputRecovery) {
1815
+ const details = {
1816
+ code: error.code,
1817
+ transient: error.transient,
1818
+ childCreated: error.childCreated
1819
+ };
1820
+ if (error.stage !== void 0) details.stage = error.stage;
1821
+ if (error.slot !== void 0) details.slot = error.slot;
1822
+ if (error.attempt !== void 0) details.attempt = error.attempt;
1823
+ if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
1824
+ super(error.message, details);
1825
+ this.name = "FusionChildRunError";
1826
+ this.events = events;
1827
+ this.response = response;
1828
+ this.stderr = stderr;
1829
+ this.exitCode = close.code;
1830
+ this.signalName = close.signal;
1831
+ this.usage = cloneFusionUsage(observed.usage);
1832
+ this.provider = observed.provider;
1833
+ this.modelName = observed.model;
1834
+ this.qualifiedId = observed.qualifiedId;
1835
+ this.outputRecovery = outputRecovery;
1836
+ }
1837
+ };
1838
+ function fusionPiChildEnv(env = process.env, provider) {
1839
+ const out = { ...env };
1840
+ const removed = new Set(FUSION_CHILD_REMOVED_ENV_KEYS);
1841
+ for (const inheritedKey of Object.keys(out)) {
1842
+ if (removed.has(inheritedKey.toUpperCase())) Reflect.deleteProperty(out, inheritedKey);
1843
+ }
1844
+ out["PI_SKIP_VERSION_CHECK"] = "1";
1845
+ if (provider === "anthropic" && out[FUSION_CLAUDE_CACHE_RETENTION_ENV] === void 0) {
1846
+ out[FUSION_CLAUDE_CACHE_RETENTION_ENV] = "long";
1847
+ }
1848
+ return out;
1849
+ }
1850
+ __name(fusionPiChildEnv, "fusionPiChildEnv");
1851
+ function resolveFusionChildExtensionPath(moduleUrl = __pvMetaUrl, pathExists = existsSync) {
1852
+ const modulePath = fileURLToPath(moduleUrl);
1853
+ const extension = modulePath.endsWith(".ts") ? "fusion-child.ts" : "fusion-child.js";
1854
+ const candidate = resolve(dirname(modulePath), "../../../extensions", extension);
1855
+ if (!pathExists(candidate)) {
1856
+ throw new Error(`Fusion child metadata extension is missing: ${candidate}`);
1857
+ }
1858
+ return candidate;
1859
+ }
1860
+ __name(resolveFusionChildExtensionPath, "resolveFusionChildExtensionPath");
1861
+ var FUSION_SANITIZED_PROVIDER = "anthropic";
1862
+ function assertFusionToolPolicyDisjoint(allowlist = FUSION_INSPECT_TOOLS, denylist = FUSION_FORBIDDEN_TOOLS) {
1863
+ for (const forbidden of denylist) {
1864
+ if (allowlist.includes(forbidden)) {
1865
+ throw new FusionError(
1866
+ `fusion inspect capability would enable the forbidden tool ${forbidden}`,
1867
+ { code: "orchestration_failed", childCreated: false }
1868
+ );
1869
+ }
1870
+ }
1871
+ }
1872
+ __name(assertFusionToolPolicyDisjoint, "assertFusionToolPolicyDisjoint");
1873
+ function researchToolAllowlist() {
1874
+ return FUSION_RESEARCH_TOOLS;
1875
+ }
1876
+ __name(researchToolAllowlist, "researchToolAllowlist");
1877
+ function fusionToolArgv(capability) {
1878
+ if (capability === "reason") return ["--no-tools"];
1879
+ if (capability === "inspect") {
1880
+ assertFusionToolPolicyDisjoint(FUSION_INSPECT_TOOLS);
1881
+ return [
1882
+ "--no-builtin-tools",
1883
+ "--tools",
1884
+ FUSION_INSPECT_TOOLS.join(","),
1885
+ "--exclude-tools",
1886
+ FUSION_FORBIDDEN_TOOLS.join(",")
1887
+ ];
1888
+ }
1889
+ if (capability === "research") {
1890
+ const allowlist = researchToolAllowlist();
1891
+ assertFusionToolPolicyDisjoint(allowlist);
1892
+ return [
1893
+ "--no-builtin-tools",
1894
+ "--tools",
1895
+ allowlist.join(","),
1896
+ "--exclude-tools",
1897
+ FUSION_FORBIDDEN_TOOLS.join(",")
1898
+ ];
1899
+ }
1900
+ throw new FusionError(`fusion capability ${String(capability)} is not supported`, {
1901
+ code: "orchestration_failed",
1902
+ childCreated: false
1903
+ });
1904
+ }
1905
+ __name(fusionToolArgv, "fusionToolArgv");
1906
+ function fusionChildExtensionPaths(model, childExtensionPath, resolveAttribution = resolveAnthropicAttributionExtensionPath) {
1907
+ if (model.provider !== FUSION_SANITIZED_PROVIDER) return [childExtensionPath];
1908
+ return [resolveAttribution(), childExtensionPath];
1909
+ }
1910
+ __name(fusionChildExtensionPaths, "fusionChildExtensionPaths");
1911
+ function buildFusionPiChildArgv(model, systemPrompt, childExtensionPath = resolveFusionChildExtensionPath(), capability = FUSION_NO_TOOLS_CAPABILITY, resolveAttribution = resolveAnthropicAttributionExtensionPath) {
1912
+ const extensionArgs = fusionChildExtensionPaths(
1913
+ model,
1914
+ childExtensionPath,
1915
+ resolveAttribution
1916
+ ).flatMap((path) => ["--extension", path]);
1917
+ return [
1918
+ "--mode",
1919
+ "text",
1920
+ "--no-session",
1921
+ ...fusionToolArgv(capability),
1922
+ "--no-extensions",
1923
+ "--no-skills",
1924
+ "--no-prompt-templates",
1925
+ "--no-themes",
1926
+ "--no-context-files",
1927
+ ...extensionArgs,
1928
+ "--provider",
1929
+ model.provider,
1930
+ "--model",
1931
+ model.model,
1932
+ "--thinking",
1933
+ model.thinkingLevel,
1934
+ "--system-prompt",
1935
+ systemPrompt
1936
+ ];
1937
+ }
1938
+ __name(buildFusionPiChildArgv, "buildFusionPiChildArgv");
1939
+ var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
1940
+ var FUSION_CHILD_RESULT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_RESULT_PREFIX, "utf8");
1941
+ var FUSION_CHILD_SETTLEMENT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_SETTLEMENT_PREFIX, "utf8");
1942
+ var FUSION_RUNTIME_GUARD_PREFIX_BYTES = Buffer.from(FUSION_RUNTIME_GUARD_PREFIX, "utf8");
1943
+ var PI_EXTENSION_ERROR_PREFIX_BYTES = Buffer.from("Extension error (", "utf8");
1944
+ function assertClosedRecord(value, keys, label) {
1945
+ if (!isJsonObject(value) || Array.isArray(value)) throw new Error(`${label} must be an object`);
1946
+ const actual = Object.keys(value).sort();
1947
+ const expected = [...keys].sort();
1948
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
1949
+ throw new Error(`${label} keys mismatch: expected ${expected.join(", ")}`);
1950
+ }
1951
+ return value;
1952
+ }
1953
+ __name(assertClosedRecord, "assertClosedRecord");
1954
+ function assertClosedRecordWithOptional(value, requiredKeys, optionalKeys, label) {
1955
+ if (!isJsonObject(value) || Array.isArray(value)) throw new Error(`${label} must be an object`);
1956
+ const allowed = /* @__PURE__ */ new Set([...requiredKeys, ...optionalKeys]);
1957
+ const actual = Object.keys(value).sort();
1958
+ const missing = requiredKeys.filter((key) => !Object.hasOwn(value, key));
1959
+ const unknownKeys = actual.filter((key) => !allowed.has(key));
1960
+ if (missing.length > 0 || unknownKeys.length > 0) {
1961
+ throw new Error(
1962
+ `${label} keys mismatch: required ${[...requiredKeys].sort().join(", ")}; optional ${[
1963
+ ...optionalKeys
1964
+ ].sort().join(", ")}`
1965
+ );
1966
+ }
1967
+ return value;
1968
+ }
1969
+ __name(assertClosedRecordWithOptional, "assertClosedRecordWithOptional");
1970
+ function requireNonBlankString(record, key, label) {
1971
+ const value = record[key];
1972
+ if (typeof value !== "string" || value.trim().length === 0)
1973
+ throw new Error(`${label}.${key} must be a non-blank string`);
1974
+ return value;
1975
+ }
1976
+ __name(requireNonBlankString, "requireNonBlankString");
1977
+ function requireSha256(record, key, label) {
1978
+ const value = record[key];
1979
+ if (typeof value !== "string" || !SHA256_HEX_PATTERN.test(value))
1980
+ throw new Error(`${label}.${key} must be a lowercase SHA-256 hex digest`);
1981
+ return value;
1982
+ }
1983
+ __name(requireSha256, "requireSha256");
1984
+ function requireUsageInteger(record, key, label) {
1985
+ const value = record[key];
1986
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
1987
+ throw new Error(`${label}.${key} must be a non-negative safe integer`);
1988
+ return value;
1989
+ }
1990
+ __name(requireUsageInteger, "requireUsageInteger");
1991
+ function requirePositiveSafeInteger(record, key, label) {
1992
+ const value = requireUsageInteger(record, key, label);
1993
+ if (value === 0) throw new Error(`${label}.${key} must be a positive safe integer`);
1994
+ return value;
1995
+ }
1996
+ __name(requirePositiveSafeInteger, "requirePositiveSafeInteger");
1997
+ function requireCostNumber(record, key, label) {
1998
+ const value = record[key];
1999
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
2000
+ throw new Error(`${label}.${key} must be a non-negative finite number`);
2001
+ return value;
2002
+ }
2003
+ __name(requireCostNumber, "requireCostNumber");
2004
+ function parseCompactUsage(value) {
2005
+ const record = assertClosedRecordWithOptional(
2006
+ value,
2007
+ ["input", "output", "cacheRead", "cacheWrite", "totalTokens", "cost"],
2008
+ ["cacheWrite1h", "reasoning"],
2009
+ "fusion child usage"
2010
+ );
2011
+ const cost = assertClosedRecord(
2012
+ record["cost"],
2013
+ ["input", "output", "cacheRead", "cacheWrite", "total"],
2014
+ "fusion child usage.cost"
2015
+ );
2016
+ const output = requireUsageInteger(record, "output", "fusion child usage");
2017
+ const cacheWrite = requireUsageInteger(record, "cacheWrite", "fusion child usage");
2018
+ const cacheWrite1h = record["cacheWrite1h"] === void 0 ? void 0 : requireUsageInteger(record, "cacheWrite1h", "fusion child usage");
2019
+ const reasoning = record["reasoning"] === void 0 ? void 0 : requireUsageInteger(record, "reasoning", "fusion child usage");
2020
+ if (cacheWrite1h !== void 0 && cacheWrite1h > cacheWrite) {
2021
+ throw new Error("fusion child usage.cacheWrite1h must not exceed cacheWrite");
2022
+ }
2023
+ if (reasoning !== void 0 && reasoning > output) {
2024
+ throw new Error("fusion child usage.reasoning must not exceed output");
2025
+ }
2026
+ return {
2027
+ input: requireUsageInteger(record, "input", "fusion child usage"),
2028
+ output,
2029
+ cacheRead: requireUsageInteger(record, "cacheRead", "fusion child usage"),
2030
+ cacheWrite,
2031
+ ...cacheWrite1h === void 0 ? {} : { cacheWrite1h },
2032
+ ...reasoning === void 0 ? {} : { reasoning },
2033
+ totalTokens: requireUsageInteger(record, "totalTokens", "fusion child usage"),
2034
+ cost: {
2035
+ input: requireCostNumber(cost, "input", "fusion child usage.cost"),
2036
+ output: requireCostNumber(cost, "output", "fusion child usage.cost"),
2037
+ cacheRead: requireCostNumber(cost, "cacheRead", "fusion child usage.cost"),
2038
+ cacheWrite: requireCostNumber(cost, "cacheWrite", "fusion child usage.cost"),
2039
+ total: requireCostNumber(cost, "total", "fusion child usage.cost")
2040
+ }
2041
+ };
2042
+ }
2043
+ __name(parseCompactUsage, "parseCompactUsage");
2044
+ var FUSION_CLAUDE_CACHE_RETENTIONS = /* @__PURE__ */ new Set([
2045
+ "none",
2046
+ "short",
2047
+ "long"
2048
+ ]);
2049
+ function parseFusionClaudeCacheObservation(value, provider) {
2050
+ const record = assertClosedRecord(
2051
+ value,
2052
+ [
2053
+ "schema_version",
2054
+ "applicability",
2055
+ "source",
2056
+ "requested_retention",
2057
+ "effective_retention",
2058
+ "breakpoint_count",
2059
+ "request_ordinal"
2060
+ ],
2061
+ "fusion child cache observation"
2062
+ );
2063
+ if (record["schema_version"] !== FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION) {
2064
+ throw new Error("fusion child cache observation schema_version mismatch");
2065
+ }
2066
+ const applicability = record["applicability"];
2067
+ const source = record["source"];
2068
+ const requested = record["requested_retention"];
2069
+ const effective = record["effective_retention"];
2070
+ const breakpointCount = requireUsageInteger(
2071
+ record,
2072
+ "breakpoint_count",
2073
+ "fusion child cache observation"
2074
+ );
2075
+ const requestOrdinal = requirePositiveSafeInteger(
2076
+ record,
2077
+ "request_ordinal",
2078
+ "fusion child cache observation"
2079
+ );
2080
+ if (requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
2081
+ throw new Error("fusion child cache observation exceeds the provider request limit");
2082
+ }
2083
+ if (breakpointCount > FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT) {
2084
+ throw new Error("fusion child cache observation exceeds the Anthropic breakpoint limit");
2085
+ }
2086
+ if (provider !== "anthropic") {
2087
+ if (applicability !== "not_applicable" || source !== "not_applicable" || requested !== null || effective !== null || breakpointCount !== 0) {
2088
+ throw new Error("non-Anthropic fusion child has contradictory cache observation");
2089
+ }
2090
+ return {
2091
+ schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
2092
+ applicability: "not_applicable",
2093
+ source: "not_applicable",
2094
+ requested_retention: null,
2095
+ effective_retention: null,
2096
+ breakpoint_count: 0,
2097
+ request_ordinal: requestOrdinal
2098
+ };
2099
+ }
2100
+ if (applicability !== "anthropic") {
2101
+ throw new Error("Anthropic fusion child cache observation is not applicable");
2102
+ }
2103
+ if (source !== "default" && source !== FUSION_CLAUDE_CACHE_RETENTION_ENV) {
2104
+ throw new Error("Anthropic fusion child cache observation source is invalid");
2105
+ }
2106
+ if (typeof requested !== "string" || !FUSION_CLAUDE_CACHE_RETENTIONS.has(requested) || typeof effective !== "string" || !FUSION_CLAUDE_CACHE_RETENTIONS.has(effective)) {
2107
+ throw new Error("Anthropic fusion child cache retention is invalid");
2108
+ }
2109
+ const requestedRetention = requested;
2110
+ const effectiveRetention = effective;
2111
+ if (source === "default" && requestedRetention !== "long") {
2112
+ throw new Error("default Anthropic cache policy did not request long retention");
2113
+ }
2114
+ if (effectiveRetention === "none" !== (breakpointCount === 0)) {
2115
+ throw new Error("Anthropic fusion child cache retention/breakpoint evidence mismatch");
2116
+ }
2117
+ if (requestedRetention === "none" && effectiveRetention !== "none") {
2118
+ throw new Error("disabled Anthropic cache policy reported active breakpoints");
2119
+ }
2120
+ if (effectiveRetention === "long" && requestedRetention !== "long") {
2121
+ throw new Error("Anthropic long cache retention was not requested");
2122
+ }
2123
+ return {
2124
+ schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
2125
+ applicability: "anthropic",
2126
+ source,
2127
+ requested_retention: requestedRetention,
2128
+ effective_retention: effectiveRetention,
2129
+ breakpoint_count: breakpointCount,
2130
+ request_ordinal: requestOrdinal
2131
+ };
2132
+ }
2133
+ __name(parseFusionClaudeCacheObservation, "parseFusionClaudeCacheObservation");
2134
+ var FUSION_OUTPUT_RECOVERY_ROLES = /* @__PURE__ */ new Set([
2135
+ "none",
2136
+ "oversized_original",
2137
+ "replacement"
2138
+ ]);
2139
+ function parseChildOutputContract(value) {
2140
+ const record = assertClosedRecord(
2141
+ value,
2142
+ ["json_rendered_bytes", "candidate_limit_bytes", "recovery_role"],
2143
+ "fusion child result.output_contract"
2144
+ );
2145
+ const candidateLimitValue = record["candidate_limit_bytes"];
2146
+ const candidateLimit = candidateLimitValue === null ? null : requirePositiveSafeInteger(
2147
+ record,
2148
+ "candidate_limit_bytes",
2149
+ "fusion child result.output_contract"
2150
+ );
2151
+ if (candidateLimit !== null && candidateLimit !== FUSION_CANDIDATE_MAX_OUTPUT_BYTES) {
2152
+ throw new Error("fusion child result candidate output limit mismatches the shared contract");
2153
+ }
2154
+ const recoveryRole = record["recovery_role"];
2155
+ if (typeof recoveryRole !== "string" || !FUSION_OUTPUT_RECOVERY_ROLES.has(recoveryRole)) {
2156
+ throw new Error("fusion child result.output_contract.recovery_role is invalid");
2157
+ }
2158
+ if (recoveryRole !== "none" && candidateLimit === null) {
2159
+ throw new Error("fusion child result output recovery has no candidate contract");
2160
+ }
2161
+ return {
2162
+ json_rendered_bytes: requireUsageInteger(
2163
+ record,
2164
+ "json_rendered_bytes",
2165
+ "fusion child result.output_contract"
2166
+ ),
2167
+ candidate_limit_bytes: candidateLimit,
2168
+ recovery_role: recoveryRole
2169
+ };
2170
+ }
2171
+ __name(parseChildOutputContract, "parseChildOutputContract");
2172
+ function parseChildResultMetadata(value) {
2173
+ const record = assertClosedRecord(
2174
+ value,
2175
+ [
2176
+ "schema_version",
2177
+ "provider",
2178
+ "model",
2179
+ "stop_reason",
2180
+ "text_blocks",
2181
+ "text_sha256",
2182
+ "usage",
2183
+ "cache_observation",
2184
+ "output_contract"
2185
+ ],
2186
+ "fusion child result"
2187
+ );
2188
+ if (record["schema_version"] !== FUSION_CHILD_RESULT_SCHEMA_VERSION)
2189
+ throw new Error("fusion child result schema_version mismatch");
2190
+ const textBlocksValue = record["text_blocks"];
2191
+ if (!Array.isArray(textBlocksValue))
2192
+ throw new Error("fusion child result.text_blocks must be an array");
2193
+ const textBlocks = textBlocksValue.map((value2, index) => {
2194
+ const label = `fusion child result.text_blocks[${String(index)}]`;
2195
+ const block = assertClosedRecord(value2, ["utf8_bytes", "sha256"], label);
2196
+ return {
2197
+ utf8_bytes: requireUsageInteger(block, "utf8_bytes", label),
2198
+ sha256: requireSha256(block, "sha256", label)
2199
+ };
2200
+ });
2201
+ const usage = parseCompactUsage(record["usage"]);
2202
+ const provider = requireNonBlankString(record, "provider", "fusion child result");
2203
+ return {
2204
+ schema_version: FUSION_CHILD_RESULT_SCHEMA_VERSION,
2205
+ provider,
2206
+ model: requireNonBlankString(record, "model", "fusion child result"),
2207
+ stop_reason: requireNonBlankString(record, "stop_reason", "fusion child result"),
2208
+ text_blocks: textBlocks,
2209
+ text_sha256: requireSha256(record, "text_sha256", "fusion child result"),
2210
+ usage,
2211
+ cache_observation: parseFusionClaudeCacheObservation(record["cache_observation"], provider),
2212
+ output_contract: parseChildOutputContract(record["output_contract"])
2213
+ };
2214
+ }
2215
+ __name(parseChildResultMetadata, "parseChildResultMetadata");
2216
+ var FUSION_RUNTIME_GUARD_CODES = /* @__PURE__ */ new Set([
2217
+ "provider_request_limit",
2218
+ "provider_payload_invalid",
2219
+ "claude_cache_policy",
2220
+ "tool_call_limit"
2221
+ ]);
2222
+ function parseFusionRuntimeGuard(stderr) {
2223
+ const frames = [];
2224
+ let cursor = 0;
2225
+ for (; ; ) {
2226
+ const frameStart = stderr.indexOf(FUSION_RUNTIME_GUARD_PREFIX_BYTES, cursor);
2227
+ if (frameStart < 0) break;
2228
+ const payloadStart = frameStart + FUSION_RUNTIME_GUARD_PREFIX_BYTES.length;
2229
+ const newline = stderr.indexOf(10, payloadStart);
2230
+ if (newline < 0) throw new Error("fusion runtime guard frame is not newline-terminated");
2231
+ const bytes = stderr.subarray(payloadStart, newline);
2232
+ const text = bytes.toString("utf8");
2233
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
2234
+ throw new Error("fusion runtime guard frame is not valid UTF-8");
2235
+ }
2236
+ const record = assertClosedRecord(
2237
+ parseJsonText(text),
2238
+ [
2239
+ "schema_version",
2240
+ "code",
2241
+ "provider",
2242
+ "model",
2243
+ "request_ordinal",
2244
+ "tool_call_count",
2245
+ "payload_bytes",
2246
+ "payload_sha256",
2247
+ "message"
2248
+ ],
2249
+ "fusion runtime guard"
2250
+ );
2251
+ if (record["schema_version"] !== FUSION_RUNTIME_GUARD_SCHEMA_VERSION) {
2252
+ throw new Error("fusion runtime guard schema_version mismatch");
2253
+ }
2254
+ const code = requireNonBlankString(record, "code", "fusion runtime guard");
2255
+ if (!FUSION_RUNTIME_GUARD_CODES.has(code)) {
2256
+ throw new Error(`fusion runtime guard code is unsupported: ${code}`);
2257
+ }
2258
+ const frame = {
2259
+ schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
2260
+ code,
2261
+ provider: requireNonBlankString(record, "provider", "fusion runtime guard"),
2262
+ model: requireNonBlankString(record, "model", "fusion runtime guard"),
2263
+ request_ordinal: requirePositiveSafeInteger(
2264
+ record,
2265
+ "request_ordinal",
2266
+ "fusion runtime guard"
2267
+ ),
2268
+ tool_call_count: requireUsageInteger(record, "tool_call_count", "fusion runtime guard"),
2269
+ payload_bytes: requireUsageInteger(record, "payload_bytes", "fusion runtime guard"),
2270
+ payload_sha256: requireSha256(record, "payload_sha256", "fusion runtime guard"),
2271
+ message: requireNonBlankString(record, "message", "fusion runtime guard")
2272
+ };
2273
+ const emptyPayloadHash = createHash3("sha256").update(Buffer.alloc(0)).digest("hex");
2274
+ if (frame.code === "claude_cache_policy" || frame.code === "provider_payload_invalid") {
2275
+ if (frame.payload_bytes !== 0 || frame.payload_sha256 !== emptyPayloadHash) {
2276
+ throw new Error("fusion runtime guard invalid-payload evidence mismatch");
2277
+ }
2278
+ }
2279
+ if (frame.code === "provider_request_limit" && frame.request_ordinal <= FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
2280
+ throw new Error("fusion runtime guard provider request limit was not exceeded");
2281
+ }
2282
+ if (frame.code === "tool_call_limit") {
2283
+ if (frame.tool_call_count <= FUSION_CHILD_MAX_TOOL_CALLS) {
2284
+ throw new Error("fusion runtime guard tool call limit was not exceeded");
2285
+ }
2286
+ if (frame.payload_bytes !== 0 || frame.payload_sha256 !== emptyPayloadHash) {
2287
+ throw new Error("fusion runtime guard tool call limit payload evidence mismatch");
2288
+ }
2289
+ }
2290
+ frames.push(frame);
2291
+ cursor = newline + 1;
2292
+ }
2293
+ if (frames.length > 1) throw new Error("fusion child emitted multiple runtime guard frames");
2294
+ return frames[0];
2295
+ }
2296
+ __name(parseFusionRuntimeGuard, "parseFusionRuntimeGuard");
2297
+ var FUSION_CHILD_SETTLEMENT_FAILURE_REASONS = /* @__PURE__ */ new Set([
2298
+ "no_records",
2299
+ "final_not_stop",
2300
+ "invalid_non_final",
2301
+ "runtime_guard",
2302
+ "cache_observation",
2303
+ "output_recovery"
2304
+ ]);
2305
+ function parseFusionChildSettlement(stderr) {
2306
+ const frames = [];
2307
+ let cursor = 0;
2308
+ for (; ; ) {
2309
+ const frameStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, cursor);
2310
+ if (frameStart < 0) break;
2311
+ const payloadStart = frameStart + FUSION_CHILD_SETTLEMENT_PREFIX_BYTES.length;
2312
+ const newline = stderr.indexOf(10, payloadStart);
2313
+ if (newline < 0) throw new Error("fusion child settlement frame is not newline-terminated");
2314
+ const bytes = stderr.subarray(payloadStart, newline);
2315
+ const text = bytes.toString("utf8");
2316
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
2317
+ throw new Error("fusion child settlement frame is not valid UTF-8");
2318
+ }
2319
+ const record = assertClosedRecord(
2320
+ parseJsonText(text),
2321
+ [
2322
+ "schema_version",
2323
+ "status",
2324
+ "record_count",
2325
+ "records_sha256",
2326
+ "final_record_index",
2327
+ "final_text_sha256",
2328
+ "recovered_error_ordinals",
2329
+ "recovered_output_cap_ordinals",
2330
+ "failure_reason"
2331
+ ],
2332
+ "fusion child settlement"
2333
+ );
2334
+ if (record["schema_version"] !== FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION) {
2335
+ throw new Error("fusion child settlement schema_version mismatch");
2336
+ }
2337
+ const status = record["status"];
2338
+ if (status !== "complete" && status !== "failed") {
2339
+ throw new Error("fusion child settlement.status is invalid");
2340
+ }
2341
+ const recordCount = requireUsageInteger(record, "record_count", "fusion child settlement");
2342
+ const finalIndexValue = record["final_record_index"];
2343
+ const finalRecordIndex = finalIndexValue === null ? null : requireUsageInteger(record, "final_record_index", "fusion child settlement");
2344
+ const finalHashValue = record["final_text_sha256"];
2345
+ const finalTextSha256 = finalHashValue === null ? null : requireSha256(record, "final_text_sha256", "fusion child settlement");
2346
+ const recoveredValue = record["recovered_error_ordinals"];
2347
+ if (!Array.isArray(recoveredValue)) {
2348
+ throw new Error("fusion child settlement.recovered_error_ordinals must be an array");
2349
+ }
2350
+ const recoveredErrorOrdinals = recoveredValue.map((value, index) => {
2351
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2352
+ throw new Error(
2353
+ `fusion child settlement.recovered_error_ordinals[${String(index)}] must be a non-negative safe integer`
2354
+ );
2355
+ }
2356
+ return value;
2357
+ });
2358
+ for (let index = 0; index < recoveredErrorOrdinals.length; index += 1) {
2359
+ const ordinal = recoveredErrorOrdinals[index];
2360
+ if (ordinal === void 0 || ordinal >= recordCount - 1 || index > 0 && ordinal <= (recoveredErrorOrdinals[index - 1] ?? -1)) {
2361
+ throw new Error("fusion child settlement recovered-error ordinals are not canonical");
2362
+ }
2363
+ }
2364
+ const recoveredOutputValue = record["recovered_output_cap_ordinals"];
2365
+ if (!Array.isArray(recoveredOutputValue)) {
2366
+ throw new Error("fusion child settlement.recovered_output_cap_ordinals must be an array");
2367
+ }
2368
+ const recoveredOutputCapOrdinals = recoveredOutputValue.map((value, index) => {
2369
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
2370
+ throw new Error(
2371
+ `fusion child settlement.recovered_output_cap_ordinals[${String(index)}] must be a non-negative safe integer`
2372
+ );
2373
+ }
2374
+ return value;
2375
+ });
2376
+ for (let index = 0; index < recoveredOutputCapOrdinals.length; index += 1) {
2377
+ const ordinal = recoveredOutputCapOrdinals[index];
2378
+ if (ordinal === void 0 || ordinal >= recordCount - 1 || index > 0 && ordinal <= (recoveredOutputCapOrdinals[index - 1] ?? -1)) {
2379
+ throw new Error("fusion child settlement recovered-output ordinals are not canonical");
2380
+ }
2381
+ }
2382
+ const failureValue = record["failure_reason"];
2383
+ let failureReason;
2384
+ if (failureValue === null) failureReason = null;
2385
+ else if (typeof failureValue === "string" && FUSION_CHILD_SETTLEMENT_FAILURE_REASONS.has(
2386
+ failureValue
2387
+ )) {
2388
+ failureReason = failureValue;
2389
+ } else {
2390
+ throw new Error("fusion child settlement.failure_reason is invalid");
2391
+ }
2392
+ if (status === "complete" !== (failureReason === null)) {
2393
+ throw new Error("fusion child settlement status/failure_reason mismatch");
2394
+ }
2395
+ if (recordCount === 0) {
2396
+ if (finalRecordIndex !== null || finalTextSha256 !== null) {
2397
+ throw new Error("fusion child settlement empty stream has final-record evidence");
2398
+ }
2399
+ } else if (finalRecordIndex !== recordCount - 1 || finalTextSha256 === null) {
2400
+ throw new Error("fusion child settlement final-record evidence mismatch");
2401
+ }
2402
+ frames.push({
2403
+ schema_version: FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
2404
+ status,
2405
+ record_count: recordCount,
2406
+ records_sha256: requireSha256(record, "records_sha256", "fusion child settlement"),
2407
+ final_record_index: finalRecordIndex,
2408
+ final_text_sha256: finalTextSha256,
2409
+ recovered_error_ordinals: recoveredErrorOrdinals,
2410
+ recovered_output_cap_ordinals: recoveredOutputCapOrdinals,
2411
+ failure_reason: failureReason
2412
+ });
2413
+ cursor = newline + 1;
2414
+ }
2415
+ if (frames.length > 1) throw new Error("fusion child emitted multiple settlement frames");
2416
+ return frames[0];
2417
+ }
2418
+ __name(parseFusionChildSettlement, "parseFusionChildSettlement");
2419
+ function assertFusionChildSettlementOrdering(stderr) {
2420
+ const settlementStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES);
2421
+ if (settlementStart < 0) return;
2422
+ if (stderr.indexOf(FUSION_CHILD_RESULT_PREFIX_BYTES, settlementStart) >= 0) {
2423
+ throw new Error("fusion child emitted result metadata after terminal settlement");
2424
+ }
2425
+ }
2426
+ __name(assertFusionChildSettlementOrdering, "assertFusionChildSettlementOrdering");
2427
+ function stripChildControlFrames(stderr) {
2428
+ const prefixes = [FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, FUSION_RUNTIME_GUARD_PREFIX_BYTES];
2429
+ const diagnostics = [];
2430
+ let cursor = 0;
2431
+ for (; ; ) {
2432
+ let next = -1;
2433
+ let prefix;
2434
+ for (const candidate of prefixes) {
2435
+ const found = stderr.indexOf(candidate, cursor);
2436
+ if (found >= 0 && (next < 0 || found < next)) {
2437
+ next = found;
2438
+ prefix = candidate;
2439
+ }
2440
+ }
2441
+ if (next < 0 || prefix === void 0) {
2442
+ if (cursor < stderr.length) diagnostics.push(stderr.subarray(cursor));
2443
+ break;
2444
+ }
2445
+ if (next > cursor) diagnostics.push(stderr.subarray(cursor, next));
2446
+ const newline = stderr.indexOf(10, next + prefix.length);
2447
+ if (newline < 0) throw new Error("fusion child control frame is not newline-terminated");
2448
+ cursor = newline + 1;
2449
+ }
2450
+ return Buffer.concat(diagnostics);
2451
+ }
2452
+ __name(stripChildControlFrames, "stripChildControlFrames");
2453
+ function assertFusionRuntimeGuardMatchesModel(guard, model) {
2454
+ const routeUnknown = guard.provider === "unknown" && guard.model === "unknown";
2455
+ if (!routeUnknown && (guard.provider !== model.provider || guard.model !== model.model)) {
2456
+ throw new Error(
2457
+ `fusion runtime guard route mismatch: expected ${model.qualifiedId}, observed ${guard.provider}/${guard.model}`
2458
+ );
2459
+ }
2460
+ if (routeUnknown && guard.code !== "provider_payload_invalid") {
2461
+ throw new Error("fusion runtime guard omitted the route for a route-bound refusal");
2462
+ }
2463
+ }
2464
+ __name(assertFusionRuntimeGuardMatchesModel, "assertFusionRuntimeGuardMatchesModel");
2465
+ function parseFusionChildStderr(stderr) {
2466
+ parseFusionRuntimeGuard(stderr);
2467
+ const settlement = parseFusionChildSettlement(stderr);
2468
+ assertFusionChildSettlementOrdering(stderr);
2469
+ const records = [];
2470
+ const diagnostics = [];
2471
+ let cursor = 0;
2472
+ for (; ; ) {
2473
+ const frameStart = stderr.indexOf(FUSION_CHILD_RESULT_PREFIX_BYTES, cursor);
2474
+ if (frameStart < 0) {
2475
+ if (cursor < stderr.length) diagnostics.push(stderr.subarray(cursor));
2476
+ break;
2477
+ }
2478
+ if (frameStart > cursor) diagnostics.push(stderr.subarray(cursor, frameStart));
2479
+ const payloadStart = frameStart + FUSION_CHILD_RESULT_PREFIX_BYTES.length;
2480
+ const newline = stderr.indexOf(10, payloadStart);
2481
+ if (newline < 0) throw new Error("fusion child metadata frame is not newline-terminated");
2482
+ const payloadBytes = stderr.subarray(payloadStart, newline);
2483
+ const payloadText = payloadBytes.toString("utf8");
2484
+ if (!Buffer.from(payloadText, "utf8").equals(payloadBytes))
2485
+ throw new Error("fusion child metadata frame is not valid UTF-8");
2486
+ let parsed;
2487
+ try {
2488
+ parsed = parseJsonText(payloadText);
2489
+ } catch (error) {
2490
+ throw new Error(
2491
+ `fusion child metadata frame is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
2492
+ );
2493
+ }
2494
+ records.push(parseChildResultMetadata(parsed));
2495
+ cursor = newline + 1;
2496
+ }
2497
+ const resultEvents = serializeFusionChildResultRecords(records);
2498
+ const events = settlement === void 0 ? resultEvents : Buffer.concat([resultEvents, Buffer.from(`${JSON.stringify(settlement)}
2499
+ `, "utf8")]);
2500
+ return {
2501
+ records,
2502
+ events,
2503
+ diagnostics: stripChildControlFrames(Buffer.concat(diagnostics))
2504
+ };
2505
+ }
2506
+ __name(parseFusionChildStderr, "parseFusionChildStderr");
2507
+ function parseToolCallLogRecord(value, label) {
2508
+ const record = assertClosedRecordWithOptional(
2509
+ value,
2510
+ [
2511
+ "schema_version",
2512
+ "ordinal",
2513
+ "tool_name",
2514
+ "arguments_sha256",
2515
+ "arguments_bytes",
2516
+ "result_bytes",
2517
+ "result_sha256",
2518
+ "status",
2519
+ "duration_ms"
2520
+ ],
2521
+ ["url", "rejected_url_sha256", "final_url", "http_status", "response_bytes", "content_sha256"],
2522
+ label
2523
+ );
2524
+ if (record["schema_version"] !== FUSION_TOOL_CALL_LOG_SCHEMA_VERSION) {
2525
+ throw new Error(`${label}.schema_version mismatch`);
2526
+ }
2527
+ const status = record["status"];
2528
+ if (status !== "ok" && status !== "error") throw new Error(`${label}.status is invalid`);
2529
+ const parsedRecord = {
2530
+ schema_version: FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
2531
+ ordinal: requireUsageInteger(record, "ordinal", label),
2532
+ tool_name: requireNonBlankString(record, "tool_name", label),
2533
+ arguments_sha256: requireSha256(record, "arguments_sha256", label),
2534
+ arguments_bytes: requireUsageInteger(record, "arguments_bytes", label),
2535
+ result_bytes: requireUsageInteger(record, "result_bytes", label),
2536
+ result_sha256: requireSha256(record, "result_sha256", label),
2537
+ status,
2538
+ duration_ms: requireUsageInteger(record, "duration_ms", label)
2539
+ };
2540
+ if (record["url"] !== void 0) parsedRecord.url = requireNonBlankString(record, "url", label);
2541
+ if (record["rejected_url_sha256"] !== void 0)
2542
+ parsedRecord.rejected_url_sha256 = requireSha256(record, "rejected_url_sha256", label);
2543
+ if (record["final_url"] !== void 0)
2544
+ parsedRecord.final_url = requireNonBlankString(record, "final_url", label);
2545
+ if (record["http_status"] !== void 0)
2546
+ parsedRecord.http_status = requireUsageInteger(record, "http_status", label);
2547
+ if (record["response_bytes"] !== void 0)
2548
+ parsedRecord.response_bytes = requireUsageInteger(record, "response_bytes", label);
2549
+ if (record["content_sha256"] !== void 0)
2550
+ parsedRecord.content_sha256 = requireSha256(record, "content_sha256", label);
2551
+ return parsedRecord;
2552
+ }
2553
+ __name(parseToolCallLogRecord, "parseToolCallLogRecord");
2554
+ function parseFusionToolCallLog(bytes) {
2555
+ if (bytes.length === 0) {
2556
+ return {
2557
+ bytes,
2558
+ records: [],
2559
+ summary: { count: 0, total_result_bytes: 0, trace_complete: true }
2560
+ };
2561
+ }
2562
+ if (bytes.at(-1) !== 10) {
2563
+ throw new Error("fusion tool-call log has trailing partial line");
2564
+ }
2565
+ const text = bytes.toString("utf8");
2566
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
2567
+ throw new Error("fusion tool-call log is not valid UTF-8");
2568
+ }
2569
+ const lines = text.split("\n");
2570
+ lines.pop();
2571
+ const records = lines.map((line, index) => {
2572
+ let parsed;
2573
+ try {
2574
+ parsed = parseJsonText(line);
2575
+ } catch (error) {
2576
+ throw new Error(
2577
+ `fusion tool-call log line ${String(index)} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
2578
+ );
2579
+ }
2580
+ return parseToolCallLogRecord(parsed, `fusion tool-call log line ${String(index)}`);
2581
+ });
2582
+ const seen = /* @__PURE__ */ new Set();
2583
+ for (const [index, record] of records.entries()) {
2584
+ if (seen.has(record.ordinal)) {
2585
+ throw new Error(`fusion tool-call log duplicate ordinal ${String(record.ordinal)}`);
2586
+ }
2587
+ seen.add(record.ordinal);
2588
+ if (record.ordinal !== index) {
2589
+ throw new Error(
2590
+ `fusion tool-call log ordinal gap: expected ${String(index)}, observed ${String(record.ordinal)}`
2591
+ );
2592
+ }
2593
+ }
2594
+ return {
2595
+ bytes,
2596
+ records,
2597
+ summary: {
2598
+ count: records.length,
2599
+ total_result_bytes: records.reduce((sum, record) => sum + record.result_bytes, 0),
2600
+ trace_complete: true
2601
+ }
2602
+ };
2603
+ }
2604
+ __name(parseFusionToolCallLog, "parseFusionToolCallLog");
2605
+ async function assertCompletedToolPolicy(trace, capability, sourcePolicy) {
2606
+ const allowed = capability === "inspect" ? FUSION_INSPECT_TOOLS : capability === "research" ? FUSION_RESEARCH_TOOLS : [];
2607
+ const allowedSet = new Set(allowed);
2608
+ const declared = capability === "research" && sourcePolicy !== void 0 ? new Set(
2609
+ (await readFusionSourcePolicyFile(sourcePolicy.path, sourcePolicy.sha256)).sources.map(
2610
+ (source) => source.canonical_url
2611
+ )
2612
+ ) : void 0;
2613
+ for (const record of trace.records) {
2614
+ if (!allowedSet.has(record.tool_name)) {
2615
+ throw new Error(`fusion child used non-allowlisted tool ${record.tool_name}`);
2616
+ }
2617
+ if (capability === "research" && record.tool_name === FUSION_WEB_FETCH_TOOL_NAME) {
2618
+ if (sourcePolicy === void 0 || declared === void 0)
2619
+ throw new Error("fusion research source policy missing during audit");
2620
+ if (record.status === "ok") {
2621
+ if (record.url === void 0) throw new Error("fusion research fetch audit is missing url");
2622
+ const canonicalUrl = canonicalizeFusionPublicUrl(record.url);
2623
+ if (record.url !== canonicalUrl)
2624
+ throw new Error("fusion research fetch audit URL was not canonical");
2625
+ if (!declared.has(canonicalUrl))
2626
+ throw new Error("fusion research fetch audit URL was not declared");
2627
+ if (record.rejected_url_sha256 !== void 0) {
2628
+ throw new Error(
2629
+ "fusion research successful fetch audit must not include rejected_url_sha256"
2630
+ );
2631
+ }
2632
+ if (record.final_url === void 0)
2633
+ throw new Error("fusion research fetch audit is missing final_url");
2634
+ if (record.http_status === void 0)
2635
+ throw new Error("fusion research fetch audit is missing http_status");
2636
+ if (record.response_bytes === void 0)
2637
+ throw new Error("fusion research fetch audit is missing response_bytes");
2638
+ if (record.content_sha256 === void 0)
2639
+ throw new Error("fusion research fetch audit is missing content_sha256");
2640
+ } else {
2641
+ if (record.url !== void 0 || record.final_url !== void 0) {
2642
+ throw new Error("fusion research rejected fetch audit must not persist raw URL");
2643
+ }
2644
+ if (record.rejected_url_sha256 === void 0) {
2645
+ throw new Error("fusion research rejected fetch audit is missing rejected_url_sha256");
2646
+ }
2647
+ }
2648
+ }
2649
+ }
2650
+ }
2651
+ __name(assertCompletedToolPolicy, "assertCompletedToolPolicy");
2652
+ function isNotFound(error) {
2653
+ return isJsonObject(error) && error["code"] === "ENOENT";
2654
+ }
2655
+ __name(isNotFound, "isNotFound");
2656
+ async function readFusionToolCallLog(path) {
2657
+ let handle;
2658
+ try {
2659
+ handle = await open(path, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
2660
+ } catch (error) {
2661
+ if (isNotFound(error)) {
2662
+ throw new Error(
2663
+ `fusion tool-call log is missing at ${path}; the inspect child never initialized its audit trail`
2664
+ );
2665
+ }
2666
+ if (isJsonObject(error) && error["code"] === "ELOOP") {
2667
+ throw new Error(
2668
+ `fusion tool-call log at ${path} is a symlink; refusing to trust a redirected audit trail`
2669
+ );
2670
+ }
2671
+ throw error;
2672
+ }
2673
+ try {
2674
+ const stats = await handle.stat();
2675
+ if (!stats.isFile()) {
2676
+ throw new Error(
2677
+ `fusion tool-call log at ${path} is not a regular file; refusing to trust a redirected audit trail`
2678
+ );
2679
+ }
2680
+ return parseFusionToolCallLog(await handle.readFile());
2681
+ } finally {
2682
+ await handle.close();
2683
+ }
2684
+ }
2685
+ __name(readFusionToolCallLog, "readFusionToolCallLog");
2686
+ async function assertFusionToolCallLogSeal(path, trace) {
2687
+ const sealPath = `${path}${FUSION_TOOL_CALL_SEAL_SUFFIX}`;
2688
+ let handle;
2689
+ try {
2690
+ handle = await open(sealPath, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
2691
+ } catch (error) {
2692
+ if (isNotFound(error)) throw new Error("fusion tool-call audit completion seal is missing");
2693
+ if (isJsonObject(error) && error["code"] === "ELOOP") {
2694
+ throw new Error("fusion tool-call audit completion seal is a symlink");
2695
+ }
2696
+ throw error;
2697
+ }
2698
+ try {
2699
+ const stats = await handle.stat();
2700
+ if (!stats.isFile())
2701
+ throw new Error("fusion tool-call audit completion seal is not a regular file");
2702
+ if (stats.size > 4096) throw new Error("fusion tool-call audit completion seal is oversized");
2703
+ const bytes = await handle.readFile();
2704
+ if (bytes.at(-1) !== 10) throw new Error("fusion tool-call audit completion seal is partial");
2705
+ const text = bytes.toString("utf8");
2706
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
2707
+ throw new Error("fusion tool-call audit completion seal is not UTF-8");
2708
+ }
2709
+ const parsed = parseJsonText(text);
2710
+ if (!isJsonObject(parsed) || Array.isArray(parsed)) {
2711
+ throw new Error("fusion tool-call audit completion seal must be an object");
2712
+ }
2713
+ const keys = Object.keys(parsed).sort();
2714
+ const expected = [
2715
+ "log_sha256",
2716
+ "record_count",
2717
+ "schema_version",
2718
+ "status",
2719
+ "total_result_bytes"
2720
+ ];
2721
+ if (keys.join("\0") !== expected.join("\0")) {
2722
+ throw new Error("fusion tool-call audit completion seal keys mismatch");
2723
+ }
2724
+ if (parsed["schema_version"] !== FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION) {
2725
+ throw new Error("fusion tool-call audit completion seal schema mismatch");
2726
+ }
2727
+ if (parsed["status"] !== "complete") {
2728
+ throw new Error("fusion tool-call audit completion seal reports a failed audit");
2729
+ }
2730
+ const recordCount = requireUsageInteger(parsed, "record_count", "fusion tool-call audit seal");
2731
+ const totalResultBytes = requireUsageInteger(
2732
+ parsed,
2733
+ "total_result_bytes",
2734
+ "fusion tool-call audit seal"
2735
+ );
2736
+ const logSha256 = requireSha256(parsed, "log_sha256", "fusion tool-call audit seal");
2737
+ if (recordCount !== trace.summary.count) {
2738
+ throw new Error("fusion tool-call audit completion seal record count mismatch");
2739
+ }
2740
+ if (recordCount > FUSION_CHILD_MAX_TOOL_CALLS) {
2741
+ throw new Error(
2742
+ `fusion tool-call audit exceeds tool-call limit ${String(FUSION_CHILD_MAX_TOOL_CALLS)}`
2743
+ );
2744
+ }
2745
+ if (totalResultBytes !== trace.summary.total_result_bytes) {
2746
+ throw new Error("fusion tool-call audit completion seal result-byte total mismatch");
2747
+ }
2748
+ if (logSha256 !== sha256Buffer(trace.bytes)) {
2749
+ throw new Error("fusion tool-call audit completion seal log hash mismatch");
2750
+ }
2751
+ if (totalResultBytes > FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES) {
2752
+ throw new Error(
2753
+ `fusion tool-call audit exceeds aggregate result-byte limit ${String(FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES)}`
2754
+ );
2755
+ }
2756
+ } finally {
2757
+ await handle.close();
2758
+ }
2759
+ }
2760
+ __name(assertFusionToolCallLogSeal, "assertFusionToolCallLogSeal");
2761
+ function sha256Buffer(bytes) {
2762
+ return createHash3("sha256").update(bytes).digest("hex");
2763
+ }
2764
+ __name(sha256Buffer, "sha256Buffer");
2765
+ function reconstructFinalText(response, record) {
2766
+ const blocks = [];
2767
+ let cursor = 0;
2768
+ for (const [index, block] of record.text_blocks.entries()) {
2769
+ const end = cursor + block.utf8_bytes;
2770
+ if (end > response.length)
2771
+ throw new Error(`Pi final text block ${String(index)} is shorter than its metadata length`);
2772
+ const bytes = response.subarray(cursor, end);
2773
+ if (sha256Buffer(bytes) !== block.sha256)
2774
+ throw new Error(`Pi final text block ${String(index)} hash mismatch`);
2775
+ blocks.push(bytes);
2776
+ if (response.at(end) !== 10)
2777
+ throw new Error(`Pi final text block ${String(index)} lacks its print-mode newline`);
2778
+ cursor = end + 1;
2779
+ }
2780
+ if (cursor !== response.length)
2781
+ throw new Error("Pi final text stdout contains bytes outside declared text blocks");
2782
+ const joined = Buffer.concat(blocks);
2783
+ if (sha256Buffer(joined) !== record.text_sha256)
2784
+ throw new Error("Pi final text aggregate hash mismatch");
2785
+ const text = joined.toString("utf8");
2786
+ if (!Buffer.from(text, "utf8").equals(joined))
2787
+ throw new Error("Pi final text is not valid UTF-8");
2788
+ if (fusionJsonRenderedTextBytes(text) !== record.output_contract.json_rendered_bytes) {
2789
+ throw new Error("Pi final text JSON-rendered byte count mismatch");
2790
+ }
2791
+ if (text.trim().length === 0) throw new Error("Pi assistant response is empty");
2792
+ return text;
2793
+ }
2794
+ __name(reconstructFinalText, "reconstructFinalText");
2795
+ async function readCandidateOutputRecovery(path, evidence) {
2796
+ let handle;
2797
+ try {
2798
+ handle = await open(path, constants.O_RDONLY | FUSION_PI_CHILD_O_NOFOLLOW);
2799
+ } catch (error) {
2800
+ if (isNotFound(error))
2801
+ throw new Error("fusion oversized candidate response artifact is missing");
2802
+ if (isJsonObject(error) && error["code"] === "ELOOP") {
2803
+ throw new Error("fusion oversized candidate response artifact is a symlink");
2804
+ }
2805
+ throw error;
2806
+ }
2807
+ try {
2808
+ const stats = await handle.stat();
2809
+ if (!stats.isFile()) {
2810
+ throw new Error("fusion oversized candidate response artifact is not a regular file");
2811
+ }
2812
+ if (stats.size > FUSION_CHILD_STDOUT_LIMIT_BYTES) {
2813
+ throw new Error(
2814
+ `fusion oversized candidate response artifact exceeds ${String(FUSION_CHILD_STDOUT_LIMIT_BYTES)} bytes`
2815
+ );
2816
+ }
2817
+ const bytes = await handle.readFile();
2818
+ if (sha256Buffer(bytes) !== evidence.original_text_sha256) {
2819
+ throw new Error("fusion oversized candidate response artifact hash mismatch");
2820
+ }
2821
+ const text = bytes.toString("utf8");
2822
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
2823
+ throw new Error("fusion oversized candidate response artifact is not valid UTF-8");
2824
+ }
2825
+ if (fusionJsonRenderedTextBytes(text) !== evidence.original_json_rendered_bytes) {
2826
+ throw new Error("fusion oversized candidate response JSON-rendered byte count mismatch");
2827
+ }
2828
+ if (evidence.original_json_rendered_bytes <= evidence.limit_bytes) {
2829
+ throw new Error("fusion output recovery original did not exceed the candidate contract");
2830
+ }
2831
+ return { ...evidence, original_text: text };
2832
+ } finally {
2833
+ await handle.close();
2834
+ }
2835
+ }
2836
+ __name(readCandidateOutputRecovery, "readCandidateOutputRecovery");
2837
+ function parsedCandidateOutputRecovery(records, status) {
2838
+ const originalRecordIndex = records.findIndex(
2839
+ (record) => record.output_contract.recovery_role === "oversized_original"
2840
+ );
2841
+ if (originalRecordIndex < 0) return void 0;
2842
+ const original = records[originalRecordIndex];
2843
+ if (original === void 0) throw new Error("Pi output recovery original record disappeared");
2844
+ const replacementRecordIndex = records.findIndex(
2845
+ (record) => record.output_contract.recovery_role === "replacement"
2846
+ );
2847
+ const replacement = replacementRecordIndex < 0 ? void 0 : records[replacementRecordIndex];
2848
+ return {
2849
+ kind: "same_session_compression",
2850
+ limit_bytes: FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
2851
+ original_record_index: originalRecordIndex,
2852
+ replacement_record_index: replacementRecordIndex < 0 ? null : replacementRecordIndex,
2853
+ original_json_rendered_bytes: original.output_contract.json_rendered_bytes,
2854
+ replacement_json_rendered_bytes: replacement?.output_contract.json_rendered_bytes ?? null,
2855
+ original_text_sha256: original.text_sha256,
2856
+ status
2857
+ };
2858
+ }
2859
+ __name(parsedCandidateOutputRecovery, "parsedCandidateOutputRecovery");
2860
+ var FusionPiCompactResultParser = class {
2861
+ static {
2862
+ __name(this, "FusionPiCompactResultParser");
2863
+ }
2864
+ expectedProvider;
2865
+ expectedModel;
2866
+ constructor(expectedProvider, expectedModel) {
2867
+ this.expectedProvider = expectedProvider;
2868
+ this.expectedModel = expectedModel;
2869
+ }
2870
+ snapshot(stderr) {
2871
+ try {
2872
+ const parsed = parseFusionChildStderr(stderr);
2873
+ return this.observedFromRecords(parsed.records);
2874
+ } catch {
2875
+ return { usage: createEmptyFusionUsage() };
2876
+ }
2877
+ }
2878
+ finishOutputRecoveryFailure(response, stderr) {
2879
+ const parsed = parseFusionChildStderr(stderr);
2880
+ const settlement = parseFusionChildSettlement(stderr);
2881
+ if (settlement === void 0) throw new Error("Pi child emitted no terminal result settlement");
2882
+ const expectedSettlement = buildFusionChildSettlement(parsed.records);
2883
+ if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
2884
+ throw new Error("Pi child terminal result settlement does not match the metadata stream");
2885
+ }
2886
+ if (settlement.status !== "failed" || settlement.failure_reason !== "output_recovery") {
2887
+ throw new Error("Pi child did not report a failed candidate output recovery");
2888
+ }
2889
+ if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
2890
+ throw new Error("Pi child reported an extension error diagnostic");
2891
+ }
2892
+ const final = parsed.records.at(-1);
2893
+ if (final === void 0) throw new Error("Pi child emitted no compact result metadata");
2894
+ for (const record of parsed.records) this.assertModel(record);
2895
+ this.assertCacheObservationOrdinals(parsed.records);
2896
+ this.assertTranscriptStopReasons(parsed.records);
2897
+ reconstructFinalText(response, final);
2898
+ const outputRecovery = parsedCandidateOutputRecovery(parsed.records, "failed");
2899
+ if (outputRecovery === void 0) {
2900
+ throw new Error("Pi child output-recovery failure omitted the oversized original");
2901
+ }
2902
+ const observed = this.observedFromRecords(parsed.records);
2903
+ return {
2904
+ usage: observed.usage,
2905
+ provider: final.provider,
2906
+ model: final.model,
2907
+ qualifiedId: `${final.provider}/${final.model}`,
2908
+ events: parsed.events,
2909
+ diagnostics: parsed.diagnostics,
2910
+ outputRecovery
2911
+ };
2912
+ }
2913
+ finish(response, stderr) {
2914
+ const parsed = parseFusionChildStderr(stderr);
2915
+ const runtimeGuard = parseFusionRuntimeGuard(stderr);
2916
+ if (runtimeGuard !== void 0) {
2917
+ throw new Error(`Pi child runtime guard refused the run: ${runtimeGuard.message}`);
2918
+ }
2919
+ const settlement = parseFusionChildSettlement(stderr);
2920
+ if (settlement === void 0) throw new Error("Pi child emitted no terminal result settlement");
2921
+ const expectedSettlement = buildFusionChildSettlement(parsed.records);
2922
+ if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
2923
+ throw new Error("Pi child terminal result settlement does not match the metadata stream");
2924
+ }
2925
+ if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
2926
+ throw new Error("Pi child reported an extension error diagnostic");
2927
+ }
2928
+ const final = parsed.records.at(-1);
2929
+ if (final === void 0) throw new Error("Pi child emitted no compact result metadata");
2930
+ for (const record of parsed.records) this.assertModel(record);
2931
+ this.assertCacheObservationOrdinals(parsed.records);
2932
+ this.assertTranscriptStopReasons(parsed.records);
2933
+ if (settlement.status !== "complete") {
2934
+ throw new Error(
2935
+ `Pi child terminal result settlement failed: ${settlement.failure_reason ?? "unknown"}`
2936
+ );
2937
+ }
2938
+ const observed = this.observedFromRecords(parsed.records);
2939
+ const text = reconstructFinalText(response, final);
2940
+ return {
2941
+ text,
2942
+ usage: observed.usage,
2943
+ firstRequestUsage: cloneFusionUsage(parsed.records[0]?.usage ?? createEmptyFusionUsage()),
2944
+ providerRequestCount: parsed.records.length,
2945
+ provider: final.provider,
2946
+ model: final.model,
2947
+ qualifiedId: `${final.provider}/${final.model}`,
2948
+ events: parsed.events,
2949
+ diagnostics: parsed.diagnostics,
2950
+ outputRecovery: parsedCandidateOutputRecovery(parsed.records, "completed")
2951
+ };
2952
+ }
2953
+ assertModel(record) {
2954
+ if (record.provider !== this.expectedProvider || record.model !== this.expectedModel) {
2955
+ throw new Error(
2956
+ `Pi assistant model mismatch: expected ${this.expectedProvider}/${this.expectedModel}, observed ${record.provider}/${record.model}`
2957
+ );
2958
+ }
2959
+ }
2960
+ assertCacheObservationOrdinals(records) {
2961
+ let priorOrdinal = 0;
2962
+ for (const [index, record] of records.entries()) {
2963
+ const ordinal = record.cache_observation.request_ordinal;
2964
+ if (ordinal <= priorOrdinal) {
2965
+ throw new Error(
2966
+ `Pi child cache observation ordinal is not increasing at result ${String(index)}: previous ${String(priorOrdinal)}, observed ${String(ordinal)}`
2967
+ );
2968
+ }
2969
+ priorOrdinal = ordinal;
2970
+ }
2971
+ }
2972
+ assertTranscriptStopReasons(records) {
2973
+ for (const [index, record] of records.entries()) {
2974
+ const isFinal = index === records.length - 1;
2975
+ if (isFinal) {
2976
+ if (record.stop_reason !== "stop") {
2977
+ throw new Error(this.stopReasonError("final", "stop", record.stop_reason, true));
2978
+ }
2979
+ } else if (record.stop_reason !== "toolUse" && !isRecoverableFusionChildErrorRecord(record) && record.output_contract.recovery_role !== "oversized_original") {
2980
+ throw new Error(
2981
+ this.stopReasonError(
2982
+ `non-final record ${index}`,
2983
+ "toolUse, a settled zero-usage retry marker, or one oversized original",
2984
+ record.stop_reason,
2985
+ true
2986
+ )
2987
+ );
2988
+ }
2989
+ }
2990
+ }
2991
+ stopReasonError(position, expected, observed, includeStopDetail) {
2992
+ const prefix = `Pi ${position} stop reason is not ${expected}: ${observed}`;
2993
+ if (!includeStopDetail) return prefix;
2994
+ switch (observed) {
2995
+ case "length":
2996
+ return `${prefix} (model output was truncated)`;
2997
+ case "error":
2998
+ return `${prefix} (Pi reported an error stop)`;
2999
+ case "aborted":
3000
+ return `${prefix} (Pi reported an aborted stop)`;
3001
+ case "pending":
3002
+ return `${prefix} (Pi reported a pending stop)`;
3003
+ default:
3004
+ return prefix;
3005
+ }
3006
+ }
3007
+ observedFromRecords(records) {
3008
+ const usage = createEmptyFusionUsage();
3009
+ for (const record of records) addFusionUsage(usage, record.usage);
3010
+ const final = records.at(-1);
3011
+ if (final === void 0) return { usage };
3012
+ return {
3013
+ usage,
3014
+ provider: final.provider,
3015
+ model: final.model,
3016
+ qualifiedId: `${final.provider}/${final.model}`
3017
+ };
3018
+ }
3019
+ };
3020
+ function appendCapped(chunks, currentBytes, chunk, limit) {
3021
+ if (currentBytes >= limit)
3022
+ return { bytes: currentBytes, accepted: Buffer.alloc(0), exceeded: true };
3023
+ const remaining = limit - currentBytes;
3024
+ if (chunk.length <= remaining) {
3025
+ chunks.push(chunk);
3026
+ return { bytes: currentBytes + chunk.length, accepted: chunk, exceeded: false };
3027
+ }
3028
+ const accepted = chunk.subarray(0, remaining);
3029
+ if (accepted.length > 0) chunks.push(accepted);
3030
+ return { bytes: limit, accepted, exceeded: true };
3031
+ }
3032
+ __name(appendCapped, "appendCapped");
3033
+ function codeOf(error) {
3034
+ return isJsonObject(error) && typeof error["code"] === "string" ? error["code"] : void 0;
3035
+ }
3036
+ __name(codeOf, "codeOf");
3037
+ function isTransientSpawnCode(code) {
3038
+ return code === "EAGAIN" || code === "EMFILE" || code === "ENFILE";
3039
+ }
3040
+ __name(isTransientSpawnCode, "isTransientSpawnCode");
3041
+ function childError(message, code, input, transient = false, childCreated = true) {
3042
+ const details = {
3043
+ code,
3044
+ stage: input.stage,
3045
+ attempt: input.attempt,
3046
+ transient,
3047
+ childCreated
3048
+ };
3049
+ if (input.slot !== void 0) details.slot = input.slot;
3050
+ return new FusionError(message, details);
3051
+ }
3052
+ __name(childError, "childError");
3053
+ function withCleanupErrors(error, cleanupErrors) {
3054
+ if (cleanupErrors.length === 0) return error;
3055
+ const details = {
3056
+ code: error.code,
3057
+ transient: error.transient,
3058
+ childCreated: error.childCreated
3059
+ };
3060
+ if (error.stage !== void 0) details.stage = error.stage;
3061
+ if (error.slot !== void 0) details.slot = error.slot;
3062
+ if (error.attempt !== void 0) details.attempt = error.attempt;
3063
+ if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
3064
+ return new FusionError(
3065
+ `${error.message}; process cleanup issues: ${cleanupErrors.join("; ")}`,
3066
+ details
3067
+ );
3068
+ }
3069
+ __name(withCleanupErrors, "withCleanupErrors");
3070
+ function defaultSpawn(command, args, options) {
3071
+ return nodeSpawn(command, args, options);
3072
+ }
3073
+ __name(defaultSpawn, "defaultSpawn");
3074
+ function trackTimer(timer) {
3075
+ return timer;
3076
+ }
3077
+ __name(trackTimer, "trackTimer");
3078
+ function rememberCleanupErrors(state, signal, errors) {
3079
+ for (const error of errors) state.cleanupErrors.push(`${signal}: ${error}`);
3080
+ }
3081
+ __name(rememberCleanupErrors, "rememberCleanupErrors");
3082
+ function terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleSyntheticClose) {
3083
+ if (state.settled || state.terminationStarted) return;
3084
+ state.terminationStarted = true;
3085
+ const termResult = sendSignal(child, platform, killProcess, "SIGTERM");
3086
+ rememberCleanupErrors(state, "SIGTERM", termResult.errors);
3087
+ if (!termResult.sent && state.primaryError === void 0) {
3088
+ state.primaryError = new FusionError(
3089
+ `Pi child SIGTERM failed: ${termResult.errors.join("; ")}`,
3090
+ {
3091
+ code: "child_exit_failed",
3092
+ childCreated: true
3093
+ }
3094
+ );
3095
+ }
3096
+ state.termTimer = trackTimer(
3097
+ setTimeout(() => {
3098
+ if (state.settled) return;
3099
+ const killResult = sendSignal(child, platform, killProcess, "SIGKILL");
3100
+ rememberCleanupErrors(state, "SIGKILL", killResult.errors);
3101
+ if (!killResult.sent && state.primaryError === void 0) {
3102
+ state.primaryError = new FusionError(
3103
+ `Pi child SIGKILL failed: ${killResult.errors.join("; ")}`,
3104
+ {
3105
+ code: "child_exit_failed",
3106
+ childCreated: true
3107
+ }
3108
+ );
3109
+ }
3110
+ }, killGraceMs)
3111
+ );
3112
+ state.waitTimer = trackTimer(
3113
+ setTimeout(() => {
3114
+ if (state.settled) return;
3115
+ const message = "Pi child did not emit close after SIGKILL wait";
3116
+ state.cleanupErrors.push(message);
3117
+ if (state.primaryError === void 0) {
3118
+ state.primaryError = new FusionError(message, {
3119
+ code: "child_exit_failed",
3120
+ childCreated: true
3121
+ });
3122
+ }
3123
+ settleSyntheticClose({ code: null, signal: "SIGKILL" });
3124
+ }, killGraceMs + sigkillWaitMs)
3125
+ );
3126
+ }
3127
+ __name(terminateChild, "terminateChild");
3128
+ function sendSignal(child, platform, killProcess, signal) {
3129
+ const errors = [];
3130
+ const pid = child.pid;
3131
+ if (platform !== "win32" && pid !== void 0) {
3132
+ try {
3133
+ if (killProcess(-pid, signal)) return { sent: true, errors };
3134
+ errors.push("process group kill returned false");
3135
+ } catch (error) {
3136
+ errors.push(
3137
+ `process group kill failed: ${error instanceof Error ? error.message : String(error)}`
3138
+ );
3139
+ }
3140
+ }
3141
+ try {
3142
+ if (child.kill(signal)) return { sent: true, errors };
3143
+ errors.push("child kill returned false");
3144
+ } catch (error) {
3145
+ errors.push(`child kill failed: ${error instanceof Error ? error.message : String(error)}`);
3146
+ }
3147
+ return { sent: false, errors };
3148
+ }
3149
+ __name(sendSignal, "sendSignal");
3150
+ function cleanupTimers(state) {
3151
+ if (state.termTimer !== void 0) clearTimeout(state.termTimer);
3152
+ if (state.waitTimer !== void 0) clearTimeout(state.waitTimer);
3153
+ if (state.timeoutTimer !== void 0) clearTimeout(state.timeoutTimer);
3154
+ if (state.idleTimer !== void 0) clearTimeout(state.idleTimer);
3155
+ state.termTimer = void 0;
3156
+ state.waitTimer = void 0;
3157
+ state.timeoutTimer = void 0;
3158
+ state.idleTimer = void 0;
3159
+ }
3160
+ __name(cleanupTimers, "cleanupTimers");
3161
+ async function writePromptToStdin(child, prompt) {
3162
+ const stdin = child.stdin;
3163
+ if (stdin === void 0 || stdin === null) throw new Error("Pi child stdin pipe is unavailable");
3164
+ await new Promise((resolve2, reject) => {
3165
+ let settled = false;
3166
+ const fail = /* @__PURE__ */ __name((error) => {
3167
+ if (settled) return;
3168
+ settled = true;
3169
+ stdin.off("error", fail);
3170
+ reject(error);
3171
+ }, "fail");
3172
+ const finish = /* @__PURE__ */ __name(() => {
3173
+ if (settled) return;
3174
+ settled = true;
3175
+ stdin.off("error", fail);
3176
+ resolve2();
3177
+ }, "finish");
3178
+ stdin.once("error", fail);
3179
+ stdin.write(Buffer.from(prompt, "utf8"), (error) => {
3180
+ if (error !== void 0 && error !== null) {
3181
+ fail(error);
3182
+ return;
3183
+ }
3184
+ stdin.end(finish);
3185
+ });
3186
+ });
3187
+ }
3188
+ __name(writePromptToStdin, "writePromptToStdin");
3189
+ async function runPiChild(options) {
3190
+ if (options.signal?.aborted) {
3191
+ throw childError(
3192
+ "Pi child launch cancelled before spawn",
3193
+ "child_cancelled",
3194
+ options,
3195
+ false,
3196
+ false
3197
+ );
3198
+ }
3199
+ const spawnImpl = options.spawn ?? defaultSpawn;
3200
+ const killProcess = options.killProcess ?? process.kill.bind(process);
3201
+ const platform = options.platform ?? process.platform;
3202
+ const capability = options.capability ?? FUSION_NO_TOOLS_CAPABILITY;
3203
+ const env = fusionPiChildEnv(options.env ?? process.env, options.model.provider);
3204
+ if (options.candidateOutputRecoveryPath !== void 0) {
3205
+ if (options.stage !== "candidate") {
3206
+ throw childError(
3207
+ "candidate output recovery may be enabled only for candidate children",
3208
+ "orchestration_failed",
3209
+ options,
3210
+ false,
3211
+ false
3212
+ );
3213
+ }
3214
+ env[FUSION_CANDIDATE_OUTPUT_RECOVERY_PATH_ENV] = options.candidateOutputRecoveryPath;
3215
+ }
3216
+ if (capability !== "reason") {
3217
+ if (options.toolCallLogPath === void 0) {
3218
+ throw childError(
3219
+ `fusion ${capability} child requires a tool-call log path`,
3220
+ "orchestration_failed",
3221
+ options,
3222
+ false,
3223
+ false
3224
+ );
3225
+ }
3226
+ env[FUSION_TOOL_CALL_LOG_PATH_ENV] = options.toolCallLogPath;
3227
+ if (capability === "research") {
3228
+ if (options.sourcePolicy === void 0) {
3229
+ throw childError(
3230
+ "fusion research child requires a source-policy path and hash",
3231
+ "orchestration_failed",
3232
+ options,
3233
+ false,
3234
+ false
3235
+ );
3236
+ }
3237
+ env[FUSION_RESEARCH_ENABLED_ENV] = "1";
3238
+ env[FUSION_SOURCE_POLICY_PATH_ENV] = options.sourcePolicy.path;
3239
+ env[FUSION_SOURCE_POLICY_SHA256_ENV] = options.sourcePolicy.sha256;
3240
+ }
3241
+ }
3242
+ const stdoutLimit = options.stdoutLimitBytes ?? FUSION_CHILD_STDOUT_LIMIT_BYTES;
3243
+ const stderrLimit = options.stderrLimitBytes ?? FUSION_CHILD_STDERR_LIMIT_BYTES;
3244
+ const timeoutMs = options.timeoutMs ?? FUSION_CHILD_TIMEOUT_MS;
3245
+ const idleTimeoutMs = options.idleTimeoutMs ?? FUSION_CHILD_IDLE_TIMEOUT_MS;
3246
+ const killGraceMs = options.killGraceMs ?? FUSION_CHILD_KILL_GRACE_MS;
3247
+ const sigkillWaitMs = options.sigkillWaitMs ?? FUSION_CHILD_SIGKILL_WAIT_MS;
3248
+ const argv = buildFusionPiChildArgv(
3249
+ options.model,
3250
+ options.systemPrompt,
3251
+ options.childExtensionPath ?? resolveFusionChildExtensionPath(),
3252
+ capability
3253
+ );
3254
+ const parser = new FusionPiCompactResultParser(options.model.provider, options.model.model);
3255
+ const stdoutChunks = [];
3256
+ const stderrChunks = [];
3257
+ let stdoutBytes = 0;
3258
+ let stderrBytes = 0;
3259
+ const state = {
3260
+ primaryError: void 0,
3261
+ cleanupErrors: [],
3262
+ terminationStarted: false,
3263
+ termTimer: void 0,
3264
+ waitTimer: void 0,
3265
+ timeoutTimer: void 0,
3266
+ idleTimer: void 0,
3267
+ settled: false
3268
+ };
3269
+ let child;
3270
+ try {
3271
+ const launchDeps = options.piLaunchDependencies === void 0 ? { platform } : { ...options.piLaunchDependencies, platform };
3272
+ const launch = resolvePiLaunch(launchDeps);
3273
+ assertWindowsCommandLineWithinLimit(launch, argv, platform, `fusion-${options.stage}`);
3274
+ child = spawnImpl(launch.executable, piLaunchArgv(launch, argv), {
3275
+ cwd: options.cwd,
3276
+ detached: platform !== "win32",
3277
+ shell: false,
3278
+ stdio: ["pipe", "pipe", "pipe"],
3279
+ env,
3280
+ windowsHide: true
3281
+ });
3282
+ } catch (error) {
3283
+ const code = codeOf(error);
3284
+ throw childError(
3285
+ `Pi child spawn failed: ${error instanceof Error ? error.message : String(error)}`,
3286
+ "child_spawn_failed",
3287
+ options,
3288
+ isTransientSpawnCode(code),
3289
+ false
3290
+ );
3291
+ }
3292
+ let settleClose = /* @__PURE__ */ __name(() => void 0, "settleClose");
3293
+ const closePromise = new Promise((resolve2) => {
3294
+ settleClose = /* @__PURE__ */ __name((close) => {
3295
+ if (state.settled) return;
3296
+ state.settled = true;
3297
+ resolve2(close);
3298
+ }, "settleClose");
3299
+ });
3300
+ const resetIdleTimer = /* @__PURE__ */ __name(() => {
3301
+ if (state.settled) return;
3302
+ if (state.idleTimer !== void 0) clearTimeout(state.idleTimer);
3303
+ state.idleTimer = trackTimer(
3304
+ setTimeout(() => {
3305
+ if (state.settled) return;
3306
+ if (state.primaryError === void 0) {
3307
+ state.primaryError = childError(
3308
+ `Pi child produced no output for ${String(idleTimeoutMs)}ms (stalled)`,
3309
+ "child_timeout",
3310
+ options
3311
+ );
3312
+ }
3313
+ terminateChild(
3314
+ child,
3315
+ state,
3316
+ platform,
3317
+ killProcess,
3318
+ killGraceMs,
3319
+ sigkillWaitMs,
3320
+ settleClose
3321
+ );
3322
+ }, idleTimeoutMs)
3323
+ );
3324
+ }, "resetIdleTimer");
3325
+ const abortListener = /* @__PURE__ */ __name(() => {
3326
+ if (state.settled) return;
3327
+ if (state.primaryError === void 0) {
3328
+ state.primaryError = childError("Pi child cancelled", "child_cancelled", options);
3329
+ }
3330
+ terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
3331
+ }, "abortListener");
3332
+ const stdoutListener = /* @__PURE__ */ __name((data) => {
3333
+ resetIdleTimer();
3334
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8");
3335
+ const appended = appendCapped(stdoutChunks, stdoutBytes, chunk, stdoutLimit);
3336
+ stdoutBytes = appended.bytes;
3337
+ if (appended.exceeded && state.primaryError === void 0) {
3338
+ state.primaryError = childError(
3339
+ `Pi child final response exceeded ${String(stdoutLimit)} bytes`,
3340
+ "child_output_cap",
3341
+ options
3342
+ );
3343
+ terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
3344
+ }
3345
+ }, "stdoutListener");
3346
+ const stderrListener = /* @__PURE__ */ __name((data) => {
3347
+ resetIdleTimer();
3348
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8");
3349
+ const appended = appendCapped(stderrChunks, stderrBytes, chunk, stderrLimit);
3350
+ stderrBytes = appended.bytes;
3351
+ if (appended.exceeded && state.primaryError === void 0) {
3352
+ state.primaryError = childError(
3353
+ `Pi child stderr exceeded ${String(stderrLimit)} bytes`,
3354
+ "child_output_cap",
3355
+ options
3356
+ );
3357
+ terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
3358
+ }
3359
+ }, "stderrListener");
3360
+ const errorListener = /* @__PURE__ */ __name((error) => {
3361
+ if (state.settled) return;
3362
+ if (state.primaryError === void 0) {
3363
+ const code = codeOf(error);
3364
+ const childCreated = child.pid !== void 0;
3365
+ state.primaryError = childError(
3366
+ `Pi child process error: ${error.message}`,
3367
+ "child_spawn_failed",
3368
+ options,
3369
+ isTransientSpawnCode(code),
3370
+ childCreated
3371
+ );
3372
+ }
3373
+ if (child.pid === void 0) settleClose({ code: null, signal: null });
3374
+ }, "errorListener");
3375
+ const closeListener = /* @__PURE__ */ __name((code, signal) => {
3376
+ settleClose({ code, signal });
3377
+ }, "closeListener");
3378
+ child.stdout?.on("data", stdoutListener);
3379
+ child.stderr?.on("data", stderrListener);
3380
+ child.once("error", errorListener);
3381
+ child.once("close", closeListener);
3382
+ options.signal?.addEventListener("abort", abortListener, { once: true });
3383
+ if (options.signal?.aborted) abortListener();
3384
+ resetIdleTimer();
3385
+ state.timeoutTimer = trackTimer(
3386
+ setTimeout(() => {
3387
+ if (state.primaryError === void 0) {
3388
+ state.primaryError = childError(
3389
+ `Pi child timed out after ${String(timeoutMs)}ms`,
3390
+ "child_timeout",
3391
+ options
3392
+ );
3393
+ }
3394
+ terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
3395
+ }, timeoutMs)
3396
+ );
3397
+ try {
3398
+ try {
3399
+ if (state.primaryError === void 0) await writePromptToStdin(child, options.userPrompt);
3400
+ } catch (error) {
3401
+ if (state.primaryError === void 0) {
3402
+ state.primaryError = childError(
3403
+ `Pi child stdin write failed: ${error instanceof Error ? error.message : String(error)}`,
3404
+ "child_stdin_failed",
3405
+ options
3406
+ );
3407
+ }
3408
+ terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
3409
+ }
3410
+ const close = await closePromise;
3411
+ const response = Buffer.concat(stdoutChunks);
3412
+ const rawStderr = Buffer.concat(stderrChunks);
3413
+ const observed = parser.snapshot(rawStderr);
3414
+ let compactEvents = Buffer.alloc(0);
3415
+ let diagnostics = rawStderr;
3416
+ try {
3417
+ const decoded = parseFusionChildStderr(rawStderr);
3418
+ compactEvents = decoded.events;
3419
+ diagnostics = decoded.diagnostics;
3420
+ } catch {
3421
+ }
3422
+ const readPartialOutputRecovery = /* @__PURE__ */ __name(async () => {
3423
+ let parsedRecords;
3424
+ try {
3425
+ parsedRecords = parseFusionChildStderr(rawStderr).records;
3426
+ } catch {
3427
+ return void 0;
3428
+ }
3429
+ const evidence = parsedCandidateOutputRecovery(parsedRecords, "failed");
3430
+ if (evidence === void 0) return void 0;
3431
+ if (options.candidateOutputRecoveryPath === void 0) {
3432
+ throw new Error("fusion child emitted output-recovery evidence without an artifact path");
3433
+ }
3434
+ return readCandidateOutputRecovery(options.candidateOutputRecoveryPath, evidence);
3435
+ }, "readPartialOutputRecovery");
3436
+ const primary = state.primaryError;
3437
+ if (primary !== void 0) {
3438
+ let outputRecovery2;
3439
+ try {
3440
+ outputRecovery2 = await readPartialOutputRecovery();
3441
+ } catch (error) {
3442
+ state.cleanupErrors.push(
3443
+ `output recovery evidence invalid: ${error instanceof Error ? error.message : String(error)}`
3444
+ );
3445
+ }
3446
+ throw new FusionChildRunError(
3447
+ withCleanupErrors(primary, state.cleanupErrors),
3448
+ compactEvents,
3449
+ response,
3450
+ diagnostics,
3451
+ close,
3452
+ observed,
3453
+ outputRecovery2
3454
+ );
3455
+ }
3456
+ let terminalSettlement;
3457
+ try {
3458
+ terminalSettlement = parseFusionChildSettlement(rawStderr);
3459
+ } catch (error) {
3460
+ throw new FusionChildRunError(
3461
+ withCleanupErrors(
3462
+ childError(
3463
+ `Pi child terminal settlement invalid: ${error instanceof Error ? error.message : String(error)}`,
3464
+ "child_event_invalid",
3465
+ options
3466
+ ),
3467
+ state.cleanupErrors
3468
+ ),
3469
+ compactEvents,
3470
+ response,
3471
+ diagnostics,
3472
+ close,
3473
+ observed
3474
+ );
3475
+ }
3476
+ if (terminalSettlement?.failure_reason === "output_recovery") {
3477
+ try {
3478
+ const failure = parser.finishOutputRecoveryFailure(response, rawStderr);
3479
+ if (options.candidateOutputRecoveryPath === void 0) {
3480
+ throw new Error(
3481
+ "fusion child output-recovery failure omitted the configured artifact path"
3482
+ );
3483
+ }
3484
+ const outputRecovery2 = await readCandidateOutputRecovery(
3485
+ options.candidateOutputRecoveryPath,
3486
+ failure.outputRecovery
3487
+ );
3488
+ const recoveryMessage = outputRecovery2.replacement_json_rendered_bytes === null ? `Pi child could not complete its one allowed same-session candidate compression continuation; the ${String(outputRecovery2.original_json_rendered_bytes)}-byte original is preserved and nothing was truncated` : `Pi child compressed candidate response is still ${String(outputRecovery2.replacement_json_rendered_bytes)} JSON-rendered bytes, exceeding the ${String(outputRecovery2.limit_bytes)}-byte output contract; both responses are preserved and nothing was truncated`;
3489
+ throw new FusionChildRunError(
3490
+ withCleanupErrors(
3491
+ childError(recoveryMessage, "child_output_cap", options),
3492
+ state.cleanupErrors
3493
+ ),
3494
+ failure.events,
3495
+ response,
3496
+ failure.diagnostics,
3497
+ close,
3498
+ {
3499
+ usage: failure.usage,
3500
+ provider: failure.provider,
3501
+ model: failure.model,
3502
+ qualifiedId: failure.qualifiedId
3503
+ },
3504
+ outputRecovery2
3505
+ );
3506
+ } catch (error) {
3507
+ if (error instanceof FusionChildRunError) throw error;
3508
+ throw new FusionChildRunError(
3509
+ withCleanupErrors(
3510
+ childError(
3511
+ `Pi child output-recovery evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
3512
+ "child_event_invalid",
3513
+ options
3514
+ ),
3515
+ state.cleanupErrors
3516
+ ),
3517
+ compactEvents,
3518
+ response,
3519
+ diagnostics,
3520
+ close,
3521
+ observed
3522
+ );
3523
+ }
3524
+ }
3525
+ if (close.code !== 0 || close.signal !== null) {
3526
+ let runtimeGuard;
3527
+ try {
3528
+ runtimeGuard = parseFusionRuntimeGuard(rawStderr);
3529
+ if (runtimeGuard !== void 0) {
3530
+ assertFusionRuntimeGuardMatchesModel(runtimeGuard, options.model);
3531
+ }
3532
+ } catch (error) {
3533
+ throw new FusionChildRunError(
3534
+ withCleanupErrors(
3535
+ childError(
3536
+ `Pi child runtime guard evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
3537
+ "child_event_invalid",
3538
+ options
3539
+ ),
3540
+ state.cleanupErrors
3541
+ ),
3542
+ compactEvents,
3543
+ response,
3544
+ diagnostics,
3545
+ close,
3546
+ observed
3547
+ );
3548
+ }
3549
+ throw new FusionChildRunError(
3550
+ withCleanupErrors(
3551
+ childError(
3552
+ runtimeGuard?.message ?? `Pi child exited with code ${close.code === null ? "null" : String(close.code)}${close.signal === null ? "" : ` (${close.signal})`}`,
3553
+ runtimeGuard === void 0 ? "child_exit_failed" : runtimeGuard.code === "claude_cache_policy" ? "child_cache_policy_invalid" : runtimeGuard.code === "provider_payload_invalid" ? "child_runtime_payload_invalid" : "child_runtime_limit_exceeded",
3554
+ options
3555
+ ),
3556
+ state.cleanupErrors
3557
+ ),
3558
+ compactEvents,
3559
+ response,
3560
+ diagnostics,
3561
+ close,
3562
+ observed
3563
+ );
3564
+ }
3565
+ let parsed;
3566
+ try {
3567
+ parsed = parser.finish(response, rawStderr);
3568
+ } catch (error) {
3569
+ throw new FusionChildRunError(
3570
+ withCleanupErrors(
3571
+ childError(
3572
+ `Pi child compact result invalid: ${error instanceof Error ? error.message : String(error)}`,
3573
+ "child_event_invalid",
3574
+ options
3575
+ ),
3576
+ state.cleanupErrors
3577
+ ),
3578
+ compactEvents,
3579
+ response,
3580
+ diagnostics,
3581
+ close,
3582
+ observed
3583
+ );
3584
+ }
3585
+ let outputRecovery;
3586
+ if (parsed.outputRecovery !== void 0) {
3587
+ if (options.candidateOutputRecoveryPath === void 0) {
3588
+ throw new FusionChildRunError(
3589
+ childError(
3590
+ "Pi child output-recovery evidence has no configured artifact path",
3591
+ "child_event_invalid",
3592
+ options
3593
+ ),
3594
+ parsed.events,
3595
+ response,
3596
+ parsed.diagnostics,
3597
+ close,
3598
+ observed
3599
+ );
3600
+ }
3601
+ try {
3602
+ outputRecovery = await readCandidateOutputRecovery(
3603
+ options.candidateOutputRecoveryPath,
3604
+ parsed.outputRecovery
3605
+ );
3606
+ } catch (error) {
3607
+ throw new FusionChildRunError(
3608
+ childError(
3609
+ `Pi child output-recovery artifact invalid: ${error instanceof Error ? error.message : String(error)}`,
3610
+ "child_event_invalid",
3611
+ options
3612
+ ),
3613
+ parsed.events,
3614
+ response,
3615
+ parsed.diagnostics,
3616
+ close,
3617
+ observed
3618
+ );
3619
+ }
3620
+ }
3621
+ let toolCallTrace;
3622
+ if (capability !== "reason") {
3623
+ const logPath = options.toolCallLogPath;
3624
+ if (logPath === void 0) {
3625
+ throw childError(
3626
+ `fusion ${capability} child completed without a tool-call log path`,
3627
+ "orchestration_failed",
3628
+ options
3629
+ );
3630
+ }
3631
+ try {
3632
+ toolCallTrace = await readFusionToolCallLog(logPath);
3633
+ await assertFusionToolCallLogSeal(logPath, toolCallTrace);
3634
+ await assertCompletedToolPolicy(toolCallTrace, capability, options.sourcePolicy);
3635
+ } catch (error) {
3636
+ throw new FusionChildRunError(
3637
+ withCleanupErrors(
3638
+ childError(
3639
+ `Pi child tool-call log invalid: ${error instanceof Error ? error.message : String(error)}`,
3640
+ "child_event_invalid",
3641
+ options
3642
+ ),
3643
+ state.cleanupErrors
3644
+ ),
3645
+ compactEvents,
3646
+ response,
3647
+ diagnostics,
3648
+ close,
3649
+ observed,
3650
+ outputRecovery
3651
+ );
3652
+ }
3653
+ }
3654
+ const result = {
3655
+ stage: options.stage,
3656
+ attempt: options.attempt,
3657
+ provider: parsed.provider,
3658
+ model: parsed.model,
3659
+ qualifiedId: parsed.qualifiedId,
3660
+ text: parsed.text,
3661
+ usage: parsed.usage,
3662
+ firstRequestUsage: parsed.firstRequestUsage,
3663
+ providerRequestCount: parsed.providerRequestCount,
3664
+ events: parsed.events,
3665
+ stderr: parsed.diagnostics,
3666
+ exitCode: close.code,
3667
+ signal: close.signal
3668
+ };
3669
+ if (options.slot !== void 0) result.slot = options.slot;
3670
+ if (outputRecovery !== void 0) result.outputRecovery = outputRecovery;
3671
+ if (toolCallTrace !== void 0) result.toolCallTrace = toolCallTrace;
3672
+ return result;
3673
+ } finally {
3674
+ cleanupTimers(state);
3675
+ options.signal?.removeEventListener("abort", abortListener);
3676
+ child.stdout?.off("data", stdoutListener);
3677
+ child.stderr?.off("data", stderrListener);
3678
+ child.off("error", errorListener);
3679
+ child.off("close", closeListener);
3680
+ }
3681
+ }
3682
+ __name(runPiChild, "runPiChild");
3683
+
3684
+ // node_modules/pi-background-tasks/src/core/fusion/orchestrator.ts?ext=pi-background-tasks
3685
+ function addFailedChildUsage(target, error) {
3686
+ if (error instanceof FusionChildRunError) addFusionUsage(target, error.usage);
3687
+ }
3688
+ __name(addFailedChildUsage, "addFailedChildUsage");
3689
+ function errorText2(error) {
3690
+ return error instanceof Error ? error.message : String(error);
3691
+ }
3692
+ __name(errorText2, "errorText");
3693
+ function isRecord2(value) {
3694
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3695
+ }
3696
+ __name(isRecord2, "isRecord");
3697
+ function hasOnlyKeys(value, allowed) {
3698
+ const allowedSet = new Set(allowed);
3699
+ return Object.keys(value).every((key) => allowedSet.has(key));
3700
+ }
3701
+ __name(hasOnlyKeys, "hasOnlyKeys");
3702
+ function isStrictCleanCanonicalInput(value) {
3703
+ if (!isRecord2(value)) return false;
3704
+ if (!hasOnlyKeys(value, ["schema_version", "workflow", "cwd", "request", "context"]))
3705
+ return false;
3706
+ const request = value["request"];
3707
+ if (!isRecord2(request)) return false;
3708
+ if (!hasOnlyKeys(request, ["source", "authority", "text", "sha256"])) return false;
3709
+ const context = value["context"];
3710
+ if (!isRecord2(context)) return false;
3711
+ if (!hasOnlyKeys(context, ["kind", "policy_id", "declared_sources"])) return false;
3712
+ if (context["kind"] !== "clean_task") return false;
3713
+ const declaredSources = context["declared_sources"];
3714
+ if (!Array.isArray(declaredSources)) return false;
3715
+ for (const source of declaredSources) {
3716
+ if (!isRecord2(source) || !hasOnlyKeys(source, ["url", "canonical_url", "purpose", "sha256"]))
3717
+ return false;
3718
+ }
3719
+ return true;
3720
+ }
3721
+ __name(isStrictCleanCanonicalInput, "isStrictCleanCanonicalInput");
3722
+ function asFusionError(error, artifactDir, messageOverride) {
3723
+ if (error instanceof FusionError) {
3724
+ const details = {
3725
+ code: error.code,
3726
+ artifactDir,
3727
+ transient: error.transient,
3728
+ childCreated: error.childCreated
3729
+ };
3730
+ if (error.stage !== void 0) details.stage = error.stage;
3731
+ if (error.slot !== void 0) details.slot = error.slot;
3732
+ if (error.attempt !== void 0) details.attempt = error.attempt;
3733
+ if (error.budget !== void 0) details.budget = error.budget;
3734
+ if (error.runProgress !== void 0) details.runProgress = error.runProgress;
3735
+ return new FusionError(messageOverride ?? error.message, details);
3736
+ }
3737
+ return new FusionError(messageOverride ?? errorText2(error), {
3738
+ code: "orchestration_failed",
3739
+ artifactDir,
3740
+ childCreated: false
3741
+ });
3742
+ }
3743
+ __name(asFusionError, "asFusionError");
3744
+ function formatFusionRunStage(name, stage) {
3745
+ const notStarted = stage.not_started_slots === void 0 ? "" : `, ${String(stage.not_started_slots)} slot(s) not started`;
3746
+ return `${name}=${stage.status} (${String(stage.children_created)} created, ${String(stage.children_completed)} completed, ${String(stage.children_failed)} failed, ${String(stage.children_cancelled)} cancelled${notStarted})`;
3747
+ }
3748
+ __name(formatFusionRunStage, "formatFusionRunStage");
3749
+ function summaryUnavailableNote(error) {
3750
+ const detail = errorText2(error);
3751
+ const detailBytes = Buffer.from(detail, "utf8");
3752
+ if (detailBytes.length > 1024) {
3753
+ return "failure-summary.json unavailable after terminal publication; write failure detail omitted because it exceeds the 1024-byte diagnostic cap.";
3754
+ }
3755
+ return `failure-summary.json unavailable after terminal publication: ${detail}`;
3756
+ }
3757
+ __name(summaryUnavailableNote, "summaryUnavailableNote");
3758
+ function withSummaryUnavailableNote(error, summaryError) {
3759
+ const details = {
3760
+ code: error.code,
3761
+ transient: error.transient,
3762
+ childCreated: error.childCreated
3763
+ };
3764
+ if (error.artifactDir !== void 0) details.artifactDir = error.artifactDir;
3765
+ if (error.stage !== void 0) details.stage = error.stage;
3766
+ if (error.slot !== void 0) details.slot = error.slot;
3767
+ if (error.attempt !== void 0) details.attempt = error.attempt;
3768
+ if (error.budget !== void 0) details.budget = error.budget;
3769
+ if (error.runProgress !== void 0) details.runProgress = error.runProgress;
3770
+ return new FusionError(`${error.message}
3771
+ ${summaryUnavailableNote(summaryError)}`, details);
3772
+ }
3773
+ __name(withSummaryUnavailableNote, "withSummaryUnavailableNote");
3774
+ function formatFusionRunProgress(progress) {
3775
+ const usage = progress.usage_so_far;
3776
+ const optionalUsage = [
3777
+ usage.cacheWrite1h === void 0 ? void 0 : `cacheWrite1h=${String(usage.cacheWrite1h)}`,
3778
+ usage.reasoning === void 0 ? void 0 : `reasoning=${String(usage.reasoning)}`
3779
+ ].filter((value) => value !== void 0);
3780
+ const optionalText = optionalUsage.length === 0 ? "" : `, ${optionalUsage.join(", ")}`;
3781
+ return `Run progress from durable attempts: ${formatFusionRunStage("candidates", progress.candidates)}; ${formatFusionRunStage("evaluation", progress.evaluation)}; ${formatFusionRunStage("merge", progress.merge)}. Usage so far: input=${String(usage.input)}, output=${String(usage.output)}, cacheRead=${String(usage.cacheRead)}, cacheWrite=${String(usage.cacheWrite)}${optionalText}, totalTokens=${String(usage.totalTokens)}, cost.input=${String(usage.cost.input)}, cost.output=${String(usage.cost.output)}, cost.cacheRead=${String(usage.cost.cacheRead)}, cost.cacheWrite=${String(usage.cost.cacheWrite)}, cost.total=${String(usage.cost.total)}.`;
3782
+ }
3783
+ __name(formatFusionRunProgress, "formatFusionRunProgress");
3784
+ function withRunProgress(error, artifactDir, progress) {
3785
+ const base = asFusionError(error, artifactDir);
3786
+ const details = {
3787
+ code: base.code,
3788
+ artifactDir,
3789
+ transient: base.transient,
3790
+ childCreated: base.childCreated,
3791
+ runProgress: progress
3792
+ };
3793
+ if (base.stage !== void 0) details.stage = base.stage;
3794
+ if (base.slot !== void 0) details.slot = base.slot;
3795
+ if (base.attempt !== void 0) details.attempt = base.attempt;
3796
+ if (base.budget !== void 0) details.budget = base.budget;
3797
+ return new FusionError(`${base.message}
3798
+ ${formatFusionRunProgress(progress)}`, details);
3799
+ }
3800
+ __name(withRunProgress, "withRunProgress");
3801
+ function withTerminalArtifactFailure(error, artifactDir, artifactError) {
3802
+ const message = `${errorText2(error)}; additionally failed to write terminal fusion artifacts: ${errorText2(artifactError)}`;
3803
+ return asFusionError(error, artifactDir, message);
3804
+ }
3805
+ __name(withTerminalArtifactFailure, "withTerminalArtifactFailure");
3806
+ function recordFailureInput(error, stage, slot, attempt, systemPrompt, prompt, responseKind) {
3807
+ if (error instanceof FusionChildRunError) {
3808
+ const base2 = {
3809
+ stage,
3810
+ attempt,
3811
+ systemPrompt,
3812
+ prompt,
3813
+ events: error.events,
3814
+ partialResponse: error.response,
3815
+ stderr: error.stderr,
3816
+ error: error.message,
3817
+ status: error.code === "child_cancelled" ? "cancelled" : "failed",
3818
+ responseKind,
3819
+ childCreated: error.childCreated,
3820
+ usage: error.usage,
3821
+ ...error.outputRecovery === void 0 ? {} : { outputRecovery: error.outputRecovery }
3822
+ };
3823
+ if (slot !== void 0) base2.slot = slot;
3824
+ if (error.provider !== void 0) base2.provider = error.provider;
3825
+ if (error.modelName !== void 0) base2.model = error.modelName;
3826
+ if (error.qualifiedId !== void 0) base2.qualifiedId = error.qualifiedId;
3827
+ return base2;
3828
+ }
3829
+ const base = {
3830
+ stage,
3831
+ attempt,
3832
+ systemPrompt,
3833
+ prompt,
3834
+ events: Buffer.alloc(0),
3835
+ partialResponse: Buffer.alloc(0),
3836
+ stderr: Buffer.alloc(0),
3837
+ error: errorText2(error),
3838
+ status: error instanceof FusionError && error.code === "child_cancelled" ? "cancelled" : "failed",
3839
+ responseKind,
3840
+ childCreated: error instanceof FusionError ? error.childCreated : false
3841
+ };
3842
+ if (slot !== void 0) base.slot = slot;
3843
+ return base;
3844
+ }
3845
+ __name(recordFailureInput, "recordFailureInput");
3846
+ function retryableSpawn(error, attempt) {
3847
+ if (!(error instanceof FusionError)) return false;
3848
+ return attempt === 1 && error.code === "child_spawn_failed" && error.transient && !error.childCreated;
3849
+ }
3850
+ __name(retryableSpawn, "retryableSpawn");
3851
+ function childOptions(input, model, stage, attempt, capability, systemPrompt, userPrompt, signal, slot, toolCallLogPath, sourcePolicy, candidateOutputRecoveryPath) {
3852
+ const out = {
3853
+ stage,
3854
+ attempt,
3855
+ cwd: input.cwd,
3856
+ model,
3857
+ capability,
3858
+ systemPrompt,
3859
+ userPrompt,
3860
+ signal
3861
+ };
3862
+ if (slot !== void 0) out.slot = slot;
3863
+ if (toolCallLogPath !== void 0) out.toolCallLogPath = toolCallLogPath;
3864
+ if (sourcePolicy !== void 0) out.sourcePolicy = sourcePolicy;
3865
+ if (candidateOutputRecoveryPath !== void 0)
3866
+ out.candidateOutputRecoveryPath = candidateOutputRecoveryPath;
3867
+ return out;
3868
+ }
3869
+ __name(childOptions, "childOptions");
3870
+ function parseEvaluationAttempt(text, expectedValidationFindings) {
3871
+ let parsed;
3872
+ try {
3873
+ parsed = parseJsonText(text);
3874
+ } catch (error) {
3875
+ return {
3876
+ evaluation: void 0,
3877
+ errors: [`evaluation output must be JSON only: ${errorText2(error)}`]
3878
+ };
3879
+ }
3880
+ const result = validateFusionEvaluation(parsed);
3881
+ if (!result.ok) return { evaluation: void 0, errors: result.errors };
3882
+ if (expectedValidationFindings === void 0 && result.value.validation_accounting !== void 0) {
3883
+ return {
3884
+ evaluation: void 0,
3885
+ errors: ["evaluation.validation_accounting is permitted only for fusion_validate"]
3886
+ };
3887
+ }
3888
+ if (expectedValidationFindings !== void 0) {
3889
+ const accountingErrors = validateEvaluationAccountsForSourceFindings(
3890
+ result.value,
3891
+ expectedValidationFindings
3892
+ );
3893
+ if (accountingErrors.length > 0) return { evaluation: void 0, errors: accountingErrors };
3894
+ }
3895
+ return { evaluation: result.value, errors: [] };
3896
+ }
3897
+ __name(parseEvaluationAttempt, "parseEvaluationAttempt");
3898
+ function randomIndex(limit, randomBytes) {
3899
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 4294967295) {
3900
+ throw new FusionError(`invalid random limit ${String(limit)}`, {
3901
+ code: "orchestration_failed",
3902
+ childCreated: false
3903
+ });
3904
+ }
3905
+ const range = 4294967296;
3906
+ const ceiling = range - range % limit;
3907
+ for (; ; ) {
3908
+ const bytes = randomBytes(4);
3909
+ if (bytes.length < 4) {
3910
+ throw new FusionError("random byte source returned too few bytes", {
3911
+ code: "orchestration_failed",
3912
+ childCreated: false
3913
+ });
3914
+ }
3915
+ const value = bytes.readUInt32BE(0);
3916
+ if (value < ceiling) return value % limit;
3917
+ }
3918
+ }
3919
+ __name(randomIndex, "randomIndex");
3920
+ function shuffledSlots(randomBytes) {
3921
+ const slots = [1, 2, 3];
3922
+ for (let i = slots.length - 1; i > 0; i--) {
3923
+ const j = randomIndex(i + 1, randomBytes);
3924
+ const left = slots[i];
3925
+ const right = slots[j];
3926
+ if (left === void 0 || right === void 0) {
3927
+ throw new FusionError("random slot shuffle failed", {
3928
+ code: "orchestration_failed",
3929
+ childCreated: false
3930
+ });
3931
+ }
3932
+ slots[i] = right;
3933
+ slots[j] = left;
3934
+ }
3935
+ return slots;
3936
+ }
3937
+ __name(shuffledSlots, "shuffledSlots");
3938
+ function candidateBySlot(results, slot) {
3939
+ const found = results.find((candidate) => candidate.slot === slot);
3940
+ if (found === void 0) {
3941
+ throw new FusionError(`candidate slot ${String(slot)} is missing`, {
3942
+ code: "orchestration_failed",
3943
+ childCreated: false
3944
+ });
3945
+ }
3946
+ return found.result;
3947
+ }
3948
+ __name(candidateBySlot, "candidateBySlot");
3949
+ function candidateModel(models, slot) {
3950
+ if (slot === 1) return models.candidates[0];
3951
+ if (slot === 2) return models.candidates[1];
3952
+ return models.candidates[2];
3953
+ }
3954
+ __name(candidateModel, "candidateModel");
3955
+ function anonymousCandidates(results, slots) {
3956
+ const firstSlot = slots[0];
3957
+ const secondSlot = slots[1];
3958
+ const thirdSlot = slots[2];
3959
+ if (firstSlot === void 0 || secondSlot === void 0 || thirdSlot === void 0) {
3960
+ throw new FusionError("anonymous candidate shuffle produced too few slots", {
3961
+ code: "orchestration_failed",
3962
+ childCreated: false
3963
+ });
3964
+ }
3965
+ const first = candidateBySlot(results, firstSlot);
3966
+ const second = candidateBySlot(results, secondSlot);
3967
+ const third = candidateBySlot(results, thirdSlot);
3968
+ return {
3969
+ map: { A: firstSlot, B: secondSlot, C: thirdSlot },
3970
+ candidates: [
3971
+ { candidate_id: "A", response: first.text },
3972
+ { candidate_id: "B", response: second.text },
3973
+ { candidate_id: "C", response: third.text }
3974
+ ]
3975
+ };
3976
+ }
3977
+ __name(anonymousCandidates, "anonymousCandidates");
3978
+ function sha256Text(value) {
3979
+ return createHash4("sha256").update(value, "utf8").digest("hex");
3980
+ }
3981
+ __name(sha256Text, "sha256Text");
3982
+ function boundedContractError(error) {
3983
+ const value = errorText2(error);
3984
+ return value.length <= 1e3 ? value : `${value.slice(0, 999)}\u2026`;
3985
+ }
3986
+ __name(boundedContractError, "boundedContractError");
3987
+ async function prepareValidationSourceData(candidates, anonymousMap, store) {
3988
+ const prepared = candidates.map((candidate) => ({ ...candidate }));
3989
+ const findings = [];
3990
+ const verified = [];
3991
+ const limitations = [];
3992
+ let normalizationCount = 0;
3993
+ const failures = [];
3994
+ for (const candidate of prepared) {
3995
+ try {
3996
+ const report = parseFusionValidationCandidateReport(
3997
+ candidate.response,
3998
+ candidate.candidate_id
3999
+ );
4000
+ findings.push(...report.findings);
4001
+ verified.push(...report.verified);
4002
+ limitations.push(...report.limitations);
4003
+ continue;
4004
+ } catch (strictError) {
4005
+ try {
4006
+ const recovered = recoverFencedFusionValidationCandidateReport(
4007
+ candidate.response,
4008
+ candidate.candidate_id
4009
+ );
4010
+ if (recovered === void 0) throw strictError;
4011
+ await store.recordValidationCandidateContractEvent({
4012
+ candidateId: candidate.candidate_id,
4013
+ slot: anonymousMap[candidate.candidate_id],
4014
+ status: "normalized",
4015
+ detail: {
4016
+ normalization: recovered.normalization,
4017
+ original_sha256: sha256Text(candidate.response),
4018
+ forwarded_sha256: sha256Text(recovered.response),
4019
+ warning: "Candidate output violated the bare-JSON contract; a single complete JSON fence was removed and recorded."
4020
+ }
4021
+ });
4022
+ candidate.response = recovered.response;
4023
+ findings.push(...recovered.report.findings);
4024
+ verified.push(...recovered.report.verified);
4025
+ limitations.push(...recovered.report.limitations);
4026
+ normalizationCount += 1;
4027
+ continue;
4028
+ } catch (recoveryError) {
4029
+ failures.push({
4030
+ candidate,
4031
+ error: boundedContractError(recoveryError === strictError ? strictError : recoveryError)
4032
+ });
4033
+ }
4034
+ }
4035
+ }
4036
+ if (normalizationCount > 0) {
4037
+ limitations.push(
4038
+ `${String(normalizationCount)} validation report${normalizationCount === 1 ? "" : "s"} required audited removal of a Markdown JSON wrapper; JSON content was unchanged.`
4039
+ );
4040
+ }
4041
+ for (const failure2 of failures) {
4042
+ await store.recordValidationCandidateContractEvent({
4043
+ candidateId: failure2.candidate.candidate_id,
4044
+ slot: anonymousMap[failure2.candidate.candidate_id],
4045
+ status: "dropped",
4046
+ detail: {
4047
+ response_sha256: sha256Text(failure2.candidate.response),
4048
+ error: failure2.error,
4049
+ warning: "Candidate output could not be parsed under the strict or fenced-JSON contract."
4050
+ }
4051
+ });
4052
+ }
4053
+ if (failures.length > 1) {
4054
+ throw new FusionError(
4055
+ `fusion_validate cannot continue: ${String(failures.length)} of 3 candidate reports violated the structured-output contract`,
4056
+ { code: "evaluation_invalid", stage: "candidate" }
4057
+ );
4058
+ }
4059
+ const failure = failures[0];
4060
+ if (failure !== void 0) {
4061
+ const synthetic = canonicalJson({
4062
+ schema_version: FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
4063
+ findings: [],
4064
+ verified: [],
4065
+ limitations: [
4066
+ "This validation report could not be parsed after strict contract checks; no findings or verification claims from it were included."
4067
+ ]
4068
+ });
4069
+ failure.candidate.response = synthetic;
4070
+ const report = parseFusionValidationCandidateReport(synthetic, failure.candidate.candidate_id);
4071
+ limitations.push(...report.limitations);
4072
+ }
4073
+ return { candidates: prepared, findings, verified, limitations };
4074
+ }
4075
+ __name(prepareValidationSourceData, "prepareValidationSourceData");
4076
+ function validateEvaluationAccountsForSourceFindings(evaluation, sourceFindings) {
4077
+ const errors = [];
4078
+ const accounting = evaluation.validation_accounting;
4079
+ if (accounting === void 0) {
4080
+ return ["validation evaluator output must include validation_accounting"];
4081
+ }
4082
+ const expected = sourceFindings.map((finding) => canonicalJson(finding)).sort();
4083
+ const actual = accounting.findings.map((finding) => canonicalJson(finding)).sort();
4084
+ if (expected.length !== actual.length || expected.some((value, index) => value !== actual[index])) {
4085
+ errors.push(
4086
+ "validation evaluator validation_accounting.findings must exactly equal host-assigned source findings"
4087
+ );
4088
+ }
4089
+ errors.push(...validateFusionFindingAccounting(accounting));
4090
+ return errors;
4091
+ }
4092
+ __name(validateEvaluationAccountsForSourceFindings, "validateEvaluationAccountsForSourceFindings");
4093
+ function resolveRunProfile(input) {
4094
+ if (input.profile !== void 0) return fusionWorkflowProfile(input.profile.id);
4095
+ const workflow = input.canonicalInput.workflow;
4096
+ const contextKind = input.canonicalInput.context?.kind;
4097
+ if (workflow !== void 0 && workflow !== "reason") {
4098
+ throw new FusionError(`fusion workflow profile is required for ${workflow} runs`, {
4099
+ code: "orchestration_failed",
4100
+ childCreated: false
4101
+ });
4102
+ }
4103
+ if (contextKind === "clean_task") {
4104
+ throw new FusionError("fusion workflow profile is required for clean-task runs", {
4105
+ code: "orchestration_failed",
4106
+ childCreated: false
4107
+ });
4108
+ }
4109
+ return fusionWorkflowProfile("reason");
4110
+ }
4111
+ __name(resolveRunProfile, "resolveRunProfile");
4112
+ var FusionOrchestrator = class {
4113
+ static {
4114
+ __name(this, "FusionOrchestrator");
4115
+ }
4116
+ childRunner;
4117
+ randomBytes;
4118
+ now;
4119
+ createArtifactStore;
4120
+ constructor(options = {}) {
4121
+ this.childRunner = options.childRunner ?? runPiChild;
4122
+ this.randomBytes = options.randomBytes ?? nodeRandomBytes;
4123
+ this.now = options.now;
4124
+ this.createArtifactStore = options.createArtifactStore ?? FusionArtifactStore.create;
4125
+ }
4126
+ async run(input) {
4127
+ if (input.canonicalInput.schema_version !== FUSION_INPUT_SCHEMA_VERSION) {
4128
+ throw new FusionError("fusion orchestrator accepts only v5 canonical input", {
4129
+ code: "orchestration_failed",
4130
+ childCreated: false
4131
+ });
4132
+ }
4133
+ const profile = resolveRunProfile(input);
4134
+ const inputWorkflow = input.canonicalInput.workflow ?? profile.id;
4135
+ const inputContextKind = input.canonicalInput.context?.kind ?? "session_projection";
4136
+ if (inputWorkflow !== profile.id || inputContextKind !== profile.contextKind) {
4137
+ throw new FusionError(
4138
+ `fusion workflow profile ${profile.id} is incompatible with canonical input workflow=${String(inputWorkflow)} context=${String(inputContextKind)}`,
4139
+ { code: "orchestration_failed", childCreated: false }
4140
+ );
4141
+ }
4142
+ if (profile.contextKind === "clean_task" && !isStrictCleanCanonicalInput(input.canonicalInput)) {
4143
+ throw new FusionError(
4144
+ "clean-task fusion input must not carry parent context fields and must match the strict clean canonical shape",
4145
+ {
4146
+ code: "orchestration_failed",
4147
+ childCreated: false
4148
+ }
4149
+ );
4150
+ }
4151
+ const candidateCapability = assertWorkflowCapability(profile, input.candidateCapability);
4152
+ const storeOptions = {
4153
+ cwd: input.cwd,
4154
+ profile,
4155
+ source: input.source,
4156
+ config: input.config,
4157
+ models: input.models,
4158
+ capabilities: {
4159
+ candidate: candidateCapability,
4160
+ evaluation: FUSION_NO_TOOLS_CAPABILITY,
4161
+ merge: FUSION_NO_TOOLS_CAPABILITY
4162
+ }
4163
+ };
4164
+ if (input.sessionId !== void 0) storeOptions.sessionId = input.sessionId;
4165
+ if (this.now !== void 0) storeOptions.now = this.now;
4166
+ let serializedParsed;
4167
+ try {
4168
+ serializedParsed = parseJsonText(input.canonicalInputSerialized);
4169
+ } catch (error) {
4170
+ throw new FusionError(
4171
+ `fusion canonical input artifact is not valid JSON: ${errorText2(error)}`,
4172
+ {
4173
+ code: "orchestration_failed",
4174
+ childCreated: false
4175
+ }
4176
+ );
4177
+ }
4178
+ if (canonicalJson(serializedParsed) !== canonicalJson(input.canonicalInput)) {
4179
+ throw new FusionError(
4180
+ "fusion canonical input serialized bytes do not match canonical input object",
4181
+ {
4182
+ code: "orchestration_failed",
4183
+ childCreated: false
4184
+ }
4185
+ );
4186
+ }
4187
+ const store = await this.createArtifactStore(storeOptions);
4188
+ input.onProgress?.({ type: "state", state: "initializing" });
4189
+ const usage = createEmptyFusionUsage();
4190
+ const calibrationWarnings = [];
4191
+ try {
4192
+ await store.writeCanonicalInput(input.canonicalInputSerialized);
4193
+ if (inputContextKind === "session_projection") {
4194
+ if (input.contextLedger === void 0) {
4195
+ throw new FusionError(
4196
+ "session-projection fusion input requires an omission ledger artifact",
4197
+ {
4198
+ code: "orchestration_failed",
4199
+ childCreated: false
4200
+ }
4201
+ );
4202
+ }
4203
+ await store.writeContextLedger(input.contextLedger);
4204
+ } else if (input.contextLedger !== void 0) {
4205
+ throw new FusionError("clean-task fusion input must not carry a parent omission ledger", {
4206
+ code: "orchestration_failed",
4207
+ childCreated: false
4208
+ });
4209
+ }
4210
+ if (profile.id === "research") {
4211
+ const cleanContext = input.canonicalInput.context;
4212
+ if (cleanContext?.kind !== "clean_task") {
4213
+ throw new FusionError("research workflow requires a clean-task canonical input", {
4214
+ code: "orchestration_failed",
4215
+ childCreated: false
4216
+ });
4217
+ }
4218
+ const policy = buildFusionSourcePolicy(input.cwd, cleanContext.declared_sources);
4219
+ await store.writeSourcePolicy(sourcePolicyCanonicalBytes(policy));
4220
+ }
4221
+ const budget = new FusionBudget(
4222
+ input.models,
4223
+ input.canonicalInput.context?.policy_id ?? "fusion-session-projection-v1",
4224
+ candidateCapability,
4225
+ profile
4226
+ );
4227
+ const budgetPlan = budget.plan(input.canonicalInput);
4228
+ await store.writeBudgetPlan(budgetPlan);
4229
+ budget.assertPlanFits(budgetPlan, store.artifactDir);
4230
+ if (budgetPlan.warnings.length > 0) {
4231
+ input.onProgress?.({
4232
+ type: "budget_warning",
4233
+ warnings: budgetPlan.warnings,
4234
+ error: "fusion budget utilization warning"
4235
+ });
4236
+ }
4237
+ await input.onReady?.({
4238
+ runId: store.runId,
4239
+ artifactDir: store.artifactDir,
4240
+ artifactDirAbs: store.artifactDirAbs
4241
+ });
4242
+ if (input.signal?.aborted === true) {
4243
+ throw new FusionError("fusion run cancelled before launch", {
4244
+ code: "child_cancelled",
4245
+ childCreated: false
4246
+ });
4247
+ }
4248
+ await store.transition("candidates_running");
4249
+ input.onProgress?.({ type: "state", state: "candidates_running" });
4250
+ const candidateResults = await this.runCandidates(
4251
+ input,
4252
+ store,
4253
+ usage,
4254
+ budget,
4255
+ calibrationWarnings,
4256
+ profile,
4257
+ candidateCapability
4258
+ );
4259
+ await store.transition("candidates_complete");
4260
+ input.onProgress?.({ type: "state", state: "candidates_complete" });
4261
+ const shuffled = anonymousCandidates(candidateResults, shuffledSlots(this.randomBytes));
4262
+ await store.setAnonymousMap(shuffled.map);
4263
+ const validationData = profile.id === "validate" ? await prepareValidationSourceData(shuffled.candidates, shuffled.map, store) : void 0;
4264
+ const evaluationCandidates = validationData?.candidates ?? shuffled.candidates;
4265
+ const blindInput = buildBlindEvaluationInput(
4266
+ input.canonicalInput,
4267
+ evaluationCandidates,
4268
+ validationData?.findings
4269
+ );
4270
+ await store.writeBlindCandidates(buildEvaluationPrompt(blindInput));
4271
+ await store.transition("evaluating");
4272
+ input.onProgress?.({ type: "state", state: "evaluating" });
4273
+ const evaluation = await this.runEvaluation(
4274
+ input,
4275
+ store,
4276
+ usage,
4277
+ blindInput,
4278
+ budget,
4279
+ calibrationWarnings,
4280
+ profile,
4281
+ validationData?.findings
4282
+ );
4283
+ await store.writeEvaluationJson(evaluation);
4284
+ await store.transition("evaluation_complete");
4285
+ input.onProgress?.({ type: "state", state: "evaluation_complete" });
4286
+ await store.transition("merging");
4287
+ input.onProgress?.({ type: "state", state: "merging" });
4288
+ const mergeInput = buildMergeInput(input.canonicalInput, evaluationCandidates, evaluation);
4289
+ const mergePrompt = buildMergePrompt(mergeInput);
4290
+ budget.assertStagePrompt("merge", profile.mergerSystemPrompt, mergePrompt);
4291
+ input.onProgress?.({ type: "merge_started" });
4292
+ const merged = await this.runChildWithRetry(
4293
+ input,
4294
+ store,
4295
+ usage,
4296
+ input.models.merger,
4297
+ "merge",
4298
+ profile.mergerSystemPrompt,
4299
+ mergePrompt,
4300
+ input.signal ?? new AbortController().signal,
4301
+ FUSION_NO_TOOLS_CAPABILITY,
4302
+ void 0,
4303
+ "md"
4304
+ );
4305
+ addFusionUsage(usage, merged.usage);
4306
+ await store.recordChildAttempt({
4307
+ result: merged,
4308
+ systemPrompt: profile.mergerSystemPrompt,
4309
+ prompt: mergePrompt,
4310
+ responseKind: "md"
4311
+ });
4312
+ await this.recordCalibrationObservation(
4313
+ input,
4314
+ store,
4315
+ budget,
4316
+ calibrationWarnings,
4317
+ "merge",
4318
+ profile.mergerSystemPrompt,
4319
+ mergePrompt,
4320
+ merged
4321
+ );
4322
+ assertChildOutputWithinContract("merge", merged.text);
4323
+ let finalMergedText = merged.text;
4324
+ if (profile.id === "validate") {
4325
+ const accounting = evaluation.validation_accounting;
4326
+ if (accounting === void 0) {
4327
+ throw new FusionError(
4328
+ "fusion_validate evaluation completed without validation accounting",
4329
+ {
4330
+ code: "evaluation_invalid",
4331
+ stage: "merge"
4332
+ }
4333
+ );
4334
+ }
4335
+ finalMergedText = renderValidatedFusionValidationReport(accounting, validationData);
4336
+ }
4337
+ if (finalMergedText !== merged.text)
4338
+ assertChildOutputWithinContract("merge", finalMergedText);
4339
+ const mergedRef = await store.writeMerged(finalMergedText);
4340
+ await store.setUsage(usage);
4341
+ const details = {
4342
+ schema_version: FUSION_RESULT_SCHEMA_VERSION,
4343
+ run_id: store.runId,
4344
+ workflow: profile.id,
4345
+ source: input.source,
4346
+ status: "completed",
4347
+ artifact_dir: store.artifactDir,
4348
+ context: {
4349
+ kind: inputContextKind,
4350
+ policy_id: input.canonicalInput.context?.policy_id ?? "fusion-session-projection-v1"
4351
+ },
4352
+ tool_policy: {
4353
+ candidate_tools: profile.candidateTools,
4354
+ evaluation_tools: [],
4355
+ merge_tools: []
4356
+ },
4357
+ models: store.snapshot().models,
4358
+ evaluator_attempts: store.snapshot().attempts.filter((attempt) => attempt.stage === "evaluation").length,
4359
+ usage,
4360
+ budget: {
4361
+ policy_id: FUSION_BUDGET_POLICY.id,
4362
+ calibration_version: budgetPlan.policy.calibration_version,
4363
+ route_table: budget.routes,
4364
+ rate_sources: budget.resultRateSources,
4365
+ unknown_provider_warnings: budget.unknownProviderWarnings,
4366
+ calibration_warnings: calibrationWarnings
4367
+ }
4368
+ };
4369
+ await store.writeCommittedResult(mergedRef, details);
4370
+ await store.transition("completed");
4371
+ input.onProgress?.({ type: "completed", runId: store.runId, artifactDir: store.artifactDir });
4372
+ return { mergedText: finalMergedText, details };
4373
+ } catch (error) {
4374
+ const cancelled = input.signal?.aborted === true || error instanceof FusionError && error.code === "child_cancelled";
4375
+ let terminalError;
4376
+ try {
4377
+ await store.setUsage(usage);
4378
+ terminalError = withRunProgress(
4379
+ error,
4380
+ store.artifactDir,
4381
+ buildFusionRunProgress(store.snapshot())
4382
+ );
4383
+ const terminalState = cancelled ? "cancelled" : "failed";
4384
+ await store.writeError(terminalState, terminalError.message);
4385
+ try {
4386
+ const terminalManifest = store.snapshot();
4387
+ await store.writeFailureSummary(
4388
+ buildFusionFailureSummary({
4389
+ manifest: terminalManifest,
4390
+ terminalError,
4391
+ progress: buildFusionRunProgress(terminalManifest),
4392
+ terminalState,
4393
+ createdAt: terminalManifest.updated_at
4394
+ })
4395
+ );
4396
+ } catch (summaryError) {
4397
+ terminalError = withSummaryUnavailableNote(terminalError, summaryError);
4398
+ }
4399
+ } catch (artifactError) {
4400
+ throw withTerminalArtifactFailure(error, store.artifactDir, artifactError);
4401
+ }
4402
+ if (cancelled) {
4403
+ input.onProgress?.({
4404
+ type: "cancelled",
4405
+ runId: store.runId,
4406
+ artifactDir: store.artifactDir,
4407
+ reason: terminalError.message
4408
+ });
4409
+ } else {
4410
+ input.onProgress?.({
4411
+ type: "failed",
4412
+ runId: store.runId,
4413
+ artifactDir: store.artifactDir,
4414
+ error: terminalError.message
4415
+ });
4416
+ }
4417
+ throw terminalError;
4418
+ }
4419
+ }
4420
+ async runCandidates(input, store, usage, budget, calibrationWarnings, profile, candidateCapability) {
4421
+ const controller = new AbortController();
4422
+ const abortListener = /* @__PURE__ */ __name(() => controller.abort(), "abortListener");
4423
+ input.signal?.addEventListener("abort", abortListener, { once: true });
4424
+ if (input.signal?.aborted) controller.abort();
4425
+ const systemPrompt = profile.candidateSystemPrompt(candidateCapability);
4426
+ const prompt = buildCandidatePrompt(input.canonicalInput);
4427
+ for (const slot of [1, 2, 3]) {
4428
+ budget.assertStagePrompt("candidate", systemPrompt, prompt, slot);
4429
+ }
4430
+ let primaryError;
4431
+ let completed = 0;
4432
+ try {
4433
+ if (controller.signal.aborted) {
4434
+ throw new FusionError("fusion candidate wave cancelled before launch", {
4435
+ code: "child_cancelled",
4436
+ stage: "candidate",
4437
+ childCreated: false
4438
+ });
4439
+ }
4440
+ const tasks = [1, 2, 3].map((slot) => {
4441
+ const model = candidateModel(input.models, slot);
4442
+ const task = this.runChildWithRetry(
4443
+ input,
4444
+ store,
4445
+ usage,
4446
+ model,
4447
+ "candidate",
4448
+ systemPrompt,
4449
+ prompt,
4450
+ controller.signal,
4451
+ candidateCapability,
4452
+ slot,
4453
+ profile.id === "validate" ? "txt" : "md"
4454
+ ).then(async (result) => {
4455
+ await store.recordChildAttempt({
4456
+ result,
4457
+ systemPrompt,
4458
+ prompt,
4459
+ responseKind: profile.id === "validate" ? "txt" : "md"
4460
+ });
4461
+ await this.recordCalibrationObservation(
4462
+ input,
4463
+ store,
4464
+ budget,
4465
+ calibrationWarnings,
4466
+ "candidate",
4467
+ systemPrompt,
4468
+ prompt,
4469
+ result,
4470
+ slot
4471
+ );
4472
+ addFusionUsage(usage, result.usage);
4473
+ await store.setUsage(usage);
4474
+ assertChildOutputWithinContract("candidate", result.text);
4475
+ completed += 1;
4476
+ input.onProgress?.({ type: "candidate_completed", slot, completed, total: 3 });
4477
+ return { slot, result };
4478
+ });
4479
+ return task.catch((error) => {
4480
+ if (primaryError === void 0) {
4481
+ primaryError = error;
4482
+ controller.abort();
4483
+ }
4484
+ throw error;
4485
+ });
4486
+ });
4487
+ const settled = await Promise.allSettled(tasks);
4488
+ if (primaryError !== void 0) throw primaryError;
4489
+ const results = [];
4490
+ for (const item of settled) {
4491
+ if (item.status === "fulfilled") results.push(item.value);
4492
+ else throw item.reason;
4493
+ }
4494
+ return results.sort((left, right) => left.slot - right.slot);
4495
+ } finally {
4496
+ input.signal?.removeEventListener("abort", abortListener);
4497
+ }
4498
+ }
4499
+ async runEvaluation(input, store, usage, blindInput, budget, calibrationWarnings, profile, expectedValidationFindings) {
4500
+ const firstPrompt = buildEvaluationPrompt(blindInput);
4501
+ budget.assertStagePrompt("evaluation", profile.evaluatorSystemPrompt, firstPrompt);
4502
+ const first = await this.runEvaluationAttempt(
4503
+ input,
4504
+ store,
4505
+ usage,
4506
+ budget,
4507
+ calibrationWarnings,
4508
+ firstPrompt,
4509
+ 1,
4510
+ false,
4511
+ profile,
4512
+ expectedValidationFindings
4513
+ );
4514
+ if (first.evaluation !== void 0) return first.evaluation;
4515
+ const errors = boundedEvaluationErrors(first.errors);
4516
+ input.onProgress?.({ type: "evaluation_retry", errors });
4517
+ const repairPrompt = buildEvaluationRepairPrompt({
4518
+ schema_version: "pi-background-tasks.fusion-evaluation-repair-input.v1",
4519
+ original_blind_input: blindInput,
4520
+ invalid_output: first.result.text,
4521
+ validation_errors: errors
4522
+ });
4523
+ budget.assertStagePrompt(
4524
+ "evaluation_repair",
4525
+ profile.evaluationRepairSystemPrompt,
4526
+ repairPrompt
4527
+ );
4528
+ const second = await this.runEvaluationAttempt(
4529
+ input,
4530
+ store,
4531
+ usage,
4532
+ budget,
4533
+ calibrationWarnings,
4534
+ repairPrompt,
4535
+ 2,
4536
+ true,
4537
+ profile,
4538
+ expectedValidationFindings
4539
+ );
4540
+ if (second.evaluation !== void 0) return second.evaluation;
4541
+ throw new FusionError(
4542
+ `evaluation schema repair failed: ${formatEvaluationErrors(second.errors)}`,
4543
+ {
4544
+ code: "evaluation_invalid",
4545
+ stage: "evaluation",
4546
+ attempt: 2
4547
+ }
4548
+ );
4549
+ }
4550
+ async runEvaluationAttempt(input, store, usage, budget, calibrationWarnings, prompt, attempt, repair, profile, expectedValidationFindings) {
4551
+ input.onProgress?.({ type: "evaluation_started", attempt, repair });
4552
+ const systemPrompt = repair ? profile.evaluationRepairSystemPrompt : profile.evaluatorSystemPrompt;
4553
+ const result = await this.runChildWithRetry(
4554
+ input,
4555
+ store,
4556
+ usage,
4557
+ input.models.evaluator,
4558
+ "evaluation",
4559
+ systemPrompt,
4560
+ prompt,
4561
+ input.signal ?? new AbortController().signal,
4562
+ FUSION_NO_TOOLS_CAPABILITY,
4563
+ void 0,
4564
+ "txt",
4565
+ attempt
4566
+ );
4567
+ addFusionUsage(usage, result.usage);
4568
+ await store.recordChildAttempt({ result, systemPrompt, prompt, responseKind: "txt" });
4569
+ await this.recordCalibrationObservation(
4570
+ input,
4571
+ store,
4572
+ budget,
4573
+ calibrationWarnings,
4574
+ "evaluation",
4575
+ systemPrompt,
4576
+ prompt,
4577
+ result
4578
+ );
4579
+ await store.setUsage(usage);
4580
+ assertChildOutputWithinContract("evaluation", result.text);
4581
+ const parsed = parseEvaluationAttempt(result.text, expectedValidationFindings);
4582
+ return { result, evaluation: parsed.evaluation, errors: parsed.errors };
4583
+ }
4584
+ async recordCalibrationObservation(input, store, budget, calibrationWarnings, stage, systemPrompt, userPrompt, result, slot) {
4585
+ const violation = budget.calibrationViolationForCompletedChild(
4586
+ stage,
4587
+ systemPrompt,
4588
+ userPrompt,
4589
+ result,
4590
+ slot
4591
+ );
4592
+ if (violation === void 0) return;
4593
+ calibrationWarnings.push(violation);
4594
+ let artifact = "calibration-violation artifact was not written";
4595
+ try {
4596
+ const ref = await store.recordCalibrationViolation({
4597
+ stage,
4598
+ attempt: result.attempt,
4599
+ violation,
4600
+ ...slot === void 0 ? {} : { slot }
4601
+ });
4602
+ artifact = ref.path;
4603
+ } catch (error) {
4604
+ artifact = `calibration-violation artifact write failed: ${errorText2(error)}`;
4605
+ }
4606
+ input.onProgress?.({ type: "calibration_warning", warning: violation, artifact });
4607
+ }
4608
+ async runChildWithRetry(input, store, usage, model, stage, systemPrompt, userPrompt, signal, capability, slot, responseKind, fixedAttempt) {
4609
+ const logicalAttempt = fixedAttempt ?? 1;
4610
+ for (let launchTry = 1; launchTry <= 2; launchTry++) {
4611
+ if (stage === "candidate" && slot !== void 0) {
4612
+ input.onProgress?.({ type: "candidate_started", slot, attempt: logicalAttempt });
4613
+ }
4614
+ const toolCallLogPath = capability !== "reason" ? store.childToolCallLogPath(stage, slot, logicalAttempt) : void 0;
4615
+ const sourcePolicy = capability === "research" ? store.sourcePolicyLaunchReference() : void 0;
4616
+ const candidateOutputRecoveryPath = stage === "candidate" && slot !== void 0 ? store.childOutputRecoveryPath(slot, logicalAttempt, responseKind) : void 0;
4617
+ try {
4618
+ return await this.childRunner(
4619
+ childOptions(
4620
+ input,
4621
+ model,
4622
+ stage,
4623
+ logicalAttempt,
4624
+ capability,
4625
+ systemPrompt,
4626
+ userPrompt,
4627
+ signal,
4628
+ slot,
4629
+ toolCallLogPath,
4630
+ sourcePolicy,
4631
+ candidateOutputRecoveryPath
4632
+ )
4633
+ );
4634
+ } catch (error) {
4635
+ if (!signal.aborted && retryableSpawn(error, launchTry) && launchTry === 1) continue;
4636
+ addFailedChildUsage(usage, error);
4637
+ await store.recordFailedAttempt(
4638
+ recordFailureInput(
4639
+ error,
4640
+ stage,
4641
+ slot,
4642
+ logicalAttempt,
4643
+ systemPrompt,
4644
+ userPrompt,
4645
+ responseKind
4646
+ )
4647
+ );
4648
+ await store.setUsage(usage);
4649
+ throw error;
4650
+ }
4651
+ }
4652
+ const details = {
4653
+ code: "orchestration_failed",
4654
+ stage,
4655
+ childCreated: false
4656
+ };
4657
+ if (slot !== void 0) details.slot = slot;
4658
+ throw new FusionError(`${stage} child did not produce a result`, details);
4659
+ }
4660
+ };
4661
+ export {
4662
+ FusionOrchestrator,
4663
+ buildFusionRunProgress,
4664
+ summaryUnavailableNote
4665
+ };