chainlesschain 0.165.6 → 0.165.9

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 (251) hide show
  1. package/package.json +2 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-vfoDb6HZ.js → AIOps-DxwjrnEA.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-Cm5f7p93.js → ActionButton-JdbYowzn.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-yRuvgWxa.js → Analytics-C06GCahv.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BROsC4h1.js → AppLayout-BUVEEyi1.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-CimHApb1.js → Artifacts-D_ORzkJd.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DI8kr8zI.js → Audit-8o_Y5ejG.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DSLzKy-n.js → BackgroundAgents-DrkHd5VU.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DkH_n2r1.js → Backup-CKBZCX11.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-CKSrUoRA.js → BaseInput-C8HkI399.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-ClwfMXUS.js → Chat-zb2H2Wux.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjMaqfL7.js → ChatBubbleRenderer-BDcNpU_F.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BYTehcQG.js → Checkbox-MwPtQmNz.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B8AMcp6J.js → Codegen-3p5QCkFK.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BNSogPo3.js → Col-BPLlhn52.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-JxPvGb8O.js → Community-C-3dBNOM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DF7bFAgE.js → Compact-BUcy-F_b.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bvhqnwrq.js → Compliance-CI8NxT8m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-BDS0CJvM.js → Cowork-DaDEBS-Q.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DqvRfxoQ.js → Cron-BSv9HXu5.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CDcIvnqS.js → Crosschain-yuiKlJmv.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D7jaUxU3.js → DID-D6LfldB0.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-B5A18wEV.js → Dashboard-CBhLqG61.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-_6IVkGag.js → Dropdown-BG8pIl3O.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-C9PeiKCG.js → EmailListRenderer-CY92VIE6.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DXjuEHrX.js → FamilyGuardDashboard-1Q_ehauM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-B4IAR9zv.js → Federation-D3XirJIU.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-CxQeSNx1.js → FormItemContext-DfI9UuGZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-B8GS-Y3N.js → GenericCardRenderer-IeCZk2m7.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-BS8SouUX.js → Git-DzK4IJAZ.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-Dq9aYb5Q.js → Governance-BHf5KfmQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BrCEe9AJ.js → Inference-BmaxSALS.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DybOpiY2.js → KnowledgeGraph-BgePFLA9.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DBpzbRfx.js → Logs-nqgxpOc4.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-xkX6izBt.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-Cc7CCSCu.js → Marketplace-Bu8to-86.js} +1 -1
  38. package/src/assets/web-panel/assets/McpTools-BFWTzEk0.js +4 -0
  39. package/src/assets/web-panel/assets/{Memory-KCqlp9ol.js → Memory-B026G2GA.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-Bys84ySN.js → MobileBridge-CM2Eeg6I.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-B-6qU-je.js → MobileProjects-B79Ob3hy.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BgY-fvCq.js → Mtc-CxevSZuV.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-B8pCmqQJ.js → MtcAudit-X396cPEX.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BbOmYutA.js → Multisig-HWIB_aZ1.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-BocLQz_M.js → NLProgramming-gnYD1Xwy.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-CSwbxEop.js → Notes-Dn2Gf_J1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-CqFNSYG3.js → NotificationSettings-BRKBQ5rq.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-C6gMfAk7.js → OrderTableRenderer-Dy___Cgo.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-QrDXVdMJ.js → Organization-cqfccxnt.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-Cxntotjt.js → Overflow-CSX4FHN3.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CIdBOwNq.js → P2P-Bwa4aghp.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-CT5aP8rD.js → PdhVaultBrowser-Dhszyjig.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-DH7OM8XU.js → Permissions-CXzTyk6e.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CnJqbWce.js → PersonalDataHub-CFCAAJbb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-5C3HG1kH.js → Pipeline-BPJbx6xt.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B4yIp7xK.js → Privacy-BbiXvEHz.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BMvt-2M0.js → ProjectInit-B-s7-wSD.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CvSTEMRG.js → ProjectSettings-Dyug206y.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BKBJxEek.js → Projects-BtAI5Fwo.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-BdwUqFWN.js → Providers-JpIPmz6N.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DtnCKlTm.js → QrScannerModal-CZdoN64A.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-sTbgwyNr.js → QuickAsk-BwvnoRIT.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Bzr14gSb.js → Recommend-ASMlMQrD.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-C_TF5IaJ.js → RemoteSession-BiJzKFKi.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-GCAM4H2K.js → Reputation-C80TWLIO.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-Dir0r6OQ.js → Row-C1VNmjmX.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CpGnlecX.js → RssFeed-T2MyGUlG.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DMMamC6u.js → Search-DXM6O83L.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-yqay5yG-.js → Security-nv69kU7W.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-C5i2wh9G.js → Services-AyHWe7Qj.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-vUqk35yj.js → Skeleton-D6PWLxFI.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DzcSkhot.js → Skills-BqVeOmNQ.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CB6qbYNr.js → Sla-BegIPl9M.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BHUI7cox.js → SpeechSettings-CKJn3xk-.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-D-RrsxTJ.js → SyncSettings-Th4cnAMl.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-DFxdOJiq.js → Tasks-BuKfBroh.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-SKAob_Aq.js → Templates-CMuWN8Ac.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BPfsfHQ-.js → Tenant-C4je8E8Q.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-8iw7FpUf.js → Terminal-agJESOcO.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C4Gjj5Su.js → TimelineRenderer-D2qDuVZ_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-COYhN18e.js → Tokens-Cvmn99rH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-B-JzXcf6.js → Trigger-D_QYIHH8.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BJ5bg0nI.js → Trust-BmzvQLnX.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-BG_ByRMf.js → UkeySign-D_LopHwH.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B3kOnSiU.js → VideoEditing-DoWl3PhQ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-CcoGFnK7.js → Wallet-5NYcAkxe.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-DdT8vRC_.js → WebAuthn-B-_Q3ewp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CZu3M9N8.js → WorkflowEditor-CHET-gi2.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DxJt3Qba.js → chat-C6nvb-n6.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DdYfwJaB.js → colors-CTDE9LTX.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BG3SUzwj.js → compact-item-KnemdF8D.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-By51z2-1.js → createContext-DUJR3Uek.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-1DqLH1KX.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-B0apZ3KJ.js → hasIn-CFNTaby8.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-ZxtP8jG7.js → index-65HO56Bv.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Da_WBdLX.js → index-B8RtbSkV.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BBPf3TpM.js → index-BHqSqnW7.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BP94CrBV.js → index-BTetEUWd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-p1_8SbI1.js → index-BXWg1crv.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BWF8_mkx.js → index-BdiC4LOs.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BhJtcPWo.js +1 -0
  102. package/src/assets/web-panel/assets/{index-SQqOlR3d.js → index-BuizJo2E.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CpjZhgNd.js → index-BwZM1rod.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CzmQElZy.js → index-C96N9xeL.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-bP_VlDb4.js → index-CHfUdnJH.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-ZFbps9po.js → index-CKvBF3Ov.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DaIz0NAW.js → index-CSD9_I2_.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BewKYrX9.js → index-Cc7dbl_p.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CNIxXMmN.js → index-CcF0D7UZ.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Q2SCX-MI.js → index-Ceu92a8D.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DE76D-kA.js → index-CnLpuTAU.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cq8MWPDT.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BkvcEo4p.js → index-CquKnw5r.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Cp6e-hGw.js → index-CyEtHvEE.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BLIbGOLX.js → index-DF-fcTvQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DCn8IcxV.js → index-DF1Y0Vuw.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bissefox.js → index-DJyPeBBk.js} +3 -3
  118. package/src/assets/web-panel/assets/{index-BZ_0qm4g.js → index-DP-PbO11.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-kf57LJ93.js → index-DZgCRMxk.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BYINoRLK.js → index-DfJzS3Qa.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-3jWkIu5j.js → index-DqPpJkte.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5rLiEo0.js → index-DzReoLyh.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CW93tZDv.js → index-JGTiDeOW.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1C9i79A.js → index-NUR0If7O.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CAhHirVH.js → index-NtAcnJYW.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-RD6O-0C6.js → index-OmuhFYKE.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-oxRrmFO9.js → index-Wlc7o_C8.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B3rWuPMW.js → index-bxTPfdri.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BIrJfqO4.js → index-k6uorYmf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BfWYsB9t.js → index-ox6mIwE2.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bos5_aMV.js → index-rVDD7AAR.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CWEuZWCK.js → index-u0h6QqVV.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Dz49VOyQ.js → index-v-mtxmWH.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-BWJktcg8.js → initDefaultProps-qEt8dI-t.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BEXBzUFV.js → motion-DetKPWy-.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-BUKwn2sx.js → move-BeNYeDV2.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-FTI7btqQ.js → mtc-parser-C0q0MK_T.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CsLtSPH3.js → omit-DLKkrKpE.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DWZxsT_S.js → pickAttrs-Bw1tWsY8.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-Bcfs43CX.js → placementArrow-DeesBLbd.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-a9bcC1p-.js → responsiveObserve-bqLhxHhn.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BDTzgOXN.js → slide-DGCXRoQm.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-Iq1j_HUg.js → statusUtils-H-re3Egl.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-DAZEYxeh.js → styleChecker-BXg4qGX_.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DVkjyDrh.js → useFlexGapSupport-CKLb2KA6.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-wXTpNTHt.js → useFs-CTAhHeyw.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CtQM_aZr.js → usePersonalDataHub-BQ4pDh5g.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-hjEoG85v.js → vnode-jrSK6j4j.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-BHVpyKg_.js → zoom-fOQE24pw.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +3 -3
  152. package/src/commands/agent.js +91 -60
  153. package/src/commands/git.js +39 -0
  154. package/src/commands/plugin.js +17 -2
  155. package/src/commands/session-group.js +131 -0
  156. package/src/commands/session-location.js +48 -0
  157. package/src/commands/session.js +11 -0
  158. package/src/commands/sync-providers.js +4 -1
  159. package/src/commands/update.js +1 -0
  160. package/src/data/changelog.json +40 -1
  161. package/src/gateways/ws/mcp-topic-handlers.js +167 -0
  162. package/src/gateways/ws/ws-agent-handler.js +20 -12
  163. package/src/harness/jsonl-session-store.js +279 -37
  164. package/src/harness/mcp-client.js +701 -91
  165. package/src/harness/session-index.js +15 -4
  166. package/src/lib/agent-network.js +166 -29
  167. package/src/lib/agent-router.js +62 -6
  168. package/src/lib/agent-sandbox.js +7 -1
  169. package/src/lib/agent-worktree.js +8 -1
  170. package/src/lib/auto-mode-safety-classifier.js +3 -17
  171. package/src/lib/background-agent-keeper-protocol.js +26 -6
  172. package/src/lib/background-agent-supervisor.js +17 -1
  173. package/src/lib/browser-evidence.js +1405 -0
  174. package/src/lib/chrome-connector.js +465 -79
  175. package/src/lib/claude-code-bridge.js +159 -54
  176. package/src/lib/claude-project-auto-memory.js +481 -0
  177. package/src/lib/claude-project-storage-layout.js +104 -0
  178. package/src/lib/cloud/cloud-client.js +40 -0
  179. package/src/lib/cowork-workflow.js +69 -6
  180. package/src/lib/execution-location-local-supervisor.mjs +226 -0
  181. package/src/lib/execution-location-runner-lifecycle.js +991 -0
  182. package/src/lib/execution-location-target-preflight.js +333 -0
  183. package/src/lib/execution-location-target.js +783 -13
  184. package/src/lib/external-agent-adapters.js +269 -0
  185. package/src/lib/file-lines.js +6 -1
  186. package/src/lib/gateway-client-resilience.js +571 -0
  187. package/src/lib/gitlab-mr-worktree.js +176 -0
  188. package/src/lib/hooks-v2-producers.js +119 -15
  189. package/src/lib/hooks-v2-workspace-context.js +44 -1
  190. package/src/lib/host-resource-budget.js +295 -0
  191. package/src/lib/ide-context.js +15 -2
  192. package/src/lib/ipfs-storage.js +127 -25
  193. package/src/lib/llm-config-defaults.js +43 -0
  194. package/src/lib/mcp-lifecycle-authority.js +887 -0
  195. package/src/lib/mcp-oauth.js +101 -20
  196. package/src/lib/mcp-tls.js +103 -3
  197. package/src/lib/orchestrator.js +47 -0
  198. package/src/lib/paths.js +260 -32
  199. package/src/lib/permission-request.js +48 -0
  200. package/src/lib/plugin-runtime/bin.js +3 -1
  201. package/src/lib/plugin-runtime/hooks.js +3 -1
  202. package/src/lib/plugin-runtime/install.js +117 -1
  203. package/src/lib/plugin-runtime/lsp.js +4 -4
  204. package/src/lib/plugin-runtime/marketplace-archive-source.js +40 -5
  205. package/src/lib/plugin-runtime/marketplace-catalog.js +13 -2
  206. package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
  207. package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
  208. package/src/lib/plugin-runtime/marketplace-source-adapter.js +33 -8
  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 +151 -6
  212. package/src/lib/plugin-runtime/settings.js +3 -1
  213. package/src/lib/plugin-runtime/trust.js +274 -13
  214. package/src/lib/process-execution-broker/credential-agent.js +210 -41
  215. package/src/lib/process-execution-broker/index.js +15 -1
  216. package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
  217. package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
  218. package/src/lib/project-mcp-trust.js +165 -10
  219. package/src/lib/resume-session.js +9 -6
  220. package/src/lib/session-core-singletons.js +26 -11
  221. package/src/lib/session-host-lease.js +52 -16
  222. package/src/lib/session-lifecycle-scope.js +153 -0
  223. package/src/lib/session-message-fabric.js +168 -21
  224. package/src/lib/session-projection.js +244 -4
  225. package/src/lib/session-store-guard.js +3 -1
  226. package/src/lib/session-workbench-store.js +466 -0
  227. package/src/lib/shell-approval.js +7 -5
  228. package/src/lib/spellcheck.js +211 -0
  229. package/src/lib/sub-agent-context.js +9 -9
  230. package/src/lib/sync-cli-db.js +485 -129
  231. package/src/lib/sync-credentials.js +31 -20
  232. package/src/lib/web-fetch.js +112 -40
  233. package/src/lib/web-search.js +127 -61
  234. package/src/lib/workflow-engine.js +42 -11
  235. package/src/lib/workspace-trust.js +793 -0
  236. package/src/repl/agent-repl.js +124 -5
  237. package/src/repl/permission-prompt.js +10 -12
  238. package/src/runtime/agent-core.js +311 -18
  239. package/src/runtime/agent-runtime.js +12 -1
  240. package/src/runtime/bootstrap.js +37 -13
  241. package/src/runtime/coding-agent-contract-shared.cjs +11 -4
  242. package/src/runtime/headless-runner.js +234 -36
  243. package/src/runtime/headless-stream.js +181 -6
  244. package/src/runtime/mcp-config.js +298 -8
  245. package/src/runtime/policies/agent-policy.js +8 -0
  246. package/src/runtime/status-diagnostics-lite.js +9 -1
  247. package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +0 -1
  248. package/src/assets/web-panel/assets/McpTools-DFaIhOfu.js +0 -7
  249. package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +0 -1
  250. package/src/assets/web-panel/assets/index-BETiL0a3.js +0 -1
  251. package/src/assets/web-panel/assets/index-BhAVHuW8.js +0 -1
@@ -27,11 +27,20 @@
27
27
  * makes.
28
28
  */
29
29
  import fs from "fs";
30
+ import crypto from "crypto";
30
31
  import os from "os";
31
32
  import path from "path";
32
33
  import http from "http";
33
34
  import executionBroker from "./process-execution-broker/index.js";
34
35
  import { redactSecrets } from "./secret-scan.js";
36
+ import {
37
+ authorizeBrowserAction,
38
+ authorizeBrowserReplay,
39
+ browserEvidenceDigest,
40
+ createBrowserEvidenceEnvelope,
41
+ describeBrowserAction,
42
+ normalizeBrowserEvidenceBinding,
43
+ } from "./browser-evidence.js";
35
44
 
36
45
  export const DEFAULT_CDP_PORT = 9222;
37
46
 
@@ -138,6 +147,44 @@ function redactBrowserDomUrl(value, cap = 1000) {
138
147
  return redactBrowserText(`${prefix}${query}`, cap);
139
148
  }
140
149
 
150
+ function isSensitiveInputTag(tag) {
151
+ return (
152
+ /\btype\s*=\s*["']?password\b/i.test(tag) ||
153
+ /\b(?:name|id)\s*=\s*["'][^"']*(?:password|passwd|token|secret|api[-_]?key)[^"']*["']/i.test(
154
+ tag,
155
+ )
156
+ );
157
+ }
158
+
159
+ export function browserDomRedactionMetadata(value) {
160
+ const html = String(value || "");
161
+ const inputTags = html.match(/<input\b[^>]*>/gi) || [];
162
+ const sensitiveInputValues = inputTags.filter(
163
+ (tag) =>
164
+ isSensitiveInputTag(tag) && /\bvalue\s*=\s*["'][^"']*["']/i.test(tag),
165
+ ).length;
166
+ const sensitiveTextareaValues = (
167
+ html.match(
168
+ /<textarea\b[^>]*(?:name|id)\s*=\s*["'][^"']*(?:password|passwd|token|secret|api[-_]?key)[^"']*["'][^>]*>[\s\S]*?<\/textarea>/gi,
169
+ ) || []
170
+ ).length;
171
+ let urlQueryValues = 0;
172
+ for (const match of html.matchAll(
173
+ /\b(?:href|src|action)\s*=\s*["']([^"']+)["']/gi,
174
+ )) {
175
+ const query = String(match[1]).split("#", 1)[0].split("?", 2)[1] || "";
176
+ urlQueryValues += query
177
+ .split(/&(?:amp;)?/iu)
178
+ .filter((part) => part.trim() !== "").length;
179
+ }
180
+ return Object.freeze({
181
+ applied: true,
182
+ sensitiveFieldValues: sensitiveInputValues + sensitiveTextareaValues,
183
+ urlQueryValues,
184
+ secretPatterns: redactSecrets(html) === html ? 0 : 1,
185
+ });
186
+ }
187
+
141
188
  /**
142
189
  * Redact secret-shaped text, sensitive form values, and DOM URL query values
143
190
  * from the bounded snapshot before it crosses the tool boundary.
@@ -145,12 +192,7 @@ function redactBrowserDomUrl(value, cap = 1000) {
145
192
  export function redactBrowserDom(value, cap = DEFAULT_DOM_CAP) {
146
193
  let html = redactSecrets(String(value || ""));
147
194
  html = html.replace(/<input\b[^>]*>/gi, (tag) => {
148
- const sensitive =
149
- /\btype\s*=\s*["']?password\b/i.test(tag) ||
150
- /\b(?:name|id)\s*=\s*["'][^"']*(?:password|passwd|token|secret|api[-_]?key)[^"']*["']/i.test(
151
- tag,
152
- );
153
- if (!sensitive) return tag;
195
+ if (!isSensitiveInputTag(tag)) return tag;
154
196
  return tag.replace(/\bvalue\s*=\s*(["'])[^"']*\1/i, 'value="[REDACTED]"');
155
197
  });
156
198
  html = html.replace(
@@ -168,10 +210,11 @@ export function redactBrowserDom(value, cap = DEFAULT_DOM_CAP) {
168
210
  function redactScreenshotFailure(error, generatedPath, cap = 300) {
169
211
  let message = String(error && error.message ? error.message : error);
170
212
  if (generatedPath) {
171
- message = message
172
- .split(String(generatedPath))
173
- .join("[SCREENSHOT_PATH]");
213
+ message = message.split(String(generatedPath)).join("[SCREENSHOT_PATH]");
174
214
  }
215
+ message = message.replace(/https?:\/\/[^\s"'<>]+/giu, (url) =>
216
+ redactBrowserUrl(url, cap),
217
+ );
175
218
  return redactBrowserText(message.split("\n")[0], cap);
176
219
  }
177
220
 
@@ -184,6 +227,15 @@ function cleanupFailedScreenshot(generatedPath, deps) {
184
227
  }
185
228
  }
186
229
 
230
+ function digestGeneratedFile(generatedPath, deps) {
231
+ if (!generatedPath) return null;
232
+ try {
233
+ return browserEvidenceDigest(deps.fs.readFileSync(generatedPath));
234
+ } catch {
235
+ return null;
236
+ }
237
+ }
238
+
187
239
  const _deps = {
188
240
  fs,
189
241
  spawn: executionBroker.spawn.bind(executionBroker),
@@ -427,9 +479,13 @@ export async function captureState({
427
479
  });
428
480
  }
429
481
  };
430
- page.on("console", onConsole);
431
- page.on("requestfailed", onRequestFailed);
432
- page.on("response", onResponse);
482
+ const canObserve =
483
+ typeof page.on === "function" && typeof page.off === "function";
484
+ if (canObserve) {
485
+ page.on("console", onConsole);
486
+ page.on("requestfailed", onRequestFailed);
487
+ page.on("response", onResponse);
488
+ }
433
489
  try {
434
490
  if (reload) {
435
491
  await page
@@ -440,6 +496,7 @@ export async function captureState({
440
496
 
441
497
  const state = {
442
498
  ok: true,
499
+ observationCaptureAvailable: canObserve,
443
500
  port,
444
501
  tab: index,
445
502
  url: redactBrowserUrl(page.url()),
@@ -452,8 +509,16 @@ export async function captureState({
452
509
  network: networkEntries,
453
510
  };
454
511
  if (includeDom) {
455
- const html = await page.content().catch(() => "");
512
+ let htmlCaptureSucceeded = true;
513
+ const html = await page.content().catch(() => {
514
+ htmlCaptureSucceeded = false;
515
+ return "";
516
+ });
456
517
  state.html = redactBrowserDom(html, domCap);
518
+ state.htmlCaptureSucceeded = htmlCaptureSucceeded;
519
+ state.htmlRedaction = browserDomRedactionMetadata(html);
520
+ state.htmlSourceChars = html.length;
521
+ state.htmlDigest = browserEvidenceDigest(state.html);
457
522
  state.htmlTruncated = html.length > domCap;
458
523
  }
459
524
  if (screenshotPath) {
@@ -468,13 +533,16 @@ export async function captureState({
468
533
  if (!state.screenshotError) {
469
534
  state.screenshotPath = screenshotPath;
470
535
  state.screenshotRef = path.basename(screenshotPath);
536
+ state.screenshotSha256 = digestGeneratedFile(screenshotPath, deps);
471
537
  }
472
538
  }
473
539
  return state;
474
540
  } finally {
475
- page.off("console", onConsole);
476
- page.off("requestfailed", onRequestFailed);
477
- page.off("response", onResponse);
541
+ if (canObserve) {
542
+ page.off("console", onConsole);
543
+ page.off("requestfailed", onRequestFailed);
544
+ page.off("response", onResponse);
545
+ }
478
546
  }
479
547
  } finally {
480
548
  // connectOverCDP close() disconnects the client; the browser lives on.
@@ -501,6 +569,8 @@ export const SUPPORTED_BROWSER_ACTIONS = Object.freeze([
501
569
  "waitForSelector",
502
570
  "screenshot",
503
571
  "assertText",
572
+ "upload",
573
+ "download",
504
574
  ]);
505
575
  export const MAX_BROWSER_ACTIONS = 30;
506
576
  export const DEFAULT_ACTION_TIMEOUT_MS = 10000;
@@ -614,12 +684,15 @@ export function normalizeBrowserActions(actions, { deps = _deps } = {}) {
614
684
  `navigate url must be http(s):// (got ${parsed.protocol})`,
615
685
  );
616
686
  }
687
+ if (parsed.username || parsed.password) {
688
+ throw new Error("navigate url must not contain embedded credentials");
689
+ }
617
690
  return { type, url: String(raw.url) };
618
691
  }
619
692
  case "waitForSelector": {
620
693
  const t = Number(raw.timeoutMs ?? raw.timeout_ms ?? 5000);
621
694
  const timeoutMs = Math.min(
622
- Math.max(Number.isFinite(t) ? t : 5000, 1),
695
+ Math.max(Number.isFinite(t) ? Math.trunc(t) : 5000, 1),
623
696
  MAX_ACTION_TIMEOUT_MS,
624
697
  );
625
698
  return {
@@ -643,7 +716,7 @@ export function normalizeBrowserActions(actions, { deps = _deps } = {}) {
643
716
  type,
644
717
  screenshotPath: path.join(
645
718
  deps.tmpdir(),
646
- `cc-browser-act-${Date.now()}-${i}.png`,
719
+ `cc-browser-act-${crypto.randomBytes(16).toString("hex")}-${i}.png`,
647
720
  ),
648
721
  };
649
722
  }
@@ -657,6 +730,41 @@ export function normalizeBrowserActions(actions, { deps = _deps } = {}) {
657
730
  expected: raw.expected,
658
731
  };
659
732
  }
733
+ case "upload": {
734
+ for (const key of ["path", "file", "files", "inputFiles"]) {
735
+ if (raw[key] != null) {
736
+ throw new Error(
737
+ `upload accepts only a managed session artifact_id; remove "${key}"`,
738
+ );
739
+ }
740
+ }
741
+ const artifactId = String(raw.artifactId ?? raw.artifact_id ?? "");
742
+ if (!/^art_[A-Za-z0-9_]+$/u.test(artifactId)) {
743
+ throw new Error("upload requires a managed artifact_id");
744
+ }
745
+ return {
746
+ type,
747
+ selector: requireSelector(raw, "upload"),
748
+ artifactId,
749
+ };
750
+ }
751
+ case "download": {
752
+ for (const key of ["path", "file", "output", "downloadPath"]) {
753
+ if (raw[key] != null) {
754
+ throw new Error(
755
+ `download path is generated internally — remove "${key}"`,
756
+ );
757
+ }
758
+ }
759
+ return {
760
+ type,
761
+ selector: requireSelector(raw, "download"),
762
+ downloadPath: path.join(
763
+ deps.tmpdir(),
764
+ `cc-browser-download-${crypto.randomBytes(16).toString("hex")}-${i}.bin`,
765
+ ),
766
+ };
767
+ }
660
768
  /* c8 ignore next 2 -- unreachable: type already validated above */
661
769
  default:
662
770
  throw new Error(`unsupported action type "${type}"`);
@@ -700,29 +808,34 @@ function connectorProfileOwnsPort(port, deps) {
700
808
  }
701
809
  }
702
810
 
703
- async function runOneAction(page, act, timeoutMs) {
811
+ async function runOneAction(
812
+ page,
813
+ act,
814
+ timeoutMs,
815
+ { resolveUploadArtifact = null } = {},
816
+ ) {
704
817
  switch (act.type) {
705
818
  case "click":
706
819
  await page.click(act.selector, { timeout: timeoutMs });
707
- return `clicked ${act.selector}`;
820
+ return { detail: `clicked ${act.selector}` };
708
821
  case "type":
709
822
  await page.fill(act.selector, act.text, { timeout: timeoutMs });
710
- return `typed ${act.text.length} chars into ${act.selector}`;
823
+ return { detail: `typed ${act.text.length} chars into ${act.selector}` };
711
824
  case "press":
712
825
  await page.keyboard.press(act.key);
713
- return `pressed ${act.key}`;
826
+ return { detail: `pressed ${act.key}` };
714
827
  case "navigate":
715
828
  await page.goto(act.url, {
716
829
  waitUntil: "domcontentloaded",
717
830
  timeout: timeoutMs,
718
831
  });
719
- return `navigated to ${act.url}`;
832
+ return { detail: `navigated to ${act.url}` };
720
833
  case "waitForSelector":
721
834
  await page.waitForSelector(act.selector, { timeout: act.timeoutMs });
722
- return `selector appeared: ${act.selector}`;
835
+ return { detail: `selector appeared: ${act.selector}` };
723
836
  case "screenshot":
724
837
  await page.screenshot({ path: act.screenshotPath });
725
- return "screenshot captured";
838
+ return { detail: "screenshot captured" };
726
839
  case "assertText": {
727
840
  const text = await page.textContent(act.selector, {
728
841
  timeout: timeoutMs,
@@ -732,10 +845,74 @@ async function runOneAction(page, act, timeoutMs) {
732
845
  }
733
846
  if (!text.includes(act.expected)) {
734
847
  throw new Error(
735
- `assertText FAILED: "${truncateForAudit(act.expected, 80)}" not found in ${act.selector} (got: ${truncateForAudit(text.trim(), 120)})`,
848
+ `assertText FAILED: expected text was not found in ${act.selector}`,
736
849
  );
737
850
  }
738
- return `assertText passed: ${act.selector} contains "${truncateForAudit(act.expected, 80)}"`;
851
+ return {
852
+ detail: `assertText passed: ${act.selector}`,
853
+ };
854
+ }
855
+ case "upload": {
856
+ if (typeof resolveUploadArtifact !== "function") {
857
+ throw new Error("upload artifact authority is unavailable");
858
+ }
859
+ const resolved = await resolveUploadArtifact(act.artifactId);
860
+ try {
861
+ if (
862
+ !resolved ||
863
+ typeof resolved.path !== "string" ||
864
+ !resolved.metadata?.id ||
865
+ !resolved.metadata?.sha256
866
+ ) {
867
+ throw new Error(
868
+ "upload artifact authority returned an invalid record",
869
+ );
870
+ }
871
+ const uploadDigest = String(resolved.metadata.sha256)
872
+ .replace(/^sha256:/u, "")
873
+ .toLowerCase();
874
+ if (
875
+ resolved.metadata.id !== act.artifactId ||
876
+ !/^[a-f0-9]{64}$/u.test(uploadDigest) ||
877
+ !Number.isFinite(Number(resolved.metadata.size)) ||
878
+ Number(resolved.metadata.size) < 0
879
+ ) {
880
+ throw new Error(
881
+ "upload artifact authority returned mismatched metadata",
882
+ );
883
+ }
884
+ try {
885
+ await page.setInputFiles(act.selector, resolved.path, {
886
+ timeout: timeoutMs,
887
+ });
888
+ } catch {
889
+ throw new Error("upload failed for the managed session artifact");
890
+ }
891
+ return {
892
+ detail: `uploaded managed artifact ${resolved.metadata.id}`,
893
+ uploadArtifact: {
894
+ id: resolved.metadata.id,
895
+ sha256: `sha256:${uploadDigest}`,
896
+ size: Number(resolved.metadata.size),
897
+ },
898
+ };
899
+ } finally {
900
+ await resolved?.cleanup?.();
901
+ }
902
+ }
903
+ case "download": {
904
+ if (typeof page.waitForEvent !== "function") {
905
+ throw new Error("download capture is unavailable in this browser");
906
+ }
907
+ const [download] = await Promise.all([
908
+ page.waitForEvent("download", { timeout: timeoutMs }),
909
+ page.click(act.selector, { timeout: timeoutMs }),
910
+ ]);
911
+ await download.saveAs(act.downloadPath);
912
+ return {
913
+ detail: "download captured",
914
+ suggestedName: redactBrowserText(download.suggestedFilename?.(), 200),
915
+ };
739
916
  }
740
917
  /* c8 ignore next 2 -- unreachable: normalizeBrowserActions validated type */
741
918
  default:
@@ -760,14 +937,47 @@ export async function performActions(
760
937
  continueOnError = false,
761
938
  sessionId = null,
762
939
  actionTimeoutMs = DEFAULT_ACTION_TIMEOUT_MS,
940
+ evidenceBinding = null,
941
+ originGrants = null,
942
+ expectedGrantRevisions = null,
943
+ replaySourceEnvelope = null,
944
+ replayAllowSideEffects = false,
945
+ replayAllowCredentials = false,
946
+ resolveUploadArtifact = null,
947
+ evidenceDomCap = 40000,
763
948
  deps = _deps,
764
949
  } = {},
765
950
  ) {
766
951
  let normalized;
767
952
  let resolvedPort;
953
+ let replay = null;
768
954
  try {
769
955
  resolvedPort = resolveLoopbackCdpPort(cdpUrl, port);
770
956
  normalized = normalizeBrowserActions(actions, { deps });
957
+ if (evidenceBinding) {
958
+ const normalizedBinding =
959
+ normalizeBrowserEvidenceBinding(evidenceBinding);
960
+ if (!sessionId || String(sessionId) !== normalizedBinding.session.id) {
961
+ throw new Error(
962
+ "browser evidence binding is not bound to the active session",
963
+ );
964
+ }
965
+ }
966
+ if (replaySourceEnvelope) {
967
+ replay = authorizeBrowserReplay({
968
+ sourceEnvelope: replaySourceEnvelope,
969
+ binding: evidenceBinding,
970
+ actions: normalized,
971
+ allowSideEffects: replayAllowSideEffects,
972
+ allowCredentials: replayAllowCredentials,
973
+ });
974
+ }
975
+ if (
976
+ (originGrants || expectedGrantRevisions || replaySourceEnvelope) &&
977
+ !evidenceBinding
978
+ ) {
979
+ throw new Error("browser evidence authority requires an exact binding");
980
+ }
771
981
  } catch (err) {
772
982
  return { ok: false, error: err.message };
773
983
  }
@@ -819,63 +1029,239 @@ export async function performActions(
819
1029
  "(~/.chainlesschain/chrome-profile) — actions run in that browser's REAL session";
820
1030
  }
821
1031
 
822
- let auditError = null;
823
- for (const act of normalized) {
824
- const started = Date.now();
825
- const step = { ok: false, action: act.type, detail: "", durationMs: 0 };
826
- const pageBefore = redactBrowserUrl(page.url());
827
- try {
828
- const detail = await runOneAction(page, act, stepTimeout);
829
- step.detail =
830
- act.type === "navigate"
831
- ? `navigated to ${redactBrowserUrl(act.url)}`
832
- : redactBrowserText(detail, 500);
833
- step.ok = true;
834
- if (act.screenshotPath) {
835
- step.screenshotPath = act.screenshotPath;
836
- step.screenshotRef = path.basename(act.screenshotPath);
837
- }
838
- } catch (err) {
839
- cleanupFailedScreenshot(act.screenshotPath, deps);
840
- step.detail = redactScreenshotFailure(err, act.screenshotPath, 500);
1032
+ const consoleEntries = [];
1033
+ const networkEntries = [];
1034
+ const onConsole = (message) => {
1035
+ if (consoleEntries.length < 200) {
1036
+ consoleEntries.push({
1037
+ type: redactBrowserText(message.type(), 40),
1038
+ text: redactBrowserText(message.text(), 500),
1039
+ });
841
1040
  }
842
- step.durationMs = Date.now() - started;
843
- const pageAfter = redactBrowserUrl(page.url());
844
- result.steps.push(step);
845
- try {
846
- const entry = {
847
- ts: new Date(started).toISOString(),
848
- action: act.type,
849
- ok: step.ok,
850
- durationMs: step.durationMs,
851
- pageBefore,
852
- pageAfter,
853
- result: step.detail,
854
- };
855
- if (sessionId) {
856
- entry.sessionId = redactBrowserText(sessionId, 160);
1041
+ };
1042
+ const onRequestFailed = (request) => {
1043
+ if (networkEntries.length < 200) {
1044
+ networkEntries.push({
1045
+ kind: "failed",
1046
+ url: redactBrowserUrl(request.url(), 300),
1047
+ error: redactBrowserText(request.failure()?.errorText || "", 300),
1048
+ });
1049
+ }
1050
+ };
1051
+ const onResponse = (response) => {
1052
+ if (response.status() >= 400 && networkEntries.length < 200) {
1053
+ networkEntries.push({
1054
+ kind: "http-error",
1055
+ url: redactBrowserUrl(response.url(), 300),
1056
+ status: response.status(),
1057
+ });
1058
+ }
1059
+ };
1060
+ const canObserve =
1061
+ typeof page.on === "function" && typeof page.off === "function";
1062
+ if (canObserve) {
1063
+ page.on("console", onConsole);
1064
+ page.on("requestfailed", onRequestFailed);
1065
+ page.on("response", onResponse);
1066
+ }
1067
+
1068
+ try {
1069
+ let auditError = null;
1070
+ const authorities = [];
1071
+ for (const act of normalized) {
1072
+ const started = Date.now();
1073
+ const step = { ok: false, action: act.type, detail: "", durationMs: 0 };
1074
+ const pageBefore = redactBrowserUrl(page.url());
1075
+ let authority = null;
1076
+ try {
1077
+ if (evidenceBinding) {
1078
+ authority = authorizeBrowserAction({
1079
+ binding: evidenceBinding,
1080
+ grants: originGrants,
1081
+ expectedGrantRevisions,
1082
+ action: act,
1083
+ currentUrl: page.url(),
1084
+ });
1085
+ }
1086
+ const outcome = await runOneAction(page, act, stepTimeout, {
1087
+ resolveUploadArtifact,
1088
+ });
1089
+ step.detail =
1090
+ act.type === "navigate"
1091
+ ? `navigated to ${redactBrowserUrl(act.url)}`
1092
+ : redactBrowserText(outcome.detail, 500);
1093
+ step.ok = true;
1094
+ if (outcome.uploadArtifact) {
1095
+ step.uploadArtifact = outcome.uploadArtifact;
1096
+ }
1097
+ if (act.screenshotPath) {
1098
+ step.screenshotPath = act.screenshotPath;
1099
+ step.screenshotRef = path.basename(act.screenshotPath);
1100
+ step.screenshotSha256 = digestGeneratedFile(
1101
+ act.screenshotPath,
1102
+ deps,
1103
+ );
1104
+ if (evidenceBinding && !step.screenshotSha256) {
1105
+ throw new Error("browser evidence screenshot digest is missing");
1106
+ }
1107
+ }
1108
+ if (act.downloadPath) {
1109
+ step.downloadPath = act.downloadPath;
1110
+ step.downloadRef = path.basename(act.downloadPath);
1111
+ step.downloadSha256 = digestGeneratedFile(act.downloadPath, deps);
1112
+ if (evidenceBinding && !step.downloadSha256) {
1113
+ throw new Error("browser evidence download digest is missing");
1114
+ }
1115
+ step.downloadSuggestedName =
1116
+ outcome.suggestedName || "download.bin";
1117
+ }
1118
+ } catch (err) {
1119
+ cleanupFailedScreenshot(act.screenshotPath, deps);
1120
+ cleanupFailedScreenshot(act.downloadPath, deps);
1121
+ step.ok = false;
1122
+ delete step.screenshotPath;
1123
+ delete step.screenshotRef;
1124
+ delete step.screenshotSha256;
1125
+ delete step.downloadPath;
1126
+ delete step.downloadRef;
1127
+ delete step.downloadSha256;
1128
+ delete step.downloadSuggestedName;
1129
+ delete step.uploadArtifact;
1130
+ step.detail =
1131
+ act.type === "navigate"
1132
+ ? `navigation failed for ${redactBrowserUrl(act.url, 400)}`
1133
+ : redactScreenshotFailure(
1134
+ err,
1135
+ act.screenshotPath || act.downloadPath,
1136
+ 500,
1137
+ );
1138
+ }
1139
+ step.durationMs = Date.now() - started;
1140
+ const pageAfter = redactBrowserUrl(page.url());
1141
+ result.steps.push(step);
1142
+ authorities.push(authority);
1143
+ try {
1144
+ const entry = {
1145
+ ts: new Date(started).toISOString(),
1146
+ action: act.type,
1147
+ ok: step.ok,
1148
+ durationMs: step.durationMs,
1149
+ pageBefore,
1150
+ pageAfter,
1151
+ result: step.detail,
1152
+ };
1153
+ if (sessionId) {
1154
+ entry.sessionId = redactBrowserText(sessionId, 160);
1155
+ }
1156
+ if (act.selector) entry.selector = truncateForAudit(act.selector);
1157
+ if (act.url) entry.url = redactBrowserUrl(act.url);
1158
+ if (act.key) entry.key = truncateForAudit(act.key, 50);
1159
+ if (act.artifactId) entry.uploadArtifactId = act.artifactId;
1160
+ if (step.ok && act.screenshotPath) {
1161
+ entry.screenshotRef = path.basename(act.screenshotPath);
1162
+ entry.screenshotSha256 = step.screenshotSha256;
1163
+ }
1164
+ if (step.ok && act.downloadPath) {
1165
+ entry.downloadRef = path.basename(act.downloadPath);
1166
+ entry.downloadSha256 = step.downloadSha256;
1167
+ }
1168
+ if (authority) {
1169
+ entry.originGrant = {
1170
+ grantId: authority.grantId,
1171
+ revision: authority.revision,
1172
+ origin: authority.origin,
1173
+ scope: authority.scope,
1174
+ };
1175
+ }
1176
+ appendActionAudit(entry, deps);
1177
+ } catch (err) {
1178
+ auditError = redactBrowserText(err.message, 300);
857
1179
  }
858
- if (act.selector) entry.selector = truncateForAudit(act.selector);
859
- if (act.url) entry.url = redactBrowserUrl(act.url);
860
- if (act.key) entry.key = truncateForAudit(act.key, 50);
861
- if (step.ok && act.screenshotPath) {
862
- entry.screenshotRef = path.basename(act.screenshotPath);
1180
+ if (!step.ok && !continueOnError) break;
1181
+ }
1182
+
1183
+ result.executed = result.steps.length;
1184
+ result.ok =
1185
+ result.steps.length === normalized.length &&
1186
+ result.steps.every((s) => s.ok);
1187
+ if (auditError) result.auditError = auditError;
1188
+ result.url = redactBrowserUrl(page.url());
1189
+ result.title = redactBrowserText(await page.title().catch(() => ""), 500);
1190
+ result.console = consoleEntries;
1191
+ result.network = networkEntries;
1192
+ if (evidenceBinding && result.steps.length > 0) {
1193
+ let domCaptureSucceeded = true;
1194
+ const sourceHtml = await page.content().catch(() => {
1195
+ domCaptureSucceeded = false;
1196
+ return "";
1197
+ });
1198
+ const normalizedCap = Math.min(
1199
+ Math.max(0, Number(evidenceDomCap) || 40000),
1200
+ DEFAULT_DOM_CAP,
1201
+ );
1202
+ const safeHtml = redactBrowserDom(sourceHtml, normalizedCap);
1203
+ const evidenceActions = result.steps.map((step, actionIndex) =>
1204
+ describeBrowserAction(
1205
+ normalized[actionIndex],
1206
+ step,
1207
+ authorities[actionIndex],
1208
+ actionIndex,
1209
+ ),
1210
+ );
1211
+ result.evidence = createBrowserEvidenceEnvelope({
1212
+ binding: evidenceBinding,
1213
+ originPermissions: authorities.filter(Boolean),
1214
+ actions: evidenceActions,
1215
+ consoleEntries,
1216
+ networkEntries,
1217
+ pageUrl: page.url(),
1218
+ pageTitle: result.title,
1219
+ domSnapshot: {
1220
+ html: safeHtml,
1221
+ sourceChars: sourceHtml.length,
1222
+ cap: normalizedCap,
1223
+ truncated: sourceHtml.length > normalizedCap,
1224
+ captureSucceeded: domCaptureSucceeded,
1225
+ redaction: browserDomRedactionMetadata(sourceHtml),
1226
+ },
1227
+ screenshots: result.steps
1228
+ .map((step, actionIndex) => ({
1229
+ actionIndex,
1230
+ digest: step.screenshotSha256,
1231
+ }))
1232
+ .filter((row) => row.digest),
1233
+ downloads: result.steps
1234
+ .map((step, actionIndex) => ({
1235
+ actionIndex,
1236
+ digest: step.downloadSha256,
1237
+ suggestedName: step.downloadSuggestedName,
1238
+ }))
1239
+ .filter((row) => row.digest),
1240
+ replay,
1241
+ observationCaptureAvailable: canObserve,
1242
+ });
1243
+ if (!domCaptureSucceeded || !canObserve) {
1244
+ result.ok = false;
1245
+ result.evidenceIncomplete = true;
1246
+ result.retrySafe = false;
1247
+ result.recovery =
1248
+ "Browser actions may have completed, but observation evidence capture was incomplete; do not retry side effects automatically.";
863
1249
  }
864
- appendActionAudit(entry, deps);
865
- } catch (err) {
866
- auditError = redactBrowserText(err.message, 300);
867
1250
  }
868
- if (!step.ok && !continueOnError) break;
1251
+ return result;
1252
+ } catch (error) {
1253
+ for (const step of result.steps || []) {
1254
+ cleanupFailedScreenshot(step.screenshotPath, deps);
1255
+ cleanupFailedScreenshot(step.downloadPath, deps);
1256
+ }
1257
+ throw error;
1258
+ } finally {
1259
+ if (canObserve) {
1260
+ page.off("console", onConsole);
1261
+ page.off("requestfailed", onRequestFailed);
1262
+ page.off("response", onResponse);
1263
+ }
869
1264
  }
870
-
871
- result.executed = result.steps.length;
872
- result.ok =
873
- result.steps.length === normalized.length &&
874
- result.steps.every((s) => s.ok);
875
- if (auditError) result.auditError = auditError;
876
- result.url = redactBrowserUrl(page.url());
877
- result.title = redactBrowserText(await page.title().catch(() => ""), 500);
878
- return result;
879
1265
  } finally {
880
1266
  // connectOverCDP close() disconnects the client; the browser lives on.
881
1267
  await browser.close().catch(() => {});