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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-ByIiF5bT.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-NlgSFZq5.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-D17cOUcb.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-B4mEZcbl.js} +4 -4
- package/src/assets/web-panel/assets/Artifacts-CASsmyQw.js +1 -0
- package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DYAia2e2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-C1lvYUSu.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-B6agK6bA.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DM43R2vy.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-Dtdp3eXp.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BAV9V-t-.js → ChatBubbleRenderer-BL5c_OQ1.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-D2xf7uZt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-D3j45jJL.js} +1 -1
- package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-voPJ9fUZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DEsyTESF.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-OHV_QXLD.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-eDTJWC-J.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BBvy-6d1.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-Dw2eoJxu.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DQHms_Bh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-Cyu7Id1c.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-sIDzkG92.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-DUW3T2ey.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-CECELXW8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-BIZR37ov.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-BEZIQX84.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-BcdUMznL.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DxxEdn2r.js → GenericCardRenderer-DsN_fr5H.js} +1 -1
- package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-63LhjBx2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-CYtQtIcH.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-BwR6UUox.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D9lVNTUA.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-C4q5h7Ut.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-DexLlXfN.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-Plksliuw.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-DZUFfeuG.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-NoDYt29V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-CEXGTkdh.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CVtTqgtg.js → MobileProjects-DnbJkbZo.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-DTAa0cwy.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-CVV7LnNS.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-BqqGrjt_.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-C4WP8DRp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Zo4DJUVI.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-DBcjRaz2.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-BT9WPBOX.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-D6b4bZ3q.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-Cw3OsYx-.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C0nyiK2b.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-zVBZEB_C.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-BOspsFew.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-CQWfZ4XH.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-C2oGJ_WL.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-BZMR4qY1.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-DAujoP63.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-COATmHxF.js → Projects-DvH_P2Uq.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-D_RGHM-v.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Mf6KfL2J.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-5D8URoI_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-Df4kC8Gy.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-DGi3f3vO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-jmkGFNWb.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-DP6Pcu3m.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-D60hSuPO.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-0mf1BQUK.js} +1 -1
- package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-CfZtLFYV.js} +4 -4
- package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D9zpZLWz.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-D1_44eMD.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-cVFa8zkz.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-Cinpzc3L.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-bqdiJE_P.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-Dgl_SLgj.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-DawWAEhy.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-Ca0nY651.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-CgaGEhLm.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-D-9EnpRx.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-CSOxDHZ6.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-BpwLZpL3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DgowwBK5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-BZutapjE.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-QZPt8_Sp.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-DlQx5UDJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-ZfdTgtWA.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-CAp0y_Cr.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-BVITZEmr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-CC2tPaNg.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-31brc9CA.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-DLMkC9C-.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-BMtveI6F.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DbD6WHW2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B30NvR21.js → index-3oBsXZmF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-ALvoNTRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-AaIKG60c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-BFRLmCR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-BKAB6-6V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BOM-VFlv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-BPpS-lGb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-Bbc2JeQG.js} +1 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +1 -0
- package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-BiRQUoyE.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-BnFLzejT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-BoO4WEJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-C-cGlCDW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C1PfeI88.js} +1 -1
- package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-C21pdBqs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-C7LHNw_N.js} +1 -1
- package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-CEXa-4zd.js} +1 -1
- package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CEwjBA9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-CFXKbOek.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-CJhREOlG.js} +1 -1
- package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-COHHQ9cU.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23L1V76.js → index-CUmLMFsy.js} +1 -1
- package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-CWgfipcC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-C_eUKGYP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-CcVszn2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-Cod97Urs.js} +3 -3
- package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-Cwbqh0KZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-D67kIagl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-D7cV0tu0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-DJvxFyIz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-DZl-UXB6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-Dm0byjm1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-DndNnYc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-WQUO2vvK.js} +1 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +1 -0
- package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-bdaJwTXJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-ep6JchNG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCyP071m.js → index-jG6h22Qg.js} +1 -1
- package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-ntVQqSML.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-D5UoBRhN.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-DltOw0CR.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-_oPkk5zj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-EES8ROCS.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-B-_39x_M.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-l6D0yJHz.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-DIeUK31L.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-dG4EzUQo.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-DWFzYGdH.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-CECdTA5H.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-D6kP-mMK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-BWfna_rd.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-Bb-j6rzo.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-D5OIsYM4.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-DNpwzFTM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-vNLpFQBW.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +17 -3
- package/src/commands/agent.js +7 -0
- package/src/commands/background-session.js +143 -3
- package/src/commands/browse.js +86 -0
- package/src/commands/cloud.js +232 -0
- package/src/commands/doctor.js +61 -6
- package/src/commands/mcp.js +61 -0
- package/src/commands/plugin.js +63 -1
- package/src/commands/routine.js +367 -0
- package/src/commands/session.js +326 -0
- package/src/data/changelog.json +7 -2
- package/src/harness/jsonl-session-store.js +82 -2
- package/src/harness/session-index.js +295 -0
- package/src/harness/session-mirror.js +142 -0
- package/src/harness/transcript-integrity.js +182 -0
- package/src/index.js +8 -0
- package/src/lib/background-agent-supervisor.js +126 -2
- package/src/lib/channels/channel-manager.js +126 -0
- package/src/lib/channels/telegram-channel.js +112 -0
- package/src/lib/channels/webhook-channel.js +140 -0
- package/src/lib/chrome-connector.js +386 -0
- package/src/lib/cloud/bundle.js +110 -0
- package/src/lib/cloud/cloud-client.js +152 -0
- package/src/lib/computer-use/authorization.js +173 -0
- package/src/lib/computer-use/computer-use-server.js +276 -0
- package/src/lib/computer-use/control-backend.js +150 -0
- package/src/lib/doctor-checkup.js +652 -0
- package/src/lib/jsonl-session-store.js +4 -0
- package/src/lib/lsp/lsp-server-registry.js +48 -21
- package/src/lib/plugin-runtime/governance.js +174 -0
- package/src/lib/pr-link-ledger.js +221 -0
- package/src/lib/routine-store.js +352 -0
- package/src/lib/session-store-guard.js +40 -0
- package/src/lib/session-usage.js +318 -2
- package/src/lib/sub-agent-context.js +23 -0
- package/src/repl/agent-repl.js +361 -3
- package/src/repl/bg-dashboard.js +355 -0
- package/src/repl/fast-mode.js +103 -0
- package/src/repl/fullscreen-tui.js +197 -0
- package/src/repl/slash-command-registry.js +11 -3
- package/src/repl/voice-dictation.js +132 -0
- package/src/runtime/agent-core.js +242 -1
- package/src/runtime/coding-agent-contract-shared.cjs +88 -0
- package/src/runtime/coding-agent-policy.cjs +17 -0
- package/src/runtime/headless-runner.js +71 -7
- package/src/runtime/headless-stream.js +14 -8
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +0 -1
- package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
- package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
- 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 —
|
|
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) {
|