chainlesschain 0.162.176 → 0.162.178

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 (260) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-BDLu5Elz.js → AIOps-DRftcSue.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DH5us-CK.js → ActionButton-BFvUaMKR.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BZ0u8IFG.js → Analytics-C0-Rru4P.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-bx-BCljj.js +9 -0
  8. package/src/assets/web-panel/assets/{Artifacts-Br2YBbh_.js → Artifacts-DAJ8B3Un.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-C3C6a482.js → Audit-hOk4L1lr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-CzHvebcj.js → BackgroundAgents-3pbDpM9z.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-DZNHYDFd.js → Backup-BtziGDn9.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-VRiC4fIx.js → BaseInput-kmshphJ3.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-Cm08WWAD.js → Chat-d7tGSosA.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CGHYrugQ.js → ChatBubbleRenderer-p-_PBpX_.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-DtByk7d7.js → Checkbox-C4Eh4Tz5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-CphXXOfD.js → Codegen-r7gmhZyA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-Hz86TnLR.js → Col-U7mDaGlL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-HP03ltpD.js → Community-Bpu8aH1T.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-B_N1mqQz.js → Compact-CHb2XBJe.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-CVztRpi8.js → Compliance-Cn_r4dey.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-wUVOyZih.js → Cowork-CdMWfDcz.js} +4 -4
  22. package/src/assets/web-panel/assets/{Cron-DW5TLJKw.js → Cron-BtnREHWy.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-CrAFGhWH.js → Crosschain-BKI9TSzr.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-pA-oD8ke.js → DID-pkNubzzJ.js} +2 -2
  25. package/src/assets/web-panel/assets/Dashboard-1rrz1snN.js +3 -0
  26. package/src/assets/web-panel/assets/{Dropdown-EweJcsek.js → Dropdown-DLyFVxDc.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-C9xVLPG4.js → EmailListRenderer-CtUWfYVq.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CDcEEqlu.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-DtZda9A_.js → Federation-D8lsDGus.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-CiYdcVz3.js → FormItemContext-CRYt_aIw.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DA13uwA0.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DzRhZD1O.js → Git-BXj5Dney.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-D5RMgOSb.js → Governance-Cm-I8RMz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-BgrPfSNk.js → Inference-CPvbhvgt.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DzjnD571.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BhWzVDtN.js → Logs-BZY_3CRZ.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-BDape9cx.js → MarkdownRenderer-up0BW6pv.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-DJtL5GV7.js → Marketplace-C5MEJ2Qe.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-CZcQygI7.js → McpTools-ihdrn43I.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-DUWSDUUu.js → Memory-Br8bPMlh.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-BTwq2WKY.js → MobileBridge-BNntgrYW.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BdV915mw.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-BiGugEQm.js → Mtc-A3wDmlqL.js} +5 -5
  44. package/src/assets/web-panel/assets/{MtcAudit-DY7a6gUe.js → MtcAudit-CF-mBLWM.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-BQbDu_w7.js → Multisig-Iy2k_MSo.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-CM2wXTLv.js → NLProgramming-kk0W7HUb.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-DI6gGs1v.js → Notes-B5u5t_4S.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-BsZOLWAB.js → NotificationSettings-BN0POf6_.js} +1 -1
  49. package/src/assets/web-panel/assets/OrderTableRenderer-CHkSUrXB.js +1 -0
  50. package/src/assets/web-panel/assets/{Organization-Cds0_tXr.js → Organization-DwWQESuN.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-WunYMD87.js → Overflow-DG6klrcC.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-Bo6j4DDv.js → P2P-BwJcKkgZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-Bh2XPD5f.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
  54. package/src/assets/web-panel/assets/{Permissions-EMvMMC8N.js → Permissions-CGMXe4TX.js} +4 -4
  55. package/src/assets/web-panel/assets/PersonalDataHub-ziYqg7-A.js +8 -0
  56. package/src/assets/web-panel/assets/{Pipeline-BOx8HOgL.js → Pipeline-BzJfeoGt.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-tBeTVQVM.js → Privacy-DLh3Bblx.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-CVYg1W4M.js → ProjectInit-USVWW36c.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Dvt5I7y1.js → ProjectSettings-CiiOn62h.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-6V-PNmcA.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-Ct8xqY-E.js → Providers-CBPWWzpu.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-Cni8Ooja.js → QrScannerModal-QVwoRUq3.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-BJVLKdxr.js → QuickAsk-CXnR-2Vn.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-DPG8Rbbm.js → Recommend-ClaNzjmF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-DsdR7dCt.js → RemoteSession-C3rGw8vJ.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-Csg1YM-b.js → Reputation-Ci-OWAoP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-D9DoFoEf.js → Row-DRhAwXOS.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BsvKrXMS.js → RssFeed-BQLJQ2_y.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DFUStfgQ.js → Search-QQUpkfFd.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-ILg1zxdK.js → Security-DdXKzuKT.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-Dd41PCaY.js → Services-x2Zh62Qd.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-Bzo1cN80.js → Skeleton-GdY4KwRz.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-BDHh17P0.js → Skills-Bv4tY1Fd.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-jl7UTiRY.js → Sla-C3QnM-11.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BePa-RF6.js → SpeechSettings-Bl3ytvmI.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-DiqqDKGZ.js → SyncSettings-Wp9IwTsP.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-DYQ_6lxj.js → Tasks-CF3rIrDe.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-JiDeI06L.js → Templates-CGfvVVM9.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-hhgI6zwS.js → Tenant-CVXmwQ3U.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-bvwrt03E.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-B8uB5Llk.js → TimelineRenderer-D0g74KhH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-C3b_6r2z.js → Tokens-BH4IwzlT.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-tIP7HKVe.js → Trigger-DJOpM533.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-vjoc11vJ.js → Trust-B43EPRLr.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DUPBnGuH.js → UkeySign-BmBFG-em.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-CBvhUY5T.js → VideoEditing-n9oJwB8k.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-DUaQ0uA5.js → Wallet-D5QeivHp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-DllSivjE.js → WebAuthn-DcNy7igO.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-xc8D3uO6.js → WorkflowEditor-BVjc7-5-.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-BVicU7oA.js → chat-Dy5Tfj3m.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-DJzLmhLm.js → colors-C4c2CNZU.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-BvYRfThE.js → compact-item-DPa3O5vN.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-DGXxCBKR.js → createContext-TLcfNA6_.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BAwdDA_5.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-SBDJB1y4.js → hasIn-D41LVFkp.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-D7HimK0M.js → index-0oevYjfT.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DCdRAgjy.js → index-388pLGJ1.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BO1itZaY.js → index-B8nLBZbv.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-DvUTDYXX.js → index-BPTGAjee.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-hxTODbM1.js → index-BPtrOBmW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-wcCL-miV.js → index-BQiIxHBy.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CKyEivf8.js → index-BSM1cHVK.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BJZf9DhV.js → index-BYPr9fkz.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DHVLJ8q0.js → index-BsQ7Ev6i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cs2rmLFt.js → index-Btk9qLL2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-z1ZylsLq.js → index-BvmziZSG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cf-O_g_8.js → index-BvopLC_T.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DMarmfrv.js → index-C7xhTVyP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CoJAO1Lm.js → index-CNk_ZooP.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-9gdI7uUW.js → index-CQrEHoOG.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bf-ctERC.js → index-CSxex23J.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-MjC4G09d.js → index-CVjgm992.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-e4fXH_WO.js → index-CWK4zOPe.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-xfluRkCT.js → index-CX0VsoP4.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DRfiXB_p.js → index-CXl0YPNG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-d5BVd-cL.js → index-Cip7LWml.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Dm_77jgR.js → index-Cq_hlWNt.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-YJkzy8ZF.js → index-CzZ49YZL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CtwqO5qo.js → index-D2Nkivlm.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BYqz4Wrr.js → index-DBUgRs4D.js} +1 -1
  121. package/src/assets/web-panel/assets/index-DEjVkdzq.js +1 -0
  122. package/src/assets/web-panel/assets/{index-CdGY9P8a.js → index-DNsBj2Ob.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DonCnfMr.js → index-Di4X8cfI.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-1SdDS6lR.js → index-DljFsVGI.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DnjPdvR6.js → index-Korc3QIe.js} +1 -1
  126. package/src/assets/web-panel/assets/index-LMOc5xrS.js +1 -0
  127. package/src/assets/web-panel/assets/{index-VMfwIH5G.js → index-MKZDje0H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DKrvvWm4.js → index-OHAfRu_3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-FxZzRyE1.js → index-WEJr8PL5.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Crk5NTb0.js → index-WinCp6du.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DCqRS6da.js → index-Yjr6cqSK.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BTrBGBJs.js → index-iQ6gtFih.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-lpaGoTH3.js → index-pt3s7yvV.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CloCgBUR.js → index-tQ3igERQ.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-C1zO1KnY.js → initDefaultProps-BFZu87WZ.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-CWLtEGmC.js → motion-DJIo7qvt.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-DzD_C3Vm.js → move-Cfr5qknL.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-D880PYJl.js → mtc-parser-Bgc-OadH.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-CFR_z2ov.js → omit-X3WI-TNp.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-srIhgrSC.js → pickAttrs-pJhU9IxK.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-C1rXpibP.js → placementArrow-BNg_Vj3T.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-C30gRcSS.js → responsiveObserve-TeSEGQ5-.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-CvtKiMfV.js → slide-D_t_P0bv.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-DNHO7ITG.js → statusUtils-DrrIMwAT.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-Dt_Sl-g4.js → styleChecker-BjZCuFRm.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-Cjev42Gh.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-Baxglj2V.js → useFs-BidCpfLX.js} +1 -1
  148. package/src/assets/web-panel/assets/usePersonalDataHub-C5mHttA9.js +1 -0
  149. package/src/assets/web-panel/assets/{vnode-mIRkdT7o.js → vnode-BrbcRiHZ.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-HzI5DlH_.js → zoom-PISX7AkR.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/__tests__/hub-wechat.test.js +45 -0
  153. package/src/commands/agenda.js +22 -6
  154. package/src/commands/agent.js +3 -3
  155. package/src/commands/background-session.js +16 -6
  156. package/src/commands/batch.js +79 -27
  157. package/src/commands/changelog.js +15 -4
  158. package/src/commands/context.js +33 -1
  159. package/src/commands/eval.js +21 -6
  160. package/src/commands/hook.js +2 -2
  161. package/src/commands/hub.js +473 -39
  162. package/src/commands/init.js +212 -110
  163. package/src/commands/llm.js +22 -6
  164. package/src/commands/loop.js +9 -2
  165. package/src/commands/skill.js +13 -1
  166. package/src/commands/statusline.js +3 -3
  167. package/src/data/changelog.json +53 -5
  168. package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
  169. package/src/harness/background-task-manager.js +18 -6
  170. package/src/harness/background-task-worker.js +11 -18
  171. package/src/harness/golden-transcript.js +3 -0
  172. package/src/harness/mcp-client.js +9 -15
  173. package/src/harness/worktree-isolator.js +18 -21
  174. package/src/index.js +18 -7
  175. package/src/lib/agent-core.js +5 -0
  176. package/src/lib/agent-ipc-bus.js +12 -3
  177. package/src/lib/agent-team/team-worktree.js +28 -6
  178. package/src/lib/agent-worktree.js +20 -19
  179. package/src/lib/approval-authority-store.js +891 -0
  180. package/src/lib/async-hook-supervisor.cjs +134 -24
  181. package/src/lib/async-hook-supervisor.js +47 -0
  182. package/src/lib/background-agent-supervisor.js +35 -12
  183. package/src/lib/background-interaction-resolver.js +399 -54
  184. package/src/lib/background-session-transport.js +3 -1
  185. package/src/lib/background-task-manager.js +5 -1
  186. package/src/lib/changelog.js +57 -0
  187. package/src/lib/chrome-connector.js +6 -2
  188. package/src/lib/claude-code-bridge.js +24 -8
  189. package/src/lib/cli-anything-bridge.js +82 -25
  190. package/src/lib/context-breakdown.js +82 -3
  191. package/src/lib/doctor-checkup.js +18 -6
  192. package/src/lib/eval/tasks.js +25 -31
  193. package/src/lib/event-runtime-host.js +225 -0
  194. package/src/lib/event-runtime-store.js +102 -21
  195. package/src/lib/event-runtime-worker.js +70 -2
  196. package/src/lib/hook-runner.cjs +71 -36
  197. package/src/lib/hook-runner.js +55 -0
  198. package/src/lib/hooks-v2-producers.js +33 -8
  199. package/src/lib/hooks-v2-runtime.js +360 -40
  200. package/src/lib/host-adb-bridge.js +18 -3
  201. package/src/lib/interaction-adapter.js +66 -0
  202. package/src/lib/lsp/__tests__/benchmark.test.js +61 -0
  203. package/src/lib/lsp/__tests__/lsp-client.test.js +6 -1
  204. package/src/lib/lsp/benchmark.js +20 -4
  205. package/src/lib/lsp/lsp-client.js +6 -4
  206. package/src/lib/lsp/lsp-server-registry.js +0 -2
  207. package/src/lib/operation-fingerprint.js +107 -2
  208. package/src/lib/orchestrator.js +8 -2
  209. package/src/lib/packer/pack-update-applier.js +14 -2
  210. package/src/lib/packer/smoke-runner.js +18 -5
  211. package/src/lib/paths.js +1 -1
  212. package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
  213. package/src/lib/personal-data-hub-wiring.js +327 -52
  214. package/src/lib/plugin-monitor-supervisor.js +20 -13
  215. package/src/lib/plugin-runtime/install.js +10 -3
  216. package/src/lib/process-execution-broker/credential-agent.js +954 -111
  217. package/src/lib/process-execution-broker/index.js +369 -85
  218. package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
  219. package/src/lib/provider-http-error.js +94 -0
  220. package/src/lib/remote-approval-bridge.js +222 -26
  221. package/src/lib/repl-bang-memorize.js +10 -2
  222. package/src/lib/repl-goal.js +19 -2
  223. package/src/lib/settings-hook-events.cjs +13 -8
  224. package/src/lib/settings-hook-events.js +20 -0
  225. package/src/lib/side-effect-ledger-store.js +65 -14
  226. package/src/lib/skill-loader.js +215 -12
  227. package/src/lib/skill-packs/generator.js +18 -9
  228. package/src/lib/skill-process-broker.js +85 -0
  229. package/src/lib/slash-commands.js +10 -2
  230. package/src/lib/status-line.cjs +28 -7
  231. package/src/lib/status-line.js +33 -0
  232. package/src/lib/sub-agent-context.js +42 -0
  233. package/src/lib/turn-binding-store.js +64 -15
  234. package/src/lib/turn-binding.js +85 -16
  235. package/src/repl/agent-repl.js +88 -49
  236. package/src/repl/remote-approval.js +13 -13
  237. package/src/repl/repl-turn-binding.js +46 -16
  238. package/src/runtime/agent-core.js +419 -84
  239. package/src/runtime/coding-agent-contract-shared.cjs +16 -0
  240. package/src/runtime/headless-runner.js +172 -44
  241. package/src/runtime/headless-stream.js +221 -11
  242. package/src/runtime/session-slash-commands.js +506 -0
  243. package/src/skills/video-editing/extractors/audio-extractor.js +13 -5
  244. package/src/skills/video-editing/extractors/video-extractor.js +13 -3
  245. package/src/skills/video-editing/media-process.js +20 -0
  246. package/src/skills/video-editing/render/audio-mix.js +13 -3
  247. package/src/skills/video-editing/render/ffmpeg-concat.js +7 -2
  248. package/src/skills/video-editing/render/ffmpeg-extract.js +7 -2
  249. package/src/workers/background-agent-worker.js +76 -40
  250. package/src/assets/web-panel/assets/AppLayout-Duu8xR14.js +0 -9
  251. package/src/assets/web-panel/assets/Dashboard-CXrd5gAw.js +0 -3
  252. package/src/assets/web-panel/assets/MobileProjects-NZvvfDME.js +0 -1
  253. package/src/assets/web-panel/assets/OrderTableRenderer-C48mu09U.js +0 -1
  254. package/src/assets/web-panel/assets/PersonalDataHub-AX3ypUDV.js +0 -9
  255. package/src/assets/web-panel/assets/Projects-Cc7FC9-e.js +0 -1
  256. package/src/assets/web-panel/assets/Terminal-CjTZPa37.js +0 -3
  257. package/src/assets/web-panel/assets/devWarning-BT46Y_QI.js +0 -1
  258. package/src/assets/web-panel/assets/index-B7nuIywP.js +0 -1
  259. package/src/assets/web-panel/assets/index-ycwAfC-i.js +0 -1
  260. package/src/assets/web-panel/assets/usePersonalDataHub-B4NkcLaP.js +0 -1
@@ -0,0 +1,891 @@
1
+ /**
2
+ * Crash-safe approval request/resolve state.
3
+ *
4
+ * This store is the durable boundary for approval cards that authorize side
5
+ * effects. Every mutation is a locked read-modify-write, every successful
6
+ * resolve is a record-level compare-and-swap, and the state file is replaced
7
+ * atomically. A lock, read, or write failure throws: callers must treat that as
8
+ * a denied approval and must not execute the side effect.
9
+ *
10
+ * Display/list reads and the optional audit callback are advisory. They may
11
+ * degrade best-effort without weakening the critical request/resolve path.
12
+ */
13
+
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import { createHash, randomUUID } from "node:crypto";
18
+ import {
19
+ canApprove,
20
+ describeAuthorityChain,
21
+ verifyApprovalBinding,
22
+ } from "./agent-authority.js";
23
+ import {
24
+ computeOperationFingerprint,
25
+ operationDescriptorKey,
26
+ shortOperationId,
27
+ summarizeOperation,
28
+ } from "./operation-fingerprint.js";
29
+ import { withFileLock } from "./with-file-lock.js";
30
+
31
+ const STATE_SCHEMA = "cc.approval-authority-state";
32
+ const STATE_SCHEMA_VERSION = 1;
33
+ const DEFAULT_TTL_MS = 5 * 60 * 1000;
34
+ const DEFAULT_MAX_RECORDS = 10_000;
35
+ const APPROVAL_BINDING_PATTERN = /^ab_[0-9a-f]{32}$/;
36
+ const OPERATION_FINGERPRINT_PATTERN = /^opf_[0-9a-f]{40}$/;
37
+ const TERMINAL_STATUSES = new Set([
38
+ "resolved",
39
+ "rejected",
40
+ "superseded",
41
+ "expired",
42
+ "cancelled",
43
+ ]);
44
+
45
+ export const APPROVAL_STATE_ERROR_CODES = Object.freeze({
46
+ INVALID: "CC_APPROVAL_STATE_INVALID",
47
+ CONFLICT: "CC_APPROVAL_STATE_CONFLICT",
48
+ LOCK_UNAVAILABLE: "CC_APPROVAL_STATE_LOCK_UNAVAILABLE",
49
+ READ_FAILED: "CC_APPROVAL_STATE_READ_FAILED",
50
+ WRITE_FAILED: "CC_APPROVAL_STATE_WRITE_FAILED",
51
+ CORRUPT: "CC_APPROVAL_STATE_CORRUPT",
52
+ BACKPRESSURE: "CC_APPROVAL_STATE_BACKPRESSURE",
53
+ });
54
+
55
+ function approvalStateError(code, message, cause = null) {
56
+ const error = new Error(message);
57
+ error.code = code;
58
+ if (cause) error.cause = cause;
59
+ return error;
60
+ }
61
+
62
+ function clone(value) {
63
+ return value == null ? value : JSON.parse(JSON.stringify(value));
64
+ }
65
+
66
+ function finiteTime(value, fallback = null) {
67
+ if (value === null || value === undefined || value === "") return fallback;
68
+ const number = Number(value);
69
+ return Number.isFinite(number) ? number : fallback;
70
+ }
71
+
72
+ function requiredId(value, fieldName) {
73
+ const normalized = String(value ?? "").trim();
74
+ if (!normalized || normalized.length > 256) {
75
+ throw approvalStateError(
76
+ APPROVAL_STATE_ERROR_CODES.INVALID,
77
+ `${fieldName} is required and must be at most 256 characters`,
78
+ );
79
+ }
80
+ return normalized;
81
+ }
82
+
83
+ function emptyState() {
84
+ return {
85
+ schema: STATE_SCHEMA,
86
+ schemaVersion: STATE_SCHEMA_VERSION,
87
+ generation: 0,
88
+ updatedAt: null,
89
+ requests: [],
90
+ };
91
+ }
92
+
93
+ function logicalDescriptorDigest(descriptor) {
94
+ return (
95
+ "opk_" +
96
+ createHash("sha256")
97
+ .update(
98
+ `cc-approval-logical-key-v1\n${operationDescriptorKey(descriptor)}`,
99
+ "utf8",
100
+ )
101
+ .digest("hex")
102
+ .slice(0, 40)
103
+ );
104
+ }
105
+
106
+ function isKnownStateError(error) {
107
+ return Object.values(APPROVAL_STATE_ERROR_CODES).includes(error?.code);
108
+ }
109
+
110
+ export function defaultApprovalAuthorityStatePath(sessionId = null) {
111
+ const sessionSuffix = sessionId
112
+ ? `session-${createHash("sha256")
113
+ .update(String(sessionId), "utf8")
114
+ .digest("hex")
115
+ .slice(0, 32)}.json`
116
+ : "state.json";
117
+ return path.join(
118
+ os.homedir(),
119
+ ".chainlesschain",
120
+ "approval-authority",
121
+ sessionSuffix,
122
+ );
123
+ }
124
+
125
+ export class ApprovalAuthorityStore {
126
+ constructor({
127
+ filePath = defaultApprovalAuthorityStatePath(),
128
+ now = () => Date.now(),
129
+ defaultTtlMs = DEFAULT_TTL_MS,
130
+ maxRecords = DEFAULT_MAX_RECORDS,
131
+ lockTimeoutMs = 2000,
132
+ lockStaleMs = 30_000,
133
+ onAudit = null,
134
+ _fs = fs,
135
+ _lock = withFileLock,
136
+ _randomUUID = randomUUID,
137
+ _beforeRename = null,
138
+ _platform = process.platform,
139
+ } = {}) {
140
+ const normalizedFilePath = String(filePath ?? "").trim();
141
+ if (!normalizedFilePath) {
142
+ throw approvalStateError(
143
+ APPROVAL_STATE_ERROR_CODES.INVALID,
144
+ "Approval state file path is required",
145
+ );
146
+ }
147
+ this.filePath = path.resolve(normalizedFilePath);
148
+ this._now = typeof now === "function" ? now : () => Number(now);
149
+ this.defaultTtlMs = Math.max(
150
+ 1,
151
+ Math.floor(Number(defaultTtlMs) || DEFAULT_TTL_MS),
152
+ );
153
+ this.maxRecords = Math.max(
154
+ 1,
155
+ Math.floor(Number(maxRecords) || DEFAULT_MAX_RECORDS),
156
+ );
157
+ this.lockTimeoutMs = Math.max(0, Number(lockTimeoutMs) || 0);
158
+ this.lockStaleMs = Math.max(1, Number(lockStaleMs) || 30_000);
159
+ this._onAudit = typeof onAudit === "function" ? onAudit : null;
160
+ this._fs = _fs;
161
+ this._lock = _lock;
162
+ this._randomUUID = _randomUUID;
163
+ this._beforeRename = _beforeRename;
164
+ this._platform = _platform;
165
+ }
166
+
167
+ _emitAudit(entry) {
168
+ if (!this._onAudit) return;
169
+ try {
170
+ this._onAudit({
171
+ timestamp: this._now(),
172
+ ...entry,
173
+ });
174
+ } catch {
175
+ // Audit delivery is advisory. Critical state is already durable.
176
+ }
177
+ }
178
+
179
+ _ensureDirectory() {
180
+ try {
181
+ this._fs.mkdirSync(path.dirname(this.filePath), {
182
+ recursive: true,
183
+ mode: 0o700,
184
+ });
185
+ } catch (cause) {
186
+ throw approvalStateError(
187
+ APPROVAL_STATE_ERROR_CODES.WRITE_FAILED,
188
+ "Could not create approval state directory",
189
+ cause,
190
+ );
191
+ }
192
+ }
193
+
194
+ _validateState(value) {
195
+ if (
196
+ !value ||
197
+ value.schema !== STATE_SCHEMA ||
198
+ value.schemaVersion !== STATE_SCHEMA_VERSION ||
199
+ !Number.isInteger(value.generation) ||
200
+ value.generation < 0 ||
201
+ !Array.isArray(value.requests)
202
+ ) {
203
+ throw approvalStateError(
204
+ APPROVAL_STATE_ERROR_CODES.CORRUPT,
205
+ "Approval state file has an invalid schema",
206
+ );
207
+ }
208
+
209
+ const requestIds = new Set();
210
+ const fingerprints = new Set();
211
+ for (const record of value.requests) {
212
+ const hasValidDecision =
213
+ (record?.status === "pending" &&
214
+ record.decision === null &&
215
+ record.resolvedAt === null &&
216
+ record.resolvedAuthority === null) ||
217
+ (record?.status === "resolved" &&
218
+ record.decision === true &&
219
+ Number.isFinite(record.resolvedAt) &&
220
+ typeof record.resolvedAuthority === "string") ||
221
+ (record?.status === "rejected" &&
222
+ record.decision === false &&
223
+ Number.isFinite(record.resolvedAt) &&
224
+ typeof record.resolvedAuthority === "string") ||
225
+ (TERMINAL_STATUSES.has(record?.status) &&
226
+ record.status !== "resolved" &&
227
+ record.status !== "rejected" &&
228
+ record.decision === null);
229
+ if (
230
+ !record ||
231
+ typeof record.requestId !== "string" ||
232
+ !record.requestId ||
233
+ requestIds.has(record.requestId) ||
234
+ !OPERATION_FINGERPRINT_PATTERN.test(record.fingerprint || "") ||
235
+ fingerprints.has(record.fingerprint) ||
236
+ !APPROVAL_BINDING_PATTERN.test(record.binding || "") ||
237
+ !/^opk_[0-9a-f]{40}$/.test(record.logicalDigest || "") ||
238
+ typeof record.sessionId !== "string" ||
239
+ !record.sessionId ||
240
+ !Number.isInteger(record.revision) ||
241
+ record.revision < 1 ||
242
+ !Number.isFinite(record.notBefore) ||
243
+ !Number.isFinite(record.notAfter) ||
244
+ record.notAfter < record.notBefore ||
245
+ !Number.isFinite(record.createdAt) ||
246
+ !Number.isFinite(record.updatedAt) ||
247
+ (record.status !== "pending" &&
248
+ !TERMINAL_STATUSES.has(record.status)) ||
249
+ !hasValidDecision ||
250
+ (record.status === "superseded" && !record.supersededBy)
251
+ ) {
252
+ throw approvalStateError(
253
+ APPROVAL_STATE_ERROR_CODES.CORRUPT,
254
+ "Approval state file contains an invalid request record",
255
+ );
256
+ }
257
+ requestIds.add(record.requestId);
258
+ fingerprints.add(record.fingerprint);
259
+ }
260
+ return value;
261
+ }
262
+
263
+ _readState() {
264
+ let serialized;
265
+ try {
266
+ serialized = this._fs.readFileSync(this.filePath, "utf8");
267
+ } catch (cause) {
268
+ if (cause?.code === "ENOENT") return emptyState();
269
+ throw approvalStateError(
270
+ APPROVAL_STATE_ERROR_CODES.READ_FAILED,
271
+ "Could not read approval state",
272
+ cause,
273
+ );
274
+ }
275
+
276
+ try {
277
+ return this._validateState(JSON.parse(serialized));
278
+ } catch (cause) {
279
+ if (isKnownStateError(cause)) throw cause;
280
+ throw approvalStateError(
281
+ APPROVAL_STATE_ERROR_CODES.CORRUPT,
282
+ "Could not parse approval state",
283
+ cause,
284
+ );
285
+ }
286
+ }
287
+
288
+ _writeState(state) {
289
+ const directory = path.dirname(this.filePath);
290
+ const temporaryPath = path.join(
291
+ directory,
292
+ `.${path.basename(this.filePath)}.${process.pid}.${this._randomUUID()}.tmp`,
293
+ );
294
+ let descriptor = null;
295
+ let renamed = false;
296
+
297
+ try {
298
+ descriptor = this._fs.openSync(temporaryPath, "wx", 0o600);
299
+ this._fs.writeFileSync(
300
+ descriptor,
301
+ `${JSON.stringify(state, null, 2)}\n`,
302
+ "utf8",
303
+ );
304
+ this._fs.fsyncSync(descriptor);
305
+ this._fs.closeSync(descriptor);
306
+ descriptor = null;
307
+
308
+ if (this._beforeRename) this._beforeRename(temporaryPath, this.filePath);
309
+ this._fs.renameSync(temporaryPath, this.filePath);
310
+ renamed = true;
311
+
312
+ // Persist the directory entry where the platform supports directory fsync.
313
+ if (this._platform !== "win32") {
314
+ const directoryDescriptor = this._fs.openSync(directory, "r");
315
+ try {
316
+ this._fs.fsyncSync(directoryDescriptor);
317
+ } finally {
318
+ this._fs.closeSync(directoryDescriptor);
319
+ }
320
+ }
321
+ } catch (cause) {
322
+ if (descriptor != null) {
323
+ try {
324
+ this._fs.closeSync(descriptor);
325
+ } catch {
326
+ // Best-effort descriptor cleanup; the critical error is rethrown.
327
+ }
328
+ }
329
+ if (!renamed) {
330
+ try {
331
+ this._fs.unlinkSync(temporaryPath);
332
+ } catch {
333
+ // Best-effort orphan cleanup.
334
+ }
335
+ }
336
+ throw approvalStateError(
337
+ APPROVAL_STATE_ERROR_CODES.WRITE_FAILED,
338
+ "Could not atomically persist approval state",
339
+ cause,
340
+ );
341
+ }
342
+ }
343
+
344
+ _mutate(operation, mutator) {
345
+ this._ensureDirectory();
346
+ let outcome;
347
+ try {
348
+ outcome = this._lock(
349
+ this.filePath,
350
+ () => {
351
+ const current = this._readState();
352
+ const draft = clone(current);
353
+ const mutation = mutator(draft) || {};
354
+ if (mutation.changed === true) {
355
+ draft.generation = current.generation + 1;
356
+ draft.updatedAt = this._now();
357
+ this._validateState(draft);
358
+ this._writeState(draft);
359
+ }
360
+ return mutation;
361
+ },
362
+ {
363
+ timeoutMs: this.lockTimeoutMs,
364
+ staleMs: this.lockStaleMs,
365
+ failIfUnavailable: true,
366
+ },
367
+ );
368
+ } catch (cause) {
369
+ this._emitAudit({
370
+ event: operation,
371
+ outcome: "state-unavailable",
372
+ reason: cause?.code || "unknown",
373
+ });
374
+ if (isKnownStateError(cause)) throw cause;
375
+ if (cause?.code === "STATE_LOCK_UNAVAILABLE") {
376
+ throw approvalStateError(
377
+ APPROVAL_STATE_ERROR_CODES.LOCK_UNAVAILABLE,
378
+ "Could not acquire approval state lock",
379
+ cause,
380
+ );
381
+ }
382
+ throw approvalStateError(
383
+ APPROVAL_STATE_ERROR_CODES.WRITE_FAILED,
384
+ "Approval state mutation failed",
385
+ cause,
386
+ );
387
+ }
388
+
389
+ this._emitAudit(
390
+ outcome.audit || {
391
+ event: operation,
392
+ outcome: "allowed",
393
+ },
394
+ );
395
+ return outcome.value;
396
+ }
397
+
398
+ _expirePending(records, now) {
399
+ let changed = false;
400
+ for (const record of records) {
401
+ if (
402
+ record.status === "pending" &&
403
+ record.notAfter != null &&
404
+ now > record.notAfter
405
+ ) {
406
+ record.status = "expired";
407
+ record.revision += 1;
408
+ record.updatedAt = now;
409
+ changed = true;
410
+ }
411
+ }
412
+ return changed;
413
+ }
414
+
415
+ _makeCapacity(records) {
416
+ if (records.length < this.maxRecords) return;
417
+ const removable = records
418
+ .filter((record) => TERMINAL_STATUSES.has(record.status))
419
+ .sort(
420
+ (left, right) =>
421
+ finiteTime(left.updatedAt, 0) - finiteTime(right.updatedAt, 0),
422
+ );
423
+ const removeIds = new Set();
424
+ for (const record of removable) {
425
+ removeIds.add(record.requestId);
426
+ if (records.length - removeIds.size < this.maxRecords) break;
427
+ }
428
+ if (removeIds.size > 0) {
429
+ const retained = records.filter(
430
+ (record) => !removeIds.has(record.requestId),
431
+ );
432
+ records.splice(0, records.length, ...retained);
433
+ }
434
+ if (records.length >= this.maxRecords) {
435
+ throw approvalStateError(
436
+ APPROVAL_STATE_ERROR_CODES.BACKPRESSURE,
437
+ "Approval state has reached its pending request limit",
438
+ );
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Persist a pending approval before its card is published.
444
+ *
445
+ * `binding` must be an approvalBindingDigest. Raw arguments and descriptor
446
+ * values are never written; only operation/binding hashes and target ids are
447
+ * durable.
448
+ */
449
+ issueRequest({ requestId, descriptor = {}, binding, now: suppliedNow } = {}) {
450
+ const normalizedRequestId = requiredId(requestId, "requestId");
451
+ const descriptorInput =
452
+ descriptor && typeof descriptor === "object" ? descriptor : {};
453
+ const sessionId = requiredId(descriptorInput.session, "descriptor.session");
454
+ if (!APPROVAL_BINDING_PATTERN.test(String(binding || ""))) {
455
+ throw approvalStateError(
456
+ APPROVAL_STATE_ERROR_CODES.INVALID,
457
+ "binding must be an approval binding digest",
458
+ );
459
+ }
460
+
461
+ const now = finiteTime(suppliedNow, finiteTime(this._now()));
462
+ if (now == null) {
463
+ throw approvalStateError(
464
+ APPROVAL_STATE_ERROR_CODES.INVALID,
465
+ "A finite approval clock is required",
466
+ );
467
+ }
468
+ if (
469
+ descriptorInput.notBefore != null &&
470
+ finiteTime(descriptorInput.notBefore) == null
471
+ ) {
472
+ throw approvalStateError(
473
+ APPROVAL_STATE_ERROR_CODES.INVALID,
474
+ "Approval notBefore must be a finite timestamp",
475
+ );
476
+ }
477
+ if (
478
+ descriptorInput.notAfter != null &&
479
+ finiteTime(descriptorInput.notAfter) == null
480
+ ) {
481
+ throw approvalStateError(
482
+ APPROVAL_STATE_ERROR_CODES.INVALID,
483
+ "Approval notAfter must be a finite timestamp",
484
+ );
485
+ }
486
+ const notBefore = finiteTime(descriptorInput.notBefore, now);
487
+ const notAfter = finiteTime(
488
+ descriptorInput.notAfter,
489
+ now + this.defaultTtlMs,
490
+ );
491
+ if (notBefore == null || notAfter == null || notAfter < notBefore) {
492
+ throw approvalStateError(
493
+ APPROVAL_STATE_ERROR_CODES.INVALID,
494
+ "Approval validity window is invalid",
495
+ );
496
+ }
497
+
498
+ const normalizedDescriptor = {
499
+ ...descriptorInput,
500
+ session: sessionId,
501
+ notBefore,
502
+ notAfter,
503
+ };
504
+ const fingerprint = computeOperationFingerprint(normalizedDescriptor);
505
+ const logicalDigest = logicalDescriptorDigest(normalizedDescriptor);
506
+ const normalizedBinding = String(binding);
507
+
508
+ return this._mutate("approval.request", (state) => {
509
+ let changed = this._expirePending(state.requests, now);
510
+ const existing = state.requests.find(
511
+ (record) => record.requestId === normalizedRequestId,
512
+ );
513
+ if (existing) {
514
+ if (
515
+ existing.status === "pending" &&
516
+ existing.fingerprint === fingerprint &&
517
+ existing.binding === normalizedBinding &&
518
+ existing.sessionId === sessionId
519
+ ) {
520
+ const value = {
521
+ requestId: normalizedRequestId,
522
+ fingerprint,
523
+ shortId: shortOperationId(fingerprint),
524
+ summary: summarizeOperation(normalizedDescriptor),
525
+ revision: existing.revision,
526
+ status: existing.status,
527
+ duplicate: true,
528
+ };
529
+ return {
530
+ changed,
531
+ value,
532
+ audit: {
533
+ event: "approval.request",
534
+ outcome: "idempotent",
535
+ requestId: normalizedRequestId,
536
+ fingerprint,
537
+ sessionId,
538
+ },
539
+ };
540
+ }
541
+ throw approvalStateError(
542
+ APPROVAL_STATE_ERROR_CODES.CONFLICT,
543
+ "Approval request id is already bound to different state",
544
+ );
545
+ }
546
+
547
+ if (state.requests.some((record) => record.fingerprint === fingerprint)) {
548
+ throw approvalStateError(
549
+ APPROVAL_STATE_ERROR_CODES.CONFLICT,
550
+ "Approval fingerprint has already been issued",
551
+ );
552
+ }
553
+
554
+ for (const record of state.requests) {
555
+ if (
556
+ record.status === "pending" &&
557
+ record.logicalDigest === logicalDigest
558
+ ) {
559
+ record.status = "superseded";
560
+ record.supersededBy = normalizedRequestId;
561
+ record.revision += 1;
562
+ record.updatedAt = now;
563
+ changed = true;
564
+ }
565
+ }
566
+
567
+ this._makeCapacity(state.requests);
568
+ const record = {
569
+ requestId: normalizedRequestId,
570
+ fingerprint,
571
+ logicalDigest,
572
+ binding: normalizedBinding,
573
+ sessionId,
574
+ status: "pending",
575
+ decision: null,
576
+ revision: 1,
577
+ notBefore,
578
+ notAfter,
579
+ createdAt: now,
580
+ updatedAt: now,
581
+ resolvedAt: null,
582
+ resolvedAuthority: null,
583
+ supersededBy: null,
584
+ };
585
+ state.requests.push(record);
586
+
587
+ return {
588
+ changed: true,
589
+ value: {
590
+ requestId: normalizedRequestId,
591
+ fingerprint,
592
+ shortId: shortOperationId(fingerprint),
593
+ summary: summarizeOperation(normalizedDescriptor),
594
+ revision: record.revision,
595
+ status: record.status,
596
+ duplicate: false,
597
+ },
598
+ audit: {
599
+ event: "approval.request",
600
+ outcome: "persisted",
601
+ requestId: normalizedRequestId,
602
+ fingerprint,
603
+ sessionId,
604
+ revision: record.revision,
605
+ },
606
+ };
607
+ });
608
+ }
609
+
610
+ /**
611
+ * Atomically resolve one pending request.
612
+ *
613
+ * Returning `{ok:true, approved:true}` is the only result that authorizes the
614
+ * caller to execute a side effect. State errors throw, and all stale,
615
+ * mismatched, unauthorized, expired, or duplicate resolutions return
616
+ * `{ok:false, approved:false}`.
617
+ */
618
+ resolveRequest(
619
+ requestId,
620
+ {
621
+ fingerprint,
622
+ binding,
623
+ sessionId,
624
+ decision,
625
+ authority,
626
+ expectedRevision,
627
+ now: suppliedNow,
628
+ } = {},
629
+ ) {
630
+ const normalizedRequestId = requiredId(requestId, "requestId");
631
+ const normalizedSessionId = requiredId(sessionId, "sessionId");
632
+ if (!OPERATION_FINGERPRINT_PATTERN.test(String(fingerprint || ""))) {
633
+ return { ok: false, approved: false, reason: "fingerprint-required" };
634
+ }
635
+ if (!APPROVAL_BINDING_PATTERN.test(String(binding || ""))) {
636
+ return { ok: false, approved: false, reason: "binding-required" };
637
+ }
638
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 1) {
639
+ return { ok: false, approved: false, reason: "revision-required" };
640
+ }
641
+ if (typeof decision !== "boolean") {
642
+ return { ok: false, approved: false, reason: "decision-required" };
643
+ }
644
+ if (
645
+ !authority ||
646
+ canApprove(authority) !== true ||
647
+ authority.sessionId !== normalizedSessionId
648
+ ) {
649
+ this._emitAudit({
650
+ event: "approval.resolve",
651
+ outcome: "rejected",
652
+ reason: "authority-denied",
653
+ requestId: normalizedRequestId,
654
+ sessionId: normalizedSessionId,
655
+ });
656
+ return { ok: false, approved: false, reason: "authority-denied" };
657
+ }
658
+
659
+ const now = finiteTime(suppliedNow, finiteTime(this._now()));
660
+ if (now == null) {
661
+ return { ok: false, approved: false, reason: "clock-required" };
662
+ }
663
+
664
+ return this._mutate("approval.resolve", (state) => {
665
+ const record = state.requests.find(
666
+ (candidate) => candidate.requestId === normalizedRequestId,
667
+ );
668
+ if (!record) {
669
+ return {
670
+ changed: false,
671
+ value: { ok: false, approved: false, reason: "unknown" },
672
+ audit: {
673
+ event: "approval.resolve",
674
+ outcome: "rejected",
675
+ reason: "unknown",
676
+ requestId: normalizedRequestId,
677
+ },
678
+ };
679
+ }
680
+
681
+ const reject = (reason, changed = false) => ({
682
+ changed,
683
+ value: { ok: false, approved: false, reason },
684
+ audit: {
685
+ event: "approval.resolve",
686
+ outcome: "rejected",
687
+ reason,
688
+ requestId: normalizedRequestId,
689
+ fingerprint: record.fingerprint,
690
+ sessionId: record.sessionId,
691
+ revision: record.revision,
692
+ },
693
+ });
694
+
695
+ if (record.status === "resolved" || record.status === "rejected") {
696
+ return reject("duplicate");
697
+ }
698
+ if (record.status !== "pending") return reject(record.status);
699
+ if (record.revision !== expectedRevision) {
700
+ return reject("stale-revision");
701
+ }
702
+ if (
703
+ record.sessionId !== normalizedSessionId ||
704
+ authority.sessionId !== record.sessionId
705
+ ) {
706
+ return reject("session-mismatch");
707
+ }
708
+ if (!verifyApprovalBinding(record.fingerprint, fingerprint)) {
709
+ return reject("fingerprint-mismatch");
710
+ }
711
+ if (!verifyApprovalBinding(record.binding, binding)) {
712
+ return reject("binding-mismatch");
713
+ }
714
+ if (record.notBefore != null && now < record.notBefore) {
715
+ return reject("not-yet-valid");
716
+ }
717
+ if (record.notAfter != null && now > record.notAfter) {
718
+ record.status = "expired";
719
+ record.revision += 1;
720
+ record.updatedAt = now;
721
+ return reject("expired", true);
722
+ }
723
+
724
+ record.status = decision ? "resolved" : "rejected";
725
+ record.decision = decision;
726
+ record.resolvedAt = now;
727
+ record.updatedAt = now;
728
+ record.resolvedAuthority = describeAuthorityChain(authority);
729
+ record.revision += 1;
730
+
731
+ return {
732
+ changed: true,
733
+ value: {
734
+ ok: true,
735
+ approved: decision,
736
+ reason: null,
737
+ requestId: normalizedRequestId,
738
+ revision: record.revision,
739
+ },
740
+ audit: {
741
+ event: "approval.resolve",
742
+ outcome: decision ? "approved" : "denied",
743
+ requestId: normalizedRequestId,
744
+ fingerprint: record.fingerprint,
745
+ sessionId: record.sessionId,
746
+ revision: record.revision,
747
+ authority: record.resolvedAuthority,
748
+ },
749
+ };
750
+ });
751
+ }
752
+
753
+ /**
754
+ * Persist a timeout/close cancellation. This transition can only reduce
755
+ * authority, so it does not require an approval envelope; it still requires
756
+ * the request revision and the same locked CAS/atomic write as resolution.
757
+ */
758
+ cancelRequest(
759
+ requestId,
760
+ { expectedRevision, reason = "cancelled", now: suppliedNow } = {},
761
+ ) {
762
+ const normalizedRequestId = requiredId(requestId, "requestId");
763
+ if (!Number.isInteger(expectedRevision) || expectedRevision < 1) {
764
+ return { ok: false, approved: false, reason: "revision-required" };
765
+ }
766
+ const terminalStatus = reason === "timeout" ? "expired" : "cancelled";
767
+ const now = finiteTime(suppliedNow, finiteTime(this._now()));
768
+ if (now == null) {
769
+ return { ok: false, approved: false, reason: "clock-required" };
770
+ }
771
+
772
+ return this._mutate("approval.cancel", (state) => {
773
+ const record = state.requests.find(
774
+ (candidate) => candidate.requestId === normalizedRequestId,
775
+ );
776
+ if (!record) {
777
+ return {
778
+ changed: false,
779
+ value: { ok: false, approved: false, reason: "unknown" },
780
+ audit: {
781
+ event: "approval.cancel",
782
+ outcome: "rejected",
783
+ reason: "unknown",
784
+ requestId: normalizedRequestId,
785
+ },
786
+ };
787
+ }
788
+ if (record.status !== "pending") {
789
+ return {
790
+ changed: false,
791
+ value: {
792
+ ok: false,
793
+ approved: false,
794
+ reason:
795
+ record.status === "resolved" || record.status === "rejected"
796
+ ? "duplicate"
797
+ : record.status,
798
+ },
799
+ audit: {
800
+ event: "approval.cancel",
801
+ outcome: "rejected",
802
+ reason: record.status,
803
+ requestId: normalizedRequestId,
804
+ revision: record.revision,
805
+ },
806
+ };
807
+ }
808
+ if (record.revision !== expectedRevision) {
809
+ return {
810
+ changed: false,
811
+ value: {
812
+ ok: false,
813
+ approved: false,
814
+ reason: "stale-revision",
815
+ },
816
+ audit: {
817
+ event: "approval.cancel",
818
+ outcome: "rejected",
819
+ reason: "stale-revision",
820
+ requestId: normalizedRequestId,
821
+ revision: record.revision,
822
+ },
823
+ };
824
+ }
825
+
826
+ record.status = terminalStatus;
827
+ record.updatedAt = now;
828
+ record.revision += 1;
829
+ return {
830
+ changed: true,
831
+ value: {
832
+ ok: true,
833
+ approved: false,
834
+ reason: terminalStatus,
835
+ requestId: normalizedRequestId,
836
+ revision: record.revision,
837
+ },
838
+ audit: {
839
+ event: "approval.cancel",
840
+ outcome: terminalStatus,
841
+ requestId: normalizedRequestId,
842
+ revision: record.revision,
843
+ },
844
+ };
845
+ });
846
+ }
847
+
848
+ /**
849
+ * Advisory snapshot for UI/status surfaces. Corrupt/unavailable state returns
850
+ * an empty list by default; pass `bestEffort:false` for a strict diagnostic.
851
+ * This method must never be used to authorize a side effect.
852
+ */
853
+ listRequests({ status = null, bestEffort = true } = {}) {
854
+ try {
855
+ const records = this._readState().requests;
856
+ return records
857
+ .filter((record) => !status || record.status === status)
858
+ .map(clone);
859
+ } catch (error) {
860
+ this._emitAudit({
861
+ event: "approval.list",
862
+ outcome: "state-unavailable",
863
+ reason: error?.code || "unknown",
864
+ });
865
+ if (!bestEffort) throw error;
866
+ return [];
867
+ }
868
+ }
869
+
870
+ /**
871
+ * Advisory lookup. Use the returned revision as the CAS token echoed by an
872
+ * approval UI, but only resolveRequest performs an authorization transition.
873
+ */
874
+ getRequest(requestId, { bestEffort = true } = {}) {
875
+ const normalizedRequestId = String(requestId ?? "");
876
+ try {
877
+ const record = this._readState().requests.find(
878
+ (candidate) => candidate.requestId === normalizedRequestId,
879
+ );
880
+ return record ? clone(record) : null;
881
+ } catch (error) {
882
+ this._emitAudit({
883
+ event: "approval.get",
884
+ outcome: "state-unavailable",
885
+ reason: error?.code || "unknown",
886
+ });
887
+ if (!bestEffort) throw error;
888
+ return null;
889
+ }
890
+ }
891
+ }