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
package/src/lib/skill-loader.js
CHANGED
|
@@ -31,6 +31,7 @@ import { parseSkillMcpServers } from "./skill-mcp.js";
|
|
|
31
31
|
import { normalizeSkillPaths } from "./skill-path-scope.js";
|
|
32
32
|
import { discoverPluginSkillLayers } from "./plugin-runtime/skills.js";
|
|
33
33
|
import settingsLoader from "./settings-loader.cjs";
|
|
34
|
+
import contextSourceLedger from "./context-source-ledger.js";
|
|
34
35
|
|
|
35
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
36
37
|
|
|
@@ -218,6 +219,40 @@ export function parseSkillMd(content) {
|
|
|
218
219
|
return { data, body };
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Read only enough of SKILL.md to parse its YAML frontmatter. The markdown
|
|
224
|
+
* body is deliberately not retained during discovery; it is materialized by
|
|
225
|
+
* CLISkillLoader.materializeSkill() only when a persona or run_skill needs it.
|
|
226
|
+
*/
|
|
227
|
+
function readSkillFrontmatter(skillMd, maxBytes = 256 * 1024) {
|
|
228
|
+
const fd = fs.openSync(skillMd, "r");
|
|
229
|
+
const chunks = [];
|
|
230
|
+
let total = 0;
|
|
231
|
+
try {
|
|
232
|
+
while (total < maxBytes) {
|
|
233
|
+
const chunk = Buffer.allocUnsafe(Math.min(8192, maxBytes - total));
|
|
234
|
+
const read = fs.readSync(fd, chunk, 0, chunk.length, total);
|
|
235
|
+
if (read <= 0) break;
|
|
236
|
+
chunks.push(chunk.subarray(0, read));
|
|
237
|
+
total += read;
|
|
238
|
+
const prefix = Buffer.concat(chunks).toString("utf-8").replace(/^\uFEFF/, "");
|
|
239
|
+
const firstLineEnd = prefix.search(/\r?\n/);
|
|
240
|
+
if (firstLineEnd >= 0 && prefix.slice(0, firstLineEnd).trim() !== "---") {
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
const match = prefix.match(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/);
|
|
244
|
+
if (match) return parseSkillMd(match[0]).data;
|
|
245
|
+
}
|
|
246
|
+
return {};
|
|
247
|
+
} finally {
|
|
248
|
+
fs.closeSync(fd);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function estimateSkillTokens(text) {
|
|
253
|
+
return Math.ceil(String(text || "").length / 4);
|
|
254
|
+
}
|
|
255
|
+
|
|
221
256
|
/**
|
|
222
257
|
* Substitute $ARGUMENTS / $1 / $2 / ... placeholders in a skill body.
|
|
223
258
|
* Inspired by open-agents substituteArguments.
|
|
@@ -290,8 +325,17 @@ export function prepareSkillBody(skill, args) {
|
|
|
290
325
|
* Multi-layer CLI skill loader
|
|
291
326
|
*/
|
|
292
327
|
export class CLISkillLoader {
|
|
293
|
-
constructor() {
|
|
328
|
+
constructor({ contextLedger = contextSourceLedger } = {}) {
|
|
294
329
|
this._cache = null;
|
|
330
|
+
this._bodyCache = new Map();
|
|
331
|
+
this._contextLedger = contextLedger;
|
|
332
|
+
this._cacheStats = {
|
|
333
|
+
descriptorScans: 0,
|
|
334
|
+
descriptorCacheHits: 0,
|
|
335
|
+
bodyCacheHits: 0,
|
|
336
|
+
bodyCacheMisses: 0,
|
|
337
|
+
bodyLoads: new Map(),
|
|
338
|
+
};
|
|
295
339
|
}
|
|
296
340
|
|
|
297
341
|
/**
|
|
@@ -449,8 +493,8 @@ export class CLISkillLoader {
|
|
|
449
493
|
continue;
|
|
450
494
|
}
|
|
451
495
|
try {
|
|
452
|
-
const
|
|
453
|
-
const
|
|
496
|
+
const data = readSkillFrontmatter(skillMd);
|
|
497
|
+
const stat = fs.statSync(skillMd);
|
|
454
498
|
|
|
455
499
|
out.push({
|
|
456
500
|
id: data.name || entry.name,
|
|
@@ -474,12 +518,17 @@ export class CLISkillLoader {
|
|
|
474
518
|
paths: normalizeSkillPaths(data),
|
|
475
519
|
dirName: entry.name,
|
|
476
520
|
hasHandler: fs.existsSync(path.join(skillDir, "handler.js")),
|
|
477
|
-
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
521
|
+
isolation: data.isolation === true,
|
|
522
|
+
// Two-tier loading: discovery retains only the descriptor. The body
|
|
523
|
+
// and embedded MCP declarations are populated on first use.
|
|
524
|
+
body: null,
|
|
525
|
+
bodyLoaded: false,
|
|
526
|
+
mcpServers: [],
|
|
481
527
|
source: layer,
|
|
482
528
|
skillDir,
|
|
529
|
+
skillMdPath: skillMd,
|
|
530
|
+
skillFileBytes: stat.size,
|
|
531
|
+
skillFileMtimeMs: stat.mtimeMs,
|
|
483
532
|
});
|
|
484
533
|
} catch {
|
|
485
534
|
// Skip malformed skill files
|
|
@@ -487,12 +536,149 @@ export class CLISkillLoader {
|
|
|
487
536
|
}
|
|
488
537
|
}
|
|
489
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Materialize a descriptor's markdown body and embedded MCP declarations.
|
|
541
|
+
* Cache entries are invalidated by file size/mtime and never include content
|
|
542
|
+
* in provenance records.
|
|
543
|
+
*/
|
|
544
|
+
materializeSkill(skill, context = {}) {
|
|
545
|
+
if (!skill || !skill.skillMdPath) {
|
|
546
|
+
throw new TypeError("A discovered skill descriptor is required");
|
|
547
|
+
}
|
|
548
|
+
const stat = fs.statSync(skill.skillMdPath);
|
|
549
|
+
const signature = `${stat.size}:${stat.mtimeMs}`;
|
|
550
|
+
let cached = this._bodyCache.get(skill.skillMdPath);
|
|
551
|
+
let cacheRead = false;
|
|
552
|
+
if (cached?.signature === signature) {
|
|
553
|
+
this._cacheStats.bodyCacheHits += 1;
|
|
554
|
+
cacheRead = true;
|
|
555
|
+
} else {
|
|
556
|
+
const content = fs.readFileSync(skill.skillMdPath, "utf-8");
|
|
557
|
+
const { body } = parseSkillMd(content);
|
|
558
|
+
cached = {
|
|
559
|
+
signature,
|
|
560
|
+
body,
|
|
561
|
+
mcpServers: parseSkillMcpServers(body),
|
|
562
|
+
rawChars: body.length,
|
|
563
|
+
estimatedTokens: estimateSkillTokens(body),
|
|
564
|
+
};
|
|
565
|
+
this._bodyCache.set(skill.skillMdPath, cached);
|
|
566
|
+
this._cacheStats.bodyCacheMisses += 1;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
skill.body = cached.body;
|
|
570
|
+
skill.mcpServers = cached.mcpServers;
|
|
571
|
+
skill.bodyLoaded = true;
|
|
572
|
+
skill.skillFileBytes = stat.size;
|
|
573
|
+
skill.skillFileMtimeMs = stat.mtimeMs;
|
|
574
|
+
|
|
575
|
+
const prior = this._cacheStats.bodyLoads.get(skill.skillMdPath) || {
|
|
576
|
+
id: skill.id,
|
|
577
|
+
source: skill.source,
|
|
578
|
+
rawChars: cached.rawChars,
|
|
579
|
+
estimatedTokens: cached.estimatedTokens,
|
|
580
|
+
loads: 0,
|
|
581
|
+
cacheHits: 0,
|
|
582
|
+
loadedBecause: [],
|
|
583
|
+
};
|
|
584
|
+
prior.loads += 1;
|
|
585
|
+
if (cacheRead) prior.cacheHits += 1;
|
|
586
|
+
const reason = context.loadedBecause || "explicit";
|
|
587
|
+
if (!prior.loadedBecause.includes(reason)) prior.loadedBecause.push(reason);
|
|
588
|
+
this._cacheStats.bodyLoads.set(skill.skillMdPath, prior);
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
this._contextLedger?.recordRead?.({
|
|
592
|
+
sessionId: context.sessionId || "unknown",
|
|
593
|
+
turnId: context.turnId || "unknown",
|
|
594
|
+
sourceType: "skill",
|
|
595
|
+
sourceId: skill.id,
|
|
596
|
+
permissionMode: context.permissionMode || "agent",
|
|
597
|
+
tokenCount: cached.estimatedTokens,
|
|
598
|
+
metadata: {
|
|
599
|
+
source: skill.source,
|
|
600
|
+
loadedBecause: reason,
|
|
601
|
+
cacheRead,
|
|
602
|
+
bodyIncluded: context.bodyIncluded === true,
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
} catch {
|
|
606
|
+
// Context observability must never make a valid skill unloadable.
|
|
607
|
+
}
|
|
608
|
+
return skill;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Record descriptor text that was actually returned to a model/tool caller. */
|
|
612
|
+
recordDescriptorUse(skills, context = {}) {
|
|
613
|
+
for (const skill of Array.isArray(skills) ? skills : []) {
|
|
614
|
+
const descriptorText = `${skill.id}\n${skill.description || ""}\n${skill.category || ""}`;
|
|
615
|
+
try {
|
|
616
|
+
this._contextLedger?.recordRead?.({
|
|
617
|
+
sessionId: context.sessionId || "unknown",
|
|
618
|
+
turnId: context.turnId || "unknown",
|
|
619
|
+
sourceType: "skill_descriptor",
|
|
620
|
+
sourceId: skill.id,
|
|
621
|
+
permissionMode: context.permissionMode || "agent",
|
|
622
|
+
tokenCount: estimateSkillTokens(descriptorText),
|
|
623
|
+
metadata: {
|
|
624
|
+
source: skill.source,
|
|
625
|
+
loadedBecause: context.loadedBecause || "list_skills",
|
|
626
|
+
bodyLoaded: skill.bodyLoaded === true,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
} catch {
|
|
630
|
+
// Observability only.
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Content-free cache/cost snapshot for `cc context --sources`. */
|
|
636
|
+
getCacheLedger() {
|
|
637
|
+
const descriptors = Array.isArray(this._cache) ? this._cache : [];
|
|
638
|
+
const descriptorChars = descriptors.reduce(
|
|
639
|
+
(sum, skill) =>
|
|
640
|
+
sum +
|
|
641
|
+
`${skill.id}\n${skill.displayName}\n${skill.description}\n${skill.category}`.length,
|
|
642
|
+
0,
|
|
643
|
+
);
|
|
644
|
+
const residentPaths = new Set(this._bodyCache.keys());
|
|
645
|
+
const lazyFileBytes = descriptors.reduce(
|
|
646
|
+
(sum, skill) =>
|
|
647
|
+
sum +
|
|
648
|
+
(residentPaths.has(skill.skillMdPath) ? 0 : skill.skillFileBytes || 0),
|
|
649
|
+
0,
|
|
650
|
+
);
|
|
651
|
+
const bodyLoads = Array.from(this._cacheStats.bodyLoads.values()).map(
|
|
652
|
+
(entry) => ({ ...entry, loadedBecause: [...entry.loadedBecause] }),
|
|
653
|
+
);
|
|
654
|
+
return {
|
|
655
|
+
descriptors: {
|
|
656
|
+
resident: descriptors.length,
|
|
657
|
+
scans: this._cacheStats.descriptorScans,
|
|
658
|
+
cacheHits: this._cacheStats.descriptorCacheHits,
|
|
659
|
+
rawChars: descriptorChars,
|
|
660
|
+
estimatedTokens: estimateSkillTokens(descriptorChars),
|
|
661
|
+
},
|
|
662
|
+
bodies: {
|
|
663
|
+
resident: this._bodyCache.size,
|
|
664
|
+
cacheHits: this._cacheStats.bodyCacheHits,
|
|
665
|
+
cacheMisses: this._cacheStats.bodyCacheMisses,
|
|
666
|
+
entries: bodyLoads,
|
|
667
|
+
},
|
|
668
|
+
savings: {
|
|
669
|
+
lazyFileBytes,
|
|
670
|
+
estimatedTokensAvoided: estimateSkillTokens(lazyFileBytes),
|
|
671
|
+
},
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
490
675
|
/**
|
|
491
676
|
* Load all skills from all layers, applying priority override
|
|
492
677
|
* Higher-priority layers override same-name skills from lower layers.
|
|
493
678
|
* @returns {object[]} Resolved skill list
|
|
494
679
|
*/
|
|
495
680
|
loadAll(options = {}) {
|
|
681
|
+
this._cacheStats.descriptorScans += 1;
|
|
496
682
|
// `cc agent --bare` (CC_SKILLS=0): no skill layer loads at all — cache the
|
|
497
683
|
// empty result so getResolvedSkills stays cheap on every call.
|
|
498
684
|
if (allSkillsDisabled(options)) {
|
|
@@ -552,7 +738,10 @@ export class CLISkillLoader {
|
|
|
552
738
|
* @returns {object[]}
|
|
553
739
|
*/
|
|
554
740
|
getResolvedSkills() {
|
|
555
|
-
if (this._cache)
|
|
741
|
+
if (this._cache) {
|
|
742
|
+
this._cacheStats.descriptorCacheHits += 1;
|
|
743
|
+
return this._cache;
|
|
744
|
+
}
|
|
556
745
|
return this.loadAll();
|
|
557
746
|
}
|
|
558
747
|
|
|
@@ -560,10 +749,16 @@ export class CLISkillLoader {
|
|
|
560
749
|
* Get auto-activated persona skills
|
|
561
750
|
* @returns {object[]} skills with category "persona" and activation "auto"
|
|
562
751
|
*/
|
|
563
|
-
getAutoActivatedPersonas() {
|
|
564
|
-
return this.getResolvedSkills()
|
|
565
|
-
(s) => s.category === "persona" && s.activation === "auto"
|
|
566
|
-
|
|
752
|
+
getAutoActivatedPersonas(context = {}) {
|
|
753
|
+
return this.getResolvedSkills()
|
|
754
|
+
.filter((s) => s.category === "persona" && s.activation === "auto")
|
|
755
|
+
.map((skill) =>
|
|
756
|
+
this.materializeSkill(skill, {
|
|
757
|
+
...context,
|
|
758
|
+
loadedBecause: context.loadedBecause || "persona_auto",
|
|
759
|
+
bodyIncluded: true,
|
|
760
|
+
}),
|
|
761
|
+
);
|
|
567
762
|
}
|
|
568
763
|
|
|
569
764
|
/**
|
|
@@ -571,6 +766,14 @@ export class CLISkillLoader {
|
|
|
571
766
|
*/
|
|
572
767
|
clearCache() {
|
|
573
768
|
this._cache = null;
|
|
769
|
+
this._bodyCache.clear();
|
|
770
|
+
this._cacheStats = {
|
|
771
|
+
descriptorScans: 0,
|
|
772
|
+
descriptorCacheHits: 0,
|
|
773
|
+
bodyCacheHits: 0,
|
|
774
|
+
bodyCacheMisses: 0,
|
|
775
|
+
bodyLoads: new Map(),
|
|
776
|
+
};
|
|
574
777
|
}
|
|
575
778
|
}
|
|
576
779
|
|
|
@@ -2,6 +2,7 @@ import executionBroker from "./process-execution-broker/index.js";
|
|
|
2
2
|
|
|
3
3
|
const defaultRunSync = executionBroker.spawnSync.bind(executionBroker);
|
|
4
4
|
const defaultRunFileSync = executionBroker.execFileSync.bind(executionBroker);
|
|
5
|
+
const defaultRun = executionBroker.spawn.bind(executionBroker);
|
|
5
6
|
|
|
6
7
|
function skillIdentity(skill = {}) {
|
|
7
8
|
const identity = String(
|
|
@@ -31,7 +32,11 @@ function declaresShellExecution(skill = {}) {
|
|
|
31
32
|
*/
|
|
32
33
|
export function createSkillProcessBroker(
|
|
33
34
|
skill,
|
|
34
|
-
{
|
|
35
|
+
{
|
|
36
|
+
run = defaultRun,
|
|
37
|
+
runSync = defaultRunSync,
|
|
38
|
+
runFileSync = defaultRunFileSync,
|
|
39
|
+
} = {},
|
|
35
40
|
) {
|
|
36
41
|
if (!declaresShellExecution(skill)) return null;
|
|
37
42
|
|
|
@@ -46,6 +51,14 @@ export function createSkillProcessBroker(
|
|
|
46
51
|
});
|
|
47
52
|
|
|
48
53
|
return Object.freeze({
|
|
54
|
+
run(file, args = [], options = {}) {
|
|
55
|
+
return run(file, args, {
|
|
56
|
+
...options,
|
|
57
|
+
...metadata,
|
|
58
|
+
shell: false,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
|
|
49
62
|
runSync(file, args = [], options = {}) {
|
|
50
63
|
return runSync(file, args, {
|
|
51
64
|
...options,
|
|
@@ -64,6 +77,7 @@ export function createSkillProcessBroker(
|
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
export const _skillProcessDefaults = {
|
|
80
|
+
run: defaultRun,
|
|
67
81
|
runSync: defaultRunSync,
|
|
68
82
|
runFileSync: defaultRunFileSync,
|
|
69
83
|
};
|
|
@@ -110,6 +110,11 @@ export class SubAgentContext {
|
|
|
110
110
|
// symlinkDirectories } — only materialize the needed packages and reuse
|
|
111
111
|
// approved dep dirs. null → full checkout (byte-identical default).
|
|
112
112
|
this._worktreeOptions = options.worktreeOptions || null;
|
|
113
|
+
// Recovery lineage collected from the child agent-loop event stream.
|
|
114
|
+
// These are provider/runtime identifiers, not reconstructed log labels.
|
|
115
|
+
this._runId = null;
|
|
116
|
+
this._checkpointIds = [];
|
|
117
|
+
this._toolUseIds = [];
|
|
113
118
|
|
|
114
119
|
// ── Isolated state ──────────────────────────────────────────────
|
|
115
120
|
// Independent message history — never shared with parent
|
|
@@ -399,6 +404,22 @@ export class SubAgentContext {
|
|
|
399
404
|
for await (const event of gen) {
|
|
400
405
|
this._iterationCount++;
|
|
401
406
|
|
|
407
|
+
if (event.type === "run-started" && event.runId) {
|
|
408
|
+
this._runId = String(event.runId);
|
|
409
|
+
}
|
|
410
|
+
if (event.type === "checkpoint" && event.id) {
|
|
411
|
+
const checkpointId = String(event.id);
|
|
412
|
+
if (!this._checkpointIds.includes(checkpointId)) {
|
|
413
|
+
this._checkpointIds.push(checkpointId);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (event.type === "tool-executing" && event.tool_use_id) {
|
|
417
|
+
const toolUseId = String(event.tool_use_id);
|
|
418
|
+
if (!this._toolUseIds.includes(toolUseId)) {
|
|
419
|
+
this._toolUseIds.push(toolUseId);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
402
423
|
if (event.type === "token-usage" && this._onUsage) {
|
|
403
424
|
// Forward real usage to the spawner. A nested child's event already
|
|
404
425
|
// carries its own attribution frame — preserve it (deepest wins).
|
|
@@ -604,6 +625,26 @@ export class SubAgentContext {
|
|
|
604
625
|
}
|
|
605
626
|
}
|
|
606
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Serializable child→parent recovery lineage. Worktree paths are included
|
|
630
|
+
* for local restore UX; executable merge callbacks and tool payloads are not.
|
|
631
|
+
*/
|
|
632
|
+
recoveryBinding(result = this.result) {
|
|
633
|
+
const worktree = result?.worktree || null;
|
|
634
|
+
return {
|
|
635
|
+
childAgentId: this.id,
|
|
636
|
+
parentAgentId: this.parentId || null,
|
|
637
|
+
traceId: this._runId,
|
|
638
|
+
parentTraceId: this._hookParentTraceId || null,
|
|
639
|
+
checkpointIds: [...this._checkpointIds],
|
|
640
|
+
toolUseIds: [...this._toolUseIds],
|
|
641
|
+
worktreeId:
|
|
642
|
+
worktree?.branch || this._worktreeBranch || null,
|
|
643
|
+
worktreePath:
|
|
644
|
+
worktree?.path || this._worktreePath || null,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
607
648
|
/**
|
|
608
649
|
* True when this sub-agent has been asked to stop — either via an external
|
|
609
650
|
* abort signal or via this context's own `abort()` (precise single-agent
|
|
@@ -647,6 +688,7 @@ export class SubAgentContext {
|
|
|
647
688
|
iterationCount: this._iterationCount,
|
|
648
689
|
createdAt: this.createdAt,
|
|
649
690
|
completedAt: this.completedAt,
|
|
691
|
+
recoveryBinding: this.recoveryBinding(),
|
|
650
692
|
worktree: this._worktreePath
|
|
651
693
|
? { path: this._worktreePath, branch: this._worktreeBranch }
|
|
652
694
|
: null,
|
|
@@ -35,46 +35,85 @@ export const _deps = {
|
|
|
35
35
|
readEvents: storeReadEvents,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
export class TurnBindingPersistenceError extends Error {
|
|
39
|
+
constructor(operation, sessionId, cause) {
|
|
40
|
+
const detail = cause?.message || String(cause || "unknown persistence error");
|
|
41
|
+
super(`Turn binding ${operation} failed for ${sessionId}: ${detail}`, {
|
|
42
|
+
cause,
|
|
43
|
+
});
|
|
44
|
+
this.name = "TurnBindingPersistenceError";
|
|
45
|
+
this.code =
|
|
46
|
+
operation === "read"
|
|
47
|
+
? "TURN_BINDING_READ_FAILED"
|
|
48
|
+
: "TURN_BINDING_PERSIST_FAILED";
|
|
49
|
+
this.operation = operation;
|
|
50
|
+
this.sessionId = sessionId;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function persistenceFailure(operation, sessionId, cause, failIfUnavailable) {
|
|
55
|
+
if (!failIfUnavailable) return false;
|
|
56
|
+
throw new TurnBindingPersistenceError(operation, sessionId, cause);
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
/**
|
|
39
60
|
* Append the current binding table as a new chained event. Each call writes the
|
|
40
61
|
* FULL table (append-only snapshot), so the newest event is authoritative — a
|
|
41
62
|
* later `loadTurnBindingLog` never has to merge partial deltas across a chain
|
|
42
63
|
* that a compaction could have truncated.
|
|
43
64
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
65
|
+
* Turn/checkpoint binding is Critical state. Persistence fails closed by
|
|
66
|
+
* default; diagnostic callers may explicitly opt into advisory behavior.
|
|
46
67
|
*
|
|
47
68
|
* @param {string} sessionId
|
|
48
69
|
* @param {TurnBindingLog|object} log a TurnBindingLog or its toJSON() shape
|
|
70
|
+
* @param {{failIfUnavailable?:boolean}} [opts]
|
|
49
71
|
* @returns {boolean} true when the snapshot was written
|
|
50
72
|
*/
|
|
51
|
-
export function persistTurnBinding(sessionId, log) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
export function persistTurnBinding(sessionId, log, opts = {}) {
|
|
74
|
+
const failIfUnavailable = opts.failIfUnavailable !== false;
|
|
75
|
+
if (!sessionId || !log) {
|
|
76
|
+
return persistenceFailure(
|
|
77
|
+
"write",
|
|
78
|
+
sessionId || "<missing-session>",
|
|
79
|
+
new TypeError("sessionId and binding log are required"),
|
|
80
|
+
failIfUnavailable,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
55
83
|
try {
|
|
84
|
+
const snapshot = log instanceof TurnBindingLog ? log.toJSON() : log;
|
|
85
|
+
if (!snapshot || !Array.isArray(snapshot.turns)) {
|
|
86
|
+
throw new TypeError("binding snapshot must contain a turns array");
|
|
87
|
+
}
|
|
56
88
|
_deps.appendEvent(sessionId, TURN_BINDING_EVENT, snapshot);
|
|
57
89
|
return true;
|
|
58
|
-
} catch {
|
|
59
|
-
|
|
60
|
-
return false;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return persistenceFailure("write", sessionId, error, failIfUnavailable);
|
|
61
92
|
}
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
/**
|
|
65
96
|
* Rebuild the TurnBindingLog from the LATEST persisted snapshot in a session
|
|
66
|
-
* transcript. Returns an empty log when the session has no binding event
|
|
67
|
-
*
|
|
97
|
+
* transcript. Returns an empty log when the session has no binding event.
|
|
98
|
+
* Read/corruption errors fail closed unless explicitly marked advisory.
|
|
68
99
|
*
|
|
69
100
|
* @param {string} sessionId
|
|
101
|
+
* @param {{failIfUnavailable?:boolean}} [opts]
|
|
70
102
|
* @returns {TurnBindingLog}
|
|
71
103
|
*/
|
|
72
|
-
export function loadTurnBindingLog(sessionId) {
|
|
104
|
+
export function loadTurnBindingLog(sessionId, opts = {}) {
|
|
105
|
+
const failIfUnavailable = opts.failIfUnavailable !== false;
|
|
73
106
|
let events = [];
|
|
74
107
|
try {
|
|
75
108
|
events = _deps.readEvents(sessionId) || [];
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const fallback = persistenceFailure(
|
|
111
|
+
"read",
|
|
112
|
+
sessionId || "<missing-session>",
|
|
113
|
+
error,
|
|
114
|
+
failIfUnavailable,
|
|
115
|
+
);
|
|
116
|
+
if (fallback === false) return new TurnBindingLog();
|
|
78
117
|
}
|
|
79
118
|
// Newest snapshot wins — scan backwards for the first well-formed one.
|
|
80
119
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
@@ -85,7 +124,17 @@ export function loadTurnBindingLog(sessionId) {
|
|
|
85
124
|
e.data &&
|
|
86
125
|
Array.isArray(e.data.turns)
|
|
87
126
|
) {
|
|
88
|
-
|
|
127
|
+
try {
|
|
128
|
+
return TurnBindingLog.fromJSON(e.data);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
const fallback = persistenceFailure(
|
|
131
|
+
"read",
|
|
132
|
+
sessionId || "<missing-session>",
|
|
133
|
+
error,
|
|
134
|
+
failIfUnavailable,
|
|
135
|
+
);
|
|
136
|
+
if (fallback === false) return new TurnBindingLog();
|
|
137
|
+
}
|
|
89
138
|
}
|
|
90
139
|
}
|
|
91
140
|
return new TurnBindingLog();
|