chainlesschain 0.166.69 → 0.166.70

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 (171) hide show
  1. package/README.md +24 -5
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-FhevRfL6.js → AIOps-BBxGVjAo.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-rOeOykx5.js → ActionButton-SrT9X1LG.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BK_wYlfz.js → Analytics-D0MPWFct.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-_2cCtZKV.js → AppLayout-DlmlYhUp.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-D5MSvVzk.js → Artifacts-BzLV8tLT.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-yd-Ah-A3.js → Audit-BWfARNeo.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-yCaw_Gu_.js → BackgroundAgents-AT_lu-OI.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-G3ADD4N-.js → Backup-6pH5UJRI.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-ByDhkLbF.js → BaseInput-CccGGr8I.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BMrc7qjz.js → Chat-B6aQ-lXU.js} +6 -6
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-cKmA5EAa.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-BHYgyZWA.js → Checkbox-Dc_dMjlV.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-DJfcy0jC.js → Codegen-CHm0YK-0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-C27zROxc.js → Col-IaBwOtml.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-CuS5tq_1.js → Community-U5N0EakJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BrYJNe7Q.js → Compact-D6bK2hw-.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-8hfahYHT.js → Compliance-B0G1zA62.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DdE0n2lp.js → Cowork-BvJxGzAT.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-CUq3aSiX.js → Cron-N5JOZWe9.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BM4XSPWG.js → Crosschain-CgnzS-_7.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CdaBY57N.js → DID-BTMEilVf.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-Cwtd52So.js → Dashboard-Bc-1wTPH.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BvlObh6U.js → Dropdown-DfJLRkz2.js} +1 -1
  26. package/src/assets/web-panel/assets/EmailListRenderer-DOkzpgap.js +1 -0
  27. package/src/assets/web-panel/assets/{EvolutionSettings-CTtk1Zo8.js → EvolutionSettings-23ZdCcyg.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BpPJUnCU.js → FamilyGuardDashboard-mBhA1oWh.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-vzjZ6_nQ.js → Federation-C9Kmj_hs.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DEDNLhkE.js → FormItemContext-CeU7fnW_.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DEl2Te5f.js → GenericCardRenderer-s-QOculU.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BzsYQnCq.js → Git-BbePEFdx.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-zxJs6Y8s.js → Governance-PnVQJUn7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-5JUsrmF1.js → Inference-sCcd1Foj.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-Br8b5Htz.js → KnowledgeGraph-DdM6cJOh.js} +1 -1
  36. package/src/assets/web-panel/assets/Logs-Dznmdibo.js +2 -0
  37. package/src/assets/web-panel/assets/MarkdownRenderer-nFgRqRUW.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-s8MmWkS9.js → Marketplace-DQ76qwhE.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-Wu-WRg4M.js → McpTools-CZqTYtmV.js} +3 -3
  40. package/src/assets/web-panel/assets/{Memory-B_i-eX9w.js → Memory-CWtnaJCd.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-Ca64kjVx.js → MobileBridge-C3Ngpr63.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-Cq9AFiO4.js → MobileProjects-CpJPlABd.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-DKvGmG3y.js → Mtc-CKL_fDu2.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-BVvWx8pF.js → MtcAudit-CIDSj87F.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-wqeJ2O9x.js → Multisig-BoLpIo5g.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DX87ysYu.js → NLProgramming-CWJX8SH5.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-ChenGdu0.js → Notes-CdGvaOEe.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-BZRa68-x.js → NotificationSettings-C067lOY0.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DZ6xG1M6.js → OrderTableRenderer-Bkdj8JZF.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-BWJp4VPX.js → Organization-CwhP3jqH.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-BoM4bYt5.js → Overflow-Ks7pF8mI.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-Cvi8YV09.js → P2P-BytzAo-_.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CNVC1NIj.js → PdhVaultBrowser-f7PUa-As.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-DNd1MRjW.js → Permissions-DYgQYc6c.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-DGSBYZ5-.js → PersonalDataHub-xa4C85B5.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-DkcoGMBo.js → Pipeline-Ci4P4D_9.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-DltwvMAW.js → Privacy-Bc0i4kUv.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-ChYbpHg8.js → ProjectInit-BNQNNbq1.js} +2 -2
  59. package/src/assets/web-panel/assets/ProjectSettings-C9a52UVV.js +2 -0
  60. package/src/assets/web-panel/assets/Projects-DoqbdOhN.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-Bo18Tx_6.js → Providers-aqNSVXqu.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-6W85KlsG.js → QrScannerModal-BhiK-alo.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DznRUedP.js → QuickAsk-DqiqZt1A.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-CQDN3_p-.js → Recommend-c0WOkaCq.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-CiJgokEj.js → RemoteSession-Cf_4L-X3.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-DexzGH7E.js → Reputation-BVFrZQ_z.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-BgZOIvSi.js → Row-CZ1AwkDl.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BWIZJ_Jr.js → RssFeed--WyTTgfC.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-B376EwWJ.js → Search-BgxZ24St.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-92mUR5OD.js → Security-DVkmHsXI.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-ClSEAOHk.js → Services-CTZQQWlI.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DVhLbv99.js → Skeleton-DWwUMTlS.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-JTcXs_xI.js → Skills-D6iyc72U.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DnKY9zy9.js → Sla-ChMCfgKV.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-GO1JefAH.js → SpeechSettings-CtwWxhK7.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-jX5tB2St.js → SyncSettings-DAbLxMOM.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-Dvm355ug.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-CAVkbTG1.js → Templates-Dig8IEP-.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-5Y7sHBkz.js → Tenant-DeR5U47w.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-CbbLShYb.js → Terminal-CHXg-gsD.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-2HptWihL.js → TimelineRenderer-Bi4GJgwI.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-B-jGxzHl.js → Tokens-D6ubE4ut.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-9DGa90DT.js → Trigger-CUQI4HlL.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-BSJEkEOO.js → Trust-CgTAWXd7.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-C807So5R.js → UkeySign-R2bAawIX.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-ByiieAv-.js → VideoEditing-46u-3sdP.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-BixbIOUv.js → Wallet-DdrACErK.js} +3 -3
  88. package/src/assets/web-panel/assets/{WebAuthn-z7akhor4.js → WebAuthn-Cq266Ihg.js} +2 -2
  89. package/src/assets/web-panel/assets/{WorkflowEditor-KvijuUuR.js → WorkflowEditor-DV0ES1wc.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-BYKoh0YG.js → chat-D5xy9-vl.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-CLT8IYBt.js → colors-BBs5Knx_.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-DlBIyYHF.js → compact-item-BuFzMOeP.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-5XmAnNLr.js → createContext-D6gC8UX1.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-jGzCD1Og.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-rPdRhm7K.js → hasIn-kBeod51t.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DuvweiCz.js → index-43tYtngc.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DCDSycWo.js → index-8LcW9fNg.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DW8vozBS.js → index-AVkVBAgT.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CM_a9spK.js → index-B9sYKnfE.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B1VfBUPB.js → index-BEipvyo3.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DqZU4_7J.js → index-BJtaYRSM.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DzExCCLS.js → index-BNgGahh_.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B-0nAxf5.js → index-BPfLeT9D.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D3f9Jzx2.js → index-BrmMhNQe.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BDtlwjSr.js → index-C61TSHk7.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CErvHCDe.js → index-C9RbA1u6.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DTslLzPJ.js → index-CDc8ZBhw.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DIVrVGvf.js → index-CSR6G5y_.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-LKT-Ol0A.js → index-Cc0nr-mJ.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BBt2CSAx.js → index-CdXTYNGI.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DZjGcIGU.js → index-CoF4d2sR.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Dea32nOK.js → index-Cy8WOPIM.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BgevTkUv.js → index-D5w7mVUz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CsnbOKdI.js → index-D8i4RtvF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-akwXS-FF.js → index-D8vRjV35.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-eHbr3f5I.js → index-DLO3EJ28.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Djd7uqwM.js → index-DP74sxrD.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BIRl2Ii5.js → index-DT8dVo6h.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-rRuCGdKW.js → index-DVY9bZaC.js} +1 -1
  120. package/src/assets/web-panel/assets/index-DVuuRGW6.js +1 -0
  121. package/src/assets/web-panel/assets/{index-Bve7Wsgf.js → index-DWpwO1tg.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ChlzaTV3.js → index-Db_4IGgr.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-kx6zDWew.js → index-DiEf8pdZ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DZiSMYoD.js → index-DjJD0ItG.js} +3 -3
  125. package/src/assets/web-panel/assets/index-DjkHUbjO.js +1 -0
  126. package/src/assets/web-panel/assets/{index-Bzd0sFkp.js → index-DkcAtMWM.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D3-NL-x-.js → index-Dn4FQfDW.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DPS4mWnI.js → index-DuFsuweQ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CY6TMc2m.js → index-WMRvzqet.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dntid6oj.js → index-Xmeq1989.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C2sFDVb_.js → index-ZMxLi0bZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-b-rQCMkx.js → index-_j1YrDlh.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-8un-eUDI.js → index-qXVSh34h.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DI_tYFBe.js → index-slOdrjUJ.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-a8411LiK.js → initDefaultProps-CdcTIUHj.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-oVcQdpCm.js → motion-DMIDKmqt.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-B-eyzzzL.js → move-DRq4Pk0-.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-BCYIxuG7.js → mtc-parser-SYFo5WKx.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-DuFtWiYh.js → omit-B_X6VxfU.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-CVAoJhGu.js → pickAttrs-DnuDKnw_.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-Y2iub2bO.js → placementArrow-DBFRmoTN.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-BHenWIVD.js → responsiveObserve-BaUVjf33.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-CKrMaL29.js → slide-rLCDXDVC.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-w8eb_1da.js → statusUtils-CzNprwdC.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-CCVi_Ww3.js → styleChecker-DsaDdSa9.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-BsmR_q0r.js → useFlexGapSupport-BKHgNy2p.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-a8GMBjnc.js → useFs-C_OQYo_E.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-BMUYSPn-.js → usePersonalDataHub-CiGRKQ5k.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-DL2pI31V.js → vnode-C9-ch6HP.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-Cbi8fISi.js → zoom-HoUcoyA4.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/agent.js +84 -0
  154. package/src/data/changelog.json +13 -0
  155. package/src/lib/decision-layer/benchmark.js +164 -0
  156. package/src/lib/decision-layer/contracts.js +377 -0
  157. package/src/lib/decision-layer/provider-authority.js +44 -0
  158. package/src/lib/decision-layer/runtime.js +238 -0
  159. package/src/lib/decision-layer/typesafe-provider.js +74 -0
  160. package/src/runtime/agent-core.js +49 -0
  161. package/src/runtime/headless-runner.js +79 -0
  162. package/src/assets/web-panel/assets/ChatBubbleRenderer-nA7n0I6S.js +0 -1
  163. package/src/assets/web-panel/assets/EmailListRenderer-e3fLhjnX.js +0 -1
  164. package/src/assets/web-panel/assets/Logs-D-tubDMJ.js +0 -2
  165. package/src/assets/web-panel/assets/MarkdownRenderer-In4itZX2.js +0 -1
  166. package/src/assets/web-panel/assets/ProjectSettings-B8FWiHTJ.js +0 -2
  167. package/src/assets/web-panel/assets/Projects-DuyhtSm4.js +0 -1
  168. package/src/assets/web-panel/assets/Tasks-BxRBzhUc.js +0 -1
  169. package/src/assets/web-panel/assets/devWarning-BmcCIHwY.js +0 -1
  170. package/src/assets/web-panel/assets/index-DY8rmd6x.js +0 -1
  171. package/src/assets/web-panel/assets/index-sbwcUeRS.js +0 -1
@@ -0,0 +1,164 @@
1
+ import { performance } from "node:perf_hooks";
2
+
3
+ import { decisionDigest } from "./contracts.js";
4
+
5
+ export const SKILL_DECISION_BENCHMARK_SCHEMA =
6
+ "chainlesschain.skill-decision-benchmark/v1";
7
+
8
+ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
9
+
10
+ function ratio(value, label) {
11
+ const number = Number(value);
12
+ if (!Number.isFinite(number) || number < 0 || number > 1) {
13
+ throw new TypeError(`${label} must be between zero and one`);
14
+ }
15
+ return number;
16
+ }
17
+
18
+ function normalizeThresholds(input) {
19
+ const p95Ms = Number(input?.p95Ms);
20
+ if (!Number.isFinite(p95Ms) || p95Ms <= 0) {
21
+ throw new TypeError("p95Ms must be positive");
22
+ }
23
+ return Object.freeze({
24
+ acceptedSuggestionErrorRate: ratio(
25
+ input?.acceptedSuggestionErrorRate,
26
+ "acceptedSuggestionErrorRate",
27
+ ),
28
+ noMatchFalseSuggestionRate: ratio(
29
+ input?.noMatchFalseSuggestionRate,
30
+ "noMatchFalseSuggestionRate",
31
+ ),
32
+ affirmativeCoverage: ratio(
33
+ input?.affirmativeCoverage,
34
+ "affirmativeCoverage",
35
+ ),
36
+ p95Ms,
37
+ });
38
+ }
39
+
40
+ function normalizeCases(cases) {
41
+ if (!Array.isArray(cases) || cases.length < 1 || cases.length > 10_000) {
42
+ throw new TypeError("decision benchmark cases are invalid or unbounded");
43
+ }
44
+ return cases.map((item, index) => {
45
+ if (
46
+ !item ||
47
+ typeof item.query !== "string" ||
48
+ item.query.trim() === "" ||
49
+ !Array.isArray(item.candidates) ||
50
+ item.candidates.length < 1 ||
51
+ !Array.isArray(item.acceptableDigests) ||
52
+ item.acceptableDigests.some((digest) => !DIGEST.test(digest)) ||
53
+ new Set(item.acceptableDigests).size !== item.acceptableDigests.length
54
+ ) {
55
+ throw new TypeError(`decision benchmark case ${index} is invalid`);
56
+ }
57
+ const candidateDigests = new Set(
58
+ item.candidates.map(({ digest }) => digest),
59
+ );
60
+ if (
61
+ item.acceptableDigests.some((digest) => !candidateDigests.has(digest))
62
+ ) {
63
+ throw new TypeError(
64
+ `decision benchmark case ${index} has truth outside the candidates`,
65
+ );
66
+ }
67
+ return Object.freeze({
68
+ query: item.query.trim(),
69
+ candidates: Object.freeze(
70
+ item.candidates.map((candidate) => Object.freeze({ ...candidate })),
71
+ ),
72
+ acceptableDigests: Object.freeze([...item.acceptableDigests]),
73
+ });
74
+ });
75
+ }
76
+
77
+ export async function runSkillDecisionBenchmark({
78
+ runtime,
79
+ cases,
80
+ thresholds,
81
+ turnIdPrefix = "benchmark",
82
+ contextRevision = "benchmark-v1",
83
+ clock = () => performance.now(),
84
+ } = {}) {
85
+ if (!runtime || typeof runtime.suggest !== "function") {
86
+ throw new TypeError("decision benchmark requires a runtime");
87
+ }
88
+ const normalizedCases = normalizeCases(cases);
89
+ const gate = normalizeThresholds(thresholds);
90
+ const latencies = [];
91
+ let accepted = 0;
92
+ let acceptedErrors = 0;
93
+ let positives = 0;
94
+ let affirmative = 0;
95
+ let noMatches = 0;
96
+ let noMatchFalseSuggestions = 0;
97
+ let abstentions = 0;
98
+ let unavailable = 0;
99
+ const rows = [];
100
+ for (const [index, item] of normalizedCases.entries()) {
101
+ const start = Number(clock());
102
+ const result = await runtime.suggest({
103
+ query: item.query,
104
+ candidates: item.candidates,
105
+ turnId: `${turnIdPrefix}-${index}`,
106
+ contextRevision,
107
+ });
108
+ const end = Number(clock());
109
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) {
110
+ throw new Error("decision benchmark clock is invalid");
111
+ }
112
+ latencies.push(end - start);
113
+ const isPositive = item.acceptableDigests.length > 0;
114
+ if (isPositive) positives += 1;
115
+ else noMatches += 1;
116
+ if (result.status === "suggestion") {
117
+ accepted += 1;
118
+ if (isPositive) affirmative += 1;
119
+ const correct = item.acceptableDigests.includes(result.selectedDigest);
120
+ if (!correct) acceptedErrors += 1;
121
+ if (!isPositive) noMatchFalseSuggestions += 1;
122
+ } else if (result.status === "abstain") abstentions += 1;
123
+ else if (result.status === "unavailable") unavailable += 1;
124
+ rows.push(
125
+ Object.freeze({
126
+ index,
127
+ status: result.status,
128
+ selectedDigest: result.selectedDigest,
129
+ acceptableDigests: item.acceptableDigests,
130
+ latencyMs: end - start,
131
+ }),
132
+ );
133
+ }
134
+ latencies.sort((left, right) => left - right);
135
+ const metrics = Object.freeze({
136
+ acceptedSuggestionErrorRate: acceptedErrors / Math.max(1, accepted),
137
+ noMatchFalseSuggestionRate:
138
+ noMatchFalseSuggestions / Math.max(1, noMatches),
139
+ affirmativeCoverage: affirmative / Math.max(1, positives),
140
+ abstentionRate: abstentions / normalizedCases.length,
141
+ unavailableRate: unavailable / normalizedCases.length,
142
+ p95Ms: latencies[Math.max(0, Math.ceil(latencies.length * 0.95) - 1)],
143
+ });
144
+ const core = {
145
+ schema: SKILL_DECISION_BENCHMARK_SCHEMA,
146
+ caseCount: normalizedCases.length,
147
+ caseDigest: decisionDigest(
148
+ "chainlesschain.skill-decision-benchmark-cases/v1",
149
+ normalizedCases,
150
+ ),
151
+ thresholds: gate,
152
+ metrics,
153
+ passed:
154
+ metrics.acceptedSuggestionErrorRate <= gate.acceptedSuggestionErrorRate &&
155
+ metrics.noMatchFalseSuggestionRate <= gate.noMatchFalseSuggestionRate &&
156
+ metrics.affirmativeCoverage >= gate.affirmativeCoverage &&
157
+ metrics.p95Ms <= gate.p95Ms,
158
+ rows: Object.freeze(rows),
159
+ };
160
+ return Object.freeze({
161
+ ...core,
162
+ reportDigest: decisionDigest(SKILL_DECISION_BENCHMARK_SCHEMA, core),
163
+ });
164
+ }
@@ -0,0 +1,377 @@
1
+ import { createHash } from "node:crypto";
2
+ import { types as utilTypes } from "node:util";
3
+
4
+ export const SKILL_DECISION_REQUEST_SCHEMA =
5
+ "chainlesschain.skill-decision-request/v1";
6
+ export const SKILL_DECISION_RESULT_SCHEMA =
7
+ "chainlesschain.skill-decision-result/v1";
8
+ export const SKILL_DECISION_OBSERVATION_SCHEMA =
9
+ "chainlesschain.skill-decision-observation/v1";
10
+
11
+ export const DECISION_MODES = Object.freeze(["off", "shadow", "suggest"]);
12
+ export const MAX_DECISION_CANDIDATES = 8;
13
+
14
+ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
15
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
16
+
17
+ function canonical(value) {
18
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
19
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
20
+ return `{${Object.keys(value)
21
+ .sort()
22
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
23
+ .join(",")}}`;
24
+ }
25
+
26
+ export function decisionDigest(domain, value) {
27
+ return `sha256:${createHash("sha256")
28
+ .update(domain)
29
+ .update("\0")
30
+ .update(canonical(value))
31
+ .digest("hex")}`;
32
+ }
33
+
34
+ function plainObject(value, label) {
35
+ if (
36
+ !value ||
37
+ typeof value !== "object" ||
38
+ Array.isArray(value) ||
39
+ utilTypes.isProxy(value) ||
40
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value))
41
+ ) {
42
+ throw new TypeError(`${label} must be a plain object`);
43
+ }
44
+ for (const key of Reflect.ownKeys(value)) {
45
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
46
+ if (
47
+ typeof key !== "string" ||
48
+ !descriptor?.enumerable ||
49
+ !("value" in descriptor)
50
+ ) {
51
+ throw new TypeError(`${label} contains unsupported properties`);
52
+ }
53
+ }
54
+ return value;
55
+ }
56
+
57
+ function boundedText(value, label, maximum) {
58
+ if (
59
+ typeof value !== "string" ||
60
+ value.trim() === "" ||
61
+ value.length > maximum ||
62
+ /\p{Cc}/u.test(value)
63
+ ) {
64
+ throw new TypeError(`${label} is invalid or unbounded`);
65
+ }
66
+ return value.trim();
67
+ }
68
+
69
+ function identifier(value, label) {
70
+ const normalized = boundedText(value, label, 256);
71
+ if (!SAFE_ID.test(normalized)) throw new TypeError(`${label} is invalid`);
72
+ return normalized;
73
+ }
74
+
75
+ function probability(value, label) {
76
+ if (
77
+ typeof value !== "number" ||
78
+ !Number.isFinite(value) ||
79
+ value < 0 ||
80
+ value > 1
81
+ ) {
82
+ throw new TypeError(`${label} must be a finite probability`);
83
+ }
84
+ return value;
85
+ }
86
+
87
+ function normalizeCandidate(candidate, index) {
88
+ plainObject(candidate, `candidates[${index}]`);
89
+ const digest = candidate.digest;
90
+ if (!DIGEST.test(digest ?? "")) {
91
+ throw new TypeError(`candidates[${index}].digest is invalid`);
92
+ }
93
+ const tags = Array.isArray(candidate.tags)
94
+ ? candidate.tags.map((tag, tagIndex) =>
95
+ boundedText(tag, `candidates[${index}].tags[${tagIndex}]`, 128),
96
+ )
97
+ : [];
98
+ if (tags.length > 32 || new Set(tags).size !== tags.length) {
99
+ throw new TypeError(`candidates[${index}].tags are invalid or unbounded`);
100
+ }
101
+ return Object.freeze({
102
+ candidateId: `c${index + 1}`,
103
+ id: identifier(candidate.id, `candidates[${index}].id`),
104
+ displayName: boundedText(
105
+ candidate.displayName || candidate.id,
106
+ `candidates[${index}].displayName`,
107
+ 512,
108
+ ),
109
+ description: boundedText(
110
+ candidate.description || "(no description)",
111
+ `candidates[${index}].description`,
112
+ 16_384,
113
+ ),
114
+ category: boundedText(
115
+ candidate.category || "uncategorized",
116
+ `candidates[${index}].category`,
117
+ 128,
118
+ ),
119
+ digest,
120
+ tags: Object.freeze(tags),
121
+ });
122
+ }
123
+
124
+ export function normalizeDecisionMode(value = "off") {
125
+ if (!DECISION_MODES.includes(value)) {
126
+ throw new TypeError("decision mode must be off, shadow, or suggest");
127
+ }
128
+ return value;
129
+ }
130
+
131
+ export function createSkillDecisionRequest({
132
+ decisionId,
133
+ tenantId,
134
+ sessionId,
135
+ turnId,
136
+ query,
137
+ candidates,
138
+ policyDigest,
139
+ contextRevision,
140
+ } = {}) {
141
+ const normalizedCandidates = Array.isArray(candidates)
142
+ ? candidates.map(normalizeCandidate)
143
+ : null;
144
+ if (
145
+ !normalizedCandidates ||
146
+ normalizedCandidates.length < 1 ||
147
+ normalizedCandidates.length > MAX_DECISION_CANDIDATES
148
+ ) {
149
+ throw new TypeError("decision candidates are invalid or unbounded");
150
+ }
151
+ if (
152
+ new Set(normalizedCandidates.map(({ digest }) => digest)).size !==
153
+ normalizedCandidates.length
154
+ ) {
155
+ throw new TypeError("decision candidates require unique content digests");
156
+ }
157
+ const binding = Object.freeze({
158
+ tenantId: identifier(tenantId, "tenantId"),
159
+ sessionId: identifier(sessionId, "sessionId"),
160
+ turnId: identifier(turnId, "turnId"),
161
+ policyDigest: DIGEST.test(policyDigest ?? "")
162
+ ? policyDigest
163
+ : decisionDigest("chainlesschain.skill-decision-policy/v1", {
164
+ version: 1,
165
+ }),
166
+ contextRevision: identifier(
167
+ contextRevision || "current",
168
+ "contextRevision",
169
+ ),
170
+ });
171
+ const candidateSetDigest = decisionDigest(
172
+ "chainlesschain.skill-decision-candidates/v1",
173
+ normalizedCandidates,
174
+ );
175
+ const state = Object.freeze({
176
+ task: boundedText(query, "query", 4096),
177
+ candidates: Object.freeze(
178
+ normalizedCandidates.map((candidate) =>
179
+ Object.freeze({
180
+ candidateId: candidate.candidateId,
181
+ name: candidate.displayName,
182
+ description: candidate.description,
183
+ category: candidate.category,
184
+ tags: candidate.tags,
185
+ }),
186
+ ),
187
+ ),
188
+ });
189
+ if (Buffer.byteLength(canonical(state), "utf8") > 96 * 1024) {
190
+ throw new TypeError("decision state is too large");
191
+ }
192
+ const criteria = Object.fromEntries(
193
+ normalizedCandidates.map((candidate) => [
194
+ candidate.candidateId,
195
+ `Use ${candidate.displayName}: ${candidate.description}`,
196
+ ]),
197
+ );
198
+ criteria.none = "None of the listed skills is suitable for this task";
199
+ const questions = {
200
+ needs_skill: {
201
+ type: "noul",
202
+ instructions:
203
+ "Does this task need one of the listed skills to be handled well?",
204
+ },
205
+ best_skill: {
206
+ type: "choice",
207
+ instructions:
208
+ "Which listed skill is the best fit for the task? Choose none when every listed skill is unsuitable.",
209
+ criteria,
210
+ },
211
+ };
212
+ for (const candidate of normalizedCandidates) {
213
+ questions[`fits_${candidate.candidateId}`] = {
214
+ type: "noul",
215
+ instructions: `Is ${candidate.candidateId} suitable for the task and its stated boundaries?`,
216
+ };
217
+ }
218
+ const core = {
219
+ schema: SKILL_DECISION_REQUEST_SCHEMA,
220
+ decisionId: identifier(decisionId, "decisionId"),
221
+ purpose: "skill-routing",
222
+ binding,
223
+ candidateSetDigest,
224
+ candidates: Object.freeze(normalizedCandidates),
225
+ payload: Object.freeze({ state, questions: Object.freeze(questions) }),
226
+ };
227
+ return Object.freeze({
228
+ ...core,
229
+ requestDigest: decisionDigest(SKILL_DECISION_REQUEST_SCHEMA, core),
230
+ });
231
+ }
232
+
233
+ function normalizeNoul(answer, label) {
234
+ plainObject(answer, label);
235
+ if (answer.type !== undefined && answer.type !== "noul") {
236
+ throw new TypeError(`${label} has the wrong answer type`);
237
+ }
238
+ return Object.freeze({
239
+ type: "noul",
240
+ noul: probability(answer.noul, `${label}.noul`),
241
+ });
242
+ }
243
+
244
+ function normalizeChoice(answer, allowed, label) {
245
+ plainObject(answer, label);
246
+ if (answer.type !== undefined && answer.type !== "choice") {
247
+ throw new TypeError(`${label} has the wrong answer type`);
248
+ }
249
+ if (!allowed.has(answer.choice)) {
250
+ throw new TypeError(`${label}.choice is outside the candidate set`);
251
+ }
252
+ plainObject(answer.probabilities, `${label}.probabilities`);
253
+ if (
254
+ Object.keys(answer.probabilities).length !== allowed.size ||
255
+ Object.keys(answer.probabilities).some((key) => !allowed.has(key))
256
+ ) {
257
+ throw new TypeError(`${label}.probabilities are incomplete`);
258
+ }
259
+ const probabilities = Object.fromEntries(
260
+ [...allowed].map((key) => [
261
+ key,
262
+ probability(answer.probabilities[key], `${label}.probabilities.${key}`),
263
+ ]),
264
+ );
265
+ const sum = Object.values(probabilities).reduce(
266
+ (total, value) => total + value,
267
+ 0,
268
+ );
269
+ if (Math.abs(sum - 1) > 0.02) {
270
+ throw new TypeError(`${label}.probabilities do not sum to one`);
271
+ }
272
+ return Object.freeze({
273
+ type: "choice",
274
+ choice: answer.choice,
275
+ confidence: probability(answer.confidence, `${label}.confidence`),
276
+ probabilities: Object.freeze(probabilities),
277
+ });
278
+ }
279
+
280
+ export function normalizeSkillDecisionProviderResult(request, value) {
281
+ plainObject(value, "decision provider result");
282
+ plainObject(value.answers, "decision provider result.answers");
283
+ const expectedKeys = [
284
+ "needs_skill",
285
+ "best_skill",
286
+ ...request.candidates.map(({ candidateId }) => `fits_${candidateId}`),
287
+ ];
288
+ if (
289
+ Object.keys(value.answers).length !== expectedKeys.length ||
290
+ expectedKeys.some((key) => !Object.hasOwn(value.answers, key))
291
+ ) {
292
+ throw new TypeError("decision provider answers are incomplete");
293
+ }
294
+ const allowed = new Set([
295
+ ...request.candidates.map(({ candidateId }) => candidateId),
296
+ "none",
297
+ ]);
298
+ const answers = {
299
+ needs_skill: normalizeNoul(
300
+ value.answers.needs_skill,
301
+ "answers.needs_skill",
302
+ ),
303
+ best_skill: normalizeChoice(
304
+ value.answers.best_skill,
305
+ allowed,
306
+ "answers.best_skill",
307
+ ),
308
+ };
309
+ for (const candidate of request.candidates) {
310
+ const key = `fits_${candidate.candidateId}`;
311
+ answers[key] = normalizeNoul(value.answers[key], `answers.${key}`);
312
+ }
313
+ const core = {
314
+ schema: SKILL_DECISION_RESULT_SCHEMA,
315
+ decisionId: request.decisionId,
316
+ requestDigest: request.requestDigest,
317
+ candidateSetDigest: request.candidateSetDigest,
318
+ provider: boundedText(value.provider, "provider", 160),
319
+ model: boundedText(value.model, "model", 160),
320
+ answers: Object.freeze(answers),
321
+ };
322
+ return Object.freeze({
323
+ ...core,
324
+ usage:
325
+ value.usage && typeof value.usage === "object"
326
+ ? Object.freeze({ ...value.usage })
327
+ : null,
328
+ resultDigest: decisionDigest(SKILL_DECISION_RESULT_SCHEMA, core),
329
+ });
330
+ }
331
+
332
+ export function resolveSkillDecision(request, result, thresholds = {}) {
333
+ const needsSkill = thresholds.needsSkill ?? 0.5;
334
+ const candidateFit = thresholds.candidateFit ?? 0.5;
335
+ const choiceConfidence = thresholds.choiceConfidence ?? 0.5;
336
+ for (const [name, value] of Object.entries({
337
+ needsSkill,
338
+ candidateFit,
339
+ choiceConfidence,
340
+ })) {
341
+ probability(value, `thresholds.${name}`);
342
+ }
343
+ const choice = result.answers.best_skill;
344
+ let status = "suggestion";
345
+ let reasonCode = "accepted";
346
+ let selected = null;
347
+ if (result.answers.needs_skill.noul < needsSkill) {
348
+ status = "no-match";
349
+ reasonCode = "needs-skill-below-threshold";
350
+ } else if (choice.choice === "none") {
351
+ status = "no-match";
352
+ reasonCode = "choice-none";
353
+ } else if (choice.confidence < choiceConfidence) {
354
+ status = "abstain";
355
+ reasonCode = "choice-confidence-below-threshold";
356
+ } else {
357
+ selected = request.candidates.find(
358
+ ({ candidateId }) => candidateId === choice.choice,
359
+ );
360
+ const fit = result.answers[`fits_${choice.choice}`].noul;
361
+ if (fit < candidateFit) {
362
+ status = "abstain";
363
+ reasonCode = "candidate-fit-below-threshold";
364
+ selected = null;
365
+ }
366
+ }
367
+ return Object.freeze({
368
+ status,
369
+ reasonCode,
370
+ selectedCandidateId: selected?.candidateId ?? null,
371
+ selectedDigest: selected?.digest ?? null,
372
+ selectedSkillId: selected?.id ?? null,
373
+ needsSkillProbability: result.answers.needs_skill.noul,
374
+ choiceConfidence: choice.confidence,
375
+ resultDigest: result.resultDigest,
376
+ });
377
+ }
@@ -0,0 +1,44 @@
1
+ import { types as utilTypes } from "node:util";
2
+
3
+ export const DECISION_PROVIDER_AUTHORITY_SCHEMA =
4
+ "chainlesschain.decision-provider-authority/v1";
5
+
6
+ const AUTHORITIES = new WeakSet();
7
+
8
+ function boundedText(value, label) {
9
+ if (
10
+ typeof value !== "string" ||
11
+ value.trim() === "" ||
12
+ value.length > 160 ||
13
+ /\p{Cc}/u.test(value)
14
+ ) {
15
+ throw new TypeError(`${label} is invalid or unbounded`);
16
+ }
17
+ return value.trim();
18
+ }
19
+
20
+ export function openDecisionProviderAuthority({
21
+ provider,
22
+ model,
23
+ decide,
24
+ } = {}) {
25
+ if (typeof decide !== "function" || utilTypes.isProxy(decide)) {
26
+ throw new TypeError("decision provider decide() is required");
27
+ }
28
+ const operation = (...args) => Reflect.apply(decide, undefined, args);
29
+ const authority = Object.freeze({
30
+ schema: DECISION_PROVIDER_AUTHORITY_SCHEMA,
31
+ provider: boundedText(provider, "provider"),
32
+ model: boundedText(model, "model"),
33
+ decide: operation,
34
+ });
35
+ AUTHORITIES.add(authority);
36
+ return authority;
37
+ }
38
+
39
+ export function captureDecisionProviderAuthority(value) {
40
+ if (!AUTHORITIES.has(value)) {
41
+ throw new TypeError("a branded decision provider authority is required");
42
+ }
43
+ return value;
44
+ }