@xopcai/xopc 0.0.250 → 0.0.252
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 +8 -5
- package/README.zh-CN.md +8 -5
- package/dist/browser-ext/dist/background.js +358 -768
- package/dist/browser-ext/manifest.json +1 -2
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-DfBjTSo9.js → activity-store-w3eq3YpP.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BN6FJXkJ.js → agent-avatar-dicebear-k039rbUz.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-C7rZH1A2.js → agent-defaults-settings-panel-D4J4oq8p.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-CgGaoo4z.js → agents-Bmth_bSQ.js} +1 -1
- package/dist/gateway/static/root/assets/{api-zxpcJfgj.js → api-2joIfbrH.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-D2gMshuS.js → app-management-settings-panel-ByJ4kiD0.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-wMu0jSKx.js → appearance-settings-BK2Jga-B.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-B3ky7cWU.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-u6EE3JV8.js → archive-plugin-DMDKtYpi.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-DbJurnv-.js → attachment-load-B764Ql3Z.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-Ew_e5HDn.js → automations-page-D-sO8CZe.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-Ds5-_-9z.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DtDFNfPM.js → binary-plugins-fxtzYRpv.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CnAwmabq.js → block-editor-Bcjtvckh.js} +50 -50
- package/dist/gateway/static/root/assets/browser-automation-inputs-BSmnusjD.js +1 -0
- package/dist/gateway/static/root/assets/browser-automations-page-DC_OQIBk.js +3 -0
- package/dist/gateway/static/root/assets/browser-settings-page-C1FojyG6.js +3 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Bs59UCut.js +7 -0
- package/dist/gateway/static/root/assets/channels-settings-CM9vNmoo.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CX-yWwLD.js → channels-status-swr-BuWjIG_a.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-CryTDNdD.js → chat-terminal-dock-5SevEo_I.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-BPZ44D-V.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-ji5aWrjG.js → cron-expression-DtsfalBM.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-Dro_w3eX.js → date-picker-tDEQV5TP.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-Ce19KKnX.js → dependency-picker-HhNwg0Y2.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DsSi-3uZ.js → desktop-pet-acU4Li-C.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-8WQ4woJy.js → desktop-pet-settings-R84AR0Pe.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CgvEBfLZ.js → extension-debug-page-CrLlRkSN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DkznBbMj.js → extension-page-BH5lmZXE.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BOjhchK2.js → extension-settings-page-BscjNAd1.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-CMNbl0FZ.js → gateway-config-swr-9HidFZUd.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-BxLcW6ij.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-CR7Xg8As.js → gateway-startup-retry-DRCPXONr.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-K5Zhl2rx.js +1 -0
- package/dist/gateway/static/root/assets/home-page-CZAg9RBv.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-C5mn73ca.js → image-generation-api-CR6Kle8H.js} +1 -1
- package/dist/gateway/static/root/assets/index-D4Ykja-U.js +72 -0
- package/dist/gateway/static/root/assets/index-zIJ3AXgR.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BYhfzsqe.js → keyboard-shortcuts-settings-B1QR49-7.js} +1 -1
- package/dist/gateway/static/root/assets/languages-5WaOu0Lf.js +2 -0
- package/dist/gateway/static/root/assets/local-app-workbench-page-wicoV-ti.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-vBlknU7C.js → local-apps-page-C74It-T8.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-CF4erhjB.js → locale-store-BfZGZKSs.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-xX-7S49A.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-Dnx6mSNe.js → management-settings-1kNnYL97.js} +1 -1
- package/dist/gateway/static/root/assets/markdown-editor-BjMTIM-m.js +1 -0
- package/dist/gateway/static/root/assets/{markdown-view-CQvzsE24.js → markdown-view-DjiwT28p.js} +2 -2
- package/dist/gateway/static/root/assets/{media-plugins-DrMYeHe7.js → media-plugins-B5c0AoS1.js} +1 -1
- package/dist/gateway/static/root/assets/messages-DaIwVWar.js +3 -0
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-AKgGy3Kx.js → mobile-pairing-wizard-CsIZqK51.js} +1 -1
- package/dist/gateway/static/root/assets/note-detail-panel-CfWxopl4.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-CYN-LCHJ.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-CX9T71by.js → page-tabs-CqJN6Ljz.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-DvEHoimN.js → preview-open-alternatives-DqRIGxf4.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-COcr_NUJ.js → product-open-page-DE2nPE_K.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B5G3LzDq.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-CuVK2mKu.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-BsRBlji7.js → registry-api-Bpy8XX-5.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-Dnd73xWt.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-CQ13P-mS.js → runtime-tools-settings-panel-Bb4b74-P.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-BUVyTpZj.js → schema-form-DoPU5j3J.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-XL4LyKZe.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-0ForzDSS.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-DVY-8_B9.js → settings-form-section-Ci-9WajJ.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CdiVYyTl.js → settings-page-CL_2F95D.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DVws6XZc.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-C88YLKm4.js → share-preview-page-XUA3oKQX.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-BwDvcacF.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BJjrATqE.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-DLir1nM0.js → skills-page-sXXAZDc9.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-BMZAfS3G.js → skills-page.utils-DD79Bl1I.js} +1 -1
- package/dist/gateway/static/root/assets/source-workspace-editor-CVWzt0oT.js +1 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-BM8ivYan.js → system-settings-panel-BEH64qBV.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-0jxMi75O.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-BOgkc_9D.js → theme-store-CISSxU1c.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-DemxChjo.js → tunnel-api-DrKDhnJm.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-CV3Ui5hu.js → use-autosave-CPYePL-Q.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-B6zLDfDl.js +2 -0
- package/dist/gateway/static/root/assets/{vendor-codemirror--gTq3YAI.js → vendor-codemirror-Qdcjid65.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-CUoCi4Sq.js → voice-api-key-field-DKqzrCXx.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-C-ryeIxj.js → voice-config-api-e9xWjwBX.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-BhDlkCMB.js → work-discovery-overlay-kTQug6bq.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-D8CcPc_C.js → workflow-run-setup-panel-m9SKj_GT.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-Bcd1XqP7.js +5 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/new-session.js +9 -0
- package/dist/src/agent/agent-manager.d.ts +2 -1
- package/dist/src/agent/agent-manager.js +2 -1
- package/dist/src/agent/coding/repository-instructions.js +2 -2
- package/dist/src/agent/coding/workspace-revision.js +2 -2
- package/dist/src/agent/commands/command-isolation.js +2 -2
- package/dist/src/agent/external-tools/composio-provider.d.ts +1 -0
- package/dist/src/agent/external-tools/composio-provider.js +64 -34
- package/dist/src/agent/external-tools/search-error.js +5 -3
- package/dist/src/agent/service.js +2 -1
- package/dist/src/agent/service.types.d.ts +2 -1
- package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.js +4 -0
- package/dist/src/agent/tool-manuals/browser.d.ts +1 -1
- package/dist/src/agent/tool-manuals/browser.js +15 -226
- package/dist/src/agent/tools/browser/tool/browser-use-tool.d.ts +4 -21
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js +48 -165
- package/dist/src/agent/tools/browser/tool/schemas.d.ts +203 -16
- package/dist/src/agent/tools/browser/tool/schemas.js +146 -16
- package/dist/src/agent/tools/browser-automation-tool.d.ts +121 -0
- package/dist/src/agent/tools/browser-automation-tool.js +197 -0
- package/dist/src/agent/tools/factory.d.ts +6 -9
- package/dist/src/agent/tools/factory.js +28 -55
- package/dist/src/agent/tools/index.d.ts +2 -2
- package/dist/src/agent/tools/index.js +3 -3
- package/dist/src/agent/tools/review-workspace.js +2 -2
- package/dist/src/auth/oauth/xopc-cloud.js +3 -1
- package/dist/src/automations/domain/defaults.js +1 -1
- package/dist/src/automations/domain/types.d.ts +5 -5
- package/dist/src/automations/domain/validation.d.ts +12 -12
- package/dist/src/automations/domain/validation.js +3 -3
- package/dist/src/automations/draft/automation-draft-validator.d.ts +3 -3
- package/dist/src/automations/draft/automation-draft-validator.js +1 -1
- package/dist/src/automations/service/action-executor.d.ts +1 -1
- package/dist/src/automations/service/action-executor.js +7 -7
- package/dist/src/browser/{recipes → automations}/index.d.ts +1 -0
- package/dist/src/browser/automations/index.js +4 -0
- package/dist/src/browser/automations/repository.d.ts +13 -0
- package/dist/src/browser/automations/repository.js +60 -0
- package/dist/src/browser/automations/runner.d.ts +17 -0
- package/dist/src/browser/automations/runner.js +173 -0
- package/dist/src/browser/automations/runtime-service.d.ts +6 -0
- package/dist/src/browser/automations/runtime-service.js +37 -0
- package/dist/src/browser/automations/schema.d.ts +144 -0
- package/dist/src/browser/automations/schema.js +135 -0
- package/dist/src/browser/automations/service.d.ts +146 -0
- package/dist/src/browser/automations/service.js +168 -0
- package/dist/src/browser/automations/types.d.ts +90 -0
- package/dist/src/browser/cache-dir-policy.d.ts +1 -1
- package/dist/src/browser/cache-dir-policy.js +1 -4
- package/dist/src/browser/drivers/browser-driver.d.ts +17 -0
- package/dist/src/browser/drivers/create-driver.d.ts +3 -0
- package/dist/src/browser/drivers/create-driver.js +84 -0
- package/dist/src/browser/drivers/extension-driver.d.ts +20 -0
- package/dist/src/browser/drivers/extension-driver.js +47 -0
- package/dist/src/browser/drivers/playwright-driver.d.ts +38 -0
- package/dist/src/browser/drivers/playwright-driver.js +406 -0
- package/dist/src/browser/index.d.ts +10 -15
- package/dist/src/browser/index.js +10 -15
- package/dist/src/browser/install-lock.d.ts +1 -2
- package/dist/src/browser/install-lock.js +3 -9
- package/dist/src/browser/install-progress.d.ts +1 -1
- package/dist/src/browser/observation/format.d.ts +2 -0
- package/dist/src/browser/observation/format.js +26 -0
- package/dist/src/browser/policy/approval-store.d.ts +17 -0
- package/dist/src/browser/policy/approval-store.js +62 -0
- package/dist/src/browser/policy/browser-policy.d.ts +3 -0
- package/dist/src/browser/policy/browser-policy.js +27 -0
- package/dist/src/browser/providers/browser-ext-install.d.ts +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +3 -5
- package/dist/src/browser/providers/browser-use.js +1 -1
- package/dist/src/browser/providers/browserbase.js +1 -1
- package/dist/src/browser/providers/extension-ws-acquire.d.ts +1 -4
- package/dist/src/browser/providers/extension-ws-acquire.js +1 -13
- package/dist/src/browser/providers/extension.d.ts +3 -22
- package/dist/src/browser/providers/extension.js +7 -15
- package/dist/src/browser/providers/index.d.ts +2 -2
- package/dist/src/browser/providers/index.js +3 -3
- package/dist/src/browser/providers/types.d.ts +0 -94
- package/dist/src/browser/readiness.d.ts +4 -22
- package/dist/src/browser/readiness.js +55 -118
- package/dist/src/browser/runtime/browser-runtime.d.ts +30 -0
- package/dist/src/browser/runtime/browser-runtime.js +362 -0
- package/dist/src/browser/url-policy.d.ts +3 -3
- package/dist/src/browser/url-policy.js +7 -6
- package/dist/src/browser/verification/expectation.d.ts +2 -0
- package/dist/src/browser/verification/expectation.js +17 -0
- package/dist/src/cli/command-loaders.js +1 -0
- package/dist/src/cli/command-manifest.js +5 -1
- package/dist/src/cli/commands/browser-cli-helpers.d.ts +0 -6
- package/dist/src/cli/commands/browser-cli-helpers.js +11 -144
- package/dist/src/cli/commands/browser.d.ts +0 -5
- package/dist/src/cli/commands/browser.js +11 -152
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/node-version.js +25 -9
- package/dist/src/cli/commands/platform.d.ts +1 -0
- package/dist/src/cli/commands/platform.js +111 -0
- package/dist/src/config/loader.js +9 -3
- package/dist/src/config/schema.d.ts +131 -84
- package/dist/src/config/schema.js +112 -59
- package/dist/src/connectors/composio-sessions.js +1 -3
- package/dist/src/extensions/marketplace.js +4 -0
- package/dist/src/files/file-service.js +139 -14
- package/dist/src/gateway/hono/lib/config-payload.d.ts +12 -26
- package/dist/src/gateway/hono/lib/config-payload.js +10 -39
- package/dist/src/gateway/hono/routes/browser-automations.d.ts +3 -0
- package/dist/src/gateway/hono/routes/browser-automations.js +77 -0
- package/dist/src/gateway/hono/routes/browser-install.js +0 -32
- package/dist/src/gateway/hono/routes/browser.d.ts +0 -17
- package/dist/src/gateway/hono/routes/browser.js +46 -568
- package/dist/src/gateway/hono/routes/config-patch/misc.js +8 -1
- package/dist/src/gateway/hono/routes/config.js +0 -11
- package/dist/src/gateway/hono/routes/lazy-bundles.js +5 -5
- package/dist/src/gateway/service.d.ts +4 -6
- package/dist/src/gateway/service.js +20 -23
- package/dist/src/platform/client.d.ts +121 -0
- package/dist/src/platform/client.js +108 -0
- package/dist/src/platform/contracts.d.ts +194 -0
- package/dist/src/platform/contracts.js +150 -0
- package/dist/src/platform/index.d.ts +4 -0
- package/dist/src/platform/index.js +9 -0
- package/dist/src/platform/resolution.d.ts +4 -0
- package/dist/src/platform/resolution.js +16 -0
- package/dist/src/platform/worker.d.ts +6 -0
- package/dist/src/platform/worker.js +75 -0
- package/dist/src/providers/xopc-cloud-config.js +3 -1
- package/dist/src/session/session-context-for-llm.js +41 -1
- package/dist/src/share/hosted-session-share.js +3 -1
- package/dist/src/share/share-thumbnail.js +1 -1
- package/dist/src/storage/sqlite/migrations/153_browser_control_v2.sql +45 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/schema.sql +16 -20
- package/dist/src/tasks/home-query-service.js +1 -1
- package/dist/src/tunnel/env.js +3 -1
- package/dist/src/voice/realtime/agentEngine.js +0 -1
- package/package.json +7 -7
- package/dist/gateway/static/root/assets/apps-page-KF1Gnv36.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-Ch-ldu35.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DtVhsHJW.js +0 -8
- package/dist/gateway/static/root/assets/browser-workflow-inputs-BX1-uM9L.js +0 -1
- package/dist/gateway/static/root/assets/browser-workflows-page-CcbUOWDF.js +0 -3
- package/dist/gateway/static/root/assets/capabilities-settings-panel--sXeylhe.js +0 -7
- package/dist/gateway/static/root/assets/channels-settings-AXSBDujZ.js +0 -1
- package/dist/gateway/static/root/assets/code-editor-D4_k-6QN.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-HBHUWohz.js +0 -2
- package/dist/gateway/static/root/assets/gateway-config-api-OEruhhX3.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-BgH3A0LO.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-C97naDkO.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BBKKme4G.js +0 -1
- package/dist/gateway/static/root/assets/html-workspace-editor-C3DKAkMc.js +0 -1
- package/dist/gateway/static/root/assets/index-BcaF-dgQ.js +0 -72
- package/dist/gateway/static/root/assets/index-DAryGvZ7.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-DNncc2tn.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-CizZInrT.js +0 -2
- package/dist/gateway/static/root/assets/markdown-editor-nDLmA_LF.js +0 -2
- package/dist/gateway/static/root/assets/markdown-split-DtBtcHWi.js +0 -1
- package/dist/gateway/static/root/assets/messages-Cf5hpZE6.js +0 -3
- package/dist/gateway/static/root/assets/note-detail-panel-Bw3jD9Jf.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-Dpqq7Z-J.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-BuT6MCTs.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DXWMxOjf.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-7rBCUWzZ.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-gmEOYNb4.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DYYoJcl7.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BhenBnUj.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-Dq7xIj0_.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-MDI9Lz6z.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-P0_RldWv.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-CxfnoRVn.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-Bxn2Dc16.js +0 -5
- package/dist/src/agent/tools/browser-recipe-tool.d.ts +0 -12
- package/dist/src/agent/tools/browser-recipe-tool.js +0 -113
- package/dist/src/browser/actions/extension-registry-bridge.d.ts +0 -5
- package/dist/src/browser/actions/extension-registry-bridge.js +0 -312
- package/dist/src/browser/actions/registry.d.ts +0 -5
- package/dist/src/browser/actions/registry.js +0 -741
- package/dist/src/browser/actions/types.d.ts +0 -65
- package/dist/src/browser/backend-from-config.d.ts +0 -22
- package/dist/src/browser/backend-from-config.js +0 -99
- package/dist/src/browser/browser-command-timeout.d.ts +0 -2
- package/dist/src/browser/browser-command-timeout.js +0 -11
- package/dist/src/browser/cdp-local-launcher.d.ts +0 -35
- package/dist/src/browser/cdp-local-launcher.js +0 -154
- package/dist/src/browser/cdp-supervisor.d.ts +0 -58
- package/dist/src/browser/cdp-supervisor.js +0 -133
- package/dist/src/browser/humanize.d.ts +0 -80
- package/dist/src/browser/humanize.js +0 -363
- package/dist/src/browser/manager.d.ts +0 -50
- package/dist/src/browser/manager.js +0 -251
- package/dist/src/browser/pipeline/output.d.ts +0 -35
- package/dist/src/browser/pipeline/output.js +0 -74
- package/dist/src/browser/pipeline/runner.d.ts +0 -27
- package/dist/src/browser/pipeline/runner.js +0 -453
- package/dist/src/browser/pipeline/schema.d.ts +0 -42
- package/dist/src/browser/pipeline/schema.js +0 -116
- package/dist/src/browser/pipeline/template.d.ts +0 -30
- package/dist/src/browser/pipeline/template.js +0 -112
- package/dist/src/browser/providers/cloakbrowser.d.ts +0 -83
- package/dist/src/browser/providers/cloakbrowser.js +0 -612
- package/dist/src/browser/recipes/index.js +0 -3
- package/dist/src/browser/recipes/repository.d.ts +0 -13
- package/dist/src/browser/recipes/repository.js +0 -81
- package/dist/src/browser/recipes/runtime-service.d.ts +0 -6
- package/dist/src/browser/recipes/runtime-service.js +0 -30
- package/dist/src/browser/recipes/service.d.ts +0 -36
- package/dist/src/browser/recipes/service.js +0 -186
- package/dist/src/browser/recipes/types.d.ts +0 -36
- package/dist/src/browser/schemas.d.ts +0 -53
- package/dist/src/browser/schemas.js +0 -68
- package/dist/src/browser/session-lifecycle.d.ts +0 -40
- package/dist/src/browser/session-lifecycle.js +0 -136
- package/dist/src/browser/snapshot-helpers.d.ts +0 -24
- package/dist/src/browser/snapshot-helpers.js +0 -70
- package/dist/src/browser/stealth.d.ts +0 -46
- package/dist/src/browser/stealth.js +0 -163
- package/dist/src/gateway/hono/routes/browser-workflows.d.ts +0 -3
- package/dist/src/gateway/hono/routes/browser-workflows.js +0 -78
- /package/dist/src/browser/{actions → automations}/types.js +0 -0
- /package/dist/src/browser/{recipes/types.js → drivers/browser-driver.js} +0 -0
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
const XOPC_EXT_PORT = 19820;
|
|
2
|
-
const XOPC_EXT_HOST = "127.0.0.1";
|
|
3
|
-
const XOPC_EXT_WS_URL = `ws://${XOPC_EXT_HOST}:${XOPC_EXT_PORT}/browser-ext`;
|
|
4
|
-
const WS_RECONNECT_BASE_DELAY = 2e3;
|
|
5
|
-
const WS_RECONNECT_MAX_DELAY = 5e3;
|
|
6
|
-
|
|
7
1
|
const LOG_COLORS = {
|
|
8
2
|
debug: "color: #888",
|
|
9
3
|
info: "color: #3b82f6",
|
|
@@ -28,32 +22,100 @@ function createLogger(prefix) {
|
|
|
28
22
|
};
|
|
29
23
|
}
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
const log$2 = createLogger("CDP");
|
|
26
|
+
const attached = /* @__PURE__ */ new Set();
|
|
27
|
+
function isDebuggableUrl(url) {
|
|
28
|
+
if (!url) return true;
|
|
29
|
+
return url.startsWith("http://") || url.startsWith("https://") || url === "about:blank" || url.startsWith("data:");
|
|
30
|
+
}
|
|
31
|
+
async function ensureAttached(tabId) {
|
|
32
32
|
try {
|
|
33
|
-
await chrome.tabs.
|
|
34
|
-
|
|
33
|
+
const tab = await chrome.tabs.get(tabId);
|
|
34
|
+
if (!isDebuggableUrl(tab.url)) {
|
|
35
|
+
attached.delete(tabId);
|
|
36
|
+
throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`);
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e;
|
|
40
|
+
attached.delete(tabId);
|
|
41
|
+
throw new Error(`Tab ${tabId} no longer exists`);
|
|
42
|
+
}
|
|
43
|
+
if (attached.has(tabId)) {
|
|
44
|
+
try {
|
|
45
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
46
|
+
expression: "1",
|
|
47
|
+
returnByValue: true
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
} catch {
|
|
51
|
+
attached.delete(tabId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const MAX_RETRIES = 3;
|
|
55
|
+
const RETRY_DELAY = 500;
|
|
56
|
+
let lastError = "";
|
|
57
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
58
|
+
try {
|
|
59
|
+
try {
|
|
60
|
+
await chrome.debugger.detach({ tabId });
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
64
|
+
attached.add(tabId);
|
|
65
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable", {});
|
|
66
|
+
await chrome.debugger.sendCommand({ tabId }, "Page.enable", {});
|
|
67
|
+
log$2.debug("Attached to tab", { tabId, attempt });
|
|
68
|
+
return;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
lastError = e instanceof Error ? e.message : String(e);
|
|
71
|
+
if (attempt < MAX_RETRIES) {
|
|
72
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
35
75
|
}
|
|
76
|
+
throw new Error(`Failed to attach debugger to tab ${tabId}: ${lastError}`);
|
|
36
77
|
}
|
|
37
|
-
async function
|
|
38
|
-
await
|
|
78
|
+
async function sendCommand(tabId, method, params) {
|
|
79
|
+
await ensureAttached(tabId);
|
|
80
|
+
return chrome.debugger.sendCommand({ tabId }, method, params ?? {});
|
|
39
81
|
}
|
|
40
|
-
async function
|
|
41
|
-
await
|
|
82
|
+
async function evaluate(tabId, expression) {
|
|
83
|
+
const result = await sendCommand(tabId, "Runtime.evaluate", {
|
|
84
|
+
expression,
|
|
85
|
+
returnByValue: true,
|
|
86
|
+
awaitPromise: true,
|
|
87
|
+
userGesture: true
|
|
88
|
+
});
|
|
89
|
+
if (result.exceptionDetails) {
|
|
90
|
+
throw new Error(`JS evaluation failed: ${result.exceptionDetails.text ?? "unknown error"}`);
|
|
91
|
+
}
|
|
92
|
+
return result.result?.value;
|
|
42
93
|
}
|
|
43
|
-
async function
|
|
44
|
-
await
|
|
94
|
+
async function captureScreenshot(tabId, options) {
|
|
95
|
+
const result = await sendCommand(tabId, "Page.captureScreenshot", {
|
|
96
|
+
format: options?.format,
|
|
97
|
+
quality: options?.quality
|
|
98
|
+
});
|
|
99
|
+
return result.data;
|
|
45
100
|
}
|
|
46
|
-
async function
|
|
47
|
-
await
|
|
101
|
+
async function dispatchInput(tabId, type, params) {
|
|
102
|
+
await sendCommand(tabId, `Input.${type}`, params);
|
|
48
103
|
}
|
|
49
|
-
|
|
50
|
-
|
|
104
|
+
chrome.debugger.onDetach.addListener((source) => {
|
|
105
|
+
if (source.tabId) attached.delete(source.tabId);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
async function sendContentMessage(tabId, message) {
|
|
109
|
+
try {
|
|
110
|
+
await chrome.tabs.sendMessage(tabId, message);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
51
113
|
}
|
|
52
|
-
async function
|
|
53
|
-
await sendContentMessage(tabId, { type: "content/
|
|
114
|
+
async function hideOverlayOnTab(tabId) {
|
|
115
|
+
await sendContentMessage(tabId, { type: "content/hide-overlay" });
|
|
54
116
|
}
|
|
55
117
|
|
|
56
|
-
const log$
|
|
118
|
+
const log$1 = createLogger("SessionManager");
|
|
57
119
|
const XOPC_TAB_GROUP_TITLE = "xopc";
|
|
58
120
|
const automationSessions = /* @__PURE__ */ new Map();
|
|
59
121
|
const WINDOW_IDLE_TIMEOUT = 12e4;
|
|
@@ -75,7 +137,7 @@ function resetWindowIdleTimer(workspace = DEFAULT_WORKSPACE) {
|
|
|
75
137
|
} catch {
|
|
76
138
|
}
|
|
77
139
|
automationSessions.delete(workspace);
|
|
78
|
-
log$
|
|
140
|
+
log$1.info("Automation tab group cleared due to idle timeout", { workspace });
|
|
79
141
|
}, WINDOW_IDLE_TIMEOUT);
|
|
80
142
|
}
|
|
81
143
|
async function waitForTabLoad(tabId, timeoutMs = 3e4) {
|
|
@@ -153,7 +215,7 @@ async function getOrCreateAutomationWindow(workspace = DEFAULT_WORKSPACE) {
|
|
|
153
215
|
};
|
|
154
216
|
automationSessions.set(workspace, session);
|
|
155
217
|
resetWindowIdleTimer(workspace);
|
|
156
|
-
log$
|
|
218
|
+
log$1.info("Created automation tab group", { workspace, windowId, groupId, tabId: tab.id });
|
|
157
219
|
return session;
|
|
158
220
|
}
|
|
159
221
|
async function getActiveTabId(workspace = DEFAULT_WORKSPACE) {
|
|
@@ -197,7 +259,7 @@ async function closeSession(workspace = DEFAULT_WORKSPACE) {
|
|
|
197
259
|
});
|
|
198
260
|
}
|
|
199
261
|
automationSessions.delete(workspace);
|
|
200
|
-
log$
|
|
262
|
+
log$1.info("Closed automation session", { workspace });
|
|
201
263
|
}
|
|
202
264
|
async function closeAllSessions() {
|
|
203
265
|
for (const workspace of [...automationSessions.keys()]) {
|
|
@@ -205,719 +267,296 @@ async function closeAllSessions() {
|
|
|
205
267
|
}
|
|
206
268
|
}
|
|
207
269
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
212
|
-
await showOverlayOnTab(tabId, "operating");
|
|
270
|
+
const observations = /* @__PURE__ */ new Map();
|
|
271
|
+
async function executeBrowserCommand(command) {
|
|
272
|
+
const startedAt = Date.now();
|
|
213
273
|
try {
|
|
214
|
-
await
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const tab = await chrome.tabs.get(tabId);
|
|
218
|
-
await hideOverlayOnTab(tabId);
|
|
219
|
-
if (tab.groupId != null) {
|
|
220
|
-
await chrome.tabGroups.update(tab.groupId, { collapsed: false }).catch(() => {
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
await chrome.tabs.update(tabId, { active: true }).catch(() => {
|
|
224
|
-
});
|
|
274
|
+
const result = await execute(command.input, command.timeoutMs, command.visualFallback, startedAt);
|
|
275
|
+
return { id: command.id, result };
|
|
276
|
+
} catch (error) {
|
|
225
277
|
return {
|
|
226
|
-
id:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
async function handleNavigate(cmd) {
|
|
236
|
-
return handleOpen(cmd);
|
|
237
|
-
}
|
|
238
|
-
async function handleReload(cmd) {
|
|
239
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
240
|
-
await showOverlayOnTab(tabId, "operating");
|
|
241
|
-
try {
|
|
242
|
-
await chrome.tabs.reload(tabId);
|
|
243
|
-
await waitForTabLoad(tabId, cmd.timeout ?? 3e4);
|
|
244
|
-
resetWindowIdleTimer();
|
|
245
|
-
const tab = await chrome.tabs.get(tabId);
|
|
246
|
-
await hideOverlayOnTab(tabId);
|
|
247
|
-
return { id: cmd.id, ok: true, data: { url: tab.url, title: tab.title } };
|
|
248
|
-
} catch (e) {
|
|
249
|
-
await hideOverlayOnTab(tabId);
|
|
250
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
async function handleBack(cmd) {
|
|
254
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
255
|
-
try {
|
|
256
|
-
await chrome.tabs.goBack(tabId);
|
|
257
|
-
await waitForTabLoad(tabId, cmd.timeout ?? 1e4);
|
|
258
|
-
const tab = await chrome.tabs.get(tabId);
|
|
259
|
-
return { id: cmd.id, ok: true, data: { url: tab.url, title: tab.title } };
|
|
260
|
-
} catch (e) {
|
|
261
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async function handleForward(cmd) {
|
|
265
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
266
|
-
try {
|
|
267
|
-
await chrome.tabs.goForward(tabId);
|
|
268
|
-
await waitForTabLoad(tabId, cmd.timeout ?? 1e4);
|
|
269
|
-
const tab = await chrome.tabs.get(tabId);
|
|
270
|
-
return { id: cmd.id, ok: true, data: { url: tab.url, title: tab.title } };
|
|
271
|
-
} catch (e) {
|
|
272
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
async function handleGetUrl(cmd) {
|
|
276
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
277
|
-
try {
|
|
278
|
-
const tab = await chrome.tabs.get(tabId);
|
|
279
|
-
return { id: cmd.id, ok: true, data: { url: tab.url } };
|
|
280
|
-
} catch (e) {
|
|
281
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
async function handleGetTitle(cmd) {
|
|
285
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
286
|
-
try {
|
|
287
|
-
const tab = await chrome.tabs.get(tabId);
|
|
288
|
-
return { id: cmd.id, ok: true, data: { title: tab.title } };
|
|
289
|
-
} catch (e) {
|
|
290
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const log$1 = createLogger("CDP");
|
|
295
|
-
const attached = /* @__PURE__ */ new Set();
|
|
296
|
-
function isDebuggableUrl(url) {
|
|
297
|
-
if (!url) return true;
|
|
298
|
-
return url.startsWith("http://") || url.startsWith("https://") || url === "about:blank" || url.startsWith("data:");
|
|
299
|
-
}
|
|
300
|
-
async function ensureAttached(tabId) {
|
|
301
|
-
try {
|
|
302
|
-
const tab = await chrome.tabs.get(tabId);
|
|
303
|
-
if (!isDebuggableUrl(tab.url)) {
|
|
304
|
-
attached.delete(tabId);
|
|
305
|
-
throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`);
|
|
306
|
-
}
|
|
307
|
-
} catch (e) {
|
|
308
|
-
if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e;
|
|
309
|
-
attached.delete(tabId);
|
|
310
|
-
throw new Error(`Tab ${tabId} no longer exists`);
|
|
311
|
-
}
|
|
312
|
-
if (attached.has(tabId)) {
|
|
313
|
-
try {
|
|
314
|
-
await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
315
|
-
expression: "1",
|
|
316
|
-
returnByValue: true
|
|
317
|
-
});
|
|
318
|
-
return;
|
|
319
|
-
} catch {
|
|
320
|
-
attached.delete(tabId);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const MAX_RETRIES = 3;
|
|
324
|
-
const RETRY_DELAY = 500;
|
|
325
|
-
let lastError = "";
|
|
326
|
-
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
327
|
-
try {
|
|
328
|
-
try {
|
|
329
|
-
await chrome.debugger.detach({ tabId });
|
|
330
|
-
} catch {
|
|
331
|
-
}
|
|
332
|
-
await chrome.debugger.attach({ tabId }, "1.3");
|
|
333
|
-
attached.add(tabId);
|
|
334
|
-
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable", {});
|
|
335
|
-
await chrome.debugger.sendCommand({ tabId }, "Page.enable", {});
|
|
336
|
-
log$1.debug("Attached to tab", { tabId, attempt });
|
|
337
|
-
return;
|
|
338
|
-
} catch (e) {
|
|
339
|
-
lastError = e instanceof Error ? e.message : String(e);
|
|
340
|
-
if (attempt < MAX_RETRIES) {
|
|
341
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY));
|
|
278
|
+
id: command.id,
|
|
279
|
+
result: {
|
|
280
|
+
ok: false,
|
|
281
|
+
error: {
|
|
282
|
+
code: /timeout/i.test(String(error)) ? "TIMEOUT" : "DRIVER_UNAVAILABLE",
|
|
283
|
+
message: error instanceof Error ? error.message : String(error)
|
|
284
|
+
}
|
|
342
285
|
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
throw new Error(`Failed to attach debugger to tab ${tabId}: ${lastError}`);
|
|
346
|
-
}
|
|
347
|
-
async function sendCommand(tabId, method, params) {
|
|
348
|
-
await ensureAttached(tabId);
|
|
349
|
-
return chrome.debugger.sendCommand({ tabId }, method, params ?? {});
|
|
350
|
-
}
|
|
351
|
-
async function evaluate(tabId, expression) {
|
|
352
|
-
const result = await sendCommand(tabId, "Runtime.evaluate", {
|
|
353
|
-
expression,
|
|
354
|
-
returnByValue: true,
|
|
355
|
-
awaitPromise: true,
|
|
356
|
-
userGesture: true
|
|
357
|
-
});
|
|
358
|
-
if (result.exceptionDetails) {
|
|
359
|
-
throw new Error(`JS evaluation failed: ${result.exceptionDetails.text ?? "unknown error"}`);
|
|
286
|
+
};
|
|
360
287
|
}
|
|
361
|
-
return result.result?.value;
|
|
362
288
|
}
|
|
363
|
-
async function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
289
|
+
async function execute(input, timeoutMs, visualFallback, startedAt) {
|
|
290
|
+
const sessionId = input.sessionId;
|
|
291
|
+
if (!sessionId) return fail("INVALID_INPUT", "Extension actions require a session id.");
|
|
292
|
+
if (input.action === "close") {
|
|
293
|
+
await closeSession(sessionId);
|
|
294
|
+
observations.delete(sessionId);
|
|
295
|
+
return success("close", "read", startedAt);
|
|
296
|
+
}
|
|
297
|
+
if (input.action === "observe") {
|
|
298
|
+
const mode = input.visual === "always" ? "always" : input.visual === "never" || !visualFallback ? "never" : "auto";
|
|
299
|
+
const observation2 = await observe(sessionId, mode);
|
|
300
|
+
return success("observe", "read", startedAt, observation2);
|
|
301
|
+
}
|
|
302
|
+
if (input.action === "navigate") {
|
|
303
|
+
const tabId2 = await getActiveTabId(sessionId);
|
|
304
|
+
await chrome.tabs.update(tabId2, { url: input.url });
|
|
305
|
+
await waitForTabLoad(tabId2, timeoutMs);
|
|
306
|
+
resetWindowIdleTimer(sessionId);
|
|
307
|
+
const observation2 = await observe(sessionId, visualFallback ? "auto" : "never");
|
|
308
|
+
return verified(input, observation2, startedAt, "read");
|
|
309
|
+
}
|
|
310
|
+
if (input.action === "tabs") return tabs(sessionId, input, startedAt, timeoutMs);
|
|
311
|
+
if (input.action === "sequence") return fail("INVALID_INPUT", "Sequences are expanded by the browser runtime.");
|
|
312
|
+
const current = observations.get(sessionId);
|
|
313
|
+
if (!current || current.revision !== input.revision) {
|
|
314
|
+
return fail("STALE_OBSERVATION", "The page changed. Observe it again.", await observe(sessionId, "never"));
|
|
315
|
+
}
|
|
316
|
+
const tabId = await getActiveTabId(sessionId);
|
|
317
|
+
const ref = "ref" in input ? input.ref : void 0;
|
|
318
|
+
if (ref && !current.nodes.some((node) => node.ref === ref)) {
|
|
319
|
+
return fail("TARGET_NOT_FOUND", `Target ${ref} was not found.`);
|
|
320
|
+
}
|
|
321
|
+
if (input.action === "click") {
|
|
322
|
+
const point = await evaluateRef(tabId, input.ref, `(element) => {
|
|
323
|
+
const rect = element.getBoundingClientRect();
|
|
324
|
+
return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
325
|
+
}`);
|
|
326
|
+
if (!point) return fail("TARGET_NOT_FOUND", `Target ${input.ref} was not found.`);
|
|
327
|
+
const coordinates = point;
|
|
328
|
+
await dispatchInput(tabId, "dispatchMouseEvent", { type: "mousePressed", ...coordinates, button: "left", clickCount: 1 });
|
|
329
|
+
await dispatchInput(tabId, "dispatchMouseEvent", { type: "mouseReleased", ...coordinates, button: "left", clickCount: 1 });
|
|
330
|
+
} else if (input.action === "fill") {
|
|
331
|
+
const changed = await evaluateRef(tabId, input.ref, `(element, value) => {
|
|
332
|
+
element.focus();
|
|
333
|
+
const prototype = element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
334
|
+
const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
|
|
335
|
+
setter?.call(element, value);
|
|
336
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
337
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
338
|
+
return true;
|
|
339
|
+
}`, input.value);
|
|
340
|
+
if (!changed) return fail("TARGET_NOT_FOUND", `Target ${input.ref} was not found.`);
|
|
341
|
+
if (input.submit) await pressKey(tabId, "Enter");
|
|
342
|
+
} else if (input.action === "select") {
|
|
343
|
+
const changed = await evaluateRef(tabId, input.ref, `(element, value) => {
|
|
344
|
+
element.value = value;
|
|
345
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
346
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
347
|
+
return true;
|
|
348
|
+
}`, input.value);
|
|
349
|
+
if (!changed) return fail("TARGET_NOT_FOUND", `Target ${input.ref} was not found.`);
|
|
350
|
+
} else if (input.action === "press") {
|
|
351
|
+
if (input.ref) await evaluateRef(tabId, input.ref, "(element) => { element.focus(); return true; }");
|
|
352
|
+
await pressKey(tabId, input.key);
|
|
353
|
+
} else if (input.action === "scroll") {
|
|
354
|
+
if (input.ref) {
|
|
355
|
+
await evaluateRef(tabId, input.ref, "(element, deltaY) => { element.scrollBy({ top: deltaY }); return true; }", input.deltaY);
|
|
356
|
+
} else {
|
|
357
|
+
await dispatchInput(tabId, "dispatchMouseEvent", { type: "mouseWheel", x: 640, y: 360, deltaX: 0, deltaY: input.deltaY });
|
|
373
358
|
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
return result.data;
|
|
383
|
-
}
|
|
384
|
-
async function dispatchInput(tabId, type, params) {
|
|
385
|
-
await sendCommand(tabId, `Input.${type}`, params);
|
|
386
|
-
}
|
|
387
|
-
chrome.debugger.onDetach.addListener((source) => {
|
|
388
|
-
if (source.tabId) attached.delete(source.tabId);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
async function handleMouseMove(cmd) {
|
|
392
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
393
|
-
const x = cmd.args?.x ?? 0;
|
|
394
|
-
const y = cmd.args?.y ?? 0;
|
|
395
|
-
try {
|
|
396
|
-
await dispatchInput(tabId, "dispatchMouseEvent", {
|
|
397
|
-
type: "mouseMoved",
|
|
398
|
-
x,
|
|
399
|
-
y
|
|
400
|
-
});
|
|
401
|
-
return { id: cmd.id, ok: true, data: { x, y } };
|
|
402
|
-
} catch (e) {
|
|
403
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
async function handleMouseClick(cmd) {
|
|
407
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
408
|
-
const x = cmd.args?.x ?? 0;
|
|
409
|
-
const y = cmd.args?.y ?? 0;
|
|
410
|
-
const button = cmd.args?.button ?? "left";
|
|
411
|
-
const clickCount = cmd.args?.clickCount ?? 1;
|
|
412
|
-
try {
|
|
413
|
-
await dispatchInput(tabId, "dispatchMouseEvent", {
|
|
414
|
-
type: "mousePressed",
|
|
415
|
-
x,
|
|
416
|
-
y,
|
|
417
|
-
button,
|
|
418
|
-
clickCount
|
|
419
|
-
});
|
|
420
|
-
await dispatchInput(tabId, "dispatchMouseEvent", {
|
|
421
|
-
type: "mouseReleased",
|
|
422
|
-
x,
|
|
423
|
-
y,
|
|
424
|
-
button,
|
|
425
|
-
clickCount
|
|
426
|
-
});
|
|
427
|
-
await showClickRippleOnTab(tabId, x, y);
|
|
428
|
-
return { id: cmd.id, ok: true, data: { x, y, button } };
|
|
429
|
-
} catch (e) {
|
|
430
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async function handleMouseWheel(cmd) {
|
|
434
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
435
|
-
const x = cmd.args?.x ?? 0;
|
|
436
|
-
const y = cmd.args?.y ?? 0;
|
|
437
|
-
const deltaX = cmd.args?.deltaX ?? 0;
|
|
438
|
-
const deltaY = cmd.args?.deltaY ?? 0;
|
|
439
|
-
try {
|
|
440
|
-
await dispatchInput(tabId, "dispatchMouseEvent", {
|
|
441
|
-
type: "mouseWheel",
|
|
442
|
-
x,
|
|
443
|
-
y,
|
|
444
|
-
deltaX,
|
|
445
|
-
deltaY
|
|
446
|
-
});
|
|
447
|
-
return { id: cmd.id, ok: true, data: { deltaX, deltaY } };
|
|
448
|
-
} catch (e) {
|
|
449
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
async function handleKeyboardPress(cmd) {
|
|
453
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
454
|
-
const key = cmd.args?.key;
|
|
455
|
-
if (!key) return { id: cmd.id, ok: false, error: "Missing required arg: key" };
|
|
456
|
-
try {
|
|
457
|
-
await dispatchInput(tabId, "dispatchKeyEvent", {
|
|
458
|
-
type: "keyDown",
|
|
459
|
-
key,
|
|
460
|
-
windowsVirtualKeyCode: key.length === 1 ? key.charCodeAt(0) : 0
|
|
461
|
-
});
|
|
462
|
-
await dispatchInput(tabId, "dispatchKeyEvent", {
|
|
463
|
-
type: "keyUp",
|
|
464
|
-
key,
|
|
465
|
-
windowsVirtualKeyCode: key.length === 1 ? key.charCodeAt(0) : 0
|
|
359
|
+
} else if (input.action === "wait") {
|
|
360
|
+
await wait(tabId, input, timeoutMs);
|
|
361
|
+
} else if (input.action === "upload") {
|
|
362
|
+
const document = await sendCommand(tabId, "DOM.getDocument", { depth: 1 });
|
|
363
|
+
const query = await sendCommand(tabId, "DOM.querySelectorAll", {
|
|
364
|
+
nodeId: document.root.nodeId,
|
|
365
|
+
selector: `[data-xopc-ref="${input.ref}"]`
|
|
466
366
|
});
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const text = cmd.args?.text;
|
|
492
|
-
if (!text) return { id: cmd.id, ok: false, error: "Missing required arg: text" };
|
|
493
|
-
try {
|
|
494
|
-
if (selector) {
|
|
495
|
-
await evaluate(tabId, `document.querySelector('${selector}')?.focus()`);
|
|
496
|
-
await showInputFlashOnTab(tabId, selector);
|
|
497
|
-
}
|
|
498
|
-
await sendCommand(tabId, "Input.insertText", { text });
|
|
499
|
-
if (cmd.args?.pressEnter) {
|
|
500
|
-
await dispatchInput(tabId, "dispatchKeyEvent", {
|
|
501
|
-
type: "keyDown",
|
|
502
|
-
key: "Enter",
|
|
503
|
-
code: "Enter",
|
|
504
|
-
windowsVirtualKeyCode: 13
|
|
505
|
-
});
|
|
506
|
-
await dispatchInput(tabId, "dispatchKeyEvent", {
|
|
507
|
-
type: "keyUp",
|
|
508
|
-
key: "Enter",
|
|
509
|
-
code: "Enter",
|
|
510
|
-
windowsVirtualKeyCode: 13
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
return { id: cmd.id, ok: true, data: { text, selector } };
|
|
514
|
-
} catch (e) {
|
|
515
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async function handleClick(cmd) {
|
|
520
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
521
|
-
const selector = cmd.args?.selector;
|
|
522
|
-
const text = cmd.args?.text;
|
|
523
|
-
try {
|
|
524
|
-
let x, y;
|
|
525
|
-
if (selector) {
|
|
526
|
-
await showHoverHighlightOnTab(tabId, selector);
|
|
527
|
-
const box = await evaluate(tabId, `(() => {
|
|
528
|
-
const el = document.querySelector('${selector}');
|
|
529
|
-
if (!el) return null;
|
|
530
|
-
const r = el.getBoundingClientRect();
|
|
531
|
-
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
532
|
-
})()`);
|
|
533
|
-
if (!box || typeof box !== "object") {
|
|
534
|
-
return { id: cmd.id, ok: false, error: `Element not found: ${selector}` };
|
|
535
|
-
}
|
|
536
|
-
const coords = box;
|
|
537
|
-
x = coords.x;
|
|
538
|
-
y = coords.y;
|
|
539
|
-
} else if (text) {
|
|
540
|
-
const box = await evaluate(tabId, `(() => {
|
|
541
|
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
542
|
-
while (walker.nextNode()) {
|
|
543
|
-
if (walker.currentNode.textContent?.trim().includes('${text.replace(/'/g, "\\'")}')) {
|
|
544
|
-
const el = walker.currentNode.parentElement;
|
|
545
|
-
if (el) { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return null;
|
|
549
|
-
})()`);
|
|
550
|
-
if (!box || typeof box !== "object") {
|
|
551
|
-
return { id: cmd.id, ok: false, error: `Element with text not found: ${text}` };
|
|
367
|
+
if (query.nodeIds.length !== 1) return fail("TARGET_NOT_FOUND", `Target ${input.ref} was not uniquely found.`);
|
|
368
|
+
await sendCommand(tabId, "DOM.setFileInputFiles", { nodeId: query.nodeIds[0], files: input.paths });
|
|
369
|
+
}
|
|
370
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
371
|
+
const observation = await observe(sessionId, visualFallback ? "auto" : "never");
|
|
372
|
+
return verified(input, observation, startedAt, "draft");
|
|
373
|
+
}
|
|
374
|
+
async function observe(sessionId, visual) {
|
|
375
|
+
const tabId = await getActiveTabId(sessionId);
|
|
376
|
+
const tab = await chrome.tabs.get(tabId);
|
|
377
|
+
const raw = await evaluate(tabId, `(() => {
|
|
378
|
+
const selector = 'a[href],button,input:not([type="hidden"]),textarea,select,summary,[contenteditable="true"],[role="button"],[role="link"],[role="checkbox"],[role="radio"],[role="combobox"],[role="menuitem"],[role="option"],[role="tab"],[tabindex]:not([tabindex="-1"])';
|
|
379
|
+
globalThis.__xopcRefCounter = globalThis.__xopcRefCounter || 0;
|
|
380
|
+
const nodes = [];
|
|
381
|
+
const seenRefs = new Set();
|
|
382
|
+
for (const element of Array.from(document.querySelectorAll(selector)).slice(0, 1500)) {
|
|
383
|
+
const rect = element.getBoundingClientRect();
|
|
384
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
385
|
+
const style = getComputedStyle(element);
|
|
386
|
+
if (style.visibility === 'hidden' || style.display === 'none') continue;
|
|
387
|
+
let ref = element.getAttribute('data-xopc-ref');
|
|
388
|
+
if (!/^xopc-ed+$/.test(ref || '') || seenRefs.has(ref)) {
|
|
389
|
+
ref = 'xopc-e' + (++globalThis.__xopcRefCounter);
|
|
390
|
+
element.setAttribute('data-xopc-ref', ref);
|
|
552
391
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
392
|
+
seenRefs.add(ref);
|
|
393
|
+
const tag = element.tagName.toLowerCase();
|
|
394
|
+
const role = element.getAttribute('role') || (tag === 'a' ? 'link' : tag === 'button' || tag === 'summary' ? 'button' : tag === 'select' ? 'combobox' : tag === 'input' && element.type === 'checkbox' ? 'checkbox' : tag === 'input' && element.type === 'radio' ? 'radio' : 'textbox');
|
|
395
|
+
const label = element.labels?.[0]?.innerText || '';
|
|
396
|
+
const name = (element.getAttribute('aria-label') || label || element.getAttribute('alt') || element.getAttribute('placeholder') || element.innerText || element.getAttribute('title') || '').trim().replace(/\\s+/g, ' ').slice(0, 240);
|
|
397
|
+
const states = [];
|
|
398
|
+
const autocomplete = (element.getAttribute('autocomplete') || '').toLowerCase();
|
|
399
|
+
const sensitive = element.type === 'password' || /^(cc-|one-time-code$|transaction-)/.test(autocomplete);
|
|
400
|
+
if (element.disabled) states.push('disabled');
|
|
401
|
+
if (element.checked) states.push('checked');
|
|
402
|
+
if (document.activeElement === element) states.push('focused');
|
|
403
|
+
if (element.getAttribute('aria-expanded') === 'true') states.push('expanded');
|
|
404
|
+
if (sensitive) states.push('sensitive');
|
|
405
|
+
if ((tag === 'button' || tag === 'input') && element.type === 'submit') states.push('submit');
|
|
406
|
+
nodes.push({ ref, role, name, value: !sensitive && 'value' in element ? String(element.value || '').slice(0, 500) : undefined, states, bounds: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
|
|
407
|
+
if (nodes.length >= 500) break;
|
|
561
408
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return { id: cmd.id, ok: true, data: { deltaY } };
|
|
583
|
-
} catch (e) {
|
|
584
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
async function handleEvaluate(cmd) {
|
|
588
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
589
|
-
const expression = cmd.args?.expression;
|
|
590
|
-
if (!expression) return { id: cmd.id, ok: false, error: "Missing required arg: expression" };
|
|
591
|
-
try {
|
|
592
|
-
const result = await evaluate(tabId, expression);
|
|
593
|
-
return { id: cmd.id, ok: true, data: { result } };
|
|
594
|
-
} catch (e) {
|
|
595
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
596
|
-
}
|
|
409
|
+
return { marker: location.href + '|' + performance.timeOrigin, nodes, hasVisualSurface: !!document.querySelector('canvas,embed[type="application/pdf"],object[type="application/pdf"]') };
|
|
410
|
+
})()`);
|
|
411
|
+
const prior = observations.get(sessionId);
|
|
412
|
+
const state = prior?.documentMarker === raw.marker ? { ...prior, revision: prior.revision + 1, nodes: raw.nodes } : { documentMarker: raw.marker, documentId: crypto.randomUUID(), revision: 1, nodes: raw.nodes };
|
|
413
|
+
const changes = diff(prior?.documentMarker === raw.marker ? prior.nodes : [], raw.nodes);
|
|
414
|
+
observations.set(sessionId, state);
|
|
415
|
+
const capture = visual === "always" || visual === "auto" && (raw.nodes.length === 0 || raw.hasVisualSurface);
|
|
416
|
+
const image = capture ? await captureScreenshot(tabId, { format: "jpeg", quality: 70}) : void 0;
|
|
417
|
+
return {
|
|
418
|
+
sessionId,
|
|
419
|
+
tabId: String(tabId),
|
|
420
|
+
revision: state.revision,
|
|
421
|
+
documentId: state.documentId,
|
|
422
|
+
url: tab.url ?? "",
|
|
423
|
+
title: tab.title ?? "",
|
|
424
|
+
focused: raw.nodes.find((node) => node.states.includes("focused"))?.ref,
|
|
425
|
+
nodes: raw.nodes,
|
|
426
|
+
changes,
|
|
427
|
+
visual: image ? { mimeType: "image/jpeg", data: image } : void 0
|
|
428
|
+
};
|
|
597
429
|
}
|
|
598
|
-
async function
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
430
|
+
async function tabs(sessionId, input, startedAt, timeoutMs) {
|
|
431
|
+
const session = await getOrCreateAutomationWindow(sessionId);
|
|
432
|
+
if (input.operation === "create") {
|
|
433
|
+
const created = await addTabToAutomationGroup(input.url ?? "about:blank", sessionId);
|
|
434
|
+
session.activeTabId = created.tabId;
|
|
435
|
+
if (input.url) await waitForTabLoad(created.tabId, timeoutMs);
|
|
436
|
+
} else if (input.operation === "activate") {
|
|
437
|
+
const tabId = Number(input.tabId);
|
|
438
|
+
if (!Number.isInteger(tabId) || !session.tabIds.includes(tabId)) return fail("TARGET_NOT_FOUND", `Tab ${input.tabId ?? ""} was not found.`);
|
|
439
|
+
session.activeTabId = tabId;
|
|
440
|
+
await chrome.tabs.update(tabId, { active: true });
|
|
441
|
+
} else if (input.operation === "close") {
|
|
442
|
+
const tabId = Number(input.tabId);
|
|
443
|
+
if (!Number.isInteger(tabId) || !session.tabIds.includes(tabId)) return fail("TARGET_NOT_FOUND", `Tab ${input.tabId ?? ""} was not found.`);
|
|
444
|
+
await chrome.tabs.remove(tabId);
|
|
445
|
+
session.tabIds = session.tabIds.filter((id) => id !== tabId);
|
|
446
|
+
session.activeTabId = session.tabIds[0] ?? null;
|
|
447
|
+
}
|
|
448
|
+
const currentTabs = await chrome.tabs.query({ groupId: session.groupId });
|
|
449
|
+
return {
|
|
450
|
+
ok: true,
|
|
451
|
+
receipt: {
|
|
452
|
+
action: "tabs",
|
|
453
|
+
risk: "read",
|
|
454
|
+
durationMs: Date.now() - startedAt,
|
|
455
|
+
verified: true,
|
|
456
|
+
tabs: currentTabs.map((tab) => ({ id: String(tab.id), url: tab.url ?? "", title: tab.title ?? "", active: tab.id === session.activeTabId }))
|
|
613
457
|
}
|
|
614
|
-
|
|
615
|
-
} catch (e) {
|
|
616
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
async function handleQuerySelector(cmd) {
|
|
620
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
621
|
-
const selector = cmd.args?.selector;
|
|
622
|
-
if (!selector) return { id: cmd.id, ok: false, error: "Missing required arg: selector" };
|
|
623
|
-
try {
|
|
624
|
-
const result = await evaluate(tabId, `(() => {
|
|
625
|
-
const el = document.querySelector('${selector}');
|
|
626
|
-
if (!el) return null;
|
|
627
|
-
const r = el.getBoundingClientRect();
|
|
628
|
-
return { tag: el.tagName.toLowerCase(), text: el.textContent?.slice(0, 200), rect: { x: r.x, y: r.y, width: r.width, height: r.height } };
|
|
629
|
-
})()`);
|
|
630
|
-
return { id: cmd.id, ok: true, data: result };
|
|
631
|
-
} catch (e) {
|
|
632
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
async function handleState(cmd) {
|
|
637
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
638
|
-
try {
|
|
639
|
-
const tab = await chrome.tabs.get(tabId);
|
|
640
|
-
return {
|
|
641
|
-
id: cmd.id,
|
|
642
|
-
ok: true,
|
|
643
|
-
data: { url: tab.url, title: tab.title, tabId, status: tab.status }
|
|
644
|
-
};
|
|
645
|
-
} catch (e) {
|
|
646
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
async function handleSnapshot(cmd) {
|
|
650
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
651
|
-
try {
|
|
652
|
-
const accessibility = await sendCommand(tabId, "Accessibility.getFullAXTree", {});
|
|
653
|
-
const nodes = accessibility.nodes?.slice(0, 200).map((n) => ({
|
|
654
|
-
role: n.role?.value,
|
|
655
|
-
name: n.name?.value
|
|
656
|
-
})).filter((n) => n.role && n.name) ?? [];
|
|
657
|
-
const tab = await chrome.tabs.get(tabId);
|
|
658
|
-
return {
|
|
659
|
-
id: cmd.id,
|
|
660
|
-
ok: true,
|
|
661
|
-
data: { url: tab.url, title: tab.title, nodes, nodeCount: nodes.length }
|
|
662
|
-
};
|
|
663
|
-
} catch (e) {
|
|
664
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
async function handleScreenshot(cmd) {
|
|
668
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
669
|
-
const fullPage = cmd.args?.fullPage ?? cmd.args?.full_page ?? false;
|
|
670
|
-
try {
|
|
671
|
-
const base64 = await captureScreenshot(tabId, {
|
|
672
|
-
format: "png",
|
|
673
|
-
fullPage
|
|
674
|
-
});
|
|
675
|
-
return { id: cmd.id, ok: true, data: { base64, format: "png", fullPage } };
|
|
676
|
-
} catch (e) {
|
|
677
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
async function handleGetViewportSize(cmd) {
|
|
681
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
682
|
-
try {
|
|
683
|
-
const size = await evaluate(tabId, `({ width: window.innerWidth, height: window.innerHeight })`);
|
|
684
|
-
return { id: cmd.id, ok: true, data: size };
|
|
685
|
-
} catch (e) {
|
|
686
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
async function handleSetViewportSize(cmd) {
|
|
690
|
-
const tabId = cmd.tabId ?? await getActiveTabId();
|
|
691
|
-
const width = cmd.args?.width ?? 1280;
|
|
692
|
-
const height = cmd.args?.height ?? 720;
|
|
693
|
-
try {
|
|
694
|
-
await sendCommand(tabId, "Emulation.setDeviceMetricsOverride", {
|
|
695
|
-
width,
|
|
696
|
-
height,
|
|
697
|
-
deviceScaleFactor: 1,
|
|
698
|
-
mobile: false
|
|
699
|
-
});
|
|
700
|
-
return { id: cmd.id, ok: true, data: { width, height } };
|
|
701
|
-
} catch (e) {
|
|
702
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
async function handleClose(cmd) {
|
|
706
|
-
try {
|
|
707
|
-
await closeSession();
|
|
708
|
-
return { id: cmd.id, ok: true, data: { closed: true } };
|
|
709
|
-
} catch (e) {
|
|
710
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
async function handleNewTab(cmd) {
|
|
714
|
-
try {
|
|
715
|
-
const url = cmd.args?.url ?? "about:blank";
|
|
716
|
-
const { tabId } = await addTabToAutomationGroup(url);
|
|
717
|
-
return { id: cmd.id, ok: true, data: { tabId, url } };
|
|
718
|
-
} catch (e) {
|
|
719
|
-
return { id: cmd.id, ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
720
|
-
}
|
|
458
|
+
};
|
|
721
459
|
}
|
|
722
|
-
async function
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
460
|
+
async function wait(tabId, input, defaultTimeout) {
|
|
461
|
+
const timeout = input.timeoutMs ?? defaultTimeout;
|
|
462
|
+
const deadline = Date.now() + timeout;
|
|
463
|
+
while (Date.now() < deadline) {
|
|
464
|
+
if (input.condition === "page_idle") {
|
|
465
|
+
const tab = await chrome.tabs.get(tabId);
|
|
466
|
+
if (tab.status === "complete") return;
|
|
467
|
+
} else if (input.condition === "text") {
|
|
468
|
+
const found = await evaluate(tabId, `document.body?.innerText.includes(${JSON.stringify(input.value ?? "")})`);
|
|
469
|
+
if (found) return;
|
|
470
|
+
} else if (input.ref) {
|
|
471
|
+
const visible = await evaluateRef(tabId, input.ref, `(element) => {
|
|
472
|
+
const rect = element.getBoundingClientRect();
|
|
473
|
+
const style = getComputedStyle(element);
|
|
474
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
|
|
475
|
+
}`);
|
|
476
|
+
if (input.condition === "visible" && visible || input.condition === "hidden" && !visible) return;
|
|
727
477
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
478
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
479
|
+
}
|
|
480
|
+
throw new Error(`Timed out waiting for ${input.condition}`);
|
|
481
|
+
}
|
|
482
|
+
async function evaluateRef(tabId, ref, functionBody, value) {
|
|
483
|
+
const expression = `(() => {
|
|
484
|
+
const matches = document.querySelectorAll('[data-xopc-ref="' + ${JSON.stringify(ref)} + '"]');
|
|
485
|
+
if (matches.length !== 1) return null;
|
|
486
|
+
const element = matches[0];
|
|
487
|
+
return (${functionBody})(element, ${JSON.stringify(value)});
|
|
488
|
+
})()`;
|
|
489
|
+
return evaluate(tabId, expression);
|
|
490
|
+
}
|
|
491
|
+
async function pressKey(tabId, key) {
|
|
492
|
+
await dispatchInput(tabId, "dispatchKeyEvent", { type: "keyDown", key });
|
|
493
|
+
await dispatchInput(tabId, "dispatchKeyEvent", { type: "keyUp", key });
|
|
494
|
+
}
|
|
495
|
+
function verified(input, observation, startedAt, risk) {
|
|
496
|
+
const expectation = "expect" in input ? input.expect : void 0;
|
|
497
|
+
const pageText = observation.nodes.map((node) => `${node.name}
|
|
498
|
+
${node.value ?? ""}`).join("\n");
|
|
499
|
+
const failure = expectation?.urlIncludes && !observation.url.includes(expectation.urlIncludes) ? `URL does not include ${expectation.urlIncludes}` : expectation?.titleIncludes && !observation.title.includes(expectation.titleIncludes) ? `Title does not include ${expectation.titleIncludes}` : expectation?.textIncludes && !pageText.includes(expectation.textIncludes) ? `Page does not include ${expectation.textIncludes}` : null;
|
|
500
|
+
return failure ? fail("EXPECTATION_FAILED", failure, observation) : success(input.action, risk, startedAt, observation);
|
|
501
|
+
}
|
|
502
|
+
function success(action, risk, startedAt, observation) {
|
|
503
|
+
return { ok: true, receipt: { action, risk, durationMs: Date.now() - startedAt, verified: true, observation } };
|
|
504
|
+
}
|
|
505
|
+
function fail(code, message, observation) {
|
|
506
|
+
return { ok: false, error: { code, message, observation } };
|
|
507
|
+
}
|
|
508
|
+
function diff(previous, next) {
|
|
509
|
+
const before = new Map(previous.map((node) => [node.ref, node]));
|
|
510
|
+
const after = new Map(next.map((node) => [node.ref, node]));
|
|
511
|
+
return {
|
|
512
|
+
added: next.filter((node) => !before.has(node.ref)),
|
|
513
|
+
changed: next.filter((node) => before.has(node.ref) && JSON.stringify(before.get(node.ref)) !== JSON.stringify(node)),
|
|
514
|
+
removed: previous.filter((node) => !after.has(node.ref)).map((node) => node.ref)
|
|
515
|
+
};
|
|
762
516
|
}
|
|
763
517
|
|
|
518
|
+
const XOPC_EXT_PORT = 19820;
|
|
519
|
+
const XOPC_EXT_HOST = "127.0.0.1";
|
|
520
|
+
const XOPC_EXT_WS_URL = `ws://${XOPC_EXT_HOST}:${XOPC_EXT_PORT}/browser-ext`;
|
|
521
|
+
const WS_RECONNECT_BASE_DELAY = 2e3;
|
|
522
|
+
const WS_RECONNECT_MAX_DELAY = 5e3;
|
|
523
|
+
|
|
764
524
|
const log = createLogger("Background");
|
|
765
525
|
let ws = null;
|
|
766
526
|
let reconnectTimer = null;
|
|
767
527
|
let reconnectDelay = WS_RECONNECT_BASE_DELAY;
|
|
768
528
|
let intentionalClose = false;
|
|
769
|
-
async function dispatchCommand(cmd) {
|
|
770
|
-
const start = Date.now();
|
|
771
|
-
try {
|
|
772
|
-
let result;
|
|
773
|
-
switch (cmd.action) {
|
|
774
|
-
// Navigation
|
|
775
|
-
case "open":
|
|
776
|
-
result = await handleOpen(cmd);
|
|
777
|
-
break;
|
|
778
|
-
case "navigate":
|
|
779
|
-
result = await handleNavigate(cmd);
|
|
780
|
-
break;
|
|
781
|
-
case "reload":
|
|
782
|
-
result = await handleReload(cmd);
|
|
783
|
-
break;
|
|
784
|
-
case "back":
|
|
785
|
-
result = await handleBack(cmd);
|
|
786
|
-
break;
|
|
787
|
-
case "forward":
|
|
788
|
-
result = await handleForward(cmd);
|
|
789
|
-
break;
|
|
790
|
-
case "get_url":
|
|
791
|
-
result = await handleGetUrl(cmd);
|
|
792
|
-
break;
|
|
793
|
-
case "get_title":
|
|
794
|
-
result = await handleGetTitle(cmd);
|
|
795
|
-
break;
|
|
796
|
-
// Page
|
|
797
|
-
case "state":
|
|
798
|
-
result = await handleState(cmd);
|
|
799
|
-
break;
|
|
800
|
-
case "snapshot":
|
|
801
|
-
result = await handleSnapshot(cmd);
|
|
802
|
-
break;
|
|
803
|
-
case "screenshot":
|
|
804
|
-
result = await handleScreenshot(cmd);
|
|
805
|
-
break;
|
|
806
|
-
case "get_viewport_size":
|
|
807
|
-
result = await handleGetViewportSize(cmd);
|
|
808
|
-
break;
|
|
809
|
-
case "set_viewport_size":
|
|
810
|
-
result = await handleSetViewportSize(cmd);
|
|
811
|
-
break;
|
|
812
|
-
case "close":
|
|
813
|
-
result = await handleClose(cmd);
|
|
814
|
-
break;
|
|
815
|
-
case "new_tab":
|
|
816
|
-
result = await handleNewTab(cmd);
|
|
817
|
-
break;
|
|
818
|
-
case "list_tabs":
|
|
819
|
-
result = await handleListTabs(cmd);
|
|
820
|
-
break;
|
|
821
|
-
case "get_cookies":
|
|
822
|
-
result = await handleGetCookies(cmd);
|
|
823
|
-
break;
|
|
824
|
-
case "dialog":
|
|
825
|
-
result = await handleDialog(cmd);
|
|
826
|
-
break;
|
|
827
|
-
// Elements
|
|
828
|
-
case "click":
|
|
829
|
-
result = await handleClick(cmd);
|
|
830
|
-
break;
|
|
831
|
-
case "scroll":
|
|
832
|
-
result = await handleScroll(cmd);
|
|
833
|
-
break;
|
|
834
|
-
case "evaluate":
|
|
835
|
-
result = await handleEvaluate(cmd);
|
|
836
|
-
break;
|
|
837
|
-
case "wait":
|
|
838
|
-
result = await handleWait(cmd);
|
|
839
|
-
break;
|
|
840
|
-
case "query_selector":
|
|
841
|
-
result = await handleQuerySelector(cmd);
|
|
842
|
-
break;
|
|
843
|
-
// Input
|
|
844
|
-
case "mouse_move":
|
|
845
|
-
result = await handleMouseMove(cmd);
|
|
846
|
-
break;
|
|
847
|
-
case "mouse_click":
|
|
848
|
-
result = await handleMouseClick(cmd);
|
|
849
|
-
break;
|
|
850
|
-
case "mouse_wheel":
|
|
851
|
-
result = await handleMouseWheel(cmd);
|
|
852
|
-
break;
|
|
853
|
-
case "keys":
|
|
854
|
-
result = await handleKeyboardPress(cmd);
|
|
855
|
-
break;
|
|
856
|
-
case "keyboard_type":
|
|
857
|
-
result = await handleKeyboardType(cmd);
|
|
858
|
-
break;
|
|
859
|
-
case "type":
|
|
860
|
-
result = await handleType(cmd);
|
|
861
|
-
break;
|
|
862
|
-
// Ping
|
|
863
|
-
case "ping":
|
|
864
|
-
result = {
|
|
865
|
-
id: cmd.id,
|
|
866
|
-
ok: true,
|
|
867
|
-
data: {
|
|
868
|
-
pong: true,
|
|
869
|
-
extensionVersion: chrome.runtime.getManifest().version,
|
|
870
|
-
timestamp: Date.now()
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
break;
|
|
874
|
-
default:
|
|
875
|
-
result = { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
|
|
876
|
-
}
|
|
877
|
-
result.durationMs = Date.now() - start;
|
|
878
|
-
return result;
|
|
879
|
-
} catch (e) {
|
|
880
|
-
return {
|
|
881
|
-
id: cmd.id,
|
|
882
|
-
ok: false,
|
|
883
|
-
error: e instanceof Error ? e.message : String(e),
|
|
884
|
-
durationMs: Date.now() - start
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
529
|
function connect() {
|
|
889
|
-
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING))
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
530
|
+
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
|
892
531
|
intentionalClose = false;
|
|
893
|
-
log.info("Connecting to xopc daemon...", { url: XOPC_EXT_WS_URL });
|
|
894
532
|
ws = new WebSocket(XOPC_EXT_WS_URL);
|
|
895
533
|
ws.onopen = () => {
|
|
896
|
-
log.info("Connected to xopc daemon");
|
|
897
534
|
reconnectDelay = WS_RECONNECT_BASE_DELAY;
|
|
898
535
|
sendStatus();
|
|
536
|
+
log.info("Connected to xopc daemon");
|
|
899
537
|
};
|
|
900
538
|
ws.onmessage = async (event) => {
|
|
901
539
|
try {
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
const result = await dispatchCommand(cmd);
|
|
540
|
+
const command = JSON.parse(String(event.data));
|
|
541
|
+
const result = await executeBrowserCommand(command);
|
|
905
542
|
ws?.send(JSON.stringify(result));
|
|
906
|
-
} catch (
|
|
907
|
-
log.error("Failed to process
|
|
908
|
-
error: e instanceof Error ? e.message : String(e)
|
|
909
|
-
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
log.error("Failed to process browser command", { error: error instanceof Error ? error.message : String(error) });
|
|
910
545
|
}
|
|
911
546
|
};
|
|
912
547
|
ws.onclose = () => {
|
|
913
548
|
ws = null;
|
|
914
|
-
if (!intentionalClose)
|
|
915
|
-
log.warn("Disconnected, reconnecting...", { delay: reconnectDelay });
|
|
916
|
-
scheduleReconnect();
|
|
917
|
-
}
|
|
549
|
+
if (!intentionalClose) scheduleReconnect();
|
|
918
550
|
};
|
|
919
|
-
|
|
551
|
+
}
|
|
552
|
+
function sendStatus() {
|
|
553
|
+
if (ws?.readyState !== WebSocket.OPEN) return;
|
|
554
|
+
const status = {
|
|
555
|
+
type: "status",
|
|
556
|
+
connected: true,
|
|
557
|
+
sessionCount: automationSessions.size
|
|
920
558
|
};
|
|
559
|
+
ws.send(JSON.stringify(status));
|
|
921
560
|
}
|
|
922
561
|
function scheduleReconnect() {
|
|
923
562
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
@@ -929,76 +568,27 @@ function scheduleReconnect() {
|
|
|
929
568
|
}
|
|
930
569
|
function disconnect() {
|
|
931
570
|
intentionalClose = true;
|
|
932
|
-
if (reconnectTimer)
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (ws) {
|
|
937
|
-
ws.close();
|
|
938
|
-
ws = null;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
async function sendStatus() {
|
|
942
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
943
|
-
try {
|
|
944
|
-
const sess = automationSessions.get(DEFAULT_WORKSPACE);
|
|
945
|
-
let activeTabId;
|
|
946
|
-
let activeUrl;
|
|
947
|
-
if (sess?.activeTabId != null) {
|
|
948
|
-
const tab = await chrome.tabs.get(sess.activeTabId).catch(() => null);
|
|
949
|
-
if (tab) {
|
|
950
|
-
activeTabId = tab.id;
|
|
951
|
-
activeUrl = tab.url;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
const status = {
|
|
955
|
-
type: "status",
|
|
956
|
-
connected: true,
|
|
957
|
-
tabCount: sess ? sess.tabIds.length : 0,
|
|
958
|
-
activeTabId,
|
|
959
|
-
activeUrl
|
|
960
|
-
};
|
|
961
|
-
ws.send(JSON.stringify(status));
|
|
962
|
-
} catch {
|
|
963
|
-
}
|
|
571
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
572
|
+
reconnectTimer = null;
|
|
573
|
+
ws?.close();
|
|
574
|
+
ws = null;
|
|
964
575
|
}
|
|
965
|
-
chrome.runtime.onMessage.addListener(
|
|
966
|
-
(message
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
sendResponse({ ok: true });
|
|
977
|
-
break;
|
|
978
|
-
case "popup/disconnect":
|
|
979
|
-
disconnect();
|
|
980
|
-
sendResponse({ ok: true });
|
|
981
|
-
break;
|
|
982
|
-
case "content/heartbeat":
|
|
983
|
-
sendResponse({ ok: true });
|
|
984
|
-
break;
|
|
985
|
-
default:
|
|
986
|
-
sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
|
|
987
|
-
}
|
|
988
|
-
return true;
|
|
989
|
-
}
|
|
990
|
-
);
|
|
991
|
-
chrome.runtime.onInstalled.addListener(() => {
|
|
992
|
-
log.info("Extension installed/updated");
|
|
993
|
-
connect();
|
|
994
|
-
});
|
|
995
|
-
chrome.runtime.onStartup.addListener(() => {
|
|
996
|
-
log.info("Browser started");
|
|
997
|
-
connect();
|
|
576
|
+
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
577
|
+
if (message.type === "popup/get-status") sendResponse({ connected: ws?.readyState === WebSocket.OPEN, url: XOPC_EXT_WS_URL });
|
|
578
|
+
else if (message.type === "popup/connect") {
|
|
579
|
+
connect();
|
|
580
|
+
sendResponse({ ok: true });
|
|
581
|
+
} else if (message.type === "popup/disconnect") {
|
|
582
|
+
disconnect();
|
|
583
|
+
sendResponse({ ok: true });
|
|
584
|
+
} else if (message.type === "content/heartbeat") sendResponse({ ok: true });
|
|
585
|
+
else sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
|
|
586
|
+
return true;
|
|
998
587
|
});
|
|
588
|
+
chrome.runtime.onInstalled.addListener(connect);
|
|
589
|
+
chrome.runtime.onStartup.addListener(connect);
|
|
999
590
|
connect();
|
|
1000
591
|
self.addEventListener("beforeunload", () => {
|
|
1001
592
|
disconnect();
|
|
1002
593
|
void closeAllSessions();
|
|
1003
594
|
});
|
|
1004
|
-
log.info("xopc Browser Bridge service worker loaded");
|