chainlesschain 0.162.197 → 0.162.198

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 (208) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-D3Gu1a5d.js → AIOps-CSN-D2xb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DrwAoKaz.js → ActionButton-CBSMlGzv.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C7Q-gd_3.js → Analytics-3yr3Nt2t.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DgBtae-n.js → AppLayout-1b-o3lMH.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-pQ6LcA-W.js → Artifacts-Dr8rJi17.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DK61tocB.js → Audit-2V689HC2.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-BOKgDFRh.js → BackgroundAgents-DIxkNrD_.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Bo9G4vTP.js → Backup-Pg6BcwrE.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Do_dE9_v.js → BaseInput-CLUpqyfH.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-COVvZzOL.js → Chat-qVS8rD4G.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-ti49X12Q.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-Z9MJMolt.js → Checkbox-Bs9AaEay.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BVzWKVbf.js → Codegen-Dvc1mq8q.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-C8MnYcbT.js → Col-C4_biO7A.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DQxqw15l.js → Community-al25XQUV.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BAUmrh3y.js → Compact-D-ZDB8M1.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-jPAkQ3zK.js → Compliance-6lIfDz1W.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BBQyxbh8.js → Cowork-DdI48XMJ.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-W5koq8Z5.js → Cron-DF5HZd2g.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BQhnu5hS.js → Crosschain-DuxeSvt3.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DD7PlI18.js → DID-B9yHkUTd.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-w_ILuwKI.js → Dashboard-Z5EuI3IM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-qiDUAyAo.js → Dropdown-vuRfO3dY.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-PDeBcI5H.js → EmailListRenderer-QFLCc1uL.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-SuArscRM.js → FamilyGuardDashboard-BZ7EXkFJ.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BCovZaUn.js → Federation-DycIEg2s.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DjlFdVFk.js → FormItemContext-Ba0H_7yG.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DoEZ7fcS.js → GenericCardRenderer--JgSTK-C.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CV0-6GPI.js → Git-DEvTbldE.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-B_SFN50w.js → Governance-BJIljy18.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-heI0Eo6k.js → Inference-CVyW3G3q.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-Cz5INS5p.js → KnowledgeGraph-v8-borge.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BSFxiHkg.js → Logs-BCdS7gGo.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-0TWTpE7k.js → Marketplace-AN6tGA2B.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-CV4-wBLm.js → McpTools-DGViXASN.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-x5bH5rw5.js → Memory-Bpvmx9X4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DaBmUu9G.js → MobileBridge-CTP-DOTC.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-C8qfktxn.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-B_zKEBif.js → Mtc-ndYr0M1s.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-BCfKD2yg.js → MtcAudit-Ci0-YK7W.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-DgYfj6c_.js → Multisig-BehSTFW7.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Dz09lFUm.js → NLProgramming-B91_YBLv.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Cuj6ugBE.js → Notes-BQlJ0-gq.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DK2WXjhg.js → NotificationSettings-CDgYMjYK.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-D1IAOQoo.js → OrderTableRenderer-BJkSlKeH.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-9fqnPvTw.js → Organization-B-b3cWCp.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-n5xHkhow.js → Overflow-CILUL4nL.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-B5AUWwH-.js → P2P-BTCFBkF0.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CoUDV6dy.js → PdhVaultBrowser-BAR6AUx5.js} +4 -4
  52. package/src/assets/web-panel/assets/{Permissions-DPRmQf_H.js → Permissions-RRf6HbWJ.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-B37bsbEN.js → PersonalDataHub-BGTMiICa.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-CCAuPqu9.js → Pipeline-945qY9YF.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B81msZdv.js → Privacy-DPZwI6lJ.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-B-Py2NbF.js → ProjectInit-BPH2NRKc.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-CYsneiRB.js → ProjectSettings-BZCRNCvb.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Y975Mh6R.js → Projects-BTI__tBt.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-uO-10aP1.js → Providers-DDCf_nIw.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Bj_6gJ0l.js → QrScannerModal-N6rbqt1r.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-BgpBCMPE.js → QuickAsk-DrwjPS7M.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DhUW3YUA.js → Recommend-Cog9Wd99.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-DdeyfbkA.js → RemoteSession-DC9pynjM.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-fxW421xd.js → Reputation-D4th4gKS.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-De73w1og.js → Row-ogiWR6v9.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-3US_Q_lj.js → RssFeed-uYlk-2Oj.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DbZ_DU74.js → Search-DYzzwvtM.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-DoIU1vMF.js → Security-Da7YOyX6.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-DB_RGqpc.js → Services-CLI1VM1X.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DnzLaayC.js → Skeleton-DK77dzDC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-VzVXHENF.js → Skills-C_z7Lmhl.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DMfvjCAI.js → Sla-D6413Q8L.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-DQQOYWbF.js → SpeechSettings-C07ydbj2.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Bmx3FG1P.js → SyncSettings-DGOm2W22.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-DOQyfb-2.js → Tasks-Cr_PVyLM.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-D6hTRT7c.js → Templates-BhL2mZ0t.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-CTPQZAzs.js → Tenant-CPk5oyUr.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-B7vII3oH.js → Terminal-ClhqUsqV.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BkiFJeXV.js → TimelineRenderer-BCC7MV5a.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-BPPdQMNo.js → Tokens-DD1Jt4oO.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-BxG8JAEI.js → Trigger-BCydFvN2.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DHD7nMpM.js → Trust-DDHT1noa.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B4BJpU47.js → UkeySign-CWPZC7oI.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CRiGpFx2.js → VideoEditing-9Ea0nJR0.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BSDSNiem.js → Wallet-BjxhLxTf.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DLTUh5Zv.js → WebAuthn-C6uCye67.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-tW2MwjhD.js → WorkflowEditor-d2g4reYo.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-SgsLQ3FC.js → chat-frTc6ScX.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BHjjLIKO.js → colors-B5K6lG3v.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-VceErdKq.js → compact-item-Cdn46r8D.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BZ332MYl.js → createContext-DU1jqVQi.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-CBTjJOjn.js → hasIn-DdNH-aW_.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CawpdGTW.js → index-32ajXmTb.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BOtvPdHt.js → index-7ehytj1K.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-vIXOpcjD.js → index-BC4eGkar.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Cr4rjlW5.js → index-BVLL-mq_.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-D_SePDWj.js → index-BXKhhx9V.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BETEjuNr.js → index-BcGwGIaR.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BY1wexYG.js → index-Be4OtL-N.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BRWhLbWF.js → index-BoFak7le.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-29wVzi-k.js → index-BwlIxAmX.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BeiqkxJG.js → index-CGiwfwB9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BQSmZGgs.js → index-CHmkkdfT.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BhXZEUAc.js → index-CQorP1Ng.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C_B9rFQE.js → index-CUoNAmE8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-iYQNU9za.js → index-Cj6P9Nst.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DePOeMDM.js → index-CtRPi4YC.js} +1 -1
  109. package/src/assets/web-panel/assets/index-CzrCHrFy.js +1 -0
  110. package/src/assets/web-panel/assets/{index-DdefYBSw.js → index-D1qIC4GR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-By5jBtkV.js → index-D23xGYjM.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CMVvxIS8.js → index-D4l82Me1.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CLc19alH.js → index-D4miNkWv.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B2u26AS3.js → index-DIVyTmZ3.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DaCCy6lq.js → index-DM5FmPZU.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DUkVN794.js → index-DRzBBdQ0.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPXFLYdW.js → index-Dca_i7H8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CpumS98o.js → index-DftZuyA7.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CrUgDQkf.js → index-DtBCCkC4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CtjLcCNU.js → index-HjCvXHf5.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CaIS_TNX.js → index-LaivuXyF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BC1xuQgX.js → index-MM4Zz2OE.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BcUenmCX.js → index-NmEXVjzt.js} +1 -1
  124. package/src/assets/web-panel/assets/index-OJsGskZw.js +1 -0
  125. package/src/assets/web-panel/assets/{index-_PHrIO4O.js → index-PjAS0sIZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DgPvuIn2.js → index-VDM2KKo9.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-B5KAGxNo.js → index-cYaWV4Mq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B40dQn0G.js → index-gg3QPC3i.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-BTlc-Bve.js → index-kVO_MDm-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-FJ3zC9LK.js → index-mgU3Q-lN.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-1n7xjKDU.js → index-t_OBzIP4.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-fU7wNHDB.js → index-yZRuZpwK.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D1oZNz0D.js → initDefaultProps-DwLTRNdo.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-C8k6doMO.js → motion-BchZDXnh.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DJ4qSbYt.js → move-e8M-zZFl.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-CdpJD85j.js → mtc-parser-Ci40fQb3.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-B0V4slKA.js → omit-C7110_f2.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-Di5NiW1F.js → pickAttrs-skteMgnM.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-B5OMHgEM.js → placementArrow-MGZ-lh2B.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-C6U9IWql.js → responsiveObserve-N4Sd7uy-.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BMyTH3rN.js → slide-C7HDGRaa.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-C7d6BG7Z.js → statusUtils-BhRexlgy.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-CR50QnQy.js → styleChecker-ChEoQkN2.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DBEeoMb8.js → useFlexGapSupport-CDbG1s2Y.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-3r099cjv.js → useFs-CUIx0tcJ.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-C5IYNdTK.js → usePersonalDataHub-bkO7L7ec.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DmEOLRHf.js → vnode-B_ZvABIG.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DnpUiVCB.js → zoom-D5shjpS8.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +4 -4
  151. package/src/commands/agent.js +3 -3
  152. package/src/commands/background-session.js +145 -0
  153. package/src/commands/mcp.js +125 -7
  154. package/src/commands/serve.js +1 -1
  155. package/src/commands/session-show.js +131 -0
  156. package/src/commands/session.js +50 -106
  157. package/src/commands/skill.js +87 -50
  158. package/src/data/changelog.json +14 -1
  159. package/src/gateways/ws/ws-agent-handler.js +8 -1
  160. package/src/harness/jsonl-session-store.js +428 -25
  161. package/src/harness/mcp-client.js +2652 -404
  162. package/src/harness/session-list-index.js +65 -7
  163. package/src/lazy-dispatch.js +76 -8
  164. package/src/lib/abort-utils.js +68 -0
  165. package/src/lib/agent-core.js +1 -59
  166. package/src/lib/chat-core.js +24 -6
  167. package/src/lib/cowork-mcp-tools.js +47 -2
  168. package/src/lib/cowork-task-runner.js +11 -3
  169. package/src/lib/doctor-checkup.js +9 -7
  170. package/src/lib/learning/skill-improver.js +21 -2
  171. package/src/lib/mcp-call-ledger-store.js +116 -3
  172. package/src/lib/mcp-call-ledger.js +59 -14
  173. package/src/lib/mcp-client.js +1 -0
  174. package/src/lib/mcp-stdio-executable-identity.js +598 -0
  175. package/src/lib/mcp-stdio-execution-authority.js +363 -0
  176. package/src/lib/mcp-tool-metadata.js +436 -0
  177. package/src/lib/mcp-tool-result.js +367 -0
  178. package/src/lib/observability/index.js +71 -0
  179. package/src/lib/pr-link-ledger.js +8 -27
  180. package/src/lib/pr-link-store.js +26 -0
  181. package/src/lib/process-execution-broker/index.js +14 -0
  182. package/src/lib/process-tree-termination.js +253 -0
  183. package/src/lib/session-host-snapshot.js +9 -5
  184. package/src/lib/session-projection.js +5 -1
  185. package/src/lib/session-tail.js +31 -1
  186. package/src/lib/skill-budget.js +222 -0
  187. package/src/lib/skill-execution-authority.js +381 -0
  188. package/src/lib/skill-execution-identity.js +39 -2
  189. package/src/lib/skill-loader.js +322 -86
  190. package/src/lib/skill-mcp.js +43 -1
  191. package/src/repl/agent-repl.js +174 -38
  192. package/src/repl/bg-dashboard.js +81 -28
  193. package/src/repl/chat-repl.js +77 -12
  194. package/src/runtime/agent-core.js +340 -108
  195. package/src/runtime/agent-runtime.js +26 -3
  196. package/src/runtime/headless-runner.js +150 -23
  197. package/src/runtime/headless-stream.js +309 -59
  198. package/src/runtime/mcp-config.js +157 -10
  199. package/src/runtime/mcp-tool-search.js +0 -0
  200. package/src/runtime/output-backpressure.js +366 -0
  201. package/src/runtime/pipe-safety.js +56 -21
  202. package/src/assets/web-panel/assets/ChatBubbleRenderer-BUUyJ5QO.js +0 -1
  203. package/src/assets/web-panel/assets/MarkdownRenderer-1mNozZ8v.js +0 -1
  204. package/src/assets/web-panel/assets/MobileProjects-DqnoFPgR.js +0 -1
  205. package/src/assets/web-panel/assets/devWarning-DosMaL-k.js +0 -1
  206. package/src/assets/web-panel/assets/index-0O5ePnUq.js +0 -1
  207. package/src/assets/web-panel/assets/index-ChAzPigF.js +0 -1
  208. package/src/lib/skill-process-broker.js +0 -85
@@ -25,6 +25,7 @@ import path from "path";
25
25
  import { randomUUID } from "node:crypto";
26
26
  import { isPromise, isProxy } from "node:util/types";
27
27
  import { logger } from "../lib/logger.js";
28
+ import { issueMcpStdioExecutionAuthority } from "../lib/mcp-stdio-execution-authority.js";
28
29
  import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
29
30
  import { createVimState, feedNormalKey } from "../lib/repl-vim.js";
30
31
  import {
@@ -126,6 +127,7 @@ import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
126
127
  import { prepareVisionTurn, resolveVisionLlm } from "../lib/image-input.js";
127
128
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
128
129
  import { installPipeSafety } from "../runtime/pipe-safety.js";
130
+ import { installOutputBackpressure } from "../runtime/output-backpressure.js";
129
131
  import {
130
132
  makeFallbackChatFn,
131
133
  normalizeFallbackModels,
@@ -323,6 +325,9 @@ async function executeTool(name, args) {
323
325
  * unit-testable via the `options._coreLoop` injection seam.
324
326
  */
325
327
  export async function agentLoop(messages, options) {
328
+ const writeOut =
329
+ options.writeOut || ((text) => process.stdout.write(String(text)));
330
+ const waitForOutput = options.waitForOutput;
326
331
  // Resume-degenerate role merge (Claude Code 2.1.187 parity), gated by the
327
332
  // one-shot `mergeRoles` flag so it fires only on the first model call after
328
333
  // resuming a session whose prior run produced no assistant response. Collapse
@@ -343,7 +348,7 @@ export async function agentLoop(messages, options) {
343
348
  const onProviderFallback =
344
349
  options.onProviderFallback ||
345
350
  ((info) =>
346
- process.stdout.write(
351
+ writeOut(
347
352
  chalk.yellow(
348
353
  `\n ⚠️ ${info.message || `已从 "${info.from}" 切换到 "${info.to}"`}\n`,
349
354
  ),
@@ -380,7 +385,7 @@ export async function agentLoop(messages, options) {
380
385
  event.type === "managed-checkpoint-error" || event.coverage === "none"
381
386
  ? chalk.yellow
382
387
  : chalk.gray;
383
- process.stdout.write(render(`${managedCheckpointLine}\n`));
388
+ writeOut(render(`${managedCheckpointLine}\n`));
384
389
  } else if (event.type === "checkpoint") {
385
390
  // Remember which file snapshot lines up with the live conversation so
386
391
  // `/rewind <n>` can restore code + conversation together (Claude-Code
@@ -393,7 +398,7 @@ export async function agentLoop(messages, options) {
393
398
  tool: event.tool,
394
399
  });
395
400
  }
396
- process.stdout.write(
401
+ writeOut(
397
402
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
398
403
  );
399
404
  } else if (event.type === "tool-executing") {
@@ -402,7 +407,7 @@ export async function agentLoop(messages, options) {
402
407
  args: event.args,
403
408
  startedAt: options.now ? options.now() : Date.now(),
404
409
  };
405
- process.stdout.write(
410
+ writeOut(
406
411
  chalk.gray(
407
412
  ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
408
413
  ),
@@ -438,9 +443,7 @@ export async function agentLoop(messages, options) {
438
443
  }
439
444
  }
440
445
  if (event.error || event.result?.error) {
441
- process.stdout.write(
442
- chalk.red(` Error: ${event.error || event.result?.error}\n`),
443
- );
446
+ writeOut(chalk.red(` Error: ${event.error || event.result?.error}\n`));
444
447
  // Record policy denials (not plain tool failures) into the caller's
445
448
  // denial log for review via `/permissions denials` (Claude-Code 2.1.193
446
449
  // recent denials). Caller passes the session log (mirrors checkpointMarks)
@@ -477,13 +480,13 @@ export async function agentLoop(messages, options) {
477
480
  const sid = options?.sessionId;
478
481
  const policy = approval.policy || "strict";
479
482
  if (sid && policy === "strict") {
480
- process.stdout.write(
483
+ writeOut(
481
484
  chalk.yellow(
482
485
  ` Hint: relax policy with cc session policy ${sid} --set trusted\n`,
483
486
  ),
484
487
  );
485
488
  } else if (sid) {
486
- process.stdout.write(
489
+ writeOut(
487
490
  chalk.yellow(
488
491
  ` Hint: per-session policy is "${policy}" — see cc session policy ${sid}\n`,
489
492
  ),
@@ -491,26 +494,26 @@ export async function agentLoop(messages, options) {
491
494
  }
492
495
  }
493
496
  } else if (event.result?.success) {
494
- process.stdout.write(chalk.green(` Done\n`));
497
+ writeOut(chalk.green(` Done\n`));
495
498
  }
496
499
  } else if (event.type === "thinking") {
497
500
  // Intermediate-step reasoning (before a tool call) — dimmed, inline.
498
501
  if (process.env.CC_REPL_THINKING !== "0" && event.text) {
499
- process.stdout.write(
502
+ writeOut(
500
503
  "\n" + chalk.dim("💭 " + event.text.replace(/\n/g, "\n ")) + "\n",
501
504
  );
502
505
  }
503
506
  } else if (event.type === "token-usage") {
504
507
  usageEvents.push(event);
505
508
  } else if (event.type === "iteration-warning") {
506
- process.stdout.write(chalk.yellow(`\n ${event.message}\n`));
509
+ writeOut(chalk.yellow(`\n ${event.message}\n`));
507
510
  } else if (event.type === "iteration-budget-exhausted") {
508
- process.stdout.write(
509
- chalk.red(`\n [Budget Exhausted] ${event.budget}\n`),
510
- );
511
+ writeOut(chalk.red(`\n [Budget Exhausted] ${event.budget}\n`));
511
512
  } else if (event.type === "response-complete") {
513
+ await waitForOutput?.();
512
514
  return { content: event.content, usageEvents, thinking: event.thinking };
513
515
  }
516
+ await waitForOutput?.();
514
517
  }
515
518
  return { content: "", usageEvents };
516
519
  }
@@ -1934,7 +1937,12 @@ export function createReplMcpHostRuntimeManager(dependencies = {}) {
1934
1937
  }
1935
1938
 
1936
1939
  const sink = persistLedger
1937
- ? createLedgerSink(sessionId, { appendEvent: appendLedgerEvent })
1940
+ ? createLedgerSink(
1941
+ sessionId,
1942
+ appendLedgerEvent === appendAuthorityEvent
1943
+ ? { recovery }
1944
+ : { appendEvent: appendLedgerEvent },
1945
+ )
1938
1946
  : null;
1939
1947
  const sharedController =
1940
1948
  !verifiedRecovery && current?.sessionId === sessionId
@@ -2068,27 +2076,93 @@ export async function startAgentRepl(options = {}) {
2068
2076
  }
2069
2077
 
2070
2078
  async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2079
+ let failureHandler = null;
2080
+ let disposePipeSafety = null;
2081
+ const flow = installOutputBackpressure({
2082
+ stdout: process.stdout,
2083
+ stderr: process.stderr,
2084
+ maxQueuedBytes: options.outputBackpressureMaxBytes,
2085
+ onFailure: (error) => failureHandler?.(error),
2086
+ });
2087
+ let restored = false;
2088
+ const outputScope = {
2089
+ flow,
2090
+ setFailureHandler(handler) {
2091
+ failureHandler = handler;
2092
+ },
2093
+ setPipeDisposer(disposer) {
2094
+ disposePipeSafety = disposer;
2095
+ },
2096
+ restore() {
2097
+ if (restored) return;
2098
+ restored = true;
2099
+ disposePipeSafety?.();
2100
+ flow.restore();
2101
+ },
2102
+ };
2103
+ try {
2104
+ return await startAgentReplInWorkspaceOwned(
2105
+ options,
2106
+ startupAdmission,
2107
+ outputScope,
2108
+ );
2109
+ } catch (error) {
2110
+ outputScope.restore();
2111
+ throw error;
2112
+ }
2113
+ }
2114
+
2115
+ async function startAgentReplInWorkspaceOwned(
2116
+ options = {},
2117
+ startupAdmission,
2118
+ outputScope,
2119
+ ) {
2071
2120
  // EPIPE guard: if the REPL's stdout is piped and the consumer closes (e.g.
2072
2121
  // `cc agent | head`), the async stream `error` would otherwise crash the
2073
2122
  // process. Route a broken pipe into the REPL's own graceful shutdown (the
2074
2123
  // rl "close" handler — MCP disconnect, kill background tasks) when the
2075
- // interface exists, else exit cleanly. `_replClosing` makes it fire once so a
2076
- // cleanup write that also EPIPEs can't loop. `_replRl` is set when rl is built.
2124
+ // interface and its close handler exist. `_replClosing` makes it fire once so
2125
+ // a cleanup write that also EPIPEs can't loop. An early EPIPE remains pending
2126
+ // until the close handler is attached; it never skips directly to exit.
2077
2127
  let _replRl = null;
2078
2128
  let _replClosing = false;
2079
- installPipeSafety(undefined, () => {
2129
+ let _replCloseReady = false;
2130
+ let _replCleanupStarted = false;
2131
+ let _turnAbort = null;
2132
+ let _replOutputFailure = null;
2133
+ const _requestReplOutputClose = (error) => {
2134
+ if (error && !_replOutputFailure) _replOutputFailure = error;
2135
+ if (error) {
2136
+ process.exitCode = error.code === "EPIPE" ? 0 : 1;
2137
+ _turnAbort?.abort(error);
2138
+ }
2080
2139
  if (_replClosing) return;
2081
2140
  _replClosing = true;
2082
- if (_replRl) {
2141
+ if (_replRl && _replCloseReady) {
2083
2142
  try {
2084
- _replRl.close(); // triggers the graceful "close" cleanup → process.exit
2085
- return;
2143
+ _replRl.close(); // triggers the graceful "close" cleanup
2086
2144
  } catch {
2087
- /* fall through to a bare exit */
2145
+ // The close handler owns shutdown. Leaving exitCode set allows the
2146
+ // event loop to drain without bypassing the teardown lifecycle.
2088
2147
  }
2089
2148
  }
2090
- process.exit(0);
2091
- });
2149
+ };
2150
+ const _replOutputFlow = outputScope.flow;
2151
+ outputScope.setFailureHandler(_requestReplOutputClose);
2152
+ const _disposeReplPipeSafety = installPipeSafety(undefined, () =>
2153
+ _requestReplOutputClose(
2154
+ Object.assign(new Error("REPL output pipe closed"), { code: "EPIPE" }),
2155
+ ),
2156
+ );
2157
+ outputScope.setPipeDisposer(_disposeReplPipeSafety);
2158
+ const _waitForReplOutput = async () => {
2159
+ try {
2160
+ await _replOutputFlow.wait();
2161
+ return true;
2162
+ } catch {
2163
+ return false;
2164
+ }
2165
+ };
2092
2166
  const { useJsonl, candidate: _startupJsonlResume } = startupAdmission;
2093
2167
  let model = options.model || "qwen2.5:7b";
2094
2168
  let provider = options.provider || "ollama";
@@ -2988,7 +3062,15 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2988
3062
  let connected = 0;
2989
3063
  for (const [name, cfg] of serverEntries) {
2990
3064
  try {
2991
- await _bundleMcpClient.connect(name, cfg);
3065
+ const authorizedConfig = { ...cfg };
3066
+ authorizedConfig.mcpStdioExecutionAuthority =
3067
+ issueMcpStdioExecutionAuthority({
3068
+ serverName: name,
3069
+ config: authorizedConfig,
3070
+ approvalKind: "explicit-config",
3071
+ approvalSource: `agent-bundle:${path.resolve(options.bundlePath)}`,
3072
+ });
3073
+ await _bundleMcpClient.connect(name, authorizedConfig);
2992
3074
  connected += 1;
2993
3075
  } catch (mcpErr) {
2994
3076
  logger.log(
@@ -3868,7 +3950,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3868
3950
  // seam (throwIfAborted at each iteration); partial conversation is kept.
3869
3951
  // Idle Esc presses (no active turn) are ignored, and escape-prefixed key
3870
3952
  // sequences (arrows etc.) never reach here as bare "escape".
3871
- let _turnAbort = null;
3953
+ _turnAbort = null;
3872
3954
  let _lastIdleEscAt = 0;
3873
3955
  let _replKeypressHandler = null;
3874
3956
  if (process.stdin.isTTY) {
@@ -4089,7 +4171,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4089
4171
  });
4090
4172
  // Custom command wins; otherwise the built-in context-usage render.
4091
4173
  if (_slCfg) {
4092
- return _sl.renderStatusLine(_slCfg, context, { cwd: process.cwd() });
4174
+ return _sl.renderStatusLine(_slCfg, context, {
4175
+ cwd: process.cwd(),
4176
+ });
4093
4177
  }
4094
4178
  const line = _sl.renderDefaultStatusLine(context);
4095
4179
  return line && line.trim() ? line : null;
@@ -4423,7 +4507,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4423
4507
  ` ${chalk.cyan("/add-dir")} Add an extra working root (/add-dir <dir>; no arg lists roots)`,
4424
4508
  );
4425
4509
  logger.log(
4426
- ` ${chalk.cyan("/reload-skills")} Re-scan skill layers without restarting`,
4510
+ ` ${chalk.cyan("/reload-skills")} Revoke process Skill grants and re-scan layers`,
4427
4511
  );
4428
4512
  logger.log(
4429
4513
  ` ${chalk.cyan("/reload-plugins")} Re-scan installed plugins after add/trust/upgrade`,
@@ -5283,7 +5367,11 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5283
5367
  dispatchAsyncHooks(
5284
5368
  _settingsHooks,
5285
5369
  "CwdChanged",
5286
- { old_cwd: oldCwd, cwd: newCwd, session_id: sessionId || null },
5370
+ {
5371
+ old_cwd: oldCwd,
5372
+ cwd: newCwd,
5373
+ session_id: sessionId || null,
5374
+ },
5287
5375
  { cwd: newCwd, supervisor: _asyncHookSupervisor },
5288
5376
  );
5289
5377
  } catch {
@@ -5847,6 +5935,11 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5847
5935
  // layers (incl. .claude/skills) without restarting the session.
5848
5936
  if (trimmed === "/reload-skills") {
5849
5937
  try {
5938
+ _replSkillLoader.revokeExecutionAuthorizations?.({
5939
+ message:
5940
+ "Skill execution authorization was revoked by /reload-skills",
5941
+ reasonCode: "reload-skills",
5942
+ });
5850
5943
  _replSkillLoader.clearCache();
5851
5944
  const n = _replSkillLoader.loadAll().length;
5852
5945
  _replBaseSystem = _buildReplBaseSystem();
@@ -5856,7 +5949,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5856
5949
  _persistReplContextSources();
5857
5950
  logger.log(
5858
5951
  chalk.green(
5859
- `✔ skills reloaded — ${n} available (${_replSkillLoader.getLayerPaths().length} layers re-scanned)`,
5952
+ `✔ Skill grants revoked and skills reloaded — ${n} available (${_replSkillLoader.getLayerPaths().length} layers re-scanned)`,
5860
5953
  ),
5861
5954
  );
5862
5955
  } catch (err) {
@@ -6069,7 +6162,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
6069
6162
  logger.log(chalk.gray(l));
6070
6163
  } else {
6071
6164
  // set — createReplGoal throws on a malformed spec (caught below).
6072
- _sessionGoal = rg.createReplGoal(cmd.spec, { now: () => Date.now() });
6165
+ _sessionGoal = rg.createReplGoal(cmd.spec, {
6166
+ now: () => Date.now(),
6167
+ });
6073
6168
  for (const l of rg.renderGoalStart(_sessionGoal))
6074
6169
  logger.log(chalk.cyan(l));
6075
6170
  }
@@ -7679,22 +7774,29 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7679
7774
  // reasoning dimmed before it. Skipped (left undefined) in replay mode.
7680
7775
  let _liveStreamed = false;
7681
7776
  let _liveThinkStarted = false;
7777
+ const _settleLiveWrite = (blocked) =>
7778
+ blocked ? _replOutputFlow.wait() : undefined;
7682
7779
  const liveOpts = _streamLive
7683
7780
  ? {
7684
7781
  onToken: (t) => {
7685
7782
  // Separate the answer from the dimmed reasoning above it (once).
7686
- if (!_liveStreamed && _liveThinkStarted)
7687
- process.stdout.write("\n");
7783
+ let blocked = false;
7784
+ if (!_liveStreamed && _liveThinkStarted) {
7785
+ blocked = process.stdout.write("\n") === false;
7786
+ }
7688
7787
  _liveStreamed = true;
7689
- process.stdout.write(t);
7788
+ blocked = process.stdout.write(t) === false || blocked;
7789
+ return _settleLiveWrite(blocked);
7690
7790
  },
7691
7791
  onThinking: (t) => {
7692
7792
  if (process.env.CC_REPL_THINKING === "0") return;
7793
+ let blocked = false;
7693
7794
  if (!_liveThinkStarted) {
7694
7795
  process.stdout.write(chalk.dim("💭 "));
7695
7796
  _liveThinkStarted = true;
7696
7797
  }
7697
- process.stdout.write(chalk.dim(t));
7798
+ blocked = process.stdout.write(chalk.dim(t)) === false || blocked;
7799
+ return _settleLiveWrite(blocked);
7698
7800
  },
7699
7801
  }
7700
7802
  : {};
@@ -7726,7 +7828,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7726
7828
  if (_settingsHooks && !_asyncHookSupervisor) {
7727
7829
  const { AsyncHookSupervisor } =
7728
7830
  await import("../lib/async-hook-supervisor.js");
7729
- _asyncHookSupervisor = new AsyncHookSupervisor({ persistStats: true });
7831
+ _asyncHookSupervisor = new AsyncHookSupervisor({
7832
+ persistStats: true,
7833
+ });
7730
7834
  }
7731
7835
  const activeMcpRuntime = _activateMcpHostRuntime();
7732
7836
  const activeRawMcpClient = activeMcpRuntime.rawClient || undefined;
@@ -7736,6 +7840,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7736
7840
  thinking: reasoning,
7737
7841
  } = await agentLoop(messages, {
7738
7842
  ...liveOpts,
7843
+ waitForOutput: _replOutputFlow.wait,
7739
7844
  mergeRoles: _mergeRolesThisTurn,
7740
7845
  // Visible auto-retry feedback (Claude-Code 2.1.181): when the model's
7741
7846
  // streaming call hits a transient connection drop and retries, tell the
@@ -8170,6 +8275,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8170
8275
  };
8171
8276
 
8172
8277
  rl.on("line", async (input) => {
8278
+ if (!(await _waitForReplOutput())) return;
8173
8279
  if (_processingLine) {
8174
8280
  const concurrentBtw = _slashCommandsDisabled
8175
8281
  ? null
@@ -8193,10 +8299,12 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8193
8299
  _processingLine = true;
8194
8300
  try {
8195
8301
  await handleLine(input);
8302
+ if (!(await _waitForReplOutput())) return;
8196
8303
  while (_pendingLines.length) {
8197
8304
  const next = _pendingLines.shift();
8198
8305
  logger.log(chalk.cyan(`▶ running queued input: ${next}`));
8199
8306
  await handleLine(next);
8307
+ if (!(await _waitForReplOutput())) return;
8200
8308
  }
8201
8309
  } finally {
8202
8310
  _processingLine = false;
@@ -8204,6 +8312,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8204
8312
  });
8205
8313
 
8206
8314
  rl.on("close", async () => {
8315
+ if (_replCleanupStarted) return;
8316
+ _replCleanupStarted = true;
8317
+ _replClosing = true;
8207
8318
  _promptInteractions.dispose();
8208
8319
  if (process.stdin.isTTY) {
8209
8320
  if (_replKeypressHandler) {
@@ -8391,6 +8502,31 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8391
8502
  } catch (_e) {
8392
8503
  // Non-critical
8393
8504
  }
8394
- process.exit(0);
8505
+ if (!_replOutputFailure) {
8506
+ try {
8507
+ await _replOutputFlow.wait();
8508
+ } catch (error) {
8509
+ _replOutputFailure ||= error;
8510
+ }
8511
+ }
8512
+ const outputExitCode = _replOutputFailure
8513
+ ? _replOutputFailure.code === "EPIPE"
8514
+ ? 0
8515
+ : 1
8516
+ : 0;
8517
+ outputScope.restore();
8518
+ process.exit(outputExitCode);
8395
8519
  });
8520
+ _replCloseReady = true;
8521
+ if (_replClosing) {
8522
+ // stdout may have broken before readline and its cleanup handler were fully
8523
+ // initialized. Close only now so the complete async teardown runs.
8524
+ queueMicrotask(() => {
8525
+ try {
8526
+ rl.close();
8527
+ } catch {
8528
+ process.exitCode = 0;
8529
+ }
8530
+ });
8531
+ }
8396
8532
  }
@@ -20,6 +20,8 @@
20
20
 
21
21
  import chalk from "chalk";
22
22
  import { phaseGroupKey } from "../lib/background-agent-phase.js";
23
+ import { installPipeSafety } from "../runtime/pipe-safety.js";
24
+ import { installOutputBackpressure } from "../runtime/output-backpressure.js";
23
25
 
24
26
  export const GROUP_ORDER = [
25
27
  "needs-input",
@@ -171,31 +173,52 @@ const ALT_EXIT = `${ESC}[?25h${ESC}[?1049l`;
171
173
  const CLEAR = `${ESC}[2J${ESC}[H`;
172
174
 
173
175
  /** Default terminal IO — swapped for a fake in tests. */
174
- export function defaultDashboardIo() {
176
+ export function defaultDashboardIo({
177
+ input = process.stdin,
178
+ stdout = process.stdout,
179
+ stderr = process.stderr,
180
+ } = {}) {
181
+ let failure = null;
182
+ const recordFailure = (error) => {
183
+ failure ||= error;
184
+ process.exitCode = error?.code === "EPIPE" ? 0 : 1;
185
+ };
186
+ const outputFlow = installOutputBackpressure({
187
+ stdout,
188
+ stderr,
189
+ onFailure: recordFailure,
190
+ });
191
+ const disposePipeSafety = installPipeSafety([stdout, stderr], () =>
192
+ recordFailure(
193
+ Object.assign(new Error("dashboard output pipe closed"), {
194
+ code: "EPIPE",
195
+ }),
196
+ ),
197
+ );
175
198
  return {
176
- isTTY: Boolean(process.stdout.isTTY && process.stdin.isTTY),
177
- write: (s) => process.stdout.write(s),
178
- enterAlt: () => process.stdout.write(ALT_ENTER),
179
- exitAlt: () => process.stdout.write(ALT_EXIT),
180
- clear: () => process.stdout.write(CLEAR),
199
+ isTTY: Boolean(stdout.isTTY && input.isTTY),
200
+ write: (s) => stdout.write(s),
201
+ enterAlt: () => stdout.write(ALT_ENTER),
202
+ exitAlt: () => stdout.write(ALT_EXIT),
203
+ clear: () => stdout.write(CLEAR),
181
204
  setRaw: (on) => {
182
- if (process.stdin.isTTY && process.stdin.setRawMode) {
183
- process.stdin.setRawMode(on);
205
+ if (input.isTTY && input.setRawMode) {
206
+ input.setRawMode(on);
184
207
  }
185
208
  },
186
209
  async listenKeys(onKey) {
187
210
  const readline = await import("node:readline");
188
- readline.emitKeypressEvents(process.stdin);
189
- process.stdin.resume();
211
+ readline.emitKeypressEvents(input);
212
+ input.resume();
190
213
  const handler = (str, key) => onKey(str, key || {});
191
- process.stdin.on("keypress", handler);
192
- return () => process.stdin.off("keypress", handler);
214
+ input.on("keypress", handler);
215
+ return () => input.off("keypress", handler);
193
216
  },
194
217
  async promptLine(question) {
195
218
  const readline = await import("node:readline");
196
219
  const rl = readline.createInterface({
197
- input: process.stdin,
198
- output: process.stdout,
220
+ input,
221
+ output: stdout,
199
222
  });
200
223
  try {
201
224
  return await new Promise((resolve) => rl.question(question, resolve));
@@ -203,6 +226,15 @@ export function defaultDashboardIo() {
203
226
  rl.close();
204
227
  }
205
228
  },
229
+ async wait() {
230
+ if (failure) throw failure;
231
+ await outputFlow.wait();
232
+ if (failure) throw failure;
233
+ },
234
+ dispose() {
235
+ disposePipeSafety();
236
+ outputFlow.restore();
237
+ },
206
238
  };
207
239
  }
208
240
 
@@ -224,23 +256,36 @@ export async function runBgDashboard(deps = {}) {
224
256
  let filterIndex = 0;
225
257
  let peek = null;
226
258
  let message = "";
259
+ let outputFailed = false;
260
+ let finishDashboard = () => {};
261
+ let renderChain = Promise.resolve();
227
262
  let model = buildDashboardModel(listAgents(), {
228
263
  filter: FILTERS[filterIndex],
229
264
  now: now(),
230
265
  });
231
266
 
232
267
  const render = () => {
233
- model = buildDashboardModel(listAgents(), {
234
- filter: FILTERS[filterIndex],
235
- now: now(),
236
- });
237
- if (selected >= model.flat.length)
238
- selected = Math.max(0, model.flat.length - 1);
239
- if (peek && !model.flat.some((s) => s.id === peek.id)) peek = null;
240
- io.clear();
241
- io.write(
242
- renderDashboard(model, { selectedIndex: selected, peek, message }) + "\n",
243
- );
268
+ renderChain = renderChain
269
+ .then(async () => {
270
+ model = buildDashboardModel(listAgents(), {
271
+ filter: FILTERS[filterIndex],
272
+ now: now(),
273
+ });
274
+ if (selected >= model.flat.length)
275
+ selected = Math.max(0, model.flat.length - 1);
276
+ if (peek && !model.flat.some((s) => s.id === peek.id)) peek = null;
277
+ io.clear();
278
+ io.write(
279
+ renderDashboard(model, { selectedIndex: selected, peek, message }) +
280
+ "\n",
281
+ );
282
+ await io.wait?.();
283
+ })
284
+ .catch(() => {
285
+ outputFailed = true;
286
+ finishDashboard();
287
+ });
288
+ return renderChain;
244
289
  };
245
290
 
246
291
  const selectedSession = () => model.flat[selected] || null;
@@ -259,13 +304,15 @@ export async function runBgDashboard(deps = {}) {
259
304
 
260
305
  io.enterAlt();
261
306
  io.setRaw(true);
262
- const timer = setInterval(render, refreshMs);
307
+ const timer = setInterval(() => void render(), refreshMs);
263
308
  timer.unref?.();
264
- render();
309
+ await render();
265
310
 
266
311
  let stopListening = () => {};
267
312
  await new Promise((resolve) => {
268
313
  const finish = () => resolve();
314
+ finishDashboard = finish;
315
+ if (outputFailed) finish();
269
316
  const onKey = async (str, key) => {
270
317
  const name = key?.name || str;
271
318
  const ctrlC = key?.ctrl && key.name === "c";
@@ -342,7 +389,7 @@ export async function runBgDashboard(deps = {}) {
342
389
  } catch (err) {
343
390
  message = `error: ${err.message}`;
344
391
  }
345
- render();
392
+ await render();
346
393
  };
347
394
  io.listenKeys(onKey).then((stop) => {
348
395
  stopListening = stop || (() => {});
@@ -353,4 +400,10 @@ export async function runBgDashboard(deps = {}) {
353
400
  stopListening();
354
401
  io.setRaw(false);
355
402
  io.exitAlt();
403
+ try {
404
+ await io.wait?.();
405
+ } catch {
406
+ // The output owner already classified the exit code.
407
+ }
408
+ io.dispose?.();
356
409
  }