chainlesschain 0.163.6 → 0.163.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
  106. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  107. package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
  116. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  117. package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +2 -2
  151. package/src/commands/agent.js +90 -1
  152. package/src/commands/chat.js +11 -2
  153. package/src/commands/compact.js +106 -23
  154. package/src/commands/loop.js +28 -10
  155. package/src/commands/routine.js +1 -1
  156. package/src/commands/scheduler-daemon.js +359 -2
  157. package/src/commands/session-observability.js +305 -0
  158. package/src/commands/session.js +38 -0
  159. package/src/data/changelog.json +14 -1
  160. package/src/gateways/ws/ws-agent-handler.js +439 -5
  161. package/src/harness/jsonl-session-store.js +463 -125
  162. package/src/harness/prompt-compressor.js +86 -13
  163. package/src/harness/provider-backed-compaction.js +100 -27
  164. package/src/lib/advisor-runtime.js +3 -1
  165. package/src/lib/agent-schedule-store.js +414 -4
  166. package/src/lib/agent-team/team-adjudication.js +40 -1
  167. package/src/lib/automation-engine.js +494 -0
  168. package/src/lib/background-agent-supervisor.js +793 -215
  169. package/src/lib/background-command-argv.js +24 -0
  170. package/src/lib/background-interaction-journal.js +361 -52
  171. package/src/lib/background-interaction-resolver.js +32 -0
  172. package/src/lib/background-launch-profile.js +2 -0
  173. package/src/lib/causal-observability.js +2661 -0
  174. package/src/lib/chat-session-admission.js +76 -0
  175. package/src/lib/cost-budget.js +5 -7
  176. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  177. package/src/lib/cowork/debate-review-cli.js +12 -0
  178. package/src/lib/cowork-adapter.js +159 -38
  179. package/src/lib/cowork-cron.js +357 -1
  180. package/src/lib/cowork-task-runner.js +112 -17
  181. package/src/lib/delivery-coordinator.js +142 -0
  182. package/src/lib/direct-model-usage.js +121 -0
  183. package/src/lib/file-lines.js +183 -47
  184. package/src/lib/llm-pricing.js +12 -17
  185. package/src/lib/loop.js +26 -2
  186. package/src/lib/routine-store.js +380 -21
  187. package/src/lib/runtime-usage-ledger.js +182 -0
  188. package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
  189. package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
  190. package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
  191. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
  192. package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
  193. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  194. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  195. package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
  196. package/src/lib/scheduler-kernel/runtime.js +63 -1
  197. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  198. package/src/lib/scheduler-kernel/store.js +2586 -85
  199. package/src/lib/session-host-lease.js +171 -2
  200. package/src/lib/session-host-snapshot.js +14 -3
  201. package/src/lib/session-persistence-failure.js +1 -0
  202. package/src/lib/session-tail.js +179 -34
  203. package/src/lib/session-transcript-structure.js +96 -0
  204. package/src/lib/sub-agent-context.js +328 -22
  205. package/src/lib/ws-agent-handler.js +4 -1
  206. package/src/repl/agent-repl.js +732 -91
  207. package/src/repl/chat-repl.js +5 -1
  208. package/src/runtime/agent-core.js +1272 -207
  209. package/src/runtime/fallback-model.js +5 -0
  210. package/src/runtime/headless-runner.js +324 -82
  211. package/src/runtime/headless-stream.js +232 -26
  212. package/src/runtime/policies/agent-policy.js +4 -0
  213. package/src/workers/background-agent-worker.js +403 -96
  214. package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
  215. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
  216. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
  217. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
  218. package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
@@ -316,3 +316,27 @@ export function stripFirstTurnPromptArgv(
316
316
  }
317
317
  return out;
318
318
  }
319
+
320
+ export const BACKGROUND_EPHEMERAL_UNSUPPORTED_CODE =
321
+ "BACKGROUND_EPHEMERAL_UNSUPPORTED";
322
+
323
+ /**
324
+ * Background sessions are resumable, attachable durable authorities. Allowing
325
+ * an ephemeral child would leave the worker's interaction journal without a
326
+ * canonical session genesis and a later resume would silently change the
327
+ * caller's no-persistence contract. Keep this guard independent of Commander
328
+ * so direct supervisor/profile callers cannot bypass the CLI validation.
329
+ */
330
+ export function assertBackgroundArgvDurable(argv, label = "background agent") {
331
+ for (const token of Array.isArray(argv) ? argv : []) {
332
+ if (token === "--") break;
333
+ if (token === "--ephemeral" || token.startsWith("--ephemeral=")) {
334
+ const error = new Error(
335
+ `${label} cannot use --ephemeral because background sessions require durable session and interaction authority`,
336
+ );
337
+ error.code = BACKGROUND_EPHEMERAL_UNSUPPORTED_CODE;
338
+ throw error;
339
+ }
340
+ }
341
+ return true;
342
+ }
@@ -9,10 +9,12 @@
9
9
 
10
10
  import { createHash } from "node:crypto";
11
11
  import {
12
- appendAuthorityEvent as storeAppendAuthorityEvent,
13
- readEvents as storeReadEvents,
14
- findLatestEvent as storeFindLatestEvent,
12
+ appendAuthorityEventWithVerifiedProjection as storeAppendAuthorityEventWithVerifiedProjection,
13
+ getSessionPresence as storeGetSessionPresence,
14
+ readVerifiedEvents as storeReadVerifiedEvents,
15
+ SESSION_PRESENCE,
15
16
  } from "../harness/jsonl-session-store.js";
17
+ import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
16
18
  import {
17
19
  normalizeInteractionBinding,
18
20
  sameInteractionBinding,
@@ -26,41 +28,35 @@ export const MAX_BACKGROUND_INTERACTION_RECORDS = 256;
26
28
  const TERMINAL_STATUSES = new Set(["resolved", "rejected", "cancelled"]);
27
29
 
28
30
  export const _deps = {
29
- appendEvent: storeAppendAuthorityEvent,
30
- readEvents: storeReadEvents,
31
- findLatestEvent: storeFindLatestEvent,
31
+ appendEventWithVerifiedProjection:
32
+ storeAppendAuthorityEventWithVerifiedProjection,
33
+ getSessionPresence: storeGetSessionPresence,
34
+ readVerifiedEvents: storeReadVerifiedEvents,
32
35
  };
33
36
 
37
+ function absentJournal(backgroundAgentId, options = {}) {
38
+ return new BackgroundInteractionJournal({
39
+ backgroundAgentId,
40
+ now: options.now,
41
+ sessionId: options.sessionId,
42
+ });
43
+ }
44
+
34
45
  function findLatestJournalEvent(sessionId, backgroundAgentId) {
35
46
  const valid = (event) => event?.data?.backgroundAgentId === backgroundAgentId;
36
- if (
37
- typeof _deps.findLatestEvent === "function" &&
38
- _deps.findLatestEvent !== storeFindLatestEvent
39
- ) {
40
- return _deps.findLatestEvent(
41
- sessionId,
42
- BACKGROUND_INTERACTION_JOURNAL_EVENT,
43
- valid,
44
- );
45
- }
46
- if (_deps.readEvents !== storeReadEvents) {
47
- const events = _deps.readEvents(sessionId) || [];
48
- for (let index = events.length - 1; index >= 0; index -= 1) {
49
- const event = events[index];
50
- if (
51
- event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT &&
52
- valid(event)
53
- ) {
54
- return event;
55
- }
47
+ const events = _deps.readVerifiedEvents(sessionId) || [];
48
+ const structure = createSessionTranscriptStructureProjection(sessionId, {
49
+ failFast: true,
50
+ });
51
+ for (const event of events) structure.accept(event);
52
+ structure.finish({ assertValid: true });
53
+ for (let index = events.length - 1; index >= 0; index -= 1) {
54
+ const event = events[index];
55
+ if (event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT && valid(event)) {
56
+ return event;
56
57
  }
57
- return null;
58
58
  }
59
- return storeFindLatestEvent(
60
- sessionId,
61
- BACKGROUND_INTERACTION_JOURNAL_EVENT,
62
- valid,
63
- );
59
+ return null;
64
60
  }
65
61
 
66
62
  function journalError(message, code, details = {}) {
@@ -70,11 +66,95 @@ function journalError(message, code, details = {}) {
70
66
  return error;
71
67
  }
72
68
 
69
+ function assertVerifiedRecoverySession(sessionId) {
70
+ const events = _deps.readVerifiedEvents(sessionId);
71
+ if (!Array.isArray(events) || events[0]?.type !== "session_start") {
72
+ throw journalError(
73
+ `Background interaction recovery requires an existing verified session: ${sessionId}`,
74
+ "INTERACTION_RECOVERY_SESSION_UNAVAILABLE",
75
+ { sessionId },
76
+ );
77
+ }
78
+ return events;
79
+ }
80
+
73
81
  function cloneJson(value) {
74
82
  if (value === undefined) return null;
75
83
  return JSON.parse(JSON.stringify(value));
76
84
  }
77
85
 
86
+ function journalSnapshotFingerprint(journal) {
87
+ return fingerprint(journal.toJSON());
88
+ }
89
+
90
+ function createJournalProjection(sessionId, backgroundAgentId) {
91
+ const structure = createSessionTranscriptStructureProjection(sessionId, {
92
+ failFast: true,
93
+ });
94
+ let latest = null;
95
+ return Object.freeze({
96
+ accept(event) {
97
+ structure.accept(event);
98
+ if (
99
+ event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT &&
100
+ event?.data?.backgroundAgentId === backgroundAgentId
101
+ ) {
102
+ latest = event.data;
103
+ }
104
+ },
105
+ finish() {
106
+ structure.finish({ assertValid: true });
107
+ const journal = latest
108
+ ? BackgroundInteractionJournal.fromJSON(latest, {
109
+ expectedSessionId: sessionId,
110
+ })
111
+ : new BackgroundInteractionJournal({ backgroundAgentId });
112
+ return Object.freeze({
113
+ journal,
114
+ fingerprint: journalSnapshotFingerprint(journal),
115
+ });
116
+ },
117
+ });
118
+ }
119
+
120
+ function assertCompleteRecoveryFallback(
121
+ fallback,
122
+ sessionId,
123
+ backgroundAgentId,
124
+ ) {
125
+ if (fallback == null) return false;
126
+ if (!fallback?.requestId) {
127
+ throw journalError(
128
+ "Legacy pending interaction recovery evidence has no request id",
129
+ "INTERACTION_RECOVERY_FALLBACK_INVALID",
130
+ { sessionId, backgroundAgentId },
131
+ );
132
+ }
133
+ const normalized = normalizeInteractionBinding(fallback.binding);
134
+ const complete =
135
+ normalized.backgroundAgentId !== null &&
136
+ normalized.sessionId !== null &&
137
+ normalized.turnId !== null &&
138
+ normalized.toolUseId !== null &&
139
+ normalized.sequence !== null;
140
+ if (
141
+ !complete ||
142
+ normalized.backgroundAgentId !== backgroundAgentId ||
143
+ normalized.sessionId !== sessionId
144
+ ) {
145
+ throw journalError(
146
+ "Legacy pending interaction recovery evidence is incomplete or belongs to another session",
147
+ "INTERACTION_RECOVERY_FALLBACK_INVALID",
148
+ {
149
+ sessionId,
150
+ backgroundAgentId,
151
+ requestId: String(fallback.requestId),
152
+ },
153
+ );
154
+ }
155
+ return true;
156
+ }
157
+
78
158
  function canonicalize(value) {
79
159
  if (value === undefined) return "__undefined__";
80
160
  if (value === null || typeof value !== "object") return JSON.stringify(value);
@@ -134,6 +214,8 @@ export class BackgroundInteractionJournal {
134
214
  "INTERACTION_JOURNAL_INVALID",
135
215
  );
136
216
  }
217
+ this.sessionId =
218
+ options.sessionId == null ? null : String(options.sessionId);
137
219
  this._now = options.now || Date.now;
138
220
  this.records = [];
139
221
  }
@@ -153,20 +235,88 @@ export class BackgroundInteractionJournal {
153
235
  const journal = new BackgroundInteractionJournal({
154
236
  backgroundAgentId: snapshot.backgroundAgentId,
155
237
  now: options.now,
238
+ sessionId: options.expectedSessionId,
239
+ });
240
+ const seen = new Set();
241
+ journal.records = snapshot.records.map((record) => {
242
+ const requestId = String(record?.requestId || "");
243
+ const binding = normalizeInteractionBinding(record?.binding);
244
+ const payload = cloneJson(record?.payload || {});
245
+ const payloadFingerprint = String(record?.payloadFingerprint || "");
246
+ const status = String(record?.status || "");
247
+ const completeBinding =
248
+ binding.backgroundAgentId === snapshot.backgroundAgentId &&
249
+ (options.expectedSessionId == null ||
250
+ binding.sessionId === options.expectedSessionId) &&
251
+ binding.sessionId !== null &&
252
+ binding.turnId !== null &&
253
+ binding.toolUseId !== null &&
254
+ binding.sequence !== null;
255
+ if (
256
+ !requestId ||
257
+ seen.has(requestId) ||
258
+ !completeBinding ||
259
+ payloadFingerprint !== fingerprint(payload) ||
260
+ (status !== "pending" && !TERMINAL_STATUSES.has(status))
261
+ ) {
262
+ throw journalError(
263
+ "Invalid background interaction journal record",
264
+ "INTERACTION_JOURNAL_CORRUPT",
265
+ { requestId: requestId || null },
266
+ );
267
+ }
268
+ seen.add(requestId);
269
+ const createdAt = Number(record.createdAt);
270
+ const settledAt =
271
+ record.settledAt == null ? null : Number(record.settledAt);
272
+ let settlement = null;
273
+ let settlementFingerprint = null;
274
+ if (status === "pending") {
275
+ if (
276
+ settledAt !== null ||
277
+ record.settlement != null ||
278
+ record.settlementFingerprint != null
279
+ ) {
280
+ throw journalError(
281
+ "Pending background interaction has terminal settlement data",
282
+ "INTERACTION_JOURNAL_CORRUPT",
283
+ { requestId },
284
+ );
285
+ }
286
+ } else {
287
+ settlement = normalizeSettlement(record.settlement);
288
+ settlementFingerprint = String(record.settlementFingerprint || "");
289
+ if (
290
+ settlement.status !== status ||
291
+ !Number.isFinite(settledAt) ||
292
+ settlementFingerprint !== fingerprint(settlement)
293
+ ) {
294
+ throw journalError(
295
+ "Terminal background interaction settlement is inconsistent",
296
+ "INTERACTION_JOURNAL_CORRUPT",
297
+ { requestId },
298
+ );
299
+ }
300
+ }
301
+ if (!Number.isFinite(createdAt) || createdAt <= 0) {
302
+ throw journalError(
303
+ "Background interaction timestamp is invalid",
304
+ "INTERACTION_JOURNAL_CORRUPT",
305
+ { requestId },
306
+ );
307
+ }
308
+ return {
309
+ requestId,
310
+ binding,
311
+ payload,
312
+ payloadFingerprint,
313
+ status,
314
+ createdAt,
315
+ settledAt,
316
+ settlement,
317
+ settlementFingerprint,
318
+ };
156
319
  });
157
- journal.records = snapshot.records.map((record) => ({
158
- requestId: String(record.requestId || ""),
159
- binding: normalizeInteractionBinding(record.binding),
160
- payload: cloneJson(record.payload || {}),
161
- payloadFingerprint: String(record.payloadFingerprint || ""),
162
- status: String(record.status || "pending"),
163
- createdAt: Number(record.createdAt) || 0,
164
- settledAt: record.settledAt == null ? null : Number(record.settledAt),
165
- settlement: record.settlement ? cloneJson(record.settlement) : null,
166
- settlementFingerprint: record.settlementFingerprint
167
- ? String(record.settlementFingerprint)
168
- : null,
169
- }));
170
320
  return journal;
171
321
  }
172
322
 
@@ -192,7 +342,16 @@ export class BackgroundInteractionJournal {
192
342
  recordPending({ requestId, binding, payload = {}, createdAt } = {}) {
193
343
  const id = String(requestId || "");
194
344
  const normalizedBinding = normalizeInteractionBinding(binding);
195
- if (!id || normalizedBinding.sequence === null) {
345
+ if (
346
+ !id ||
347
+ normalizedBinding.backgroundAgentId !== this.backgroundAgentId ||
348
+ (this.sessionId !== null &&
349
+ normalizedBinding.sessionId !== this.sessionId) ||
350
+ normalizedBinding.sessionId === null ||
351
+ normalizedBinding.turnId === null ||
352
+ normalizedBinding.toolUseId === null ||
353
+ normalizedBinding.sequence === null
354
+ ) {
196
355
  throw journalError(
197
356
  "A requestId and complete interaction binding are required",
198
357
  "INTERACTION_REQUEST_INVALID",
@@ -311,6 +470,8 @@ export class BackgroundInteractionPersistenceError extends Error {
311
470
  : "INTERACTION_JOURNAL_PERSIST_FAILED";
312
471
  this.operation = operation;
313
472
  this.sessionId = sessionId;
473
+ if (cause?.commitState) this.commitState = cause.commitState;
474
+ if (cause?.code) this.causeCode = cause.code;
314
475
  }
315
476
  }
316
477
 
@@ -324,10 +485,44 @@ export function persistBackgroundInteractionJournal(
324
485
  if (!sessionId || !(journal instanceof BackgroundInteractionJournal)) {
325
486
  throw new TypeError("sessionId and interaction journal are required");
326
487
  }
327
- _deps.appendEvent(
488
+ // Re-parse the complete candidate at the final write boundary. A mutate
489
+ // callback can add records after the source snapshot was validated; those
490
+ // records must still be bound to this exact session and agent.
491
+ BackgroundInteractionJournal.fromJSON(journal.toJSON(), {
492
+ expectedSessionId: sessionId,
493
+ });
494
+ const expectedFingerprint =
495
+ options.expectedFingerprint ??
496
+ journalSnapshotFingerprint(
497
+ new BackgroundInteractionJournal({
498
+ backgroundAgentId: journal.backgroundAgentId,
499
+ }),
500
+ );
501
+ _deps.appendEventWithVerifiedProjection(
328
502
  sessionId,
329
503
  BACKGROUND_INTERACTION_JOURNAL_EVENT,
330
504
  journal.toJSON(),
505
+ {
506
+ createProjection: () =>
507
+ createJournalProjection(sessionId, journal.backgroundAgentId),
508
+ validateProjection(projected) {
509
+ if (
510
+ expectedFingerprint !== undefined &&
511
+ projected.fingerprint !== expectedFingerprint
512
+ ) {
513
+ throw journalError(
514
+ "Background interaction journal changed before the mutation could commit",
515
+ "INTERACTION_JOURNAL_STALE",
516
+ {
517
+ sessionId,
518
+ backgroundAgentId: journal.backgroundAgentId,
519
+ expectedFingerprint,
520
+ actualFingerprint: projected.fingerprint,
521
+ },
522
+ );
523
+ }
524
+ },
525
+ },
331
526
  );
332
527
  return true;
333
528
  } catch (error) {
@@ -350,19 +545,28 @@ export function loadBackgroundInteractionJournal(
350
545
  if (!sessionId || !backgroundAgentId) {
351
546
  throw new TypeError("sessionId and backgroundAgentId are required");
352
547
  }
548
+ const presence = _deps.getSessionPresence(sessionId);
549
+ if (presence === SESSION_PRESENCE.ABSENT && options.allowAbsent === true) {
550
+ return absentJournal(backgroundAgentId, { ...options, sessionId });
551
+ }
353
552
  const event = findLatestJournalEvent(sessionId, backgroundAgentId);
354
553
  if (event) {
355
- return BackgroundInteractionJournal.fromJSON(event.data, options);
554
+ return BackgroundInteractionJournal.fromJSON(event.data, {
555
+ ...options,
556
+ expectedSessionId: sessionId,
557
+ });
356
558
  }
357
559
  return new BackgroundInteractionJournal({
358
560
  backgroundAgentId,
359
561
  now: options.now,
562
+ sessionId,
360
563
  });
361
564
  } catch (error) {
362
565
  if (!failIfUnavailable) {
363
566
  return new BackgroundInteractionJournal({
364
567
  backgroundAgentId,
365
568
  now: options.now,
569
+ sessionId,
366
570
  });
367
571
  }
368
572
  throw new BackgroundInteractionPersistenceError(
@@ -383,16 +587,84 @@ export function updateBackgroundInteractionJournal(
383
587
  mutate,
384
588
  options = {},
385
589
  ) {
590
+ const expectedFingerprint = journalSnapshotFingerprint(journal);
386
591
  const draft = BackgroundInteractionJournal.fromJSON(journal.toJSON(), {
387
592
  now: options.now || journal._now,
593
+ expectedSessionId: sessionId,
388
594
  });
389
595
  const result = mutate(draft);
390
596
  if (!options.persistIf || options.persistIf(result) !== false) {
391
- persistBackgroundInteractionJournal(sessionId, draft, options);
597
+ try {
598
+ persistBackgroundInteractionJournal(sessionId, draft, {
599
+ ...options,
600
+ expectedFingerprint,
601
+ });
602
+ } catch (error) {
603
+ // Authority append can durably reach the transcript before its metadata
604
+ // settlement reports an ambiguous result. Only an exact verified
605
+ // readback of the intended full snapshot adjudicates that ambiguity as
606
+ // committed; every other state remains fail closed.
607
+ if (error?.commitState === "unknown") {
608
+ try {
609
+ const readback = options.readbackJournal;
610
+ const verified =
611
+ typeof readback === "function"
612
+ ? readback(sessionId, journal.backgroundAgentId, options)
613
+ : loadBackgroundInteractionJournal(
614
+ sessionId,
615
+ journal.backgroundAgentId,
616
+ options,
617
+ );
618
+ if (
619
+ journalSnapshotFingerprint(verified) ===
620
+ journalSnapshotFingerprint(draft)
621
+ ) {
622
+ return { journal: verified, result, adjudicated: true };
623
+ }
624
+ } catch {
625
+ // Keep the original ambiguous persistence error and its commitState.
626
+ }
627
+ }
628
+ throw error;
629
+ }
392
630
  }
393
631
  return { journal: draft, result };
394
632
  }
395
633
 
634
+ /**
635
+ * Classify a transport response that arrived after its live child request was
636
+ * removed. A still-pending row belongs to recovery authority and must never be
637
+ * settled by a late client. Only an exact replay of an already-terminal result
638
+ * is acknowledged, without appending another journal snapshot.
639
+ */
640
+ export function classifyLateBackgroundInteractionSettlement(
641
+ journal,
642
+ requestId,
643
+ binding,
644
+ outcome,
645
+ ) {
646
+ if (!(journal instanceof BackgroundInteractionJournal)) {
647
+ throw new TypeError("background interaction journal is required");
648
+ }
649
+ const record = journal.get(requestId);
650
+ if (!record || record.status === "pending") {
651
+ return Object.freeze({
652
+ accepted: false,
653
+ duplicate: false,
654
+ delivered: false,
655
+ reason: record
656
+ ? "interaction_no_live_child"
657
+ : "interaction_request_unknown",
658
+ });
659
+ }
660
+ const duplicate = journal.settle(requestId, binding, outcome);
661
+ return Object.freeze({
662
+ accepted: true,
663
+ duplicate: duplicate.applied === false,
664
+ delivered: false,
665
+ });
666
+ }
667
+
396
668
  /**
397
669
  * Recovery entry point used when a worker is observed lost or starts again.
398
670
  * An optional state-file fallback lets pre-journal pendingQuestion records be
@@ -403,19 +675,56 @@ export function rejectPendingBackgroundInteractions(
403
675
  backgroundAgentId,
404
676
  options = {},
405
677
  ) {
678
+ const presence = _deps.getSessionPresence(sessionId);
679
+ if (presence === SESSION_PRESENCE.ABSENT && options.fallbackRequest == null) {
680
+ return {
681
+ journal: absentJournal(backgroundAgentId, { ...options, sessionId }),
682
+ rejected: [],
683
+ changed: false,
684
+ };
685
+ }
686
+ // Normalize absent/missing/tombstoned recovery to a stable domain error
687
+ // before loading a canonical journal. Structurally corrupt live transcripts
688
+ // still surface as read failures and therefore block cleanup fail closed.
689
+ if (options.fallbackRequest != null) assertVerifiedRecoverySession(sessionId);
406
690
  let journal = loadBackgroundInteractionJournal(
407
691
  sessionId,
408
692
  backgroundAgentId,
409
693
  options,
410
694
  );
411
695
  const fallback = options.fallbackRequest;
696
+ const hasValidFallback = assertCompleteRecoveryFallback(
697
+ fallback,
698
+ sessionId,
699
+ backgroundAgentId,
700
+ );
412
701
  const hasFallback =
413
- fallback?.requestId &&
414
- normalizeInteractionBinding(fallback.binding).sequence !== null &&
415
- !journal.get(fallback.requestId);
702
+ hasValidFallback && !journal.get(String(fallback.requestId));
703
+ if (hasValidFallback && !hasFallback) {
704
+ const existing = journal.get(String(fallback.requestId));
705
+ const normalizedBinding = normalizeInteractionBinding(fallback.binding);
706
+ // The supervisor state intentionally stores a compact display projection
707
+ // of the question, not the complete canonical IPC payload (for example it
708
+ // may omit defaultValue/policyDigest). Once the exact request id and full
709
+ // binding match, the verified journal remains payload authority.
710
+ if (!sameInteractionBinding(existing.binding, normalizedBinding)) {
711
+ throw journalError(
712
+ "Legacy pending interaction recovery evidence conflicts with the canonical journal",
713
+ "INTERACTION_RECOVERY_FALLBACK_INVALID",
714
+ {
715
+ sessionId,
716
+ backgroundAgentId,
717
+ requestId: String(fallback.requestId),
718
+ },
719
+ );
720
+ }
721
+ }
416
722
  if (!journal.pending().length && !hasFallback) {
417
723
  return { journal, rejected: [], changed: false };
418
724
  }
725
+ // Give missing/tombstoned recovery a stable domain error before the atomic
726
+ // verified projection handles the normal append path.
727
+ assertVerifiedRecoverySession(sessionId);
419
728
  const mutation = updateBackgroundInteractionJournal(
420
729
  sessionId,
421
730
  journal,
@@ -80,6 +80,8 @@ function sendIpc(target, message, onError = () => {}) {
80
80
  * @param {string} [options.backgroundAgentId]
81
81
  * @param {string|null} [options.sessionId]
82
82
  * @param {string|null} [options.turnId]
83
+ * @param {object|null} [options.sessionHostWriteDelegation]
84
+ * @param {string|null} [options.turnIpcToken]
83
85
  * @param {number} [options.defaultTimeoutMs]
84
86
  * @param {()=>string} [options.idFactory]
85
87
  */
@@ -89,6 +91,11 @@ export function createBackgroundInteractionClient(options = {}) {
89
91
  options.backgroundAgentId ?? processLike?.env?.CC_BACKGROUND_AGENT_ID;
90
92
  const sessionId = options.sessionId ?? null;
91
93
  const turnId = options.turnId ?? null;
94
+ const sessionHostWriteDelegation = options.sessionHostWriteDelegation ?? null;
95
+ const turnIpcToken =
96
+ options.turnIpcToken ??
97
+ asNullableString(processLike?.env?.CC_BACKGROUND_TURN_IPC_NONCE);
98
+ const senderPid = Number(processLike?.pid ?? process.pid);
92
99
  const defaultTimeoutMs =
93
100
  Number.isFinite(options.defaultTimeoutMs) && options.defaultTimeoutMs > 0
94
101
  ? options.defaultTimeoutMs
@@ -205,6 +212,12 @@ export function createBackgroundInteractionClient(options = {}) {
205
212
  protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
206
213
  requestId,
207
214
  binding,
215
+ sessionHostWriteDelegation,
216
+ senderPid:
217
+ Number.isSafeInteger(senderPid) && senderPid > 0
218
+ ? senderPid
219
+ : null,
220
+ turnIpcToken,
208
221
  payload: {
209
222
  kind: payload.kind || "question",
210
223
  question: String(payload.question || payload.prompt || ""),
@@ -261,6 +274,9 @@ export function createBackgroundInteractionClient(options = {}) {
261
274
  * @param {object} [options]
262
275
  * @param {string|null} [options.backgroundAgentId]
263
276
  * @param {string|null} [options.sessionId]
277
+ * @param {string|null} [options.turnIpcToken]
278
+ * @param {boolean} [options.requireSenderPid]
279
+ * @param {boolean} [options.requireDelegationOwnerPid]
264
280
  * @returns {()=>void} detach function
265
281
  */
266
282
  export function attachInteractionRequestHandler(
@@ -324,6 +340,8 @@ export function attachInteractionRequestHandler(
324
340
  const binding = normalizeInteractionBinding(message.binding);
325
341
  const expectedAgentId = asNullableString(options.backgroundAgentId);
326
342
  const expectedSessionId = asNullableString(options.sessionId);
343
+ const expectedTurnIpcToken = asNullableString(options.turnIpcToken);
344
+ const senderPid = Number(message.senderPid);
327
345
  if (
328
346
  (expectedAgentId && binding.backgroundAgentId !== expectedAgentId) ||
329
347
  (expectedSessionId && binding.sessionId !== expectedSessionId)
@@ -335,6 +353,20 @@ export function attachInteractionRequestHandler(
335
353
  );
336
354
  return;
337
355
  }
356
+ if (
357
+ (expectedTurnIpcToken && message.turnIpcToken !== expectedTurnIpcToken) ||
358
+ (options.requireSenderPid === true &&
359
+ (!Number.isSafeInteger(senderPid) || senderPid < 1)) ||
360
+ (options.requireDelegationOwnerPid === true &&
361
+ Number(message.sessionHostWriteDelegation?.ownerPid) !== senderPid)
362
+ ) {
363
+ rejectRequest(
364
+ message,
365
+ "INTERACTION_CHILD_IDENTITY_MISMATCH",
366
+ "Background interaction did not originate from this turn child",
367
+ );
368
+ return;
369
+ }
338
370
 
339
371
  const prior = completed.get(message.requestId);
340
372
  if (prior) {
@@ -10,6 +10,7 @@ import { createHash } from "node:crypto";
10
10
  import { homedir } from "node:os";
11
11
  import { dirname, join, parse, resolve } from "node:path";
12
12
  import { getConfigPath } from "./paths.js";
13
+ import { assertBackgroundArgvDurable } from "./background-command-argv.js";
13
14
 
14
15
  export const BACKGROUND_LAUNCH_PROFILE_VERSION = 1;
15
16
 
@@ -314,6 +315,7 @@ export function captureBackgroundLaunchProfile({
314
315
  worktree = null,
315
316
  governance = null,
316
317
  } = {}) {
318
+ assertBackgroundArgvDurable(argv, "background launch profile");
317
319
  const profile = emptyParsedProfile(cwd, worktree, governance);
318
320
  const omitted = new Set();
319
321
  const addDirs = [];