chainlesschain 0.166.47 → 0.166.56

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 (180) hide show
  1. package/README.md +22 -0
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CDpT0WXV.js → AIOps-CBFnwYRX.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BNfUljWx.js → ActionButton-Sl4eXWpR.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-C9323DEx.js → Analytics-DB0lD9Kb.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CRrjb2db.js → AppLayout-j77EmkRK.js} +3 -3
  8. package/src/assets/web-panel/assets/{Artifacts-Db-DGqJS.js → Artifacts-CU3izCW9.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-CJdHRJ0W.js → Audit-C44fSibb.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-_RSCnTYs.js → BackgroundAgents-pM_NnJW3.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-8GFQi-dM.js → Backup-AOHXqhxN.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C87NtgUZ.js → BaseInput-DaYc8oA_.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-DcTMWS8G.js → Chat-CmlCAXxW.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-5ZDPTW_H.js → ChatBubbleRenderer-CkCSkJDJ.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-DM9EmYee.js → Checkbox-DISI7iaA.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-M2YqDRNN.js → Codegen-BYpeKUpf.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-oB1LLxyf.js → Col-Dk6VwWGq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-TcVZK5tF.js → Community-BDyTedMu.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-XKmq-3K_.js → Compact-CTNlGM9X.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-C6CBiDRy.js → Compliance-C_tw3IdT.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-Dz9CGqXH.js → Cowork-cduHWEmo.js} +4 -4
  22. package/src/assets/web-panel/assets/{Cron-CxJDgDVL.js → Cron-Dqotjv-s.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-DMP0JsaQ.js → Crosschain-ByW7RsJQ.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-I5IQeogb.js → DID-BI-lafPj.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-BfLW2MXj.js → Dashboard-8m50TRZL.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-BK8EjAlh.js → Dropdown-CN3hmRTc.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-C3iqSv5I.js → EmailListRenderer-t0lzxsDT.js} +1 -1
  28. package/src/assets/web-panel/assets/EvolutionSettings-DabpA3cQ.js +1 -0
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-T19rnqm-.js → FamilyGuardDashboard-BYN4UshY.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-CozrbPMy.js → Federation-n44OtIXp.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-k1dCLNNJ.js → FormItemContext-B-WXDu_I.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-DbaW_Jvt.js → GenericCardRenderer-Bm7v-mTf.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-CQHRyEIt.js → Git-BMIS6sbo.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-BMJYZgMy.js → Governance-B1EhHobE.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-B7mpNU7V.js → Inference-Dy_HJWHo.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-Br0Blg6B.js → KnowledgeGraph-BVdfUbZy.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-HIdk0S6M.js → Logs-Dph_oLQk.js} +2 -2
  38. package/src/assets/web-panel/assets/MarkdownRenderer-CNHYqcxm.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-Bkfq69UP.js → Marketplace-Bw4HWORj.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-dtmx0bHO.js → McpTools-Be6DPfA1.js} +3 -3
  41. package/src/assets/web-panel/assets/{Memory-CrRlOuEf.js → Memory-DlvLafC9.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-D4ZIZiAH.js → MobileBridge-Upc_Jwlt.js} +1 -1
  43. package/src/assets/web-panel/assets/{MobileProjects-C90Rw7GE.js → MobileProjects-I17jrUbY.js} +1 -1
  44. package/src/assets/web-panel/assets/{Mtc-CC6k_uh4.js → Mtc-DyBzY0GH.js} +4 -4
  45. package/src/assets/web-panel/assets/{MtcAudit-T_CUIYUy.js → MtcAudit-2-7xAwEo.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-N_e0RRWk.js → Multisig-DtUZpFmZ.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-Iq3VzEv0.js → NLProgramming-iInH7jBr.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-B5sKJUJt.js → Notes-BO3Z3vaS.js} +4 -4
  49. package/src/assets/web-panel/assets/{NotificationSettings-K8UVp8qQ.js → NotificationSettings-ClX_0jFE.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-BWNyM-6m.js → OrderTableRenderer-Czw742w4.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-DMO1NJxI.js → Organization-BATEx24i.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-BBY6b31Z.js → Overflow-B_PXMNia.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-DmsL0y0v.js → P2P-BSiE8PqT.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-C65AX_RY.js → PdhVaultBrowser-BbklUgZY.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-CWcpRtWa.js → Permissions-B7bpflT_.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub--ZNDIdsn.js → PersonalDataHub-BWZS4BLy.js} +3 -3
  57. package/src/assets/web-panel/assets/{Pipeline-DCdj5NGb.js → Pipeline-DP2YSs6H.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-BRKu7Q3E.js → Privacy-DB9ptoH0.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-BJ7ozIAb.js → ProjectInit-BAfePyYk.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-B2cVLiXW.js → ProjectSettings-BEXn4Cm3.js} +2 -2
  61. package/src/assets/web-panel/assets/{Projects-Ce9r_W-p.js → Projects-BzSs0rzn.js} +1 -1
  62. package/src/assets/web-panel/assets/{Providers-BDespvGZ.js → Providers-CRX9TdRO.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-BUNO4fDU.js → QrScannerModal-DUWcSY5L.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-D2TUqeBV.js → QuickAsk-Cgo3Z_hs.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-BNNQPHBH.js → Recommend-Dcinx0IW.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-D2t1V_Cm.js → RemoteSession-BMZzTnAN.js} +2 -2
  67. package/src/assets/web-panel/assets/{Reputation-DVK5hEqI.js → Reputation-BdZzpOp9.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-BB9RTwmE.js → Row-DqM8GVcx.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-DmEO7liZ.js → RssFeed-Soj_QBK3.js} +2 -2
  70. package/src/assets/web-panel/assets/{Search-UwEYjnDB.js → Search-Cn2es0-v.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-DebqSsMp.js → Security-t7C4hz3n.js} +3 -3
  72. package/src/assets/web-panel/assets/{Services-CuuPts3C.js → Services-ChF-e_Uf.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-DbWpmbgf.js → Skeleton-BiPIyK-2.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-BaK7SxzT.js → Skills-HgkKJenJ.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-BFHv51Jb.js → Sla-BSfluv_k.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-B9b1R6uK.js → SpeechSettings-Sm0Gz9tM.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-BnO0CN02.js → SyncSettings-DlhBMnfU.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-Dy01Vq49.js → Tasks-C7OUvsnp.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-BCy2AMHc.js → Templates-CrMtw87y.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-4Tx9y_Y1.js → Tenant-DSNSB6j5.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-CPOoUH8J.js → Terminal-Cmh78FQg.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-C85GlJzu.js → TimelineRenderer-BE8b8lHR.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-_xneFBGF.js → Tokens-BrtgdIvJ.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-BizG4rTr.js → Trigger-HCmaoKDp.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-Dh8EIn3n.js → Trust-GASQ4F-2.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-C5NWoHWQ.js → UkeySign-B8kSzWGr.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-0YfrySL6.js → VideoEditing-Nn-clhv5.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-BZVH8Ags.js → Wallet-DdYMKTYk.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-kNofa6vm.js → WebAuthn-xJXUr_eK.js} +3 -3
  90. package/src/assets/web-panel/assets/{WorkflowEditor-BNO34_j0.js → WorkflowEditor-CbSa87av.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-ifQ2FlwD.js → chat-CVrNLGUV.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-1TL8H6EH.js → colors-e2veLfIA.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DBfRjs_T.js → compact-item-CNd7nhUq.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-Bs6V4iBf.js → createContext-DEudTVqv.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DW7he-SA.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-Bz6LMY_0.js → hasIn-BlSAFZa3.js} +1 -1
  97. package/src/assets/web-panel/assets/{index--WvT2Il3.js → index-0y7tHZdT.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DMVktlxG.js → index-4ZT0vIJt.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-B6RClMZl.js → index-7-7rr4Mf.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BB1Af7Fv.js → index-B1C1wS5c.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-lUL3bD2z.js → index-BG-VNepa.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CtB45Hok.js → index-BMIhGZUR.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DOww5Qny.js → index-BRNCKg4q.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BiZ7Nz5-.js → index-B_UNX_xq.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C9QYxq61.js → index-BmVFeSEq.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BoCTDcn6.js → index-Bp4Gcn9u.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CXtiLagi.js → index-BpH6T1gA.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-B5SJCUZ7.js → index-BptI9bLu.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cn7EIWmI.js → index-Bqeb4jnC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DvM2xBUA.js → index-Bwtt-0CQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-EK3XtMxA.js → index-C4h_Bvd2.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DDhy4eCS.js → index-C6zGNZ5H.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-rGhS02f8.js → index-CHMrAZiR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BQL_tldd.js → index-CK6Rb66R.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C7d6QlfR.js → index-CU4Y4VVN.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Cykt9BbN.js → index-CWuv47z0.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BXJXMaZe.js → index-CYNoXhxl.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-f_7L9GvP.js → index-CgVwz2jD.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-vHtnQsnV.js → index-ClROBPWd.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BhPDhmvN.js → index-D7yJceTZ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cu_V6ZJH.js → index-DFv-nsIT.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CNKHoXWI.js → index-DFzvcZ3Q.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-vb1XIxEB.js → index-DJ5gsewX.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DWPp-d07.js → index-DVORJydf.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-pOUFfRdN.js → index-D_D7lcnQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-cNwsev6n.js → index-DtrqgNBl.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CBh48_Mh.js → index-DwjWRUTf.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BxqC3LiO.js → index-DyFZ7gDd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-XJIV7HEw.js → index-Ec6VBiV-.js} +1 -1
  130. package/src/assets/web-panel/assets/index-IkoHuWRe.js +1 -0
  131. package/src/assets/web-panel/assets/{index-CXVBQ82k.js → index-Jk99aZIr.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-84mi5rsv.js → index-Lm9pm1zm.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-C6c0j-Ki.js → index-MuTb62K6.js} +1 -1
  134. package/src/assets/web-panel/assets/index-MwqsDbb1.js +1 -0
  135. package/src/assets/web-panel/assets/{index-DEPPzM8I.js → index-hzUfIn4u.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-DpRIjwTt.js → initDefaultProps-C6fpNcA5.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-CG4_9o-Z.js → motion-Bjh9Come.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-Dku1fXtf.js → move-DFa4ul-_.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-BvAVeX-3.js → mtc-parser-KoYqA_e5.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-DFu0x78D.js → omit-ByTjP-wG.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-CmI-FNcK.js → pickAttrs-CG_rmTxx.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-CGERYewB.js → placementArrow-LDePZ8V9.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-D7G8bF5J.js → responsiveObserve-DZFzH4RR.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-B2sXwWXy.js → slide-DfXQyTvD.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-DI5yh0n3.js → statusUtils-CuhqsJfO.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-deGJ5dp_.js → styleChecker-qqVjJw5u.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-OqHBXEmi.js → useFlexGapSupport-BPxnCxcb.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-Dqgx0Qv_.js → useFs-D11NLCjV.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-Be-lveh2.js → usePersonalDataHub-HbZVcaCX.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-CFxB876W.js → vnode-CZHQlfTu.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-BFOYrl4e.js → zoom-CV3rUknJ.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/commands/context.js +31 -11
  154. package/src/commands/evolution-deployment.js +70 -0
  155. package/src/commands/plugin.js +36 -5
  156. package/src/data/changelog.json +78 -0
  157. package/src/gateways/ws/evolution-deployment-topic-handlers.js +20 -0
  158. package/src/generated/cc-agent-protocol.schema.json +44 -1
  159. package/src/harness/prompt-compressor.js +12 -4
  160. package/src/lib/background-agent-list-index.js +222 -0
  161. package/src/lib/background-agent-supervisor.js +38 -0
  162. package/src/lib/codex-app-server-adapter.js +1 -0
  163. package/src/lib/context-memory-kernel/provider-context.js +69 -4
  164. package/src/lib/deferred-question-context.js +160 -4
  165. package/src/lib/eval/plugin-suite.js +286 -22
  166. package/src/lib/evolution/evolution-deployment-config.js +16 -1
  167. package/src/lib/evolution/evolution-deployment-profile.js +102 -58
  168. package/src/lib/evolution/evolution-test-deployment.js +386 -0
  169. package/src/lib/model-context-window.js +3 -2
  170. package/src/lib/runtime-usage-ledger.js +5 -0
  171. package/src/lib/session-host-snapshot.js +16 -0
  172. package/src/repl/agent-repl.js +19 -4
  173. package/src/runtime/agent-core.js +13 -0
  174. package/src/runtime/headless-runner.js +4 -0
  175. package/src/runtime/headless-stream.js +171 -14
  176. package/src/assets/web-panel/assets/EvolutionSettings-BJmBzpW6.js +0 -1
  177. package/src/assets/web-panel/assets/MarkdownRenderer-HxeocBHy.js +0 -1
  178. package/src/assets/web-panel/assets/devWarning-BHnbnuYL.js +0 -1
  179. package/src/assets/web-panel/assets/index-CsRz9-ZH.js +0 -1
  180. package/src/assets/web-panel/assets/index-DnjMFf3m.js +0 -1
@@ -7,6 +7,8 @@ import { encodeName } from "../plugin-runtime/scopes.js";
7
7
 
8
8
  export const PLUGIN_EVAL_SUITE_SCHEMA = "chainlesschain.plugin-eval-suite/v1";
9
9
  export const PLUGIN_EVAL_REPORT_SCHEMA = "chainlesschain.plugin-eval-report/v1";
10
+ export const PLUGIN_EVAL_HOLDOUT_SCHEMA =
11
+ "chainlesschain.plugin-eval-holdout/v1";
10
12
 
11
13
  const DEFAULT_SUITE_PATH = "evals/suite.json";
12
14
  const MAX_SUITE_BYTES = 1024 * 1024;
@@ -22,6 +24,7 @@ const MAX_ASSERTION_FILE_BYTES = 4 * 1024 * 1024;
22
24
  const MAX_RUNTIME_OUTPUT_BYTES = 16 * 1024 * 1024;
23
25
  const MAX_RUNTIME_TOOL_CALLS = 10_000;
24
26
  const MAX_TRIGGER_SIGNALS = 256;
27
+ const MAX_SAMPLE_RUNS = 20;
25
28
  const TASK_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u;
26
29
  const EXPECTATIONS = new Set([
27
30
  "should_trigger",
@@ -35,6 +38,7 @@ const ASSERTION_TYPES = new Set([
35
38
  "file_contains",
36
39
  "json_equals",
37
40
  ]);
41
+ const ARM_ORDERS = new Set(["balanced", "control-first", "candidate-first"]);
38
42
 
39
43
  function digest(bytes, domain) {
40
44
  return `sha256:${crypto
@@ -122,6 +126,25 @@ function suiteRelativePath(root, suiteFile) {
122
126
  return relation.split(path.sep).join("/");
123
127
  }
124
128
 
129
+ function readBoundedJsonFile(file, label) {
130
+ const target = path.resolve(file);
131
+ const stat = fs.lstatSync(target);
132
+ if (!stat.isFile() || stat.isSymbolicLink()) {
133
+ throw new TypeError(`${label} must be a regular file, not a symlink`);
134
+ }
135
+ if (stat.size === 0 || stat.size > MAX_SUITE_BYTES) {
136
+ throw new RangeError(`${label} must be under ${MAX_SUITE_BYTES} bytes`);
137
+ }
138
+ const bytes = fs.readFileSync(target);
139
+ let value;
140
+ try {
141
+ value = JSON.parse(bytes.toString("utf8"));
142
+ } catch {
143
+ throw new TypeError(`${label} is not valid JSON`);
144
+ }
145
+ return { target, bytes, value: plainObject(value, label) };
146
+ }
147
+
125
148
  function collectPluginSnapshot(root) {
126
149
  const rootStat = fs.lstatSync(root);
127
150
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
@@ -214,6 +237,24 @@ function parseThreshold(value, label, { min = 0, max = 1 } = {}) {
214
237
  return value;
215
238
  }
216
239
 
240
+ function parseSampleRuns(value, label = "sampling.runs") {
241
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_SAMPLE_RUNS) {
242
+ throw new RangeError(
243
+ `${label} must be an integer between 1 and ${MAX_SAMPLE_RUNS}`,
244
+ );
245
+ }
246
+ return value;
247
+ }
248
+
249
+ function parseArmOrder(value, label = "sampling.armOrder") {
250
+ if (!ARM_ORDERS.has(value)) {
251
+ throw new TypeError(
252
+ `${label} must be balanced, control-first, or candidate-first`,
253
+ );
254
+ }
255
+ return value;
256
+ }
257
+
217
258
  function normalizeAssertion(value, taskId, index) {
218
259
  const assertion = plainObject(value, `task ${taskId} assertion ${index}`);
219
260
  if (!ASSERTION_TYPES.has(assertion.type)) {
@@ -414,6 +455,8 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
414
455
  }
415
456
  const thresholds = raw.thresholds || {};
416
457
  plainObject(thresholds, "plugin eval suite.thresholds");
458
+ const sampling = raw.sampling || {};
459
+ plainObject(sampling, "plugin eval suite.sampling");
417
460
  const maxCandidateCostUsd = parseThreshold(
418
461
  thresholds.maxCandidateCostUsd,
419
462
  "thresholds.maxCandidateCostUsd",
@@ -421,6 +464,7 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
421
464
  );
422
465
  return {
423
466
  schema: PLUGIN_EVAL_SUITE_SCHEMA,
467
+ pluginRoot: fs.realpathSync(root),
424
468
  identity: {
425
469
  name: manifest.metadata.name,
426
470
  version: manifest.metadata.version,
@@ -433,6 +477,10 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
433
477
  ),
434
478
  payload: snapshot,
435
479
  tasks,
480
+ sampling: {
481
+ runs: parseSampleRuns(sampling.runs ?? 1),
482
+ armOrder: parseArmOrder(sampling.armOrder || "balanced"),
483
+ },
436
484
  thresholds: {
437
485
  minPassRateDelta:
438
486
  parseThreshold(
@@ -450,6 +498,87 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
450
498
  };
451
499
  }
452
500
 
501
+ /**
502
+ * Attach reviewer-owned tasks from a file outside the plugin payload. The
503
+ * digest is reported separately so an author-controlled payload cannot mutate
504
+ * or silently absorb the holdout corpus.
505
+ */
506
+ export function attachPluginEvalHoldout(definition, holdoutFile) {
507
+ if (!holdoutFile) return definition;
508
+ const { target, bytes, value } = readBoundedJsonFile(
509
+ holdoutFile,
510
+ "plugin eval holdout",
511
+ );
512
+ const holdoutReal = fs.realpathSync(target);
513
+ const relation = path.relative(definition.pluginRoot, holdoutReal);
514
+ if (
515
+ relation === "" ||
516
+ (!relation.startsWith(`..${path.sep}`) &&
517
+ relation !== ".." &&
518
+ !path.isAbsolute(relation))
519
+ ) {
520
+ throw new TypeError(
521
+ "plugin eval holdout must be outside the plugin payload root",
522
+ );
523
+ }
524
+ if (value.schema !== PLUGIN_EVAL_HOLDOUT_SCHEMA) {
525
+ throw new TypeError(
526
+ `plugin eval holdout must use ${PLUGIN_EVAL_HOLDOUT_SCHEMA}`,
527
+ );
528
+ }
529
+ const identity = plainObject(value.plugin, "plugin eval holdout.plugin");
530
+ if (
531
+ identity.name !== definition.identity.name ||
532
+ identity.version !== definition.identity.version
533
+ ) {
534
+ throw new TypeError(
535
+ "plugin eval holdout identity must match the parsed plugin manifest",
536
+ );
537
+ }
538
+ if (
539
+ !Array.isArray(value.tasks) ||
540
+ value.tasks.length === 0 ||
541
+ value.tasks.length > MAX_TASKS
542
+ ) {
543
+ throw new RangeError(`plugin eval holdout requires 1-${MAX_TASKS} tasks`);
544
+ }
545
+ const holdoutTasks = value.tasks.map((task, index) => ({
546
+ ...normalizeTask(task, index),
547
+ source: "holdout",
548
+ }));
549
+ if (definition.tasks.length + holdoutTasks.length > MAX_TASKS) {
550
+ throw new RangeError(
551
+ `plugin eval author and holdout tasks may not exceed ${MAX_TASKS}`,
552
+ );
553
+ }
554
+ const ids = new Set(definition.tasks.map((task) => task.id));
555
+ for (const task of holdoutTasks) {
556
+ if (ids.has(task.id)) {
557
+ throw new TypeError(`duplicate plugin eval task id: ${task.id}`);
558
+ }
559
+ ids.add(task.id);
560
+ }
561
+ return {
562
+ ...definition,
563
+ tasks: [
564
+ ...definition.tasks.map((task) => ({
565
+ ...task,
566
+ source: task.source || "author",
567
+ })),
568
+ ...holdoutTasks,
569
+ ],
570
+ holdout: {
571
+ schema: PLUGIN_EVAL_HOLDOUT_SCHEMA,
572
+ // Reports may be shared as CI artifacts; do not disclose the reviewer's
573
+ // absolute workstation path. The digest is the authoritative binding.
574
+ path: path.basename(target),
575
+ digest: digest(bytes, "chainlesschain.plugin-eval-holdout-bytes/v1"),
576
+ tasks: holdoutTasks.length,
577
+ independentFromPayload: true,
578
+ },
579
+ };
580
+ }
581
+
453
582
  function materializePlugin(definition, workspace) {
454
583
  const nameDir = path.join(
455
584
  workspace,
@@ -555,6 +684,7 @@ function buildTasks(definition, { candidate }) {
555
684
  return definition.tasks.map((task) => ({
556
685
  id: task.id,
557
686
  description: task.description,
687
+ source: task.source || "author",
558
688
  prompt: task.prompt,
559
689
  expectedFiles: task.expectedFiles,
560
690
  ...(task.timeoutMs === undefined ? {} : { timeoutMs: task.timeoutMs }),
@@ -708,6 +838,7 @@ function projectArm(summary, definition, { candidate }) {
708
838
  metrics.pluginTriggered === false));
709
839
  return {
710
840
  ...result,
841
+ source: task.source || "author",
711
842
  expectation,
712
843
  pluginTriggered: metrics.pluginTriggered,
713
844
  activationEvidenceAvailable: metrics.available === true,
@@ -753,10 +884,110 @@ function reportArm(arm) {
753
884
  usage: arm.usage,
754
885
  totalCostUsd: arm.totalCostUsd,
755
886
  ms: arm.ms,
887
+ sampleRuns: arm.sampleRuns,
756
888
  results: arm.results,
757
889
  };
758
890
  }
759
891
 
892
+ function aggregateArms(arms) {
893
+ const results = arms.flatMap((arm, sampleIndex) =>
894
+ arm.results.map((result) => ({ ...result, sample: sampleIndex + 1 })),
895
+ );
896
+ const total = results.length;
897
+ const measured = results.filter((result) =>
898
+ Array.isArray(result.unrelatedChanges),
899
+ );
900
+ const tasksWithUnrelatedChanges = measured.filter(
901
+ (result) => result.unrelatedChanges.length > 0,
902
+ ).length;
903
+ const passed = results.filter((result) => result.pass).length;
904
+ const effectivePassed = results.filter(
905
+ (result) => result.effectivePass,
906
+ ).length;
907
+ const triggered = results.filter(
908
+ (result) => result.pluginTriggered === true,
909
+ ).length;
910
+ return {
911
+ results,
912
+ passed,
913
+ failed: total - passed,
914
+ total,
915
+ passRate: total > 0 ? passed / total : 0,
916
+ effectivePassed,
917
+ effectiveFailed: total - effectivePassed,
918
+ effectivePassRate: total > 0 ? effectivePassed / total : 0,
919
+ artifactChecksPassed: results.filter((result) => result.artifactCheckPassed)
920
+ .length,
921
+ executionsSucceeded: results.filter((result) => result.executionSucceeded)
922
+ .length,
923
+ tasksWithUnrelatedChanges,
924
+ unrelatedChangeRate: measured.length
925
+ ? tasksWithUnrelatedChanges / measured.length
926
+ : 0,
927
+ triggerRate: total > 0 ? triggered / total : 0,
928
+ activationEvidenceComplete: results.every(
929
+ (result) => result.activationEvidenceAvailable,
930
+ ),
931
+ ...sumUsage(results),
932
+ ms: arms.reduce((totalMs, arm) => totalMs + arm.ms, 0),
933
+ sampleRuns: arms.length,
934
+ };
935
+ }
936
+
937
+ // Wilson score intervals stay well behaved for the small and extreme samples
938
+ // common in plugin author evals (unlike p +/- z*sqrt(p(1-p)/n)).
939
+ function wilson95(successes, total) {
940
+ if (!Number.isSafeInteger(total) || total <= 0) return null;
941
+ const z = 1.959963984540054;
942
+ const z2 = z * z;
943
+ const p = successes / total;
944
+ const denominator = 1 + z2 / total;
945
+ const center = (p + z2 / (2 * total)) / denominator;
946
+ const radius =
947
+ (z * Math.sqrt((p * (1 - p) + z2 / (4 * total)) / total)) / denominator;
948
+ return {
949
+ lower: Math.max(0, center - radius),
950
+ upper: Math.min(1, center + radius),
951
+ };
952
+ }
953
+
954
+ function confidenceComparison(control, candidate) {
955
+ const controlInterval = wilson95(control.effectivePassed, control.total);
956
+ const candidateInterval = wilson95(
957
+ candidate.effectivePassed,
958
+ candidate.total,
959
+ );
960
+ if (!controlInterval || !candidateInterval) return null;
961
+ return {
962
+ method: "wilson-95-newcombe-difference",
963
+ control: controlInterval,
964
+ candidate: candidateInterval,
965
+ passRateDelta: {
966
+ lower: candidateInterval.lower - controlInterval.upper,
967
+ upper: candidateInterval.upper - controlInterval.lower,
968
+ },
969
+ };
970
+ }
971
+
972
+ function sampleArmSequence(sampleRuns, armOrder, suiteDigest) {
973
+ if (armOrder === "control-first") {
974
+ return Array.from({ length: sampleRuns }, () => ["control", "candidate"]);
975
+ }
976
+ if (armOrder === "candidate-first") {
977
+ return Array.from({ length: sampleRuns }, () => ["candidate", "control"]);
978
+ }
979
+ // Bind the first arm to the suite digest, then alternate. This balances
980
+ // provider drift without introducing an unrecorded random source.
981
+ const startsWithCandidate =
982
+ Number.parseInt(String(suiteDigest).slice(-2), 16) % 2 === 1;
983
+ return Array.from({ length: sampleRuns }, (_, index) => {
984
+ const candidateFirst = startsWithCandidate
985
+ ? index % 2 === 0
986
+ : index % 2 === 1;
987
+ return candidateFirst ? ["candidate", "control"] : ["control", "candidate"];
988
+ });
989
+ }
990
+
760
991
  export async function runPluginEval(
761
992
  definition,
762
993
  {
@@ -768,6 +999,8 @@ export async function runPluginEval(
768
999
  model = null,
769
1000
  dryRun = false,
770
1001
  minPassRateDelta,
1002
+ sampleRuns,
1003
+ armOrder,
771
1004
  onResult,
772
1005
  } = {},
773
1006
  ) {
@@ -777,30 +1010,49 @@ export async function runPluginEval(
777
1010
  ) {
778
1011
  throw new TypeError("controlRunAgent and candidateRunAgent are required");
779
1012
  }
780
- const runArm = async (arm, tasks, runAgent) =>
781
- runSuite(tasks, {
782
- runAgent: instrumentRunAgent(runAgent, definition.identity),
783
- keepWorkspaces,
784
- onResult: onResult ? (result) => onResult({ arm, result }) : undefined,
785
- });
786
- const control = projectArm(
787
- await runArm(
788
- "control",
789
- buildTasks(definition, { candidate: false }),
790
- controlRunAgent,
791
- ),
792
- definition,
793
- { candidate: false },
1013
+ const resolvedSampleRuns = parseSampleRuns(
1014
+ sampleRuns === undefined ? definition.sampling?.runs || 1 : sampleRuns,
1015
+ "sampleRuns",
794
1016
  );
795
- const candidate = projectArm(
796
- await runArm(
797
- "candidate",
798
- buildTasks(definition, { candidate: true }),
799
- candidateRunAgent,
800
- ),
801
- definition,
802
- { candidate: true },
1017
+ const resolvedArmOrder = parseArmOrder(
1018
+ armOrder === undefined
1019
+ ? definition.sampling?.armOrder || "balanced"
1020
+ : armOrder,
1021
+ "armOrder",
1022
+ );
1023
+ const runArm = async (arm, tasks, runAgent, sample) =>
1024
+ projectArm(
1025
+ await runSuite(tasks, {
1026
+ runAgent: instrumentRunAgent(runAgent, definition.identity),
1027
+ keepWorkspaces,
1028
+ onResult: onResult
1029
+ ? (result) => onResult({ arm, sample, result })
1030
+ : undefined,
1031
+ }),
1032
+ definition,
1033
+ { candidate: arm === "candidate" },
1034
+ );
1035
+ const armRuns = { control: [], candidate: [] };
1036
+ const sequence = sampleArmSequence(
1037
+ resolvedSampleRuns,
1038
+ resolvedArmOrder,
1039
+ definition.suiteDigest,
803
1040
  );
1041
+ for (const [sampleIndex, orderedArms] of sequence.entries()) {
1042
+ for (const arm of orderedArms) {
1043
+ const candidate = arm === "candidate";
1044
+ armRuns[arm].push(
1045
+ await runArm(
1046
+ arm,
1047
+ buildTasks(definition, { candidate }),
1048
+ candidate ? candidateRunAgent : controlRunAgent,
1049
+ sampleIndex + 1,
1050
+ ),
1051
+ );
1052
+ }
1053
+ }
1054
+ const control = aggregateArms(armRuns.control);
1055
+ const candidate = aggregateArms(armRuns.candidate);
804
1056
  const requiredDelta =
805
1057
  minPassRateDelta === undefined
806
1058
  ? definition.thresholds.minPassRateDelta
@@ -859,10 +1111,19 @@ export async function runPluginEval(
859
1111
  digest: definition.suiteDigest,
860
1112
  tasks: definition.tasks.length,
861
1113
  },
1114
+ holdout: definition.holdout || null,
862
1115
  selection: {
863
1116
  provider,
864
1117
  model,
865
1118
  dryRun: dryRun === true,
1119
+ sampleRuns: resolvedSampleRuns,
1120
+ armOrder: resolvedArmOrder,
1121
+ },
1122
+ sampling: {
1123
+ runs: resolvedSampleRuns,
1124
+ armOrder: resolvedArmOrder,
1125
+ sequence,
1126
+ deterministic: true,
866
1127
  },
867
1128
  thresholds: {
868
1129
  ...definition.thresholds,
@@ -877,6 +1138,9 @@ export async function runPluginEval(
877
1138
  ? "regression"
878
1139
  : "neutral",
879
1140
  },
1141
+ statistics: {
1142
+ confidence95: confidenceComparison(control, candidate),
1143
+ },
880
1144
  arms: {
881
1145
  control: reportArm(control),
882
1146
  candidate: reportArm(candidate),
@@ -80,7 +80,7 @@ export function assessEvolutionDeploymentReadiness(
80
80
  : "no enabled signed Evolution deployment",
81
81
  disabled
82
82
  ? "review the saved signed deployment, then run cc evolution deployment enable"
83
- : "configure a signed descriptor and trust root with cc evolution deployment configure --descriptor <path> --trust-root <path>",
83
+ : "before production, run cc evolution deployment init-test --module <absolute-path>; otherwise configure a signed descriptor and trust root",
84
84
  );
85
85
  }
86
86
  if (missingCommands.length > 0) {
@@ -126,6 +126,10 @@ export async function getEvolutionDeploymentStatus(options = {}) {
126
126
  fromEnvironment || (saved.profile?.enabled ? saved.profile : null);
127
127
  const base = {
128
128
  source,
129
+ deploymentMode:
130
+ source === "environment"
131
+ ? "external"
132
+ : saved.profile?.deploymentMode || "none",
129
133
  effectiveEnabled: source !== "none",
130
134
  profileEnabled: saved.profile?.enabled === true,
131
135
  profilePath: saved.filePath,
@@ -183,6 +187,8 @@ export async function configureEvolutionDeployment(
183
187
  rootRotationPath = null,
184
188
  revocationPath = null,
185
189
  enabled = true,
190
+ deploymentMode,
191
+ testPrivateKeyPath,
186
192
  },
187
193
  options = {},
188
194
  ) {
@@ -239,6 +245,11 @@ export async function configureEvolutionDeployment(
239
245
  verified.descriptor.revision,
240
246
  ),
241
247
  };
248
+ const preserveTestMode =
249
+ deploymentMode === undefined &&
250
+ current?.deploymentMode === "test" &&
251
+ current.descriptorPath === verified.descriptorPath &&
252
+ current.trustRootPath === verified.trustRootPath;
242
253
  return {
243
254
  enabled: enabled === true,
244
255
  descriptorPath: verified.descriptorPath,
@@ -246,6 +257,10 @@ export async function configureEvolutionDeployment(
246
257
  revisionFloors,
247
258
  activeTrustRootDigest: verified.descriptor.trustRootDigest,
248
259
  revokedDescriptorRevisions,
260
+ deploymentMode: preserveTestMode ? "test" : deploymentMode || "managed",
261
+ testPrivateKeyPath: preserveTestMode
262
+ ? current.testPrivateKeyPath
263
+ : testPrivateKeyPath || null,
249
264
  };
250
265
  }, options);
251
266
  return getEvolutionDeploymentStatus(options);