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
@@ -13,6 +13,7 @@
13
13
  * -> toolCallIds[] (tool_use_ids this turn issued)
14
14
  * -> permissionDecisionIds[] (approval decisions made this turn)
15
15
  * -> childAgentIds[] (sub-agents spawned this turn)
16
+ * -> childBindings[] (child trace/checkpoint/worktree lineage)
16
17
  * -> worktreeId (isolation worktree, if any)
17
18
  * -> coverage: full | partial | none
18
19
  *
@@ -90,6 +91,7 @@ function emptyRecord(turnId, conversationOffset) {
90
91
  toolCallIds: [],
91
92
  permissionDecisionIds: [],
92
93
  childAgentIds: [],
94
+ childBindings: [],
93
95
  worktreeId: null,
94
96
  // side-effect flags → coverage (kept internal; surfaced via `coverage`)
95
97
  _flags: {
@@ -160,6 +162,39 @@ export class TurnBindingLog {
160
162
  return this;
161
163
  }
162
164
 
165
+ recordChildBinding(turnId, binding) {
166
+ if (!binding || typeof binding !== "object") return this;
167
+ const childAgentId =
168
+ binding.childAgentId == null ? null : String(binding.childAgentId);
169
+ if (!childAgentId) return this;
170
+ const normalized = {
171
+ childAgentId,
172
+ parentAgentId:
173
+ binding.parentAgentId == null ? null : String(binding.parentAgentId),
174
+ traceId: binding.traceId == null ? null : String(binding.traceId),
175
+ parentTraceId:
176
+ binding.parentTraceId == null ? null : String(binding.parentTraceId),
177
+ checkpointIds: Array.isArray(binding.checkpointIds)
178
+ ? [...new Set(binding.checkpointIds.map(String))]
179
+ : [],
180
+ toolUseIds: Array.isArray(binding.toolUseIds)
181
+ ? [...new Set(binding.toolUseIds.map(String))]
182
+ : [],
183
+ worktreeId:
184
+ binding.worktreeId == null ? null : String(binding.worktreeId),
185
+ worktreePath:
186
+ binding.worktreePath == null ? null : String(binding.worktreePath),
187
+ };
188
+ const rec = this._rec(turnId);
189
+ const index = rec.childBindings.findIndex(
190
+ (item) => item.childAgentId === childAgentId,
191
+ );
192
+ if (index === -1) rec.childBindings.push(normalized);
193
+ else rec.childBindings[index] = normalized;
194
+ pushUnique(rec.childAgentIds, childAgentId);
195
+ return this;
196
+ }
197
+
163
198
  /** Bind (or re-bind to the earliest) file checkpoint for this turn. */
164
199
  bindCheckpoint(turnId, checkpointId) {
165
200
  const rec = this._rec(turnId);
@@ -231,6 +266,11 @@ export class TurnBindingLog {
231
266
  toolCallIds: [...rec.toolCallIds],
232
267
  permissionDecisionIds: [...rec.permissionDecisionIds],
233
268
  childAgentIds: [...rec.childAgentIds],
269
+ childBindings: rec.childBindings.map((binding) => ({
270
+ ...binding,
271
+ checkpointIds: [...binding.checkpointIds],
272
+ toolUseIds: [...binding.toolUseIds],
273
+ })),
234
274
  worktreeId: rec.worktreeId,
235
275
  coverage: computeCoverage(rec._flags),
236
276
  };
@@ -260,6 +300,22 @@ export class TurnBindingLog {
260
300
  childAgentIds: Array.isArray(r.childAgentIds)
261
301
  ? [...r.childAgentIds]
262
302
  : [],
303
+ childBindings: Array.isArray(r.childBindings)
304
+ ? r.childBindings.map((binding) => ({
305
+ childAgentId: String(binding.childAgentId),
306
+ parentAgentId: binding.parentAgentId ?? null,
307
+ traceId: binding.traceId ?? null,
308
+ parentTraceId: binding.parentTraceId ?? null,
309
+ checkpointIds: Array.isArray(binding.checkpointIds)
310
+ ? binding.checkpointIds.map(String)
311
+ : [],
312
+ toolUseIds: Array.isArray(binding.toolUseIds)
313
+ ? binding.toolUseIds.map(String)
314
+ : [],
315
+ worktreeId: binding.worktreeId ?? null,
316
+ worktreePath: binding.worktreePath ?? null,
317
+ }))
318
+ : [],
263
319
  worktreeId: r.worktreeId ?? null,
264
320
  _flags: { ...emptyRecord(id)._flags, ...(r._flags || {}) },
265
321
  });
@@ -428,16 +484,12 @@ export function buildTurnBindingFromMarks(turns = [], marks = []) {
428
484
  /**
429
485
  * Stateful feeder turning raw agent-loop events into TurnBindingLog records —
430
486
  * the SHARED producer core for every runner (headless and the interactive
431
- * REPL), so id synthesis and the event→table mapping can never drift between
432
- * them. Provider tool_use ids are not visible on the yielded event stream, so
433
- * tool-call ids are synthesized `<nonce>:c<seq>` and turn ids `<nonce>:t<seq>`
434
- * (mirrors the side-effect ledger's op-id scheme — stable within a run, unique
435
- * across resumes as long as `nonce` is per-run unique).
487
+ * REPL). Provider tool ids are consumed directly; ids are synthesized only for
488
+ * legacy/custom event sources that omit `tool_use_id`.
436
489
  *
437
- * Dirty-gating: `beginTurn` alone never marks the table dirty, so a tool-free
438
- * turn costs the caller zero persistence writes; only a recorded signal
439
- * (checkpoint / tool call / permission decision / child agent / a supersede
440
- * prune) does.
490
+ * Every begun turn marks the table dirty. Tool-free turns still need an
491
+ * explicit `full` coverage record; otherwise restore/history consumers cannot
492
+ * distinguish "nothing happened" from "the producer failed to record".
441
493
  *
442
494
  * @param {{log?: TurnBindingLog, nonce?: string}} [opts]
443
495
  */
@@ -458,8 +510,7 @@ export function createTurnBindingFeed({
458
510
  * contract `pickPersistedTurn` relies on). `supersede: true` first prunes
459
511
  * records at/after that offset (live-REPL rewind/clear/compaction rule);
460
512
  * append-only runners leave it off. `worktreeId` stamps the run's isolation
461
- * worktree onto the record (does NOT mark dirty by itself — a tool-free
462
- * turn in a worktree still writes nothing).
513
+ * worktree onto the record.
463
514
  */
464
515
  beginTurn(
465
516
  conversationOffset,
@@ -471,6 +522,7 @@ export function createTurnBindingFeed({
471
522
  currentTurnId = `${nonce}:t${turnSeq++}`;
472
523
  log.startTurn(currentTurnId, { conversationOffset });
473
524
  if (worktreeId != null) log.setWorktree(currentTurnId, worktreeId);
525
+ dirty = true;
474
526
  return currentTurnId;
475
527
  },
476
528
  currentTurnId: () => currentTurnId,
@@ -486,27 +538,41 @@ export function createTurnBindingFeed({
486
538
  }
487
539
  break;
488
540
  case "tool-executing": {
489
- const callId = `${nonce}:c${callSeq++}`;
541
+ const callId =
542
+ event.tool_use_id == null
543
+ ? `${nonce}:c${callSeq++}`
544
+ : String(event.tool_use_id);
490
545
  currentCallId = callId;
491
546
  log.recordToolCall(turnId, callId, { name: event.tool });
492
547
  dirty = true;
493
548
  break;
494
549
  }
495
550
  case "tool-result": {
496
- // A gated tool surfaces its permission decision on `result.policy`;
497
- // re-derive a stable decision id feeder-side (core's own id hangs
498
- // off the provider call id, which the event stream does not expose).
551
+ const callId =
552
+ event.tool_use_id == null
553
+ ? currentCallId
554
+ : String(event.tool_use_id);
555
+ // New core events expose the exact decision id. Legacy/custom event
556
+ // sources retain the deterministic compatibility derivation.
499
557
  const policy = event.result?.policy;
500
558
  if (policy && (policy.via || policy.decision)) {
501
559
  log.recordPermissionDecision(
502
560
  turnId,
503
- `${currentCallId || "call"}:perm:${policy.via || policy.decision}`,
561
+ event.permission_decision_id ||
562
+ `${callId || "call"}:perm:${policy.via || policy.decision}`,
504
563
  );
505
564
  dirty = true;
506
565
  }
566
+ if (event.result?.userEdited === true) {
567
+ log.markUserEdit(turnId);
568
+ dirty = true;
569
+ }
507
570
  // Foreground spawn_sub_agent reports its child id on the result.
508
571
  if (event.result?.subAgentId) {
509
572
  log.recordChildAgent(turnId, event.result.subAgentId);
573
+ if (event.result.childBinding) {
574
+ log.recordChildBinding(turnId, event.result.childBinding);
575
+ }
510
576
  dirty = true;
511
577
  }
512
578
  currentCallId = null;
@@ -515,6 +581,9 @@ export function createTurnBindingFeed({
515
581
  case "background-sub-agent-result":
516
582
  if (event.subAgentId) {
517
583
  log.recordChildAgent(turnId, event.subAgentId);
584
+ if (event.childBinding) {
585
+ log.recordChildBinding(turnId, event.childBinding);
586
+ }
518
587
  dirty = true;
519
588
  }
520
589
  break;
@@ -1123,8 +1123,10 @@ export async function startAgentRepl(options = {}) {
1123
1123
  // P1 explicit turn→checkpoint binding — the REPL as PRODUCER (mirrors the
1124
1124
  // headless runner's feed via the shared feeder core): one record per
1125
1125
  // interactive turn, persisted dirty-gated at each settle. Lazily created on
1126
- // the first prompt turn of a persistable (JSONL) session; advisory only.
1126
+ // the first prompt turn of a persistable (JSONL) session. This is Critical
1127
+ // recovery state: a failure blocks subsequent model/tool turns.
1127
1128
  let _turnBindingProducer = null;
1129
+ let _turnBindingCriticalError = null;
1128
1130
  // Apply --output-style or the settings.json `outputStyle` default at startup.
1129
1131
  try {
1130
1132
  const { resolveOutputStyle } = await import("../lib/output-styles.js");
@@ -3650,6 +3652,11 @@ export async function startAgentRepl(options = {}) {
3650
3652
  _sanitizeRolesNextTurn =
3651
3653
  messages[messages.length - 1]?.role === "user";
3652
3654
  sessionId = resumeId;
3655
+ // The producer owns one immutable session id. Recreate it lazily
3656
+ // on the next turn so bindings from the resumed conversation can
3657
+ // never be appended to the previously active session.
3658
+ _turnBindingProducer = null;
3659
+ _turnBindingCriticalError = null;
3653
3660
  // The prior session's checkpoint marks (turn-index → checkpoint id)
3654
3661
  // no longer map to this swapped-in history; keeping them would make
3655
3662
  // a later /rewind restore files from the WRONG session's snapshot.
@@ -3671,6 +3678,8 @@ export async function startAgentRepl(options = {}) {
3671
3678
  _sanitizeRolesNextTurn =
3672
3679
  messages[messages.length - 1]?.role === "user";
3673
3680
  sessionId = existing.id;
3681
+ _turnBindingProducer = null;
3682
+ _turnBindingCriticalError = null;
3674
3683
  // Drop the prior session's checkpoint marks (see JSONL branch).
3675
3684
  _checkpointMarks.length = 0;
3676
3685
  _clearedConversation = null; // stash belongs to the swapped-out session
@@ -5267,6 +5276,15 @@ export async function startAgentRepl(options = {}) {
5267
5276
  }
5268
5277
  }
5269
5278
 
5279
+ if (_turnBindingCriticalError) {
5280
+ logger.error(
5281
+ `[recovery] critical turn-binding state is unavailable: ${_turnBindingCriticalError.message}. ` +
5282
+ "Resume or start a healthy session before running another model/tool turn.",
5283
+ );
5284
+ prompt();
5285
+ return;
5286
+ }
5287
+
5270
5288
  // Add user message (keep the object ref so a /btw aside can be injected for
5271
5289
  // this turn's model call and then stripped before persistence).
5272
5290
  const _userMsg = { role: "user", content: _userMessageContent };
@@ -5283,11 +5301,26 @@ export async function startAgentRepl(options = {}) {
5283
5301
  if (!_turnBindingProducer) {
5284
5302
  const { createReplTurnBindingProducer } =
5285
5303
  await import("./repl-turn-binding.js");
5286
- _turnBindingProducer = createReplTurnBindingProducer({ sessionId });
5304
+ _turnBindingProducer = createReplTurnBindingProducer({
5305
+ sessionId,
5306
+ onError: (error, phase) => {
5307
+ _turnBindingCriticalError = error;
5308
+ logger.warn(
5309
+ `[recovery] turn binding ${phase} failed for ${sessionId}: ${error.message}`,
5310
+ );
5311
+ },
5312
+ });
5287
5313
  }
5288
5314
  _turnBindingProducer?.beginTurn(messages.length);
5289
- } catch {
5290
- /* advisory — never disturb the turn */
5315
+ } catch (error) {
5316
+ _turnBindingCriticalError =
5317
+ error instanceof Error ? error : new Error(String(error));
5318
+ if (messages[messages.length - 1] === _userMsg) messages.pop();
5319
+ logger.error(
5320
+ `[recovery] refusing the turn because binding state is unavailable: ${_turnBindingCriticalError.message}`,
5321
+ );
5322
+ prompt();
5323
+ return;
5291
5324
  }
5292
5325
  }
5293
5326
 
@@ -5722,8 +5755,9 @@ export async function startAgentRepl(options = {}) {
5722
5755
  // Non-critical
5723
5756
  }
5724
5757
  }
5725
- // Persist the explicit turn-binding table for this settled turn
5726
- // (dirty-gated: a tool-free Q&A turn writes nothing; never throws).
5758
+ // Persist the explicit turn-binding table for this settled turn,
5759
+ // including tool-free turns. A failure is Critical: the producer throws,
5760
+ // this turn reports an error, and the guard above blocks later turns.
5727
5761
  _turnBindingProducer?.persistIfDirty();
5728
5762
  // Auto-compact when context grows too large
5729
5763
  if (
@@ -8,10 +8,9 @@
8
8
  * publishes permission.resolved so the phone/web UI clears it)
9
9
  * - remote answer first → cancel the local readline prompt and print
10
10
  * the outcome
11
- * - remote leg times out or the bridge closes mid-ask → NOT a decision:
12
- * keep waiting on the terminal. A silent phone must never auto-deny a
13
- * user sitting at the keyboard — the fail-closed timeout is headless
14
- * semantics only.
11
+ * - remote leg times out or the bridge closes mid-ask → NOT a remote human
12
+ * decision, so keep waiting on the terminal. A later local answer still
13
+ * needs the bridge's durable CAS; an expired/closed card cannot be revived.
15
14
  *
16
15
  * Pure over an injected bridge + local prompt handle ({promise, cancel}),
17
16
  * so the race is unit-testable without a WS server or readline.
@@ -46,8 +45,9 @@ export function describeAskContext({
46
45
  };
47
46
  }
48
47
 
49
- // Remote outcomes that are NOT a decision for an interactive session: the
50
- // devices went silent (timeout) or the bridge was torn down mid-ask (closed).
48
+ // These outcomes are not a remote human decision. The terminal may still
49
+ // answer, but its result is accepted only if resolveLocally can durably settle
50
+ // the same live card.
51
51
  const NON_DECISIVE_REMOTE = new Set(["timeout", "closed"]);
52
52
 
53
53
  /**
@@ -90,14 +90,14 @@ export async function raceLocalAndRemote({
90
90
  winner = await localLeg;
91
91
  }
92
92
  if (winner.src === "local") {
93
- if (requestId) {
94
- try {
95
- bridge.resolveLocally(requestId, winner.approved);
96
- } catch {
97
- // clearing the device card is best-effort
98
- }
93
+ if (!requestId) return false;
94
+ try {
95
+ const persisted = bridge.resolveLocally(requestId, winner.approved);
96
+ return persisted === true && winner.approved === true;
97
+ } catch {
98
+ // A local "yes" without a durable CAS is not an approval.
99
+ return false;
99
100
  }
100
- return winner.approved;
101
101
  }
102
102
  try {
103
103
  local.cancel?.();
@@ -14,10 +14,9 @@
14
14
  * anchored at/after its own offset — under `pickPersistedTurn`'s
15
15
  * exact-offset matching a stale record from the discarded timeline would
16
16
  * otherwise shadow the new turn and could offer the wrong checkpoint.
17
- * 3. Per-settle persistence: dirty-gated `persistTurnBinding` after each turn
18
- * (a tool-free Q&A turn writes nothing), best-effort — the producer is
19
- * advisory and must NEVER disturb the turn itself, so every method
20
- * swallows its own failures.
17
+ * 3. Per-settle persistence: `persistTurnBinding` after each turn, including
18
+ * tool-free turns. Failures are reported through `onError` and fail closed
19
+ * by default; tests/diagnostics may explicitly request advisory mode.
21
20
  *
22
21
  * I/O goes through `_deps` so tests drive the producer hermetically.
23
22
  */
@@ -37,15 +36,35 @@ export const _deps = {
37
36
  };
38
37
 
39
38
  /**
40
- * @param {{sessionId?: string, nonce?: string}} [opts]
39
+ * @param {{sessionId?: string, nonce?: string, failClosed?:boolean,
40
+ * onError?:(error:Error, phase:string)=>void}} [opts]
41
41
  * @returns {object|null} null when the session cannot persist (no sessionId)
42
42
  */
43
- export function createReplTurnBindingProducer({ sessionId, nonce } = {}) {
43
+ export function createReplTurnBindingProducer({
44
+ sessionId,
45
+ nonce,
46
+ onError,
47
+ failClosed = true,
48
+ } = {}) {
44
49
  if (!sessionId) return null;
45
50
  // Per-process nonce (mirrors the headless runner's per-run nonce): restored
46
51
  // turn ids keep their original nonce, new ids can't collide across resumes.
47
52
  const runNonce = nonce || String(_deps.now());
48
53
  let feed = null;
54
+ let lastError = null;
55
+ const report = (error, phase) => {
56
+ lastError = error instanceof Error ? error : new Error(String(error));
57
+ try {
58
+ onError?.(lastError, phase);
59
+ } catch {
60
+ // Reporting must not replace the underlying recovery warning.
61
+ }
62
+ };
63
+ const rejectOr = (error, phase, fallback) => {
64
+ report(error, phase);
65
+ if (failClosed) throw lastError;
66
+ return fallback;
67
+ };
49
68
  return {
50
69
  /**
51
70
  * Anchor a new interactive turn. Call just AFTER the user message was
@@ -61,8 +80,8 @@ export function createReplTurnBindingProducer({ sessionId, nonce } = {}) {
61
80
  });
62
81
  }
63
82
  return feed.beginTurn(conversationOffset, { supersede: true });
64
- } catch {
65
- return null; // advisory — never disturb the turn
83
+ } catch (error) {
84
+ return rejectOr(error, "load", null);
66
85
  }
67
86
  },
68
87
  /** Fold one agent-loop event into the current turn (fed by agentLoop). */
@@ -70,21 +89,32 @@ export function createReplTurnBindingProducer({ sessionId, nonce } = {}) {
70
89
  if (!feed) return false;
71
90
  try {
72
91
  return feed.handleEvent(event);
73
- } catch {
74
- return false;
92
+ } catch (error) {
93
+ return rejectOr(error, "event", false);
75
94
  }
76
95
  },
77
- /** Persist the table after a settled turn (dirty-gated, best-effort). */
96
+ /** Persist the table after a settled turn (dirty-gated, fail-closed). */
78
97
  persistIfDirty() {
79
98
  if (!feed || !feed.isDirty()) return false;
99
+ let ok = false;
80
100
  try {
81
- const ok = _deps.persistTurnBinding(sessionId, feed.log);
82
- if (ok) feed.clearDirty();
83
- return ok;
84
- } catch {
85
- return false;
101
+ ok = _deps.persistTurnBinding(sessionId, feed.log);
102
+ } catch (error) {
103
+ return rejectOr(error, "persist", false);
104
+ }
105
+ if (!ok) {
106
+ return rejectOr(
107
+ new Error("turn-binding snapshot was not persisted"),
108
+ "persist",
109
+ false,
110
+ );
86
111
  }
112
+ feed.clearDirty();
113
+ lastError = null;
114
+ return true;
87
115
  },
116
+ lastError: () => lastError,
117
+ sessionId: () => sessionId,
88
118
  /** The live table (for tests / future in-process consumers). */
89
119
  log: () => (feed ? feed.log : null),
90
120
  };