chainlesschain 0.163.3 → 0.163.4

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/package.json +3 -2
  2. package/scripts/command-lifecycle-report.mjs +330 -38
  3. package/src/assets/web-panel/assets/{AIOps-DIY6KxV-.js → AIOps-DB5X7qqV.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BXH0muPt.js → ActionButton-DxHRtKLk.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-D39w8vmg.js → Analytics-DIH5QrxT.js} +2 -2
  6. package/src/assets/web-panel/assets/{AppLayout-CUU1zvLs.js → AppLayout-uu0oxVz7.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-D8azHO3T.js → Artifacts-BNSMiW9Y.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-D0tAwXTg.js → Audit-DcAzB4_x.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-C29mhJll.js → BackgroundAgents-ClSsvjIc.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-D6Aa1Gro.js → Backup-BMciY0js.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-B1uoHbPQ.js → BaseInput-Hn3b57Sh.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-Bs4DLOR5.js → Chat-CoSlC53V.js} +5 -5
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DUYIBSle.js → ChatBubbleRenderer-CFevJ3So.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-CNcBPEa4.js → Checkbox-B4L4Eu4w.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CIY89763.js → Codegen-C77FgRxF.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-CsPYFmgo.js → Col-BrLtJLH3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BBocEge4.js → Community-Be7pXXhA.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-TQ8Ux0Lf.js → Compact-DuGPecjo.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-DfBsZTft.js → Compliance-CzS66mjP.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-Cg-I6FVH.js → Cowork-CEhjCoyK.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DTQzfRTp.js → Cron-D7ir8Vja.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DGxEKGpR.js → Crosschain-BcXmuLxf.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DLZ1RDSr.js → DID-Cto1dtBy.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-XneICKNI.js → Dashboard-B7KjgwOX.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BRU00J1-.js → Dropdown-COMqxijg.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-BzCbJuAy.js → EmailListRenderer-BZrya-nf.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D7BRyNBG.js → FamilyGuardDashboard-AmVeMq0J.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-D9DeFIhW.js → Federation-kZU8c-27.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-6rAtA9Ko.js → FormItemContext-sOV22v2-.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-8Hqy80pU.js → GenericCardRenderer-BUuLtGCz.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Cmvkj1rt.js → Git-K-ZxsLsH.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BRiQUjjw.js → Governance-D3OEnawI.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CKN_EzVf.js → Inference-Bll0gRmP.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-BCDHwpwQ.js → KnowledgeGraph-DBXwjr53.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DK9ovhaB.js → Logs-CEzPwEfP.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BILPGakp.js → Marketplace-BYyRyOPz.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DTVCOr9-.js → McpTools-DVu8w_rM.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-Hzc1iow4.js → Memory-tenuZ_hO.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-GXfgnGYV.js → MobileBridge-Bxgy4ozS.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-B-3TJWsS.js → MobileProjects-Dgx1D6sc.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-DwSJMWtK.js → Mtc-CRfJ8r8j.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-D2uxf-DA.js → MtcAudit-BBJq4Aiz.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-DG6kbKK0.js → Multisig-B3ii9lai.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-f5p49EDU.js → NLProgramming-FIi-Cubv.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-FUgHyzSD.js → Notes-DIq8MPf1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-DCHGee7e.js → NotificationSettings-BFOD9eGd.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-Dm6LlIoa.js → OrderTableRenderer--41u7vre.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BawA7qAg.js → Organization-B_0AWztW.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-D6a5wZU1.js → Overflow-B6I7TuXv.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-K8WavwnA.js → P2P-Cdf6qens.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-4JAJYaqx.js → PdhVaultBrowser-B_0FcQJY.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-BDWvi8PD.js → Permissions-6nFBjsQp.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DF_K4E9i.js → PersonalDataHub-nQYV7xpn.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-BYeZlesI.js → Pipeline-CBVod5jm.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BIHlgnIc.js → Privacy-D52jW1Nt.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-Cvf-6B5P.js → ProjectInit-Bdx3BYhf.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CnwfJrRz.js → ProjectSettings-b7xr4DUa.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-b-jPXzF4.js → Projects-DhfifCKx.js} +1 -1
  60. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-VLf3gAfL.js → QrScannerModal-FQKSmc3e.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-B5vGjRiF.js → QuickAsk-CSTrhzcF.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend--UW4iYs-.js → Recommend-CxMABOUh.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DMSPcfKR.js → RemoteSession-Cv2T8yRa.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-kQXlEHmj.js → Reputation-CE1k4Vvu.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-CxGu3pit.js → Row-BNgNcAV_.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-Bi5lo8MZ.js → RssFeed-5ZiBZ3ph.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-Cq_H5RTh.js → Search-mDOIF1UV.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-YFbmkaQS.js → Security-CtuCTC-e.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-DLOGdaLm.js → Services-DxFO2Zg7.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CteIxb3D.js → Skeleton-CKmKkH9y.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CccopaIZ.js → Skills-Bmzl9qkk.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-AzfEQKX1.js → Sla-9RqaWHOC.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-ekGo5l77.js → SpeechSettings-CGdzFIYw.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-BmZmAgYH.js → SyncSettings-CvNuCeBT.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-D7q3Z4uL.js → Tasks-B_sLdcnw.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CX1Tgeka.js → Templates-D9_YijdK.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-Zc59jF4i.js → Tenant-Cuc7BUSP.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CXOD7y83.js → Terminal-6VYRnJgd.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-B1aBHPBo.js → TimelineRenderer-GtK8xd8Y.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-zALlOq_U.js → Tokens-BZ94Lt5W.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CQfJMXaq.js → Trigger-DEcZgDaX.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BU0RcM-w.js → Trust-CC3z9THe.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-IFZ3HsbH.js → UkeySign-Cvtw8cV3.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-BzslgW0v.js → VideoEditing-CL4e6Bn3.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-BZAcQNS6.js → Wallet-ycEemP27.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CBK1DMi0.js → WebAuthn-DgZ4X_aw.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-BlMTPzO-.js → WorkflowEditor-D0qMhSv7.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-B51-8QVn.js → chat-DsAg01Zn.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-504RTByU.js → colors-B0oqAhrQ.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-C-GP6IQM.js → compact-item-CmX0Rabz.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-DOkIj7ID.js → createContext-Be4cn1Ub.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DN7V-_FW.js → hasIn-D5v12kVq.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-kt_5rfcZ.js → index-0eWLkQKT.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CUhQpiYv.js → index-301ogenv.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-f4Bo4KXm.js → index-5LGY0JbG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index--n-ZciV3.js → index-7VKDn-t1.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D1phyca8.js → index-8WvYGnON.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-7jIqHbzw.js → index-B1YGjOWC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DEMPUVa3.js → index-B3mUHuVc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bkxhaeta.js → index-BA4oulaP.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CYopNjcJ.js → index-BBREnn1V.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-P2mfY27Z.js → index-BBvyHguy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DA2f1UV3.js → index-BKmsS2jj.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CP2A226L.js → index-BRIqLy1m.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CfiEM64n.js → index-BXGSbelg.js} +3 -3
  108. package/src/assets/web-panel/assets/{index-B2-x9bS8.js → index-BaE0Ui07.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CySTJKeq.js → index-BoAIAZ5C.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-kAqQXfP6.js → index-BqltWojQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DAOpQvl_.js → index-CGiIyTZw.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CKB0vbJU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-C4DV_5uA.js → index-CZ2wUEgr.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-yYTQZypF.js → index-CZpfzcuy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-et5JIPHO.js → index-Ckrk3pIV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DaA__-q1.js → index-CqoCZ5vR.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-By6u5IV1.js → index-CyWBir7N.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CKTfZyfU.js → index-CyyGTMKd.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B_vGU-Js.js → index-D0BSkxYQ.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Cao5DZy7.js → index-D5YVopyr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-C3TSohzc.js → index-DKUBj36_.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BFIFJ5Dr.js → index-DM724NgU.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-ConjaJro.js → index-DQRsQen0.js} +1 -1
  124. package/src/assets/web-panel/assets/index-DgoXmwqB.js +1 -0
  125. package/src/assets/web-panel/assets/{index-C9_48OzU.js → index-DikJ0Qqe.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C0k_Nqg9.js → index-Dn2dSdXx.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DE-hmI2l.js → index-DyUaFWEk.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-so8eIIxF.js → index-Hfe_dEGx.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-FcV8J_Qz.js → index-_JjRLSD0.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D9LyiN-L.js → index-frSojpHj.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CX78k-qf.js → index-jK3ax6Z6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-C8UltbXK.js → index-qMuZ-NRR.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CqcLo5WV.js → index-q_fQ94SE.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CPYEIdpV.js → initDefaultProps-D4VdhJ9_.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-Cjim9pxs.js → motion-CZJA0MQW.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DuU-cgz3.js → move-BqEs7Sb1.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DJHNAkta.js → mtc-parser-BpHEtsu1.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DSQBgdBj.js → omit-BW94tY9W.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-CTmT_j5m.js → pickAttrs-BnBjPWP9.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-Lfm6v-r4.js → placementArrow-uUkjQ_hu.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CgrcPcDi.js → responsiveObserve-B-klUqXK.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CuE3110i.js → slide-CvY1cNAs.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-DiK5nbpf.js → statusUtils-Ix5sEDT2.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-DPEJdQaZ.js → styleChecker-C8qcXOne.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-CnnodGmp.js → useFlexGapSupport-KNF6Kj8f.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DWF84Yav.js → useFs-DGJr345n.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-a5b5Dw2f.js → usePersonalDataHub-BboN99rI.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DAluzXOq.js → vnode-Bgt8Vgte.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-Dokg7uAZ.js → zoom-BhJtErgt.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/artifacts.js +98 -0
  153. package/src/commands/mcp.js +1 -1
  154. package/src/commands/review.js +221 -22
  155. package/src/commands/session-mcp-recovery.js +5 -5
  156. package/src/data/changelog.json +14 -1
  157. package/src/gateways/ws/ws-agent-handler.js +492 -14
  158. package/src/harness/jsonl-session-store.js +39 -1
  159. package/src/harness/mcp-client.js +172 -29
  160. package/src/harness/prompt-compressor.js +24 -0
  161. package/src/harness/provider-backed-compaction.js +205 -0
  162. package/src/lib/app-preview.js +3 -0
  163. package/src/lib/command-lifecycle-report.js +1340 -145
  164. package/src/lib/delivery-coordinator.js +30 -5
  165. package/src/lib/delivery-production-adapter.js +1777 -0
  166. package/src/lib/delivery-production-runner.js +240 -0
  167. package/src/lib/durable-security-store.js +29 -17
  168. package/src/lib/execution-trace/trace-context.cjs +179 -0
  169. package/src/lib/execution-trace/trace-context.js +8 -175
  170. package/src/lib/mcp-call-ledger-store.js +58 -6
  171. package/src/lib/mcp-host-recovery-runtime.js +2 -0
  172. package/src/lib/mcp-ledger-recovery-admission.js +39 -3
  173. package/src/lib/mcp-recovery-adjudication.js +256 -19
  174. package/src/lib/mcp-sandbox-policy.js +29 -0
  175. package/src/lib/mcp-stdio-capsule-builder-worker.cjs +495 -0
  176. package/src/lib/mcp-stdio-executable-identity.js +33 -2
  177. package/src/lib/mcp-stdio-immutable-vfs-resolver.cjs +830 -0
  178. package/src/lib/mcp-stdio-package-materialization.js +721 -333
  179. package/src/lib/observability/index.js +2 -0
  180. package/src/lib/observability/otlp-exporter.js +2 -2
  181. package/src/lib/packer/pack-update-applier.js +8 -1
  182. package/src/lib/packer/pkg-config-generator.js +34 -0
  183. package/src/lib/process-execution-broker/index.js +34 -5
  184. package/src/lib/process-execution-broker/platform-sandbox.js +82 -1
  185. package/src/lib/scheduler-kernel/contract.js +315 -0
  186. package/src/lib/scheduler-kernel/store.js +1345 -0
  187. package/src/lib/session-host-lease.js +403 -30
  188. package/src/lib/session-persistence-failure.js +4 -1
  189. package/src/lib/sub-agent-context.js +8 -0
  190. package/src/repl/agent-repl.js +285 -38
  191. package/src/runtime/agent-core.js +1044 -90
  192. package/src/runtime/headless-runner.js +260 -166
  193. package/src/runtime/headless-stream.js +254 -25
  194. package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +0 -1
  195. package/src/assets/web-panel/assets/Providers-CxC7JuYv.js +0 -1
  196. package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +0 -1
  197. package/src/assets/web-panel/assets/index-Bya64sZE.js +0 -1
  198. package/src/assets/web-panel/assets/index-D_FWpDZM.js +0 -1
@@ -16,6 +16,8 @@ import crypto from "node:crypto";
16
16
  import fs from "node:fs";
17
17
  import { builtinModules, createRequire } from "node:module";
18
18
  import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { Worker } from "node:worker_threads";
19
21
  import { getCacheDir, getMachineSecurityAnchorDir } from "./paths.js";
20
22
  import {
21
23
  mutateSecurityStore,
@@ -34,60 +36,43 @@ export const MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE =
34
36
  "CC_MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED";
35
37
 
36
38
  const MATERIALIZATION_SCHEMA =
37
- "chainlesschain.mcp-stdio-package-materialization/v2";
38
- const MATERIALIZATION_VERSION = 2;
39
- const CAPSULE_SCHEMA = "chainlesschain.mcp-stdio-node-capsule/v1";
39
+ "chainlesschain.mcp-stdio-package-materialization/v3";
40
+ const MATERIALIZATION_VERSION = 3;
41
+ const CAPSULE_SCHEMA = "chainlesschain.mcp-stdio-node-capsule/v2";
40
42
  const CAPSULE_RELATIVE_PATH = "capsule/server.cjs";
41
- const CAPSULE_BUILDER = "esbuild";
43
+ const CAPSULE_BUILDER = "esbuild-wasm";
42
44
  const CAPSULE_BUILDER_VERSION = "0.28.1";
43
- const CAPSULE_BUILDER_BINARIES = Object.freeze({
44
- "darwin-arm64": Object.freeze({
45
- packageName: "@esbuild/darwin-arm64",
46
- relativePath: "bin/esbuild",
47
- sha256: "e2dc9a52440a2a34f09434a2f4843cb1e30f84e40dcf238976ec61ef8cd7f36a",
48
- }),
49
- "darwin-x64": Object.freeze({
50
- packageName: "@esbuild/darwin-x64",
51
- relativePath: "bin/esbuild",
52
- sha256: "dd53ccf32f9b5b3ab30d41388ef1fc8f81c44ca57ee7a32a7364a1753308d009",
53
- }),
54
- "linux-arm64": Object.freeze({
55
- packageName: "@esbuild/linux-arm64",
56
- relativePath: "bin/esbuild",
57
- sha256: "51e829ba36f36be6d9aea6e329ddc4f9350302339b16aaca96a3cb97f64a8ebb",
58
- }),
59
- "linux-ia32": Object.freeze({
60
- packageName: "@esbuild/linux-ia32",
61
- relativePath: "bin/esbuild",
62
- sha256:
63
- "9cd7515a75d6f96b0aa055861cf987888b4765c890501b6274f4bdff4061a5e0d9fd",
64
- }),
65
- "linux-x64": Object.freeze({
66
- packageName: "@esbuild/linux-x64",
67
- relativePath: "bin/esbuild",
68
- sha256: "0c6588b092a2c291a72bab90659f3c9e0e25e0fe59c9ac12b4dae4d945e5548c",
69
- }),
70
- "win32-arm64": Object.freeze({
71
- packageName: "@esbuild/win32-arm64",
72
- relativePath: "esbuild.exe",
73
- sha256: "bfb8798ab678f1ce4a723739f4a3eabab3244d7a04eeb12be2eb9f58095c13ef",
74
- }),
75
- "win32-ia32": Object.freeze({
76
- packageName: "@esbuild/win32-ia32",
77
- relativePath: "esbuild.exe",
78
- sha256: "8fa99b6e0945830fce8d7e208fdb21763aa4aea875751f4a0eec7f6e262af1dd",
79
- }),
80
- "win32-x64": Object.freeze({
81
- packageName: "@esbuild/win32-x64",
82
- relativePath: "esbuild.exe",
83
- sha256: "ec02ee9b14ab332416fedd10614dfb80eed5304d94f67745067c011934a8c3c3",
84
- }),
85
- });
45
+ const CAPSULE_BUILDER_WASM_SHA256 =
46
+ "cc8c5e14db584cd75c6c9fc16e1aae3d5b8e99ab7f333aeee71f59e23fa9f24e";
47
+ const CAPSULE_BUILDER_WASM_BYTES = 13_940_120;
48
+ const CAPSULE_BUILDER_API_SHA256 =
49
+ "4ec4cc1b6f2fdd0a117aa20ab5c49da9868ce7329322082a457927cdc64d89c1";
50
+ const CAPSULE_BUILDER_API_BYTES = 133_235;
51
+ const CAPSULE_RESOLVER_SCHEMA =
52
+ "chainlesschain.mcp-stdio-immutable-vfs-resolver/v1";
53
+ const CAPSULE_WORKER_SCHEMA =
54
+ "chainlesschain.mcp-stdio-capsule-builder-worker/v1";
55
+ const CAPSULE_BUILD_TIMEOUT_MS = 120_000;
56
+ const CAPSULE_WORKER_MAX_OLD_GENERATION_MB = 256;
57
+ // Updated with the checked-in source digest whenever either pinned host module
58
+ // changes. The worker receives source bytes, never a pathname to execute.
59
+ const CAPSULE_BUILDER_WORKER_SHA256 =
60
+ "1386f5ad51ba9e946fbf676d8aba4c8a11b3219916a413ac6fc24578d998ace1";
61
+ const CAPSULE_BUILDER_WORKER_BYTES = 16_293;
62
+ const CAPSULE_RESOLVER_SHA256 =
63
+ "48e1fc27454e6dcfd3018849912699e6ef8417db1716d1c8eff2cd1072a786d2";
64
+ const CAPSULE_RESOLVER_BYTES = 24_417;
65
+ const CAPSULE_STDIN_WRAPPER_SCHEMA =
66
+ "chainlesschain.mcp-stdio-capsule-stdin-wrapper/v1";
86
67
  const INDEX_LABEL = "MCP stdio package materialization index";
87
68
  const MANIFEST_LABEL = "MCP stdio package materialization manifest";
88
- const MAX_FILES = 50_000;
89
- const MAX_TOTAL_BYTES = 1024 * 1024 * 1024;
90
- const MAX_FILE_BYTES = 256 * 1024 * 1024;
69
+ const MAX_FILES = 10_000;
70
+ // Source bytes cross a Worker boundary and are copied into the immutable VFS
71
+ // and browser realm. Keep the external-memory ceiling well below the V8 heap
72
+ // limit because Worker resourceLimits do not bound ArrayBuffer/WASM memory.
73
+ const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
74
+ const MAX_FILE_BYTES = 16 * 1024 * 1024;
75
+ const MAX_CAPSULE_BYTES = 32 * 1024 * 1024;
91
76
  const MAX_DEPTH = 64;
92
77
  const HASH_CHUNK_BYTES = 1024 * 1024;
93
78
  const EXACT_VERSION =
@@ -96,7 +81,9 @@ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
96
81
 
97
82
  export const _deps = {
98
83
  fs,
84
+ onVfsSnapshotCaptured: null,
99
85
  processBrokerRunSync: null,
86
+ Worker,
100
87
  };
101
88
 
102
89
  const require = createRequire(import.meta.url);
@@ -141,7 +128,7 @@ export function getMcpStdioPackageMaterializationRoot(options = {}) {
141
128
  return path.resolve(
142
129
  options.root ||
143
130
  process.env.CC_MCP_PACKAGE_MATERIALIZATION_ROOT ||
144
- path.join(getCacheDir(), "mcp-stdio-package-materializations-v2"),
131
+ path.join(getCacheDir(), "mcp-stdio-package-materializations-v3"),
145
132
  );
146
133
  }
147
134
 
@@ -151,7 +138,7 @@ export function getMcpStdioPackageMaterializationIndexPath(options = {}) {
151
138
  process.env.CC_MCP_PACKAGE_MATERIALIZATION_INDEX ||
152
139
  path.join(
153
140
  getMachineSecurityAnchorDir(),
154
- "mcp-stdio-package-materializations-v2.json",
141
+ "mcp-stdio-package-materializations-v3.json",
155
142
  ),
156
143
  );
157
144
  }
@@ -523,6 +510,10 @@ function resolvePackageEntrypoint(treeRoot, exact, requestedBin) {
523
510
  });
524
511
  }
525
512
 
513
+ function materializationFileMode(mode) {
514
+ return Number(mode) & (process.platform === "win32" ? 0o666 : 0o777);
515
+ }
516
+
526
517
  function hashFile(file, expectedBytes) {
527
518
  let descriptor;
528
519
  try {
@@ -563,13 +554,26 @@ function hashFile(file, expectedBytes) {
563
554
  ) {
564
555
  throw new Error("file changed while it was being hashed");
565
556
  }
566
- return digest.digest("hex");
557
+ return Object.freeze({
558
+ sha256: digest.digest("hex"),
559
+ // Node can synthesize pathname and descriptor execute bits differently
560
+ // on Windows. Record the descriptor view used to read the bytes and keep
561
+ // only permissions; the regular-file type is validated independently.
562
+ mode: materializationFileMode(after.mode),
563
+ });
567
564
  } finally {
568
565
  if (descriptor !== undefined) _deps.fs.closeSync(descriptor);
569
566
  }
570
567
  }
571
568
 
572
- function collectTree(root) {
569
+ function collectTree(
570
+ root,
571
+ {
572
+ maxFiles = MAX_FILES,
573
+ maxTotalBytes = MAX_TOTAL_BYTES,
574
+ maxFileBytes = MAX_FILE_BYTES,
575
+ } = {},
576
+ ) {
573
577
  const files = [];
574
578
  let totalBytes = 0;
575
579
  const visit = (directory, depth) => {
@@ -598,22 +602,23 @@ function collectTree(root) {
598
602
  );
599
603
  }
600
604
  const stat = _deps.fs.statSync(absolute, { bigint: true });
601
- if (stat.size < 0n || stat.size > BigInt(MAX_FILE_BYTES)) {
605
+ if (stat.size < 0n || stat.size > BigInt(maxFileBytes)) {
602
606
  throw new Error(
603
607
  `materialized file exceeds the size limit: ${relative}`,
604
608
  );
605
609
  }
606
610
  totalBytes += Number(stat.size);
607
- if (files.length >= MAX_FILES || totalBytes > MAX_TOTAL_BYTES) {
611
+ if (files.length >= maxFiles || totalBytes > maxTotalBytes) {
608
612
  throw new Error(
609
613
  "materialized dependency tree exceeds its aggregate budget",
610
614
  );
611
615
  }
616
+ const attested = hashFile(absolute, Number(stat.size));
612
617
  files.push({
613
618
  path: relative,
614
619
  bytes: Number(stat.size),
615
- mode: Number(stat.mode),
616
- sha256: hashFile(absolute, Number(stat.size)),
620
+ mode: attested.mode,
621
+ sha256: attested.sha256,
617
622
  });
618
623
  }
619
624
  };
@@ -648,172 +653,392 @@ function resolveContainedPath(root, relative, label) {
648
653
  return resolved;
649
654
  }
650
655
 
651
- function copyAttestedFile(sourceRoot, snapshotRoot, record) {
652
- const source = resolveContainedPath(
653
- sourceRoot,
654
- record.path,
655
- "MCP capsule source",
656
+ function snapshotFileIdentity(stat) {
657
+ return Object.freeze({
658
+ dev: stat.dev,
659
+ ino: stat.ino,
660
+ mode: stat.mode,
661
+ size: stat.size,
662
+ mtimeNs: stat.mtimeNs,
663
+ ctimeNs: stat.ctimeNs,
664
+ });
665
+ }
666
+
667
+ function sameSnapshotFileIdentity(left, right) {
668
+ return Boolean(
669
+ left &&
670
+ right &&
671
+ left.dev === right.dev &&
672
+ left.ino === right.ino &&
673
+ left.mode === right.mode &&
674
+ left.size === right.size &&
675
+ left.mtimeNs === right.mtimeNs &&
676
+ left.ctimeNs === right.ctimeNs,
656
677
  );
657
- const destination = resolveContainedPath(
658
- snapshotRoot,
659
- record.path,
660
- "MCP capsule snapshot",
678
+ }
679
+
680
+ function openSnapshotInputDescriptor(file) {
681
+ return _deps.fs.openSync(
682
+ file,
683
+ Number(_deps.fs.constants.O_RDONLY) |
684
+ Number(_deps.fs.constants.O_NOFOLLOW || 0) |
685
+ Number(_deps.fs.constants.O_NONBLOCK || 0),
661
686
  );
662
- _deps.fs.mkdirSync(path.dirname(destination), {
663
- recursive: true,
664
- mode: 0o700,
665
- });
666
- let sourceDescriptor;
667
- let destinationDescriptor;
687
+ }
688
+
689
+ function snapshotIdentityThroughDescriptor(file) {
690
+ let descriptor;
668
691
  try {
669
- sourceDescriptor = _deps.fs.openSync(
670
- source,
671
- Number(_deps.fs.constants.O_RDONLY) |
672
- Number(_deps.fs.constants.O_NOFOLLOW || 0) |
673
- Number(_deps.fs.constants.O_NONBLOCK || 0),
674
- );
675
- const before = _deps.fs.fstatSync(sourceDescriptor, { bigint: true });
676
- if (!before.isFile() || before.size !== BigInt(record.bytes)) {
677
- throw new Error(`MCP capsule source changed before copy: ${record.path}`);
692
+ descriptor = openSnapshotInputDescriptor(file);
693
+ const stat = _deps.fs.fstatSync(descriptor, { bigint: true });
694
+ if (!stat.isFile()) {
695
+ throw new Error("MCP capsule build input descriptor is not regular");
678
696
  }
679
- destinationDescriptor = _deps.fs.openSync(
680
- destination,
681
- Number(_deps.fs.constants.O_WRONLY) |
682
- Number(_deps.fs.constants.O_CREAT) |
683
- Number(_deps.fs.constants.O_EXCL) |
684
- Number(_deps.fs.constants.O_NOFOLLOW || 0),
685
- 0o600,
697
+ return snapshotFileIdentity(stat);
698
+ } finally {
699
+ if (descriptor !== undefined) _deps.fs.closeSync(descriptor);
700
+ }
701
+ }
702
+
703
+ function readSnapshotInputAttestation(canonicalSnapshotRoot, inputRelative) {
704
+ const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
705
+ const requested = resolveContainedPath(
706
+ canonicalSnapshotRoot,
707
+ inputRelative,
708
+ "MCP capsule build input",
709
+ );
710
+ const pathBefore = _deps.fs.lstatSync(requested, { bigint: true });
711
+ if (pathBefore.isSymbolicLink() || !pathBefore.isFile()) {
712
+ throw new Error(
713
+ `MCP capsule build input is not one regular file: ${inputRelative}`,
686
714
  );
687
- const digest = crypto.createHash("sha256");
688
- const chunk = Buffer.allocUnsafe(
689
- Math.max(1, Math.min(record.bytes, HASH_CHUNK_BYTES)),
715
+ }
716
+ const canonicalPath = realpath(requested);
717
+ const reboundRelative = path
718
+ .relative(canonicalSnapshotRoot, canonicalPath)
719
+ .split(path.sep)
720
+ .join("/");
721
+ if (reboundRelative !== inputRelative) {
722
+ throw new Error(
723
+ `MCP capsule build input changed through a path alias: ${inputRelative}`,
690
724
  );
725
+ }
726
+
727
+ let descriptor;
728
+ try {
729
+ descriptor = openSnapshotInputDescriptor(canonicalPath);
730
+ const descriptorBefore = _deps.fs.fstatSync(descriptor, { bigint: true });
731
+ const pathIdentityBefore = snapshotFileIdentity(pathBefore);
732
+ const descriptorIdentityBefore = snapshotFileIdentity(descriptorBefore);
733
+ // Node's Windows pathname and descriptor stat projections do not share a
734
+ // stable dev/ino namespace on every filesystem. Bind the pathname to the
735
+ // primary reader with a second descriptor instead: descriptor-to-
736
+ // descriptor identity is stable, while pathname metadata is checked only
737
+ // against the later pathname view. This retains file-ID binding without
738
+ // falling back to mutable size/timestamp comparisons.
739
+ const pathnameDescriptorIdentityBefore =
740
+ snapshotIdentityThroughDescriptor(canonicalPath);
741
+ if (
742
+ !descriptorBefore.isFile() ||
743
+ descriptorBefore.size < 0n ||
744
+ descriptorBefore.size > BigInt(MAX_FILE_BYTES) ||
745
+ !sameSnapshotFileIdentity(
746
+ descriptorIdentityBefore,
747
+ pathnameDescriptorIdentityBefore,
748
+ )
749
+ ) {
750
+ throw new Error(
751
+ `MCP capsule build input identity changed before read: ${inputRelative}`,
752
+ );
753
+ }
754
+
755
+ const bytes = Number(descriptorBefore.size);
756
+ // Slow buffers own their ArrayBuffer, so ownership can be transferred to
757
+ // the isolated builder Worker without detaching an unrelated slab.
758
+ const content = Buffer.allocUnsafeSlow(bytes);
691
759
  let offset = 0;
692
- while (offset < record.bytes) {
760
+ while (offset < bytes) {
693
761
  const count = _deps.fs.readSync(
694
- sourceDescriptor,
695
- chunk,
696
- 0,
697
- Math.min(chunk.length, record.bytes - offset),
762
+ descriptor,
763
+ content,
764
+ offset,
765
+ bytes - offset,
698
766
  offset,
699
767
  );
700
768
  if (count <= 0) {
701
- throw new Error(`MCP capsule source ended during copy: ${record.path}`);
702
- }
703
- digest.update(chunk.subarray(0, count));
704
- let written = 0;
705
- while (written < count) {
706
- const writeCount = _deps.fs.writeSync(
707
- destinationDescriptor,
708
- chunk,
709
- written,
710
- count - written,
711
- offset + written,
769
+ throw new Error(
770
+ `MCP capsule build input ended during read: ${inputRelative}`,
712
771
  );
713
- if (writeCount <= 0) {
714
- throw new Error(
715
- `MCP capsule snapshot ended during copy: ${record.path}`,
716
- );
717
- }
718
- written += writeCount;
719
772
  }
720
773
  offset += count;
721
774
  }
722
- const after = _deps.fs.fstatSync(sourceDescriptor, { bigint: true });
775
+
776
+ const descriptorAfter = _deps.fs.fstatSync(descriptor, { bigint: true });
777
+ const pathAfter = _deps.fs.lstatSync(requested, { bigint: true });
778
+ const canonicalPathAfter = realpath(requested);
723
779
  if (
724
- after.dev !== before.dev ||
725
- after.ino !== before.ino ||
726
- after.size !== before.size ||
727
- after.mtimeNs !== before.mtimeNs ||
728
- after.ctimeNs !== before.ctimeNs ||
729
- digest.digest("hex") !== record.sha256
780
+ pathAfter.isSymbolicLink() ||
781
+ !pathAfter.isFile() ||
782
+ canonicalPathAfter !== canonicalPath
730
783
  ) {
731
- throw new Error(`MCP capsule source changed during copy: ${record.path}`);
784
+ throw new Error(
785
+ `MCP capsule build input changed during read: ${inputRelative}`,
786
+ );
732
787
  }
733
- _deps.fs.fchmodSync(destinationDescriptor, record.mode & 0o777);
734
- _deps.fs.fsyncSync(destinationDescriptor);
735
- } finally {
736
- if (destinationDescriptor !== undefined) {
737
- _deps.fs.closeSync(destinationDescriptor);
788
+ const descriptorIdentityAfter = snapshotFileIdentity(descriptorAfter);
789
+ const pathIdentityAfter = snapshotFileIdentity(pathAfter);
790
+ const pathnameDescriptorIdentityAfter =
791
+ snapshotIdentityThroughDescriptor(canonicalPathAfter);
792
+ if (
793
+ !sameSnapshotFileIdentity(
794
+ descriptorIdentityBefore,
795
+ descriptorIdentityAfter,
796
+ ) ||
797
+ !sameSnapshotFileIdentity(pathIdentityBefore, pathIdentityAfter) ||
798
+ !sameSnapshotFileIdentity(
799
+ descriptorIdentityAfter,
800
+ pathnameDescriptorIdentityAfter,
801
+ )
802
+ ) {
803
+ throw new Error(
804
+ `MCP capsule build input changed during read: ${inputRelative}`,
805
+ );
738
806
  }
739
- if (sourceDescriptor !== undefined) _deps.fs.closeSync(sourceDescriptor);
740
- }
741
- if (hashFile(destination, record.bytes) !== record.sha256) {
742
- throw new Error(`MCP capsule snapshot copy is invalid: ${record.path}`);
807
+ return Object.freeze({
808
+ path: inputRelative,
809
+ canonicalPath,
810
+ bytes,
811
+ sha256: sha256(content),
812
+ identity: descriptorIdentityAfter,
813
+ content,
814
+ });
815
+ } finally {
816
+ if (descriptor !== undefined) _deps.fs.closeSync(descriptor);
743
817
  }
744
818
  }
745
819
 
746
- function createAttestedSnapshot(treeRoot, closure, staging) {
747
- const snapshotRoot = path.join(
748
- staging,
749
- `.capsule-source-${crypto.randomUUID()}`,
750
- );
751
- _deps.fs.mkdirSync(snapshotRoot, { recursive: false, mode: 0o700 });
752
- for (const record of closure.files) {
753
- copyAttestedFile(treeRoot, snapshotRoot, record);
754
- }
755
- const observed = collectTree(snapshotRoot);
820
+ function readPinnedCapsuleAsset({
821
+ root,
822
+ relativePath,
823
+ bytes,
824
+ sha256: expectedSha256,
825
+ label,
826
+ }) {
827
+ const observed = readSnapshotInputAttestation(root, relativePath);
756
828
  if (
757
- observed.fileCount !== closure.fileCount ||
758
- observed.totalBytes !== closure.totalBytes ||
759
- observed.closureDigest !== closure.closureDigest ||
760
- canonicalJson(observed.files) !== canonicalJson(closure.files)
829
+ observed.bytes !== bytes ||
830
+ observed.sha256 !== expectedSha256 ||
831
+ materializationFileMode(observed.identity.mode) === 0
761
832
  ) {
762
- throw new Error(
763
- "MCP capsule source snapshot does not match its attestation",
764
- );
833
+ throw new Error(`${label} identity is invalid`);
765
834
  }
766
- return snapshotRoot;
835
+ return observed;
767
836
  }
768
837
 
769
- function resolveCapsuleBuilderBinary() {
770
- const platform = `${process.platform}-${process.arch}`;
771
- const expected = CAPSULE_BUILDER_BINARIES[platform];
772
- if (!expected) {
773
- throw new Error(`MCP capsule builder does not support ${platform}`);
774
- }
775
- const packageJsonPath = require.resolve(
776
- `${expected.packageName}/package.json`,
838
+ function resolvePinnedCapsuleBuilder() {
839
+ // Vitest can evaluate this module through a non-file URL when desktop tests
840
+ // import the CLI process broker transitively. Resolve host-only assets only
841
+ // when a capsule build actually needs them; production builds still fail
842
+ // closed if the module does not have a file-backed identity.
843
+ const workerPath = fileURLToPath(
844
+ new URL("./mcp-stdio-capsule-builder-worker.cjs", import.meta.url),
777
845
  );
778
- const packageJson = JSON.parse(
779
- _deps.fs.readFileSync(packageJsonPath, "utf8"),
846
+ const resolverPath = fileURLToPath(
847
+ new URL("./mcp-stdio-immutable-vfs-resolver.cjs", import.meta.url),
780
848
  );
849
+ const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
850
+ const packageJsonPath = realpath(
851
+ require.resolve("esbuild-wasm/package.json"),
852
+ );
853
+ const packageRoot = path.dirname(packageJsonPath);
854
+ const packageJsonAsset = readSnapshotInputAttestation(
855
+ packageRoot,
856
+ "package.json",
857
+ );
858
+ let packageJson;
859
+ try {
860
+ packageJson = JSON.parse(packageJsonAsset.content.toString("utf8"));
861
+ } catch {
862
+ throw new Error("MCP capsule builder package identity is invalid");
863
+ }
781
864
  if (
782
- packageJson.name !== expected.packageName ||
865
+ packageJson.name !== CAPSULE_BUILDER ||
783
866
  packageJson.version !== CAPSULE_BUILDER_VERSION
784
867
  ) {
785
868
  throw new Error(
786
- `MCP capsule builder must be ${expected.packageName}@${CAPSULE_BUILDER_VERSION}`,
869
+ `MCP capsule builder must be ${CAPSULE_BUILDER}@${CAPSULE_BUILDER_VERSION}`,
787
870
  );
788
871
  }
789
- const candidate = path.join(
790
- path.dirname(packageJsonPath),
791
- ...expected.relativePath.split("/"),
792
- );
793
- const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
794
- const binary = realpath(candidate);
795
- const stat = _deps.fs.lstatSync(binary);
796
- if (
797
- stat.isSymbolicLink() ||
798
- !stat.isFile() ||
799
- stat.size <= 0 ||
800
- stat.size > MAX_FILE_BYTES ||
801
- hashFile(binary, stat.size) !== expected.sha256
802
- ) {
803
- throw new Error("MCP capsule builder binary identity is invalid");
804
- }
872
+ const api = readPinnedCapsuleAsset({
873
+ root: packageRoot,
874
+ relativePath: "lib/browser.js",
875
+ bytes: CAPSULE_BUILDER_API_BYTES,
876
+ sha256: CAPSULE_BUILDER_API_SHA256,
877
+ label: "MCP capsule builder API",
878
+ });
879
+ const wasm = readPinnedCapsuleAsset({
880
+ root: packageRoot,
881
+ relativePath: "esbuild.wasm",
882
+ bytes: CAPSULE_BUILDER_WASM_BYTES,
883
+ sha256: CAPSULE_BUILDER_WASM_SHA256,
884
+ label: "MCP capsule builder WASM",
885
+ });
886
+ const worker = readPinnedCapsuleAsset({
887
+ root: path.dirname(workerPath),
888
+ relativePath: path.basename(workerPath),
889
+ bytes: CAPSULE_BUILDER_WORKER_BYTES,
890
+ sha256: CAPSULE_BUILDER_WORKER_SHA256,
891
+ label: "MCP capsule builder Worker",
892
+ });
893
+ const resolver = readPinnedCapsuleAsset({
894
+ root: path.dirname(resolverPath),
895
+ relativePath: path.basename(resolverPath),
896
+ bytes: CAPSULE_RESOLVER_BYTES,
897
+ sha256: CAPSULE_RESOLVER_SHA256,
898
+ label: "MCP capsule immutable VFS resolver",
899
+ });
805
900
  return Object.freeze({
806
- binary,
807
- platform,
808
- sha256: expected.sha256,
901
+ apiSource: api.content.toString("utf8"),
902
+ apiBytes: api.bytes,
903
+ apiSha256: api.sha256,
904
+ wasmBytes: wasm.content,
905
+ wasmSha256: wasm.sha256,
906
+ workerSource: worker.content.toString("utf8"),
907
+ workerSha256: worker.sha256,
908
+ resolverSource: resolver.content.toString("utf8"),
909
+ resolverSha256: resolver.sha256,
809
910
  });
810
911
  }
811
912
 
913
+ function capsuleWorkerError(payload) {
914
+ const error = new Error(
915
+ String(payload?.message || "MCP capsule builder Worker failed").slice(
916
+ 0,
917
+ 8_000,
918
+ ),
919
+ );
920
+ error.name = String(payload?.name || "Error").slice(0, 200);
921
+ if (typeof payload?.code === "string") error.code = payload.code;
922
+ if (typeof payload?.stack === "string") error.stack = payload.stack;
923
+ return error;
924
+ }
925
+
926
+ async function runCapsuleBuilderWorker({
927
+ builder,
928
+ files,
929
+ fileCount,
930
+ totalBytes,
931
+ entryPath,
932
+ vfsRoot,
933
+ banner,
934
+ }) {
935
+ const nonce = crypto.randomBytes(32).toString("hex");
936
+ const transferList = [builder.wasmBytes.buffer];
937
+ for (const record of files) transferList.push(record[1].buffer);
938
+ let worker;
939
+ let timer;
940
+ const listeners = {};
941
+ try {
942
+ worker = new _deps.Worker(builder.workerSource, {
943
+ eval: true,
944
+ resourceLimits: {
945
+ maxOldGenerationSizeMb: CAPSULE_WORKER_MAX_OLD_GENERATION_MB,
946
+ maxYoungGenerationSizeMb: 64,
947
+ stackSizeMb: 8,
948
+ },
949
+ transferList,
950
+ workerData: {
951
+ schema: CAPSULE_WORKER_SCHEMA,
952
+ nonce,
953
+ browserApiSource: builder.apiSource,
954
+ resolverSource: builder.resolverSource,
955
+ wasmBytes: builder.wasmBytes,
956
+ builderVersion: CAPSULE_BUILDER_VERSION,
957
+ files,
958
+ fileCount,
959
+ totalBytes,
960
+ entryPath,
961
+ vfsRoot,
962
+ banner,
963
+ maxOutputBytes: MAX_CAPSULE_BYTES,
964
+ maxMetafileBytes: 16 * 1024 * 1024,
965
+ },
966
+ });
967
+ return await new Promise((resolve, reject) => {
968
+ let settled = false;
969
+ let terminal = null;
970
+ const fail = (error) => {
971
+ if (settled) return;
972
+ settled = true;
973
+ reject(error);
974
+ };
975
+ listeners.message = (message) => {
976
+ if (terminal) {
977
+ fail(
978
+ new Error(
979
+ "MCP capsule builder Worker emitted two terminal messages",
980
+ ),
981
+ );
982
+ return;
983
+ }
984
+ if (!message || message.nonce !== nonce) {
985
+ fail(new Error("MCP capsule builder Worker nonce mismatch"));
986
+ return;
987
+ }
988
+ terminal = message;
989
+ };
990
+ listeners.messageerror = () => {
991
+ fail(new Error("MCP capsule builder Worker message was not cloneable"));
992
+ };
993
+ listeners.error = (error) => fail(error);
994
+ listeners.exit = (code) => {
995
+ if (settled) return;
996
+ if (code !== 0) {
997
+ fail(
998
+ new Error(`MCP capsule builder Worker exited with status ${code}`),
999
+ );
1000
+ return;
1001
+ }
1002
+ if (!terminal) {
1003
+ fail(new Error("MCP capsule builder Worker exited without a result"));
1004
+ return;
1005
+ }
1006
+ if (terminal.ok !== true) {
1007
+ fail(capsuleWorkerError(terminal.error));
1008
+ return;
1009
+ }
1010
+ settled = true;
1011
+ resolve(terminal);
1012
+ };
1013
+ worker.on("message", listeners.message);
1014
+ worker.once("messageerror", listeners.messageerror);
1015
+ worker.once("error", listeners.error);
1016
+ worker.once("exit", listeners.exit);
1017
+ timer = setTimeout(() => {
1018
+ fail(new Error("MCP capsule builder Worker timed out"));
1019
+ }, CAPSULE_BUILD_TIMEOUT_MS);
1020
+ timer.unref?.();
1021
+ });
1022
+ } finally {
1023
+ if (timer) clearTimeout(timer);
1024
+ if (worker) {
1025
+ if (listeners.message) worker.off("message", listeners.message);
1026
+ if (listeners.messageerror) {
1027
+ worker.off("messageerror", listeners.messageerror);
1028
+ }
1029
+ if (listeners.error) worker.off("error", listeners.error);
1030
+ if (listeners.exit) worker.off("exit", listeners.exit);
1031
+ await worker.terminate();
1032
+ }
1033
+ }
1034
+ }
1035
+
812
1036
  function capsuleRuntimeGuard() {
813
1037
  return `;(() => {
814
1038
  const Module = require("node:module");
815
1039
  const allowed = new Set(Module.builtinModules.flatMap((name) => [name, name.startsWith("node:") ? name : "node:" + name]));
816
1040
  const originalLoad = Module._load;
1041
+ const originalResolveFilename = Module._resolveFilename;
817
1042
  const blocked = (kind, request) => {
818
1043
  const error = new Error("MCP stdio capsule blocked " + kind + (request === undefined ? "" : ": " + String(request)));
819
1044
  error.code = kind === "native module loading" ? "CC_MCP_STDIO_NATIVE_MODULE_BLOCKED" : "CC_MCP_STDIO_EXTERNAL_MODULE_BLOCKED";
@@ -828,6 +1053,15 @@ function capsuleRuntimeGuard() {
828
1053
  return Reflect.apply(originalLoad, this, arguments);
829
1054
  },
830
1055
  });
1056
+ Object.defineProperty(Module, "_resolveFilename", {
1057
+ configurable: false,
1058
+ enumerable: false,
1059
+ writable: false,
1060
+ value: function(request) {
1061
+ if (typeof request !== "string" || !allowed.has(request)) blocked("external module resolution", request);
1062
+ return Reflect.apply(originalResolveFilename, this, arguments);
1063
+ },
1064
+ });
831
1065
  Object.defineProperty(process, "dlopen", {
832
1066
  configurable: false,
833
1067
  enumerable: false,
@@ -837,177 +1071,325 @@ function capsuleRuntimeGuard() {
837
1071
  })();`;
838
1072
  }
839
1073
 
840
- function buildCapsule({
1074
+ export function esbuildRelativeEntrypointArg(
1075
+ entrypointRelative,
1076
+ canonicalSnapshotRoot,
1077
+ canonicalEntrypoint,
1078
+ ) {
1079
+ if (
1080
+ typeof entrypointRelative !== "string" ||
1081
+ entrypointRelative.length === 0 ||
1082
+ entrypointRelative.includes("\0") ||
1083
+ entrypointRelative.includes("\\") ||
1084
+ path.posix.isAbsolute(entrypointRelative) ||
1085
+ /^[A-Za-z]:/.test(entrypointRelative) ||
1086
+ path.posix.normalize(entrypointRelative) !== entrypointRelative ||
1087
+ entrypointRelative === "." ||
1088
+ entrypointRelative === ".." ||
1089
+ entrypointRelative.startsWith("../")
1090
+ ) {
1091
+ throw new Error(
1092
+ "MCP capsule entrypoint must be one canonical relative POSIX path",
1093
+ );
1094
+ }
1095
+ for (const [label, candidate] of [
1096
+ ["snapshot root", canonicalSnapshotRoot],
1097
+ ["entrypoint", canonicalEntrypoint],
1098
+ ]) {
1099
+ if (
1100
+ typeof candidate !== "string" ||
1101
+ candidate.includes("\0") ||
1102
+ !path.isAbsolute(candidate) ||
1103
+ path.normalize(candidate) !== candidate
1104
+ ) {
1105
+ throw new Error(`MCP capsule ${label} must be canonical and absolute`);
1106
+ }
1107
+ }
1108
+ const reboundRelative = path
1109
+ .relative(canonicalSnapshotRoot, canonicalEntrypoint)
1110
+ .split(path.sep)
1111
+ .join("/");
1112
+ if (reboundRelative !== entrypointRelative) {
1113
+ throw new Error("MCP capsule entrypoint escaped its canonical snapshot");
1114
+ }
1115
+ // This is a logical source label for esbuild's stdin mode, not a positional
1116
+ // entrypoint. Positional node_modules paths are package-classified by the
1117
+ // native Windows CLI even when they are absolute or repeatedly prefixed.
1118
+ return entrypointRelative;
1119
+ }
1120
+
1121
+ async function buildCapsule({
841
1122
  treeRoot,
842
1123
  entrypointRelative,
843
1124
  closure,
844
1125
  staging,
845
- processBrokerRunSync,
846
- env,
847
1126
  }) {
848
- const snapshotRoot = createAttestedSnapshot(treeRoot, closure, staging);
849
1127
  const capsuleRoot = path.join(staging, "capsule");
850
1128
  const capsulePath = path.join(staging, ...CAPSULE_RELATIVE_PATH.split("/"));
851
- const metafilePath = path.join(
852
- staging,
853
- `.capsule-meta-${crypto.randomUUID()}.json`,
854
- );
855
1129
  const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
856
- const canonicalSnapshotRoot = realpath(snapshotRoot);
1130
+ const canonicalTreeRoot = realpath(treeRoot);
1131
+ const vfsRoot = "/chainlesschain-source";
1132
+ const wrapperRelative = "__chainlesschain_capsule_entry__.cjs";
1133
+ const wrapperPath = `${vfsRoot}/${wrapperRelative}`;
1134
+ const maxOutputBytes = MAX_CAPSULE_BYTES;
1135
+ const maxMetafileBytes = 16 * 1024 * 1024;
857
1136
  _deps.fs.mkdirSync(capsuleRoot, { recursive: false, mode: 0o700 });
858
- try {
859
- const snapshotEntrypoint = resolveContainedPath(
860
- snapshotRoot,
861
- entrypointRelative,
862
- "MCP capsule entrypoint",
1137
+ const sourceEntrypoint = resolveContainedPath(
1138
+ canonicalTreeRoot,
1139
+ entrypointRelative,
1140
+ "MCP capsule entrypoint",
1141
+ );
1142
+ const canonicalEntrypoint = realpath(sourceEntrypoint);
1143
+ const boundEntrypointRelative = esbuildRelativeEntrypointArg(
1144
+ entrypointRelative,
1145
+ canonicalTreeRoot,
1146
+ canonicalEntrypoint,
1147
+ );
1148
+ if (closure.files.some((record) => record.path === wrapperRelative)) {
1149
+ throw new Error("MCP capsule source collides with its host wrapper");
1150
+ }
1151
+
1152
+ const closureByPath = new Map(
1153
+ closure.files.map((record) => [record.path, record]),
1154
+ );
1155
+ const expectedByVfsPath = new Map();
1156
+ const workerFiles = [];
1157
+ for (const record of closure.files) {
1158
+ const observed = readSnapshotInputAttestation(
1159
+ canonicalTreeRoot,
1160
+ record.path,
863
1161
  );
864
- const canonicalEntrypointRelative = path
865
- .relative(canonicalSnapshotRoot, realpath(snapshotEntrypoint))
866
- .split(path.sep)
867
- .join("/");
868
- if (canonicalEntrypointRelative !== entrypointRelative) {
869
- throw new Error("MCP capsule entrypoint changed through a path alias");
870
- }
871
- const builderEntrypoint = `.${path.sep}${entrypointRelative
872
- .split("/")
873
- .join(path.sep)}`;
874
- const builder = resolveCapsuleBuilderBinary();
875
- const runThroughProcessBroker =
876
- processBrokerRunSync || _deps.processBrokerRunSync;
877
- if (typeof runThroughProcessBroker !== "function") {
1162
+ if (
1163
+ observed.bytes !== record.bytes ||
1164
+ observed.sha256 !== record.sha256 ||
1165
+ materializationFileMode(observed.identity.mode) !== record.mode
1166
+ ) {
878
1167
  throw new Error(
879
- "MCP capsule construction requires a host-owned Process Broker runner",
1168
+ `MCP capsule build input changed before capture: ${record.path}`,
880
1169
  );
881
1170
  }
882
- const result = runThroughProcessBroker(
883
- builder.binary,
884
- [
885
- builderEntrypoint,
886
- "--bundle",
887
- "--charset=utf8",
888
- "--format=cjs",
889
- "--legal-comments=none",
890
- "--log-level=warning",
891
- `--metafile=${metafilePath}`,
892
- `--outfile=${capsulePath}`,
893
- "--packages=bundle",
894
- "--platform=node",
895
- "--preserve-symlinks",
896
- "--supported:dynamic-import=false",
897
- "--target=node22",
898
- "--tree-shaking=false",
899
- `--banner:js=${capsuleRuntimeGuard()}`,
900
- ],
901
- {
902
- cwd: canonicalSnapshotRoot,
903
- env: sanitizeMcpStdioHostEnvironment(env),
904
- encoding: "utf8",
905
- shell: false,
906
- windowsHide: true,
907
- maxBuffer: 8 * 1024 * 1024,
908
- },
1171
+ const vfsPath = `${vfsRoot}/${record.path}`;
1172
+ expectedByVfsPath.set(vfsPath, record);
1173
+ workerFiles.push([vfsPath, observed.content]);
1174
+ }
1175
+ if (!closureByPath.has(entrypointRelative)) {
1176
+ throw new Error("MCP capsule entrypoint is outside its closure");
1177
+ }
1178
+ const wrapperSpecifier = `./${boundEntrypointRelative}`;
1179
+ const wrapperSource = Buffer.from(
1180
+ `"use strict";\nrequire(${JSON.stringify(wrapperSpecifier)});\n`,
1181
+ "utf8",
1182
+ );
1183
+ const ownedWrapperSource = Buffer.allocUnsafeSlow(wrapperSource.length);
1184
+ wrapperSource.copy(ownedWrapperSource);
1185
+ const wrapperBytes = ownedWrapperSource.length;
1186
+ const wrapperSha256 = sha256(ownedWrapperSource);
1187
+ expectedByVfsPath.set(wrapperPath, {
1188
+ path: wrapperRelative,
1189
+ bytes: wrapperBytes,
1190
+ sha256: wrapperSha256,
1191
+ wrapper: true,
1192
+ });
1193
+ workerFiles.push([wrapperPath, ownedWrapperSource]);
1194
+
1195
+ const builder = resolvePinnedCapsuleBuilder();
1196
+ if (typeof _deps.onVfsSnapshotCaptured === "function") {
1197
+ await _deps.onVfsSnapshotCaptured(
1198
+ Object.freeze({
1199
+ treeRoot: canonicalTreeRoot,
1200
+ fileCount: closure.fileCount,
1201
+ totalBytes: closure.totalBytes,
1202
+ closureDigest: closure.closureDigest,
1203
+ }),
909
1204
  );
910
- const stderr = String(result?.stderr || "").trim();
911
- if (result?.error || result?.status !== 0 || stderr) {
912
- const detail = String(
913
- result?.error?.message || stderr || "esbuild failed",
914
- )
915
- .trim()
916
- .slice(0, 2000);
917
- throw new Error(`MCP capsule build failed closed: ${detail}`);
1205
+ }
1206
+ const result = await runCapsuleBuilderWorker({
1207
+ builder,
1208
+ files: workerFiles,
1209
+ fileCount: workerFiles.length,
1210
+ totalBytes: closure.totalBytes + wrapperBytes,
1211
+ entryPath: wrapperPath,
1212
+ vfsRoot,
1213
+ banner: capsuleRuntimeGuard(),
1214
+ });
1215
+ if (
1216
+ !(result.output instanceof Uint8Array) ||
1217
+ result.output.byteLength <= 0 ||
1218
+ result.output.byteLength > maxOutputBytes ||
1219
+ !result.metafile ||
1220
+ typeof result.metafile !== "object" ||
1221
+ !result.metafile.inputs ||
1222
+ typeof result.metafile.inputs !== "object" ||
1223
+ !result.metafile.outputs ||
1224
+ typeof result.metafile.outputs !== "object" ||
1225
+ Buffer.byteLength(JSON.stringify(result.metafile)) > maxMetafileBytes ||
1226
+ !Array.isArray(result.warnings) ||
1227
+ result.warnings.length !== 0 ||
1228
+ !result.audit ||
1229
+ result.audit.root !== vfsRoot ||
1230
+ result.audit.fileCount !== workerFiles.length ||
1231
+ !Array.isArray(result.audit.loaded) ||
1232
+ !Array.isArray(result.audit.resolutions)
1233
+ ) {
1234
+ throw new Error("MCP capsule builder Worker result is invalid");
1235
+ }
1236
+
1237
+ const metafileInputs = new Map();
1238
+ for (const [input, metadata] of Object.entries(result.metafile.inputs)) {
1239
+ const prefix = "cc-immutable-vfs:";
1240
+ if (!input.startsWith(prefix)) {
1241
+ throw new Error(`MCP capsule build reported a non-VFS input: ${input}`);
918
1242
  }
919
- const metafile = JSON.parse(_deps.fs.readFileSync(metafilePath, "utf8"));
1243
+ const vfsPath = input.slice(prefix.length);
920
1244
  if (
921
- !metafile ||
922
- typeof metafile !== "object" ||
923
- !metafile.inputs ||
924
- typeof metafile.inputs !== "object" ||
925
- !metafile.outputs ||
926
- typeof metafile.outputs !== "object"
1245
+ !vfsPath.startsWith(`${vfsRoot}/`) ||
1246
+ path.posix.normalize(vfsPath) !== vfsPath ||
1247
+ metafileInputs.has(vfsPath) ||
1248
+ !metadata ||
1249
+ typeof metadata !== "object"
927
1250
  ) {
1251
+ throw new Error(`MCP capsule build reported an unsafe input: ${input}`);
1252
+ }
1253
+ metafileInputs.set(vfsPath, metadata);
1254
+ }
1255
+ const loaded = [...result.audit.loaded];
1256
+ if (
1257
+ new Set(loaded).size !== loaded.length ||
1258
+ loaded.some(
1259
+ (input) =>
1260
+ typeof input !== "string" ||
1261
+ !input.startsWith(`${vfsRoot}/`) ||
1262
+ path.posix.normalize(input) !== input,
1263
+ ) ||
1264
+ canonicalJson([...metafileInputs.keys()].sort()) !==
1265
+ canonicalJson([...loaded].sort())
1266
+ ) {
1267
+ throw new Error("MCP capsule metafile did not match immutable VFS loads");
1268
+ }
1269
+ const inputs = loaded
1270
+ .filter((input) => input !== wrapperPath)
1271
+ .sort()
1272
+ .map((input) => {
1273
+ const expected = expectedByVfsPath.get(input);
1274
+ const metadata = metafileInputs.get(input);
1275
+ if (!expected || expected.wrapper || metadata?.bytes !== expected.bytes) {
1276
+ throw new Error(`MCP capsule build used an unattested input: ${input}`);
1277
+ }
1278
+ return {
1279
+ path: expected.path,
1280
+ bytes: expected.bytes,
1281
+ sha256: expected.sha256,
1282
+ };
1283
+ });
1284
+ const wrapperMetadata = metafileInputs.get(wrapperPath);
1285
+ if (
1286
+ wrapperMetadata?.bytes !== wrapperBytes ||
1287
+ !inputs.some((input) => input.path === entrypointRelative)
1288
+ ) {
1289
+ throw new Error("MCP capsule build did not bind its approved entrypoint");
1290
+ }
1291
+
1292
+ const externalBuiltins = [];
1293
+ if (Object.keys(result.metafile.outputs).length !== 1) {
1294
+ throw new Error("MCP capsule build emitted an invalid output graph");
1295
+ }
1296
+ for (const output of Object.values(result.metafile.outputs)) {
1297
+ if (!output || typeof output !== "object") {
928
1298
  throw new Error("MCP capsule build metadata is invalid");
929
1299
  }
930
- const sourceByPath = new Map(
931
- closure.files.map((record) => [record.path, record]),
932
- );
933
- const inputs = Object.keys(metafile.inputs)
934
- .map((input) => input.split(path.sep).join("/"))
935
- .sort()
936
- .map((input) => {
937
- const absolute = realpath(
938
- path.resolve(snapshotRoot, ...input.split("/")),
1300
+ for (const imported of output.imports || []) {
1301
+ if (!imported.external || !NODE_BUILTINS.has(imported.path)) {
1302
+ throw new Error(
1303
+ `MCP capsule build retained an external dependency: ${imported.path}`,
939
1304
  );
940
- const relative = path
941
- .relative(canonicalSnapshotRoot, absolute)
942
- .split(path.sep)
943
- .join("/");
944
- const record = sourceByPath.get(relative);
945
- if (!record) {
946
- throw new Error(
947
- `MCP capsule build used an unattested input: ${input}`,
948
- );
949
- }
950
- return {
951
- path: relative,
952
- bytes: record.bytes,
953
- sha256: record.sha256,
954
- };
955
- });
956
- if (new Set(inputs.map((input) => input.path)).size !== inputs.length) {
957
- throw new Error("MCP capsule build reported duplicate input aliases");
958
- }
959
- if (
960
- inputs.length === 0 ||
961
- !inputs.some((input) => input.path === entrypointRelative)
962
- ) {
963
- throw new Error("MCP capsule build did not bind its approved entrypoint");
964
- }
965
- const externalBuiltins = [];
966
- for (const output of Object.values(metafile.outputs)) {
967
- for (const imported of output.imports || []) {
968
- if (!imported.external || !NODE_BUILTINS.has(imported.path)) {
969
- throw new Error(
970
- `MCP capsule build retained an external dependency: ${imported.path}`,
971
- );
972
- }
973
- externalBuiltins.push(imported.path);
974
1305
  }
1306
+ externalBuiltins.push(imported.path);
975
1307
  }
976
- const postBuildClosure = collectTree(treeRoot);
1308
+ }
1309
+ for (const resolution of result.audit.resolutions) {
977
1310
  if (
978
- postBuildClosure.fileCount !== closure.fileCount ||
979
- postBuildClosure.totalBytes !== closure.totalBytes ||
980
- postBuildClosure.closureDigest !== closure.closureDigest ||
981
- canonicalJson(postBuildClosure.files) !== canonicalJson(closure.files)
1311
+ !resolution ||
1312
+ typeof resolution !== "object" ||
1313
+ (resolution.external === true
1314
+ ? !NODE_BUILTINS.has(resolution.path)
1315
+ : !expectedByVfsPath.has(resolution.path))
982
1316
  ) {
983
- throw new Error("MCP package dependency closure changed during bundling");
1317
+ throw new Error("MCP capsule resolver audit is invalid");
984
1318
  }
985
- const capsuleClosure = collectTree(capsuleRoot);
986
- if (
987
- capsuleClosure.fileCount !== 1 ||
988
- capsuleClosure.files[0]?.path !== path.basename(capsulePath)
989
- ) {
990
- throw new Error("MCP capsule output is not one regular file");
1319
+ }
1320
+
1321
+ const output = Buffer.from(
1322
+ result.output.buffer,
1323
+ result.output.byteOffset,
1324
+ result.output.byteLength,
1325
+ );
1326
+ let descriptor;
1327
+ try {
1328
+ descriptor = _deps.fs.openSync(
1329
+ capsulePath,
1330
+ Number(_deps.fs.constants.O_WRONLY) |
1331
+ Number(_deps.fs.constants.O_CREAT) |
1332
+ Number(_deps.fs.constants.O_EXCL) |
1333
+ Number(_deps.fs.constants.O_NOFOLLOW || 0),
1334
+ 0o600,
1335
+ );
1336
+ let offset = 0;
1337
+ while (offset < output.length) {
1338
+ const written = _deps.fs.writeSync(
1339
+ descriptor,
1340
+ output,
1341
+ offset,
1342
+ output.length - offset,
1343
+ offset,
1344
+ );
1345
+ if (written <= 0) throw new Error("MCP capsule output write stalled");
1346
+ offset += written;
991
1347
  }
992
- return Object.freeze({
993
- schema: CAPSULE_SCHEMA,
994
- relativePath: CAPSULE_RELATIVE_PATH,
995
- sha256: capsuleClosure.files[0].sha256,
996
- bytes: capsuleClosure.files[0].bytes,
997
- closureDigest: capsuleClosure.closureDigest,
998
- builder: CAPSULE_BUILDER,
999
- builderVersion: CAPSULE_BUILDER_VERSION,
1000
- builderPlatform: builder.platform,
1001
- builderBinarySha256: builder.sha256,
1002
- nodeTarget: "node22",
1003
- inputCount: inputs.length,
1004
- inputDigest: sha256(canonicalJson(inputs)),
1005
- externalBuiltins: Object.freeze([...new Set(externalBuiltins)].sort()),
1006
- });
1348
+ _deps.fs.fsyncSync(descriptor);
1007
1349
  } finally {
1008
- if (_deps.fs.existsSync(metafilePath)) _deps.fs.rmSync(metafilePath);
1009
- _deps.fs.rmSync(snapshotRoot, { recursive: true, force: true });
1350
+ if (descriptor !== undefined) _deps.fs.closeSync(descriptor);
1010
1351
  }
1352
+
1353
+ const postBuildClosure = collectTree(treeRoot);
1354
+ if (
1355
+ postBuildClosure.fileCount !== closure.fileCount ||
1356
+ postBuildClosure.totalBytes !== closure.totalBytes ||
1357
+ postBuildClosure.closureDigest !== closure.closureDigest ||
1358
+ canonicalJson(postBuildClosure.files) !== canonicalJson(closure.files)
1359
+ ) {
1360
+ throw new Error("MCP package dependency closure changed during bundling");
1361
+ }
1362
+ const capsuleClosure = collectTree(capsuleRoot, {
1363
+ maxFiles: 1,
1364
+ maxTotalBytes: MAX_CAPSULE_BYTES,
1365
+ maxFileBytes: MAX_CAPSULE_BYTES,
1366
+ });
1367
+ if (
1368
+ capsuleClosure.fileCount !== 1 ||
1369
+ capsuleClosure.files[0]?.path !== path.basename(capsulePath)
1370
+ ) {
1371
+ throw new Error("MCP capsule output is not one regular file");
1372
+ }
1373
+ return Object.freeze({
1374
+ schema: CAPSULE_SCHEMA,
1375
+ relativePath: CAPSULE_RELATIVE_PATH,
1376
+ sha256: capsuleClosure.files[0].sha256,
1377
+ bytes: capsuleClosure.files[0].bytes,
1378
+ closureDigest: capsuleClosure.closureDigest,
1379
+ builder: CAPSULE_BUILDER,
1380
+ builderVersion: CAPSULE_BUILDER_VERSION,
1381
+ builderWasmSha256: builder.wasmSha256,
1382
+ builderApiSha256: builder.apiSha256,
1383
+ builderWorkerSha256: builder.workerSha256,
1384
+ resolverSchema: CAPSULE_RESOLVER_SCHEMA,
1385
+ resolverSha256: builder.resolverSha256,
1386
+ wrapperSchema: CAPSULE_STDIN_WRAPPER_SCHEMA,
1387
+ wrapperSha256,
1388
+ nodeTarget: "node22",
1389
+ inputCount: inputs.length,
1390
+ inputDigest: sha256(canonicalJson(inputs)),
1391
+ externalBuiltins: Object.freeze([...new Set(externalBuiltins)].sort()),
1392
+ });
1011
1393
  }
1012
1394
 
1013
1395
  function assertSafeGeneration(root, generation) {
@@ -1042,15 +1424,19 @@ function validateManifest(manifest) {
1042
1424
  !/^[a-f0-9]{64}$/.test(manifest.capsule.sha256) ||
1043
1425
  !Number.isSafeInteger(manifest.capsule?.bytes) ||
1044
1426
  manifest.capsule.bytes <= 0 ||
1045
- manifest.capsule.bytes > MAX_FILE_BYTES ||
1427
+ manifest.capsule.bytes > MAX_CAPSULE_BYTES ||
1046
1428
  typeof manifest.capsule?.closureDigest !== "string" ||
1047
1429
  !/^[a-f0-9]{64}$/.test(manifest.capsule.closureDigest) ||
1048
1430
  manifest.capsule?.builder !== CAPSULE_BUILDER ||
1049
1431
  manifest.capsule?.builderVersion !== CAPSULE_BUILDER_VERSION ||
1050
- typeof manifest.capsule?.builderPlatform !== "string" ||
1051
- !CAPSULE_BUILDER_BINARIES[manifest.capsule.builderPlatform] ||
1052
- manifest.capsule?.builderBinarySha256 !==
1053
- CAPSULE_BUILDER_BINARIES[manifest.capsule.builderPlatform]?.sha256 ||
1432
+ manifest.capsule?.builderWasmSha256 !== CAPSULE_BUILDER_WASM_SHA256 ||
1433
+ manifest.capsule?.builderApiSha256 !== CAPSULE_BUILDER_API_SHA256 ||
1434
+ manifest.capsule?.builderWorkerSha256 !== CAPSULE_BUILDER_WORKER_SHA256 ||
1435
+ manifest.capsule?.resolverSchema !== CAPSULE_RESOLVER_SCHEMA ||
1436
+ manifest.capsule?.resolverSha256 !== CAPSULE_RESOLVER_SHA256 ||
1437
+ manifest.capsule?.wrapperSchema !== CAPSULE_STDIN_WRAPPER_SCHEMA ||
1438
+ typeof manifest.capsule?.wrapperSha256 !== "string" ||
1439
+ !/^[a-f0-9]{64}$/.test(manifest.capsule.wrapperSha256) ||
1054
1440
  manifest.capsule?.nodeTarget !== "node22" ||
1055
1441
  !Number.isSafeInteger(manifest.capsule?.inputCount) ||
1056
1442
  manifest.capsule.inputCount <= 0 ||
@@ -1163,7 +1549,11 @@ function verifyPublishedGeneration(root, record, expectedFingerprint) {
1163
1549
  const capsuleRoot = path.dirname(entrypoint);
1164
1550
  let capsuleClosure;
1165
1551
  try {
1166
- capsuleClosure = collectTree(capsuleRoot);
1552
+ capsuleClosure = collectTree(capsuleRoot, {
1553
+ maxFiles: 1,
1554
+ maxTotalBytes: MAX_CAPSULE_BYTES,
1555
+ maxFileBytes: MAX_CAPSULE_BYTES,
1556
+ });
1167
1557
  } catch (cause) {
1168
1558
  throw materializationError(
1169
1559
  MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
@@ -1209,7 +1599,7 @@ function verifyPublishedGeneration(root, record, expectedFingerprint) {
1209
1599
  });
1210
1600
  }
1211
1601
 
1212
- export function materializeMcpStdioNpmPackage({
1602
+ export async function materializeMcpStdioNpmPackage({
1213
1603
  approvalRecord,
1214
1604
  config,
1215
1605
  packageSpec,
@@ -1263,13 +1653,11 @@ export function materializeMcpStdioNpmPackage({
1263
1653
  const lock = validateNpmLock(treeRoot, source);
1264
1654
  const entry = resolvePackageEntrypoint(treeRoot, source, binName);
1265
1655
  const closure = collectTree(treeRoot);
1266
- const capsule = buildCapsule({
1656
+ const capsule = await buildCapsule({
1267
1657
  treeRoot,
1268
1658
  entrypointRelative: entry.entrypointRelative,
1269
1659
  closure,
1270
1660
  staging,
1271
- processBrokerRunSync,
1272
- env,
1273
1661
  });
1274
1662
  const generation = sha256(
1275
1663
  canonicalJson({