@wenathlan/saddle 1.8.18 → 2.0.2
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/Dockerfile +1339 -0
- package/README.md +33 -12
- package/alternatives.ts +1218 -0
- package/biome.json +153 -0
- package/boards.json +1517 -0
- package/compute.ts +3144 -0
- package/cores.json +1801 -0
- package/dist/acquisition.d.ts +359 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +629 -0
- package/dist/acquisition.js.map +1 -0
- package/dist/alternatives.d.ts +509 -0
- package/dist/alternatives.d.ts.map +1 -0
- package/dist/alternatives.js +951 -0
- package/dist/alternatives.js.map +1 -0
- package/dist/automation.d.ts +426 -0
- package/dist/automation.d.ts.map +1 -0
- package/dist/automation.js +561 -0
- package/dist/automation.js.map +1 -0
- package/dist/browser.d.ts +562 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +1057 -0
- package/dist/browser.js.map +1 -0
- package/dist/capacitor.config.d.ts +13 -0
- package/dist/capacitor.config.d.ts.map +1 -0
- package/dist/capacitor.config.js +26 -0
- package/dist/capacitor.config.js.map +1 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +111 -0
- package/dist/cli.js.map +1 -0
- package/dist/communication.d.ts +192 -0
- package/dist/communication.d.ts.map +1 -0
- package/dist/communication.js +418 -0
- package/dist/communication.js.map +1 -0
- package/dist/compute.d.ts +1161 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +2273 -0
- package/dist/compute.js.map +1 -0
- package/dist/distribution.d.ts +529 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +811 -0
- package/dist/distribution.js.map +1 -0
- package/dist/execution.d.ts +530 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +761 -0
- package/dist/execution.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +60 -0
- package/dist/format.js.map +1 -0
- package/dist/foundation.d.ts +176 -0
- package/dist/foundation.d.ts.map +1 -0
- package/dist/foundation.js +309 -0
- package/dist/foundation.js.map +1 -0
- package/dist/index.d.ts +733 -123
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1018 -123
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +236 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +236 -0
- package/dist/integration.js.map +1 -0
- package/dist/intelligence.d.ts +94 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +145 -0
- package/dist/intelligence.js.map +1 -0
- package/dist/{isolation/contracts.d.ts → isolation.d.ts} +12 -1
- package/dist/isolation.d.ts.map +1 -0
- package/dist/{isolation/contracts.js → isolation.js} +13 -1
- package/dist/isolation.js.map +1 -0
- package/dist/media.d.ts +344 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +986 -0
- package/dist/media.js.map +1 -0
- package/dist/modes.d.ts +359 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +134 -0
- package/dist/modes.js.map +1 -0
- package/dist/{library/public.d.ts → operations.d.ts} +15 -1
- package/dist/operations.d.ts.map +1 -0
- package/dist/{library/public.js → operations.js} +18 -7
- package/dist/operations.js.map +1 -0
- package/dist/orchestrator.d.ts +1339 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +4308 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/performance.d.ts +416 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +827 -0
- package/dist/performance.js.map +1 -0
- package/dist/quantum.d.ts +810 -0
- package/dist/quantum.d.ts.map +1 -0
- package/dist/quantum.js +2073 -0
- package/dist/quantum.js.map +1 -0
- package/dist/render.d.ts +381 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +1073 -0
- package/dist/render.js.map +1 -0
- package/dist/scheduler.d.ts +596 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +1259 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/security.d.ts +501 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +1170 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/{server/node.js → server.js} +16 -2
- package/dist/server.js.map +1 -0
- package/dist/tiers.d.ts +1002 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +2165 -0
- package/dist/tiers.js.map +1 -0
- package/dist/virtual.d.ts +641 -0
- package/dist/virtual.d.ts.map +1 -0
- package/dist/virtual.js +1285 -0
- package/dist/virtual.js.map +1 -0
- package/dist/virtualcpu.d.ts +316 -0
- package/dist/virtualcpu.d.ts.map +1 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualcpu.js.map +1 -0
- package/dist/virtualgpu.d.ts +345 -0
- package/dist/virtualgpu.d.ts.map +1 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualgpu.js.map +1 -0
- package/dist/virtualization.d.ts +607 -0
- package/dist/virtualization.d.ts.map +1 -0
- package/dist/virtualization.js +1187 -0
- package/dist/virtualization.js.map +1 -0
- package/dist/virtualmemory.d.ts +818 -0
- package/dist/virtualmemory.d.ts.map +1 -0
- package/dist/virtualmemory.js +1796 -0
- package/dist/virtualmemory.js.map +1 -0
- package/dist/webscrape.d.ts +720 -0
- package/dist/webscrape.d.ts.map +1 -0
- package/dist/webscrape.js +2697 -0
- package/dist/webscrape.js.map +1 -0
- package/docker.config +769 -0
- package/docs/CONVERSA.txt +1523 -0
- package/docs/alternatives.md +499 -0
- package/docs/architecture-1.8.19.md +76 -0
- package/docs/architecture-1.8.19.mmd +33 -0
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/architecture.md +355 -0
- package/docs/brancharchive-2026-08-18.md +18 -0
- package/docs/browser.md +7 -0
- package/docs/consolidation.md +70 -0
- package/docs/e2ugh-engine.md +528 -0
- package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
- package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
- package/docs/hardware.md +342 -0
- package/docs/optimization.md +126 -0
- package/docs/performance.md +499 -0
- package/docs/planning.1.8.18.md +94 -0
- package/docs/planning.1.8.19.md +49 -0
- package/docs/releasenotes-1.8.19.md +35 -0
- package/docs/research-1.8.19-virtual-browser.md +27 -0
- package/docs/security.md +454 -0
- package/docs/todo-1.8.18.md +12 -0
- package/docs/todo-1.8.19.md +241 -0
- package/docs/viability.md +777 -0
- package/docs/virtualization.md +335 -0
- package/docs/web-duplicatecleanup.md +9 -0
- package/docs/web-ideas.md +106 -0
- package/docs/workflowimprovements-2026-08-19.md +70 -0
- package/gpumonitor.cpp +1543 -0
- package/gpus.json +2408 -0
- package/index.ts +1478 -0
- package/media.ts +1337 -0
- package/mttg.config +1549 -0
- package/orchestrator.ts +5462 -0
- package/package.json +304 -56
- package/passage.config +1331 -0
- package/performance.ts +1049 -0
- package/processors.json +2217 -0
- package/qemu.config +1062 -0
- package/qemubridge.py +1340 -0
- package/quantum.ts +2393 -0
- package/render.ts +1325 -0
- package/scheduler.ts +1600 -0
- package/security.ts +1449 -0
- package/tiers.ts +2871 -0
- package/tsconfig.json +29 -0
- package/virtualcpu.ts +1273 -0
- package/virtualgpu.ts +1181 -0
- package/virtualhardware.c +1011 -0
- package/virtualhardware.json +739 -0
- package/virtualization.ts +1628 -0
- package/virtualizationcore.cpp +4635 -0
- package/virtualmemory.ts +2303 -0
- package/vm.config.json +1910 -0
- package/{extension → web/extension}/manifest.json +1 -1
- package/web/readme.md +382 -0
- package/web/tsconfig.json +25 -0
- package/dist/adapters/forge.d.ts +0 -17
- package/dist/adapters/forge.d.ts.map +0 -1
- package/dist/adapters/forge.js +0 -19
- package/dist/adapters/forge.js.map +0 -1
- package/dist/adapters/forgejo.d.ts +0 -49
- package/dist/adapters/forgejo.d.ts.map +0 -1
- package/dist/adapters/forgejo.js +0 -8
- package/dist/adapters/forgejo.js.map +0 -1
- package/dist/adapters/github.d.ts +0 -12
- package/dist/adapters/github.d.ts.map +0 -1
- package/dist/adapters/github.js +0 -20
- package/dist/adapters/github.js.map +0 -1
- package/dist/adapters/gitlab.d.ts +0 -17
- package/dist/adapters/gitlab.d.ts.map +0 -1
- package/dist/adapters/gitlab.js +0 -10
- package/dist/adapters/gitlab.js.map +0 -1
- package/dist/adapters/huggingface.d.ts +0 -17
- package/dist/adapters/huggingface.d.ts.map +0 -1
- package/dist/adapters/huggingface.js +0 -6
- package/dist/adapters/huggingface.js.map +0 -1
- package/dist/adapters/socket.d.ts +0 -11
- package/dist/adapters/socket.d.ts.map +0 -1
- package/dist/adapters/socket.js +0 -17
- package/dist/adapters/socket.js.map +0 -1
- package/dist/adapters/transport.d.ts +0 -7
- package/dist/adapters/transport.d.ts.map +0 -1
- package/dist/adapters/transport.js +0 -36
- package/dist/adapters/transport.js.map +0 -1
- package/dist/ai/chunk.d.ts +0 -2
- package/dist/ai/chunk.d.ts.map +0 -1
- package/dist/ai/chunk.js +0 -36
- package/dist/ai/chunk.js.map +0 -1
- package/dist/ai/llmstxt.d.ts +0 -6
- package/dist/ai/llmstxt.d.ts.map +0 -1
- package/dist/ai/llmstxt.js +0 -12
- package/dist/ai/llmstxt.js.map +0 -1
- package/dist/ai/provenance.d.ts +0 -22
- package/dist/ai/provenance.d.ts.map +0 -1
- package/dist/ai/provenance.js +0 -25
- package/dist/ai/provenance.js.map +0 -1
- package/dist/ai/rag.d.ts +0 -26
- package/dist/ai/rag.d.ts.map +0 -1
- package/dist/ai/rag.js +0 -22
- package/dist/ai/rag.js.map +0 -1
- package/dist/ai/tokens.d.ts +0 -15
- package/dist/ai/tokens.d.ts.map +0 -1
- package/dist/ai/tokens.js +0 -9
- package/dist/ai/tokens.js.map +0 -1
- package/dist/api/auth.d.ts +0 -16
- package/dist/api/auth.d.ts.map +0 -1
- package/dist/api/auth.js +0 -19
- package/dist/api/auth.js.map +0 -1
- package/dist/api/contracts.d.ts +0 -28
- package/dist/api/contracts.d.ts.map +0 -1
- package/dist/api/contracts.js +0 -14
- package/dist/api/contracts.js.map +0 -1
- package/dist/api/control.d.ts +0 -10
- package/dist/api/control.d.ts.map +0 -1
- package/dist/api/control.js +0 -35
- package/dist/api/control.js.map +0 -1
- package/dist/api/http.d.ts +0 -7
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/http.js +0 -12
- package/dist/api/http.js.map +0 -1
- package/dist/api/rate.d.ts +0 -28
- package/dist/api/rate.d.ts.map +0 -1
- package/dist/api/rate.js +0 -36
- package/dist/api/rate.js.map +0 -1
- package/dist/api/security.d.ts +0 -12
- package/dist/api/security.d.ts.map +0 -1
- package/dist/api/security.js +0 -56
- package/dist/api/security.js.map +0 -1
- package/dist/api/service.d.ts +0 -5
- package/dist/api/service.d.ts.map +0 -1
- package/dist/api/service.js +0 -70
- package/dist/api/service.js.map +0 -1
- package/dist/apps/registry.d.ts +0 -33
- package/dist/apps/registry.d.ts.map +0 -1
- package/dist/apps/registry.js +0 -24
- package/dist/apps/registry.js.map +0 -1
- package/dist/binary/archive.d.ts +0 -27
- package/dist/binary/archive.d.ts.map +0 -1
- package/dist/binary/archive.js +0 -46
- package/dist/binary/archive.js.map +0 -1
- package/dist/binary/build.d.ts +0 -29
- package/dist/binary/build.d.ts.map +0 -1
- package/dist/binary/build.js +0 -17
- package/dist/binary/build.js.map +0 -1
- package/dist/binary/transform.d.ts +0 -70
- package/dist/binary/transform.d.ts.map +0 -1
- package/dist/binary/transform.js +0 -105
- package/dist/binary/transform.js.map +0 -1
- package/dist/bot/adapter.d.ts +0 -5
- package/dist/bot/adapter.d.ts.map +0 -1
- package/dist/bot/adapter.js +0 -11
- package/dist/bot/adapter.js.map +0 -1
- package/dist/bot/bot.d.ts +0 -28
- package/dist/bot/bot.d.ts.map +0 -1
- package/dist/bot/bot.js +0 -52
- package/dist/bot/bot.js.map +0 -1
- package/dist/bot/commands.d.ts +0 -8
- package/dist/bot/commands.d.ts.map +0 -1
- package/dist/bot/commands.js +0 -19
- package/dist/bot/commands.js.map +0 -1
- package/dist/bot/permissions.d.ts +0 -13
- package/dist/bot/permissions.d.ts.map +0 -1
- package/dist/bot/permissions.js +0 -16
- package/dist/bot/permissions.js.map +0 -1
- package/dist/browser/actions.d.ts +0 -33
- package/dist/browser/actions.d.ts.map +0 -1
- package/dist/browser/actions.js +0 -44
- package/dist/browser/actions.js.map +0 -1
- package/dist/browser/agent.d.ts +0 -21
- package/dist/browser/agent.d.ts.map +0 -1
- package/dist/browser/agent.js +0 -13
- package/dist/browser/agent.js.map +0 -1
- package/dist/browser/context.d.ts +0 -43
- package/dist/browser/context.d.ts.map +0 -1
- package/dist/browser/context.js +0 -54
- package/dist/browser/context.js.map +0 -1
- package/dist/browser/fingerprint.d.ts +0 -4
- package/dist/browser/fingerprint.d.ts.map +0 -1
- package/dist/browser/fingerprint.js +0 -12
- package/dist/browser/fingerprint.js.map +0 -1
- package/dist/browser/index.d.ts +0 -11
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -11
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/playwright.d.ts +0 -11
- package/dist/browser/playwright.d.ts.map +0 -1
- package/dist/browser/playwright.js +0 -24
- package/dist/browser/playwright.js.map +0 -1
- package/dist/browser/recorder.d.ts +0 -21
- package/dist/browser/recorder.d.ts.map +0 -1
- package/dist/browser/recorder.js +0 -58
- package/dist/browser/recorder.js.map +0 -1
- package/dist/browser/session.d.ts +0 -14
- package/dist/browser/session.d.ts.map +0 -1
- package/dist/browser/session.js +0 -21
- package/dist/browser/session.js.map +0 -1
- package/dist/browser/snapshot.d.ts +0 -69
- package/dist/browser/snapshot.d.ts.map +0 -1
- package/dist/browser/snapshot.js +0 -133
- package/dist/browser/snapshot.js.map +0 -1
- package/dist/captcha/contract.d.ts +0 -35
- package/dist/captcha/contract.d.ts.map +0 -1
- package/dist/captcha/contract.js +0 -17
- package/dist/captcha/contract.js.map +0 -1
- package/dist/captcha/evidence.d.ts +0 -10
- package/dist/captcha/evidence.d.ts.map +0 -1
- package/dist/captcha/evidence.js +0 -9
- package/dist/captcha/evidence.js.map +0 -1
- package/dist/captcha/guard.d.ts +0 -23
- package/dist/captcha/guard.d.ts.map +0 -1
- package/dist/captcha/guard.js +0 -12
- package/dist/captcha/guard.js.map +0 -1
- package/dist/cli/main.d.ts +0 -4
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -46
- package/dist/cli/main.js.map +0 -1
- package/dist/core/errors.d.ts +0 -84
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js +0 -54
- package/dist/core/errors.js.map +0 -1
- package/dist/core/events.d.ts +0 -9
- package/dist/core/events.d.ts.map +0 -1
- package/dist/core/events.js +0 -21
- package/dist/core/events.js.map +0 -1
- package/dist/core/hash.d.ts +0 -10
- package/dist/core/hash.d.ts.map +0 -1
- package/dist/core/hash.js +0 -82
- package/dist/core/hash.js.map +0 -1
- package/dist/core/ids.d.ts +0 -10
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -15
- package/dist/core/ids.js.map +0 -1
- package/dist/delivery/manifest.d.ts +0 -35
- package/dist/delivery/manifest.d.ts.map +0 -1
- package/dist/delivery/manifest.js +0 -68
- package/dist/delivery/manifest.js.map +0 -1
- package/dist/deploy/index.d.ts +0 -6
- package/dist/deploy/index.d.ts.map +0 -1
- package/dist/deploy/index.js +0 -6
- package/dist/deploy/index.js.map +0 -1
- package/dist/dispatch/resumable.d.ts +0 -102
- package/dist/dispatch/resumable.d.ts.map +0 -1
- package/dist/dispatch/resumable.js +0 -64
- package/dist/dispatch/resumable.js.map +0 -1
- package/dist/dispatch/workflow.d.ts +0 -6
- package/dist/dispatch/workflow.d.ts.map +0 -1
- package/dist/dispatch/workflow.js +0 -37
- package/dist/dispatch/workflow.js.map +0 -1
- package/dist/domain/artifacts.d.ts +0 -12
- package/dist/domain/artifacts.d.ts.map +0 -1
- package/dist/domain/artifacts.js +0 -14
- package/dist/domain/artifacts.js.map +0 -1
- package/dist/domain/jobs.d.ts +0 -12
- package/dist/domain/jobs.d.ts.map +0 -1
- package/dist/domain/jobs.js +0 -20
- package/dist/domain/jobs.js.map +0 -1
- package/dist/domain/providers.d.ts +0 -10
- package/dist/domain/providers.d.ts.map +0 -1
- package/dist/domain/providers.js +0 -8
- package/dist/domain/providers.js.map +0 -1
- package/dist/domain/runtime.d.ts +0 -14
- package/dist/domain/runtime.d.ts.map +0 -1
- package/dist/domain/runtime.js +0 -10
- package/dist/domain/runtime.js.map +0 -1
- package/dist/domain/sessions.d.ts +0 -12
- package/dist/domain/sessions.d.ts.map +0 -1
- package/dist/domain/sessions.js +0 -57
- package/dist/domain/sessions.js.map +0 -1
- package/dist/examples/localjob.d.ts +0 -2
- package/dist/examples/localjob.d.ts.map +0 -1
- package/dist/examples/localjob.js.map +0 -1
- package/dist/examples/publicapi.d.ts +0 -2
- package/dist/examples/publicapi.d.ts.map +0 -1
- package/dist/examples/publicapi.js.map +0 -1
- package/dist/extension/build.d.ts +0 -9
- package/dist/extension/build.d.ts.map +0 -1
- package/dist/extension/build.js +0 -85
- package/dist/extension/build.js.map +0 -1
- package/dist/extension/content.d.ts +0 -6
- package/dist/extension/content.d.ts.map +0 -1
- package/dist/extension/content.js +0 -135
- package/dist/extension/content.js.map +0 -1
- package/dist/extension/index.d.ts +0 -7
- package/dist/extension/index.d.ts.map +0 -1
- package/dist/extension/index.js +0 -7
- package/dist/extension/index.js.map +0 -1
- package/dist/extension/pagebridge.d.ts +0 -6
- package/dist/extension/pagebridge.d.ts.map +0 -1
- package/dist/extension/pagebridge.js +0 -42
- package/dist/extension/pagebridge.js.map +0 -1
- package/dist/extension/permissions.d.ts +0 -26
- package/dist/extension/permissions.d.ts.map +0 -1
- package/dist/extension/permissions.js +0 -28
- package/dist/extension/permissions.js.map +0 -1
- package/dist/extension/popup.d.ts +0 -5
- package/dist/extension/popup.d.ts.map +0 -1
- package/dist/extension/popup.js +0 -26
- package/dist/extension/popup.js.map +0 -1
- package/dist/extension/protocol.d.ts +0 -58
- package/dist/extension/protocol.d.ts.map +0 -1
- package/dist/extension/protocol.js +0 -101
- package/dist/extension/protocol.js.map +0 -1
- package/dist/extension/serviceworker.d.ts +0 -25
- package/dist/extension/serviceworker.d.ts.map +0 -1
- package/dist/extension/serviceworker.js +0 -102
- package/dist/extension/serviceworker.js.map +0 -1
- package/dist/extension/worker.d.ts +0 -28
- package/dist/extension/worker.d.ts.map +0 -1
- package/dist/extension/worker.js +0 -22
- package/dist/extension/worker.js.map +0 -1
- package/dist/format/check.d.ts +0 -8
- package/dist/format/check.d.ts.map +0 -1
- package/dist/format/check.js +0 -38
- package/dist/format/check.js.map +0 -1
- package/dist/isolation/contracts.d.ts.map +0 -1
- package/dist/isolation/contracts.js.map +0 -1
- package/dist/library/public.d.ts.map +0 -1
- package/dist/library/public.js.map +0 -1
- package/dist/mcp/browser.d.ts +0 -9
- package/dist/mcp/browser.d.ts.map +0 -1
- package/dist/mcp/browser.js +0 -13
- package/dist/mcp/browser.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -35
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -40
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/transport.d.ts +0 -7
- package/dist/mcp/transport.d.ts.map +0 -1
- package/dist/mcp/transport.js +0 -17
- package/dist/mcp/transport.js.map +0 -1
- package/dist/memory/bridge.d.ts +0 -14
- package/dist/memory/bridge.d.ts.map +0 -1
- package/dist/memory/bridge.js +0 -16
- package/dist/memory/bridge.js.map +0 -1
- package/dist/memory/engine.d.ts +0 -38
- package/dist/memory/engine.d.ts.map +0 -1
- package/dist/memory/engine.js +0 -109
- package/dist/memory/engine.js.map +0 -1
- package/dist/memory/modes.d.ts +0 -43
- package/dist/memory/modes.d.ts.map +0 -1
- package/dist/memory/modes.js +0 -66
- package/dist/memory/modes.js.map +0 -1
- package/dist/memory/objects.d.ts +0 -21
- package/dist/memory/objects.d.ts.map +0 -1
- package/dist/memory/objects.js +0 -20
- package/dist/memory/objects.js.map +0 -1
- package/dist/memory/planner.d.ts +0 -66
- package/dist/memory/planner.d.ts.map +0 -1
- package/dist/memory/planner.js +0 -108
- package/dist/memory/planner.js.map +0 -1
- package/dist/memory/targets.d.ts +0 -78
- package/dist/memory/targets.d.ts.map +0 -1
- package/dist/memory/targets.js +0 -29
- package/dist/memory/targets.js.map +0 -1
- package/dist/memory/transforms.d.ts +0 -19
- package/dist/memory/transforms.d.ts.map +0 -1
- package/dist/memory/transforms.js +0 -14
- package/dist/memory/transforms.js.map +0 -1
- package/dist/modes/matrix.d.ts +0 -23
- package/dist/modes/matrix.d.ts.map +0 -1
- package/dist/modes/matrix.js +0 -18
- package/dist/modes/matrix.js.map +0 -1
- package/dist/modes/modes.d.ts +0 -33
- package/dist/modes/modes.d.ts.map +0 -1
- package/dist/modes/modes.js +0 -15
- package/dist/modes/modes.js.map +0 -1
- package/dist/modes/resolve.d.ts +0 -96
- package/dist/modes/resolve.d.ts.map +0 -1
- package/dist/modes/resolve.js +0 -48
- package/dist/modes/resolve.js.map +0 -1
- package/dist/observability/metrics.d.ts +0 -14
- package/dist/observability/metrics.d.ts.map +0 -1
- package/dist/observability/metrics.js +0 -18
- package/dist/observability/metrics.js.map +0 -1
- package/dist/packager/manifest.d.ts +0 -66
- package/dist/packager/manifest.d.ts.map +0 -1
- package/dist/packager/manifest.js +0 -94
- package/dist/packager/manifest.js.map +0 -1
- package/dist/packager/publish.d.ts +0 -51
- package/dist/packager/publish.d.ts.map +0 -1
- package/dist/packager/publish.js +0 -18
- package/dist/packager/publish.js.map +0 -1
- package/dist/packager/targetcli.d.ts +0 -7
- package/dist/packager/targetcli.d.ts.map +0 -1
- package/dist/packager/targetcli.js +0 -22
- package/dist/packager/targetcli.js.map +0 -1
- package/dist/persistence/adapter.d.ts +0 -5
- package/dist/persistence/adapter.d.ts.map +0 -1
- package/dist/persistence/adapter.js +0 -11
- package/dist/persistence/adapter.js.map +0 -1
- package/dist/persistence/drizzle.d.ts +0 -2
- package/dist/persistence/drizzle.d.ts.map +0 -1
- package/dist/persistence/drizzle.js +0 -12
- package/dist/persistence/drizzle.js.map +0 -1
- package/dist/persistence/memory.d.ts +0 -2
- package/dist/persistence/memory.d.ts.map +0 -1
- package/dist/persistence/memory.js +0 -27
- package/dist/persistence/memory.js.map +0 -1
- package/dist/persistence/migrations.d.ts +0 -19
- package/dist/persistence/migrations.d.ts.map +0 -1
- package/dist/persistence/migrations.js +0 -13
- package/dist/persistence/migrations.js.map +0 -1
- package/dist/persistence/prisma.d.ts +0 -2
- package/dist/persistence/prisma.d.ts.map +0 -1
- package/dist/persistence/prisma.js +0 -25
- package/dist/persistence/prisma.js.map +0 -1
- package/dist/persistence/schema.d.ts +0 -63
- package/dist/persistence/schema.d.ts.map +0 -1
- package/dist/persistence/schema.js +0 -28
- package/dist/persistence/schema.js.map +0 -1
- package/dist/persistence/sql.d.ts +0 -3
- package/dist/persistence/sql.d.ts.map +0 -1
- package/dist/persistence/sql.js +0 -37
- package/dist/persistence/sql.js.map +0 -1
- package/dist/protocol/blocks.d.ts +0 -10
- package/dist/protocol/blocks.d.ts.map +0 -1
- package/dist/protocol/blocks.js +0 -26
- package/dist/protocol/blocks.js.map +0 -1
- package/dist/protocol/json.d.ts +0 -6
- package/dist/protocol/json.d.ts.map +0 -1
- package/dist/protocol/json.js +0 -6
- package/dist/protocol/json.js.map +0 -1
- package/dist/protocol/ndjson.d.ts +0 -3
- package/dist/protocol/ndjson.d.ts.map +0 -1
- package/dist/protocol/ndjson.js +0 -20
- package/dist/protocol/ndjson.js.map +0 -1
- package/dist/protocol/sse.d.ts +0 -7
- package/dist/protocol/sse.d.ts.map +0 -1
- package/dist/protocol/sse.js +0 -25
- package/dist/protocol/sse.js.map +0 -1
- package/dist/proxy/pool.d.ts +0 -9
- package/dist/proxy/pool.d.ts.map +0 -1
- package/dist/proxy/pool.js +0 -27
- package/dist/proxy/pool.js.map +0 -1
- package/dist/queue/idempotency.d.ts +0 -10
- package/dist/queue/idempotency.d.ts.map +0 -1
- package/dist/queue/idempotency.js +0 -13
- package/dist/queue/idempotency.js.map +0 -1
- package/dist/queue/persistent.d.ts +0 -14
- package/dist/queue/persistent.d.ts.map +0 -1
- package/dist/queue/persistent.js +0 -66
- package/dist/queue/persistent.js.map +0 -1
- package/dist/queue/queue.d.ts +0 -7
- package/dist/queue/queue.d.ts.map +0 -1
- package/dist/queue/queue.js +0 -55
- package/dist/queue/queue.js.map +0 -1
- package/dist/queue/saga.d.ts +0 -5
- package/dist/queue/saga.d.ts.map +0 -1
- package/dist/queue/saga.js +0 -20
- package/dist/queue/saga.js.map +0 -1
- package/dist/release/assets.d.ts +0 -144
- package/dist/release/assets.d.ts.map +0 -1
- package/dist/release/assets.js +0 -157
- package/dist/release/assets.js.map +0 -1
- package/dist/release/evidence.d.ts +0 -92
- package/dist/release/evidence.d.ts.map +0 -1
- package/dist/release/evidence.js +0 -148
- package/dist/release/evidence.js.map +0 -1
- package/dist/release/verify.d.ts +0 -12
- package/dist/release/verify.d.ts.map +0 -1
- package/dist/release/verify.js +0 -112
- package/dist/release/verify.js.map +0 -1
- package/dist/runners/chain.d.ts +0 -125
- package/dist/runners/chain.d.ts.map +0 -1
- package/dist/runners/chain.js +0 -95
- package/dist/runners/chain.js.map +0 -1
- package/dist/runners/health.d.ts +0 -36
- package/dist/runners/health.d.ts.map +0 -1
- package/dist/runners/health.js +0 -26
- package/dist/runners/health.js.map +0 -1
- package/dist/runners/heartbeat.d.ts +0 -23
- package/dist/runners/heartbeat.d.ts.map +0 -1
- package/dist/runners/heartbeat.js +0 -29
- package/dist/runners/heartbeat.js.map +0 -1
- package/dist/runners/inprocess.d.ts +0 -17
- package/dist/runners/inprocess.d.ts.map +0 -1
- package/dist/runners/inprocess.js +0 -20
- package/dist/runners/inprocess.js.map +0 -1
- package/dist/runners/scheduler.d.ts +0 -5
- package/dist/runners/scheduler.d.ts.map +0 -1
- package/dist/runners/scheduler.js +0 -19
- package/dist/runners/scheduler.js.map +0 -1
- package/dist/runtime/abort.d.ts +0 -8
- package/dist/runtime/abort.d.ts.map +0 -1
- package/dist/runtime/abort.js +0 -13
- package/dist/runtime/abort.js.map +0 -1
- package/dist/runtime/compatibility.d.ts +0 -37
- package/dist/runtime/compatibility.d.ts.map +0 -1
- package/dist/runtime/compatibility.js +0 -10
- package/dist/runtime/compatibility.js.map +0 -1
- package/dist/runtime/detect.d.ts +0 -13
- package/dist/runtime/detect.d.ts.map +0 -1
- package/dist/runtime/detect.js +0 -18
- package/dist/runtime/detect.js.map +0 -1
- package/dist/runtime/engine.d.ts +0 -20
- package/dist/runtime/engine.d.ts.map +0 -1
- package/dist/runtime/engine.js +0 -61
- package/dist/runtime/engine.js.map +0 -1
- package/dist/runtime/retry.d.ts +0 -19
- package/dist/runtime/retry.d.ts.map +0 -1
- package/dist/runtime/retry.js +0 -54
- package/dist/runtime/retry.js.map +0 -1
- package/dist/runtime/worker.d.ts +0 -10
- package/dist/runtime/worker.d.ts.map +0 -1
- package/dist/runtime/worker.js +0 -25
- package/dist/runtime/worker.js.map +0 -1
- package/dist/scrape/cache.d.ts +0 -45
- package/dist/scrape/cache.d.ts.map +0 -1
- package/dist/scrape/cache.js +0 -45
- package/dist/scrape/cache.js.map +0 -1
- package/dist/scrape/crawl.d.ts +0 -47
- package/dist/scrape/crawl.d.ts.map +0 -1
- package/dist/scrape/crawl.js +0 -114
- package/dist/scrape/crawl.js.map +0 -1
- package/dist/scrape/extract.d.ts +0 -63
- package/dist/scrape/extract.d.ts.map +0 -1
- package/dist/scrape/extract.js +0 -47
- package/dist/scrape/extract.js.map +0 -1
- package/dist/scrape/normalize.d.ts +0 -52
- package/dist/scrape/normalize.d.ts.map +0 -1
- package/dist/scrape/normalize.js +0 -102
- package/dist/scrape/normalize.js.map +0 -1
- package/dist/scrape/robots.d.ts +0 -37
- package/dist/scrape/robots.d.ts.map +0 -1
- package/dist/scrape/robots.js +0 -71
- package/dist/scrape/robots.js.map +0 -1
- package/dist/scrape/schema.d.ts +0 -23
- package/dist/scrape/schema.d.ts.map +0 -1
- package/dist/scrape/schema.js +0 -92
- package/dist/scrape/schema.js.map +0 -1
- package/dist/scrape/scraper.d.ts +0 -6
- package/dist/scrape/scraper.d.ts.map +0 -1
- package/dist/scrape/scraper.js +0 -46
- package/dist/scrape/scraper.js.map +0 -1
- package/dist/scrape/semantic.d.ts +0 -25
- package/dist/scrape/semantic.d.ts.map +0 -1
- package/dist/scrape/semantic.js +0 -27
- package/dist/scrape/semantic.js.map +0 -1
- package/dist/server/node.d.ts +0 -7
- package/dist/server/node.d.ts.map +0 -1
- package/dist/server/node.js.map +0 -1
- package/dist/sessions/file.d.ts +0 -25
- package/dist/sessions/file.d.ts.map +0 -1
- package/dist/sessions/file.js +0 -13
- package/dist/sessions/file.js.map +0 -1
- package/dist/sessions/replay.d.ts +0 -9
- package/dist/sessions/replay.d.ts.map +0 -1
- package/dist/sessions/replay.js +0 -70
- package/dist/sessions/replay.js.map +0 -1
- package/dist/sessions/store.d.ts +0 -25
- package/dist/sessions/store.d.ts.map +0 -1
- package/dist/sessions/store.js +0 -13
- package/dist/sessions/store.js.map +0 -1
- package/dist/storage/adapter.d.ts +0 -5
- package/dist/storage/adapter.d.ts.map +0 -1
- package/dist/storage/adapter.js +0 -11
- package/dist/storage/adapter.js.map +0 -1
- package/dist/storage/cache.d.ts +0 -23
- package/dist/storage/cache.d.ts.map +0 -1
- package/dist/storage/cache.js +0 -79
- package/dist/storage/cache.js.map +0 -1
- package/dist/storage/checksum.d.ts +0 -3
- package/dist/storage/checksum.d.ts.map +0 -1
- package/dist/storage/checksum.js +0 -19
- package/dist/storage/checksum.js.map +0 -1
- package/dist/storage/chunked.d.ts +0 -21
- package/dist/storage/chunked.d.ts.map +0 -1
- package/dist/storage/chunked.js +0 -67
- package/dist/storage/chunked.js.map +0 -1
- package/dist/storage/content.d.ts +0 -24
- package/dist/storage/content.d.ts.map +0 -1
- package/dist/storage/content.js +0 -44
- package/dist/storage/content.js.map +0 -1
- package/dist/storage/filehosting.d.ts +0 -2
- package/dist/storage/filehosting.d.ts.map +0 -1
- package/dist/storage/filehosting.js +0 -24
- package/dist/storage/filehosting.js.map +0 -1
- package/dist/storage/githubcontents.d.ts +0 -2
- package/dist/storage/githubcontents.d.ts.map +0 -1
- package/dist/storage/githubcontents.js +0 -29
- package/dist/storage/githubcontents.js.map +0 -1
- package/dist/storage/index.d.ts +0 -12
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -12
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/local.d.ts +0 -2
- package/dist/storage/local.d.ts.map +0 -1
- package/dist/storage/local.js +0 -72
- package/dist/storage/local.js.map +0 -1
- package/dist/storage/memory.d.ts +0 -6
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js +0 -36
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/pool.d.ts +0 -95
- package/dist/storage/pool.d.ts.map +0 -1
- package/dist/storage/pool.js +0 -202
- package/dist/storage/pool.js.map +0 -1
- package/dist/storage/s3compatible.d.ts +0 -3
- package/dist/storage/s3compatible.d.ts.map +0 -1
- package/dist/storage/s3compatible.js +0 -70
- package/dist/storage/s3compatible.js.map +0 -1
- package/dist/storage/sync.d.ts +0 -47
- package/dist/storage/sync.d.ts.map +0 -1
- package/dist/storage/sync.js +0 -75
- package/dist/storage/sync.js.map +0 -1
- package/dist/surfaces/adapters.d.ts +0 -97
- package/dist/surfaces/adapters.d.ts.map +0 -1
- package/dist/surfaces/adapters.js +0 -50
- package/dist/surfaces/adapters.js.map +0 -1
- package/dist/surfaces/controls.d.ts +0 -18
- package/dist/surfaces/controls.d.ts.map +0 -1
- package/dist/surfaces/controls.js +0 -45
- package/dist/surfaces/controls.js.map +0 -1
- package/dist/surfaces/manifest.d.ts +0 -68
- package/dist/surfaces/manifest.d.ts.map +0 -1
- package/dist/surfaces/manifest.js +0 -42
- package/dist/surfaces/manifest.js.map +0 -1
- package/dist/surfaces/n8n.d.ts +0 -25
- package/dist/surfaces/n8n.d.ts.map +0 -1
- package/dist/surfaces/n8n.js +0 -31
- package/dist/surfaces/n8n.js.map +0 -1
- package/dist/surfaces/operations.d.ts +0 -32
- package/dist/surfaces/operations.d.ts.map +0 -1
- package/dist/surfaces/operations.js +0 -53
- package/dist/surfaces/operations.js.map +0 -1
- package/dist/surfaces/requirements.d.ts +0 -29
- package/dist/surfaces/requirements.d.ts.map +0 -1
- package/dist/surfaces/requirements.js +0 -50
- package/dist/surfaces/requirements.js.map +0 -1
- package/dist/surfaces/targets.d.ts +0 -196
- package/dist/surfaces/targets.d.ts.map +0 -1
- package/dist/surfaces/targets.js +0 -27
- package/dist/surfaces/targets.js.map +0 -1
- package/dist/webhook/delivery.d.ts +0 -18
- package/dist/webhook/delivery.d.ts.map +0 -1
- package/dist/webhook/delivery.js +0 -36
- package/dist/webhook/delivery.js.map +0 -1
- package/dist/webhook/receiver.d.ts +0 -26
- package/dist/webhook/receiver.d.ts.map +0 -1
- package/dist/webhook/receiver.js +0 -24
- package/dist/webhook/receiver.js.map +0 -1
- package/dist/webhook/signature.d.ts +0 -3
- package/dist/webhook/signature.d.ts.map +0 -1
- package/dist/webhook/signature.js +0 -7
- package/dist/webhook/signature.js.map +0 -1
- package/dist/workflow/manifest.d.ts +0 -22
- package/dist/workflow/manifest.d.ts.map +0 -1
- package/dist/workflow/manifest.js +0 -21
- package/dist/workflow/manifest.js.map +0 -1
- package/dist/workflow/registry.d.ts +0 -7
- package/dist/workflow/registry.d.ts.map +0 -1
- package/dist/workflow/registry.js +0 -18
- package/dist/workflow/registry.js.map +0 -1
- package/dist/workflow/templates.d.ts +0 -6
- package/dist/workflow/templates.d.ts.map +0 -1
- package/dist/workflow/templates.js +0 -18
- package/dist/workflow/templates.js.map +0 -1
- package/dist/workflow/triggers.d.ts +0 -64
- package/dist/workflow/triggers.d.ts.map +0 -1
- package/dist/workflow/triggers.js +0 -99
- package/dist/workflow/triggers.js.map +0 -1
- /package/{extension/README.md → docs/extension.md} +0 -0
package/dist/browser.js
ADDED
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser.ts — browser, virtual-browser and browser-extension contracts.
|
|
3
|
+
*
|
|
4
|
+
* This file is the v2.0.0 grand-merge consolidation of the former nested
|
|
5
|
+
* folders of the repository (browser, extension) into the single
|
|
6
|
+
* root-level domain file of the saddle family standard: one optimized
|
|
7
|
+
* TypeScript file per correlated domain, ordinal sections, JSDoc on every
|
|
8
|
+
* block, intra-domain imports dissolved, cross-domain imports rewritten to
|
|
9
|
+
* ./<domain>.js. Every capability of every source file is preserved here;
|
|
10
|
+
* the pure re-export barrels (browser/index.ts, extension/index.ts) folded their surface into this file directly.
|
|
11
|
+
*/
|
|
12
|
+
import { executiondecision, executionrequest } from "./isolation.js";
|
|
13
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
14
|
+
import { basename, dirname, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
17
|
+
/* Section 1: browser/fingerprint.ts — fingerprint profiles keep session settings coherent without modifying browser internals. */
|
|
18
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
19
|
+
/**
|
|
20
|
+
* fingerprint profiles keep session settings coherent without modifying browser internals.
|
|
21
|
+
*/
|
|
22
|
+
const profiles = Object.freeze({
|
|
23
|
+
desktopwindows: { os: "windows", platform: "Win32", browser: "chrome", locale: "en-US", timezone: "America/New_York", touch: false, devicepixelratio: 1 },
|
|
24
|
+
desktopmacos: { os: "macos", platform: "MacIntel", browser: "safari", locale: "en-US", timezone: "America/Los_Angeles", touch: false, devicepixelratio: 2 },
|
|
25
|
+
mobileandroid: { os: "android", platform: "Linux armv8l", browser: "chrome", locale: "en-US", timezone: "America/Chicago", touch: true, devicepixelratio: 2.75 }
|
|
26
|
+
});
|
|
27
|
+
export function fingerprintprofile(name = "desktopwindows", overrides = {}) { return { ...(profiles[name] ?? profiles.desktopwindows), ...overrides, name }; }
|
|
28
|
+
export function fingerprintvalidate(profile) { return Boolean(profile?.os && profile?.platform && profile?.browser && profile?.locale && profile?.timezone && typeof profile.touch === "boolean"); }
|
|
29
|
+
export function fingerprintfor(sessionid, options = {}) { const names = options.profiles ?? Object.keys(profiles); const index = [...String(sessionid)].reduce((sum, value) => sum + value.charCodeAt(0), 0) % names.length; return fingerprintprofile(names[index], options.overrides); }
|
|
30
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
31
|
+
/* Section 2: browser/session.ts — browser sessions bind one fingerprint to one proxy and one event recorder. */
|
|
32
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
33
|
+
/**
|
|
34
|
+
* browser sessions bind one fingerprint to one proxy and one event recorder.
|
|
35
|
+
*/
|
|
36
|
+
export function browsersession(options = {}) {
|
|
37
|
+
const id = options.id ?? `browsersession${Date.now().toString(36)}`;
|
|
38
|
+
const fingerprint = options.fingerprint ?? fingerprintfor(id, options);
|
|
39
|
+
if (!fingerprintvalidate(fingerprint))
|
|
40
|
+
throw new TypeError("browser fingerprint is incoherent");
|
|
41
|
+
const events = [];
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
fingerprint,
|
|
45
|
+
proxy: options.proxy,
|
|
46
|
+
record(event) { if (!event || !Number.isFinite(event.t) || event.t < 0 || typeof event.type !== "string")
|
|
47
|
+
throw new TypeError("browser event is invalid"); events.push({ ...event }); return event; },
|
|
48
|
+
events() { return events.map((event) => ({ ...event })); },
|
|
49
|
+
manifest() { return { id, fingerprint: { ...fingerprint }, proxy: options.proxy?.id ?? options.proxy, eventcount: events.length }; }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
53
|
+
/* Section 3: browser/actions.ts — browser actions normalize adapter outcomes and preserve failure metadata for agents and replay. */
|
|
54
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
55
|
+
/**
|
|
56
|
+
* browser actions normalize adapter outcomes and preserve failure metadata for agents and replay.
|
|
57
|
+
*/
|
|
58
|
+
export const browseractions = Object.freeze(["navigate", "click", "type", "fill", "key", "scroll", "upload", "screenshot", "snapshot"]);
|
|
59
|
+
/** Creates a stable action result independent of the underlying browser vendor. */
|
|
60
|
+
export function actionresult(action, options = {}) {
|
|
61
|
+
if (!browseractions.includes(action))
|
|
62
|
+
throw new TypeError(`unsupported browser action: ${action}`);
|
|
63
|
+
return { version: 1, action, ok: true, startedat: Number(options.startedat ?? Date.now()), finishedat: Number(options.finishedat ?? Date.now()), tabid: options.tabid === undefined ? undefined : String(options.tabid), frameid: options.frameid === undefined ? undefined : String(options.frameid), snapshotid: options.snapshotid, value: options.value, metadata: options.metadata ?? {} };
|
|
64
|
+
}
|
|
65
|
+
/** Creates a stable action failure without leaking adapter internals or credentials. */
|
|
66
|
+
export function actionfailure(action, error, options = {}) {
|
|
67
|
+
if (!action || typeof action !== "string")
|
|
68
|
+
throw new TypeError("browser action failure requires an action name");
|
|
69
|
+
return { version: 1, action, ok: false, code: String(options.code ?? error?.code ?? "BROWSER_ACTION_FAILED"), message: String(error?.message ?? error ?? "browser action failed"), retryable: Boolean(options.retryable ?? error?.retryable), tabid: options.tabid === undefined ? undefined : String(options.tabid), frameid: options.frameid === undefined ? undefined : String(options.frameid), snapshotid: options.snapshotid, metadata: options.metadata ?? {} };
|
|
70
|
+
}
|
|
71
|
+
/** Executes a bounded list of actions through an injected adapter and keeps per-action outcomes. */
|
|
72
|
+
export async function actionbatch(adapter, actions = [], options = {}) {
|
|
73
|
+
if (!adapter || typeof adapter !== "object")
|
|
74
|
+
throw new TypeError("browser action batch requires an adapter");
|
|
75
|
+
if (!Array.isArray(actions) || actions.length > (options.maxactions ?? 100))
|
|
76
|
+
throw new TypeError("browser action batch is invalid or too large");
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const item of actions) {
|
|
79
|
+
const action = String(item?.action ?? "");
|
|
80
|
+
const method = adapter[action];
|
|
81
|
+
if (typeof method !== "function") {
|
|
82
|
+
results.push(actionfailure(action, new Error(`browser adapter does not support ${action}`), { code: "UNSUPPORTED_ACTION" }));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const startedat = Date.now();
|
|
86
|
+
try {
|
|
87
|
+
const value = await method(item.options ?? item.value);
|
|
88
|
+
results.push(actionresult(action, { ...item, startedat, finishedat: Date.now(), value }));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
results.push(actionfailure(action, error, { ...item, tabid: item.tabid, frameid: item.frameid, snapshotid: item.snapshotid }));
|
|
92
|
+
if (options.stoponerror)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
99
|
+
/* Section 4: browser/context.ts — browser context tracks tabs and frames without owning a browser implementation. */
|
|
100
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
101
|
+
/**
|
|
102
|
+
* browser context tracks tabs and frames without owning a browser implementation.
|
|
103
|
+
*/
|
|
104
|
+
/** Creates a serializable browser context registry for an injected browser adapter. */
|
|
105
|
+
export function browsercontext(options = {}) {
|
|
106
|
+
const sessionid = String(options.sessionid ?? `context${Date.now().toString(36)}`);
|
|
107
|
+
const tabs = new Map();
|
|
108
|
+
let activeid = options.activeid === undefined ? undefined : String(options.activeid);
|
|
109
|
+
function opentab(input = {}) {
|
|
110
|
+
const id = String(input.id ?? `tab${tabs.size + 1}`);
|
|
111
|
+
const tab = { id, url: String(input.url ?? "about:blank"), title: String(input.title ?? ""), active: Boolean(input.active), frames: new Map() };
|
|
112
|
+
tabs.set(id, tab);
|
|
113
|
+
if (tab.active || activeid === undefined) {
|
|
114
|
+
activeid = id;
|
|
115
|
+
tab.active = true;
|
|
116
|
+
}
|
|
117
|
+
return describetab(tab);
|
|
118
|
+
}
|
|
119
|
+
function closetab(id) {
|
|
120
|
+
const key = String(id);
|
|
121
|
+
if (!tabs.delete(key))
|
|
122
|
+
return false;
|
|
123
|
+
if (activeid === key)
|
|
124
|
+
activeid = tabs.keys().next().value;
|
|
125
|
+
if (activeid && tabs.has(activeid))
|
|
126
|
+
tabs.get(activeid).active = true;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function setactive(id) {
|
|
130
|
+
const key = String(id);
|
|
131
|
+
const tab = tabs.get(key);
|
|
132
|
+
if (!tab)
|
|
133
|
+
throw new Error(`unknown browser tab: ${key}`);
|
|
134
|
+
for (const item of tabs.values())
|
|
135
|
+
item.active = false;
|
|
136
|
+
tab.active = true;
|
|
137
|
+
activeid = key;
|
|
138
|
+
return describetab(tab);
|
|
139
|
+
}
|
|
140
|
+
function openframe(tabid, input = {}) {
|
|
141
|
+
const tab = requiretab(tabid);
|
|
142
|
+
const id = String(input.id ?? `frame${tab.frames.size + 1}`);
|
|
143
|
+
tab.frames.set(id, { id, url: String(input.url ?? tab.url), parentid: input.parentid === undefined ? undefined : String(input.parentid), name: String(input.name ?? "") });
|
|
144
|
+
return { ...tab.frames.get(id) };
|
|
145
|
+
}
|
|
146
|
+
function closeframe(tabid, frameid) { return Boolean(requiretab(tabid).frames.delete(String(frameid))); }
|
|
147
|
+
function activetab() { return activeid === undefined ? undefined : describetab(requiretab(activeid)); }
|
|
148
|
+
function describe() { return { sessionid, activeid, tabs: [...tabs.values()].map(describetab) }; }
|
|
149
|
+
return { sessionid, opentab, closetab, setactive, openframe, closeframe, activetab, describe };
|
|
150
|
+
function requiretab(id) { const tab = tabs.get(String(id)); if (!tab)
|
|
151
|
+
throw new Error(`unknown browser tab: ${id}`); return tab; }
|
|
152
|
+
function describetab(tab) { return { id: tab.id, url: tab.url, title: tab.title, active: tab.active, frames: [...tab.frames.values()].map((frame) => ({ ...frame })) }; }
|
|
153
|
+
}
|
|
154
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
155
|
+
/* Section 5: browser/snapshot.ts — browser snapshots turn page state into bounded serializable data with stable references. */
|
|
156
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
157
|
+
/**
|
|
158
|
+
* browser snapshots turn page state into bounded serializable data with stable references.
|
|
159
|
+
*/
|
|
160
|
+
/** Creates a validated page snapshot for browser, MCP and extension adapters. */
|
|
161
|
+
export function pagesnapshot(input = {}) {
|
|
162
|
+
if (!input || typeof input !== "object")
|
|
163
|
+
throw new TypeError("page snapshot must be an object");
|
|
164
|
+
const snapshot = {
|
|
165
|
+
version: 1,
|
|
166
|
+
snapshotid: String(input.snapshotid ?? `snapshot${Date.now().toString(36)}`),
|
|
167
|
+
tabid: input.tabid === undefined ? undefined : String(input.tabid),
|
|
168
|
+
frameid: input.frameid === undefined ? undefined : String(input.frameid),
|
|
169
|
+
url: String(input.url ?? ""),
|
|
170
|
+
title: String(input.title ?? ""),
|
|
171
|
+
text: String(input.text ?? "").slice(0, input.maxtext ?? 100000),
|
|
172
|
+
elements: normalizeelements(input.elements)
|
|
173
|
+
};
|
|
174
|
+
if (!snapshot.snapshotid || !snapshot.url)
|
|
175
|
+
throw new TypeError("page snapshot requires snapshotid and url");
|
|
176
|
+
return snapshot;
|
|
177
|
+
}
|
|
178
|
+
/** Creates an element reference bound to a snapshot and browser context. */
|
|
179
|
+
export function snapshotref(snapshot, input = {}) {
|
|
180
|
+
const current = pagesnapshot(snapshot);
|
|
181
|
+
const ref = String(input.ref ?? "");
|
|
182
|
+
if (!current.elements.some((element) => element.ref === ref))
|
|
183
|
+
throw new TypeError(`snapshot reference not found: ${ref}`);
|
|
184
|
+
return { version: 1, snapshotid: current.snapshotid, tabid: current.tabid, frameid: current.frameid, ref };
|
|
185
|
+
}
|
|
186
|
+
/** Throws a stable error when an action uses a reference from a previous page state. */
|
|
187
|
+
export function assertfreshsnapshot(snapshot, reference) {
|
|
188
|
+
const current = pagesnapshot(snapshot);
|
|
189
|
+
if (!reference || reference.snapshotid !== current.snapshotid || (reference.tabid !== undefined && reference.tabid !== current.tabid) || (reference.frameid !== undefined && reference.frameid !== current.frameid)) {
|
|
190
|
+
const error = new Error("browser snapshot is stale");
|
|
191
|
+
error.code = "STALE_SNAPSHOT";
|
|
192
|
+
error.retryable = true;
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
/** Computes additions, removals and changed labels between two page snapshots (the page-side diff of the former browser/snapshot.ts; the extension-protocol diff of the same name lives in the protocol section below). */
|
|
198
|
+
export function pagesnapshotdiff(previous, current) {
|
|
199
|
+
const before = pagesnapshot(previous);
|
|
200
|
+
const after = pagesnapshot(current);
|
|
201
|
+
const oldmap = new Map(before.elements.map((element) => [element.ref, element]));
|
|
202
|
+
const newmap = new Map(after.elements.map((element) => [element.ref, element]));
|
|
203
|
+
const added = after.elements.filter((element) => !oldmap.has(element.ref));
|
|
204
|
+
const removed = before.elements.filter((element) => !newmap.has(element.ref));
|
|
205
|
+
const changed = after.elements.filter((element) => oldmap.has(element.ref) && JSON.stringify(oldmap.get(element.ref)) !== JSON.stringify(element));
|
|
206
|
+
return { from: before.snapshotid, to: after.snapshotid, added, removed, changed };
|
|
207
|
+
}
|
|
208
|
+
/** Projects a page snapshot into a byte-bounded, allowlisted browser context. */
|
|
209
|
+
export function projectcontext(snapshot, options = {}) {
|
|
210
|
+
const current = pagesnapshot(snapshot);
|
|
211
|
+
const maxbytes = normalizebudget(options.maxbytes ?? 32768);
|
|
212
|
+
const allowed = normalizefields(options.fields ?? options.allowlist);
|
|
213
|
+
const context = { snapshotid: current.snapshotid };
|
|
214
|
+
const truncated = [];
|
|
215
|
+
for (const field of allowed) {
|
|
216
|
+
if (field === "snapshotid")
|
|
217
|
+
continue;
|
|
218
|
+
const value = current[field];
|
|
219
|
+
if (value === undefined)
|
|
220
|
+
continue;
|
|
221
|
+
if (field === "elements") {
|
|
222
|
+
const elements = [];
|
|
223
|
+
for (const element of value) {
|
|
224
|
+
if (fitscontext(context, field, [...elements, element], maxbytes))
|
|
225
|
+
elements.push(element);
|
|
226
|
+
else {
|
|
227
|
+
truncated.push("elements");
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (fitscontext(context, field, elements, maxbytes))
|
|
232
|
+
context[field] = elements;
|
|
233
|
+
else
|
|
234
|
+
truncated.push("elements");
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (fitscontext(context, field, value, maxbytes)) {
|
|
238
|
+
context[field] = value;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (typeof value === "string")
|
|
242
|
+
context[field] = clipcontextstring(context, field, value, maxbytes);
|
|
243
|
+
else
|
|
244
|
+
truncated.push(field);
|
|
245
|
+
if (typeof value === "string" && context[field].length < value.length)
|
|
246
|
+
truncated.push(field);
|
|
247
|
+
}
|
|
248
|
+
const bytes = contextbytes(context);
|
|
249
|
+
if (bytes > maxbytes)
|
|
250
|
+
throw new RangeError("browser context budget is smaller than stable snapshot identity");
|
|
251
|
+
return { version: 1, snapshotid: current.snapshotid, context, fields: Object.keys(context), bytes, maxbytes, truncated: [...new Set(truncated)] };
|
|
252
|
+
}
|
|
253
|
+
function normalizeelements(elements) {
|
|
254
|
+
if (!Array.isArray(elements))
|
|
255
|
+
return [];
|
|
256
|
+
return elements.slice(0, 500).map((element, index) => ({ ref: String(element?.ref ?? `e${index + 1}`), role: String(element?.role ?? "generic"), name: String(element?.name ?? "").trim().slice(0, 200), value: element?.value === undefined ? undefined : String(element.value).slice(0, 500), disabled: Boolean(element?.disabled) }));
|
|
257
|
+
}
|
|
258
|
+
const snapshotfields = Object.freeze(["snapshotid", "tabid", "frameid", "url", "title", "text", "elements"]);
|
|
259
|
+
function normalizefields(fields) {
|
|
260
|
+
if (fields === undefined)
|
|
261
|
+
return [...snapshotfields];
|
|
262
|
+
if (!Array.isArray(fields) || fields.length === 0)
|
|
263
|
+
throw new TypeError("browser context fields must be a non-empty array");
|
|
264
|
+
const normalized = [...new Set(fields.map((field) => String(field)))];
|
|
265
|
+
if (normalized.some((field) => !snapshotfields.includes(field)))
|
|
266
|
+
throw new TypeError("browser context field is not allowlisted");
|
|
267
|
+
return normalized;
|
|
268
|
+
}
|
|
269
|
+
function normalizebudget(value) { const normalized = Number(value); if (!Number.isSafeInteger(normalized) || normalized <= 0)
|
|
270
|
+
throw new RangeError("browser context maxbytes must be a positive safe integer"); return normalized; }
|
|
271
|
+
function contextbytes(value) { return new TextEncoder().encode(JSON.stringify(value)).byteLength; }
|
|
272
|
+
function fitscontext(context, field, value, maxbytes) { return contextbytes({ ...context, [field]: value }) <= maxbytes; }
|
|
273
|
+
function clipcontextstring(context, field, value, maxbytes) {
|
|
274
|
+
let low = 0;
|
|
275
|
+
let high = value.length;
|
|
276
|
+
let result = "";
|
|
277
|
+
while (low <= high) {
|
|
278
|
+
const middle = Math.floor((low + high) / 2);
|
|
279
|
+
const candidate = value.slice(0, middle);
|
|
280
|
+
if (fitscontext(context, field, candidate, maxbytes)) {
|
|
281
|
+
result = candidate;
|
|
282
|
+
low = middle + 1;
|
|
283
|
+
}
|
|
284
|
+
else
|
|
285
|
+
high = middle - 1;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
290
|
+
/* Section 6: browser/recorder.ts — browser recorder captures action and snapshot boundaries for deterministic replay. */
|
|
291
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
292
|
+
/**
|
|
293
|
+
* browser recorder captures action and snapshot boundaries for deterministic replay.
|
|
294
|
+
* The optional event limit keeps long sessions bounded without owning persistence.
|
|
295
|
+
*/
|
|
296
|
+
/** Creates a recorder that links actions to snapshots and optionally bounds events. */
|
|
297
|
+
export function actionrecorder(options = {}) {
|
|
298
|
+
const startedat = Number(options.startedat ?? Date.now());
|
|
299
|
+
const maxevents = limit(options.maxevents);
|
|
300
|
+
const events = [];
|
|
301
|
+
let lastsnapshotid;
|
|
302
|
+
let dropped = 0;
|
|
303
|
+
function snapshot(value) {
|
|
304
|
+
lastsnapshotid = value?.snapshotid;
|
|
305
|
+
add({ type: "snapshot", t: Date.now() - startedat, snapshotid: lastsnapshotid, tabid: value?.tabid, frameid: value?.frameid, context: recordercontext(value) });
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
function action(input = {}) {
|
|
309
|
+
const context = recordercontext(input.context ?? input);
|
|
310
|
+
const event = { type: "action", t: Date.now() - startedat, action: String(input.action), snapshotid: input.snapshotid ?? lastsnapshotid, tabid: input.tabid, frameid: input.frameid, windowid: input.windowid, context, payload: clonevalue(input.payload ?? {}) };
|
|
311
|
+
add(event);
|
|
312
|
+
return clonevalue(event);
|
|
313
|
+
}
|
|
314
|
+
function list() { return events.map(clonevalue); }
|
|
315
|
+
function manifest() { return { version: 1, startedat, eventcount: events.length, dropped, lastsnapshotid, events: list() }; }
|
|
316
|
+
function exportjson() { return JSON.stringify(manifest()); }
|
|
317
|
+
function clear() { events.length = 0; dropped = 0; lastsnapshotid = undefined; }
|
|
318
|
+
return { snapshot, action, list, manifest, exportjson, clear };
|
|
319
|
+
function add(event) {
|
|
320
|
+
events.push(event);
|
|
321
|
+
while (events.length > maxevents) {
|
|
322
|
+
events.shift();
|
|
323
|
+
dropped += 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function limit(value) {
|
|
328
|
+
if (value === undefined)
|
|
329
|
+
return Infinity;
|
|
330
|
+
const numeric = Number(value);
|
|
331
|
+
if (!Number.isInteger(numeric) || numeric < 0)
|
|
332
|
+
throw new TypeError("maxevents must be a non-negative integer");
|
|
333
|
+
return numeric;
|
|
334
|
+
}
|
|
335
|
+
function clonevalue(value) {
|
|
336
|
+
if (Array.isArray(value))
|
|
337
|
+
return value.map(clonevalue);
|
|
338
|
+
if (value && typeof value === "object")
|
|
339
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clonevalue(item)]));
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
function recordercontext(value = {}) {
|
|
343
|
+
const context = {};
|
|
344
|
+
for (const name of ["windowid", "tabid", "frameid"])
|
|
345
|
+
if (value[name] !== undefined)
|
|
346
|
+
context[name] = String(value[name]);
|
|
347
|
+
return Object.keys(context).length ? context : undefined;
|
|
348
|
+
}
|
|
349
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
350
|
+
/* Section 7: browser/agent.ts — browser agent delegates browser actions to an injected runtime adapter. */
|
|
351
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
352
|
+
/**
|
|
353
|
+
* browser agent delegates browser actions to an injected runtime adapter.
|
|
354
|
+
*/
|
|
355
|
+
export function browseragent(adapter) {
|
|
356
|
+
const required = ["navigate", "click", "type", "screenshot", "html", "text", "title", "scrolltobottom", "executecommands"];
|
|
357
|
+
for (const name of required)
|
|
358
|
+
if (typeof adapter?.[name] !== "function")
|
|
359
|
+
throw new TypeError(`browser adapter requires ${name}`);
|
|
360
|
+
const optional = (name) => (...args) => { if (typeof adapter[name] !== "function")
|
|
361
|
+
throw new Error(`browser adapter does not support ${name}`); return adapter[name](...args); };
|
|
362
|
+
return { navigate: (options) => adapter.navigate(options), click: (target) => adapter.click(target), type: (value) => adapter.type(value), screenshot: (options) => adapter.screenshot(options), html: () => adapter.html(), text: () => adapter.text(), title: () => adapter.title(), scrolltobottom: (options) => adapter.scrolltobottom(options), executecommands: (commands) => adapter.executecommands(commands), fill: optional("fill"), key: optional("key"), upload: optional("upload"), snapshot: optional("snapshot"), tabs: optional("tabs"), frames: optional("frames") };
|
|
363
|
+
}
|
|
364
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
365
|
+
/* Section 8: browser/virtual.ts */
|
|
366
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
367
|
+
// @ts-nocheck
|
|
368
|
+
/**
|
|
369
|
+
* virtual browser contracts select remote browser capabilities without launching a browser or touching user state.
|
|
370
|
+
*/
|
|
371
|
+
const engines = new Set(["chromium", "gecko", "webkit", "custom"]);
|
|
372
|
+
const displays = new Set(["webrtc", "cdp", "webdriver", "remote-display", "custom"]);
|
|
373
|
+
const storages = new Set(["ephemeral", "caller-managed"]);
|
|
374
|
+
const receipts = new Set(["declared", "observed", "completed", "unknown"]);
|
|
375
|
+
const distributions = Object.freeze({
|
|
376
|
+
chromium: new Set(["chromium", "chrome", "edge", "brave", "vivaldi", "opera", "ungoogled-chromium"]),
|
|
377
|
+
gecko: new Set(["firefox", "tor-browser"]),
|
|
378
|
+
webkit: new Set(["safari"]),
|
|
379
|
+
custom: new Set(["saddle-browser", "custom"])
|
|
380
|
+
});
|
|
381
|
+
/** Creates a data-only request for a remote, adapter-owned browser session. */
|
|
382
|
+
export function virtualbrowserrequest(input = {}) {
|
|
383
|
+
const engine = valid(input.engine, engines, "browser engine");
|
|
384
|
+
const distribution = nonempty(input.distribution, "browser distribution");
|
|
385
|
+
if (!distributions[engine].has(distribution))
|
|
386
|
+
throw new TypeError("browser distribution does not match engine");
|
|
387
|
+
const display = valid(input.display ?? "webrtc", displays, "browser display transport");
|
|
388
|
+
const storage = valid(input.storage ?? "ephemeral", storages, "browser storage policy");
|
|
389
|
+
const execution = executionrequest({
|
|
390
|
+
id: input.id,
|
|
391
|
+
effect: "browser-session",
|
|
392
|
+
target: "remote",
|
|
393
|
+
source: input.source,
|
|
394
|
+
budget: input.budget
|
|
395
|
+
});
|
|
396
|
+
return Object.freeze({
|
|
397
|
+
version: 1,
|
|
398
|
+
state: "requested",
|
|
399
|
+
id: execution.id,
|
|
400
|
+
engine,
|
|
401
|
+
distribution,
|
|
402
|
+
display,
|
|
403
|
+
storage,
|
|
404
|
+
execution,
|
|
405
|
+
localaccess: Object.freeze({ browser: false, filesystem: false, storage: false, process: false }),
|
|
406
|
+
effects: Object.freeze([])
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/** Evaluates a virtual browser request without contacting a runtime or provider. */
|
|
410
|
+
export function virtualbrowserdecision(input, configuration = {}) {
|
|
411
|
+
const request = normalizerequest(input);
|
|
412
|
+
const base = executiondecision(request.execution, configuration);
|
|
413
|
+
const adapter = configuration.adapter ?? {};
|
|
414
|
+
const supportedengines = list(adapter.engines);
|
|
415
|
+
const supporteddistributions = list(adapter.distributions);
|
|
416
|
+
const reasons = [...base.reasons];
|
|
417
|
+
if (!supportedengines.includes(request.engine))
|
|
418
|
+
reasons.push("adapter-engine");
|
|
419
|
+
if (!supporteddistributions.includes(request.distribution))
|
|
420
|
+
reasons.push("adapter-distribution");
|
|
421
|
+
const allowed = reasons.length === 0;
|
|
422
|
+
return Object.freeze({
|
|
423
|
+
version: 1,
|
|
424
|
+
request,
|
|
425
|
+
state: allowed ? "caller-delegates" : "denied",
|
|
426
|
+
reasons: Object.freeze([...new Set(reasons)].sort()),
|
|
427
|
+
adapter: allowed ? Object.freeze({ owner: String(adapter.owner), engines: Object.freeze(supportedengines), distributions: Object.freeze(supporteddistributions) }) : null,
|
|
428
|
+
effects: Object.freeze([])
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/** Projects an adapter handoff without launching a browser or transferring user state. */
|
|
432
|
+
export function virtualbrowserhandoff(input = {}) {
|
|
433
|
+
const decision = virtualbrowserdecision(input.request, input.configuration);
|
|
434
|
+
if (decision.state !== "caller-delegates")
|
|
435
|
+
return Object.freeze({ version: 1, state: "browser-disabled", code: "VIRTUAL_BROWSER_POLICY_DENIED", decision, effects: Object.freeze([]) });
|
|
436
|
+
return Object.freeze({ version: 1, state: "caller-delegates", code: "REMOTE_BROWSER_ADAPTER_REQUIRED", decision, effects: Object.freeze([]) });
|
|
437
|
+
}
|
|
438
|
+
/** Records declared or observed remote-browser capabilities without asserting isolation or trust. */
|
|
439
|
+
export function virtualbrowserreceipt(input = {}) {
|
|
440
|
+
const request = normalizerequest(input.request ?? input);
|
|
441
|
+
const state = valid(input.state ?? "declared", receipts, "browser receipt state");
|
|
442
|
+
const adapterid = nonempty(input.adapterid, "browser adapter id");
|
|
443
|
+
const image = nonempty(input.image, "browser image reference");
|
|
444
|
+
return Object.freeze({
|
|
445
|
+
version: 1,
|
|
446
|
+
state,
|
|
447
|
+
request,
|
|
448
|
+
adapterid,
|
|
449
|
+
image,
|
|
450
|
+
capabilities: Object.freeze(list(input.capabilities)),
|
|
451
|
+
effects: Object.freeze([])
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function list(value) { return [...new Set(Array.isArray(value) ? value.map((entry) => String(entry)) : [])].sort(); }
|
|
455
|
+
function normalizerequest(input) {
|
|
456
|
+
if (input?.execution?.effect === "browser-session")
|
|
457
|
+
return virtualbrowserrequest({ id: input.id, source: input.execution.source, engine: input.engine, distribution: input.distribution, display: input.display, storage: input.storage, budget: input.execution.budget });
|
|
458
|
+
return virtualbrowserrequest(input);
|
|
459
|
+
}
|
|
460
|
+
function valid(value, collection, name) { const output = String(value ?? ""); if (!collection.has(output))
|
|
461
|
+
throw new TypeError(`${name} is invalid`); return output; }
|
|
462
|
+
function nonempty(value, name) { const output = String(value ?? ""); if (!output)
|
|
463
|
+
throw new TypeError(`${name} is required`); return output; }
|
|
464
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
465
|
+
/* Section 9: browser/playwright.ts — browser playwright adapter keeps the optional Node browser provider outside the transport-neutral surface. */
|
|
466
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
467
|
+
/**
|
|
468
|
+
* browser playwright adapter keeps the optional Node browser provider outside the transport-neutral surface.
|
|
469
|
+
*/
|
|
470
|
+
/** Creates a caller-owned Playwright browser session when the optional peer is installed. */
|
|
471
|
+
export async function createplaywrightsession(options = {}) {
|
|
472
|
+
let playwright;
|
|
473
|
+
try {
|
|
474
|
+
playwright = await import("playwright");
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
const missing = new Error("optional peer dependency 'playwright' is required for the Playwright adapter", { cause: error });
|
|
478
|
+
missing.code = "OPTIONAL_DEPENDENCY_MISSING";
|
|
479
|
+
throw missing;
|
|
480
|
+
}
|
|
481
|
+
const browsername = options.browser ?? "chromium";
|
|
482
|
+
const browsertype = playwright[browsername];
|
|
483
|
+
if (!browsertype || typeof browsertype.launch !== "function")
|
|
484
|
+
throw new TypeError(`unsupported Playwright browser: ${browsername}`);
|
|
485
|
+
const browser = await browsertype.launch({ headless: options.headless ?? true, ...(options.launch ?? {}) });
|
|
486
|
+
const context = await browser.newContext(options.context ?? {});
|
|
487
|
+
const page = await context.newPage();
|
|
488
|
+
return { browser, context, page, close: () => browser.close() };
|
|
489
|
+
}
|
|
490
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
491
|
+
/* Section 10: extension/permissions.ts — extension permission policies keep the browser capability boundary explicit and caller-owned. */
|
|
492
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
493
|
+
/**
|
|
494
|
+
* extension permission policies keep the browser capability boundary explicit and caller-owned.
|
|
495
|
+
*/
|
|
496
|
+
export const extensionpermissions = Object.freeze(["activeTab", "scripting", "storage"]);
|
|
497
|
+
/** Creates a minimal permission policy without requesting broad host access. */
|
|
498
|
+
export function permissionpolicy(options = {}) {
|
|
499
|
+
const requested = [...new Set((options.requested ?? extensionpermissions).map(String))];
|
|
500
|
+
const optional = [...new Set((options.optional ?? []).map(String))];
|
|
501
|
+
const unknown = requested.concat(optional).filter((permission) => !extensionpermissions.includes(permission));
|
|
502
|
+
if (unknown.length)
|
|
503
|
+
throw new TypeError(`unsupported extension permission: ${unknown[0]}`);
|
|
504
|
+
return { version: 1, requested, optional, hostpermissions: [], allows(permission) { return requested.includes(String(permission)); }, missing(permissions = []) { return [...new Set(permissions.map(String))].filter((permission) => !requested.includes(permission)); } };
|
|
505
|
+
}
|
|
506
|
+
/** Requests an optional capability through an injected browser permission function. */
|
|
507
|
+
export async function requestpermission(policy, permission, request) {
|
|
508
|
+
const name = String(permission ?? "");
|
|
509
|
+
if (!policy?.optional?.includes(name))
|
|
510
|
+
throw new TypeError(`permission is not optional: ${name}`);
|
|
511
|
+
if (typeof request !== "function")
|
|
512
|
+
throw new TypeError("permission request function is required");
|
|
513
|
+
try {
|
|
514
|
+
return { permission: name, granted: Boolean(await request(name)) };
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
return { permission: name, granted: false, code: String(error?.code ?? "PERMISSION_REQUEST_FAILED"), message: String(error?.message ?? error) };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
521
|
+
/* Section 11: extension/protocol.ts — extension protocol defines serializable commands, responses, errors and page snapshots. */
|
|
522
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
523
|
+
/**
|
|
524
|
+
* extension protocol defines serializable commands, responses, errors and page snapshots.
|
|
525
|
+
*/
|
|
526
|
+
export const protocolversion = 1;
|
|
527
|
+
export const extensioncommands = Object.freeze(["snapshot", "readpage", "pagefacts", "clickref", "fillref"]);
|
|
528
|
+
/** Creates a compact identifier without embedding a host, port or credential. */
|
|
529
|
+
export function createid(prefix = "msg", source) {
|
|
530
|
+
const generator = source ?? (() => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}${Math.random().toString(16).slice(2)}`);
|
|
531
|
+
const value = generator().replaceAll("-", "");
|
|
532
|
+
return `${prefix}${value}`;
|
|
533
|
+
}
|
|
534
|
+
/** Creates a versioned command for the extension message bus. */
|
|
535
|
+
export function createcommand(command, payload = {}, options = {}) {
|
|
536
|
+
if (!extensioncommands.includes(command))
|
|
537
|
+
throw new TypeError(`unsupported extension command: ${command}`);
|
|
538
|
+
return createmessage("command", { ...options, command, payload });
|
|
539
|
+
}
|
|
540
|
+
/** Creates a serializable message envelope. */
|
|
541
|
+
export function createmessage(type, options = {}) {
|
|
542
|
+
if (!["command", "response", "error", "event"].includes(type))
|
|
543
|
+
throw new TypeError(`unsupported extension message type: ${type}`);
|
|
544
|
+
const message = { version: protocolversion, type, id: options.id ?? createid(type) };
|
|
545
|
+
if (options.requestid)
|
|
546
|
+
message.requestid = options.requestid;
|
|
547
|
+
if (options.command)
|
|
548
|
+
message.command = options.command;
|
|
549
|
+
if (options.payload !== undefined)
|
|
550
|
+
message.payload = options.payload;
|
|
551
|
+
if (options.error)
|
|
552
|
+
message.error = options.error;
|
|
553
|
+
assertserializable(message);
|
|
554
|
+
return message;
|
|
555
|
+
}
|
|
556
|
+
/** Creates a correlated successful response. */
|
|
557
|
+
export function createresponse(request, payload = {}) {
|
|
558
|
+
assertmessage(request);
|
|
559
|
+
return createmessage("response", { requestid: request.id, payload });
|
|
560
|
+
}
|
|
561
|
+
/** Creates a correlated error response with stable error fields. */
|
|
562
|
+
export function createerror(request, error, options = {}) {
|
|
563
|
+
const requestid = request?.id ?? options.requestid;
|
|
564
|
+
const failure = { code: options.code ?? error?.code ?? "extension_error", message: String(error?.message ?? error ?? "extension request failed"), retryable: Boolean(options.retryable ?? error?.retryable) };
|
|
565
|
+
return createmessage("error", { requestid, error: failure });
|
|
566
|
+
}
|
|
567
|
+
/** Validates a message before a privileged context handles it. */
|
|
568
|
+
export function assertmessage(message) {
|
|
569
|
+
if (!message || typeof message !== "object")
|
|
570
|
+
throw new TypeError("extension message must be an object");
|
|
571
|
+
if (message.version !== protocolversion)
|
|
572
|
+
throw new TypeError(`unsupported extension protocol version: ${message.version}`);
|
|
573
|
+
if (typeof message.type !== "string" || typeof message.id !== "string")
|
|
574
|
+
throw new TypeError("extension message requires type and id");
|
|
575
|
+
if (message.type === "command" && !extensioncommands.includes(message.command))
|
|
576
|
+
throw new TypeError(`unsupported extension command: ${message.command}`);
|
|
577
|
+
if (message.payload !== undefined && (!message.payload || typeof message.payload !== "object" || Array.isArray(message.payload)))
|
|
578
|
+
throw new TypeError("extension payload must be an object");
|
|
579
|
+
return message;
|
|
580
|
+
}
|
|
581
|
+
/** Creates a structured page snapshot with stable element references. */
|
|
582
|
+
export function createsnapshot(input = {}) {
|
|
583
|
+
const snapshot = {
|
|
584
|
+
version: protocolversion,
|
|
585
|
+
snapshotid: String(input.snapshotid ?? createid("snap")),
|
|
586
|
+
createdat: Number(input.createdat ?? Date.now()),
|
|
587
|
+
windowid: input.windowid === undefined ? undefined : String(input.windowid),
|
|
588
|
+
tabid: input.tabid === undefined ? undefined : String(input.tabid),
|
|
589
|
+
frameid: input.frameid === undefined ? undefined : String(input.frameid),
|
|
590
|
+
url: String(input.url ?? ""),
|
|
591
|
+
title: String(input.title ?? ""),
|
|
592
|
+
text: String(input.text ?? ""),
|
|
593
|
+
elements: Array.isArray(input.elements) ? input.elements.map((element) => ({ ref: String(element.ref), role: String(element.role ?? "generic"), name: String(element.name ?? ""), value: element.value === undefined ? undefined : String(element.value), disabled: Boolean(element.disabled) })) : []
|
|
594
|
+
};
|
|
595
|
+
assertserializable(snapshot);
|
|
596
|
+
return snapshot;
|
|
597
|
+
}
|
|
598
|
+
/** Returns whether a reference still belongs to the current page snapshot. */
|
|
599
|
+
export function isfreshsnapshot(snapshotid, currentid) { return Boolean(snapshotid && currentid && snapshotid === currentid); }
|
|
600
|
+
/** Computes bounded additions, removals and changed elements between extension snapshots. */
|
|
601
|
+
export function snapshotdiff(previous, current) {
|
|
602
|
+
const before = createsnapshot(previous);
|
|
603
|
+
const after = createsnapshot(current);
|
|
604
|
+
const oldmap = new Map(before.elements.map((element) => [element.ref, element]));
|
|
605
|
+
const newmap = new Map(after.elements.map((element) => [element.ref, element]));
|
|
606
|
+
return {
|
|
607
|
+
from: before.snapshotid,
|
|
608
|
+
to: after.snapshotid,
|
|
609
|
+
contextchanged: before.windowid !== after.windowid || before.tabid !== after.tabid || before.frameid !== after.frameid,
|
|
610
|
+
added: after.elements.filter((element) => !oldmap.has(element.ref)),
|
|
611
|
+
removed: before.elements.filter((element) => !newmap.has(element.ref)),
|
|
612
|
+
changed: after.elements.filter((element) => oldmap.has(element.ref) && JSON.stringify(oldmap.get(element.ref)) !== JSON.stringify(element))
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function assertserializable(value) {
|
|
616
|
+
try {
|
|
617
|
+
JSON.stringify(value);
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
throw new TypeError(`extension message is not serializable: ${error.message}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
624
|
+
/* Section 12: extension/content.ts — content bridge runs in the isolated world and exposes bounded page facts and user initiated actions. */
|
|
625
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
626
|
+
/**
|
|
627
|
+
* content bridge runs in the isolated world and exposes bounded page facts and user initiated actions.
|
|
628
|
+
* It stays classic JavaScript because Chrome injects programmatic content scripts as files.
|
|
629
|
+
*/
|
|
630
|
+
(function installglobalbridge(global) {
|
|
631
|
+
const installedkey = "__saddlecontentbridge";
|
|
632
|
+
const protocolversion = 1;
|
|
633
|
+
const commands = ["snapshot", "readpage", "pagefacts", "clickref", "fillref"];
|
|
634
|
+
const pagechannelname = "saddle.pagefacts.v1";
|
|
635
|
+
function createbridge(documentref, now = () => Date.now(), options = {}) {
|
|
636
|
+
let snapshotid = null;
|
|
637
|
+
let sequence = 0;
|
|
638
|
+
const references = new Map();
|
|
639
|
+
const pagechannel = options.pagechannel ?? (typeof global.addEventListener === "function" && typeof global.postMessage === "function" ? createpagechannel(global, now, options.timeout) : null);
|
|
640
|
+
function snapshotpage() {
|
|
641
|
+
const nextid = `snap${++sequence}${now()}`;
|
|
642
|
+
const elements = [];
|
|
643
|
+
references.clear();
|
|
644
|
+
const candidates = documentref.querySelectorAll?.("a,button,input,textarea,select,[role]") ?? [];
|
|
645
|
+
for (const element of Array.from(candidates).slice(0, 100)) {
|
|
646
|
+
if (!visible(element))
|
|
647
|
+
continue;
|
|
648
|
+
const ref = `e${elements.length + 1}`;
|
|
649
|
+
references.set(ref, { element, snapshotid: nextid });
|
|
650
|
+
elements.push({ ref, role: roleof(element), name: nameof(element) });
|
|
651
|
+
}
|
|
652
|
+
snapshotid = nextid;
|
|
653
|
+
return { version: protocolversion, snapshotid, createdat: now(), url: String(documentref.location?.href ?? ""), title: String(documentref.title ?? ""), text: String(documentref.body?.innerText ?? "").slice(0, 100000), elements };
|
|
654
|
+
}
|
|
655
|
+
function readpage() {
|
|
656
|
+
const page = snapshotpage();
|
|
657
|
+
return { version: page.version, snapshotid: page.snapshotid, url: page.url, title: page.title, text: page.text };
|
|
658
|
+
}
|
|
659
|
+
function resolve(ref, requestedid) {
|
|
660
|
+
if (requestedid !== snapshotid)
|
|
661
|
+
throw failure("stale_snapshot", "page snapshot is stale");
|
|
662
|
+
const entry = references.get(String(ref));
|
|
663
|
+
if (!entry || entry.snapshotid !== snapshotid)
|
|
664
|
+
throw failure("unknown_reference", `unknown page reference: ${ref}`);
|
|
665
|
+
return entry.element;
|
|
666
|
+
}
|
|
667
|
+
function handle(request) {
|
|
668
|
+
if (request?.version !== protocolversion || request.type !== "command" || !commands.includes(request.command))
|
|
669
|
+
throw failure("invalid_message", "invalid content command");
|
|
670
|
+
const payload = request.payload ?? {};
|
|
671
|
+
if (request.command === "snapshot")
|
|
672
|
+
return snapshotpage();
|
|
673
|
+
if (request.command === "readpage")
|
|
674
|
+
return readpage();
|
|
675
|
+
if (request.command === "pagefacts") {
|
|
676
|
+
if (!pagechannel)
|
|
677
|
+
throw failure("page_bridge_unavailable", "page bridge is not available");
|
|
678
|
+
return pagechannel.readpage();
|
|
679
|
+
}
|
|
680
|
+
const element = resolve(payload.ref, payload.snapshotid);
|
|
681
|
+
if (request.command === "clickref") {
|
|
682
|
+
element.click?.();
|
|
683
|
+
return { ref: payload.ref, clicked: true, snapshotid };
|
|
684
|
+
}
|
|
685
|
+
if (!isfillable(element))
|
|
686
|
+
throw failure("not_fillable", `element is not fillable: ${payload.ref}`);
|
|
687
|
+
setvalue(element, payload.value);
|
|
688
|
+
return { ref: payload.ref, filled: true, snapshotid };
|
|
689
|
+
}
|
|
690
|
+
return { handle, snapshotpage, readpage, pagechannel };
|
|
691
|
+
}
|
|
692
|
+
function createpagechannel(globalref = global, now = () => Date.now(), timeout = 1500) {
|
|
693
|
+
if (typeof globalref.addEventListener !== "function" || typeof globalref.postMessage !== "function")
|
|
694
|
+
throw new TypeError("page channel requires window messaging APIs");
|
|
695
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1)
|
|
696
|
+
throw new TypeError("page channel timeout must be a positive safe integer");
|
|
697
|
+
const pending = new Map();
|
|
698
|
+
let sequence = 0;
|
|
699
|
+
const token = `token${now()}${Math.random().toString(16).slice(2)}`;
|
|
700
|
+
function listener(event) {
|
|
701
|
+
const response = event?.data;
|
|
702
|
+
if (event?.source && event.source !== globalref)
|
|
703
|
+
return;
|
|
704
|
+
if (response?.channel !== pagechannelname || response.type !== "response" || response.token !== token)
|
|
705
|
+
return;
|
|
706
|
+
const request = pending.get(response.requestid);
|
|
707
|
+
if (!request)
|
|
708
|
+
return;
|
|
709
|
+
pending.delete(response.requestid);
|
|
710
|
+
globalref.clearTimeout?.(request.timer);
|
|
711
|
+
request.resolve(response.payload ?? {});
|
|
712
|
+
}
|
|
713
|
+
function readpage() {
|
|
714
|
+
const requestid = `page${now()}${++sequence}`;
|
|
715
|
+
return new Promise((resolve, reject) => {
|
|
716
|
+
const timer = globalref.setTimeout(() => {
|
|
717
|
+
pending.delete(requestid);
|
|
718
|
+
const error = new Error("page bridge response timed out");
|
|
719
|
+
error.code = "page_bridge_timeout";
|
|
720
|
+
reject(error);
|
|
721
|
+
}, timeout);
|
|
722
|
+
pending.set(requestid, { resolve, timer });
|
|
723
|
+
globalref.postMessage({ channel: pagechannelname, version: 1, type: "request", requestid, token }, "*");
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
globalref.addEventListener("message", listener);
|
|
727
|
+
return { readpage, dispose() { globalref.removeEventListener?.("message", listener); for (const request of pending.values())
|
|
728
|
+
globalref.clearTimeout?.(request.timer); pending.clear(); } };
|
|
729
|
+
}
|
|
730
|
+
function install(runtime = global.chrome?.runtime, documentref = global.document) {
|
|
731
|
+
if (!runtime?.onMessage || !documentref)
|
|
732
|
+
throw new TypeError("content bridge requires runtime and document");
|
|
733
|
+
if (global[installedkey])
|
|
734
|
+
return global[installedkey];
|
|
735
|
+
const bridge = createbridge(documentref);
|
|
736
|
+
const listener = (message, sender, sendresponse) => {
|
|
737
|
+
Promise.resolve().then(() => bridge.handle(message)).then((payload) => sendresponse({ version: protocolversion, type: "response", id: `resp${Date.now()}`, requestid: message?.id, payload })).catch((error) => sendresponse({ version: protocolversion, type: "error", id: `err${Date.now()}`, requestid: message?.id, error: { code: error.code ?? "content_error", message: error.message } }));
|
|
738
|
+
return true;
|
|
739
|
+
};
|
|
740
|
+
runtime.onMessage.addListener(listener);
|
|
741
|
+
global[installedkey] = { bridge, listener, dispose() { runtime.onMessage.removeListener?.(listener); delete global[installedkey]; } };
|
|
742
|
+
return global[installedkey];
|
|
743
|
+
}
|
|
744
|
+
function visible(element) {
|
|
745
|
+
const style = global.getComputedStyle?.(element);
|
|
746
|
+
if (style && (style.display === "none" || style.visibility === "hidden"))
|
|
747
|
+
return false;
|
|
748
|
+
return typeof element.getClientRects !== "function" || element.getClientRects().length > 0;
|
|
749
|
+
}
|
|
750
|
+
function roleof(element) { return String(element.getAttribute?.("role") || element.tagName || "generic").toLowerCase(); }
|
|
751
|
+
function nameof(element) { return String(element.getAttribute?.("aria-label") || element.innerText || element.textContent || element.getAttribute?.("placeholder") || "").trim().replace(/\s+/g, " ").slice(0, 200); }
|
|
752
|
+
function isfillable(element) { return ["INPUT", "TEXTAREA"].includes(String(element.tagName).toUpperCase()); }
|
|
753
|
+
function setvalue(element, value) { element.value = String(value ?? ""); element.dispatchEvent?.(new Event("input", { bubbles: true })); element.dispatchEvent?.(new Event("change", { bubbles: true })); }
|
|
754
|
+
function failure(code, message) { const error = new Error(message); error.code = code; return error; }
|
|
755
|
+
global.saddlecontent = { createbridge, createpagechannel, install };
|
|
756
|
+
if (global.chrome?.runtime?.onMessage && global.document)
|
|
757
|
+
install();
|
|
758
|
+
})(globalThis);
|
|
759
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
760
|
+
/* Section 13: extension/popup.ts — popup sends user initiated read commands through the service worker. */
|
|
761
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
762
|
+
/**
|
|
763
|
+
* popup sends user initiated read commands through the service worker.
|
|
764
|
+
* The DOM bootstrap of the former extension/popup.ts runs only when a
|
|
765
|
+
* document exists (the popup page of the built extension); importing the
|
|
766
|
+
* merged module from Node never touches the DOM.
|
|
767
|
+
*/
|
|
768
|
+
if (typeof document !== "undefined") {
|
|
769
|
+
const status = document.querySelector("#status");
|
|
770
|
+
const output = document.querySelector("#output");
|
|
771
|
+
for (const button of document.querySelectorAll("[data-command]"))
|
|
772
|
+
button.addEventListener("click", () => request(button.dataset.command));
|
|
773
|
+
async function request(command) {
|
|
774
|
+
try {
|
|
775
|
+
status.textContent = "Reading active tab…";
|
|
776
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
777
|
+
if (!tab?.id)
|
|
778
|
+
throw new Error("no active tab");
|
|
779
|
+
const response = await chrome.runtime.sendMessage(createcommand(command, { tabid: tab.id }));
|
|
780
|
+
if (response?.type === "error")
|
|
781
|
+
throw new Error(response.error?.message ?? "extension request failed");
|
|
782
|
+
output.textContent = JSON.stringify(response?.payload ?? response, null, 2);
|
|
783
|
+
status.textContent = "Complete.";
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
status.textContent = "Request failed.";
|
|
787
|
+
output.textContent = String(error.message ?? error);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
792
|
+
/* Section 14: extension/pagebridge.ts — page bridge exposes bounded, read-only page facts to the isolated content world. */
|
|
793
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
794
|
+
/**
|
|
795
|
+
* page bridge exposes bounded, read-only page facts to the isolated content world.
|
|
796
|
+
* It never executes page supplied commands and never forwards extension credentials.
|
|
797
|
+
*/
|
|
798
|
+
(function installpagebridge(global) {
|
|
799
|
+
const channel = "saddle.pagefacts.v1";
|
|
800
|
+
const installedkey = "__saddlepagebridge";
|
|
801
|
+
function createpagebridge(globalref = global) {
|
|
802
|
+
function readpage() {
|
|
803
|
+
const documentref = globalref.document;
|
|
804
|
+
return {
|
|
805
|
+
url: String(documentref?.location?.href ?? ""),
|
|
806
|
+
title: String(documentref?.title ?? "").slice(0, 500),
|
|
807
|
+
text: String(documentref?.body?.innerText ?? "").slice(0, 100000)
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function listener(event) {
|
|
811
|
+
const request = event?.data;
|
|
812
|
+
if (event?.source && event.source !== globalref)
|
|
813
|
+
return;
|
|
814
|
+
if (request?.channel !== channel || request.type !== "request" || typeof request.requestid !== "string" || typeof request.token !== "string")
|
|
815
|
+
return;
|
|
816
|
+
globalref.postMessage({ channel, version: 1, type: "response", requestid: request.requestid, token: request.token, payload: readpage() }, "*");
|
|
817
|
+
}
|
|
818
|
+
return { channel, listener, readpage };
|
|
819
|
+
}
|
|
820
|
+
function install(globalref = global) {
|
|
821
|
+
if (typeof globalref?.addEventListener !== "function" || !globalref.document)
|
|
822
|
+
throw new TypeError("page bridge requires a window and document");
|
|
823
|
+
if (globalref[installedkey])
|
|
824
|
+
return globalref[installedkey];
|
|
825
|
+
const bridge = createpagebridge(globalref);
|
|
826
|
+
globalref.addEventListener("message", bridge.listener);
|
|
827
|
+
globalref[installedkey] = { ...bridge, dispose() { globalref.removeEventListener?.("message", bridge.listener); delete globalref[installedkey]; } };
|
|
828
|
+
return globalref[installedkey];
|
|
829
|
+
}
|
|
830
|
+
global.saddlepagebridge = { channel, createpagebridge, install };
|
|
831
|
+
if (global.document && typeof global.addEventListener === "function")
|
|
832
|
+
install(global);
|
|
833
|
+
})(globalThis);
|
|
834
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
835
|
+
/* Section 15: extension/worker.ts — worker binds the generic extension router to the Manifest V3 runtime APIs. */
|
|
836
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
837
|
+
/**
|
|
838
|
+
* worker binds the generic extension router to the Manifest V3 runtime APIs.
|
|
839
|
+
*/
|
|
840
|
+
/** Installs the service worker listeners against a caller supplied Chrome API object. */
|
|
841
|
+
export function startworker(chromeapi = globalThis.chrome) {
|
|
842
|
+
if (!chromeapi?.runtime?.onMessage || !chromeapi?.tabs || !chromeapi?.scripting)
|
|
843
|
+
throw new TypeError("extension worker requires runtime tabs and scripting APIs");
|
|
844
|
+
const router = createworkerrouter({ tabs: chromeapi.tabs, scripting: chromeapi.scripting, storage: chromeapi.storage?.session });
|
|
845
|
+
const listener = (message, sender, sendresponse) => {
|
|
846
|
+
router.handle(message, sender).then(sendresponse).catch((error) => sendresponse(createerror(message, error)));
|
|
847
|
+
return true;
|
|
848
|
+
};
|
|
849
|
+
const startup = () => router.rehydrate().catch(() => undefined);
|
|
850
|
+
chromeapi.runtime.onMessage.addListener(listener);
|
|
851
|
+
chromeapi.runtime.onStartup?.addListener(startup);
|
|
852
|
+
return { router, dispose() { chromeapi.runtime.onMessage.removeListener?.(listener); chromeapi.runtime.onStartup?.removeListener?.(startup); } };
|
|
853
|
+
}
|
|
854
|
+
if (globalThis.chrome?.runtime?.onMessage)
|
|
855
|
+
startworker();
|
|
856
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
857
|
+
/* Section 16: extension/serviceworker.ts — service worker router forwards user initiated commands to the active tab and persists resumable state. */
|
|
858
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
859
|
+
/**
|
|
860
|
+
* service worker router forwards user initiated commands to the active tab and persists resumable state.
|
|
861
|
+
*/
|
|
862
|
+
/** Creates a browser independent router around Chrome tabs, scripting and storage APIs. */
|
|
863
|
+
export function createworkerrouter(options = {}) {
|
|
864
|
+
const tabs = options.tabs;
|
|
865
|
+
const scripting = options.scripting;
|
|
866
|
+
const storage = options.storage;
|
|
867
|
+
const contentfile = options.contentfile ?? "content.js";
|
|
868
|
+
const statekey = options.statekey ?? "saddleextensionstate";
|
|
869
|
+
const maxpending = options.maxpending ?? 32;
|
|
870
|
+
if (typeof tabs?.sendMessage !== "function")
|
|
871
|
+
throw new TypeError("extension router requires tabs.sendMessage");
|
|
872
|
+
if (!Number.isSafeInteger(maxpending) || maxpending < 1)
|
|
873
|
+
throw new TypeError("extension router maxpending must be a positive safe integer");
|
|
874
|
+
async function ensurecontent(tabid) {
|
|
875
|
+
if (!Number.isInteger(tabid))
|
|
876
|
+
throw new TypeError("extension command requires a tab id");
|
|
877
|
+
if (typeof scripting?.executeScript !== "function")
|
|
878
|
+
throw new TypeError("extension router requires scripting.executeScript");
|
|
879
|
+
await scripting.executeScript({ target: { tabId: tabid }, files: [contentfile] });
|
|
880
|
+
}
|
|
881
|
+
async function readstate() {
|
|
882
|
+
if (typeof storage?.get !== "function")
|
|
883
|
+
return {};
|
|
884
|
+
const result = await storage.get(statekey);
|
|
885
|
+
return result?.[statekey] ?? {};
|
|
886
|
+
}
|
|
887
|
+
async function savestate(value) {
|
|
888
|
+
if (typeof storage?.set === "function")
|
|
889
|
+
await storage.set({ [statekey]: value });
|
|
890
|
+
}
|
|
891
|
+
async function pendingstate() {
|
|
892
|
+
const value = await readstate();
|
|
893
|
+
return { ...value, pending: Array.isArray(value.pending) ? value.pending.filter(validpending) : [] };
|
|
894
|
+
}
|
|
895
|
+
async function enqueue(request, sender) {
|
|
896
|
+
const state = await pendingstate();
|
|
897
|
+
const record = pendingrecord(request, sender);
|
|
898
|
+
const pending = state.pending.filter((item) => item.requestid !== record.requestid);
|
|
899
|
+
if (pending.length >= maxpending && !state.pending.some((item) => item.requestid === record.requestid))
|
|
900
|
+
throw extensionerror("PENDING_LIMIT", "extension pending command limit reached");
|
|
901
|
+
pending.push(record);
|
|
902
|
+
await savestate({ ...state, pending });
|
|
903
|
+
return record;
|
|
904
|
+
}
|
|
905
|
+
async function complete(requestid, patch = {}) {
|
|
906
|
+
const state = await pendingstate();
|
|
907
|
+
await savestate({ ...state, ...patch, pending: state.pending.filter((item) => item.requestid !== requestid) });
|
|
908
|
+
}
|
|
909
|
+
async function markfailure(requestid, error) {
|
|
910
|
+
const state = await pendingstate();
|
|
911
|
+
const pending = state.pending.map((item) => item.requestid === requestid ? { ...item, attempts: item.attempts + 1, lasterror: { code: String(error?.code ?? "extension_error"), message: String(error?.message ?? error) }, updatedat: Date.now() } : item);
|
|
912
|
+
await savestate({ ...state, pending });
|
|
913
|
+
}
|
|
914
|
+
async function dispatch(request, sender = {}) {
|
|
915
|
+
const tabid = request.payload?.tabid ?? sender.tab?.id;
|
|
916
|
+
await ensurecontent(tabid);
|
|
917
|
+
const response = await tabs.sendMessage(tabid, request);
|
|
918
|
+
return decorate(response, sender, tabid);
|
|
919
|
+
}
|
|
920
|
+
async function handle(message, sender = {}) {
|
|
921
|
+
const request = assertmessage(message);
|
|
922
|
+
if (request.type !== "command")
|
|
923
|
+
throw new TypeError("extension router accepts commands only");
|
|
924
|
+
const tabid = request.payload?.tabid ?? sender.tab?.id;
|
|
925
|
+
await enqueue(request, { ...sender, tab: { ...sender.tab, id: tabid } });
|
|
926
|
+
try {
|
|
927
|
+
const response = await dispatch(request, { ...sender, tab: { ...sender.tab, id: tabid } });
|
|
928
|
+
await complete(request.id, response?.type === "response" && response.payload?.snapshotid ? { tabid, snapshotid: response.payload.snapshotid, frameid: response.payload.frameid, windowid: response.payload.windowid, updatedat: Date.now() } : { updatedat: Date.now() });
|
|
929
|
+
return response;
|
|
930
|
+
}
|
|
931
|
+
catch (error) {
|
|
932
|
+
await markfailure(request.id, error);
|
|
933
|
+
throw error;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async function rehydrate() { return pendingstate(); }
|
|
937
|
+
async function resume(requestid, sender = {}) {
|
|
938
|
+
const state = await pendingstate();
|
|
939
|
+
const pending = state.pending.find((item) => item.requestid === requestid);
|
|
940
|
+
if (!pending)
|
|
941
|
+
throw extensionerror("PENDING_NOT_FOUND", `pending command not found: ${requestid}`);
|
|
942
|
+
const tabid = pending.tabid ?? sender.tab?.id;
|
|
943
|
+
const response = await dispatch(pending.message, { ...sender, frameId: pending.frameid ?? sender.frameId, tab: { ...sender.tab, id: tabid, windowId: pending.windowid ?? sender.tab?.windowId } });
|
|
944
|
+
await complete(requestid, response?.type === "response" && response.payload?.snapshotid ? { tabid, snapshotid: response.payload.snapshotid, frameid: response.payload.frameid, windowid: response.payload.windowid, updatedat: Date.now() } : { updatedat: Date.now() });
|
|
945
|
+
return response;
|
|
946
|
+
}
|
|
947
|
+
async function cancel(requestid) { const state = await pendingstate(); await savestate({ ...state, pending: state.pending.filter((item) => item.requestid !== requestid) }); }
|
|
948
|
+
return { ensurecontent, readstate, savestate, rehydrate, enqueue, resume, cancel, handle };
|
|
949
|
+
}
|
|
950
|
+
function pendingrecord(request, sender = {}) { return { requestid: request.id, command: request.command, message: request, tabid: request.payload?.tabid ?? sender.tab?.id, frameid: request.payload?.frameid ?? sender.frameId, windowid: request.payload?.windowid ?? sender.tab?.windowId, attempts: 0, createdat: Date.now(), updatedat: Date.now() }; }
|
|
951
|
+
function decorate(response, sender, tabid) {
|
|
952
|
+
if (response?.type !== "response" || !response.payload || typeof response.payload !== "object" || !response.payload.snapshotid)
|
|
953
|
+
return response;
|
|
954
|
+
const payload = { ...response.payload, tabid: response.payload.tabid ?? (tabid === undefined ? undefined : String(tabid)), frameid: response.payload.frameid ?? (sender.frameId === undefined ? undefined : String(sender.frameId)), windowid: response.payload.windowid ?? (sender.tab?.windowId === undefined ? undefined : String(sender.tab.windowId)) };
|
|
955
|
+
return { ...response, payload };
|
|
956
|
+
}
|
|
957
|
+
function validpending(value) { return Boolean(value && typeof value === "object" && typeof value.requestid === "string" && value.message && value.message.type === "command" && Number.isSafeInteger(value.attempts) && value.attempts >= 0); }
|
|
958
|
+
function extensionerror(code, message) { const error = new Error(message); error.code = code; return error; }
|
|
959
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
960
|
+
/* Section 17: extension/build.ts — extension build adapter creates a versioned, unpacked Manifest V3 artifact for release packaging. */
|
|
961
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
962
|
+
/**
|
|
963
|
+
* extension build adapter creates a versioned, unpacked Manifest V3 artifact for release packaging.
|
|
964
|
+
*/
|
|
965
|
+
/* dist/browser.js resolves the repository root as its parent directory; the
|
|
966
|
+
source browser.ts sits at the repository root itself. */
|
|
967
|
+
const modulepath = resolve(dirname(fileURLToPath(import.meta.url)));
|
|
968
|
+
const rootpath = basename(modulepath) === "dist" ? resolve(modulepath, "..") : modulepath;
|
|
969
|
+
const extensionassets = resolve(rootpath, "web", "extension");
|
|
970
|
+
const entries = [
|
|
971
|
+
"manifest.json",
|
|
972
|
+
"worker.js",
|
|
973
|
+
"serviceworker.js",
|
|
974
|
+
"content.js",
|
|
975
|
+
"pagebridge.js",
|
|
976
|
+
"popup.js",
|
|
977
|
+
"popup.html",
|
|
978
|
+
"popup.css",
|
|
979
|
+
"protocol.js",
|
|
980
|
+
"permissions.js",
|
|
981
|
+
"icons/icon32.png",
|
|
982
|
+
"icons/icon64.png",
|
|
983
|
+
"icons/icon128.png",
|
|
984
|
+
];
|
|
985
|
+
function parsearguments(argumentslist) {
|
|
986
|
+
const options = {};
|
|
987
|
+
for (let index = 0; index < argumentslist.length; index += 1) {
|
|
988
|
+
const argument = argumentslist[index];
|
|
989
|
+
if (argument === "--version")
|
|
990
|
+
options.version = argumentslist[++index];
|
|
991
|
+
else if (argument === "--output")
|
|
992
|
+
options.output = argumentslist[++index];
|
|
993
|
+
else
|
|
994
|
+
throw new TypeError(`unsupported extension build argument: ${argument}`);
|
|
995
|
+
}
|
|
996
|
+
return options;
|
|
997
|
+
}
|
|
998
|
+
function validversion(version) {
|
|
999
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(version ?? ""));
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Extracts the consolidated section of one extension entry from the merged
|
|
1003
|
+
* browser.ts source. The grand merge folded the seven former extension
|
|
1004
|
+
* scripts into ordinal sections of this file; the build emits each section
|
|
1005
|
+
* under its stable browser `.js` filename exactly the way the former
|
|
1006
|
+
* per-file sources were emitted.
|
|
1007
|
+
*/
|
|
1008
|
+
async function extensionsectionsourcetext(entry) {
|
|
1009
|
+
const section = entry.replace(/\.js$/, "");
|
|
1010
|
+
const browserts = resolve(rootpath, "browser.ts");
|
|
1011
|
+
const text = await readFile(browserts, "utf8");
|
|
1012
|
+
const pattern = new RegExp(`\\/\\* Section \\d+: extension\\/${section}\\.ts[^\\n]*\\*\\/\\n\\/\\*[\\u2550 ]*\\*\\/\\n([\\s\\S]*?)\\n(?=\\/\\*[\\u2550 ]*\\*\\/\\n\\/\\* Section \\d+:|$)`);
|
|
1013
|
+
const match = text.match(pattern);
|
|
1014
|
+
if (!match)
|
|
1015
|
+
throw new TypeError(`extension section not found: ${section}`);
|
|
1016
|
+
return match[1];
|
|
1017
|
+
}
|
|
1018
|
+
/** Resolves a static interface asset of the extension from web/extension. */
|
|
1019
|
+
async function resolveasset(entry) {
|
|
1020
|
+
return resolve(extensionassets, entry);
|
|
1021
|
+
}
|
|
1022
|
+
/** Builds the extension into an isolated directory and returns its manifest. */
|
|
1023
|
+
export async function buildextension(options = {}) {
|
|
1024
|
+
const packagefile = JSON.parse(await readFile(resolve(rootpath, "package.json"), "utf8"));
|
|
1025
|
+
const version = String(options.version ?? packagefile.version);
|
|
1026
|
+
if (!validversion(version))
|
|
1027
|
+
throw new TypeError(`invalid extension version: ${version}`);
|
|
1028
|
+
const output = resolve(process.cwd(), options.output ?? "build/extension");
|
|
1029
|
+
await rm(output, { force: true, recursive: true });
|
|
1030
|
+
await mkdir(output, { recursive: true });
|
|
1031
|
+
const manifest = JSON.parse(await readFile(resolve(extensionassets, "manifest.json"), "utf8"));
|
|
1032
|
+
manifest.version = version;
|
|
1033
|
+
for (const entry of entries) {
|
|
1034
|
+
const destination = resolve(output, entry);
|
|
1035
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
1036
|
+
if (entry === "manifest.json")
|
|
1037
|
+
await writeFile(destination, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1038
|
+
else if (/\.js$/.test(entry)) {
|
|
1039
|
+
await writeFile(destination, `${await extensionsectionsourcetext(entry)}\n`);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
const source = await resolveasset(entry);
|
|
1043
|
+
await cp(source, destination);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return { output, manifest };
|
|
1047
|
+
}
|
|
1048
|
+
if (process.argv[1] === fileURLToPath(import.meta.url))
|
|
1049
|
+
buildextension(parsearguments(process.argv.slice(2)))
|
|
1050
|
+
.then(({ output }) => {
|
|
1051
|
+
console.log(`extension artifact: ${output}`);
|
|
1052
|
+
})
|
|
1053
|
+
.catch((error) => {
|
|
1054
|
+
console.error(error.message);
|
|
1055
|
+
process.exitCode = 1;
|
|
1056
|
+
});
|
|
1057
|
+
//# sourceMappingURL=browser.js.map
|