browxai 0.7.0
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/LICENSE +21 -0
- package/README.md +139 -0
- package/THIRD_PARTY_NOTICES.md +45 -0
- package/dist/cli/chrome.d.ts +1 -0
- package/dist/cli/chrome.js +130 -0
- package/dist/cli/command-registry.d.ts +15 -0
- package/dist/cli/command-registry.js +35 -0
- package/dist/cli/doctor-plugins.d.ts +18 -0
- package/dist/cli/doctor-plugins.js +338 -0
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.js +407 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +200 -0
- package/dist/cli/register-commands.d.ts +1 -0
- package/dist/cli/register-commands.js +22 -0
- package/dist/cli/serve.d.ts +14 -0
- package/dist/cli/serve.js +151 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +129 -0
- package/dist/engine/adapters/adb.d.ts +72 -0
- package/dist/engine/adapters/adb.js +200 -0
- package/dist/engine/adapters/android-cdp.d.ts +54 -0
- package/dist/engine/adapters/android-cdp.js +110 -0
- package/dist/engine/adapters/android.engine.d.ts +1 -0
- package/dist/engine/adapters/android.engine.js +31 -0
- package/dist/engine/adapters/chromium.engine.d.ts +1 -0
- package/dist/engine/adapters/chromium.engine.js +44 -0
- package/dist/engine/adapters/firefox.engine.d.ts +1 -0
- package/dist/engine/adapters/firefox.engine.js +43 -0
- package/dist/engine/adapters/playwright-chromium.d.ts +43 -0
- package/dist/engine/adapters/playwright-chromium.js +56 -0
- package/dist/engine/adapters/playwright-firefox.d.ts +52 -0
- package/dist/engine/adapters/playwright-firefox.js +97 -0
- package/dist/engine/adapters/playwright-webkit.d.ts +40 -0
- package/dist/engine/adapters/playwright-webkit.js +79 -0
- package/dist/engine/adapters/safari/bidi-client.d.ts +46 -0
- package/dist/engine/adapters/safari/bidi-client.js +130 -0
- package/dist/engine/adapters/safari/launch.d.ts +56 -0
- package/dist/engine/adapters/safari/launch.js +104 -0
- package/dist/engine/adapters/safari/webdriver-client.d.ts +102 -0
- package/dist/engine/adapters/safari/webdriver-client.js +175 -0
- package/dist/engine/adapters/safari.engine.d.ts +1 -0
- package/dist/engine/adapters/safari.engine.js +52 -0
- package/dist/engine/adapters/safaridriver-hybrid.d.ts +56 -0
- package/dist/engine/adapters/safaridriver-hybrid.js +127 -0
- package/dist/engine/adapters/webkit.engine.d.ts +1 -0
- package/dist/engine/adapters/webkit.engine.js +47 -0
- package/dist/engine/capabilities.d.ts +53 -0
- package/dist/engine/capabilities.js +122 -0
- package/dist/engine/capability-registry.d.ts +9 -0
- package/dist/engine/capability-registry.js +20 -0
- package/dist/engine/index.d.ts +18 -0
- package/dist/engine/index.js +14 -0
- package/dist/engine/register-engines.d.ts +5 -0
- package/dist/engine/register-engines.js +16 -0
- package/dist/engine/registry.d.ts +145 -0
- package/dist/engine/registry.js +67 -0
- package/dist/engine/select.d.ts +48 -0
- package/dist/engine/select.js +128 -0
- package/dist/engine/session-cdp.d.ts +13 -0
- package/dist/engine/session-cdp.js +22 -0
- package/dist/engine/tool-gate.d.ts +19 -0
- package/dist/engine/tool-gate.js +226 -0
- package/dist/engine/types.d.ts +71 -0
- package/dist/engine/types.js +16 -0
- package/dist/helper/bridge.d.ts +48 -0
- package/dist/helper/bridge.js +200 -0
- package/dist/helper/browx-page.d.ts +1 -0
- package/dist/helper/browx-page.js +47 -0
- package/dist/helper/overlay-hide.d.ts +9 -0
- package/dist/helper/overlay-hide.js +49 -0
- package/dist/helper/stealth.d.ts +10 -0
- package/dist/helper/stealth.js +88 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +15 -0
- package/dist/page/a11y.d.ts +81 -0
- package/dist/page/a11y.js +219 -0
- package/dist/page/action-substrate.d.ts +64 -0
- package/dist/page/action-substrate.js +118 -0
- package/dist/page/actionresult-blocks.d.ts +99 -0
- package/dist/page/actionresult-blocks.js +144 -0
- package/dist/page/actionresult-shape.d.ts +48 -0
- package/dist/page/actionresult-shape.js +155 -0
- package/dist/page/actionresult-types.d.ts +368 -0
- package/dist/page/actionresult-types.js +4 -0
- package/dist/page/actionresult.d.ts +4 -0
- package/dist/page/actionresult.js +299 -0
- package/dist/page/actions-probe.d.ts +32 -0
- package/dist/page/actions-probe.js +294 -0
- package/dist/page/actions-scroll.d.ts +40 -0
- package/dist/page/actions-scroll.js +53 -0
- package/dist/page/actions.d.ts +132 -0
- package/dist/page/actions.js +453 -0
- package/dist/page/archive-assets.d.ts +39 -0
- package/dist/page/archive-assets.js +187 -0
- package/dist/page/archive.d.ts +47 -0
- package/dist/page/archive.js +349 -0
- package/dist/page/asset-export.d.ts +122 -0
- package/dist/page/asset-export.js +376 -0
- package/dist/page/await_network.d.ts +16 -0
- package/dist/page/await_network.js +23 -0
- package/dist/page/bbox.d.ts +37 -0
- package/dist/page/bbox.js +115 -0
- package/dist/page/canvas-capture.d.ts +82 -0
- package/dist/page/canvas-capture.js +257 -0
- package/dist/page/canvas-diff.d.ts +51 -0
- package/dist/page/canvas-diff.js +131 -0
- package/dist/page/canvas-gesture.d.ts +53 -0
- package/dist/page/canvas-gesture.js +167 -0
- package/dist/page/canvas-transform.d.ts +96 -0
- package/dist/page/canvas-transform.js +150 -0
- package/dist/page/canvas.d.ts +8 -0
- package/dist/page/canvas.js +50 -0
- package/dist/page/capture-substrate.d.ts +111 -0
- package/dist/page/capture-substrate.js +139 -0
- package/dist/page/clipboard.d.ts +25 -0
- package/dist/page/clipboard.js +50 -0
- package/dist/page/clock.d.ts +36 -0
- package/dist/page/clock.js +167 -0
- package/dist/page/compose.d.ts +55 -0
- package/dist/page/compose.js +169 -0
- package/dist/page/console.d.ts +39 -0
- package/dist/page/console.js +73 -0
- package/dist/page/coverage.d.ts +97 -0
- package/dist/page/coverage.js +280 -0
- package/dist/page/dom-export.d.ts +41 -0
- package/dist/page/dom-export.js +193 -0
- package/dist/page/dom-walk.d.ts +91 -0
- package/dist/page/dom-walk.js +267 -0
- package/dist/page/dom_diff.d.ts +48 -0
- package/dist/page/dom_diff.js +121 -0
- package/dist/page/downloads.d.ts +80 -0
- package/dist/page/downloads.js +244 -0
- package/dist/page/drop-files.d.ts +78 -0
- package/dist/page/drop-files.js +310 -0
- package/dist/page/element-export-discovery.d.ts +64 -0
- package/dist/page/element-export-discovery.js +346 -0
- package/dist/page/element-export.d.ts +46 -0
- package/dist/page/element-export.js +251 -0
- package/dist/page/emulation-substrate.d.ts +53 -0
- package/dist/page/emulation-substrate.js +87 -0
- package/dist/page/emulation.d.ts +60 -0
- package/dist/page/emulation.js +162 -0
- package/dist/page/export-playwright-script.d.ts +47 -0
- package/dist/page/export-playwright-script.js +304 -0
- package/dist/page/extract-resolve.d.ts +22 -0
- package/dist/page/extract-resolve.js +341 -0
- package/dist/page/extract-schema.d.ts +20 -0
- package/dist/page/extract-schema.js +200 -0
- package/dist/page/extract-types.d.ts +127 -0
- package/dist/page/extract-types.js +8 -0
- package/dist/page/extract-warnings.d.ts +8 -0
- package/dist/page/extract-warnings.js +56 -0
- package/dist/page/extract.d.ts +9 -0
- package/dist/page/extract.js +174 -0
- package/dist/page/fill-form.d.ts +58 -0
- package/dist/page/fill-form.js +261 -0
- package/dist/page/find.d.ts +158 -0
- package/dist/page/find.js +470 -0
- package/dist/page/frames.d.ts +45 -0
- package/dist/page/frames.js +133 -0
- package/dist/page/generate-locator.d.ts +57 -0
- package/dist/page/generate-locator.js +136 -0
- package/dist/page/gestures.d.ts +128 -0
- package/dist/page/gestures.js +198 -0
- package/dist/page/har.d.ts +91 -0
- package/dist/page/har.js +174 -0
- package/dist/page/heap.d.ts +97 -0
- package/dist/page/heap.js +285 -0
- package/dist/page/inspect.d.ts +34 -0
- package/dist/page/inspect.js +75 -0
- package/dist/page/layout-thrash.d.ts +34 -0
- package/dist/page/layout-thrash.js +232 -0
- package/dist/page/learning.d.ts +21 -0
- package/dist/page/learning.js +84 -0
- package/dist/page/locator.d.ts +54 -0
- package/dist/page/locator.js +142 -0
- package/dist/page/memory-diff.d.ts +48 -0
- package/dist/page/memory-diff.js +105 -0
- package/dist/page/network-mask.d.ts +8 -0
- package/dist/page/network-mask.js +18 -0
- package/dist/page/network-playwright.d.ts +96 -0
- package/dist/page/network-playwright.js +353 -0
- package/dist/page/network-substrate-select.d.ts +18 -0
- package/dist/page/network-substrate-select.js +32 -0
- package/dist/page/network-substrate.d.ts +109 -0
- package/dist/page/network-substrate.js +161 -0
- package/dist/page/network-ws.d.ts +46 -0
- package/dist/page/network-ws.js +113 -0
- package/dist/page/network.d.ts +194 -0
- package/dist/page/network.js +415 -0
- package/dist/page/overflow-detect.d.ts +102 -0
- package/dist/page/overflow-detect.js +449 -0
- package/dist/page/pdf.d.ts +69 -0
- package/dist/page/pdf.js +109 -0
- package/dist/page/perf-audit-analysers.d.ts +40 -0
- package/dist/page/perf-audit-analysers.js +369 -0
- package/dist/page/perf-audit-runner.d.ts +20 -0
- package/dist/page/perf-audit-runner.js +195 -0
- package/dist/page/perf-audit-types.d.ts +41 -0
- package/dist/page/perf-audit-types.js +5 -0
- package/dist/page/perf-audit.d.ts +37 -0
- package/dist/page/perf-audit.js +377 -0
- package/dist/page/perf.d.ts +127 -0
- package/dist/page/perf.js +373 -0
- package/dist/page/plan.d.ts +192 -0
- package/dist/page/plan.js +308 -0
- package/dist/page/point_probe.d.ts +46 -0
- package/dist/page/point_probe.js +99 -0
- package/dist/page/recording.d.ts +67 -0
- package/dist/page/recording.js +172 -0
- package/dist/page/refs.d.ts +92 -0
- package/dist/page/refs.js +134 -0
- package/dist/page/regions.d.ts +23 -0
- package/dist/page/regions.js +32 -0
- package/dist/page/routes.d.ts +40 -0
- package/dist/page/routes.js +87 -0
- package/dist/page/safari-actions.d.ts +12 -0
- package/dist/page/safari-actions.js +144 -0
- package/dist/page/sample.d.ts +64 -0
- package/dist/page/sample.js +216 -0
- package/dist/page/screenshot-on.d.ts +51 -0
- package/dist/page/screenshot-on.js +150 -0
- package/dist/page/screenshot-save.d.ts +36 -0
- package/dist/page/screenshot-save.js +53 -0
- package/dist/page/screenshot-schedule.d.ts +50 -0
- package/dist/page/screenshot-schedule.js +155 -0
- package/dist/page/script-substrate.d.ts +32 -0
- package/dist/page/script-substrate.js +47 -0
- package/dist/page/seed-random.d.ts +45 -0
- package/dist/page/seed-random.js +144 -0
- package/dist/page/set-of-marks.d.ts +96 -0
- package/dist/page/set-of-marks.js +245 -0
- package/dist/page/shadow.d.ts +136 -0
- package/dist/page/shadow.js +400 -0
- package/dist/page/shortcut.d.ts +50 -0
- package/dist/page/shortcut.js +147 -0
- package/dist/page/snapshot-substrate-safari.d.ts +30 -0
- package/dist/page/snapshot-substrate-safari.js +84 -0
- package/dist/page/snapshot-substrate-select.d.ts +24 -0
- package/dist/page/snapshot-substrate-select.js +34 -0
- package/dist/page/snapshot-substrate.d.ts +58 -0
- package/dist/page/snapshot-substrate.js +135 -0
- package/dist/page/snapshot.d.ts +24 -0
- package/dist/page/snapshot.js +162 -0
- package/dist/page/solve-captcha.d.ts +76 -0
- package/dist/page/solve-captcha.js +286 -0
- package/dist/page/storage-substrate-types.d.ts +221 -0
- package/dist/page/storage-substrate-types.js +6 -0
- package/dist/page/storage-substrate.d.ts +215 -0
- package/dist/page/storage-substrate.js +280 -0
- package/dist/page/structural.d.ts +9 -0
- package/dist/page/structural.js +152 -0
- package/dist/page/substrate-bundle-safari.d.ts +8 -0
- package/dist/page/substrate-bundle-safari.js +42 -0
- package/dist/page/substrate-bundle.d.ts +6 -0
- package/dist/page/substrate-bundle.js +53 -0
- package/dist/page/text_search.d.ts +44 -0
- package/dist/page/text_search.js +90 -0
- package/dist/page/upload.d.ts +28 -0
- package/dist/page/upload.js +62 -0
- package/dist/page/verify.d.ts +63 -0
- package/dist/page/verify.js +451 -0
- package/dist/page/video.d.ts +115 -0
- package/dist/page/video.js +169 -0
- package/dist/page/visibility.d.ts +22 -0
- package/dist/page/visibility.js +94 -0
- package/dist/page/watch.d.ts +29 -0
- package/dist/page/watch.js +99 -0
- package/dist/page/workers.d.ts +126 -0
- package/dist/page/workers.js +490 -0
- package/dist/page/ws-interactive.d.ts +82 -0
- package/dist/page/ws-interactive.js +318 -0
- package/dist/plugin/cli.d.ts +45 -0
- package/dist/plugin/cli.js +496 -0
- package/dist/plugin/command-registry.d.ts +9 -0
- package/dist/plugin/command-registry.js +23 -0
- package/dist/plugin/depgraph.d.ts +37 -0
- package/dist/plugin/depgraph.js +186 -0
- package/dist/plugin/manifest.d.ts +182 -0
- package/dist/plugin/manifest.js +219 -0
- package/dist/plugin/package-manager.d.ts +22 -0
- package/dist/plugin/package-manager.js +40 -0
- package/dist/plugin/resolver.d.ts +85 -0
- package/dist/plugin/resolver.js +166 -0
- package/dist/plugin/runtime.d.ts +77 -0
- package/dist/plugin/runtime.js +402 -0
- package/dist/plugin/types.d.ts +113 -0
- package/dist/plugin/types.js +4 -0
- package/dist/policy/confirm.d.ts +76 -0
- package/dist/policy/confirm.js +162 -0
- package/dist/policy/origin.d.ts +17 -0
- package/dist/policy/origin.js +79 -0
- package/dist/sdk/client.d.ts +21 -0
- package/dist/sdk/client.js +174 -0
- package/dist/sdk/index.d.ts +32 -0
- package/dist/sdk/index.js +61 -0
- package/dist/sdk/plugin-types.d.ts +33 -0
- package/dist/sdk/plugin-types.js +22 -0
- package/dist/sdk/registry.d.ts +17 -0
- package/dist/sdk/registry.js +94 -0
- package/dist/sdk/socket-transport.d.ts +20 -0
- package/dist/sdk/socket-transport.js +90 -0
- package/dist/sdk/tool-types.d.ts +634 -0
- package/dist/sdk/tool-types.js +28 -0
- package/dist/sdk/transport-in-process.d.ts +21 -0
- package/dist/sdk/transport-in-process.js +44 -0
- package/dist/sdk/transport-registry.d.ts +19 -0
- package/dist/sdk/transport-registry.js +31 -0
- package/dist/sdk/transport-socket.d.ts +12 -0
- package/dist/sdk/transport-socket.js +77 -0
- package/dist/sdk/transport-stdio-child.d.ts +10 -0
- package/dist/sdk/transport-stdio-child.js +47 -0
- package/dist/sdk/transport.d.ts +10 -0
- package/dist/sdk/transport.js +35 -0
- package/dist/sdk/types.d.ts +176 -0
- package/dist/sdk/types.js +10 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +327 -0
- package/dist/session/artifacts.d.ts +52 -0
- package/dist/session/artifacts.js +177 -0
- package/dist/session/byob-attach.d.ts +26 -0
- package/dist/session/byob-attach.js +187 -0
- package/dist/session/byob.d.ts +8 -0
- package/dist/session/byob.js +20 -0
- package/dist/session/cache-storage.d.ts +100 -0
- package/dist/session/cache-storage.js +166 -0
- package/dist/session/device-emu.d.ts +149 -0
- package/dist/session/device-emu.js +545 -0
- package/dist/session/device.d.ts +14 -0
- package/dist/session/device.js +44 -0
- package/dist/session/dialog.d.ts +62 -0
- package/dist/session/dialog.js +164 -0
- package/dist/session/emulation.d.ts +69 -0
- package/dist/session/emulation.js +168 -0
- package/dist/session/extensions.d.ts +113 -0
- package/dist/session/extensions.js +237 -0
- package/dist/session/fs-picker.d.ts +144 -0
- package/dist/session/fs-picker.js +666 -0
- package/dist/session/idb-storage.d.ts +86 -0
- package/dist/session/idb-storage.js +229 -0
- package/dist/session/incognito.d.ts +3 -0
- package/dist/session/incognito.js +20 -0
- package/dist/session/launch-options.d.ts +41 -0
- package/dist/session/launch-options.js +200 -0
- package/dist/session/managed.d.ts +3 -0
- package/dist/session/managed.js +16 -0
- package/dist/session/metrics.d.ts +45 -0
- package/dist/session/metrics.js +75 -0
- package/dist/session/notification.d.ts +122 -0
- package/dist/session/notification.js +426 -0
- package/dist/session/permission.d.ts +144 -0
- package/dist/session/permission.js +600 -0
- package/dist/session/playwright-post-wire.d.ts +8 -0
- package/dist/session/playwright-post-wire.js +148 -0
- package/dist/session/policy-buffer.d.ts +21 -0
- package/dist/session/policy-buffer.js +47 -0
- package/dist/session/profile-snapshot.d.ts +11 -0
- package/dist/session/profile-snapshot.js +53 -0
- package/dist/session/registry.d.ts +365 -0
- package/dist/session/registry.js +98 -0
- package/dist/session/safari-post-wire.d.ts +8 -0
- package/dist/session/safari-post-wire.js +28 -0
- package/dist/session/safari-session.d.ts +10 -0
- package/dist/session/safari-session.js +39 -0
- package/dist/session/storage.d.ts +148 -0
- package/dist/session/storage.js +350 -0
- package/dist/session/types.d.ts +113 -0
- package/dist/session/types.js +5 -0
- package/dist/session/wedge.d.ts +15 -0
- package/dist/session/wedge.js +41 -0
- package/dist/tools/action-core-tools.d.ts +13 -0
- package/dist/tools/action-core-tools.js +156 -0
- package/dist/tools/action-form-tools.d.ts +12 -0
- package/dist/tools/action-form-tools.js +179 -0
- package/dist/tools/action-gesture-tools.d.ts +9 -0
- package/dist/tools/action-gesture-tools.js +115 -0
- package/dist/tools/action-history-tools.d.ts +8 -0
- package/dist/tools/action-history-tools.js +67 -0
- package/dist/tools/action-tool.d.ts +42 -0
- package/dist/tools/action-tool.js +58 -0
- package/dist/tools/action-tools.d.ts +20 -0
- package/dist/tools/action-tools.js +28 -0
- package/dist/tools/batch-act-tools.d.ts +10 -0
- package/dist/tools/batch-act-tools.js +276 -0
- package/dist/tools/batch-human-tools.d.ts +8 -0
- package/dist/tools/batch-human-tools.js +148 -0
- package/dist/tools/canvas-tools.d.ts +40 -0
- package/dist/tools/canvas-tools.js +368 -0
- package/dist/tools/capture-report-diagnostics-tools.d.ts +7 -0
- package/dist/tools/capture-report-diagnostics-tools.js +318 -0
- package/dist/tools/capture-report-element-export-tools.d.ts +8 -0
- package/dist/tools/capture-report-element-export-tools.js +197 -0
- package/dist/tools/capture-report-export-tools.d.ts +8 -0
- package/dist/tools/capture-report-export-tools.js +246 -0
- package/dist/tools/capture-report-marks-tools.d.ts +9 -0
- package/dist/tools/capture-report-marks-tools.js +221 -0
- package/dist/tools/capture-report-upload-tools.d.ts +8 -0
- package/dist/tools/capture-report-upload-tools.js +277 -0
- package/dist/tools/config-approval-tools.d.ts +8 -0
- package/dist/tools/config-approval-tools.js +166 -0
- package/dist/tools/deep-coverage-tools.d.ts +8 -0
- package/dist/tools/deep-coverage-tools.js +325 -0
- package/dist/tools/deep-determinism-tools.d.ts +8 -0
- package/dist/tools/deep-determinism-tools.js +276 -0
- package/dist/tools/deep-perf-tools.d.ts +19 -0
- package/dist/tools/deep-perf-tools.js +324 -0
- package/dist/tools/device-emulation-tools.d.ts +9 -0
- package/dist/tools/device-emulation-tools.js +137 -0
- package/dist/tools/extensions-batch-tools.d.ts +18 -0
- package/dist/tools/extensions-batch-tools.js +24 -0
- package/dist/tools/extensions-rebuild.d.ts +22 -0
- package/dist/tools/extensions-rebuild.js +208 -0
- package/dist/tools/extensions-tools.d.ts +2 -0
- package/dist/tools/extensions-tools.js +331 -0
- package/dist/tools/forms-fill-tools.d.ts +8 -0
- package/dist/tools/forms-fill-tools.js +109 -0
- package/dist/tools/forms-plan-tools.d.ts +7 -0
- package/dist/tools/forms-plan-tools.js +159 -0
- package/dist/tools/forms-recording-mode-tools.d.ts +8 -0
- package/dist/tools/forms-recording-mode-tools.js +71 -0
- package/dist/tools/forms-recording-tools.d.ts +14 -0
- package/dist/tools/forms-recording-tools.js +22 -0
- package/dist/tools/forms-refs-tools.d.ts +8 -0
- package/dist/tools/forms-refs-tools.js +90 -0
- package/dist/tools/gesture-coord-tools.d.ts +8 -0
- package/dist/tools/gesture-coord-tools.js +168 -0
- package/dist/tools/gesture-emulation-tools.d.ts +8 -0
- package/dist/tools/gesture-emulation-tools.js +135 -0
- package/dist/tools/gesture-network-tools.d.ts +17 -0
- package/dist/tools/gesture-network-tools.js +27 -0
- package/dist/tools/gesture-route-tools.d.ts +8 -0
- package/dist/tools/gesture-route-tools.js +142 -0
- package/dist/tools/gesture-websocket-tools.d.ts +8 -0
- package/dist/tools/gesture-websocket-tools.js +122 -0
- package/dist/tools/gesture-worker-tools.d.ts +9 -0
- package/dist/tools/gesture-worker-tools.js +200 -0
- package/dist/tools/host-build.d.ts +76 -0
- package/dist/tools/host-build.js +516 -0
- package/dist/tools/host.d.ts +287 -0
- package/dist/tools/host.js +1 -0
- package/dist/tools/input-tools.d.ts +10 -0
- package/dist/tools/input-tools.js +176 -0
- package/dist/tools/live-emulation-tools.d.ts +9 -0
- package/dist/tools/live-emulation-tools.js +353 -0
- package/dist/tools/plugin-runtime.d.ts +36 -0
- package/dist/tools/plugin-runtime.js +274 -0
- package/dist/tools/read-observe-buffer-tools.d.ts +9 -0
- package/dist/tools/read-observe-buffer-tools.js +385 -0
- package/dist/tools/read-observe-capture-tools.d.ts +12 -0
- package/dist/tools/read-observe-capture-tools.js +376 -0
- package/dist/tools/read-observe-dom-tools.d.ts +8 -0
- package/dist/tools/read-observe-dom-tools.js +308 -0
- package/dist/tools/read-observe-extract-tools.d.ts +8 -0
- package/dist/tools/read-observe-extract-tools.js +232 -0
- package/dist/tools/read-observe-verify-tools.d.ts +8 -0
- package/dist/tools/read-observe-verify-tools.js +316 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.js +58 -0
- package/dist/tools/secrets-captcha-tools.d.ts +9 -0
- package/dist/tools/secrets-captcha-tools.js +231 -0
- package/dist/tools/session-dialog-permission-tools.d.ts +9 -0
- package/dist/tools/session-dialog-permission-tools.js +287 -0
- package/dist/tools/session-lifecycle-tools.d.ts +8 -0
- package/dist/tools/session-lifecycle-tools.js +314 -0
- package/dist/tools/session-notification-device-tools.d.ts +9 -0
- package/dist/tools/session-notification-device-tools.js +156 -0
- package/dist/tools/session-policy-tools.d.ts +16 -0
- package/dist/tools/session-policy-tools.js +22 -0
- package/dist/tools/session-registry.d.ts +28 -0
- package/dist/tools/session-registry.js +427 -0
- package/dist/tools/storage-artifact-har-video-tools.d.ts +8 -0
- package/dist/tools/storage-artifact-har-video-tools.js +311 -0
- package/dist/tools/storage-cache-idb-tools.d.ts +8 -0
- package/dist/tools/storage-cache-idb-tools.js +347 -0
- package/dist/tools/storage-state-cookies-tools.d.ts +8 -0
- package/dist/tools/storage-state-cookies-tools.js +223 -0
- package/dist/tools/storage-tools.d.ts +17 -0
- package/dist/tools/storage-tools.js +25 -0
- package/dist/tools/storage-web-auth-tools.d.ts +10 -0
- package/dist/tools/storage-web-auth-tools.js +230 -0
- package/dist/tools/tool-metadata.d.ts +8 -0
- package/dist/tools/tool-metadata.js +185 -0
- package/dist/util/batch.d.ts +83 -0
- package/dist/util/batch.js +191 -0
- package/dist/util/capabilities.d.ts +504 -0
- package/dist/util/capabilities.js +254 -0
- package/dist/util/config-store.d.ts +103 -0
- package/dist/util/config-store.js +206 -0
- package/dist/util/config.d.ts +11 -0
- package/dist/util/config.js +28 -0
- package/dist/util/credentials.d.ts +136 -0
- package/dist/util/credentials.js +622 -0
- package/dist/util/deadline.d.ts +22 -0
- package/dist/util/deadline.js +62 -0
- package/dist/util/diagnostics.d.ts +161 -0
- package/dist/util/diagnostics.js +579 -0
- package/dist/util/egress-sanitiser.d.ts +29 -0
- package/dist/util/egress-sanitiser.js +52 -0
- package/dist/util/failure.d.ts +8 -0
- package/dist/util/failure.js +50 -0
- package/dist/util/flake-check.d.ts +109 -0
- package/dist/util/flake-check.js +342 -0
- package/dist/util/invariant.d.ts +25 -0
- package/dist/util/invariant.js +66 -0
- package/dist/util/logging.d.ts +6 -0
- package/dist/util/logging.js +12 -0
- package/dist/util/predicates.d.ts +62 -0
- package/dist/util/predicates.js +340 -0
- package/dist/util/secrets.d.ts +104 -0
- package/dist/util/secrets.js +219 -0
- package/dist/util/tokens.d.ts +6 -0
- package/dist/util/tokens.js +24 -0
- package/dist/util/url-sanitizer.d.ts +19 -0
- package/dist/util/url-sanitizer.js +70 -0
- package/dist/util/version.d.ts +2 -0
- package/dist/util/version.js +21 -0
- package/dist/util/workspace.d.ts +7 -0
- package/dist/util/workspace.js +22 -0
- package/package.json +120 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
// Per-session File System Access API policy. Sibling of `dialog_policy` /
|
|
2
|
+
// `permission_policy`. Plugs the FS-picker blind spot: modern web editors
|
|
3
|
+
// (VSCode for the web, Figma, anything calling `showOpenFilePicker` /
|
|
4
|
+
// `showSaveFilePicker` / `showDirectoryPicker`) deadlock under a headless
|
|
5
|
+
// session — the picker dialog blocks every subsequent browser event until
|
|
6
|
+
// the human clicks a real OS file chooser that doesn't exist in headless,
|
|
7
|
+
// and even on attached Chrome the human can't see the picker through the
|
|
8
|
+
// agent's session.
|
|
9
|
+
//
|
|
10
|
+
// Policy modes (mirror `permission_policy`):
|
|
11
|
+
// - "allow" — the agent provides files server-side via `fs_picker_respond`;
|
|
12
|
+
// the init-script stub returns synthetic FileSystem*Handle
|
|
13
|
+
// objects to the page. For showSaveFilePicker, the agent
|
|
14
|
+
// supplies a workspace-rooted destination path and writes
|
|
15
|
+
// from `createWritable()` are persisted there.
|
|
16
|
+
// - "deny" — the stub throws `NotAllowedError` ("user dismissed the
|
|
17
|
+
// picker"). The page sees the standard rejection path.
|
|
18
|
+
// - "raise" — DEFAULT (deterministic anti-deadlock). Stub throws
|
|
19
|
+
// `NotAllowedError` AND records the request as
|
|
20
|
+
// `handledAs:"raised"`. The next ActionResult flips
|
|
21
|
+
// `ok:false` with a stable hint pointing at
|
|
22
|
+
// `set_fs_picker_policy`. The page never blocks (the
|
|
23
|
+
// picker rejects immediately), but a picker call never
|
|
24
|
+
// silently changes app state under an unaware caller
|
|
25
|
+
// either.
|
|
26
|
+
// - "ask-human" — server records the request, blocks on the bridge
|
|
27
|
+
// (`await_human({kind:"input"})` mechanism), then either
|
|
28
|
+
// resolves with agent-provided files or denies per the
|
|
29
|
+
// human's response.
|
|
30
|
+
//
|
|
31
|
+
// Per-API override map. The top-level `mode` is the default; the per-API
|
|
32
|
+
// map (`perAPI: { showSaveFilePicker: "allow", … }`) overrides it for a
|
|
33
|
+
// specific picker. Mirrors `permission_policy.perPermission`.
|
|
34
|
+
//
|
|
35
|
+
// Per-action capture. Every page-side picker call is appended to a buffer
|
|
36
|
+
// with a timestamp. `since(ts)` slices for the action window — same pattern
|
|
37
|
+
// as `dialog_policy` / `permission_policy`; `raisedSince(ts)` drives the
|
|
38
|
+
// `ok:false` flip.
|
|
39
|
+
//
|
|
40
|
+
// Why one layer (init-script stubs) instead of two (CDP + init-script):
|
|
41
|
+
// - There is no CDP analogue for the File System Access API — Chromium
|
|
42
|
+
// exposes the picker UX only via the real OS file chooser, which
|
|
43
|
+
// headless can't drive and which (on attached Chrome) wouldn't route
|
|
44
|
+
// through the agent. The init-script stub IS the policy enforcement
|
|
45
|
+
// point. The native `window.show*FilePicker` is replaced before any
|
|
46
|
+
// page script runs, so the original is never called.
|
|
47
|
+
//
|
|
48
|
+
// FileSystemFileHandle.createWritable() handling:
|
|
49
|
+
// - In `allow` mode for `showSaveFilePicker`, the agent supplies a
|
|
50
|
+
// workspace-rooted `path` via `fs_picker_respond`. The init-script
|
|
51
|
+
// stub returns a synthetic `FileSystemFileHandle` whose
|
|
52
|
+
// `createWritable()` returns a stub `FileSystemWritableFileStream`
|
|
53
|
+
// that routes every `write(chunk)` / `truncate()` / `close()` /
|
|
54
|
+
// `abort()` through a server-side binding (`__browx_fs_picker_write`),
|
|
55
|
+
// which append-writes to the workspace path. Workspace-escape on the
|
|
56
|
+
// path is rejected at `fs_picker_respond` time (the agent never
|
|
57
|
+
// supplies a path to the page-side stub directly).
|
|
58
|
+
// - In `allow` mode for `showOpenFilePicker`, the agent supplies either
|
|
59
|
+
// inline `{contents, name}` (base64 file bytes the page reads back
|
|
60
|
+
// via `getFile()`) or a workspace-rooted `{path}` (server reads the
|
|
61
|
+
// file once at respond time and inlines the bytes). The handle's
|
|
62
|
+
// `createWritable()` returns a no-op stub — open-pickers are
|
|
63
|
+
// read-side; the agent didn't supply a destination.
|
|
64
|
+
// - `showDirectoryPicker` returns a minimal directory handle: `.name`
|
|
65
|
+
// is the basename of the agent-supplied path (or "browxai-virtual"
|
|
66
|
+
// when synthetic); `entries()` / `values()` / `keys()` iterate empty.
|
|
67
|
+
// Best-effort by construction — a real directory tree would require
|
|
68
|
+
// either reading the workspace path recursively (heavy + a footgun
|
|
69
|
+
// when the workspace holds artefacts the page shouldn't see) or
|
|
70
|
+
// synthesising one from agent input (complex). MVP scope is "the
|
|
71
|
+
// picker dialog doesn't deadlock and the page can check that it got
|
|
72
|
+
// a directory" — most modern editors will then re-prompt for
|
|
73
|
+
// individual files.
|
|
74
|
+
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
75
|
+
import { dirname, resolve, sep, basename } from "node:path";
|
|
76
|
+
import { log } from "../util/logging.js";
|
|
77
|
+
import { PolicyRecordBuffer } from "./policy-buffer.js";
|
|
78
|
+
/** The three File System Access API entry points browxai governs. v1 set.
|
|
79
|
+
* Re-exported in `fs_picker_policy` tool docs + tool-reference.md. */
|
|
80
|
+
export const SUPPORTED_FS_PICKER_APIS = [
|
|
81
|
+
"showOpenFilePicker",
|
|
82
|
+
"showSaveFilePicker",
|
|
83
|
+
"showDirectoryPicker",
|
|
84
|
+
];
|
|
85
|
+
/** Hint emitted on `ActionResult.failure.hint` when `raise` mode fired.
|
|
86
|
+
* Stable, agent-facing string — referenced in docs/tool-reference.md. */
|
|
87
|
+
export const UNHANDLED_FS_PICKER_HINT = "unhandled File System Access picker — set fsPickerPolicy (open_session/set_fs_picker_policy) " +
|
|
88
|
+
'to "allow", "deny", or "ask-human" before driving an action that may trigger one. ' +
|
|
89
|
+
"The picker was rejected page-side (NotAllowedError) so the page is not deadlocked, but " +
|
|
90
|
+
"the app effect is the user-dismissed branch. In `allow` mode, supply the file(s) with " +
|
|
91
|
+
"`fs_picker_respond` before (or in parallel with) the action that triggers the picker.";
|
|
92
|
+
/** Mutable per-session state. The page-side stubs read `current(api)` on
|
|
93
|
+
* every call, so a `set_fs_picker_policy` call takes effect on the very
|
|
94
|
+
* next picker without page reload. */
|
|
95
|
+
export class FsPickerPolicyState {
|
|
96
|
+
policy;
|
|
97
|
+
/** Bounded record ring (shared `PolicyRecordBuffer` — the hard cap so a chatty
|
|
98
|
+
* page can't grow this without bound; the per-action slice is the only
|
|
99
|
+
* consumer, older records are noise). */
|
|
100
|
+
records;
|
|
101
|
+
/** Per-API response queue. `fs_picker_respond` pushes; the binding
|
|
102
|
+
* dequeues on the next matching picker call. Per-API so a queued
|
|
103
|
+
* open-file response doesn't satisfy a save-file picker (different
|
|
104
|
+
* semantics, different agent intent). */
|
|
105
|
+
responses = {
|
|
106
|
+
showOpenFilePicker: [],
|
|
107
|
+
showSaveFilePicker: [],
|
|
108
|
+
showDirectoryPicker: [],
|
|
109
|
+
};
|
|
110
|
+
/** Contexts we've already installed the init-script + binding on.
|
|
111
|
+
* Idempotent install guard — BYOB reconnect / context rebuild MUST not
|
|
112
|
+
* double-wire. */
|
|
113
|
+
wired = new WeakSet();
|
|
114
|
+
constructor(initial = { mode: "raise" }, cap = 200) {
|
|
115
|
+
this.policy = normalise(initial);
|
|
116
|
+
this.records = new PolicyRecordBuffer(cap);
|
|
117
|
+
}
|
|
118
|
+
/** Resolved policy snapshot. */
|
|
119
|
+
current() {
|
|
120
|
+
return {
|
|
121
|
+
mode: this.policy.mode,
|
|
122
|
+
...(this.policy.perAPI ? { perAPI: { ...this.policy.perAPI } } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Effective mode for one API — per-API map wins, else top-level. */
|
|
126
|
+
modeFor(api) {
|
|
127
|
+
return this.policy.perAPI?.[api] ?? this.policy.mode;
|
|
128
|
+
}
|
|
129
|
+
set(next) {
|
|
130
|
+
this.policy = normalise(next);
|
|
131
|
+
return this.current();
|
|
132
|
+
}
|
|
133
|
+
/** Append a request record. Caps the buffer at `cap`. */
|
|
134
|
+
record(rec) {
|
|
135
|
+
this.records.record(rec);
|
|
136
|
+
}
|
|
137
|
+
/** Slice records with `ts >= since`. Used by the action-window. */
|
|
138
|
+
since(since) {
|
|
139
|
+
return this.records.since(since);
|
|
140
|
+
}
|
|
141
|
+
/** True if any record in `[since, now]` was handled in `raise` mode.
|
|
142
|
+
* When true, the action-window flips the result to `ok:false`. */
|
|
143
|
+
raisedSince(since) {
|
|
144
|
+
return this.records.matchedSince(since, (r) => r.handledAs === "raised");
|
|
145
|
+
}
|
|
146
|
+
/** Queue an agent-supplied response for the next picker of `api`.
|
|
147
|
+
* Pushed by `fs_picker_respond`; consumed by the binding on the next
|
|
148
|
+
* matching call. */
|
|
149
|
+
pushResponse(api, files) {
|
|
150
|
+
this.responses[api].push(files);
|
|
151
|
+
}
|
|
152
|
+
/** Dequeue the next agent-supplied response for `api`. Returns
|
|
153
|
+
* `undefined` when the queue is empty (the binding falls back to a
|
|
154
|
+
* best-effort empty file list so the page still gets a usable
|
|
155
|
+
* shape — not deadlocking is the contract). */
|
|
156
|
+
dequeueResponse(api) {
|
|
157
|
+
return this.responses[api].shift();
|
|
158
|
+
}
|
|
159
|
+
/** Has this context already been wired? Idempotent install guard. */
|
|
160
|
+
hasContext(c) {
|
|
161
|
+
return this.wired.has(c);
|
|
162
|
+
}
|
|
163
|
+
/** Mark a context as wired. */
|
|
164
|
+
markContext(c) {
|
|
165
|
+
this.wired.add(c);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Idempotent normaliser. Rejects unknown top-level modes; per-API map is
|
|
169
|
+
* validated per-entry (an unknown API name in the map throws so the caller
|
|
170
|
+
* gets a fast error instead of silent fallthrough). */
|
|
171
|
+
function normalise(p) {
|
|
172
|
+
if (!isFsPickerMode(p.mode)) {
|
|
173
|
+
throw new Error(`fsPickerPolicy: invalid mode "${String(p.mode)}" — expected "allow" | "deny" | "raise" | "ask-human"`);
|
|
174
|
+
}
|
|
175
|
+
if (p.perAPI) {
|
|
176
|
+
const cleaned = {};
|
|
177
|
+
for (const [name, mode] of Object.entries(p.perAPI)) {
|
|
178
|
+
if (!SUPPORTED_FS_PICKER_APIS.includes(name)) {
|
|
179
|
+
throw new Error(`fsPickerPolicy.perAPI: unknown API "${name}" — supported: ${SUPPORTED_FS_PICKER_APIS.join(", ")}`);
|
|
180
|
+
}
|
|
181
|
+
if (mode === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
if (!isFsPickerMode(mode)) {
|
|
184
|
+
throw new Error(`fsPickerPolicy.perAPI["${name}"]: invalid mode "${String(mode)}" — expected "allow" | "deny" | "raise" | "ask-human"`);
|
|
185
|
+
}
|
|
186
|
+
cleaned[name] = mode;
|
|
187
|
+
}
|
|
188
|
+
return { mode: p.mode, perAPI: cleaned };
|
|
189
|
+
}
|
|
190
|
+
return { mode: p.mode };
|
|
191
|
+
}
|
|
192
|
+
function isFsPickerMode(m) {
|
|
193
|
+
return m === "allow" || m === "deny" || m === "raise" || m === "ask-human";
|
|
194
|
+
}
|
|
195
|
+
/** Parse the spec's compact string form for the top-level mode, or accept
|
|
196
|
+
* the object form. Idempotent. */
|
|
197
|
+
export function parseFsPickerPolicyArg(v) {
|
|
198
|
+
if (!v)
|
|
199
|
+
return { mode: "raise" };
|
|
200
|
+
if (typeof v === "object")
|
|
201
|
+
return normalise(v);
|
|
202
|
+
if (isFsPickerMode(v))
|
|
203
|
+
return { mode: v };
|
|
204
|
+
throw new Error(`fsPickerPolicy: invalid value "${v}" — expected "allow" | "deny" | "raise" | "ask-human"`);
|
|
205
|
+
}
|
|
206
|
+
/** Resolve + validate a workspace-rooted file path. Mirrors the workspace-
|
|
207
|
+
* escape rejection in `src/page/upload.ts`. Exported for tests; used by
|
|
208
|
+
* the binding install + by `fs_picker_respond`. */
|
|
209
|
+
export function resolveWorkspaceFsPath(workspaceRoot, path) {
|
|
210
|
+
const resolved = resolve(workspaceRoot, path);
|
|
211
|
+
if (resolved !== workspaceRoot && !resolved.startsWith(workspaceRoot + sep)) {
|
|
212
|
+
throw new Error("fs_picker_respond: `path` must resolve inside $BROWX_WORKSPACE — stage the file there, or use `contents` (base64)");
|
|
213
|
+
}
|
|
214
|
+
return resolved;
|
|
215
|
+
}
|
|
216
|
+
/** Init script that replaces the page-side File System Access entry points
|
|
217
|
+
* with stubs that route through the per-session policy. Stringified so it
|
|
218
|
+
* can be passed to `addInitScript` and `page.evaluate`. Keep browser-only
|
|
219
|
+
* JS — no TS-only syntax. Re-injected on `framenavigated` (idempotent:
|
|
220
|
+
* guards on `window.__browx_fs_picker_installed`).
|
|
221
|
+
*
|
|
222
|
+
* The stubs consult `window.__browx_fs_picker_check({api, suggestedName})`
|
|
223
|
+
* (an exposeBinding callable from page context) — it returns one of:
|
|
224
|
+
* - `{decision:"allow", files:[{handleId, name, mimeType, contents?}]}`:
|
|
225
|
+
* the agent staged file(s); the stub builds synthetic
|
|
226
|
+
* `FileSystemFileHandle` / `FileSystemDirectoryHandle` objects whose
|
|
227
|
+
* `getFile()` returns a synthetic `File` and `createWritable()` returns
|
|
228
|
+
* a synthetic writable stream routed through
|
|
229
|
+
* `__browx_fs_picker_write({handleId, op, data?})`.
|
|
230
|
+
* - `{decision:"deny"}`: the stub throws `NotAllowedError`.
|
|
231
|
+
*
|
|
232
|
+
* Stubs are written to be browser-only JS (no TS-only syntax). The
|
|
233
|
+
* install guards on `window.__browx_fs_picker_installed`. */
|
|
234
|
+
export const FS_PICKER_PAGE_SCRIPT = `(() => {
|
|
235
|
+
if (window.__browx_fs_picker_installed) return;
|
|
236
|
+
window.__browx_fs_picker_installed = true;
|
|
237
|
+
|
|
238
|
+
function check(api, suggestedName) {
|
|
239
|
+
try {
|
|
240
|
+
if (typeof window.__browx_fs_picker_check === "function") {
|
|
241
|
+
return Promise.resolve(window.__browx_fs_picker_check(JSON.stringify({
|
|
242
|
+
api: api, suggestedName: suggestedName,
|
|
243
|
+
})));
|
|
244
|
+
}
|
|
245
|
+
} catch (_) {}
|
|
246
|
+
// Binding missing — safe-by-default deny so the page never deadlocks.
|
|
247
|
+
return Promise.resolve(JSON.stringify({ decision: "deny" }));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function notAllowed(msg) {
|
|
251
|
+
var e = new Error(msg || "The user aborted a request.");
|
|
252
|
+
try { e.name = "NotAllowedError"; } catch (_) {}
|
|
253
|
+
return e;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function b64ToBytes(b64) {
|
|
257
|
+
try {
|
|
258
|
+
var binary = atob(b64);
|
|
259
|
+
var len = binary.length;
|
|
260
|
+
var bytes = new Uint8Array(len);
|
|
261
|
+
for (var i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
|
|
262
|
+
return bytes;
|
|
263
|
+
} catch (_) {
|
|
264
|
+
return new Uint8Array(0);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function syntheticFile(spec) {
|
|
269
|
+
var name = spec.name || "browxai-virtual";
|
|
270
|
+
var mimeType = spec.mimeType || "application/octet-stream";
|
|
271
|
+
var bytes = spec.contents ? b64ToBytes(spec.contents) : new Uint8Array(0);
|
|
272
|
+
try {
|
|
273
|
+
return new File([bytes], name, { type: mimeType });
|
|
274
|
+
} catch (_) {
|
|
275
|
+
// Fallback Blob-shaped object for environments without File (test
|
|
276
|
+
// pages); the constructor is universally available on real browsers.
|
|
277
|
+
var blob = new Blob([bytes], { type: mimeType });
|
|
278
|
+
blob.name = name;
|
|
279
|
+
return blob;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function syntheticWritable(handleId) {
|
|
284
|
+
// Route every operation through the server-side binding. Each call is
|
|
285
|
+
// ack'd by the binding so we surface back-pressure to a determined page
|
|
286
|
+
// (await stream.write(buf) resolves only after the write hit disk).
|
|
287
|
+
function call(op, data) {
|
|
288
|
+
try {
|
|
289
|
+
if (typeof window.__browx_fs_picker_write === "function") {
|
|
290
|
+
return Promise.resolve(window.__browx_fs_picker_write(JSON.stringify({
|
|
291
|
+
handleId: handleId, op: op, data: data == null ? null : data,
|
|
292
|
+
})));
|
|
293
|
+
}
|
|
294
|
+
} catch (_) {}
|
|
295
|
+
return Promise.resolve(undefined);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
write: function (data) {
|
|
299
|
+
// Accept BufferSource | Blob | string | { type:"write"|"seek"|"truncate", … }
|
|
300
|
+
if (data == null) return Promise.resolve(undefined);
|
|
301
|
+
if (typeof data === "string") return call("write", data);
|
|
302
|
+
if (data.type === "seek") return call("seek", String(data.position || 0));
|
|
303
|
+
if (data.type === "truncate") return call("truncate", String(data.size || 0));
|
|
304
|
+
if (data.type === "write" && data.data != null) return call("write", encodeForBinding(data.data));
|
|
305
|
+
return call("write", encodeForBinding(data));
|
|
306
|
+
},
|
|
307
|
+
seek: function (position) { return call("seek", String(position || 0)); },
|
|
308
|
+
truncate: function (size) { return call("truncate", String(size || 0)); },
|
|
309
|
+
close: function () { return call("close"); },
|
|
310
|
+
abort: function () { return call("abort"); },
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function encodeForBinding(data) {
|
|
315
|
+
// base64-encode any BufferSource / Blob / string for binding transport.
|
|
316
|
+
// exposeBinding payloads are strings; we wrap as "b64:<base64>" so the
|
|
317
|
+
// server side can distinguish from a literal text write.
|
|
318
|
+
function bytesToB64(bytes) {
|
|
319
|
+
var s = "";
|
|
320
|
+
for (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
321
|
+
try { return "b64:" + btoa(s); } catch (_) { return "b64:"; }
|
|
322
|
+
}
|
|
323
|
+
if (typeof data === "string") return data;
|
|
324
|
+
if (data instanceof ArrayBuffer) return bytesToB64(new Uint8Array(data));
|
|
325
|
+
if (ArrayBuffer.isView && ArrayBuffer.isView(data)) {
|
|
326
|
+
return bytesToB64(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
327
|
+
}
|
|
328
|
+
if (data instanceof Blob) {
|
|
329
|
+
// Synchronous-from-page Promise; the binding awaits.
|
|
330
|
+
return data.arrayBuffer().then(function (ab) { return bytesToB64(new Uint8Array(ab)); });
|
|
331
|
+
}
|
|
332
|
+
return String(data);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function syntheticFileHandle(spec) {
|
|
336
|
+
var handleId = spec.handleId;
|
|
337
|
+
var name = spec.name || "browxai-virtual";
|
|
338
|
+
return {
|
|
339
|
+
kind: "file",
|
|
340
|
+
name: name,
|
|
341
|
+
getFile: function () { return Promise.resolve(syntheticFile(spec)); },
|
|
342
|
+
createWritable: function () { return Promise.resolve(syntheticWritable(handleId)); },
|
|
343
|
+
// Minimal queryPermission / requestPermission — the page often probes
|
|
344
|
+
// before reading. Always "granted" for our virtual handles.
|
|
345
|
+
queryPermission: function () { return Promise.resolve("granted"); },
|
|
346
|
+
requestPermission: function () { return Promise.resolve("granted"); },
|
|
347
|
+
// Comparison helper expected by some libraries.
|
|
348
|
+
isSameEntry: function (other) { return Promise.resolve(other === this); },
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function syntheticDirectoryHandle(spec) {
|
|
353
|
+
var name = spec.name || "browxai-virtual";
|
|
354
|
+
// MVP scope: empty directory. Most editors will fall back to per-file
|
|
355
|
+
// pickers when iteration yields nothing.
|
|
356
|
+
var empty = {
|
|
357
|
+
next: function () { return Promise.resolve({ value: undefined, done: true }); },
|
|
358
|
+
return: function () { return Promise.resolve({ value: undefined, done: true }); },
|
|
359
|
+
};
|
|
360
|
+
var emptyIter = { __asyncIterator__: true };
|
|
361
|
+
emptyIter[Symbol.asyncIterator] = function () { return empty; };
|
|
362
|
+
return {
|
|
363
|
+
kind: "directory",
|
|
364
|
+
name: name,
|
|
365
|
+
entries: function () { return emptyIter; },
|
|
366
|
+
values: function () { return emptyIter; },
|
|
367
|
+
keys: function () { return emptyIter; },
|
|
368
|
+
getFileHandle: function () { return Promise.reject(notAllowed("Not found in virtual directory")); },
|
|
369
|
+
getDirectoryHandle: function () { return Promise.reject(notAllowed("Not found in virtual directory")); },
|
|
370
|
+
removeEntry: function () { return Promise.resolve(undefined); },
|
|
371
|
+
resolve: function () { return Promise.resolve(null); },
|
|
372
|
+
queryPermission: function () { return Promise.resolve("granted"); },
|
|
373
|
+
requestPermission: function () { return Promise.resolve("granted"); },
|
|
374
|
+
isSameEntry: function (other) { return Promise.resolve(other === this); },
|
|
375
|
+
[Symbol.asyncIterator]: function () { return empty; },
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function installStub(apiName, isDirectory, isMulti) {
|
|
380
|
+
Object.defineProperty(window, apiName, {
|
|
381
|
+
configurable: true,
|
|
382
|
+
writable: true,
|
|
383
|
+
value: function (options) {
|
|
384
|
+
var suggestedName = options && options.suggestedName ? String(options.suggestedName) : undefined;
|
|
385
|
+
return check(apiName, suggestedName).then(function (raw) {
|
|
386
|
+
var resp;
|
|
387
|
+
try { resp = typeof raw === "string" ? JSON.parse(raw) : (raw || {}); } catch (_) { resp = { decision: "deny" }; }
|
|
388
|
+
if (resp.decision !== "allow") {
|
|
389
|
+
throw notAllowed("The user aborted a " + apiName + " request.");
|
|
390
|
+
}
|
|
391
|
+
var files = Array.isArray(resp.files) ? resp.files : [];
|
|
392
|
+
if (isDirectory) {
|
|
393
|
+
var dirSpec = files[0] || { handleId: resp.handleIdFallback || "dir-0", name: "browxai-virtual" };
|
|
394
|
+
return syntheticDirectoryHandle(dirSpec);
|
|
395
|
+
}
|
|
396
|
+
if (isMulti) {
|
|
397
|
+
return files.map(function (f) { return syntheticFileHandle(f); });
|
|
398
|
+
}
|
|
399
|
+
var spec = files[0] || { handleId: resp.handleIdFallback || "file-0", name: "browxai-virtual" };
|
|
400
|
+
return syntheticFileHandle(spec);
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// showOpenFilePicker: returns Array<FileSystemFileHandle> (multi by default).
|
|
407
|
+
installStub("showOpenFilePicker", false, true);
|
|
408
|
+
// showSaveFilePicker: returns FileSystemFileHandle (single).
|
|
409
|
+
installStub("showSaveFilePicker", false, false);
|
|
410
|
+
// showDirectoryPicker: returns FileSystemDirectoryHandle (single).
|
|
411
|
+
installStub("showDirectoryPicker", true, false);
|
|
412
|
+
})();`;
|
|
413
|
+
/** Server-side wire-up. Installs:
|
|
414
|
+
* - `__browx_fs_picker_check` exposeBinding: synchronous-from-page consult
|
|
415
|
+
* that records the request, dequeues an agent-staged response when the
|
|
416
|
+
* policy is `allow` (or routes ask-human via `askHandler`), and returns
|
|
417
|
+
* `{decision, files?}`. Files carry a `handleId` the page-side stub
|
|
418
|
+
* uses to route subsequent `createWritable()` ops back to the right
|
|
419
|
+
* target.
|
|
420
|
+
* - `__browx_fs_picker_write` exposeBinding: target of every
|
|
421
|
+
* `createWritable()`-driven write. Writes append-mode to the
|
|
422
|
+
* workspace-rooted path the agent supplied. `close` / `abort` clean
|
|
423
|
+
* up the per-handle target.
|
|
424
|
+
* - The page-side init script (see above), re-injected by Playwright on
|
|
425
|
+
* every new document.
|
|
426
|
+
*
|
|
427
|
+
* Idempotent on the same context. Errors during install are logged and
|
|
428
|
+
* swallowed — the page-side stub falls back to safe-by-default deny when
|
|
429
|
+
* the binding is missing, so the page still doesn't deadlock.
|
|
430
|
+
*/
|
|
431
|
+
export async function attachFsPickerPolicy(context, state, workspaceRoot, askHandler) {
|
|
432
|
+
if (state.hasContext(context))
|
|
433
|
+
return;
|
|
434
|
+
state.markContext(context);
|
|
435
|
+
// Per-context handle map. Each `allow`-mode response mints a fresh
|
|
436
|
+
// handle id; the page stub round-trips it on every write op so we
|
|
437
|
+
// route writes to the right target file.
|
|
438
|
+
const handles = new Map();
|
|
439
|
+
let handleCounter = 0;
|
|
440
|
+
try {
|
|
441
|
+
await context.exposeBinding("__browx_fs_picker_check", async (_source, payload) => {
|
|
442
|
+
try {
|
|
443
|
+
const o = JSON.parse(payload);
|
|
444
|
+
const api = o.api;
|
|
445
|
+
if (!SUPPORTED_FS_PICKER_APIS.includes(api)) {
|
|
446
|
+
// Unknown API — safe-by-default deny.
|
|
447
|
+
return JSON.stringify({ decision: "deny" });
|
|
448
|
+
}
|
|
449
|
+
const suggestedName = o.suggestedName;
|
|
450
|
+
const mode = state.modeFor(api);
|
|
451
|
+
const ts = Date.now();
|
|
452
|
+
const baseRec = {
|
|
453
|
+
api,
|
|
454
|
+
ts,
|
|
455
|
+
...(suggestedName ? { suggestedName } : {}),
|
|
456
|
+
};
|
|
457
|
+
switch (mode) {
|
|
458
|
+
case "allow": {
|
|
459
|
+
const files = state.dequeueResponse(api) ?? [];
|
|
460
|
+
const prepared = prepareAllowResponse(api, files, workspaceRoot, handles, () => `h${++handleCounter}`);
|
|
461
|
+
state.record({ ...baseRec, handledAs: "allowed" });
|
|
462
|
+
return JSON.stringify({ decision: "allow", files: prepared });
|
|
463
|
+
}
|
|
464
|
+
case "deny": {
|
|
465
|
+
state.record({ ...baseRec, handledAs: "denied" });
|
|
466
|
+
return JSON.stringify({ decision: "deny" });
|
|
467
|
+
}
|
|
468
|
+
case "ask-human": {
|
|
469
|
+
const askResult = await askHandler(api, suggestedName).catch(() => null);
|
|
470
|
+
state.record({ ...baseRec, handledAs: "asked-human" });
|
|
471
|
+
if (!askResult)
|
|
472
|
+
return JSON.stringify({ decision: "deny" });
|
|
473
|
+
const prepared = prepareAllowResponse(api, askResult, workspaceRoot, handles, () => `h${++handleCounter}`);
|
|
474
|
+
return JSON.stringify({ decision: "allow", files: prepared });
|
|
475
|
+
}
|
|
476
|
+
case "raise":
|
|
477
|
+
default: {
|
|
478
|
+
state.record({ ...baseRec, handledAs: "raised" });
|
|
479
|
+
return JSON.stringify({ decision: "deny" });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
log.warn("session.fs-picker: check handler error", {
|
|
485
|
+
error: err instanceof Error ? err.message : String(err),
|
|
486
|
+
});
|
|
487
|
+
return JSON.stringify({ decision: "deny" });
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
await context.exposeBinding("__browx_fs_picker_write", (_source, payload) => {
|
|
491
|
+
try {
|
|
492
|
+
const o = JSON.parse(payload);
|
|
493
|
+
const id = o.handleId;
|
|
494
|
+
const op = o.op;
|
|
495
|
+
if (!id || !op)
|
|
496
|
+
return undefined;
|
|
497
|
+
const target = handles.get(id);
|
|
498
|
+
if (!target)
|
|
499
|
+
return undefined;
|
|
500
|
+
if (target.closed && op !== "close" && op !== "abort")
|
|
501
|
+
return undefined;
|
|
502
|
+
if (target.path === null) {
|
|
503
|
+
// Open-picker read-side; the page is writing back to a virtual
|
|
504
|
+
// handle. Drop on the floor with a one-time warning so the page
|
|
505
|
+
// doesn't see an error mid-flight.
|
|
506
|
+
if (op === "write") {
|
|
507
|
+
log.warn("session.fs-picker: write to a read-only virtual handle dropped — open-picker responses don't carry a writable destination; use showSaveFilePicker for writes");
|
|
508
|
+
}
|
|
509
|
+
if (op === "close" || op === "abort")
|
|
510
|
+
target.closed = true;
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
// `target.path` was validated against `workspace.root` (workspace-
|
|
514
|
+
// rooted; workspace-escape rejected at fs_picker_respond time via
|
|
515
|
+
// `resolveWorkspaceFsPath(workspaceRoot, …)` — see prepareAllow-
|
|
516
|
+
// Response). Every mutation below routes through this validated
|
|
517
|
+
// path, never cwd.
|
|
518
|
+
const path = target.path;
|
|
519
|
+
switch (op) {
|
|
520
|
+
case "write": {
|
|
521
|
+
const bytes = decodeChunk(o.data);
|
|
522
|
+
// workspace-rooted write — `path` came from workspace.root.
|
|
523
|
+
if (!target.truncated) {
|
|
524
|
+
const dir = dirname(path);
|
|
525
|
+
if (!existsSync(dir))
|
|
526
|
+
mkdirSync(dir, { recursive: true });
|
|
527
|
+
writeFileSync(path, bytes);
|
|
528
|
+
target.truncated = true;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
// workspace-rooted append — `path` came from workspace.root.
|
|
532
|
+
appendFileSync(path, bytes);
|
|
533
|
+
}
|
|
534
|
+
target.bytesWritten += bytes.length;
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
case "truncate": {
|
|
538
|
+
// Best-effort: rewrite empty up to the requested size.
|
|
539
|
+
// workspace-rooted write — `path` came from workspace.root.
|
|
540
|
+
const size = Number(o.data ?? 0);
|
|
541
|
+
const dir = dirname(path);
|
|
542
|
+
if (!existsSync(dir))
|
|
543
|
+
mkdirSync(dir, { recursive: true });
|
|
544
|
+
writeFileSync(path, Buffer.alloc(Math.max(0, size)));
|
|
545
|
+
target.truncated = true;
|
|
546
|
+
target.bytesWritten = Math.max(0, size);
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
case "seek": {
|
|
550
|
+
// No-op in MVP: Node fs has no native seek-and-overwrite for
|
|
551
|
+
// append-mode; would need fd APIs. Most save-picker flows do
|
|
552
|
+
// a single write+close sequence, so seek is rare.
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
case "close": {
|
|
556
|
+
target.closed = true;
|
|
557
|
+
// If the page never wrote anything, ensure an empty file
|
|
558
|
+
// exists so callers see a deterministic artefact.
|
|
559
|
+
// workspace-rooted write — `path` came from workspace.root.
|
|
560
|
+
if (!target.truncated) {
|
|
561
|
+
const dir = dirname(path);
|
|
562
|
+
if (!existsSync(dir))
|
|
563
|
+
mkdirSync(dir, { recursive: true });
|
|
564
|
+
writeFileSync(path, Buffer.alloc(0));
|
|
565
|
+
target.truncated = true;
|
|
566
|
+
}
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
case "abort": {
|
|
570
|
+
target.closed = true;
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
default:
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
log.warn("session.fs-picker: write handler error", {
|
|
579
|
+
error: err instanceof Error ? err.message : String(err),
|
|
580
|
+
});
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
log.warn("session.fs-picker: exposeBinding install failed; page-side stub falls back to deny", {
|
|
587
|
+
error: err instanceof Error ? err.message : String(err),
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
await context.addInitScript({ content: FS_PICKER_PAGE_SCRIPT });
|
|
592
|
+
for (const page of context.pages()) {
|
|
593
|
+
await page.evaluate(FS_PICKER_PAGE_SCRIPT).catch(() => undefined);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
log.warn("session.fs-picker: addInitScript failed", {
|
|
598
|
+
error: err instanceof Error ? err.message : String(err),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/** Build the page-side response shape for an `allow` / `ask-human` decision.
|
|
603
|
+
* Each file gets a fresh handleId; save-picker `path` entries register a
|
|
604
|
+
* write target so subsequent `createWritable()` writes land in the
|
|
605
|
+
* workspace. Workspace-escape on `path` is rejected here (the agent's
|
|
606
|
+
* response is the only place a path enters the system; `fs_picker_respond`
|
|
607
|
+
* is the validation gate). */
|
|
608
|
+
function prepareAllowResponse(api, files, workspaceRoot, handles, nextId) {
|
|
609
|
+
// Directory picker: agent supplies (at most) one entry; we honour the
|
|
610
|
+
// basename of `path` as `.name` if supplied.
|
|
611
|
+
if (api === "showDirectoryPicker") {
|
|
612
|
+
const entry = files[0];
|
|
613
|
+
const handleId = nextId();
|
|
614
|
+
const path = entry?.path !== undefined ? resolveWorkspaceFsPath(workspaceRoot, entry.path) : null;
|
|
615
|
+
const name = path ? basename(path) : (entry?.name ?? "browxai-virtual");
|
|
616
|
+
handles.set(handleId, {
|
|
617
|
+
api,
|
|
618
|
+
path: null, // directory handle: writes go to per-file children, not the dir itself
|
|
619
|
+
truncated: false,
|
|
620
|
+
closed: false,
|
|
621
|
+
bytesWritten: 0,
|
|
622
|
+
});
|
|
623
|
+
return [{ handleId, name, mimeType: "" }];
|
|
624
|
+
}
|
|
625
|
+
// showSaveFilePicker is single-file; showOpenFilePicker is multi.
|
|
626
|
+
const slice = api === "showSaveFilePicker" ? files.slice(0, 1) : files;
|
|
627
|
+
// Empty-list edge: still hand back ONE virtual file so the page's
|
|
628
|
+
// promise resolves with a usable shape instead of an empty array
|
|
629
|
+
// (the spec says showSaveFilePicker returns a single handle; an empty
|
|
630
|
+
// result would force a downstream NPE on most page code).
|
|
631
|
+
const effective = slice.length > 0 ? slice : [{ name: "browxai-virtual" }];
|
|
632
|
+
return effective.map((f) => {
|
|
633
|
+
const handleId = nextId();
|
|
634
|
+
let path = null;
|
|
635
|
+
let name = f.name ?? "browxai-virtual";
|
|
636
|
+
if (f.path !== undefined) {
|
|
637
|
+
path = resolveWorkspaceFsPath(workspaceRoot, f.path);
|
|
638
|
+
name = basename(path);
|
|
639
|
+
}
|
|
640
|
+
const target = {
|
|
641
|
+
api,
|
|
642
|
+
path: api === "showSaveFilePicker" ? path : null,
|
|
643
|
+
truncated: false,
|
|
644
|
+
closed: false,
|
|
645
|
+
bytesWritten: 0,
|
|
646
|
+
};
|
|
647
|
+
handles.set(handleId, target);
|
|
648
|
+
const out = {
|
|
649
|
+
handleId,
|
|
650
|
+
name,
|
|
651
|
+
mimeType: f.mimeType ?? "application/octet-stream",
|
|
652
|
+
};
|
|
653
|
+
if (f.contents !== undefined)
|
|
654
|
+
out.contents = f.contents;
|
|
655
|
+
return out;
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
/** Decode a page-side write payload. Either `"b64:<base64>"` for binary
|
|
659
|
+
* data or a literal string for text writes. Returns a Buffer. */
|
|
660
|
+
function decodeChunk(raw) {
|
|
661
|
+
if (raw === null || raw === undefined)
|
|
662
|
+
return Buffer.alloc(0);
|
|
663
|
+
if (raw.startsWith("b64:"))
|
|
664
|
+
return Buffer.from(raw.slice("b64:".length), "base64");
|
|
665
|
+
return Buffer.from(raw, "utf8");
|
|
666
|
+
}
|