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
@@ -0,0 +1,315 @@
1
+ /**
2
+ * background-session-transport — local control channel for interactive attach.
3
+ *
4
+ * The background-agent worker hosts a per-session NDJSON server on a local
5
+ * pipe (Windows named pipe / POSIX domain socket). `cc attach <id>` connects
6
+ * to it to send follow-up prompts into the running session and receive
7
+ * lifecycle events (turn-started / turn-ended / idle), turning the log-only
8
+ * attach into a real interactive takeover.
9
+ *
10
+ * Auth model: the worker generates a random token, stored in the 0600 state
11
+ * file next to the pipe path. A client must open with
12
+ * `{type:"hello", token}` — anything else (or a wrong token, or silence for
13
+ * 5s) destroys the connection. Named pipes are reachable by other local
14
+ * users on Windows, so possession of the state file IS the capability.
15
+ *
16
+ * Protocol (one JSON object per line, both directions):
17
+ * client → worker: {type:"hello", token} · {type:"prompt", text}
18
+ * · {type:"status"} · {type:"stop"} · {type:"detach"}
19
+ * worker → client: {type:"hello", ...status} · {type:"accepted", queued}
20
+ * · {type:"status", ...status} · {type:"error", message}
21
+ * · {type:"turn-started"|"turn-ended"|"idle", ...}
22
+ */
23
+
24
+ import net from "node:net";
25
+ import { unlinkSync } from "node:fs";
26
+ import { join } from "node:path";
27
+
28
+ const HELLO_TIMEOUT_MS = 5000;
29
+ const MAX_LINE_BYTES = 1024 * 1024;
30
+
31
+ /** Pipe endpoint for a session id (Windows named pipe / POSIX socket file). */
32
+ export function transportPipePath(id, dir) {
33
+ if (process.platform === "win32") return `\\\\.\\pipe\\cc-bg-${id}`;
34
+ return join(dir, `${id}.sock`);
35
+ }
36
+
37
+ /**
38
+ * NDJSON line framing with a carry buffer — a chunk boundary can split a
39
+ * line, so the remainder must carry into the next chunk instead of being
40
+ * parsed (and dropped) as its own line.
41
+ */
42
+ export function createNdjsonReader(onMessage, onError = () => {}) {
43
+ let carry = "";
44
+ return (chunk) => {
45
+ carry += chunk.toString("utf8");
46
+ if (carry.length > MAX_LINE_BYTES) {
47
+ carry = "";
48
+ onError(new Error("line too long"));
49
+ return;
50
+ }
51
+ let index;
52
+ while ((index = carry.indexOf("\n")) !== -1) {
53
+ const line = carry.slice(0, index).replace(/\r$/, "");
54
+ carry = carry.slice(index + 1);
55
+ if (!line.trim()) continue;
56
+ let message;
57
+ try {
58
+ message = JSON.parse(line);
59
+ } catch (error) {
60
+ onError(error);
61
+ continue;
62
+ }
63
+ onMessage(message);
64
+ }
65
+ };
66
+ }
67
+
68
+ function writeMessage(socket, message) {
69
+ if (!socket || socket.destroyed) return false;
70
+ try {
71
+ socket.write(`${JSON.stringify(message)}\n`);
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Start the worker-side control server.
80
+ *
81
+ * @param {object} opts
82
+ * @param {string} opts.id background agent id
83
+ * @param {string} opts.dir backgroundAgentsDir() (POSIX socket location)
84
+ * @param {string} opts.token shared secret from the session state file
85
+ * @param {(text:string)=>{queued:number}} opts.onPrompt queue a follow-up
86
+ * prompt; throw to reject (message is relayed to the client)
87
+ * @param {()=>void} [opts.onStop] kill the current agent turn
88
+ * @param {(count:number)=>void} [opts.onClientChange]
89
+ * @param {()=>object} [opts.getStatus] session status payload for hello/status
90
+ * @returns {Promise<{pipePath:string, clientCount:()=>number,
91
+ * broadcast:(msg:object)=>void, close:()=>Promise<void>}>}
92
+ */
93
+ export function startBackgroundSessionServer(opts) {
94
+ const { id, dir, token, onPrompt, onStop, onClientChange, getStatus } = opts;
95
+ const pipePath = opts.pipePath || transportPipePath(id, dir);
96
+ if (process.platform !== "win32") {
97
+ try {
98
+ unlinkSync(pipePath); // stale socket from a crashed prior worker
99
+ } catch {
100
+ /* fine — usually ENOENT */
101
+ }
102
+ }
103
+ const clients = new Set();
104
+ const notifyClients = () => {
105
+ try {
106
+ onClientChange?.(clients.size);
107
+ } catch {
108
+ /* observer errors never take the server down */
109
+ }
110
+ };
111
+
112
+ const server = net.createServer((socket) => {
113
+ let authed = false;
114
+ const helloTimer = setTimeout(() => {
115
+ if (!authed) socket.destroy();
116
+ }, HELLO_TIMEOUT_MS);
117
+ helloTimer.unref?.();
118
+
119
+ socket.on(
120
+ "data",
121
+ createNdjsonReader(
122
+ (message) => {
123
+ if (!authed) {
124
+ if (message.type === "hello" && message.token === token) {
125
+ authed = true;
126
+ clearTimeout(helloTimer);
127
+ clients.add(socket);
128
+ writeMessage(socket, {
129
+ type: "hello",
130
+ ...(getStatus ? getStatus() : { id }),
131
+ });
132
+ notifyClients();
133
+ } else {
134
+ socket.destroy();
135
+ }
136
+ return;
137
+ }
138
+ switch (message.type) {
139
+ case "prompt": {
140
+ const text = String(message.text || "").trim();
141
+ if (!text) {
142
+ writeMessage(socket, {
143
+ type: "error",
144
+ message: "prompt text is empty",
145
+ });
146
+ return;
147
+ }
148
+ try {
149
+ const result = onPrompt(text);
150
+ writeMessage(socket, {
151
+ type: "accepted",
152
+ queued: result?.queued ?? 1,
153
+ });
154
+ } catch (error) {
155
+ writeMessage(socket, {
156
+ type: "error",
157
+ message: error?.message || String(error),
158
+ });
159
+ }
160
+ return;
161
+ }
162
+ case "status": {
163
+ writeMessage(socket, {
164
+ type: "status",
165
+ ...(getStatus ? getStatus() : { id }),
166
+ });
167
+ return;
168
+ }
169
+ case "stop": {
170
+ try {
171
+ onStop?.();
172
+ writeMessage(socket, { type: "stopping" });
173
+ } catch (error) {
174
+ writeMessage(socket, {
175
+ type: "error",
176
+ message: error?.message || String(error),
177
+ });
178
+ }
179
+ return;
180
+ }
181
+ case "detach": {
182
+ socket.end();
183
+ return;
184
+ }
185
+ default:
186
+ writeMessage(socket, {
187
+ type: "error",
188
+ message: `unknown message type: ${message.type}`,
189
+ });
190
+ }
191
+ },
192
+ () => {
193
+ // Unparseable input on an authed connection is a broken client —
194
+ // and on an unauthed one, a probe. Either way, drop it.
195
+ socket.destroy();
196
+ },
197
+ ),
198
+ );
199
+
200
+ const drop = () => {
201
+ clearTimeout(helloTimer);
202
+ if (clients.delete(socket)) notifyClients();
203
+ };
204
+ socket.on("close", drop);
205
+ socket.on("error", drop);
206
+ });
207
+
208
+ return new Promise((resolve, reject) => {
209
+ server.once("error", reject);
210
+ server.listen(pipePath, () => {
211
+ server.removeListener("error", reject);
212
+ server.on("error", () => {
213
+ /* post-listen errors must not crash the worker */
214
+ });
215
+ resolve({
216
+ pipePath,
217
+ clientCount: () => clients.size,
218
+ broadcast: (message) => {
219
+ for (const socket of clients) writeMessage(socket, message);
220
+ },
221
+ close: () =>
222
+ new Promise((done) => {
223
+ for (const socket of clients) socket.destroy();
224
+ clients.clear();
225
+ server.close(() => {
226
+ if (process.platform !== "win32") {
227
+ try {
228
+ unlinkSync(pipePath);
229
+ } catch {
230
+ /* already gone */
231
+ }
232
+ }
233
+ done();
234
+ });
235
+ }),
236
+ });
237
+ });
238
+ });
239
+ }
240
+
241
+ /**
242
+ * Client side: connect, authenticate, and expose send/close. Resolves only
243
+ * after the server's hello ack — so a resolved connection is usable.
244
+ *
245
+ * @param {object} opts
246
+ * @param {string} opts.pipePath from the session state file `transport.pipe`
247
+ * @param {string} opts.token from the session state file `transport.token`
248
+ * @param {(msg:object)=>void} [opts.onEvent] server-pushed messages
249
+ * @param {()=>void} [opts.onClose]
250
+ * @returns {Promise<{send:(msg:object)=>boolean, close:()=>void, hello:object}>}
251
+ */
252
+ export function connectBackgroundSession(opts) {
253
+ const { pipePath, token, onEvent, onClose, timeoutMs = 5000 } = opts;
254
+ return new Promise((resolve, reject) => {
255
+ let settled = false;
256
+ const socket = net.connect(pipePath);
257
+ const timer = setTimeout(() => {
258
+ if (!settled) {
259
+ settled = true;
260
+ socket.destroy();
261
+ reject(new Error("session transport handshake timed out"));
262
+ }
263
+ }, timeoutMs);
264
+ timer.unref?.();
265
+
266
+ socket.on(
267
+ "data",
268
+ createNdjsonReader((message) => {
269
+ if (!settled) {
270
+ if (message.type === "hello") {
271
+ settled = true;
272
+ clearTimeout(timer);
273
+ resolve({
274
+ hello: message,
275
+ send: (msg) => writeMessage(socket, msg),
276
+ close: () => {
277
+ writeMessage(socket, { type: "detach" });
278
+ socket.end();
279
+ },
280
+ });
281
+ }
282
+ return;
283
+ }
284
+ try {
285
+ onEvent?.(message);
286
+ } catch {
287
+ /* rendering errors never kill the connection */
288
+ }
289
+ }),
290
+ );
291
+ socket.on("connect", () => {
292
+ writeMessage(socket, { type: "hello", token });
293
+ });
294
+ socket.on("error", (error) => {
295
+ if (!settled) {
296
+ settled = true;
297
+ clearTimeout(timer);
298
+ reject(error);
299
+ }
300
+ });
301
+ socket.on("close", () => {
302
+ if (!settled) {
303
+ settled = true;
304
+ clearTimeout(timer);
305
+ reject(new Error("session transport closed during handshake"));
306
+ } else {
307
+ try {
308
+ onClose?.();
309
+ } catch {
310
+ /* observer errors are not connection errors */
311
+ }
312
+ }
313
+ });
314
+ });
315
+ }
@@ -0,0 +1,159 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { getHomeDir } from "./paths.js";
10
+ import { MAX_RECENT_DENIALS, formatDenialChain } from "./repl-denials.js";
11
+
12
+ export const RECENT_DENIALS_VERSION = 1;
13
+ export const DEFAULT_RECENT_DENIAL_LIMIT = 100;
14
+
15
+ export const _deps = {
16
+ existsSync,
17
+ mkdirSync,
18
+ readFileSync,
19
+ renameSync,
20
+ writeFileSync,
21
+ getHomeDir,
22
+ now: () => Date.now(),
23
+ };
24
+
25
+ export function recentDenialsPath() {
26
+ return join(_deps.getHomeDir(), "recent-denials.json");
27
+ }
28
+
29
+ function emptyStore() {
30
+ return { version: RECENT_DENIALS_VERSION, denials: [] };
31
+ }
32
+
33
+ function readStore(file = recentDenialsPath()) {
34
+ if (!_deps.existsSync(file)) return emptyStore();
35
+ try {
36
+ const parsed = JSON.parse(_deps.readFileSync(file, "utf8"));
37
+ return {
38
+ version: RECENT_DENIALS_VERSION,
39
+ denials: Array.isArray(parsed?.denials) ? parsed.denials : [],
40
+ };
41
+ } catch {
42
+ return emptyStore();
43
+ }
44
+ }
45
+
46
+ function writeStore(store, file = recentDenialsPath()) {
47
+ _deps.mkdirSync(dirname(file), { recursive: true });
48
+ const tmp = `${file}.${process.pid}.tmp`;
49
+ _deps.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n", "utf8");
50
+ _deps.renameSync(tmp, file);
51
+ }
52
+
53
+ function normalizeRecord(record, metadata = {}) {
54
+ if (!record || typeof record !== "object") return null;
55
+ return {
56
+ at: typeof record.at === "number" ? record.at : _deps.now(),
57
+ tool: String(record.tool || "?"),
58
+ summary: String(record.summary || ""),
59
+ reason: String(record.reason || ""),
60
+ via: String(record.via || "policy"),
61
+ rule: record.rule == null ? null : String(record.rule),
62
+ count: Number.isFinite(record.count) && record.count > 1 ? record.count : 1,
63
+ sessionId: metadata.sessionId || record.sessionId || null,
64
+ permissionMode: metadata.permissionMode || record.permissionMode || null,
65
+ cwd: metadata.cwd || record.cwd || null,
66
+ source: metadata.source || record.source || "agent",
67
+ // Layer-by-layer explanation (settings-rules → shell-policy →
68
+ // approval-gate) when the denial carried one; null for older records.
69
+ chain: Array.isArray(record.chain) ? record.chain : null,
70
+ };
71
+ }
72
+
73
+ function sameRecentDenial(a, b) {
74
+ return (
75
+ a &&
76
+ b &&
77
+ a.tool === b.tool &&
78
+ a.summary === b.summary &&
79
+ a.via === b.via &&
80
+ a.rule === b.rule &&
81
+ a.sessionId === b.sessionId &&
82
+ a.permissionMode === b.permissionMode &&
83
+ a.cwd === b.cwd &&
84
+ a.source === b.source
85
+ );
86
+ }
87
+
88
+ function parseLimit(value, fallback = DEFAULT_RECENT_DENIAL_LIMIT) {
89
+ const n = Number(value);
90
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
91
+ }
92
+
93
+ export function appendRecentDenials(records, metadata = {}, options = {}) {
94
+ const incoming = Array.isArray(records) ? records : [records];
95
+ const normalized = incoming
96
+ .map((record) => normalizeRecord(record, metadata))
97
+ .filter(Boolean);
98
+ if (!normalized.length) return readRecentDenials(options);
99
+
100
+ const max = parseLimit(options.max || options.limit);
101
+ const file = options.file || recentDenialsPath();
102
+ const store = readStore(file);
103
+ for (const record of normalized) {
104
+ const last = store.denials[store.denials.length - 1];
105
+ if (sameRecentDenial(last, record)) {
106
+ last.count = (last.count || 1) + (record.count || 1);
107
+ last.at = record.at;
108
+ if (record.reason) last.reason = record.reason;
109
+ } else {
110
+ store.denials.push(record);
111
+ }
112
+ }
113
+ while (store.denials.length > max) store.denials.shift();
114
+ writeStore(store, file);
115
+ return store.denials.slice(-Math.min(max, store.denials.length));
116
+ }
117
+
118
+ export function readRecentDenials(options = {}) {
119
+ const max = parseLimit(options.limit);
120
+ const store = readStore(options.file || recentDenialsPath());
121
+ return store.denials.slice(-Math.min(max, store.denials.length));
122
+ }
123
+
124
+ export function clearRecentDenials(options = {}) {
125
+ const file = options.file || recentDenialsPath();
126
+ writeStore(emptyStore(), file);
127
+ return { file, cleared: true };
128
+ }
129
+
130
+ export function formatRecentDenials(records, options = {}) {
131
+ const now = typeof options.now === "number" ? options.now : _deps.now();
132
+ const denials = Array.isArray(records) ? records : [];
133
+ if (!denials.length) return " (no recent policy denials)";
134
+ const lines = [
135
+ ` Recent policy denials (most recent first, ${denials.length}):`,
136
+ ];
137
+ for (let i = denials.length - 1; i >= 0; i--) {
138
+ const d = denials[i] || {};
139
+ const ago =
140
+ typeof d.at === "number"
141
+ ? ` · ${Math.max(0, Math.round((now - d.at) / 1000))}s ago`
142
+ : "";
143
+ const where = d.rule ? `${d.via}:${d.rule}` : d.via || "policy";
144
+ const what = d.summary ? `${d.tool} ${d.summary}` : d.tool || "?";
145
+ const times = d.count > 1 ? ` x${d.count}` : "";
146
+ const session = d.sessionId ? ` · session ${d.sessionId}` : "";
147
+ const mode = d.permissionMode ? ` · mode ${d.permissionMode}` : "";
148
+ lines.push(` - ${what}${times} [${where}${session}${mode}${ago}]`);
149
+ if (d.cwd) lines.push(` cwd: ${d.cwd}`);
150
+ const chainLine = formatDenialChain(d.chain);
151
+ if (chainLine) lines.push(` chain: ${chainLine}`);
152
+ if (d.reason) lines.push(` ${d.reason}`);
153
+ }
154
+ return lines.join("\n");
155
+ }
156
+
157
+ export function recentDenialRetentionLimit() {
158
+ return Math.max(DEFAULT_RECENT_DENIAL_LIMIT, MAX_RECENT_DENIALS);
159
+ }