chainlesschain 0.165.2 → 0.165.4
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 +151 -29
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
- package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
- package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
- package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
- package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
- package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
- package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
- package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
- package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
- package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
- package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
- package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
- package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
- package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
- package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
- package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
- package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -35
- package/src/commands/artifacts.js +363 -33
- package/src/commands/cowork.js +202 -36
- package/src/commands/session-budget.js +155 -0
- package/src/commands/session-location.js +948 -0
- package/src/commands/session.js +72 -46
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/artifact-protocol.js +77 -13
- package/src/gateways/ws/session-protocol.js +391 -24
- package/src/gateways/ws/ws-agent-handler.js +191 -14
- package/src/gateways/ws/ws-server.js +14 -3
- package/src/gateways/ws/ws-session-gateway.js +101 -5
- package/src/harness/jsonl-session-store.js +1210 -0
- package/src/lib/agent-team/team-runner.js +43 -13
- package/src/lib/artifact-access-ledger.js +461 -0
- package/src/lib/artifact-cleanup-ledger.js +861 -0
- package/src/lib/artifact-deletion-ledger.js +677 -0
- package/src/lib/artifact-store.js +256 -51
- package/src/lib/background-agent-keeper-protocol.js +75 -4
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +149 -5
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/command-lifecycle-report.js +8 -4
- package/src/lib/cowork-adapter.js +9 -5
- package/src/lib/cowork-task-runner.js +20 -0
- package/src/lib/cowork-workflow.js +178 -20
- package/src/lib/direct-model-usage.js +62 -22
- package/src/lib/dynamic-workflow-runtime.js +3184 -111
- package/src/lib/execution-location-result-apply.js +339 -0
- package/src/lib/execution-location-result-artifact.js +271 -0
- package/src/lib/execution-location-result-review.js +253 -0
- package/src/lib/execution-location-result-store.js +331 -0
- package/src/lib/execution-location-result.js +617 -0
- package/src/lib/execution-location-target.js +236 -1
- package/src/lib/managed-tool-checkpoint.js +47 -4
- package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
- package/src/lib/session-budget-production-root.js +376 -0
- package/src/lib/session-budget-runtime.js +109 -18
- package/src/lib/session-budget-usage.js +63 -0
- package/src/lib/session-host-snapshot.js +19 -2
- package/src/lib/session-resource-budget.js +766 -105
- package/src/lib/sub-agent-context.js +69 -22
- package/src/lib/web-ui-server.js +12 -1
- package/src/lib/with-file-lock.js +175 -26
- package/src/repl/agent-repl.js +237 -16
- package/src/runtime/agent-core.js +123 -0
- package/src/runtime/headless-runner.js +135 -46
- package/src/runtime/headless-stream.js +133 -25
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/workers/background-agent-keeper.js +112 -19
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import {
|
|
2
|
+
openSessionBudget,
|
|
3
|
+
SessionBudgetSidecarStore,
|
|
4
|
+
} from "./session-budget-runtime.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeSessionResourceBudgetSnapshot,
|
|
7
|
+
SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
8
|
+
} from "./session-resource-budget.js";
|
|
9
|
+
|
|
10
|
+
export const SESSION_BUDGET_ROOT_SCHEMA =
|
|
11
|
+
"chainlesschain.session-budget-root/v1";
|
|
12
|
+
|
|
13
|
+
const OPTION_FIELDS = Object.freeze([
|
|
14
|
+
["sessionMaxConcurrent", "maxConcurrent", "--session-max-concurrent", true],
|
|
15
|
+
["sessionMaxSpawns", "maxSpawns", "--session-max-spawns", true],
|
|
16
|
+
["sessionMaxDepth", "maxDepth", "--session-max-depth", true],
|
|
17
|
+
["sessionMaxTurns", "maxTurns", "--session-max-turns", true],
|
|
18
|
+
["sessionMaxTokens", "maxTokens", "--session-max-tokens", true],
|
|
19
|
+
["sessionMaxCostUsd", "maxUsd", "--session-max-cost-usd", false],
|
|
20
|
+
["sessionMaxWallMs", "maxWallMs", "--session-max-wall-ms", true],
|
|
21
|
+
["sessionMaxToolMs", "maxToolMs", "--session-max-tool-ms", true],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const LIMIT_FIELDS = Object.freeze(
|
|
25
|
+
OPTION_FIELDS.map(([, field, flag, integer]) => ({
|
|
26
|
+
field,
|
|
27
|
+
flag,
|
|
28
|
+
integer,
|
|
29
|
+
})),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function normalizeLimit(raw, flag, { integer, allowZero = false } = {}) {
|
|
33
|
+
if (raw === undefined || raw === null || raw === "") return null;
|
|
34
|
+
const value = Number(raw);
|
|
35
|
+
if (
|
|
36
|
+
!Number.isFinite(value) ||
|
|
37
|
+
(allowZero ? value < 0 : value <= 0) ||
|
|
38
|
+
(integer && !Number.isSafeInteger(value))
|
|
39
|
+
) {
|
|
40
|
+
throw new TypeError(
|
|
41
|
+
`${flag} requires ${allowZero ? "a non-negative" : "a positive"}${integer ? " integer" : " number"}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveSessionBudgetRootOptions(options = {}) {
|
|
48
|
+
const limits = {};
|
|
49
|
+
let hasLimit = false;
|
|
50
|
+
for (const [optionName, field, flag, integer] of OPTION_FIELDS) {
|
|
51
|
+
const value = normalizeLimit(options[optionName], flag, {
|
|
52
|
+
integer,
|
|
53
|
+
allowZero: field === "maxDepth",
|
|
54
|
+
});
|
|
55
|
+
if (value !== null) {
|
|
56
|
+
limits[field] = value;
|
|
57
|
+
hasLimit = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
schema: SESSION_BUDGET_ROOT_SCHEMA,
|
|
62
|
+
enabled: options.sessionBudget === true || hasLimit,
|
|
63
|
+
limits: Object.freeze(limits),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizeSessionBudgetRootConfig(config) {
|
|
68
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
69
|
+
const error = new TypeError("session budget root config must be an object");
|
|
70
|
+
error.code = "CC_SESSION_BUDGET_CONFIG_INVALID";
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
if (config.schema !== SESSION_BUDGET_ROOT_SCHEMA) {
|
|
75
|
+
throw new TypeError("session budget root schema is unsupported");
|
|
76
|
+
}
|
|
77
|
+
if (typeof config.enabled !== "boolean") {
|
|
78
|
+
throw new TypeError("session budget root enabled flag is invalid");
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
!config.limits ||
|
|
82
|
+
typeof config.limits !== "object" ||
|
|
83
|
+
Array.isArray(config.limits)
|
|
84
|
+
) {
|
|
85
|
+
throw new TypeError("session budget root limits must be an object");
|
|
86
|
+
}
|
|
87
|
+
const allowed = new Set(LIMIT_FIELDS.map(({ field }) => field));
|
|
88
|
+
if (Object.keys(config.limits).some((field) => !allowed.has(field))) {
|
|
89
|
+
throw new TypeError("session budget root contains an unknown limit");
|
|
90
|
+
}
|
|
91
|
+
const limits = {};
|
|
92
|
+
for (const { field, flag, integer } of LIMIT_FIELDS) {
|
|
93
|
+
const value = normalizeLimit(config.limits[field], flag, {
|
|
94
|
+
integer,
|
|
95
|
+
allowZero: field === "maxDepth",
|
|
96
|
+
});
|
|
97
|
+
if (value !== null) limits[field] = value;
|
|
98
|
+
}
|
|
99
|
+
if (!config.enabled && Object.keys(limits).length > 0) {
|
|
100
|
+
throw new TypeError("disabled session budget root cannot contain limits");
|
|
101
|
+
}
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
schema: SESSION_BUDGET_ROOT_SCHEMA,
|
|
104
|
+
enabled: config.enabled,
|
|
105
|
+
limits: Object.freeze(limits),
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
error.code ||= "CC_SESSION_BUDGET_CONFIG_INVALID";
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function combineSignals(...signals) {
|
|
114
|
+
const active = signals.filter(Boolean);
|
|
115
|
+
if (active.length === 0) return null;
|
|
116
|
+
if (active.length === 1) return active[0];
|
|
117
|
+
return AbortSignal.any(active);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function budgetRootError(code, message, extra = {}) {
|
|
121
|
+
const error = new Error(message);
|
|
122
|
+
error.code = code;
|
|
123
|
+
Object.assign(error, extra);
|
|
124
|
+
return error;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function openProductionSessionBudgetRoot(
|
|
128
|
+
sessionId,
|
|
129
|
+
config,
|
|
130
|
+
{
|
|
131
|
+
persist = false,
|
|
132
|
+
signal = null,
|
|
133
|
+
table = undefined,
|
|
134
|
+
open = openSessionBudget,
|
|
135
|
+
store = undefined,
|
|
136
|
+
registry = undefined,
|
|
137
|
+
} = {},
|
|
138
|
+
) {
|
|
139
|
+
if (config?.enabled !== true) {
|
|
140
|
+
return Object.freeze({
|
|
141
|
+
enabled: false,
|
|
142
|
+
options: Object.freeze({}),
|
|
143
|
+
close: () => false,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!persist || !sessionId) {
|
|
147
|
+
throw budgetRootError(
|
|
148
|
+
"CC_SESSION_BUDGET_REQUIRES_DURABLE_SESSION",
|
|
149
|
+
"session budget root requires a durable --session/--resume/--continue target",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (typeof open !== "function") {
|
|
153
|
+
throw new TypeError("session budget root requires an opener");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const handle = open(String(sessionId), {
|
|
157
|
+
limits: config.limits || {},
|
|
158
|
+
table,
|
|
159
|
+
...(store ? { store } : {}),
|
|
160
|
+
...(registry ? { registry } : {}),
|
|
161
|
+
});
|
|
162
|
+
let closed = false;
|
|
163
|
+
try {
|
|
164
|
+
const status = handle.status();
|
|
165
|
+
if (status.recoveryRequired) {
|
|
166
|
+
throw budgetRootError(
|
|
167
|
+
"CC_SESSION_BUDGET_RECOVERY_REQUIRED",
|
|
168
|
+
`session budget recovery is required for ${sessionId}; inspect it with \`cc session budget status ${sessionId}\``,
|
|
169
|
+
{
|
|
170
|
+
sessionId: String(sessionId),
|
|
171
|
+
pendingRecovery: status.pendingRecovery,
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const combinedSignal = combineSignals(signal, handle.budget.signal);
|
|
176
|
+
return Object.freeze({
|
|
177
|
+
enabled: true,
|
|
178
|
+
sessionId: String(sessionId),
|
|
179
|
+
handle,
|
|
180
|
+
budget: handle.budget,
|
|
181
|
+
options: Object.freeze({
|
|
182
|
+
sessionBudget: handle.budget,
|
|
183
|
+
...(combinedSignal ? { signal: combinedSignal } : {}),
|
|
184
|
+
}),
|
|
185
|
+
close() {
|
|
186
|
+
if (closed) return false;
|
|
187
|
+
closed = true;
|
|
188
|
+
return handle.close();
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (!closed) {
|
|
193
|
+
closed = true;
|
|
194
|
+
try {
|
|
195
|
+
handle.close();
|
|
196
|
+
} catch {
|
|
197
|
+
// Preserve the admission/recovery failure that prevented dispatch.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function projectPending(snapshot) {
|
|
205
|
+
return [
|
|
206
|
+
...snapshot.inFlight.work.map((entry) => ({
|
|
207
|
+
authorityId: entry.id,
|
|
208
|
+
resourceType: "work",
|
|
209
|
+
kind: entry.kind,
|
|
210
|
+
...(entry.depth !== undefined ? { depth: entry.depth } : {}),
|
|
211
|
+
...(entry.elapsedMs !== undefined ? { elapsedMs: entry.elapsedMs } : {}),
|
|
212
|
+
})),
|
|
213
|
+
...snapshot.inFlight.tools.map((entry) => ({
|
|
214
|
+
authorityId: entry.id,
|
|
215
|
+
resourceType: "tool",
|
|
216
|
+
kind: entry.kind,
|
|
217
|
+
...(entry.elapsedMs !== undefined ? { elapsedMs: entry.elapsedMs } : {}),
|
|
218
|
+
})),
|
|
219
|
+
].sort((left, right) => left.authorityId.localeCompare(right.authorityId));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function projectRecoveryAdjudication(value) {
|
|
223
|
+
if (!value) return null;
|
|
224
|
+
return Object.freeze({
|
|
225
|
+
schema: value.schema,
|
|
226
|
+
count: value.count,
|
|
227
|
+
headDigest: value.headDigest,
|
|
228
|
+
last: Object.freeze({ ...value.last }),
|
|
229
|
+
...(value.history
|
|
230
|
+
? {
|
|
231
|
+
history: Object.freeze({
|
|
232
|
+
schema: value.history.schema,
|
|
233
|
+
baseSequence: value.history.baseSequence,
|
|
234
|
+
baseDigest: value.history.baseDigest,
|
|
235
|
+
retainedCount: value.history.entries.length,
|
|
236
|
+
complete: value.history.baseSequence === 0,
|
|
237
|
+
}),
|
|
238
|
+
}
|
|
239
|
+
: {}),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function readProductionSessionBudget(
|
|
244
|
+
sessionId,
|
|
245
|
+
{ store = new SessionBudgetSidecarStore() } = {},
|
|
246
|
+
) {
|
|
247
|
+
const record = store.read(String(sessionId));
|
|
248
|
+
if (!record) return null;
|
|
249
|
+
const snapshot = normalizeSessionResourceBudgetSnapshot(record.snapshot);
|
|
250
|
+
const pending = projectPending(snapshot);
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
schema: SESSION_BUDGET_ROOT_SCHEMA,
|
|
253
|
+
sessionId: String(sessionId),
|
|
254
|
+
revision: record.revision,
|
|
255
|
+
usageUnknown: record.usageUnknown === true,
|
|
256
|
+
limits: Object.freeze({ ...snapshot.limits }),
|
|
257
|
+
totals: Object.freeze({ ...snapshot.totals }),
|
|
258
|
+
state: Object.freeze({
|
|
259
|
+
started: snapshot.state.started,
|
|
260
|
+
abort: snapshot.state.abort
|
|
261
|
+
? Object.freeze({ ...snapshot.state.abort })
|
|
262
|
+
: null,
|
|
263
|
+
recoveryAdjudication: projectRecoveryAdjudication(
|
|
264
|
+
snapshot.state.recoveryAdjudication,
|
|
265
|
+
),
|
|
266
|
+
}),
|
|
267
|
+
recoveryRequired: pending.length > 0,
|
|
268
|
+
pendingRecovery: Object.freeze(
|
|
269
|
+
pending.map((entry) => Object.freeze(entry)),
|
|
270
|
+
),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function readProductionSessionBudgetRecoveryReceipts(
|
|
275
|
+
sessionId,
|
|
276
|
+
{ store = new SessionBudgetSidecarStore() } = {},
|
|
277
|
+
) {
|
|
278
|
+
const normalizedSessionId = String(sessionId);
|
|
279
|
+
const record = store.read(normalizedSessionId);
|
|
280
|
+
if (!record) return null;
|
|
281
|
+
const snapshot = normalizeSessionResourceBudgetSnapshot(record.snapshot);
|
|
282
|
+
const adjudication = snapshot.state.recoveryAdjudication;
|
|
283
|
+
const history = adjudication?.history || null;
|
|
284
|
+
const entries = history?.entries || [];
|
|
285
|
+
return Object.freeze({
|
|
286
|
+
schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
|
|
287
|
+
sessionId: normalizedSessionId,
|
|
288
|
+
revision: record.revision,
|
|
289
|
+
count: adjudication?.count || 0,
|
|
290
|
+
headDigest: adjudication?.headDigest || null,
|
|
291
|
+
baseSequence: history?.baseSequence ?? adjudication?.count ?? 0,
|
|
292
|
+
baseDigest: history
|
|
293
|
+
? history.baseDigest
|
|
294
|
+
: adjudication?.headDigest || null,
|
|
295
|
+
complete: adjudication
|
|
296
|
+
? Boolean(history && history.baseSequence === 0)
|
|
297
|
+
: true,
|
|
298
|
+
entries: Object.freeze(
|
|
299
|
+
entries.map((entry) => Object.freeze(structuredClone(entry))),
|
|
300
|
+
),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function adjudicateProductionSessionBudgetRecovery(
|
|
305
|
+
sessionId,
|
|
306
|
+
adjudication,
|
|
307
|
+
{
|
|
308
|
+
store = new SessionBudgetSidecarStore(),
|
|
309
|
+
registry = new Map(),
|
|
310
|
+
open = openSessionBudget,
|
|
311
|
+
} = {},
|
|
312
|
+
) {
|
|
313
|
+
const request = Array.isArray(adjudication)
|
|
314
|
+
? { abandoned: adjudication, settled: [] }
|
|
315
|
+
: adjudication && typeof adjudication === "object"
|
|
316
|
+
? {
|
|
317
|
+
abandoned: adjudication.abandoned || [],
|
|
318
|
+
settled: adjudication.settled || [],
|
|
319
|
+
}
|
|
320
|
+
: { abandoned: [], settled: [] };
|
|
321
|
+
if (request.abandoned.length === 0 && request.settled.length === 0) {
|
|
322
|
+
throw budgetRootError(
|
|
323
|
+
"CC_SESSION_BUDGET_RECOVERY_IDS_REQUIRED",
|
|
324
|
+
"session budget recovery requires every exact authority id as --abandon or --settlement",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
const handle = open(String(sessionId), { store, registry });
|
|
328
|
+
let result;
|
|
329
|
+
try {
|
|
330
|
+
const pending = handle.budget.pendingRecovery();
|
|
331
|
+
if (pending.length === 0) {
|
|
332
|
+
throw budgetRootError(
|
|
333
|
+
"CC_SESSION_BUDGET_RECOVERY_NOT_REQUIRED",
|
|
334
|
+
`session budget recovery is not required for ${sessionId}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
result = handle.budget.adjudicateRecovery(request);
|
|
338
|
+
if (!result.ok) {
|
|
339
|
+
throw budgetRootError(
|
|
340
|
+
"CC_SESSION_BUDGET_RECOVERY_INCOMPLETE",
|
|
341
|
+
"session budget recovery requires the exact pending authority-id set",
|
|
342
|
+
{ pending: result.pending },
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return Object.freeze({
|
|
346
|
+
schema: SESSION_BUDGET_ROOT_SCHEMA,
|
|
347
|
+
sessionId: String(sessionId),
|
|
348
|
+
abandoned: Object.freeze([...result.abandoned]),
|
|
349
|
+
settled: Object.freeze([...result.settled]),
|
|
350
|
+
adjudication: Object.freeze({ ...result.adjudication }),
|
|
351
|
+
receipt: Object.freeze(structuredClone(result.receipt)),
|
|
352
|
+
status: Object.freeze({ ...handle.status() }),
|
|
353
|
+
});
|
|
354
|
+
} finally {
|
|
355
|
+
handle.close();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function sessionBudgetAdmissionError(reason, operation) {
|
|
360
|
+
return budgetRootError(
|
|
361
|
+
"CC_SESSION_BUDGET_EXHAUSTED",
|
|
362
|
+
`session budget blocked ${operation}: ${reason || "session-aborted"}`,
|
|
363
|
+
{ budgetReason: reason || "session-aborted" },
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function sessionBudgetUsageUnknownError(operation, callId = null) {
|
|
368
|
+
return budgetRootError(
|
|
369
|
+
"CC_SESSION_BUDGET_USAGE_UNKNOWN",
|
|
370
|
+
`session budget requires usage adjudication after ${operation}`,
|
|
371
|
+
{
|
|
372
|
+
budgetReason: "provider-usage-unknown",
|
|
373
|
+
...(callId ? { callId: String(callId) } : {}),
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
}
|
|
@@ -206,12 +206,29 @@ function mergeUsageUnknownSnapshot(baseSnapshot, markerSnapshot, filePath) {
|
|
|
206
206
|
const work = new Map(base.inFlight.work.map((entry) => [entry.id, entry]));
|
|
207
207
|
for (const entry of markerEntries) {
|
|
208
208
|
const existing = work.get(entry.id);
|
|
209
|
-
if (existing
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
if (existing) {
|
|
210
|
+
if (
|
|
211
|
+
existing.kind !== entry.kind ||
|
|
212
|
+
(existing.depth ?? null) !== (entry.depth ?? null)
|
|
213
|
+
) {
|
|
214
|
+
throw runtimeError(
|
|
215
|
+
"corrupt",
|
|
216
|
+
filePath,
|
|
217
|
+
new TypeError("conflicting usage-unknown settlement identity"),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
work.set(entry.id, {
|
|
221
|
+
...existing,
|
|
222
|
+
...(existing.elapsedMs !== undefined || entry.elapsedMs !== undefined
|
|
223
|
+
? {
|
|
224
|
+
elapsedMs: Math.max(
|
|
225
|
+
Number(existing.elapsedMs || 0),
|
|
226
|
+
Number(entry.elapsedMs || 0),
|
|
227
|
+
),
|
|
228
|
+
}
|
|
229
|
+
: {}),
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
215
232
|
}
|
|
216
233
|
work.set(entry.id, { ...entry });
|
|
217
234
|
if (work.size + base.inFlight.tools.length > MAX_IN_FLIGHT_RESOURCES) {
|
|
@@ -879,14 +896,6 @@ export class SessionBudgetSidecarStore {
|
|
|
879
896
|
"ERR_SESSION_BUDGET_MARKER_CONFLICT",
|
|
880
897
|
);
|
|
881
898
|
}
|
|
882
|
-
if (resolveUsageUnknown && !clean) {
|
|
883
|
-
throw runtimeError(
|
|
884
|
-
"recovery",
|
|
885
|
-
filePath,
|
|
886
|
-
new Error("usage finalization snapshot must be clean"),
|
|
887
|
-
"ERR_SESSION_BUDGET_RECOVERY_REQUIRED",
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
899
|
const actualRevision = current?.revision ?? null;
|
|
891
900
|
if (actualRevision !== expectedRevision) {
|
|
892
901
|
throw runtimeError(
|
|
@@ -916,13 +925,24 @@ export class SessionBudgetSidecarStore {
|
|
|
916
925
|
snapshot: canonicalSnapshot,
|
|
917
926
|
};
|
|
918
927
|
writeRecordAt(filePath, record, this._fs, this._ioOptions);
|
|
928
|
+
let nextUsageUnknownMarker = null;
|
|
919
929
|
if (resolveUsageUnknown) {
|
|
920
|
-
|
|
930
|
+
if (clean) {
|
|
931
|
+
this._clearUsageUnknownLocked(id, expectedMarker);
|
|
932
|
+
} else {
|
|
933
|
+
nextUsageUnknownMarker = this._replaceUsageUnknownLocked(
|
|
934
|
+
id,
|
|
935
|
+
canonicalSnapshot,
|
|
936
|
+
expectedMarker,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
921
939
|
}
|
|
922
940
|
return {
|
|
923
941
|
revision,
|
|
924
942
|
filePath,
|
|
925
|
-
...(resolveUsageUnknown
|
|
943
|
+
...(resolveUsageUnknown
|
|
944
|
+
? { usageUnknownMarker: nextUsageUnknownMarker }
|
|
945
|
+
: {}),
|
|
926
946
|
};
|
|
927
947
|
},
|
|
928
948
|
{
|
|
@@ -935,7 +955,11 @@ export class SessionBudgetSidecarStore {
|
|
|
935
955
|
return result;
|
|
936
956
|
}
|
|
937
957
|
|
|
938
|
-
markUsageUnknown(
|
|
958
|
+
markUsageUnknown(
|
|
959
|
+
sessionId,
|
|
960
|
+
snapshot,
|
|
961
|
+
{ expectedUsageUnknownMarker = null } = {},
|
|
962
|
+
) {
|
|
939
963
|
const id = String(sessionId);
|
|
940
964
|
const filePath = this.pathForSession(id);
|
|
941
965
|
this._assertSupported(filePath);
|
|
@@ -945,6 +969,12 @@ export class SessionBudgetSidecarStore {
|
|
|
945
969
|
markerPath,
|
|
946
970
|
"snapshot",
|
|
947
971
|
);
|
|
972
|
+
const expectedMarker = expectedUsageUnknownMarker
|
|
973
|
+
? normalizeUsageUnknownMarkerIdentity(
|
|
974
|
+
expectedUsageUnknownMarker,
|
|
975
|
+
markerPath,
|
|
976
|
+
)
|
|
977
|
+
: null;
|
|
948
978
|
if (usageSettlementEntries(canonicalSnapshot).length === 0) {
|
|
949
979
|
throw runtimeError(
|
|
950
980
|
"marker",
|
|
@@ -965,6 +995,18 @@ export class SessionBudgetSidecarStore {
|
|
|
965
995
|
filePath,
|
|
966
996
|
() => {
|
|
967
997
|
const current = readRecordAt(markerPath, id, this._fs, this._ioOptions);
|
|
998
|
+
const actualMarker = usageUnknownMarkerIdentity(current);
|
|
999
|
+
const markerMatches = current
|
|
1000
|
+
? sameUsageUnknownMarkerIdentity(expectedMarker, actualMarker)
|
|
1001
|
+
: expectedMarker === null;
|
|
1002
|
+
if (!current && !markerMatches) {
|
|
1003
|
+
throw runtimeError(
|
|
1004
|
+
"conflict",
|
|
1005
|
+
markerPath,
|
|
1006
|
+
new Error("usage-unknown marker disappeared after it was observed"),
|
|
1007
|
+
"ERR_SESSION_BUDGET_MARKER_CONFLICT",
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
968
1010
|
if (current?.revision === Number.MAX_SAFE_INTEGER) {
|
|
969
1011
|
throw runtimeError(
|
|
970
1012
|
"revision",
|
|
@@ -997,7 +1039,7 @@ export class SessionBudgetSidecarStore {
|
|
|
997
1039
|
this._ioOptions,
|
|
998
1040
|
);
|
|
999
1041
|
const usageUnknownMarker = usageUnknownMarkerIdentity(committed);
|
|
1000
|
-
if (
|
|
1042
|
+
if (!markerMatches) {
|
|
1001
1043
|
// Preserve the newly reported settlement, but do not let a runtime
|
|
1002
1044
|
// that did not observe the prior marker later finalize both. The
|
|
1003
1045
|
// caller fails closed and a fresh recovery host must see the union.
|
|
@@ -1019,6 +1061,52 @@ export class SessionBudgetSidecarStore {
|
|
|
1019
1061
|
);
|
|
1020
1062
|
}
|
|
1021
1063
|
|
|
1064
|
+
_replaceUsageUnknownLocked(sessionId, snapshot, expectedMarker) {
|
|
1065
|
+
const id = String(sessionId);
|
|
1066
|
+
const markerPath = this.usageUnknownPathForSession(id);
|
|
1067
|
+
const marker = readRecordAt(markerPath, id, this._fs, this._ioOptions);
|
|
1068
|
+
const actualMarker = usageUnknownMarkerIdentity(marker);
|
|
1069
|
+
if (!sameUsageUnknownMarkerIdentity(expectedMarker, actualMarker)) {
|
|
1070
|
+
throw runtimeError(
|
|
1071
|
+
"conflict",
|
|
1072
|
+
markerPath,
|
|
1073
|
+
new Error("usage-unknown marker changed before settlement"),
|
|
1074
|
+
"ERR_SESSION_BUDGET_MARKER_CONFLICT",
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
if (marker.revision === Number.MAX_SAFE_INTEGER) {
|
|
1078
|
+
throw runtimeError(
|
|
1079
|
+
"revision",
|
|
1080
|
+
markerPath,
|
|
1081
|
+
new Error("usage-unknown marker revision is exhausted"),
|
|
1082
|
+
"ERR_SESSION_BUDGET_UNSAFE_REVISION",
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
const canonicalSnapshot = canonicalizeSnapshot(
|
|
1086
|
+
snapshot,
|
|
1087
|
+
markerPath,
|
|
1088
|
+
"snapshot",
|
|
1089
|
+
);
|
|
1090
|
+
if (usageSettlementEntries(canonicalSnapshot).length === 0) {
|
|
1091
|
+
throw runtimeError(
|
|
1092
|
+
"marker",
|
|
1093
|
+
markerPath,
|
|
1094
|
+
new TypeError("usage-unknown marker requires a pending settlement"),
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const record = {
|
|
1098
|
+
version: SESSION_BUDGET_SIDECAR_VERSION,
|
|
1099
|
+
sessionId: id,
|
|
1100
|
+
revision: marker.revision + 1,
|
|
1101
|
+
storedAt: new Date(this._now()).toISOString(),
|
|
1102
|
+
writer: { pid: process.pid },
|
|
1103
|
+
snapshot: canonicalSnapshot,
|
|
1104
|
+
};
|
|
1105
|
+
writeRecordAt(markerPath, record, this._fs, this._ioOptions);
|
|
1106
|
+
const committed = readRecordAt(markerPath, id, this._fs, this._ioOptions);
|
|
1107
|
+
return usageUnknownMarkerIdentity(committed);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1022
1110
|
clearUsageUnknown(sessionId, { expectedUsageUnknownMarker = null } = {}) {
|
|
1023
1111
|
const id = String(sessionId);
|
|
1024
1112
|
const filePath = this.pathForSession(id);
|
|
@@ -1194,6 +1282,9 @@ class SharedSessionBudgetRuntime {
|
|
|
1194
1282
|
const marker = this.store.markUsageUnknown(
|
|
1195
1283
|
this.sessionId,
|
|
1196
1284
|
change.snapshot,
|
|
1285
|
+
{
|
|
1286
|
+
expectedUsageUnknownMarker: this.usageUnknownMarker,
|
|
1287
|
+
},
|
|
1197
1288
|
);
|
|
1198
1289
|
const usageUnknownMarker = normalizeUsageUnknownMarkerIdentity(
|
|
1199
1290
|
marker?.usageUnknownMarker,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionBudgetAdmissionError,
|
|
3
|
+
sessionBudgetUsageUnknownError,
|
|
4
|
+
} from "./session-budget-production-root.js";
|
|
5
|
+
|
|
6
|
+
function isRootUsage(event) {
|
|
7
|
+
return !event?.attribution;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function beginSessionBudgetUsage(
|
|
11
|
+
sessionBudget,
|
|
12
|
+
event,
|
|
13
|
+
operation = "provider usage",
|
|
14
|
+
) {
|
|
15
|
+
if (
|
|
16
|
+
!isRootUsage(event) ||
|
|
17
|
+
typeof sessionBudget?.beginUsageSettlement !== "function"
|
|
18
|
+
) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const admission = sessionBudget.beginUsageSettlement({ id: event.callId });
|
|
22
|
+
if (!admission?.ok) {
|
|
23
|
+
throw sessionBudgetAdmissionError(admission?.reason, operation);
|
|
24
|
+
}
|
|
25
|
+
return admission;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function recordSessionBudgetUsage(
|
|
29
|
+
sessionBudget,
|
|
30
|
+
event,
|
|
31
|
+
operation = "provider usage settlement",
|
|
32
|
+
) {
|
|
33
|
+
if (!isRootUsage(event) || typeof sessionBudget?.recordUsage !== "function") {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const status = sessionBudget.recordUsage({
|
|
37
|
+
...(event?.callId ? { callId: event.callId } : {}),
|
|
38
|
+
provider: event?.provider || null,
|
|
39
|
+
model: event?.model || null,
|
|
40
|
+
usage: event?.usage || null,
|
|
41
|
+
});
|
|
42
|
+
if (status?.aborted) {
|
|
43
|
+
throw sessionBudgetAdmissionError(status.reason, operation);
|
|
44
|
+
}
|
|
45
|
+
return status;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function markSessionBudgetUsageUnknown(sessionBudget, event) {
|
|
49
|
+
if (
|
|
50
|
+
!isRootUsage(event) ||
|
|
51
|
+
typeof sessionBudget?.markUsageUnknown !== "function"
|
|
52
|
+
) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return sessionBudget.markUsageUnknown({ callId: event.callId });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function rejectSessionBudgetUsageUnknown(
|
|
59
|
+
event,
|
|
60
|
+
operation = "provider usage settlement",
|
|
61
|
+
) {
|
|
62
|
+
throw sessionBudgetUsageUnknownError(operation, event?.callId || null);
|
|
63
|
+
}
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
sanitizePersistedMessages,
|
|
28
28
|
} from "./session-message-provenance.js";
|
|
29
29
|
import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
|
|
30
|
+
import { normalizeSessionBudgetRootConfig } from "./session-budget-production-root.js";
|
|
30
31
|
|
|
31
32
|
export const SESSION_HOST_SNAPSHOT_SCHEMA =
|
|
32
33
|
"chainlesschain.session-host-snapshot/v1";
|
|
@@ -123,6 +124,14 @@ function titleFromEvents(events) {
|
|
|
123
124
|
return title;
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
function sessionBudgetRootFromEvents(events) {
|
|
128
|
+
const sessionStart = events.find((event) => event?.type === "session_start");
|
|
129
|
+
if (!Object.hasOwn(sessionStart?.data || {}, "sessionBudgetRoot")) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return normalizeSessionBudgetRootConfig(sessionStart.data.sessionBudgetRoot);
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
function validatePlainSessionEvents(events, sessionId) {
|
|
127
136
|
// Validate the complete nested graph without invoking accessors/Proxy traps,
|
|
128
137
|
// then retain the original in-process objects so an already-established
|
|
@@ -292,12 +301,14 @@ export function projectVerifiedSessionHostSnapshot(sessionId, events) {
|
|
|
292
301
|
}),
|
|
293
302
|
messages,
|
|
294
303
|
recovery,
|
|
304
|
+
sessionBudgetRoot: sessionBudgetRootFromEvents(safeEvents),
|
|
295
305
|
});
|
|
296
306
|
}
|
|
297
307
|
|
|
298
308
|
function createStreamingSessionHostProjection(sessionId) {
|
|
299
309
|
let title = "Untitled";
|
|
300
310
|
let lastEventType = null;
|
|
311
|
+
let sessionBudgetRoot = null;
|
|
301
312
|
const structure = createSessionTranscriptStructureProjection(sessionId, {
|
|
302
313
|
failFast: true,
|
|
303
314
|
});
|
|
@@ -313,8 +324,13 @@ function createStreamingSessionHostProjection(sessionId) {
|
|
|
313
324
|
throw new TypeError("Verified session event must be plain JSON data");
|
|
314
325
|
}
|
|
315
326
|
structure.accept(safeEvent);
|
|
316
|
-
if (safeEvent.type === "session_start"
|
|
317
|
-
title = String(safeEvent.data.title);
|
|
327
|
+
if (safeEvent.type === "session_start") {
|
|
328
|
+
if (safeEvent.data?.title) title = String(safeEvent.data.title);
|
|
329
|
+
if (Object.hasOwn(safeEvent.data || {}, "sessionBudgetRoot")) {
|
|
330
|
+
sessionBudgetRoot = normalizeSessionBudgetRootConfig(
|
|
331
|
+
safeEvent.data.sessionBudgetRoot,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
318
334
|
} else if (safeEvent.type === "session_rename" && safeEvent.data?.title) {
|
|
319
335
|
title = String(safeEvent.data.title);
|
|
320
336
|
}
|
|
@@ -347,6 +363,7 @@ function createStreamingSessionHostProjection(sessionId) {
|
|
|
347
363
|
}),
|
|
348
364
|
messages,
|
|
349
365
|
recovery,
|
|
366
|
+
sessionBudgetRoot,
|
|
350
367
|
});
|
|
351
368
|
},
|
|
352
369
|
};
|