chainlesschain 0.162.147 → 0.162.149

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 (257) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DywJ7xTv.js → AIOps-BJif14yR.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BTlqcY-q.js → ActionButton-CXek6gJY.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-FdG1Mvwy.js → Analytics-BgQhdAJi.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CVsnbvN1.js → AppLayout-DTYl5NtR.js} +5 -5
  8. package/src/assets/web-panel/assets/{Audit-ITdpJ7vR.js → Audit-BzATQAeR.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BfXqnXo1.js → Backup-BzdPEQhL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C09ip3_E.js → BaseInput-BM0KVh8E.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-qdkhB42l.js → Chat-D7fHXHkz.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-D2KfETZD.js → ChatBubbleRenderer-Wm34RR-b.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-j6WZCuff.js → Checkbox-CAiSQ9vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CGZ-K3uK.js → Codegen-Df40CrEe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CIlPOAu6.js → Col-DIT2fNFU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-sY-i-hic.js → Community-CSTIbW2k.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DcoEfyL8.js → Compact-DtqXZZUi.js} +1 -1
  18. package/src/assets/web-panel/assets/Compliance-CUtzw6o7.js +1 -0
  19. package/src/assets/web-panel/assets/{Cowork-DLY_fkLv.js → Cowork-CezmlnmU.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-D049pZBC.js → Cron-P4BITarg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DCXdE8M9.js → Crosschain-BRRiYwvq.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-JCvPZtjW.js → DID-L5ijB9i4.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Cg2xu3iE.js → Dashboard-CFxro4ob.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DjJLjbLh.js → Dropdown-BmAFCQ71.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DugNcSLR.js → EmailListRenderer-DJBCEuon.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-J6Nb-E30.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CQ3Ttpbl.js → Federation-DsVCpHMF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cz0NKag4.js → FormItemContext-DP3bP5th.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-NYcMP6iz.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-B4hYN18N.js → Git-I3g_bAw9.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DOQPCcC0.js → Governance-jHS5-ioS.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-HtLF746W.js → Inference-CPL7sfx9.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BXrk8rMm.js → KnowledgeGraph-Jf236h4C.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DS0JnD3-.js → Logs-Cm2tcSKg.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-Cqnjihrz.js → Marketplace-CZK82rhi.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Cx3Psk-U.js → McpTools-CZCeji7a.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-BjyysPtj.js → Memory-3l2KVS9k.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
  39. package/src/assets/web-panel/assets/MobileBridge-VYdpoLh6.js +1 -0
  40. package/src/assets/web-panel/assets/{MobileProjects-Xwf-fdOQ.js → MobileProjects-CiB9cmm1.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Djkql5m5.js → Mtc-wCFZbBuL.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-D2BAZlu0.js → MtcAudit-1a3THIyG.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-ByqP1ZzH.js → Multisig-DMzLB2hG.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-C_kn0nE2.js → NLProgramming-D9wZbf6l.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-7l7eXHPq.js → Notes-hrFe2ZM-.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BrCDoitw.js → NotificationSettings-CHGX5Jwm.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-DdoNAxsM.js → Organization-By7FkhtY.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bw7R6dE3.js → Overflow--khGSzJn.js} +1 -1
  50. package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DJNtBugA.js → P2P-CAG9dH35.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dwhd5_ue.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-BYoSfhPR.js → Permissions-D35ec6JA.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-aYCYJbnH.js → PersonalDataHub-CxBF7hW_.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-DJp6CprF.js → Pipeline-s6EtOO1s.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-Cq9HZweR.js → Privacy-BbJYmWmO.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CmF0HsSt.js → ProjectInit-VGQq1jMT.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-P8V5Aea2.js → ProjectSettings-B1ew3Teh.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-D589v0q2.js → Providers-mrO47FIK.js} +1 -1
  61. package/src/assets/web-panel/assets/QrScannerModal-IJF2mHyw.js +1 -0
  62. package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-lvRV2osB.js → QuickAsk-CU0wN_Z0.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-sb84MTpY.js → Recommend-DhN37R6G.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-D_xOX_hu.js +4 -0
  66. package/src/assets/web-panel/assets/{Reputation-d9Y3vy-W.js → Reputation-D_Ssv7uH.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DMdhLUTp.js → Row-Kx5dKxX7.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-OL-SMDkz.js → RssFeed-BlGwqZkY.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-BjStLKq1.js → Search-hLFQzxpb.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-MeI411qr.js → Security-DMlvsVt4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Services-UajosuhT.js → Services-Bd0Qsj2m.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-CGpG-QJ1.js → Skeleton-DFclq9X3.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-C6P2RPY-.js → Skills-BNtln2qY.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-B_fPprgw.js → Sla-Dch5H19G.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-CqxLjSlQ.js → SpeechSettings-2UfQcU1g.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-DixSfFOQ.js → SyncSettings-CD9YQr4i.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-PavzPBxc.js → Tasks-BNu-7MzI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-BYdGRnfX.js → Templates-D1n9CaIR.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BQLgnarD.js → Tenant-DenL3iBW.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-Bv7uZRM7.js → TimelineRenderer-D87IfukO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DxhgszRJ.js → Tokens-BkiZc8E3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-nQYHayuc.js → Trigger-BNRSytGN.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CvqZDuZ4.js → Trust-Bwbd4X2m.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DaR8GV2I.js → UkeySign-B8O2bs0o.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-C6Mpsokw.js → VideoEditing-C2fL8zmH.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-B57VRrWc.js → Wallet-BORYDdE1.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BbtEEtpq.js → WebAuthn-C0V5S2FM.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-C5LwvKmH.js → WorkflowEditor-2dR0fcHL.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-MzuPR5jh.js → chat-BmuAFAn8.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-HyW8mi_y.js → colors-CN3smMcK.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DvUSzWAp.js → compact-item-DCnxb4kW.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-4MppZl7X.js → createContext-vlR43pHn.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-D9q3CJ-u.js → hasIn-D04tHYFd.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C9I5MuJ0.js → index-0DnaZGkC.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Caqtu6Et.js → index-430S68MN.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-eOQO6KnV.js → index-B78xL3s2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Ci6adKh8.js → index-BTPEZTk1.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CifKUNtV.js → index-BXRg8GFQ.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D7TDMMfu.js → index-BasvsWDM.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DNYqw0MO.js → index-BeGDEXFs.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-m0b6Fj9q.js → index-BqhASEL4.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BF2JsbiX.js → index-BudvKQfG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DFqvWeGc.js → index-C-6NO5il.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-SDblbKj6.js → index-C5Sxb1sE.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-UUMCA2Sq.js → index-CP9m9Ggr.js} +4 -4
  111. package/src/assets/web-panel/assets/{index--Bm6OqmU.js → index-CTOCnIQ7.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-Dv7ffI2g.js → index-ClhAHDK3.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcunsBIx.js → index-CnXebZLL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CvAqCAo9.js → index-CngCC8hC.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DcMkjb92.js → index-Cr-A0FYJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BFGfC5RS.js → index-CsAsCPJ6.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DDZ6fP5Z.js → index-D28DeAAB.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CgIfXOD9.js → index-D5LZVZ0L.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DmZ6-suL.js → index-D65_XseV.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cs-RKRUM.js → index-D8vwp4qp.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B994UQfE.js → index-DDwLgQY9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Btz8pU68.js → index-DIpY0qM5.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B5E4DhVc.js → index-DTWg-18U.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D2jrnaq3.js → index-DTtRscUa.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BmCo2Xdp.js → index-DYAtmqiv.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Zvtru2oE.js → index-D_n8_vfI.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CLAZs1Vd.js → index-DbADHwhr.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CucRo4Vz.js → index-DbVsQBOH.js} +1 -1
  130. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
  131. package/src/assets/web-panel/assets/{index-C0DiL97c.js → index-HhNjnCfe.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Bl-E4W4q.js → index-ToUh2b1-.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-ryFLxB1p.js → index-TslMTwNy.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BGQtFso0.js → index-_FIkN3jd.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-zLwYpvX0.js → index-hpvvtAZ3.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-Dvg5xYuP.js → index-jzR7Ujuw.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-xvUVuFnA.js → index-ljuBiQW9.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-CC--PMsC.js → initDefaultProps-C3wUb4je.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-DdrBjRF1.js → motion-D-jYFUNA.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D712Gwl2.js → move-tqb8nwJ2.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-39y-keKO.js → mtc-parser-DSOjMJMz.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-6Y51gDB9.js → omit-CJ7VimIW.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-Cy6EHbq9.js → pickAttrs-qXiG1iT3.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BYxdAm0p.js → placementArrow-CkGBcy1Z.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-C9R6q_cr.js → responsiveObserve-D3WvTlLO.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-CXUJlilB.js → slide-BEE2ftge.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-DOtrOeql.js → statusUtils-CsfBpxiG.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-D5r39o92.js → styleChecker-xPrIiJPI.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-C5Yd-SCR.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-BkrT-Zbc.js → useFs-D_MDS8HI.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-BwnnnZjU.js → usePersonalDataHub-BqJh-usA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
  156. package/src/assets/web-panel/assets/{vnode-SU-N4pum.js → vnode-chm_p-gz.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-DHScfpTP.js → zoom-3-YDNz0Z.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/assets/web-panel/remote-session-sw.js +43 -0
  160. package/src/command-manifest.json +22 -1
  161. package/src/commands/agent.js +44 -5
  162. package/src/commands/codeintel.js +252 -0
  163. package/src/commands/eval.js +248 -0
  164. package/src/commands/plugin.js +354 -0
  165. package/src/commands/serve.js +10 -0
  166. package/src/commands/team.js +456 -0
  167. package/src/gateways/remote-session-relay.js +160 -0
  168. package/src/gateways/ws/message-dispatcher.js +77 -2
  169. package/src/gateways/ws/remote-session-protocol.js +466 -0
  170. package/src/gateways/ws/ws-server.js +299 -0
  171. package/src/harness/golden-transcript.js +65 -0
  172. package/src/harness/mcp-client.js +6 -2
  173. package/src/harness/prompt-compressor.js +27 -1
  174. package/src/harness/remote-command-ledger.js +158 -0
  175. package/src/harness/remote-session-audit-sink.js +132 -0
  176. package/src/harness/remote-session-audit.js +110 -0
  177. package/src/harness/remote-session-crypto.js +217 -0
  178. package/src/harness/remote-session-push-apns.js +246 -0
  179. package/src/harness/remote-session-push-errors.js +15 -0
  180. package/src/harness/remote-session-push-fcm.js +238 -0
  181. package/src/harness/remote-session-push-huawei.js +174 -0
  182. package/src/harness/remote-session-push-oppo.js +171 -0
  183. package/src/harness/remote-session-push-senders.js +42 -0
  184. package/src/harness/remote-session-push-vivo.js +179 -0
  185. package/src/harness/remote-session-push-web.js +299 -0
  186. package/src/harness/remote-session-push-xiaomi.js +99 -0
  187. package/src/harness/remote-session-push.js +104 -0
  188. package/src/harness/remote-session-registry.js +420 -0
  189. package/src/index.js +6 -0
  190. package/src/lib/agent-core.js +2 -0
  191. package/src/lib/agent-sandbox.js +161 -5
  192. package/src/lib/agent-team/task-lease.js +434 -0
  193. package/src/lib/agent-team/team-budget.js +165 -0
  194. package/src/lib/agent-team/team-mailbox.js +106 -0
  195. package/src/lib/agent-team/team-runner.js +266 -0
  196. package/src/lib/agent-team/team-worktree.js +204 -0
  197. package/src/lib/agents.js +18 -0
  198. package/src/lib/async-hook-supervisor.cjs +305 -0
  199. package/src/lib/did-manager.js +6 -4
  200. package/src/lib/eval/runner.js +163 -0
  201. package/src/lib/eval/tasks.js +456 -0
  202. package/src/lib/eval/trend.js +185 -0
  203. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  204. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  205. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  206. package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
  207. package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
  208. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  209. package/src/lib/lsp/code-intelligence.js +356 -0
  210. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  211. package/src/lib/lsp/lsp-client.js +318 -0
  212. package/src/lib/lsp/lsp-manager.js +323 -0
  213. package/src/lib/lsp/lsp-server-registry.js +196 -0
  214. package/src/lib/mcp-oauth.js +23 -6
  215. package/src/lib/plugin-monitor-supervisor.js +238 -0
  216. package/src/lib/plugin-runtime/agents.js +51 -0
  217. package/src/lib/plugin-runtime/bin.js +100 -0
  218. package/src/lib/plugin-runtime/hooks.js +100 -0
  219. package/src/lib/plugin-runtime/install.js +388 -0
  220. package/src/lib/plugin-runtime/lsp.js +75 -0
  221. package/src/lib/plugin-runtime/manifest.js +458 -0
  222. package/src/lib/plugin-runtime/mcp.js +89 -0
  223. package/src/lib/plugin-runtime/monitors.js +100 -0
  224. package/src/lib/plugin-runtime/policy.js +144 -0
  225. package/src/lib/plugin-runtime/reload.js +79 -0
  226. package/src/lib/plugin-runtime/scopes.js +197 -0
  227. package/src/lib/plugin-runtime/settings.js +119 -0
  228. package/src/lib/plugin-runtime/signature.js +107 -0
  229. package/src/lib/plugin-runtime/skills.js +43 -0
  230. package/src/lib/plugin-runtime/trust.js +140 -0
  231. package/src/lib/sandbox-egress-proxy.js +162 -0
  232. package/src/lib/sandbox-network-policy.js +134 -0
  233. package/src/lib/sandbox-v2.js +10 -4
  234. package/src/lib/session-manager.js +7 -3
  235. package/src/lib/settings-hook-events.cjs +78 -6
  236. package/src/lib/settings-hooks.cjs +29 -3
  237. package/src/lib/settings-loader.cjs +35 -1
  238. package/src/lib/skill-loader.js +18 -0
  239. package/src/lib/telemetry/span-recorder.js +237 -0
  240. package/src/repl/agent-repl.js +400 -4
  241. package/src/runtime/agent-core.js +546 -52
  242. package/src/runtime/agent-runtime.js +8 -13
  243. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  244. package/src/runtime/coding-agent-policy.cjs +32 -7
  245. package/src/runtime/fallback-model.js +134 -7
  246. package/src/runtime/headless-runner.js +353 -108
  247. package/src/runtime/mcp-config.js +46 -0
  248. package/src/runtime/policies/agent-policy.js +2 -0
  249. package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +0 -1
  250. package/src/assets/web-panel/assets/MobileBridge-CYaITZ7w.js +0 -1
  251. package/src/assets/web-panel/assets/OrderTableRenderer-CmtqIdwr.js +0 -1
  252. package/src/assets/web-panel/assets/Projects-DPd5-lNS.js +0 -1
  253. package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +0 -3
  254. package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +0 -1
  255. package/src/assets/web-panel/assets/index-D_oeGLr6.js +0 -1
  256. package/src/assets/web-panel/assets/index-Qa57YG19.js +0 -1
  257. package/src/assets/web-panel/assets/useFlexGapSupport-DfAD5U4Z.js +0 -1
@@ -0,0 +1,420 @@
1
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "crypto";
2
+
3
+ export const REMOTE_SESSION_PROTOCOL_VERSION = "1.0";
4
+ export const REMOTE_SESSION_SCOPES = Object.freeze([
5
+ "observe",
6
+ "prompt",
7
+ "approve",
8
+ "interrupt",
9
+ ]);
10
+
11
+ const DEFAULT_TOKEN_TTL_MS = 5 * 60 * 1000;
12
+ const DEFAULT_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
13
+
14
+ function hashToken(token) {
15
+ return createHash("sha256").update(String(token), "utf8").digest();
16
+ }
17
+
18
+ function tokensMatch(token, digest) {
19
+ const candidate = hashToken(token);
20
+ return (
21
+ candidate.length === digest.length && timingSafeEqual(candidate, digest)
22
+ );
23
+ }
24
+
25
+ function normalizeScopes(scopes) {
26
+ const requested = scopes || REMOTE_SESSION_SCOPES;
27
+ if (!Array.isArray(requested) || requested.length === 0) {
28
+ throw new TypeError("Remote session scopes must be a non-empty array");
29
+ }
30
+ const unique = [...new Set(requested)];
31
+ for (const scope of unique) {
32
+ if (!REMOTE_SESSION_SCOPES.includes(scope)) {
33
+ throw new Error(`Unsupported remote session scope: ${scope}`);
34
+ }
35
+ }
36
+ return unique;
37
+ }
38
+
39
+ /**
40
+ * Org-level constraints an administrator can impose on Remote Sessions. Lives
41
+ * next to the registry (its single enforcement point) so both the direct WS
42
+ * join and the relay pairing path go through the same checks. Permissive by
43
+ * default — an unconfigured policy is a no-op.
44
+ */
45
+ export class RemoteSessionPolicy {
46
+ constructor({
47
+ allowedScopes = null,
48
+ maxDevices = null,
49
+ maxSessionTtlMs = null,
50
+ maxTokenTtlMs = null,
51
+ allowRelayPairing = true,
52
+ } = {}) {
53
+ this.allowedScopes = allowedScopes ? [...new Set(allowedScopes)] : null;
54
+ if (this.allowedScopes) {
55
+ if (this.allowedScopes.length === 0) {
56
+ throw new Error("allowedScopes cannot be empty");
57
+ }
58
+ for (const scope of this.allowedScopes) {
59
+ if (!REMOTE_SESSION_SCOPES.includes(scope)) {
60
+ throw new Error(`Unknown scope in org policy: ${scope}`);
61
+ }
62
+ }
63
+ }
64
+ this.maxDevices =
65
+ maxDevices == null ? null : Math.max(0, Math.floor(maxDevices));
66
+ this.maxSessionTtlMs =
67
+ maxSessionTtlMs == null ? null : Math.max(1, Math.floor(maxSessionTtlMs));
68
+ this.maxTokenTtlMs =
69
+ maxTokenTtlMs == null ? null : Math.max(1, Math.floor(maxTokenTtlMs));
70
+ this.allowRelayPairing = allowRelayPairing !== false;
71
+ }
72
+
73
+ /**
74
+ * Narrow requested scopes to the org-allowed set. Throws only when the request
75
+ * and the allow-list are wholly disjoint (i.e. nothing could be granted).
76
+ */
77
+ applyScopes(requestedScopes) {
78
+ if (!this.allowedScopes) {
79
+ return { scopes: requestedScopes || null, narrowed: false };
80
+ }
81
+ const requested =
82
+ requestedScopes && requestedScopes.length
83
+ ? [...new Set(requestedScopes)]
84
+ : [...REMOTE_SESSION_SCOPES];
85
+ const granted = requested.filter((scope) =>
86
+ this.allowedScopes.includes(scope),
87
+ );
88
+ if (granted.length === 0) {
89
+ throw new Error("Remote session scopes are not permitted by org policy");
90
+ }
91
+ return { scopes: granted, narrowed: granted.length !== requested.length };
92
+ }
93
+
94
+ capSessionTtl(ttlMs) {
95
+ return this.maxSessionTtlMs == null
96
+ ? ttlMs
97
+ : Math.min(ttlMs, this.maxSessionTtlMs);
98
+ }
99
+
100
+ capTokenTtl(ttlMs) {
101
+ return this.maxTokenTtlMs == null
102
+ ? ttlMs
103
+ : Math.min(ttlMs, this.maxTokenTtlMs);
104
+ }
105
+
106
+ enforceJoin({ deviceCount, via } = {}) {
107
+ if (via === "relay" && !this.allowRelayPairing) {
108
+ throw new Error("Relay pairing is disabled by org policy");
109
+ }
110
+ if (this.maxDevices != null && deviceCount >= this.maxDevices) {
111
+ throw new Error("Org policy device limit reached");
112
+ }
113
+ }
114
+
115
+ describe() {
116
+ return {
117
+ allowedScopes: this.allowedScopes,
118
+ maxDevices: this.maxDevices,
119
+ maxSessionTtlMs: this.maxSessionTtlMs,
120
+ maxTokenTtlMs: this.maxTokenTtlMs,
121
+ allowRelayPairing: this.allowRelayPairing,
122
+ };
123
+ }
124
+
125
+ static fromEnv(env = {}) {
126
+ const options = {};
127
+ const scopes = env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOWED_SCOPES;
128
+ if (scopes) {
129
+ options.allowedScopes = scopes
130
+ .split(",")
131
+ .map((scope) => scope.trim())
132
+ .filter(Boolean);
133
+ }
134
+ const num = (value) => {
135
+ const parsed = Number(value);
136
+ return Number.isFinite(parsed) ? parsed : null;
137
+ };
138
+ if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_DEVICES != null) {
139
+ options.maxDevices = num(env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_DEVICES);
140
+ }
141
+ if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_SESSION_TTL_MS != null) {
142
+ options.maxSessionTtlMs = num(
143
+ env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_SESSION_TTL_MS,
144
+ );
145
+ }
146
+ if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_TOKEN_TTL_MS != null) {
147
+ options.maxTokenTtlMs = num(
148
+ env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_TOKEN_TTL_MS,
149
+ );
150
+ }
151
+ if (env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOW_RELAY != null) {
152
+ options.allowRelayPairing = !/^(0|false|no|off)$/i.test(
153
+ String(env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOW_RELAY).trim(),
154
+ );
155
+ }
156
+ return new RemoteSessionPolicy(options);
157
+ }
158
+ }
159
+
160
+ function publicSession(session) {
161
+ return {
162
+ protocolVersion: REMOTE_SESSION_PROTOCOL_VERSION,
163
+ sessionId: session.sessionId,
164
+ agentSessionId: session.agentSessionId,
165
+ name: session.name,
166
+ hostClientId: session.hostClientId,
167
+ createdAt: session.createdAt,
168
+ expiresAt: session.expiresAt,
169
+ memberCount: session.members.size,
170
+ };
171
+ }
172
+
173
+ /** In-memory authorization state for one local CLI process. */
174
+ export class RemoteSessionRegistry {
175
+ constructor(options = {}) {
176
+ this.now = options.now || Date.now;
177
+ this.tokenTtlMs = options.tokenTtlMs || DEFAULT_TOKEN_TTL_MS;
178
+ this.sessionTtlMs = options.sessionTtlMs || DEFAULT_SESSION_TTL_MS;
179
+ this.policy = options.policy || new RemoteSessionPolicy();
180
+ this.sessions = new Map();
181
+ this.tokens = new Map();
182
+ }
183
+
184
+ create({ hostClientId, agentSessionId, name, scopes } = {}) {
185
+ if (!hostClientId) throw new Error("hostClientId is required");
186
+ if (!agentSessionId) throw new Error("agentSessionId is required");
187
+ const now = this.now();
188
+ const session = {
189
+ sessionId: randomUUID(),
190
+ agentSessionId,
191
+ name: name || agentSessionId,
192
+ hostClientId,
193
+ createdAt: now,
194
+ expiresAt: now + this.policy.capSessionTtl(this.sessionTtlMs),
195
+ members: new Map([
196
+ [
197
+ hostClientId,
198
+ {
199
+ clientId: hostClientId,
200
+ scopes: REMOTE_SESSION_SCOPES,
201
+ joinedAt: now,
202
+ },
203
+ ],
204
+ ]),
205
+ };
206
+ this.sessions.set(session.sessionId, session);
207
+ return {
208
+ session: publicSession(session),
209
+ pairing: this.issuePairingToken(session.sessionId, { scopes }),
210
+ };
211
+ }
212
+
213
+ issuePairingToken(sessionId, { scopes } = {}) {
214
+ const session = this.requireSession(sessionId);
215
+ const token = randomBytes(32).toString("base64url");
216
+ const now = this.now();
217
+ const { scopes: applied, narrowed } = this.policy.applyScopes(scopes);
218
+ const grantedScopes = normalizeScopes(applied);
219
+ this.tokens.set(sessionId, {
220
+ digest: hashToken(token),
221
+ scopes: grantedScopes,
222
+ createdAt: now,
223
+ expiresAt: Math.min(
224
+ now + this.policy.capTokenTtl(this.tokenTtlMs),
225
+ session.expiresAt,
226
+ ),
227
+ });
228
+ return {
229
+ token,
230
+ expiresAt: this.tokens.get(sessionId).expiresAt,
231
+ scopes: grantedScopes,
232
+ policyNarrowed: narrowed,
233
+ };
234
+ }
235
+
236
+ join({
237
+ sessionId,
238
+ clientId,
239
+ token,
240
+ via = "direct",
241
+ pushToken = null,
242
+ pushProvider = null,
243
+ } = {}) {
244
+ if (!clientId) throw new Error("clientId is required");
245
+ const session = this.requireSession(sessionId);
246
+ const pairing = this.tokens.get(sessionId);
247
+ if (!pairing || pairing.expiresAt <= this.now()) {
248
+ this.tokens.delete(sessionId);
249
+ throw new Error("Pairing token is missing or expired");
250
+ }
251
+ if (!tokensMatch(token, pairing.digest)) {
252
+ throw new Error("Invalid pairing token");
253
+ }
254
+ // Org policy is enforced only after the token proves the caller is invited,
255
+ // so a device-limit / relay-disabled message never leaks to unauthenticated
256
+ // probes. Existing (non-host) devices count against the limit.
257
+ const deviceCount = [...session.members.values()].filter(
258
+ (member) => member.clientId !== session.hostClientId,
259
+ ).length;
260
+ this.policy.enforceJoin({ deviceCount, via });
261
+ // Pairing credentials are deliberately one-time. The host must explicitly
262
+ // issue another token for each additional device.
263
+ this.tokens.delete(sessionId);
264
+ const member = {
265
+ clientId,
266
+ scopes: pairing.scopes,
267
+ joinedAt: this.now(),
268
+ pushToken: pushToken || null,
269
+ pushProvider: pushToken ? pushProvider || null : null,
270
+ };
271
+ session.members.set(clientId, member);
272
+ return { session: publicSession(session), member: { ...member } };
273
+ }
274
+
275
+ /**
276
+ * A device registers (or refreshes / clears) its own vendor push token after
277
+ * pairing — e.g. once FCM assigns one. Only an existing member may set its own
278
+ * token. A null token clears push for that device.
279
+ */
280
+ registerPush(sessionId, clientId, { token = null, provider = null } = {}) {
281
+ const session = this.requireSession(sessionId);
282
+ const member = session.members.get(clientId);
283
+ if (!member) {
284
+ throw new Error("Device is not paired with this remote session");
285
+ }
286
+ member.pushToken = token || null;
287
+ member.pushProvider = token ? provider || null : null;
288
+ return {
289
+ clientId,
290
+ hasPush: Boolean(member.pushToken),
291
+ provider: member.pushProvider,
292
+ };
293
+ }
294
+
295
+ /** Non-host members that carry a push token, for wake-up dispatch. */
296
+ pushTargets(sessionId, { excludeClientId } = {}) {
297
+ const session = this.requireSession(sessionId);
298
+ const targets = [];
299
+ for (const member of session.members.values()) {
300
+ if (member.clientId === session.hostClientId) continue;
301
+ if (member.clientId === excludeClientId) continue;
302
+ if (!member.pushToken) continue;
303
+ targets.push({
304
+ clientId: member.clientId,
305
+ pushToken: member.pushToken,
306
+ pushProvider: member.pushProvider,
307
+ });
308
+ }
309
+ return targets;
310
+ }
311
+
312
+ authorize(sessionId, clientId, scope) {
313
+ const session = this.requireSession(sessionId);
314
+ const member = session.members.get(clientId);
315
+ if (!member)
316
+ throw new Error("Client is not paired with this remote session");
317
+ if (!member.scopes.includes(scope)) {
318
+ throw new Error(`Remote session scope required: ${scope}`);
319
+ }
320
+ return { session, member };
321
+ }
322
+
323
+ members(sessionId) {
324
+ const session = this.requireSession(sessionId);
325
+ return [...session.members.values()].map((member) => ({ ...member }));
326
+ }
327
+
328
+ /**
329
+ * Host-only view of every paired device on a session. The host flag lets the
330
+ * UI keep the host row un-revocable and label it distinctly.
331
+ */
332
+ listDevices(sessionId, hostClientId) {
333
+ const session = this.requireSession(sessionId);
334
+ if (hostClientId && session.hostClientId !== hostClientId) {
335
+ throw new Error("Only the host can list paired devices");
336
+ }
337
+ return {
338
+ session: publicSession(session),
339
+ devices: [...session.members.values()].map((member) => ({
340
+ clientId: member.clientId,
341
+ scopes: [...member.scopes],
342
+ joinedAt: member.joinedAt,
343
+ isHost: member.clientId === session.hostClientId,
344
+ hasPush: Boolean(member.pushToken),
345
+ pushProvider: member.pushProvider || null,
346
+ })),
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Host-initiated revocation of a single paired device. The host cannot revoke
352
+ * itself (use close() to end the whole session). Returns the removed member so
353
+ * callers can push a revocation notice to that device.
354
+ */
355
+ revokeMember(sessionId, hostClientId, clientId) {
356
+ if (!clientId) throw new Error("clientId is required");
357
+ const session = this.requireSession(sessionId);
358
+ if (session.hostClientId !== hostClientId) {
359
+ throw new Error("Only the host can revoke devices");
360
+ }
361
+ if (clientId === session.hostClientId) {
362
+ throw new Error("Cannot revoke the host device");
363
+ }
364
+ const member = session.members.get(clientId);
365
+ if (!member)
366
+ throw new Error("Device is not paired with this remote session");
367
+ session.members.delete(clientId);
368
+ return { session: publicSession(session), member: { ...member } };
369
+ }
370
+
371
+ findHosted(agentSessionId, hostClientId) {
372
+ const matches = [];
373
+ for (const session of this.sessions.values()) {
374
+ if (
375
+ session.agentSessionId === agentSessionId &&
376
+ session.hostClientId === hostClientId &&
377
+ session.expiresAt > this.now()
378
+ ) {
379
+ matches.push(session);
380
+ }
381
+ }
382
+ return matches;
383
+ }
384
+
385
+ removeClient(clientId) {
386
+ const affected = [];
387
+ for (const [sessionId, session] of this.sessions) {
388
+ if (!session.members.delete(clientId)) continue;
389
+ if (session.hostClientId === clientId) {
390
+ this.sessions.delete(sessionId);
391
+ this.tokens.delete(sessionId);
392
+ affected.push({ sessionId, closed: true });
393
+ } else {
394
+ affected.push({ sessionId, closed: false });
395
+ }
396
+ }
397
+ return affected;
398
+ }
399
+
400
+ close(sessionId, clientId) {
401
+ const session = this.requireSession(sessionId);
402
+ if (session.hostClientId !== clientId) {
403
+ throw new Error("Only the host can close a Remote Session");
404
+ }
405
+ this.sessions.delete(sessionId);
406
+ this.tokens.delete(sessionId);
407
+ return publicSession(session);
408
+ }
409
+
410
+ requireSession(sessionId) {
411
+ const session = this.sessions.get(sessionId);
412
+ if (!session) throw new Error("Remote session not found");
413
+ if (session.expiresAt <= this.now()) {
414
+ this.sessions.delete(sessionId);
415
+ this.tokens.delete(sessionId);
416
+ throw new Error("Remote session expired");
417
+ }
418
+ return session;
419
+ }
420
+ }
package/src/index.js CHANGED
@@ -17,6 +17,8 @@ import { registerAskCommand } from "./commands/ask.js";
17
17
  import { registerLlmCommand } from "./commands/llm.js";
18
18
  import { registerHubCommand } from "./commands/hub.js";
19
19
  import { registerAndroidCommand } from "./commands/android.js";
20
+ import { registerEvalCommand } from "./commands/eval.js";
21
+ import { registerTeamCommand } from "./commands/team.js";
20
22
  import {
21
23
  registerAgentCommand,
22
24
  registerSubAgentV2Command,
@@ -58,6 +60,7 @@ import { registerRCacheCommand } from "./commands/rcache.js";
58
60
  import { registerSessionCommand } from "./commands/session.js";
59
61
  import { registerCostCommand } from "./commands/cost.js";
60
62
  import { registerContextCommand } from "./commands/context.js";
63
+ import { registerCodeIntelCommand } from "./commands/codeintel.js";
61
64
  import { registerCheckpointCommand } from "./commands/checkpoint.js";
62
65
  import { registerGoalCommand } from "./commands/goal.js";
63
66
  import { registerCommandCommand } from "./commands/command.js";
@@ -428,6 +431,8 @@ export function createProgram(opts = {}) {
428
431
  registerHubCommand(program);
429
432
  registerAndroidCommand(program);
430
433
  registerAgentCommand(program);
434
+ registerEvalCommand(program);
435
+ registerTeamCommand(program);
431
436
  registerSubAgentV2Command(program);
432
437
  registerExecBackendV2Command(program);
433
438
  registerTodoV2Command(program);
@@ -468,6 +473,7 @@ export function createProgram(opts = {}) {
468
473
  registerSessionCommand(program);
469
474
  registerCostCommand(program);
470
475
  registerContextCommand(program);
476
+ registerCodeIntelCommand(program);
471
477
  registerCheckpointCommand(program);
472
478
  registerGoalCommand(program);
473
479
  registerCommandCommand(program);
@@ -42,4 +42,6 @@ export {
42
42
  _toAnthropicMessages,
43
43
  _anthropicThinkingParams,
44
44
  _normalizeAnthropicResponse,
45
+ _resetFileFreshness,
46
+ disposeSharedCodeIntel,
45
47
  } from "../runtime/agent-core.js";
@@ -1,29 +1,107 @@
1
1
  /** OS-isolated shell execution for the coding agent. */
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
+ import { proxyEnv } from "./sandbox-egress-proxy.js";
4
5
 
5
6
  export const DEFAULT_SANDBOX_IMAGE = "node:22-bookworm-slim";
6
7
  export const _deps = { spawnSync };
7
8
 
9
+ function stringList(value) {
10
+ if (!Array.isArray(value)) return [];
11
+ return [
12
+ ...new Set(
13
+ value.map((entry) => String(entry || "").trim()).filter(Boolean),
14
+ ),
15
+ ];
16
+ }
17
+
18
+ function resolvePolicyPaths(entries, cwd) {
19
+ return stringList(entries).map((entry) => {
20
+ if (entry.startsWith("~/")) {
21
+ return path.resolve(
22
+ process.env.HOME || process.env.USERPROFILE || "",
23
+ entry.slice(2),
24
+ );
25
+ }
26
+ return path.resolve(cwd, entry);
27
+ });
28
+ }
29
+
30
+ export function normalizeSandboxPolicy(settings = {}, cwd = process.cwd()) {
31
+ const filesystem = settings.filesystem || {};
32
+ const network = settings.network || {};
33
+ return {
34
+ allowRead: resolvePolicyPaths(filesystem.allowRead, cwd),
35
+ denyRead: resolvePolicyPaths(filesystem.denyRead, cwd),
36
+ allowWrite: resolvePolicyPaths(filesystem.allowWrite, cwd),
37
+ denyWrite: resolvePolicyPaths(filesystem.denyWrite, cwd),
38
+ allowedDomains: stringList(network.allowedDomains),
39
+ deniedDomains: stringList(network.deniedDomains),
40
+ excludedCommands: stringList(settings.excludedCommands),
41
+ allowUnsandboxedCommands: settings.allowUnsandboxedCommands !== false,
42
+ failIfUnavailable: settings.failIfUnavailable === true,
43
+ };
44
+ }
45
+
8
46
  export function normalizeAgentSandbox(value, options = {}) {
9
- if (!value) return null;
47
+ const settings = options.settings || {};
48
+ if (!value && settings.enabled !== true) return null;
49
+ if (value === false || settings.enabled === false) return null;
10
50
  const image =
11
51
  typeof value === "string" && value.trim() && value !== "true"
12
52
  ? value.trim()
13
53
  : DEFAULT_SANDBOX_IMAGE;
14
54
  return {
15
- engine: "docker",
16
- image,
55
+ engine: settings.engine || "docker",
56
+ image: settings.image || image,
17
57
  cwd: path.resolve(options.cwd || process.cwd()),
18
- network: options.network === true,
58
+ network: options.network === true || settings.network === true,
59
+ policy: normalizeSandboxPolicy(settings, options.cwd || process.cwd()),
19
60
  };
20
61
  }
21
62
 
22
63
  export function executeSandboxedShell(command, sandbox, options = {}) {
23
- if (!sandbox || sandbox.engine !== "docker") {
64
+ if (!sandbox || !["docker", "bubblewrap"].includes(sandbox.engine)) {
24
65
  throw new Error("A supported agent sandbox configuration is required");
25
66
  }
26
67
  const hostCwd = path.resolve(options.cwd || sandbox.cwd);
68
+ const policy = sandbox.policy || normalizeSandboxPolicy({}, hostCwd);
69
+ // An egress proxy (see sandbox-egress-proxy.js) ENFORCES the domain allow/deny
70
+ // policy: the caller starts it and passes `{ env }` here, and the sandboxed
71
+ // process routes its HTTP(S) traffic through it. Without a proxy, a
72
+ // domain-restricted network request has no enforcement point, so we refuse
73
+ // rather than grant unrestricted access.
74
+ const egress = options.egressProxy || null;
75
+ if (
76
+ (policy.allowedDomains.length || policy.deniedDomains.length) &&
77
+ sandbox.network &&
78
+ !egress
79
+ ) {
80
+ return {
81
+ stdout: "",
82
+ stderr:
83
+ "Domain-restricted sandbox networking requires a configured sandbox proxy; refusing unrestricted network access",
84
+ exitCode: 1,
85
+ failedToStart: true,
86
+ };
87
+ }
88
+ if (sandbox.engine === "bubblewrap") {
89
+ return executeBubblewrapShell(command, sandbox, options, hostCwd, policy);
90
+ }
91
+ if (
92
+ policy.allowRead.length ||
93
+ policy.denyRead.length ||
94
+ policy.allowWrite.length ||
95
+ policy.denyWrite.length
96
+ ) {
97
+ return {
98
+ stdout: "",
99
+ stderr:
100
+ "The Docker sandbox backend cannot enforce fine-grained filesystem policy; use engine=bubblewrap",
101
+ exitCode: 1,
102
+ failedToStart: true,
103
+ };
104
+ }
27
105
  const args = ["run", "--rm", "--init"];
28
106
  if (!sandbox.network) args.push("--network", "none");
29
107
  args.push("--mount", `type=bind,source=${hostCwd},target=/workspace`);
@@ -31,6 +109,17 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
31
109
  if (process.platform !== "win32" && process.getuid && process.getgid) {
32
110
  args.push("--user", `${process.getuid()}:${process.getgid()}`);
33
111
  }
112
+ // Route egress through the host proxy so the domain policy is enforced. The
113
+ // container reaches the host proxy via host.docker.internal (mapped to the
114
+ // gateway on Linux via host-gateway); the caller builds `egress.env` with that
115
+ // hostname (proxyEnv(port, "host.docker.internal")).
116
+ if (egress && egress.port && sandbox.network) {
117
+ args.push("--add-host", "host.docker.internal:host-gateway");
118
+ const penv = proxyEnv(egress.port, "host.docker.internal");
119
+ for (const [k, v] of Object.entries(penv)) {
120
+ args.push("--env", `${k}=${v}`);
121
+ }
122
+ }
34
123
  const env = options.env || {};
35
124
  for (const key of ["CLAUDECODE", "CC_SESSION_ID", "CLAUDE_CODE_SESSION_ID"]) {
36
125
  if (env[key] != null) args.push("--env", `${key}=${env[key]}`);
@@ -61,6 +150,65 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
61
150
  };
62
151
  }
63
152
 
153
+ function executeBubblewrapShell(command, sandbox, options, hostCwd, policy) {
154
+ const args = [
155
+ "--die-with-parent",
156
+ "--new-session",
157
+ "--unshare-all",
158
+ "--ro-bind",
159
+ "/",
160
+ "/",
161
+ "--bind",
162
+ hostCwd,
163
+ hostCwd,
164
+ "--chdir",
165
+ hostCwd,
166
+ "--proc",
167
+ "/proc",
168
+ "--dev",
169
+ "/dev",
170
+ "--tmpfs",
171
+ "/tmp",
172
+ ];
173
+ if (sandbox.network) args.push("--share-net");
174
+ for (const target of policy.allowWrite) args.push("--bind", target, target);
175
+ for (const target of policy.denyWrite) args.push("--ro-bind", target, target);
176
+ for (const target of policy.denyRead) args.push("--tmpfs", target);
177
+ args.push("--", "sh", "-lc", String(command || ""));
178
+ // bwrap `--share-net` shares the host network namespace, so the egress proxy
179
+ // is reachable at 127.0.0.1 directly — merge its env into the child's.
180
+ const egress = options.egressProxy || null;
181
+ const bwrapEnv =
182
+ egress && egress.port && sandbox.network
183
+ ? { ...(options.env || {}), ...proxyEnv(egress.port, "127.0.0.1") }
184
+ : options.env;
185
+ const result = _deps.spawnSync("bwrap", args, {
186
+ cwd: hostCwd,
187
+ env: bwrapEnv,
188
+ encoding: "utf8",
189
+ timeout: options.timeout,
190
+ maxBuffer: options.maxBuffer,
191
+ windowsHide: true,
192
+ });
193
+ if (result.error) {
194
+ return {
195
+ stdout: result.stdout || "",
196
+ stderr:
197
+ result.error.code === "ENOENT"
198
+ ? "bubblewrap is not installed"
199
+ : result.error.message,
200
+ exitCode: typeof result.status === "number" ? result.status : 1,
201
+ failedToStart: true,
202
+ };
203
+ }
204
+ return {
205
+ stdout: result.stdout || "",
206
+ stderr: result.stderr || "",
207
+ exitCode: typeof result.status === "number" ? result.status : 1,
208
+ signal: result.signal || null,
209
+ };
210
+ }
211
+
64
212
  export function sandboxSummary(sandbox) {
65
213
  if (!sandbox) return null;
66
214
  return {
@@ -68,5 +216,13 @@ export function sandboxSummary(sandbox) {
68
216
  image: sandbox.image,
69
217
  network: sandbox.network ? "enabled" : "disabled",
70
218
  workspace: "read-write",
219
+ policy: {
220
+ additionalReadPaths: sandbox.policy?.allowRead?.length || 0,
221
+ additionalWritePaths: sandbox.policy?.allowWrite?.length || 0,
222
+ networkRestricted:
223
+ Boolean(sandbox.policy?.allowedDomains?.length) ||
224
+ Boolean(sandbox.policy?.deniedDomains?.length),
225
+ failIfUnavailable: sandbox.policy?.failIfUnavailable === true,
226
+ },
71
227
  };
72
228
  }