@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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { assertBrowserUrlAllowed, checkPostRedirectUrl, containsApiKeyPattern } from "../url-policy.js";
|
|
2
|
+
import { consumeBrowserApproval, createBrowserApproval } from "../policy/approval-store.js";
|
|
3
|
+
import { browserRiskNeedsApproval, classifyBrowserRisk } from "../policy/browser-policy.js";
|
|
4
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
//#region src/browser/runtime/browser-runtime.ts
|
|
7
|
+
var BrowserRuntime = class {
|
|
8
|
+
driver = null;
|
|
9
|
+
sessionsByTask = /* @__PURE__ */ new Map();
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
async execute(taskKey, input, signal) {
|
|
14
|
+
this.evictExpiredSessions();
|
|
15
|
+
if (!this.options.getConfig().enabled) return failure("DRIVER_UNAVAILABLE", "Browser Control is disabled.");
|
|
16
|
+
if (signal?.aborted) return failure("ABORTED", "Operation was aborted.");
|
|
17
|
+
let session;
|
|
18
|
+
let driver;
|
|
19
|
+
try {
|
|
20
|
+
session = await this.resolveSession(taskKey, input.sessionId);
|
|
21
|
+
if (!session) return failure("SESSION_NOT_FOUND", "The browser session does not belong to this task.");
|
|
22
|
+
driver = await this.getDriver();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return failure("DRIVER_UNAVAILABLE", error instanceof Error ? error.message : String(error));
|
|
25
|
+
}
|
|
26
|
+
session.lastUsedAt = Date.now();
|
|
27
|
+
this.options.emit?.("browser.action.started", {
|
|
28
|
+
sessionId: session.id,
|
|
29
|
+
action: input.action
|
|
30
|
+
});
|
|
31
|
+
let result;
|
|
32
|
+
try {
|
|
33
|
+
result = await this.dispatch(driver, session, input, signal);
|
|
34
|
+
result = this.constrainResult(result);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
result = failure("DRIVER_UNAVAILABLE", error instanceof Error ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
if (result.ok) {
|
|
39
|
+
if (result.receipt.observation) session.observation = result.receipt.observation;
|
|
40
|
+
this.options.emit?.("browser.action.completed", {
|
|
41
|
+
sessionId: session.id,
|
|
42
|
+
receipt: withoutVisual(result.receipt)
|
|
43
|
+
});
|
|
44
|
+
if (result.receipt.observation) this.options.emit?.("browser.observation.created", withoutVisual(result.receipt.observation));
|
|
45
|
+
} else if ("error" in result) {
|
|
46
|
+
if (result.error.observation) session.observation = result.error.observation;
|
|
47
|
+
this.options.emit?.("browser.session.failed", {
|
|
48
|
+
sessionId: session.id,
|
|
49
|
+
error: result.error
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
async shutdown() {
|
|
55
|
+
await this.driver?.disconnect();
|
|
56
|
+
this.driver = null;
|
|
57
|
+
this.sessionsByTask.clear();
|
|
58
|
+
}
|
|
59
|
+
async closeTaskSession(taskKey) {
|
|
60
|
+
const session = this.sessionsByTask.get(taskKey);
|
|
61
|
+
if (!session) return;
|
|
62
|
+
await this.driver?.closeSession(session.id);
|
|
63
|
+
this.sessionsByTask.delete(taskKey);
|
|
64
|
+
this.options.emit?.("browser.session.closed", { sessionId: session.id });
|
|
65
|
+
}
|
|
66
|
+
async dispatch(driver, session, input, signal) {
|
|
67
|
+
if (input.action === "close") {
|
|
68
|
+
await this.closeTaskSession(session.taskKey);
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
receipt: {
|
|
72
|
+
action: "close",
|
|
73
|
+
risk: "read",
|
|
74
|
+
durationMs: 0,
|
|
75
|
+
verified: true
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (input.action === "observe") return {
|
|
80
|
+
ok: true,
|
|
81
|
+
receipt: {
|
|
82
|
+
action: "observe",
|
|
83
|
+
risk: "read",
|
|
84
|
+
durationMs: 0,
|
|
85
|
+
verified: true,
|
|
86
|
+
observation: await driver.observe(session.id, input, signal)
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
if (input.action === "navigate") {
|
|
90
|
+
const blocked = this.validateUrl(input.url);
|
|
91
|
+
if (blocked) return failure("BLOCKED_URL", blocked);
|
|
92
|
+
const crossDomainPolicy = this.crossDomainPolicy(session.observation?.url, input.url);
|
|
93
|
+
if (crossDomainPolicy === "deny") return failure("BLOCKED_URL", "Cross-domain navigation is denied by policy.");
|
|
94
|
+
const authorization = this.authorize(session.taskKey, input, crossDomainPolicy === "ask" ? "external_effect" : "read", crossDomainPolicy);
|
|
95
|
+
if (authorization) return authorization;
|
|
96
|
+
const result = await driver.navigate(session.id, input, signal);
|
|
97
|
+
return this.validateRedirect(input.url, result);
|
|
98
|
+
}
|
|
99
|
+
if (input.action === "tabs") {
|
|
100
|
+
if (input.url) {
|
|
101
|
+
const blocked = this.validateUrl(input.url);
|
|
102
|
+
if (blocked) return failure("BLOCKED_URL", blocked);
|
|
103
|
+
const crossDomainPolicy = this.crossDomainPolicy(session.observation?.url, input.url);
|
|
104
|
+
if (crossDomainPolicy === "deny") return failure("BLOCKED_URL", "Cross-domain navigation is denied by policy.");
|
|
105
|
+
const authorization = this.authorize(session.taskKey, input, crossDomainPolicy === "ask" ? "external_effect" : "read", crossDomainPolicy);
|
|
106
|
+
if (authorization) return authorization;
|
|
107
|
+
}
|
|
108
|
+
const result = await driver.tabs(session.id, input, signal);
|
|
109
|
+
if (!input.url || !result.ok) return result;
|
|
110
|
+
const finalUrl = result.receipt.tabs?.find((tab) => tab.active)?.url;
|
|
111
|
+
const blocked = finalUrl ? this.blockedRedirect(finalUrl) : "The created tab did not report its final URL.";
|
|
112
|
+
return blocked ? failure("BLOCKED_URL", blocked) : result;
|
|
113
|
+
}
|
|
114
|
+
if (input.action === "sequence") return this.executeSequence(driver, session, input, signal);
|
|
115
|
+
const target = findActionTarget(input, session.observation);
|
|
116
|
+
const risk = classifyBrowserRisk(input, target);
|
|
117
|
+
if (input.action === "upload") {
|
|
118
|
+
const invalidPath = this.invalidUploadPath(input.paths);
|
|
119
|
+
if (invalidPath) return failure("INVALID_INPUT", invalidPath);
|
|
120
|
+
}
|
|
121
|
+
const authorization = this.authorize(session.taskKey, input, risk, void 0, target);
|
|
122
|
+
if (authorization) return authorization;
|
|
123
|
+
return withRisk(await driver.perform(session.id, input, signal), risk);
|
|
124
|
+
}
|
|
125
|
+
async executeSequence(driver, session, input, signal) {
|
|
126
|
+
const config = this.options.getConfig();
|
|
127
|
+
if (input.steps.length === 0 || input.steps.length > config.limits.maxSequenceLength) return failure("INVALID_INPUT", `Sequence length must be between 1 and ${config.limits.maxSequenceLength}.`);
|
|
128
|
+
const initialDocument = session.observation?.documentId;
|
|
129
|
+
let revision = input.revision;
|
|
130
|
+
let last;
|
|
131
|
+
let highestRisk = "draft";
|
|
132
|
+
for (const step of input.steps) {
|
|
133
|
+
const risk = classifyBrowserRisk(step, findActionTarget(step, session.observation));
|
|
134
|
+
if (riskRank(risk) > riskRank(highestRisk)) highestRisk = risk;
|
|
135
|
+
}
|
|
136
|
+
const authorization = this.authorize(session.taskKey, input, highestRisk);
|
|
137
|
+
if (authorization) return authorization;
|
|
138
|
+
for (const step of input.steps) {
|
|
139
|
+
const primitive = {
|
|
140
|
+
...step,
|
|
141
|
+
sessionId: session.id,
|
|
142
|
+
revision
|
|
143
|
+
};
|
|
144
|
+
last = await driver.perform(session.id, primitive, signal);
|
|
145
|
+
if (!last.ok) return last;
|
|
146
|
+
const observation = last.receipt.observation;
|
|
147
|
+
if (observation) {
|
|
148
|
+
session.observation = observation;
|
|
149
|
+
revision = observation.revision;
|
|
150
|
+
if (initialDocument && observation.documentId !== initialDocument) return failure("STALE_OBSERVATION", "Sequence stopped because navigation replaced the document.", observation);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!last?.ok) return failure("INVALID_INPUT", "Sequence did not execute.");
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
receipt: {
|
|
157
|
+
...last.receipt,
|
|
158
|
+
action: "sequence",
|
|
159
|
+
risk: highestRisk
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
authorize(sessionKey, input, risk, explicitPolicy, target) {
|
|
164
|
+
const security = this.options.getConfig().security;
|
|
165
|
+
const policy = explicitPolicy ?? (input.action === "upload" ? security.uploads : security.consequentialActions);
|
|
166
|
+
if (!browserRiskNeedsApproval(risk) || policy === "allow") return null;
|
|
167
|
+
if (policy === "deny") return failure("APPROVAL_REQUIRED", `Browser action is denied by policy (${risk}).`);
|
|
168
|
+
if (consumeBrowserApproval(input.approvalId, sessionKey, input)) return null;
|
|
169
|
+
const approval = createBrowserApproval(sessionKey, input, risk, browserApprovalSummary(input, target));
|
|
170
|
+
this.options.emit?.("browser.approval.required", approval);
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
error: {
|
|
174
|
+
code: "APPROVAL_REQUIRED",
|
|
175
|
+
message: "This browser action needs local-owner approval before it can run.",
|
|
176
|
+
approval: {
|
|
177
|
+
id: approval.id,
|
|
178
|
+
risk,
|
|
179
|
+
summary: approval.summary,
|
|
180
|
+
expiresAt: approval.expiresAt
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
validateUrl(url) {
|
|
186
|
+
if (containsApiKeyPattern(url)) return "URL contains a credential-like value.";
|
|
187
|
+
try {
|
|
188
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
189
|
+
assertBrowserUrlAllowed(url, { privateHostAllowed: this.options.getConfig().security.allowedPrivateHosts.some((candidate) => candidate.toLowerCase() === host) });
|
|
190
|
+
return null;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return error instanceof Error ? error.message : String(error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
validateRedirect(requestedUrl, result) {
|
|
196
|
+
if (!result.ok || !result.receipt.observation) return result;
|
|
197
|
+
const finalUrl = result.receipt.observation.url;
|
|
198
|
+
if (finalUrl === requestedUrl) return result;
|
|
199
|
+
const blocked = this.blockedRedirect(finalUrl);
|
|
200
|
+
return blocked ? failure("BLOCKED_URL", blocked, result.receipt.observation) : result;
|
|
201
|
+
}
|
|
202
|
+
blockedRedirect(finalUrl) {
|
|
203
|
+
try {
|
|
204
|
+
const host = new URL(finalUrl).hostname.toLowerCase();
|
|
205
|
+
return checkPostRedirectUrl(finalUrl, { privateHostAllowed: this.options.getConfig().security.allowedPrivateHosts.some((candidate) => candidate.toLowerCase() === host) });
|
|
206
|
+
} catch {
|
|
207
|
+
return "Browser returned an invalid final URL.";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
crossDomainPolicy(currentUrl, nextUrl) {
|
|
211
|
+
if (!currentUrl || currentUrl === "about:blank") return "allow";
|
|
212
|
+
try {
|
|
213
|
+
if (new URL(currentUrl).hostname === new URL(nextUrl).hostname) return "allow";
|
|
214
|
+
} catch {
|
|
215
|
+
return "allow";
|
|
216
|
+
}
|
|
217
|
+
return this.options.getConfig().security.crossDomainNavigation;
|
|
218
|
+
}
|
|
219
|
+
invalidUploadPath(paths) {
|
|
220
|
+
const roots = this.options.allowedUploadRoots ?? [];
|
|
221
|
+
if (roots.length === 0) return "File uploads are unavailable because no upload root is configured.";
|
|
222
|
+
const normalizedRoots = roots.map((root) => resolve(root));
|
|
223
|
+
const invalid = paths.find((path) => {
|
|
224
|
+
if (!isAbsolute(path)) return true;
|
|
225
|
+
const absolute = resolve(path);
|
|
226
|
+
return !normalizedRoots.some((root) => {
|
|
227
|
+
const child = relative(root, absolute);
|
|
228
|
+
return child === "" || !child.startsWith("..") && !isAbsolute(child);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
return invalid ? `Upload path is outside the allowed workspace: ${invalid}` : null;
|
|
232
|
+
}
|
|
233
|
+
async resolveSession(taskKey, requestedId) {
|
|
234
|
+
const existing = this.sessionsByTask.get(taskKey);
|
|
235
|
+
if (existing) return !requestedId || requestedId === existing.id ? existing : null;
|
|
236
|
+
if (requestedId) return null;
|
|
237
|
+
const session = {
|
|
238
|
+
id: randomUUID(),
|
|
239
|
+
taskKey,
|
|
240
|
+
lastUsedAt: Date.now()
|
|
241
|
+
};
|
|
242
|
+
const driver = await this.getDriver();
|
|
243
|
+
await driver.createSession(session.id);
|
|
244
|
+
this.sessionsByTask.set(taskKey, session);
|
|
245
|
+
this.options.emit?.("browser.session.started", {
|
|
246
|
+
sessionId: session.id,
|
|
247
|
+
taskKey,
|
|
248
|
+
driver: driver.kind
|
|
249
|
+
});
|
|
250
|
+
return session;
|
|
251
|
+
}
|
|
252
|
+
async getDriver() {
|
|
253
|
+
if (this.driver) return this.driver;
|
|
254
|
+
this.driver = await this.options.createDriver();
|
|
255
|
+
await this.driver.connect();
|
|
256
|
+
return this.driver;
|
|
257
|
+
}
|
|
258
|
+
constrainResult(result) {
|
|
259
|
+
const observation = "error" in result ? result.error.observation : result.receipt.observation;
|
|
260
|
+
if (!observation) return result;
|
|
261
|
+
const { maxNodes, maxCharacters } = this.options.getConfig().observation;
|
|
262
|
+
const nodes = [];
|
|
263
|
+
let characters = 0;
|
|
264
|
+
for (const node of observation.nodes.slice(0, maxNodes)) {
|
|
265
|
+
const size = node.name.length + (node.value?.length ?? 0) + (node.description?.length ?? 0);
|
|
266
|
+
if (characters + size > maxCharacters) break;
|
|
267
|
+
characters += size;
|
|
268
|
+
nodes.push(node);
|
|
269
|
+
}
|
|
270
|
+
if (nodes.length === observation.nodes.length) return result;
|
|
271
|
+
const refs = new Set(nodes.map((node) => node.ref));
|
|
272
|
+
const constrained = {
|
|
273
|
+
...observation,
|
|
274
|
+
nodes,
|
|
275
|
+
changes: {
|
|
276
|
+
added: observation.changes.added.filter((node) => refs.has(node.ref)),
|
|
277
|
+
changed: observation.changes.changed.filter((node) => refs.has(node.ref)),
|
|
278
|
+
removed: observation.changes.removed
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
return "error" in result ? {
|
|
282
|
+
ok: false,
|
|
283
|
+
error: {
|
|
284
|
+
...result.error,
|
|
285
|
+
observation: constrained
|
|
286
|
+
}
|
|
287
|
+
} : {
|
|
288
|
+
ok: true,
|
|
289
|
+
receipt: {
|
|
290
|
+
...result.receipt,
|
|
291
|
+
observation: constrained
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
evictExpiredSessions() {
|
|
296
|
+
const expiresBefore = Date.now() - this.options.getConfig().limits.sessionTimeoutMs;
|
|
297
|
+
for (const [taskKey, session] of this.sessionsByTask) {
|
|
298
|
+
if (session.lastUsedAt >= expiresBefore) continue;
|
|
299
|
+
this.driver?.closeSession(session.id).catch(() => {});
|
|
300
|
+
this.sessionsByTask.delete(taskKey);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
function failure(code, message, observation) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
error: {
|
|
308
|
+
code,
|
|
309
|
+
message,
|
|
310
|
+
observation
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function withRisk(result, risk) {
|
|
315
|
+
return result.ok ? {
|
|
316
|
+
ok: true,
|
|
317
|
+
receipt: {
|
|
318
|
+
...result.receipt,
|
|
319
|
+
risk
|
|
320
|
+
}
|
|
321
|
+
} : result;
|
|
322
|
+
}
|
|
323
|
+
function riskRank(risk) {
|
|
324
|
+
return [
|
|
325
|
+
"read",
|
|
326
|
+
"draft",
|
|
327
|
+
"external_effect",
|
|
328
|
+
"destructive",
|
|
329
|
+
"sensitive"
|
|
330
|
+
].indexOf(risk);
|
|
331
|
+
}
|
|
332
|
+
function findActionTarget(input, observation) {
|
|
333
|
+
const ref = "ref" in input && input.ref ? input.ref : input.action === "press" ? observation?.focused : void 0;
|
|
334
|
+
return ref ? observation?.nodes.find((node) => node.ref === ref) : void 0;
|
|
335
|
+
}
|
|
336
|
+
function browserApprovalSummary(input, target) {
|
|
337
|
+
const targetName = target?.name.trim() ? ` “${target.name.slice(0, 120)}”` : "";
|
|
338
|
+
switch (input.action) {
|
|
339
|
+
case "navigate": return `Navigate to ${safeUrlLabel(input.url)}.`;
|
|
340
|
+
case "tabs": return input.url ? `Open a tab at ${safeUrlLabel(input.url)}.` : `Change browser tabs (${input.operation}).`;
|
|
341
|
+
case "click": return `Click${targetName || ` element ${input.ref}`}.`;
|
|
342
|
+
case "fill": return `${target?.states.includes("sensitive") ? "Fill a sensitive field" : "Fill field"}${targetName || ` ${input.ref}`}${input.submit ? " and submit the form" : ""}.`;
|
|
343
|
+
case "upload": return `Upload ${input.paths.length} file${input.paths.length === 1 ? "" : "s"}${targetName ? ` using${targetName}` : ""}.`;
|
|
344
|
+
case "press": return `Press ${input.key}${targetName ? ` on${targetName}` : ""}.`;
|
|
345
|
+
case "sequence": return `Run ${input.steps.length} browser actions as one sequence.`;
|
|
346
|
+
default: return `Run browser action “${input.action}”${targetName ? ` on${targetName}` : ""}.`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function safeUrlLabel(raw) {
|
|
350
|
+
try {
|
|
351
|
+
const url = new URL(raw);
|
|
352
|
+
return `${url.origin}${url.pathname}`.slice(0, 240);
|
|
353
|
+
} catch {
|
|
354
|
+
return "the requested URL";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function withoutVisual(value) {
|
|
358
|
+
if (!value || typeof value !== "object") return value;
|
|
359
|
+
return JSON.parse(JSON.stringify(value, (key, child) => key === "visual" ? void 0 : child));
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
export { BrowserRuntime };
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* no cloud metadata endpoints, no API key exfiltration attempts.
|
|
6
6
|
*
|
|
7
7
|
* @param raw URL string from the agent
|
|
8
|
-
* @param options.
|
|
8
|
+
* @param options.privateHostAllowed When true, skip private-IP blocking (cloud metadata still blocked)
|
|
9
9
|
*/
|
|
10
10
|
export declare function assertBrowserUrlAllowed(raw: string, options?: {
|
|
11
|
-
|
|
11
|
+
privateHostAllowed?: boolean;
|
|
12
12
|
}): void;
|
|
13
13
|
/**
|
|
14
14
|
* Check whether a URL targets an always-blocked cloud metadata endpoint.
|
|
@@ -33,5 +33,5 @@ export declare function containsApiKeyPattern(raw: string): boolean;
|
|
|
33
33
|
* @returns Error message string if blocked, `undefined` if safe.
|
|
34
34
|
*/
|
|
35
35
|
export declare function checkPostRedirectUrl(finalUrl: string, options?: {
|
|
36
|
-
|
|
36
|
+
privateHostAllowed?: boolean;
|
|
37
37
|
}): string | undefined;
|
|
@@ -7,7 +7,7 @@ import { isIPv4, isIPv6 } from "node:net";
|
|
|
7
7
|
*/
|
|
8
8
|
const ALWAYS_BLOCKED_HOSTNAMES = new Set(["metadata.google.internal", "metadata.goog"]);
|
|
9
9
|
/**
|
|
10
|
-
* Cloud metadata / IMDS IPv4 addresses — always blocked regardless of `
|
|
10
|
+
* Cloud metadata / IMDS IPv4 addresses — always blocked regardless of `privateHostAllowed`.
|
|
11
11
|
* Covers AWS, GCP, Azure, DigitalOcean, Oracle, Alibaba Cloud, and ECS task metadata.
|
|
12
12
|
*/
|
|
13
13
|
const ALWAYS_BLOCKED_IPV4 = new Set([
|
|
@@ -30,13 +30,13 @@ const API_KEY_PATTERN = /(?:sk-ant-|sk-[a-zA-Z0-9]{20,}|OPENAI_API_KEY|ANTHROPIC
|
|
|
30
30
|
* no cloud metadata endpoints, no API key exfiltration attempts.
|
|
31
31
|
*
|
|
32
32
|
* @param raw URL string from the agent
|
|
33
|
-
* @param options.
|
|
33
|
+
* @param options.privateHostAllowed When true, skip private-IP blocking (cloud metadata still blocked)
|
|
34
34
|
*/
|
|
35
35
|
function assertBrowserUrlAllowed(raw, options) {
|
|
36
36
|
const parsed = parseAndValidateUrl(raw);
|
|
37
37
|
assertNotApiKeyExfiltration(raw);
|
|
38
38
|
assertNotAlwaysBlocked(parsed.hostname);
|
|
39
|
-
assertNotPrivate(parsed.hostname, options?.
|
|
39
|
+
assertNotPrivate(parsed.hostname, options?.privateHostAllowed ?? false);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Check whether a URL targets an always-blocked cloud metadata endpoint.
|
|
@@ -79,7 +79,7 @@ function containsApiKeyPattern(raw) {
|
|
|
79
79
|
*/
|
|
80
80
|
function checkPostRedirectUrl(finalUrl, options) {
|
|
81
81
|
if (isAlwaysBlockedUrl(finalUrl)) return "Blocked: redirect landed on a cloud metadata endpoint";
|
|
82
|
-
if (options?.
|
|
82
|
+
if (options?.privateHostAllowed) return void 0;
|
|
83
83
|
try {
|
|
84
84
|
const host = new URL(finalUrl.trim()).hostname.toLowerCase();
|
|
85
85
|
if (host === "localhost" || host.endsWith(".localhost")) return "Blocked: redirect landed on localhost";
|
|
@@ -111,13 +111,14 @@ function assertNotAlwaysBlocked(hostname) {
|
|
|
111
111
|
}
|
|
112
112
|
function assertNotPrivate(hostname, allowPrivate) {
|
|
113
113
|
const host = hostname.toLowerCase();
|
|
114
|
+
if (allowPrivate) return;
|
|
114
115
|
if (host === "localhost" || host.endsWith(".localhost")) throw new Error("Blocked: localhost");
|
|
115
116
|
if (isIPv4(host)) {
|
|
116
|
-
if (
|
|
117
|
+
if (isBlockedIPv4(host)) throw new Error("Blocked: private or non-public IPv4 address");
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
if (isIPv6(host)) {
|
|
120
|
-
if (
|
|
121
|
+
if (isBlockedIPv6(host)) throw new Error("Blocked: private or loopback IPv6 address");
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
if (host.endsWith(".local") || host.endsWith(".internal")) throw new Error("Blocked: internal hostname");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/browser/verification/expectation.ts
|
|
2
|
+
function verifyBrowserExpectation(expectation, observation) {
|
|
3
|
+
if (!expectation) return null;
|
|
4
|
+
if (expectation.urlIncludes && !observation.url.includes(expectation.urlIncludes)) return `URL does not include ${JSON.stringify(expectation.urlIncludes)}`;
|
|
5
|
+
if (expectation.titleIncludes && !observation.title.includes(expectation.titleIncludes)) return `Title does not include ${JSON.stringify(expectation.titleIncludes)}`;
|
|
6
|
+
if (expectation.textIncludes) {
|
|
7
|
+
if (!observation.nodes.map((node) => `${node.name}\n${node.value ?? ""}\n${node.description ?? ""}`).join("\n").includes(expectation.textIncludes)) return `Page does not include ${JSON.stringify(expectation.textIncludes)}`;
|
|
8
|
+
}
|
|
9
|
+
if (expectation.ref && expectation.state) {
|
|
10
|
+
const present = observation.nodes.some((node) => node.ref === expectation.ref);
|
|
11
|
+
if (expectation.state === "visible" && !present) return `Target ${expectation.ref} is not visible`;
|
|
12
|
+
if (expectation.state === "hidden" && present) return `Target ${expectation.ref} is still visible`;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { verifyBrowserExpectation };
|
|
@@ -18,6 +18,7 @@ const REGISTRY_COMMAND_MODULES = {
|
|
|
18
18
|
doctor: () => import("./commands/doctor/index.js"),
|
|
19
19
|
support: () => import("./commands/support.js"),
|
|
20
20
|
runtime: () => import("./commands/runtime.js"),
|
|
21
|
+
platform: () => import("./commands/platform.js"),
|
|
21
22
|
image: () => import("./commands/image.js"),
|
|
22
23
|
channels: () => import("./commands/channels.js"),
|
|
23
24
|
models: () => import("./commands/models.js"),
|
|
@@ -91,6 +91,10 @@ const ROOT_HELP_COMMANDS = [
|
|
|
91
91
|
name: "runtime",
|
|
92
92
|
description: "Manage Node.js and Python tool runtimes used by agents"
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: "platform",
|
|
96
|
+
description: "Connect to XOPC Cloud or an enterprise platform"
|
|
97
|
+
},
|
|
94
98
|
{
|
|
95
99
|
name: "update [options]",
|
|
96
100
|
description: "Check for and install xopc updates"
|
|
@@ -137,7 +141,7 @@ const ROOT_HELP_COMMANDS = [
|
|
|
137
141
|
},
|
|
138
142
|
{
|
|
139
143
|
name: "browser",
|
|
140
|
-
description: "Browser
|
|
144
|
+
description: "Browser Control v2 diagnostics and extension setup"
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
name: "agents",
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI helpers for `xopc browser` — shared runtime setup for CLI browser commands.
|
|
3
|
-
*/
|
|
4
|
-
export declare function executeBrowserCliAction(action: string, args: Record<string, unknown>): Promise<void>;
|
|
5
|
-
export declare function validatePipelineCli(file: string): Promise<void>;
|
|
6
|
-
export declare function runPipelineCli(file: string, args: Record<string, unknown>): Promise<void>;
|
|
7
1
|
export declare function doctorCli(): Promise<void>;
|
|
@@ -1,152 +1,19 @@
|
|
|
1
1
|
import { init_loader, loadConfig } from "../../config/loader.js";
|
|
2
|
-
import {
|
|
3
|
-
import { BrowserManager } from "../../browser/manager.js";
|
|
4
|
-
import { resolveBrowserBackendFromConfig } from "../../browser/backend-from-config.js";
|
|
5
|
-
import { createBrowserActionRegistry } from "../../browser/actions/registry.js";
|
|
6
|
-
import { runBrowserPipeline, validateBrowserPipeline } from "../../browser/pipeline/runner.js";
|
|
7
|
-
import "../../browser/index.js";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { checkBrowserReadiness } from "../../browser/readiness.js";
|
|
9
3
|
//#region src/cli/commands/browser-cli-helpers.ts
|
|
10
4
|
init_loader();
|
|
11
|
-
|
|
12
|
-
/** When set, `xopc browser open --headless` overrides config for this CLI invocation. */
|
|
13
|
-
let browserCliHeadlessOverride;
|
|
14
|
-
async function getManager() {
|
|
15
|
-
if (_manager) return _manager;
|
|
5
|
+
async function doctorCli() {
|
|
16
6
|
const config = await loadConfig();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
getBackend: () => resolveBrowserBackendFromConfig(config)
|
|
23
|
-
});
|
|
24
|
-
return _manager;
|
|
25
|
-
}
|
|
26
|
-
async function getActionContext() {
|
|
27
|
-
const manager = await getManager();
|
|
28
|
-
await manager.ensureConnected();
|
|
29
|
-
return {
|
|
30
|
-
page: manager.getExtensionProvider() ? null : await manager.getPage("cli"),
|
|
31
|
-
manager,
|
|
32
|
-
config: await loadConfig(),
|
|
33
|
-
taskId: "cli"
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
async function executeBrowserCliAction(action, args) {
|
|
37
|
-
const prevOverride = browserCliHeadlessOverride;
|
|
38
|
-
if (typeof args.headless === "boolean") browserCliHeadlessOverride = args.headless;
|
|
39
|
-
try {
|
|
40
|
-
const registry = createBrowserActionRegistry();
|
|
41
|
-
const ctx = await getActionContext();
|
|
42
|
-
const result = await registry.execute(action, ctx, args);
|
|
43
|
-
if (result.ok) {
|
|
44
|
-
if (result.text) console.log(result.text);
|
|
45
|
-
if (result.data && !result.text) console.log(JSON.stringify(result.data, null, 2));
|
|
46
|
-
} else {
|
|
47
|
-
console.error(`[${result.error?.code}] ${result.error?.message}`);
|
|
48
|
-
process.exitCode = 1;
|
|
49
|
-
}
|
|
50
|
-
if (action === "close") {
|
|
51
|
-
await _manager?.shutdown();
|
|
52
|
-
_manager = null;
|
|
53
|
-
}
|
|
54
|
-
} finally {
|
|
55
|
-
browserCliHeadlessOverride = prevOverride;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async function validatePipelineCli(file) {
|
|
59
|
-
let yamlSource;
|
|
60
|
-
try {
|
|
61
|
-
yamlSource = await readFile(file, "utf8");
|
|
62
|
-
} catch (e) {
|
|
63
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
64
|
-
console.error(`Failed to read pipeline source: ${msg}`);
|
|
65
|
-
process.exitCode = 1;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const registry = createBrowserActionRegistry();
|
|
69
|
-
const result = validateBrowserPipeline(yamlSource, registry);
|
|
70
|
-
if (result.ok) console.log(`✓ Pipeline "${result.document.name}" is valid (${result.document.pipeline.length} steps).`);
|
|
71
|
-
else {
|
|
72
|
-
console.error("✗ Pipeline validation failed:");
|
|
73
|
-
for (const err of result.errors) console.error(` ${err.path}: ${err.message}`);
|
|
74
|
-
process.exitCode = 1;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async function runPipelineCli(file, args) {
|
|
78
|
-
let yamlSource;
|
|
79
|
-
try {
|
|
80
|
-
yamlSource = await readFile(file, "utf8");
|
|
81
|
-
} catch (e) {
|
|
82
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
83
|
-
console.error(`Failed to read pipeline source: ${msg}`);
|
|
84
|
-
process.exitCode = 1;
|
|
7
|
+
const readiness = await checkBrowserReadiness(config);
|
|
8
|
+
console.log("Browser Control v2");
|
|
9
|
+
console.log(`Driver: ${config.browser.driver.kind}`);
|
|
10
|
+
if (!readiness) {
|
|
11
|
+
console.log("Status: ready");
|
|
85
12
|
return;
|
|
86
13
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (result.ok) {
|
|
91
|
-
if (result.text) console.log(result.text);
|
|
92
|
-
if (result.data && !result.text) console.log(JSON.stringify(result.data, null, 2));
|
|
93
|
-
} else {
|
|
94
|
-
console.error(`Pipeline failed: [${result.error?.code}] ${result.error?.message}`);
|
|
95
|
-
if (result.diagnostics?.snapshot) console.error(`Snapshot:\n${result.diagnostics.snapshot.slice(0, 2e3)}`);
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
await _manager?.shutdown();
|
|
99
|
-
_manager = null;
|
|
100
|
-
}
|
|
101
|
-
async function doctorCli() {
|
|
102
|
-
console.log("Browser Doctor");
|
|
103
|
-
console.log("──────────────");
|
|
104
|
-
try {
|
|
105
|
-
const pw = await loadPlaywrightCoreModule();
|
|
106
|
-
const chromium = pw.chromium ?? pw.default?.chromium;
|
|
107
|
-
if (chromium) {
|
|
108
|
-
console.log("✓ playwright-core installed");
|
|
109
|
-
const execPath = chromium.executablePath?.();
|
|
110
|
-
if (execPath) console.log(` Chromium path: ${execPath}`);
|
|
111
|
-
else console.log(" ⚠ No default Chromium path detected. Run: npx playwright install chromium");
|
|
112
|
-
} else console.log("✗ playwright-core chromium not available");
|
|
113
|
-
} catch {
|
|
114
|
-
console.log("✗ playwright-core not installed");
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
await loadConfig();
|
|
118
|
-
console.log(`\nConfig:`);
|
|
119
|
-
console.log(` enabled: true`);
|
|
120
|
-
console.log(` headless: false`);
|
|
121
|
-
console.log(` backend: extension`);
|
|
122
|
-
console.log(` provider: local`);
|
|
123
|
-
{
|
|
124
|
-
const port = 19820;
|
|
125
|
-
const host = "127.0.0.1";
|
|
126
|
-
console.log(`\nExtension Bridge:`);
|
|
127
|
-
console.log(` endpoint: ws://${host}:${port}/browser-ext`);
|
|
128
|
-
try {
|
|
129
|
-
const { browserExtDoctor } = await import("../../browser/providers/browser-ext-install.js");
|
|
130
|
-
const artifacts = await browserExtDoctor();
|
|
131
|
-
console.log(` artifacts: ${artifacts.installed ? "✓ installed" : "✗ not installed"}`);
|
|
132
|
-
if (artifacts.extensionDir) console.log(` path: ${artifacts.extensionDir}`);
|
|
133
|
-
if (artifacts.needsRefresh) console.log(` refresh: ⚠ bundled update available`);
|
|
134
|
-
} catch {
|
|
135
|
-
console.log(` artifacts: ⚠ could not check`);
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const data = await (await fetch(`http://${host}:${port}/`)).json();
|
|
139
|
-
if (data.ok) {
|
|
140
|
-
console.log(` server: ✓ running`);
|
|
141
|
-
console.log(` extension: ${data.connected ? "✓ connected" : "⚠ waiting for connection"}`);
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
console.log(` server: ✗ not running (start xopc with backend: 'extension' to enable)`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} catch {
|
|
148
|
-
console.log("\n⚠ Could not load config");
|
|
149
|
-
}
|
|
14
|
+
console.error(`Status: ${readiness.hint.reason}`);
|
|
15
|
+
if (readiness.hint.detail) console.error(`Detail: ${readiness.hint.detail}`);
|
|
16
|
+
process.exitCode = 1;
|
|
150
17
|
}
|
|
151
18
|
//#endregion
|
|
152
|
-
export { doctorCli
|
|
19
|
+
export { doctorCli };
|