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.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
  106. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  107. package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
  116. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  117. package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +2 -2
  151. package/src/commands/agent.js +90 -1
  152. package/src/commands/chat.js +11 -2
  153. package/src/commands/compact.js +106 -23
  154. package/src/commands/loop.js +28 -10
  155. package/src/commands/routine.js +1 -1
  156. package/src/commands/scheduler-daemon.js +359 -2
  157. package/src/commands/session-observability.js +305 -0
  158. package/src/commands/session.js +38 -0
  159. package/src/data/changelog.json +14 -1
  160. package/src/gateways/ws/ws-agent-handler.js +439 -5
  161. package/src/harness/jsonl-session-store.js +463 -125
  162. package/src/harness/prompt-compressor.js +86 -13
  163. package/src/harness/provider-backed-compaction.js +100 -27
  164. package/src/lib/advisor-runtime.js +3 -1
  165. package/src/lib/agent-schedule-store.js +414 -4
  166. package/src/lib/agent-team/team-adjudication.js +40 -1
  167. package/src/lib/automation-engine.js +494 -0
  168. package/src/lib/background-agent-supervisor.js +793 -215
  169. package/src/lib/background-command-argv.js +24 -0
  170. package/src/lib/background-interaction-journal.js +361 -52
  171. package/src/lib/background-interaction-resolver.js +32 -0
  172. package/src/lib/background-launch-profile.js +2 -0
  173. package/src/lib/causal-observability.js +2661 -0
  174. package/src/lib/chat-session-admission.js +76 -0
  175. package/src/lib/cost-budget.js +5 -7
  176. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  177. package/src/lib/cowork/debate-review-cli.js +12 -0
  178. package/src/lib/cowork-adapter.js +159 -38
  179. package/src/lib/cowork-cron.js +357 -1
  180. package/src/lib/cowork-task-runner.js +112 -17
  181. package/src/lib/delivery-coordinator.js +142 -0
  182. package/src/lib/direct-model-usage.js +121 -0
  183. package/src/lib/file-lines.js +183 -47
  184. package/src/lib/llm-pricing.js +12 -17
  185. package/src/lib/loop.js +26 -2
  186. package/src/lib/routine-store.js +380 -21
  187. package/src/lib/runtime-usage-ledger.js +182 -0
  188. package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
  189. package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
  190. package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
  191. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
  192. package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
  193. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  194. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  195. package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
  196. package/src/lib/scheduler-kernel/runtime.js +63 -1
  197. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  198. package/src/lib/scheduler-kernel/store.js +2586 -85
  199. package/src/lib/session-host-lease.js +171 -2
  200. package/src/lib/session-host-snapshot.js +14 -3
  201. package/src/lib/session-persistence-failure.js +1 -0
  202. package/src/lib/session-tail.js +179 -34
  203. package/src/lib/session-transcript-structure.js +96 -0
  204. package/src/lib/sub-agent-context.js +328 -22
  205. package/src/lib/ws-agent-handler.js +4 -1
  206. package/src/repl/agent-repl.js +732 -91
  207. package/src/repl/chat-repl.js +5 -1
  208. package/src/runtime/agent-core.js +1272 -207
  209. package/src/runtime/fallback-model.js +5 -0
  210. package/src/runtime/headless-runner.js +324 -82
  211. package/src/runtime/headless-stream.js +232 -26
  212. package/src/runtime/policies/agent-policy.js +4 -0
  213. package/src/workers/background-agent-worker.js +403 -96
  214. package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
  215. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
  216. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
  217. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
  218. 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
- iterateFileLinesSync,
51
- iterateFileLinesReverseSync,
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 tail = Buffer.allocUnsafe(size - tailStart);
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
- JSON.parse(text);
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 = iterateFileLinesReverseSync(filePath);
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 = JSON.parse(current.value.line);
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 iterateFileLinesReverseSync(filePath)) {
838
+ for (const { line } of iterateCanonicalJsonlLinesReverseSync(filePath)) {
742
839
  try {
743
- const event = JSON.parse(line);
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 iterateFileLinesSync(filePath)) {
1305
+ for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
1205
1306
  let event;
1206
1307
  try {
1207
- event = JSON.parse(line);
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
- return withSessionHostWriterLock(
1584
- sessionId,
1585
- filePath,
1586
- () => {
1587
- const projection = createProjection();
1588
- if (
1589
- !projection ||
1590
- typeof projection.accept !== "function" ||
1591
- typeof projection.finish !== "function"
1592
- ) {
1593
- throw new TypeError(
1594
- "Authority projection must provide accept() and finish()",
1595
- );
1596
- }
1597
- if (!existsSync(filePath)) {
1598
- throw unverifiedTranscriptError(sessionId, {
1599
- status: "missing",
1600
- reason: "authority transcript does not exist",
1601
- lastHash: null,
1602
- chainedEvents: 0,
1603
- });
1604
- }
1605
- _resolveChainTail(filePath);
1606
- const verification = verifyTranscriptFile(filePath, {
1607
- onVerifiedEvent: (event) => {
1608
- const accepted = projection.accept(event);
1609
- if (accepted && typeof accepted.then === "function") {
1610
- throw new TypeError(
1611
- "Authority projection accept() must be synchronous",
1612
- );
1613
- }
1614
- },
1615
- });
1616
- assertVerifiedTranscriptAnchor(sessionId, verification);
1617
- const authority = Object.freeze({
1618
- headHash: verification.lastHash,
1619
- eventCount: verification.chainedEvents,
1620
- readMessages: () => rebuildVerifiedMessagesFromFile(filePath),
1621
- });
1622
- const projected = projection.finish(authority);
1623
- if (projected && typeof projected.then === "function") {
1624
- throw new TypeError(
1625
- "Authority projection finish() must be synchronous",
1626
- );
1627
- }
1628
- const validation = validateProjection(projected, authority);
1629
- if (validation && typeof validation.then === "function") {
1630
- throw new TypeError(
1631
- "Authority projection validation must be synchronous",
1632
- );
1633
- }
1634
- if (validation === false) {
1635
- throw new Error("Authority projection validation rejected the append");
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
- const persistedData = encodeEventMessageProvenance(type, data);
1638
- const appended = appendVerifiedWsAuthorityEventLocked(
1639
- sessionId,
1640
- filePath,
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 iterateFileLinesSync(filePath, {
1889
- ioMetrics,
1890
- })) {
2099
+ for (const { line, lineNo, terminated } of iterateCanonicalJsonlLinesSync(
2100
+ filePath,
2101
+ { ioMetrics },
2102
+ )) {
1891
2103
  let event;
1892
2104
  try {
1893
- event = JSON.parse(line);
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 iterateFileLinesSync(filePath)) {
2947
+ for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
2635
2948
  try {
2636
- events.push(JSON.parse(line));
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) => projection.accept(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 iterateFileLinesReverseSync(filePath, { ioMetrics })) {
3399
+ for (const { line } of iterateCanonicalJsonlLinesReverseSync(filePath, {
3400
+ ioMetrics,
3401
+ })) {
3081
3402
  let event;
3082
3403
  try {
3083
- event = JSON.parse(line);
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 iterateFileLinesSync(filePath)) {
3480
+ for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
3159
3481
  try {
3160
- const event = JSON.parse(line);
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
- appendFileSync(
3746
- workingPath,
3747
- `${JSON.stringify({ ...core, prevHash, hash })}\n`,
3748
- { encoding: "utf8", mode: 0o600 },
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: { ...core, prevHash, hash },
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
- let hasStartEvent = false;
4434
- for (const { line } of iterateFileLinesSync(filePath)) {
4757
+ const structure = createSessionTranscriptStructureProjection(sessionId);
4758
+ for (const { line } of iterateCanonicalJsonlLinesSync(filePath)) {
4435
4759
  try {
4436
- const event = JSON.parse(line);
4437
- eventCount += 1;
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
- hasStartEvent,
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