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.
- package/README.md +10 -0
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DT-VPWuN.js → AIOps-DRftcSue.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DyOreOVN.js → ActionButton-BFvUaMKR.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DslNsKux.js → Analytics-C0-Rru4P.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-bx-BCljj.js +9 -0
- package/src/assets/web-panel/assets/{Artifacts-DoaAtEcY.js → Artifacts-DAJ8B3Un.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DPG3WdJ9.js → Audit-hOk4L1lr.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C_JO6R5G.js → BackgroundAgents-3pbDpM9z.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cv3BooyX.js → Backup-BtziGDn9.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D2uQXWu-.js → BaseInput-kmshphJ3.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-B4Vczyf3.js → Chat-d7tGSosA.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-p-_PBpX_.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-qTgk4VeP.js → Checkbox-C4Eh4Tz5.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlLWcIzj.js → Codegen-r7gmhZyA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DNO73qp7.js → Col-U7mDaGlL.js} +1 -1
- package/src/assets/web-panel/assets/{Community-vRDNpc4j.js → Community-Bpu8aH1T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CEWtIXni.js → Compact-CHb2XBJe.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-KgYC9Fxi.js → Compliance-Cn_r4dey.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-zj5wvkpT.js → Cowork-CdMWfDcz.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BLDoCozU.js → Cron-BtnREHWy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BYnff4VY.js → Crosschain-BKI9TSzr.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C75VFsVm.js → DID-pkNubzzJ.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-1rrz1snN.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-B5dbO4Kx.js → Dropdown-DLyFVxDc.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BDipZtHJ.js → EmailListRenderer-CtUWfYVq.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmIzt8jj.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9QOS-uW.js → Federation-D8lsDGus.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B47HRwAm.js → FormItemContext-CRYt_aIw.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-3R4mXmgV.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DrNXZf1C.js → Git-BXj5Dney.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BGd7JR98.js → Governance-Cm-I8RMz.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-D-2he1ed.js → Inference-CPvbhvgt.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C0sHIbK5.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DiBQO-jL.js → Logs-BZY_3CRZ.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DL_5eV2D.js → MarkdownRenderer-up0BW6pv.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-VR59OdrC.js → Marketplace-C5MEJ2Qe.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DrPzwSd3.js → McpTools-ihdrn43I.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CBgKALwQ.js → Memory-Br8bPMlh.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-pQ9Tn-Mn.js → MobileBridge-BNntgrYW.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-mASB6jyj.js → MobileProjects-BdV915mw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DTMgE7HC.js → Mtc-A3wDmlqL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CwTnCtiL.js → MtcAudit-CF-mBLWM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-ngm6WU8f.js → Multisig-Iy2k_MSo.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-4YO4Qww9.js → NLProgramming-kk0W7HUb.js} +1 -1
- package/src/assets/web-panel/assets/Notes-B5u5t_4S.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-r2H8yPKb.js → NotificationSettings-BN0POf6_.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CHkSUrXB.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DrO7jRWS.js → Organization-DwWQESuN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cnwu4i4t.js → Overflow-DG6klrcC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-zi-cDwA_.js → P2P-BwJcKkgZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DBEryDv7.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-lqXK2C3x.js → Permissions-CGMXe4TX.js} +4 -4
- package/src/assets/web-panel/assets/PersonalDataHub-ziYqg7-A.js +8 -0
- package/src/assets/web-panel/assets/{Pipeline-Kep5KAGW.js → Pipeline-BzJfeoGt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CHdvOAh1.js → Privacy-DLh3Bblx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BhKfdwe9.js → ProjectInit-USVWW36c.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Yavk9K8A.js → ProjectSettings-CiiOn62h.js} +2 -2
- package/src/assets/web-panel/assets/Projects-6V-PNmcA.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bfym6SQn.js → Providers-CBPWWzpu.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CLSQcrlR.js → QrScannerModal-QVwoRUq3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-VPa2WKQN.js → QuickAsk-CXnR-2Vn.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CGXR8jVU.js → Recommend-ClaNzjmF.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C_20YfMB.js → RemoteSession-C3rGw8vJ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-awZh68UV.js → Reputation-Ci-OWAoP.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Ctb4oiYN.js → Row-DRhAwXOS.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D3sdjvUV.js → RssFeed-BQLJQ2_y.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C-zfA4kT.js → Search-QQUpkfFd.js} +1 -1
- package/src/assets/web-panel/assets/{Security-D9XlmHA3.js → Security-DdXKzuKT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C3UKnr41.js → Services-x2Zh62Qd.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BQxagZfF.js → Skeleton-GdY4KwRz.js} +1 -1
- package/src/assets/web-panel/assets/Skills-Bv4tY1Fd.js +1 -0
- package/src/assets/web-panel/assets/{Sla-DveENvek.js → Sla-C3QnM-11.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BFjXLNgF.js → SpeechSettings-Bl3ytvmI.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BpHgbaP9.js → SyncSettings-Wp9IwTsP.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-CF3rIrDe.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DEiaoyve.js → Templates-CGfvVVM9.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-ITg774nY.js → Tenant-CVXmwQ3U.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D3ZBcvnb.js → Terminal-bvwrt03E.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BsLU6JjU.js → TimelineRenderer-D0g74KhH.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Cb5Pquc9.js → Tokens-BH4IwzlT.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dwfy-m9N.js → Trigger-DJOpM533.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CnlSRGG0.js → Trust-B43EPRLr.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Y4TlvKVo.js → UkeySign-BmBFG-em.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BpKLtTTO.js → VideoEditing-n9oJwB8k.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-LLBzIq8x.js → Wallet-D5QeivHp.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BeSRiusP.js → WebAuthn-DcNy7igO.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BK_l1tXO.js → WorkflowEditor-BVjc7-5-.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DYd-X7Wg.js → chat-Dy5Tfj3m.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C9uip71j.js → colors-C4c2CNZU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-JBrfjUP6.js → compact-item-DPa3O5vN.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D03fbuiq.js → createContext-TLcfNA6_.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BAwdDA_5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQOR6l-.js → hasIn-D41LVFkp.js} +1 -1
- package/src/assets/web-panel/assets/{index-rIxxfdTN.js → index-0oevYjfT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CChaISMb.js → index-388pLGJ1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfOs_bmg.js → index-B8nLBZbv.js} +3 -3
- package/src/assets/web-panel/assets/{index-D-1SqEpQ.js → index-BPTGAjee.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYv-LWdy.js → index-BPtrOBmW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpn19c9k.js → index-BQiIxHBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpYKLrUT.js → index-BSM1cHVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CS63r8h3.js → index-BYPr9fkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q7mmL6w4.js → index-BsQ7Ev6i.js} +1 -1
- package/src/assets/web-panel/assets/{index-SO6lgMg0.js → index-Btk9qLL2.js} +1 -1
- package/src/assets/web-panel/assets/{index-iahNhZwm.js → index-BvmziZSG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnQgRjsm.js → index-BvopLC_T.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ba-Ps_AJ.js → index-C7xhTVyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuNFCZCx.js → index-CNk_ZooP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CL0ixCxk.js → index-CQrEHoOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CChQBM1d.js → index-CSxex23J.js} +1 -1
- package/src/assets/web-panel/assets/{index-5YN7F5bO.js → index-CVjgm992.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwoDpKzW.js → index-CWK4zOPe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcBZwlnG.js → index-CX0VsoP4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B46YcgsV.js → index-CXl0YPNG.js} +1 -1
- package/src/assets/web-panel/assets/{index-l08afIbV.js → index-Cip7LWml.js} +1 -1
- package/src/assets/web-panel/assets/{index-6GuLRpBQ.js → index-Cq_hlWNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYLZY4uh.js → index-CzZ49YZL.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0X76LjX.js → index-D2Nkivlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdH9l8wl.js → index-DBUgRs4D.js} +1 -1
- package/src/assets/web-panel/assets/index-DEjVkdzq.js +1 -0
- package/src/assets/web-panel/assets/{index-D4UY5Z60.js → index-DNsBj2Ob.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce0Bf1YF.js → index-Di4X8cfI.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByHO3SqZ.js → index-DljFsVGI.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2zmOsyD.js → index-Korc3QIe.js} +1 -1
- package/src/assets/web-panel/assets/index-LMOc5xrS.js +1 -0
- package/src/assets/web-panel/assets/{index-BNCdRmWf.js → index-MKZDje0H.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8KvuDz0.js → index-OHAfRu_3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaokIHt3.js → index-WEJr8PL5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CILVyK3b.js → index-WinCp6du.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVZWiUE_.js → index-Yjr6cqSK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqWk1FLG.js → index-iQ6gtFih.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNMAAUVk.js → index-pt3s7yvV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck17i0OD.js → index-tQ3igERQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CG9z1c6w.js → initDefaultProps-BFZu87WZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B1Ky7Zff.js → motion-DJIo7qvt.js} +1 -1
- package/src/assets/web-panel/assets/{move-DME8X2P1.js → move-Cfr5qknL.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cuJYSWB7.js → mtc-parser-Bgc-OadH.js} +1 -1
- package/src/assets/web-panel/assets/{omit-UI_EE0uZ.js → omit-X3WI-TNp.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BIoE_Z_V.js → pickAttrs-pJhU9IxK.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B48-9HKN.js → placementArrow-BNg_Vj3T.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D7x_GDgD.js → responsiveObserve-TeSEGQ5-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DjzC5_7m.js → slide-D_t_P0bv.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C6BIymhh.js → statusUtils-DrrIMwAT.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4dGrdD9.js → styleChecker-BjZCuFRm.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Bken4aNE.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BMlqECqv.js → useFs-BidCpfLX.js} +1 -1
- package/src/assets/web-panel/assets/usePersonalDataHub-C5mHttA9.js +1 -0
- package/src/assets/web-panel/assets/{vnode-CKJQmxa0.js → vnode-BrbcRiHZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CaZunuau.js → zoom-PISX7AkR.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/__tests__/hub-wechat.test.js +45 -0
- package/src/commands/background-session.js +5 -2
- package/src/commands/context.js +33 -1
- package/src/commands/hub.js +473 -39
- package/src/commands/init.js +212 -110
- package/src/commands/llm.js +22 -6
- package/src/commands/skill.js +11 -1
- package/src/data/changelog.json +76 -270
- package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
- package/src/harness/golden-transcript.js +3 -0
- package/src/index.js +18 -7
- package/src/lib/approval-authority-store.js +891 -0
- package/src/lib/background-interaction-resolver.js +399 -54
- package/src/lib/background-session-transport.js +3 -1
- package/src/lib/context-breakdown.js +82 -3
- package/src/lib/event-runtime-host.js +225 -0
- package/src/lib/event-runtime-store.js +102 -21
- package/src/lib/event-runtime-worker.js +70 -2
- package/src/lib/hooks-v2-producers.js +33 -8
- package/src/lib/hooks-v2-runtime.js +360 -40
- package/src/lib/interaction-adapter.js +66 -0
- package/src/lib/operation-fingerprint.js +107 -2
- package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
- package/src/lib/personal-data-hub-wiring.js +327 -52
- package/src/lib/process-execution-broker/credential-agent.js +954 -111
- package/src/lib/process-execution-broker/index.js +369 -85
- package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
- package/src/lib/provider-http-error.js +94 -0
- package/src/lib/remote-approval-bridge.js +222 -26
- package/src/lib/side-effect-ledger-store.js +65 -14
- package/src/lib/skill-loader.js +215 -12
- package/src/lib/skill-process-broker.js +15 -1
- package/src/lib/sub-agent-context.js +42 -0
- package/src/lib/turn-binding-store.js +64 -15
- package/src/lib/turn-binding.js +85 -16
- package/src/repl/agent-repl.js +40 -6
- package/src/repl/remote-approval.js +13 -13
- package/src/repl/repl-turn-binding.js +46 -16
- package/src/runtime/agent-core.js +286 -35
- package/src/runtime/coding-agent-contract-shared.cjs +16 -0
- package/src/runtime/headless-runner.js +147 -34
- package/src/runtime/headless-stream.js +147 -7
- package/src/workers/background-agent-worker.js +70 -38
- package/src/assets/web-panel/assets/AppLayout-BNJJAYME.js +0 -9
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DE6reP99.js +0 -1
- package/src/assets/web-panel/assets/Dashboard-C4Gutni2.js +0 -3
- package/src/assets/web-panel/assets/Notes-DP1k45mF.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-CoM_Zw0J.js +0 -1
- package/src/assets/web-panel/assets/PersonalDataHub-b63T3HJM.js +0 -9
- package/src/assets/web-panel/assets/Projects-CN8E_5Mq.js +0 -1
- package/src/assets/web-panel/assets/Skills-q6eb6KPZ.js +0 -1
- package/src/assets/web-panel/assets/Tasks-DvepRb8-.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B8LQ8leS.js +0 -1
- package/src/assets/web-panel/assets/index-Bh8BPtqR.js +0 -1
- package/src/assets/web-panel/assets/index-CeVrHLzZ.js +0 -1
- 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
|
-
|
|
28
|
-
|
|
29
|
-
} from "./
|
|
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
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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)
|
|
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:
|
|
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
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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"
|
|
331
|
-
|
|
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.
|
|
31
|
-
*
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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);
|