chainlesschain 0.163.5 → 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 (236) hide show
  1. package/completions/_cc +1 -1
  2. package/completions/cc.bash +1 -1
  3. package/completions/cc.fish +1 -1
  4. package/completions/cc.ps1 +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-Cv2EE1Zy.js → AIOps-axqQfwv8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-DV-Ro0By.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-Bt72WEVu.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-BtepG55E.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-B7KaJ7S_.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-FNFaUg7A.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-CC7YBhyj.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-RkgTNVtj.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-hHEmGZy-.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-z5wXOQP1.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DVaDJnxy.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-CJmaRDR5.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-DF29_2bI.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-DtkQkBzn.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-xoHDxp7I.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-BUm-R7ht.js} +4 -4
  24. package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-Cgl14ZkB.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BNcbmoqh.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-Bwja0Zo9.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-Bk5XjEdJ.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-Ci36Oitq.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer--U04ZN4q.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-C4KghYn7.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-QL7x_Q_0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-2HassK5k.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-DoBIp7oe.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference--W01QhZ-.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BBJ4yAel.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-D5lNngSI.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-CkEVOMKH.js} +5 -5
  42. package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-B1Ow1xVE.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-SgYbUfb6.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-BWdOvQAj.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-DcTXSSma.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-Dt3WG4oj.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-B9B38UZx.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-B0S4GGvG.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-DZ5U58Ng.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-PjL5ZW34.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-Cv1GYs-c.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-Bbkp_PL3.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-BD8QUgoZ.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-BO-2PeaB.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-D_OPVa02.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-6JC2EDdb.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-CUshzUAG.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Cs2Aalri.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-1ak49tcj.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-B_wQadhX.js → Projects-BV4oApsm.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-CXXxvafH.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-PTcDsF58.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CDwucrJw.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CJlLVRXR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-DlN-ETsO.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-DI23LR6m.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-Cl7UQMrM.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-CexHNfh8.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-BneBWvRN.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-DX7Phgrp.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-BWXO8Mte.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-DCyOjKPJ.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CXqERsTB.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-bhbrbz8E.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Dexb1f_6.js → SpeechSettings-3IPU0HBZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-DBNTQLbM.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-quP0bkN-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-BouWmgho.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-qv0D9s7z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-BxvbE8BB.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-CFMKKyVG.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-L8as3UuJ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-MxmA4VZG.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-C0VvJYRi.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-Bf5QZj8N.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-BIJ9riRz.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-B8p9Mhkw.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-BVsuaPen.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BfkjoKpV.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-DeAlsWK8.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-Cs97bz5j.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-BqbG60LH.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-i_RY0b1X.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-0-agYlWL.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-79V_zv1s.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-B-3tEpTc.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-B-JVTrpr.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-B1CbOH1i.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-B5UkyA3c.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-BESLFt4q.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-BOVkf1K3.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-BYUVuwKt.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-BcUXAB2s.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-BkBi5TmC.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-Bm7lLR4c.js} +1 -1
  110. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  111. package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-BuLQobzW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CE2VtTlp.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-CED2PL_y.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-CG3PRyAL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-togc_uYn.js → index-CIG8fQ2_.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-COdGA-8n.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-0Auljb10.js → index-CeWoNHau.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-Cf3WCLmy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-CgsqdnRV.js} +1 -1
  120. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  121. package/src/assets/web-panel/assets/{index-h31MwrME.js → index-D2B4VPyP.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-D9evtvg2.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DUUrv141.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-DiSkx4UB.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-Dk2qouAz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-Dk7-v4hP.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-DmbfhJUP.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-DqBgBpgp.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-PT_5sBMA.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-U4f5izHz.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-YXgLng0K.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-ZxO_-E8a.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-dIHr4B_u.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-gyFpvL7l.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-s1opAL-M.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-yObj1O4n.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-C9h9yKL3.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BvmLsPdu.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-CoL4ekU1.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-C4pMy6xV.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-DPuJfNxx.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-D0KEeaK2.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BpkyINxt.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-DQI_wOq8.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DYfdDiRg.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-B5dtvb0D.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-CF310mSj.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-C7_iyvJK.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-D3dsi0lZ.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-WdbOlvdQ.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +5 -5
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +76 -4
  157. package/src/commands/agent.js +90 -1
  158. package/src/commands/automation.js +462 -3
  159. package/src/commands/background-session.js +102 -1
  160. package/src/commands/chat.js +11 -2
  161. package/src/commands/compact.js +106 -23
  162. package/src/commands/cowork.js +6 -1
  163. package/src/commands/loop.js +187 -38
  164. package/src/commands/routine.js +12 -5
  165. package/src/commands/scheduler-daemon.js +807 -0
  166. package/src/commands/session-observability.js +305 -0
  167. package/src/commands/session.js +38 -0
  168. package/src/data/changelog.json +28 -2
  169. package/src/gateways/ws/ws-agent-handler.js +439 -5
  170. package/src/harness/jsonl-session-store.js +463 -125
  171. package/src/harness/prompt-compressor.js +86 -13
  172. package/src/harness/provider-backed-compaction.js +100 -27
  173. package/src/lib/advisor-runtime.js +3 -1
  174. package/src/lib/agent-schedule-store.js +737 -47
  175. package/src/lib/agent-team/team-adjudication.js +40 -1
  176. package/src/lib/automation-center-routines.js +363 -0
  177. package/src/lib/automation-center.js +435 -0
  178. package/src/lib/automation-engine.js +568 -19
  179. package/src/lib/automation-execution-authority.js +736 -0
  180. package/src/lib/background-agent-supervisor.js +871 -215
  181. package/src/lib/background-command-argv.js +24 -0
  182. package/src/lib/background-interaction-journal.js +361 -52
  183. package/src/lib/background-interaction-resolver.js +32 -0
  184. package/src/lib/background-launch-profile.js +2 -0
  185. package/src/lib/background-needs-input-incident.js +221 -0
  186. package/src/lib/causal-observability.js +2661 -0
  187. package/src/lib/chat-session-admission.js +76 -0
  188. package/src/lib/cost-budget.js +5 -7
  189. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  190. package/src/lib/cowork/debate-review-cli.js +12 -0
  191. package/src/lib/cowork-adapter.js +159 -38
  192. package/src/lib/cowork-cron.js +673 -19
  193. package/src/lib/cowork-observe.js +5 -1
  194. package/src/lib/cowork-task-runner.js +112 -17
  195. package/src/lib/delivery-coordinator.js +142 -0
  196. package/src/lib/direct-model-usage.js +121 -0
  197. package/src/lib/file-lines.js +183 -47
  198. package/src/lib/llm-pricing.js +12 -17
  199. package/src/lib/loop.js +62 -3
  200. package/src/lib/permission-engine.js +14 -7
  201. package/src/lib/public-ide-capability-manifest.js +2 -0
  202. package/src/lib/routine-store.js +546 -86
  203. package/src/lib/runtime-usage-ledger.js +182 -0
  204. package/src/lib/scheduler-kernel/agenda-adapter.js +604 -29
  205. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  206. package/src/lib/scheduler-kernel/automation-adapter.js +994 -0
  207. package/src/lib/scheduler-kernel/automation-event-adapter.js +757 -0
  208. package/src/lib/scheduler-kernel/contract.js +11 -3
  209. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +343 -15
  210. package/src/lib/scheduler-kernel/loop-adapter.js +913 -0
  211. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  212. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  213. package/src/lib/scheduler-kernel/routine-adapter.js +691 -22
  214. package/src/lib/scheduler-kernel/runtime.js +63 -1
  215. package/src/lib/scheduler-kernel/service.js +328 -0
  216. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  217. package/src/lib/scheduler-kernel/store.js +3439 -206
  218. package/src/lib/session-host-lease.js +171 -2
  219. package/src/lib/session-host-snapshot.js +14 -3
  220. package/src/lib/session-persistence-failure.js +1 -0
  221. package/src/lib/session-tail.js +179 -34
  222. package/src/lib/session-transcript-structure.js +96 -0
  223. package/src/lib/sub-agent-context.js +328 -22
  224. package/src/lib/ws-agent-handler.js +4 -1
  225. package/src/repl/agent-repl.js +732 -91
  226. package/src/repl/chat-repl.js +5 -1
  227. package/src/runtime/agent-core.js +1274 -208
  228. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  229. package/src/runtime/fallback-model.js +5 -0
  230. package/src/runtime/headless-runner.js +324 -82
  231. package/src/runtime/headless-stream.js +232 -26
  232. package/src/runtime/policies/agent-policy.js +4 -0
  233. package/src/workers/background-agent-worker.js +468 -98
  234. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
  235. package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
  236. package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
@@ -59,6 +59,14 @@ import {
59
59
  forkSession,
60
60
  } from "../harness/jsonl-session-store.js";
61
61
  import { classifyStreamRetryReason } from "../lib/stream-retry.js";
62
+ import {
63
+ markRuntimeLedgerPersistenceError,
64
+ projectRuntimeTokenUsage,
65
+ projectRuntimeUsageBoundary,
66
+ runtimeUsageEventType,
67
+ runtimeToolCallId,
68
+ } from "../lib/runtime-usage-ledger.js";
69
+ import { runMeteredDirectModelCall } from "../lib/direct-model-usage.js";
62
70
  import {
63
71
  createSessionMcpLedgerSink,
64
72
  formatMcpLedgerRecoveryNotice,
@@ -180,6 +188,219 @@ import {
180
188
  nextTier,
181
189
  } from "./permission-tier.js";
182
190
 
191
+ export const REPL_RUNTIME_LEDGER_PERSISTENCE_FAILURE_MESSAGE =
192
+ "CC_RUNTIME_USAGE_LEDGER_FAILED: runtime usage telemetry was not durably persisted; restart this session";
193
+
194
+ /** One fail-closed latch is created for each REPL process/session host. */
195
+ export function createReplRuntimeLedgerTerminalLatch(options = {}) {
196
+ const onTrip = typeof options.onTrip === "function" ? options.onTrip : null;
197
+ let terminalError = null;
198
+ return Object.freeze({
199
+ trip(error) {
200
+ if (terminalError) return terminalError;
201
+ if (error?.runtimeLedgerPersistence !== true) return null;
202
+ terminalError = new Error(
203
+ REPL_RUNTIME_LEDGER_PERSISTENCE_FAILURE_MESSAGE,
204
+ { cause: error },
205
+ );
206
+ terminalError.code = "CC_RUNTIME_USAGE_LEDGER_FAILED";
207
+ terminalError.runtimeLedgerPersistence = true;
208
+ try {
209
+ onTrip?.(terminalError);
210
+ } catch {
211
+ // Terminal state must survive a best-effort host notification failure.
212
+ }
213
+ return terminalError;
214
+ },
215
+ assertOpen() {
216
+ if (terminalError) throw terminalError;
217
+ },
218
+ isTripped() {
219
+ return terminalError !== null;
220
+ },
221
+ error() {
222
+ return terminalError;
223
+ },
224
+ });
225
+ }
226
+
227
+ /** Persisted sessions may only use the deterministic local suggestion engine. */
228
+ export function resolveReplPromptSuggestionGenerator(useJsonl, generator) {
229
+ return useJsonl ? undefined : generator;
230
+ }
231
+
232
+ export function resolveReplMeteredSessionId(useJsonl, sessionId) {
233
+ return useJsonl && sessionId ? sessionId : null;
234
+ }
235
+
236
+ function attachReplCompactionLedgerMetadata(error, callId, settled) {
237
+ let target = error;
238
+ if (!target || (typeof target !== "object" && typeof target !== "function")) {
239
+ target = new Error("semantic compaction provider call failed", {
240
+ cause: error,
241
+ });
242
+ }
243
+ try {
244
+ Object.defineProperties(target, {
245
+ compactionCallId: {
246
+ configurable: true,
247
+ value: callId || undefined,
248
+ },
249
+ usageLedgerSettled: {
250
+ configurable: true,
251
+ value: settled === true,
252
+ },
253
+ });
254
+ return target;
255
+ } catch {
256
+ const wrapped = new Error("semantic compaction provider call failed", {
257
+ cause: error,
258
+ });
259
+ Object.defineProperties(wrapped, {
260
+ compactionCallId: {
261
+ configurable: true,
262
+ value: callId || undefined,
263
+ },
264
+ usageLedgerSettled: {
265
+ configurable: true,
266
+ value: settled === true,
267
+ },
268
+ });
269
+ return wrapped;
270
+ }
271
+ }
272
+
273
+ function asReplRuntimeLedgerPersistenceError(error, message) {
274
+ if (error && (typeof error === "object" || typeof error === "function")) {
275
+ try {
276
+ const marked = markRuntimeLedgerPersistenceError(error);
277
+ if (marked?.runtimeLedgerPersistence === true) return marked;
278
+ } catch {
279
+ // Frozen/non-extensible errors are wrapped below.
280
+ }
281
+ }
282
+ const wrapped = new Error(message, { cause: error });
283
+ return markRuntimeLedgerPersistenceError(wrapped);
284
+ }
285
+
286
+ /** Meter one direct REPL call and expose only secret-free ledger metadata. */
287
+ export async function runReplMeteredModelCallWithLedger({
288
+ sessionId,
289
+ persist,
290
+ provider,
291
+ model,
292
+ source = "model",
293
+ call,
294
+ attachErrorMetadata = false,
295
+ }) {
296
+ let callId = null;
297
+ let usageLedgerSettled = false;
298
+ try {
299
+ const result = await runMeteredDirectModelCall({
300
+ sessionId,
301
+ persist: sessionId
302
+ ? async (type, data) => {
303
+ await persist(type, data);
304
+ if (type === "model_usage_started") callId = data.callId;
305
+ else usageLedgerSettled = true;
306
+ }
307
+ : null,
308
+ provider,
309
+ model,
310
+ source,
311
+ call,
312
+ });
313
+ return Object.freeze({ result, callId, usageLedgerSettled });
314
+ } catch (error) {
315
+ if (attachErrorMetadata) {
316
+ throw attachReplCompactionLedgerMetadata(
317
+ error,
318
+ callId,
319
+ usageLedgerSettled,
320
+ );
321
+ }
322
+ throw error;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Execute a REPL-owned direct tool surface behind the same durable call ledger
328
+ * used by the main agent loop. `/auto` and `/plan execute` do not yield
329
+ * `tool-executing` / `tool-result` events, so their host must bracket the real
330
+ * execution here instead of relying on the loop wrapper.
331
+ */
332
+ export async function runReplDirectToolWithLedger({
333
+ sessionId = null,
334
+ tool,
335
+ args,
336
+ execute,
337
+ persistStarted = appendEvent,
338
+ persistSettlement = appendToolCallCompact,
339
+ now = Date.now,
340
+ callId,
341
+ terminalLatch = null,
342
+ }) {
343
+ if (typeof execute !== "function") {
344
+ throw new TypeError("direct REPL tool execution requires an executor");
345
+ }
346
+ terminalLatch?.assertOpen();
347
+ const id = runtimeToolCallId(callId);
348
+ if (sessionId) {
349
+ try {
350
+ await persistStarted(sessionId, "tool_call_started", { id, tool });
351
+ } catch (error) {
352
+ const persistenceError = asReplRuntimeLedgerPersistenceError(
353
+ error,
354
+ "direct REPL tool start was not durable",
355
+ );
356
+ throw terminalLatch?.trip(persistenceError) || persistenceError;
357
+ }
358
+ }
359
+
360
+ const startedAt = now();
361
+ let result;
362
+ let executionError;
363
+ let executionFailed = false;
364
+ try {
365
+ result = await execute(tool, args);
366
+ } catch (error) {
367
+ executionFailed = true;
368
+ executionError = error;
369
+ }
370
+
371
+ const observedDuration = result?.toolTelemetryRecord?.durationMs;
372
+ const durationMs =
373
+ observedDuration ?? Math.max(0, Number(now()) - Number(startedAt));
374
+ if (sessionId) {
375
+ try {
376
+ await persistSettlement(sessionId, {
377
+ id,
378
+ tool,
379
+ isError: Boolean(
380
+ executionFailed ||
381
+ result?.error ||
382
+ result?.is_error ||
383
+ result?.isError,
384
+ ),
385
+ skill: tool === "run_skill" ? args?.skill_name : undefined,
386
+ ...extractPluginUsageAttribution(result),
387
+ durationMs,
388
+ });
389
+ } catch (error) {
390
+ const persistenceError = asReplRuntimeLedgerPersistenceError(
391
+ error,
392
+ "direct REPL tool settlement was not durable",
393
+ );
394
+ throw terminalLatch?.trip(persistenceError) || persistenceError;
395
+ }
396
+ }
397
+
398
+ if (executionFailed) {
399
+ throw terminalLatch?.trip(executionError) || executionError;
400
+ }
401
+ return result;
402
+ }
403
+
183
404
  /**
184
405
  * Reference to the runtime DB for hook execution (set during startAgentRepl)
185
406
  */
@@ -342,6 +563,17 @@ export async function agentLoop(messages, options) {
342
563
  const writeOut =
343
564
  options.writeOut || ((text) => process.stdout.write(String(text)));
344
565
  const waitForOutput = options.waitForOutput;
566
+ const persistBoundary = options._appendUsageBoundary || appendEvent;
567
+ const persistTokenUsage = options._appendTokenUsage || appendTokenUsage;
568
+ const persistToolCall =
569
+ options._appendToolCallCompact || appendToolCallCompact;
570
+ const persistRuntimeLedger = (action) => {
571
+ try {
572
+ return action();
573
+ } catch (error) {
574
+ throw markRuntimeLedgerPersistenceError(error);
575
+ }
576
+ };
345
577
  // Resume-degenerate role merge (Claude Code 2.1.187 parity), gated by the
346
578
  // one-shot `mergeRoles` flag so it fires only on the first model call after
347
579
  // resuming a session whose prior run produced no assistant response. Collapse
@@ -370,9 +602,95 @@ export async function agentLoop(messages, options) {
370
602
  // `_coreLoop` is an injectable seam (defaults to agent-core's loop) so the
371
603
  // wrapper's event translation can be unit-tested without a live model.
372
604
  const runCoreLoop = options._coreLoop || coreAgentLoop;
373
- // The tool-result event carries no args; remember the last tool-executing
374
- // pair so a recorded denial can show what was attempted (/permissions denials).
375
- let _lastExec = null;
605
+ const onUsageBoundary =
606
+ options.onUsageBoundary ||
607
+ (options.persistUsageTelemetry === true && options.sessionId
608
+ ? (event) => {
609
+ persistRuntimeLedger(() =>
610
+ persistBoundary(
611
+ options.sessionId,
612
+ runtimeUsageEventType("started"),
613
+ projectRuntimeUsageBoundary(event, "started"),
614
+ ),
615
+ );
616
+ }
617
+ : undefined);
618
+ const onUsageSettlement =
619
+ options.onUsageSettlement ||
620
+ (options.persistUsageTelemetry === true && options.sessionId
621
+ ? (event) => {
622
+ if (event?.type === "token-usage") {
623
+ persistRuntimeLedger(() =>
624
+ persistTokenUsage(
625
+ options.sessionId,
626
+ projectRuntimeTokenUsage(event),
627
+ ),
628
+ );
629
+ return;
630
+ }
631
+ persistRuntimeLedger(() =>
632
+ persistBoundary(
633
+ options.sessionId,
634
+ runtimeUsageEventType("unknown"),
635
+ projectRuntimeUsageBoundary(
636
+ event?.type === "compaction-usage-unknown"
637
+ ? {
638
+ ...event,
639
+ code: event.code || "provider_transport_outcome_unknown",
640
+ }
641
+ : event,
642
+ "unknown",
643
+ ),
644
+ ),
645
+ );
646
+ }
647
+ : undefined);
648
+ const childToolExecs = new Map();
649
+ const onToolCallBoundary =
650
+ options.onToolCallBoundary ||
651
+ (options.persistUsageTelemetry === true && options.sessionId
652
+ ? (event) => {
653
+ const id = runtimeToolCallId(event?.tool_use_id);
654
+ childToolExecs.set(id, {
655
+ tool: event?.tool || "?",
656
+ startedAt: options.now ? options.now() : Date.now(),
657
+ });
658
+ persistRuntimeLedger(() =>
659
+ persistBoundary(options.sessionId, "tool_call_started", {
660
+ id,
661
+ tool: event?.tool || "?",
662
+ }),
663
+ );
664
+ }
665
+ : undefined);
666
+ const onToolCallSettlement =
667
+ options.onToolCallSettlement ||
668
+ (options.persistUsageTelemetry === true && options.sessionId
669
+ ? (event) => {
670
+ const id = runtimeToolCallId(event?.tool_use_id);
671
+ const started = childToolExecs.get(id);
672
+ childToolExecs.delete(id);
673
+ persistRuntimeLedger(() =>
674
+ persistToolCall(options.sessionId, {
675
+ id,
676
+ tool: event?.tool || started?.tool || "?",
677
+ isError: Boolean(event?.error || event?.result?.error),
678
+ skill: event?.attribution?.skill,
679
+ durationMs: started
680
+ ? Math.max(
681
+ 0,
682
+ (options.now ? options.now() : Date.now()) -
683
+ started.startedAt,
684
+ )
685
+ : undefined,
686
+ }),
687
+ );
688
+ }
689
+ : undefined);
690
+ // Tool results carry a stable provider id. Keep every in-flight call keyed by
691
+ // that id because read-only batches may expose all starts before any result.
692
+ const _activeExecs = new Map();
693
+ const _unsettledExecs = [];
376
694
  // The core loop runs tools serially. A classified non-MCP side effect stays
377
695
  // live from its yielded `tool-executing` boundary until the matching result.
378
696
  let _currentSideEffectOpId = null;
@@ -383,6 +701,10 @@ export async function agentLoop(messages, options) {
383
701
  // too would trim the same history twice. Placed AFTER the spread so a
384
702
  // caller's options.autoCompact can never silently re-enable the double pass.
385
703
  autoCompact: false,
704
+ onUsageBoundary,
705
+ onUsageSettlement,
706
+ onToolCallBoundary,
707
+ onToolCallSettlement,
386
708
  onProviderFallback,
387
709
  })) {
388
710
  // P1 explicit turn→checkpoint binding — the REPL as PRODUCER: fold every
@@ -419,11 +741,23 @@ export async function agentLoop(messages, options) {
419
741
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
420
742
  );
421
743
  } else if (event.type === "tool-executing") {
422
- _lastExec = {
744
+ const exec = {
745
+ id: runtimeToolCallId(event.tool_use_id),
746
+ providerId: event.tool_use_id || null,
423
747
  tool: event.tool,
424
748
  args: event.args,
425
749
  startedAt: options.now ? options.now() : Date.now(),
426
750
  };
751
+ _unsettledExecs.push(exec);
752
+ if (exec.providerId) _activeExecs.set(exec.providerId, exec);
753
+ if (options.persistUsageTelemetry === true && options.sessionId) {
754
+ persistRuntimeLedger(() =>
755
+ persistBoundary(options.sessionId, "tool_call_started", {
756
+ id: exec.id,
757
+ tool: event.tool,
758
+ }),
759
+ );
760
+ }
427
761
  _currentSideEffectOpId = null;
428
762
  if (options.sideEffects) {
429
763
  const sideEffect = classifyToolSideEffect(event.tool, event.args);
@@ -455,6 +789,20 @@ export async function agentLoop(messages, options) {
455
789
  ),
456
790
  );
457
791
  } else if (event.type === "tool-result") {
792
+ const exec =
793
+ (event.tool_use_id ? _activeExecs.get(event.tool_use_id) : null) ||
794
+ _unsettledExecs.find(
795
+ (candidate) =>
796
+ !candidate.settled &&
797
+ (!event.tool || candidate.tool === event.tool),
798
+ ) ||
799
+ null;
800
+ if (exec) {
801
+ exec.settled = true;
802
+ if (exec.providerId) _activeExecs.delete(exec.providerId);
803
+ const activeIndex = _unsettledExecs.indexOf(exec);
804
+ if (activeIndex >= 0) _unsettledExecs.splice(activeIndex, 1);
805
+ }
458
806
  if (options.sideEffects && _currentSideEffectOpId) {
459
807
  const sideEffectError = event.error || event.result?.error || null;
460
808
  if (event.permission_decision) {
@@ -475,10 +823,10 @@ export async function agentLoop(messages, options) {
475
823
  }
476
824
  const durationMs =
477
825
  event.result?.toolTelemetryRecord?.durationMs ??
478
- (_lastExec?.tool === event.tool
826
+ (exec?.tool === event.tool
479
827
  ? Math.max(
480
828
  0,
481
- (options.now ? options.now() : Date.now()) - _lastExec.startedAt,
829
+ (options.now ? options.now() : Date.now()) - exec.startedAt,
482
830
  )
483
831
  : undefined);
484
832
  // 用量归因: persist a compact tool_call record (name + error flag +
@@ -488,18 +836,22 @@ export async function agentLoop(messages, options) {
488
836
  // options.persistToolCalls === false.
489
837
  if (options.sessionId && options.persistToolCalls !== false) {
490
838
  try {
491
- appendToolCallCompact(options.sessionId, {
839
+ persistToolCall(options.sessionId, {
840
+ id: exec?.id,
492
841
  tool: event.tool,
493
842
  isError: Boolean(event.error || event.result?.error),
494
843
  skill:
495
- event.tool === "run_skill" && _lastExec?.tool === "run_skill"
496
- ? _lastExec.args?.skill_name
844
+ event.tool === "run_skill" && exec?.tool === "run_skill"
845
+ ? exec.args?.skill_name
497
846
  : undefined,
498
847
  ...extractPluginUsageAttribution(event.result),
499
848
  durationMs,
500
849
  });
501
850
  } catch (_e) {
502
- // persistence is best-effort — never break the turn
851
+ if (options.persistUsageTelemetry === true) {
852
+ throw markRuntimeLedgerPersistenceError(_e);
853
+ }
854
+ // Legacy non-JSONL persistence remains best-effort.
503
855
  }
504
856
  }
505
857
  if (event.error || event.result?.error) {
@@ -514,8 +866,8 @@ export async function agentLoop(messages, options) {
514
866
  result: event.result,
515
867
  error: event.error,
516
868
  argsSummary:
517
- _lastExec && _lastExec.tool === event.tool
518
- ? formatToolArgs(event.tool, _lastExec.args)
869
+ exec && exec.tool === event.tool
870
+ ? formatToolArgs(event.tool, exec.args)
519
871
  : "",
520
872
  });
521
873
  if (denial) {
@@ -565,6 +917,43 @@ export async function agentLoop(messages, options) {
565
917
  }
566
918
  } else if (event.type === "token-usage") {
567
919
  usageEvents.push(event);
920
+ if (
921
+ event.ledgerPersisted !== true &&
922
+ options.persistUsageTelemetry === true &&
923
+ options.sessionId
924
+ ) {
925
+ persistRuntimeLedger(() =>
926
+ persistTokenUsage(options.sessionId, projectRuntimeTokenUsage(event)),
927
+ );
928
+ }
929
+ } else if (
930
+ event.type === "model-usage-started" ||
931
+ event.type === "model-usage-unknown" ||
932
+ event.type === "compaction-usage-unknown"
933
+ ) {
934
+ if (
935
+ event.ledgerPersisted !== true &&
936
+ options.persistUsageTelemetry === true &&
937
+ options.sessionId
938
+ ) {
939
+ const outcome =
940
+ event.type === "model-usage-started" ? "started" : "unknown";
941
+ persistRuntimeLedger(() =>
942
+ persistBoundary(
943
+ options.sessionId,
944
+ runtimeUsageEventType(outcome),
945
+ projectRuntimeUsageBoundary(
946
+ event.type === "compaction-usage-unknown"
947
+ ? {
948
+ ...event,
949
+ code: event.code || "provider_transport_outcome_unknown",
950
+ }
951
+ : event,
952
+ outcome,
953
+ ),
954
+ ),
955
+ );
956
+ }
568
957
  } else if (event.type === "iteration-warning") {
569
958
  writeOut(chalk.yellow(`\n ${event.message}\n`));
570
959
  } else if (event.type === "iteration-budget-exhausted") {
@@ -1336,32 +1725,57 @@ export function settleReplCompactionCandidate({
1336
1725
  appendUsage = appendTokenUsage,
1337
1726
  }) {
1338
1727
  if (usageUnknownEvent) {
1728
+ const block = Object.freeze({
1729
+ code: "CC_COMPACTION_USAGE_UNKNOWN",
1730
+ reason: usageUnknownEvent.reason || "provider_transport_outcome_unknown",
1731
+ commitState: "provider-usage-unknown",
1732
+ usageOutcome: "unknown",
1733
+ });
1734
+ if (
1735
+ useJsonl &&
1736
+ sessionId &&
1737
+ usageUnknownEvent.usageLedgerSettled !== true
1738
+ ) {
1739
+ const usagePersistenceError = asReplRuntimeLedgerPersistenceError(
1740
+ new Error("semantic compaction usage-unknown row was not durable"),
1741
+ "semantic compaction usage-unknown row was not durable",
1742
+ );
1743
+ return {
1744
+ applied: false,
1745
+ error: usagePersistenceError,
1746
+ usagePersistenceError,
1747
+ block,
1748
+ };
1749
+ }
1339
1750
  return {
1340
1751
  applied: false,
1341
- block: Object.freeze({
1342
- code: "CC_COMPACTION_USAGE_UNKNOWN",
1343
- reason:
1344
- usageUnknownEvent.reason || "provider_transport_outcome_unknown",
1345
- commitState: "provider-usage-unknown",
1346
- usageOutcome: "unknown",
1347
- }),
1752
+ block,
1348
1753
  };
1349
1754
  }
1350
1755
 
1351
- let usagePersistenceError = null;
1352
1756
  if (usageEvent) {
1353
1757
  addUsage(costStore, [usageEvent]);
1354
- if (useJsonl && sessionId) {
1758
+ if (useJsonl && sessionId && usageEvent.usageLedgerSettled !== true) {
1355
1759
  try {
1356
1760
  appendUsage(sessionId, {
1357
1761
  provider: usageEvent.provider,
1358
1762
  model: usageEvent.model,
1359
1763
  usage: usageEvent.usage,
1764
+ ...(usageEvent.callId ? { callId: usageEvent.callId } : {}),
1765
+ ...(usageEvent.source ? { source: usageEvent.source } : {}),
1360
1766
  });
1361
1767
  } catch (error) {
1362
- // The paid request is accounted in-memory exactly once. Never retry an
1363
- // append whose durable commit state may be unknown.
1364
- usagePersistenceError = error;
1768
+ const usagePersistenceError = asReplRuntimeLedgerPersistenceError(
1769
+ error,
1770
+ "semantic compaction token usage was not durable",
1771
+ );
1772
+ // The paid request is accounted in-memory exactly once, but compaction
1773
+ // and all later input must stop when the durable settlement is unknown.
1774
+ return {
1775
+ applied: false,
1776
+ error: usagePersistenceError,
1777
+ usagePersistenceError,
1778
+ };
1365
1779
  }
1366
1780
  }
1367
1781
  }
@@ -1374,7 +1788,7 @@ export function settleReplCompactionCandidate({
1374
1788
  "REPL messages changed while provider compaction was running",
1375
1789
  );
1376
1790
  error.code = "CC_COMPACTION_LOCAL_STATE_CHANGED";
1377
- return { applied: false, error, usagePersistenceError };
1791
+ return { applied: false, error };
1378
1792
  }
1379
1793
 
1380
1794
  if (useJsonl && sessionId && stats?.strategy !== "none") {
@@ -1385,13 +1799,13 @@ export function settleReplCompactionCandidate({
1385
1799
  messages: compacted,
1386
1800
  });
1387
1801
  } catch (error) {
1388
- return { applied: false, error, usagePersistenceError };
1802
+ return { applied: false, error };
1389
1803
  }
1390
1804
  }
1391
1805
 
1392
1806
  messages.length = 0;
1393
1807
  messages.push(...compacted);
1394
- return { applied: true, usagePersistenceError };
1808
+ return { applied: true };
1395
1809
  }
1396
1810
 
1397
1811
  function validateRawReplReplayMessages(rebuiltMessages) {
@@ -2145,6 +2559,14 @@ export function createReplMcpHostRuntimeManager(dependencies = {}) {
2145
2559
  * the public startAgentRepl() below always supplies the real fixed bindings.
2146
2560
  */
2147
2561
  export async function runReplStartupBoundary(options, startupDependencies) {
2562
+ if (options.observabilityScope != null && options.ephemeral === true) {
2563
+ throw Object.assign(
2564
+ new Error(
2565
+ "observability scope requires durable session persistence and cannot be combined with ephemeral mode",
2566
+ ),
2567
+ { code: "CC_OBSERVABILITY_SCOPE_EPHEMERAL_CONFLICT" },
2568
+ );
2569
+ }
2148
2570
  const prepareStartup = dependencyDataFunction(
2149
2571
  startupDependencies,
2150
2572
  "prepareReplStartupResume",
@@ -2163,6 +2585,25 @@ export async function runReplStartupBoundary(options, startupDependencies) {
2163
2585
  ),
2164
2586
  );
2165
2587
  const startupCandidate = startupAdmission.candidate;
2588
+ if (
2589
+ options.observabilityScope != null &&
2590
+ startupAdmission.useJsonl !== true
2591
+ ) {
2592
+ throw Object.assign(
2593
+ new Error(
2594
+ "observability scope requires durable JSONL session persistence, but JSONL storage is unavailable",
2595
+ ),
2596
+ { code: "CC_OBSERVABILITY_SCOPE_JSONL_REQUIRED" },
2597
+ );
2598
+ }
2599
+ if (options.observabilityScope != null && startupCandidate?.ok === true) {
2600
+ throw Object.assign(
2601
+ new Error(
2602
+ "an existing REPL session scope cannot be overwritten; create a new scoped session",
2603
+ ),
2604
+ { code: "CC_OBSERVABILITY_SCOPE_IMMUTABLE" },
2605
+ );
2606
+ }
2166
2607
  if (startupCandidate && !startupCandidate.ok) {
2167
2608
  const refuseStartup = dependencyDataFunction(
2168
2609
  startupDependencies,
@@ -2222,6 +2663,36 @@ export async function runReplStartupBoundary(options, startupDependencies) {
2222
2663
  );
2223
2664
  }
2224
2665
 
2666
+ /** Start a REPL JSONL session while preserving legacy best-effort behavior. */
2667
+ export function startReplJsonlSession(
2668
+ startSession,
2669
+ requestedId,
2670
+ meta,
2671
+ observabilityScope,
2672
+ ) {
2673
+ try {
2674
+ const sessionId = startSession(requestedId, {
2675
+ ...meta,
2676
+ ...(observabilityScope != null ? { observabilityScope } : {}),
2677
+ });
2678
+ if (
2679
+ observabilityScope != null &&
2680
+ (typeof sessionId !== "string" || !sessionId)
2681
+ ) {
2682
+ throw new Error("JSONL session creation returned no durable session id");
2683
+ }
2684
+ return sessionId || null;
2685
+ } catch (error) {
2686
+ if (observabilityScope == null) return null;
2687
+ const failure = new Error(
2688
+ "scoped JSONL session could not be durably created",
2689
+ { cause: error },
2690
+ );
2691
+ failure.code = "CC_OBSERVABILITY_SCOPE_START_FAILED";
2692
+ throw failure;
2693
+ }
2694
+ }
2695
+
2225
2696
  /** Start the agentic REPL with non-overridable production bindings. */
2226
2697
  export async function startAgentRepl(options = {}) {
2227
2698
  const sessionHostLeaseScope = { lease: null };
@@ -2374,6 +2845,85 @@ async function startAgentReplInWorkspaceOwned(
2374
2845
  }
2375
2846
  };
2376
2847
  const { useJsonl, candidate: _startupJsonlResume } = startupAdmission;
2848
+ // A usage-ledger durability failure makes this REPL terminal. Keep the latch
2849
+ // above every direct-model surface (Advisor, /btw, /auto, /plan, /goal), and
2850
+ // clear steering immediately so queued work cannot start another paid call.
2851
+ let _processingLine = false;
2852
+ const _pendingLines = [];
2853
+ const _runtimeLedgerTerminalLatch = createReplRuntimeLedgerTerminalLatch({
2854
+ onTrip: (terminalError) => {
2855
+ _pendingLines.length = 0;
2856
+ try {
2857
+ _turnAbort?.abort(terminalError);
2858
+ } catch {
2859
+ // An already-settled turn needs no further cancellation.
2860
+ }
2861
+ },
2862
+ });
2863
+ const _reportRuntimeLedgerTerminal = ({ concurrent = false } = {}) => {
2864
+ const message = REPL_RUNTIME_LEDGER_PERSISTENCE_FAILURE_MESSAGE;
2865
+ if (concurrent) process.stderr.write(`\n${message}\n`);
2866
+ else logger.error(message);
2867
+ };
2868
+ const _runReplMeteredModelCall = async ({
2869
+ call,
2870
+ callProvider,
2871
+ callModel,
2872
+ source = "model",
2873
+ includeLedgerMetadata = false,
2874
+ }) => {
2875
+ _runtimeLedgerTerminalLatch.assertOpen();
2876
+ try {
2877
+ const durableSessionId = resolveReplMeteredSessionId(useJsonl, sessionId);
2878
+ const metered = await runReplMeteredModelCallWithLedger({
2879
+ sessionId: durableSessionId,
2880
+ persist: durableSessionId
2881
+ ? (type, data) => appendEvent(durableSessionId, type, data)
2882
+ : null,
2883
+ provider: callProvider || provider,
2884
+ model: callModel || model,
2885
+ source,
2886
+ call,
2887
+ attachErrorMetadata: includeLedgerMetadata,
2888
+ });
2889
+ return includeLedgerMetadata ? metered : metered.result;
2890
+ } catch (error) {
2891
+ const terminalError = _runtimeLedgerTerminalLatch.trip(error);
2892
+ if (terminalError) throw terminalError;
2893
+ throw error;
2894
+ }
2895
+ };
2896
+ // Direct chat helpers keep their public string-returning API while routing
2897
+ // each private provider envelope through the durable per-call REPL meter.
2898
+ const _directChatCallWrapper = ({
2899
+ call,
2900
+ provider: callProvider,
2901
+ model: callModel,
2902
+ }) =>
2903
+ _runReplMeteredModelCall({
2904
+ call,
2905
+ callProvider,
2906
+ callModel,
2907
+ source: "model",
2908
+ });
2909
+ const _runReplDirectTool = async (tool, args) => {
2910
+ _runtimeLedgerTerminalLatch.assertOpen();
2911
+ try {
2912
+ const durableSessionId = resolveReplMeteredSessionId(useJsonl, sessionId);
2913
+ return await runReplDirectToolWithLedger({
2914
+ sessionId: durableSessionId,
2915
+ tool,
2916
+ args,
2917
+ execute: executeTool,
2918
+ now: options.now || Date.now,
2919
+ terminalLatch: _runtimeLedgerTerminalLatch,
2920
+ });
2921
+ } catch (error) {
2922
+ const terminalError = _runtimeLedgerTerminalLatch.trip(error);
2923
+ if (terminalError) throw terminalError;
2924
+ throw error;
2925
+ }
2926
+ };
2377
2927
  let model = options.model || "qwen2.5:7b";
2378
2928
  let provider = options.provider || "ollama";
2379
2929
  // Extended thinking (Anthropic; opt-in via --think/--ultrathink). Carried from
@@ -2518,24 +3068,36 @@ async function startAgentReplInWorkspaceOwned(
2518
3068
  model,
2519
3069
  provider,
2520
3070
  llmQuery: async (prompt) => {
2521
- const response = await chatWithTools(
2522
- [{ role: "user", content: prompt }],
2523
- {
2524
- provider,
2525
- model,
2526
- baseUrl,
2527
- apiKey,
2528
- signal: _sessionHostLeaseScope?.lease?.signal,
2529
- enabledToolNames: [],
2530
- extraToolDefinitions: [],
2531
- maxOutputTokens: 2048,
2532
- },
2533
- );
3071
+ const {
3072
+ result: response,
3073
+ callId,
3074
+ usageLedgerSettled,
3075
+ } = await _runReplMeteredModelCall({
3076
+ callProvider: provider,
3077
+ callModel: model,
3078
+ source: "semantic-compaction",
3079
+ includeLedgerMetadata: true,
3080
+ call: () =>
3081
+ chatWithTools([{ role: "user", content: prompt }], {
3082
+ provider,
3083
+ model,
3084
+ baseUrl,
3085
+ apiKey,
3086
+ signal: _sessionHostLeaseScope?.lease?.signal,
3087
+ enabledToolNames: [],
3088
+ extraToolDefinitions: [],
3089
+ hostManagedToolPolicy: null,
3090
+ contextEngine: null,
3091
+ maxOutputTokens: 2048,
3092
+ }),
3093
+ });
2534
3094
  return {
2535
3095
  summary: response?.message?.content || "",
2536
3096
  usage: response?.usage || null,
2537
3097
  provider,
2538
3098
  model,
3099
+ ...(callId ? { callId } : {}),
3100
+ ...(usageLedgerSettled ? { usageLedgerSettled: true } : {}),
2539
3101
  };
2540
3102
  },
2541
3103
  });
@@ -2915,11 +3477,12 @@ async function startAgentReplInWorkspaceOwned(
2915
3477
  model,
2916
3478
  };
2917
3479
  if (useJsonl) {
2918
- try {
2919
- sessionId = jsonlStartSession(options.sessionId || null, meta);
2920
- } catch (_err) {
2921
- // Non-critical
2922
- }
3480
+ sessionId = startReplJsonlSession(
3481
+ jsonlStartSession,
3482
+ options.sessionId || null,
3483
+ meta,
3484
+ options.observabilityScope,
3485
+ );
2923
3486
  if (
2924
3487
  sessionId &&
2925
3488
  _sessionHostLeaseScope &&
@@ -3760,7 +4323,7 @@ async function startAgentReplInWorkspaceOwned(
3760
4323
  // transcript snapshot and ZERO tools. Compact call/outcome metadata shares
3761
4324
  // the session hash chain, while advice text itself is never persisted.
3762
4325
  try {
3763
- const { createConfiguredAdvisorRuntime } =
4326
+ const { createConfiguredAdvisorRuntime, invokeToolFreeAdvisor } =
3764
4327
  await import("../lib/advisor-runtime.js");
3765
4328
  const advisorOverrides = {};
3766
4329
  if (options.advisorEnabled !== undefined) {
@@ -3775,6 +4338,13 @@ async function startAgentReplInWorkspaceOwned(
3775
4338
  baseUrl,
3776
4339
  apiKey,
3777
4340
  overrides: advisorOverrides,
4341
+ invoke: (request) =>
4342
+ _runReplMeteredModelCall({
4343
+ callProvider: request?.provider,
4344
+ callModel: request?.model,
4345
+ source: "model",
4346
+ call: () => invokeToolFreeAdvisor(request),
4347
+ }),
3778
4348
  onEvent: (event) => {
3779
4349
  if (!useJsonl || !sessionId) return;
3780
4350
  try {
@@ -4080,7 +4650,10 @@ async function startAgentReplInWorkspaceOwned(
4080
4650
  getSuggestionContext: () => ({ messages: messages.slice() }),
4081
4651
  persistSuggestionEnabled: _persistPromptSuggestionsEnabled,
4082
4652
  clipboardBinding: options.clipboardBinding || null,
4083
- generateSuggestions: options.generatePromptSuggestions,
4653
+ generateSuggestions: resolveReplPromptSuggestionGenerator(
4654
+ useJsonl,
4655
+ options.generatePromptSuggestions,
4656
+ ),
4084
4657
  suggestionDebounceMs: options.promptSuggestionDebounceMs,
4085
4658
  screenReader: _screenReaderMode,
4086
4659
  write: (text) => process.stdout.write(String(text)),
@@ -4524,9 +5097,6 @@ async function startAgentReplInWorkspaceOwned(
4524
5097
  // line instead of racing a second concurrent turn; the queue drains FIFO
4525
5098
  // when the current turn finishes. `/btw` is the deliberate exception: its
4526
5099
  // tool-free snapshot call may run concurrently without mutating main state.
4527
- let _processingLine = false;
4528
- const _pendingLines = [];
4529
-
4530
5100
  const runBtwSideQuestion = async (btw, { concurrent = false } = {}) => {
4531
5101
  if (!btw || btw.error) {
4532
5102
  const message = btw?.error || "usage: /btw [--fork] <question>";
@@ -4535,7 +5105,13 @@ async function startAgentReplInWorkspaceOwned(
4535
5105
  return { ok: false, error: message };
4536
5106
  }
4537
5107
  try {
4538
- const chatFn = createChatFn({ provider, model, baseUrl, apiKey });
5108
+ const chatFn = createChatFn({
5109
+ provider,
5110
+ model,
5111
+ baseUrl,
5112
+ apiKey,
5113
+ callWrapper: _directChatCallWrapper,
5114
+ });
4539
5115
  const { answer } = await runBtwQuestion({
4540
5116
  messages,
4541
5117
  question: btw.text,
@@ -4570,6 +5146,11 @@ async function startAgentReplInWorkspaceOwned(
4570
5146
  else logger.log(`\n${block}\n`);
4571
5147
  return { ok: true, answer, forkedId };
4572
5148
  } catch (error) {
5149
+ const terminalError = _runtimeLedgerTerminalLatch.trip(error);
5150
+ if (terminalError) {
5151
+ _reportRuntimeLedgerTerminal({ concurrent });
5152
+ return { ok: false, error: terminalError.message, terminal: true };
5153
+ }
4573
5154
  const message = `/btw failed: ${error.message}`;
4574
5155
  if (concurrent) process.stderr.write(`\n${message}\n`);
4575
5156
  else logger.error(message);
@@ -4613,6 +5194,16 @@ async function startAgentReplInWorkspaceOwned(
4613
5194
  prompt();
4614
5195
  return;
4615
5196
  }
5197
+ if (
5198
+ _runtimeLedgerTerminalLatch.isTripped() &&
5199
+ trimmed !== "/exit" &&
5200
+ trimmed !== "/quit"
5201
+ ) {
5202
+ _pendingLines.length = 0;
5203
+ _reportRuntimeLedgerTerminal();
5204
+ prompt();
5205
+ return;
5206
+ }
4616
5207
 
4617
5208
  // Per-turn version-skew check (one-time): catches an `npm i -g` that landed
4618
5209
  // while this REPL kept running — the next prompt nudges a restart.
@@ -6085,6 +6676,12 @@ async function startAgentReplInWorkspaceOwned(
6085
6676
  ) {
6086
6677
  throw error;
6087
6678
  }
6679
+ const terminalError = _runtimeLedgerTerminalLatch.trip(error);
6680
+ if (terminalError) {
6681
+ _reportRuntimeLedgerTerminal();
6682
+ prompt();
6683
+ return;
6684
+ }
6088
6685
  logger.error(`Compaction failed: ${error.message}`);
6089
6686
  prompt();
6090
6687
  return;
@@ -6109,9 +6706,14 @@ async function startAgentReplInWorkspaceOwned(
6109
6706
  costStore: _costStore,
6110
6707
  });
6111
6708
  if (settlement.usagePersistenceError) {
6112
- logger.warn(
6113
- `Compaction usage was counted locally but not durably persisted: ${settlement.usagePersistenceError.message}`,
6709
+ const terminalError = _runtimeLedgerTerminalLatch.trip(
6710
+ settlement.usagePersistenceError,
6114
6711
  );
6712
+ if (terminalError) {
6713
+ _reportRuntimeLedgerTerminal();
6714
+ prompt();
6715
+ return;
6716
+ }
6115
6717
  }
6116
6718
  if (settlement.block) {
6117
6719
  _compactionUsageBlock = settlement.block;
@@ -6813,7 +7415,13 @@ async function startAgentReplInWorkspaceOwned(
6813
7415
  const result = await startDebate({
6814
7416
  target: targetLabel,
6815
7417
  code,
6816
- llmOptions: { provider, model, baseUrl, apiKey },
7418
+ llmOptions: {
7419
+ provider,
7420
+ model,
7421
+ baseUrl,
7422
+ apiKey,
7423
+ callWrapper: _directChatCallWrapper,
7424
+ },
6817
7425
  });
6818
7426
  for (const review of result.reviews) {
6819
7427
  const vc =
@@ -6835,7 +7443,9 @@ async function startAgentReplInWorkspaceOwned(
6835
7443
  content: `[Cowork Debate Result] ${result.verdict} (consensus: ${result.consensusScore}%)\n${result.summary.substring(0, 500)}`,
6836
7444
  });
6837
7445
  } catch (err) {
6838
- logger.error(`Debate failed: ${err.message}`);
7446
+ const terminalError = _runtimeLedgerTerminalLatch.trip(err);
7447
+ if (terminalError) _reportRuntimeLedgerTerminal();
7448
+ else logger.error(`Debate failed: ${err.message}`);
6839
7449
  }
6840
7450
  } else if (subCmd === "compare" && coworkInput) {
6841
7451
  try {
@@ -6844,7 +7454,13 @@ async function startAgentReplInWorkspaceOwned(
6844
7454
  process.stdout.write(chalk.gray("\n Generating variants...\n"));
6845
7455
  const result = await compare({
6846
7456
  prompt: coworkInput,
6847
- llmOptions: { provider, model, baseUrl, apiKey },
7457
+ llmOptions: {
7458
+ provider,
7459
+ model,
7460
+ baseUrl,
7461
+ apiKey,
7462
+ callWrapper: _directChatCallWrapper,
7463
+ },
6848
7464
  });
6849
7465
  for (const v of result.variants) {
6850
7466
  process.stdout.write(
@@ -6859,7 +7475,9 @@ async function startAgentReplInWorkspaceOwned(
6859
7475
  content: `[Cowork Compare Result] Winner: ${result.winner}. ${result.reason}`,
6860
7476
  });
6861
7477
  } catch (err) {
6862
- logger.error(`Compare failed: ${err.message}`);
7478
+ const terminalError = _runtimeLedgerTerminalLatch.trip(err);
7479
+ if (terminalError) _reportRuntimeLedgerTerminal();
7480
+ else logger.error(`Compare failed: ${err.message}`);
6863
7481
  }
6864
7482
  } else if (subCmd === "graph" && coworkInput) {
6865
7483
  try {
@@ -7044,10 +7662,16 @@ async function startAgentReplInWorkspaceOwned(
7044
7662
  // Submit new goal
7045
7663
  // Lazy-init autonomous agent with LLM chat function
7046
7664
  if (!autonomousAgent._initialized) {
7047
- const chatFn = createChatFn({ provider, model, baseUrl, apiKey });
7665
+ const chatFn = createChatFn({
7666
+ provider,
7667
+ model,
7668
+ baseUrl,
7669
+ apiKey,
7670
+ callWrapper: _directChatCallWrapper,
7671
+ });
7048
7672
  autonomousAgent.initialize({
7049
7673
  llmChat: chatFn,
7050
- toolExecutor: executeTool,
7674
+ toolExecutor: _runReplDirectTool,
7051
7675
  });
7052
7676
  }
7053
7677
 
@@ -7214,7 +7838,7 @@ async function startAgentReplInWorkspaceOwned(
7214
7838
  process.stdout.write(
7215
7839
  chalk.gray(` [${item.tool}] ${item.title}\n`),
7216
7840
  );
7217
- return await executeTool(item.tool, item.params);
7841
+ return await _runReplDirectTool(item.tool, item.params);
7218
7842
  }
7219
7843
  return { skipped: true };
7220
7844
  },
@@ -7249,7 +7873,13 @@ async function startAgentReplInWorkspaceOwned(
7249
7873
  await import("../lib/interactive-planner.js");
7250
7874
  const { TerminalInteractionAdapter } =
7251
7875
  await import("../lib/interaction-adapter.js");
7252
- const chatFn = createChatFn({ provider, model, baseUrl, apiKey });
7876
+ const chatFn = createChatFn({
7877
+ provider,
7878
+ model,
7879
+ baseUrl,
7880
+ apiKey,
7881
+ callWrapper: _directChatCallWrapper,
7882
+ });
7253
7883
  const planner = new CLIInteractivePlanner({
7254
7884
  llmChat: chatFn,
7255
7885
  db,
@@ -8352,11 +8982,12 @@ async function startAgentReplInWorkspaceOwned(
8352
8982
  model: telemetry.model || activeModel,
8353
8983
  reason: classifyStreamRetryReason(error),
8354
8984
  });
8355
- } catch {
8356
- /* usage telemetry is best-effort */
8985
+ } catch (persistenceError) {
8986
+ throw markRuntimeLedgerPersistenceError(persistenceError);
8357
8987
  }
8358
8988
  }
8359
8989
  },
8990
+ strictUsageTelemetry: useJsonl,
8360
8991
  // Stream-stall hint (Claude-Code 2.1.185): the connection is alive but
8361
8992
  // the API has gone silent mid-response — tell the user we're still
8362
8993
  // waiting instead of leaving a frozen spinner. stderr so it never
@@ -8391,6 +9022,7 @@ async function startAgentReplInWorkspaceOwned(
8391
9022
  contextEngine,
8392
9023
  iterationBudget,
8393
9024
  sessionId,
9025
+ persistUsageTelemetry: useJsonl,
8394
9026
  skillLoader: _replSkillLoader,
8395
9027
  cwd: process.cwd(),
8396
9028
  additionalDirectories,
@@ -8459,24 +9091,6 @@ async function startAgentReplInWorkspaceOwned(
8459
9091
  // Running spend for `/cost` (in-memory, works without persistence).
8460
9092
  if (usageEvents?.length) addUsage(_costStore, usageEvents);
8461
9093
 
8462
- if (sessionId && usageEvents?.length) {
8463
- for (const ue of usageEvents) {
8464
- try {
8465
- appendTokenUsage(sessionId, {
8466
- provider: ue.provider,
8467
- model: ue.model,
8468
- usage: ue.usage,
8469
- // 用量归因: sub-agent / isolated-skill usage carries its
8470
- // attribution frame into the transcript; main-loop events stay
8471
- // exactly as before (absence ⇒ origin "main").
8472
- ...(ue.attribution ? { attribution: ue.attribution } : {}),
8473
- });
8474
- } catch (_e) {
8475
- /* best-effort */
8476
- }
8477
- }
8478
- }
8479
-
8480
9094
  // Feed the status line: the last usage event's input+output ≈ the tokens
8481
9095
  // now resident in the context window (what the next call resends). Track
8482
9096
  // the active model too, so the built-in readout reflects auto-switches.
@@ -8507,12 +9121,18 @@ async function startAgentReplInWorkspaceOwned(
8507
9121
  `Condition: ${cond.text || cond.source}\n\n` +
8508
9122
  `Latest assistant output:\n${String(finalText || "").slice(0, 2000)}\n\n` +
8509
9123
  `Reply with STRICT JSON only: {"met": true|false, "reason": "<short>"}.`;
8510
- const jr = await chatWithTools([{ role: "user", content: p }], {
8511
- model: activeModel,
8512
- provider,
8513
- baseUrl,
8514
- apiKey,
8515
- enabledToolNames: [],
9124
+ const jr = await _runReplMeteredModelCall({
9125
+ callProvider: provider,
9126
+ callModel: activeModel,
9127
+ source: "model",
9128
+ call: () =>
9129
+ chatWithTools([{ role: "user", content: p }], {
9130
+ model: activeModel,
9131
+ provider,
9132
+ baseUrl,
9133
+ apiKey,
9134
+ enabledToolNames: [],
9135
+ }),
8516
9136
  });
8517
9137
  const text = jr?.message?.content || "";
8518
9138
  let met = false;
@@ -8547,7 +9167,9 @@ async function startAgentReplInWorkspaceOwned(
8547
9167
  logger.log(chalk.cyan(l));
8548
9168
  if (done) _sessionGoal = null; // completed or exhausted → drop
8549
9169
  } catch (err) {
8550
- logger.verbose(`/goal eval skipped: ${err.message}`);
9170
+ const terminalError = _runtimeLedgerTerminalLatch.trip(err);
9171
+ if (terminalError) _reportRuntimeLedgerTerminal();
9172
+ else logger.verbose(`/goal eval skipped: ${err.message}`);
8551
9173
  }
8552
9174
  }
8553
9175
 
@@ -8660,6 +9282,7 @@ async function startAgentReplInWorkspaceOwned(
8660
9282
  feature("PROMPT_COMPRESSOR") &&
8661
9283
  _compressor &&
8662
9284
  !_compactionUsageBlock &&
9285
+ !_runtimeLedgerTerminalLatch.isTripped() &&
8663
9286
  _compressor.shouldAutoCompact(messages)
8664
9287
  ) {
8665
9288
  try {
@@ -8688,9 +9311,10 @@ async function startAgentReplInWorkspaceOwned(
8688
9311
  costStore: _costStore,
8689
9312
  });
8690
9313
  if (settlement.usagePersistenceError) {
8691
- logger.warn(
8692
- `Auto-compaction usage was counted locally but not durably persisted: ${settlement.usagePersistenceError.message}`,
9314
+ const terminalError = _runtimeLedgerTerminalLatch.trip(
9315
+ settlement.usagePersistenceError,
8693
9316
  );
9317
+ if (terminalError) throw terminalError;
8694
9318
  }
8695
9319
  if (settlement.block) {
8696
9320
  _compactionUsageBlock = settlement.block;
@@ -8725,6 +9349,7 @@ async function startAgentReplInWorkspaceOwned(
8725
9349
  ) {
8726
9350
  throw error;
8727
9351
  }
9352
+ if (error?.runtimeLedgerPersistence === true) throw error;
8728
9353
  logger.warn(`Auto-compaction failed: ${error.message}`);
8729
9354
  }
8730
9355
  }
@@ -8772,6 +9397,12 @@ async function startAgentReplInWorkspaceOwned(
8772
9397
  }
8773
9398
  } catch (err) {
8774
9399
  _turnAbort = null;
9400
+ const terminalError = _runtimeLedgerTerminalLatch.trip(err);
9401
+ if (terminalError) {
9402
+ _reportRuntimeLedgerTerminal();
9403
+ prompt();
9404
+ return;
9405
+ }
8775
9406
  // Esc interrupt: an aborted turn is normal flow, not an error — the
8776
9407
  // partial conversation stays usable and queued lines still drain.
8777
9408
  if (err?.name === "AbortError" || /abort/i.test(err?.message || "")) {
@@ -8806,6 +9437,16 @@ async function startAgentReplInWorkspaceOwned(
8806
9437
 
8807
9438
  rl.on("line", async (input) => {
8808
9439
  if (!(await _waitForReplOutput())) return;
9440
+ if (_runtimeLedgerTerminalLatch.isTripped()) {
9441
+ const terminalInput = input.trim();
9442
+ if (terminalInput === "/exit" || terminalInput === "/quit") {
9443
+ logger.log(chalk.gray("\nGoodbye!"));
9444
+ rl.close();
9445
+ } else {
9446
+ _reportRuntimeLedgerTerminal();
9447
+ }
9448
+ return;
9449
+ }
8809
9450
  if (_processingLine) {
8810
9451
  const concurrentBtw = _slashCommandsDisabled
8811
9452
  ? null
@@ -8830,7 +9471,7 @@ async function startAgentReplInWorkspaceOwned(
8830
9471
  try {
8831
9472
  await handleLine(input);
8832
9473
  if (!(await _waitForReplOutput())) return;
8833
- while (_pendingLines.length) {
9474
+ while (_pendingLines.length && !_runtimeLedgerTerminalLatch.isTripped()) {
8834
9475
  const next = _pendingLines.shift();
8835
9476
  logger.log(chalk.cyan(`▶ running queued input: ${next}`));
8836
9477
  await handleLine(next);