chainlesschain 0.162.157 → 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/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CfMP2Oud.js → AIOps-ByIiF5bT.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Zu0N8BQL.js → ActionButton-NlgSFZq5.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DZTwj5T_.js → Analytics-D17cOUcb.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CVFJh47v.js → AppLayout-B4mEZcbl.js} +5 -5
- package/src/assets/web-panel/assets/Artifacts-CASsmyQw.js +1 -0
- package/src/assets/web-panel/assets/Artifacts-CTVt96zW.css +1 -0
- package/src/assets/web-panel/assets/{Audit-BMfiLj7T.js → Audit-DYAia2e2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BslPCdGF.js → BackgroundAgents-C1lvYUSu.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CQ_fMC1r.js → Backup-B6agK6bA.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CsFsARiC.js → BaseInput-DM43R2vy.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwfBobco.js → Chat-Dtdp3eXp.js} +6 -6
- package/src/assets/web-panel/assets/Chat-KSGPtU0L.css +1 -0
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-K6Nf3xeN.js → ChatBubbleRenderer-BL5c_OQ1.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DkSyjjPp.js → Checkbox-D2xf7uZt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B7Fx3zZ6.js → Codegen-D3j45jJL.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Dzfqn8Ep.js → Col-voPJ9fUZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DhHPdzMM.js → Community-DEsyTESF.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Ct18_8y7.js → Compact-OHV_QXLD.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CY64IqBi.js → Compliance-eDTJWC-J.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BYSmWlUl.js → Cowork-BBvy-6d1.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-_4xtP3Ug.js → Cron-Dw2eoJxu.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B4qY-Ycr.js → Crosschain-DQHms_Bh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CkCzWkx1.js → DID-Cyu7Id1c.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C1scT7Ke.js → Dashboard-sIDzkG92.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DBnQ2EWd.js → Dropdown-DUW3T2ey.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9JhOTq0.js → EmailListRenderer-CECELXW8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CWXv5RUv.js → FamilyGuardDashboard-BIZR37ov.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-fYdcbz9W.js → Federation-BEZIQX84.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CJqOT3Yj.js → FormItemContext-BcdUMznL.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DGCsHc5K.js → GenericCardRenderer-DsN_fr5H.js} +1 -1
- package/src/assets/web-panel/assets/{Git-EH2K-H0Q.js → Git-63LhjBx2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DJn1X2pR.js → Governance-CYtQtIcH.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dch1E8qd.js → Inference-BwR6UUox.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-QIur8j8-.js → KnowledgeGraph-D9lVNTUA.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DUgHldPg.js → Logs-C4q5h7Ut.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DbzhFwvz.css +1 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-DexLlXfN.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DafrSfvd.js → Marketplace-Plksliuw.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D2xRbfJx.js → McpTools-DZUFfeuG.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C6RbVHAo.js → Memory-NoDYt29V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BPtVBKqn.js → MobileBridge-CEXGTkdh.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Cuw832cr.js → MobileProjects-DnbJkbZo.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D6O9fWFa.js → Mtc-DTAa0cwy.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CWZcXIH1.js → MtcAudit-CVV7LnNS.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DhIBxTFd.js → Multisig-BqqGrjt_.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-wkBFS33e.js → NLProgramming-C4WP8DRp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B09FxncA.js → Notes-Zo4DJUVI.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-oQwtH6-9.js → NotificationSettings-DBcjRaz2.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CsF-NIjz.js → Organization-BT9WPBOX.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-bLxv9Dqi.js → Overflow-D6b4bZ3q.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DVwQtm4q.js → P2P-Cw3OsYx-.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Bgic3QKm.js → PdhVaultBrowser-C0nyiK2b.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-as80-9ep.js → Permissions-zVBZEB_C.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BGmUw36a.js → PersonalDataHub-BOspsFew.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BM96zBxG.js → Pipeline-CQWfZ4XH.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BBOzBSWe.js → Privacy-C2oGJ_WL.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-GHX41V28.js → ProjectInit-BZMR4qY1.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DraqzxKD.js → ProjectSettings-DAujoP63.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BzzdUml4.js → Projects-DvH_P2Uq.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CSeYNAtW.js → Providers-D_RGHM-v.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DlzBCdXh.js → QrScannerModal-Mf6KfL2J.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-p8iB7od_.js → QuickAsk-5D8URoI_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DjOVXacd.js → Recommend-Df4kC8Gy.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DGi3f3vO.js +4 -0
- package/src/assets/web-panel/assets/{Reputation-CjT3M0Di.js → Reputation-jmkGFNWb.js} +1 -1
- package/src/assets/web-panel/assets/{Row-tw8m6T0c.js → Row-DP6Pcu3m.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B47KBx_f.js → RssFeed-D60hSuPO.js} +3 -3
- package/src/assets/web-panel/assets/{Search-D2sx1PBe.js → Search-0mf1BQUK.js} +1 -1
- package/src/assets/web-panel/assets/{Security-JmtL8g7D.js → Security-CfZtLFYV.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BxrNqjin.js → Services-D9zpZLWz.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-udk8L18t.js → Skeleton-D1_44eMD.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DooLT9of.js → Skills-cVFa8zkz.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-ITVZE1xI.js → Sla-Cinpzc3L.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DCkfqY1n.js → SpeechSettings-bqdiJE_P.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CkNlgFjM.js → SyncSettings-Dgl_SLgj.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BMAoyCM_.js → Tasks-DawWAEhy.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BnrFTlrz.js → Templates-Ca0nY651.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BjNsy3-i.js → Tenant-CgaGEhLm.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C0gxLL7V.js → Terminal-D-9EnpRx.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CUtj_KHA.js → TimelineRenderer-CSOxDHZ6.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DOg61zlq.js → Tokens-BpwLZpL3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D4ldCewj.js → Trigger-DgowwBK5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-3lk_Zd8j.js → Trust-BZutapjE.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-kWhFR6Li.js → UkeySign-QZPt8_Sp.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D3GOFVWg.js → VideoEditing-DlQx5UDJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DzrZfBuh.js → Wallet-ZfdTgtWA.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CyVJVp_g.js → WebAuthn-CAp0y_Cr.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-i_b0wi9b.js → WorkflowEditor-BVITZEmr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DwPCK8I7.js → chat-CC2tPaNg.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D0vaVqZU.js → colors-31brc9CA.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Don7nGW2.js → compact-item-DLMkC9C-.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D84R5qy2.js → createContext-BMtveI6F.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BeUqVVKU.js → hasIn-DbD6WHW2.js} +1 -1
- package/src/assets/web-panel/assets/{index-m9bn0F1p.js → index-3oBsXZmF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfYRv-zA.js → index-ALvoNTRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-4MkfPLGv.js → index-AaIKG60c.js} +1 -1
- package/src/assets/web-panel/assets/{index-LMo9bBLJ.js → index-BFRLmCR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-wesQQ2cH.js → index-BKAB6-6V.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzT56x-_.js → index-BOM-VFlv.js} +1 -1
- package/src/assets/web-panel/assets/{index-L73NrmlW.js → index-BPpS-lGb.js} +1 -1
- package/src/assets/web-panel/assets/{index-WRw1pM5E.js → index-Bbc2JeQG.js} +1 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +1 -0
- package/src/assets/web-panel/assets/{index-Br3SOdER.js → index-BiRQUoyE.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8r7WKk-.js → index-BnFLzejT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAdCTTBC.js → index-BoO4WEJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlfTf0ka.js → index-C-cGlCDW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B57al0Z5.js → index-C1PfeI88.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4b5XqIy.js → index-C21pdBqs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnnxzIx0.js → index-C7LHNw_N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNpJf-sf.js → index-CEXa-4zd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DT40zd7K.js → index-CEwjBA9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CL71rIwo.js → index-CFXKbOek.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ1N07Sp.js → index-CJhREOlG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0_sKVi7.js → index-COHHQ9cU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBzBLX-K.js → index-CUmLMFsy.js} +1 -1
- package/src/assets/web-panel/assets/{index-De85D9Pc.js → index-CWgfipcC.js} +1 -1
- package/src/assets/web-panel/assets/{index-x7VYlV41.js → index-C_eUKGYP.js} +1 -1
- package/src/assets/web-panel/assets/{index-hjgrIX5I.js → index-CcVszn2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMT8n9vB.js → index-Cod97Urs.js} +3 -3
- package/src/assets/web-panel/assets/{index-DFFzwrNs.js → index-Cwbqh0KZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-YKLCotoo.js → index-D67kIagl.js} +1 -1
- package/src/assets/web-panel/assets/{index-IGGttsCv.js → index-D7cV0tu0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQor_7NF.js → index-DJvxFyIz.js} +1 -1
- package/src/assets/web-panel/assets/{index-D84BnuFg.js → index-DZl-UXB6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrnVEEQO.js → index-Dm0byjm1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BER98o4P.js → index-DndNnYc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA0-EJKx.js → index-WQUO2vvK.js} +1 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +1 -0
- package/src/assets/web-panel/assets/{index-CExWtm98.js → index-bdaJwTXJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-MKZrJrP_.js → index-ep6JchNG.js} +1 -1
- package/src/assets/web-panel/assets/{index-7QUwcGTg.js → index-jG6h22Qg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BohrJpBf.js → index-ntVQqSML.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DGeU4vPS.js → initDefaultProps-D5UoBRhN.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dgqj9ab3.js → motion-DltOw0CR.js} +1 -1
- package/src/assets/web-panel/assets/{move-BFyEU6Lr.js → move-_oPkk5zj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CueCXXPC.js → mtc-parser-EES8ROCS.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D2pU5OyE.js → omit-B-_39x_M.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CKTf6zD6.js → pickAttrs-l6D0yJHz.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DieJaGCN.js → placementArrow-DIeUK31L.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Bxr-rmU6.js → responsiveObserve-dG4EzUQo.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CDEDk3Se.js → slide-DWFzYGdH.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-icf2eCTx.js → statusUtils-CECdTA5H.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-qKvgmBhE.js → styleChecker-D6kP-mMK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-1_UhiyJZ.js → useFlexGapSupport-BWfna_rd.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CFGLXDWh.js → useFs-Bb-j6rzo.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-UafOXvQ6.js → usePersonalDataHub-D5OIsYM4.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BfPqSx79.js → vnode-DNpwzFTM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXsVcRB6.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 +11 -1
- 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 +12 -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 +425 -4
- 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/remote-control.js +35 -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/lib/web-ui-server.js +120 -7
- package/src/repl/agent-repl.js +592 -61
- 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/remote-approval.js +111 -0
- package/src/repl/slash-command-registry.js +11 -3
- package/src/repl/voice-dictation.js +132 -0
- package/src/runtime/agent-core.js +278 -1
- package/src/runtime/coding-agent-contract-shared.cjs +141 -0
- package/src/runtime/coding-agent-policy.cjs +33 -0
- package/src/runtime/headless-runner.js +71 -7
- package/src/runtime/headless-stream.js +14 -8
- package/src/runtime/policies/agent-policy.js +6 -0
- package/src/assets/web-panel/assets/Artifacts-BbN5V7au.js +0 -1
- package/src/assets/web-panel/assets/Artifacts-CRkepl4t.css +0 -1
- package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-iFKeNf6Y.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-CIGk7gk-.js +0 -4
- package/src/assets/web-panel/assets/devWarning-BBwWehQ7.js +0 -1
- package/src/assets/web-panel/assets/index-DJ8etkBT.js +0 -1
- package/src/assets/web-panel/assets/index-OeaK-zXZ.js +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL-side remote-approval glue (gap-analysis 批26 — 批17/18 REPL 遗留收口).
|
|
3
|
+
*
|
|
4
|
+
* Interactive sessions race the LOCAL terminal prompt against a paired
|
|
5
|
+
* device's decision (headless gates on the remote decision alone):
|
|
6
|
+
*
|
|
7
|
+
* - local answer first → settle the device card too (resolveLocally
|
|
8
|
+
* publishes permission.resolved so the phone/web UI clears it)
|
|
9
|
+
* - remote answer first → cancel the local readline prompt and print
|
|
10
|
+
* the outcome
|
|
11
|
+
* - remote leg times out or the bridge closes mid-ask → NOT a decision:
|
|
12
|
+
* keep waiting on the terminal. A silent phone must never auto-deny a
|
|
13
|
+
* user sitting at the keyboard — the fail-closed timeout is headless
|
|
14
|
+
* semantics only.
|
|
15
|
+
*
|
|
16
|
+
* Pure over an injected bridge + local prompt handle ({promise, cancel}),
|
|
17
|
+
* so the race is unit-testable without a WS server or readline.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Normalize the shapes the REPL's two confirmers receive (ApprovalGate
|
|
22
|
+
* `{tool, args, riskLevel}` / settings-hook `{tool, args, rule, reason}`)
|
|
23
|
+
* into the bridge's `{tool, action, detail}` ask payload.
|
|
24
|
+
*/
|
|
25
|
+
export function describeAskContext({
|
|
26
|
+
tool,
|
|
27
|
+
args,
|
|
28
|
+
rule = null,
|
|
29
|
+
reason = null,
|
|
30
|
+
riskLevel = null,
|
|
31
|
+
} = {}) {
|
|
32
|
+
const detail =
|
|
33
|
+
typeof args?.command === "string"
|
|
34
|
+
? args.command
|
|
35
|
+
: typeof args?.path === "string"
|
|
36
|
+
? args.path
|
|
37
|
+
: args
|
|
38
|
+
? JSON.stringify(args).slice(0, 2000)
|
|
39
|
+
: null;
|
|
40
|
+
return {
|
|
41
|
+
tool: tool || null,
|
|
42
|
+
action: rule
|
|
43
|
+
? `ask-rule:${rule}`
|
|
44
|
+
: reason || (riskLevel ? `${riskLevel}-risk` : null),
|
|
45
|
+
detail,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remote outcomes that are NOT a decision for an interactive session: the
|
|
50
|
+
// devices went silent (timeout) or the bridge was torn down mid-ask (closed).
|
|
51
|
+
const NON_DECISIVE_REMOTE = new Set(["timeout", "closed"]);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Race the local interactive prompt against the paired-device decision.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} opts
|
|
57
|
+
* @param {import("../lib/remote-approval-bridge.js").RemoteApprovalBridge} opts.bridge
|
|
58
|
+
* @param {{tool?, action?, detail?}} opts.ask - describeAskContext() payload
|
|
59
|
+
* @param {{promise: Promise<boolean>, cancel?: () => void}} opts.local -
|
|
60
|
+
* the already-started local prompt; `cancel` closes its readline when the
|
|
61
|
+
* device answers first (implementations must mute their idle-timeout
|
|
62
|
+
* message after cancel).
|
|
63
|
+
* @param {(text: string) => void} [opts.writeOut]
|
|
64
|
+
* @returns {Promise<boolean>} approved
|
|
65
|
+
*/
|
|
66
|
+
export async function raceLocalAndRemote({
|
|
67
|
+
bridge,
|
|
68
|
+
ask = {},
|
|
69
|
+
local,
|
|
70
|
+
writeOut = () => {},
|
|
71
|
+
}) {
|
|
72
|
+
let requestId = null;
|
|
73
|
+
const remoteLeg = bridge
|
|
74
|
+
.requestDecision({
|
|
75
|
+
...ask,
|
|
76
|
+
onRequestId: (id) => {
|
|
77
|
+
requestId = id;
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.then((decision) => ({ src: "remote", decision }));
|
|
81
|
+
const localLeg = local.promise.then(
|
|
82
|
+
(approved) => ({ src: "local", approved: approved === true }),
|
|
83
|
+
() => ({ src: "local", approved: false }),
|
|
84
|
+
);
|
|
85
|
+
let winner = await Promise.race([remoteLeg, localLeg]);
|
|
86
|
+
if (
|
|
87
|
+
winner.src === "remote" &&
|
|
88
|
+
NON_DECISIVE_REMOTE.has(winner.decision?.via)
|
|
89
|
+
) {
|
|
90
|
+
winner = await localLeg;
|
|
91
|
+
}
|
|
92
|
+
if (winner.src === "local") {
|
|
93
|
+
if (requestId) {
|
|
94
|
+
try {
|
|
95
|
+
bridge.resolveLocally(requestId, winner.approved);
|
|
96
|
+
} catch {
|
|
97
|
+
// clearing the device card is best-effort
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return winner.approved;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
local.cancel?.();
|
|
104
|
+
} catch {
|
|
105
|
+
// readline already closed
|
|
106
|
+
}
|
|
107
|
+
writeOut(
|
|
108
|
+
`\n ✓ ${winner.decision.approved ? "allowed" : "denied"} from paired device\n`,
|
|
109
|
+
);
|
|
110
|
+
return winner.decision.approved === true;
|
|
111
|
+
}
|
|
@@ -35,7 +35,13 @@ export class SlashCommandRegistry {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
getCommand(name) {
|
|
38
|
-
|
|
38
|
+
const direct = this._commands.get(name);
|
|
39
|
+
if (direct) return direct;
|
|
40
|
+
// Alias resolution: a command registered with aliases answers to them too.
|
|
41
|
+
for (const cmd of this._commands.values()) {
|
|
42
|
+
if (cmd.aliases && cmd.aliases.includes(name)) return cmd;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
getAllCommands() {
|
|
@@ -43,7 +49,7 @@ export class SlashCommandRegistry {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
hasCommand(name) {
|
|
46
|
-
return this.
|
|
52
|
+
return Boolean(this.getCommand(name));
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
_registerBuiltinCommands() {
|
|
@@ -188,8 +194,10 @@ export class SlashCommandRegistry {
|
|
|
188
194
|
],
|
|
189
195
|
];
|
|
190
196
|
|
|
197
|
+
// Claude-Code 2.1.205 parity: /checkup is an alias of /doctor.
|
|
198
|
+
const ALIASES = { "/doctor": ["/checkup"] };
|
|
191
199
|
for (const [name, description, handler] of commands) {
|
|
192
|
-
this.register(name, { description, handler });
|
|
200
|
+
this.register(name, { description, handler, aliases: ALIASES[name] });
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
}
|
|
@@ -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
|
}
|
|
@@ -3187,6 +3244,111 @@ async function executeToolInner(
|
|
|
3187
3244
|
}
|
|
3188
3245
|
}
|
|
3189
3246
|
|
|
3247
|
+
case "browser_state": {
|
|
3248
|
+
// P1 #8: first-class Chrome CDP observation — the agent side of the
|
|
3249
|
+
// "locate error → fix → verify" web loop against the user's real,
|
|
3250
|
+
// logged-in browser (`cc browse chrome launch`). DOM is capped harder
|
|
3251
|
+
// than the CLI command's 150k default so a page dump cannot flood the
|
|
3252
|
+
// conversation; screenshots go to a generated temp file (never an
|
|
3253
|
+
// agent-chosen path) to keep the tool strictly read-only.
|
|
3254
|
+
try {
|
|
3255
|
+
const { captureState } = await import("../lib/chrome-connector.js");
|
|
3256
|
+
const screenshotPath = args.screenshot
|
|
3257
|
+
? path.join(os.tmpdir(), `cc-browser-state-${Date.now()}.png`)
|
|
3258
|
+
: null;
|
|
3259
|
+
const state = await captureState({
|
|
3260
|
+
port: args.port != null ? Number(args.port) : undefined,
|
|
3261
|
+
tab: args.tab != null ? Number(args.tab) : undefined,
|
|
3262
|
+
reload: args.reload === true,
|
|
3263
|
+
watchMs: args.watch_ms != null ? Number(args.watch_ms) : undefined,
|
|
3264
|
+
includeDom: args.include_dom !== false,
|
|
3265
|
+
domCap: args.dom_cap != null ? Number(args.dom_cap) : 40000,
|
|
3266
|
+
screenshotPath,
|
|
3267
|
+
});
|
|
3268
|
+
if (!state.ok) {
|
|
3269
|
+
return attachDescriptor({
|
|
3270
|
+
error: `browser_state failed: ${state.error}`,
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
return attachDescriptor(state);
|
|
3274
|
+
} catch (err) {
|
|
3275
|
+
return attachDescriptor({
|
|
3276
|
+
error: `browser_state failed: ${err.message}`,
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
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
|
+
|
|
3190
3352
|
case "search_files": {
|
|
3191
3353
|
// An explicit directory scopes the search to one root; otherwise span
|
|
3192
3354
|
// cwd plus any --add-dir roots so cross-package searches find matches.
|
|
@@ -3395,13 +3557,46 @@ async function executeToolInner(
|
|
|
3395
3557
|
// Check if skill requests isolation (via SKILL.md frontmatter)
|
|
3396
3558
|
const skillIsolation = match.isolation === true;
|
|
3397
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;
|
|
3398
3568
|
// Run skill through isolated sub-agent context
|
|
3399
3569
|
const subCtx = SubAgentContext.create({
|
|
3400
3570
|
role: `skill-${args.skill_name}`,
|
|
3401
3571
|
task: `Execute the "${args.skill_name}" skill with input: ${(args.input || "").substring(0, 200)}`,
|
|
3402
3572
|
allowedTools: ["read_file", "search_files", "list_dir"],
|
|
3403
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,
|
|
3404
3598
|
});
|
|
3599
|
+
skillSubRef = subCtx;
|
|
3405
3600
|
try {
|
|
3406
3601
|
const result = await subCtx.run(args.input);
|
|
3407
3602
|
return attachDescriptor({
|
|
@@ -3986,6 +4181,27 @@ function _backgroundSubAgentResultText(entry) {
|
|
|
3986
4181
|
);
|
|
3987
4182
|
}
|
|
3988
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
|
+
|
|
3989
4205
|
async function _executeSpawnSubAgent(args, ctx) {
|
|
3990
4206
|
// Nesting cap: refuse before any context/registry work.
|
|
3991
4207
|
const currentDepth = ctx.subAgentDepth || 0;
|
|
@@ -4135,6 +4351,40 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4135
4351
|
model: mdModel || parentLlm.model || undefined,
|
|
4136
4352
|
};
|
|
4137
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
|
+
|
|
4138
4388
|
const subCtx = SubAgentContext.create({
|
|
4139
4389
|
role,
|
|
4140
4390
|
task,
|
|
@@ -4148,7 +4398,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4148
4398
|
// Same shared counter object so the child's own spawns draw from the run's
|
|
4149
4399
|
// single total-sub-agent pool (breadth cap spans the whole tree).
|
|
4150
4400
|
subAgentBudget: ctx.subAgentBudget || null,
|
|
4401
|
+
onUsage,
|
|
4151
4402
|
});
|
|
4403
|
+
subCtxRef = subCtx;
|
|
4152
4404
|
|
|
4153
4405
|
const emit = (type, payload) => {
|
|
4154
4406
|
if (!interaction || typeof interaction.emit !== "function") return;
|
|
@@ -5759,8 +6011,18 @@ export async function* agentLoop(messages, options) {
|
|
|
5759
6011
|
// any are still running (it waits, injects, and gives the model one more
|
|
5760
6012
|
// turn) so a background result can never be silently lost.
|
|
5761
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
|
+
: [],
|
|
5762
6023
|
};
|
|
5763
6024
|
const backgroundSubAgents = toolContext.backgroundSubAgents;
|
|
6025
|
+
const subAgentUsageSink = toolContext.subAgentUsageSink;
|
|
5764
6026
|
|
|
5765
6027
|
throwIfAborted(signal);
|
|
5766
6028
|
|
|
@@ -5896,6 +6158,10 @@ export async function* agentLoop(messages, options) {
|
|
|
5896
6158
|
budget.consume();
|
|
5897
6159
|
throwIfAborted(signal);
|
|
5898
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
|
+
|
|
5899
6165
|
// Emit progressive warnings (once per level)
|
|
5900
6166
|
const level = budget.warningLevel();
|
|
5901
6167
|
if (
|
|
@@ -6470,7 +6736,9 @@ export async function* agentLoop(messages, options) {
|
|
|
6470
6736
|
}
|
|
6471
6737
|
}
|
|
6472
6738
|
|
|
6473
|
-
// Budget exhausted —
|
|
6739
|
+
// Budget exhausted — flush any child usage the final iteration produced,
|
|
6740
|
+
// then yield exhaustion event + final message
|
|
6741
|
+
yield* _drainSubAgentUsage(subAgentUsageSink);
|
|
6474
6742
|
yield { type: "iteration-budget-exhausted", budget: budget.toSummary() };
|
|
6475
6743
|
yield {
|
|
6476
6744
|
type: "response-complete",
|
|
@@ -6523,6 +6791,15 @@ export function formatToolArgs(name, args) {
|
|
|
6523
6791
|
return `${args.level || "info"}: ${(args.title || "").substring(0, 50)}`;
|
|
6524
6792
|
case "publish_artifact":
|
|
6525
6793
|
return `${args.kind || "other"}: ${(args.title || args.path || "").substring(0, 60)}`;
|
|
6794
|
+
case "browser_state":
|
|
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
|
+
}
|
|
6526
6803
|
case "schedule":
|
|
6527
6804
|
return args.action === "cron"
|
|
6528
6805
|
? `cron ${args.cron || ""}`.trim()
|