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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer-use authorization (gap-2026-07-11 P1#6) — the trust core shared by
|
|
3
|
+
* the computer-use MCP server. GUI control is the highest-privilege surface an
|
|
4
|
+
* agent can hold (it can click "OK" on anything), so it is:
|
|
5
|
+
* - OFF unless explicitly enabled (`cc mcp computer-use` / --enable);
|
|
6
|
+
* - capability-gated: each verb (screenshot/click/type/scroll/clipboard/
|
|
7
|
+
* app-launch/window-list) can be individually withheld;
|
|
8
|
+
* - per-app scoped: an allowlist confines control to named apps, and a
|
|
9
|
+
* high-risk denylist (terminals, IDEs, file managers, system settings,
|
|
10
|
+
* password managers) always requires confirmation even when allowed.
|
|
11
|
+
*
|
|
12
|
+
* Pure + dependency-free so it is exhaustively unit-tested; the server and the
|
|
13
|
+
* OS backend consume its verdicts.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const COMPUTER_USE_CAPABILITIES = [
|
|
17
|
+
"screenshot",
|
|
18
|
+
"window_list",
|
|
19
|
+
"app_launch",
|
|
20
|
+
"click",
|
|
21
|
+
"type",
|
|
22
|
+
"scroll",
|
|
23
|
+
"clipboard_read",
|
|
24
|
+
"clipboard_write",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Read-only observation is lower risk than input injection; the tool-priority
|
|
28
|
+
// guidance (MCP/API > shell > browser action > computer use) lives here so the
|
|
29
|
+
// server can surface it and the model prefers cheaper, auditable paths.
|
|
30
|
+
export const TOOL_PRIORITY = [
|
|
31
|
+
"mcp/api",
|
|
32
|
+
"shell",
|
|
33
|
+
"browser action",
|
|
34
|
+
"computer use",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Apps where an unattended click/type is disproportionately dangerous — a
|
|
38
|
+
// terminal runs commands, a password manager reveals secrets, system settings
|
|
39
|
+
// change the machine. Matched case-insensitively as a substring of the app /
|
|
40
|
+
// window identifier. Confirmation is forced even inside the allowlist.
|
|
41
|
+
export const HIGH_RISK_APP_PATTERNS = [
|
|
42
|
+
"terminal",
|
|
43
|
+
"powershell",
|
|
44
|
+
"cmd.exe",
|
|
45
|
+
"conhost",
|
|
46
|
+
"windows terminal",
|
|
47
|
+
"iterm",
|
|
48
|
+
"code", // VS Code / VSCodium
|
|
49
|
+
"jetbrains",
|
|
50
|
+
"intellij",
|
|
51
|
+
"explorer.exe",
|
|
52
|
+
"finder",
|
|
53
|
+
"nautilus",
|
|
54
|
+
"settings",
|
|
55
|
+
"control panel",
|
|
56
|
+
"regedit",
|
|
57
|
+
"keychain",
|
|
58
|
+
"1password",
|
|
59
|
+
"bitwarden",
|
|
60
|
+
"lastpass",
|
|
61
|
+
"keepass",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {object} config computer-use config subtree
|
|
66
|
+
* { enabled, capabilities?: string[], appAllowlist?: string[],
|
|
67
|
+
* highRiskPatterns?: string[], confirmAll?: boolean }
|
|
68
|
+
*/
|
|
69
|
+
export function createAuthorizer(config = {}) {
|
|
70
|
+
const enabled = config.enabled === true;
|
|
71
|
+
const caps = new Set(
|
|
72
|
+
Array.isArray(config.capabilities) && config.capabilities.length > 0
|
|
73
|
+
? config.capabilities
|
|
74
|
+
: COMPUTER_USE_CAPABILITIES,
|
|
75
|
+
);
|
|
76
|
+
const allowlist = Array.isArray(config.appAllowlist)
|
|
77
|
+
? config.appAllowlist.map((a) => String(a).toLowerCase())
|
|
78
|
+
: null; // null = no app restriction (any app), [] = deny every app
|
|
79
|
+
const highRisk = (
|
|
80
|
+
Array.isArray(config.highRiskPatterns)
|
|
81
|
+
? config.highRiskPatterns
|
|
82
|
+
: HIGH_RISK_APP_PATTERNS
|
|
83
|
+
).map((p) => String(p).toLowerCase());
|
|
84
|
+
const confirmAll = config.confirmAll === true;
|
|
85
|
+
|
|
86
|
+
const INPUT_VERBS = new Set([
|
|
87
|
+
"click",
|
|
88
|
+
"type",
|
|
89
|
+
"scroll",
|
|
90
|
+
"app_launch",
|
|
91
|
+
"clipboard_write",
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
function isHighRiskApp(app) {
|
|
95
|
+
const a = String(app || "").toLowerCase();
|
|
96
|
+
if (!a) return false;
|
|
97
|
+
return highRisk.some((p) => a.includes(p));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @returns {{ allowed:boolean, requiresConfirmation:boolean, reason?:string }}
|
|
102
|
+
*/
|
|
103
|
+
function authorize(capability, context = {}) {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
return {
|
|
106
|
+
allowed: false,
|
|
107
|
+
requiresConfirmation: false,
|
|
108
|
+
reason:
|
|
109
|
+
"computer-use is disabled — enable it explicitly (cc mcp computer-use --enable)",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!COMPUTER_USE_CAPABILITIES.includes(capability)) {
|
|
113
|
+
return {
|
|
114
|
+
allowed: false,
|
|
115
|
+
requiresConfirmation: false,
|
|
116
|
+
reason: `unknown capability: ${capability}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (!caps.has(capability)) {
|
|
120
|
+
return {
|
|
121
|
+
allowed: false,
|
|
122
|
+
requiresConfirmation: false,
|
|
123
|
+
reason: `capability "${capability}" is not enabled for this session`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const app = context.app || context.window || null;
|
|
128
|
+
|
|
129
|
+
// App allowlist applies to app-targeted verbs (input + launch). Pure
|
|
130
|
+
// observation (screenshot / window_list / clipboard_read) is not app-scoped.
|
|
131
|
+
if (allowlist && (INPUT_VERBS.has(capability) || app)) {
|
|
132
|
+
const target = String(app || context.target || "").toLowerCase();
|
|
133
|
+
if (!target) {
|
|
134
|
+
return {
|
|
135
|
+
allowed: false,
|
|
136
|
+
requiresConfirmation: false,
|
|
137
|
+
reason: `capability "${capability}" needs a target app while an allowlist is configured`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const ok = allowlist.some(
|
|
141
|
+
(a) => target.includes(a) || a.includes(target),
|
|
142
|
+
);
|
|
143
|
+
if (!ok) {
|
|
144
|
+
return {
|
|
145
|
+
allowed: false,
|
|
146
|
+
requiresConfirmation: false,
|
|
147
|
+
reason: `app "${app || context.target}" is not in the computer-use allowlist`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// High-risk apps and input verbs (or confirmAll) require confirmation.
|
|
153
|
+
const highRiskApp = isHighRiskApp(app);
|
|
154
|
+
const requiresConfirmation =
|
|
155
|
+
confirmAll || highRiskApp || INPUT_VERBS.has(capability);
|
|
156
|
+
return {
|
|
157
|
+
allowed: true,
|
|
158
|
+
requiresConfirmation,
|
|
159
|
+
reason: highRiskApp
|
|
160
|
+
? `high-risk app (${app}) — confirmation required`
|
|
161
|
+
: requiresConfirmation
|
|
162
|
+
? `${capability} injects input — confirmation required`
|
|
163
|
+
: undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
enabled,
|
|
169
|
+
capabilities: [...caps],
|
|
170
|
+
authorize,
|
|
171
|
+
isHighRiskApp,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer-use MCP server (gap-2026-07-11 P1#6) — exposes GUI control verbs to
|
|
3
|
+
* an MCP client, gated by the authorization core (authorization.js) and driven
|
|
4
|
+
* by an OS backend (control-backend.js). Streamable-HTTP MCP, same transport
|
|
5
|
+
* shape as `cc mcp serve`: POST JSON-RPC → application/json.
|
|
6
|
+
*
|
|
7
|
+
* Every tools/call runs through the authorizer first. A verb the authorizer
|
|
8
|
+
* marks `requiresConfirmation` is refused unless the caller supplied
|
|
9
|
+
* `confirm: true` in the tool args (the MCP client's approval surface / the
|
|
10
|
+
* agent's confirm flow owns the human check) — an unattended agent can never
|
|
11
|
+
* silently click a high-risk app.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import http from "node:http";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
18
|
+
import {
|
|
19
|
+
createAuthorizer,
|
|
20
|
+
COMPUTER_USE_CAPABILITIES,
|
|
21
|
+
TOOL_PRIORITY,
|
|
22
|
+
} from "./authorization.js";
|
|
23
|
+
import { createWindowsBackend } from "./control-backend.js";
|
|
24
|
+
|
|
25
|
+
const rpcResult = (id, result) =>
|
|
26
|
+
JSON.stringify({ jsonrpc: "2.0", id, result });
|
|
27
|
+
const rpcError = (id, code, message) =>
|
|
28
|
+
JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
|
|
29
|
+
const ok = (data) => ({
|
|
30
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
31
|
+
});
|
|
32
|
+
const fail = (msg) => ({
|
|
33
|
+
content: [{ type: "text", text: msg }],
|
|
34
|
+
isError: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function bearerMatches(authHeader, token) {
|
|
38
|
+
const m = /^Bearer\s+(.+)$/.exec(String(authHeader || ""));
|
|
39
|
+
if (!m) return false;
|
|
40
|
+
const a = Buffer.from(m[1]);
|
|
41
|
+
const b = Buffer.from(token);
|
|
42
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const TOOL_SCHEMAS = {
|
|
46
|
+
computer_screenshot: {
|
|
47
|
+
description: "Capture the screen to a PNG (read-only observation).",
|
|
48
|
+
inputSchema: { type: "object", properties: {} },
|
|
49
|
+
},
|
|
50
|
+
computer_window_list: {
|
|
51
|
+
description: "List visible windows (pid, app, title).",
|
|
52
|
+
inputSchema: { type: "object", properties: {} },
|
|
53
|
+
},
|
|
54
|
+
computer_clipboard_read: {
|
|
55
|
+
description: "Read the system clipboard text.",
|
|
56
|
+
inputSchema: { type: "object", properties: {} },
|
|
57
|
+
},
|
|
58
|
+
computer_clipboard_write: {
|
|
59
|
+
description: "Write text to the system clipboard.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: { text: { type: "string" } },
|
|
63
|
+
required: ["text"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
computer_click: {
|
|
67
|
+
description:
|
|
68
|
+
"Click at screen coordinates (input injection; requires confirm:true for high-risk apps).",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
x: { type: "number" },
|
|
73
|
+
y: { type: "number" },
|
|
74
|
+
app: { type: "string" },
|
|
75
|
+
confirm: { type: "boolean" },
|
|
76
|
+
},
|
|
77
|
+
required: ["x", "y"],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
computer_type: {
|
|
81
|
+
description: "Type text into the focused window (input injection).",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
text: { type: "string" },
|
|
86
|
+
app: { type: "string" },
|
|
87
|
+
confirm: { type: "boolean" },
|
|
88
|
+
},
|
|
89
|
+
required: ["text"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
computer_scroll: {
|
|
93
|
+
description: "Scroll by N notches (positive up, negative down).",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
amount: { type: "number" },
|
|
98
|
+
app: { type: "string" },
|
|
99
|
+
confirm: { type: "boolean" },
|
|
100
|
+
},
|
|
101
|
+
required: ["amount"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
computer_app_launch: {
|
|
105
|
+
description: "Launch an application by path/name.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
app: { type: "string" },
|
|
110
|
+
args: { type: "array", items: { type: "string" } },
|
|
111
|
+
confirm: { type: "boolean" },
|
|
112
|
+
},
|
|
113
|
+
required: ["app"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const TOOL_CAPABILITY = {
|
|
119
|
+
computer_screenshot: "screenshot",
|
|
120
|
+
computer_window_list: "window_list",
|
|
121
|
+
computer_clipboard_read: "clipboard_read",
|
|
122
|
+
computer_clipboard_write: "clipboard_write",
|
|
123
|
+
computer_click: "click",
|
|
124
|
+
computer_type: "type",
|
|
125
|
+
computer_scroll: "scroll",
|
|
126
|
+
computer_app_launch: "app_launch",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Execute one tool call through authorize → confirm-gate → backend.
|
|
131
|
+
* Pure orchestration (no HTTP) so it is unit-tested directly.
|
|
132
|
+
*/
|
|
133
|
+
export function handleToolCall(name, args = {}, { authorizer, backend }) {
|
|
134
|
+
const capability = TOOL_CAPABILITY[name];
|
|
135
|
+
if (!capability) return fail(`unknown tool: ${name}`);
|
|
136
|
+
|
|
137
|
+
const context = { app: args.app || null, target: args.app || null };
|
|
138
|
+
const verdict = authorizer.authorize(capability, context);
|
|
139
|
+
if (!verdict.allowed) return fail(`[computer-use denied] ${verdict.reason}`);
|
|
140
|
+
if (verdict.requiresConfirmation && args.confirm !== true) {
|
|
141
|
+
return fail(
|
|
142
|
+
`[computer-use needs confirmation] ${verdict.reason || capability} — re-call with confirm:true after the user approves`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let result;
|
|
147
|
+
switch (name) {
|
|
148
|
+
case "computer_screenshot":
|
|
149
|
+
result = backend.screenshot(
|
|
150
|
+
args.path ||
|
|
151
|
+
join(tmpdir(), `cc-screenshot-${randomBytes(4).toString("hex")}.png`),
|
|
152
|
+
);
|
|
153
|
+
break;
|
|
154
|
+
case "computer_window_list":
|
|
155
|
+
result = backend.windowList();
|
|
156
|
+
break;
|
|
157
|
+
case "computer_clipboard_read":
|
|
158
|
+
result = backend.clipboardRead();
|
|
159
|
+
break;
|
|
160
|
+
case "computer_clipboard_write":
|
|
161
|
+
result = backend.clipboardWrite(args.text);
|
|
162
|
+
break;
|
|
163
|
+
case "computer_click":
|
|
164
|
+
result = backend.click(args.x, args.y);
|
|
165
|
+
break;
|
|
166
|
+
case "computer_type":
|
|
167
|
+
result = backend.type(args.text);
|
|
168
|
+
break;
|
|
169
|
+
case "computer_scroll":
|
|
170
|
+
result = backend.scroll(args.amount);
|
|
171
|
+
break;
|
|
172
|
+
case "computer_app_launch":
|
|
173
|
+
result = backend.appLaunch(args.app, args.args || []);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
return fail(`unhandled tool: ${name}`);
|
|
177
|
+
}
|
|
178
|
+
return result.ok ? ok(result) : fail(result.error || `${name} failed`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function availableTools(authorizer) {
|
|
182
|
+
return Object.entries(TOOL_SCHEMAS)
|
|
183
|
+
.filter(([name]) => authorizer.capabilities.includes(TOOL_CAPABILITY[name]))
|
|
184
|
+
.map(([name, t]) => ({
|
|
185
|
+
name,
|
|
186
|
+
description: t.description,
|
|
187
|
+
inputSchema: t.inputSchema,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function startComputerUseServer(opts = {}) {
|
|
192
|
+
const authorizer = createAuthorizer({
|
|
193
|
+
enabled: true, // starting the server IS the explicit opt-in
|
|
194
|
+
...opts.config,
|
|
195
|
+
});
|
|
196
|
+
const backend = opts.backend || createWindowsBackend();
|
|
197
|
+
const token =
|
|
198
|
+
opts.token === false ? null : opts.token || randomBytes(16).toString("hex");
|
|
199
|
+
|
|
200
|
+
const server = http.createServer((req, res) => {
|
|
201
|
+
const send = (status, body) => {
|
|
202
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
203
|
+
res.end(body);
|
|
204
|
+
};
|
|
205
|
+
if (req.method !== "POST")
|
|
206
|
+
return send(405, rpcError(null, -32600, "POST only"));
|
|
207
|
+
if (token && !bearerMatches(req.headers.authorization, token)) {
|
|
208
|
+
return send(401, rpcError(null, -32001, "unauthorized"));
|
|
209
|
+
}
|
|
210
|
+
let raw = "";
|
|
211
|
+
let bytes = 0;
|
|
212
|
+
let aborted = false;
|
|
213
|
+
req.on("data", (c) => {
|
|
214
|
+
if (aborted) return;
|
|
215
|
+
bytes += c.length;
|
|
216
|
+
if (bytes > 1024 * 1024) {
|
|
217
|
+
aborted = true;
|
|
218
|
+
send(413, rpcError(null, -32600, "request too large"));
|
|
219
|
+
req.destroy();
|
|
220
|
+
} else raw += c;
|
|
221
|
+
});
|
|
222
|
+
req.on("end", () => {
|
|
223
|
+
if (aborted) return;
|
|
224
|
+
let msg;
|
|
225
|
+
try {
|
|
226
|
+
msg = JSON.parse(raw);
|
|
227
|
+
} catch {
|
|
228
|
+
return send(400, rpcError(null, -32700, "parse error"));
|
|
229
|
+
}
|
|
230
|
+
const { id, method, params } = msg || {};
|
|
231
|
+
if (method === "initialize") {
|
|
232
|
+
return send(
|
|
233
|
+
200,
|
|
234
|
+
rpcResult(id, {
|
|
235
|
+
protocolVersion: params?.protocolVersion || "2025-03-26",
|
|
236
|
+
capabilities: { tools: {} },
|
|
237
|
+
serverInfo: { name: "cc-computer-use", version: "1.0.0" },
|
|
238
|
+
instructions: `Tool priority (prefer the cheapest, most auditable): ${TOOL_PRIORITY.join(" > ")}. Input verbs need confirm:true.`,
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (method === "notifications/initialized") {
|
|
243
|
+
res.writeHead(202);
|
|
244
|
+
return res.end();
|
|
245
|
+
}
|
|
246
|
+
if (method === "tools/list") {
|
|
247
|
+
return send(200, rpcResult(id, { tools: availableTools(authorizer) }));
|
|
248
|
+
}
|
|
249
|
+
if (method === "tools/call") {
|
|
250
|
+
const result = handleToolCall(params?.name, params?.arguments || {}, {
|
|
251
|
+
authorizer,
|
|
252
|
+
backend,
|
|
253
|
+
});
|
|
254
|
+
return send(200, rpcResult(id, result));
|
|
255
|
+
}
|
|
256
|
+
return send(200, rpcError(id, -32601, `unknown method: ${method}`));
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return new Promise((resolve, reject) => {
|
|
261
|
+
server.once("error", reject);
|
|
262
|
+
server.listen(opts.port || 0, "127.0.0.1", () => {
|
|
263
|
+
const port = server.address().port;
|
|
264
|
+
resolve({
|
|
265
|
+
server,
|
|
266
|
+
port,
|
|
267
|
+
token,
|
|
268
|
+
url: `http://127.0.0.1:${port}`,
|
|
269
|
+
capabilities: authorizer.capabilities,
|
|
270
|
+
stop: () => new Promise((r) => server.close(() => r())),
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { COMPUTER_USE_CAPABILITIES };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer-use OS control backend. The default backend drives Windows via
|
|
3
|
+
* PowerShell + .NET (System.Windows.Forms / System.Drawing) with zero npm
|
|
4
|
+
* deps; every primitive is a small injectable `runPowerShell` call so the
|
|
5
|
+
* server logic is testable without a display. POSIX support is a documented
|
|
6
|
+
* gap (returns an unsupported error) — the Windows-first host matches the
|
|
7
|
+
* project's U-Key/hardware posture.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
export const _deps = { spawnSync, platform: () => process.platform };
|
|
13
|
+
|
|
14
|
+
/** Run a PowerShell script, returning { ok, stdout, stderr }. */
|
|
15
|
+
function runPowerShell(script, deps = _deps) {
|
|
16
|
+
const res = deps.spawnSync(
|
|
17
|
+
"powershell.exe",
|
|
18
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
19
|
+
{ encoding: "utf-8", windowsHide: true, maxBuffer: 16 * 1024 * 1024 },
|
|
20
|
+
);
|
|
21
|
+
if (res.error) return { ok: false, stdout: "", stderr: res.error.message };
|
|
22
|
+
return {
|
|
23
|
+
ok: res.status === 0,
|
|
24
|
+
stdout: String(res.stdout || ""),
|
|
25
|
+
stderr: String(res.stderr || ""),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// PowerShell script fragments kept as constants so tests can assert the backend
|
|
30
|
+
// dispatches the right one per verb without a real display.
|
|
31
|
+
export const PS = {
|
|
32
|
+
screenshot: (outPath) => `
|
|
33
|
+
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
|
|
34
|
+
$b = [System.Windows.Forms.SystemInformation]::VirtualScreen
|
|
35
|
+
$bmp = New-Object System.Drawing.Bitmap($b.Width, $b.Height)
|
|
36
|
+
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
37
|
+
$g.CopyFromScreen($b.X, $b.Y, 0, 0, $bmp.Size)
|
|
38
|
+
$bmp.Save(${JSON.stringify(outPath)}, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
39
|
+
Write-Output "$($b.Width)x$($b.Height)"`,
|
|
40
|
+
windowList: `
|
|
41
|
+
Get-Process | Where-Object { $_.MainWindowTitle } |
|
|
42
|
+
Select-Object -Property Id, ProcessName, MainWindowTitle |
|
|
43
|
+
ConvertTo-Json -Compress`,
|
|
44
|
+
clipboardRead: `Get-Clipboard -Raw`,
|
|
45
|
+
clipboardWrite: (text) =>
|
|
46
|
+
`Set-Clipboard -Value ${JSON.stringify(String(text))}`,
|
|
47
|
+
click: (x, y) => `
|
|
48
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
49
|
+
[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${Number(x)}, ${Number(y)})
|
|
50
|
+
Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern void mouse_event(int f,int dx,int dy,int d,int e);' -Name U -Namespace W
|
|
51
|
+
[W.U]::mouse_event(0x02,0,0,0,0); [W.U]::mouse_event(0x04,0,0,0,0)`,
|
|
52
|
+
type: (text) => `
|
|
53
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
54
|
+
[System.Windows.Forms.SendKeys]::SendWait(${JSON.stringify(escapeSendKeys(String(text)))})`,
|
|
55
|
+
scroll: (amount) => `
|
|
56
|
+
Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern void mouse_event(int f,int dx,int dy,int d,int e);' -Name S -Namespace W
|
|
57
|
+
[W.S]::mouse_event(0x0800,0,0,${Number(amount) * 120},0)`,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** SendKeys treats +^%~(){}[] as control chars — escape literals in braces. */
|
|
61
|
+
export function escapeSendKeys(text) {
|
|
62
|
+
return text.replace(/[+^%~(){}\[\]]/g, (ch) => `{${ch}}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createWindowsBackend(deps = _deps) {
|
|
66
|
+
const ps = (script) => runPowerShell(script, deps);
|
|
67
|
+
const unsupported = (verb) => ({
|
|
68
|
+
ok: false,
|
|
69
|
+
error: `computer-use ${verb} is only implemented on Windows in this build`,
|
|
70
|
+
});
|
|
71
|
+
const guard = (verb, fn) =>
|
|
72
|
+
deps.platform() === "win32" ? fn() : unsupported(verb);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
platform: deps.platform(),
|
|
76
|
+
screenshot(outPath) {
|
|
77
|
+
return guard("screenshot", () => {
|
|
78
|
+
const r = ps(PS.screenshot(outPath));
|
|
79
|
+
return r.ok
|
|
80
|
+
? { ok: true, path: outPath, size: r.stdout.trim() }
|
|
81
|
+
: { ok: false, error: r.stderr || "screenshot failed" };
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
windowList() {
|
|
85
|
+
return guard("window_list", () => {
|
|
86
|
+
const r = ps(PS.windowList);
|
|
87
|
+
if (!r.ok)
|
|
88
|
+
return { ok: false, error: r.stderr || "window list failed" };
|
|
89
|
+
try {
|
|
90
|
+
const parsed = r.stdout.trim() ? JSON.parse(r.stdout) : [];
|
|
91
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
windows: rows.map((w) => ({
|
|
95
|
+
pid: w.Id,
|
|
96
|
+
app: w.ProcessName,
|
|
97
|
+
title: w.MainWindowTitle,
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
return { ok: false, error: "could not parse window list" };
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
clipboardRead() {
|
|
106
|
+
return guard("clipboard_read", () => {
|
|
107
|
+
const r = ps(PS.clipboardRead);
|
|
108
|
+
return r.ok
|
|
109
|
+
? { ok: true, text: r.stdout.replace(/\r?\n$/, "") }
|
|
110
|
+
: { ok: false, error: r.stderr || "clipboard read failed" };
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
clipboardWrite(text) {
|
|
114
|
+
return guard("clipboard_write", () => {
|
|
115
|
+
const r = ps(PS.clipboardWrite(text));
|
|
116
|
+
return r.ok ? { ok: true } : { ok: false, error: r.stderr };
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
click(x, y) {
|
|
120
|
+
return guard("click", () => {
|
|
121
|
+
const r = ps(PS.click(x, y));
|
|
122
|
+
return r.ok ? { ok: true, x, y } : { ok: false, error: r.stderr };
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
type(text) {
|
|
126
|
+
return guard("type", () => {
|
|
127
|
+
const r = ps(PS.type(text));
|
|
128
|
+
return r.ok ? { ok: true } : { ok: false, error: r.stderr };
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
scroll(amount) {
|
|
132
|
+
return guard("scroll", () => {
|
|
133
|
+
const r = ps(PS.scroll(amount));
|
|
134
|
+
return r.ok ? { ok: true } : { ok: false, error: r.stderr };
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
appLaunch(app, args = []) {
|
|
138
|
+
return guard("app_launch", () => {
|
|
139
|
+
const res = deps.spawnSync(app, args, {
|
|
140
|
+
windowsHide: false,
|
|
141
|
+
detached: true,
|
|
142
|
+
stdio: "ignore",
|
|
143
|
+
});
|
|
144
|
+
return res.error
|
|
145
|
+
? { ok: false, error: res.error.message }
|
|
146
|
+
: { ok: true, app };
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|