chainlesschain 0.162.154 → 0.162.155

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 (198) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CIpoXyVI.js → AIOps-Cth_zZeA.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Ch_QXIPk.js → ActionButton-zfwA3Oxn.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CvaBYShv.js → Analytics-FeKc4aU2.js} +2 -2
  7. package/src/assets/web-panel/assets/AppLayout-C6WIrDZk.js +9 -0
  8. package/src/assets/web-panel/assets/{Audit-Bng8eHCW.js → Audit-C2gRq5Qi.js} +1 -1
  9. package/src/assets/web-panel/assets/BackgroundAgents-Dq2340Kl.css +1 -0
  10. package/src/assets/web-panel/assets/BackgroundAgents-TeTIFiW-.js +1 -0
  11. package/src/assets/web-panel/assets/{Backup-DvBs8-lm.js → Backup-CLJk3k6i.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-DKwajm0g.js → BaseInput-5ms537NY.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-BoGpZ3uO.js → Chat-4Nr5U9PJ.js} +5 -5
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-BFEnA8LN.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-BMJEmxFQ.js → Checkbox-B2Bca5ev.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Bsoag5CV.js → Codegen-D6Uysdbv.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-CDjH9-sp.js → Col-B3XQ8xc7.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-Cg2QHPQ6.js → Community-CqwKtoPz.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BU2BaqP-.js → Compact-BZZINTus.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-D6ZYMCqZ.js → Compliance-Dr3BXkWA.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DaZ8VW-I.js → Cowork-rx_x9Wqc.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-DQY1RH9v.js → Cron-BpZr7rHc.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BVR28vk9.js → Crosschain-CeV5EYVg.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-N44KvTP-.js → DID-fe2Qaowz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Bjhlx6wS.js → Dashboard-Fh_EdWVa.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-CpLYxpy1.js → Dropdown-N1PIwWYf.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-srEITSPI.js → EmailListRenderer-B6vK93Ed.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-juOC_fX2.js → FamilyGuardDashboard-e3iUZhAg.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-nRaQtaXW.js → Federation-uInc9YcN.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DflhTj09.js → FormItemContext-DmQZSX0z.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DulI_3NI.js → GenericCardRenderer-CV_Z8OAo.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BxOPefXn.js → Git-jXAhNN8d.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-C6G_Wnwi.js → Governance-Blw1esBB.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-BnFYa2BF.js → Inference-CoK8Ia0Z.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-TqQLEabm.js → KnowledgeGraph-5xrglMTK.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BH4AVh7s.js → Logs-BGIG9798.js} +2 -2
  37. package/src/assets/web-panel/assets/{Marketplace-DoWLTaEE.js → Marketplace-DFqhJMuY.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D6yC7OG_.js → McpTools-7U8tEhwK.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DLCeHj21.js → Memory-BrvboPLE.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BZBE0GRl.js → MobileBridge-nzcd7d1H.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DJA15G1J.js → MobileProjects-DETVNKa7.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CyTKJDHn.js → Mtc-SZcCYec3.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-DZKjgEVc.js → MtcAudit-Cx_d3N17.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-2rMPNhSO.js → Multisig-DtcmSUo-.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-VeMk4Fx-.js → NLProgramming-Dgx2QHl-.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-jr2GHMhh.js → Notes-oHTGEHoJ.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-WN9VZyFb.js → NotificationSettings-qKD-IlOl.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CjNO-e9T.js → OrderTableRenderer-CiU4Sy1J.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-DdjVHuzT.js → Organization-BBNXnltP.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-l8vBsGGq.js → Overflow-Df2fDbvT.js} +1 -1
  51. package/src/assets/web-panel/assets/{OverrideContext-BlgL9440.js → OverrideContext-C0w0P4A3.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-CVQDHHwq.js → P2P-DUHj6zjO.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CTsNh8qE.js → PdhVaultBrowser-B2nWgdoV.js} +4 -4
  54. package/src/assets/web-panel/assets/{Permissions-DLW87uyK.js → Permissions-CiW0afny.js} +3 -3
  55. package/src/assets/web-panel/assets/{PersonalDataHub-N8OvASTx.js → PersonalDataHub-cc1mHU_D.js} +2 -2
  56. package/src/assets/web-panel/assets/{Pipeline-BPZmdlz2.js → Pipeline-B93zTapU.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-MFZw_GNh.js → Privacy-Db1jXWfd.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-zW0pEsfU.js → ProjectInit-DFoZ1h9U.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CppVHr_o.js → ProjectSettings-BkkUnmYs.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BTglkJO2.js → Projects-Xa779XLC.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-CPHZ6JKT.js → Providers-DOF01IEN.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-DO4klNZ2.js → QrScannerModal-TohRsKd0.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-B37TTqUa.js → QuickAsk-DjLcr1ZU.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-n7KIfK7y.js → Recommend--Ism6bMx.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-SjVoS9mm.js → RemoteSession-B8CL3YHM.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-Dt6RTeHi.js → Reputation-CaRxB3Ep.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Dty6jz5V.js → Row-BioxZBBH.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-DDLhqw5j.js → RssFeed-B2LipwH3.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DrYbXi-W.js → Search-BpjLBV8u.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Fc9Z6fbB.js → Security-TibHuJWP.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-BAhPgjSM.js → Services-DtkuWseW.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-8JF7EPfE.js → Skeleton-BIcPQae6.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-DoTWIQOX.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-CSvjcATc.js → Sla-uUSU185m.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-Demx7Gnx.js → SpeechSettings-B7Bgc9mk.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CKLnRWJr.js → SyncSettings-K-XrJ2s9.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-mhITRxjv.js → Tasks-B3hEXNcI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-AjQI5rxD.js → Templates-CaFnyGDK.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-dbtEh1yR.js → Tenant-C2X0PtEC.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-DX-9jjfk.js → Terminal-CKU5yVsT.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C11EDZRt.js → TimelineRenderer-8trZeYDC.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-GiA9fiZn.js → Tokens-BUEEe9US.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BPcnclJn.js → Trigger-COU97I7Y.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-C-DVY8nq.js → Trust-DNWQChaP.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DxodSwME.js → UkeySign-DWX_VtCp.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D_WV0O8O.js → VideoEditing-D9eIDhsp.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-DSeNlVSU.js → Wallet-BOnCCgIL.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-Bwuuvpz1.js → WebAuthn-DN4dD-W8.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-8M96EU1N.js → WorkflowEditor-ClvDb_jX.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-D5S6SmLm.js → chat-DIxQ_Eeb.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-DVfhbsdP.js → collapseMotion-CqM1QOwM.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-5fbnGkAP.js → colors-BOX3WKcP.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DaSuuMlW.js → compact-item-Bny82VDi.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BNOHcXAF.js → createContext-Cnoy3enS.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-Dr9dzlQ5.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-CL0F2SnX.js → echarts-C61Vn3mP.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-DONwZUra.js → hasIn-BJ7fkx3F.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-BsDmGpcT.js → icons-DEmcnxBf.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-B_yDD9Lm.js → index--CcGiwAI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-5_fj5gVd.js → index--Xb2lq6E.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-uR5KTNZA.js → index-2N9Y1cLx.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-MOgO5Yvx.js → index-7GLXvG8p.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CLXf-8ek.js → index-B1-h2QrC.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D7gs_3Ck.js → index-B6WoW8DJ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CztESwB4.js → index-B7Fah_BX.js} +1 -1
  106. package/src/assets/web-panel/assets/index-B92M3t6B.js +1 -0
  107. package/src/assets/web-panel/assets/{index-BEO5f7Vf.js → index-BDkN6YOI.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-_6S41ikV.js → index-BJ8-oLHI.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CrkCFohi.js → index-BM0mQhVv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BrAJSu-v.js → index-BP7HFsAK.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BcqOK7Lv.js → index-BWPey0Uf.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BLzMuz38.js → index-BbZynivw.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DbdQBn4n.js → index-BcmXbIwv.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-KwV9g_cf.js → index-Bk5yST66.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-ClGqO-2X.js → index-Bvheiep2.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Dd7xwGcD.js → index-C--PPqwi.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DP2-3A_j.js → index-C1nHEOFE.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CAYmc5Nh.js → index-CEPTiBWy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-LRIt-AFl.js → index-CEq5Vh0i.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dm5A-MN5.js → index-CN6emwbq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cf2OCa8q.js → index-CQ-xLCVb.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DHx2lhHW.js → index-CW6n-y1L.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DM3Ug61e.js → index-CZ6CHiCQ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BJp4kRjw.js → index-CZCTan8Q.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-PsMErAB1.js → index-CnjtAKdw.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CEzd5m5a.js → index-D2qNdrNW.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-B4L3gsNA.js → index-D9QEc7Tq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BcWMZqb4.js → index-DBhkJqVF.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CSfYJqXt.js → index-DSRIjAWO.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CmUOjyL6.js → index-DxX8hhmL.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-RkInlwV0.js → index-Kio1ekYg.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CTZeOnzO.js → index-OaNFXV8N.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BK2N5yVW.js → index-Ty4w18mR.js} +4 -4
  134. package/src/assets/web-panel/assets/{index-Cn5LtyIH.js → index-ceH9DsAi.js} +1 -1
  135. package/src/assets/web-panel/assets/index-d2UcGpK-.js +1 -0
  136. package/src/assets/web-panel/assets/{index-xo2f4DZR.js → index-qOBKrw-g.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-vYtprlNs.js → index-rn6xd12s.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CxwLynp9.js → initDefaultProps-BmxMBuUZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-XbPvGJbW.js → motion-DnXNwo2k.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D1u5pcgS.js → move-WnIJwmZN.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDxhTBMH.js → mtc-parser-DAto-c2w.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-EtIFGg5k.js → omit-BtvW9W0H.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-D33sWb0l.js → pickAttrs-DFqI9-_C.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BwFRzaGo.js → placementArrow-Dax6wOK3.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CBk2WqvQ.js → responsiveObserve-BUFWWC83.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-B52HtrKn.js → slide-DyARyuSz.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-D4qxqWnH.js → statusUtils-BZSDugpi.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-BxaCFlOL.js → styleChecker-B7o_K-4R.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-Dg7Bg8VG.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-C6VAU1_6.js → useFs-DQYXo5Rk.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Dc9sO2d7.js → useMergedState-DXbJa_zE.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-rEG6oXG8.js → usePersonalDataHub-E8NBAnkq.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-CAeFRzPf.js → useRefs-BrCfMLMc.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-LwExUYDO.js → useState-CnxtbLoC.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BMxUs6UX.js → vendor-Bn1JzzAn.js} +1 -1
  156. package/src/assets/web-panel/assets/{vnode-DOTafkEi.js → vnode-DD71_w1b.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-BuwwN5K2.js → zoom-zc9EMd3q.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/command-manifest.json +36 -1
  160. package/src/commands/agent.js +23 -2
  161. package/src/commands/agents.js +62 -1
  162. package/src/commands/auto-mode.js +87 -0
  163. package/src/commands/background-session.js +499 -0
  164. package/src/commands/command.js +4 -1
  165. package/src/commands/permissions.js +49 -0
  166. package/src/commands/remote-control.js +407 -0
  167. package/src/data/changelog.json +7 -2
  168. package/src/gateways/ws/background-agent-protocol.js +337 -0
  169. package/src/gateways/ws/message-dispatcher.js +10 -0
  170. package/src/gateways/ws/remote-session-protocol.js +102 -1
  171. package/src/gateways/ws/ws-server.js +61 -0
  172. package/src/harness/mcp-client.js +8 -0
  173. package/src/index.js +10 -0
  174. package/src/lib/auto-mode-config.js +396 -0
  175. package/src/lib/background-agent-supervisor.js +202 -17
  176. package/src/lib/background-session-transport.js +315 -0
  177. package/src/lib/permission-denial-store.js +159 -0
  178. package/src/lib/remote-approval-bridge.js +360 -0
  179. package/src/lib/remote-control.js +270 -0
  180. package/src/lib/repl-denials.js +26 -0
  181. package/src/lib/shell-approval.js +4 -0
  182. package/src/lib/ws-rpc-client.js +150 -0
  183. package/src/repl/agent-repl.js +208 -16
  184. package/src/repl/permission-tier.js +31 -0
  185. package/src/runtime/agent-core.js +57 -0
  186. package/src/runtime/headless-runner.js +126 -8
  187. package/src/runtime/headless-stream.js +87 -1
  188. package/src/runtime/mcp-config.js +11 -0
  189. package/src/runtime/mcp-tool-search.js +0 -0
  190. package/src/runtime/policies/agent-policy.js +17 -0
  191. package/src/workers/background-agent-worker.js +218 -37
  192. package/src/assets/web-panel/assets/AppLayout-B6Q6olcr.js +0 -9
  193. package/src/assets/web-panel/assets/ChatBubbleRenderer-mDQl_PXO.js +0 -1
  194. package/src/assets/web-panel/assets/Skills-DL39dY0t.js +0 -1
  195. package/src/assets/web-panel/assets/devWarning-K_woVElh.js +0 -1
  196. package/src/assets/web-panel/assets/index-3ILvaBpo.js +0 -1
  197. package/src/assets/web-panel/assets/index-JpB-FoZq.js +0 -1
  198. package/src/assets/web-panel/assets/useFlexGapSupport-B1lWlqXN.js +0 -1
@@ -15,9 +15,11 @@
15
15
  * Permission model: headless cannot show an interactive approval prompt, so the
16
16
  * default is fail-closed (deny MEDIUM/HIGH-risk shell). --permission-mode opts
17
17
  * into a looser tier:
18
- * - (default) / plan → STRICT + deny-confirmer (plan also restricts tools)
19
- * - acceptEdits → TRUSTED + deny-confirmer (HIGH-risk shell still denied)
20
- * - bypassPermissions → AUTOPILOT (everything allowed, no confirm)
18
+ * - (default) / manual / dontAsk / plan → STRICT + deny-confirmer
19
+ * (plan also restricts tools)
20
+ * - auto / acceptEdits → TRUSTED + deny-confirmer
21
+ * (HIGH-risk shell still denied)
22
+ * - bypassPermissions → AUTOPILOT (everything allowed)
21
23
  */
22
24
 
23
25
  import { bootstrap } from "./bootstrap.js";
@@ -33,6 +35,7 @@ import {
33
35
  resolvePermissionPromptTool,
34
36
  makePermissionPromptConfirmer,
35
37
  } from "./mcp-config.js";
38
+ import { maybeApplyToolSearch } from "./mcp-tool-search.js";
36
39
  import { IterationBudget } from "../lib/iteration-budget.js";
37
40
  import {
38
41
  startSession as jsonlStartSession,
@@ -68,6 +71,9 @@ export const READ_ONLY_TOOLS = Object.freeze([
68
71
 
69
72
  const VALID_PERMISSION_MODES = Object.freeze([
70
73
  "default",
74
+ "manual",
75
+ "auto",
76
+ "dontAsk",
71
77
  "plan",
72
78
  "acceptEdits",
73
79
  "bypassPermissions",
@@ -81,19 +87,30 @@ export { installPipeSafety } from "./pipe-safety.js";
81
87
  import { installPipeSafety } from "./pipe-safety.js";
82
88
 
83
89
  /**
84
- * Resolve a --permission-mode string into the session-policy tier + a
85
- * non-interactive confirmer + whether to clamp tools to the read-only set.
90
+ * Normalize a public --permission-mode spelling to the canonical internal mode.
86
91
  *
87
- * @param {string} mode
88
- * @returns {{ sessionPolicy: string, confirmer: (ctx:any)=>Promise<boolean>, readOnly: boolean }}
92
+ * @param {string} [mode]
93
+ * @returns {string}
89
94
  */
90
- export function resolvePermissionMode(mode = "default") {
95
+ export function normalizePermissionMode(mode = "default") {
91
96
  const m = mode || "default";
92
97
  if (!VALID_PERMISSION_MODES.includes(m)) {
93
98
  throw new Error(
94
99
  `Invalid --permission-mode "${m}". Expected one of: ${VALID_PERMISSION_MODES.join(", ")}`,
95
100
  );
96
101
  }
102
+ return m;
103
+ }
104
+
105
+ /**
106
+ * Resolve a --permission-mode string into the session-policy tier + a
107
+ * non-interactive confirmer + whether to clamp tools to the read-only set.
108
+ *
109
+ * @param {string} mode
110
+ * @returns {{ sessionPolicy: string, confirmer: (ctx:any)=>Promise<boolean>, readOnly: boolean, allowInteractiveApprovals: boolean }}
111
+ */
112
+ export function resolvePermissionMode(mode = "default") {
113
+ const m = normalizePermissionMode(mode);
97
114
  // Headless can't ask a human — deny when a confirm would be required.
98
115
  const denyConfirmer = async () => false;
99
116
  const allowConfirmer = async () => true;
@@ -103,25 +120,38 @@ export function resolvePermissionMode(mode = "default") {
103
120
  sessionPolicy: "autopilot",
104
121
  confirmer: allowConfirmer,
105
122
  readOnly: false,
123
+ allowInteractiveApprovals: false,
106
124
  };
125
+ case "auto":
107
126
  case "acceptEdits":
108
127
  return {
109
128
  sessionPolicy: "trusted",
110
129
  confirmer: denyConfirmer,
111
130
  readOnly: false,
131
+ allowInteractiveApprovals: true,
112
132
  };
113
133
  case "plan":
114
134
  return {
115
135
  sessionPolicy: "strict",
116
136
  confirmer: denyConfirmer,
117
137
  readOnly: true,
138
+ allowInteractiveApprovals: true,
139
+ };
140
+ case "dontAsk":
141
+ return {
142
+ sessionPolicy: "strict",
143
+ confirmer: denyConfirmer,
144
+ readOnly: false,
145
+ allowInteractiveApprovals: false,
118
146
  };
147
+ case "manual":
119
148
  case "default":
120
149
  default:
121
150
  return {
122
151
  sessionPolicy: "strict",
123
152
  confirmer: denyConfirmer,
124
153
  readOnly: false,
154
+ allowInteractiveApprovals: true,
125
155
  };
126
156
  }
127
157
  }
@@ -610,6 +640,28 @@ export async function runAgentHeadless(options = {}, deps = {}) {
610
640
  let approvalGate = null;
611
641
  try {
612
642
  approvalGate = await getApprovalGate();
643
+ if (approvalGate && (options.permissionMode || "default") === "auto") {
644
+ // autoMode.decisions: user-configured riskLevel → allow/ask/deny
645
+ // classifier. Only wrap when settings actually customize the map so the
646
+ // unconfigured auto path keeps the byte-identical trusted-tier mapping.
647
+ try {
648
+ const {
649
+ loadAutoModeConfig,
650
+ resolveAutoModeDecisions,
651
+ createAutoModeApprovalGate,
652
+ } = await import("../lib/auto-mode-config.js");
653
+ const autoConfig = loadAutoModeConfig({
654
+ cwd,
655
+ settingsFile: options.settingsFile,
656
+ });
657
+ const resolved = resolveAutoModeDecisions(autoConfig.effective);
658
+ if (resolved.customized) {
659
+ approvalGate = createAutoModeApprovalGate(approvalGate, resolved);
660
+ }
661
+ } catch {
662
+ // fail to the default trusted mapping — never block the run
663
+ }
664
+ }
613
665
  if (approvalGate) {
614
666
  if (typeof approvalGate.setSessionPolicy === "function") {
615
667
  approvalGate.setSessionPolicy(sessionId, perm.sessionPolicy);
@@ -800,6 +852,22 @@ export async function runAgentHeadless(options = {}, deps = {}) {
800
852
  writeErr(` mcp: ${c.server} (${c.tools} tools)\n`);
801
853
  }
802
854
  }
855
+ // MCP tool search (context scaling): when the tool schemas would eat a
856
+ // significant share of the context window, defer them behind the
857
+ // internal tool_search tool. Below-threshold / disabled → no-op, the
858
+ // wiring object is untouched.
859
+ if (mcp) {
860
+ try {
861
+ (deps.maybeApplyToolSearch || maybeApplyToolSearch)(mcp, {
862
+ model,
863
+ provider,
864
+ cwd: options.cwd || process.cwd(),
865
+ writeErr: isText ? writeErr : () => {},
866
+ });
867
+ } catch {
868
+ // best-effort — full schemas still work without deferral
869
+ }
870
+ }
803
871
  } catch (err) {
804
872
  writeErr(`Error: ${err.message}\n`);
805
873
  emitHeadlessError(err.message);
@@ -861,6 +929,35 @@ export async function runAgentHeadless(options = {}, deps = {}) {
861
929
  }
862
930
  }
863
931
 
932
+ // --remote-control: route CONFIRM-tier approvals to paired mobile/web
933
+ // devices (第四阶段 #2). Self-hosts a lightweight WS server + approval
934
+ // bridge for THIS run's session, prints the pairing URI/QR to stderr, and
935
+ // installs the remote confirmer on the gate (same override point as
936
+ // --permission-prompt-tool; that flag wins when both are given since it is
937
+ // installed above and this block skips). Fail-closed on timeout.
938
+ let _remoteApproval = null;
939
+ if (options.remoteControl && !options.permissionPromptTool) {
940
+ try {
941
+ const { startHeadlessRemoteApproval } =
942
+ await import("../lib/remote-approval-bridge.js");
943
+ _remoteApproval = await (
944
+ deps.startHeadlessRemoteApproval || startHeadlessRemoteApproval
945
+ )({
946
+ agentSessionId: sessionId,
947
+ writeErr,
948
+ isText,
949
+ });
950
+ if (approvalGate && typeof approvalGate.setConfirmer === "function") {
951
+ approvalGate.setConfirmer(_remoteApproval.confirmer);
952
+ }
953
+ } catch (err) {
954
+ // Remote approval could not come up → keep headless fail-closed rather
955
+ // than running un-gated; say why so the user can fix pairing.
956
+ writeErr(` remote-control: unavailable (${err.message})\n`);
957
+ _remoteApproval = null;
958
+ }
959
+ }
960
+
864
961
  // --otlp <file>: attach an OpenTelemetry recorder so the real agent loop's
865
962
  // model/tool/retry spans are captured and exported as OTLP/JSON on exit.
866
963
  // Off by default (zero cost) — only built when a path is requested.
@@ -1350,6 +1447,15 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1350
1447
  } catch {
1351
1448
  // best-effort — never mask the run's own outcome
1352
1449
  }
1450
+ // Tear down the --remote-control approval bridge + its self-hosted WS
1451
+ // server so the run's port/socket never outlives the invocation.
1452
+ if (_remoteApproval) {
1453
+ try {
1454
+ await _remoteApproval.close();
1455
+ } catch {
1456
+ // best-effort — never mask the run's own outcome
1457
+ }
1458
+ }
1353
1459
  // settings.json Stop + SessionEnd hooks when the run finishes. Stop is the
1354
1460
  // canonical async-hook trigger ("run the test suite at turn end"); fire its
1355
1461
  // async hooks fire-and-forget, then settle so a fast background check can
@@ -1502,6 +1608,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1502
1608
  } else if (isStream) {
1503
1609
  emitStream({ type: "denials_summary", count: denials.length, denials });
1504
1610
  }
1611
+ try {
1612
+ const { appendRecentDenials } =
1613
+ await import("../lib/permission-denial-store.js");
1614
+ appendRecentDenials(denials, {
1615
+ sessionId,
1616
+ permissionMode: options.permissionMode || "default",
1617
+ cwd,
1618
+ source: "headless",
1619
+ });
1620
+ } catch {
1621
+ // Persisting the review surface is best-effort; never affect the run.
1622
+ }
1505
1623
  }
1506
1624
 
1507
1625
  if (isStream) {
@@ -42,6 +42,7 @@ import {
42
42
  resolvePermissionPromptTool,
43
43
  makePermissionPromptConfirmer,
44
44
  } from "./mcp-config.js";
45
+ import { maybeApplyToolSearch } from "./mcp-tool-search.js";
45
46
  import { IterationBudget } from "../lib/iteration-budget.js";
46
47
  import { CostBudget } from "../lib/cost-budget.js";
47
48
  import {
@@ -723,10 +724,34 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
723
724
  let approvalGate = null;
724
725
  try {
725
726
  approvalGate = await getApprovalGate();
727
+ if (approvalGate && (options.permissionMode || "default") === "auto") {
728
+ // autoMode.decisions: user-configured riskLevel → allow/ask/deny
729
+ // classifier (same wiring as headless-runner). Only wrap when settings
730
+ // customize the map so the unconfigured path stays byte-identical.
731
+ try {
732
+ const {
733
+ loadAutoModeConfig,
734
+ resolveAutoModeDecisions,
735
+ createAutoModeApprovalGate,
736
+ } = await import("../lib/auto-mode-config.js");
737
+ const autoConfig = loadAutoModeConfig({
738
+ cwd,
739
+ settingsFile: options.settingsFile,
740
+ });
741
+ const resolved = resolveAutoModeDecisions(autoConfig.effective);
742
+ if (resolved.customized) {
743
+ approvalGate = createAutoModeApprovalGate(approvalGate, resolved);
744
+ }
745
+ } catch {
746
+ // fail to the default trusted mapping — never block the run
747
+ }
748
+ }
726
749
  if (approvalGate) {
727
750
  approvalGate.setSessionPolicy?.(sessionId, perm.sessionPolicy);
728
751
  approvalGate.setConfirmer?.(
729
- interactive ? interactiveConfirm : perm.confirmer,
752
+ interactive && perm.allowInteractiveApprovals
753
+ ? interactiveConfirm
754
+ : perm.confirmer,
730
755
  );
731
756
  }
732
757
  } catch {
@@ -949,6 +974,20 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
949
974
  loadJetbrainsMcp: deps.loadJetbrainsMcp,
950
975
  },
951
976
  );
977
+ // MCP tool search (context scaling): defer big tool schemas behind the
978
+ // internal tool_search tool. Below-threshold / disabled → no-op.
979
+ if (mcp) {
980
+ try {
981
+ (deps.maybeApplyToolSearch || maybeApplyToolSearch)(mcp, {
982
+ model,
983
+ provider,
984
+ cwd: options.cwd || process.cwd(),
985
+ writeErr,
986
+ });
987
+ } catch {
988
+ // best-effort — full schemas still work without deferral
989
+ }
990
+ }
952
991
  } catch (err) {
953
992
  emit({
954
993
  type: "result",
@@ -987,6 +1026,43 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
987
1026
  }
988
1027
  }
989
1028
 
1029
+ // --remote-control: route CONFIRM-tier approvals to paired mobile/web
1030
+ // devices (第四阶段 #2). Pairing info is emitted as a stream event so panel
1031
+ // hosts can render the QR. --permission-prompt-tool and --interactive-
1032
+ // approvals win when given (this block skips) — explicit routing beats the
1033
+ // ambient device bridge.
1034
+ let _remoteApproval = null;
1035
+ if (
1036
+ options.remoteControl &&
1037
+ !options.permissionPromptTool &&
1038
+ options.interactiveApprovals !== true
1039
+ ) {
1040
+ try {
1041
+ const { startHeadlessRemoteApproval } =
1042
+ await import("../lib/remote-approval-bridge.js");
1043
+ _remoteApproval = await startHeadlessRemoteApproval({
1044
+ agentSessionId: sessionId,
1045
+ });
1046
+ emit({
1047
+ type: "remote_control",
1048
+ subtype: "pairing",
1049
+ pairing_uri: _remoteApproval.pairing.uri,
1050
+ remote_session_id: _remoteApproval.pairing.remoteSessionId,
1051
+ expires_at: _remoteApproval.pairing.expiresAt,
1052
+ });
1053
+ if (approvalGate && typeof approvalGate.setConfirmer === "function") {
1054
+ approvalGate.setConfirmer(_remoteApproval.confirmer);
1055
+ }
1056
+ } catch (err) {
1057
+ emit({
1058
+ type: "remote_control",
1059
+ subtype: "unavailable",
1060
+ error: err.message,
1061
+ });
1062
+ _remoteApproval = null;
1063
+ }
1064
+ }
1065
+
990
1066
  const loopOptionsBase = {
991
1067
  model,
992
1068
  provider,
@@ -1723,6 +1799,16 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1723
1799
  }
1724
1800
  }
1725
1801
 
1802
+ // Tear down the --remote-control approval bridge + its self-hosted WS
1803
+ // server so the stream session's port never outlives the invocation.
1804
+ if (_remoteApproval) {
1805
+ try {
1806
+ await _remoteApproval.close();
1807
+ } catch {
1808
+ // best-effort
1809
+ }
1810
+ }
1811
+
1726
1812
  // settings.json SessionEnd hooks (observe-only) when stdin closes.
1727
1813
  if (settingsHooks) {
1728
1814
  try {
@@ -169,6 +169,14 @@ export async function setupMcpFromConfig(servers, deps = {}) {
169
169
  // (created before this field existed) is passed in.
170
170
  if (!Array.isArray(result.resources)) result.resources = [];
171
171
  if (!Array.isArray(result.prompts)) result.prompts = [];
172
+ // Per-server usage instructions from the MCP initialize response (surfaced
173
+ // by tool search; absent for servers that send none).
174
+ if (
175
+ !result.instructionsByServer ||
176
+ typeof result.instructionsByServer !== "object"
177
+ ) {
178
+ result.instructionsByServer = {};
179
+ }
172
180
  const {
173
181
  mcpClient,
174
182
  extraToolDefinitions,
@@ -261,6 +269,9 @@ export async function setupMcpFromConfig(servers, deps = {}) {
261
269
  resources: resources.length,
262
270
  prompts: prompts.length,
263
271
  });
272
+ if (typeof res?.instructions === "string" && res.instructions.trim()) {
273
+ result.instructionsByServer[name] = res.instructions.trim();
274
+ }
264
275
  for (const r of resources) {
265
276
  if (!r || !r.uri) continue;
266
277
  result.resources.push({ ...r, server: name });
Binary file
@@ -44,6 +44,17 @@ export function resolveAgentPolicy({
44
44
  // IDEA built-in MCP tri-state (undefined=auto when the JetBrains plugin
45
45
  // injected the endpoint / true=--jetbrains / false=--no-jetbrains).
46
46
  jetbrains: overrides.jetbrains,
47
+ // Consumed by the interactive REPL (startAgentRepl) — plain passthrough
48
+ // (undefined = unset). These were silently dropped by this allowlist
49
+ // before 2026-07-09, breaking --vim/--think/--thinking-budget/
50
+ // --fallback-model/--pdh/--permission-mode for interactive sessions.
51
+ permissionMode: overrides.permissionMode,
52
+ vimMode: overrides.vimMode,
53
+ thinking: overrides.thinking,
54
+ thinkingBudget: overrides.thinkingBudget,
55
+ fallbackModels: overrides.fallbackModels,
56
+ pdh: overrides.pdh,
57
+ outputStyle: overrides.outputStyle,
47
58
  };
48
59
  }
49
60
 
@@ -58,6 +69,12 @@ export function resolveServerPolicy(overrides = {}) {
58
69
  project: overrides.project || process.cwd(),
59
70
  httpPort: overrides.httpPort || null,
60
71
  bundlePath: overrides.bundlePath || null,
72
+ // startServer() reads these for the Remote Session E2EE relay. They were
73
+ // silently dropped by this allowlist before 2026-07-09 — `cc serve
74
+ // --remote-session-relay-url` never actually enabled the relay (same
75
+ // allowlist-drop class as the batch-9 resolveAgentPolicy fix).
76
+ remoteSessionRelayUrl: overrides.remoteSessionRelayUrl || null,
77
+ remoteSessionPeerId: overrides.remoteSessionPeerId || null,
61
78
  };
62
79
  }
63
80
 
@@ -1,60 +1,241 @@
1
+ /**
2
+ * background-agent-worker — detached supervisor process for one background
3
+ * session. Spawns the agent CLI child with output to the session log, keeps a
4
+ * heartbeat in the state file, and (since batch 10) hosts a local session
5
+ * transport so `cc attach <id>` can send follow-up prompts interactively.
6
+ *
7
+ * Turn loop: the initial task runs as turn 1. While a client is attached the
8
+ * worker stays alive between turns (phase "idle"); a received prompt starts
9
+ * the next turn via `job.followUpArgv + ["-p", text]` on the same session id
10
+ * (headless `--session` resumes the conversation). The worker finalizes and
11
+ * exits only when the current turn has ended, the prompt queue is empty, and
12
+ * no client is attached.
13
+ */
14
+
1
15
  import { spawn } from "node:child_process";
2
16
  import { readFileSync } from "node:fs";
17
+ import { randomBytes } from "node:crypto";
3
18
  import {
19
+ DEFAULT_HEARTBEAT_INTERVAL_MS,
20
+ backgroundAgentsDir,
4
21
  openBackgroundLogFile,
5
22
  readBackgroundAgentState,
6
23
  removeJobFile,
7
24
  writeBackgroundAgentState,
8
25
  } from "../lib/background-agent-supervisor.js";
26
+ import { startBackgroundSessionServer } from "../lib/background-session-transport.js";
9
27
 
10
28
  const jobFile = process.argv[2];
11
29
  let job;
12
- try {
13
- job = JSON.parse(readFileSync(jobFile, "utf8"));
14
- removeJobFile(jobFile);
15
- const log = openBackgroundLogFile(job.id);
16
- const child = spawn(process.execPath, [job.cliEntry, ...job.argv], {
30
+ let log;
31
+ let server = null;
32
+ let child = null;
33
+ let heartbeat = null;
34
+ let finalized = false;
35
+ let phase = "turn";
36
+ let turnCount = 0;
37
+ let lastExit = { code: 0, signal: null };
38
+ let transportState = null;
39
+ const promptQueue = [];
40
+
41
+ function mergeState(patch) {
42
+ const current = readBackgroundAgentState(job.id) || { id: job.id };
43
+ const next = { ...current, id: job.id, ...patch };
44
+ writeBackgroundAgentState(next);
45
+ return next;
46
+ }
47
+
48
+ function writeHeartbeat() {
49
+ const current = readBackgroundAgentState(job.id) || { id: job.id };
50
+ if (current.status && current.status !== "running") return false;
51
+ writeBackgroundAgentState({
52
+ ...current,
53
+ id: job.id,
54
+ pid: process.pid,
55
+ workerPid: process.pid,
56
+ agentPid: child?.pid ?? current.agentPid ?? null,
57
+ status: "running",
58
+ heartbeatAt: Date.now(),
59
+ // Re-assert the transport endpoint so a launcher write racing the
60
+ // worker's initial merge self-heals within one heartbeat.
61
+ ...(transportState ? { transport: transportState } : {}),
62
+ });
63
+ return true;
64
+ }
65
+
66
+ function finalize(code, signal, errorMessage) {
67
+ if (finalized) return;
68
+ finalized = true;
69
+ clearInterval(heartbeat);
70
+ const current = readBackgroundAgentState(job.id) || { id: job.id };
71
+ // An external `cc daemon stop` already recorded its own terminal status —
72
+ // don't overwrite it; just clear the dead transport endpoint.
73
+ if (!current.status || current.status === "running") {
74
+ writeBackgroundAgentState({
75
+ ...current,
76
+ id: job.id,
77
+ status: errorMessage ? "failed" : code === 0 ? "completed" : "failed",
78
+ endedAt: Date.now(),
79
+ exitCode: errorMessage ? (code ?? 1) : code,
80
+ signal: signal || null,
81
+ ...(errorMessage ? { error: errorMessage } : {}),
82
+ phase: null,
83
+ transport: null,
84
+ });
85
+ } else {
86
+ writeBackgroundAgentState({ ...current, phase: null, transport: null });
87
+ }
88
+ const done = () => {
89
+ try {
90
+ log?.close();
91
+ } catch {
92
+ /* log fd already closed */
93
+ }
94
+ process.exit(errorMessage ? 1 : (code ?? 1));
95
+ };
96
+ if (server) {
97
+ server.broadcast({ type: "closing" });
98
+ server.close().then(done, done);
99
+ } else {
100
+ done();
101
+ }
102
+ }
103
+
104
+ function startTurn(argv, promptText) {
105
+ phase = "turn";
106
+ turnCount++;
107
+ child = spawn(process.execPath, [job.cliEntry, ...argv], {
17
108
  cwd: job.cwd,
18
109
  env: { ...process.env, CC_BACKGROUND_AGENT_ID: job.id },
19
110
  stdio: ["ignore", log.fd, log.fd],
20
111
  windowsHide: true,
21
112
  });
113
+ mergeState({
114
+ status: "running",
115
+ phase,
116
+ turnCount,
117
+ agentPid: child.pid,
118
+ heartbeatAt: Date.now(),
119
+ });
120
+ server?.broadcast({
121
+ type: "turn-started",
122
+ turn: turnCount,
123
+ prompt: promptText || null,
124
+ });
22
125
  child.on("exit", (code, signal) => {
23
- const current = readBackgroundAgentState(job.id) || {};
24
- if (current.status === "running") {
25
- writeBackgroundAgentState({
26
- ...current,
27
- status: code === 0 ? "completed" : "failed",
28
- endedAt: Date.now(),
29
- exitCode: code,
30
- signal: signal || null,
31
- });
32
- }
33
- log.close();
34
- process.exit(code ?? 1);
126
+ lastExit = { code, signal };
127
+ child = null;
128
+ server?.broadcast({ type: "turn-ended", turn: turnCount, exitCode: code });
129
+ maybeContinue();
35
130
  });
36
131
  child.on("error", (error) => {
37
- const current = readBackgroundAgentState(job.id) || { id: job.id };
38
- writeBackgroundAgentState({
39
- ...current,
40
- status: "failed",
41
- endedAt: Date.now(),
42
- exitCode: 1,
43
- error: error.message,
44
- });
45
- log.close();
46
- process.exit(1);
132
+ child = null;
133
+ finalize(1, null, error.message);
47
134
  });
48
- } catch (error) {
49
- if (job?.id) {
50
- const current = readBackgroundAgentState(job.id) || { id: job.id };
51
- writeBackgroundAgentState({
52
- ...current,
53
- status: "failed",
54
- endedAt: Date.now(),
55
- exitCode: 1,
56
- error: error.message,
135
+ }
136
+
137
+ function maybeContinue() {
138
+ if (finalized) return;
139
+ // External stop marked the session terminal while a turn was in flight —
140
+ // don't start another turn against a stopped session.
141
+ const current = readBackgroundAgentState(job.id);
142
+ if (current?.status && current.status !== "running") {
143
+ finalize(lastExit.code, lastExit.signal);
144
+ return;
145
+ }
146
+ if (promptQueue.length && Array.isArray(job.followUpArgv)) {
147
+ const text = promptQueue.shift();
148
+ startTurn([...job.followUpArgv, "-p", text], text);
149
+ return;
150
+ }
151
+ if (server && server.clientCount() > 0) {
152
+ phase = "idle";
153
+ mergeState({ phase, agentPid: null, heartbeatAt: Date.now() });
154
+ server.broadcast({ type: "idle", turn: turnCount });
155
+ return;
156
+ }
157
+ finalize(lastExit.code, lastExit.signal);
158
+ }
159
+
160
+ function getStatus() {
161
+ return {
162
+ id: job.id,
163
+ sessionId: job.sessionId || null,
164
+ title: job.title || null,
165
+ phase,
166
+ turn: turnCount,
167
+ interactive: Array.isArray(job.followUpArgv),
168
+ };
169
+ }
170
+
171
+ async function main() {
172
+ job = JSON.parse(readFileSync(jobFile, "utf8"));
173
+ removeJobFile(jobFile);
174
+ log = openBackgroundLogFile(job.id);
175
+
176
+ // Session transport (best-effort): interactive attach needs it, but a
177
+ // transport failure must never take down the background task itself.
178
+ try {
179
+ const token = randomBytes(16).toString("hex");
180
+ server = await startBackgroundSessionServer({
181
+ id: job.id,
182
+ dir: backgroundAgentsDir(),
183
+ token,
184
+ getStatus,
185
+ onPrompt: (text) => {
186
+ if (!Array.isArray(job.followUpArgv)) {
187
+ throw new Error(
188
+ "this session was launched without follow-up support — start a new one with cc agent --bg",
189
+ );
190
+ }
191
+ promptQueue.push(text);
192
+ const queued = promptQueue.length;
193
+ if (!child) maybeContinue();
194
+ return { queued };
195
+ },
196
+ onStop: () => {
197
+ child?.kill("SIGTERM");
198
+ },
199
+ onClientChange: (count) => {
200
+ // Last client detached while the session is idle → nothing left to
201
+ // wait for; finalize with the last turn's exit code.
202
+ if (count === 0 && !child && promptQueue.length === 0) {
203
+ finalize(lastExit.code, lastExit.signal);
204
+ }
205
+ },
57
206
  });
207
+ transportState = { pipe: server.pipePath, token };
208
+ mergeState({ transport: transportState });
209
+ } catch {
210
+ server = null;
58
211
  }
59
- process.exit(1);
212
+
213
+ writeHeartbeat();
214
+ heartbeat = setInterval(() => {
215
+ try {
216
+ if (!writeHeartbeat()) {
217
+ clearInterval(heartbeat);
218
+ // External stop: the child tree is being killed by the stopper; make
219
+ // sure the worker itself never lingers holding the pipe.
220
+ if (!child) finalize(lastExit.code, lastExit.signal);
221
+ }
222
+ } catch {
223
+ /* do not let heartbeat persistence kill the worker */
224
+ }
225
+ }, DEFAULT_HEARTBEAT_INTERVAL_MS);
226
+ heartbeat.unref?.();
227
+
228
+ startTurn(job.argv, null);
60
229
  }
230
+
231
+ main().catch((error) => {
232
+ if (job?.id) {
233
+ try {
234
+ finalize(1, null, error.message);
235
+ return;
236
+ } catch {
237
+ /* fall through to hard exit */
238
+ }
239
+ }
240
+ process.exit(1);
241
+ });