chainlesschain 0.162.177 → 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 (207) 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-DT-VPWuN.js → AIOps-DRftcSue.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DyOreOVN.js → ActionButton-BFvUaMKR.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DslNsKux.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-DoaAtEcY.js → Artifacts-DAJ8B3Un.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DPG3WdJ9.js → Audit-hOk4L1lr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-C_JO6R5G.js → BackgroundAgents-3pbDpM9z.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-Cv3BooyX.js → Backup-BtziGDn9.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-D2uQXWu-.js → BaseInput-kmshphJ3.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-B4Vczyf3.js → Chat-d7tGSosA.js} +5 -5
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-p-_PBpX_.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-qTgk4VeP.js → Checkbox-C4Eh4Tz5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DlLWcIzj.js → Codegen-r7gmhZyA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-DNO73qp7.js → Col-U7mDaGlL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-vRDNpc4j.js → Community-Bpu8aH1T.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-CEWtIXni.js → Compact-CHb2XBJe.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-KgYC9Fxi.js → Compliance-Cn_r4dey.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-zj5wvkpT.js → Cowork-CdMWfDcz.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-BLDoCozU.js → Cron-BtnREHWy.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BYnff4VY.js → Crosschain-BKI9TSzr.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-C75VFsVm.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-B5dbO4Kx.js → Dropdown-DLyFVxDc.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-BDipZtHJ.js → EmailListRenderer-CtUWfYVq.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmIzt8jj.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-D9QOS-uW.js → Federation-D8lsDGus.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-B47HRwAm.js → FormItemContext-CRYt_aIw.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-3R4mXmgV.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DrNXZf1C.js → Git-BXj5Dney.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BGd7JR98.js → Governance-Cm-I8RMz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-D-2he1ed.js → Inference-CPvbhvgt.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-C0sHIbK5.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DiBQO-jL.js → Logs-BZY_3CRZ.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DL_5eV2D.js → MarkdownRenderer-up0BW6pv.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-VR59OdrC.js → Marketplace-C5MEJ2Qe.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DrPzwSd3.js → McpTools-ihdrn43I.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-CBgKALwQ.js → Memory-Br8bPMlh.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-pQ9Tn-Mn.js → MobileBridge-BNntgrYW.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-mASB6jyj.js → MobileProjects-BdV915mw.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-DTMgE7HC.js → Mtc-A3wDmlqL.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-CwTnCtiL.js → MtcAudit-CF-mBLWM.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-ngm6WU8f.js → Multisig-Iy2k_MSo.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-4YO4Qww9.js → NLProgramming-kk0W7HUb.js} +1 -1
  47. package/src/assets/web-panel/assets/Notes-B5u5t_4S.js +7 -0
  48. package/src/assets/web-panel/assets/{NotificationSettings-r2H8yPKb.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-DrO7jRWS.js → Organization-DwWQESuN.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-Cnwu4i4t.js → Overflow-DG6klrcC.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-zi-cDwA_.js → P2P-BwJcKkgZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-DBEryDv7.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
  54. package/src/assets/web-panel/assets/{Permissions-lqXK2C3x.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-Kep5KAGW.js → Pipeline-BzJfeoGt.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CHdvOAh1.js → Privacy-DLh3Bblx.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-BhKfdwe9.js → ProjectInit-USVWW36c.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Yavk9K8A.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-Bfym6SQn.js → Providers-CBPWWzpu.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-CLSQcrlR.js → QrScannerModal-QVwoRUq3.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-VPa2WKQN.js → QuickAsk-CXnR-2Vn.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-CGXR8jVU.js → Recommend-ClaNzjmF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-C_20YfMB.js → RemoteSession-C3rGw8vJ.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-awZh68UV.js → Reputation-Ci-OWAoP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Ctb4oiYN.js → Row-DRhAwXOS.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-D3sdjvUV.js → RssFeed-BQLJQ2_y.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-C-zfA4kT.js → Search-QQUpkfFd.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-D9XlmHA3.js → Security-DdXKzuKT.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-C3UKnr41.js → Services-x2Zh62Qd.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-BQxagZfF.js → Skeleton-GdY4KwRz.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-Bv4tY1Fd.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-DveENvek.js → Sla-C3QnM-11.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BFjXLNgF.js → SpeechSettings-Bl3ytvmI.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-BpHgbaP9.js → SyncSettings-Wp9IwTsP.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-CF3rIrDe.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-DEiaoyve.js → Templates-CGfvVVM9.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-ITg774nY.js → Tenant-CVXmwQ3U.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-D3ZBcvnb.js → Terminal-bvwrt03E.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BsLU6JjU.js → TimelineRenderer-D0g74KhH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Cb5Pquc9.js → Tokens-BH4IwzlT.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-Dwfy-m9N.js → Trigger-DJOpM533.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CnlSRGG0.js → Trust-B43EPRLr.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-Y4TlvKVo.js → UkeySign-BmBFG-em.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-BpKLtTTO.js → VideoEditing-n9oJwB8k.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-LLBzIq8x.js → Wallet-D5QeivHp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BeSRiusP.js → WebAuthn-DcNy7igO.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-BK_l1tXO.js → WorkflowEditor-BVjc7-5-.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-DYd-X7Wg.js → chat-Dy5Tfj3m.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-C9uip71j.js → colors-C4c2CNZU.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-JBrfjUP6.js → compact-item-DPa3O5vN.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-D03fbuiq.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-DLQOR6l-.js → hasIn-D41LVFkp.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-rIxxfdTN.js → index-0oevYjfT.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CChaISMb.js → index-388pLGJ1.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BfOs_bmg.js → index-B8nLBZbv.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-D-1SqEpQ.js → index-BPTGAjee.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BYv-LWdy.js → index-BPtrOBmW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cpn19c9k.js → index-BQiIxHBy.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BpYKLrUT.js → index-BSM1cHVK.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CS63r8h3.js → index-BYPr9fkz.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Q7mmL6w4.js → index-BsQ7Ev6i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-SO6lgMg0.js → index-Btk9qLL2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-iahNhZwm.js → index-BvmziZSG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BnQgRjsm.js → index-BvopLC_T.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ba-Ps_AJ.js → index-C7xhTVyP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BuNFCZCx.js → index-CNk_ZooP.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CL0ixCxk.js → index-CQrEHoOG.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CChQBM1d.js → index-CSxex23J.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-5YN7F5bO.js → index-CVjgm992.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CwoDpKzW.js → index-CWK4zOPe.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcBZwlnG.js → index-CX0VsoP4.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B46YcgsV.js → index-CXl0YPNG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-l08afIbV.js → index-Cip7LWml.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-6GuLRpBQ.js → index-Cq_hlWNt.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DYLZY4uh.js → index-CzZ49YZL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B0X76LjX.js → index-D2Nkivlm.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BdH9l8wl.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-D4UY5Z60.js → index-DNsBj2Ob.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Ce0Bf1YF.js → index-Di4X8cfI.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-ByHO3SqZ.js → index-DljFsVGI.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C2zmOsyD.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-BNCdRmWf.js → index-MKZDje0H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D8KvuDz0.js → index-OHAfRu_3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DaokIHt3.js → index-WEJr8PL5.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CILVyK3b.js → index-WinCp6du.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CVZWiUE_.js → index-Yjr6cqSK.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CqWk1FLG.js → index-iQ6gtFih.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMAAUVk.js → index-pt3s7yvV.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Ck17i0OD.js → index-tQ3igERQ.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-CG9z1c6w.js → initDefaultProps-BFZu87WZ.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-B1Ky7Zff.js → motion-DJIo7qvt.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-DME8X2P1.js → move-Cfr5qknL.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cuJYSWB7.js → mtc-parser-Bgc-OadH.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-UI_EE0uZ.js → omit-X3WI-TNp.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-BIoE_Z_V.js → pickAttrs-pJhU9IxK.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-B48-9HKN.js → placementArrow-BNg_Vj3T.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-D7x_GDgD.js → responsiveObserve-TeSEGQ5-.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-DjzC5_7m.js → slide-D_t_P0bv.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-C6BIymhh.js → statusUtils-DrrIMwAT.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-B4dGrdD9.js → styleChecker-BjZCuFRm.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-Bken4aNE.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-BMlqECqv.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-CKJQmxa0.js → vnode-BrbcRiHZ.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CaZunuau.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/background-session.js +5 -2
  154. package/src/commands/context.js +33 -1
  155. package/src/commands/hub.js +473 -39
  156. package/src/commands/init.js +212 -110
  157. package/src/commands/llm.js +22 -6
  158. package/src/commands/skill.js +11 -1
  159. package/src/data/changelog.json +76 -270
  160. package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
  161. package/src/harness/golden-transcript.js +3 -0
  162. package/src/index.js +18 -7
  163. package/src/lib/approval-authority-store.js +891 -0
  164. package/src/lib/background-interaction-resolver.js +399 -54
  165. package/src/lib/background-session-transport.js +3 -1
  166. package/src/lib/context-breakdown.js +82 -3
  167. package/src/lib/event-runtime-host.js +225 -0
  168. package/src/lib/event-runtime-store.js +102 -21
  169. package/src/lib/event-runtime-worker.js +70 -2
  170. package/src/lib/hooks-v2-producers.js +33 -8
  171. package/src/lib/hooks-v2-runtime.js +360 -40
  172. package/src/lib/interaction-adapter.js +66 -0
  173. package/src/lib/operation-fingerprint.js +107 -2
  174. package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
  175. package/src/lib/personal-data-hub-wiring.js +327 -52
  176. package/src/lib/process-execution-broker/credential-agent.js +954 -111
  177. package/src/lib/process-execution-broker/index.js +369 -85
  178. package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
  179. package/src/lib/provider-http-error.js +94 -0
  180. package/src/lib/remote-approval-bridge.js +222 -26
  181. package/src/lib/side-effect-ledger-store.js +65 -14
  182. package/src/lib/skill-loader.js +215 -12
  183. package/src/lib/skill-process-broker.js +15 -1
  184. package/src/lib/sub-agent-context.js +42 -0
  185. package/src/lib/turn-binding-store.js +64 -15
  186. package/src/lib/turn-binding.js +85 -16
  187. package/src/repl/agent-repl.js +40 -6
  188. package/src/repl/remote-approval.js +13 -13
  189. package/src/repl/repl-turn-binding.js +46 -16
  190. package/src/runtime/agent-core.js +286 -35
  191. package/src/runtime/coding-agent-contract-shared.cjs +16 -0
  192. package/src/runtime/headless-runner.js +147 -34
  193. package/src/runtime/headless-stream.js +147 -7
  194. package/src/workers/background-agent-worker.js +70 -38
  195. package/src/assets/web-panel/assets/AppLayout-BNJJAYME.js +0 -9
  196. package/src/assets/web-panel/assets/ChatBubbleRenderer-DE6reP99.js +0 -1
  197. package/src/assets/web-panel/assets/Dashboard-C4Gutni2.js +0 -3
  198. package/src/assets/web-panel/assets/Notes-DP1k45mF.js +0 -7
  199. package/src/assets/web-panel/assets/OrderTableRenderer-CoM_Zw0J.js +0 -1
  200. package/src/assets/web-panel/assets/PersonalDataHub-b63T3HJM.js +0 -9
  201. package/src/assets/web-panel/assets/Projects-CN8E_5Mq.js +0 -1
  202. package/src/assets/web-panel/assets/Skills-q6eb6KPZ.js +0 -1
  203. package/src/assets/web-panel/assets/Tasks-DvepRb8-.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-B8LQ8leS.js +0 -1
  205. package/src/assets/web-panel/assets/index-Bh8BPtqR.js +0 -1
  206. package/src/assets/web-panel/assets/index-CeVrHLzZ.js +0 -1
  207. package/src/assets/web-panel/assets/usePersonalDataHub-DLdrfmMa.js +0 -1
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Provider HTTP error parsing shared by agent/chat streaming and `cc llm test`.
3
+ * Keep structured provider diagnostics while redacting credential-shaped text.
4
+ */
5
+
6
+ function safePart(value) {
7
+ if (value == null) return "";
8
+ return String(value)
9
+ .replace(/\s+/g, " ")
10
+ .replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]+=*\b/gi, "$1***")
11
+ .replace(/\bsk-[A-Za-z0-9_-]{8,}\b/gi, "sk-***")
12
+ .trim()
13
+ .slice(0, 400);
14
+ }
15
+
16
+ function errorDetail(payload) {
17
+ if (payload == null) return { code: "", message: "" };
18
+ const raw =
19
+ typeof payload === "object" && payload.error != null
20
+ ? payload.error
21
+ : payload;
22
+ if (typeof raw === "string") {
23
+ return { code: "", message: safePart(raw) };
24
+ }
25
+ if (!raw || typeof raw !== "object") {
26
+ return { code: "", message: safePart(raw) };
27
+ }
28
+ return {
29
+ code: safePart(raw.code || raw.type),
30
+ message: safePart(raw.message || raw.error_description || raw.detail),
31
+ };
32
+ }
33
+
34
+ /**
35
+ * A 403 is not necessarily a bad key: providers also use it for overdue
36
+ * accounts and model-access denials. Preserve the safe code/message so clients
37
+ * can show the real corrective action.
38
+ */
39
+ export function formatProviderHttpError(provider, status, payload = null) {
40
+ const base = `${provider} API error: HTTP ${status}`;
41
+ const detail = errorDetail(payload);
42
+ const detailText = [detail.code, detail.message].filter(Boolean).join(": ");
43
+ const detailSentence = detailText.replace(/[.!?]+$/, "");
44
+ const classified = `${detail.code} ${detail.message}`.toLowerCase();
45
+ const authDetail =
46
+ /unauthori[sz]ed|authentication|invalid.*api[\s_-]*key|api[\s_-]*key.*invalid|incorrect.*api[\s_-]*key|missing.*api[\s_-]*key/.test(
47
+ classified,
48
+ );
49
+ const billingDetail =
50
+ /account.?overdue|overdue balance|insufficient.*balance|billing|payment required/.test(
51
+ classified,
52
+ );
53
+
54
+ if (status === 401 || (status === 403 && authDetail)) {
55
+ return (
56
+ `${base} — authentication failed: the API key for provider "${provider}" ` +
57
+ `is missing or invalid. Check "cc config get llm.provider" and ` +
58
+ `"cc config get llm.apiKey" (or run Configure LLM).` +
59
+ (detailSentence ? ` Provider response: ${detailSentence}.` : "")
60
+ );
61
+ }
62
+ if (status === 403 && billingDetail) {
63
+ return (
64
+ `${base} — ${detailSentence || "the provider account cannot be billed"}. ` +
65
+ "Check the provider account balance/billing status, then retry."
66
+ );
67
+ }
68
+ if (status === 403) {
69
+ return (
70
+ `${base} — access forbidden` +
71
+ (detailSentence ? `: ${detailSentence}` : "") +
72
+ ". Check the API key, model access permissions, and account status/balance."
73
+ );
74
+ }
75
+ if (status === 429) return `${base} — rate limited; please retry shortly.`;
76
+ return detailSentence ? `${base} — ${detailSentence}` : base;
77
+ }
78
+
79
+ export async function formatProviderResponseError(provider, response) {
80
+ let payload = null;
81
+ try {
82
+ const text = await response.text();
83
+ if (text) {
84
+ try {
85
+ payload = JSON.parse(text);
86
+ } catch {
87
+ payload = text;
88
+ }
89
+ }
90
+ } catch {
91
+ // A broken/empty body still retains the HTTP status diagnosis.
92
+ }
93
+ return formatProviderHttpError(provider, response.status, payload);
94
+ }
@@ -23,10 +23,12 @@
23
23
 
24
24
  import { randomBytes } from "crypto";
25
25
  import { WsRpcClient } from "./ws-rpc-client.js";
26
+ import { ORIGIN, approvalBindingDigest } from "./agent-authority.js";
26
27
  import {
27
- fingerprintsMatch,
28
- OperationApprovalRegistry,
29
- } from "./operation-fingerprint.js";
28
+ ApprovalAuthorityStore,
29
+ defaultApprovalAuthorityStatePath,
30
+ } from "./approval-authority-store.js";
31
+ import { OperationApprovalRegistry } from "./operation-fingerprint.js";
30
32
  import {
31
33
  buildDirectPairingUri,
32
34
  pickLanAddress,
@@ -46,6 +48,9 @@ export class RemoteApprovalBridge {
46
48
  decisionTimeoutMs = DEFAULT_DECISION_TIMEOUT_MS,
47
49
  createClient = null,
48
50
  now = Date.now,
51
+ approvalStore = undefined,
52
+ approvalStateFile = null,
53
+ onSecurityError = null,
49
54
  } = {}) {
50
55
  if (!wsUrl) throw new Error("wsUrl is required");
51
56
  if (!agentSessionId) throw new Error("agentSessionId is required");
@@ -63,12 +68,65 @@ export class RemoteApprovalBridge {
63
68
  this._pending = new Map(); // requestId → { resolve, timer, fingerprint }
64
69
  this._counter = 0;
65
70
  this._closed = false;
71
+ this._securityErrors = [];
72
+ this._onSecurityError =
73
+ typeof onSecurityError === "function" ? onSecurityError : null;
74
+ const durableStore =
75
+ approvalStore === undefined
76
+ ? new ApprovalAuthorityStore({
77
+ filePath:
78
+ approvalStateFile ||
79
+ defaultApprovalAuthorityStatePath(agentSessionId),
80
+ now: () => this._now(),
81
+ })
82
+ : approvalStore;
83
+ if (
84
+ !durableStore ||
85
+ typeof durableStore.issueRequest !== "function" ||
86
+ typeof durableStore.resolveRequest !== "function" ||
87
+ typeof durableStore.cancelRequest !== "function"
88
+ ) {
89
+ throw new Error(
90
+ "approvalStore must implement issueRequest/resolveRequest/cancelRequest",
91
+ );
92
+ }
66
93
  // §8.2 cross-device approval registry: full-tuple operation fingerprints,
67
94
  // single-winner across concurrent cards for one logical operation, at-most-
68
95
  // once resolution, and validity-window enforcement — all fail-closed.
69
96
  this._registry = new OperationApprovalRegistry({
70
97
  clock: () => this._now(),
98
+ store: durableStore,
71
99
  });
100
+ this._approvalStore = durableStore;
101
+ }
102
+
103
+ _recordSecurityError({ action, requestId = null, reason, errorCode = null }) {
104
+ const entry = {
105
+ timestamp: this._now(),
106
+ action: String(action || "approval"),
107
+ requestId: requestId ? String(requestId) : null,
108
+ reason: String(reason || "rejected"),
109
+ errorCode: errorCode ? String(errorCode) : null,
110
+ };
111
+ this._securityErrors.push(entry);
112
+ if (this._securityErrors.length > 1000) this._securityErrors.shift();
113
+ if (this._onSecurityError) {
114
+ try {
115
+ this._onSecurityError({ ...entry });
116
+ } catch {
117
+ // Observability is advisory; the approval remains denied.
118
+ }
119
+ }
120
+ return entry;
121
+ }
122
+
123
+ getSecurityErrors(limit = this._securityErrors.length) {
124
+ const count = Math.max(
125
+ 0,
126
+ Math.min(Math.floor(Number(limit) || 0), this._securityErrors.length),
127
+ );
128
+ if (count === 0) return [];
129
+ return this._securityErrors.slice(-count).map((entry) => ({ ...entry }));
72
130
  }
73
131
 
74
132
  /** Connect, register the client-hosted remote session, start listening. */
@@ -123,34 +181,76 @@ export class RemoteApprovalBridge {
123
181
  const requestId = message.event.requestId || message.event.approvalId;
124
182
  const pending = requestId ? this._pending.get(requestId) : null;
125
183
  if (!pending) return;
126
- // Confused-deputy guard: a resolve that echoes a fingerprint must match the
127
- // operation this ask published. A mismatch (stale card / replayed / swapped
128
- // body / different session·workspace·env·policy) is rejected — the ask stays
129
- // pending and fails closed on timeout. An absent fingerprint is a legacy
130
- // device: resolved via this ask's own fingerprint (still window-guarded).
131
- if (
132
- message.event.fingerprint != null &&
133
- !fingerprintsMatch(pending.fingerprint, message.event.fingerprint)
134
- ) {
184
+ // The device must echo the complete durable capability tuple. Missing,
185
+ // mismatched, stale, duplicate, or expired resolutions never settle an
186
+ // approval; only the store's successful CAS can return approved=true.
187
+ const rawAnswer = message.event.answer ?? message.event.approved;
188
+ const hasDecision =
189
+ rawAnswer === true ||
190
+ rawAnswer === false ||
191
+ rawAnswer === "true" ||
192
+ rawAnswer === "false" ||
193
+ rawAnswer === "yes" ||
194
+ rawAnswer === "no";
195
+ if (!hasDecision) {
196
+ this._recordSecurityError({
197
+ action: "approval.resolve",
198
+ requestId,
199
+ reason: "decision-required",
200
+ });
135
201
  return;
136
202
  }
137
- // Fail-closed §8.2 enforcement: single-winner (superseded card), at-most-once
138
- // (duplicate), and validity window (not-yet-valid / expired). A rejected
139
- // verdict leaves the ask pending so it still fails closed on timeout.
203
+ const decision =
204
+ rawAnswer === true || rawAnswer === "true" || rawAnswer === "yes";
205
+ const authority = {
206
+ origin: ORIGIN.REMOTE,
207
+ authenticated: true,
208
+ scopes: ["approve"],
209
+ principalId: message.from || "paired-device",
210
+ sessionId: this.agentSessionId,
211
+ };
140
212
  const verdict = this._registry.resolve(pending.fingerprint, {
213
+ requestId,
214
+ fingerprint: message.event.fingerprint,
215
+ binding: message.event.binding,
216
+ sessionId: this.agentSessionId,
217
+ decision,
218
+ authority,
219
+ expectedRevision: message.event.revision,
141
220
  now: this._now(),
142
221
  });
143
- if (!verdict.ok) return;
222
+ if (!verdict.ok) {
223
+ this._recordSecurityError({
224
+ action: "approval.resolve",
225
+ requestId,
226
+ reason: verdict.reason,
227
+ errorCode: verdict.errorCode,
228
+ });
229
+ if (verdict.reason === "state-unavailable") {
230
+ this._settleDeniedForStateFailure(requestId, pending, verdict);
231
+ }
232
+ return;
233
+ }
144
234
  this._pending.delete(requestId);
145
235
  clearTimeout(pending.timer);
146
- const answer = message.event.answer ?? message.event.approved;
147
236
  pending.resolve({
148
- approved: answer === true || answer === "true" || answer === "yes",
237
+ approved: verdict.ok === true && verdict.approved === true,
149
238
  via: "remote",
150
239
  from: message.from || null,
151
240
  });
152
241
  }
153
242
 
243
+ _settleDeniedForStateFailure(requestId, pending, verdict) {
244
+ this._pending.delete(requestId);
245
+ clearTimeout(pending.timer);
246
+ pending.resolve({
247
+ approved: false,
248
+ via: "state-error",
249
+ from: null,
250
+ errorCode: verdict?.errorCode || null,
251
+ });
252
+ }
253
+
154
254
  /** Number of devices that can approve (excludes this host). */
155
255
  async approverCount() {
156
256
  try {
@@ -202,7 +302,42 @@ export class RemoteApprovalBridge {
202
302
  ? askedAt + effectiveTimeout
203
303
  : null,
204
304
  };
205
- const card = this._registry.issue(desc);
305
+ const binding = approvalBindingDigest({
306
+ toolCallId: requestId,
307
+ args: {
308
+ tool: tool || null,
309
+ action,
310
+ detail,
311
+ workspace,
312
+ session: desc.session,
313
+ targetEnv,
314
+ },
315
+ policyDigest: policyVersion,
316
+ });
317
+ let card;
318
+ try {
319
+ // Persist first. A card that was not durably issued must never become
320
+ // visible to a device and can therefore never authorize a side effect.
321
+ card = this._registry.issue(desc, {
322
+ requestId,
323
+ binding,
324
+ now: askedAt,
325
+ });
326
+ } catch (error) {
327
+ const errorCode = error?.code || "CC_APPROVAL_STATE_UNKNOWN";
328
+ this._recordSecurityError({
329
+ action: "approval.request",
330
+ requestId,
331
+ reason: "state-unavailable",
332
+ errorCode,
333
+ });
334
+ return Promise.resolve({
335
+ approved: false,
336
+ via: "state-error",
337
+ from: null,
338
+ errorCode,
339
+ });
340
+ }
206
341
  if (onRequestId) {
207
342
  try {
208
343
  onRequestId(requestId);
@@ -212,6 +347,20 @@ export class RemoteApprovalBridge {
212
347
  }
213
348
  return new Promise((resolve) => {
214
349
  const timer = setTimeout(() => {
350
+ const verdict = this._registry.cancel(card.fingerprint, {
351
+ requestId,
352
+ expectedRevision: card.revision,
353
+ reason: "timeout",
354
+ now: this._now(),
355
+ });
356
+ if (!verdict.ok) {
357
+ this._recordSecurityError({
358
+ action: "approval.cancel",
359
+ requestId,
360
+ reason: verdict.reason,
361
+ errorCode: verdict.errorCode,
362
+ });
363
+ }
215
364
  this._pending.delete(requestId);
216
365
  this._publish({
217
366
  type: "permission.resolved",
@@ -225,6 +374,8 @@ export class RemoteApprovalBridge {
225
374
  this._pending.set(requestId, {
226
375
  timer,
227
376
  fingerprint: card.fingerprint,
377
+ binding,
378
+ revision: card.revision,
228
379
  resolve: (decision) => {
229
380
  this._publish({
230
381
  type: "permission.resolved",
@@ -244,6 +395,8 @@ export class RemoteApprovalBridge {
244
395
  // Full fingerprint (protocol) + short id / secret-free summary (what the
245
396
  // operator eyeballs on the device to confirm it is the SAME card).
246
397
  fingerprint: card.fingerprint,
398
+ binding,
399
+ revision: card.revision,
247
400
  shortId: card.shortId,
248
401
  summary: card.summary,
249
402
  notBefore: desc.notBefore,
@@ -257,12 +410,39 @@ export class RemoteApprovalBridge {
257
410
  resolveLocally(requestId, approved) {
258
411
  const pending = this._pending.get(requestId);
259
412
  if (!pending) return false;
413
+ const verdict = this._registry.resolve(pending.fingerprint, {
414
+ requestId,
415
+ fingerprint: pending.fingerprint,
416
+ binding: pending.binding,
417
+ sessionId: this.agentSessionId,
418
+ decision: approved === true,
419
+ authority: {
420
+ origin: ORIGIN.USER,
421
+ principalId: "local-terminal",
422
+ sessionId: this.agentSessionId,
423
+ },
424
+ expectedRevision: pending.revision,
425
+ now: this._now(),
426
+ });
427
+ if (!verdict.ok) {
428
+ this._recordSecurityError({
429
+ action: "approval.resolve",
430
+ requestId,
431
+ reason: verdict.reason,
432
+ errorCode: verdict.errorCode,
433
+ });
434
+ if (verdict.reason === "state-unavailable") {
435
+ this._settleDeniedForStateFailure(requestId, pending, verdict);
436
+ }
437
+ return false;
438
+ }
260
439
  this._pending.delete(requestId);
261
440
  clearTimeout(pending.timer);
262
- // Consume the registry card so a late remote resolve for the SAME operation
263
- // is a `duplicate`, never a second settle.
264
- this._registry.resolve(pending.fingerprint, { now: this._now() });
265
- pending.resolve({ approved, via: "local", from: null });
441
+ pending.resolve({
442
+ approved: verdict.ok === true && verdict.approved === true,
443
+ via: "local",
444
+ from: null,
445
+ });
266
446
  return true;
267
447
  }
268
448
 
@@ -327,10 +507,12 @@ export class RemoteApprovalBridge {
327
507
  const decision = await Promise.race([remote, local]);
328
508
  // Local answer won → settle the remote ask too (publishes
329
509
  // permission.resolved so device UIs clear the pending card).
330
- if (decision.via === "local" && requestId) {
331
- this.resolveLocally(requestId, decision.approved);
510
+ if (decision.via === "local") {
511
+ if (!requestId) return false;
512
+ const persisted = this.resolveLocally(requestId, decision.approved);
513
+ return persisted && decision.approved === true;
332
514
  }
333
- return decision.approved;
515
+ return decision.approved === true;
334
516
  };
335
517
  }
336
518
 
@@ -338,6 +520,20 @@ export class RemoteApprovalBridge {
338
520
  this._closed = true;
339
521
  for (const [requestId, pending] of this._pending) {
340
522
  clearTimeout(pending.timer);
523
+ const verdict = this._registry.cancel(pending.fingerprint, {
524
+ requestId,
525
+ expectedRevision: pending.revision,
526
+ reason: "closed",
527
+ now: this._now(),
528
+ });
529
+ if (!verdict.ok) {
530
+ this._recordSecurityError({
531
+ action: "approval.cancel",
532
+ requestId,
533
+ reason: verdict.reason,
534
+ errorCode: verdict.errorCode,
535
+ });
536
+ }
341
537
  pending.resolve({ approved: false, via: "closed", from: null });
342
538
  this._pending.delete(requestId);
343
539
  }
@@ -26,41 +26,82 @@ export const _deps = {
26
26
  readEvents: storeReadEvents,
27
27
  };
28
28
 
29
+ export class SideEffectLedgerPersistenceError extends Error {
30
+ constructor(operation, sessionId, cause) {
31
+ const detail = cause?.message || String(cause || "unknown persistence error");
32
+ super(`Side-effect ledger ${operation} failed for ${sessionId}: ${detail}`, {
33
+ cause,
34
+ });
35
+ this.name = "SideEffectLedgerPersistenceError";
36
+ this.code =
37
+ operation === "read"
38
+ ? "SIDE_EFFECT_LEDGER_READ_FAILED"
39
+ : "SIDE_EFFECT_LEDGER_PERSIST_FAILED";
40
+ this.operation = operation;
41
+ this.sessionId = sessionId;
42
+ }
43
+ }
44
+
45
+ function persistenceFailure(operation, sessionId, cause, failIfUnavailable) {
46
+ if (!failIfUnavailable) return false;
47
+ throw new SideEffectLedgerPersistenceError(operation, sessionId, cause);
48
+ }
49
+
29
50
  /**
30
- * Append the current ledger as a chained event. Best-effort — a persistence
31
- * failure must not crash the operation it is guarding.
51
+ * Append the current ledger as a chained event. This is Critical state:
52
+ * persistence failures reject by default so the guarded operation cannot run.
53
+ * Diagnostic callers may explicitly request advisory best-effort behavior.
32
54
  *
33
55
  * @param {string} sessionId
34
56
  * @param {SideEffectLedger|object} ledger
57
+ * @param {{failIfUnavailable?:boolean}} [opts]
35
58
  * @returns {boolean}
36
59
  */
37
- export function persistSideEffectLedger(sessionId, ledger) {
38
- if (!sessionId || !ledger) return false;
39
- const snapshot =
40
- ledger instanceof SideEffectLedger ? ledger.toJSON() : ledger;
41
- if (!snapshot || !Array.isArray(snapshot.ops)) return false;
60
+ export function persistSideEffectLedger(sessionId, ledger, opts = {}) {
61
+ const failIfUnavailable = opts.failIfUnavailable !== false;
62
+ if (!sessionId || !ledger) {
63
+ return persistenceFailure(
64
+ "write",
65
+ sessionId || "<missing-session>",
66
+ new TypeError("sessionId and ledger are required"),
67
+ failIfUnavailable,
68
+ );
69
+ }
70
+
42
71
  try {
72
+ const snapshot =
73
+ ledger instanceof SideEffectLedger ? ledger.toJSON() : ledger;
74
+ if (!snapshot || !Array.isArray(snapshot.ops)) {
75
+ throw new TypeError("ledger snapshot must contain an ops array");
76
+ }
43
77
  _deps.appendEvent(sessionId, SIDE_EFFECT_LEDGER_EVENT, snapshot);
44
78
  return true;
45
- } catch {
46
- return false;
79
+ } catch (error) {
80
+ return persistenceFailure("write", sessionId, error, failIfUnavailable);
47
81
  }
48
82
  }
49
83
 
50
84
  /**
51
85
  * Rebuild the ledger from the LATEST persisted snapshot, or an empty ledger when
52
- * the session never persisted one (or on read error).
86
+ * the session never persisted one. Read/corruption errors reject by default.
53
87
  *
54
88
  * @param {string} sessionId
55
- * @param {{clock?:Function}} [opts]
89
+ * @param {{clock?:Function,failIfUnavailable?:boolean}} [opts]
56
90
  * @returns {SideEffectLedger}
57
91
  */
58
92
  export function loadSideEffectLedger(sessionId, opts = {}) {
93
+ const failIfUnavailable = opts.failIfUnavailable !== false;
59
94
  let events = [];
60
95
  try {
61
96
  events = _deps.readEvents(sessionId) || [];
62
- } catch {
63
- return new SideEffectLedger(opts);
97
+ } catch (error) {
98
+ const fallback = persistenceFailure(
99
+ "read",
100
+ sessionId || "<missing-session>",
101
+ error,
102
+ failIfUnavailable,
103
+ );
104
+ if (fallback === false) return new SideEffectLedger(opts);
64
105
  }
65
106
  for (let i = events.length - 1; i >= 0; i--) {
66
107
  const e = events[i];
@@ -70,7 +111,17 @@ export function loadSideEffectLedger(sessionId, opts = {}) {
70
111
  e.data &&
71
112
  Array.isArray(e.data.ops)
72
113
  ) {
73
- return SideEffectLedger.fromJSON(e.data, opts);
114
+ try {
115
+ return SideEffectLedger.fromJSON(e.data, opts);
116
+ } catch (error) {
117
+ const fallback = persistenceFailure(
118
+ "read",
119
+ sessionId || "<missing-session>",
120
+ error,
121
+ failIfUnavailable,
122
+ );
123
+ if (fallback === false) return new SideEffectLedger(opts);
124
+ }
74
125
  }
75
126
  }
76
127
  return new SideEffectLedger(opts);