chainlesschain 0.166.82 → 0.166.84

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 (188) hide show
  1. package/package.json +1 -1
  2. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  3. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DMzLSUWK.js → AIOps-OYLnAlB5.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CgnA23oA.js → ActionButton-uc9kbLR6.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CtXmm8Qe.js → Analytics-BH901FG0.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-C5RIh5jA.js → AppLayout-qypKb8GL.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-DM525zx0.js → Artifacts-Q-uPWyLY.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-ChBx6f2l.js → Audit-B8hMGMim.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-BBHDxCda.js → BackgroundAgents-BSs8rJL_.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-B-qzCAoM.js → Backup-2YMU_nCd.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C5yoIWAD.js → BaseInput-DyMppTuT.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-NmTrci9a.js → Chat-B5QGMaDO.js} +5 -5
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-B8ts_JnN.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-KdFSU3vd.js → Checkbox-DAvtgEGd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-L__BOv_4.js → Codegen-BSChEVM3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-Dfuw4IQV.js → Col-G0zYohIP.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BRlEhSbx.js → Community-CzCN0B_p.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-D7dRkv8n.js → Compact-DrIN3G8z.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-CdlFYNR3.js → Compliance-CIsLa64Z.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-Ey7ksHq4.js → Cowork-CGYoafsB.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DBH__wGJ.js → Cron-DJ1JPXuH.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-D3nu9dw7.js → Crosschain-B-IqVFJl.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-DS6vZBNv.js → DID-CAAczPz_.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-DwFRk2T0.js → Dashboard-Dkpel8VE.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D7b73q3I.js → Dropdown-ETNBf8-Z.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-DPNaVH36.js → EmailListRenderer-CJGDyjVD.js} +1 -1
  28. package/src/assets/web-panel/assets/{EvolutionSettings-BEsOtdpf.js → EvolutionSettings-CL_hvVYe.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-ijSqsSsF.js → FamilyGuardDashboard-iexOh6Dh.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-C0BOzPDh.js → Federation-DnDHvV5D.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-DkF5NoAg.js → FormItemContext-DLCDJjUp.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-DQsMcyJj.js → GenericCardRenderer-CaCgXCjS.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-DQRt88PU.js → Git-BVm-euUF.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-DIjPmDN8.js → Governance-DPnUvLWr.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-BWMFtT7i.js → Inference-5qCWSYKl.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-M7TfkUVl.js → KnowledgeGraph-DXdv2oEh.js} +1 -1
  37. package/src/assets/web-panel/assets/Logs-BF2f7bBA.js +2 -0
  38. package/src/assets/web-panel/assets/MarkdownRenderer-qNJS3ygh.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-Crd9MYHg.js → Marketplace-Bp-0Y7Ng.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-C37yOQV0.js → McpTools-dm-Xdi63.js} +2 -2
  41. package/src/assets/web-panel/assets/{Memory-BBsijJCd.js → Memory-TBHK031z.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-CiXIQCju.js → MobileBridge-BTILAlds.js} +1 -1
  43. package/src/assets/web-panel/assets/{MobileProjects-C0KSvQm5.js → MobileProjects-DC0Clgzs.js} +1 -1
  44. package/src/assets/web-panel/assets/{Mtc-C0XaQsOY.js → Mtc-BvyWkmRw.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-DPqggKhw.js → MtcAudit-D7ksLoNa.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-Bie0ZVHe.js → Multisig-Dpzu7X1t.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-BbuJlQ5b.js → NLProgramming-DZ0ADHSB.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-CgqrJV8w.js → Notes-hIdkmRug.js} +4 -4
  49. package/src/assets/web-panel/assets/{NotificationSettings-CusdZEQl.js → NotificationSettings-R3jyP6Vf.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-B7RrCvCQ.js → OrderTableRenderer-d1zrFkqx.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-BnFpz3bb.js → Organization-DoA5WMcc.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-C_4_HyyZ.js → Overflow-DHjIloAQ.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-D0KpHxeg.js → P2P-2TMon_Wy.js} +2 -2
  54. package/src/assets/web-panel/assets/PdhVaultBrowser-DEUxGSWO.js +7 -0
  55. package/src/assets/web-panel/assets/{Permissions-D2LxL66N.js → Permissions-CxKcZ-8X.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-CvSNIxt0.js → PersonalDataHub-CcYtkHqc.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-rUl-SM7V.js → Pipeline-BxwOuq22.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-BD8cWSNd.js → Privacy-VIx9pdsH.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-RM7J4X2F.js → ProjectInit-COjp_erK.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-BIH38RsB.js → ProjectSettings-x9NrZU80.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-DMgkw7QJ.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-B9Kja2RK.js → Providers-B_fR7Y7Y.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-ByDKowTD.js → QrScannerModal-BUvzrn3V.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-DqEN1QOE.js → QuickAsk-C0sJwLy2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CLrYwNiv.js → Recommend-CJC7Wpce.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-DyDltVPC.js → RemoteSession-B62J6ZP0.js} +3 -3
  67. package/src/assets/web-panel/assets/{Reputation-BfP8rJ1-.js → Reputation-4f48kVBn.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-BpnP6r1H.js → Row-EzEMaYoV.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-CBNI1lCN.js → RssFeed-Dy0wD83j.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-CLjrM-xj.js → Search-GeVRHQND.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-dRXtg-Mq.js → Security-omLr1J7w.js} +3 -3
  72. package/src/assets/web-panel/assets/{Services-DAHCtOHL.js → Services-DmGPaDAs.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-D11Jgtur.js → Skeleton-CAla21Gm.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-DchvTO4k.js → Skills-QqEyIKOI.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-BFOc2SbL.js → Sla-Cwi15-pZ.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-Dud1xDd2.js → SpeechSettings-Cs8Zi1J8.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-CILfsA2S.js → SyncSettings-ipuJsDKP.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-C6Qrah_5.js → Tasks-BbVAg9Z7.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates--puiHyCR.js → Templates-slUp9yXF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-cz5vITz_.js → Tenant-6edCSG5K.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-YRs75Gz8.js → Terminal-DKq84G3h.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-C5myb_QZ.js → TimelineRenderer-Dg0Aw6se.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-Bk60iMIj.js → Tokens-D63h7qYd.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-BAb-Fd4z.js → Trigger-F7gTP_gv.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-BlqwmpEB.js → Trust-zw8Zqp8k.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-C6jvuF-7.js → UkeySign-C-YBUmod.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-7zbsdQUj.js → VideoEditing-AhVxTgmC.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-BAouk_kp.js → Wallet-C58zxjIg.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-BQapv504.js → WebAuthn-D-n-rlfI.js} +5 -5
  90. package/src/assets/web-panel/assets/{WorkflowEditor-ChUXM-Qx.js → WorkflowEditor-Ddc8gGD4.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-7B6U8zgJ.js → chat-C66V_U7w.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-C8UqaLpe.js → colors-BcJyVZRj.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-x47giTCj.js → compact-item-UgUXWcgP.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-D6ZwjgaZ.js → createContext-oxEKHKYP.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-BoDvrsgY.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-Cv3d9-Bu.js → hasIn-CbqzrKQO.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BM-NYjej.js → index-7lSY8B9T.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CUUgXcPh.js → index-B-h0Jgtj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CrGQsbpb.js → index-BMOY4PSW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DvoMGeL9.js → index-BNIq0NAn.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-nogjrcQE.js → index-BQlGMcZ9.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C1ElqYDD.js → index-BT2PZ9df.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-yYdCdtY1.js → index-BZ7mv_oS.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-N9hNOl1i.js → index-BdFgUn1S.js} +1 -1
  105. package/src/assets/web-panel/assets/index-BhqRbl5P.js +1 -0
  106. package/src/assets/web-panel/assets/{index-DVBG0Jqz.js → index-Bmhw_Ler.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dv5E07PZ.js → index-Bp9rfr-P.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BTBkdyLk.js → index-BpqJNeIj.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-EDWA9JL1.js → index-BrWZmUdA.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DtBUgc0G.js → index-Bu1A2sXy.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BMPk5D1m.js → index-C-p9hhAz.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D9lFBkS4.js → index-C3j9jTfB.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DLHnYXVa.js → index-C6X26RJu.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-jgN2e0Oy.js → index-CH13yc9x.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CXG6yiAf.js → index-CJluGB2F.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CQ0-ny-o.js → index-CM2S5TJZ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B7Dorc7r.js → index-CUmYwZyf.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-IVelXaiQ.js → index-CrG-1Kqs.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Cc2bhSuI.js → index-Crqqbu-W.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DD-V-N_m.js → index-CuZupgUw.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-BUzw-kDo.js → index-D3xNhHF3.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Ce666S9v.js → index-D8rgeCoC.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Cbf4RqOd.js → index-DAIuMvVR.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BGxo1YH0.js → index-DBEL07sa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BTisPSqa.js → index-DMbJ06sF.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CXIUyx4U.js → index-DO-5vgTW.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CUtcWTSe.js → index-DR4YKlLt.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D4ZHX6wo.js → index-DRrxJygS.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CiVEatgH.js → index-DWFNv_EG.js} +1 -1
  130. package/src/assets/web-panel/assets/index-DreT6MIR.js +1 -0
  131. package/src/assets/web-panel/assets/{index-Dc0hePXV.js → index-Ov77O9mH.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Cc6jr0Yb.js → index-RnxuL8wM.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-dbM4QvQZ.js → index-SzGgAqai.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CaPZ_8kP.js → index-UpN_kC9R.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-WhoNHp50.js → index-YvNh1UdB.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-DkKS5xtR.js → initDefaultProps-BK9ncFiF.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-CU-zBUIg.js → motion-BxzXi1Pe.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-zBE0VEd4.js → move-CRmwRuah.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-B9WyO1je.js → mtc-parser-BQq5_Znx.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-6sCoBNTL.js → omit-Ch4VNY2j.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-DDRv_sFc.js → pickAttrs-CUioBhrz.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-Cy_CBDw5.js → placementArrow-Hj3o1CrW.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-CIczLq9K.js → responsiveObserve-PfqJrlkI.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DFfZCvnG.js → slide-BXcnR-Lu.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-DW-0HUl-.js → statusUtils-C74a-ofo.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-BPYaBorP.js → styleChecker-Bq57I3ls.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-DYPoOaZc.js → useFlexGapSupport-ITPUjcDw.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-ODUEWR17.js → useFs-DzPuXDIS.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-CstUt15X.js → usePersonalDataHub-DYSgt9L5.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-D2GW8xCL.js → vnode-NPs_EJEk.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-Bz6GoDwq.js → zoom-DZzO6KVS.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/commands/agent.js +21 -10
  154. package/src/data/changelog.json +39 -0
  155. package/src/gateways/ws/session-protocol.js +19 -5
  156. package/src/gateways/ws/ws-agent-handler.js +6 -0
  157. package/src/gateways/ws/ws-session-gateway.js +133 -21
  158. package/src/lib/agent-execution-policy.js +177 -0
  159. package/src/lib/authority-launch-environment.js +99 -0
  160. package/src/lib/claude-project-auto-memory.js +7 -30
  161. package/src/lib/evolution/volcengine-function-replay-store.js +38 -28
  162. package/src/lib/host-tool-policy-authority.js +151 -0
  163. package/src/lib/permission-authority.js +109 -6
  164. package/src/lib/plugin-runtime/settings.js +9 -1
  165. package/src/lib/sandbox-egress-authority-monitor.js +3 -2
  166. package/src/lib/scoped-permission-store.js +229 -119
  167. package/src/lib/session-manager.js +20 -5
  168. package/src/lib/settings-authority-domain.cjs +966 -0
  169. package/src/lib/settings-authority-record.cjs +701 -0
  170. package/src/lib/settings-loader.cjs +175 -19
  171. package/src/lib/settings-source-observation.cjs +278 -0
  172. package/src/lib/sub-agent-context.js +36 -18
  173. package/src/lib/unattended-action-policy.js +456 -38
  174. package/src/repl/agent-repl.js +43 -15
  175. package/src/repl/repl-tool-executor.js +39 -0
  176. package/src/runtime/agent-core.js +272 -5
  177. package/src/runtime/agent-runtime.js +15 -3
  178. package/src/runtime/headless-runner.js +13 -4
  179. package/src/runtime/headless-stream.js +9 -0
  180. package/src/runtime/policies/agent-policy.js +5 -4
  181. package/src/assets/web-panel/assets/ChatBubbleRenderer-pPuUDG2V.js +0 -1
  182. package/src/assets/web-panel/assets/Logs-BZv9Z6BY.js +0 -2
  183. package/src/assets/web-panel/assets/MarkdownRenderer-D5NZGdg3.js +0 -1
  184. package/src/assets/web-panel/assets/PdhVaultBrowser-Cfd_C9nc.js +0 -7
  185. package/src/assets/web-panel/assets/Projects-CTgx3Fxc.js +0 -1
  186. package/src/assets/web-panel/assets/devWarning-BFfV5AtN.js +0 -1
  187. package/src/assets/web-panel/assets/index-DUz_bv5i.js +0 -1
  188. package/src/assets/web-panel/assets/index-rQ59l7GO.js +0 -1
@@ -13,9 +13,10 @@ import projectRoot from "./project-root.cjs";
13
13
  import permissionRules from "./permission-rules.cjs";
14
14
  import { getMachineSecurityAnchorDir } from "./paths.js";
15
15
  import {
16
- mutateSecurityStore,
17
16
  readSecurityStore,
17
+ writeSecurityStore,
18
18
  } from "./durable-security-store.js";
19
+ import { withFileLock } from "./with-file-lock.js";
19
20
 
20
21
  const { findGitProjectRoot } = projectRoot;
21
22
  const { parseRule } = permissionRules;
@@ -27,6 +28,58 @@ const RULE_ID_PATTERN = /^spr_[0-9a-f]{32}$/;
27
28
  const DECISIONS = new Set(["allow", "ask", "deny"]);
28
29
  const DEFAULT_MAX_RECORDS = 1000;
29
30
 
31
+ // Official mutations revoke every provider in this module instance. Path
32
+ // matching alone misses aliases and files that did not exist at admission.
33
+ // This is an in-process event owner, not a cross-process notification journal.
34
+ const ownerId = randomUUID();
35
+ let permissionRevision = Object.freeze({
36
+ ownerId,
37
+ revision: 0,
38
+ state: "ready",
39
+ });
40
+ const revisionListeners = new Set();
41
+
42
+ export function getScopedPermissionRevision() {
43
+ if (permissionRevision.state !== "ready") {
44
+ throw scopedPermissionError(
45
+ "CC_SCOPED_PERMISSION_AUTHORITY_UNAVAILABLE",
46
+ "Scoped permission authority is unavailable",
47
+ );
48
+ }
49
+ return permissionRevision;
50
+ }
51
+
52
+ export function subscribeScopedPermissionRevision(listener) {
53
+ if (typeof listener !== "function")
54
+ throw new TypeError(
55
+ "scoped permission revision listener must be a function",
56
+ );
57
+ revisionListeners.add(listener);
58
+ return () => revisionListeners.delete(listener);
59
+ }
60
+
61
+ function beginMutation() {
62
+ getScopedPermissionRevision();
63
+ const revision = permissionRevision.revision + 1;
64
+ permissionRevision = Object.freeze({
65
+ ownerId,
66
+ revision,
67
+ state: Number.isSafeInteger(revision) ? "mutating" : "invalid",
68
+ });
69
+ for (const listener of [...revisionListeners]) {
70
+ try {
71
+ listener(permissionRevision);
72
+ } catch {
73
+ // One observer must not prevent the other shells from being revoked.
74
+ }
75
+ }
76
+ if (permissionRevision.state === "invalid") getScopedPermissionRevision();
77
+ }
78
+
79
+ function finishMutation(state) {
80
+ permissionRevision = Object.freeze({ ...permissionRevision, state });
81
+ }
82
+
30
83
  export const SCOPED_PERMISSION_ERROR_CODES = Object.freeze({
31
84
  INVALID: "CC_SCOPED_PERMISSION_INVALID",
32
85
  CONFLICT: "CC_SCOPED_PERMISSION_CONFLICT",
@@ -181,24 +234,89 @@ export class ScopedPermissionStore {
181
234
  this._randomId = randomId;
182
235
  }
183
236
 
184
- _read() {
185
- const stored = readSecurityStore(this.filePath, STORE_LABEL);
186
- if (Object.keys(stored).length === 0) return emptyState(this.workspace);
187
- return validateState(stored, this.workspace);
237
+ _capture() {
238
+ const revision = getScopedPermissionRevision();
239
+ const captured = Object.freeze({
240
+ filePath: path.resolve(this.filePath),
241
+ workspace: Object.freeze({ ...this.workspace }),
242
+ now: this._now.bind(this),
243
+ randomId: this._randomId.bind(this),
244
+ maxRecords: this.maxRecords,
245
+ lock: this._lock || withFileLock,
246
+ });
247
+ if (getScopedPermissionRevision() !== revision)
248
+ throw scopedPermissionError(
249
+ "CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
250
+ "Scoped permission authority changed while capturing the operation",
251
+ );
252
+ return captured;
253
+ }
254
+
255
+ _read(captured = this._capture()) {
256
+ getScopedPermissionRevision();
257
+ const stored = readSecurityStore(captured.filePath, STORE_LABEL);
258
+ if (Object.keys(stored).length === 0) return emptyState(captured.workspace);
259
+ return validateState(stored, captured.workspace);
260
+ }
261
+
262
+ _mutate(captured, update) {
263
+ getScopedPermissionRevision(); // Reject observer reentry before the lock.
264
+ fs.mkdirSync(path.dirname(captured.filePath), {
265
+ recursive: true,
266
+ mode: 0o700,
267
+ });
268
+ let started = false;
269
+ let committed = false;
270
+ try {
271
+ captured.lock(
272
+ captured.filePath,
273
+ () => {
274
+ const draft = this._read(captured);
275
+ if (update(draft) === false) return; // Idempotent revoke does no I/O.
276
+ validateState(draft, captured.workspace);
277
+ started = true;
278
+ beginMutation();
279
+ // No draft field aliases the public instance during notification.
280
+ validateState(draft, captured.workspace);
281
+ writeSecurityStore(captured.filePath, STORE_LABEL, draft);
282
+ committed = true;
283
+ },
284
+ { timeoutMs: 2000, staleMs: 30000, failIfUnavailable: true },
285
+ );
286
+ if (started) finishMutation("ready");
287
+ } catch (error) {
288
+ if (!error.commitState)
289
+ error.commitState = committed ? "committed" : "not-committed";
290
+ if (started)
291
+ finishMutation(
292
+ error.commitState === "unknown" ||
293
+ permissionRevision.state === "invalid"
294
+ ? "invalid"
295
+ : "ready",
296
+ );
297
+ throw error;
298
+ }
188
299
  }
189
300
 
190
301
  list() {
191
- const state = this._read();
192
- const now = this._now();
302
+ const revision = getScopedPermissionRevision();
303
+ const captured = this._capture();
304
+ const state = this._read(captured);
305
+ const now = captured.now();
306
+ if (getScopedPermissionRevision() !== revision)
307
+ throw scopedPermissionError(
308
+ "CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
309
+ "Scoped permission authority changed while reading rules",
310
+ );
193
311
  return Object.freeze({
194
312
  schema: state.schema,
195
313
  schemaVersion: state.schemaVersion,
196
314
  generation: state.generation,
197
315
  updatedAt: state.updatedAt,
198
316
  workspace: structuredClone(state.workspace),
199
- file: this.filePath,
317
+ file: captured.filePath,
200
318
  rules: state.rules.map((record) =>
201
- projectRecord(record, now, this.filePath),
319
+ projectRecord(record, now, captured.filePath),
202
320
  ),
203
321
  });
204
322
  }
@@ -210,12 +328,13 @@ export class ScopedPermissionStore {
210
328
  reason = "",
211
329
  expectedGeneration = null,
212
330
  } = {}) {
331
+ const captured = this._capture();
213
332
  const normalizedDecision = String(decision || "")
214
333
  .trim()
215
334
  .toLowerCase();
216
335
  const normalizedRule = String(rule || "").trim();
217
336
  const normalizedReason = String(reason || "").trim();
218
- const now = this._now();
337
+ const now = captured.now();
219
338
  const expiry = Number(expiresAt);
220
339
  if (!DECISIONS.has(normalizedDecision)) {
221
340
  throw scopedPermissionError(
@@ -243,71 +362,67 @@ export class ScopedPermissionStore {
243
362
  }
244
363
 
245
364
  let created = null;
246
- mutateSecurityStore(
247
- this.filePath,
248
- STORE_LABEL,
249
- (draft) => {
250
- const current =
251
- Object.keys(draft).length === 0
252
- ? emptyState(this.workspace)
253
- : validateState(draft, this.workspace);
254
- if (
255
- expectedGeneration !== null &&
256
- Number(expectedGeneration) !== current.generation
257
- ) {
258
- throw scopedPermissionError(
259
- SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
260
- `Scoped permission generation changed (expected ${expectedGeneration}, current ${current.generation})`,
261
- );
262
- }
365
+ this._mutate(captured, (draft) => {
366
+ const current =
367
+ Object.keys(draft).length === 0
368
+ ? emptyState(captured.workspace)
369
+ : validateState(draft, captured.workspace);
370
+ if (
371
+ expectedGeneration !== null &&
372
+ Number(expectedGeneration) !== current.generation
373
+ ) {
374
+ throw scopedPermissionError(
375
+ SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
376
+ `Scoped permission generation changed (expected ${expectedGeneration}, current ${current.generation})`,
377
+ );
378
+ }
263
379
 
264
- const retained = [...current.rules];
265
- while (retained.length >= this.maxRecords) {
266
- const inactiveIndex = retained.findIndex(
267
- (record) => statusFor(record, now) !== "active",
268
- );
269
- if (inactiveIndex < 0) break;
270
- retained.splice(inactiveIndex, 1);
271
- }
272
- if (retained.length >= this.maxRecords) {
273
- throw scopedPermissionError(
274
- SCOPED_PERMISSION_ERROR_CODES.INVALID,
275
- `Scoped permission store is full (${this.maxRecords} retained rules)`,
276
- );
277
- }
278
- const id = this._randomId();
279
- if (
280
- !RULE_ID_PATTERN.test(id) ||
281
- retained.some((item) => item.id === id)
282
- ) {
283
- throw scopedPermissionError(
284
- SCOPED_PERMISSION_ERROR_CODES.INVALID,
285
- "Could not allocate a valid unique scoped permission id",
286
- );
287
- }
288
- created = {
289
- id,
290
- decision: normalizedDecision,
291
- rule: normalizedRule,
292
- revision: 1,
293
- createdAt: now,
294
- expiresAt: expiry,
295
- revokedAt: null,
296
- reason: normalizedReason,
297
- };
298
- Object.assign(draft, current, {
299
- generation: current.generation + 1,
300
- updatedAt: now,
301
- rules: [...retained, created],
302
- });
303
- validateState(draft, this.workspace);
304
- },
305
- this._lock ? { lock: this._lock } : undefined,
306
- );
307
- return projectRecord(created, now, this.filePath);
380
+ const retained = [...current.rules];
381
+ while (retained.length >= captured.maxRecords) {
382
+ const inactiveIndex = retained.findIndex(
383
+ (record) => statusFor(record, now) !== "active",
384
+ );
385
+ if (inactiveIndex < 0) break;
386
+ retained.splice(inactiveIndex, 1);
387
+ }
388
+ if (retained.length >= captured.maxRecords) {
389
+ throw scopedPermissionError(
390
+ SCOPED_PERMISSION_ERROR_CODES.INVALID,
391
+ `Scoped permission store is full (${captured.maxRecords} retained rules)`,
392
+ );
393
+ }
394
+ const id = captured.randomId();
395
+ if (
396
+ !RULE_ID_PATTERN.test(id) ||
397
+ retained.some((item) => item.id === id)
398
+ ) {
399
+ throw scopedPermissionError(
400
+ SCOPED_PERMISSION_ERROR_CODES.INVALID,
401
+ "Could not allocate a valid unique scoped permission id",
402
+ );
403
+ }
404
+ created = {
405
+ id,
406
+ decision: normalizedDecision,
407
+ rule: normalizedRule,
408
+ revision: 1,
409
+ createdAt: now,
410
+ expiresAt: expiry,
411
+ revokedAt: null,
412
+ reason: normalizedReason,
413
+ };
414
+ Object.assign(draft, current, {
415
+ generation: current.generation + 1,
416
+ updatedAt: now,
417
+ rules: [...retained, created],
418
+ });
419
+ validateState(draft, captured.workspace);
420
+ });
421
+ return projectRecord(created, now, captured.filePath);
308
422
  }
309
423
 
310
424
  revoke({ id, expectedRevision = null } = {}) {
425
+ const captured = this._capture();
311
426
  const normalizedId = String(id || "").trim();
312
427
  if (!RULE_ID_PATTERN.test(normalizedId)) {
313
428
  throw scopedPermissionError(
@@ -315,55 +430,50 @@ export class ScopedPermissionStore {
315
430
  "A valid scoped permission id is required",
316
431
  );
317
432
  }
318
- const now = this._now();
433
+ const now = captured.now();
319
434
  let revoked = null;
320
- mutateSecurityStore(
321
- this.filePath,
322
- STORE_LABEL,
323
- (draft) => {
324
- const current =
325
- Object.keys(draft).length === 0
326
- ? emptyState(this.workspace)
327
- : validateState(draft, this.workspace);
328
- const index = current.rules.findIndex(
329
- (record) => record.id === normalizedId,
435
+ this._mutate(captured, (draft) => {
436
+ const current =
437
+ Object.keys(draft).length === 0
438
+ ? emptyState(captured.workspace)
439
+ : validateState(draft, captured.workspace);
440
+ const index = current.rules.findIndex(
441
+ (record) => record.id === normalizedId,
442
+ );
443
+ if (index < 0) {
444
+ throw scopedPermissionError(
445
+ SCOPED_PERMISSION_ERROR_CODES.NOT_FOUND,
446
+ `Scoped permission rule not found: ${normalizedId}`,
330
447
  );
331
- if (index < 0) {
332
- throw scopedPermissionError(
333
- SCOPED_PERMISSION_ERROR_CODES.NOT_FOUND,
334
- `Scoped permission rule not found: ${normalizedId}`,
335
- );
336
- }
337
- const record = current.rules[index];
338
- if (
339
- expectedRevision !== null &&
340
- Number(expectedRevision) !== record.revision
341
- ) {
342
- throw scopedPermissionError(
343
- SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
344
- `Scoped permission revision changed (expected ${expectedRevision}, current ${record.revision})`,
345
- );
346
- }
347
- if (record.revokedAt !== null) {
348
- revoked = record;
349
- return;
350
- }
351
- revoked = {
352
- ...record,
353
- revision: record.revision + 1,
354
- revokedAt: now,
355
- };
356
- const rules = [...current.rules];
357
- rules[index] = revoked;
358
- Object.assign(draft, current, {
359
- generation: current.generation + 1,
360
- updatedAt: now,
361
- rules,
362
- });
363
- validateState(draft, this.workspace);
364
- },
365
- this._lock ? { lock: this._lock } : undefined,
366
- );
367
- return projectRecord(revoked, now, this.filePath);
448
+ }
449
+ const record = current.rules[index];
450
+ if (
451
+ expectedRevision !== null &&
452
+ Number(expectedRevision) !== record.revision
453
+ ) {
454
+ throw scopedPermissionError(
455
+ SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
456
+ `Scoped permission revision changed (expected ${expectedRevision}, current ${record.revision})`,
457
+ );
458
+ }
459
+ if (record.revokedAt !== null) {
460
+ revoked = record;
461
+ return false;
462
+ }
463
+ revoked = {
464
+ ...record,
465
+ revision: record.revision + 1,
466
+ revokedAt: now,
467
+ };
468
+ const rules = [...current.rules];
469
+ rules[index] = revoked;
470
+ Object.assign(draft, current, {
471
+ generation: current.generation + 1,
472
+ updatedAt: now,
473
+ rules,
474
+ });
475
+ validateState(draft, captured.workspace);
476
+ });
477
+ return projectRecord(revoked, now, captured.filePath);
368
478
  }
369
479
  }
@@ -108,7 +108,7 @@ export function saveMessages(db, sessionId, messages, metadata) {
108
108
  /**
109
109
  * Get a session by ID
110
110
  */
111
- export function getSession(db, sessionId) {
111
+ export function getSession(db, sessionId, { strictMetadata = false } = {}) {
112
112
  ensureSessionsTable(db);
113
113
 
114
114
  // Try exact match first, then prefix match
@@ -127,16 +127,31 @@ export function getSession(db, sessionId) {
127
127
 
128
128
  if (!session) return null;
129
129
 
130
+ let metadata;
131
+ if (strictMetadata) {
132
+ metadata =
133
+ typeof session.metadata === "string"
134
+ ? JSON.parse(session.metadata)
135
+ : (session.metadata ?? {});
136
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
137
+ const error = new TypeError("Session authority metadata is invalid");
138
+ error.code = "CC_SESSION_AUTHORITY_METADATA_INVALID";
139
+ throw error;
140
+ }
141
+ } else {
142
+ metadata =
143
+ typeof session.metadata === "string"
144
+ ? safeJsonParse(session.metadata, {})
145
+ : session.metadata || {};
146
+ }
147
+
130
148
  return {
131
149
  ...session,
132
150
  // A single corrupt (non-JSON, non-empty) column must not make the whole
133
151
  // session unloadable — that would block resume of the user's own data.
134
152
  // Matches the safeJsonParse fallback listSessions already uses.
135
153
  messages: safeJsonParse(session.messages, []),
136
- metadata:
137
- typeof session.metadata === "string"
138
- ? safeJsonParse(session.metadata, {})
139
- : session.metadata || {},
154
+ metadata,
140
155
  };
141
156
  }
142
157