chainlesschain 0.162.184 → 0.162.186

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 (192) hide show
  1. package/bin/chainlesschain.js +21 -5
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DzAl5fGx.js → AIOps-DTXiO-TB.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DAHtMGeC.js → ActionButton-BbmDW-Pz.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BHQaexv5.js → Analytics-CSfgXJuD.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-Phz_pD0n.js → AppLayout-BS0FFLb7.js} +4 -4
  8. package/src/assets/web-panel/assets/{Artifacts-DHt47kCA.js → Artifacts-B19WFPK2.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit--Y_8FULl.js → Audit-CQhZBi7n.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-N1zchH7D.js → BackgroundAgents-BOLZCpGH.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-B_BoyN30.js → Backup-Dz3R_ePD.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-Gl4uQHcT.js → BaseInput-CYfgMpax.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-Cg-eIyAZ.js → Chat-5CymMr1j.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BzeUi3JH.js → ChatBubbleRenderer-B2A_TJtl.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-D0MGR2wA.js → Checkbox-CDXHVYcu.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BdObJEFn.js → Codegen-uq9Z9oiS.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col--ghxXT3z.js → Col-BuW1K_65.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BUFeXOqz.js → Community-Bbg-PvRY.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DhQO2_Mr.js → Compact-co0pPPTw.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-3HoTqKYd.js → Compliance-B-pFAv7z.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-B3zmO4VW.js → Cowork-54ZQS4gC.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-CJDrZOcc.js → Cron-B1BF2Seq.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-SMl15obd.js → Crosschain-Dey6qQTj.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-CgTrTWrl.js → DID-ltERvfAj.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-dXFKaKol.js → Dashboard-DuWxOcqN.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-qQHEhCvI.js → Dropdown-CrZ8ekTo.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-C_ej0MWH.js → EmailListRenderer-fIyHXhUS.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D6ajoE7I.js → FamilyGuardDashboard-BtRyrRPm.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-Cio2y49h.js → Federation-EUC6Wydj.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-ClJz1cYp.js → FormItemContext-BOpka2G-.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-Dr_8Maca.js → GenericCardRenderer-BXtrrYHn.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-CevLwp5u.js → Git-8WMb7PiR.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-CW9YyQUY.js → Governance-DNWOeDTV.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-CQqeAyER.js → Inference-Bne9b_Lb.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-cuGVxHrw.js → KnowledgeGraph-Cp-6Us9b.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DIuHri5d.js → Logs-Ck-uSPAx.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-R91Qw5JA.js → MarkdownRenderer-DxjsdAXw.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-CY1VhFGx.js → Marketplace-Vdq7brrJ.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-BBAbi1vt.js → McpTools-ggLnw4hY.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-BWYGHxJA.js → Memory-BKIjNVCU.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CPIFkW7A.js → MobileBridge-BElCY25a.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-DQyWxCho.js → MobileProjects-DpLgovdH.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-Bo3GnQbx.js → Mtc-bNdrF_Zh.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-C8-c4rSW.js → MtcAudit-Dw8w1Cqk.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-ChqMTtoq.js → Multisig-CfGKvlqc.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-C48TyhvI.js → NLProgramming-D6OJf7YN.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-zogUFWxp.js → Notes-B3n_LzIS.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-v-K9XpSB.js → NotificationSettings-Uu1V_ICM.js} +1 -1
  49. package/src/assets/web-panel/assets/OrderTableRenderer-aGyWwWcp.js +1 -0
  50. package/src/assets/web-panel/assets/{Organization-Boi1dQfR.js → Organization-qjFFmuwK.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-DM5YWwgA.js → Overflow-J4ZgnwZz.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-C57LKpKK.js → P2P-YEZF-uVH.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BFkPqu6Q.js → PdhVaultBrowser-yqx7fwqk.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-D6fB9YSp.js → Permissions-DdEjlZ02.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-Bp7EefEo.js → PersonalDataHub-DbAPYvns.js} +3 -3
  56. package/src/assets/web-panel/assets/{Pipeline-BVln3hZf.js → Pipeline-Bxlk_ZJU.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-Bf8P3nF6.js → Privacy-CpE-05tz.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-DM6wA2E2.js → ProjectInit-DWlhcGHT.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CGbc5Iho.js → ProjectSettings-BnH6BiTQ.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-CHw2fXDB.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-B2kJcxS6.js → Providers-Bdz_sTyb.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-Cxu7dXYy.js → QrScannerModal-BHh2b3pH.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DTlrTPms.js → QuickAsk-D84M09pe.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cm3X-cCj.js → Recommend-Czc30K6M.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-DNb2kjvt.js → RemoteSession-q_TU5qxt.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-CZUFFjc1.js → Reputation-CnTce4WM.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DAPgloxA.js → Row-C60UFLay.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-dsx9S793.js → RssFeed-BOUFEu9_.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DnnC8kz-.js → Search-D4__blE-.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-VuZknRMK.js → Security-BoOm7_JK.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-B3XNX8Vx.js → Services-AvzrW6HA.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-Dl1nVtCj.js → Skeleton-BZlrHozi.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-CKaMLuk0.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-HzU56n1z.js → Sla-DYCNJF8h.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-BvCSU6d2.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-DWadz7Sl.js → SyncSettings-B6tW8dVO.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-oNC_2NVz.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-BBAgvrlM.js → Templates-DvLPvvjL.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BsbbvGN8.js → Tenant-BDt9UBn_.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-uscpZI4q.js → Terminal-ddCUnH-X.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-mTyjNE6r.js → TimelineRenderer-CA4sDL47.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Bz97oBgs.js → Tokens-By03hnVs.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BTRKC140.js → Trigger-Dz2mH-ZU.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-B4A_KuyX.js → Trust-DShSWHeJ.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DES9TmLU.js → UkeySign-JN8lP3gt.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-ClXs7Qxp.js → VideoEditing-BgPy4gV3.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-IzZqFz-7.js → Wallet-CQGEsXw_.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BtOP04g_.js → WebAuthn-D8xR-ds_.js} +3 -3
  89. package/src/assets/web-panel/assets/{WorkflowEditor-_0cqnopY.js → WorkflowEditor-Df61_p1p.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-ax7193uU.js → chat-fdPby427.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-Bu4WGJah.js → colors-Dimqhufo.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-BX1dlTW0.js → compact-item-DHayKcqZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-CktA_Dj7.js → createContext-hZUhypRH.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-ePxoBp92.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-D5wd817l.js → hasIn-Dw7zC_dV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-ytp2H5Zx.js → index--DVpUpz3.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-tYPSfGbd.js → index-BHJUptSx.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-A4uBPQCz.js → index-BIxxvaGN.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DmVUeWUk.js → index-BM5BNqp9.js} +1 -1
  100. package/src/assets/web-panel/assets/index-BTBDdCoo.js +1 -0
  101. package/src/assets/web-panel/assets/{index-Ci3y1ZAv.js → index-BVHdkz8F.js} +17 -17
  102. package/src/assets/web-panel/assets/{index-Cv7bVWJ5.js → index-BYdNwhkv.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CUForzsG.js → index-Bhmpp3cm.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C8DkWyNr.js → index-BuMSdwno.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BRfXoRuh.js → index-BwnUluxu.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-o14NNnxw.js → index-C1yMYKi-.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C8tLR2TG.js → index-C8XXPz2n.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-5PYYkr8S.js → index-CBQK-LEF.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DkD1K-fF.js → index-CIOE8tt6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CjEJjl5C.js → index-CPZ3U5iH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D9op8poE.js → index-CTiVrnpR.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DYo9S5OT.js → index-CcpDboUd.js} +1 -1
  113. package/src/assets/web-panel/assets/index-CfOGx8Ns.js +1 -0
  114. package/src/assets/web-panel/assets/{index-D-o4Rwjk.js → index-CsCo_A0I.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-h3KJZr95.js → index-CuaVa8tX.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DF1TysIF.js → index-CvKHpaao.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CfzxR61U.js → index-D7jQEwB6.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Db3CpWTr.js → index-DAjvkQ4K.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DmPJ6T6F.js → index-DC2R4zvM.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C3HmzCLt.js → index-DJP7TjWm.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BAtBNfkr.js → index-DSlda4-6.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D_cycOET.js → index-DVM_KgzF.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Dzdr0zDf.js → index-DX1DwDFI.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-bFCaM7Eq.js → index-DnwDYBnG.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Bad2CYeH.js → index-DxoPzPHy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-iHcPtQ2e.js → index-DyEhrvpm.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-jzlzm4VK.js → index-DzJXkoZs.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CP8ELJBc.js → index-JE6Udz2r.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CeVZaJSC.js → index-Urt2R5aE.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BLLsUNDc.js → index-cEnkyHtX.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BcGdL3sG.js → index-j0pxzOu7.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-PBLzwYnu.js → index-lPjBQJCy.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CYRE0NJA.js → index-ulvBurtw.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BQtoRyTO.js → index-y5LHQ5Ck.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-D2Iut83B.js → initDefaultProps-B4pR3px5.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-BuaJVdK9.js → motion-CE7dWPPs.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-CaEuM92y.js → move-Hw8cUObc.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-wuKNBwys.js → mtc-parser-CvRjE4Fa.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-7a3eklaq.js → omit-CkFeLVre.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-Dn4z7KvJ.js → pickAttrs-5ZMqmbbb.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-DVn_qT4-.js → placementArrow-BZ4oeZ0q.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-Dodz1OMQ.js → responsiveObserve-lwoiX_Us.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-CxoGskbp.js → slide-D_as37l8.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-DBh_epQH.js → statusUtils-BsA-4Bs7.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-RXQ4gG5P.js → styleChecker-BVar4sLQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-1Wxm0HEG.js → useFlexGapSupport-j1IwVpGd.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CI_ySNxJ.js → useFs-7Q9bEnUp.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-C6973rZP.js → usePersonalDataHub-BBwTIRMd.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-CalynwKr.js → vnode-C7Dk-M8w.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CM1cd-Q0.js → zoom-D3UHaTFB.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/eval.js +7 -0
  153. package/src/commands/status.js +55 -0
  154. package/src/commands/team.js +31 -6
  155. package/src/data/changelog.json +26 -0
  156. package/src/gateways/terminal/PtyManager.js +45 -15
  157. package/src/gateways/ws/ws-agent-handler.js +15 -1
  158. package/src/gateways/ws/ws-session-gateway.js +88 -0
  159. package/src/harness/background-task-command-runner.js +234 -0
  160. package/src/harness/background-task-manager.js +150 -20
  161. package/src/harness/background-task-worker.js +17 -11
  162. package/src/lazy-dispatch.js +12 -1
  163. package/src/lib/background-agent-supervisor.js +46 -0
  164. package/src/lib/event-runtime-host.js +12 -10
  165. package/src/lib/hook-runner.cjs +7 -1
  166. package/src/lib/hook-runner.js +2 -0
  167. package/src/lib/hooks-v2-runtime.js +94 -6
  168. package/src/lib/hooks-v2-workspace-context.js +258 -0
  169. package/src/lib/lsp/__tests__/lsp-client.test.js +1 -4
  170. package/src/lib/observability/index.js +191 -19
  171. package/src/lib/observability/otlp-exporter.js +1209 -207
  172. package/src/lib/otlp-exporter.js +8 -36
  173. package/src/lib/plugin-runtime/bin.js +20 -4
  174. package/src/lib/plugin-runtime/sandbox-policy.js +1 -4
  175. package/src/lib/process-execution-broker/index.js +706 -40
  176. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +212 -2
  177. package/src/lib/process-execution-broker/linux-generic-bwrap.js +124 -6
  178. package/src/lib/process-execution-broker/platform-sandbox.js +6 -2
  179. package/src/program-base.js +9 -2
  180. package/src/repl/agent-repl.js +8 -0
  181. package/src/runtime/agent-core.js +272 -38
  182. package/src/runtime/headless-runner.js +56 -22
  183. package/src/runtime/headless-stream.js +8 -0
  184. package/src/workers/background-agent-worker.js +5 -11
  185. package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +0 -1
  186. package/src/assets/web-panel/assets/Projects-D0p9EOxm.js +0 -1
  187. package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +0 -1
  188. package/src/assets/web-panel/assets/SpeechSettings-CRle7Jl2.js +0 -1
  189. package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +0 -1
  190. package/src/assets/web-panel/assets/devWarning-x877iKKS.js +0 -1
  191. package/src/assets/web-panel/assets/index-D7pfPouB.js +0 -1
  192. package/src/assets/web-panel/assets/index-e4i-VLBd.js +0 -1
@@ -14,6 +14,7 @@ import { spawnSync as nativeSpawnSync } from "node:child_process";
14
14
  import {
15
15
  LINUX_GENERIC_BWRAP_BACKEND,
16
16
  LINUX_GENERIC_CONTRACT_KIND,
17
+ LINUX_GENERIC_PTY_LAUNCHER_PATH,
17
18
  planLinuxGenericBubblewrap,
18
19
  } from "./linux-generic-bwrap.js";
19
20
 
@@ -345,8 +346,12 @@ function parseMountInfo(runtime) {
345
346
  }
346
347
  const root = decodeMountInfoPath(fields[3]);
347
348
  const mountPoint = decodeMountInfoPath(fields[4]);
349
+ const optionalFields = fields.slice(6, separator);
348
350
  const fsType = fields[separator + 1];
349
351
  if (
352
+ !fields[5] ||
353
+ fields[5].includes("\0") ||
354
+ optionalFields.some((field) => !field || field.includes("\0")) ||
350
355
  !fsType ||
351
356
  fsType.includes("\0") ||
352
357
  fields.slice(separator + 1).some((field) => !field)
@@ -361,6 +366,7 @@ function parseMountInfo(runtime) {
361
366
  root,
362
367
  mountPoint,
363
368
  fsType,
369
+ optionalFields: Object.freeze(optionalFields),
364
370
  lineDigest: sha256(line),
365
371
  }),
366
372
  );
@@ -399,7 +405,75 @@ function sameDirectoryIdentity(left, right) {
399
405
  );
400
406
  }
401
407
 
402
- function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
408
+ function bindSourceMountTopology(entries, bindSources) {
409
+ const normalizedSources = bindSources
410
+ .map((source) => ({
411
+ kind: source.kind,
412
+ source: source.source,
413
+ recursive: source.recursive === true,
414
+ }))
415
+ .sort(
416
+ (left, right) =>
417
+ left.source.localeCompare(right.source) ||
418
+ left.kind.localeCompare(right.kind),
419
+ );
420
+ const sourceKeys = new Set();
421
+ for (const source of normalizedSources) {
422
+ if (
423
+ typeof source.kind !== "string" ||
424
+ !source.kind ||
425
+ typeof source.source !== "string" ||
426
+ !path.posix.isAbsolute(source.source) ||
427
+ path.posix.normalize(source.source) !== source.source
428
+ ) {
429
+ throw new Error("bind_source_mount_topology_invalid");
430
+ }
431
+ const key = `${source.kind}\0${source.source}`;
432
+ if (sourceKeys.has(key)) {
433
+ throw new Error("bind_source_mount_topology_duplicate");
434
+ }
435
+ sourceKeys.add(key);
436
+ }
437
+
438
+ let importedMountEntryCount = 0;
439
+ const sourceMountBindings = normalizedSources.map((source) => {
440
+ const mountedBy = containingMount(entries, source.source);
441
+ const importedMounts = new Map([[mountedBy.mountId, mountedBy]]);
442
+ if (source.recursive) {
443
+ for (const entry of entries) {
444
+ if (isWithin(source.source, entry.mountPoint)) {
445
+ importedMounts.set(entry.mountId, entry);
446
+ }
447
+ }
448
+ }
449
+ const imported = [...importedMounts.values()];
450
+ if (imported.some((entry) => entry.optionalFields.length !== 0)) {
451
+ throw new Error("bind_source_mount_propagation_not_private");
452
+ }
453
+ importedMountEntryCount += imported.length;
454
+ return {
455
+ ...source,
456
+ containingMount: mountedBy.lineDigest,
457
+ importedMountEntryDigests: imported
458
+ .map((entry) => entry.lineDigest)
459
+ .sort(),
460
+ };
461
+ });
462
+ return {
463
+ sourceMountSetDigest: sha256(stableJson(normalizedSources)),
464
+ sourceMountCount: normalizedSources.length,
465
+ importedMountEntryCount,
466
+ sourceMountBindings,
467
+ sourceMountPropagationPrivateAtAttestation: true,
468
+ };
469
+ }
470
+
471
+ function workspaceMountTopology(
472
+ runtime,
473
+ workspaceRoot,
474
+ workspaceIdentity,
475
+ bindSources,
476
+ ) {
403
477
  const entries = parseMountInfo(runtime);
404
478
  const descendants = entries.filter(
405
479
  (entry) =>
@@ -498,6 +572,7 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
498
572
  const lineage = entries.filter((entry) =>
499
573
  isWithin(entry.mountPoint, workspaceRoot),
500
574
  );
575
+ const bindSourceTopology = bindSourceMountTopology(entries, bindSources);
501
576
  const binding = {
502
577
  version: MOUNT_TOPOLOGY_VERSION,
503
578
  source: "proc-self-mountinfo",
@@ -512,6 +587,7 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
512
587
  forbiddenIdentityDigests: forbiddenIdentities
513
588
  .map((entry) => sha256(stableJson(entry)))
514
589
  .sort(),
590
+ ...bindSourceTopology,
515
591
  strictDescendantMountsAtAttestation: 0,
516
592
  recursiveBind: true,
517
593
  mountTopologyAtomic: false,
@@ -525,6 +601,11 @@ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
525
601
  filesystemEntryCount: binding.filesystemEntryDigests.length,
526
602
  aliasCount: binding.aliasDigests.length,
527
603
  forbiddenIdentityCount: binding.forbiddenIdentityDigests.length,
604
+ sourceMountSetDigest: binding.sourceMountSetDigest,
605
+ sourceMountCount: binding.sourceMountCount,
606
+ importedMountEntryCount: binding.importedMountEntryCount,
607
+ sourceMountPropagationPrivateAtAttestation:
608
+ binding.sourceMountPropagationPrivateAtAttestation,
528
609
  strictDescendantMountsAtAttestation:
529
610
  binding.strictDescendantMountsAtAttestation,
530
611
  rootAliasAttested: true,
@@ -697,10 +778,20 @@ function createTrustedResources(
697
778
  const cwdPins = openPair(contract.workingDirectory, {
698
779
  directory: true,
699
780
  });
700
- const initialWorkspaceMountTopology = workspaceMountTopology(
781
+ // Fail before executing any helper when the writable recursive source is
782
+ // already propagation-capable. The complete exact source set is attested
783
+ // after the actual system and /etc descriptors have been acquired.
784
+ workspaceMountTopology(
701
785
  runtime,
702
786
  contract.workspaceRoot,
703
787
  workspace.identity,
788
+ [
789
+ {
790
+ kind: "workspace",
791
+ source: contract.workspaceRoot,
792
+ recursive: true,
793
+ },
794
+ ],
704
795
  );
705
796
 
706
797
  const supervisor = openPair(BWRAP_PATH, {
@@ -771,6 +862,76 @@ function createTrustedResources(
771
862
  throw error;
772
863
  }
773
864
 
865
+ let ptyLauncher = null;
866
+ let ptyLauncherBytes = 0;
867
+ let ptyLauncherDigest = null;
868
+ if (sandboxOpts?.pty === true) {
869
+ ptyLauncher = openPair(LINUX_GENERIC_PTY_LAUNCHER_PATH, {
870
+ directory: false,
871
+ rootRequired: true,
872
+ executable: true,
873
+ });
874
+ ptyLauncherBytes = Number(runtime.fs.fstatSync(ptyLauncher.probeFd).size);
875
+ const probeDigest = hashOpenFile(
876
+ runtime,
877
+ ptyLauncher.probeFd,
878
+ ptyLauncherBytes,
879
+ );
880
+ ptyLauncherDigest = hashOpenFile(
881
+ runtime,
882
+ ptyLauncher.finalFd,
883
+ ptyLauncherBytes,
884
+ );
885
+ if (probeDigest !== ptyLauncherDigest) {
886
+ throw new Error("pty_launcher_digest_mismatch");
887
+ }
888
+ ptyLauncher.identity = Object.freeze({
889
+ ...ptyLauncher.identity,
890
+ sha256: ptyLauncherDigest,
891
+ bytes: ptyLauncherBytes,
892
+ });
893
+
894
+ const launcherCapabilityFd = runtime.fs.openSync(
895
+ LINUX_GENERIC_PTY_LAUNCHER_PATH,
896
+ openFlags({ directory: false }),
897
+ );
898
+ ownedFds.add(launcherCapabilityFd);
899
+ const launcherCapabilityStat = runtime.fs.fstatSync(launcherCapabilityFd);
900
+ if (
901
+ !statMatchesIdentity(launcherCapabilityStat, ptyLauncher.identity, {
902
+ directory: false,
903
+ }) ||
904
+ hashOpenFile(runtime, launcherCapabilityFd, ptyLauncherBytes) !==
905
+ ptyLauncherDigest
906
+ ) {
907
+ throw new Error("pty_launcher_capability_reader_unattested");
908
+ }
909
+ const launcherCapabilityResult = runtime.spawnSync(
910
+ "/proc/self/fd/3",
911
+ ["--help"],
912
+ {
913
+ shell: false,
914
+ encoding: "utf8",
915
+ stdio: ["ignore", "pipe", "pipe", launcherCapabilityFd],
916
+ timeout: 10_000,
917
+ env: { PATH: "/usr/bin:/bin", LANG: "C", LC_ALL: "C" },
918
+ },
919
+ );
920
+ closeFd(launcherCapabilityFd);
921
+ const launcherHelp =
922
+ `${String(launcherCapabilityResult?.stdout || "")}\n` +
923
+ String(launcherCapabilityResult?.stderr || "");
924
+ if (
925
+ launcherCapabilityResult?.error ||
926
+ launcherCapabilityResult?.status !== 0 ||
927
+ !launcherHelp.split(/\s+/).includes("--ctty")
928
+ ) {
929
+ const error = new Error("pty_launcher_capability_probe_failed");
930
+ error.code = "LINUX_GENERIC_BWRAP_UNAVAILABLE";
931
+ throw error;
932
+ }
933
+ }
934
+
774
935
  const system = [];
775
936
  const systemSymlinks = [];
776
937
  for (const destination of SYSTEM_DESTINATIONS) {
@@ -824,6 +985,33 @@ function createTrustedResources(
824
985
  identity: pair.identity,
825
986
  });
826
987
  }
988
+ const sourceMountSources = Object.freeze([
989
+ Object.freeze({
990
+ kind: "workspace",
991
+ source: contract.workspaceRoot,
992
+ recursive: true,
993
+ }),
994
+ ...system.map((entry) =>
995
+ Object.freeze({
996
+ kind: "system",
997
+ source: entry.destination,
998
+ recursive: true,
999
+ }),
1000
+ ),
1001
+ ...etc.map((entry) =>
1002
+ Object.freeze({
1003
+ kind: "etc",
1004
+ source: entry.destination,
1005
+ recursive: false,
1006
+ }),
1007
+ ),
1008
+ ]);
1009
+ const initialWorkspaceMountTopology = workspaceMountTopology(
1010
+ runtime,
1011
+ contract.workspaceRoot,
1012
+ workspace.identity,
1013
+ sourceMountSources,
1014
+ );
827
1015
 
828
1016
  const filter = buildNetworkSeccompFilter(runtime.arch);
829
1017
  const openSeccomp = (phase) => {
@@ -969,6 +1157,7 @@ function createTrustedResources(
969
1157
  issued.origin === (spawnOpts?.origin || "unknown") &&
970
1158
  issued.command === command &&
971
1159
  issued.sync === (sandboxOpts?.sync === true) &&
1160
+ issued.pty === (sandboxOpts?.pty === true) &&
972
1161
  issued.shell === false &&
973
1162
  Array.isArray(stdio) &&
974
1163
  issued.stdio.length === stdio.length &&
@@ -986,6 +1175,7 @@ function createTrustedResources(
986
1175
  runtime,
987
1176
  contract.workspaceRoot,
988
1177
  workspace.identity,
1178
+ sourceMountSources,
989
1179
  );
990
1180
  } catch {
991
1181
  return false;
@@ -1001,6 +1191,14 @@ function createTrustedResources(
1001
1191
  !descriptorAttested(supervisor.finalFd, supervisor.identity, false) ||
1002
1192
  hashOpenFile(runtime, supervisor.finalFd, supervisorBytes) !==
1003
1193
  supervisorFinalDigest ||
1194
+ (sandboxOpts?.pty === true &&
1195
+ (!descriptorAttested(
1196
+ ptyLauncher?.finalFd,
1197
+ ptyLauncher?.identity,
1198
+ false,
1199
+ ) ||
1200
+ hashOpenFile(runtime, ptyLauncher.finalFd, ptyLauncherBytes) !==
1201
+ ptyLauncherDigest)) ||
1004
1202
  hashOpenFile(runtime, seccomp.finalFd, filter.buffer.length) !==
1005
1203
  filter.sha256
1006
1204
  ) {
@@ -1249,6 +1447,17 @@ function createTrustedResources(
1249
1447
  sha256: supervisorFinalDigest,
1250
1448
  bytes: supervisorBytes,
1251
1449
  },
1450
+ ...(ptyLauncher
1451
+ ? {
1452
+ ptyLauncher: {
1453
+ probeFd: ptyLauncher.probeFd,
1454
+ finalFd: ptyLauncher.finalFd,
1455
+ identity: ptyLauncher.identity,
1456
+ sha256: ptyLauncherDigest,
1457
+ bytes: ptyLauncherBytes,
1458
+ },
1459
+ }
1460
+ : {}),
1252
1461
  workspace: {
1253
1462
  probeFd: workspace.probeFd,
1254
1463
  finalFd: workspace.finalFd,
@@ -1291,6 +1500,7 @@ export function applyLinuxGenericWorkspaceSandbox(
1291
1500
  cwd: spawnOpts?.cwd || process.cwd(),
1292
1501
  shell: spawnOpts?.shell,
1293
1502
  sync: sandboxOpts?.sync === true,
1503
+ pty: sandboxOpts?.pty === true,
1294
1504
  stdio: spawnOpts?.stdio,
1295
1505
  requiredBoundaries,
1296
1506
  };
@@ -24,6 +24,7 @@ import path from "node:path";
24
24
 
25
25
  export const LINUX_GENERIC_BWRAP_BACKEND = "linux-bwrap-workspace";
26
26
  export const LINUX_GENERIC_CONTRACT_KIND = "strict-workspace-command";
27
+ export const LINUX_GENERIC_PTY_LAUNCHER_PATH = "/usr/bin/setsid";
27
28
 
28
29
  const SUPPORTED_BOUNDARIES = new Set(["filesystem", "network"]);
29
30
  const SYSTEM_RUNTIME_DESTINATIONS = new Set([
@@ -97,6 +98,7 @@ const CONTRACT_VERSION = 1;
97
98
  const PLAN_VERSION = 1;
98
99
  const SANDBOX_HOME = "/home/sandbox";
99
100
  const SUPERVISOR_STAGING_PATH = "/run/.chainless-bwrap-supervisor";
101
+ const PTY_LAUNCHER_STAGING_PATH = "/run/.chainless-pty-launcher";
100
102
  const SCRATCH_MOUNTS = ["/tmp", "/run", "/var/tmp", SANDBOX_HOME];
101
103
 
102
104
  const issuedContracts = new WeakMap();
@@ -194,6 +196,12 @@ function normalizeShell(shell) {
194
196
  throw new TypeError("strong Linux workspace sandbox requires shell:false");
195
197
  }
196
198
 
199
+ function normalizePty(pty) {
200
+ if (pty === undefined || pty === null || pty === false) return false;
201
+ if (pty === true) return true;
202
+ throw new TypeError("strong Linux workspace sandbox requires pty:boolean");
203
+ }
204
+
197
205
  function normalizeStdio(stdio) {
198
206
  let entries;
199
207
  if (stdio === undefined || stdio === null || stdio === "pipe") {
@@ -324,6 +332,10 @@ export function issueLinuxGenericSandboxExecutionContract(
324
332
  const args = normalizeArgs(spec?.args || []);
325
333
  const shell = normalizeShell(spec?.shell);
326
334
  const sync = spec?.sync === true;
335
+ const pty = normalizePty(spec?.pty);
336
+ if (pty && sync) {
337
+ throw new TypeError("strong Linux workspace PTY cannot be synchronous");
338
+ }
327
339
  const stdio = normalizeStdio(spec?.stdio);
328
340
  const requiredBoundaries = normalizeBoundaries(spec?.requiredBoundaries);
329
341
  if (
@@ -355,6 +367,7 @@ export function issueLinuxGenericSandboxExecutionContract(
355
367
  args,
356
368
  shell,
357
369
  sync,
370
+ pty,
358
371
  stdio,
359
372
  requiredBoundaries,
360
373
  };
@@ -383,11 +396,13 @@ function issuedBindingMatches(issued, provenance = {}) {
383
396
  let args;
384
397
  let boundaries;
385
398
  let shell;
399
+ let pty;
386
400
  let stdio;
387
401
  try {
388
402
  args = normalizeArgs(provenance.args || []);
389
403
  boundaries = normalizeBoundaries(provenance.requiredBoundaries);
390
404
  shell = normalizeShell(provenance.shell);
405
+ pty = normalizePty(provenance.pty);
391
406
  stdio = normalizeStdio(provenance.stdio);
392
407
  } catch {
393
408
  return false;
@@ -397,6 +412,7 @@ function issuedBindingMatches(issued, provenance = {}) {
397
412
  provenance.command === issued.command &&
398
413
  provenance.cwd === issued.workingDirectory &&
399
414
  provenance.sync === issued.sync &&
415
+ pty === issued.pty &&
400
416
  shell === issued.shell &&
401
417
  args.length === issued.args.length &&
402
418
  args.every((value, index) => value === issued.args[index]) &&
@@ -514,12 +530,23 @@ function validateWorkspaceMountTopology(value, issued) {
514
530
  !/^[a-f0-9]{64}$/.test(value.digest || "") ||
515
531
  !Number.isSafeInteger(value.lineageEntryCount) ||
516
532
  value.lineageEntryCount < 1 ||
533
+ value.lineageEntryCount > 4096 ||
517
534
  !Number.isSafeInteger(value.filesystemEntryCount) ||
518
535
  value.filesystemEntryCount < 1 ||
536
+ value.filesystemEntryCount > 16384 ||
519
537
  !Number.isSafeInteger(value.aliasCount) ||
520
- value.aliasCount < 1 ||
538
+ value.aliasCount !== 1 ||
521
539
  !Number.isSafeInteger(value.forbiddenIdentityCount) ||
522
540
  value.forbiddenIdentityCount < 1 ||
541
+ value.forbiddenIdentityCount > 64 ||
542
+ !/^[a-f0-9]{64}$/.test(value.sourceMountSetDigest || "") ||
543
+ !Number.isSafeInteger(value.sourceMountCount) ||
544
+ value.sourceMountCount < 2 ||
545
+ value.sourceMountCount > 64 ||
546
+ !Number.isSafeInteger(value.importedMountEntryCount) ||
547
+ value.importedMountEntryCount < value.sourceMountCount ||
548
+ value.importedMountEntryCount > 65536 ||
549
+ value.sourceMountPropagationPrivateAtAttestation !== true ||
523
550
  value.strictDescendantMountsAtAttestation !== 0 ||
524
551
  value.rootAliasAttested !== true ||
525
552
  value.recursiveBind !== true ||
@@ -538,6 +565,11 @@ function validateWorkspaceMountTopology(value, issued) {
538
565
  filesystemEntryCount: value.filesystemEntryCount,
539
566
  aliasCount: value.aliasCount,
540
567
  forbiddenIdentityCount: value.forbiddenIdentityCount,
568
+ sourceMountSetDigest: value.sourceMountSetDigest,
569
+ sourceMountCount: value.sourceMountCount,
570
+ importedMountEntryCount: value.importedMountEntryCount,
571
+ sourceMountPropagationPrivateAtAttestation:
572
+ value.sourceMountPropagationPrivateAtAttestation,
541
573
  strictDescendantMountsAtAttestation:
542
574
  value.strictDescendantMountsAtAttestation,
543
575
  rootAliasAttested: value.rootAliasAttested,
@@ -579,6 +611,30 @@ function validateResourceContract(resources, issued) {
579
611
  ) {
580
612
  throw new Error("bubblewrap supervisor is not root-owned and pinned");
581
613
  }
614
+ let ptyLauncher = null;
615
+ if (issued.pty) {
616
+ if (
617
+ resources.ptyLauncher?.identity?.realPath !==
618
+ LINUX_GENERIC_PTY_LAUNCHER_PATH ||
619
+ !validateRootOwnedIdentity(resources.ptyLauncher?.identity, {
620
+ directory: false,
621
+ }) ||
622
+ !/^[a-f0-9]{64}$/.test(resources.ptyLauncher?.sha256 || "") ||
623
+ !Number.isSafeInteger(resources.ptyLauncher?.bytes) ||
624
+ resources.ptyLauncher.bytes <= 0 ||
625
+ !Number.isInteger(resources.ptyLauncher?.probeFd) ||
626
+ !Number.isInteger(resources.ptyLauncher?.finalFd)
627
+ ) {
628
+ throw new Error("PTY session launcher is not root-owned and pinned");
629
+ }
630
+ ptyLauncher = {
631
+ identity: resources.ptyLauncher.identity,
632
+ sha256: resources.ptyLauncher.sha256,
633
+ bytes: resources.ptyLauncher.bytes,
634
+ };
635
+ } else if (resources.ptyLauncher !== undefined) {
636
+ throw new Error("non-PTY launch supplied a PTY session launcher");
637
+ }
582
638
  const system = Array.isArray(resources.system) ? resources.system : [];
583
639
  if (!system.some((entry) => entry.destination === "/usr")) {
584
640
  throw new Error("root-owned /usr runtime mount is required");
@@ -657,6 +713,7 @@ function validateResourceContract(resources, issued) {
657
713
  sha256: resources.supervisor.sha256,
658
714
  bytes: resources.supervisor.bytes,
659
715
  },
716
+ ptyLauncher,
660
717
  system,
661
718
  symlinks,
662
719
  etc,
@@ -749,6 +806,15 @@ function directoryArguments(directoryDestinations, fileDestinations) {
749
806
 
750
807
  function descriptorLayout(stdio, resources, validated, workspaceRoot, phase) {
751
808
  const descriptors = [
809
+ ...(validated.ptyLauncher
810
+ ? [
811
+ {
812
+ kind: "ptyLauncher",
813
+ fd: resources.ptyLauncher[`${phase}Fd`],
814
+ destination: PTY_LAUNCHER_STAGING_PATH,
815
+ },
816
+ ]
817
+ : []),
752
818
  {
753
819
  kind: "supervisor",
754
820
  fd: resources.supervisor[`${phase}Fd`],
@@ -791,6 +857,7 @@ function descriptorLayout(stdio, resources, validated, workspaceRoot, phase) {
791
857
  function buildPolicyArgs({ issued, validated, descriptors, environment }) {
792
858
  const byKind = (kind) =>
793
859
  descriptors.filter((descriptor) => descriptor.kind === kind);
860
+ const ptyLauncher = byKind("ptyLauncher")[0] || null;
794
861
  const supervisor = byKind("supervisor")[0];
795
862
  const workspace = byKind("workspace")[0];
796
863
  const seccomp = byKind("seccomp")[0];
@@ -805,7 +872,7 @@ function buildPolicyArgs({ issued, validated, descriptors, environment }) {
805
872
  );
806
873
  const args = [
807
874
  "--die-with-parent",
808
- "--new-session",
875
+ ...(issued.pty ? [] : ["--new-session"]),
809
876
  "--unshare-user",
810
877
  "--disable-userns",
811
878
  "--assert-userns-disabled",
@@ -824,6 +891,15 @@ function buildPolicyArgs({ issued, validated, descriptors, environment }) {
824
891
  args.push("--dir", directory);
825
892
  }
826
893
  args.push(
894
+ ...(ptyLauncher
895
+ ? [
896
+ "--perms",
897
+ "0000",
898
+ "--file",
899
+ String(ptyLauncher.childFd),
900
+ PTY_LAUNCHER_STAGING_PATH,
901
+ ]
902
+ : []),
827
903
  "--perms",
828
904
  "0000",
829
905
  "--file",
@@ -951,6 +1027,15 @@ function policyBinding({
951
1027
  ? { identity: validated.target.identity }
952
1028
  : {}),
953
1029
  },
1030
+ pty: issued.pty
1031
+ ? {
1032
+ mode: "dedicated-controlling-terminal",
1033
+ launcher: validated.ptyLauncher,
1034
+ launcherDescriptorBound: true,
1035
+ launcherDescriptorConsumedBeforeTarget: true,
1036
+ bwrapNewSession: false,
1037
+ }
1038
+ : { mode: "none" },
954
1039
  requiredBoundaries: [...requiredBoundaries].sort(),
955
1040
  environmentDigest: sha256(stableJson(environment)),
956
1041
  policyArgs,
@@ -1067,6 +1152,7 @@ export function planLinuxGenericBubblewrap(
1067
1152
  cwd: "/",
1068
1153
  shell: false,
1069
1154
  encoding: "utf8",
1155
+ timeout: 10_000,
1070
1156
  stdio: [
1071
1157
  ...issued.stdio.map((_, index) =>
1072
1158
  index === 1 || index === 2 ? "pipe" : "ignore",
@@ -1138,13 +1224,22 @@ export function planLinuxGenericBubblewrap(
1138
1224
  const supervisor = finalDescriptors.find(
1139
1225
  (entry) => entry.kind === "supervisor",
1140
1226
  );
1141
- const command = `/proc/self/fd/${supervisor.childFd}`;
1142
- const args = Object.freeze([
1227
+ const ptyLauncher = finalDescriptors.find(
1228
+ (entry) => entry.kind === "ptyLauncher",
1229
+ );
1230
+ const supervisorCommand = `/proc/self/fd/${supervisor.childFd}`;
1231
+ const command = issued.pty
1232
+ ? `/proc/self/fd/${ptyLauncher.childFd}`
1233
+ : supervisorCommand;
1234
+ const bwrapArgs = [
1143
1235
  ...policyArgs,
1144
1236
  "--",
1145
1237
  validated.target.resolvedCommand,
1146
1238
  ...validated.target.args,
1147
- ]);
1239
+ ];
1240
+ const args = Object.freeze(
1241
+ issued.pty ? ["--ctty", supervisorCommand, ...bwrapArgs] : bwrapArgs,
1242
+ );
1148
1243
  const options = Object.freeze({
1149
1244
  ...passthroughSpawnOptions(spawnOptions),
1150
1245
  cwd: "/",
@@ -1187,10 +1282,16 @@ export function planLinuxGenericBubblewrap(
1187
1282
  targetRuntime: "generic-command",
1188
1283
  contentSnapshot: false,
1189
1284
  handleAtomic: false,
1285
+ // Every descriptor bind source is private at both attestations, which
1286
+ // prevents ongoing host propagation after bwrap clones that topology.
1287
+ // The final check is still not atomic with the subsequent native spawn.
1190
1288
  mountTopologyAtomic: false,
1191
1289
  contractDigest: issued.contractDigest,
1192
1290
  policyDigest,
1193
1291
  mountTopologyDigest: validated.workspaceMountTopology.digest,
1292
+ sourceMountSetDigest:
1293
+ validated.workspaceMountTopology.sourceMountSetDigest,
1294
+ sourceMountPropagationPrivateAtAttestation: true,
1194
1295
  emptyRoot: true,
1195
1296
  undeclaredRootReadOnly: true,
1196
1297
  workspaceReadWrite: true,
@@ -1228,6 +1329,9 @@ export function planLinuxGenericBubblewrap(
1228
1329
  "no-strict-descendants-or-forbidden-root-aliases-at-attestation",
1229
1330
  mountTopologySource: validated.workspaceMountTopology.source,
1230
1331
  mountTopologyDigest: validated.workspaceMountTopology.digest,
1332
+ sourceMountSetDigest:
1333
+ validated.workspaceMountTopology.sourceMountSetDigest,
1334
+ sourceMountPropagationPrivateAtAttestation: true,
1231
1335
  mountTopologyAtomic: false,
1232
1336
  }),
1233
1337
  networkPolicy: Object.freeze({
@@ -1235,6 +1339,19 @@ export function planLinuxGenericBubblewrap(
1235
1339
  namespaceIdentityChanged: true,
1236
1340
  seccomp: "deny-network-creation",
1237
1341
  }),
1342
+ ptyPolicy: issued.pty
1343
+ ? Object.freeze({
1344
+ mode: "dedicated-controlling-terminal",
1345
+ launcherPath: validated.ptyLauncher.identity.realPath,
1346
+ launcherSha256: validated.ptyLauncher.sha256,
1347
+ launcherBytes: validated.ptyLauncher.bytes,
1348
+ launcherDescriptorBound: true,
1349
+ launcherExecutablePinned: true,
1350
+ launcherDescriptorConsumedBeforeTarget: true,
1351
+ launcherStagingPathHidden: true,
1352
+ bwrapNewSession: false,
1353
+ })
1354
+ : null,
1238
1355
  postSpawn: Object.freeze({ required: false, mode: "none" }),
1239
1356
  cleanup: finalCleanup,
1240
1357
  });
@@ -1248,7 +1365,8 @@ export function verifyLinuxGenericBubblewrapPlan(plan) {
1248
1365
  Object.isFrozen(plan) &&
1249
1366
  Object.isFrozen(plan.args) &&
1250
1367
  Object.isFrozen(plan.options) &&
1251
- Object.isFrozen(plan.options.stdio)
1368
+ Object.isFrozen(plan.options.stdio) &&
1369
+ (plan.ptyPolicy === null || Object.isFrozen(plan.ptyPolicy))
1252
1370
  );
1253
1371
  }
1254
1372
 
@@ -321,6 +321,7 @@ function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
321
321
  profile,
322
322
  requiredBoundaries,
323
323
  sync: request.sync === true,
324
+ pty: request.pty === true,
324
325
  executionContract:
325
326
  request.executionContract &&
326
327
  typeof request.executionContract === "object" &&
@@ -3040,7 +3041,7 @@ function validateLinuxPluginContract(
3040
3041
  return { ok: false, reason: "execution_contract_missing" };
3041
3042
  }
3042
3043
  if (
3043
- sync !== true ||
3044
+ (sync !== true && sync !== false) ||
3044
3045
  spawnOpts?.shell !== false ||
3045
3046
  spawnOpts?.detached === true ||
3046
3047
  !linuxStdioIsNarrow(spawnOpts?.stdio) ||
@@ -4553,7 +4554,8 @@ function buildLinuxNetworkSeccompFilter(arch) {
4553
4554
  instructions.push([0x54, 0, 0, ~architecture.x32SyscallBit]);
4554
4555
  }
4555
4556
  instructions.push(
4556
- // No socket family is needed by this direct foreground Plugin Node route.
4557
+ // No socket family is needed by this direct one-shot Plugin bin route,
4558
+ // whether the Broker call is synchronous or asynchronously supervised.
4557
4559
  // Blocking creation rather than relying only on CLONE_NEWNET also closes
4558
4560
  // non-network-namespaced transports such as AF_VSOCK.
4559
4561
  [0x15, 0, 1, architecture.socketSyscall],
@@ -6179,6 +6181,7 @@ export function applyLinuxSandbox(
6179
6181
  * profile?: string,
6180
6182
  * requiredBoundaries?: string[],
6181
6183
  * sync?: boolean,
6184
+ * pty?: boolean,
6182
6185
  * executionContract?: Readonly<Object>|null
6183
6186
  * }|null} explicitRequest
6184
6187
  * @returns {ReturnType<typeof createSandboxPlan>}
@@ -6239,6 +6242,7 @@ export function applySandbox(
6239
6242
  profileName: profiles[profileName] ? profileName : "default",
6240
6243
  requiredBoundaries: sandboxRequest.requiredBoundaries,
6241
6244
  sync: sandboxRequest.sync,
6245
+ pty: sandboxRequest.pty,
6242
6246
  executionContract: sandboxRequest.executionContract,
6243
6247
  };
6244
6248
 
@@ -21,7 +21,14 @@ export function createBaseProgram() {
21
21
  .option("--verbose", "Enable verbose output")
22
22
  .option("--quiet", "Suppress non-essential output")
23
23
  // M5/M6 Runtime Convergence options
24
- .option("--jsii-runtime <runtime>", "JSII runtime to use: native|quickjs", "native")
25
- .option("--otlp-endpoint <endpoint>", "OTLP/HTTP endpoint for trace export (e.g. http://localhost:4318)");
24
+ .option(
25
+ "--jsii-runtime <runtime>",
26
+ "JSII runtime to use: native|quickjs",
27
+ "native",
28
+ )
29
+ .option(
30
+ "--otlp-endpoint <endpoint>",
31
+ "OTel Collector endpoint (protocol via OTEL_EXPORTER_OTLP_PROTOCOL; HTTP example: http://localhost:4318)",
32
+ );
26
33
  return program;
27
34
  }
@@ -121,6 +121,7 @@ import {
121
121
  questionWithIdleTimeout,
122
122
  } from "./permission-prompt.js";
123
123
  import { describeAskContext, raceLocalAndRemote } from "./remote-approval.js";
124
+ import { runWithHostHooksV2Workspace } from "../lib/hooks-v2-workspace-context.js";
124
125
  import {
125
126
  parsePermissionTier,
126
127
  parsePermissionModeArg,
@@ -477,6 +478,13 @@ export async function agentLoop(messages, options) {
477
478
  * Start the agentic REPL
478
479
  */
479
480
  export async function startAgentRepl(options = {}) {
481
+ const trustedWorkspaceRoot = process.cwd();
482
+ return runWithHostHooksV2Workspace(trustedWorkspaceRoot, () =>
483
+ startAgentReplInWorkspace(options),
484
+ );
485
+ }
486
+
487
+ async function startAgentReplInWorkspace(options = {}) {
480
488
  // EPIPE guard: if the REPL's stdout is piped and the consumer closes (e.g.
481
489
  // `cc agent | head`), the async stream `error` would otherwise crash the
482
490
  // process. Route a broken pipe into the REPL's own graceful shutdown (the