chainlesschain 0.162.158 → 0.162.160

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 (219) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-CkRHjW8e.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-BP3MeNi2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-CjbjQsOr.js} +2 -2
  6. package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-i0adH1M8.js} +5 -5
  7. package/src/assets/web-panel/assets/Artifacts-CCCG2GOU.js +1 -0
  8. package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DrsgvwHC.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-DkXNkbZM.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-DarTWCex.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DaDzhcto.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-C4EzGw6V.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-lkavIWMS.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-gddNgaQi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-BKldFWf9.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-ChR-brDs.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DlHtZX86.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-BAhfcBpC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-gIrKkm6m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BYnzLBO3.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-ByUDIljI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DOdNbHFp.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-BJHaNWD2.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-Drm_tClz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-CA1WLzGN.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-Di1SKjNO.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-CubPwv_N.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-CsUFe5bU.js} +1 -1
  30. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +1 -0
  31. package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-D-Qx9dhO.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-_YMYaU4n.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-J7Yzh-K_.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-oMTSYfGY.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-DKUqYDaF.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-BEv3Gx61.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-CtA8XD2y.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-ug7c1mMj.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-WJaMs4i2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-DXnK_-ru.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-C4Ev7726.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-BMFb9_zk.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Bl93nvBX.js} +4 -4
  47. package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-BN2W5cl6.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-h1A0XB2a.js → OrderTableRenderer-Dxc9oN4o.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-CAs48637.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-Bhq27Zoc.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-D61-2542.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C1j4iUxz.js} +5 -5
  53. package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-rJ2-fScM.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-DFIlTok6.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-BjIgEWoZ.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-BQoH5wcX.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-CYR_-_Ub.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-nkatCq-9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-BnKM9fsF.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Cfe82dbz.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-r1RbPufV.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-CjCbM2WE.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-BuMGPH6V.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-Bmj3NY1F.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-BIsNcE_1.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-BObmojD2.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-BRmbkQNS.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-_rF3DRpY.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D0If9ZV4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-o7zIksUg.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-oUR7zi9u.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-fqKyGi03.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-B1wZ_gxx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-B3qJWYXq.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-BH5_dUfr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-CCIVpjRP.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-2GZtTGnB.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-BlMkfs_u.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-U6hi5OMc.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-C-_BerGJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DaKzBgN3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-CLvcsco3.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-BFn_Y1zZ.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-B0V18dLT.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-BbmP7Imu.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-Dewj_0kt.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-B1OIpZnh.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-CSGNVdri.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-8S0vcg0u.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-vZyc27TN.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DWw3-UhA.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-B4pP2ycr.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-BDHyovnK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-BKEmQQvR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BQssf0EQ.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-BZnauCqN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-BdyokEau.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-BeBv896m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-BjC-TrOb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-BkWd6YL7.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-BoIPHP4R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-BuB5P0TR.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-BwKrTVV0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C6r27A-o.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-CB2MOS_8.js} +3 -3
  109. package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CFQWZvak.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-CI3slBKI.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCyP071m.js → index-CJgKP2cm.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-CLPr2vC_.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-CbZqw7pg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-CfEvacz7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-CwXBblm-.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-D1i_8oop.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-D8qXVfoV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-DKwl1AkQ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D23L1V76.js → index-DLAvUtOH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-DLgkAhEE.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-DPgY5GLy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-Dc40pX3F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-DcfLXAW7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-DeCS12Mh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B30NvR21.js → index-Dg0Izipr.js} +1 -1
  126. package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
  127. package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-DtHWHJnj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-Q6oyLnKC.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-dxocDVD_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-t-q3rkVm.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-tr1x96W9.js} +1 -1
  132. package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
  133. package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-xx5M5Rbj.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-Di5YFId0.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-BVSN0aeL.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-DHNiaP2l.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-xJPf3gQD.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-C7qzUdkN.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-pSpjg3Le.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-cs_jL_UC.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-DkS_Zs4r.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-B5O1Kpxo.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-BX02GnZp.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-EmBYRftd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-CfP7M1m7.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-7xh0If_O.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-DmitvTR2.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-manifest.json +17 -3
  152. package/src/commands/agent.js +72 -6
  153. package/src/commands/background-session.js +173 -3
  154. package/src/commands/browse.js +86 -0
  155. package/src/commands/cloud.js +232 -0
  156. package/src/commands/doctor.js +61 -6
  157. package/src/commands/mcp.js +107 -0
  158. package/src/commands/plugin.js +63 -1
  159. package/src/commands/routine.js +367 -0
  160. package/src/commands/session.js +396 -0
  161. package/src/data/changelog.json +12 -2
  162. package/src/harness/jsonl-session-store.js +152 -3
  163. package/src/harness/mcp-client.js +50 -0
  164. package/src/harness/session-index.js +295 -0
  165. package/src/harness/session-mirror.js +142 -0
  166. package/src/harness/transcript-integrity.js +182 -0
  167. package/src/index.js +8 -0
  168. package/src/lib/agent-sandbox.js +68 -0
  169. package/src/lib/agents.js +12 -4
  170. package/src/lib/api-key-helper.js +62 -0
  171. package/src/lib/background-agent-supervisor.js +158 -2
  172. package/src/lib/channels/channel-manager.js +126 -0
  173. package/src/lib/channels/telegram-channel.js +112 -0
  174. package/src/lib/channels/webhook-channel.js +140 -0
  175. package/src/lib/chrome-connector.js +386 -0
  176. package/src/lib/cloud/bundle.js +110 -0
  177. package/src/lib/cloud/cloud-client.js +152 -0
  178. package/src/lib/computer-use/authorization.js +173 -0
  179. package/src/lib/computer-use/computer-use-server.js +276 -0
  180. package/src/lib/computer-use/control-backend.js +150 -0
  181. package/src/lib/dependency-install-policy.js +130 -0
  182. package/src/lib/doctor-checkup.js +652 -0
  183. package/src/lib/exit-codes.cjs +77 -0
  184. package/src/lib/headless-manifest.js +175 -0
  185. package/src/lib/hook-runner.cjs +70 -4
  186. package/src/lib/ide-context-redaction.js +130 -0
  187. package/src/lib/ide-context.js +89 -13
  188. package/src/lib/jsonl-session-store.js +6 -0
  189. package/src/lib/llm-config-defaults.js +24 -8
  190. package/src/lib/lsp/lsp-server-registry.js +48 -21
  191. package/src/lib/plugin-runtime/governance.js +174 -0
  192. package/src/lib/pr-link-ledger.js +221 -0
  193. package/src/lib/project-mcp-trust.js +87 -0
  194. package/src/lib/routine-store.js +352 -0
  195. package/src/lib/sensitive-file-guard.js +51 -0
  196. package/src/lib/session-store-guard.js +40 -0
  197. package/src/lib/session-usage.js +318 -2
  198. package/src/lib/sub-agent-context.js +30 -3
  199. package/src/repl/agent-repl.js +361 -3
  200. package/src/repl/bg-dashboard.js +355 -0
  201. package/src/repl/fast-mode.js +103 -0
  202. package/src/repl/fullscreen-tui.js +197 -0
  203. package/src/repl/slash-command-registry.js +11 -3
  204. package/src/repl/voice-dictation.js +132 -0
  205. package/src/runtime/agent-core.js +344 -7
  206. package/src/runtime/coding-agent-contract-shared.cjs +107 -2
  207. package/src/runtime/coding-agent-policy.cjs +17 -0
  208. package/src/runtime/headless-runner.js +137 -14
  209. package/src/runtime/headless-stream.js +43 -9
  210. package/src/runtime/mcp-config.js +32 -1
  211. package/src/runtime/policies/agent-policy.js +3 -0
  212. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
  213. package/src/assets/web-panel/assets/ChatBubbleRenderer-BAV9V-t-.js +0 -1
  214. package/src/assets/web-panel/assets/GenericCardRenderer-DxxEdn2r.js +0 -1
  215. package/src/assets/web-panel/assets/MobileProjects-CVtTqgtg.js +0 -1
  216. package/src/assets/web-panel/assets/Projects-COATmHxF.js +0 -1
  217. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
  218. package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
  219. 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
+ }
@@ -673,8 +673,8 @@ Key behaviors:
673
673
  When the user's problem involves data processing, calculations, file operations, text parsing, API calls, web scraping, or any task that can be solved programmatically:
674
674
  - Proactively write and execute code using run_code tool
675
675
  - Choose the best language: Python for data/math/scraping, Node.js for JSON/API, Bash for system tasks
676
- - Missing Python packages are auto-installed via pip when import errors are detected
677
- - Scripts are persisted in .chainlesschain/agent-scripts/ for reference
676
+ - Missing Python packages are NOT auto-installed by default; the tool result tells you (and the user) how to opt in (settings runCode.autoInstall)
677
+ - Scripts run from a temp file by default; pass persist:true to keep one in .chainlesschain/agent-scripts/ for reference
678
678
  - Show the results and explain them clearly
679
679
  - If the first attempt fails, debug and retry with a different approach
680
680
 
@@ -1263,6 +1263,8 @@ export async function executeTool(name, args, context = {}) {
1263
1263
  // Sensitive-file write guard (Claude-Code 2.1.160 parity): shell startup
1264
1264
  // files / PowerShell profiles / git+husky hooks execute code on the user's
1265
1265
  // next shell or commit — even otherwise-permitted edit flows confirm first.
1266
+ // Auto-exec configs (.vscode/tasks.json, .mcp.json, .idea run configs, …)
1267
+ // ride the same gate via autoExecConfigReason.
1266
1268
  // An explicit settings `allow` rule is the only bypass (exact user
1267
1269
  // pre-authorization); headless without a confirmer fails closed.
1268
1270
  if (
@@ -1272,9 +1274,10 @@ export async function executeTool(name, args, context = {}) {
1272
1274
  settingsVerdict.decision !== "allow" &&
1273
1275
  args?.path
1274
1276
  ) {
1275
- const { sensitiveFileReason } =
1277
+ const { sensitiveFileReason, autoExecConfigReason } =
1276
1278
  await import("../lib/sensitive-file-guard.js");
1277
- const sensReason = sensitiveFileReason(args.path);
1279
+ const sensReason =
1280
+ sensitiveFileReason(args.path) || autoExecConfigReason(args.path);
1278
1281
  if (sensReason) {
1279
1282
  const ok = await requestInteractivePermission(name, args, context, cwd, {
1280
1283
  tool: name,
@@ -1291,6 +1294,38 @@ export async function executeTool(name, args, context = {}) {
1291
1294
  }
1292
1295
  }
1293
1296
 
1297
+ // Session-store write guard (transcript tamper protection): JSONL
1298
+ // transcripts under ~/.chainlesschain/sessions are hash-chained and treated
1299
+ // as an audit surface — an agent editing a transcript rewrites the history
1300
+ // that `cc session verify` and resume trust. Same posture as the
1301
+ // sensitive-file guard: confirm-first, an explicit settings `allow` rule is
1302
+ // the only bypass, headless without a confirmer fails closed.
1303
+ if (
1304
+ (name === "write_file" ||
1305
+ name === "edit_file" ||
1306
+ name === "notebook_edit") &&
1307
+ settingsVerdict.decision !== "allow" &&
1308
+ args?.path
1309
+ ) {
1310
+ const { sessionStorePathReason } =
1311
+ await import("../lib/session-store-guard.js");
1312
+ const storeReason = sessionStorePathReason(args.path, { cwd });
1313
+ if (storeReason) {
1314
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
1315
+ tool: name,
1316
+ args,
1317
+ rule: null,
1318
+ reason: `session store: ${storeReason}`,
1319
+ });
1320
+ if (!ok) {
1321
+ return {
1322
+ error: `[Session Store] Writing "${args.path}" would modify a session transcript (${storeReason}) — denied. Add a settings allow rule to pre-authorize.`,
1323
+ policy: { decision: "ask", via: "session-store-guard" },
1324
+ };
1325
+ }
1326
+ }
1327
+ }
1328
+
1294
1329
  // Destructive-git guard (Claude-Code 2.1.183 parity: "destructive git
1295
1330
  // commands blocked when unintended"). The `git` tool otherwise runs any
1296
1331
  // command unguarded in auto mode — including `reset --hard`, `clean -fd`,
@@ -1438,6 +1473,14 @@ export async function executeTool(name, args, context = {}) {
1438
1473
  parentMessages: context.parentMessages,
1439
1474
  interaction: context.interaction,
1440
1475
  sessionId: context.sessionId || null,
1476
+ // Parent LLM config — documented at toolContext as forwarded to
1477
+ // spawn_sub_agent for provider/key inheritance; without this line it
1478
+ // never actually reached executeToolInner (sub-agents silently fell
1479
+ // back to config defaults and their usage was mis-attributed).
1480
+ llmOptions: context.llmOptions || null,
1481
+ // 用量归因: shared per-run sink for child-loop (sub-agent / isolated
1482
+ // skill) token usage, drained by agentLoop as attributed events.
1483
+ subAgentUsageSink: context.subAgentUsageSink || null,
1441
1484
  hostManagedToolPolicy: context.hostManagedToolPolicy || null,
1442
1485
  externalToolDescriptors: context.externalToolDescriptors || null,
1443
1486
  externalToolExecutors: context.externalToolExecutors || null,
@@ -2074,6 +2117,7 @@ async function executeToolInner(
2074
2117
  settingsHooks = null,
2075
2118
  signal = null,
2076
2119
  backgroundSubAgents = null,
2120
+ subAgentUsageSink = null,
2077
2121
  },
2078
2122
  ) {
2079
2123
  const localToolDescriptor =
@@ -2706,6 +2750,21 @@ async function executeToolInner(
2706
2750
  }
2707
2751
  output = res.stdout ?? "";
2708
2752
  }
2753
+ // PR/session linking (gap-2026-07-11 P1#9): a successful `gh pr …`
2754
+ // (or `git push`) ties this session to the PR it touched. Async and
2755
+ // best-effort — never delays or fails the tool result.
2756
+ if (sessionId) {
2757
+ import("../lib/pr-link-ledger.js")
2758
+ .then((m) =>
2759
+ m.recordFromShellCommand({
2760
+ sessionId,
2761
+ command: args.command,
2762
+ output: String(output || ""),
2763
+ cwd: args.cwd || cwd,
2764
+ }),
2765
+ )
2766
+ .catch(() => {});
2767
+ }
2709
2768
  return attachDescriptor(
2710
2769
  {
2711
2770
  stdout: output.substring(0, 30000),
@@ -2883,6 +2942,7 @@ async function executeToolInner(
2883
2942
  settingsHooks,
2884
2943
  signal,
2885
2944
  backgroundSubAgents,
2945
+ subAgentUsageSink,
2886
2946
  }),
2887
2947
  );
2888
2948
  }
@@ -3221,6 +3281,77 @@ async function executeToolInner(
3221
3281
  }
3222
3282
  }
3223
3283
 
3284
+ case "browser_act": {
3285
+ // Gap-analysis #6: the ACTION side of the Chrome connector.
3286
+ // browser_state stays the read-only default; browser_act explicitly
3287
+ // drives the user's logged-in browser (click/type/press/navigate/
3288
+ // waitForSelector/screenshot/assertText) and is gated like run_code:
3289
+ // HIGH risk through the ApprovalGate (CONFIRM even on the trusted/auto
3290
+ // tier), pre-authorizable only by an explicit settings allow rule
3291
+ // (ruleAllowed). Unlike run_code there is no legacy ungated behavior to
3292
+ // preserve, so the gate applies whenever an ApprovalGate is wired —
3293
+ // headless without a confirmer fails closed. Screenshot paths are
3294
+ // generated inside performActions (never agent-chosen), and each
3295
+ // executed step is audit-logged to ~/.chainlesschain/browser-actions/.
3296
+ try {
3297
+ if (
3298
+ approvalGate &&
3299
+ !ruleAllowed &&
3300
+ typeof approvalGate.decide === "function"
3301
+ ) {
3302
+ const { APPROVAL_RISK, APPROVAL_DECISION } =
3303
+ await import("@chainlesschain/session-core");
3304
+ const summary = Array.isArray(args.actions)
3305
+ ? args.actions
3306
+ .map((a) => a?.type)
3307
+ .filter(Boolean)
3308
+ .slice(0, 10)
3309
+ .join(",")
3310
+ : "";
3311
+ const gate = await approvalGate.decide({
3312
+ sessionId,
3313
+ riskLevel: APPROVAL_RISK.HIGH,
3314
+ tool: "browser_act",
3315
+ args: { actions: summary },
3316
+ });
3317
+ if (gate.decision !== APPROVAL_DECISION.ALLOW) {
3318
+ const tierLabel =
3319
+ typeof gate.policy === "string" ? `"${gate.policy}" ` : "";
3320
+ return attachDescriptor({
3321
+ 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.`,
3322
+ approval: {
3323
+ decision: gate.decision,
3324
+ via: gate.via,
3325
+ riskLevel: "high",
3326
+ policy: gate.policy,
3327
+ },
3328
+ });
3329
+ }
3330
+ }
3331
+ const { performActions } = await import("../lib/chrome-connector.js");
3332
+ const result = await performActions(args.actions, {
3333
+ port: args.port != null ? Number(args.port) : undefined,
3334
+ cdpUrl: args.cdp_url != null ? String(args.cdp_url) : null,
3335
+ tab: args.tab != null ? Number(args.tab) : undefined,
3336
+ continueOnError: args.continue_on_error === true,
3337
+ sessionId: sessionId ? String(sessionId) : null,
3338
+ });
3339
+ if (!result.ok && result.error) {
3340
+ // Nothing ran (validation / attach failure) — surface as an error.
3341
+ // A step-level failure returns the per-step outcomes instead so the
3342
+ // model can see exactly which step broke.
3343
+ return attachDescriptor({
3344
+ error: `browser_act failed: ${result.error}`,
3345
+ });
3346
+ }
3347
+ return attachDescriptor(result);
3348
+ } catch (err) {
3349
+ return attachDescriptor({
3350
+ error: `browser_act failed: ${err.message}`,
3351
+ });
3352
+ }
3353
+ }
3354
+
3224
3355
  case "search_files": {
3225
3356
  // An explicit directory scopes the search to one root; otherwise span
3226
3357
  // cwd plus any --add-dir roots so cross-package searches find matches.
@@ -3429,13 +3560,46 @@ async function executeToolInner(
3429
3560
  // Check if skill requests isolation (via SKILL.md frontmatter)
3430
3561
  const skillIsolation = match.isolation === true;
3431
3562
  if (skillIsolation) {
3563
+ // 用量归因: an isolated skill runs as a child loop whose real token
3564
+ // usage would otherwise be invisible — forward it into the parent
3565
+ // run's sink tagged origin:"skill" so `cc session usage --by skill`
3566
+ // can break it out. A nested frame passes through unchanged.
3567
+ const skillUsageSink = Array.isArray(subAgentUsageSink)
3568
+ ? subAgentUsageSink
3569
+ : null;
3570
+ let skillSubRef = null;
3432
3571
  // Run skill through isolated sub-agent context
3433
3572
  const subCtx = SubAgentContext.create({
3434
3573
  role: `skill-${args.skill_name}`,
3435
3574
  task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
3436
3575
  allowedTools: ["read_file", "search_files", "list_dir"],
3437
3576
  cwd,
3577
+ onUsage: skillUsageSink
3578
+ ? (u) => {
3579
+ try {
3580
+ skillUsageSink.push(
3581
+ u && u.attribution
3582
+ ? u
3583
+ : {
3584
+ provider: u?.provider ?? null,
3585
+ model: u?.model ?? null,
3586
+ usage: u?.usage || null,
3587
+ attribution: {
3588
+ origin: "skill",
3589
+ skill: args.skill_name,
3590
+ subagentId: skillSubRef?.id || null,
3591
+ parentSessionId: sessionId || null,
3592
+ depth: (subAgentDepth || 0) + 1,
3593
+ },
3594
+ },
3595
+ );
3596
+ } catch (_e) {
3597
+ // usage forwarding is best-effort
3598
+ }
3599
+ }
3600
+ : null,
3438
3601
  });
3602
+ skillSubRef = subCtx;
3439
3603
  try {
3440
3604
  const result = await subCtx.run(args.input);
3441
3605
  return attachDescriptor({
@@ -3747,7 +3911,10 @@ async function _executeRunCode(args, cwd) {
3747
3911
  const lang = args.language;
3748
3912
  const code = args.code;
3749
3913
  const timeoutSec = Math.min(Math.max(args.timeout || 60, 1), 300);
3750
- const persist = args.persist !== false; // default true
3914
+ // gap-analysis 2026-07-11 P0 "依赖安装与凭据": agent-generated scripts
3915
+ // default to the OS temp dir; only an explicit persist:true lands them in
3916
+ // the project (.chainlesschain/agent-scripts/).
3917
+ const persist = args.persist === true; // default false (temp dir)
3751
3918
 
3752
3919
  const extMap = { python: ".py", node: ".js", bash: ".sh" };
3753
3920
  const ext = extMap[lang];
@@ -3817,6 +3984,51 @@ async function _executeRunCode(args, cwd) {
3817
3984
  };
3818
3985
  }
3819
3986
 
3987
+ // Auto-install is OPT-IN (settings runCode.autoInstall / env
3988
+ // CC_RUN_CODE_AUTO_INSTALL=1) with an optional package allowlist;
3989
+ // every attempt — including refused ones — is audited.
3990
+ const {
3991
+ resolveAutoInstallPolicy,
3992
+ isPackageAllowed,
3993
+ autoInstallDisabledHint,
3994
+ recordInstallAudit,
3995
+ } = await import("../lib/dependency-install-policy.js");
3996
+ const installPolicy = resolveAutoInstallPolicy({ cwd });
3997
+ if (!installPolicy.enabled) {
3998
+ recordInstallAudit({
3999
+ package: packageName,
4000
+ interpreter,
4001
+ cwd,
4002
+ outcome: "disabled",
4003
+ });
4004
+ return {
4005
+ error: (stderr || message).substring(0, 5000),
4006
+ stderr: stderr.substring(0, 5000),
4007
+ exitCode: err.status,
4008
+ language: lang,
4009
+ ...classified,
4010
+ hint: autoInstallDisabledHint(packageName),
4011
+ scriptPath: persist ? scriptPath : undefined,
4012
+ };
4013
+ }
4014
+ if (!isPackageAllowed(packageName, installPolicy.allowlist)) {
4015
+ recordInstallAudit({
4016
+ package: packageName,
4017
+ interpreter,
4018
+ cwd,
4019
+ outcome: "blocked",
4020
+ });
4021
+ return {
4022
+ error: (stderr || message).substring(0, 5000),
4023
+ stderr: stderr.substring(0, 5000),
4024
+ exitCode: err.status,
4025
+ language: lang,
4026
+ ...classified,
4027
+ hint: `Package "${packageName}" is not in runCode.installAllowlist — install it manually or add it to the allowlist.`,
4028
+ scriptPath: persist ? scriptPath : undefined,
4029
+ };
4030
+ }
4031
+
3820
4032
  // Attempt pip install
3821
4033
  try {
3822
4034
  execSync(`${interpreter} -m pip install ${packageName}`, {
@@ -3825,6 +4037,12 @@ async function _executeRunCode(args, cwd) {
3825
4037
  maxBuffer: 2 * 1024 * 1024,
3826
4038
  stdio: ["pipe", "pipe", "pipe"],
3827
4039
  });
4040
+ recordInstallAudit({
4041
+ package: packageName,
4042
+ interpreter,
4043
+ cwd,
4044
+ outcome: "installed",
4045
+ });
3828
4046
 
3829
4047
  // Retry execution
3830
4048
  const retryStart = Date.now();
@@ -3845,6 +4063,13 @@ async function _executeRunCode(args, cwd) {
3845
4063
  scriptPath: persist ? scriptPath : undefined,
3846
4064
  };
3847
4065
  } catch (pipErr) {
4066
+ recordInstallAudit({
4067
+ package: packageName,
4068
+ interpreter,
4069
+ cwd,
4070
+ outcome: "failed",
4071
+ detail: (pipErr.stderr || pipErr.message || "").substring(0, 200),
4072
+ });
3848
4073
  return {
3849
4074
  error: (stderr || message).substring(0, 5000),
3850
4075
  stderr: stderr.substring(0, 5000),
@@ -4020,6 +4245,27 @@ function _backgroundSubAgentResultText(entry) {
4020
4245
  );
4021
4246
  }
4022
4247
 
4248
+ /**
4249
+ * Drain the run's attributed child-loop usage sink (spawn_sub_agent /
4250
+ * isolated run_skill — see toolContext.subAgentUsageSink), re-yielding each
4251
+ * record as a regular `token-usage` event that carries its `attribution`
4252
+ * frame. Consumers that ignore `attribution` see ordinary usage events;
4253
+ * attribution-aware consumers (REPL persistence, headless runner) can split
4254
+ * child spend from the main conversation's.
4255
+ */
4256
+ function* _drainSubAgentUsage(sink) {
4257
+ while (Array.isArray(sink) && sink.length > 0) {
4258
+ const u = sink.shift();
4259
+ yield {
4260
+ type: "token-usage",
4261
+ provider: u?.provider ?? null,
4262
+ model: u?.model ?? null,
4263
+ usage: u?.usage || {},
4264
+ attribution: u?.attribution || null,
4265
+ };
4266
+ }
4267
+ }
4268
+
4023
4269
  async function _executeSpawnSubAgent(args, ctx) {
4024
4270
  // Nesting cap: refuse before any context/registry work.
4025
4271
  const currentDepth = ctx.subAgentDepth || 0;
@@ -4051,6 +4297,17 @@ async function _executeSpawnSubAgent(args, ctx) {
4051
4297
  tools: explicitTools,
4052
4298
  profile: profileName,
4053
4299
  } = args;
4300
+ // Extended sub-agent contract (gap 2026-07-11 P1): per-spawn deny-list,
4301
+ // iteration cap and worktree isolation — spawn args win over the agent
4302
+ // file's frontmatter defaults.
4303
+ let disallowedTools = Array.isArray(args.disallowedTools)
4304
+ ? args.disallowedTools.filter(Boolean)
4305
+ : null;
4306
+ let subMaxTurns =
4307
+ Number.isFinite(Number(args.maxTurns)) && Number(args.maxTurns) > 0
4308
+ ? Math.floor(Number(args.maxTurns))
4309
+ : null;
4310
+ let subIsolation = args.isolation === "worktree" ? "worktree" : null;
4054
4311
 
4055
4312
  // Named subagent delegation (cc agents / .chainlesschain|.claude/agents/*.md):
4056
4313
  // load the agent's persona (its body = system prompt) + tool allow-list.
@@ -4068,6 +4325,13 @@ async function _executeSpawnSubAgent(args, ctx) {
4068
4325
  }
4069
4326
  role = role || md.name;
4070
4327
  if (!explicitTools && Array.isArray(md.tools)) explicitTools = md.tools;
4328
+ if (!disallowedTools && Array.isArray(md.disallowedTools)) {
4329
+ disallowedTools = md.disallowedTools;
4330
+ }
4331
+ if (!subMaxTurns && md.maxTurns) subMaxTurns = md.maxTurns;
4332
+ if (!subIsolation && md.isolation === "worktree") {
4333
+ subIsolation = "worktree";
4334
+ }
4071
4335
  if (md.model) mdModel = md.model;
4072
4336
  if (md.systemPrompt) {
4073
4337
  mdProfile = { name: md.name, systemPrompt: md.systemPrompt };
@@ -4107,9 +4371,19 @@ async function _executeSpawnSubAgent(args, ctx) {
4107
4371
  // profile.systemPrompt seam) when no declarative profile was requested.
4108
4372
  if (!profile && mdProfile) profile = mdProfile;
4109
4373
 
4110
- const allowedTools = Array.isArray(explicitTools)
4374
+ let allowedTools = Array.isArray(explicitTools)
4111
4375
  ? explicitTools
4112
4376
  : profile?.toolAllowlist || null;
4377
+ // Deny-list: subtract from the resolved allow-list; with no allow-list
4378
+ // ("all tools"), subtract from the full built-in contract set. spawn itself
4379
+ // is always denied downstream by depth/breadth caps, so no special-casing.
4380
+ if (Array.isArray(disallowedTools) && disallowedTools.length > 0) {
4381
+ const deny = new Set(disallowedTools.map((t) => String(t).trim()));
4382
+ const base = Array.isArray(allowedTools)
4383
+ ? allowedTools
4384
+ : listCodingAgentToolNames();
4385
+ allowedTools = base.filter((t) => !deny.has(t));
4386
+ }
4113
4387
 
4114
4388
  // Auto-condense parent context if caller didn't provide explicit context
4115
4389
  let resolvedContext = inheritedContext || null;
@@ -4169,6 +4443,40 @@ async function _executeSpawnSubAgent(args, ctx) {
4169
4443
  model: mdModel || parentLlm.model || undefined,
4170
4444
  };
4171
4445
 
4446
+ // 用量归因: forward the child's real token usage into the parent run's sink
4447
+ // (threaded through toolContext) so agentLoop re-yields it as attributed
4448
+ // `token-usage` events. A nested child's already-attributed record passes
4449
+ // through unchanged (deepest frame wins). subCtxRef closes over the created
4450
+ // context so the frame can carry the sub-agent's id.
4451
+ const usageSink = Array.isArray(ctx.subAgentUsageSink)
4452
+ ? ctx.subAgentUsageSink
4453
+ : null;
4454
+ let subCtxRef = null;
4455
+ const onUsage = usageSink
4456
+ ? (u) => {
4457
+ try {
4458
+ usageSink.push(
4459
+ u && u.attribution
4460
+ ? u
4461
+ : {
4462
+ provider: u?.provider ?? null,
4463
+ model: u?.model ?? null,
4464
+ usage: u?.usage || null,
4465
+ attribution: {
4466
+ origin: "subagent",
4467
+ subagentId: subCtxRef?.id || null,
4468
+ role: subCtxRef?.role || role || null,
4469
+ parentSessionId,
4470
+ depth: currentDepth + 1,
4471
+ },
4472
+ },
4473
+ );
4474
+ } catch (_e) {
4475
+ // usage forwarding is best-effort
4476
+ }
4477
+ }
4478
+ : null;
4479
+
4172
4480
  const subCtx = SubAgentContext.create({
4173
4481
  role,
4174
4482
  task,
@@ -4182,7 +4490,13 @@ async function _executeSpawnSubAgent(args, ctx) {
4182
4490
  // Same shared counter object so the child's own spawns draw from the run's
4183
4491
  // single total-sub-agent pool (breadth cap spans the whole tree).
4184
4492
  subAgentBudget: ctx.subAgentBudget || null,
4493
+ onUsage,
4494
+ // Extended contract (gap 2026-07-11): per-agent iteration cap + opt-in
4495
+ // worktree isolation. undefined keeps the profile/flag defaults intact.
4496
+ ...(subMaxTurns ? { maxIterations: subMaxTurns } : {}),
4497
+ ...(subIsolation === "worktree" ? { useWorktree: true } : {}),
4185
4498
  });
4499
+ subCtxRef = subCtx;
4186
4500
 
4187
4501
  const emit = (type, payload) => {
4188
4502
  if (!interaction || typeof interaction.emit !== "function") return;
@@ -5793,8 +6107,18 @@ export async function* agentLoop(messages, options) {
5793
6107
  // any are still running (it waits, injects, and gives the model one more
5794
6108
  // turn) so a background result can never be silently lost.
5795
6109
  backgroundSubAgents: new Map(),
6110
+ // 用量归因: per-run sink for child-loop (spawn_sub_agent / isolated
6111
+ // run_skill) token usage. Child loops consume their own generator events,
6112
+ // so their real usage never reaches this loop's consumers — the spawn
6113
+ // wiring pushes it here and the loop drains it at iteration boundaries as
6114
+ // `token-usage` events carrying an `attribution` frame. Callers may pass
6115
+ // their own array to observe it directly.
6116
+ subAgentUsageSink: Array.isArray(options.subAgentUsageSink)
6117
+ ? options.subAgentUsageSink
6118
+ : [],
5796
6119
  };
5797
6120
  const backgroundSubAgents = toolContext.backgroundSubAgents;
6121
+ const subAgentUsageSink = toolContext.subAgentUsageSink;
5798
6122
 
5799
6123
  throwIfAborted(signal);
5800
6124
 
@@ -5930,6 +6254,10 @@ export async function* agentLoop(messages, options) {
5930
6254
  budget.consume();
5931
6255
  throwIfAborted(signal);
5932
6256
 
6257
+ // Surface attributed child-loop usage collected since the last boundary
6258
+ // (blocking spawns push during executeTool; background spawns push live).
6259
+ yield* _drainSubAgentUsage(subAgentUsageSink);
6260
+
5933
6261
  // Emit progressive warnings (once per level)
5934
6262
  const level = budget.warningLevel();
5935
6263
  if (
@@ -6504,7 +6832,9 @@ export async function* agentLoop(messages, options) {
6504
6832
  }
6505
6833
  }
6506
6834
 
6507
- // Budget exhausted — yield exhaustion event + final message
6835
+ // Budget exhausted — flush any child usage the final iteration produced,
6836
+ // then yield exhaustion event + final message
6837
+ yield* _drainSubAgentUsage(subAgentUsageSink);
6508
6838
  yield { type: "iteration-budget-exhausted", budget: budget.toSummary() };
6509
6839
  yield {
6510
6840
  type: "response-complete",
@@ -6559,6 +6889,13 @@ export function formatToolArgs(name, args) {
6559
6889
  return `${args.kind || "other"}: ${(args.title || args.path || "").substring(0, 60)}`;
6560
6890
  case "browser_state":
6561
6891
  return `tab=${args.tab ?? 0} port=${args.port ?? 9222}${args.reload ? " reload" : ""}`;
6892
+ case "browser_act": {
6893
+ const kinds = Array.isArray(args.actions)
6894
+ ? args.actions.map((a) => a?.type).filter(Boolean)
6895
+ : [];
6896
+ const head = kinds.slice(0, 4).join(",");
6897
+ return `${kinds.length} action(s)${head ? `: ${head}` : ""}${kinds.length > 4 ? ",…" : ""} tab=${args.tab ?? 0} port=${args.port ?? 9222}`;
6898
+ }
6562
6899
  case "schedule":
6563
6900
  return args.action === "cron"
6564
6901
  ? `cron ${args.cron || ""}`.trim()