chainlesschain 0.166.77 → 0.166.79
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 +9 -8
- package/completions/_cc +1 -1
- package/completions/cc.bash +1 -1
- package/completions/cc.fish +1 -1
- package/completions/cc.ps1 +1 -1
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +10 -0
- package/src/assets/linux-subreaper/linux-arm64/supervisor +0 -0
- package/src/assets/linux-subreaper/linux-x64/manifest.json +10 -0
- package/src/assets/linux-subreaper/linux-x64/supervisor +0 -0
- package/src/assets/web-panel/assets/{AIOps-CpiCBhRh.js → AIOps-CwD6Pm_Y.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CnU-Xvx-.js → ActionButton-Du5Rp-O6.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BJW-CNtf.js → Analytics-DY9l72ma.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-UFQJL3G9.js → AppLayout-7t1WIaCJ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr_IUDoY.js → Artifacts-BLIA5yny.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-aglWo6rr.js → Audit-BC_N0QTg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dm_TCiXC.js → BackgroundAgents-CA7SxDtg.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DUdX4-gA.js → Backup-BZ4AzS1_.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BTcLjy1o.js → BaseInput-BhiggGaH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Bhy9qZrl.js → Chat-CDdBoIBI.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-hsBsrl31.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CblrPa21.js → Checkbox-asoMKSEK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-SEAHzOlr.js → Codegen-B0gElLdm.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DJz8iFOR.js → Col-DrNzLpF6.js} +1 -1
- package/src/assets/web-panel/assets/{Community-RufKhcQt.js → Community-DCzB5qkI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dlifx08U.js → Compact-Db-Eua_j.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DdhQWcHh.js → Compliance-B76NByjl.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BLNn6oOm.js → Cowork-C-nK6Qpk.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Dv-JWY2d.js → Cron-C-GlQO19.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-k7Pz7O-b.js → Crosschain-Cuuz9IIh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C_10syUW.js → DID-gwFaT973.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CMyH6Szx.js → Dashboard-DHDt5jEf.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DjmD2Saa.js → Dropdown-CJ9kog-Y.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-36_IRUSo.js → EmailListRenderer-DOSXFRiQ.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-CmtU0XmM.js → EvolutionSettings-CHYMP7rR.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DEgf9kyP.js → FamilyGuardDashboard-C1gzG5EK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DGO1wQls.js → Federation-CEdNVWUw.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Doj1FmKW.js → FormItemContext-CwVOWPX8.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-GzOU27iT.js → GenericCardRenderer-CgNUs3kT.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dd7D1EDK.js → Git-DM4MNnQo.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-6IY8LcjI.js → Governance-DdOtjkvd.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BB9gl8q1.js → Inference-28PidFfr.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CeCed5wd.js → KnowledgeGraph-Bpj_qNUK.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CYOeHuaI.js → Logs-C6Ix19Ay.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-Bk8Zqc2R.js → MarkdownRenderer-Bj6mtPbf.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D-_xgHgM.js → Marketplace-DqkhRSMP.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-NnaUTF5t.js → McpTools-1WQ2SjaE.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CndN9pMt.js → Memory-BWTi7v6R.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BXblFmhY.js → MobileBridge-DB4wu3UE.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CC26Dm2k.js → MobileProjects-Bh9pWx2f.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BhaqWCiW.js → Mtc-BVF1Wrno.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-D6fktM2w.js → MtcAudit-DtNfslPf.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CFvahz7s.js → Multisig-4qcXP0bf.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Co73Ivya.js → NLProgramming-BRJl5Ehw.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B4g33L1o.js → Notes-BRY_bgMk.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-lXd17EbN.js → NotificationSettings-DirXMFz4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BiOB0GIi.js → OrderTableRenderer-BlrY8lwu.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-JQ7_wQIA.js → Organization-CVzQ1vO-.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-gwgJgozQ.js → Overflow-A3rXTPG4.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D9L_0gie.js → P2P-Db9ABWz0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CbOPsFE9.js → PdhVaultBrowser-CPv0H2OZ.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Cz42HNQf.js → Permissions-COnW4sIR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DjorYtrF.js → PersonalDataHub-C1hG7rp7.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DyWPhmm5.js → Pipeline---GLRDEe.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DyfLMl2j.js → Privacy-aaN9R3Lu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyqDx1OE.js → ProjectInit-C-kw49hE.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DsBw0Csy.js → ProjectSettings-y5tTkBTC.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DZHlhyPB.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CCI_klN-.js → Providers-LqxnV72o.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DNsnlDdS.js → QrScannerModal-D_q-Y5F0.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-B9r2iaee.js → QuickAsk-_E_KB5o8.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BJDe03GR.js → Recommend-BN8S5WJA.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-J7JR3rRo.js → RemoteSession-BFzrY_aW.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DR39AJeV.js → Reputation-C1HCLaTV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dh77m3NZ.js → Row-B8T2-Qv7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BUdg28nJ.js → RssFeed-CNQLk8v1.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BNC7sGhj.js → Search-BOx720Kc.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DISENGI7.js → Security-Ds8z6PF6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BBBGpJ1-.js → Services-C0-YkRSF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DMcIoIv2.js → Skeleton-DUHlSySD.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-QAyFbI3r.js → Skills-C8CBbbh0.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CSg5wLH7.js → Sla-lQltyYfB.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DMPxTc5x.js → SpeechSettings-B5vyOma-.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CPG7l30y.js → SyncSettings-BKDPu_y-.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Y1kOVvVL.js → Tasks-Bgvf7LTx.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-jt9NagL0.js → Templates-BMZyYYS7.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauuJICx.js → Tenant-7PYTig8f.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-E9rnKeGq.js → Terminal-C2W0LMdR.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Cc7z2APG.js → TimelineRenderer-CqREew_l.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CV0UthRA.js → Tokens-BhTyxTuD.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BFcJJGfu.js → Trigger-DxNSSZy_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BAydbNJA.js → Trust-Dy_wfbIh.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DcOS6sqL.js → UkeySign-DJC5ANqx.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BLHJZvif.js → VideoEditing-CNFevrO4.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-4QFLKHwW.js → Wallet-CXzuS1YK.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BocEOOSj.js → WebAuthn-BWS6HP8c.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DCfF7D0B.js → WorkflowEditor-swiN_7Vj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CxvfsYGr.js → chat-NwH7qlBs.js} +1 -1
- package/src/assets/web-panel/assets/{colors-KtQB9DmQ.js → colors-h6nRhBTw.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Dx4J6arN.js → compact-item-BQ6B6rP2.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cq0olgzo.js → createContext-CQkJuRcG.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bz6vqBGV.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Bf9-h3D-.js → hasIn-Diwl1WyI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpO6DKuu.js → index-0ZztPzIm.js} +1 -1
- package/src/assets/web-panel/assets/index-1b7DEw-w.js +1 -0
- package/src/assets/web-panel/assets/{index-DQDR-_ai.js → index-AzeSQJDK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcC4cbj.js → index-B3QgcUBV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO6XvO-C.js → index-B5gj_uCC.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClUfSChI.js → index-BI3ALCgN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3xfiItB.js → index-BRl3KUj0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFdEDvFn.js → index-BYYb8oeV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqnggDos.js → index-BcKDUGYf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_8UcDan.js → index-BcjlIMP9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmQuG0fr.js → index-BheCvudN.js} +1 -1
- package/src/assets/web-panel/assets/{index-iE9tqEN5.js → index-BjcARUtr.js} +1 -1
- package/src/assets/web-panel/assets/{index-nmyDOh_b.js → index-BnQUin2K.js} +1 -1
- package/src/assets/web-panel/assets/{index-Br1mDMfO.js → index-Bs6HyCfP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZWjYfWt.js → index-BvPz6vZ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-0fi8DIVQ.js → index-C3Ye1ISy.js} +1 -1
- package/src/assets/web-panel/assets/{index-hSzyoXri.js → index-C7-T-u0L.js} +3 -3
- package/src/assets/web-panel/assets/{index-DfrFNMvz.js → index-CRWpixu2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9yaUHm9.js → index-CatBQDSQ.js} +1 -1
- package/src/assets/web-panel/assets/index-CfD4tMuW.js +1 -0
- package/src/assets/web-panel/assets/{index-DMR-keCL.js → index-CqH-oOR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy865Hhq.js → index-CwsoVDi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DI5bRqNG.js → index-CyBtEVLM.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt5N6VM_.js → index-D5P498Rk.js} +1 -1
- package/src/assets/web-panel/assets/{index-6dDDSCHD.js → index-DKaB4xTf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKtQyb2o.js → index-DQYZa1Ci.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaIclw-E.js → index-DZ9CNcbJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-_FIz-0tp.js → index-DZAsByy4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBRlZdZj.js → index-D_HY0nyN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsCdl2Od.js → index-Da8frO_A.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cfsn7N18.js → index-DjB8IC9D.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeYP_kT9.js → index-Dwpthe2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8WuPI-h.js → index-Fm95dO4u.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK2U_AHi.js → index-GTefQSbY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnU-dUWd.js → index-OEWSrqit.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDAe1UmZ.js → index-TYMmUsxZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2BfSIrn.js → index-iHNOr_wL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz7f4Jaq.js → index-mxxZtsHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHmdMKYl.js → index-pYwYBVeQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DvzKJ6o5.js → initDefaultProps-BeUrpC_H.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D1yTvmxW.js → motion-CHW5EXt1.js} +1 -1
- package/src/assets/web-panel/assets/{move-DxtSIF8K.js → move-CWaoWJvF.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDsQgIHY.js → mtc-parser-CEC6Us1u.js} +1 -1
- package/src/assets/web-panel/assets/{omit-UVBJW8kh.js → omit-1D-kP_Qj.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D4QqtTyX.js → pickAttrs-CsS0myLl.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-QXmu6vBf.js → placementArrow-bMThOvkI.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D-PmXO9M.js → responsiveObserve-B72knbW8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CKbGxEZJ.js → slide-C3bI_2yy.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BbgXPkXm.js → statusUtils-0-FmcCAS.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DOr9rk68.js → styleChecker-DXUyqoWY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B2wO2r36.js → useFlexGapSupport-BSSnL3f1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-SeVGBK34.js → useFs-B0Cvvl0S.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DUJBCxjD.js → usePersonalDataHub-6D-d4WU5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CF4EYsig.js → vnode-zjIrlxT_.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-pRUs5pqI.js → zoom-Hw7NEisu.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/command-manifest.json +1 -1
- package/src/commands/checkpoint.js +42 -7
- package/src/commands/orchestrate.js +85 -25
- package/src/commands/session-location.js +5 -0
- package/src/commands/session-show.js +109 -1
- package/src/commands/session.js +26 -2
- package/src/commands/tokens.js +9 -4
- package/src/data/changelog.json +26 -0
- package/src/generated/cc-agent-protocol.schema.json +71 -0
- package/src/harness/jsonl-session-store.js +357 -114
- package/src/harness/mcp-client.js +44 -1
- package/src/lib/agent-router.js +35 -7
- package/src/lib/agent-sandbox.js +302 -2
- package/src/lib/app-server/server.js +14 -0
- package/src/lib/background-agent-supervisor.js +14 -4
- package/src/lib/causal-observability.js +64 -1
- package/src/lib/checkpoint-summary-projection.js +98 -0
- package/src/lib/checkpoint-timeline-authority.js +15 -51
- package/src/lib/claude-code-bridge.js +302 -60
- package/src/lib/codex-app-server-adapter.js +161 -35
- package/src/lib/cost-budget.js +4 -0
- package/src/lib/doctor-checkup.js +31 -2
- package/src/lib/durable-security-store.js +6 -1
- package/src/lib/evolution/evolution-artifact-identity.js +21 -0
- package/src/lib/evolution/evolution-artifact-ports.js +5 -11
- package/src/lib/evolution/evolution-eval-cohort-receipt-reconciliation.js +389 -0
- package/src/lib/execution-location-failure-site.js +63 -0
- package/src/lib/execution-location-target.js +102 -8
- package/src/lib/external-agent-adapters.js +2 -0
- package/src/lib/image-file-boundary.js +154 -0
- package/src/lib/image-input.js +16 -3
- package/src/lib/llm-pricing.js +114 -8
- package/src/lib/lsp/code-intelligence.js +7 -1
- package/src/lib/mcp-call-ledger-store.js +7 -2
- package/src/lib/model-capabilities.js +21 -10
- package/src/lib/model-context-catalog.js +60 -1
- package/src/lib/openai-responses.js +7 -1
- package/src/lib/orchestrator.js +8 -2
- package/src/lib/process-execution-broker/index.js +93 -1
- package/src/lib/process-execution-broker/linux-subreaper-artifact.js +120 -0
- package/src/lib/process-execution-broker/linux-subreaper-helper.js +344 -0
- package/src/lib/process-execution-broker/linux-subreaper-process.js +326 -0
- package/src/lib/process-execution-broker/linux-subreaper-supervisor.c +360 -0
- package/src/lib/process-execution-broker/owned-posix-process-group-worker.mjs +131 -0
- package/src/lib/process-execution-broker/owned-posix-process-group.js +267 -0
- package/src/lib/process-execution-broker/process-ownership-journal.js +297 -0
- package/src/lib/process-execution-broker/process-ownership-quarantine.js +91 -0
- package/src/lib/runtime-usage-ledger.js +3 -0
- package/src/lib/sandbox-docker-egress.js +507 -0
- package/src/lib/sandbox-egress-proxy.js +167 -10
- package/src/lib/sandbox-egress-worker-thread.js +38 -0
- package/src/lib/sandbox-egress-worker.js +196 -0
- package/src/lib/secure-fs.js +158 -42
- package/src/lib/session-branch-history.js +240 -0
- package/src/lib/session-history-origins.js +285 -0
- package/src/lib/session-input-receipt.js +110 -0
- package/src/lib/session-resource-budget.js +8 -8
- package/src/lib/session-transcript-history.js +456 -0
- package/src/lib/session-transcript-page.js +164 -0
- package/src/lib/session-transcript-reference.js +38 -0
- package/src/lib/session-usage.js +8 -0
- package/src/lib/token-tracker.js +32 -54
- package/src/lib/usage-pricing-context.js +93 -0
- package/src/runtime/agent-core.js +141 -21
- package/src/runtime/headless-stream.js +202 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D0WrJQaQ.js +0 -1
- package/src/assets/web-panel/assets/Projects-CRaSI25Z.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CmgDNkoO.js +0 -1
- package/src/assets/web-panel/assets/index-BpIZ2S01.js +0 -1
- package/src/assets/web-panel/assets/index-C-VmKuyv.js +0 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { canonicalDigest } from "@chainlesschain/context-memory-kernel";
|
|
2
|
+
import {
|
|
3
|
+
readVerifiedProjection,
|
|
4
|
+
projectWsTurnMessages,
|
|
5
|
+
} from "../harness/jsonl-session-store.js";
|
|
6
|
+
import { contextItemsToMessages } from "./context-memory-kernel/message-adapter.js";
|
|
7
|
+
import {
|
|
8
|
+
displayTranscriptText,
|
|
9
|
+
transcriptTextParts,
|
|
10
|
+
boundTranscriptRow,
|
|
11
|
+
} from "./session-transcript-page.js";
|
|
12
|
+
import { createSessionHistoryOrigins } from "./session-history-origins.js";
|
|
13
|
+
import { getDurableSystemMessageProvenance } from "./session-message-provenance.js";
|
|
14
|
+
import {
|
|
15
|
+
BRANCH_HISTORY_SCHEMA,
|
|
16
|
+
BRANCH_HISTORY_SYSTEM_ORIGINS_SCHEMA,
|
|
17
|
+
BRANCH_HISTORY_MESSAGE,
|
|
18
|
+
branchContextDigest,
|
|
19
|
+
withSessionBranchHistory,
|
|
20
|
+
createBranchHistoryImport,
|
|
21
|
+
} from "./session-branch-history.js";
|
|
22
|
+
|
|
23
|
+
const HASH = /^[a-f0-9]{64}$/u;
|
|
24
|
+
const SCHEMA = "chainlesschain.session-transcript-page/v2";
|
|
25
|
+
const CHANGES_SCHEMA = "chainlesschain.session-transcript-changes/v1";
|
|
26
|
+
const MAX_PAGE_BYTES = 1024 * 1024;
|
|
27
|
+
const MAX_HISTORY_RANGES = 16384;
|
|
28
|
+
|
|
29
|
+
// Enumerate all possible display sources, including replacement snapshots.
|
|
30
|
+
// Their physical sequence never changes when the selected history rewinds.
|
|
31
|
+
function eventMessages(event, imported = null) {
|
|
32
|
+
if (event.type === BRANCH_HISTORY_MESSAGE)
|
|
33
|
+
return imported?.kind === "archive" ? [imported.message] : [];
|
|
34
|
+
if (
|
|
35
|
+
["compact", "checkpoint_timeline_commit"].includes(event.type) &&
|
|
36
|
+
Array.isArray(event.data?.messages)
|
|
37
|
+
)
|
|
38
|
+
return event.data.messages;
|
|
39
|
+
if (["user_message", "assistant_message", "system"].includes(event.type))
|
|
40
|
+
return [event.data];
|
|
41
|
+
if (event.type === "ws_turn") {
|
|
42
|
+
const pair = projectWsTurnMessages(event);
|
|
43
|
+
if (pair) return [pair.user, pair.assistant];
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const displayable = (message) =>
|
|
48
|
+
["user", "assistant", "tool"].includes(message?.role);
|
|
49
|
+
|
|
50
|
+
// compact also historically meant import, rewind or a session-end snapshot.
|
|
51
|
+
// Only a verified Kernel compaction payload proves this is a context update.
|
|
52
|
+
// A copied fork retains the source payload, so do not compare its sessionId to
|
|
53
|
+
// the destination namespace (the store independently verifies fork authority).
|
|
54
|
+
function isContextCompaction(data) {
|
|
55
|
+
const canonical = data?.canonical;
|
|
56
|
+
if (
|
|
57
|
+
canonical?.schema !== "chainlesschain.context-compaction/v1" ||
|
|
58
|
+
canonical.schemaVersion !== 1 ||
|
|
59
|
+
!["committed", "degraded"].includes(canonical.status) ||
|
|
60
|
+
!Array.isArray(canonical.outputItems)
|
|
61
|
+
)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
const { digest, ...body } = canonical;
|
|
65
|
+
if (canonicalDigest(body, "chainlesschain.compaction-event/v1") !== digest)
|
|
66
|
+
return false;
|
|
67
|
+
const content = (messages) =>
|
|
68
|
+
messages
|
|
69
|
+
.filter((message) => message?.role !== "system")
|
|
70
|
+
.map(({ role, content, tool_calls, tool_call_id }) => ({
|
|
71
|
+
role,
|
|
72
|
+
content,
|
|
73
|
+
tool_calls,
|
|
74
|
+
tool_call_id,
|
|
75
|
+
}));
|
|
76
|
+
return (
|
|
77
|
+
canonicalDigest(
|
|
78
|
+
content(contextItemsToMessages(canonical.outputItems)),
|
|
79
|
+
) === canonicalDigest(content(data.messages))
|
|
80
|
+
);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function staleCursor() {
|
|
87
|
+
const error = new Error("Transcript history changed; reload the latest page");
|
|
88
|
+
error.code = "SESSION_TRANSCRIPT_CURSOR_STALE";
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createSessionTranscriptHistoryProjection(
|
|
93
|
+
sessionId,
|
|
94
|
+
{ limit = 50, cursor = null, after = null } = {},
|
|
95
|
+
branch = null,
|
|
96
|
+
) {
|
|
97
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
98
|
+
throw new TypeError("transcript page limit must be 1–100");
|
|
99
|
+
if (cursor !== null && after !== null)
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
"History before and after cursors are mutually exclusive",
|
|
102
|
+
);
|
|
103
|
+
const incremental = after !== null;
|
|
104
|
+
const encodedCursor = incremental ? after : cursor;
|
|
105
|
+
let anchor = null;
|
|
106
|
+
if (encodedCursor !== null) {
|
|
107
|
+
if (
|
|
108
|
+
typeof encodedCursor !== "string" ||
|
|
109
|
+
!/^[\w-]{1,1024}$/u.test(encodedCursor)
|
|
110
|
+
)
|
|
111
|
+
throw new TypeError("invalid transcript history cursor");
|
|
112
|
+
try {
|
|
113
|
+
anchor = JSON.parse(
|
|
114
|
+
Buffer.from(encodedCursor, "base64url").toString("utf8"),
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new TypeError("invalid transcript history cursor");
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
!anchor ||
|
|
121
|
+
anchor.sessionId !== sessionId ||
|
|
122
|
+
!HASH.test(anchor.generation) ||
|
|
123
|
+
!HASH.test(anchor.revision) ||
|
|
124
|
+
!Number.isSafeInteger(anchor.eventCount) ||
|
|
125
|
+
anchor.eventCount < 1 ||
|
|
126
|
+
(incremental
|
|
127
|
+
? anchor.v !== 1 ||
|
|
128
|
+
anchor.view !== "history-sync" ||
|
|
129
|
+
!Number.isSafeInteger(anchor.offset) ||
|
|
130
|
+
anchor.offset < 0
|
|
131
|
+
: anchor.v !== 2 ||
|
|
132
|
+
!Number.isSafeInteger(anchor.before) ||
|
|
133
|
+
anchor.before < 1)
|
|
134
|
+
)
|
|
135
|
+
throw new TypeError("invalid transcript history cursor");
|
|
136
|
+
}
|
|
137
|
+
let generation = null;
|
|
138
|
+
let eventCount = 0;
|
|
139
|
+
let count = 0;
|
|
140
|
+
let bytes = 0;
|
|
141
|
+
let rows = [];
|
|
142
|
+
let incrementalFull = false;
|
|
143
|
+
let anchored = !anchor;
|
|
144
|
+
let coverage = { kind: "from-origin", boundaryEvent: null, reason: null };
|
|
145
|
+
const origins = createSessionHistoryOrigins();
|
|
146
|
+
const branchImport = createBranchHistoryImport();
|
|
147
|
+
let physical = 0;
|
|
148
|
+
let ranges = [];
|
|
149
|
+
let needsReplay = false;
|
|
150
|
+
|
|
151
|
+
function displayRow(message, event, index, ordinal) {
|
|
152
|
+
return boundTranscriptRow({
|
|
153
|
+
id: `${sessionId}:${event.hash}:${index}`,
|
|
154
|
+
eventId: event.hash,
|
|
155
|
+
itemIndex: index,
|
|
156
|
+
ordinal,
|
|
157
|
+
role: message.role,
|
|
158
|
+
...displayTranscriptText(message.content),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function collect(message, event, index, ordinal) {
|
|
162
|
+
if (incremental) {
|
|
163
|
+
if (ordinal < anchor.offset || incrementalFull) return;
|
|
164
|
+
} else if (anchor && ordinal >= anchor.before) return;
|
|
165
|
+
const row = displayRow(message, event, index, ordinal);
|
|
166
|
+
const rowBytes = Buffer.byteLength(JSON.stringify(row));
|
|
167
|
+
if (
|
|
168
|
+
incremental &&
|
|
169
|
+
(rows.length >= limit || bytes + rowBytes > MAX_PAGE_BYTES)
|
|
170
|
+
) {
|
|
171
|
+
// Keep the FIRST contiguous prefix after the cursor. Skipping an oversized
|
|
172
|
+
// next row and admitting later small rows would permanently omit messages.
|
|
173
|
+
incrementalFull = true;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
rows.push(row);
|
|
177
|
+
bytes += rowBytes;
|
|
178
|
+
while (rows.length > limit || bytes > MAX_PAGE_BYTES)
|
|
179
|
+
bytes -= Buffer.byteLength(JSON.stringify(rows.shift()));
|
|
180
|
+
}
|
|
181
|
+
function append(message, event, index) {
|
|
182
|
+
if (!displayable(message)) return;
|
|
183
|
+
if (ranges.at(-1)?.end === physical) ranges.at(-1).end += 1;
|
|
184
|
+
else {
|
|
185
|
+
if (ranges.length >= MAX_HISTORY_RANGES) {
|
|
186
|
+
const error = new Error(
|
|
187
|
+
"Transcript history has too many disjoint ranges",
|
|
188
|
+
);
|
|
189
|
+
error.code = "SESSION_TRANSCRIPT_HISTORY_LIMIT";
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
ranges.push({ start: physical, end: physical + 1 });
|
|
193
|
+
}
|
|
194
|
+
physical += 1;
|
|
195
|
+
collect(message, event, index, count++);
|
|
196
|
+
}
|
|
197
|
+
function rewind(cutoff) {
|
|
198
|
+
let remaining = cutoff;
|
|
199
|
+
ranges = ranges.filter((range) => {
|
|
200
|
+
if (remaining === 0) return false;
|
|
201
|
+
const kept = Math.min(remaining, range.end - range.start);
|
|
202
|
+
range.end = range.start + kept;
|
|
203
|
+
remaining -= kept;
|
|
204
|
+
return true;
|
|
205
|
+
});
|
|
206
|
+
count = cutoff;
|
|
207
|
+
rows = [];
|
|
208
|
+
bytes = 0;
|
|
209
|
+
incrementalFull = false;
|
|
210
|
+
needsReplay = true;
|
|
211
|
+
}
|
|
212
|
+
function boundary(event, reason) {
|
|
213
|
+
coverage = { kind: "snapshot-boundary", boundaryEvent: event.hash, reason };
|
|
214
|
+
}
|
|
215
|
+
function visitSelected(authority, visit) {
|
|
216
|
+
let source = 0;
|
|
217
|
+
let rangeIndex = 0;
|
|
218
|
+
let ordinal = 0;
|
|
219
|
+
const replayImport = createBranchHistoryImport();
|
|
220
|
+
authority.replayEvents((event) => {
|
|
221
|
+
const imported = replayImport.accept(event);
|
|
222
|
+
eventMessages(event, imported).forEach((message, index) => {
|
|
223
|
+
if (!displayable(message)) return;
|
|
224
|
+
while (ranges[rangeIndex] && source >= ranges[rangeIndex].end)
|
|
225
|
+
rangeIndex += 1;
|
|
226
|
+
const range = ranges[rangeIndex];
|
|
227
|
+
if (range && source >= range.start)
|
|
228
|
+
visit(message, event, index, ordinal++);
|
|
229
|
+
source += 1;
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
replayImport.finish();
|
|
233
|
+
if (ordinal !== count || source !== physical)
|
|
234
|
+
throw new Error("Transcript history replay did not match its selection");
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
accept(event) {
|
|
238
|
+
if (!HASH.test(event.hash))
|
|
239
|
+
throw new TypeError("verified event hash required");
|
|
240
|
+
eventCount += 1;
|
|
241
|
+
generation ||= event.hash;
|
|
242
|
+
const imported = branchImport.accept(event);
|
|
243
|
+
const messages = eventMessages(event, imported);
|
|
244
|
+
if (imported?.kind === "start") {
|
|
245
|
+
coverage = { ...imported.coverage };
|
|
246
|
+
} else if (imported?.kind === "archive") {
|
|
247
|
+
append(messages[0], event, 0);
|
|
248
|
+
} else if (imported?.kind === "archive-part") {
|
|
249
|
+
// A logical row may span several bounded persistence records.
|
|
250
|
+
} else if (imported?.kind === "context") {
|
|
251
|
+
physical += messages.filter(displayable).length;
|
|
252
|
+
} else if (imported?.kind === "origin") {
|
|
253
|
+
origins.appendMappedPersisted(imported.message, imported.origin);
|
|
254
|
+
} else if (imported?.kind === "complete") {
|
|
255
|
+
// The complete import is independently anchored in this session.
|
|
256
|
+
} else if (
|
|
257
|
+
["compact", "checkpoint_timeline_commit"].includes(event.type) &&
|
|
258
|
+
Array.isArray(event.data?.messages)
|
|
259
|
+
) {
|
|
260
|
+
const cutoff =
|
|
261
|
+
event.type === "checkpoint_timeline_commit"
|
|
262
|
+
? origins.rewind(event, count)
|
|
263
|
+
: null;
|
|
264
|
+
if (cutoff !== null) {
|
|
265
|
+
rewind(cutoff);
|
|
266
|
+
generation = event.hash;
|
|
267
|
+
physical += messages.filter(displayable).length;
|
|
268
|
+
} else if (
|
|
269
|
+
event.type === "checkpoint_timeline_commit" &&
|
|
270
|
+
origins.summarize(event)
|
|
271
|
+
) {
|
|
272
|
+
// The verified rewrite changes model context only. Original rows,
|
|
273
|
+
// coverage and old display cursors continue to describe the archive.
|
|
274
|
+
physical += messages.filter(displayable).length;
|
|
275
|
+
} else if (
|
|
276
|
+
event.type === "compact" &&
|
|
277
|
+
count > 0 &&
|
|
278
|
+
isContextCompaction(event.data)
|
|
279
|
+
) {
|
|
280
|
+
origins.compact(event.data);
|
|
281
|
+
physical += messages.filter(displayable).length;
|
|
282
|
+
} else {
|
|
283
|
+
// Legacy or unverifiable lineage can authorize only its snapshot.
|
|
284
|
+
generation = event.hash;
|
|
285
|
+
count = 0;
|
|
286
|
+
bytes = 0;
|
|
287
|
+
rows = [];
|
|
288
|
+
incrementalFull = false;
|
|
289
|
+
ranges = [];
|
|
290
|
+
needsReplay = false;
|
|
291
|
+
origins.snapshot(messages);
|
|
292
|
+
boundary(
|
|
293
|
+
event,
|
|
294
|
+
event.type === "checkpoint_timeline_commit"
|
|
295
|
+
? "timeline-replacement"
|
|
296
|
+
: "context-snapshot",
|
|
297
|
+
);
|
|
298
|
+
messages.forEach((message, index) => append(message, event, index));
|
|
299
|
+
}
|
|
300
|
+
} else if (event.type === "session_branch") {
|
|
301
|
+
boundary(event, "branch-snapshot");
|
|
302
|
+
} else {
|
|
303
|
+
messages.forEach((message, index) => {
|
|
304
|
+
origins.appendPersisted(message, count);
|
|
305
|
+
append(message, event, index);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (anchor && eventCount === anchor.eventCount) {
|
|
309
|
+
anchored =
|
|
310
|
+
event.hash === anchor.revision &&
|
|
311
|
+
(incremental ? anchor.offset : anchor.before) <= count &&
|
|
312
|
+
generation === anchor.generation;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
finish(authority) {
|
|
316
|
+
branchImport.finish();
|
|
317
|
+
if (branch) {
|
|
318
|
+
const prefix = origins.branchPrefix(
|
|
319
|
+
branch.messages,
|
|
320
|
+
authority.headHash,
|
|
321
|
+
count,
|
|
322
|
+
);
|
|
323
|
+
if (!prefix) return branch.consume(null);
|
|
324
|
+
rewind(prefix.cutoff);
|
|
325
|
+
const contextEntries = prefix.entries.filter(
|
|
326
|
+
({ message }) =>
|
|
327
|
+
["user", "assistant"].includes(message.role) ||
|
|
328
|
+
(message.role === "system" &&
|
|
329
|
+
getDurableSystemMessageProvenance(message)),
|
|
330
|
+
);
|
|
331
|
+
const contextOrigins = contextEntries.map(({ origin }) => origin);
|
|
332
|
+
return withSessionBranchHistory(
|
|
333
|
+
{
|
|
334
|
+
parentSessionId: sessionId,
|
|
335
|
+
contextDigest: branchContextDigest(branch.messages),
|
|
336
|
+
contextOrigins,
|
|
337
|
+
descriptor: {
|
|
338
|
+
schema: contextEntries.some(
|
|
339
|
+
({ message, origin }) => message.role === "system" && origin,
|
|
340
|
+
)
|
|
341
|
+
? BRANCH_HISTORY_SYSTEM_ORIGINS_SCHEMA
|
|
342
|
+
: BRANCH_HISTORY_SCHEMA,
|
|
343
|
+
sourceGeneration: generation,
|
|
344
|
+
totalMessages: count,
|
|
345
|
+
contextMessageCount: contextOrigins.length,
|
|
346
|
+
coverage: { ...coverage },
|
|
347
|
+
},
|
|
348
|
+
visit(visitor) {
|
|
349
|
+
visitSelected(authority, (message, event, index) => {
|
|
350
|
+
visitor({
|
|
351
|
+
role: message.role,
|
|
352
|
+
text: transcriptTextParts(message.content).join(""),
|
|
353
|
+
sourceEventId: event.hash,
|
|
354
|
+
sourceItemIndex: index,
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
branch.consume,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (anchor && (!anchored || generation !== anchor.generation))
|
|
363
|
+
throw staleCursor();
|
|
364
|
+
if (needsReplay) {
|
|
365
|
+
// Scan under the same store lock and revalidate the same exact head.
|
|
366
|
+
// Even a rewind into a prefix older than the page buffer stays bounded.
|
|
367
|
+
rows = [];
|
|
368
|
+
bytes = 0;
|
|
369
|
+
incrementalFull = false;
|
|
370
|
+
visitSelected(authority, collect);
|
|
371
|
+
}
|
|
372
|
+
const first = rows[0]?.ordinal ?? 0;
|
|
373
|
+
const snapshot = {
|
|
374
|
+
sessionId,
|
|
375
|
+
generation,
|
|
376
|
+
revision: authority.headHash,
|
|
377
|
+
eventCount: authority.eventCount,
|
|
378
|
+
totalMessages: count,
|
|
379
|
+
messages: rows,
|
|
380
|
+
contextOnly: false,
|
|
381
|
+
coverage,
|
|
382
|
+
};
|
|
383
|
+
const syncCursor = (offset) =>
|
|
384
|
+
Buffer.from(
|
|
385
|
+
JSON.stringify({
|
|
386
|
+
v: 1,
|
|
387
|
+
view: "history-sync",
|
|
388
|
+
sessionId,
|
|
389
|
+
generation,
|
|
390
|
+
revision: authority.headHash,
|
|
391
|
+
eventCount: authority.eventCount,
|
|
392
|
+
offset,
|
|
393
|
+
}),
|
|
394
|
+
).toString("base64url");
|
|
395
|
+
if (incremental) {
|
|
396
|
+
const nextOffset = rows.length
|
|
397
|
+
? rows.at(-1).ordinal + 1
|
|
398
|
+
: anchor.offset;
|
|
399
|
+
return {
|
|
400
|
+
schema: CHANGES_SCHEMA,
|
|
401
|
+
...snapshot,
|
|
402
|
+
from: anchor.offset,
|
|
403
|
+
nextCursor: syncCursor(nextOffset),
|
|
404
|
+
hasMore: nextOffset < count,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
schema: SCHEMA,
|
|
409
|
+
...snapshot,
|
|
410
|
+
// Only the latest page establishes an update baseline. Older navigation
|
|
411
|
+
// must not advance it past messages the latest view has not received.
|
|
412
|
+
syncCursor:
|
|
413
|
+
!anchor && authority.eventCount > 0 ? syncCursor(count) : null,
|
|
414
|
+
nextCursor:
|
|
415
|
+
first > 0
|
|
416
|
+
? Buffer.from(
|
|
417
|
+
JSON.stringify({
|
|
418
|
+
v: 2,
|
|
419
|
+
sessionId,
|
|
420
|
+
generation,
|
|
421
|
+
revision: authority.headHash,
|
|
422
|
+
eventCount: authority.eventCount,
|
|
423
|
+
before: first,
|
|
424
|
+
}),
|
|
425
|
+
).toString("base64url")
|
|
426
|
+
: null,
|
|
427
|
+
};
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function createSessionTranscriptBranchProjection(
|
|
433
|
+
sessionId,
|
|
434
|
+
messages,
|
|
435
|
+
consume,
|
|
436
|
+
) {
|
|
437
|
+
if (
|
|
438
|
+
!Array.isArray(messages) ||
|
|
439
|
+
typeof consume !== "function" ||
|
|
440
|
+
consume.constructor?.name === "AsyncFunction"
|
|
441
|
+
)
|
|
442
|
+
throw new TypeError(
|
|
443
|
+
"Branch history requires messages and a synchronous consumer",
|
|
444
|
+
);
|
|
445
|
+
return createSessionTranscriptHistoryProjection(
|
|
446
|
+
sessionId,
|
|
447
|
+
{},
|
|
448
|
+
{ messages, consume },
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function readSessionTranscriptHistory(sessionId, options = {}) {
|
|
453
|
+
return readVerifiedProjection(sessionId, () =>
|
|
454
|
+
createSessionTranscriptHistoryProjection(sessionId, options),
|
|
455
|
+
);
|
|
456
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
readVerifiedProjection,
|
|
4
|
+
projectWsTurnMessages,
|
|
5
|
+
} from "../harness/jsonl-session-store.js";
|
|
6
|
+
|
|
7
|
+
const MAX_PAGE_BYTES = 1024 * 1024;
|
|
8
|
+
const MAX_TEXT_CHARS = 200000;
|
|
9
|
+
|
|
10
|
+
export function transcriptTextParts(content) {
|
|
11
|
+
return typeof content === "string"
|
|
12
|
+
? [content]
|
|
13
|
+
: Array.isArray(content)
|
|
14
|
+
? content.map((part) =>
|
|
15
|
+
typeof part?.text === "string"
|
|
16
|
+
? part.text
|
|
17
|
+
: ["image", "image_url", "input_image"].includes(part?.type)
|
|
18
|
+
? "[image]"
|
|
19
|
+
: "",
|
|
20
|
+
)
|
|
21
|
+
: [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function displayTranscriptText(content) {
|
|
25
|
+
const parts = transcriptTextParts(content);
|
|
26
|
+
let text = "";
|
|
27
|
+
let truncated = false;
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
const remaining = MAX_TEXT_CHARS - text.length;
|
|
30
|
+
text += part.slice(0, remaining);
|
|
31
|
+
if (part.length > remaining) truncated = true;
|
|
32
|
+
}
|
|
33
|
+
return { text, truncated };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Escaped control characters can exceed the JSON byte budget before the
|
|
37
|
+
// character limit. Keep a shortened row rather than silently dropping it.
|
|
38
|
+
export function boundTranscriptRow(row) {
|
|
39
|
+
if (Buffer.byteLength(JSON.stringify(row)) <= MAX_PAGE_BYTES) return row;
|
|
40
|
+
const text = row.text;
|
|
41
|
+
row.truncated = true;
|
|
42
|
+
let low = 0;
|
|
43
|
+
let high = text.length - 1;
|
|
44
|
+
while (low < high) {
|
|
45
|
+
const middle = Math.ceil((low + high) / 2);
|
|
46
|
+
row.text = text.slice(0, middle);
|
|
47
|
+
if (Buffer.byteLength(JSON.stringify(row)) <= MAX_PAGE_BYTES) low = middle;
|
|
48
|
+
else high = middle - 1;
|
|
49
|
+
}
|
|
50
|
+
row.text = text.slice(0, low);
|
|
51
|
+
return row;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createSessionTranscriptPageProjection(
|
|
55
|
+
sessionId,
|
|
56
|
+
{ limit = 50, cursor = null } = {},
|
|
57
|
+
) {
|
|
58
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
59
|
+
throw new TypeError("transcript page limit must be 1–100");
|
|
60
|
+
let before = Infinity;
|
|
61
|
+
let expectedGeneration = null;
|
|
62
|
+
if (cursor != null) {
|
|
63
|
+
if (
|
|
64
|
+
typeof cursor !== "string" ||
|
|
65
|
+
cursor.length > 1024 ||
|
|
66
|
+
!/^[\w-]+$/u.test(cursor)
|
|
67
|
+
)
|
|
68
|
+
throw new TypeError("invalid transcript cursor");
|
|
69
|
+
const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
70
|
+
if (
|
|
71
|
+
value.sessionId !== sessionId ||
|
|
72
|
+
!Number.isSafeInteger(value.before) ||
|
|
73
|
+
value.before < 0 ||
|
|
74
|
+
!/^[a-f0-9]{64}$/u.test(value.generation)
|
|
75
|
+
)
|
|
76
|
+
throw new TypeError("invalid transcript cursor");
|
|
77
|
+
before = value.before;
|
|
78
|
+
expectedGeneration = value.generation;
|
|
79
|
+
}
|
|
80
|
+
let generation = null;
|
|
81
|
+
let count = 0;
|
|
82
|
+
let bytes = 0;
|
|
83
|
+
let rows = [];
|
|
84
|
+
const identity = (event) =>
|
|
85
|
+
/^[a-f0-9]{64}$/u.test(event.hash || "")
|
|
86
|
+
? event.hash
|
|
87
|
+
: createHash("sha256").update(JSON.stringify(event)).digest("hex");
|
|
88
|
+
function append(message) {
|
|
89
|
+
if (!["user", "assistant", "tool"].includes(message?.role)) return;
|
|
90
|
+
const ordinal = count++;
|
|
91
|
+
if (ordinal >= before) return;
|
|
92
|
+
const content = displayTranscriptText(message.content);
|
|
93
|
+
const row = boundTranscriptRow({
|
|
94
|
+
id: `${generation}:${ordinal}`,
|
|
95
|
+
ordinal,
|
|
96
|
+
role: message.role,
|
|
97
|
+
...content,
|
|
98
|
+
});
|
|
99
|
+
rows.push(row);
|
|
100
|
+
bytes += Buffer.byteLength(JSON.stringify(row));
|
|
101
|
+
while (rows.length > limit || bytes > MAX_PAGE_BYTES)
|
|
102
|
+
bytes -= Buffer.byteLength(JSON.stringify(rows.shift()));
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
accept(event) {
|
|
106
|
+
if (!generation) generation = identity(event);
|
|
107
|
+
if (
|
|
108
|
+
(event.type === "compact" ||
|
|
109
|
+
event.type === "checkpoint_timeline_commit") &&
|
|
110
|
+
Array.isArray(event.data?.messages)
|
|
111
|
+
) {
|
|
112
|
+
// Match canonical resume semantics. A rewind or replacement checkpoint
|
|
113
|
+
// invalidates every old cursor; never revive discarded messages.
|
|
114
|
+
generation = identity(event);
|
|
115
|
+
count = 0;
|
|
116
|
+
bytes = 0;
|
|
117
|
+
rows = [];
|
|
118
|
+
for (const message of event.data.messages) append(message);
|
|
119
|
+
} else if (
|
|
120
|
+
["user_message", "assistant_message", "system"].includes(event.type)
|
|
121
|
+
)
|
|
122
|
+
append(event.data);
|
|
123
|
+
else if (event.type === "ws_turn") {
|
|
124
|
+
const pair = projectWsTurnMessages(event);
|
|
125
|
+
if (pair) {
|
|
126
|
+
append(pair.user);
|
|
127
|
+
append(pair.assistant);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
finish(authority) {
|
|
132
|
+
if (expectedGeneration && generation !== expectedGeneration) {
|
|
133
|
+
const error = new Error(
|
|
134
|
+
"Transcript generation changed; reload the latest page",
|
|
135
|
+
);
|
|
136
|
+
error.code = "SESSION_TRANSCRIPT_CURSOR_STALE";
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const first = rows[0]?.ordinal ?? 0;
|
|
140
|
+
return {
|
|
141
|
+
schema: "chainlesschain.session-transcript-page/v1",
|
|
142
|
+
sessionId,
|
|
143
|
+
generation,
|
|
144
|
+
revision: authority.headHash,
|
|
145
|
+
eventCount: authority.eventCount,
|
|
146
|
+
totalMessages: count,
|
|
147
|
+
messages: rows,
|
|
148
|
+
nextCursor:
|
|
149
|
+
first > 0
|
|
150
|
+
? Buffer.from(
|
|
151
|
+
JSON.stringify({ sessionId, generation, before: first }),
|
|
152
|
+
).toString("base64url")
|
|
153
|
+
: null,
|
|
154
|
+
contextOnly: true,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function readSessionTranscriptPage(sessionId, options = {}) {
|
|
161
|
+
return readVerifiedProjection(sessionId, () =>
|
|
162
|
+
createSessionTranscriptPageProjection(sessionId, options),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const HASH = /^[a-f0-9]{64}$/u;
|
|
2
|
+
const hash = (value) => typeof value === "string" && HASH.test(value);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display association only. Message writers return a synchronous append receipt,
|
|
6
|
+
* not the event body. The caller determines the role from the writer it invoked;
|
|
7
|
+
* consumers still need the history reader's full chain/anchor verification.
|
|
8
|
+
*/
|
|
9
|
+
export function transcriptMessageEventId(receipt) {
|
|
10
|
+
if (!receipt || receipt.commitState !== "committed" || !hash(receipt.hash))
|
|
11
|
+
return null;
|
|
12
|
+
return receipt.hash;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function transcriptResultReferences({
|
|
16
|
+
sessionId,
|
|
17
|
+
userEventId = null,
|
|
18
|
+
assistantEventId = null,
|
|
19
|
+
clientMessageId = null,
|
|
20
|
+
}) {
|
|
21
|
+
if (
|
|
22
|
+
typeof sessionId !== "string" ||
|
|
23
|
+
!sessionId ||
|
|
24
|
+
sessionId.length > 256 ||
|
|
25
|
+
!hash(assistantEventId)
|
|
26
|
+
)
|
|
27
|
+
return null;
|
|
28
|
+
return {
|
|
29
|
+
schema: "chainlesschain.session-transcript-references/v1",
|
|
30
|
+
sessionId,
|
|
31
|
+
assistantEventId,
|
|
32
|
+
...(hash(userEventId) ? { userEventId } : {}),
|
|
33
|
+
...(typeof clientMessageId === "string" &&
|
|
34
|
+
/^[a-zA-Z0-9_-]{1,80}$/u.test(clientMessageId)
|
|
35
|
+
? { clientMessageId }
|
|
36
|
+
: {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/src/lib/session-usage.js
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
listJsonlSessions,
|
|
14
14
|
} from "../harness/jsonl-session-store.js";
|
|
15
15
|
import { extractPluginUsageAttribution } from "./plugin-usage-attribution.js";
|
|
16
|
+
import {
|
|
17
|
+
projectUsagePricingContext,
|
|
18
|
+
mergeUsagePricingBucket as mergePricingBucket,
|
|
19
|
+
} from "./usage-pricing-context.js";
|
|
16
20
|
|
|
17
21
|
// Injectable disk seams (tests override). readEvents skips malformed JSON lines
|
|
18
22
|
// but does NOT guard readFileSync, so an unreadable session file (EACCES /
|
|
@@ -140,6 +144,7 @@ export function extractUsage(event) {
|
|
|
140
144
|
cacheCreationTokens,
|
|
141
145
|
timestamp: event.timestamp || null,
|
|
142
146
|
...(attribution ? { attribution } : {}),
|
|
147
|
+
...projectUsagePricingContext(raw),
|
|
143
148
|
};
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -560,6 +565,7 @@ export function aggregateUsage(events) {
|
|
|
560
565
|
entry.cacheReadTokens += u.cacheReadTokens || 0;
|
|
561
566
|
entry.cacheCreationTokens += u.cacheCreationTokens || 0;
|
|
562
567
|
entry.calls += 1;
|
|
568
|
+
mergePricingBucket(entry, u);
|
|
563
569
|
byKey.set(key, entry);
|
|
564
570
|
}
|
|
565
571
|
|
|
@@ -634,6 +640,8 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
|
|
|
634
640
|
entry.cacheReadTokens += row.cacheReadTokens || 0;
|
|
635
641
|
entry.cacheCreationTokens += row.cacheCreationTokens || 0;
|
|
636
642
|
entry.calls += row.calls;
|
|
643
|
+
for (const bucket of row.pricingBuckets || [])
|
|
644
|
+
mergePricingBucket(entry, bucket);
|
|
637
645
|
byKey.set(key, entry);
|
|
638
646
|
}
|
|
639
647
|
}
|