chainlesschain 0.162.158 → 0.162.159

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 (201) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-ByIiF5bT.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-NlgSFZq5.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-D17cOUcb.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-B4mEZcbl.js} +4 -4
  6. package/src/assets/web-panel/assets/Artifacts-CASsmyQw.js +1 -0
  7. package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DYAia2e2.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-C1lvYUSu.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-B6agK6bA.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DM43R2vy.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-Dtdp3eXp.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BAV9V-t-.js → ChatBubbleRenderer-BL5c_OQ1.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-D2xf7uZt.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-D3j45jJL.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-voPJ9fUZ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DEsyTESF.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-OHV_QXLD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-eDTJWC-J.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BBvy-6d1.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-Dw2eoJxu.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DQHms_Bh.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-Cyu7Id1c.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-sIDzkG92.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-DUW3T2ey.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-CECELXW8.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-BIZR37ov.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-BEZIQX84.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-BcdUMznL.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DxxEdn2r.js → GenericCardRenderer-DsN_fr5H.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-63LhjBx2.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-CYtQtIcH.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-BwR6UUox.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D9lVNTUA.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-C4q5h7Ut.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-DexLlXfN.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-Plksliuw.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-DZUFfeuG.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-NoDYt29V.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-CEXGTkdh.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CVtTqgtg.js → MobileProjects-DnbJkbZo.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-DTAa0cwy.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-CVV7LnNS.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-BqqGrjt_.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-C4WP8DRp.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Zo4DJUVI.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-DBcjRaz2.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-BT9WPBOX.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-D6b4bZ3q.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-Cw3OsYx-.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C0nyiK2b.js} +5 -5
  52. package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-zVBZEB_C.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-BOspsFew.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-CQWfZ4XH.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-C2oGJ_WL.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-BZMR4qY1.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-DAujoP63.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-COATmHxF.js → Projects-DvH_P2Uq.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-D_RGHM-v.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Mf6KfL2J.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-5D8URoI_.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-Df4kC8Gy.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-DGi3f3vO.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-jmkGFNWb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-DP6Pcu3m.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-D60hSuPO.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-0mf1BQUK.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-CfZtLFYV.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D9zpZLWz.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-D1_44eMD.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-cVFa8zkz.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-Cinpzc3L.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-bqdiJE_P.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-Dgl_SLgj.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-DawWAEhy.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-Ca0nY651.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-CgaGEhLm.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-D-9EnpRx.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-CSOxDHZ6.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-BpwLZpL3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DgowwBK5.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-BZutapjE.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-QZPt8_Sp.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-DlQx5UDJ.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-ZfdTgtWA.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-CAp0y_Cr.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-BVITZEmr.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-CC2tPaNg.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-31brc9CA.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-DLMkC9C-.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-BMtveI6F.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DbD6WHW2.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B30NvR21.js → index-3oBsXZmF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-ALvoNTRF.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-AaIKG60c.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-BFRLmCR3.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-BKAB6-6V.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BOM-VFlv.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-BPpS-lGb.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-Bbc2JeQG.js} +1 -1
  102. package/src/assets/web-panel/assets/index-BhXiaiev.js +1 -0
  103. package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-BiRQUoyE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-BnFLzejT.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-BoO4WEJB.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-C-cGlCDW.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C1PfeI88.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-C21pdBqs.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-C7LHNw_N.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-CEXa-4zd.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CEwjBA9q.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-CFXKbOek.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-CJhREOlG.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-COHHQ9cU.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D23L1V76.js → index-CUmLMFsy.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-CWgfipcC.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-C_eUKGYP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-CcVszn2b.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-Cod97Urs.js} +3 -3
  120. package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-Cwbqh0KZ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-D67kIagl.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-D7cV0tu0.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-DJvxFyIz.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-DZl-UXB6.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-Dm0byjm1.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-DndNnYc0.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-WQUO2vvK.js} +1 -1
  128. package/src/assets/web-panel/assets/index-WpaYaE3D.js +1 -0
  129. package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-bdaJwTXJ.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-ep6JchNG.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BCyP071m.js → index-jG6h22Qg.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-ntVQqSML.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-D5UoBRhN.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-DltOw0CR.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-_oPkk5zj.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-EES8ROCS.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-B-_39x_M.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-l6D0yJHz.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-DIeUK31L.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-dG4EzUQo.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-DWFzYGdH.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-CECdTA5H.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-D6kP-mMK.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-BWfna_rd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-Bb-j6rzo.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-D5OIsYM4.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-DNpwzFTM.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-vNLpFQBW.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-manifest.json +17 -3
  151. package/src/commands/agent.js +7 -0
  152. package/src/commands/background-session.js +143 -3
  153. package/src/commands/browse.js +86 -0
  154. package/src/commands/cloud.js +232 -0
  155. package/src/commands/doctor.js +61 -6
  156. package/src/commands/mcp.js +61 -0
  157. package/src/commands/plugin.js +63 -1
  158. package/src/commands/routine.js +367 -0
  159. package/src/commands/session.js +326 -0
  160. package/src/data/changelog.json +7 -2
  161. package/src/harness/jsonl-session-store.js +82 -2
  162. package/src/harness/session-index.js +295 -0
  163. package/src/harness/session-mirror.js +142 -0
  164. package/src/harness/transcript-integrity.js +182 -0
  165. package/src/index.js +8 -0
  166. package/src/lib/background-agent-supervisor.js +126 -2
  167. package/src/lib/channels/channel-manager.js +126 -0
  168. package/src/lib/channels/telegram-channel.js +112 -0
  169. package/src/lib/channels/webhook-channel.js +140 -0
  170. package/src/lib/chrome-connector.js +386 -0
  171. package/src/lib/cloud/bundle.js +110 -0
  172. package/src/lib/cloud/cloud-client.js +152 -0
  173. package/src/lib/computer-use/authorization.js +173 -0
  174. package/src/lib/computer-use/computer-use-server.js +276 -0
  175. package/src/lib/computer-use/control-backend.js +150 -0
  176. package/src/lib/doctor-checkup.js +652 -0
  177. package/src/lib/jsonl-session-store.js +4 -0
  178. package/src/lib/lsp/lsp-server-registry.js +48 -21
  179. package/src/lib/plugin-runtime/governance.js +174 -0
  180. package/src/lib/pr-link-ledger.js +221 -0
  181. package/src/lib/routine-store.js +352 -0
  182. package/src/lib/session-store-guard.js +40 -0
  183. package/src/lib/session-usage.js +318 -2
  184. package/src/lib/sub-agent-context.js +23 -0
  185. package/src/repl/agent-repl.js +361 -3
  186. package/src/repl/bg-dashboard.js +355 -0
  187. package/src/repl/fast-mode.js +103 -0
  188. package/src/repl/fullscreen-tui.js +197 -0
  189. package/src/repl/slash-command-registry.js +11 -3
  190. package/src/repl/voice-dictation.js +132 -0
  191. package/src/runtime/agent-core.js +242 -1
  192. package/src/runtime/coding-agent-contract-shared.cjs +88 -0
  193. package/src/runtime/coding-agent-policy.cjs +17 -0
  194. package/src/runtime/headless-runner.js +71 -7
  195. package/src/runtime/headless-stream.js +14 -8
  196. package/src/runtime/policies/agent-policy.js +3 -0
  197. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
  198. package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +0 -1
  199. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
  200. package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
  201. package/src/assets/web-panel/assets/index-TMM9FuW-.js +0 -1
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Voice dictation for the agent REPL (gap-2026-07-11 P2#11).
3
+ *
4
+ * Claude Code's `/voice` dictates a prompt by speech. cc generalizes it to a
5
+ * pluggable STT backend with a strict local-first priority so nothing leaves
6
+ * the box unless the user opts into cloud transcription:
7
+ *
8
+ * local Whisper > system STT (OS dictation) > cloud (opt-in)
9
+ *
10
+ * Two capture modes mirror the upstream UX:
11
+ * - hold : push-to-talk — record while a key is held, transcribe on release.
12
+ * - tap : toggle — tap to start, tap again to stop.
13
+ *
14
+ * Real audio capture is a host binding (a Whisper binary, an OS dictation API);
15
+ * it is injected, so on a host with no backend — or a headless/SSH session where
16
+ * there is no microphone — `/voice` degrades to a clear message instead of
17
+ * pretending to listen. This module is the pure decision core (mode/command
18
+ * parse, environment degradation, backend priority, status render).
19
+ */
20
+
21
+ export const VOICE_MODES = Object.freeze(["off", "hold", "tap"]);
22
+ export const DEFAULT_VOICE_MODE = "off";
23
+
24
+ /**
25
+ * STT backends in priority order. `local` never sends audio off the box;
26
+ * `cloud` requires explicit opt-in (config voice.allowCloud). Each entry names
27
+ * the capability probe the host binding fills in.
28
+ */
29
+ export const STT_BACKENDS = Object.freeze([
30
+ { id: "whisper-local", kind: "local", label: "local Whisper" },
31
+ { id: "system-stt", kind: "system", label: "system STT" },
32
+ { id: "cloud", kind: "cloud", label: "cloud transcription" },
33
+ ]);
34
+
35
+ /**
36
+ * Parse `/voice [hold|tap|off|status]`. Returns null for non-/voice input,
37
+ * `{ error }` for a bad arg, else `{ action }`. Bare `/voice` shows status.
38
+ * Pure.
39
+ */
40
+ export function parseVoiceCommand(input) {
41
+ const t = String(input || "").trim();
42
+ if (t !== "/voice" && !t.startsWith("/voice ")) return null;
43
+ const arg = t.slice("/voice".length).trim().toLowerCase();
44
+ if (!arg || arg === "status") return { action: "status" };
45
+ if (arg === "hold" || arg === "ptt" || arg === "push")
46
+ return { action: "hold" };
47
+ if (arg === "tap" || arg === "toggle") return { action: "tap" };
48
+ if (arg === "off" || arg === "stop" || arg === "0") return { action: "off" };
49
+ return {
50
+ error: `Unknown /voice option "${arg}". Use: hold | tap | off | status`,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Decide whether voice capture is usable in this environment. Headless / SSH /
56
+ * CI sessions have no local microphone, so dictation degrades. Pure over the
57
+ * passed env + tty flags.
58
+ * @returns {{ supported, reason }}
59
+ */
60
+ export function detectVoiceEnvironment({ env = {}, isTTY = true } = {}) {
61
+ if (!isTTY) {
62
+ return {
63
+ supported: false,
64
+ reason: "not a TTY — no interactive microphone",
65
+ };
66
+ }
67
+ if (env.SSH_TTY || env.SSH_CONNECTION) {
68
+ return {
69
+ supported: false,
70
+ reason:
71
+ "remote SSH session — capture audio locally and paste, or use a cloud STT relay",
72
+ };
73
+ }
74
+ if (isTruthy(env.CI)) {
75
+ return { supported: false, reason: "CI environment — no microphone" };
76
+ }
77
+ if (isTruthy(env.CC_NO_VOICE)) {
78
+ return { supported: false, reason: "disabled via CC_NO_VOICE" };
79
+ }
80
+ return { supported: true, reason: "" };
81
+ }
82
+
83
+ /**
84
+ * Pick the highest-priority available STT backend. `available` is a map of
85
+ * backend id → boolean from the host capability probe. Cloud is only eligible
86
+ * when `allowCloud` is true (config voice.allowCloud). Returns
87
+ * `{ backend, reason }` or `{ backend: null, reason }`. Pure.
88
+ */
89
+ export function resolveSttBackend({
90
+ available = {},
91
+ allowCloud = false,
92
+ prefer = null,
93
+ } = {}) {
94
+ const eligible = STT_BACKENDS.filter((b) => {
95
+ if (!available[b.id]) return false;
96
+ if (b.kind === "cloud" && !allowCloud) return false;
97
+ return true;
98
+ });
99
+ if (eligible.length === 0) {
100
+ return {
101
+ backend: null,
102
+ reason:
103
+ "no STT backend available — install a local Whisper build or enable system dictation (voice.allowCloud for cloud)",
104
+ };
105
+ }
106
+ // Honor an explicit preference if it's eligible; else take the top priority.
107
+ const chosen =
108
+ (prefer && eligible.find((b) => b.id === prefer)) || eligible[0];
109
+ return { backend: chosen, reason: `using ${chosen.label}` };
110
+ }
111
+
112
+ /** One-line `/voice status`. Pure. */
113
+ export function renderVoiceStatus({ mode = "off", backend = null, env } = {}) {
114
+ const m = VOICE_MODES.includes(mode) ? mode : "off";
115
+ if (env && !env.supported) {
116
+ return `Voice: unavailable — ${env.reason}.`;
117
+ }
118
+ if (m === "off") {
119
+ return "Voice: off. /voice hold (push-to-talk) or /voice tap (toggle).";
120
+ }
121
+ const be = backend ? backend.label : "no backend";
122
+ const how =
123
+ m === "hold" ? "hold the dictation key to talk" : "tap to start/stop";
124
+ return `Voice: ${m} — ${how}; ${be}.`;
125
+ }
126
+
127
+ function isTruthy(v) {
128
+ const n = String(v == null ? "" : v)
129
+ .toLowerCase()
130
+ .trim();
131
+ return n === "1" || n === "true" || n === "yes" || n === "on";
132
+ }
@@ -1291,6 +1291,38 @@ export async function executeTool(name, args, context = {}) {
1291
1291
  }
1292
1292
  }
1293
1293
 
1294
+ // Session-store write guard (transcript tamper protection): JSONL
1295
+ // transcripts under ~/.chainlesschain/sessions are hash-chained and treated
1296
+ // as an audit surface — an agent editing a transcript rewrites the history
1297
+ // that `cc session verify` and resume trust. Same posture as the
1298
+ // sensitive-file guard: confirm-first, an explicit settings `allow` rule is
1299
+ // the only bypass, headless without a confirmer fails closed.
1300
+ if (
1301
+ (name === "write_file" ||
1302
+ name === "edit_file" ||
1303
+ name === "notebook_edit") &&
1304
+ settingsVerdict.decision !== "allow" &&
1305
+ args?.path
1306
+ ) {
1307
+ const { sessionStorePathReason } =
1308
+ await import("../lib/session-store-guard.js");
1309
+ const storeReason = sessionStorePathReason(args.path, { cwd });
1310
+ if (storeReason) {
1311
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
1312
+ tool: name,
1313
+ args,
1314
+ rule: null,
1315
+ reason: `session store: ${storeReason}`,
1316
+ });
1317
+ if (!ok) {
1318
+ return {
1319
+ error: `[Session Store] Writing "${args.path}" would modify a session transcript (${storeReason}) — denied. Add a settings allow rule to pre-authorize.`,
1320
+ policy: { decision: "ask", via: "session-store-guard" },
1321
+ };
1322
+ }
1323
+ }
1324
+ }
1325
+
1294
1326
  // Destructive-git guard (Claude-Code 2.1.183 parity: "destructive git
1295
1327
  // commands blocked when unintended"). The `git` tool otherwise runs any
1296
1328
  // command unguarded in auto mode — including `reset --hard`, `clean -fd`,
@@ -1438,6 +1470,14 @@ export async function executeTool(name, args, context = {}) {
1438
1470
  parentMessages: context.parentMessages,
1439
1471
  interaction: context.interaction,
1440
1472
  sessionId: context.sessionId || null,
1473
+ // Parent LLM config — documented at toolContext as forwarded to
1474
+ // spawn_sub_agent for provider/key inheritance; without this line it
1475
+ // never actually reached executeToolInner (sub-agents silently fell
1476
+ // back to config defaults and their usage was mis-attributed).
1477
+ llmOptions: context.llmOptions || null,
1478
+ // 用量归因: shared per-run sink for child-loop (sub-agent / isolated
1479
+ // skill) token usage, drained by agentLoop as attributed events.
1480
+ subAgentUsageSink: context.subAgentUsageSink || null,
1441
1481
  hostManagedToolPolicy: context.hostManagedToolPolicy || null,
1442
1482
  externalToolDescriptors: context.externalToolDescriptors || null,
1443
1483
  externalToolExecutors: context.externalToolExecutors || null,
@@ -2074,6 +2114,7 @@ async function executeToolInner(
2074
2114
  settingsHooks = null,
2075
2115
  signal = null,
2076
2116
  backgroundSubAgents = null,
2117
+ subAgentUsageSink = null,
2077
2118
  },
2078
2119
  ) {
2079
2120
  const localToolDescriptor =
@@ -2706,6 +2747,21 @@ async function executeToolInner(
2706
2747
  }
2707
2748
  output = res.stdout ?? "";
2708
2749
  }
2750
+ // PR/session linking (gap-2026-07-11 P1#9): a successful `gh pr …`
2751
+ // (or `git push`) ties this session to the PR it touched. Async and
2752
+ // best-effort — never delays or fails the tool result.
2753
+ if (sessionId) {
2754
+ import("../lib/pr-link-ledger.js")
2755
+ .then((m) =>
2756
+ m.recordFromShellCommand({
2757
+ sessionId,
2758
+ command: args.command,
2759
+ output: String(output || ""),
2760
+ cwd: args.cwd || cwd,
2761
+ }),
2762
+ )
2763
+ .catch(() => {});
2764
+ }
2709
2765
  return attachDescriptor(
2710
2766
  {
2711
2767
  stdout: output.substring(0, 30000),
@@ -2883,6 +2939,7 @@ async function executeToolInner(
2883
2939
  settingsHooks,
2884
2940
  signal,
2885
2941
  backgroundSubAgents,
2942
+ subAgentUsageSink,
2886
2943
  }),
2887
2944
  );
2888
2945
  }
@@ -3221,6 +3278,77 @@ async function executeToolInner(
3221
3278
  }
3222
3279
  }
3223
3280
 
3281
+ case "browser_act": {
3282
+ // Gap-analysis #6: the ACTION side of the Chrome connector.
3283
+ // browser_state stays the read-only default; browser_act explicitly
3284
+ // drives the user's logged-in browser (click/type/press/navigate/
3285
+ // waitForSelector/screenshot/assertText) and is gated like run_code:
3286
+ // HIGH risk through the ApprovalGate (CONFIRM even on the trusted/auto
3287
+ // tier), pre-authorizable only by an explicit settings allow rule
3288
+ // (ruleAllowed). Unlike run_code there is no legacy ungated behavior to
3289
+ // preserve, so the gate applies whenever an ApprovalGate is wired —
3290
+ // headless without a confirmer fails closed. Screenshot paths are
3291
+ // generated inside performActions (never agent-chosen), and each
3292
+ // executed step is audit-logged to ~/.chainlesschain/browser-actions/.
3293
+ try {
3294
+ if (
3295
+ approvalGate &&
3296
+ !ruleAllowed &&
3297
+ typeof approvalGate.decide === "function"
3298
+ ) {
3299
+ const { APPROVAL_RISK, APPROVAL_DECISION } =
3300
+ await import("@chainlesschain/session-core");
3301
+ const summary = Array.isArray(args.actions)
3302
+ ? args.actions
3303
+ .map((a) => a?.type)
3304
+ .filter(Boolean)
3305
+ .slice(0, 10)
3306
+ .join(",")
3307
+ : "";
3308
+ const gate = await approvalGate.decide({
3309
+ sessionId,
3310
+ riskLevel: APPROVAL_RISK.HIGH,
3311
+ tool: "browser_act",
3312
+ args: { actions: summary },
3313
+ });
3314
+ if (gate.decision !== APPROVAL_DECISION.ALLOW) {
3315
+ const tierLabel =
3316
+ typeof gate.policy === "string" ? `"${gate.policy}" ` : "";
3317
+ return attachDescriptor({
3318
+ error: `[ApprovalGate] browser_act denied by the ${tierLabel}approval policy (via ${gate.via}). Retrying won't help — driving the user's browser needs their approval. Tell the user (they can approve it or relax the policy) and continue with other work.`,
3319
+ approval: {
3320
+ decision: gate.decision,
3321
+ via: gate.via,
3322
+ riskLevel: "high",
3323
+ policy: gate.policy,
3324
+ },
3325
+ });
3326
+ }
3327
+ }
3328
+ const { performActions } = await import("../lib/chrome-connector.js");
3329
+ const result = await performActions(args.actions, {
3330
+ port: args.port != null ? Number(args.port) : undefined,
3331
+ cdpUrl: args.cdp_url != null ? String(args.cdp_url) : null,
3332
+ tab: args.tab != null ? Number(args.tab) : undefined,
3333
+ continueOnError: args.continue_on_error === true,
3334
+ sessionId: sessionId ? String(sessionId) : null,
3335
+ });
3336
+ if (!result.ok && result.error) {
3337
+ // Nothing ran (validation / attach failure) — surface as an error.
3338
+ // A step-level failure returns the per-step outcomes instead so the
3339
+ // model can see exactly which step broke.
3340
+ return attachDescriptor({
3341
+ error: `browser_act failed: ${result.error}`,
3342
+ });
3343
+ }
3344
+ return attachDescriptor(result);
3345
+ } catch (err) {
3346
+ return attachDescriptor({
3347
+ error: `browser_act failed: ${err.message}`,
3348
+ });
3349
+ }
3350
+ }
3351
+
3224
3352
  case "search_files": {
3225
3353
  // An explicit directory scopes the search to one root; otherwise span
3226
3354
  // cwd plus any --add-dir roots so cross-package searches find matches.
@@ -3429,13 +3557,46 @@ async function executeToolInner(
3429
3557
  // Check if skill requests isolation (via SKILL.md frontmatter)
3430
3558
  const skillIsolation = match.isolation === true;
3431
3559
  if (skillIsolation) {
3560
+ // 用量归因: an isolated skill runs as a child loop whose real token
3561
+ // usage would otherwise be invisible — forward it into the parent
3562
+ // run's sink tagged origin:"skill" so `cc session usage --by skill`
3563
+ // can break it out. A nested frame passes through unchanged.
3564
+ const skillUsageSink = Array.isArray(subAgentUsageSink)
3565
+ ? subAgentUsageSink
3566
+ : null;
3567
+ let skillSubRef = null;
3432
3568
  // Run skill through isolated sub-agent context
3433
3569
  const subCtx = SubAgentContext.create({
3434
3570
  role: `skill-${args.skill_name}`,
3435
3571
  task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
3436
3572
  allowedTools: ["read_file", "search_files", "list_dir"],
3437
3573
  cwd,
3574
+ onUsage: skillUsageSink
3575
+ ? (u) => {
3576
+ try {
3577
+ skillUsageSink.push(
3578
+ u && u.attribution
3579
+ ? u
3580
+ : {
3581
+ provider: u?.provider ?? null,
3582
+ model: u?.model ?? null,
3583
+ usage: u?.usage || null,
3584
+ attribution: {
3585
+ origin: "skill",
3586
+ skill: args.skill_name,
3587
+ subagentId: skillSubRef?.id || null,
3588
+ parentSessionId: sessionId || null,
3589
+ depth: (subAgentDepth || 0) + 1,
3590
+ },
3591
+ },
3592
+ );
3593
+ } catch (_e) {
3594
+ // usage forwarding is best-effort
3595
+ }
3596
+ }
3597
+ : null,
3438
3598
  });
3599
+ skillSubRef = subCtx;
3439
3600
  try {
3440
3601
  const result = await subCtx.run(args.input);
3441
3602
  return attachDescriptor({
@@ -4020,6 +4181,27 @@ function _backgroundSubAgentResultText(entry) {
4020
4181
  );
4021
4182
  }
4022
4183
 
4184
+ /**
4185
+ * Drain the run's attributed child-loop usage sink (spawn_sub_agent /
4186
+ * isolated run_skill — see toolContext.subAgentUsageSink), re-yielding each
4187
+ * record as a regular `token-usage` event that carries its `attribution`
4188
+ * frame. Consumers that ignore `attribution` see ordinary usage events;
4189
+ * attribution-aware consumers (REPL persistence, headless runner) can split
4190
+ * child spend from the main conversation's.
4191
+ */
4192
+ function* _drainSubAgentUsage(sink) {
4193
+ while (Array.isArray(sink) && sink.length > 0) {
4194
+ const u = sink.shift();
4195
+ yield {
4196
+ type: "token-usage",
4197
+ provider: u?.provider ?? null,
4198
+ model: u?.model ?? null,
4199
+ usage: u?.usage || {},
4200
+ attribution: u?.attribution || null,
4201
+ };
4202
+ }
4203
+ }
4204
+
4023
4205
  async function _executeSpawnSubAgent(args, ctx) {
4024
4206
  // Nesting cap: refuse before any context/registry work.
4025
4207
  const currentDepth = ctx.subAgentDepth || 0;
@@ -4169,6 +4351,40 @@ async function _executeSpawnSubAgent(args, ctx) {
4169
4351
  model: mdModel || parentLlm.model || undefined,
4170
4352
  };
4171
4353
 
4354
+ // 用量归因: forward the child's real token usage into the parent run's sink
4355
+ // (threaded through toolContext) so agentLoop re-yields it as attributed
4356
+ // `token-usage` events. A nested child's already-attributed record passes
4357
+ // through unchanged (deepest frame wins). subCtxRef closes over the created
4358
+ // context so the frame can carry the sub-agent's id.
4359
+ const usageSink = Array.isArray(ctx.subAgentUsageSink)
4360
+ ? ctx.subAgentUsageSink
4361
+ : null;
4362
+ let subCtxRef = null;
4363
+ const onUsage = usageSink
4364
+ ? (u) => {
4365
+ try {
4366
+ usageSink.push(
4367
+ u && u.attribution
4368
+ ? u
4369
+ : {
4370
+ provider: u?.provider ?? null,
4371
+ model: u?.model ?? null,
4372
+ usage: u?.usage || null,
4373
+ attribution: {
4374
+ origin: "subagent",
4375
+ subagentId: subCtxRef?.id || null,
4376
+ role: subCtxRef?.role || role || null,
4377
+ parentSessionId,
4378
+ depth: currentDepth + 1,
4379
+ },
4380
+ },
4381
+ );
4382
+ } catch (_e) {
4383
+ // usage forwarding is best-effort
4384
+ }
4385
+ }
4386
+ : null;
4387
+
4172
4388
  const subCtx = SubAgentContext.create({
4173
4389
  role,
4174
4390
  task,
@@ -4182,7 +4398,9 @@ async function _executeSpawnSubAgent(args, ctx) {
4182
4398
  // Same shared counter object so the child's own spawns draw from the run's
4183
4399
  // single total-sub-agent pool (breadth cap spans the whole tree).
4184
4400
  subAgentBudget: ctx.subAgentBudget || null,
4401
+ onUsage,
4185
4402
  });
4403
+ subCtxRef = subCtx;
4186
4404
 
4187
4405
  const emit = (type, payload) => {
4188
4406
  if (!interaction || typeof interaction.emit !== "function") return;
@@ -5793,8 +6011,18 @@ export async function* agentLoop(messages, options) {
5793
6011
  // any are still running (it waits, injects, and gives the model one more
5794
6012
  // turn) so a background result can never be silently lost.
5795
6013
  backgroundSubAgents: new Map(),
6014
+ // 用量归因: per-run sink for child-loop (spawn_sub_agent / isolated
6015
+ // run_skill) token usage. Child loops consume their own generator events,
6016
+ // so their real usage never reaches this loop's consumers — the spawn
6017
+ // wiring pushes it here and the loop drains it at iteration boundaries as
6018
+ // `token-usage` events carrying an `attribution` frame. Callers may pass
6019
+ // their own array to observe it directly.
6020
+ subAgentUsageSink: Array.isArray(options.subAgentUsageSink)
6021
+ ? options.subAgentUsageSink
6022
+ : [],
5796
6023
  };
5797
6024
  const backgroundSubAgents = toolContext.backgroundSubAgents;
6025
+ const subAgentUsageSink = toolContext.subAgentUsageSink;
5798
6026
 
5799
6027
  throwIfAborted(signal);
5800
6028
 
@@ -5930,6 +6158,10 @@ export async function* agentLoop(messages, options) {
5930
6158
  budget.consume();
5931
6159
  throwIfAborted(signal);
5932
6160
 
6161
+ // Surface attributed child-loop usage collected since the last boundary
6162
+ // (blocking spawns push during executeTool; background spawns push live).
6163
+ yield* _drainSubAgentUsage(subAgentUsageSink);
6164
+
5933
6165
  // Emit progressive warnings (once per level)
5934
6166
  const level = budget.warningLevel();
5935
6167
  if (
@@ -6504,7 +6736,9 @@ export async function* agentLoop(messages, options) {
6504
6736
  }
6505
6737
  }
6506
6738
 
6507
- // Budget exhausted — yield exhaustion event + final message
6739
+ // Budget exhausted — flush any child usage the final iteration produced,
6740
+ // then yield exhaustion event + final message
6741
+ yield* _drainSubAgentUsage(subAgentUsageSink);
6508
6742
  yield { type: "iteration-budget-exhausted", budget: budget.toSummary() };
6509
6743
  yield {
6510
6744
  type: "response-complete",
@@ -6559,6 +6793,13 @@ export function formatToolArgs(name, args) {
6559
6793
  return `${args.kind || "other"}: ${(args.title || args.path || "").substring(0, 60)}`;
6560
6794
  case "browser_state":
6561
6795
  return `tab=${args.tab ?? 0} port=${args.port ?? 9222}${args.reload ? " reload" : ""}`;
6796
+ case "browser_act": {
6797
+ const kinds = Array.isArray(args.actions)
6798
+ ? args.actions.map((a) => a?.type).filter(Boolean)
6799
+ : [];
6800
+ const head = kinds.slice(0, 4).join(",");
6801
+ return `${kinds.length} action(s)${head ? `: ${head}` : ""}${kinds.length > 4 ? ",…" : ""} tab=${args.tab ?? 0} port=${args.port ?? 9222}`;
6802
+ }
6562
6803
  case "schedule":
6563
6804
  return args.action === "cron"
6564
6805
  ? `cron ${args.cron || ""}`.trim()
@@ -1058,6 +1058,94 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
1058
1058
  tags: ["tool:browser_state", "contract:coding-agent", "tier:extension"],
1059
1059
  },
1060
1060
  },
1061
+ {
1062
+ name: "browser_act",
1063
+ title: "Browser Actions",
1064
+ kind: "browser",
1065
+ tier: "extension",
1066
+ description:
1067
+ "Perform EXPLICIT actions in the user's connected Chrome over loopback CDP: click, type, press, navigate (http/https only), waitForSelector, screenshot (saved to a generated temp path — never a chosen one), assertText. Approval-gated (HIGH risk): use browser_state first to observe; only act when the task genuinely requires driving the browser. Steps run in order and fail fast unless continue_on_error; each returns {ok, action, detail, durationMs} and every executed step is audit-logged under ~/.chainlesschain/browser-actions/. Requires a debuggable Chrome (`cc browse chrome launch`).",
1068
+ inputSchema: {
1069
+ type: "object",
1070
+ properties: {
1071
+ actions: {
1072
+ type: "array",
1073
+ description:
1074
+ "Ordered action steps. Each item: {type: click|type|press|navigate|waitForSelector|screenshot|assertText} plus its fields — click/waitForSelector/assertText need selector; type needs selector+text; press needs key; navigate needs url (http/https); waitForSelector accepts timeout_ms (capped 30000); assertText needs expected (substring); screenshot takes NO path (generated internally).",
1075
+ items: {
1076
+ type: "object",
1077
+ properties: {
1078
+ type: {
1079
+ type: "string",
1080
+ enum: [
1081
+ "click",
1082
+ "type",
1083
+ "press",
1084
+ "navigate",
1085
+ "waitForSelector",
1086
+ "screenshot",
1087
+ "assertText",
1088
+ ],
1089
+ description: "Action kind",
1090
+ },
1091
+ selector: {
1092
+ type: "string",
1093
+ description:
1094
+ "CSS selector (click/type/waitForSelector/assertText)",
1095
+ },
1096
+ text: { type: "string", description: "Text to type (type)" },
1097
+ key: {
1098
+ type: "string",
1099
+ description: "Key to press, e.g. Enter (press)",
1100
+ },
1101
+ url: {
1102
+ type: "string",
1103
+ description: "http(s):// URL to open (navigate)",
1104
+ },
1105
+ timeout_ms: {
1106
+ type: "number",
1107
+ description:
1108
+ "waitForSelector timeout in ms (default 5000, max 30000)",
1109
+ },
1110
+ expected: {
1111
+ type: "string",
1112
+ description: "Expected substring (assertText)",
1113
+ },
1114
+ },
1115
+ required: ["type"],
1116
+ },
1117
+ },
1118
+ cdp_url: {
1119
+ type: "string",
1120
+ description:
1121
+ "CDP endpoint — loopback-only, e.g. http://127.0.0.1:9222 (default port 9222)",
1122
+ },
1123
+ port: {
1124
+ type: "number",
1125
+ description: "CDP port of the debuggable Chrome (default 9222)",
1126
+ },
1127
+ tab: {
1128
+ type: "number",
1129
+ description: "Tab index to act in (default 0)",
1130
+ },
1131
+ continue_on_error: {
1132
+ type: "boolean",
1133
+ description:
1134
+ "Keep executing remaining steps after a failed one (default false: fail fast)",
1135
+ },
1136
+ },
1137
+ required: ["actions"],
1138
+ },
1139
+ ...TOOL_POLICY_METADATA.browser_act,
1140
+ permissions: {
1141
+ level: "elevated",
1142
+ scopes: ["browser:act"],
1143
+ },
1144
+ telemetry: {
1145
+ category: "browser",
1146
+ tags: ["tool:browser_act", "contract:coding-agent", "tier:extension"],
1147
+ },
1148
+ },
1061
1149
  ]);
1062
1150
 
1063
1151
  const CODING_AGENT_MVP_TOOL_NAMES = Object.freeze(
@@ -320,6 +320,23 @@ const TOOL_POLICY_METADATA = Object.freeze({
320
320
  approvalFlow: "auto",
321
321
  isReadOnly: true,
322
322
  },
323
+ // DRIVES the user's connected Chrome over loopback CDP — clicks, typing,
324
+ // key presses, navigation, screenshots, text assertions. Unlike
325
+ // browser_state (pure observation, the default) this mutates the page and
326
+ // acts inside whatever session that browser is logged into, so it carries
327
+ // the same gating as run_shell/run_code: HIGH risk (the ApprovalGate
328
+ // CONFIRMs HIGH even on the trusted/auto tier, so auto mode still prompts),
329
+ // blocked in plan mode, plan approval + explicit confirmation required.
330
+ browser_act: {
331
+ riskLevel: RISK_LEVELS.HIGH,
332
+ category: TOOL_CATEGORIES.EXECUTE,
333
+ availableInPlanMode: false,
334
+ planModeBehavior: "blocked",
335
+ requiresPlanApproval: true,
336
+ requiresConfirmation: true,
337
+ approvalFlow: "policy",
338
+ isReadOnly: false,
339
+ },
323
340
  });
324
341
 
325
342
  function normalizeGitCommand(command) {