chainlesschain 0.162.177 → 0.162.178

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 (207) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DT-VPWuN.js → AIOps-DRftcSue.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DyOreOVN.js → ActionButton-BFvUaMKR.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DslNsKux.js → Analytics-C0-Rru4P.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-bx-BCljj.js +9 -0
  8. package/src/assets/web-panel/assets/{Artifacts-DoaAtEcY.js → Artifacts-DAJ8B3Un.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DPG3WdJ9.js → Audit-hOk4L1lr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-C_JO6R5G.js → BackgroundAgents-3pbDpM9z.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-Cv3BooyX.js → Backup-BtziGDn9.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-D2uQXWu-.js → BaseInput-kmshphJ3.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-B4Vczyf3.js → Chat-d7tGSosA.js} +5 -5
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-p-_PBpX_.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-qTgk4VeP.js → Checkbox-C4Eh4Tz5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DlLWcIzj.js → Codegen-r7gmhZyA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-DNO73qp7.js → Col-U7mDaGlL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-vRDNpc4j.js → Community-Bpu8aH1T.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-CEWtIXni.js → Compact-CHb2XBJe.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-KgYC9Fxi.js → Compliance-Cn_r4dey.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-zj5wvkpT.js → Cowork-CdMWfDcz.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-BLDoCozU.js → Cron-BtnREHWy.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BYnff4VY.js → Crosschain-BKI9TSzr.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-C75VFsVm.js → DID-pkNubzzJ.js} +2 -2
  25. package/src/assets/web-panel/assets/Dashboard-1rrz1snN.js +3 -0
  26. package/src/assets/web-panel/assets/{Dropdown-B5dbO4Kx.js → Dropdown-DLyFVxDc.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-BDipZtHJ.js → EmailListRenderer-CtUWfYVq.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmIzt8jj.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-D9QOS-uW.js → Federation-D8lsDGus.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-B47HRwAm.js → FormItemContext-CRYt_aIw.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-3R4mXmgV.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DrNXZf1C.js → Git-BXj5Dney.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BGd7JR98.js → Governance-Cm-I8RMz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-D-2he1ed.js → Inference-CPvbhvgt.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-C0sHIbK5.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DiBQO-jL.js → Logs-BZY_3CRZ.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DL_5eV2D.js → MarkdownRenderer-up0BW6pv.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-VR59OdrC.js → Marketplace-C5MEJ2Qe.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DrPzwSd3.js → McpTools-ihdrn43I.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-CBgKALwQ.js → Memory-Br8bPMlh.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-pQ9Tn-Mn.js → MobileBridge-BNntgrYW.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-mASB6jyj.js → MobileProjects-BdV915mw.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-DTMgE7HC.js → Mtc-A3wDmlqL.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-CwTnCtiL.js → MtcAudit-CF-mBLWM.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-ngm6WU8f.js → Multisig-Iy2k_MSo.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-4YO4Qww9.js → NLProgramming-kk0W7HUb.js} +1 -1
  47. package/src/assets/web-panel/assets/Notes-B5u5t_4S.js +7 -0
  48. package/src/assets/web-panel/assets/{NotificationSettings-r2H8yPKb.js → NotificationSettings-BN0POf6_.js} +1 -1
  49. package/src/assets/web-panel/assets/OrderTableRenderer-CHkSUrXB.js +1 -0
  50. package/src/assets/web-panel/assets/{Organization-DrO7jRWS.js → Organization-DwWQESuN.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-Cnwu4i4t.js → Overflow-DG6klrcC.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-zi-cDwA_.js → P2P-BwJcKkgZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-DBEryDv7.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
  54. package/src/assets/web-panel/assets/{Permissions-lqXK2C3x.js → Permissions-CGMXe4TX.js} +4 -4
  55. package/src/assets/web-panel/assets/PersonalDataHub-ziYqg7-A.js +8 -0
  56. package/src/assets/web-panel/assets/{Pipeline-Kep5KAGW.js → Pipeline-BzJfeoGt.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CHdvOAh1.js → Privacy-DLh3Bblx.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-BhKfdwe9.js → ProjectInit-USVWW36c.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Yavk9K8A.js → ProjectSettings-CiiOn62h.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-6V-PNmcA.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-Bfym6SQn.js → Providers-CBPWWzpu.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-CLSQcrlR.js → QrScannerModal-QVwoRUq3.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-VPa2WKQN.js → QuickAsk-CXnR-2Vn.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-CGXR8jVU.js → Recommend-ClaNzjmF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-C_20YfMB.js → RemoteSession-C3rGw8vJ.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-awZh68UV.js → Reputation-Ci-OWAoP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Ctb4oiYN.js → Row-DRhAwXOS.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-D3sdjvUV.js → RssFeed-BQLJQ2_y.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-C-zfA4kT.js → Search-QQUpkfFd.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-D9XlmHA3.js → Security-DdXKzuKT.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-C3UKnr41.js → Services-x2Zh62Qd.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-BQxagZfF.js → Skeleton-GdY4KwRz.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-Bv4tY1Fd.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-DveENvek.js → Sla-C3QnM-11.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BFjXLNgF.js → SpeechSettings-Bl3ytvmI.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-BpHgbaP9.js → SyncSettings-Wp9IwTsP.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-CF3rIrDe.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-DEiaoyve.js → Templates-CGfvVVM9.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-ITg774nY.js → Tenant-CVXmwQ3U.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-D3ZBcvnb.js → Terminal-bvwrt03E.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BsLU6JjU.js → TimelineRenderer-D0g74KhH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Cb5Pquc9.js → Tokens-BH4IwzlT.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-Dwfy-m9N.js → Trigger-DJOpM533.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CnlSRGG0.js → Trust-B43EPRLr.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-Y4TlvKVo.js → UkeySign-BmBFG-em.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-BpKLtTTO.js → VideoEditing-n9oJwB8k.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-LLBzIq8x.js → Wallet-D5QeivHp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BeSRiusP.js → WebAuthn-DcNy7igO.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-BK_l1tXO.js → WorkflowEditor-BVjc7-5-.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-DYd-X7Wg.js → chat-Dy5Tfj3m.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-C9uip71j.js → colors-C4c2CNZU.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-JBrfjUP6.js → compact-item-DPa3O5vN.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-D03fbuiq.js → createContext-TLcfNA6_.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BAwdDA_5.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DLQOR6l-.js → hasIn-D41LVFkp.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-rIxxfdTN.js → index-0oevYjfT.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CChaISMb.js → index-388pLGJ1.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BfOs_bmg.js → index-B8nLBZbv.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-D-1SqEpQ.js → index-BPTGAjee.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BYv-LWdy.js → index-BPtrOBmW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cpn19c9k.js → index-BQiIxHBy.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BpYKLrUT.js → index-BSM1cHVK.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CS63r8h3.js → index-BYPr9fkz.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Q7mmL6w4.js → index-BsQ7Ev6i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-SO6lgMg0.js → index-Btk9qLL2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-iahNhZwm.js → index-BvmziZSG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BnQgRjsm.js → index-BvopLC_T.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ba-Ps_AJ.js → index-C7xhTVyP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BuNFCZCx.js → index-CNk_ZooP.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CL0ixCxk.js → index-CQrEHoOG.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CChQBM1d.js → index-CSxex23J.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-5YN7F5bO.js → index-CVjgm992.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CwoDpKzW.js → index-CWK4zOPe.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcBZwlnG.js → index-CX0VsoP4.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B46YcgsV.js → index-CXl0YPNG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-l08afIbV.js → index-Cip7LWml.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-6GuLRpBQ.js → index-Cq_hlWNt.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DYLZY4uh.js → index-CzZ49YZL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B0X76LjX.js → index-D2Nkivlm.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BdH9l8wl.js → index-DBUgRs4D.js} +1 -1
  121. package/src/assets/web-panel/assets/index-DEjVkdzq.js +1 -0
  122. package/src/assets/web-panel/assets/{index-D4UY5Z60.js → index-DNsBj2Ob.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Ce0Bf1YF.js → index-Di4X8cfI.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-ByHO3SqZ.js → index-DljFsVGI.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C2zmOsyD.js → index-Korc3QIe.js} +1 -1
  126. package/src/assets/web-panel/assets/index-LMOc5xrS.js +1 -0
  127. package/src/assets/web-panel/assets/{index-BNCdRmWf.js → index-MKZDje0H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D8KvuDz0.js → index-OHAfRu_3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DaokIHt3.js → index-WEJr8PL5.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CILVyK3b.js → index-WinCp6du.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CVZWiUE_.js → index-Yjr6cqSK.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CqWk1FLG.js → index-iQ6gtFih.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMAAUVk.js → index-pt3s7yvV.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Ck17i0OD.js → index-tQ3igERQ.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-CG9z1c6w.js → initDefaultProps-BFZu87WZ.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-B1Ky7Zff.js → motion-DJIo7qvt.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-DME8X2P1.js → move-Cfr5qknL.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cuJYSWB7.js → mtc-parser-Bgc-OadH.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-UI_EE0uZ.js → omit-X3WI-TNp.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-BIoE_Z_V.js → pickAttrs-pJhU9IxK.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-B48-9HKN.js → placementArrow-BNg_Vj3T.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-D7x_GDgD.js → responsiveObserve-TeSEGQ5-.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-DjzC5_7m.js → slide-D_t_P0bv.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-C6BIymhh.js → statusUtils-DrrIMwAT.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-B4dGrdD9.js → styleChecker-BjZCuFRm.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-Bken4aNE.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-BMlqECqv.js → useFs-BidCpfLX.js} +1 -1
  148. package/src/assets/web-panel/assets/usePersonalDataHub-C5mHttA9.js +1 -0
  149. package/src/assets/web-panel/assets/{vnode-CKJQmxa0.js → vnode-BrbcRiHZ.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CaZunuau.js → zoom-PISX7AkR.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/__tests__/hub-wechat.test.js +45 -0
  153. package/src/commands/background-session.js +5 -2
  154. package/src/commands/context.js +33 -1
  155. package/src/commands/hub.js +473 -39
  156. package/src/commands/init.js +212 -110
  157. package/src/commands/llm.js +22 -6
  158. package/src/commands/skill.js +11 -1
  159. package/src/data/changelog.json +76 -270
  160. package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
  161. package/src/harness/golden-transcript.js +3 -0
  162. package/src/index.js +18 -7
  163. package/src/lib/approval-authority-store.js +891 -0
  164. package/src/lib/background-interaction-resolver.js +399 -54
  165. package/src/lib/background-session-transport.js +3 -1
  166. package/src/lib/context-breakdown.js +82 -3
  167. package/src/lib/event-runtime-host.js +225 -0
  168. package/src/lib/event-runtime-store.js +102 -21
  169. package/src/lib/event-runtime-worker.js +70 -2
  170. package/src/lib/hooks-v2-producers.js +33 -8
  171. package/src/lib/hooks-v2-runtime.js +360 -40
  172. package/src/lib/interaction-adapter.js +66 -0
  173. package/src/lib/operation-fingerprint.js +107 -2
  174. package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
  175. package/src/lib/personal-data-hub-wiring.js +327 -52
  176. package/src/lib/process-execution-broker/credential-agent.js +954 -111
  177. package/src/lib/process-execution-broker/index.js +369 -85
  178. package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
  179. package/src/lib/provider-http-error.js +94 -0
  180. package/src/lib/remote-approval-bridge.js +222 -26
  181. package/src/lib/side-effect-ledger-store.js +65 -14
  182. package/src/lib/skill-loader.js +215 -12
  183. package/src/lib/skill-process-broker.js +15 -1
  184. package/src/lib/sub-agent-context.js +42 -0
  185. package/src/lib/turn-binding-store.js +64 -15
  186. package/src/lib/turn-binding.js +85 -16
  187. package/src/repl/agent-repl.js +40 -6
  188. package/src/repl/remote-approval.js +13 -13
  189. package/src/repl/repl-turn-binding.js +46 -16
  190. package/src/runtime/agent-core.js +286 -35
  191. package/src/runtime/coding-agent-contract-shared.cjs +16 -0
  192. package/src/runtime/headless-runner.js +147 -34
  193. package/src/runtime/headless-stream.js +147 -7
  194. package/src/workers/background-agent-worker.js +70 -38
  195. package/src/assets/web-panel/assets/AppLayout-BNJJAYME.js +0 -9
  196. package/src/assets/web-panel/assets/ChatBubbleRenderer-DE6reP99.js +0 -1
  197. package/src/assets/web-panel/assets/Dashboard-C4Gutni2.js +0 -3
  198. package/src/assets/web-panel/assets/Notes-DP1k45mF.js +0 -7
  199. package/src/assets/web-panel/assets/OrderTableRenderer-CoM_Zw0J.js +0 -1
  200. package/src/assets/web-panel/assets/PersonalDataHub-b63T3HJM.js +0 -9
  201. package/src/assets/web-panel/assets/Projects-CN8E_5Mq.js +0 -1
  202. package/src/assets/web-panel/assets/Skills-q6eb6KPZ.js +0 -1
  203. package/src/assets/web-panel/assets/Tasks-DvepRb8-.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-B8LQ8leS.js +0 -1
  205. package/src/assets/web-panel/assets/index-Bh8BPtqR.js +0 -1
  206. package/src/assets/web-panel/assets/index-CeVrHLzZ.js +0 -1
  207. package/src/assets/web-panel/assets/usePersonalDataHub-DLdrfmMa.js +0 -1
@@ -61,6 +61,7 @@ import { SIDE_EFFECT_LEDGER_EVENT } from "../lib/side-effect-ledger-store.js";
61
61
  import { TurnBindingLog, createTurnBindingFeed } from "../lib/turn-binding.js";
62
62
  import { TURN_BINDING_EVENT } from "../lib/turn-binding-store.js";
63
63
  import { operationIdempotencyKey } from "../lib/idempotency.js";
64
+ import { CLISkillLoader } from "../lib/skill-loader.js";
64
65
  import { expandFileRefsAsync } from "./file-ref-expander.js";
65
66
  import { composeSystemPrompt } from "./system-prompt.js";
66
67
  import { buildUserContent } from "../lib/image-input.js";
@@ -79,6 +80,7 @@ import {
79
80
  } from "../lib/exit-codes.cjs";
80
81
  import { isolationLevel } from "../lib/agent-sandbox.js";
81
82
  import { createBackgroundPhaseReporter } from "../lib/background-phase-reporter.js";
83
+ import { createBackgroundInteractionClient } from "../lib/background-interaction-resolver.js";
82
84
  import { withQuietStdout } from "./quiet-stdout.js";
83
85
  import { CostBudget } from "../lib/cost-budget.js";
84
86
  import { estimateTokens } from "../harness/prompt-compressor.js";
@@ -734,21 +736,51 @@ export async function runAgentHeadless(options = {}, deps = {}) {
734
736
  deps.backgroundPhaseReporter || createBackgroundPhaseReporter();
735
737
 
736
738
  const sideEffectRunNonce = String(deps.now ? deps.now() : Date.now());
739
+ const _backgroundPolicyDigest = _bgPhase.enabled
740
+ ? computePolicyDigest({
741
+ permissionMode: options.permissionMode,
742
+ allowedTools: options.allowedTools,
743
+ disallowedTools: options.disallowedTools,
744
+ permissionRules,
745
+ })
746
+ : null;
747
+ const _backgroundInteraction = _bgPhase.enabled
748
+ ? deps.backgroundInteractionClient ||
749
+ createBackgroundInteractionClient({
750
+ backgroundAgentId: process.env.CC_BACKGROUND_AGENT_ID,
751
+ sessionId,
752
+ // One headless invocation is one user turn even when its model/tool
753
+ // loop contains several internal iterations.
754
+ turnId: options.turnId || sideEffectRunNonce,
755
+ })
756
+ : null;
737
757
  let sideEffectLedger = new SideEffectLedger({ clock: deps.now || null });
758
+ let sideEffectLedgerLoadError = null;
738
759
  let sideEffectSeq = 0;
739
760
  let currentSideEffectOpId = null;
740
761
  let resumeSideEffectContext = null;
741
762
  let resumeAsyncHookContext = null;
742
763
  const persistSideEffectLedger = () => {
743
764
  if (!persist) return;
765
+ if (sideEffectLedgerLoadError) throw sideEffectLedgerLoadError;
744
766
  try {
745
- store.appendEvent(
767
+ const persisted = store.appendEvent(
746
768
  sessionId,
747
769
  SIDE_EFFECT_LEDGER_EVENT,
748
770
  sideEffectLedger.toJSON(),
749
771
  );
750
- } catch {
751
- // best-effort — never fail the run over ledger persistence
772
+ if (persisted === false) {
773
+ throw new Error("session store rejected the ledger snapshot");
774
+ }
775
+ } catch (cause) {
776
+ const error = new Error(
777
+ `Side-effect ledger persistence failed for ${sessionId}: ${
778
+ cause?.message || String(cause)
779
+ }`,
780
+ { cause },
781
+ );
782
+ error.code = "SIDE_EFFECT_LEDGER_PERSIST_FAILED";
783
+ throw error;
752
784
  }
753
785
  };
754
786
  // P1 explicit turn→checkpoint binding: feed the TurnBindingLog in real time
@@ -761,6 +793,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
761
793
  // context. Only writes when a turn actually recorded something, and only for
762
794
  // persisted runs — a tool-free Q&A or ephemeral run stays byte-identical.
763
795
  let turnBindingLog = new TurnBindingLog();
796
+ let turnBindingLoadError = null;
764
797
  if (persist) {
765
798
  try {
766
799
  const events = store.readEvents(sessionId) || [];
@@ -776,8 +809,15 @@ export async function runAgentHeadless(options = {}, deps = {}) {
776
809
  break;
777
810
  }
778
811
  }
779
- } catch {
780
- // fresh log — best effort
812
+ } catch (cause) {
813
+ const error = new Error(
814
+ `Turn binding read failed for ${sessionId}: ${
815
+ cause?.message || String(cause)
816
+ }`,
817
+ { cause },
818
+ );
819
+ error.code = "TURN_BINDING_READ_FAILED";
820
+ turnBindingLoadError = error;
781
821
  }
782
822
  }
783
823
  // Event→table folding + id synthesis live in the SHARED feeder core
@@ -788,11 +828,26 @@ export async function runAgentHeadless(options = {}, deps = {}) {
788
828
  : null;
789
829
  const persistTurnBindingLog = () => {
790
830
  if (!turnBindingFeed || !turnBindingFeed.isDirty()) return;
831
+ if (turnBindingLoadError) throw turnBindingLoadError;
791
832
  try {
792
- store.appendEvent(sessionId, TURN_BINDING_EVENT, turnBindingLog.toJSON());
833
+ const persisted = store.appendEvent(
834
+ sessionId,
835
+ TURN_BINDING_EVENT,
836
+ turnBindingLog.toJSON(),
837
+ );
838
+ if (persisted === false) {
839
+ throw new Error("session store rejected the binding snapshot");
840
+ }
793
841
  turnBindingFeed.clearDirty();
794
- } catch {
795
- // best-effort — never fail the run over binding persistence
842
+ } catch (cause) {
843
+ const error = new Error(
844
+ `Turn binding persistence failed for ${sessionId}: ${
845
+ cause?.message || String(cause)
846
+ }`,
847
+ { cause },
848
+ );
849
+ error.code = "TURN_BINDING_PERSIST_FAILED";
850
+ throw error;
796
851
  }
797
852
  };
798
853
  if (persist) {
@@ -812,8 +867,17 @@ export async function runAgentHeadless(options = {}, deps = {}) {
812
867
  break;
813
868
  }
814
869
  }
815
- } catch {
816
- // fresh ledger — best effort
870
+ } catch (cause) {
871
+ const error = new Error(
872
+ `Side-effect ledger read failed for ${sessionId}: ${
873
+ cause?.message || String(cause)
874
+ }`,
875
+ { cause },
876
+ );
877
+ error.code = "SIDE_EFFECT_LEDGER_READ_FAILED";
878
+ // Keep read-only turns available; classified side effects call the
879
+ // persistence gate above before the generator can execute the tool.
880
+ sideEffectLedgerLoadError = error;
817
881
  }
818
882
  // On resume, surface any operation that was in flight when the prior run
819
883
  // died: its outcome is UNKNOWN, so the model is told to VERIFY before any
@@ -968,10 +1032,19 @@ export async function runAgentHeadless(options = {}, deps = {}) {
968
1032
  const _leanNoProjectMemory = options.projectMemory === false;
969
1033
  let _loadedInstructions = null;
970
1034
  let _loadedPersonaSkills = [];
1035
+ let _skillCacheLedger = null;
1036
+ const _runtimeSkillLoader =
1037
+ options.skillLoader || new CLISkillLoader();
971
1038
  const systemContent = composeSystemPrompt(
972
1039
  buildSystemPrompt(cwd, {
973
1040
  additionalDirectories,
974
1041
  projectMemory: options.projectMemory,
1042
+ sessionId,
1043
+ skillLoader: _runtimeSkillLoader,
1044
+ onSkillsLoaded: (skills, cacheLedger) => {
1045
+ _loadedPersonaSkills = Array.isArray(skills) ? skills : [];
1046
+ _skillCacheLedger = cacheLedger || null;
1047
+ },
975
1048
  }),
976
1049
  {
977
1050
  systemPrompt: options.systemPrompt,
@@ -982,9 +1055,6 @@ export async function runAgentHeadless(options = {}, deps = {}) {
982
1055
  onInstructionsLoaded: (loaded) => {
983
1056
  _loadedInstructions = loaded;
984
1057
  },
985
- onSkillsLoaded: (skills) => {
986
- _loadedPersonaSkills = Array.isArray(skills) ? skills : [];
987
- },
988
1058
  },
989
1059
  );
990
1060
 
@@ -1367,10 +1437,17 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1367
1437
  // Persist the admitted runtime schema surface so `cc context --sources` can
1368
1438
  // attribute MCP cost to the actual server/tool definitions used by this run,
1369
1439
  // rather than guessing from the current environment on a later invocation.
1370
- if (
1371
- persist &&
1372
- (mcp?.extraToolDefinitions?.length || _loadedPersonaSkills.length)
1373
- ) {
1440
+ const persistContextSources = () => {
1441
+ if (!persist) return false;
1442
+ _skillCacheLedger =
1443
+ _runtimeSkillLoader.getCacheLedger?.() || _skillCacheLedger;
1444
+ if (
1445
+ !mcp?.extraToolDefinitions?.length &&
1446
+ !_loadedPersonaSkills.length &&
1447
+ !_skillCacheLedger?.descriptors?.resident
1448
+ ) {
1449
+ return false;
1450
+ }
1374
1451
  try {
1375
1452
  store.appendEvent(sessionId, "context_sources", {
1376
1453
  mcp: (mcp?.extraToolDefinitions || []).map((definition) => ({
@@ -1384,13 +1461,22 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1384
1461
  id: skill?.id || skill?.displayName || "skill",
1385
1462
  displayName: skill?.displayName || skill?.id || "skill",
1386
1463
  source: skill?.source || "skill",
1387
- body: typeof skill?.body === "string" ? skill.body : "",
1464
+ tokens: estimateTokens(
1465
+ `## Persona: ${skill?.displayName || skill?.id || "skill"}\n${
1466
+ typeof skill?.body === "string" ? skill.body : ""
1467
+ }`,
1468
+ ),
1469
+ bodyLoaded: skill?.bodyLoaded === true,
1388
1470
  })),
1471
+ skillCache: _skillCacheLedger,
1389
1472
  });
1473
+ return true;
1390
1474
  } catch {
1391
1475
  // Source attribution is observability-only; never fail the agent run.
1476
+ return false;
1392
1477
  }
1393
- }
1478
+ };
1479
+ persistContextSources();
1394
1480
 
1395
1481
  const loopOptions = {
1396
1482
  model,
@@ -1409,6 +1495,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1409
1495
  baseUrl,
1410
1496
  apiKey,
1411
1497
  cwd,
1498
+ skillLoader: _runtimeSkillLoader,
1412
1499
  additionalDirectories,
1413
1500
  sandbox: options.sandbox || null,
1414
1501
  sessionId,
@@ -1435,27 +1522,39 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1435
1522
  disabledTools,
1436
1523
  iterationBudget: budget,
1437
1524
  toolAdmission: options.toolAdmission || null,
1438
- // `ask_user_question` in a BACKGROUND turn child (P0 state-machine
1439
- // producer for `needs_input`): there is no live channel to a human
1440
- // mid-turn, but the question IS the structured needs-input signal. Park
1441
- // it — record `phase: "needs_input"` + the question in the shared state
1442
- // file (dashboard "Needs input" gets real data; the user's reply arrives
1443
- // as the next attach prompt) and tell the model to end the turn. A
1444
- // non-background run supplies no interaction → the handler's existing
1445
- // `user_not_reachable` path stays byte-identical.
1525
+ // `ask_user_question` in a BACKGROUND turn child: send a bound request to
1526
+ // the worker over Node IPC and await the answer here. `executeTool` remains
1527
+ // suspended, so the model continues the SAME turn/tool call after the user
1528
+ // answers instead of receiving the answer as a follow-up turn.
1446
1529
  ...(_bgPhase.enabled
1447
1530
  ? {
1448
1531
  interaction: {
1449
- askUser: async ({ question, options: qOptions } = {}) => {
1532
+ askUser: async ({
1533
+ question,
1534
+ options: qOptions,
1535
+ multiSelect,
1536
+ timeoutMs,
1537
+ defaultValue,
1538
+ toolUseId,
1539
+ turnId,
1540
+ policyDigest,
1541
+ } = {}) => {
1450
1542
  _bgPhase.reportQuestion({
1451
1543
  question: typeof question === "string" ? question : "",
1452
1544
  options: Array.isArray(qOptions) ? qOptions : null,
1453
1545
  });
1454
- throw new Error(
1455
- "question parked for the user (background session). End this " +
1456
- "turn now — the user's reply will arrive as the next " +
1457
- "message. Do not repeat the question.",
1458
- );
1546
+ return _backgroundInteraction.request({
1547
+ kind: "question",
1548
+ question,
1549
+ options: qOptions,
1550
+ multiSelect,
1551
+ timeoutMs,
1552
+ defaultValue,
1553
+ toolUseId,
1554
+ turnId,
1555
+ policyDigest: policyDigest || _backgroundPolicyDigest,
1556
+ sessionId,
1557
+ });
1459
1558
  },
1460
1559
  },
1461
1560
  }
@@ -1824,10 +1923,15 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1824
1923
  if (isText) writeErr(line + "\n");
1825
1924
  emitStream({
1826
1925
  type: "tool_use",
1926
+ ...(event.tool_use_id ? { id: event.tool_use_id } : {}),
1927
+ tool: event.tool,
1928
+ args: event.args,
1929
+ });
1930
+ toolCalls.push({
1931
+ ...(event.tool_use_id ? { id: event.tool_use_id } : {}),
1827
1932
  tool: event.tool,
1828
1933
  args: event.args,
1829
1934
  });
1830
- toolCalls.push({ tool: event.tool, args: event.args });
1831
1935
  // P0-2: record an irreversible effect as STARTED (persisted before
1832
1936
  // it settles) so a crash before the matching tool-result leaves a
1833
1937
  // reconcilable "in flight" marker instead of a silent replay.
@@ -1882,6 +1986,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1882
1986
  if (isText && err) writeErr(` Error: ${err}\n`);
1883
1987
  emitStream({
1884
1988
  type: "tool_result",
1989
+ ...(event.tool_use_id ? { id: event.tool_use_id } : {}),
1885
1990
  tool: event.tool,
1886
1991
  is_error: Boolean(err),
1887
1992
  error: err,
@@ -2198,6 +2303,14 @@ export async function runAgentHeadless(options = {}, deps = {}) {
2198
2303
  // "the model call failed" from "the run itself errored" (1).
2199
2304
  return { exitCode: classifyLoopError(err), result: message, isError: true };
2200
2305
  } finally {
2306
+ // Capture body-cache hits/misses caused by list_skills/run_skill during the
2307
+ // turn. `cc context --sources` reads the newest snapshot.
2308
+ persistContextSources();
2309
+ try {
2310
+ _backgroundInteraction?.close?.();
2311
+ } catch {
2312
+ // Best-effort listener cleanup; never mask the run result.
2313
+ }
2201
2314
  // Drop the signal handlers first — a normal return must not leave a
2202
2315
  // process-wide SIGINT/SIGTERM listener behind (a later Ctrl-C would wrongly
2203
2316
  // exit with 130, and repeated in-process runs would leak listeners).
@@ -72,10 +72,13 @@ import {
72
72
  startSession as jsonlStartSession,
73
73
  appendUserMessage as jsonlAppendUserMessage,
74
74
  appendAssistantMessage as jsonlAppendAssistantMessage,
75
+ appendEvent as jsonlAppendEvent,
76
+ readEvents as jsonlReadEvents,
75
77
  rebuildMessages as jsonlRebuildMessages,
76
78
  sessionExists as jsonlSessionExists,
77
79
  } from "../harness/jsonl-session-store.js";
78
80
  import {
81
+ SideEffectLedger,
79
82
  reconcileSideEffects,
80
83
  classifyToolSideEffect,
81
84
  } from "../lib/side-effect-ledger.js";
@@ -85,6 +88,8 @@ import {
85
88
  persistSideEffectLedger,
86
89
  } from "../lib/side-effect-ledger-store.js";
87
90
  import { operationIdempotencyKey } from "../lib/idempotency.js";
91
+ import { TurnBindingLog, createTurnBindingFeed } from "../lib/turn-binding.js";
92
+ import { TURN_BINDING_EVENT } from "../lib/turn-binding-store.js";
88
93
  import { getPlanModeManager } from "../lib/plan-mode.js";
89
94
  import {
90
95
  SESSION_SLASH_COMMANDS,
@@ -697,7 +702,15 @@ function buildSideEffectRecovery(ledger) {
697
702
  async function runTurn(
698
703
  messages,
699
704
  loopOptions,
700
- { runLoop, emit, costBudget, nextToolUseId, sideEffects, turnNumber },
705
+ {
706
+ runLoop,
707
+ emit,
708
+ costBudget,
709
+ nextToolUseId,
710
+ sideEffects,
711
+ turnNumber,
712
+ turnBindingFeed,
713
+ },
701
714
  ) {
702
715
  const usage = {
703
716
  input_tokens: 0,
@@ -741,12 +754,15 @@ async function runTurn(
741
754
  collapseConsecutiveMessagesInPlace(messages);
742
755
  }
743
756
  for await (const event of runLoop(messages, loopOptions)) {
757
+ turnBindingFeed?.handleEvent(event);
744
758
  switch (event.type) {
745
759
  case "tool-executing": {
746
760
  // Additive protocol-v1 correlation id ("tu-<n>", session-scoped —
747
761
  // agent-sdk docs/PROTOCOL.md §1.2.1): the matching tool_result below
748
762
  // echoes the same id so UIs can pair calls without adjacency.
749
- const toolUseId = nextToolUseId ? nextToolUseId() : undefined;
763
+ const toolUseId =
764
+ event.tool_use_id ||
765
+ (nextToolUseId ? nextToolUseId() : undefined);
750
766
  const pm = getPlanModeManager();
751
767
  const planItem = pm.startPlanItemForTool(event.tool, {
752
768
  toolUseId,
@@ -826,7 +842,10 @@ async function runTurn(
826
842
  // recent tool_use (same adjacency rule the is_error attribution
827
843
  // below has always used).
828
844
  const lastCall =
829
- toolCalls.length > 0 ? toolCalls[toolCalls.length - 1] : null;
845
+ (event.tool_use_id
846
+ ? toolCalls.find((call) => call.id === event.tool_use_id)
847
+ : null) ||
848
+ (toolCalls.length > 0 ? toolCalls[toolCalls.length - 1] : null);
830
849
  if (lastCall) lastCall.is_error = Boolean(err);
831
850
  const pm = getPlanModeManager();
832
851
  const settledItem = lastCall?.planItemId
@@ -1107,6 +1126,8 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1107
1126
  appendUserMessage: deps.appendUserMessage || jsonlAppendUserMessage,
1108
1127
  appendAssistantMessage:
1109
1128
  deps.appendAssistantMessage || jsonlAppendAssistantMessage,
1129
+ appendEvent: deps.appendEvent || jsonlAppendEvent,
1130
+ readEvents: deps.readEvents || jsonlReadEvents,
1110
1131
  rebuildMessages: deps.rebuildMessages || jsonlRebuildMessages,
1111
1132
  };
1112
1133
  // --ephemeral forces persistence OFF even with an explicit session id (the
@@ -1125,12 +1146,15 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1125
1146
  const loadLedger = deps.loadSideEffectLedger || loadSideEffectLedger;
1126
1147
  const persistLedger = deps.persistSideEffectLedger || persistSideEffectLedger;
1127
1148
  let sideEffectLedger = null;
1149
+ let sideEffectLedgerLoadError = null;
1128
1150
  if (persist) {
1129
1151
  try {
1130
1152
  sideEffectLedger = loadLedger(sessionId);
1131
- } catch {
1132
- // ledger is best-effort — never block the stream over it
1133
- sideEffectLedger = null;
1153
+ } catch (error) {
1154
+ // Read-only turns remain available, but the first classified side effect
1155
+ // fails before the generator can resume into the actual operation.
1156
+ sideEffectLedger = new SideEffectLedger();
1157
+ sideEffectLedgerLoadError = error;
1134
1158
  }
1135
1159
  }
1136
1160
  let sideEffectSeq = 0;
@@ -1141,11 +1165,111 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1141
1165
  const sideEffects = sideEffectLedger
1142
1166
  ? {
1143
1167
  ledger: sideEffectLedger,
1144
- persist: () => persistLedger(sessionId, sideEffectLedger),
1168
+ persist: () => {
1169
+ if (sideEffectLedgerLoadError) throw sideEffectLedgerLoadError;
1170
+ const persisted = persistLedger(sessionId, sideEffectLedger);
1171
+ if (persisted === false) {
1172
+ const error = new Error(
1173
+ `Side-effect ledger persistence was rejected for ${sessionId}`,
1174
+ );
1175
+ error.code = "SIDE_EFFECT_LEDGER_PERSIST_FAILED";
1176
+ throw error;
1177
+ }
1178
+ return persisted;
1179
+ },
1145
1180
  nextOpId: () => `${sideEffectRunNonce}:${sideEffectSeq++}`,
1146
1181
  }
1147
1182
  : null;
1148
1183
 
1184
+ // P1 unified turn/checkpoint producer for long-lived stream sessions. Load
1185
+ // the latest snapshot on resume, then append one explicit coverage record
1186
+ // for every input turn (including tool-free turns).
1187
+ let turnBindingLog = new TurnBindingLog();
1188
+ let turnBindingPersistenceError = null;
1189
+ let turnBindingDegradationEmitted = false;
1190
+ let turnBindingTerminalEmitted = false;
1191
+ const recordTurnBindingFailure = (error, operation) => {
1192
+ if (turnBindingPersistenceError) return turnBindingPersistenceError;
1193
+ const wrapped = new Error(
1194
+ `Turn binding ${operation} failed for ${sessionId}: ${
1195
+ error?.message || String(error)
1196
+ }`,
1197
+ { cause: error },
1198
+ );
1199
+ wrapped.code =
1200
+ operation === "read"
1201
+ ? "TURN_BINDING_READ_FAILED"
1202
+ : "TURN_BINDING_PERSIST_FAILED";
1203
+ turnBindingPersistenceError = wrapped;
1204
+ return wrapped;
1205
+ };
1206
+ const emitTurnBindingFailure = () => {
1207
+ if (!turnBindingPersistenceError) return false;
1208
+ if (!turnBindingDegradationEmitted) {
1209
+ emit({
1210
+ type: "recovery_degraded",
1211
+ component: "turn_binding",
1212
+ session_id: sessionId,
1213
+ error: turnBindingPersistenceError.message,
1214
+ });
1215
+ turnBindingDegradationEmitted = true;
1216
+ }
1217
+ if (!turnBindingTerminalEmitted) {
1218
+ emit({
1219
+ type: "result",
1220
+ subtype: "error",
1221
+ is_error: true,
1222
+ error: turnBindingPersistenceError.message,
1223
+ session_id: sessionId,
1224
+ });
1225
+ turnBindingTerminalEmitted = true;
1226
+ }
1227
+ return true;
1228
+ };
1229
+ if (persist) {
1230
+ try {
1231
+ const priorEvents = store.readEvents(sessionId) || [];
1232
+ for (let index = priorEvents.length - 1; index >= 0; index--) {
1233
+ const event = priorEvents[index];
1234
+ if (
1235
+ event?.type === TURN_BINDING_EVENT &&
1236
+ event.data &&
1237
+ typeof event.data === "object"
1238
+ ) {
1239
+ turnBindingLog = TurnBindingLog.fromJSON(event.data);
1240
+ break;
1241
+ }
1242
+ }
1243
+ } catch (error) {
1244
+ recordTurnBindingFailure(error, "read");
1245
+ }
1246
+ }
1247
+ const turnBindingFeed = persist
1248
+ ? createTurnBindingFeed({
1249
+ log: turnBindingLog,
1250
+ nonce: sideEffectRunNonce,
1251
+ })
1252
+ : null;
1253
+ const persistTurnBindingLog = () => {
1254
+ if (!turnBindingFeed?.isDirty()) return true;
1255
+ if (turnBindingPersistenceError) return false;
1256
+ try {
1257
+ const persisted = store.appendEvent(
1258
+ sessionId,
1259
+ TURN_BINDING_EVENT,
1260
+ turnBindingLog.toJSON(),
1261
+ );
1262
+ if (persisted === false) {
1263
+ throw new Error("session store rejected the binding snapshot");
1264
+ }
1265
+ turnBindingFeed.clearDirty();
1266
+ return true;
1267
+ } catch (error) {
1268
+ recordTurnBindingFailure(error, "persistence");
1269
+ return false;
1270
+ }
1271
+ };
1272
+
1149
1273
  // ── Interactive approvals (--interactive-approvals; chat-panel UX) ────────
1150
1274
  // CONFIRM-tier decisions (risky shell via the ApprovalGate, settings/hook
1151
1275
  // `ask`) normally fail closed in headless. With this opt-in they become a
@@ -1962,6 +2086,11 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1962
2086
  for (;;) {
1963
2087
  const parsed = await nextEvent();
1964
2088
  if (parsed == null) break; // stdin closed
2089
+ if (turnBindingPersistenceError) {
2090
+ emitTurnBindingFailure();
2091
+ sawError = true;
2092
+ break;
2093
+ }
1965
2094
 
1966
2095
  if (parsed.slashCommand) {
1967
2096
  const requestId =
@@ -2418,6 +2547,9 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
2418
2547
  }
2419
2548
  }
2420
2549
  turns += 1;
2550
+ turnBindingFeed?.beginTurn(messages.length, {
2551
+ worktreeId: options.worktreeId ?? null,
2552
+ });
2421
2553
 
2422
2554
  // Per-turn abort scope: an {"type":"interrupt"} from the pump above
2423
2555
  // aborts THIS turn (LLM fetch included — the signal reaches chatWithTools)
@@ -2488,6 +2620,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
2488
2620
  nextToolUseId,
2489
2621
  sideEffects,
2490
2622
  turnNumber: turns,
2623
+ turnBindingFeed,
2491
2624
  },
2492
2625
  );
2493
2626
  } catch (err) {
@@ -2531,6 +2664,13 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
2531
2664
  });
2532
2665
  sawError = true;
2533
2666
  continue;
2667
+ } finally {
2668
+ persistTurnBindingLog();
2669
+ }
2670
+ if (turnBindingPersistenceError) {
2671
+ emitTurnBindingFailure();
2672
+ sawError = true;
2673
+ break;
2534
2674
  }
2535
2675
  currentAbort = null;
2536
2676