chainlesschain 0.166.85 → 0.166.87

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 (201) hide show
  1. package/README.md +8 -0
  2. package/package.json +3 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-Cgo7OFpi.js → AIOps-DdQjhXg9.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-lUMnE2Ln.js → ActionButton-CuwA3hPA.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-CsvKzTot.js → Analytics-DTVXJSAC.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-C2z1h1Pv.js → AppLayout-j06JBfEQ.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-C5Q0ZhNb.js → Artifacts-BFAK8LEW.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-DXnfPKsI.js → Audit-BiGxRpOf.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-DjbGcLm3.js → BackgroundAgents-B6uHaPkZ.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-9bVrKvXE.js → Backup-BNAesB8q.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-bx5ri7BD.js → BaseInput-BOuetfCd.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-CCFmeUMY.js → Chat-cVzo3m_D.js} +4 -4
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-DZ2Ys-CI.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-ei7MQXe9.js → Checkbox-BaCTS-H8.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-CXgBJ9iu.js → Codegen-CBE8NiYh.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-DMkMsizS.js → Col-C9wLiUTN.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-CvpB_UFR.js → Community-jdClBSIp.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-gay5hbFX.js → Compact-BIjauIlM.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-dG8JQyRL.js → Compliance-CZ-AoaNj.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-BA3xfaTW.js → Cowork-BbTKtDS4.js} +4 -4
  23. package/src/assets/web-panel/assets/{Cron-D1_HsxyY.js → Cron-ImKdtwNi.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-DZqoqoy5.js → Crosschain-B54GSHf5.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-Cwnr8hCO.js → DID-BQt5WUzs.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-CxevypiI.js → Dashboard--gNOffif.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-Bs7-yWgr.js → Dropdown-342A5YJw.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-CcEs2Puo.js → EmailListRenderer-CtIZ3ZHd.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-C9ipBzAQ.js → EvolutionSettings-CyZPoTio.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Dt6--mGn.js → FamilyGuardDashboard-B1MCkFEu.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-CD0JN5FF.js → Federation-DZWoERUy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-POypz3Xv.js → FormItemContext-Onmb0mp3.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9f-0F5.js → GenericCardRenderer-u1qhNNc7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-BbTh5Atv.js → Git-BEowR1C5.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-C71dgoir.js → Governance-BiYd3AAg.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DwSHxbJB.js → Inference-CsC4Vjh5.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-Cx1oQYQZ.js → KnowledgeGraph-BXA7ikPM.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BAJWe00o.js → Logs-C9WoplQm.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-CD8a7lHT.js → MarkdownRenderer-DecEsXsy.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-fwuY7P7Y.js → Marketplace-CCoJBfRL.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-Di4693uc.js → McpTools-CzaOWBoA.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-Cz3L-GUB.js → Memory-BPwh7u1-.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-BuXKIkSb.js → MobileBridge-Bk47v6z2.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-D-CY--Sb.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-LnACMFd0.js → Mtc-ExVyAGlW.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-BZy7yE0t.js → MtcAudit-Rrw10csE.js} +5 -5
  47. package/src/assets/web-panel/assets/{Multisig-BFG5iVrE.js → Multisig-8TxxwxZZ.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DgXi04vJ.js → NLProgramming-CNckjtnY.js} +1 -1
  49. package/src/assets/web-panel/assets/Notes-C4j6cZTu.js +7 -0
  50. package/src/assets/web-panel/assets/{NotificationSettings-AixrsHw5.js → NotificationSettings-CchU8rWd.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-ZJBoyJ16.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-UNCBC3ux.js → Organization-F0Je7EdZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{Overflow-Drh8oAFT.js → Overflow-B2k-JA3u.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-bc2q0xEB.js → P2P-yui9Xxe6.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-BGwKxcDM.js → PdhVaultBrowser-CegM7hT6.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-DOnUxUzu.js → Permissions-CSFL0uvh.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B3qPZkrr.js → PersonalDataHub-BrMcd-vr.js} +6 -6
  58. package/src/assets/web-panel/assets/{Pipeline-B6hfa4-i.js → Pipeline-fzrliaVP.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bun3c35F.js → Privacy-BrkUjJ_g.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CCObdzk9.js → ProjectInit-CF_QCsW9.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-C6lKrSXY.js → ProjectSettings-Duwrd8aC.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DYcta7MZ.js → Projects-B0dLVXzU.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-BtykxKCe.js → Providers-Db8iS1xQ.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-D9CDyroY.js → QrScannerModal-CHbNzEQs.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-CJauCxXx.js → QuickAsk-CIESMfeR.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BbiYZofR.js → Recommend-BTMbCMtR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-BymCrgFW.js → RemoteSession-DavyK6J-.js} +3 -3
  68. package/src/assets/web-panel/assets/{Reputation-BWrGpMpF.js → Reputation-BFKtMelA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-9pOYIhiW.js → Row-BI9AZUvw.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-D3bQJ4qB.js → RssFeed-1nwXt8NY.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-BpKOOFE0.js → Search-Ck8PDYbk.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-DC6C0NSz.js → Security-BgDqpCbq.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-Lkf1046J.js → Services-DF8anXm4.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-eCQ_SsDb.js → Skeleton-CoHyY4ZX.js} +1 -1
  75. package/src/assets/web-panel/assets/Skills-dOaD4Ztl.js +1 -0
  76. package/src/assets/web-panel/assets/{Sla-BQbKy8XJ.js → Sla-XuOawvQY.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-DHsRx-XA.js → SpeechSettings-DFJ7nAiu.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-CC281X_J.js → SyncSettings-B2xYj-Pi.js} +2 -2
  79. package/src/assets/web-panel/assets/Tasks-D_SzNTLi.js +1 -0
  80. package/src/assets/web-panel/assets/{Templates-Cr2vC0tg.js → Templates-BXFmKh7W.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-DeYO5pIG.js → Tenant-CRg2AXbd.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-DBNgbfPr.js → Terminal-DQgxnuep.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-MoxgaF3x.js → TimelineRenderer-BrPQhQVc.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-CT2QfAN7.js → Tokens-RByy4USy.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DPUnomxs.js → Trigger-pE3y8u73.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-DnufuaTx.js → Trust-DhID9C33.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CJ6iOdPs.js → UkeySign-CPxEPNBH.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-xBWkH8bL.js → VideoEditing-ZD8DUxeQ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-WYV-4hMR.js → Wallet-Br9JREEd.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DJ5Mhd9L.js → WebAuthn-Cn-YacJg.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-B__UdOjN.js → WorkflowEditor-BTu0oqMp.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CLlmBVhp.js → chat-CEjws_QF.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-B8zoPQYm.js → colors-Bv2RNebW.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CvJ-dUsH.js → compact-item-gSc3EofP.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-LAtMm8SC.js → createContext-kyFDaqWC.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Dcc8pILJ.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CLYvg42H.js → hasIn-BuMWcjhx.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cv7vwX3z.js → index--nrQmk8X.js} +1 -1
  99. package/src/assets/web-panel/assets/index-05NB95_V.js +1 -0
  100. package/src/assets/web-panel/assets/{index-BUVrYDNV.js → index-42PTBO7a.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B6zlMzZz.js → index-BCS5j4SQ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-oX216HRH.js → index-BSSIJ-rv.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B02qgCao.js → index-BUOCcXou.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_RPWP_5.js → index-Bg5jis0q.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DX99e8Xy.js → index-C-LPpwe9.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DDm9m4vC.js → index-C56Drzol.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DHq2Of6z.js → index-C7Jo20JB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C_dq-6_3.js → index-C9U9SjcT.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D0Z13xcZ.js → index-CF3TjDNz.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B1hHp9_M.js → index-CMS3Mk93.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfOOACfd.js → index-CNt2X-aK.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cb6q8wlI.js +1 -0
  113. package/src/assets/web-panel/assets/{index-DYW4Z3y-.js → index-Cj4RA7oA.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C2KFnUsv.js → index-CkfvP2dN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BkcQJvsz.js → index-Cm8K8Evn.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CVDcRQuX.js → index-CvSuM_CB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BCzyJ1xS.js → index-Czo_hw97.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cpda0SAz.js → index-D3m3EXbL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-iJ6s24bA.js → index-DR561Sqc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Di9vKira.js → index-DRa4R7vv.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-09YzRb6Z.js → index-DYKSuD02.js} +3 -3
  122. package/src/assets/web-panel/assets/{index-C5v7cEtm.js → index-DYcPWVN9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BGrQ2uEl.js → index-DbVUA5l3.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-4rr5jFOL.js → index-DbkyTMj-.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-GR3omApz.js → index-Dej4AGOz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-f1ptYBMr.js → index-EpxWlmip.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3uD5H7u.js → index-GpvWtvw3.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C6PETyel.js → index-NQ0c4hJ1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CnLXLaEF.js → index-OjZUHz5z.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DpeV-6y4.js → index-U-ytt84A.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DNQUdPam.js → index-_0bfDTDu.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BM2HqHdT.js → index-jmMAqBAm.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Bu7-I2L4.js → index-nLemnBZ2.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CUcE6rIa.js → index-q65c1kp8.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DX0aHEHL.js → index-sJ1F5Txa.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CxJ4NPVq.js → index-xfS_WO9b.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-QD-koeaw.js → initDefaultProps-B-wqiSu7.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CTItLW4-.js → motion-BK4_B4MD.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-CGsdTOdN.js → move-BU2TgDDO.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-C1kW_CNU.js → mtc-parser-DA3E4_EX.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B4PilaYy.js → omit-CaHz1e_q.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DBGUE12e.js → pickAttrs-NXZ19amf.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DZxCLjuo.js → placementArrow-BCXI9AHO.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-CZjQiocN.js → responsiveObserve-DdlZ9tLk.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-Btz5hh2_.js → slide-DCZm_Vc4.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-CfuxuLIt.js → statusUtils-CuEULKQr.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DdO0Obo_.js → styleChecker-Bo_UXG8x.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-CxftJ-Zs.js → useFlexGapSupport-B9PaTcFH.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-pVJChh6K.js → useFs-DwSTGdVR.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-wv7Q0uNE.js → usePersonalDataHub-FswSxnDC.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DVOVfAqC.js → vnode-EUztvYTj.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-B5tebZuT.js → zoom-D41MAgV-.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +1 -1
  155. package/src/commands/eval.js +118 -18
  156. package/src/commands/memory.js +34 -15
  157. package/src/commands/sandbox.js +9 -0
  158. package/src/data/changelog.json +26 -0
  159. package/src/lib/causal-observability.js +6 -45
  160. package/src/lib/context-memory-kernel/durable-memory-port.js +54 -12
  161. package/src/lib/context-memory-kernel/index.js +1 -0
  162. package/src/lib/context-memory-kernel/memory-query-index.js +278 -0
  163. package/src/lib/context-memory-kernel/memory-service.js +60 -18
  164. package/src/lib/context-memory-kernel/runtime.js +5 -3
  165. package/src/lib/context-memory-kernel/segmented-memory-port.js +955 -0
  166. package/src/lib/eval/outcomes.js +20 -10
  167. package/src/lib/eval/runner.js +49 -5
  168. package/src/lib/eval/tasks.js +8 -3
  169. package/src/lib/eval/verify01-contracts.js +349 -0
  170. package/src/lib/eval/verify01-execution.js +605 -0
  171. package/src/lib/execution-support.js +58 -0
  172. package/src/lib/llm-pricing.js +98 -11
  173. package/src/lib/model-catalog-review.js +116 -0
  174. package/src/lib/model-context-catalog.js +23 -1
  175. package/src/lib/openai-responses.js +39 -0
  176. package/src/lib/packer/pack-update-applier.js +13 -8
  177. package/src/lib/permission-authority.js +146 -10
  178. package/src/lib/process-execution-broker/index.js +8 -0
  179. package/src/lib/process-execution-broker/linux-subreaper-process.js +24 -2
  180. package/src/lib/process-execution-broker/process-ownership-journal.js +168 -7
  181. package/src/lib/process-execution-broker/process-ownership-quarantine.js +56 -1
  182. package/src/lib/process-execution-broker/process-recovery-cgroup.js +272 -0
  183. package/src/lib/sandbox-egress-authority-monitor.js +62 -5
  184. package/src/lib/scoped-permission-store.js +104 -1
  185. package/src/lib/settings-authority-domain.cjs +37 -2
  186. package/src/lib/settings-loader.cjs +67 -34
  187. package/src/lib/settings-permission-authority.cjs +373 -0
  188. package/src/runtime/agent-core.js +88 -407
  189. package/src/runtime/headless-runner.js +8 -3
  190. package/src/runtime/headless-stream.js +10 -3
  191. package/src/runtime/permission-authority-host.js +151 -0
  192. package/src/runtime/provider-stream-state.js +379 -0
  193. package/src/assets/web-panel/assets/ChatBubbleRenderer-ClqsIfTT.js +0 -1
  194. package/src/assets/web-panel/assets/MobileProjects-Bk-lK4Um.js +0 -1
  195. package/src/assets/web-panel/assets/Notes-BAj0Kgg4.js +0 -7
  196. package/src/assets/web-panel/assets/OrderTableRenderer-Dwqk3FlG.js +0 -1
  197. package/src/assets/web-panel/assets/Skills-BvDSQ3-d.js +0 -1
  198. package/src/assets/web-panel/assets/Tasks-CHHSWHom.js +0 -1
  199. package/src/assets/web-panel/assets/devWarning-DgZ4fdGI.js +0 -1
  200. package/src/assets/web-panel/assets/index-jLEdASOV.js +0 -1
  201. package/src/assets/web-panel/assets/index-jesGo4rl.js +0 -1
@@ -40,6 +40,7 @@ export const PRICE_TABLE = Object.freeze({
40
40
  match,
41
41
  ...model.pricing,
42
42
  exact: true,
43
+ ...(model.pricingTerms ? { terms: model.pricingTerms } : {}),
43
44
  })),
44
45
  // Current Anthropic list prices (USD per 1M tokens), verified 2026-06-21
45
46
  // against the claude-api reference. The Opus tier dropped to $5/$25 with
@@ -64,7 +65,12 @@ export const PRICE_TABLE = Object.freeze({
64
65
  match,
65
66
  ...model.pricing,
66
67
  exact: true,
67
- terms: GPT6_PRICING_TERMS,
68
+ terms: Object.freeze({
69
+ ...GPT6_PRICING_TERMS,
70
+ ...(model.cacheReadMultiplier == null
71
+ ? {}
72
+ : { cacheReadMultiplier: model.cacheReadMultiplier }),
73
+ }),
68
74
  })),
69
75
  // GPT-5 family (2026). Matching is longest-pattern-first, so dated/variant
70
76
  // ids (gpt-5.5, gpt-5.5-pro, gpt-5.5-instant, …) resolve to the most
@@ -120,14 +126,16 @@ export const PRICE_TABLE = Object.freeze({
120
126
  /**
121
127
  * Merge user-supplied price overrides (typically `config.llm.pricing`) onto the
122
128
  * built-in table. Override shape mirrors PRICE_TABLE:
123
- * { "<provider>": [ { match: "<substr>", in: <num>, out: <num> }, ... ] }
129
+ * { "<provider>": [ { match: "<model>", in: <num>, out: <num>, exact?, terms? }, ... ] }
124
130
  *
125
131
  * Per provider, a user entry whose `match` equals a built-in pattern REPLACES
126
132
  * it; brand-new patterns are prepended so they win ties; unknown providers are
127
133
  * added. Malformed entries (missing match / non-numeric / negative / Infinity
128
134
  * rate) are skipped so a bad config line can't crash cost reporting or produce a
129
135
  * negative cost that undercounts spend. Returns a new table; never mutates
130
- * PRICE_TABLE or the input.
136
+ * PRICE_TABLE or the input. Explicit terms replace (rather than partly inherit)
137
+ * the matched terms. Invalid terms throw: silently ignoring a requested tier
138
+ * or cache policy can materially misstate a hard budget.
131
139
  *
132
140
  * @param {object} [overrides]
133
141
  * @param {object} [base=PRICE_TABLE]
@@ -168,6 +176,12 @@ export function mergePricing(overrides, base = PRICE_TABLE) {
168
176
  match: e.match.toLowerCase(),
169
177
  in: Number(e.in),
170
178
  out: Number(e.out),
179
+ ...(Object.hasOwn(e, "exact")
180
+ ? { exact: validateExactMatch(e.exact) }
181
+ : {}),
182
+ ...(Object.hasOwn(e, "terms")
183
+ ? { terms: normalizePricingTerms(e.terms) }
184
+ : {}),
171
185
  explicitOverride: true,
172
186
  }));
173
187
  if (valid.length === 0) continue;
@@ -180,6 +194,79 @@ export function mergePricing(overrides, base = PRICE_TABLE) {
180
194
  return merged;
181
195
  }
182
196
 
197
+ function validateExactMatch(value) {
198
+ if (typeof value !== "boolean")
199
+ throw new TypeError("pricing exact must be a boolean");
200
+ return value;
201
+ }
202
+
203
+ /** Validate and copy the documented request-level override schema. */
204
+ export function normalizePricingTerms(value) {
205
+ const object = (v) =>
206
+ v !== null &&
207
+ typeof v === "object" &&
208
+ (Object.getPrototypeOf(v) === Object.prototype ||
209
+ Object.getPrototypeOf(v) === null);
210
+ const finite = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
211
+ const fail = () => {
212
+ throw new TypeError(
213
+ "Invalid pricing terms: expected finite non-negative multipliers and a positive long-context threshold",
214
+ );
215
+ };
216
+ const allowed = new Set([
217
+ "cacheReadMultiplier",
218
+ "cacheWriteMultiplier",
219
+ "longContext",
220
+ "serviceMultipliers",
221
+ ]);
222
+ if (!object(value) || Reflect.ownKeys(value).some((key) => !allowed.has(key)))
223
+ fail();
224
+ const result = {};
225
+ for (const key of ["cacheReadMultiplier", "cacheWriteMultiplier"]) {
226
+ if (!Object.hasOwn(value, key)) continue;
227
+ if (!finite(value[key])) fail();
228
+ result[key] = value[key];
229
+ }
230
+ if (Object.hasOwn(value, "longContext")) {
231
+ const tier = value.longContext;
232
+ if (
233
+ !object(tier) ||
234
+ Reflect.ownKeys(tier).some(
235
+ (key) =>
236
+ !["threshold", "inputMultiplier", "outputMultiplier"].includes(key),
237
+ ) ||
238
+ !Number.isSafeInteger(tier.threshold) ||
239
+ tier.threshold <= 0 ||
240
+ !finite(tier.inputMultiplier) ||
241
+ !finite(tier.outputMultiplier)
242
+ )
243
+ fail();
244
+ result.longContext = Object.freeze({
245
+ threshold: tier.threshold,
246
+ inputMultiplier: tier.inputMultiplier,
247
+ outputMultiplier: tier.outputMultiplier,
248
+ });
249
+ }
250
+ if (Object.hasOwn(value, "serviceMultipliers")) {
251
+ const tiers = value.serviceMultipliers;
252
+ if (
253
+ !object(tiers) ||
254
+ Reflect.ownKeys(tiers).length === 0 ||
255
+ Reflect.ownKeys(tiers).some(
256
+ (key) =>
257
+ !["standard", "default", "auto", "batch", "flex", "fast"].includes(
258
+ key,
259
+ ) || !finite(tiers[key]),
260
+ )
261
+ )
262
+ fail();
263
+ result.serviceMultipliers = Object.freeze(
264
+ Object.fromEntries(Object.entries(tiers)),
265
+ );
266
+ }
267
+ return Object.freeze(result);
268
+ }
269
+
183
270
  /**
184
271
  * Look up the rate for a provider/model, or null if unpriced.
185
272
  * @param {string} provider
@@ -199,14 +286,12 @@ export function lookupRate(provider, model, table = PRICE_TABLE) {
199
286
  const sorted = [...entries].sort((a, b) => b.match.length - a.match.length);
200
287
  for (const e of sorted) {
201
288
  if (e.exact ? m === e.match : m.includes(e.match)) {
202
- // An unrecognized new version must not inherit an older Opus price.
289
+ // An unrecognized new generation must not inherit an older family price.
203
290
  // Explicit operator overrides remain authoritative.
204
291
  if (
205
- e.match === "opus" &&
206
- Number(/^claude-opus-(\d+)(?:[.-]|$)/u.exec(m)?.[1]) >= 5 &&
207
- !e.explicitOverride &&
208
- e.in === 5 &&
209
- e.out === 25
292
+ ["opus", "sonnet"].includes(e.match) &&
293
+ Number(/^claude-(?:opus|sonnet)-(\d+)(?:[.-]|$)/u.exec(m)?.[1]) >= 5 &&
294
+ !e.explicitOverride
210
295
  )
211
296
  continue;
212
297
  return {
@@ -258,7 +343,7 @@ export function estimateCost({
258
343
  table,
259
344
  } = {}) {
260
345
  const rate = lookupRate(provider, model, table);
261
- const tierMultiplier = rate?.terms
346
+ const tierMultiplier = rate?.terms?.serviceMultipliers
262
347
  ? Object.hasOwn(rate.terms.serviceMultipliers, serviceTier)
263
348
  ? rate.terms.serviceMultipliers[serviceTier]
264
349
  : null
@@ -359,7 +444,9 @@ export function priceRollup(aggregate, { table } = {}) {
359
444
  est[field] = estimates.reduce((sum, entry) => sum + entry[field], 0);
360
445
  }
361
446
  } else if (
362
- lookupRate(row.provider, row.model, table)?.terms &&
447
+ (lookupRate(row.provider, row.model, table)?.terms?.longContext ||
448
+ lookupRate(row.provider, row.model, table)?.terms
449
+ ?.serviceMultipliers) &&
363
450
  row.calls > 1
364
451
  ) {
365
452
  // A legacy aggregate cannot establish which individual requests crossed
@@ -0,0 +1,116 @@
1
+ import { resolveModelCapabilityProfile } from "./model-capabilities.js";
2
+ import { lookupRate } from "./llm-pricing.js";
3
+ import { isDeepStrictEqual } from "node:util";
4
+
5
+ /** Compare reviewed release facts with local behavior; never change a catalog. */
6
+ export function reviewModelCatalog(review, snapshots = {}) {
7
+ if (
8
+ review?.schema !== "chainlesschain.model-catalog-review/v1" ||
9
+ !Array.isArray(review.models) ||
10
+ !review.models.length
11
+ ) {
12
+ throw new TypeError("Invalid reviewed model catalog");
13
+ }
14
+ const mismatches = [];
15
+ for (const expected of review.models) {
16
+ const actual = resolveModelCapabilityProfile(expected);
17
+ const rate = lookupRate(expected.provider, expected.model);
18
+ for (const field of [
19
+ "runtimeProtocol",
20
+ "contextWindowTokens",
21
+ "advertisedMaxOutputTokens",
22
+ ]) {
23
+ if (Object.hasOwn(expected, field) && actual[field] !== expected[field])
24
+ mismatches.push({
25
+ model: expected.model,
26
+ field,
27
+ expected: expected[field],
28
+ actual: actual[field],
29
+ });
30
+ }
31
+ if (!actual.sources.length)
32
+ mismatches.push({
33
+ model: expected.model,
34
+ field: "official-source",
35
+ actual: null,
36
+ });
37
+ for (const field of ["in", "out"]) {
38
+ if (rate?.[field] !== expected.pricing[field])
39
+ mismatches.push({
40
+ model: expected.model,
41
+ field,
42
+ expected: expected.pricing[field],
43
+ actual: rate?.[field] ?? null,
44
+ });
45
+ }
46
+ for (const field of [
47
+ "cacheReadMultiplier",
48
+ "cacheWriteMultiplier",
49
+ "longContext",
50
+ "serviceMultipliers",
51
+ ]) {
52
+ if (
53
+ Object.hasOwn(expected.pricing, field) &&
54
+ !isDeepStrictEqual(
55
+ rate?.terms?.[field] ?? null,
56
+ expected.pricing[field],
57
+ )
58
+ ) {
59
+ mismatches.push({
60
+ model: expected.model,
61
+ field,
62
+ expected: expected.pricing[field],
63
+ actual: rate?.terms?.[field] ?? null,
64
+ });
65
+ }
66
+ }
67
+ if (
68
+ expected.reasoningEfforts &&
69
+ JSON.stringify(actual.reasoningEfforts) !==
70
+ JSON.stringify(expected.reasoningEfforts)
71
+ )
72
+ mismatches.push({
73
+ model: expected.model,
74
+ field: "reasoningEfforts",
75
+ expected: expected.reasoningEfforts,
76
+ actual: actual.reasoningEfforts ?? null,
77
+ });
78
+ }
79
+ const upstream = [];
80
+ for (const [name, source] of Object.entries(snapshots)) {
81
+ if (
82
+ !["codex", "claude"].includes(name) ||
83
+ typeof source !== "string" ||
84
+ source.length > 16 * 1024 * 1024
85
+ )
86
+ throw new TypeError("Invalid upstream release snapshot");
87
+ const text = source
88
+ .replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "")
89
+ .replace(/<[^>]+>/g, " ");
90
+ const version =
91
+ name === "codex"
92
+ ? /Codex CLI\s+(\d+\.\d+\.\d+)/.exec(text)?.[1]
93
+ : /<Update\s+label="(\d+\.\d+\.\d+)"/u.exec(source)?.[1] ||
94
+ /^##\s+(\d+\.\d+\.\d+)/m.exec(text)?.[1];
95
+ if (!version)
96
+ throw new Error(
97
+ `Cannot identify the ${name} release in the official snapshot`,
98
+ );
99
+ upstream.push({
100
+ name,
101
+ reviewedVersion: review.upstream[name],
102
+ observedVersion: version,
103
+ reviewRequired: version !== review.upstream[name],
104
+ });
105
+ }
106
+ return {
107
+ schema: "chainlesschain.model-catalog-review-result/v1",
108
+ reviewedAt: review.reviewedAt,
109
+ localContractPassed: mismatches.length === 0,
110
+ mismatches,
111
+ upstream,
112
+ reviewRequired:
113
+ mismatches.length > 0 || upstream.some((row) => row.reviewRequired),
114
+ automaticEnablement: false,
115
+ };
116
+ }
@@ -45,7 +45,7 @@ export const CONTEXT_WINDOWS = Object.freeze({
45
45
  }),
46
46
  });
47
47
 
48
- export const MODEL_CAPABILITY_CATALOG_VERSION = "2026-09-27";
48
+ export const MODEL_CAPABILITY_CATALOG_VERSION = "2026-10-05";
49
49
 
50
50
  // Explicit ownership prevents a model name served by an unrelated provider
51
51
  // from silently inheriting another provider's catalog window.
@@ -85,6 +85,18 @@ export const LEGACY_MODEL_PROVIDERS = Object.freeze({
85
85
  // Official pages reviewed on the catalog version date. These describe the
86
86
  // named models, not endpoint access, account entitlement, or a live CLI run.
87
87
  export const DOCUMENTED_OPENAI_MODELS = Object.freeze({
88
+ "gpt-6.1-sol": Object.freeze({
89
+ contextWindowTokens: 1050000,
90
+ advertisedMaxOutputTokens: 128000,
91
+ requiresResponsesForTools: true,
92
+ reasoningEfforts: Object.freeze(["low", "medium", "high", "xhigh", "max"]),
93
+ pricing: Object.freeze({ in: 2, out: 10 }),
94
+ // This generation has a 5% cache-read rate, unlike GPT-6 Sol's 10%.
95
+ cacheReadMultiplier: 0.05,
96
+ sources: Object.freeze([
97
+ "https://developers.openai.com/api/docs/models/gpt-6.1-sol",
98
+ ]),
99
+ }),
88
100
  "gpt-4o": Object.freeze({
89
101
  contextWindowTokens: 128000,
90
102
  advertisedMaxOutputTokens: 16384,
@@ -132,11 +144,21 @@ export const DOCUMENTED_OPENAI_MODELS = Object.freeze({
132
144
  });
133
145
 
134
146
  export const DOCUMENTED_ANTHROPIC_MODELS = Object.freeze({
147
+ "claude-sonnet-5-5": Object.freeze({
148
+ contextWindowTokens: 1000000,
149
+ advertisedMaxOutputTokens: null,
150
+ pricing: Object.freeze({ in: 2, out: 10 }),
151
+ pricingTerms: Object.freeze({ cacheReadMultiplier: 0.1 }),
152
+ sources: Object.freeze([
153
+ "https://code.claude.com/docs/en/changelog#2-1-284",
154
+ ]),
155
+ }),
135
156
  "claude-opus-5-5": Object.freeze({
136
157
  contextWindowTokens: 1000000,
137
158
  // The release notes establish the window and pricing, not an output cap.
138
159
  advertisedMaxOutputTokens: null,
139
160
  pricing: Object.freeze({ in: 4, out: 20 }),
161
+ pricingTerms: Object.freeze({ cacheReadMultiplier: 0.05 }),
140
162
  sources: Object.freeze([
141
163
  "https://code.claude.com/docs/en/changelog#2-1-280",
142
164
  ]),
@@ -176,7 +176,16 @@ export function createOpenAIResponsesBody({
176
176
  maxOutputTokens = null,
177
177
  reasoning = null,
178
178
  stream = false,
179
+ modelProfile = null,
179
180
  }) {
181
+ if (
182
+ modelProfile?.advertisedMaxOutputTokens != null &&
183
+ maxOutputTokens > modelProfile.advertisedMaxOutputTokens
184
+ ) {
185
+ throw new RangeError(
186
+ `maxOutputTokens exceeds the documented limit (${modelProfile.advertisedMaxOutputTokens}) for ${model}`,
187
+ );
188
+ }
180
189
  return {
181
190
  model,
182
191
  input: toOpenAIResponsesInput(messages),
@@ -189,6 +198,36 @@ export function createOpenAIResponsesBody({
189
198
  };
190
199
  }
191
200
 
201
+ /** Translate UI intensity without sending an effort rejected by the model. */
202
+ export function createOpenAIResponsesReasoning(
203
+ options = {},
204
+ modelProfile = null,
205
+ ) {
206
+ const want = options.thinking;
207
+ if (!want || want === "off" || want === "none") return null;
208
+ const requested =
209
+ typeof options.thinkingEffort === "string"
210
+ ? options.thinkingEffort.toLowerCase()
211
+ : null;
212
+ const fallback = ["ultra", "ultrathink"].includes(String(want))
213
+ ? "xhigh"
214
+ : want === "think"
215
+ ? "medium"
216
+ : "high";
217
+ const generic = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
218
+ const effort =
219
+ requested && generic.includes(requested) ? requested : fallback;
220
+ if (
221
+ modelProfile?.reasoningEfforts &&
222
+ !modelProfile.reasoningEfforts.includes(requested || effort)
223
+ ) {
224
+ throw new RangeError(
225
+ `Unsupported reasoning effort for ${modelProfile.model}; allowed: ${modelProfile.reasoningEfforts.join(", ")}`,
226
+ );
227
+ }
228
+ return { effort, summary: "auto" };
229
+ }
230
+
192
231
  export function normalizeOpenAIResponsesUsage(usage) {
193
232
  if (!usage || typeof usage !== "object" || Array.isArray(usage)) return null;
194
233
  const input = nonNegativeInteger(usage.input_tokens);
@@ -853,6 +853,11 @@ async function scheduleWindowsTransaction(ctx) {
853
853
  function windowsGenerationJournalHelperSource() {
854
854
  return String.raw`param()
855
855
  $ErrorActionPreference = 'Stop'
856
+ # The journal contains only JSON primitives. Use the inbox .NET serializer and
857
+ # file/path APIs so each phase does not repeat PowerShell module discovery.
858
+ # Keep the same synchronous atomic replacement and durable flush protocol.
859
+ [void][Reflection.Assembly]::Load('System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')
860
+ $Serializer = [Web.Script.Serialization.JavaScriptSerializer]::new()
856
861
  $Action = [string]$env:JOURNAL_ACTION
857
862
  $JournalPath = [string]$env:JOURNAL_FILE
858
863
  $RetiredPath = [string]$env:JOURNAL_RETIRED
@@ -872,7 +877,7 @@ if (($Phase -eq 'committed') -ne ($Decision -eq 'commit')) { throw 'Invalid phas
872
877
  if ($Action -eq 'write' -and $Phase -eq 'prepared') {
873
878
  if ([IO.File]::Exists($JournalPath)) { throw 'A generation journal already exists' }
874
879
  $Bytes = [Convert]::FromBase64String([string]$env:JOURNAL_INITIAL_BASE64)
875
- $Journal = [Text.Encoding]::UTF8.GetString($Bytes) | ConvertFrom-Json
880
+ $Journal = $Serializer.DeserializeObject([Text.Encoding]::UTF8.GetString($Bytes))
876
881
  $ParentSource = @'
877
882
  using System;
878
883
  using System.Runtime.InteropServices;
@@ -900,10 +905,10 @@ public static class CcParentProcess {
900
905
  }
901
906
  '@
902
907
  Add-Type -TypeDefinition $ParentSource -Language CSharp
903
- $Journal | Add-Member -NotePropertyName ownerPid -NotePropertyValue ([CcParentProcess]::Get())
908
+ $Journal.Add('ownerPid', [CcParentProcess]::Get())
904
909
  } else {
905
910
  if (-not [IO.File]::Exists($JournalPath)) { throw 'Generation journal is missing' }
906
- $Journal = Get-Content -Raw -LiteralPath $JournalPath | ConvertFrom-Json
911
+ $Journal = $Serializer.DeserializeObject([IO.File]::ReadAllText($JournalPath, [Text.Encoding]::UTF8))
907
912
  }
908
913
 
909
914
  $ValidBefore = {
@@ -931,7 +936,7 @@ if ($Action -eq 'retire') {
931
936
  if ([string]$Journal.phase -ne $Phase -or [string]$Journal.decision -ne $Decision) {
932
937
  throw 'Generation journal decision changed before retirement'
933
938
  }
934
- $ReplacedPath = Join-Path (Split-Path -Parent $JournalPath) ('.chainlesschain.journal-retired-previous-' + [guid]::NewGuid().ToString('N'))
939
+ $ReplacedPath = [IO.Path]::Combine([IO.Path]::GetDirectoryName($JournalPath), ('.chainlesschain.journal-retired-previous-' + [guid]::NewGuid().ToString('N')))
935
940
  try {
936
941
  if ([IO.File]::Exists($RetiredPath)) {
937
942
  [IO.File]::Replace($JournalPath, $RetiredPath, $ReplacedPath, $true)
@@ -985,11 +990,11 @@ if ($Phase -ne 'prepared') {
985
990
  $Journal.phase = $Phase
986
991
  $Journal.decision = $Decision
987
992
  $Journal.updatedAt = [DateTimeOffset]::UtcNow.ToString('o')
988
- $Directory = Split-Path -Parent $JournalPath
989
- $StagingPath = Join-Path $Directory ('.chainlesschain.journal-' + [guid]::NewGuid().ToString('N'))
990
- $ReplacedPath = Join-Path $Directory ('.chainlesschain.journal-previous-' + [guid]::NewGuid().ToString('N'))
993
+ $Directory = [IO.Path]::GetDirectoryName($JournalPath)
994
+ $StagingPath = [IO.Path]::Combine($Directory, ('.chainlesschain.journal-' + [guid]::NewGuid().ToString('N')))
995
+ $ReplacedPath = [IO.Path]::Combine($Directory, ('.chainlesschain.journal-previous-' + [guid]::NewGuid().ToString('N')))
991
996
  try {
992
- $Payload = $Journal | ConvertTo-Json -Compress -Depth 8
997
+ $Payload = $Serializer.Serialize($Journal)
993
998
  $PayloadBytes = [Text.UTF8Encoding]::new($false).GetBytes($Payload + [Environment]::NewLine)
994
999
  $Stream = [IO.File]::Open($StagingPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
995
1000
  try {
@@ -1,11 +1,13 @@
1
1
  /** Resolve static settings plus CLI-owned workspace-scoped permission rules. */
2
2
 
3
3
  import settingsLoader from "./settings-loader.cjs";
4
+ import persistentSettings from "./settings-permission-authority.cjs";
4
5
  import path from "node:path";
5
6
  import {
6
7
  ScopedPermissionStore,
7
8
  getScopedPermissionRevision,
8
9
  subscribeScopedPermissionRevision,
10
+ projectScopedPermissionObservation,
9
11
  } from "./scoped-permission-store.js";
10
12
 
11
13
  const {
@@ -86,16 +88,41 @@ export function loadPermissionAuthority({
86
88
  env = process.env,
87
89
  baseRules = null,
88
90
  scopedStore = null,
91
+ settingsAuthority: persistentAuthority = null,
89
92
  } = {}) {
90
93
  const settingsRevision = getSettingsPermissionRevision();
91
94
  const scopedRevision = baseRules ? null : getScopedPermissionRevision();
92
- const settingsObservation = inspectSettingsSources({
93
- cwd,
94
- settingsFile,
95
- managedSettingsFile,
96
- env,
97
- });
98
- const loaded = projectSettingsObservation(settingsObservation, { env });
95
+ const persistent = persistentAuthority
96
+ ? persistentSettings.readSettingsPermissionAuthority(persistentAuthority)
97
+ : null;
98
+ if (persistentAuthority) {
99
+ const bound =
100
+ persistentSettings.settingsPermissionAuthorityOptions(
101
+ persistentAuthority,
102
+ );
103
+ if (
104
+ path.resolve(cwd) !== bound.cwd ||
105
+ (settingsFile &&
106
+ path.resolve(cwd, settingsFile) !== bound.settingsFile) ||
107
+ (managedSettingsFile &&
108
+ path.resolve(managedSettingsFile) !== bound.managedSettingsFile)
109
+ )
110
+ throw new TypeError("persistent settings authority options mismatch");
111
+ }
112
+ const settingsObservation =
113
+ persistent?.observation ||
114
+ inspectSettingsSources({
115
+ cwd,
116
+ settingsFile,
117
+ managedSettingsFile,
118
+ env,
119
+ });
120
+ const loaded = persistent
121
+ ? persistentSettings.projectSettingsPermissionObservation(
122
+ settingsObservation,
123
+ { env },
124
+ )
125
+ : projectSettingsObservation(settingsObservation, { env });
99
126
  const sources = baseRules ? {} : { ...loaded.sources };
100
127
  let rules = baseRules
101
128
  ? applyManagedPermissionPolicy(baseRules, loaded.managed, sources)
@@ -105,7 +132,29 @@ export function loadPermissionAuthority({
105
132
  // Explicit caller rules retain their historical replacement semantics.
106
133
  // Managed-only policy also suppresses every user-owned scoped grant.
107
134
  if (!baseRules) {
108
- scoped = (scopedStore || new ScopedPermissionStore({ cwd })).list();
135
+ const bound = persistentAuthority
136
+ ? persistentSettings.settingsPermissionAuthorityOptions(
137
+ persistentAuthority,
138
+ )
139
+ : null;
140
+ const store =
141
+ scopedStore ||
142
+ new ScopedPermissionStore({
143
+ cwd,
144
+ ...(bound
145
+ ? {
146
+ filePath: bound.scopedFile,
147
+ settingsAuthority: persistentAuthority,
148
+ }
149
+ : {}),
150
+ });
151
+ scoped = persistent
152
+ ? projectScopedPermissionObservation(
153
+ store,
154
+ persistentAuthority,
155
+ persistent.scopedObservation,
156
+ )
157
+ : store.list();
109
158
  const managedOnly =
110
159
  loaded.managed?.allowManagedPermissionRulesOnly === true;
111
160
  scoped = {
@@ -143,6 +192,17 @@ export function loadPermissionAuthority({
143
192
  error.code = "CC_SCOPED_PERMISSION_AUTHORITY_CHANGED";
144
193
  throw error;
145
194
  }
195
+ if (
196
+ persistent &&
197
+ persistentSettings.readSettingsPermissionAuthority(persistentAuthority)
198
+ .snapshot !== persistent.snapshot
199
+ ) {
200
+ const error = new Error(
201
+ "persistent settings changed while permission authority was loading",
202
+ );
203
+ error.code = "CC_SETTINGS_PERMISSION_AUTHORITY_CHANGED";
204
+ throw error;
205
+ }
146
206
  return freezeSnapshot({
147
207
  ...loaded,
148
208
  rules,
@@ -151,12 +211,17 @@ export function loadPermissionAuthority({
151
211
  hasRules: hasRules(rules),
152
212
  settingsRevision,
153
213
  settingsObservation,
214
+ ...(persistent ? { persistentSettingsRevision: persistent.snapshot } : {}),
154
215
  ...(scopedRevision ? { scopedRevision } : {}),
155
216
  });
156
217
  }
157
218
 
158
219
  export function createPermissionRulesProvider(options = {}) {
159
- const cwd = path.resolve(options.cwd || process.cwd());
220
+ const persistentAuthority = options.settingsAuthority ?? null;
221
+ const bound = persistentAuthority
222
+ ? persistentSettings.settingsPermissionAuthorityOptions(persistentAuthority)
223
+ : null;
224
+ const cwd = path.resolve(options.cwd || bound?.cwd || process.cwd());
160
225
  const captured = Object.freeze({
161
226
  cwd,
162
227
  settingsFile: options.settingsFile
@@ -172,15 +237,86 @@ export function createPermissionRulesProvider(options = {}) {
172
237
  ? freezeSnapshot(cloneRules(options.baseRules))
173
238
  : null,
174
239
  scopedStore: options.scopedStore || null,
240
+ settingsAuthority: persistentAuthority,
175
241
  });
242
+ if (
243
+ bound &&
244
+ (cwd !== bound.cwd ||
245
+ (captured.settingsFile && captured.settingsFile !== bound.settingsFile) ||
246
+ (captured.managedSettingsFile &&
247
+ captured.managedSettingsFile !== bound.managedSettingsFile))
248
+ )
249
+ throw new TypeError("persistent settings authority options mismatch");
176
250
  const provider = () => loadPermissionAuthority(captured);
251
+ const localAuthority = captured.baseRules
252
+ ? settingsAuthority
253
+ : combinedAuthority;
177
254
  providers.set(
178
255
  provider,
179
- captured.baseRules ? settingsAuthority : combinedAuthority,
256
+ persistentAuthority
257
+ ? persistentProviderAuthority(persistentAuthority, localAuthority)
258
+ : localAuthority,
180
259
  );
181
260
  return Object.freeze(provider);
182
261
  }
183
262
 
263
+ function persistentProviderAuthority(binding, localAuthority) {
264
+ let last = null;
265
+ function getSnapshot() {
266
+ const local = localAuthority.getSnapshot();
267
+ const durable =
268
+ persistentSettings.readSettingsPermissionAuthority(binding).snapshot;
269
+ if (last?.local !== local || last?.durable !== durable)
270
+ last = Object.freeze({ local, durable });
271
+ return last;
272
+ }
273
+ return Object.freeze({
274
+ getSnapshot,
275
+ assertWorkspace(cwd) {
276
+ if (
277
+ path.resolve(cwd) !==
278
+ persistentSettings.settingsPermissionAuthorityOptions(binding).cwd
279
+ )
280
+ throw Object.assign(
281
+ new Error("persistent permission authority workspace mismatch"),
282
+ { code: "CC_SETTINGS_AUTHORITY_BINDING_CHANGED" },
283
+ );
284
+ },
285
+ assertWritableRoots(roots) {
286
+ persistentSettings.assertSettingsPermissionWritableRoots(binding, roots);
287
+ },
288
+ subscribePolicyRevision(listener) {
289
+ if (typeof listener !== "function")
290
+ throw new TypeError("permission revision listener must be a function");
291
+ let known = getSnapshot();
292
+ const remove = localAuthority.subscribePolicyRevision(listener);
293
+ // Persistence prevents official ABA loss between polls. Timers provide
294
+ // bounded observation, not a promise that another process has stopped
295
+ // before synchronous addRule returns.
296
+ const timer = setInterval(() => {
297
+ let next;
298
+ try {
299
+ next = getSnapshot();
300
+ } catch {
301
+ next = null;
302
+ }
303
+ if (next === known) return;
304
+ known = next;
305
+ try {
306
+ listener(next);
307
+ } catch {
308
+ /* isolate observers */
309
+ }
310
+ }, 100);
311
+ timer.unref?.();
312
+ return () => {
313
+ clearInterval(timer);
314
+ remove();
315
+ };
316
+ },
317
+ });
318
+ }
319
+
184
320
  // A legacy callback cannot advertise a trusted revision subscription simply
185
321
  // by copying properties onto itself. It still receives sampled revalidation.
186
322
  export function permissionRulesProviderAuthority(provider) {