chainlesschain 0.163.6 → 0.163.7
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +90 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +414 -4
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-engine.js +494 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +357 -1
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
- package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
- package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
- package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +2586 -85
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1272 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
|
@@ -32,9 +32,7 @@ import {
|
|
|
32
32
|
} from "../lib/secure-file-identity.js";
|
|
33
33
|
import {
|
|
34
34
|
computeEventHash,
|
|
35
|
-
latestChainHash,
|
|
36
35
|
TRANSCRIPT_CHAIN_STATUS,
|
|
37
|
-
verifyTranscriptText,
|
|
38
36
|
} from "./transcript-integrity.js";
|
|
39
37
|
import { withFileLock } from "../lib/with-file-lock.js";
|
|
40
38
|
import {
|
|
@@ -47,8 +45,10 @@ import {
|
|
|
47
45
|
SESSION_FORK_AUTHORITY_FIELD,
|
|
48
46
|
} from "../lib/session-message-provenance.js";
|
|
49
47
|
import {
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
assertFileLineByteLength,
|
|
49
|
+
DEFAULT_MAX_FILE_LINE_BYTES,
|
|
50
|
+
iterateFileLinesSync as iterateRawFileLinesSync,
|
|
51
|
+
iterateFileLinesReverseSync as iterateRawFileLinesReverseSync,
|
|
52
52
|
} from "../lib/file-lines.js";
|
|
53
53
|
import {
|
|
54
54
|
emptySessionMeta,
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
} from "./session-list-index.js";
|
|
70
70
|
import { createSessionPersistenceFailure } from "../lib/session-persistence-failure.js";
|
|
71
71
|
import { withSessionHostWriteAuthority } from "../lib/session-host-lease.js";
|
|
72
|
+
import { createSessionTranscriptStructureProjection } from "../lib/session-transcript-structure.js";
|
|
72
73
|
import {
|
|
73
74
|
listSessionAntiRollbackIds,
|
|
74
75
|
publishSessionAntiRollbackAnchor,
|
|
@@ -89,6 +90,7 @@ export const _sessionScaleFaultHooks = Object.seal({
|
|
|
89
90
|
beforeTranscriptDirectoryFsync: null,
|
|
90
91
|
afterTranscriptDirectoryFsync: null,
|
|
91
92
|
afterTranscriptAppend: null,
|
|
93
|
+
beforeVerifiedProjectionRepair: null,
|
|
92
94
|
afterForkCopy: null,
|
|
93
95
|
afterForkLineage: null,
|
|
94
96
|
afterForkPublish: null,
|
|
@@ -99,6 +101,9 @@ export const WS_TURN_EVENT = "ws_turn";
|
|
|
99
101
|
export const WS_TURN_CLAIM_EVENT = "ws_turn_claim";
|
|
100
102
|
export const WS_TURN_SCHEMA_VERSION = 1;
|
|
101
103
|
export const WS_TURN_REQUEST_ID_MAX_BYTES = 128;
|
|
104
|
+
export const CANONICAL_JSONL_RECORD_MAX_BYTES = DEFAULT_MAX_FILE_LINE_BYTES;
|
|
105
|
+
export const CANONICAL_JSONL_RECORD_TOO_LARGE_CODE =
|
|
106
|
+
"CC_SESSION_JSONL_RECORD_TOO_LARGE";
|
|
102
107
|
const WS_TURN_CONTENT_MAX_BYTES = 4 * 1024 * 1024;
|
|
103
108
|
const WS_TURN_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/;
|
|
104
109
|
const WS_TURN_INPUT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -110,6 +115,91 @@ export {
|
|
|
110
115
|
SESSION_GENERATION_AUTHORITY_SCHEMA,
|
|
111
116
|
};
|
|
112
117
|
|
|
118
|
+
function canonicalJsonlSizeOptions(maxRecordBytes) {
|
|
119
|
+
return {
|
|
120
|
+
maxLineBytes: resolveCanonicalJsonlRecordMaxBytes(maxRecordBytes),
|
|
121
|
+
code: CANONICAL_JSONL_RECORD_TOO_LARGE_CODE,
|
|
122
|
+
label: "Canonical session JSONL record",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveCanonicalJsonlRecordMaxBytes(
|
|
127
|
+
maxRecordBytes = CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
128
|
+
) {
|
|
129
|
+
const parsed = Number(maxRecordBytes);
|
|
130
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
131
|
+
throw new TypeError("maxRecordBytes must be a positive safe integer");
|
|
132
|
+
}
|
|
133
|
+
// Optional limits are test seams / caller-side tightening only. No caller
|
|
134
|
+
// may raise the canonical on-disk hard cap.
|
|
135
|
+
return Math.min(parsed, CANONICAL_JSONL_RECORD_MAX_BYTES);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function assertCanonicalJsonlRecordByteLength(
|
|
139
|
+
byteLength,
|
|
140
|
+
maxRecordBytes = CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
141
|
+
) {
|
|
142
|
+
assertFileLineByteLength(
|
|
143
|
+
byteLength,
|
|
144
|
+
canonicalJsonlSizeOptions(maxRecordBytes),
|
|
145
|
+
);
|
|
146
|
+
return byteLength;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function assertCanonicalJsonlRecordSize(
|
|
150
|
+
serialized,
|
|
151
|
+
maxRecordBytes = CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
152
|
+
) {
|
|
153
|
+
if (typeof serialized !== "string") {
|
|
154
|
+
throw new TypeError("Canonical session JSONL record must be a string");
|
|
155
|
+
}
|
|
156
|
+
return assertCanonicalJsonlRecordByteLength(
|
|
157
|
+
Buffer.byteLength(serialized, "utf8"),
|
|
158
|
+
maxRecordBytes,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function serializeCanonicalJsonlRecord(
|
|
163
|
+
event,
|
|
164
|
+
maxRecordBytes = CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
165
|
+
) {
|
|
166
|
+
const serialized = JSON.stringify(event);
|
|
167
|
+
assertCanonicalJsonlRecordSize(serialized, maxRecordBytes);
|
|
168
|
+
return serialized;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function parseCanonicalJsonlRecord(
|
|
172
|
+
line,
|
|
173
|
+
maxRecordBytes = CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
174
|
+
) {
|
|
175
|
+
assertCanonicalJsonlRecordSize(line, maxRecordBytes);
|
|
176
|
+
return JSON.parse(line);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function rethrowCanonicalJsonlRecordLimit(error) {
|
|
180
|
+
if (error?.code === CANONICAL_JSONL_RECORD_TOO_LARGE_CODE) throw error;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function canonicalLineOptions(options = {}) {
|
|
184
|
+
return {
|
|
185
|
+
...options,
|
|
186
|
+
maxLineBytes: CANONICAL_JSONL_RECORD_MAX_BYTES,
|
|
187
|
+
lineTooLargeCode: CANONICAL_JSONL_RECORD_TOO_LARGE_CODE,
|
|
188
|
+
lineLabel: "Canonical session JSONL record",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function iterateCanonicalJsonlLinesSync(filePath, options = {}) {
|
|
193
|
+
return iterateRawFileLinesSync(filePath, canonicalLineOptions(options));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function iterateCanonicalJsonlLinesReverseSync(filePath, options = {}) {
|
|
197
|
+
return iterateRawFileLinesReverseSync(
|
|
198
|
+
filePath,
|
|
199
|
+
canonicalLineOptions(options),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
113
203
|
function wsTurnError(message, code) {
|
|
114
204
|
const error = new Error(message);
|
|
115
205
|
error.code = code;
|
|
@@ -674,17 +764,23 @@ function inspectPhysicalTail(filePath, { dryRun = false } = {}) {
|
|
|
674
764
|
found = true;
|
|
675
765
|
}
|
|
676
766
|
}
|
|
677
|
-
const
|
|
767
|
+
const tailBytes = size - tailStart;
|
|
768
|
+
assertFileLineByteLength(
|
|
769
|
+
tailBytes,
|
|
770
|
+
canonicalJsonlSizeOptions(CANONICAL_JSONL_RECORD_MAX_BYTES),
|
|
771
|
+
);
|
|
772
|
+
const tail = Buffer.allocUnsafe(tailBytes);
|
|
678
773
|
if (tail.length > 0) readSync(fd, tail, 0, tail.length, tailStart);
|
|
679
774
|
try {
|
|
680
775
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(tail);
|
|
681
|
-
|
|
776
|
+
parseCanonicalJsonlRecord(text);
|
|
682
777
|
// A valid legacy/manual last record merely lacks its newline; retain
|
|
683
778
|
// it and normalize before appending the next chained event.
|
|
684
779
|
result.action = "normalize-newline";
|
|
685
780
|
result.changed = true;
|
|
686
781
|
normalizeNewline = !dryRun;
|
|
687
|
-
} catch {
|
|
782
|
+
} catch (error) {
|
|
783
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
688
784
|
// Crash tail: discard only the one incomplete physical record.
|
|
689
785
|
result.action = "discard-partial-record";
|
|
690
786
|
result.changed = true;
|
|
@@ -714,19 +810,20 @@ function _resolveChainTail(filePath) {
|
|
|
714
810
|
// Fast path: almost every append follows a newline-terminated record. Read
|
|
715
811
|
// that tail once and avoid opening the file a second time just to inspect its
|
|
716
812
|
// last byte. The slow repair path runs only for an unterminated crash tail.
|
|
717
|
-
const initial =
|
|
813
|
+
const initial = iterateCanonicalJsonlLinesReverseSync(filePath);
|
|
718
814
|
const first = initial.next();
|
|
719
815
|
if (!first.done && first.value.terminated) {
|
|
720
816
|
try {
|
|
721
817
|
let current = first;
|
|
722
818
|
while (!current.done) {
|
|
723
819
|
try {
|
|
724
|
-
const event =
|
|
820
|
+
const event = parseCanonicalJsonlRecord(current.value.line);
|
|
725
821
|
return {
|
|
726
822
|
prevHash: typeof event?.hash === "string" ? event.hash : null,
|
|
727
823
|
recovery: null,
|
|
728
824
|
};
|
|
729
|
-
} catch {
|
|
825
|
+
} catch (error) {
|
|
826
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
730
827
|
current = initial.next();
|
|
731
828
|
}
|
|
732
829
|
}
|
|
@@ -738,14 +835,15 @@ function _resolveChainTail(filePath) {
|
|
|
738
835
|
initial.return?.();
|
|
739
836
|
|
|
740
837
|
const recovery = inspectPhysicalTail(filePath);
|
|
741
|
-
for (const { line } of
|
|
838
|
+
for (const { line } of iterateCanonicalJsonlLinesReverseSync(filePath)) {
|
|
742
839
|
try {
|
|
743
|
-
const event =
|
|
840
|
+
const event = parseCanonicalJsonlRecord(line);
|
|
744
841
|
return {
|
|
745
842
|
prevHash: typeof event?.hash === "string" ? event.hash : null,
|
|
746
843
|
recovery: recovery.changed ? recovery : null,
|
|
747
844
|
};
|
|
748
|
-
} catch {
|
|
845
|
+
} catch (error) {
|
|
846
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
749
847
|
// A malformed historical line is verified separately; keep searching so
|
|
750
848
|
// this append never chains from arbitrary bytes.
|
|
751
849
|
}
|
|
@@ -934,6 +1032,10 @@ function appendTranscriptEventAtPath({
|
|
|
934
1032
|
expectedState,
|
|
935
1033
|
parentDevice,
|
|
936
1034
|
}) {
|
|
1035
|
+
// Bound the complete persisted envelope (including hash/prevHash) before
|
|
1036
|
+
// opening or writing the canonical transcript.
|
|
1037
|
+
const serializedEvent = serializeCanonicalJsonlRecord(event);
|
|
1038
|
+
const bytes = Buffer.from(`${serializedEvent}\n`, "utf8");
|
|
937
1039
|
let fd = null;
|
|
938
1040
|
let wroteBytes = false;
|
|
939
1041
|
let appendCompleted = false;
|
|
@@ -975,7 +1077,6 @@ function appendTranscriptEventAtPath({
|
|
|
975
1077
|
}
|
|
976
1078
|
}
|
|
977
1079
|
}
|
|
978
|
-
const bytes = Buffer.from(`${JSON.stringify(event)}\n`, "utf8");
|
|
979
1080
|
let offset = 0;
|
|
980
1081
|
while (offset < bytes.length) {
|
|
981
1082
|
const written = writeSync(fd, bytes, offset, bytes.length - offset);
|
|
@@ -1201,11 +1302,12 @@ function antiRollbackAnchorMatchesCandidate(anchor, candidate) {
|
|
|
1201
1302
|
function transcriptHashAtEventCount(filePath, eventCount) {
|
|
1202
1303
|
if (eventCount === 0) return null;
|
|
1203
1304
|
let count = 0;
|
|
1204
|
-
for (const { line } of
|
|
1305
|
+
for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
|
|
1205
1306
|
let event;
|
|
1206
1307
|
try {
|
|
1207
|
-
event =
|
|
1208
|
-
} catch {
|
|
1308
|
+
event = parseCanonicalJsonlRecord(line);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
1209
1311
|
continue;
|
|
1210
1312
|
}
|
|
1211
1313
|
if (typeof event?.hash !== "string") continue;
|
|
@@ -1580,79 +1682,188 @@ export function appendAuthorityEventWithVerifiedProjection(
|
|
|
1580
1682
|
);
|
|
1581
1683
|
}
|
|
1582
1684
|
const filePath = sessionPath(sessionId);
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1685
|
+
let intendedAppend = null;
|
|
1686
|
+
try {
|
|
1687
|
+
return runClassifiedAppend(
|
|
1688
|
+
sessionId,
|
|
1689
|
+
"verified-projection-authority-append",
|
|
1690
|
+
() =>
|
|
1691
|
+
withSessionHostWriterLock(
|
|
1692
|
+
sessionId,
|
|
1693
|
+
filePath,
|
|
1694
|
+
() => {
|
|
1695
|
+
const projection = createProjection();
|
|
1696
|
+
if (
|
|
1697
|
+
!projection ||
|
|
1698
|
+
typeof projection.accept !== "function" ||
|
|
1699
|
+
typeof projection.finish !== "function"
|
|
1700
|
+
) {
|
|
1701
|
+
throw new TypeError(
|
|
1702
|
+
"Authority projection must provide accept() and finish()",
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (!existsSync(filePath)) {
|
|
1706
|
+
throw unverifiedTranscriptError(sessionId, {
|
|
1707
|
+
status: "missing",
|
|
1708
|
+
reason: "authority transcript does not exist",
|
|
1709
|
+
lastHash: null,
|
|
1710
|
+
chainedEvents: 0,
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
_resolveChainTail(filePath);
|
|
1714
|
+
const verification = verifyTranscriptFile(filePath, {
|
|
1715
|
+
onVerifiedEvent: (event) => {
|
|
1716
|
+
const accepted = projection.accept(event);
|
|
1717
|
+
if (accepted && typeof accepted.then === "function") {
|
|
1718
|
+
throw new TypeError(
|
|
1719
|
+
"Authority projection accept() must be synchronous",
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1724
|
+
assertVerifiedTranscriptAnchor(sessionId, verification);
|
|
1725
|
+
const authority = Object.freeze({
|
|
1726
|
+
headHash: verification.lastHash,
|
|
1727
|
+
eventCount: verification.chainedEvents,
|
|
1728
|
+
readMessages: () => rebuildVerifiedMessagesFromFile(filePath),
|
|
1729
|
+
});
|
|
1730
|
+
const projected = projection.finish(authority);
|
|
1731
|
+
if (projected && typeof projected.then === "function") {
|
|
1732
|
+
throw new TypeError(
|
|
1733
|
+
"Authority projection finish() must be synchronous",
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
const validation = validateProjection(projected, authority);
|
|
1737
|
+
if (validation && typeof validation.then === "function") {
|
|
1738
|
+
throw new TypeError(
|
|
1739
|
+
"Authority projection validation must be synchronous",
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
if (validation === false) {
|
|
1743
|
+
throw new Error(
|
|
1744
|
+
"Authority projection validation rejected the append",
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
const persistedData = encodeEventMessageProvenance(type, data);
|
|
1748
|
+
let appended;
|
|
1749
|
+
try {
|
|
1750
|
+
appended = appendVerifiedWsAuthorityEventLocked(
|
|
1751
|
+
sessionId,
|
|
1752
|
+
filePath,
|
|
1753
|
+
type,
|
|
1754
|
+
persistedData,
|
|
1755
|
+
authority.headHash,
|
|
1756
|
+
);
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
// The transcript append can succeed before its metadata/anti-rollback
|
|
1759
|
+
// anchor reports an ambiguous settlement. Perform an exact locked
|
|
1760
|
+
// readback before releasing the writer authority: if the intended
|
|
1761
|
+
// event is now the verified physical head, repair the rebuildable
|
|
1762
|
+
// sidecar and publish a fresh anti-rollback witness. Callers may then
|
|
1763
|
+
// safely treat the authority mutation as committed exactly once.
|
|
1764
|
+
if (error?.commitState !== "unknown") throw error;
|
|
1765
|
+
let intended = null;
|
|
1766
|
+
const verification = verifyTranscriptFile(filePath, {
|
|
1767
|
+
onVerifiedEvent: (event) => {
|
|
1768
|
+
intended = event;
|
|
1769
|
+
},
|
|
1770
|
+
});
|
|
1771
|
+
const exactPhysicalCommit =
|
|
1772
|
+
verification.status === TRANSCRIPT_CHAIN_STATUS.VERIFIED &&
|
|
1773
|
+
verification.malformedLines === 0 &&
|
|
1774
|
+
!verification.truncatedTail &&
|
|
1775
|
+
intended?.prevHash === authority.headHash &&
|
|
1776
|
+
intended?.type === type &&
|
|
1777
|
+
JSON.stringify(intended?.data) ===
|
|
1778
|
+
JSON.stringify(persistedData);
|
|
1779
|
+
if (!exactPhysicalCommit) throw error;
|
|
1780
|
+
intendedAppend = Object.freeze({
|
|
1781
|
+
previousHeadHash: authority.headHash,
|
|
1782
|
+
type,
|
|
1783
|
+
data: persistedData,
|
|
1784
|
+
hash: intended.hash,
|
|
1785
|
+
});
|
|
1786
|
+
try {
|
|
1787
|
+
runSessionScaleFaultHook("beforeVerifiedProjectionRepair", {
|
|
1788
|
+
sessionId,
|
|
1789
|
+
type,
|
|
1790
|
+
hash: intended.hash,
|
|
1791
|
+
});
|
|
1792
|
+
const repairedMeta = rebuildSessionMetaUnlocked(
|
|
1793
|
+
getSessionsDir(),
|
|
1794
|
+
sessionId,
|
|
1795
|
+
filePath,
|
|
1796
|
+
);
|
|
1797
|
+
publishSessionAntiRollbackWitness(
|
|
1798
|
+
sessionId,
|
|
1799
|
+
repairedMeta,
|
|
1800
|
+
filePath,
|
|
1801
|
+
"live",
|
|
1802
|
+
);
|
|
1803
|
+
assertVerifiedTranscriptAnchor(sessionId, verification);
|
|
1804
|
+
} catch (cause) {
|
|
1805
|
+
const repairError = new Error(
|
|
1806
|
+
`Session authority repair failed after exact physical commit: ${sessionId}`,
|
|
1807
|
+
{ cause },
|
|
1808
|
+
);
|
|
1809
|
+
repairError.code = "SESSION_INDEX_ANCHOR_FAILED";
|
|
1810
|
+
repairError.sessionId = sessionId;
|
|
1811
|
+
repairError.commitState = "unknown";
|
|
1812
|
+
throw repairError;
|
|
1813
|
+
}
|
|
1814
|
+
appended = Object.freeze({
|
|
1815
|
+
hash: intended.hash,
|
|
1816
|
+
event: intended,
|
|
1817
|
+
commitState: "committed-after-readback",
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
if (intendedAppend === null) {
|
|
1821
|
+
intendedAppend = Object.freeze({
|
|
1822
|
+
previousHeadHash: authority.headHash,
|
|
1823
|
+
type,
|
|
1824
|
+
data: persistedData,
|
|
1825
|
+
hash: appended.hash,
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
return Object.freeze({ hash: appended.hash });
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
failIfUnavailable: true,
|
|
1832
|
+
timeoutMs: 30_000,
|
|
1833
|
+
retryMs: 1,
|
|
1834
|
+
maxRetryMs: 8,
|
|
1835
|
+
retryJitterMs: 4,
|
|
1836
|
+
yieldAfterReleaseMs: 2,
|
|
1837
|
+
},
|
|
1838
|
+
),
|
|
1839
|
+
);
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
// A strict lock-release failure happens after the callback returned. The
|
|
1842
|
+
// append is already settled at that point, but runClassifiedAppend must
|
|
1843
|
+
// conservatively label the operation unknown. Adjudicate only an exact
|
|
1844
|
+
// verified head match; otherwise preserve the unknown outcome.
|
|
1845
|
+
if (intendedAppend) {
|
|
1846
|
+
try {
|
|
1847
|
+
const events = readVerifiedEvents(sessionId);
|
|
1848
|
+
const head = events.at(-1);
|
|
1849
|
+
if (
|
|
1850
|
+
head?.hash === intendedAppend.hash &&
|
|
1851
|
+
head?.prevHash === intendedAppend.previousHeadHash &&
|
|
1852
|
+
head?.type === intendedAppend.type &&
|
|
1853
|
+
JSON.stringify(head?.data) === JSON.stringify(intendedAppend.data)
|
|
1854
|
+
) {
|
|
1855
|
+
return Object.freeze({
|
|
1856
|
+
hash: head.hash,
|
|
1857
|
+
commitState: "committed-after-lock-release-readback",
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
} catch {
|
|
1861
|
+
// Preserve the original classified persistence error.
|
|
1636
1862
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
type,
|
|
1642
|
-
persistedData,
|
|
1643
|
-
authority.headHash,
|
|
1644
|
-
);
|
|
1645
|
-
return Object.freeze({ hash: appended.hash });
|
|
1646
|
-
},
|
|
1647
|
-
{
|
|
1648
|
-
failIfUnavailable: true,
|
|
1649
|
-
timeoutMs: 30_000,
|
|
1650
|
-
retryMs: 1,
|
|
1651
|
-
maxRetryMs: 8,
|
|
1652
|
-
retryJitterMs: 4,
|
|
1653
|
-
yieldAfterReleaseMs: 2,
|
|
1654
|
-
},
|
|
1655
|
-
);
|
|
1863
|
+
}
|
|
1864
|
+
if (intendedAppend && !error?.commitState) error.commitState = "unknown";
|
|
1865
|
+
throw error;
|
|
1866
|
+
}
|
|
1656
1867
|
}
|
|
1657
1868
|
|
|
1658
1869
|
/**
|
|
@@ -1885,13 +2096,15 @@ function verifyTranscriptFile(filePath, options = {}) {
|
|
|
1885
2096
|
reason,
|
|
1886
2097
|
});
|
|
1887
2098
|
|
|
1888
|
-
for (const { line, lineNo, terminated } of
|
|
1889
|
-
|
|
1890
|
-
|
|
2099
|
+
for (const { line, lineNo, terminated } of iterateCanonicalJsonlLinesSync(
|
|
2100
|
+
filePath,
|
|
2101
|
+
{ ioMetrics },
|
|
2102
|
+
)) {
|
|
1891
2103
|
let event;
|
|
1892
2104
|
try {
|
|
1893
|
-
event =
|
|
1894
|
-
} catch {
|
|
2105
|
+
event = parseCanonicalJsonlRecord(line);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
1895
2108
|
result.malformedLines += 1;
|
|
1896
2109
|
if (!terminated) {
|
|
1897
2110
|
result.truncatedTail = true;
|
|
@@ -2242,11 +2455,98 @@ export function startSession(sessionId, meta = {}) {
|
|
|
2242
2455
|
title: meta.title || "Untitled",
|
|
2243
2456
|
provider: meta.provider || "",
|
|
2244
2457
|
model: meta.model || "",
|
|
2458
|
+
...(meta.observabilityScope != null
|
|
2459
|
+
? {
|
|
2460
|
+
observabilityScope: normalizeObservabilityScope(
|
|
2461
|
+
meta.observabilityScope,
|
|
2462
|
+
),
|
|
2463
|
+
usageTelemetryProtocol: "call-ledger",
|
|
2464
|
+
usageTelemetryVersion: 1,
|
|
2465
|
+
}
|
|
2466
|
+
: {}),
|
|
2245
2467
|
});
|
|
2246
2468
|
|
|
2247
2469
|
return id;
|
|
2248
2470
|
}
|
|
2249
2471
|
|
|
2472
|
+
/** Return the anchored transcript revision used by delivery causality bindings. */
|
|
2473
|
+
export function getVerifiedSessionObservabilityAuthority(sessionId) {
|
|
2474
|
+
return readVerifiedProjection(sessionId, () => {
|
|
2475
|
+
let sessionStartSeen = false;
|
|
2476
|
+
let hasObservabilityScope = false;
|
|
2477
|
+
let observabilityScope = null;
|
|
2478
|
+
return {
|
|
2479
|
+
accept(event) {
|
|
2480
|
+
if (event?.type === "session_start" && !sessionStartSeen) {
|
|
2481
|
+
sessionStartSeen = true;
|
|
2482
|
+
if (Object.hasOwn(event.data || {}, "observabilityScope")) {
|
|
2483
|
+
hasObservabilityScope = true;
|
|
2484
|
+
observabilityScope = event.data.observabilityScope;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
},
|
|
2488
|
+
finish(authority) {
|
|
2489
|
+
if (!authority.headHash || authority.eventCount <= 0) {
|
|
2490
|
+
throw unverifiedTranscriptError(sessionId, {
|
|
2491
|
+
status: "empty",
|
|
2492
|
+
reason: "session has no anchored transcript events",
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
if (!hasObservabilityScope) {
|
|
2496
|
+
const error = new Error(
|
|
2497
|
+
`session ${sessionId} has no observabilityScope authority`,
|
|
2498
|
+
);
|
|
2499
|
+
error.code = "SESSION_OBSERVABILITY_SCOPE_MISSING";
|
|
2500
|
+
throw error;
|
|
2501
|
+
}
|
|
2502
|
+
const normalizedScope = normalizeObservabilityScope(observabilityScope);
|
|
2503
|
+
return Object.freeze({
|
|
2504
|
+
sessionId,
|
|
2505
|
+
headHash: authority.headHash,
|
|
2506
|
+
eventCount: authority.eventCount,
|
|
2507
|
+
observabilityScope: Object.freeze(normalizedScope),
|
|
2508
|
+
});
|
|
2509
|
+
},
|
|
2510
|
+
};
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
function hasAsciiControlCharacter(value) {
|
|
2515
|
+
for (let index = 0; index < value.length; index++) {
|
|
2516
|
+
const code = value.charCodeAt(index);
|
|
2517
|
+
if (code <= 0x1f || code === 0x7f) return true;
|
|
2518
|
+
}
|
|
2519
|
+
return false;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
function normalizeObservabilityScope(value) {
|
|
2523
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2524
|
+
throw new TypeError("observabilityScope must be an object");
|
|
2525
|
+
}
|
|
2526
|
+
const normalize = (input, field) => {
|
|
2527
|
+
if (input == null) return null;
|
|
2528
|
+
if (typeof input !== "string") {
|
|
2529
|
+
throw new TypeError(`observabilityScope.${field} must be a string`);
|
|
2530
|
+
}
|
|
2531
|
+
const output = input.trim();
|
|
2532
|
+
if (!output || output.length > 256 || hasAsciiControlCharacter(output)) {
|
|
2533
|
+
throw new TypeError(`observabilityScope.${field} is invalid`);
|
|
2534
|
+
}
|
|
2535
|
+
return output;
|
|
2536
|
+
};
|
|
2537
|
+
const normalized = {
|
|
2538
|
+
workspaceId: normalize(value.workspaceId, "workspaceId"),
|
|
2539
|
+
teamId: normalize(value.teamId, "teamId"),
|
|
2540
|
+
policyId: normalize(value.policyId, "policyId"),
|
|
2541
|
+
};
|
|
2542
|
+
if (!Object.values(normalized).some((scopeId) => scopeId != null)) {
|
|
2543
|
+
throw new TypeError(
|
|
2544
|
+
"observabilityScope requires at least one non-null dimension",
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
return normalized;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2250
2550
|
export function appendUserMessage(sessionId, content) {
|
|
2251
2551
|
return appendEvent(sessionId, "user_message", {
|
|
2252
2552
|
role: "user",
|
|
@@ -2502,10 +2802,23 @@ export function appendToolCall(sessionId, toolName, args) {
|
|
|
2502
2802
|
*/
|
|
2503
2803
|
export function appendToolCallCompact(
|
|
2504
2804
|
sessionId,
|
|
2505
|
-
{ tool, isError, skill, plugin, pluginVersion, durationMs } = {},
|
|
2805
|
+
{ id, tool, isError, skill, plugin, pluginVersion, durationMs } = {},
|
|
2506
2806
|
) {
|
|
2807
|
+
const cleanId =
|
|
2808
|
+
id === undefined
|
|
2809
|
+
? null
|
|
2810
|
+
: typeof id === "string" &&
|
|
2811
|
+
id.trim() &&
|
|
2812
|
+
id.length <= 128 &&
|
|
2813
|
+
!/\p{Cc}/u.test(id)
|
|
2814
|
+
? id.trim()
|
|
2815
|
+
: null;
|
|
2816
|
+
if (id !== undefined && !cleanId) {
|
|
2817
|
+
throw new TypeError("compact tool call id must be a bounded string");
|
|
2818
|
+
}
|
|
2507
2819
|
const duration = normalizeCompactDuration(durationMs);
|
|
2508
2820
|
appendEvent(sessionId, "tool_call", {
|
|
2821
|
+
...(cleanId ? { id: cleanId } : {}),
|
|
2509
2822
|
tool: tool || "?",
|
|
2510
2823
|
is_error: Boolean(isError),
|
|
2511
2824
|
...(skill ? { skill: String(skill) } : {}),
|
|
@@ -2631,10 +2944,11 @@ export function readEvents(sessionId) {
|
|
|
2631
2944
|
if (!existsSync(filePath)) return [];
|
|
2632
2945
|
|
|
2633
2946
|
const events = [];
|
|
2634
|
-
for (const { line } of
|
|
2947
|
+
for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
|
|
2635
2948
|
try {
|
|
2636
|
-
events.push(
|
|
2637
|
-
} catch {
|
|
2949
|
+
events.push(parseCanonicalJsonlRecord(line));
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
2638
2952
|
// Skip malformed lines
|
|
2639
2953
|
}
|
|
2640
2954
|
}
|
|
@@ -2823,11 +3137,16 @@ export function readVerifiedProjection(
|
|
|
2823
3137
|
}),
|
|
2824
3138
|
);
|
|
2825
3139
|
}
|
|
3140
|
+
const structure = createSessionTranscriptStructureProjection(sessionId);
|
|
2826
3141
|
const verification = verifyTranscriptFile(filePath, {
|
|
2827
3142
|
ioMetrics: options.ioMetrics,
|
|
2828
|
-
onVerifiedEvent: (event) =>
|
|
3143
|
+
onVerifiedEvent: (event) => {
|
|
3144
|
+
structure.accept(event);
|
|
3145
|
+
projection.accept(event);
|
|
3146
|
+
},
|
|
2829
3147
|
});
|
|
2830
3148
|
assertVerifiedTranscriptAnchor(sessionId, verification);
|
|
3149
|
+
structure.finish({ assertValid: true });
|
|
2831
3150
|
return projection.finish(
|
|
2832
3151
|
Object.freeze({
|
|
2833
3152
|
headHash: verification.lastHash,
|
|
@@ -2945,6 +3264,10 @@ function readVerifiedTranscriptBytesAtPath(sessionId, filePath, parentDevice) {
|
|
|
2945
3264
|
"the read descriptor does not match the canonical path",
|
|
2946
3265
|
);
|
|
2947
3266
|
}
|
|
3267
|
+
// Verify with the bounded line iterator before materializing the requested
|
|
3268
|
+
// full transcript. A single attacker-controlled record can therefore never
|
|
3269
|
+
// force readFileSync/JSON parsing to allocate an unbounded line first.
|
|
3270
|
+
const verification = verifyTranscriptFile(filePath);
|
|
2948
3271
|
const text = readFileSync(fd, "utf8");
|
|
2949
3272
|
const afterStats = fstatSync(fd, { bigint: true });
|
|
2950
3273
|
const after = physicalTranscriptStateFromStats(afterStats);
|
|
@@ -2964,10 +3287,6 @@ function readVerifiedTranscriptBytesAtPath(sessionId, filePath, parentDevice) {
|
|
|
2964
3287
|
"the transcript changed while verified bytes were read",
|
|
2965
3288
|
);
|
|
2966
3289
|
}
|
|
2967
|
-
const verification = {
|
|
2968
|
-
...verifyTranscriptText(text),
|
|
2969
|
-
lastHash: latestChainHash(text),
|
|
2970
|
-
};
|
|
2971
3290
|
assertVerifiedTranscriptAnchor(sessionId, verification, published);
|
|
2972
3291
|
return text;
|
|
2973
3292
|
} finally {
|
|
@@ -3077,11 +3396,14 @@ function rebuildMessagesFromFile(
|
|
|
3077
3396
|
// replay state, but the file itself is still never loaded as one giant string.
|
|
3078
3397
|
const suffix = [];
|
|
3079
3398
|
let checkpoint = [];
|
|
3080
|
-
for (const { line } of
|
|
3399
|
+
for (const { line } of iterateCanonicalJsonlLinesReverseSync(filePath, {
|
|
3400
|
+
ioMetrics,
|
|
3401
|
+
})) {
|
|
3081
3402
|
let event;
|
|
3082
3403
|
try {
|
|
3083
|
-
event =
|
|
3084
|
-
} catch {
|
|
3404
|
+
event = parseCanonicalJsonlRecord(line);
|
|
3405
|
+
} catch (error) {
|
|
3406
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
3085
3407
|
continue;
|
|
3086
3408
|
}
|
|
3087
3409
|
if (
|
|
@@ -3155,11 +3477,12 @@ export function toIsoSafe(ts) {
|
|
|
3155
3477
|
|
|
3156
3478
|
function rebuildSessionMetaUnlocked(dir, sessionId, filePath) {
|
|
3157
3479
|
let meta = emptySessionMeta(sessionId);
|
|
3158
|
-
for (const { line } of
|
|
3480
|
+
for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
|
|
3159
3481
|
try {
|
|
3160
|
-
const event =
|
|
3482
|
+
const event = parseCanonicalJsonlRecord(line);
|
|
3161
3483
|
meta = applyEventToSessionMeta(meta, event, event?.hash);
|
|
3162
|
-
} catch {
|
|
3484
|
+
} catch (error) {
|
|
3485
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
3163
3486
|
// The validator/repair path reports malformed records. The index remains
|
|
3164
3487
|
// a best-effort projection over all intact events.
|
|
3165
3488
|
}
|
|
@@ -3742,17 +4065,19 @@ export function forkSession(sourceId, options = {}) {
|
|
|
3742
4065
|
},
|
|
3743
4066
|
};
|
|
3744
4067
|
const hash = computeEventHash(prevHash, core);
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
4068
|
+
const lineageEvent = { ...core, prevHash, hash };
|
|
4069
|
+
const serializedLineage =
|
|
4070
|
+
serializeCanonicalJsonlRecord(lineageEvent);
|
|
4071
|
+
appendFileSync(workingPath, `${serializedLineage}\n`, {
|
|
4072
|
+
encoding: "utf8",
|
|
4073
|
+
mode: 0o600,
|
|
4074
|
+
});
|
|
3750
4075
|
ensurePrivateFile(workingPath);
|
|
3751
4076
|
runSessionScaleFaultHook("afterForkLineage", {
|
|
3752
4077
|
sourceId,
|
|
3753
4078
|
sessionId: newId,
|
|
3754
4079
|
filePath: workingPath,
|
|
3755
|
-
event:
|
|
4080
|
+
event: lineageEvent,
|
|
3756
4081
|
});
|
|
3757
4082
|
target = inspectTarget();
|
|
3758
4083
|
targetMeta = readSessionMeta(sessionsDir, newId);
|
|
@@ -4426,16 +4751,14 @@ export function validateJsonlSession(sessionId) {
|
|
|
4426
4751
|
}
|
|
4427
4752
|
|
|
4428
4753
|
let malformedLines = 0;
|
|
4429
|
-
let eventCount = 0;
|
|
4430
4754
|
let messageCount = 0;
|
|
4431
4755
|
let invalidWsTurns = 0;
|
|
4432
4756
|
let invalidWsClaims = 0;
|
|
4433
|
-
|
|
4434
|
-
for (const { line } of
|
|
4757
|
+
const structure = createSessionTranscriptStructureProjection(sessionId);
|
|
4758
|
+
for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
|
|
4435
4759
|
try {
|
|
4436
|
-
const event =
|
|
4437
|
-
|
|
4438
|
-
if (event?.type === "session_start") hasStartEvent = true;
|
|
4760
|
+
const event = parseCanonicalJsonlRecord(line);
|
|
4761
|
+
structure.accept(event);
|
|
4439
4762
|
if (
|
|
4440
4763
|
event?.type === "user_message" ||
|
|
4441
4764
|
event?.type === "assistant_message"
|
|
@@ -4451,24 +4774,39 @@ export function validateJsonlSession(sessionId) {
|
|
|
4451
4774
|
) {
|
|
4452
4775
|
invalidWsClaims += 1;
|
|
4453
4776
|
}
|
|
4454
|
-
} catch {
|
|
4777
|
+
} catch (error) {
|
|
4778
|
+
rethrowCanonicalJsonlRecordLimit(error);
|
|
4455
4779
|
malformedLines++;
|
|
4456
4780
|
}
|
|
4457
4781
|
}
|
|
4458
4782
|
|
|
4783
|
+
const structural = structure.finish();
|
|
4784
|
+
const reason =
|
|
4785
|
+
malformedLines > 0
|
|
4786
|
+
? "session contains malformed JSONL records"
|
|
4787
|
+
: invalidWsTurns > 0
|
|
4788
|
+
? "session contains invalid WebSocket turn settlements"
|
|
4789
|
+
: invalidWsClaims > 0
|
|
4790
|
+
? "session contains invalid WebSocket turn claims"
|
|
4791
|
+
: structural.reason;
|
|
4792
|
+
|
|
4459
4793
|
return {
|
|
4460
4794
|
sessionId,
|
|
4461
4795
|
valid:
|
|
4462
4796
|
malformedLines === 0 &&
|
|
4463
4797
|
invalidWsTurns === 0 &&
|
|
4464
4798
|
invalidWsClaims === 0 &&
|
|
4465
|
-
|
|
4799
|
+
structural.valid,
|
|
4800
|
+
reason,
|
|
4466
4801
|
malformedLines,
|
|
4467
4802
|
invalidWsTurns,
|
|
4468
4803
|
invalidWsClaims,
|
|
4469
|
-
eventCount,
|
|
4804
|
+
eventCount: structural.eventCount,
|
|
4470
4805
|
messageCount,
|
|
4471
|
-
hasStartEvent,
|
|
4806
|
+
hasStartEvent: structural.hasStartEvent,
|
|
4807
|
+
sessionStartCount: structural.sessionStartCount,
|
|
4808
|
+
sessionStartIndex: structural.sessionStartIndex,
|
|
4809
|
+
sessionStartIsFirst: structural.sessionStartIsFirst,
|
|
4472
4810
|
};
|
|
4473
4811
|
}
|
|
4474
4812
|
|