chainlesschain 0.165.5 → 0.165.8

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 (256) hide show
  1. package/package.json +2 -1
  2. package/src/assets/web-panel/assets/{AIOps-CEeYTeDB.js → AIOps-B3gWmXjo.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BeRUQ3V2.js → ActionButton-G9pdT-0e.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DrV718XF.js → Analytics-Ca30J7m2.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Cd2Frt7Q.js → AppLayout-BtVTm8PU.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CUJMlORU.js → Artifacts-BhViNa6u.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CXJcfT6p.js → Audit-DhKq-FnY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DOv6Z7H9.js → BackgroundAgents-BL0SVbnE.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BwdDyBI0.js → Backup-BWkblnFi.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BmLn4kYH.js → BaseInput-ClaRNrF8.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-COg8pAW2.js → Chat-sZE2Z77o.js} +4 -4
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-BAfKOnRe.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-BCP4uoU8.js → Checkbox-Cvhay5WM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-pRt6Y9JI.js → Codegen-BpejL-4N.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DLaprKhS.js → Col-g2Ki6g_1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DyATsz5f.js → Community-D2xKbNo9.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BXAaupZK.js → Compact-DbL1Pds3.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DpcJBELr.js → Compliance-DGtZCV4c.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DG4EFWyd.js → Cowork-4kQpoNsI.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-F6Oy5n2P.js → Cron-BxTYXXAN.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BOdgtalX.js → Crosschain-k0J0LT2N.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DT1dSkjH.js → DID-BaqhoPgN.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-NBUIqWzC.js → Dashboard-T4RVPIM1.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CSpHeMYB.js → Dropdown-DszOToWq.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CEG0xISr.js → EmailListRenderer-C9KJU7HF.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BTZ0BZna.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BuuCTn2V.js → Federation-BI-_2xZX.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-KAVceevk.js → FormItemContext-DsnDEPsl.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DnHpcQvW.js → GenericCardRenderer-BLncAKKK.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-C_B_0HwB.js → Git-BPQhGXDn.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DPSZuzuu.js → Governance-CyElbUQW.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BV-06V7q.js → Inference-CcSntBQD.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-wpDuupn6.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-Bseca1_I.js → Logs-CNEtr-wz.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-d3kM4DDL.js → Marketplace-Dd2notAJ.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DQ_xZ23i.js → McpTools-BBsN2CSW.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-CRIeYO4L.js → Memory-Dg2uTvwx.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BsllIa9l.js → MobileBridge-r1YMRiJV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-BUuQy30X.js → MobileProjects-C-89kSm4.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BOFDIzrC.js → Mtc-CrPZcVgf.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-BQg-vyvG.js → MtcAudit-sxi7hqJb.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-Bo1jrPdN.js → Multisig-CAgOMfF1.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Bmod8alw.js → NLProgramming-DafaPts3.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CGGFL8w-.js → Notes-CJM-xUW_.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BsZvCJxC.js → NotificationSettings-L0ZjfAh_.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-C1-Quo_h.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-BjOeDYOi.js → Organization-DeLtgAD8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DwVV3l5A.js → Overflow-CKC2b10z.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cu3DQT9W.js → P2P-eyf2rzY1.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Cg0gR7dH.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-CsAXxRN5.js → Permissions-B8p3W6JK.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-C-nNmnYp.js → PersonalDataHub-BnRMxTq3.js} +5 -5
  54. package/src/assets/web-panel/assets/{Pipeline-BOnW7nNc.js → Pipeline-DjCNI1li.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-ClLSyGKt.js → Privacy-u6gXLbWT.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DuLvjjq7.js → ProjectInit-BZAw4tkM.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-DQYYbDD9.js → ProjectSettings-B1Tr72q7.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Drw8MBGK.js → Projects-DO7HFuBQ.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-ckqHQe6K.js → Providers-BjCztyhj.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-DW5BXk-G.js → QrScannerModal-ZWyCUbR1.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DDLnhNTp.js → QuickAsk-DhDjeW4G.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-PJQ2C91U.js → Recommend-R_CN2MuU.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-s6wmda34.js → RemoteSession-uxFcujeu.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DC2sb-I3.js → Reputation-DX8D7rks.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-hPGk1eGj.js → Row-BYahQILD.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-RIvfZql0.js → RssFeed-CUSQ2tm-.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DFg1G1eR.js → Search-CnN_S8Cs.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-D6H5p8Dp.js → Security-DcZlh-O4.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-nxYiFV_w.js → Services-ZhBOjzIX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-BmgFNAdB.js → Skeleton-D5Qnq_lI.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CsaaD2av.js → Skills-BC9cZ3Be.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-yM5y9yvK.js → Sla-DauJ3E8_.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-CFVhEyyr.js → SyncSettings-DUvJbSct.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-DdO02Inn.js → Tasks-BXa-5noQ.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Eo2d8Llu.js → Templates-JxaiUt_3.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-ChESOXgy.js → Tenant-BW4ERQN8.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BJ7oDxTI.js → Terminal-DltUbdtv.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-hWX0fUvz.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-DVBZQnd-.js → Tokens-D_VEV60K.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-D89cQfVq.js → Trigger-CCXTCs0R.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BIVSb4-V.js → Trust-lkGRIPcG.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B58gWopl.js → UkeySign-BfGeQ9HX.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DhHI7bmG.js → VideoEditing-BEC7pYvc.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-C4FheWS_.js → Wallet-BZdG24o0.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DnPRq-Bx.js → WebAuthn-yoEddedp.js} +3 -3
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BerRPjpJ.js → WorkflowEditor-BXZEprOW.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BzZ52iKr.js → chat-gg0bzZal.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BI_E4BAn.js → colors-DjRVRuin.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-d50mr3yk.js → compact-item-su-dieAc.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-C6FsQ1IH.js → createContext-Ci3S8oKF.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DLZEMgLP.js → hasIn-D8F10heH.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-D-aBumtY.js → index-11wtYQiF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CjuKZBqJ.js → index-1IgxgALJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-9H_M2jju.js → index-1agckWSq.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DE0V6cp-.js → index-5JqhujnR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Y2-q7QLm.js → index-6Tniqk1s.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DoFtCVZ7.js → index-8Z-8BIlN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-KNGgO4Wm.js → index-BCI5v0yB.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DY4j5zkF.js → index-BDJntYmJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BZjjdQ62.js → index-BGzixOGZ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BTpIOTfR.js → index-BMMOVoLD.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BB9SqVSt.js → index-BTFpdBtt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-AHr7ryWf.js → index-Ba_HyPnE.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Pvf30m8Q.js → index-BbF1IHof.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CRN5TRTQ.js → index-BgT9su_e.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CUaEHfQ0.js → index-BzPB6kGZ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CdKt9fOd.js → index-C6ygzplr.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Dec0Kf_Z.js → index-CC3ZDjzY.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-IPkwSAW_.js → index-CEmXRhAE.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
  113. package/src/assets/web-panel/assets/{index-C3k8kevG.js → index-CMPqd5Cv.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-A5CDWP7y.js → index-COWd8uGx.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CO8DAF7U.js → index-CPIYSp4H.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CVzuawCX.js → index-CR0h6seT.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Co7s9LsB.js → index-CUmDhC4j.js} +1 -1
  118. package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
  119. package/src/assets/web-panel/assets/{index-CLoTQr0W.js → index-CusgZPtc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B81mo1Et.js → index-DBAETXft.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BcSUl9fA.js → index-DIJ2qFOC.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Q5Tn735s.js → index-Dbq4NMET.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-GXE0D8Nx.js → index-DkMEF5VT.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-TWG-_D1M.js → index-DkfQR3hM.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BAE3hwev.js → index-DnLucOn9.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CgkoH3_g.js → index-DugB_aKa.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BRGx3w6T.js → index-DyLUarhZ.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-BeM4hfht.js → index-T6_6ygJZ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DsscwxPE.js → index-Tu8eWqh-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bmr_vxXD.js → index-W7XUgdVL.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BoNZlzhQ.js → index-dHZinfko.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DqkbHHg8.js → index-jyNgp5FI.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D-4Hzgej.js → initDefaultProps-Cv4p8e4w.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-D4Wl4xU7.js → motion-B8EwmMOw.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Cd5eQvL0.js → move-B8R9F8TK.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-B4ilslCW.js → mtc-parser-DoFYz5Bp.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-Ckarx-tf.js → omit-CHWYLBjI.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-QJ0bpCmc.js → pickAttrs-CXuPDO77.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CKHm-tq8.js → placementArrow-BaAtUUon.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CV6a_w_Y.js → responsiveObserve-DCc4zcDD.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-mHpeQFgk.js → slide-B_lx89oZ.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DIaQNQWA.js → statusUtils-DE8DPmR0.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-PJpzavV8.js → styleChecker-1ktXOsL_.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-BH7zTw8S.js → useFlexGapSupport-xH89-wjh.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BDNG3JDd.js → useFs-DscvfW5p.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CY2VWdUg.js → usePersonalDataHub-RdMKQta7.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-D0kw9jhz.js → vnode-Z3wTni2T.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-B5g7TN7r.js → zoom-B0iChCsm.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +3 -3
  151. package/src/commands/agent.js +105 -62
  152. package/src/commands/background-session.js +100 -10
  153. package/src/commands/git.js +39 -0
  154. package/src/commands/mcp.js +5 -0
  155. package/src/commands/plugin.js +160 -29
  156. package/src/commands/remote-control.js +295 -110
  157. package/src/commands/session-group.js +131 -0
  158. package/src/commands/session-location.js +48 -0
  159. package/src/commands/session-message.js +171 -0
  160. package/src/commands/session.js +22 -0
  161. package/src/commands/sync-providers.js +4 -1
  162. package/src/commands/update.js +1 -0
  163. package/src/data/changelog.json +41 -2
  164. package/src/gateways/ws/ws-agent-handler.js +20 -12
  165. package/src/harness/jsonl-session-store.js +279 -37
  166. package/src/harness/mcp-client.js +832 -91
  167. package/src/harness/session-index.js +15 -4
  168. package/src/lib/agent-worktree.js +8 -1
  169. package/src/lib/auto-mode-safety-classifier.js +3 -17
  170. package/src/lib/background-agent-keeper-protocol.js +26 -6
  171. package/src/lib/background-agent-supervisor.js +17 -1
  172. package/src/lib/background-session-transport.js +153 -27
  173. package/src/lib/browser-evidence.js +1405 -0
  174. package/src/lib/chrome-connector.js +465 -79
  175. package/src/lib/claude-project-auto-memory.js +481 -0
  176. package/src/lib/claude-project-storage-layout.js +104 -0
  177. package/src/lib/cloud/cloud-client.js +40 -0
  178. package/src/lib/execution-location-local-supervisor.mjs +226 -0
  179. package/src/lib/execution-location-runner-lifecycle.js +991 -0
  180. package/src/lib/execution-location-target-preflight.js +333 -0
  181. package/src/lib/execution-location-target.js +783 -13
  182. package/src/lib/file-lines.js +6 -1
  183. package/src/lib/gateway-client-resilience.js +571 -0
  184. package/src/lib/gitlab-mr-worktree.js +176 -0
  185. package/src/lib/hooks-v2-producers.js +119 -15
  186. package/src/lib/hooks-v2-workspace-context.js +44 -1
  187. package/src/lib/host-resource-budget.js +295 -0
  188. package/src/lib/ide-context.js +15 -2
  189. package/src/lib/llm-config-defaults.js +43 -0
  190. package/src/lib/mcp-client.js +2 -0
  191. package/src/lib/mcp-headers-helper.js +47 -5
  192. package/src/lib/mcp-lifecycle-authority.js +887 -0
  193. package/src/lib/mcp-oauth.js +156 -26
  194. package/src/lib/mcp-tls.js +330 -0
  195. package/src/lib/output-styles.js +12 -0
  196. package/src/lib/paths.js +260 -32
  197. package/src/lib/permission-request.js +48 -0
  198. package/src/lib/plugin-runtime/bin.js +3 -1
  199. package/src/lib/plugin-runtime/hooks.js +3 -1
  200. package/src/lib/plugin-runtime/install.js +730 -71
  201. package/src/lib/plugin-runtime/lsp.js +4 -4
  202. package/src/lib/plugin-runtime/marketplace-archive-source.js +366 -0
  203. package/src/lib/plugin-runtime/marketplace-catalog.js +63 -6
  204. package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
  205. package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
  206. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
  207. package/src/lib/plugin-runtime/marketplace-source-adapter.js +188 -0
  208. package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
  209. package/src/lib/plugin-runtime/mcp.js +3 -1
  210. package/src/lib/plugin-runtime/monitors.js +3 -1
  211. package/src/lib/plugin-runtime/remote-source.js +583 -58
  212. package/src/lib/plugin-runtime/settings.js +3 -1
  213. package/src/lib/plugin-runtime/trust.js +274 -13
  214. package/src/lib/plugin-security.js +393 -41
  215. package/src/lib/plugin-source-identity.js +850 -0
  216. package/src/lib/process-execution-broker/credential-agent.js +210 -41
  217. package/src/lib/process-execution-broker/index.js +15 -1
  218. package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
  219. package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
  220. package/src/lib/project-mcp-trust.js +165 -10
  221. package/src/lib/remote-approval-bridge.js +136 -46
  222. package/src/lib/remote-control.js +217 -31
  223. package/src/lib/resume-session.js +9 -6
  224. package/src/lib/session-core-singletons.js +26 -11
  225. package/src/lib/session-host-lease.js +52 -16
  226. package/src/lib/session-lifecycle-scope.js +153 -0
  227. package/src/lib/session-message-fabric.js +1252 -0
  228. package/src/lib/session-projection.js +286 -3
  229. package/src/lib/session-runtime-retention.js +155 -0
  230. package/src/lib/session-store-guard.js +3 -1
  231. package/src/lib/session-workbench-store.js +466 -0
  232. package/src/lib/settings-loader.cjs +29 -0
  233. package/src/lib/spellcheck.js +211 -0
  234. package/src/lib/sub-agent-context.js +9 -9
  235. package/src/lib/sync-cli-db.js +485 -129
  236. package/src/lib/sync-credentials.js +31 -20
  237. package/src/lib/web-fetch.js +112 -40
  238. package/src/lib/web-search.js +127 -61
  239. package/src/lib/workspace-trust.js +793 -0
  240. package/src/repl/agent-repl.js +175 -19
  241. package/src/repl/permission-prompt.js +29 -8
  242. package/src/repl/repl-keybindings.js +55 -0
  243. package/src/runtime/agent-core.js +362 -20
  244. package/src/runtime/bootstrap.js +37 -13
  245. package/src/runtime/coding-agent-contract-shared.cjs +11 -4
  246. package/src/runtime/headless-runner.js +281 -102
  247. package/src/runtime/headless-stream.js +199 -30
  248. package/src/runtime/mcp-config.js +298 -6
  249. package/src/runtime/policies/agent-policy.js +10 -0
  250. package/src/runtime/status-diagnostics-lite.js +9 -1
  251. package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +0 -1
  252. package/src/assets/web-panel/assets/MarkdownRenderer-B4RLp2zs.js +0 -1
  253. package/src/assets/web-panel/assets/SpeechSettings-DPy1XMYh.js +0 -1
  254. package/src/assets/web-panel/assets/devWarning-CagEcIex.js +0 -1
  255. package/src/assets/web-panel/assets/index-pzT9PpOU.js +0 -1
  256. package/src/assets/web-panel/assets/index-z1kFadaX.js +0 -1
@@ -38,8 +38,18 @@ import {
38
38
  } from "./scopes.js";
39
39
  import {
40
40
  enforcePluginPolicy,
41
+ enforcePluginSourcePolicy,
42
+ resolvePluginManagedPolicy,
41
43
  verifyPluginManifest,
42
44
  } from "../plugin-security.js";
45
+ import {
46
+ assertPluginGitTransportSafe,
47
+ canonicalizePluginSource,
48
+ normalizePluginLocalSourcePath,
49
+ parsePluginGitSource,
50
+ redactPluginSourceForDisplay,
51
+ } from "../plugin-source-identity.js";
52
+ import { assertRegistryResolutionAuthority } from "./remote-source.js";
43
53
  import {
44
54
  writePluginLock,
45
55
  buildPluginSbom,
@@ -106,12 +116,18 @@ export const _deps = {
106
116
  mkdtempSync: fs.mkdtempSync,
107
117
  randomToken: () => crypto.randomBytes(16).toString("hex"),
108
118
  spawnSync: (...args) => executionBroker.spawnSync(...args),
119
+ gitExecutable: null,
109
120
  beforeTransactionPhaseHook: null,
110
121
  transactionPhaseHook: null,
111
122
  };
112
123
 
113
124
  const pendingPluginTransactions = new WeakMap();
114
125
  const ownedPluginLifecycleLocks = new Map();
126
+ const SOURCE_POLICY_PREFLIGHT = Symbol("plugin-source-policy-preflight");
127
+ const UPDATE_MATERIALIZATION = Symbol("plugin-update-materialization");
128
+ const NULL_DEVICE = process.platform === "win32" ? "NUL" : "/dev/null";
129
+ let cachedTrustedGitExecutable = null;
130
+ let cachedTrustedSshExecutable = null;
115
131
 
116
132
  /**
117
133
  * Install a plugin from a local directory into a scope's immutable version dir.
@@ -120,8 +136,11 @@ const ownedPluginLifecycleLocks = new Map();
120
136
  * @returns {{ name, version, scope, dir, warnings }}
121
137
  */
122
138
  export function installFromDirectory(srcDir, opts = {}) {
139
+ opts = preflightManagedSource(srcDir, opts, "install", {
140
+ materializedKind: "directory",
141
+ });
123
142
  const scope = opts.scope || "user";
124
- const src = path.resolve(srcDir);
143
+ const src = path.resolve(opts.cwd || process.cwd(), srcDir);
125
144
  if (!_deps.existsSync(src)) {
126
145
  throw new Error(`source directory does not exist: ${src}`);
127
146
  }
@@ -157,7 +176,10 @@ export function installFromDirectory(srcDir, opts = {}) {
157
176
  assertInstalledNameDirectoryIdentity(name, { scope, cwd: opts.cwd });
158
177
  assertExpectedPluginIdentity(name, version, opts.expectedIdentity);
159
178
  let sourceMetadata = normalizeSourceMetadata(
160
- opts.sourceMetadata ?? { type: "local", source: src },
179
+ bindSourcePolicyAuthority(
180
+ opts.sourceMetadata ?? { type: "local", source: src },
181
+ opts,
182
+ ),
161
183
  );
162
184
  if (!sourceMetadata) {
163
185
  throw new Error("plugin source metadata has no valid source identity");
@@ -184,6 +206,13 @@ export function installFromDirectory(srcDir, opts = {}) {
184
206
  {
185
207
  name,
186
208
  source: opts.policySource || sourceMetadata?.source || null,
209
+ sourceKind:
210
+ sourceMetadata?.type === "registry"
211
+ ? "registry"
212
+ : sourceMetadata?.type === "git"
213
+ ? "git"
214
+ : null,
215
+ sourcePolicyPreflighted: true,
187
216
  action: "install",
188
217
  },
189
218
  opts.managedPolicy,
@@ -297,6 +326,10 @@ export function installFromDirectory(srcDir, opts = {}) {
297
326
  // and may ship its own `.plugin-lock.json` or provenance metadata.
298
327
  _deps.rmSync(path.join(staged, LOCK_FILENAME), { force: true });
299
328
  _deps.rmSync(path.join(staged, SOURCE_METADATA_FILENAME), { force: true });
329
+ assertStagedRegistryCommandPayload(
330
+ staged,
331
+ opts[SOURCE_POLICY_PREFLIGHT]?.registryAuthority,
332
+ );
300
333
 
301
334
  const remoteSbomPayloadComparison = buildRemoteSbomPayloadComparison({
302
335
  remoteArtifactEvidence:
@@ -508,17 +541,34 @@ export function installFromDirectory(srcDir, opts = {}) {
508
541
  * An optional `#ref` (branch / tag / commit) pins the checkout.
509
542
  */
510
543
  export function installFromSource(source, opts = {}) {
544
+ const governedOpts = preflightManagedSource(source, opts, "install");
511
545
  return _withMaterializedSource(
512
546
  source,
513
547
  (dir, info) => {
514
548
  const sourceMetadata =
515
- normalizeSourceMetadata(opts.sourceMetadata) ||
516
549
  normalizeSourceMetadata(
517
- info
518
- ? { type: "git", source: info.url, ref: info.ref || null }
519
- : { type: "local", source: path.resolve(String(source || "")) },
550
+ bindSourcePolicyAuthority(governedOpts.sourceMetadata, governedOpts),
551
+ ) ||
552
+ normalizeSourceMetadata(
553
+ bindSourcePolicyAuthority(
554
+ info
555
+ ? { type: "git", source: info.url, ref: info.ref || null }
556
+ : {
557
+ type: "local",
558
+ source:
559
+ governedOpts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
560
+ path.resolve(
561
+ governedOpts.cwd || process.cwd(),
562
+ String(source || ""),
563
+ ),
564
+ },
565
+ governedOpts,
566
+ ),
520
567
  );
521
- const res = installFromDirectory(dir, { ...opts, sourceMetadata });
568
+ const res = installFromDirectory(dir, {
569
+ ...governedOpts,
570
+ sourceMetadata,
571
+ });
522
572
  if (!info) return res;
523
573
  const result = {
524
574
  ...res,
@@ -528,8 +578,180 @@ export function installFromSource(source, opts = {}) {
528
578
  transferPendingTransaction(res, result);
529
579
  return result;
530
580
  },
581
+ governedOpts,
582
+ );
583
+ }
584
+
585
+ function describeMaterializedSource(source, opts, materializedKind = null) {
586
+ if (materializedKind !== "directory") {
587
+ const git = parsePluginGitSource(source);
588
+ if (git) return { kind: "git", git };
589
+ }
590
+ return {
591
+ kind: "directory",
592
+ path: path.resolve(
593
+ opts.cwd || process.cwd(),
594
+ normalizePluginLocalSourcePath(source),
595
+ ),
596
+ };
597
+ }
598
+
599
+ function preflightManagedSource(source, opts, action, options = {}) {
600
+ if (opts[SOURCE_POLICY_PREFLIGHT]) return opts;
601
+ const materialized = describeMaterializedSource(
602
+ source,
531
603
  opts,
604
+ options.materializedKind,
532
605
  );
606
+ const registrySource =
607
+ opts.sourceMetadata?.type === "registry"
608
+ ? opts.sourceMetadata.registry || opts.sourceMetadata.source
609
+ : null;
610
+ // Policy follows the bytes that will actually be materialized. The only
611
+ // intentional indirection is a registry-backed install, whose catalog URL
612
+ // is authoritative in sourceMetadata; a caller-supplied display/legacy
613
+ // policySource must never substitute a different allowlisted identity.
614
+ const policySource = registrySource ?? source;
615
+ const policyKind = registrySource ? "registry" : materialized.kind;
616
+ const managedPolicy = resolvePluginManagedPolicy(opts);
617
+ let registryAuthority = null;
618
+ if (registrySource) {
619
+ registryAuthority = assertRegistryResolutionAuthority(
620
+ opts.registryResolutionAuthority,
621
+ {
622
+ registryUrl: registrySource,
623
+ source,
624
+ ref: materialized.git?.ref || null,
625
+ },
626
+ );
627
+ if (
628
+ opts.sourceMetadata.resolvedSource !== registryAuthority.declaredSource ||
629
+ (opts.sourceMetadata.ref || null) !== (registryAuthority.ref || null)
630
+ ) {
631
+ throw new Error(
632
+ "registry source metadata does not match materialized source",
633
+ );
634
+ }
635
+ if (
636
+ !opts.sourceMetadata.catalogAuthority ||
637
+ typeof opts.sourceMetadata.catalogAuthority !== "object" ||
638
+ Array.isArray(opts.sourceMetadata.catalogAuthority)
639
+ ) {
640
+ throw new Error("registry source metadata requires catalog authority");
641
+ }
642
+ const declaredDocumentSha256 =
643
+ opts.sourceMetadata.catalogAuthority?.registryDocumentSha256 || null;
644
+ if (
645
+ declaredDocumentSha256 &&
646
+ declaredDocumentSha256 !== registryAuthority.documentSha256
647
+ ) {
648
+ throw new Error(
649
+ "registry source authority does not match catalog document digest",
650
+ );
651
+ }
652
+ if (registryAuthority.sourceType === "archive") {
653
+ const archive = opts.sourceMetadata.catalogAuthority?.archiveSource;
654
+ if (
655
+ !archive ||
656
+ archive.archiveSha256 !== registryAuthority.archiveSha256 ||
657
+ archive.payloadSha256 !== registryAuthority.payloadSha256
658
+ ) {
659
+ throw new Error(
660
+ "registry source authority does not match archive digest authority",
661
+ );
662
+ }
663
+ }
664
+ if (registryAuthority.sourceType === "command") {
665
+ const command = opts.sourceMetadata.catalogAuthority?.commandSource;
666
+ if (
667
+ !command ||
668
+ command.descriptorIdentity !== registryAuthority.declaredSource ||
669
+ command.payloadSha256 !== registryAuthority.payloadSha256 ||
670
+ command.mode !== registryAuthority.commandMode
671
+ ) {
672
+ throw new Error(
673
+ "registry source authority does not match command source authority",
674
+ );
675
+ }
676
+ }
677
+ }
678
+ // Resolve managed identity before exists/clone/cache/process work. The
679
+ // manifest name is checked again by installFromDirectory after materializing
680
+ // bytes; source policy cannot wait for that later gate.
681
+ const decision = enforcePluginSourcePolicy(policySource, managedPolicy, {
682
+ action,
683
+ cwd: opts.cwd,
684
+ kindHint: policyKind,
685
+ });
686
+ const policyIdentity = canonicalizePluginSource(policySource, {
687
+ cwd: opts.cwd,
688
+ kindHint: policyKind,
689
+ });
690
+ const identityDigest = (identity) =>
691
+ crypto
692
+ .createHash("sha256")
693
+ .update(
694
+ canonicalJson({
695
+ identity: identity.identityDigest || identity.key,
696
+ ref: identity.ref ?? null,
697
+ path: identity.path ?? null,
698
+ }),
699
+ )
700
+ .digest("hex");
701
+ const sourceAuthority = {
702
+ policyDigest: decision.policyDigest || null,
703
+ sourceDigest: identityDigest(policyIdentity),
704
+ resolvedSourceDigest: registrySource
705
+ ? registryResolvedSourceAuthorityDigest(registryAuthority)
706
+ : null,
707
+ registryDocumentSha256: registryAuthority?.documentSha256 || null,
708
+ };
709
+ const preflight = {
710
+ decision,
711
+ materialized,
712
+ sourceAuthority,
713
+ registryAuthority,
714
+ };
715
+ const boundSourceMetadata = opts.sourceMetadata
716
+ ? bindSourcePolicyAuthority(opts.sourceMetadata, {
717
+ [SOURCE_POLICY_PREFLIGHT]: preflight,
718
+ })
719
+ : null;
720
+ const normalizedSourceMetadata = boundSourceMetadata
721
+ ? normalizeSourceMetadata(boundSourceMetadata)
722
+ : null;
723
+ if (opts.sourceMetadata && !normalizedSourceMetadata) {
724
+ throw new Error("plugin source metadata is invalid");
725
+ }
726
+ return {
727
+ ...opts,
728
+ managedPolicy,
729
+ ...(normalizedSourceMetadata
730
+ ? { sourceMetadata: normalizedSourceMetadata }
731
+ : {}),
732
+ [SOURCE_POLICY_PREFLIGHT]: preflight,
733
+ };
734
+ }
735
+
736
+ function bindSourcePolicyAuthority(sourceMetadata, opts) {
737
+ const authority = opts[SOURCE_POLICY_PREFLIGHT]?.sourceAuthority;
738
+ if (!authority || !sourceMetadata) return sourceMetadata;
739
+ return {
740
+ ...sourceMetadata,
741
+ policyDigest: authority.policyDigest,
742
+ sourceDigest: authority.sourceDigest,
743
+ ...(authority.resolvedSourceDigest
744
+ ? { resolvedSourceDigest: authority.resolvedSourceDigest }
745
+ : {}),
746
+ ...(authority.registryDocumentSha256 && sourceMetadata.catalogAuthority
747
+ ? {
748
+ catalogAuthority: {
749
+ ...sourceMetadata.catalogAuthority,
750
+ registryDocumentSha256: authority.registryDocumentSha256,
751
+ },
752
+ }
753
+ : {}),
754
+ };
533
755
  }
534
756
 
535
757
  /**
@@ -538,12 +760,21 @@ export function installFromSource(source, opts = {}) {
538
760
  * Single fetch shared by installFromSource + updatePlugin.
539
761
  */
540
762
  function _withMaterializedSource(source, fn, opts = {}) {
541
- const raw = String(source || "");
542
- const asDir = path.resolve(raw);
543
- if (_deps.existsSync(asDir) && _deps.lstatSync(asDir).isDirectory()) {
544
- return fn(asDir, null);
763
+ const materialized =
764
+ opts[SOURCE_POLICY_PREFLIGHT]?.materialized ||
765
+ describeMaterializedSource(source, opts);
766
+ if (materialized.kind === "directory") {
767
+ if (
768
+ _deps.existsSync(materialized.path) &&
769
+ _deps.lstatSync(materialized.path).isDirectory()
770
+ ) {
771
+ return fn(materialized.path, null);
772
+ }
773
+ throw new Error(
774
+ `source directory does not exist: ${redactPluginSourceForDisplay(materialized.path)}`,
775
+ );
545
776
  }
546
- const git = parseGitSource(raw);
777
+ const git = materialized.git;
547
778
  if (git) {
548
779
  if (opts.offline === true) {
549
780
  const cached = readMarketplaceSourceCache(opts.sourceMetadata, {
@@ -562,7 +793,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
562
793
  }
563
794
  return result;
564
795
  }
565
- const cloned = fetchGitRepo(git.url, git.ref);
796
+ const cloned = fetchGitRepo(git.url, git.ref, { cwd: opts.cwd });
566
797
  try {
567
798
  const result = fn(cloned, git);
568
799
  if (result && typeof result === "object") {
@@ -592,9 +823,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
592
823
  }
593
824
  }
594
825
  }
595
- throw new Error(
596
- `source not found as a local directory or git URL: ${source}`,
597
- );
826
+ throw new Error("source is neither a local directory nor a git URL");
598
827
  }
599
828
 
600
829
  /**
@@ -606,6 +835,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
606
835
  * Returns { name, version, previousVersion, updated, reinstalled }.
607
836
  */
608
837
  export function updatePlugin(source, opts = {}) {
838
+ opts = preflightManagedSource(source, opts, "upgrade");
609
839
  const scope = opts.scope || "user";
610
840
  const updateMaterialized = (dir, info) => {
611
841
  const manifest = parsePluginManifest(dir);
@@ -627,8 +857,7 @@ export function updatePlugin(source, opts = {}) {
627
857
  updatePlugin(source, {
628
858
  ...opts,
629
859
  _lifecycleLock: lifecycleLock,
630
- _materializedDir: dir,
631
- _materializedGitInfo: info,
860
+ [UPDATE_MATERIALIZATION]: { dir, info },
632
861
  }),
633
862
  );
634
863
  }
@@ -640,11 +869,21 @@ export function updatePlugin(source, opts = {}) {
640
869
  assertNoRetainedInstallRecovery(name, { scope, cwd: opts.cwd });
641
870
  assertExpectedPluginIdentity(name, version, opts.expectedIdentity);
642
871
  const sourceMetadata =
643
- normalizeSourceMetadata(opts.sourceMetadata) ||
644
872
  normalizeSourceMetadata(
645
- info
646
- ? { type: "git", source: info.url, ref: info.ref || null }
647
- : { type: "local", source: path.resolve(String(source || "")) },
873
+ bindSourcePolicyAuthority(opts.sourceMetadata, opts),
874
+ ) ||
875
+ normalizeSourceMetadata(
876
+ bindSourcePolicyAuthority(
877
+ info
878
+ ? { type: "git", source: info.url, ref: info.ref || null }
879
+ : {
880
+ type: "local",
881
+ source:
882
+ opts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
883
+ path.resolve(opts.cwd || process.cwd(), String(source || "")),
884
+ },
885
+ opts,
886
+ ),
648
887
  );
649
888
  const previousVersion = getActiveVersion(name, { scope, cwd: opts.cwd });
650
889
  assertReplacementApprovals({
@@ -799,10 +1038,10 @@ export function updatePlugin(source, opts = {}) {
799
1038
  transferPendingTransaction(res, result);
800
1039
  return result;
801
1040
  };
802
- if (opts._materializedDir) {
1041
+ if (opts[UPDATE_MATERIALIZATION]) {
803
1042
  return updateMaterialized(
804
- opts._materializedDir,
805
- opts._materializedGitInfo || null,
1043
+ opts[UPDATE_MATERIALIZATION].dir,
1044
+ opts[UPDATE_MATERIALIZATION].info || null,
806
1045
  );
807
1046
  }
808
1047
  return _withMaterializedSource(source, updateMaterialized, opts);
@@ -1570,19 +1809,158 @@ function rollbackRecoveredWholeNameUninstall(transaction) {
1570
1809
  * not remote-looking. `owner/repo` expands to a GitHub HTTPS URL.
1571
1810
  */
1572
1811
  export function parseGitSource(raw) {
1573
- const [loc, ref] = String(raw || "").split("#");
1574
- if (!loc) return null;
1575
- if (
1576
- /^(https?|git|ssh|file):\/\//.test(loc) ||
1577
- loc.endsWith(".git") ||
1578
- /^git@/.test(loc)
1579
- ) {
1580
- return { url: loc, ref: ref || null };
1812
+ return parsePluginGitSource(raw);
1813
+ }
1814
+
1815
+ function isPathWithin(child, root) {
1816
+ const relative = path.relative(root, child);
1817
+ return (
1818
+ relative === "" ||
1819
+ (!path.isAbsolute(relative) &&
1820
+ relative !== ".." &&
1821
+ !relative.startsWith(`..${path.sep}`))
1822
+ );
1823
+ }
1824
+
1825
+ function validateTrustedExecutable(value, cwd) {
1826
+ if (typeof value !== "string" || !path.isAbsolute(value)) return null;
1827
+ try {
1828
+ const executable = fs.realpathSync.native(value);
1829
+ const stat = fs.statSync(executable);
1830
+ if (!stat.isFile()) return null;
1831
+ if (process.platform !== "win32" && (Number(stat.mode) & 0o111) === 0) {
1832
+ return null;
1833
+ }
1834
+ if (isPathWithin(executable, path.resolve(cwd))) return null;
1835
+ return executable;
1836
+ } catch {
1837
+ return null;
1838
+ }
1839
+ }
1840
+
1841
+ function resolveTrustedExecutable({
1842
+ cwd,
1843
+ executableName,
1844
+ configured,
1845
+ configuredLabel,
1846
+ injected = null,
1847
+ cached = null,
1848
+ }) {
1849
+ if (injected != null) {
1850
+ const trusted = validateTrustedExecutable(injected, cwd);
1851
+ if (!trusted)
1852
+ throw new Error(`configured ${configuredLabel} is not trusted`);
1853
+ return trusted;
1854
+ }
1855
+ const trustedCached = validateTrustedExecutable(cached, cwd);
1856
+ if (trustedCached) return trustedCached;
1857
+ if (configured != null) {
1858
+ const trusted = validateTrustedExecutable(configured, cwd);
1859
+ if (!trusted) {
1860
+ throw new Error(`${configuredLabel} must be a trusted absolute file`);
1861
+ }
1862
+ return trusted;
1581
1863
  }
1582
- if (/^[\w.-]+\/[\w.-]+$/.test(loc)) {
1583
- return { url: `https://github.com/${loc}.git`, ref: ref || null };
1864
+ for (const rawEntry of String(process.env.PATH || "").split(path.delimiter)) {
1865
+ const entry = rawEntry.replace(/^"|"$/gu, "");
1866
+ if (!entry || !path.isAbsolute(entry)) continue;
1867
+ const trusted = validateTrustedExecutable(
1868
+ path.join(entry, executableName),
1869
+ cwd,
1870
+ );
1871
+ if (trusted) return trusted;
1584
1872
  }
1585
- return null;
1873
+ throw new Error(
1874
+ `trusted ${configuredLabel} was not found on an absolute PATH entry`,
1875
+ );
1876
+ }
1877
+
1878
+ function resolveTrustedGitExecutable(cwd) {
1879
+ const executable = resolveTrustedExecutable({
1880
+ cwd,
1881
+ executableName: process.platform === "win32" ? "git.exe" : "git",
1882
+ configured: process.env.CHAINLESSCHAIN_GIT_BIN,
1883
+ configuredLabel: "Git executable",
1884
+ injected: _deps.gitExecutable,
1885
+ cached: cachedTrustedGitExecutable,
1886
+ });
1887
+ cachedTrustedGitExecutable = executable;
1888
+ return executable;
1889
+ }
1890
+
1891
+ function resolveTrustedSshExecutable(cwd) {
1892
+ const executable = resolveTrustedExecutable({
1893
+ cwd,
1894
+ executableName: process.platform === "win32" ? "ssh.exe" : "ssh",
1895
+ configured: process.env.CHAINLESSCHAIN_SSH_BIN,
1896
+ configuredLabel: "SSH executable",
1897
+ cached: cachedTrustedSshExecutable,
1898
+ });
1899
+ cachedTrustedSshExecutable = executable;
1900
+ return executable;
1901
+ }
1902
+
1903
+ function hardenedPluginGitEnvironment() {
1904
+ const environment = {};
1905
+ for (const [key, value] of Object.entries(process.env)) {
1906
+ if (!key.toUpperCase().startsWith("GIT_")) environment[key] = value;
1907
+ }
1908
+ return {
1909
+ ...environment,
1910
+ GIT_ATTR_NOSYSTEM: "1",
1911
+ GIT_CONFIG_COUNT: "0",
1912
+ GIT_CONFIG_GLOBAL: NULL_DEVICE,
1913
+ GIT_CONFIG_NOSYSTEM: "1",
1914
+ GIT_EXTERNAL_DIFF: "",
1915
+ GIT_OPTIONAL_LOCKS: "0",
1916
+ GIT_PAGER: "",
1917
+ GIT_TERMINAL_PROMPT: "0",
1918
+ LC_ALL: "C",
1919
+ };
1920
+ }
1921
+
1922
+ function quoteSshCommandArgument(value) {
1923
+ if (process.platform === "win32") {
1924
+ return `"${String(value).replace(/"/gu, '\\"')}"`;
1925
+ }
1926
+ return `'${String(value).replace(/'/gu, `'\\''`)}'`;
1927
+ }
1928
+
1929
+ function hardenedPluginGitArgs(args, { url, cwd }) {
1930
+ const parsed = parsePluginGitSource(url);
1931
+ const sshTransport =
1932
+ /^ssh:\/\//iu.test(parsed?.url || "") ||
1933
+ (!(parsed?.url || "").includes("://") &&
1934
+ /^(?:[^@\s/:]+@)?(?:\[[^\]]+\]|[^\s/:]+):/u.test(parsed?.url || "") &&
1935
+ !/^[a-z]:[\\/]/iu.test(parsed?.url || ""));
1936
+ const sshArgs = sshTransport
1937
+ ? [
1938
+ "-c",
1939
+ `core.sshCommand=${quoteSshCommandArgument(resolveTrustedSshExecutable(cwd))} -F ${quoteSshCommandArgument(NULL_DEVICE)} -o BatchMode=yes -o ClearAllForwardings=yes -o PermitLocalCommand=no -o ProxyCommand=none -o CanonicalizeHostname=no`,
1940
+ ]
1941
+ : [];
1942
+ const fileTransport = /^file:\/\//iu.test(parsed?.url || "");
1943
+ return [
1944
+ "--no-pager",
1945
+ "-c",
1946
+ `core.hooksPath=${NULL_DEVICE}`,
1947
+ "-c",
1948
+ "core.fsmonitor=false",
1949
+ "-c",
1950
+ "core.untrackedCache=false",
1951
+ "-c",
1952
+ "credential.helper=",
1953
+ "-c",
1954
+ "diff.external=",
1955
+ "-c",
1956
+ "http.followRedirects=false",
1957
+ "-c",
1958
+ "protocol.ext.allow=never",
1959
+ "-c",
1960
+ `protocol.file.allow=${fileTransport ? "always" : "never"}`,
1961
+ ...sshArgs,
1962
+ ...args,
1963
+ ];
1586
1964
  }
1587
1965
 
1588
1966
  /**
@@ -1590,30 +1968,46 @@ export function parseGitSource(raw) {
1590
1968
  * checkout path. Uses `git` via spawn WITHOUT a shell (url/ref are argv, not a
1591
1969
  * command line — no injection). Caller removes the temp dir's parent.
1592
1970
  */
1593
- export function fetchGitRepo(url, ref) {
1971
+ export function fetchGitRepo(url, ref, options = {}) {
1594
1972
  // git argv-injection guard: a value starting with "-" is parsed by git as an
1595
1973
  // OPTION, not a URL/ref — e.g. a registry-supplied ref "-f" reaches
1596
1974
  // `git checkout <ref>` on the full-clone retry path, and an option-looking
1597
1975
  // url reaches `git clone`. Real git URLs/refs never start with "-"
1598
1976
  // (check-ref-format forbids it), so reject instead of trying to escape.
1599
1977
  if (String(url).startsWith("-")) {
1600
- throw new Error(`refusing git source that looks like an option: ${url}`);
1978
+ throw new Error("refusing git source that looks like an option");
1601
1979
  }
1602
1980
  if (ref != null && String(ref).startsWith("-")) {
1603
- throw new Error(`refusing git ref that looks like an option: ${ref}`);
1981
+ throw new Error("refusing git ref that looks like an option");
1604
1982
  }
1983
+ const git = parsePluginGitSource(ref ? `${url}#${ref}` : url);
1984
+ assertPluginGitTransportSafe(git, { allowFile: true });
1985
+ const cwd = path.resolve(options.cwd || process.cwd());
1986
+ const executable = resolveTrustedGitExecutable(cwd);
1605
1987
  const base = _deps.mkdtempSync(path.join(os.tmpdir(), "cc-plugin-git-"));
1606
1988
  const dir = path.join(base, "repo");
1607
- const run = (args) =>
1608
- _deps.spawnSync("git", args, {
1609
- encoding: "utf8",
1610
- timeout: 120000,
1611
- windowsHide: true,
1612
- origin: "plugin:install-git",
1613
- policy: "allow",
1614
- scope: "plugin-install",
1615
- shell: false,
1616
- });
1989
+ const run = (args) => {
1990
+ const hardenedArgs = hardenedPluginGitArgs(args, { url, cwd });
1991
+ const auditRedactArgIndexes = hardenedArgs.flatMap((argument, index) =>
1992
+ argument === url || (ref != null && argument === ref) ? [index] : [],
1993
+ );
1994
+ try {
1995
+ return _deps.spawnSync(executable, hardenedArgs, {
1996
+ cwd: base,
1997
+ encoding: "utf8",
1998
+ env: hardenedPluginGitEnvironment(),
1999
+ timeout: 120000,
2000
+ windowsHide: true,
2001
+ origin: "plugin:install-git",
2002
+ policy: "allow",
2003
+ scope: "plugin-install",
2004
+ shell: false,
2005
+ auditRedactArgIndexes,
2006
+ });
2007
+ } catch (error) {
2008
+ return { error, status: null, stdout: "", stderr: "" };
2009
+ }
2010
+ };
1617
2011
 
1618
2012
  // Reduce host-specific checkout differences before the v2 inventory hashes
1619
2013
  // the materialized staged bytes. Repository .gitattributes may still define
@@ -1636,6 +2030,11 @@ export function fetchGitRepo(url, ref) {
1636
2030
  if (res.error && res.error.code === "ENOENT") {
1637
2031
  throw new Error("git is not installed (needed to fetch a remote plugin)");
1638
2032
  }
2033
+ if (res.error) {
2034
+ throw new Error(
2035
+ `git process failed for ${redactPluginSourceForDisplay(url)}`,
2036
+ );
2037
+ }
1639
2038
  if (res.status !== 0) {
1640
2039
  // A commit SHA can't be used with --branch/--depth; retry with a full clone
1641
2040
  // then checkout the ref explicitly.
@@ -1651,8 +2050,9 @@ export function fetchGitRepo(url, ref) {
1651
2050
  if (co.status === 0) return dir;
1652
2051
  }
1653
2052
  }
1654
- const reason = (res.stderr || "").trim() || `git exited ${res.status}`;
1655
- throw new Error(`git clone failed for ${url}: ${reason}`);
2053
+ throw new Error(
2054
+ `git clone failed for ${redactPluginSourceForDisplay(url)} (git exited ${res.status})`,
2055
+ );
1656
2056
  }
1657
2057
  return dir;
1658
2058
  }
@@ -2987,13 +3387,19 @@ function sameSourceAuthority(current, candidate) {
2987
3387
  if (current?.type !== candidate?.type) return false;
2988
3388
  if (current.type === "registry") {
2989
3389
  return Boolean(
2990
- current.registry &&
2991
- candidate.registry &&
2992
- current.registry === candidate.registry,
3390
+ current.sourceDigest &&
3391
+ candidate.sourceDigest &&
3392
+ current.sourceDigest === candidate.sourceDigest &&
3393
+ current.resolvedSourceDigest &&
3394
+ candidate.resolvedSourceDigest &&
3395
+ current.resolvedSourceDigest === candidate.resolvedSourceDigest,
2993
3396
  );
2994
3397
  }
2995
- if (current.type === "git") return current.source === candidate.source;
2996
- return current.type === "local" && current.source === candidate.source;
3398
+ return Boolean(
3399
+ current.sourceDigest &&
3400
+ candidate.sourceDigest &&
3401
+ current.sourceDigest === candidate.sourceDigest,
3402
+ );
2997
3403
  }
2998
3404
 
2999
3405
  function effectivePluginRecord(
@@ -3229,23 +3635,160 @@ function normalizeSourceMetadata(value) {
3229
3635
  type,
3230
3636
  source,
3231
3637
  ref: cleanBounded(value.ref, 256),
3638
+ sourceDigest:
3639
+ normalizeSourceAuthorityDigest(value.sourceDigest) ||
3640
+ pluginSourceAuthorityDigest(value.source, type, value.ref),
3232
3641
  };
3233
3642
  const registry = sanitizeSource(value.registry);
3234
3643
  const resolvedSource = sanitizeSource(value.resolvedSource);
3235
3644
  const packageName = cleanBounded(value.package, 256);
3645
+ const catalogAuthority =
3646
+ value.catalogAuthority != null
3647
+ ? normalizeCatalogAuthority(value.catalogAuthority)
3648
+ : null;
3236
3649
  if (registry) metadata.registry = registry;
3237
3650
  if (resolvedSource) metadata.resolvedSource = resolvedSource;
3651
+ const resolvedSourceDigest =
3652
+ normalizeSourceAuthorityDigest(value.resolvedSourceDigest) ||
3653
+ (value.resolvedSource
3654
+ ? catalogAuthority?.archiveSource
3655
+ ? archiveSourceAuthorityDigest(
3656
+ value.resolvedSource,
3657
+ catalogAuthority.archiveSource,
3658
+ )
3659
+ : catalogAuthority?.commandSource
3660
+ ? commandSourceAuthorityDigest(
3661
+ value.resolvedSource,
3662
+ catalogAuthority.commandSource,
3663
+ )
3664
+ : pluginSourceAuthorityDigest(
3665
+ value.resolvedSource,
3666
+ "git",
3667
+ value.ref,
3668
+ )
3669
+ : null);
3670
+ const policyDigest = normalizeSourceAuthorityDigest(value.policyDigest);
3671
+ if (resolvedSourceDigest) {
3672
+ metadata.resolvedSourceDigest = resolvedSourceDigest;
3673
+ }
3674
+ if (policyDigest) metadata.policyDigest = policyDigest;
3238
3675
  if (packageName) metadata.package = packageName;
3239
3676
  if (value.offline === true) metadata.offline = true;
3240
- if (value.catalogAuthority != null) {
3241
- metadata.catalogAuthority = normalizeCatalogAuthority(
3242
- value.catalogAuthority,
3243
- );
3244
- }
3677
+ if (catalogAuthority) metadata.catalogAuthority = catalogAuthority;
3245
3678
  assertCatalogRemoteArtifactBindings(metadata);
3679
+ assertCatalogArchiveSourceBindings(metadata);
3680
+ assertCatalogCommandSourceBindings(metadata);
3246
3681
  return metadata;
3247
3682
  }
3248
3683
 
3684
+ function assertCatalogArchiveSourceBindings(metadata) {
3685
+ const archive = metadata.catalogAuthority?.archiveSource;
3686
+ if (!archive) return;
3687
+ if (
3688
+ metadata.type !== "registry" ||
3689
+ !metadata.registry ||
3690
+ !metadata.resolvedSource
3691
+ ) {
3692
+ throw new Error(
3693
+ "marketplace archive authority requires registry and resolved source metadata",
3694
+ );
3695
+ }
3696
+ let registryOrigin;
3697
+ try {
3698
+ registryOrigin = new URL(metadata.registry).origin;
3699
+ } catch {
3700
+ throw new Error("marketplace archive registry URL is invalid");
3701
+ }
3702
+ if (
3703
+ archive.registryOrigin !== registryOrigin ||
3704
+ archive.url !== metadata.resolvedSource
3705
+ ) {
3706
+ throw new Error(
3707
+ "marketplace archive authority does not match the selected registry and source",
3708
+ );
3709
+ }
3710
+ }
3711
+
3712
+ function assertCatalogCommandSourceBindings(metadata) {
3713
+ const command = metadata.catalogAuthority?.commandSource;
3714
+ if (!command) return;
3715
+ if (
3716
+ metadata.type !== "registry" ||
3717
+ !metadata.registry ||
3718
+ metadata.resolvedSource !== command.descriptorIdentity
3719
+ ) {
3720
+ throw new Error(
3721
+ "marketplace command authority requires matching registry source metadata",
3722
+ );
3723
+ }
3724
+ }
3725
+
3726
+ function normalizeSourceAuthorityDigest(value) {
3727
+ const normalized = cleanBounded(value, 64)?.toLowerCase() || null;
3728
+ return normalized && /^[a-f0-9]{64}$/u.test(normalized) ? normalized : null;
3729
+ }
3730
+
3731
+ function pluginSourceAuthorityDigest(source, type, ref = null) {
3732
+ const identity = canonicalizePluginSource(source, {
3733
+ kindHint:
3734
+ type === "registry" ? "registry" : type === "local" ? "directory" : "git",
3735
+ ref: ref || undefined,
3736
+ });
3737
+ return crypto
3738
+ .createHash("sha256")
3739
+ .update(
3740
+ canonicalJson({
3741
+ identity: identity.identityDigest || identity.key,
3742
+ ref: identity.ref ?? null,
3743
+ path: identity.path ?? null,
3744
+ }),
3745
+ )
3746
+ .digest("hex");
3747
+ }
3748
+
3749
+ function archiveSourceAuthorityDigest(source, archive) {
3750
+ return crypto
3751
+ .createHash("sha256")
3752
+ .update(
3753
+ canonicalJson({
3754
+ kind: "archive",
3755
+ source: sanitizeSource(source),
3756
+ archiveSha256: archive.archiveSha256,
3757
+ payloadSha256: archive.payloadSha256,
3758
+ }),
3759
+ )
3760
+ .digest("hex");
3761
+ }
3762
+
3763
+ function commandSourceAuthorityDigest(source, command) {
3764
+ return crypto
3765
+ .createHash("sha256")
3766
+ .update(
3767
+ canonicalJson({
3768
+ kind: "command",
3769
+ descriptorIdentity: sanitizeSource(source),
3770
+ mode: command.mode || command.commandMode || null,
3771
+ payloadSha256: command.payloadSha256 || null,
3772
+ }),
3773
+ )
3774
+ .digest("hex");
3775
+ }
3776
+
3777
+ function registryResolvedSourceAuthorityDigest(authority) {
3778
+ if (!authority) return null;
3779
+ if (authority.sourceType === "archive") {
3780
+ return archiveSourceAuthorityDigest(authority.declaredSource, authority);
3781
+ }
3782
+ if (authority.sourceType === "command") {
3783
+ return commandSourceAuthorityDigest(authority.declaredSource, authority);
3784
+ }
3785
+ return pluginSourceAuthorityDigest(
3786
+ authority.declaredSource,
3787
+ "git",
3788
+ authority.ref,
3789
+ );
3790
+ }
3791
+
3249
3792
  function normalizeMigrationIssuedAt(value) {
3250
3793
  const date = value == null ? new Date() : new Date(String(value));
3251
3794
  if (!Number.isFinite(date.getTime())) {
@@ -3545,6 +4088,13 @@ function normalizeCatalogAuthority(value) {
3545
4088
  const remoteArtifactEvidence = normalizeRemoteArtifactEvidence(
3546
4089
  value.remoteArtifactEvidence,
3547
4090
  );
4091
+ const archiveSource = normalizeArchiveSourceAuthority(value.archiveSource);
4092
+ const commandSource = normalizeCommandSourceAuthority(value.commandSource);
4093
+ if (archiveSource && commandSource) {
4094
+ throw new Error(
4095
+ "catalogAuthority cannot combine archive and command source authority",
4096
+ );
4097
+ }
3548
4098
  const remoteSbomPayloadComparison = assertRemoteSbomPayloadComparison(
3549
4099
  value.remoteSbomPayloadComparison,
3550
4100
  {
@@ -3651,6 +4201,8 @@ function normalizeCatalogAuthority(value) {
3651
4201
  ...(registryNetworkAuthority ? { registryNetworkAuthority } : {}),
3652
4202
  ...(artifactExpectations ? { artifactExpectations } : {}),
3653
4203
  ...(remoteArtifactEvidence ? { remoteArtifactEvidence } : {}),
4204
+ ...(archiveSource ? { archiveSource } : {}),
4205
+ ...(commandSource ? { commandSource } : {}),
3654
4206
  ...(remoteSbomPayloadComparison ? { remoteSbomPayloadComparison } : {}),
3655
4207
  ...(publisherDeclaration ? { publisherDeclaration } : {}),
3656
4208
  ...(publisherAuthority ? { publisherAuthority } : {}),
@@ -3661,6 +4213,108 @@ function normalizeCatalogAuthority(value) {
3661
4213
  };
3662
4214
  }
3663
4215
 
4216
+ function normalizeArchiveSourceAuthority(value) {
4217
+ if (value == null) return null;
4218
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4219
+ throw new Error("catalogAuthority.archiveSource must be an object");
4220
+ }
4221
+ const url = cleanBounded(value.url, 4096);
4222
+ const registryOrigin = cleanBounded(value.registryOrigin, 4096);
4223
+ const archiveSha256 = cleanBounded(value.archiveSha256, 64);
4224
+ const payloadSha256 = cleanBounded(value.payloadSha256, 64);
4225
+ let archiveUrl;
4226
+ let selectedOrigin;
4227
+ try {
4228
+ archiveUrl = new URL(url);
4229
+ selectedOrigin = new URL(registryOrigin).origin;
4230
+ } catch {
4231
+ throw new Error("catalogAuthority.archiveSource URL authority is invalid");
4232
+ }
4233
+ if (
4234
+ archiveUrl.origin !== selectedOrigin ||
4235
+ archiveUrl.username ||
4236
+ archiveUrl.password
4237
+ ) {
4238
+ throw new Error(
4239
+ "catalogAuthority.archiveSource must use the selected registry origin",
4240
+ );
4241
+ }
4242
+ if (
4243
+ value.schemaVersion !== "cc-plugin-marketplace-archive-source/v1" ||
4244
+ value.status !== "digest-verified-and-extracted" ||
4245
+ !/^[a-f0-9]{64}$/u.test(archiveSha256 || "") ||
4246
+ !/^[a-f0-9]{64}$/u.test(payloadSha256 || "")
4247
+ ) {
4248
+ throw new Error(
4249
+ "catalogAuthority.archiveSource digest authority is invalid",
4250
+ );
4251
+ }
4252
+ const compressedBytes = Number(value.compressedBytes);
4253
+ const fileCount = Number(value.fileCount);
4254
+ const totalBytes = Number(value.totalBytes);
4255
+ if (
4256
+ !Number.isSafeInteger(compressedBytes) ||
4257
+ compressedBytes <= 0 ||
4258
+ compressedBytes > 64 * 1024 * 1024 ||
4259
+ !Number.isSafeInteger(fileCount) ||
4260
+ fileCount <= 0 ||
4261
+ fileCount > 10_000 ||
4262
+ !Number.isSafeInteger(totalBytes) ||
4263
+ totalBytes <= 0 ||
4264
+ totalBytes > 256 * 1024 * 1024
4265
+ ) {
4266
+ throw new Error("catalogAuthority.archiveSource size authority is invalid");
4267
+ }
4268
+ return {
4269
+ schemaVersion: value.schemaVersion,
4270
+ status: value.status,
4271
+ url: archiveUrl.href,
4272
+ registryOrigin: selectedOrigin,
4273
+ archiveSha256,
4274
+ payloadSha256,
4275
+ compressedBytes,
4276
+ fileCount,
4277
+ totalBytes,
4278
+ fromCache: value.fromCache === true,
4279
+ };
4280
+ }
4281
+
4282
+ function normalizeCommandSourceAuthority(value) {
4283
+ if (value == null) return null;
4284
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4285
+ throw new Error("catalogAuthority.commandSource must be an object");
4286
+ }
4287
+ const descriptorIdentity = cleanBounded(value.descriptorIdentity, 128);
4288
+ const payloadSha256 = cleanBounded(value.payloadSha256, 64)?.toLowerCase();
4289
+ const mode = value.mode;
4290
+ const fileCount = Number(value.fileCount);
4291
+ const totalBytes = Number(value.totalBytes);
4292
+ if (
4293
+ value.schemaVersion !== "cc-plugin-marketplace-command-source/v1" ||
4294
+ value.status !== "descriptor-executed-and-verified" ||
4295
+ !/^command:[a-f0-9]{32}$/u.test(descriptorIdentity || "") ||
4296
+ !/^[a-f0-9]{64}$/u.test(payloadSha256 || "") ||
4297
+ !["copy", "link"].includes(mode) ||
4298
+ !Number.isSafeInteger(fileCount) ||
4299
+ fileCount <= 0 ||
4300
+ fileCount > 10_000 ||
4301
+ !Number.isSafeInteger(totalBytes) ||
4302
+ totalBytes <= 0 ||
4303
+ totalBytes > 256 * 1024 * 1024
4304
+ ) {
4305
+ throw new Error("catalogAuthority.commandSource authority is invalid");
4306
+ }
4307
+ return {
4308
+ schemaVersion: value.schemaVersion,
4309
+ status: value.status,
4310
+ descriptorIdentity,
4311
+ mode,
4312
+ payloadSha256,
4313
+ fileCount,
4314
+ totalBytes,
4315
+ };
4316
+ }
4317
+
3664
4318
  function normalizeArtifactExpectations(value) {
3665
4319
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3666
4320
  const digest = (candidate, label) => {
@@ -3928,16 +4582,7 @@ function sanitizeSource(value, preservePath = false) {
3928
4582
  const raw = cleanBounded(value, 4096);
3929
4583
  if (!raw) return null;
3930
4584
  if (preservePath) return raw;
3931
- try {
3932
- const parsed = new URL(raw);
3933
- parsed.username = "";
3934
- parsed.password = "";
3935
- parsed.search = "";
3936
- parsed.hash = "";
3937
- return parsed.toString();
3938
- } catch {
3939
- return raw;
3940
- }
4585
+ return redactPluginSourceForDisplay(raw);
3941
4586
  }
3942
4587
 
3943
4588
  function cleanBounded(value, max) {
@@ -4075,6 +4720,20 @@ function writeDurableFileAtomic(directory, target, value) {
4075
4720
  }
4076
4721
  }
4077
4722
 
4723
+ function assertStagedRegistryCommandPayload(staged, authority) {
4724
+ if (authority?.sourceType !== "command") return;
4725
+ const payload = buildMarketplacePayloadSbom(staged);
4726
+ if (
4727
+ payload.digest !== authority.payloadSha256 ||
4728
+ payload.fileCount !== authority.fileCount ||
4729
+ payload.totalBytes !== authority.totalBytes
4730
+ ) {
4731
+ throw new Error(
4732
+ "registry command source payload changed before immutable installation",
4733
+ );
4734
+ }
4735
+ }
4736
+
4078
4737
  /**
4079
4738
  * Copy `src` → `dst`, refusing to write outside `root` and skipping symlinks
4080
4739
  * (which could later resolve outside the plugin sandbox). Directories recurse.