chainlesschain 0.162.197 → 0.162.200

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 (233) hide show
  1. package/bin/chainlesschain.js +14 -6
  2. package/package.json +4 -1
  3. package/scripts/command-lifecycle-report.mjs +94 -0
  4. package/src/assets/web-panel/assets/{AIOps-D3Gu1a5d.js → AIOps-DcQ0bWKy.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DrwAoKaz.js → ActionButton-GecwfFPt.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-C7Q-gd_3.js → Analytics-CD6iSt9v.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-DgBtae-n.js → AppLayout-CDEtuEYf.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-pQ6LcA-W.js → Artifacts-DS3jAjeW.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DK61tocB.js → Audit-CcSJA048.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-BOKgDFRh.js → BackgroundAgents-DRo9Morb.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-Bo9G4vTP.js → Backup-Vw5MrWft.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-Do_dE9_v.js → BaseInput-DNHIu823.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-COVvZzOL.js → Chat-DvAsO8-b.js} +4 -4
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-9aDuObay.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-Z9MJMolt.js → Checkbox-CKCa7tvn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BVzWKVbf.js → Codegen-CIFvZdaT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-C8MnYcbT.js → Col-C79yz7bK.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-DQxqw15l.js → Community-DVvZskLJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BAUmrh3y.js → Compact-oaew61hV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-jPAkQ3zK.js → Compliance-DqA7wmRx.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-BBQyxbh8.js → Cowork-B-qGadxa.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-W5koq8Z5.js → Cron-CUlCl4sS.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BQhnu5hS.js → Crosschain-CNOlr9DU.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-DD7PlI18.js → DID-DJqFCT_3.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-w_ILuwKI.js → Dashboard-CznGpphf.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-qiDUAyAo.js → Dropdown-_cJw218E.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-PDeBcI5H.js → EmailListRenderer-Dr-1BZ9C.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-SuArscRM.js → FamilyGuardDashboard-Dxf4xtSk.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BCovZaUn.js → Federation-DOiG3i_1.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DjlFdVFk.js → FormItemContext-BzbMcFB9.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DoEZ7fcS.js → GenericCardRenderer-VbBrYttP.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-CV0-6GPI.js → Git-CLG8E6ED.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-B_SFN50w.js → Governance-LGJQfk_q.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-heI0Eo6k.js → Inference-DlQKx9lF.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-Cz5INS5p.js → KnowledgeGraph-DU8vEouV.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BSFxiHkg.js → Logs-FZIQOdln.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BJNWIrbw.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-0TWTpE7k.js → Marketplace-C_xd-zZI.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-CV4-wBLm.js → McpTools-q-YktphS.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-x5bH5rw5.js → Memory-Cq9OhDin.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-DaBmUu9G.js → MobileBridge-B03_R98T.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-DZrk-qmF.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-B_zKEBif.js → Mtc-BidCKW28.js} +5 -5
  44. package/src/assets/web-panel/assets/{MtcAudit-BCfKD2yg.js → MtcAudit-r-iaMVM7.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-DgYfj6c_.js → Multisig-CgTUFvff.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-Dz09lFUm.js → NLProgramming-B7NvzfAV.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-Cuj6ugBE.js → Notes-80CWNqJI.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-DK2WXjhg.js → NotificationSettings-0H6xdiY3.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-D1IAOQoo.js → OrderTableRenderer-CKP4AFWy.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-9fqnPvTw.js → Organization-B8sxCV_h.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-n5xHkhow.js → Overflow-CW9JBBhQ.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-B5AUWwH-.js → P2P-bdSQV7oZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CoUDV6dy.js → PdhVaultBrowser-DQad42nW.js} +4 -4
  54. package/src/assets/web-panel/assets/{Permissions-DPRmQf_H.js → Permissions-CueexxTR.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-B37bsbEN.js → PersonalDataHub-fqxflhR8.js} +6 -6
  56. package/src/assets/web-panel/assets/{Pipeline-CCAuPqu9.js → Pipeline-KFwwnIMC.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-B81msZdv.js → Privacy-ClnD9buY.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-B-Py2NbF.js → ProjectInit-CZEz6sm3.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CYsneiRB.js → ProjectSettings-B6NzpmOI.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-Y975Mh6R.js → Projects-DYyuwPCs.js} +1 -1
  61. package/src/assets/web-panel/assets/Providers-C3tz_jdh.js +1 -0
  62. package/src/assets/web-panel/assets/{QrScannerModal-Bj_6gJ0l.js → QrScannerModal-BzuOBbqB.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-BgpBCMPE.js → QuickAsk-CmNO9qSj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-DhUW3YUA.js → Recommend-VJNj3_NF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-DdeyfbkA.js → RemoteSession-CrbdbSBF.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-fxW421xd.js → Reputation-DTyMd45j.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-De73w1og.js → Row-DHrQ-Icz.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-3US_Q_lj.js → RssFeed-DqWxY9c5.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-DbZ_DU74.js → Search-ctF1Ks3J.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-DoIU1vMF.js → Security-BrQz8uo5.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-DB_RGqpc.js → Services-D4scVFar.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DnzLaayC.js → Skeleton-oUScaOjv.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-VzVXHENF.js → Skills-CL-y0eLt.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DMfvjCAI.js → Sla-DQExhwXj.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-DQQOYWbF.js → SpeechSettings-iNp_I7kN.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-Bmx3FG1P.js → SyncSettings-CTDlMo0A.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-DOQyfb-2.js → Tasks-4kb8l3sc.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-D6hTRT7c.js → Templates-BXcL9oXW.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-CTPQZAzs.js → Tenant-DLE8eS0D.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-B7vII3oH.js → Terminal-DGpUZPPK.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BkiFJeXV.js → TimelineRenderer-C1pWXGPC.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-BPPdQMNo.js → Tokens-BYfbfqhZ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BxG8JAEI.js → Trigger-K9YfLOCw.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DHD7nMpM.js → Trust-Ctkq77Hy.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-B4BJpU47.js → UkeySign-C6_CSe9J.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-CRiGpFx2.js → VideoEditing-Be3rpOWs.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-BSDSNiem.js → Wallet-D0UIplxk.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-DLTUh5Zv.js → WebAuthn-CF9bM9I3.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-tW2MwjhD.js → WorkflowEditor-D0H1XVPY.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-SgsLQ3FC.js → chat-Cgzzbe0o.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-BHjjLIKO.js → colors-BwNxEVS0.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-VceErdKq.js → compact-item-D60Z4Hhs.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BZ332MYl.js → createContext-MFk58XB0.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BJiw369i.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-CBTjJOjn.js → hasIn-CRj2SvGO.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DaCCy6lq.js → index--s45_1cb.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CMVvxIS8.js → index--tquQ2ZG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BhXZEUAc.js → index-1T1rhokn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BQSmZGgs.js → index-B-M82_rx.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BeiqkxJG.js → index-BE_el1Zs.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-29wVzi-k.js → index-BhRkR7IO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-iYQNU9za.js → index-Bo0y96-K.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BRWhLbWF.js → index-CJgu5JUK.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BPXFLYdW.js → index-CLWwdebO.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cr4rjlW5.js → index-CNAK5QG_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CaIS_TNX.js → index-CQjJjPRD.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-1n7xjKDU.js → index-CTDbAfae.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BETEjuNr.js → index-CVI_9sRK.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-By5jBtkV.js → index-CY14oMQK.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DgPvuIn2.js → index-CbiTJkZf.js} +1 -1
  111. package/src/assets/web-panel/assets/index-CiMouosn.js +1 -0
  112. package/src/assets/web-panel/assets/{index-CawpdGTW.js → index-CqWpxMqx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-_PHrIO4O.js → index-CvB8lYUR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BOtvPdHt.js → index-D51XwtCY.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CrUgDQkf.js → index-D6HvK_1O.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BY1wexYG.js → index-D9jVIwLJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C_B9rFQE.js → index-DA4tE98u.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B5KAGxNo.js → index-DBwOtXw0.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BC1xuQgX.js → index-DKdNzTIb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B2u26AS3.js → index-DRySdmvN.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DePOeMDM.js → index-DWzyTev7.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DXcKlwy7.js +1 -0
  123. package/src/assets/web-panel/assets/{index-BTlc-Bve.js → index-DYjs7jAP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CLc19alH.js → index-DbROzMBp.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-fU7wNHDB.js → index-DhFtk5PR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D_SePDWj.js → index-Di2AbMLg.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-vIXOpcjD.js → index-DvmocQsM.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BcUenmCX.js → index-Dw-o0zEF.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DUkVN794.js → index-DysPoP38.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CtjLcCNU.js → index-LX9pJxm_.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B40dQn0G.js → index-OI_fXWx0.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-FJ3zC9LK.js → index-eDTOX8Xj.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DdefYBSw.js → index-gWKDsTzF.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CpumS98o.js → index-q_GDUVAA.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-D1oZNz0D.js → initDefaultProps-C61kZqEd.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-C8k6doMO.js → motion-DZnT5U1L.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-DJ4qSbYt.js → move-L8wzwbel.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-CdpJD85j.js → mtc-parser-DLdYSejc.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-B0V4slKA.js → omit-D53cbS59.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-Di5NiW1F.js → pickAttrs-Bbc5HPtu.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-B5OMHgEM.js → placementArrow-BkvmO860.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-C6U9IWql.js → responsiveObserve-u2y1ryS1.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-BMyTH3rN.js → slide-BBqj7svU.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-C7d6BG7Z.js → statusUtils-GN0mqu2d.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-CR50QnQy.js → styleChecker-BvwPKoBT.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-DBEeoMb8.js → useFlexGapSupport-CCt8tnfY.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-3r099cjv.js → useFs-DXAxl2SV.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-C5IYNdTK.js → usePersonalDataHub-DihlR2dj.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-DmEOLRHf.js → vnode-D7BOX2gi.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-DnpUiVCB.js → zoom-CVbO-nL7.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +4 -4
  153. package/src/commands/agent.js +3 -3
  154. package/src/commands/background-session.js +145 -0
  155. package/src/commands/mcp.js +244 -7
  156. package/src/commands/serve.js +1 -1
  157. package/src/commands/session-show.js +129 -0
  158. package/src/commands/session.js +97 -145
  159. package/src/commands/skill.js +87 -50
  160. package/src/data/changelog.json +40 -1
  161. package/src/gateways/ws/session-protocol.js +14 -4
  162. package/src/gateways/ws/ws-agent-handler.js +39 -1
  163. package/src/harness/jsonl-session-store.js +1547 -127
  164. package/src/harness/mcp-client.js +2690 -409
  165. package/src/harness/session-index.js +80 -80
  166. package/src/harness/session-list-index.js +210 -7
  167. package/src/lazy-dispatch.js +76 -8
  168. package/src/lib/abort-utils.js +68 -0
  169. package/src/lib/agent-core.js +1 -59
  170. package/src/lib/background-interaction-journal.js +2 -2
  171. package/src/lib/chat-core.js +24 -6
  172. package/src/lib/command-lifecycle-report.js +516 -0
  173. package/src/lib/cowork-mcp-tools.js +47 -2
  174. package/src/lib/cowork-task-runner.js +11 -3
  175. package/src/lib/doctor-checkup.js +9 -7
  176. package/src/lib/learning/skill-improver.js +21 -2
  177. package/src/lib/mcp-call-ledger-store.js +116 -3
  178. package/src/lib/mcp-call-ledger.js +59 -14
  179. package/src/lib/mcp-client.js +1 -0
  180. package/src/lib/mcp-stdio-environment.js +48 -0
  181. package/src/lib/mcp-stdio-executable-identity.js +1133 -0
  182. package/src/lib/mcp-stdio-execution-authority.js +363 -0
  183. package/src/lib/mcp-stdio-package-materialization.js +1424 -0
  184. package/src/lib/mcp-tool-metadata.js +436 -0
  185. package/src/lib/mcp-tool-result.js +367 -0
  186. package/src/lib/observability/index.js +71 -0
  187. package/src/lib/packer/native-update-state.js +979 -1
  188. package/src/lib/packer/pack-update-applier.js +1050 -125
  189. package/src/lib/packer/pkg-config-generator.js +1 -0
  190. package/src/lib/paths.js +53 -0
  191. package/src/lib/pr-link-ledger.js +8 -27
  192. package/src/lib/pr-link-store.js +26 -0
  193. package/src/lib/process-execution-broker/index.js +192 -35
  194. package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
  195. package/src/lib/process-tree-termination.js +253 -0
  196. package/src/lib/recent-session.js +17 -17
  197. package/src/lib/secure-file-identity.js +47 -0
  198. package/src/lib/secure-fs.js +21 -2
  199. package/src/lib/session-anti-rollback-anchor.js +766 -0
  200. package/src/lib/session-host-lease.js +492 -0
  201. package/src/lib/session-host-snapshot.js +9 -5
  202. package/src/lib/session-persistence-failure.js +109 -0
  203. package/src/lib/session-picker.js +26 -3
  204. package/src/lib/session-projection.js +5 -1
  205. package/src/lib/session-search.js +25 -11
  206. package/src/lib/session-tail.js +31 -1
  207. package/src/lib/side-effect-ledger-store.js +2 -2
  208. package/src/lib/skill-budget.js +222 -0
  209. package/src/lib/skill-execution-authority.js +385 -0
  210. package/src/lib/skill-execution-identity.js +39 -2
  211. package/src/lib/skill-loader.js +322 -86
  212. package/src/lib/skill-mcp.js +43 -1
  213. package/src/lib/turn-binding-store.js +2 -2
  214. package/src/repl/agent-repl.js +483 -77
  215. package/src/repl/bg-dashboard.js +81 -28
  216. package/src/repl/chat-repl.js +77 -12
  217. package/src/runtime/agent-core.js +428 -113
  218. package/src/runtime/agent-runtime.js +26 -3
  219. package/src/runtime/cleanup-deadline.js +143 -0
  220. package/src/runtime/headless-runner.js +494 -149
  221. package/src/runtime/headless-stream.js +504 -75
  222. package/src/runtime/mcp-config.js +157 -10
  223. package/src/runtime/mcp-tool-search.js +0 -0
  224. package/src/runtime/output-backpressure.js +419 -0
  225. package/src/runtime/pipe-safety.js +56 -21
  226. package/src/assets/web-panel/assets/ChatBubbleRenderer-BUUyJ5QO.js +0 -1
  227. package/src/assets/web-panel/assets/MarkdownRenderer-1mNozZ8v.js +0 -1
  228. package/src/assets/web-panel/assets/MobileProjects-DqnoFPgR.js +0 -1
  229. package/src/assets/web-panel/assets/Providers-uO-10aP1.js +0 -1
  230. package/src/assets/web-panel/assets/devWarning-DosMaL-k.js +0 -1
  231. package/src/assets/web-panel/assets/index-0O5ePnUq.js +0 -1
  232. package/src/assets/web-panel/assets/index-ChAzPigF.js +0 -1
  233. package/src/lib/skill-process-broker.js +0 -85
@@ -53,6 +53,8 @@ import {
53
53
  findLatestEvent as jsonlFindLatestEvent,
54
54
  rebuildMessages as jsonlRebuildMessages,
55
55
  sessionExists as jsonlSessionExists,
56
+ sessionHasPersistedEvidence as jsonlSessionHasPersistedEvidence,
57
+ resolveSessionAuthority as jsonlResolveSessionAuthority,
56
58
  getLastSessionId as jsonlGetLastSessionId,
57
59
  verifySession as jsonlVerifySession,
58
60
  } from "../harness/jsonl-session-store.js";
@@ -71,6 +73,7 @@ import {
71
73
  } from "../lib/mcp-call-ledger-store.js";
72
74
  import { createMcpHostRecoveryRuntime } from "../lib/mcp-host-recovery-runtime.js";
73
75
  import { readSessionHostResumeState } from "../lib/session-host-snapshot.js";
76
+ import { acquireSessionHostLease } from "../lib/session-host-lease.js";
74
77
  import { TurnBindingLog, createTurnBindingFeed } from "../lib/turn-binding.js";
75
78
  import { TURN_BINDING_EVENT } from "../lib/turn-binding-store.js";
76
79
  import { operationIdempotencyKey } from "../lib/idempotency.js";
@@ -153,6 +156,15 @@ const VALID_OUTPUT_FORMATS = Object.freeze(["text", "json", "stream-json"]);
153
156
  // the stream-json driver + REPL); re-exported here for existing importers.
154
157
  export { installPipeSafety } from "./pipe-safety.js";
155
158
  import { installPipeSafety } from "./pipe-safety.js";
159
+ import { createHeadlessOutputBackpressure } from "./output-backpressure.js";
160
+ import {
161
+ cleanupDeadlineError,
162
+ createCleanupDeadline,
163
+ } from "./cleanup-deadline.js";
164
+ import {
165
+ createSessionPersistenceFailure,
166
+ projectSessionPersistenceFailure,
167
+ } from "../lib/session-persistence-failure.js";
156
168
 
157
169
  /**
158
170
  * Normalize a public --permission-mode spelling to the canonical internal mode.
@@ -288,6 +300,19 @@ export function resolveHeadlessSession(options = {}, store = {}, fallbackId) {
288
300
  } else if (typeof resume === "string" && resume.trim()) {
289
301
  resumeId = resume.trim();
290
302
  }
303
+ if (resumeId && typeof store.resolveSessionAuthority === "function") {
304
+ const authority = store.resolveSessionAuthority(resumeId);
305
+ if (authority && !authority.readable) {
306
+ const error = new Error(
307
+ `Canonical session ${authority.id} is not resumable (${authority.presence})`,
308
+ );
309
+ error.code = "SESSION_CANONICAL_UNAVAILABLE";
310
+ error.sessionId = authority.id;
311
+ error.presence = authority.presence;
312
+ throw error;
313
+ }
314
+ if (authority?.id) resumeId = authority.id;
315
+ }
291
316
  const persist =
292
317
  ephemeral === true
293
318
  ? false
@@ -361,14 +386,161 @@ export function applyForkSession(opts = {}, store = {}) {
361
386
  * @param {object} [deps] Injection seam for tests.
362
387
  * @returns {Promise<{ exitCode:number, result:string, isError:boolean, sessionSnapshot?:object }>}
363
388
  */
389
+ const HEADLESS_SESSION_RESOLUTION = Symbol("headlessSessionResolution");
390
+ const HEADLESS_SESSION_HOST_LEASE = Symbol("headlessSessionHostLease");
391
+
392
+ async function withHeadlessSessionHostLease(options, deps, task) {
393
+ const prompt = (options.prompt || "").trim();
394
+ if (!prompt) {
395
+ throw new Error(
396
+ "runAgentHeadless requires a non-empty prompt (use -p, a positional arg, or pipe stdin).",
397
+ );
398
+ }
399
+ const outputFormat = options.outputFormat || "text";
400
+ if (!VALID_OUTPUT_FORMATS.includes(outputFormat)) {
401
+ throw new Error(
402
+ `Invalid --output-format "${outputFormat}". Expected one of: ${VALID_OUTPUT_FORMATS.join(", ")}`,
403
+ );
404
+ }
405
+ const hasInjectedSessionStore =
406
+ typeof deps.sessionHasPersistedEvidence === "function" ||
407
+ typeof deps.sessionExists === "function" ||
408
+ typeof deps.rebuildMessages === "function" ||
409
+ typeof deps.appendEvent === "function" ||
410
+ typeof deps.appendAuthorityEvent === "function" ||
411
+ typeof deps.readVerifiedEvents === "function";
412
+ const resolution = resolveHeadlessSession(
413
+ options,
414
+ {
415
+ getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
416
+ resolveSessionAuthority:
417
+ deps.resolveSessionAuthority ||
418
+ (!hasInjectedSessionStore ? jsonlResolveSessionAuthority : undefined),
419
+ },
420
+ `headless-${Date.now()}-${process.pid}`,
421
+ );
422
+ const authoritySessionId =
423
+ resolution.resumeId || (resolution.persist ? resolution.sessionId : null);
424
+ const acquireHostLease =
425
+ deps.acquireSessionHostLease ||
426
+ (!hasInjectedSessionStore ? acquireSessionHostLease : null);
427
+ let lease = null;
428
+ try {
429
+ if (authoritySessionId && typeof acquireHostLease === "function") {
430
+ lease = acquireHostLease(authoritySessionId, { hostKind: "headless" });
431
+ }
432
+ const scopedOptions = lease?.signal
433
+ ? {
434
+ ...options,
435
+ signal: options.signal
436
+ ? AbortSignal.any([options.signal, lease.signal])
437
+ : lease.signal,
438
+ }
439
+ : options;
440
+ return await task(scopedOptions, {
441
+ ...deps,
442
+ [HEADLESS_SESSION_RESOLUTION]: resolution,
443
+ [HEADLESS_SESSION_HOST_LEASE]: lease,
444
+ });
445
+ } finally {
446
+ lease?.release?.();
447
+ }
448
+ }
449
+
364
450
  export async function runAgentHeadless(options = {}, deps = {}) {
365
451
  const trustedWorkspaceRoot = options.cwd || process.cwd();
366
- return runWithHostHooksV2Workspace(trustedWorkspaceRoot, () =>
367
- runAgentHeadlessInWorkspace(options, deps),
368
- );
452
+ const pipeState = { closed: false };
453
+ const stdout = deps.stdout || process.stdout;
454
+ const stderr = deps.stderr || process.stderr;
455
+ const shouldInstallPipeSafety =
456
+ typeof deps.installPipeSafety === "function" ||
457
+ (!deps.writeOut && !deps.writeErr);
458
+ const pipeAbort = shouldInstallPipeSafety ? new AbortController() : null;
459
+ const managesNativeOutput = !deps.writeOut || !deps.writeErr;
460
+ const outputAbort = managesNativeOutput ? new AbortController() : null;
461
+ const handlePipeClosed = () => {
462
+ if (pipeState.closed) return;
463
+ pipeState.closed = true;
464
+ process.exitCode = 0;
465
+ pipeAbort?.abort();
466
+ };
467
+ const outputFlow =
468
+ deps.outputFlow ||
469
+ createHeadlessOutputBackpressure({
470
+ stdout,
471
+ stderr,
472
+ writeOut: deps.writeOut,
473
+ writeErr: deps.writeErr,
474
+ maxQueuedBytes: deps.outputBackpressureMaxBytes,
475
+ drainTimeoutMs: deps.outputBackpressureTimeoutMs,
476
+ onFailure: (error) => {
477
+ outputAbort?.abort(error);
478
+ if (error?.code === "EPIPE") handlePipeClosed();
479
+ },
480
+ });
481
+ const abortSignals = [
482
+ options.signal,
483
+ pipeAbort?.signal,
484
+ outputAbort?.signal,
485
+ ].filter(Boolean);
486
+ const runtimeOptions =
487
+ abortSignals.length === 0
488
+ ? options
489
+ : {
490
+ ...options,
491
+ signal:
492
+ abortSignals.length === 1
493
+ ? abortSignals[0]
494
+ : AbortSignal.any(abortSignals),
495
+ };
496
+ const runtimeDeps = {
497
+ ...deps,
498
+ writeOut: outputFlow.writeOut,
499
+ writeErr: outputFlow.writeErr,
500
+ outputFlow,
501
+ };
502
+ const installSafety = deps.installPipeSafety || installPipeSafety;
503
+ const disposePipeSafety = shouldInstallPipeSafety
504
+ ? installSafety([stdout, stderr], handlePipeClosed)
505
+ : null;
506
+
507
+ let outcome;
508
+ let failure = null;
509
+ try {
510
+ outcome = await runWithHostHooksV2Workspace(trustedWorkspaceRoot, () =>
511
+ withHeadlessSessionHostLease(
512
+ runtimeOptions,
513
+ runtimeDeps,
514
+ (scopedOptions, scopedDeps) =>
515
+ runAgentHeadlessInWorkspace(scopedOptions, scopedDeps, pipeState),
516
+ ),
517
+ );
518
+ } catch (error) {
519
+ failure = error;
520
+ } finally {
521
+ try {
522
+ await outputFlow.wait();
523
+ } catch (error) {
524
+ failure ||= error;
525
+ }
526
+ outputFlow.dispose();
527
+ disposePipeSafety?.();
528
+ }
529
+
530
+ if (pipeState.closed) {
531
+ return outcome
532
+ ? { ...outcome, exitCode: 0, isError: false }
533
+ : { exitCode: 0, result: "", isError: false };
534
+ }
535
+ if (failure) throw failure;
536
+ return outcome;
369
537
  }
370
538
 
371
- async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
539
+ async function runAgentHeadlessInWorkspace(
540
+ options = {},
541
+ deps = {},
542
+ pipeState = { closed: false },
543
+ ) {
372
544
  const prompt = (options.prompt || "").trim();
373
545
  if (!prompt) {
374
546
  throw new Error(
@@ -386,21 +558,33 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
386
558
  const isStream = outputFormat === "stream-json";
387
559
  const isJson = outputFormat === "json";
388
560
  const isText = outputFormat === "text";
389
- const writeOut = deps.writeOut || ((s) => process.stdout.write(s));
390
- const writeErr = deps.writeErr || ((s) => process.stderr.write(s));
391
- if (!deps.writeOut && !deps.writeErr) installPipeSafety();
561
+ const writeOut = deps.writeOut;
562
+ const writeErr = deps.writeErr;
563
+ const cleanupDeadline = createCleanupDeadline({
564
+ timeoutMs: deps.cleanupDeadlineMs,
565
+ label: "headless-runner-cleanup",
566
+ });
392
567
 
393
568
  const hasInjectedSessionStore =
569
+ typeof deps.sessionHasPersistedEvidence === "function" ||
394
570
  typeof deps.sessionExists === "function" ||
395
571
  typeof deps.rebuildMessages === "function" ||
396
572
  typeof deps.appendEvent === "function" ||
397
573
  typeof deps.appendAuthorityEvent === "function" ||
398
574
  typeof deps.readVerifiedEvents === "function";
399
- const { sessionId, resumeId, persist } = resolveHeadlessSession(
400
- options,
401
- { getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId },
402
- `headless-${Date.now()}-${process.pid}`,
403
- );
575
+ const { sessionId, resumeId, persist } =
576
+ deps[HEADLESS_SESSION_RESOLUTION] ||
577
+ resolveHeadlessSession(
578
+ options,
579
+ {
580
+ getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
581
+ resolveSessionAuthority:
582
+ deps.resolveSessionAuthority ||
583
+ (!hasInjectedSessionStore ? jsonlResolveSessionAuthority : undefined),
584
+ },
585
+ `headless-${Date.now()}-${process.pid}`,
586
+ );
587
+ const sessionHostLease = deps[HEADLESS_SESSION_HOST_LEASE] || null;
404
588
 
405
589
  const emitHeadlessError = (resultMsg) => {
406
590
  if (isStream) {
@@ -435,27 +619,36 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
435
619
  // settings/plugin loading, bootstrap, model access, hooks, MCP and tools.
436
620
  const canReadCanonicalResume =
437
621
  !hasInjectedSessionStore || typeof deps.readVerifiedEvents === "function";
438
- const canonicalResume =
439
- resumeId && canReadCanonicalResume
440
- ? readSessionHostResumeState(resumeId, {
622
+ const canonicalAdmissionId = resumeId || (persist ? sessionId : null);
623
+ const canonicalAdmission =
624
+ canonicalAdmissionId && canReadCanonicalResume
625
+ ? readSessionHostResumeState(canonicalAdmissionId, {
626
+ sessionHasPersistedEvidence:
627
+ deps.sessionHasPersistedEvidence ||
628
+ (!hasInjectedSessionStore
629
+ ? jsonlSessionHasPersistedEvidence
630
+ : undefined),
441
631
  sessionExists: deps.sessionExists || jsonlSessionExists,
442
632
  readVerifiedEvents:
443
633
  deps.readVerifiedEvents || jsonlReadVerifiedEvents,
444
634
  })
445
635
  : null;
446
- if (canonicalResume && !canonicalResume.snapshot.verified) {
636
+ if (canonicalAdmission && !canonicalAdmission.snapshot.verified) {
447
637
  const message =
448
638
  "CC_SESSION_HOST_SNAPSHOT_UNVERIFIED: canonical JSONL session is not " +
449
- "fully verified; resume was refused";
639
+ "fully verified; resume/write admission was refused";
450
640
  emitHeadlessError(message);
451
641
  writeErr(`${message}\n`);
452
642
  return {
453
643
  exitCode: 1,
454
644
  result: message,
455
645
  isError: true,
456
- sessionSnapshot: canonicalResume.snapshot,
646
+ sessionSnapshot: canonicalAdmission.snapshot,
457
647
  };
458
648
  }
649
+ // A persist-only target must pass the same canonical admission boundary,
650
+ // but it does not opt into replaying the target's prior conversation.
651
+ const canonicalResume = resumeId ? canonicalAdmission : null;
459
652
 
460
653
  // `let` (not const): a custom-command macro's `model:` frontmatter may
461
654
  // override it below (when the user passed no explicit --model), mirroring
@@ -533,7 +726,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
533
726
  cwd,
534
727
  settingsFile: options.settingsFile,
535
728
  });
536
- if (notice) process.stderr.write(notice + "\n");
729
+ if (notice) writeErr(notice + "\n");
537
730
  } catch {
538
731
  /* trust notice is best-effort */
539
732
  }
@@ -935,7 +1128,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
935
1128
  if (!persist) return;
936
1129
  if (sideEffectLedgerLoadError) throw sideEffectLedgerLoadError;
937
1130
  try {
938
- const persisted = store.appendEvent(
1131
+ const persisted = store.appendAuthorityEvent(
939
1132
  sessionId,
940
1133
  SIDE_EFFECT_LEDGER_EVENT,
941
1134
  sideEffectLedger.toJSON(),
@@ -990,13 +1183,16 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
990
1183
  // (createTurnBindingFeed) so the interactive REPL producer can never drift
991
1184
  // from this runner's mapping. No feed at all for non-persisted runs.
992
1185
  const turnBindingFeed = persist
993
- ? createTurnBindingFeed({ log: turnBindingLog, nonce: sideEffectRunNonce })
1186
+ ? createTurnBindingFeed({
1187
+ log: turnBindingLog,
1188
+ nonce: sideEffectRunNonce,
1189
+ })
994
1190
  : null;
995
1191
  const persistTurnBindingLog = () => {
996
1192
  if (!turnBindingFeed || !turnBindingFeed.isDirty()) return;
997
1193
  if (turnBindingLoadError) throw turnBindingLoadError;
998
1194
  try {
999
- const persisted = store.appendEvent(
1195
+ const persisted = store.appendAuthorityEvent(
1000
1196
  sessionId,
1001
1197
  TURN_BINDING_EVENT,
1002
1198
  turnBindingLog.toJSON(),
@@ -1180,7 +1376,9 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1180
1376
  }
1181
1377
 
1182
1378
  const budget = Number.isFinite(options.maxTurns)
1183
- ? new IterationBudget({ limit: Math.max(1, Math.floor(options.maxTurns)) })
1379
+ ? new IterationBudget({
1380
+ limit: Math.max(1, Math.floor(options.maxTurns)),
1381
+ })
1184
1382
  : new IterationBudget();
1185
1383
 
1186
1384
  // Effective system prompt: built-in base, optionally replaced by
@@ -1372,9 +1570,10 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1372
1570
  { role: "user", content: userMessageContent },
1373
1571
  ]);
1374
1572
 
1375
- // Persist the user turn up front (best-effort) so a session is recoverable
1376
- // even if the run crashes mid-loop. startSession is append-safe: only seed
1377
- // the header when the file does not yet exist.
1573
+ // Persist the user turn before model/tool side effects. A full or read-only
1574
+ // session disk is a recoverability contract failure, so it fails closed
1575
+ // while the requested turn is still side-effect free. startSession is
1576
+ // append-safe: only seed the header when the file does not yet exist.
1378
1577
  if (persist) {
1379
1578
  try {
1380
1579
  if (!store.sessionExists(sessionId)) {
@@ -1387,8 +1586,62 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1387
1586
  // Persist the expanded content so a resumed session faithfully replays
1388
1587
  // what the model actually saw (the file snapshot, not just the @token).
1389
1588
  store.appendUserMessage(sessionId, userContent);
1390
- } catch {
1391
- // Persistence is best-effort — never fail the run over it.
1589
+ } catch (error) {
1590
+ const persistenceError = createSessionPersistenceFailure(error, {
1591
+ sessionId,
1592
+ operation: "user-turn-append",
1593
+ });
1594
+ const persistence = projectSessionPersistenceFailure(persistenceError, {
1595
+ phase: "before-model",
1596
+ });
1597
+ if (persistence) {
1598
+ try {
1599
+ deps.onPersistenceFailure?.(persistence);
1600
+ } catch {
1601
+ // Observability cannot change persistence semantics.
1602
+ }
1603
+ const resultMsg = `${persistence.code}: session turn was not durably persisted`;
1604
+ if (isStream) {
1605
+ writeOut(
1606
+ JSON.stringify({
1607
+ type: "result",
1608
+ subtype: "error_persistence",
1609
+ is_error: true,
1610
+ error: resultMsg,
1611
+ session_id: sessionId,
1612
+ persistence,
1613
+ }) + "\n",
1614
+ );
1615
+ } else if (isJson) {
1616
+ writeOut(
1617
+ JSON.stringify(
1618
+ buildResultEnvelope({
1619
+ subtype: "error_persistence",
1620
+ isError: true,
1621
+ result: resultMsg,
1622
+ sessionId,
1623
+ toolCalls: [],
1624
+ usage: {},
1625
+ numTurns: 0,
1626
+ durationMs: 0,
1627
+ persistence,
1628
+ }),
1629
+ ) + "\n",
1630
+ );
1631
+ } else {
1632
+ writeErr(
1633
+ `${resultMsg} (${persistence.fs_code}; ${persistence.commit_state})\n`,
1634
+ );
1635
+ }
1636
+ return {
1637
+ exitCode: 1,
1638
+ result: resultMsg,
1639
+ isError: true,
1640
+ persistence,
1641
+ };
1642
+ }
1643
+ // Preserve legacy best-effort handling for errors outside the explicit
1644
+ // ENOSPC/EROFS durability contract.
1392
1645
  }
1393
1646
  }
1394
1647
 
@@ -1465,9 +1718,12 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1465
1718
  bundle: mcp,
1466
1719
  sessionId,
1467
1720
  sink: persist
1468
- ? createSessionMcpLedgerSink(sessionId, {
1469
- appendEvent: store.appendAuthorityEvent,
1470
- })
1721
+ ? createSessionMcpLedgerSink(
1722
+ sessionId,
1723
+ hasInjectedSessionStore
1724
+ ? { appendEvent: store.appendAuthorityEvent }
1725
+ : { recovery: resumeMcpRecovery },
1726
+ )
1471
1727
  : null,
1472
1728
  recovery: resumeMcpRecovery,
1473
1729
  recoveryError: resumeMcpRecoveryError,
@@ -1536,7 +1792,13 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1536
1792
  ppt = resolvePermissionPromptTool(mcp, options.permissionPromptTool);
1537
1793
  } catch (err) {
1538
1794
  writeErr(`Error: ${err.message}\n`);
1539
- if (mcp?.mcpClient) await mcp.mcpClient.disconnectAll().catch(() => {});
1795
+ await cleanupDeadline.run("mcp", () => mcp?.mcpClient?.disconnectAll?.());
1796
+ const cleanupReport = cleanupDeadline.report();
1797
+ try {
1798
+ deps.onCleanupReport?.(cleanupReport);
1799
+ } catch {
1800
+ // Metrics/reporting cannot change cleanup semantics.
1801
+ }
1540
1802
  emitHeadlessError(err.message);
1541
1803
  // A bad --permission-prompt-tool reference is a CONFIG error.
1542
1804
  return {
@@ -1831,6 +2093,24 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
1831
2093
  const suffix = retryIn > 0 ? ` · will retry in ${retryIn}s` : "";
1832
2094
  writeErr(` ⏳ waiting for API response (silent ${silent}s)${suffix}…\n`);
1833
2095
  },
2096
+ onProviderFallback: (info) => {
2097
+ if (isStream) {
2098
+ emitStream({
2099
+ type: "raw",
2100
+ subtype: "provider_fallback",
2101
+ text:
2102
+ info.message || `provider switched from ${info.from} to ${info.to}`,
2103
+ from: info.from,
2104
+ to: info.to,
2105
+ reason: info.reason,
2106
+ session_id: sessionId,
2107
+ });
2108
+ } else {
2109
+ writeErr(
2110
+ `[provider] ${info.message || `switched from ${info.from} to ${info.to}`}\n`,
2111
+ );
2112
+ }
2113
+ },
1834
2114
  };
1835
2115
 
1836
2116
  // Goal binding (cc goal, Phase 1). `--goal <id>` binds explicitly; `--goal`
@@ -2149,8 +2429,11 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2149
2429
  process.once("SIGINT", _onHardSignal);
2150
2430
  process.once("SIGTERM", _onHardSignal);
2151
2431
 
2432
+ let loopFailureOutcome = null;
2433
+ let cleanupFailure = null;
2152
2434
  try {
2153
2435
  while (true) {
2436
+ sessionHostLease?.assert?.();
2154
2437
  // P1 turn→checkpoint binding: one drain of the loop = one turn. Anchor
2155
2438
  // the conversation offset before the model sees the new user turn. A
2156
2439
  // `--worktree` run stamps its isolation worktree (branch name, threaded
@@ -2174,7 +2457,11 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2174
2457
  case "checkpoint": {
2175
2458
  if (isText)
2176
2459
  writeErr(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`);
2177
- emitStream({ type: "checkpoint", id: event.id, tool: event.tool });
2460
+ emitStream({
2461
+ type: "checkpoint",
2462
+ id: event.id,
2463
+ tool: event.tool,
2464
+ });
2178
2465
  break;
2179
2466
  }
2180
2467
  case "tool-executing": {
@@ -2218,6 +2505,11 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2218
2505
  })
2219
2506
  .start(opId);
2220
2507
  persistSideEffectLedger();
2508
+ // The generator executes the tool only after this yielded
2509
+ // prewrite is handled. Recheck the same durable lease at that
2510
+ // final host boundary so a fenced host never resumes into the
2511
+ // external operation.
2512
+ sessionHostLease?.assert?.();
2221
2513
  }
2222
2514
  }
2223
2515
  break;
@@ -2421,6 +2713,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2421
2713
  if (isStream && event.type) emitStream(event);
2422
2714
  break;
2423
2715
  }
2716
+ await deps.outputFlow?.wait?.();
2424
2717
  // Hard cost cap reached: stop consuming the loop so no further paid LLM
2425
2718
  // call is made (break out of the for-await, not just the switch).
2426
2719
  if (stopForCost) break;
@@ -2611,16 +2904,18 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2611
2904
  }
2612
2905
  // Provider/transport failures get their own exit code (5) so CI can tell
2613
2906
  // "the model call failed" from "the run itself errored" (1).
2614
- return { exitCode: classifyLoopError(err), result: message, isError: true };
2907
+ loopFailureOutcome = {
2908
+ exitCode: classifyLoopError(err),
2909
+ result: message,
2910
+ isError: true,
2911
+ };
2615
2912
  } finally {
2616
2913
  // Capture body-cache hits/misses caused by list_skills/run_skill during the
2617
2914
  // turn. `cc context --sources` reads the newest snapshot.
2618
2915
  persistContextSources();
2619
- try {
2620
- _backgroundInteraction?.close?.();
2621
- } catch {
2622
- // Best-effort listener cleanup; never mask the run result.
2623
- }
2916
+ await cleanupDeadline.run("background-interaction", () =>
2917
+ _backgroundInteraction?.close?.(),
2918
+ );
2624
2919
  // Drop the signal handlers first — a normal return must not leave a
2625
2920
  // process-wide SIGINT/SIGTERM listener behind (a later Ctrl-C would wrongly
2626
2921
  // exit with 130, and repeated in-process runs would leak listeners).
@@ -2629,136 +2924,158 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2629
2924
  // Tear down ad-hoc MCP servers (--mcp-config) before returning, whether the
2630
2925
  // loop completed or threw. Best-effort: a failed disconnect never masks the
2631
2926
  // run's own outcome.
2632
- if (mcp?.mcpClient) {
2633
- try {
2634
- await mcp.mcpClient.disconnectAll();
2635
- } catch {
2636
- // ignore — disconnect is best-effort
2637
- }
2638
- }
2927
+ await cleanupDeadline.run("mcp", () => mcp?.mcpClient?.disconnectAll?.());
2639
2928
  // Kill any background run_shell tasks this run spawned so a backgrounded
2640
2929
  // command (e.g. a dev server) doesn't outlive the headless invocation.
2641
- try {
2642
- killAllBackgroundShellTasks();
2643
- } catch {
2644
- // best-effort — never mask the run's own outcome
2645
- }
2930
+ await cleanupDeadline.run("background-shells", () =>
2931
+ killAllBackgroundShellTasks(),
2932
+ );
2646
2933
  // Tear down the --remote-control approval bridge + its self-hosted WS
2647
2934
  // server so the run's port/socket never outlives the invocation.
2648
- if (_remoteApproval) {
2649
- try {
2650
- await _remoteApproval.close();
2651
- } catch {
2652
- // best-effort — never mask the run's own outcome
2653
- }
2654
- }
2935
+ await cleanupDeadline.run("remote-approval", () =>
2936
+ _remoteApproval?.close?.(),
2937
+ );
2655
2938
  // settings.json Stop + SessionEnd hooks when the run finishes. Stop is the
2656
2939
  // canonical async-hook trigger ("run the test suite at turn end"); fire its
2657
2940
  // async hooks fire-and-forget, then settle so a fast background check can
2658
2941
  // report back within this one-shot run.
2659
- if (settingsHooks) {
2660
- try {
2661
- const { runObserveHooks, dispatchAsyncHooks } =
2662
- await import("../lib/settings-hook-events.js");
2663
- const stopPayload = { reason: endReason, cwd, session_id: sessionId };
2664
- const stopOutcome = runObserveHooks(
2665
- settingsHooks,
2666
- "Stop",
2667
- stopPayload,
2668
- { cwd },
2669
- );
2670
- const stopFailures = (stopOutcome?.results || []).filter(
2671
- (result) =>
2672
- result?.nonBlockingError === true ||
2673
- result?.malformedDecision === true ||
2674
- result?.breakerOpen === true,
2675
- );
2676
- if (stopFailures.length > 0) {
2677
- emitHooksV2Event("StopFailure", {
2678
- schema_version: 1,
2679
- session_id: sessionId,
2680
- phase: "stop-hook",
2681
- failures: stopFailures.map((failure) => ({
2682
- command: failure.command || null,
2683
- exit_code: failure.exitCode ?? null,
2684
- reason: failure.reason || failure.error || null,
2685
- })),
2686
- });
2687
- }
2688
- // Skip the async Stop dispatch/settle here if the auto-rewake re-drive
2689
- // loop already fired + drained it this run (avoids double-execution).
2690
- if (_hookSupervisor && !_asyncStopHandled) {
2691
- dispatchAsyncHooks(settingsHooks, "Stop", stopPayload, {
2942
+ await cleanupDeadline.run("settings-hooks", async () => {
2943
+ if (settingsHooks) {
2944
+ try {
2945
+ const { runObserveHooks, dispatchAsyncHooks } =
2946
+ await import("../lib/settings-hook-events.js");
2947
+ const stopPayload = {
2948
+ reason: pipeState.closed ? "pipe_closed" : endReason,
2692
2949
  cwd,
2693
- supervisor: _hookSupervisor,
2694
- });
2695
- // Bounded settle: wait for in-flight async hooks (Stop + any
2696
- // PostToolUse dispatched during the loop) to finish, capped so a
2697
- // slow/hung check can never stall the run. Default 5s; tunable.
2698
- const waitMs = Number.isFinite(options.asyncHookWaitMs)
2699
- ? options.asyncHookWaitMs
2700
- : 5000;
2701
- const started = Date.now();
2702
- while (
2703
- _hookSupervisor.runningCount() > 0 &&
2704
- Date.now() - started < waitMs
2705
- ) {
2706
- await new Promise((r) => setTimeout(r, 50));
2707
- }
2708
- // Surface any results/rewakes out-of-band on stderr (never touches the
2709
- // stdout envelope). A rewake means a background check FAILED and would
2710
- // re-engage the agent in an interactive session — flag it clearly so a
2711
- // headless caller/CI can react.
2712
- for (const rw of _hookSupervisor.drainRewakes()) {
2950
+ session_id: sessionId,
2951
+ };
2952
+ const stopOutcome = runObserveHooks(
2953
+ settingsHooks,
2954
+ "Stop",
2955
+ stopPayload,
2956
+ { cwd },
2957
+ );
2958
+ const stopFailures = (stopOutcome?.results || []).filter(
2959
+ (result) =>
2960
+ result?.nonBlockingError === true ||
2961
+ result?.malformedDecision === true ||
2962
+ result?.breakerOpen === true,
2963
+ );
2964
+ if (stopFailures.length > 0) {
2713
2965
  emitHooksV2Event("StopFailure", {
2714
2966
  schema_version: 1,
2715
2967
  session_id: sessionId,
2716
- phase: "async-stop-hook",
2717
- command: rw.command || null,
2718
- exit_code: rw.exitCode ?? null,
2719
- reason: rw.error || "async stop hook requested rewake",
2968
+ phase: "stop-hook",
2969
+ failures: stopFailures.map((failure) => ({
2970
+ command: failure.command || null,
2971
+ exit_code: failure.exitCode ?? null,
2972
+ reason: failure.reason || failure.error || null,
2973
+ })),
2720
2974
  });
2721
- writeErr(
2722
- `[async-hook REWAKE] ${rw.command} failed` +
2723
- `${rw.exitCode != null ? ` (exit ${rw.exitCode})` : ""}` +
2724
- `${rw.error ? `: ${rw.error}` : ""}\n`,
2725
- );
2726
2975
  }
2727
- for (const rs of _hookSupervisor.drainResults()) {
2728
- if (rs.additionalContext) {
2729
- writeErr(`[async-hook] ${rs.command}: ${rs.additionalContext}\n`);
2976
+ // Skip the async Stop dispatch/settle here if the auto-rewake re-drive
2977
+ // loop already fired + drained it this run (avoids double-execution).
2978
+ if (_hookSupervisor && !_asyncStopHandled) {
2979
+ dispatchAsyncHooks(settingsHooks, "Stop", stopPayload, {
2980
+ cwd,
2981
+ supervisor: _hookSupervisor,
2982
+ });
2983
+ // Bounded settle: wait for in-flight async hooks (Stop + any
2984
+ // PostToolUse dispatched during the loop) to finish, capped so a
2985
+ // slow/hung check can never stall the run. Default 5s; tunable.
2986
+ const waitMs = Number.isFinite(options.asyncHookWaitMs)
2987
+ ? options.asyncHookWaitMs
2988
+ : 5000;
2989
+ const started = Date.now();
2990
+ while (
2991
+ _hookSupervisor.runningCount() > 0 &&
2992
+ Date.now() - started < waitMs
2993
+ ) {
2994
+ await new Promise((r) => setTimeout(r, 50));
2995
+ }
2996
+ // Surface any results/rewakes out-of-band on stderr (never touches the
2997
+ // stdout envelope). A rewake means a background check FAILED and would
2998
+ // re-engage the agent in an interactive session — flag it clearly so a
2999
+ // headless caller/CI can react.
3000
+ for (const rw of _hookSupervisor.drainRewakes()) {
3001
+ emitHooksV2Event("StopFailure", {
3002
+ schema_version: 1,
3003
+ session_id: sessionId,
3004
+ phase: "async-stop-hook",
3005
+ command: rw.command || null,
3006
+ exit_code: rw.exitCode ?? null,
3007
+ reason: rw.error || "async stop hook requested rewake",
3008
+ });
3009
+ writeErr(
3010
+ `[async-hook REWAKE] ${rw.command} failed` +
3011
+ `${rw.exitCode != null ? ` (exit ${rw.exitCode})` : ""}` +
3012
+ `${rw.error ? `: ${rw.error}` : ""}\n`,
3013
+ );
3014
+ }
3015
+ for (const rs of _hookSupervisor.drainResults()) {
3016
+ if (rs.additionalContext) {
3017
+ writeErr(
3018
+ `[async-hook] ${rs.command}: ${rs.additionalContext}\n`,
3019
+ );
3020
+ }
2730
3021
  }
2731
3022
  }
3023
+ // Always reap the supervisor (kills any straggler child + detaches the
3024
+ // exit reaper), whether the async Stop was handled here or in re-drive.
3025
+ if (_hookSupervisor) _hookSupervisor.stopAll();
3026
+ runObserveHooks(
3027
+ settingsHooks,
3028
+ "SessionEnd",
3029
+ {
3030
+ reason: pipeState.closed ? "pipe_closed" : "completed",
3031
+ cwd,
3032
+ session_id: sessionId,
3033
+ },
3034
+ { cwd },
3035
+ );
3036
+ } catch (error) {
3037
+ emitHooksV2Event("StopFailure", {
3038
+ schema_version: 1,
3039
+ session_id: sessionId,
3040
+ phase: "stop-hook-dispatch",
3041
+ reason: error?.message || String(error),
3042
+ });
3043
+ // observe-only + best-effort async surfacing
2732
3044
  }
2733
- // Always reap the supervisor (kills any straggler child + detaches the
2734
- // exit reaper), whether the async Stop was handled here or in re-drive.
2735
- if (_hookSupervisor) _hookSupervisor.stopAll();
2736
- runObserveHooks(
2737
- settingsHooks,
2738
- "SessionEnd",
2739
- { reason: "completed", cwd, session_id: sessionId },
2740
- { cwd },
2741
- );
2742
- } catch (error) {
2743
- emitHooksV2Event("StopFailure", {
2744
- schema_version: 1,
2745
- session_id: sessionId,
2746
- phase: "stop-hook-dispatch",
2747
- reason: error?.message || String(error),
2748
- });
2749
- // observe-only + best-effort async surfacing
2750
3045
  }
3046
+ });
3047
+ const cleanupReport = cleanupDeadline.report();
3048
+ try {
3049
+ deps.onCleanupReport?.(cleanupReport);
3050
+ } catch {
3051
+ // Metrics/reporting cannot change cleanup semantics.
3052
+ }
3053
+ if (cleanupReport.timedOut) {
3054
+ cleanupFailure = cleanupDeadlineError(cleanupReport);
2751
3055
  }
2752
3056
  }
2753
3057
 
3058
+ // Preserve the primary model/runtime failure. A simultaneous cleanup timeout
3059
+ // remains observable through onCleanupReport but must not replace it.
3060
+ if (loopFailureOutcome) return loopFailureOutcome;
3061
+ if (cleanupFailure) throw cleanupFailure;
3062
+
3063
+ // A downstream consumer closed stdout/stderr. The abort above unwound the
3064
+ // model loop through the same `finally` as every other termination, so MCP,
3065
+ // background tasks, approval bridges, and hooks are settled. Do not attempt
3066
+ // more writes to the closed pipe or persist a partial assistant answer.
3067
+ if (pipeState.closed) {
3068
+ return { exitCode: 0, result: finalText, isError: false };
3069
+ }
3070
+
2754
3071
  // coreAgentLoop emits run-ended reason "budget-exhausted" when the iteration
2755
3072
  // cap is hit; treat that as the max-turns error surface. A cost-budget stop
2756
3073
  // (--max-budget-usd) is its own error surface so callers can tell them apart.
2757
3074
  const exhausted =
2758
3075
  endReason === "budget-exhausted" || endReason === "max_turns";
2759
3076
  const costStopped = endReason === "cost-budget-exhausted";
2760
- const isError = exhausted || costStopped || endReason === "no-response";
2761
- const subtype = exhausted
3077
+ let isError = exhausted || costStopped || endReason === "no-response";
3078
+ let subtype = exhausted
2762
3079
  ? "error_max_turns"
2763
3080
  : costStopped
2764
3081
  ? "error_max_budget"
@@ -2766,6 +3083,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2766
3083
  ? "error"
2767
3084
  : "success";
2768
3085
  const durationMs = (deps.now ? deps.now() : Date.now()) - startedAt;
3086
+ let persistenceFailure = null;
2769
3087
 
2770
3088
  // Persist the assistant turn so a later --resume / --continue replays it.
2771
3089
  // The user turn was already recorded up front; only append on a clean run.
@@ -2792,8 +3110,30 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2792
3110
  store.appendTokenUsage(sessionId, au);
2793
3111
  }
2794
3112
  store.appendTokenUsage(sessionId, usage);
2795
- } catch {
2796
- // Persistence is best-effort — never fail the run over it.
3113
+ } catch (error) {
3114
+ const persistenceError = createSessionPersistenceFailure(error, {
3115
+ sessionId,
3116
+ operation: "assistant-turn-append",
3117
+ });
3118
+ persistenceFailure = projectSessionPersistenceFailure(persistenceError, {
3119
+ phase: "after-model",
3120
+ });
3121
+ if (persistenceFailure) {
3122
+ isError = true;
3123
+ subtype = "error_persistence";
3124
+ try {
3125
+ deps.onPersistenceFailure?.(persistenceFailure);
3126
+ } catch {
3127
+ // Observability cannot change persistence semantics.
3128
+ }
3129
+ if (isText) {
3130
+ writeErr(
3131
+ `CC_SESSION_PERSISTENCE_FAILED: assistant turn durability is ${persistenceFailure.commit_state} (${persistenceFailure.fs_code})\n`,
3132
+ );
3133
+ }
3134
+ }
3135
+ // Preserve legacy best-effort handling for errors outside the explicit
3136
+ // ENOSPC/EROFS durability contract.
2797
3137
  }
2798
3138
  }
2799
3139
 
@@ -2854,7 +3194,9 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2854
3194
  if (isText) {
2855
3195
  writeErr(
2856
3196
  `\n ${denials.length} tool call(s) were denied by policy this run:\n` +
2857
- formatDenials(denials, { now: deps.now ? deps.now() : Date.now() }) +
3197
+ formatDenials(denials, {
3198
+ now: deps.now ? deps.now() : Date.now(),
3199
+ }) +
2858
3200
  "\n",
2859
3201
  );
2860
3202
  } else if (isStream) {
@@ -2887,6 +3229,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2887
3229
  ...(compactionDegradations.length
2888
3230
  ? { compaction_degradations: compactionDegradations }
2889
3231
  : {}),
3232
+ ...(persistenceFailure ? { persistence: persistenceFailure } : {}),
2890
3233
  });
2891
3234
  } else if (isJson) {
2892
3235
  writeOut(
@@ -2902,6 +3245,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2902
3245
  durationMs,
2903
3246
  denials,
2904
3247
  compactionDegradations,
3248
+ persistence: persistenceFailure,
2905
3249
  }),
2906
3250
  ) + "\n",
2907
3251
  );
@@ -2923,12 +3267,10 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2923
3267
  );
2924
3268
  if (!isStream) {
2925
3269
  const sum = _otlpRecorder.summary();
2926
- process.stderr.write(
2927
- `[otlp] ${sum.spanCount} span(s) → ${options.otlp}\n`,
2928
- );
3270
+ writeErr(`[otlp] ${sum.spanCount} span(s) → ${options.otlp}\n`);
2929
3271
  }
2930
3272
  } catch (err) {
2931
- process.stderr.write(`[otlp] export failed: ${err.message}\n`);
3273
+ writeErr(`[otlp] export failed: ${err.message}\n`);
2932
3274
  }
2933
3275
  }
2934
3276
  if (_collectorEnabled) {
@@ -2948,6 +3290,7 @@ async function runAgentHeadlessInWorkspace(options = {}, deps = {}) {
2948
3290
  exitCode: exitCodeForEndReason(endReason, isError),
2949
3291
  result: finalText,
2950
3292
  isError,
3293
+ ...(persistenceFailure ? { persistence: persistenceFailure } : {}),
2951
3294
  };
2952
3295
  }
2953
3296
 
@@ -2962,6 +3305,7 @@ function buildResultEnvelope({
2962
3305
  durationMs,
2963
3306
  denials,
2964
3307
  compactionDegradations,
3308
+ persistence,
2965
3309
  }) {
2966
3310
  const env = {
2967
3311
  type: "result",
@@ -2980,5 +3324,6 @@ function buildResultEnvelope({
2980
3324
  if (Array.isArray(compactionDegradations) && compactionDegradations.length) {
2981
3325
  env.compaction_degradations = compactionDegradations;
2982
3326
  }
3327
+ if (persistence) env.persistence = persistence;
2983
3328
  return env;
2984
3329
  }