chainlesschain 0.166.45 → 0.166.47

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 (230) hide show
  1. package/README.md +6 -4
  2. package/package.json +3 -1
  3. package/src/assets/web-panel/assets/{AIOps-CrMhYNeH.js → AIOps-CDpT0WXV.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DLY5blZ3.js → ActionButton-BNfUljWx.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-C0NhKQB0.js → Analytics-C9323DEx.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DLyepgMq.js → AppLayout-CRrjb2db.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-Depk01Sa.js → Artifacts-Db-DGqJS.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-jKe6NQ3J.js → Audit-CJdHRJ0W.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-CG4GKT9s.js → BackgroundAgents-_RSCnTYs.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DYWCkzwM.js → Backup-8GFQi-dM.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-xm5vodmJ.js → BaseInput-C87NtgUZ.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-B20_PNd3.js → Chat-DcTMWS8G.js} +5 -5
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-D4d-eh0F.js → ChatBubbleRenderer-5ZDPTW_H.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-CfgwkV_r.js → Checkbox-DM9EmYee.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-DbnoWI44.js → Codegen-M2YqDRNN.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-CfzzhgGw.js → Col-oB1LLxyf.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BU7mGM8p.js → Community-TcVZK5tF.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DpYim0an.js → Compact-XKmq-3K_.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BNJcO6ra.js → Compliance-C6CBiDRy.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-CecZ-pWj.js → Cowork-Dz9CGqXH.js} +4 -4
  21. package/src/assets/web-panel/assets/{Cron-BG_C1fe3.js → Cron-CxJDgDVL.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BAeILQLL.js → Crosschain-DMP0JsaQ.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CYqOEkXi.js → DID-I5IQeogb.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-BYU8wmL1.js → Dashboard-BfLW2MXj.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DYlJeTTa.js → Dropdown-BK8EjAlh.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-C0M6py3K.js → EmailListRenderer-C3iqSv5I.js} +1 -1
  27. package/src/assets/web-panel/assets/EvolutionSettings-BJmBzpW6.js +1 -0
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Du90iDch.js → FamilyGuardDashboard-T19rnqm-.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-DkFp_hzB.js → Federation-CozrbPMy.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-BGgtGTfG.js → FormItemContext-k1dCLNNJ.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-BIDsSCei.js → GenericCardRenderer-DbaW_Jvt.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git--d3Te93J.js → Git-CQHRyEIt.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-CZwD0vmO.js → Governance-BMJYZgMy.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-DiDcSFEW.js → Inference-B7mpNU7V.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-Bi0hSfzg.js → KnowledgeGraph-Br0Blg6B.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-C8NHSc0K.js → Logs-HIdk0S6M.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DfDuADqF.js → MarkdownRenderer-HxeocBHy.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-D6kD0bNf.js → Marketplace-Bkfq69UP.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-mTSamCr5.js → McpTools-dtmx0bHO.js} +3 -3
  40. package/src/assets/web-panel/assets/{Memory-YQBXfLjU.js → Memory-CrRlOuEf.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-D2awuupp.js → MobileBridge-D4ZIZiAH.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-Bl7lZc_-.js → MobileProjects-C90Rw7GE.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-CBqjLK2Q.js → Mtc-CC6k_uh4.js} +5 -5
  44. package/src/assets/web-panel/assets/{MtcAudit-C4poRInZ.js → MtcAudit-T_CUIYUy.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-C_J0zXYZ.js → Multisig-N_e0RRWk.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-Ci43sWOU.js → NLProgramming-Iq3VzEv0.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-DN7A-GFZ.js → Notes-B5sKJUJt.js} +4 -4
  48. package/src/assets/web-panel/assets/{NotificationSettings-D5jNm71V.js → NotificationSettings-K8UVp8qQ.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-C9UsPOG8.js → OrderTableRenderer-BWNyM-6m.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-BapYMJrP.js → Organization-DMO1NJxI.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BwxwjhFw.js → Overflow-BBY6b31Z.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-TObXXOu3.js → P2P-DmsL0y0v.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-UwJvMbFO.js → PdhVaultBrowser-C65AX_RY.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-Damnxv-O.js → Permissions-CWcpRtWa.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-d_7jlBb-.js → PersonalDataHub--ZNDIdsn.js} +3 -3
  56. package/src/assets/web-panel/assets/{Pipeline-DA87VjLw.js → Pipeline-DCdj5NGb.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-DzO_UvWn.js → Privacy-BRKu7Q3E.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-CwqECDhs.js → ProjectInit-BJ7ozIAb.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CT5YdIl6.js → ProjectSettings-B2cVLiXW.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-D3KdZPCv.js → Projects-Ce9r_W-p.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-BjBIS8kk.js → Providers-BDespvGZ.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-mMFqiKkI.js → QrScannerModal-BUNO4fDU.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-BwGi9ZU1.js → QuickAsk-D2TUqeBV.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-BY65SR5s.js → Recommend-BNNQPHBH.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-BGsVH_XU.js → RemoteSession-D2t1V_Cm.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-DpPbxSgI.js → Reputation-DVK5hEqI.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-B5YBYoVz.js → Row-BB9RTwmE.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-DvM5jaoh.js → RssFeed-DmEO7liZ.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-6RX39EPO.js → Search-UwEYjnDB.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Beal-7cM.js → Security-DebqSsMp.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-D6rjVLe-.js → Services-CuuPts3C.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-Dur7R7vz.js → Skeleton-DbWpmbgf.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-D1QkGwMk.js → Skills-BaK7SxzT.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-3ObrX8Es.js → Sla-BFHv51Jb.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-DG_KAiH1.js → SpeechSettings-B9b1R6uK.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-BaHq0SJ2.js → SyncSettings-BnO0CN02.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-BDpAkBAB.js → Tasks-Dy01Vq49.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-tYdoRd-c.js → Templates-BCy2AMHc.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-yGS5zIQ1.js → Tenant-4Tx9y_Y1.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-B3QbmVFa.js → Terminal-CPOoUH8J.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-B_7Pr5dS.js → TimelineRenderer-C85GlJzu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DPvpDa53.js → Tokens-_xneFBGF.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-DcSAD0GZ.js → Trigger-BizG4rTr.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-BBzEq-uy.js → Trust-Dh8EIn3n.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-Dssav7-5.js → UkeySign-C5NWoHWQ.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-CPOZf4yi.js → VideoEditing-0YfrySL6.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-BzBVRXEq.js → Wallet-BZVH8Ags.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-DdSGaCCh.js → WebAuthn-kNofa6vm.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-CKjBFoSs.js → WorkflowEditor-BNO34_j0.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-B9aMGLg0.js → chat-ifQ2FlwD.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-GFCMctSV.js → colors-1TL8H6EH.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-BqSDFAYJ.js → compact-item-DBfRjs_T.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BwUm3Ffp.js → createContext-Bs6V4iBf.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BHnbnuYL.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DN1kMJOT.js → hasIn-Bz6LMY_0.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B4dI_csl.js → index--WvT2Il3.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B7Yu5wwV.js → index-84mi5rsv.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DAK28GY8.js → index-B5SJCUZ7.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CmEHzOsw.js → index-B6RClMZl.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-By9GbJhu.js → index-BB1Af7Fv.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-7W1e9Vi4.js → index-BQL_tldd.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BDL2MwxN.js → index-BXJXMaZe.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CEMl-Qte.js → index-BhPDhmvN.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BCnbC2z7.js → index-BiZ7Nz5-.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cwih67e8.js → index-BoCTDcn6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DI90Wo_P.js → index-BxqC3LiO.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-THsr_UUJ.js → index-C6c0j-Ki.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-PKwZkQDC.js → index-C7d6QlfR.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-rCTtYCGu.js → index-C9QYxq61.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Ci0zIpSX.js → index-CBh48_Mh.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-tMF_deZb.js → index-CNKHoXWI.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B9Pe-EyB.js → index-CXVBQ82k.js} +3 -3
  113. package/src/assets/web-panel/assets/{index-B4MfxM5W.js → index-CXtiLagi.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DaFr7XB1.js → index-Cn7EIWmI.js} +1 -1
  115. package/src/assets/web-panel/assets/index-CsRz9-ZH.js +1 -0
  116. package/src/assets/web-panel/assets/{index-D8jcp9Rj.js → index-CtB45Hok.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Cli0VREv.js → index-Cu_V6ZJH.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DO-IKERp.js → index-Cykt9BbN.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-y7cfh9rP.js → index-DDhy4eCS.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CH2wK5dx.js → index-DEPPzM8I.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BeycSy7n.js → index-DMVktlxG.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BwssFWI1.js → index-DOww5Qny.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-PJt1Zrx2.js → index-DWPp-d07.js} +1 -1
  124. package/src/assets/web-panel/assets/index-DnjMFf3m.js +1 -0
  125. package/src/assets/web-panel/assets/{index-BPGlcDvs.js → index-DvM2xBUA.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BL_0ndQc.js → index-EK3XtMxA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DnJt9DaX.js → index-XJIV7HEw.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BpiR6uzH.js → index-cNwsev6n.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-bwb4cg_D.js → index-f_7L9GvP.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DbDhKroq.js → index-lUL3bD2z.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DvvJWwT8.js → index-pOUFfRdN.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BBMW8bQO.js → index-rGhS02f8.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CRikhGgx.js → index-vHtnQsnV.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BHUxz4dy.js → index-vb1XIxEB.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-B6e5CZ5p.js → initDefaultProps-DpRIjwTt.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-riMM1KfJ.js → motion-CG4_9o-Z.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-C1NSDMjc.js → move-Dku1fXtf.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-Bibuny9B.js → mtc-parser-BvAVeX-3.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-Cmf6FoRn.js → omit-DFu0x78D.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-BSsYBUCY.js → pickAttrs-CmI-FNcK.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-gSB7-GAS.js → placementArrow-CGERYewB.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-qaqttvKa.js → responsiveObserve-D7G8bF5J.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-RX2gxl3k.js → slide-B2sXwWXy.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-DEJmkhqw.js → statusUtils-DI5yh0n3.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-CIWmJ78O.js → styleChecker-deGJ5dp_.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-D-UPSQBE.js → useFlexGapSupport-OqHBXEmi.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-9QFtqADc.js → useFs-Dqgx0Qv_.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-DM6kyift.js → usePersonalDataHub-Be-lveh2.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-C-q6FNSJ.js → vnode-CFxB876W.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-D4WJY2m8.js → zoom-BFOYrl4e.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +3 -3
  153. package/src/commands/agent.js +11 -7
  154. package/src/commands/background-session.js +31 -3
  155. package/src/commands/chat.js +5 -1
  156. package/src/commands/eval.js +86 -28
  157. package/src/commands/llm.js +104 -1
  158. package/src/commands/plugin.js +130 -1
  159. package/src/commands/sandbox.js +121 -2
  160. package/src/commands/serve.js +11 -2
  161. package/src/commands/skill.js +29 -0
  162. package/src/data/changelog.json +26 -0
  163. package/src/gateways/ws/session-protocol.js +13 -0
  164. package/src/gateways/ws/ws-agent-handler.js +17 -0
  165. package/src/gateways/ws/ws-server.js +3 -0
  166. package/src/gateways/ws/ws-session-state.js +131 -0
  167. package/src/generated/cc-agent-protocol.schema.json +4 -0
  168. package/src/lazy-dispatch.js +8 -1
  169. package/src/lib/agent-sandbox.js +337 -53
  170. package/src/lib/app-server/cli-agent-kernel-adapter.js +28 -0
  171. package/src/lib/app-server/server.js +93 -0
  172. package/src/lib/background-agent-keeper-client.js +2 -0
  173. package/src/lib/background-agent-keeper-protocol.js +7 -0
  174. package/src/lib/background-agent-supervisor.js +194 -47
  175. package/src/lib/codex-app-server-adapter.js +81 -27
  176. package/src/lib/context-memory-kernel/durable-memory-port.js +31 -8
  177. package/src/lib/context-memory-kernel/provider-context.js +53 -19
  178. package/src/lib/deferred-question-context.js +122 -0
  179. package/src/lib/eval/evidence.js +202 -64
  180. package/src/lib/eval/plugin-suite.js +917 -0
  181. package/src/lib/eval/runner.js +10 -3
  182. package/src/lib/evolution/agent-evolution-ingress.js +60 -8
  183. package/src/lib/evolution/agent-evolution-runtime-composition.js +236 -1
  184. package/src/lib/evolution/agent-model-projection.js +97 -1
  185. package/src/lib/evolution/evidence-backed-wiki-maintainer.js +154 -1
  186. package/src/lib/evolution/evolution-artifact-ports.js +6 -0
  187. package/src/lib/evolution/evolution-deployment-loader.js +188 -10
  188. package/src/lib/evolution/evolution-eval-gate.js +8 -0
  189. package/src/lib/evolution/evolution-eval-runtime-composition.js +255 -0
  190. package/src/lib/evolution/evolution-evidence-artifact-adapter.js +58 -2
  191. package/src/lib/evolution/evolution-ledger-file-backend.js +23 -2
  192. package/src/lib/evolution/evolution-ledger-manifest-chain.js +223 -7
  193. package/src/lib/evolution/evolution-ledger-v2-journal.js +525 -0
  194. package/src/lib/evolution/evolution-ledger-v2-manifest-backend.js +46 -2
  195. package/src/lib/evolution/evolution-ledger.js +57 -1
  196. package/src/lib/evolution/evolution-run-ledger-adapter.js +93 -0
  197. package/src/lib/evolution/evolution-run-wiki-evidence-resolver.js +259 -0
  198. package/src/lib/evolution/governed-hub-llm.js +238 -5
  199. package/src/lib/evolution/governed-hub-resolver.js +82 -4
  200. package/src/lib/evolution/governed-hub-skill.js +11 -5
  201. package/src/lib/evolution/governed-skill-synthesis-provider-chat.js +10 -1
  202. package/src/lib/evolution/skill-runtime-revalidation-ledger-adapter.js +254 -0
  203. package/src/lib/evolution/skill-runtime-revalidation.js +530 -0
  204. package/src/lib/evolution/skill-target-matrix-eval.js +4 -4
  205. package/src/lib/evolution/wiki-evidence-commit-guard.js +366 -0
  206. package/src/lib/evolution/wiki-informed-skill-proposer.js +13 -1
  207. package/src/lib/goal-context.js +14 -5
  208. package/src/lib/interaction-adapter.js +166 -1
  209. package/src/lib/model-capabilities.js +271 -0
  210. package/src/lib/model-context-catalog.js +105 -0
  211. package/src/lib/model-context-window.js +4 -49
  212. package/src/lib/openai-responses.js +428 -0
  213. package/src/lib/plugin-runtime/scopes.js +7 -1
  214. package/src/lib/remote-read-loop-guard.js +2 -2
  215. package/src/lib/runtime-usage-ledger.js +16 -0
  216. package/src/lib/skill-loader.js +12 -0
  217. package/src/lib/with-file-lock.js +3 -1
  218. package/src/repl/agent-repl.js +6 -2
  219. package/src/runtime/agent-core.js +357 -44
  220. package/src/runtime/agent-runtime.js +7 -0
  221. package/src/runtime/coding-agent-contract-shared.cjs +13 -1
  222. package/src/runtime/headless-runner.js +4 -1
  223. package/src/runtime/headless-stream.js +133 -44
  224. package/src/runtime/runtime-factory.js +26 -2
  225. package/src/workers/background-agent-keeper.js +23 -13
  226. package/src/workers/background-agent-worker.js +27 -13
  227. package/src/assets/web-panel/assets/EvolutionSettings-_QnjPFZJ.js +0 -1
  228. package/src/assets/web-panel/assets/devWarning-LxtRBsCm.js +0 -1
  229. package/src/assets/web-panel/assets/index-36hxBH-o.js +0 -1
  230. package/src/assets/web-panel/assets/index-C0b78zWZ.js +0 -1
@@ -0,0 +1,917 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { runEvalSuite } from "./runner.js";
5
+ import { parsePluginManifest } from "../plugin-runtime/manifest.js";
6
+ import { encodeName } from "../plugin-runtime/scopes.js";
7
+
8
+ export const PLUGIN_EVAL_SUITE_SCHEMA = "chainlesschain.plugin-eval-suite/v1";
9
+ export const PLUGIN_EVAL_REPORT_SCHEMA = "chainlesschain.plugin-eval-report/v1";
10
+
11
+ const DEFAULT_SUITE_PATH = "evals/suite.json";
12
+ const MAX_SUITE_BYTES = 1024 * 1024;
13
+ const MAX_TASKS = 64;
14
+ const MAX_FIXTURES_PER_TASK = 128;
15
+ const MAX_ASSERTIONS_PER_TASK = 128;
16
+ const MAX_FIXTURE_BYTES = 256 * 1024;
17
+ const MAX_PLUGIN_FILES = 4096;
18
+ const MAX_PLUGIN_ENTRIES = 8192;
19
+ const MAX_PLUGIN_DEPTH = 64;
20
+ const MAX_PLUGIN_BYTES = 32 * 1024 * 1024;
21
+ const MAX_ASSERTION_FILE_BYTES = 4 * 1024 * 1024;
22
+ const MAX_RUNTIME_OUTPUT_BYTES = 16 * 1024 * 1024;
23
+ const MAX_RUNTIME_TOOL_CALLS = 10_000;
24
+ const MAX_TRIGGER_SIGNALS = 256;
25
+ const TASK_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u;
26
+ const EXPECTATIONS = new Set([
27
+ "should_trigger",
28
+ "should_not_trigger",
29
+ "optional",
30
+ ]);
31
+ const ASSERTION_TYPES = new Set([
32
+ "file_exists",
33
+ "file_absent",
34
+ "file_equals",
35
+ "file_contains",
36
+ "json_equals",
37
+ ]);
38
+
39
+ function digest(bytes, domain) {
40
+ return `sha256:${crypto
41
+ .createHash("sha256")
42
+ .update(domain)
43
+ .update("\0")
44
+ .update(bytes)
45
+ .digest("hex")}`;
46
+ }
47
+
48
+ function canonical(value) {
49
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
50
+ if (value && typeof value === "object") {
51
+ return `{${Object.keys(value)
52
+ .sort()
53
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
54
+ .join(",")}}`;
55
+ }
56
+ return JSON.stringify(value);
57
+ }
58
+
59
+ function plainObject(value, label) {
60
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
61
+ throw new TypeError(`${label} must be an object`);
62
+ }
63
+ return value;
64
+ }
65
+
66
+ function boundedString(
67
+ value,
68
+ label,
69
+ max,
70
+ { allowEmpty = false, allowFormatting = false } = {},
71
+ ) {
72
+ const controls =
73
+ typeof value === "string" && allowFormatting
74
+ ? value.replace(/[\t\r\n]/gu, "")
75
+ : value;
76
+ if (
77
+ typeof value !== "string" ||
78
+ (!allowEmpty && value.trim().length === 0) ||
79
+ value.length > max ||
80
+ /\p{Cc}/u.test(controls)
81
+ ) {
82
+ throw new TypeError(
83
+ `${label} must be a string of at most ${max} characters`,
84
+ );
85
+ }
86
+ return value;
87
+ }
88
+
89
+ function relativeTaskPath(value, label) {
90
+ const raw = boundedString(value, label, 512).replaceAll("\\", "/");
91
+ if (raw.startsWith("/") || /^[a-zA-Z]:\//u.test(raw)) {
92
+ throw new TypeError(`${label} must be relative`);
93
+ }
94
+ const normalized = path.posix.normalize(raw);
95
+ if (
96
+ normalized === "." ||
97
+ normalized === ".." ||
98
+ normalized.startsWith("../") ||
99
+ normalized === ".chainlesschain" ||
100
+ normalized.startsWith(".chainlesschain/")
101
+ ) {
102
+ throw new TypeError(
103
+ `${label} must stay inside the task workspace and outside .chainlesschain`,
104
+ );
105
+ }
106
+ return normalized;
107
+ }
108
+
109
+ function suiteRelativePath(root, suiteFile) {
110
+ const candidate = path.resolve(root, suiteFile || DEFAULT_SUITE_PATH);
111
+ const relation = path.relative(root, candidate);
112
+ if (
113
+ relation === "" ||
114
+ relation === ".." ||
115
+ relation.startsWith(`..${path.sep}`) ||
116
+ path.isAbsolute(relation)
117
+ ) {
118
+ throw new TypeError(
119
+ "plugin eval suite must be a file inside the plugin root",
120
+ );
121
+ }
122
+ return relation.split(path.sep).join("/");
123
+ }
124
+
125
+ function collectPluginSnapshot(root) {
126
+ const rootStat = fs.lstatSync(root);
127
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
128
+ throw new TypeError("plugin root must be a real directory, not a symlink");
129
+ }
130
+ const files = [];
131
+ let totalBytes = 0;
132
+ let entriesVisited = 0;
133
+ const walk = (directory, relativeDirectory = "", depth = 0) => {
134
+ if (depth > MAX_PLUGIN_DEPTH) {
135
+ throw new RangeError(
136
+ `plugin eval payload exceeds the ${MAX_PLUGIN_DEPTH}-directory depth limit`,
137
+ );
138
+ }
139
+ const entries = fs
140
+ .readdirSync(directory, { withFileTypes: true })
141
+ .sort((a, b) => a.name.localeCompare(b.name));
142
+ for (const entry of entries) {
143
+ if (!relativeDirectory && entry.name === ".git") continue;
144
+ entriesVisited += 1;
145
+ if (entriesVisited > MAX_PLUGIN_ENTRIES) {
146
+ throw new RangeError(
147
+ `plugin eval payload exceeds ${MAX_PLUGIN_ENTRIES} filesystem entries`,
148
+ );
149
+ }
150
+ const relative = relativeDirectory
151
+ ? `${relativeDirectory}/${entry.name}`
152
+ : entry.name;
153
+ const absolute = path.join(directory, entry.name);
154
+ const stat = fs.lstatSync(absolute);
155
+ if (stat.isSymbolicLink()) {
156
+ throw new TypeError(`plugin eval refuses symlinks: ${relative}`);
157
+ }
158
+ if (stat.isDirectory()) {
159
+ walk(absolute, relative, depth + 1);
160
+ continue;
161
+ }
162
+ if (!stat.isFile()) {
163
+ throw new TypeError(`plugin eval refuses special files: ${relative}`);
164
+ }
165
+ if (stat.size > MAX_PLUGIN_BYTES - totalBytes) {
166
+ throw new RangeError(
167
+ `plugin eval payload exceeds ${MAX_PLUGIN_BYTES} bytes`,
168
+ );
169
+ }
170
+ const bytes = fs.readFileSync(absolute);
171
+ totalBytes += bytes.length;
172
+ if (
173
+ files.length + 1 > MAX_PLUGIN_FILES ||
174
+ totalBytes > MAX_PLUGIN_BYTES
175
+ ) {
176
+ throw new RangeError(
177
+ `plugin eval payload exceeds ${MAX_PLUGIN_FILES} files or ${MAX_PLUGIN_BYTES} bytes`,
178
+ );
179
+ }
180
+ files.push({ relative, bytes, mode: stat.mode & 0o777 });
181
+ }
182
+ };
183
+ walk(root);
184
+ const hash = crypto.createHash("sha256");
185
+ hash.update("chainlesschain.plugin-eval-payload/v1\0");
186
+ for (const file of files) {
187
+ hash.update(file.relative);
188
+ hash.update("\0");
189
+ hash.update(String(file.bytes.length));
190
+ hash.update("\0");
191
+ hash.update(String(file.mode));
192
+ hash.update("\0");
193
+ hash.update(file.bytes);
194
+ hash.update("\0");
195
+ }
196
+ return {
197
+ files,
198
+ fileCount: files.length,
199
+ byteCount: totalBytes,
200
+ digest: `sha256:${hash.digest("hex")}`,
201
+ };
202
+ }
203
+
204
+ function parseThreshold(value, label, { min = 0, max = 1 } = {}) {
205
+ if (value == null) return null;
206
+ if (
207
+ typeof value !== "number" ||
208
+ !Number.isFinite(value) ||
209
+ value < min ||
210
+ value > max
211
+ ) {
212
+ throw new RangeError(`${label} must be a number between ${min} and ${max}`);
213
+ }
214
+ return value;
215
+ }
216
+
217
+ function normalizeAssertion(value, taskId, index) {
218
+ const assertion = plainObject(value, `task ${taskId} assertion ${index}`);
219
+ if (!ASSERTION_TYPES.has(assertion.type)) {
220
+ throw new TypeError(
221
+ `task ${taskId} assertion ${index} has an unsupported type`,
222
+ );
223
+ }
224
+ const normalized = {
225
+ type: assertion.type,
226
+ path: relativeTaskPath(
227
+ assertion.path,
228
+ `task ${taskId} assertion ${index}.path`,
229
+ ),
230
+ };
231
+ if (["file_equals", "file_contains"].includes(assertion.type)) {
232
+ normalized.value = boundedString(
233
+ assertion.value,
234
+ `task ${taskId} assertion ${index}.value`,
235
+ MAX_FIXTURE_BYTES,
236
+ { allowEmpty: true, allowFormatting: true },
237
+ );
238
+ } else if (assertion.type === "json_equals") {
239
+ if (assertion.value === undefined) {
240
+ throw new TypeError(
241
+ `task ${taskId} assertion ${index}.value is required`,
242
+ );
243
+ }
244
+ const encoded = canonical(assertion.value);
245
+ if (Buffer.byteLength(encoded, "utf8") > MAX_FIXTURE_BYTES) {
246
+ throw new RangeError(
247
+ `task ${taskId} assertion ${index}.value exceeds its size limit`,
248
+ );
249
+ }
250
+ normalized.value = assertion.value;
251
+ }
252
+ return normalized;
253
+ }
254
+
255
+ function normalizeTask(value, index) {
256
+ const task = plainObject(value, `tasks[${index}]`);
257
+ const id = boundedString(task.id, `tasks[${index}].id`, 128);
258
+ if (!TASK_ID.test(id))
259
+ throw new TypeError(`invalid plugin eval task id: ${id}`);
260
+ const expectation = task.expectation || "should_trigger";
261
+ if (!EXPECTATIONS.has(expectation)) {
262
+ throw new TypeError(`task ${id} has an invalid expectation`);
263
+ }
264
+ const fixturesInput = task.fixtures || [];
265
+ if (
266
+ !Array.isArray(fixturesInput) ||
267
+ fixturesInput.length > MAX_FIXTURES_PER_TASK
268
+ ) {
269
+ throw new RangeError(`task ${id} has too many fixtures`);
270
+ }
271
+ let fixtureBytes = 0;
272
+ const fixturePaths = new Set();
273
+ const fixtures = fixturesInput.map((fixtureValue, fixtureIndex) => {
274
+ const fixture = plainObject(
275
+ fixtureValue,
276
+ `task ${id} fixture ${fixtureIndex}`,
277
+ );
278
+ const fixturePath = relativeTaskPath(
279
+ fixture.path,
280
+ `task ${id} fixture ${fixtureIndex}.path`,
281
+ );
282
+ if (fixturePaths.has(fixturePath)) {
283
+ throw new TypeError(
284
+ `task ${id} has duplicate fixture path ${fixturePath}`,
285
+ );
286
+ }
287
+ fixturePaths.add(fixturePath);
288
+ const encoding = fixture.encoding || "utf8";
289
+ if (!new Set(["utf8", "base64"]).has(encoding)) {
290
+ throw new TypeError(
291
+ `task ${id} fixture ${fixtureIndex} has invalid encoding`,
292
+ );
293
+ }
294
+ const content = boundedString(
295
+ fixture.content,
296
+ `task ${id} fixture ${fixtureIndex}.content`,
297
+ MAX_FIXTURE_BYTES,
298
+ { allowEmpty: true, allowFormatting: true },
299
+ );
300
+ const bytes = Buffer.from(content, encoding);
301
+ fixtureBytes += bytes.length;
302
+ if (bytes.length > MAX_FIXTURE_BYTES || fixtureBytes > MAX_SUITE_BYTES) {
303
+ throw new RangeError(`task ${id} fixture content exceeds its size limit`);
304
+ }
305
+ return { path: fixturePath, bytes };
306
+ });
307
+ if (
308
+ !Array.isArray(task.assertions) ||
309
+ task.assertions.length === 0 ||
310
+ task.assertions.length > MAX_ASSERTIONS_PER_TASK
311
+ ) {
312
+ throw new RangeError(
313
+ `task ${id} requires 1-${MAX_ASSERTIONS_PER_TASK} assertions`,
314
+ );
315
+ }
316
+ const assertions = task.assertions.map((assertion, assertionIndex) =>
317
+ normalizeAssertion(assertion, id, assertionIndex),
318
+ );
319
+ const expectedInput = task.expectedFiles || [
320
+ ...new Set(
321
+ assertions
322
+ .filter((assertion) => assertion.type !== "file_absent")
323
+ .map((assertion) => assertion.path),
324
+ ),
325
+ ];
326
+ if (!Array.isArray(expectedInput) || expectedInput.length > 128) {
327
+ throw new RangeError(`task ${id} has too many expectedFiles`);
328
+ }
329
+ const expectedFiles = [
330
+ ...new Set(
331
+ expectedInput.map((file, fileIndex) =>
332
+ relativeTaskPath(file, `task ${id}.expectedFiles[${fileIndex}]`),
333
+ ),
334
+ ),
335
+ ];
336
+ const timeoutMs = task.timeoutMs == null ? undefined : Number(task.timeoutMs);
337
+ if (
338
+ timeoutMs !== undefined &&
339
+ (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 600000)
340
+ ) {
341
+ throw new RangeError(`task ${id}.timeoutMs must be 1000-600000`);
342
+ }
343
+ return {
344
+ id,
345
+ description: boundedString(
346
+ task.description || "",
347
+ `task ${id}.description`,
348
+ 1024,
349
+ { allowEmpty: true, allowFormatting: true },
350
+ ),
351
+ prompt: boundedString(task.prompt, `task ${id}.prompt`, 32768, {
352
+ allowFormatting: true,
353
+ }),
354
+ expectation,
355
+ fixtures,
356
+ assertions,
357
+ expectedFiles,
358
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
359
+ };
360
+ }
361
+
362
+ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
363
+ const root = path.resolve(pluginRoot);
364
+ const manifest = parsePluginManifest(root);
365
+ if (manifest.ok !== true) {
366
+ throw new TypeError(
367
+ `plugin manifest is invalid: ${manifest.errors.join("; ") || "unknown error"}`,
368
+ );
369
+ }
370
+ const snapshot = collectPluginSnapshot(root);
371
+ const suitePath = suiteRelativePath(root, suiteFile);
372
+ const suiteEntry = snapshot.files.find((file) => file.relative === suitePath);
373
+ if (
374
+ !suiteEntry ||
375
+ suiteEntry.bytes.length === 0 ||
376
+ suiteEntry.bytes.length > MAX_SUITE_BYTES
377
+ ) {
378
+ throw new TypeError(
379
+ `plugin eval suite ${suitePath} must be a non-empty regular file under ${MAX_SUITE_BYTES} bytes`,
380
+ );
381
+ }
382
+ let raw;
383
+ try {
384
+ raw = JSON.parse(suiteEntry.bytes.toString("utf8"));
385
+ } catch {
386
+ throw new TypeError(`plugin eval suite ${suitePath} is not valid JSON`);
387
+ }
388
+ plainObject(raw, "plugin eval suite");
389
+ if (raw.schema !== PLUGIN_EVAL_SUITE_SCHEMA) {
390
+ throw new TypeError(
391
+ `plugin eval suite must use ${PLUGIN_EVAL_SUITE_SCHEMA}`,
392
+ );
393
+ }
394
+ const identity = plainObject(raw.plugin, "plugin eval suite.plugin");
395
+ if (
396
+ identity.name !== manifest.metadata.name ||
397
+ identity.version !== manifest.metadata.version
398
+ ) {
399
+ throw new TypeError(
400
+ "plugin eval suite identity must match the parsed plugin manifest",
401
+ );
402
+ }
403
+ if (
404
+ !Array.isArray(raw.tasks) ||
405
+ raw.tasks.length === 0 ||
406
+ raw.tasks.length > MAX_TASKS
407
+ ) {
408
+ throw new RangeError(`plugin eval suite requires 1-${MAX_TASKS} tasks`);
409
+ }
410
+ const tasks = raw.tasks.map(normalizeTask);
411
+ const ids = new Set(tasks.map((task) => task.id));
412
+ if (ids.size !== tasks.length) {
413
+ throw new TypeError("plugin eval task ids must be unique");
414
+ }
415
+ const thresholds = raw.thresholds || {};
416
+ plainObject(thresholds, "plugin eval suite.thresholds");
417
+ const maxCandidateCostUsd = parseThreshold(
418
+ thresholds.maxCandidateCostUsd,
419
+ "thresholds.maxCandidateCostUsd",
420
+ { min: 0, max: 1_000_000 },
421
+ );
422
+ return {
423
+ schema: PLUGIN_EVAL_SUITE_SCHEMA,
424
+ identity: {
425
+ name: manifest.metadata.name,
426
+ version: manifest.metadata.version,
427
+ skills: manifest.components.skills.map((skill) => skill.name),
428
+ },
429
+ suitePath,
430
+ suiteDigest: digest(
431
+ suiteEntry.bytes,
432
+ "chainlesschain.plugin-eval-suite-bytes/v1",
433
+ ),
434
+ payload: snapshot,
435
+ tasks,
436
+ thresholds: {
437
+ minPassRateDelta:
438
+ parseThreshold(
439
+ thresholds.minPassRateDelta,
440
+ "thresholds.minPassRateDelta",
441
+ { min: -1, max: 1 },
442
+ ) ?? 0,
443
+ maxUnrelatedChangeRate:
444
+ parseThreshold(
445
+ thresholds.maxUnrelatedChangeRate,
446
+ "thresholds.maxUnrelatedChangeRate",
447
+ ) ?? 1,
448
+ maxCandidateCostUsd,
449
+ },
450
+ };
451
+ }
452
+
453
+ function materializePlugin(definition, workspace) {
454
+ const nameDir = path.join(
455
+ workspace,
456
+ ".chainlesschain",
457
+ "plugins",
458
+ encodeName(definition.identity.name),
459
+ );
460
+ const versionDir = path.join(nameDir, definition.identity.version);
461
+ for (const file of definition.payload.files) {
462
+ const target = path.join(versionDir, ...file.relative.split("/"));
463
+ fs.mkdirSync(path.dirname(target), { recursive: true });
464
+ fs.writeFileSync(target, file.bytes, { flag: "wx" });
465
+ fs.chmodSync(target, file.mode);
466
+ }
467
+ fs.writeFileSync(path.join(nameDir, ".active"), definition.identity.version, {
468
+ encoding: "utf8",
469
+ flag: "wx",
470
+ });
471
+ }
472
+
473
+ function readTaskFile(workspace, relative) {
474
+ const segments = relative.split("/");
475
+ let target = workspace;
476
+ try {
477
+ for (const [index, segment] of segments.entries()) {
478
+ target = path.join(target, segment);
479
+ const component = fs.lstatSync(target);
480
+ if (component.isSymbolicLink()) return null;
481
+ if (index < segments.length - 1 && !component.isDirectory()) return null;
482
+ }
483
+ const stat = fs.lstatSync(target);
484
+ if (
485
+ !stat.isFile() ||
486
+ stat.isSymbolicLink() ||
487
+ stat.size > MAX_ASSERTION_FILE_BYTES
488
+ ) {
489
+ return null;
490
+ }
491
+ const workspaceReal = fs.realpathSync(workspace);
492
+ const targetReal = fs.realpathSync(target);
493
+ const relation = path.relative(workspaceReal, targetReal);
494
+ if (
495
+ relation === ".." ||
496
+ relation.startsWith(`..${path.sep}`) ||
497
+ path.isAbsolute(relation)
498
+ ) {
499
+ return null;
500
+ }
501
+ const noFollow = fs.constants.O_NOFOLLOW || 0;
502
+ const descriptor = fs.openSync(target, fs.constants.O_RDONLY | noFollow);
503
+ try {
504
+ const opened = fs.fstatSync(descriptor);
505
+ if (!opened.isFile() || opened.size > MAX_ASSERTION_FILE_BYTES)
506
+ return null;
507
+ return fs.readFileSync(descriptor);
508
+ } finally {
509
+ fs.closeSync(descriptor);
510
+ }
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+
516
+ function checkAssertions(task, workspace) {
517
+ for (const assertion of task.assertions) {
518
+ const bytes = readTaskFile(workspace, assertion.path);
519
+ if (assertion.type === "file_absent") {
520
+ if (bytes !== null) {
521
+ return { pass: false, detail: `${assertion.path} should be absent` };
522
+ }
523
+ continue;
524
+ }
525
+ if (bytes === null) {
526
+ return { pass: false, detail: `${assertion.path} is missing` };
527
+ }
528
+ if (assertion.type === "file_exists") continue;
529
+ const text = bytes.toString("utf8");
530
+ if (assertion.type === "file_equals" && text !== assertion.value) {
531
+ return { pass: false, detail: `${assertion.path} content did not match` };
532
+ }
533
+ if (assertion.type === "file_contains" && !text.includes(assertion.value)) {
534
+ return {
535
+ pass: false,
536
+ detail: `${assertion.path} did not contain the expected text`,
537
+ };
538
+ }
539
+ if (assertion.type === "json_equals") {
540
+ let actual;
541
+ try {
542
+ actual = JSON.parse(text);
543
+ } catch {
544
+ return { pass: false, detail: `${assertion.path} is not valid JSON` };
545
+ }
546
+ if (canonical(actual) !== canonical(assertion.value)) {
547
+ return { pass: false, detail: `${assertion.path} JSON did not match` };
548
+ }
549
+ }
550
+ }
551
+ return { pass: true, detail: "all declarative assertions passed" };
552
+ }
553
+
554
+ function buildTasks(definition, { candidate }) {
555
+ return definition.tasks.map((task) => ({
556
+ id: task.id,
557
+ description: task.description,
558
+ prompt: task.prompt,
559
+ expectedFiles: task.expectedFiles,
560
+ ...(task.timeoutMs === undefined ? {} : { timeoutMs: task.timeoutMs }),
561
+ setup(workspace) {
562
+ for (const fixture of task.fixtures) {
563
+ const target = path.join(workspace, ...fixture.path.split("/"));
564
+ fs.mkdirSync(path.dirname(target), { recursive: true });
565
+ fs.writeFileSync(target, fixture.bytes, { flag: "wx" });
566
+ }
567
+ if (candidate) materializePlugin(definition, workspace);
568
+ },
569
+ check(workspace) {
570
+ return checkAssertions(task, workspace);
571
+ },
572
+ }));
573
+ }
574
+
575
+ function normalizedUsage(value) {
576
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
577
+ const output = {};
578
+ for (const key of [
579
+ "input_tokens",
580
+ "output_tokens",
581
+ "cache_read_input_tokens",
582
+ "cache_creation_input_tokens",
583
+ "reasoning_tokens",
584
+ ]) {
585
+ const amount = value[key];
586
+ if (typeof amount === "number" && Number.isFinite(amount) && amount >= 0) {
587
+ output[key] = amount;
588
+ }
589
+ }
590
+ return output;
591
+ }
592
+
593
+ export function parsePluginEvalRuntimeMetrics(output, identity) {
594
+ try {
595
+ const serialized = String(output || "");
596
+ if (Buffer.byteLength(serialized, "utf8") > MAX_RUNTIME_OUTPUT_BYTES) {
597
+ return { available: false, pluginTriggered: null };
598
+ }
599
+ const events = serialized
600
+ .split(/\r?\n/u)
601
+ .filter((line) => line.trim())
602
+ .map((line) => JSON.parse(line));
603
+ const terminals = events.filter((event) => event?.type === "result");
604
+ if (terminals.length !== 1 || events.at(-1) !== terminals[0]) {
605
+ return { available: false, pluginTriggered: null };
606
+ }
607
+ const terminal = terminals[0];
608
+ const toolCalls = Array.isArray(terminal.tool_calls)
609
+ ? terminal.tool_calls
610
+ : [];
611
+ if (toolCalls.length > MAX_RUNTIME_TOOL_CALLS) {
612
+ return { available: false, pluginTriggered: null };
613
+ }
614
+ const skillNames = new Set(identity.skills || []);
615
+ const signals = [];
616
+ for (const call of toolCalls) {
617
+ if (call?.plugin === identity.name) {
618
+ if (signals.length < MAX_TRIGGER_SIGNALS) {
619
+ signals.push({
620
+ source: "plugin_attribution",
621
+ tool: call.tool || null,
622
+ });
623
+ }
624
+ continue;
625
+ }
626
+ if (
627
+ call?.tool === "run_skill" &&
628
+ skillNames.has(call?.args?.skill_name)
629
+ ) {
630
+ if (signals.length < MAX_TRIGGER_SIGNALS) {
631
+ signals.push({
632
+ source: "skill_name",
633
+ tool: "run_skill",
634
+ skill: call.args.skill_name,
635
+ });
636
+ }
637
+ }
638
+ }
639
+ const cost = terminal.total_cost_usd;
640
+ return {
641
+ available: true,
642
+ pluginTriggered: signals.length > 0,
643
+ signals,
644
+ usage: normalizedUsage(terminal.usage),
645
+ totalCostUsd:
646
+ typeof cost === "number" && Number.isFinite(cost) && cost >= 0
647
+ ? cost
648
+ : null,
649
+ turns:
650
+ Number.isSafeInteger(terminal.num_turns) && terminal.num_turns >= 0
651
+ ? terminal.num_turns
652
+ : null,
653
+ toolCallCount: toolCalls.length,
654
+ };
655
+ } catch {
656
+ return { available: false, pluginTriggered: null };
657
+ }
658
+ }
659
+
660
+ function instrumentRunAgent(runAgent, identity) {
661
+ return async (input) => {
662
+ const result = await runAgent(input);
663
+ return {
664
+ ...result,
665
+ evaluationMetrics: parsePluginEvalRuntimeMetrics(
666
+ result?.output,
667
+ identity,
668
+ ),
669
+ };
670
+ };
671
+ }
672
+
673
+ function sumUsage(results) {
674
+ const totals = {};
675
+ let cost = 0;
676
+ let costAvailable = true;
677
+ for (const result of results) {
678
+ const metrics = result.evaluationMetrics;
679
+ if (metrics?.usage) {
680
+ for (const [key, value] of Object.entries(metrics.usage)) {
681
+ totals[key] = (totals[key] || 0) + value;
682
+ }
683
+ }
684
+ if (typeof metrics?.totalCostUsd === "number") cost += metrics.totalCostUsd;
685
+ else costAvailable = false;
686
+ }
687
+ return {
688
+ usage: totals,
689
+ totalCostUsd: costAvailable ? cost : null,
690
+ };
691
+ }
692
+
693
+ function projectArm(summary, definition, { candidate }) {
694
+ const byId = new Map(definition.tasks.map((task) => [task.id, task]));
695
+ const results = summary.results.map((result) => {
696
+ const task = byId.get(result.id);
697
+ const metrics = result.evaluationMetrics || {
698
+ available: false,
699
+ pluginTriggered: null,
700
+ };
701
+ const expectation = candidate ? task.expectation : "should_not_trigger";
702
+ const activationPassed =
703
+ metrics.available === true &&
704
+ (expectation === "optional" ||
705
+ (expectation === "should_trigger" &&
706
+ metrics.pluginTriggered === true) ||
707
+ (expectation === "should_not_trigger" &&
708
+ metrics.pluginTriggered === false));
709
+ return {
710
+ ...result,
711
+ expectation,
712
+ pluginTriggered: metrics.pluginTriggered,
713
+ activationEvidenceAvailable: metrics.available === true,
714
+ activationPassed,
715
+ effectivePass: result.pass && activationPassed,
716
+ };
717
+ });
718
+ const effectivePassed = results.filter(
719
+ (result) => result.effectivePass,
720
+ ).length;
721
+ const triggered = results.filter(
722
+ (result) => result.pluginTriggered === true,
723
+ ).length;
724
+ return {
725
+ ...summary,
726
+ results,
727
+ effectivePassed,
728
+ effectiveFailed: results.length - effectivePassed,
729
+ effectivePassRate: results.length ? effectivePassed / results.length : 0,
730
+ triggerRate: results.length ? triggered / results.length : 0,
731
+ activationEvidenceComplete: results.every(
732
+ (result) => result.activationEvidenceAvailable,
733
+ ),
734
+ ...sumUsage(results),
735
+ };
736
+ }
737
+
738
+ function reportArm(arm) {
739
+ return {
740
+ passed: arm.passed,
741
+ failed: arm.failed,
742
+ total: arm.total,
743
+ passRate: arm.passRate,
744
+ effectivePassed: arm.effectivePassed,
745
+ effectiveFailed: arm.effectiveFailed,
746
+ effectivePassRate: arm.effectivePassRate,
747
+ artifactChecksPassed: arm.artifactChecksPassed,
748
+ executionsSucceeded: arm.executionsSucceeded,
749
+ unrelatedChangeRate: arm.unrelatedChangeRate,
750
+ tasksWithUnrelatedChanges: arm.tasksWithUnrelatedChanges,
751
+ triggerRate: arm.triggerRate,
752
+ activationEvidenceComplete: arm.activationEvidenceComplete,
753
+ usage: arm.usage,
754
+ totalCostUsd: arm.totalCostUsd,
755
+ ms: arm.ms,
756
+ results: arm.results,
757
+ };
758
+ }
759
+
760
+ export async function runPluginEval(
761
+ definition,
762
+ {
763
+ controlRunAgent,
764
+ candidateRunAgent,
765
+ runSuite = runEvalSuite,
766
+ keepWorkspaces = false,
767
+ provider = null,
768
+ model = null,
769
+ dryRun = false,
770
+ minPassRateDelta,
771
+ onResult,
772
+ } = {},
773
+ ) {
774
+ if (
775
+ typeof controlRunAgent !== "function" ||
776
+ typeof candidateRunAgent !== "function"
777
+ ) {
778
+ throw new TypeError("controlRunAgent and candidateRunAgent are required");
779
+ }
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 },
794
+ );
795
+ const candidate = projectArm(
796
+ await runArm(
797
+ "candidate",
798
+ buildTasks(definition, { candidate: true }),
799
+ candidateRunAgent,
800
+ ),
801
+ definition,
802
+ { candidate: true },
803
+ );
804
+ const requiredDelta =
805
+ minPassRateDelta === undefined
806
+ ? definition.thresholds.minPassRateDelta
807
+ : parseThreshold(minPassRateDelta, "minPassRateDelta", {
808
+ min: -1,
809
+ max: 1,
810
+ });
811
+ const passRateDelta = candidate.effectivePassRate - control.effectivePassRate;
812
+ const reasons = [];
813
+ if (dryRun) reasons.push("dry_run");
814
+ if (
815
+ !control.activationEvidenceComplete ||
816
+ !candidate.activationEvidenceComplete
817
+ ) {
818
+ reasons.push("activation_evidence_incomplete");
819
+ }
820
+ if (candidate.effectivePassed !== candidate.total) {
821
+ reasons.push("candidate_tasks_failed");
822
+ }
823
+ if (passRateDelta < requiredDelta) reasons.push("minimum_gain_not_met");
824
+ if (
825
+ candidate.unrelatedChangeRate > definition.thresholds.maxUnrelatedChangeRate
826
+ ) {
827
+ reasons.push("unrelated_change_threshold_exceeded");
828
+ }
829
+ if (
830
+ definition.thresholds.maxCandidateCostUsd !== null &&
831
+ (candidate.totalCostUsd === null ||
832
+ candidate.totalCostUsd > definition.thresholds.maxCandidateCostUsd)
833
+ ) {
834
+ reasons.push("candidate_cost_threshold_not_met");
835
+ }
836
+ const uniqueReasons = [...new Set(reasons)];
837
+ const status = dryRun
838
+ ? "INSUFFICIENT_EVIDENCE"
839
+ : uniqueReasons.length
840
+ ? "FAIL"
841
+ : "PASS";
842
+ return {
843
+ schema: PLUGIN_EVAL_REPORT_SCHEMA,
844
+ scope: "local-plugin-eval",
845
+ productionAttested: false,
846
+ status,
847
+ passed: status === "PASS",
848
+ reasons: uniqueReasons,
849
+ plugin: {
850
+ name: definition.identity.name,
851
+ version: definition.identity.version,
852
+ payloadDigest: definition.payload.digest,
853
+ payloadFiles: definition.payload.fileCount,
854
+ payloadBytes: definition.payload.byteCount,
855
+ },
856
+ suite: {
857
+ schema: definition.schema,
858
+ path: definition.suitePath,
859
+ digest: definition.suiteDigest,
860
+ tasks: definition.tasks.length,
861
+ },
862
+ selection: {
863
+ provider,
864
+ model,
865
+ dryRun: dryRun === true,
866
+ },
867
+ thresholds: {
868
+ ...definition.thresholds,
869
+ minPassRateDelta: requiredDelta,
870
+ },
871
+ comparison: {
872
+ passRateDelta,
873
+ outcome:
874
+ passRateDelta > 0
875
+ ? "gain"
876
+ : passRateDelta < 0
877
+ ? "regression"
878
+ : "neutral",
879
+ },
880
+ arms: {
881
+ control: reportArm(control),
882
+ candidate: reportArm(candidate),
883
+ },
884
+ };
885
+ }
886
+
887
+ function escapeHtml(value) {
888
+ return String(value)
889
+ .replaceAll("&", "&amp;")
890
+ .replaceAll("<", "&lt;")
891
+ .replaceAll(">", "&gt;")
892
+ .replaceAll('"', "&quot;")
893
+ .replaceAll("'", "&#39;");
894
+ }
895
+
896
+ export function renderPluginEvalHtml(report) {
897
+ const rows = report.arms.candidate.results
898
+ .map(
899
+ (result) =>
900
+ `<tr><td>${escapeHtml(result.id)}</td><td>${escapeHtml(result.expectation)}</td><td>${escapeHtml(result.pluginTriggered)}</td><td>${escapeHtml(result.effectivePass)}</td><td>${escapeHtml(result.detail || result.error || "")}</td></tr>`,
901
+ )
902
+ .join("");
903
+ return [
904
+ "<!doctype html>",
905
+ '<html lang="en"><head><meta charset="utf-8">',
906
+ `<title>Plugin eval: ${escapeHtml(report.plugin.name)}</title>`,
907
+ "<style>body{font-family:system-ui,sans-serif;margin:2rem;max-width:1100px}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:.5rem;text-align:left}code{overflow-wrap:anywhere}</style>",
908
+ "</head><body>",
909
+ `<h1>Plugin eval: ${escapeHtml(report.plugin.name)} ${escapeHtml(report.plugin.version)}</h1>`,
910
+ `<p>Status: <strong>${escapeHtml(report.status)}</strong>; outcome: ${escapeHtml(report.comparison.outcome)}; pass-rate delta: ${escapeHtml(report.comparison.passRateDelta)}</p>`,
911
+ `<p>Payload: <code>${escapeHtml(report.plugin.payloadDigest)}</code><br>Suite: <code>${escapeHtml(report.suite.digest)}</code></p>`,
912
+ "<table><thead><tr><th>Task</th><th>Expectation</th><th>Triggered</th><th>Effective pass</th><th>Detail</th></tr></thead>",
913
+ `<tbody>${rows}</tbody></table>`,
914
+ `<h2>Machine-readable report</h2><pre>${escapeHtml(JSON.stringify(report, null, 2))}</pre>`,
915
+ "</body></html>\n",
916
+ ].join("");
917
+ }