@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +349 -29
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +3 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +519 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +49 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +556 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +135 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +1075 -0
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +227 -0
- package/src/serve/worker/client/core.ts +384 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +223 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1124 -0
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +605 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge core — process-local state for forwarding MCP tool calls
|
|
3
|
+
* from external clients to the (one) connected sandbox peer in the
|
|
4
|
+
* browser. Transport-agnostic: the HTTP MCP server and the WS server
|
|
5
|
+
* both plug into this through `dispatch()` / `handleSandboxMessage()`
|
|
6
|
+
* / `registerSandboxPeer()`.
|
|
7
|
+
*
|
|
8
|
+
* Multi-tab is naive: last-connection-wins. New peer replaces the
|
|
9
|
+
* old, pending calls against the old peer reject with a clear error.
|
|
10
|
+
*
|
|
11
|
+
* Every tool call is forwarded over WS to the browser. The bridge does not
|
|
12
|
+
* execute data-plane tools itself; it acts as a relay with a tool-name
|
|
13
|
+
* allow-list pinned by the peer's `hello`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import type {
|
|
18
|
+
BridgeMessage,
|
|
19
|
+
HealthReport,
|
|
20
|
+
ToolCallResponse,
|
|
21
|
+
WorkerOpPayload,
|
|
22
|
+
WorkerSubPayload,
|
|
23
|
+
WorkerResFrame,
|
|
24
|
+
WorkerSnapFrame,
|
|
25
|
+
} from '../protocol.js';
|
|
26
|
+
import {
|
|
27
|
+
NO_SANDBOX_ERROR_MESSAGE,
|
|
28
|
+
NO_WORKER_RELAY_ERROR_MESSAGE,
|
|
29
|
+
WORKER_RELAY_CAPABILITY,
|
|
30
|
+
} from '../protocol.js';
|
|
31
|
+
|
|
32
|
+
/** Subset of `@inbrowser/agent`'s `ToolResult` shape the bridge emits. */
|
|
33
|
+
export interface BridgeToolResult {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
summary: string;
|
|
36
|
+
data?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Function the bridge calls to send a message to the current sandbox peer. */
|
|
40
|
+
export type SendToPeer = (msg: BridgeMessage) => void;
|
|
41
|
+
|
|
42
|
+
export interface BridgeOptions {
|
|
43
|
+
/** Sandbox label surfaced in /health and audit-log paths. */
|
|
44
|
+
project?: string;
|
|
45
|
+
/** Bridge version surfaced in /health + Hello messages. */
|
|
46
|
+
version: string;
|
|
47
|
+
/**
|
|
48
|
+
* Per-call request timeout in ms when forwarding to the browser.
|
|
49
|
+
* Defaults to 30s; the bridge rejects the MCP call with a clear
|
|
50
|
+
* "sandbox call timed out" error after this.
|
|
51
|
+
*/
|
|
52
|
+
callTimeoutMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Hook called whenever a tool finishes (success or failure).
|
|
55
|
+
* Use this for the audit log — write the entry to disk here.
|
|
56
|
+
* The bridge does not own audit-log persistence.
|
|
57
|
+
*/
|
|
58
|
+
onToolEvent?: (event: BridgeToolEvent) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BridgeToolEvent {
|
|
62
|
+
timestamp: string;
|
|
63
|
+
mode: 'sandbox';
|
|
64
|
+
project: string;
|
|
65
|
+
tool: string;
|
|
66
|
+
args: Record<string, unknown>;
|
|
67
|
+
result: BridgeToolResult | { ok: false; summary: string; error?: { code: string; message: string } };
|
|
68
|
+
durationMs: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Bridge {
|
|
72
|
+
readonly project: string;
|
|
73
|
+
readonly version: string;
|
|
74
|
+
readonly startedAt: string;
|
|
75
|
+
/** Stable per-process identity (see HealthReport.instanceId). */
|
|
76
|
+
readonly instanceId: string;
|
|
77
|
+
/**
|
|
78
|
+
* Record a tool event in the bridge's audit pipeline. In-process sandbox
|
|
79
|
+
* tools use this path; forwarded tools log inside dispatch().
|
|
80
|
+
*/
|
|
81
|
+
recordToolEvent(event: BridgeToolEvent): void;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register a browser-side peer. Returns a disconnect function the
|
|
85
|
+
* caller MUST invoke when the WS closes. Last-wins: a new
|
|
86
|
+
* registration disconnects the previous peer (its pending calls
|
|
87
|
+
* fail with a clear error).
|
|
88
|
+
*
|
|
89
|
+
* `capabilities` come from the peer's `hello` — the bridge only sends
|
|
90
|
+
* `worker-*` frames to a peer that declared `'worker-relay'`.
|
|
91
|
+
*
|
|
92
|
+
* `onReplaced` fires when a NEWER registration displaces this peer. The
|
|
93
|
+
* transport MUST use it to close the old socket: the browser side's
|
|
94
|
+
* close handler tears down its relayed worker subscriptions — without
|
|
95
|
+
* this, a replaced tab's SharedWorker listeners would live until the tab
|
|
96
|
+
* closed, streaming snaps the bridge drops as stale-generation forever.
|
|
97
|
+
*/
|
|
98
|
+
registerSandboxPeer(
|
|
99
|
+
send: SendToPeer,
|
|
100
|
+
tools: string[],
|
|
101
|
+
sandboxId: string,
|
|
102
|
+
capabilities?: string[],
|
|
103
|
+
onReplaced?: () => void,
|
|
104
|
+
): () => void;
|
|
105
|
+
|
|
106
|
+
/** True if a sandbox peer is currently registered. */
|
|
107
|
+
isSandboxConnected(): boolean;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generation counter of the CURRENT peer registration (0 = no peer has
|
|
111
|
+
* ever registered). The transport captures this right after registering
|
|
112
|
+
* and tags every inbound message with it, so a frame arriving on a
|
|
113
|
+
* REPLACED peer's socket (tab refresh mid-flight) can never resolve a new
|
|
114
|
+
* peer's pending call or deliver a stale subscription snapshot.
|
|
115
|
+
*/
|
|
116
|
+
peerGeneration(): number;
|
|
117
|
+
|
|
118
|
+
/** Tool names the bridge currently exposes to MCP. */
|
|
119
|
+
toolNames(): string[];
|
|
120
|
+
|
|
121
|
+
/** Dispatch a tool call to the connected sandbox peer. */
|
|
122
|
+
dispatch(name: string, args: Record<string, unknown>): Promise<BridgeToolResult>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Relay a generic worker op to the peer's SharedWorker. Resolves with the
|
|
126
|
+
* worker's `res.value`; rejects with an Error carrying `.code` on worker
|
|
127
|
+
* failure, timeout, no peer, or a peer without the worker relay.
|
|
128
|
+
*/
|
|
129
|
+
dispatchWorkerOp(op: WorkerOpPayload): Promise<unknown>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Register a worker subscription. Ownership lives HERE: the registry
|
|
133
|
+
* survives peer churn, and every registered sub is re-issued to a new
|
|
134
|
+
* peer on registration (RTDB value / auth-state subs re-deliver a fresh
|
|
135
|
+
* initial snapshot, so replay is cursor-free). `onSnap` receives every
|
|
136
|
+
* relayed snap value verbatim — including the worker host's
|
|
137
|
+
* `{ __error: { code, message } }` establishment-failure convention.
|
|
138
|
+
* Returns the unsubscribe function.
|
|
139
|
+
*/
|
|
140
|
+
subscribeWorker(sub: WorkerSubPayload, onSnap: (value: unknown) => void): () => void;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Handle a message from the sandbox peer (tool-result, pong, …).
|
|
144
|
+
* `generation` is the peer generation the transport captured at
|
|
145
|
+
* registration; when provided, `worker-res`/`worker-snap` frames from a
|
|
146
|
+
* stale generation are dropped.
|
|
147
|
+
*/
|
|
148
|
+
handleSandboxMessage(msg: BridgeMessage, generation?: number): void;
|
|
149
|
+
|
|
150
|
+
/** /health endpoint payload. */
|
|
151
|
+
health(): HealthReport;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface PendingCall {
|
|
155
|
+
id: string;
|
|
156
|
+
resolve: (result: BridgeToolResult) => void;
|
|
157
|
+
timer: ReturnType<typeof setTimeout>;
|
|
158
|
+
tool: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface ActivePeer {
|
|
162
|
+
send: SendToPeer;
|
|
163
|
+
tools: Set<string>;
|
|
164
|
+
sandboxId: string;
|
|
165
|
+
capabilities: Set<string>;
|
|
166
|
+
onReplaced?: () => void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface PendingWorkerOp {
|
|
170
|
+
resolve: (value: unknown) => void;
|
|
171
|
+
reject: (err: Error) => void;
|
|
172
|
+
timer: ReturnType<typeof setTimeout>;
|
|
173
|
+
method: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface WorkerSubEntry {
|
|
177
|
+
sub: WorkerSubPayload;
|
|
178
|
+
onSnap: (value: unknown) => void;
|
|
179
|
+
/**
|
|
180
|
+
* JSON of the last snap value delivered to `onSnap` — the re-issue dedup
|
|
181
|
+
* baseline. Lives on the bridge-side entry because this registry is the
|
|
182
|
+
* only state that SURVIVES peer churn (the browser tab and the worker
|
|
183
|
+
* listener are both rebuilt per peer).
|
|
184
|
+
*/
|
|
185
|
+
lastDeliveredJson?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Set when this sub was just (re-)issued to a newly registered peer and
|
|
188
|
+
* the next inbound snap is the fresh listener's INITIAL snapshot. When
|
|
189
|
+
* that snapshot is byte-identical to `lastDeliveredJson`, nothing changed
|
|
190
|
+
* while the peer churned and the frame is suppressed — real Firestore /
|
|
191
|
+
* RTDB listeners fire on CHANGE, not on transport reconnection. Live,
|
|
192
|
+
* two tabs fighting over last-wins registration (each reconnecting after
|
|
193
|
+
* ~500ms) re-issued every sub once per registration, so a consumer's
|
|
194
|
+
* doc listener fired ~1/sec with identical data forever. A snapshot that
|
|
195
|
+
* DIFFERS from the baseline (state changed while no peer was attached)
|
|
196
|
+
* still delivers. Scoped to the one-frame re-issue window so steady-state
|
|
197
|
+
* delivery is untouched.
|
|
198
|
+
*/
|
|
199
|
+
awaitingReissueSnap?: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Build the typed Error worker-op rejections carry. `denialContext` (spike
|
|
203
|
+
* gap 6) is re-attached when the wire error carried one, so the structured
|
|
204
|
+
* denial frame survives the bridge hop. */
|
|
205
|
+
function workerOpError(
|
|
206
|
+
code: string,
|
|
207
|
+
message: string,
|
|
208
|
+
denialContext?: unknown,
|
|
209
|
+
): Error & { code: string; denialContext?: unknown } {
|
|
210
|
+
const err = new Error(message) as Error & { code: string; denialContext?: unknown };
|
|
211
|
+
err.code = code;
|
|
212
|
+
if (denialContext !== undefined) err.denialContext = denialContext;
|
|
213
|
+
return err;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function createBridge(opts: BridgeOptions): Bridge {
|
|
217
|
+
const project = opts.project ?? 'sandbox';
|
|
218
|
+
const version = opts.version;
|
|
219
|
+
const callTimeoutMs = opts.callTimeoutMs ?? 30_000;
|
|
220
|
+
const startedAt = new Date().toISOString();
|
|
221
|
+
// Per-process identity (see HealthReport.instanceId): the discovery pointer
|
|
222
|
+
// records this so a proxy can verify it reached THIS server, not a different
|
|
223
|
+
// sandbox squatting the same port on the other loopback family.
|
|
224
|
+
const instanceId = randomUUID();
|
|
225
|
+
const onToolEvent = opts.onToolEvent;
|
|
226
|
+
|
|
227
|
+
let peer: ActivePeer | null = null;
|
|
228
|
+
const pending = new Map<string, PendingCall>();
|
|
229
|
+
// ── worker relay state ──
|
|
230
|
+
// Generation counter: bumped on every peer registration. Inbound frames
|
|
231
|
+
// tagged with an older generation are stale (a replaced tab's socket) and
|
|
232
|
+
// must not resolve ops / deliver snaps registered under the new peer.
|
|
233
|
+
let generation = 0;
|
|
234
|
+
const workerPending = new Map<string, PendingWorkerOp>();
|
|
235
|
+
// Subscription ownership lives on the bridge: entries survive peer churn
|
|
236
|
+
// and are re-issued to every newly registered relay-capable peer.
|
|
237
|
+
const workerSubs = new Map<string, WorkerSubEntry>();
|
|
238
|
+
|
|
239
|
+
function failAllPending(reason: string) {
|
|
240
|
+
for (const call of pending.values()) {
|
|
241
|
+
clearTimeout(call.timer);
|
|
242
|
+
call.resolve({
|
|
243
|
+
ok: false,
|
|
244
|
+
summary: reason,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
pending.clear();
|
|
248
|
+
for (const op of workerPending.values()) {
|
|
249
|
+
clearTimeout(op.timer);
|
|
250
|
+
op.reject(workerOpError('unavailable', reason));
|
|
251
|
+
}
|
|
252
|
+
workerPending.clear();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function peerHasRelay(): boolean {
|
|
256
|
+
return peer !== null && peer.capabilities.has(WORKER_RELAY_CAPABILITY);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function registerSandboxPeer(
|
|
260
|
+
send: SendToPeer,
|
|
261
|
+
tools: string[],
|
|
262
|
+
sandboxId: string,
|
|
263
|
+
capabilities: string[] = [],
|
|
264
|
+
onReplaced?: () => void,
|
|
265
|
+
): () => void {
|
|
266
|
+
if (peer) {
|
|
267
|
+
// Last-wins: kick the old peer and reject its pending calls.
|
|
268
|
+
failAllPending('sandbox peer replaced by a newer connection');
|
|
269
|
+
// Tear the old peer down (the transport closes its socket). The
|
|
270
|
+
// browser's close handler tears down its relayed worker
|
|
271
|
+
// subscriptions — otherwise the replaced tab's SharedWorker
|
|
272
|
+
// listeners would keep posting snaps this bridge drops as
|
|
273
|
+
// stale-generation until the tab closed.
|
|
274
|
+
try {
|
|
275
|
+
peer.onReplaced?.();
|
|
276
|
+
} catch {
|
|
277
|
+
// A failing transport hook must not block the new registration.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
generation += 1;
|
|
281
|
+
const myPeer: ActivePeer = {
|
|
282
|
+
send,
|
|
283
|
+
tools: new Set(tools),
|
|
284
|
+
sandboxId,
|
|
285
|
+
capabilities: new Set(capabilities),
|
|
286
|
+
onReplaced,
|
|
287
|
+
};
|
|
288
|
+
peer = myPeer;
|
|
289
|
+
// Re-issue every registered worker subscription to the new peer — the
|
|
290
|
+
// replay is cursor-free because each (re)subscribe delivers a fresh
|
|
291
|
+
// initial snapshot. That snapshot is a DUPLICATE whenever nothing
|
|
292
|
+
// changed while the peer churned, so `awaitingReissueSnap` arms the
|
|
293
|
+
// per-sub dedup (see WorkerSubEntry) — without it, tabs cycling through
|
|
294
|
+
// last-wins registration re-fire every consumer listener with
|
|
295
|
+
// byte-identical data on every registration.
|
|
296
|
+
if (myPeer.capabilities.has(WORKER_RELAY_CAPABILITY)) {
|
|
297
|
+
for (const [subId, entry] of workerSubs) {
|
|
298
|
+
entry.awaitingReissueSnap = true;
|
|
299
|
+
try {
|
|
300
|
+
myPeer.send({ type: 'worker-sub', subId, sub: entry.sub });
|
|
301
|
+
} catch {
|
|
302
|
+
// Socket failure surfaces via the transport's close handler.
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return () => {
|
|
307
|
+
if (peer === myPeer) {
|
|
308
|
+
failAllPending(NO_SANDBOX_ERROR_MESSAGE);
|
|
309
|
+
peer = null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isSandboxConnected(): boolean {
|
|
315
|
+
return peer !== null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function peerGeneration(): number {
|
|
319
|
+
return generation;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function toolNames(): string[] {
|
|
323
|
+
return peer ? Array.from(peer.tools).sort() : [];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function dispatch(
|
|
327
|
+
name: string,
|
|
328
|
+
args: Record<string, unknown>,
|
|
329
|
+
): Promise<BridgeToolResult> {
|
|
330
|
+
const startedAtMs = Date.now();
|
|
331
|
+
let result: BridgeToolResult;
|
|
332
|
+
try {
|
|
333
|
+
result = await dispatchSandbox(name, args);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
result = {
|
|
336
|
+
ok: false,
|
|
337
|
+
summary: err instanceof Error ? err.message : String(err),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (onToolEvent) {
|
|
341
|
+
try {
|
|
342
|
+
onToolEvent({
|
|
343
|
+
timestamp: new Date(startedAtMs).toISOString(),
|
|
344
|
+
mode: 'sandbox',
|
|
345
|
+
project,
|
|
346
|
+
tool: name,
|
|
347
|
+
args,
|
|
348
|
+
result,
|
|
349
|
+
durationMs: Date.now() - startedAtMs,
|
|
350
|
+
});
|
|
351
|
+
} catch {
|
|
352
|
+
// Audit-log failures must not break tool dispatch.
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function dispatchSandbox(
|
|
359
|
+
name: string,
|
|
360
|
+
args: Record<string, unknown>,
|
|
361
|
+
): Promise<BridgeToolResult> {
|
|
362
|
+
if (!peer) {
|
|
363
|
+
return Promise.resolve({ ok: false, summary: NO_SANDBOX_ERROR_MESSAGE });
|
|
364
|
+
}
|
|
365
|
+
if (!peer.tools.has(name)) {
|
|
366
|
+
return Promise.resolve({
|
|
367
|
+
ok: false,
|
|
368
|
+
summary: `tool '${name}' is not registered with the connected sandbox peer`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return new Promise<BridgeToolResult>((resolve) => {
|
|
372
|
+
const id = randomUUID();
|
|
373
|
+
const timer = setTimeout(() => {
|
|
374
|
+
if (pending.has(id)) {
|
|
375
|
+
pending.delete(id);
|
|
376
|
+
resolve({
|
|
377
|
+
ok: false,
|
|
378
|
+
summary: `sandbox call timed out after ${callTimeoutMs}ms (tool: ${name})`,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}, callTimeoutMs);
|
|
382
|
+
pending.set(id, { id, resolve, timer, tool: name });
|
|
383
|
+
try {
|
|
384
|
+
peer!.send({
|
|
385
|
+
type: 'tool-call',
|
|
386
|
+
id,
|
|
387
|
+
name,
|
|
388
|
+
args,
|
|
389
|
+
});
|
|
390
|
+
} catch (err) {
|
|
391
|
+
clearTimeout(timer);
|
|
392
|
+
pending.delete(id);
|
|
393
|
+
resolve({
|
|
394
|
+
ok: false,
|
|
395
|
+
summary: `failed to send tool call to sandbox: ${err instanceof Error ? err.message : String(err)}`,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function dispatchWorkerOp(op: WorkerOpPayload): Promise<unknown> {
|
|
402
|
+
if (!peer) {
|
|
403
|
+
return Promise.reject(workerOpError('unavailable', NO_SANDBOX_ERROR_MESSAGE));
|
|
404
|
+
}
|
|
405
|
+
if (!peerHasRelay()) {
|
|
406
|
+
return Promise.reject(workerOpError('unimplemented', NO_WORKER_RELAY_ERROR_MESSAGE));
|
|
407
|
+
}
|
|
408
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
409
|
+
const id = randomUUID();
|
|
410
|
+
const timer = setTimeout(() => {
|
|
411
|
+
if (workerPending.has(id)) {
|
|
412
|
+
workerPending.delete(id);
|
|
413
|
+
reject(
|
|
414
|
+
workerOpError(
|
|
415
|
+
'deadline-exceeded',
|
|
416
|
+
`sandbox worker op timed out after ${callTimeoutMs}ms (op: ${op.method})`,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}, callTimeoutMs);
|
|
421
|
+
workerPending.set(id, { resolve, reject, timer, method: op.method });
|
|
422
|
+
try {
|
|
423
|
+
peer!.send({ type: 'worker-op', id, op });
|
|
424
|
+
} catch (err) {
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
workerPending.delete(id);
|
|
427
|
+
reject(
|
|
428
|
+
workerOpError(
|
|
429
|
+
'unavailable',
|
|
430
|
+
`failed to send worker op to sandbox: ${err instanceof Error ? err.message : String(err)}`,
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function subscribeWorker(
|
|
438
|
+
sub: WorkerSubPayload,
|
|
439
|
+
onSnap: (value: unknown) => void,
|
|
440
|
+
): () => void {
|
|
441
|
+
const subId = randomUUID();
|
|
442
|
+
workerSubs.set(subId, { sub, onSnap });
|
|
443
|
+
if (peerHasRelay()) {
|
|
444
|
+
try {
|
|
445
|
+
peer!.send({ type: 'worker-sub', subId, sub });
|
|
446
|
+
} catch {
|
|
447
|
+
// Socket failure surfaces via the transport's close handler; the
|
|
448
|
+
// registry entry replays on the next peer registration.
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// No peer (or a relay-less peer) is NOT an error here: the registry is
|
|
452
|
+
// replayed on the next registration, mirroring RTDB's offline semantics.
|
|
453
|
+
return () => {
|
|
454
|
+
if (!workerSubs.delete(subId)) return;
|
|
455
|
+
if (peerHasRelay()) {
|
|
456
|
+
try {
|
|
457
|
+
peer!.send({ type: 'worker-unsub', subId });
|
|
458
|
+
} catch {}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function handleSandboxMessage(msg: BridgeMessage, msgGeneration?: number): void {
|
|
464
|
+
// Frames from a REPLACED peer's socket must not act on the current
|
|
465
|
+
// peer's state (subscriptions make stale delivery likely on tab
|
|
466
|
+
// refresh: the old tab's worker port keeps firing until its WS dies).
|
|
467
|
+
const stale = msgGeneration !== undefined && msgGeneration !== generation;
|
|
468
|
+
switch (msg.type) {
|
|
469
|
+
case 'worker-res': {
|
|
470
|
+
if (stale) return;
|
|
471
|
+
const res = msg as WorkerResFrame;
|
|
472
|
+
const op = workerPending.get(res.id);
|
|
473
|
+
if (!op) return; // late or unknown — drop silently
|
|
474
|
+
clearTimeout(op.timer);
|
|
475
|
+
workerPending.delete(res.id);
|
|
476
|
+
if (res.ok) {
|
|
477
|
+
op.resolve(res.value);
|
|
478
|
+
} else {
|
|
479
|
+
op.reject(
|
|
480
|
+
workerOpError(
|
|
481
|
+
res.error?.code ?? 'unknown',
|
|
482
|
+
res.error?.message ?? 'unknown sandbox error',
|
|
483
|
+
res.error?.denialContext,
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
case 'worker-snap': {
|
|
490
|
+
if (stale) return;
|
|
491
|
+
const snap = msg as WorkerSnapFrame;
|
|
492
|
+
const entry = workerSubs.get(snap.subId);
|
|
493
|
+
if (!entry) return; // unsubscribed or unknown — drop silently
|
|
494
|
+
// Re-issue dedup (see WorkerSubEntry.awaitingReissueSnap): the first
|
|
495
|
+
// snap after a peer (re)registration is the re-established listener's
|
|
496
|
+
// initial snapshot — suppress it when byte-identical to what the
|
|
497
|
+
// consumer already holds. Values are JSON-clean (they crossed the WS
|
|
498
|
+
// legs) and produced by the same serializer each time, so string
|
|
499
|
+
// equality is exact. One-frame window: the flag clears on the first
|
|
500
|
+
// snap either way, keeping steady-state delivery 1:1.
|
|
501
|
+
const json = JSON.stringify(snap.value);
|
|
502
|
+
const duplicate = entry.awaitingReissueSnap === true && json === entry.lastDeliveredJson;
|
|
503
|
+
entry.awaitingReissueSnap = false;
|
|
504
|
+
entry.lastDeliveredJson = json;
|
|
505
|
+
if (duplicate) return;
|
|
506
|
+
try {
|
|
507
|
+
entry.onSnap(snap.value);
|
|
508
|
+
} catch {
|
|
509
|
+
// Consumer callback failures must not break the relay loop.
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
default:
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
switch (msg.type) {
|
|
517
|
+
case 'tool-result': {
|
|
518
|
+
const response = msg as ToolCallResponse;
|
|
519
|
+
const call = pending.get(response.id);
|
|
520
|
+
if (!call) return; // late or unknown — drop silently
|
|
521
|
+
clearTimeout(call.timer);
|
|
522
|
+
pending.delete(response.id);
|
|
523
|
+
if (response.ok && response.result) {
|
|
524
|
+
call.resolve({
|
|
525
|
+
ok: response.result.ok,
|
|
526
|
+
summary: response.result.summary,
|
|
527
|
+
data: response.result.data,
|
|
528
|
+
});
|
|
529
|
+
} else {
|
|
530
|
+
const error = response.error;
|
|
531
|
+
call.resolve({
|
|
532
|
+
ok: false,
|
|
533
|
+
summary: error?.message ?? 'unknown sandbox error',
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case 'pong': {
|
|
539
|
+
// Keepalive ack; no-op.
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case 'ping': {
|
|
543
|
+
// Browser-initiated ping; echo back.
|
|
544
|
+
if (peer) {
|
|
545
|
+
peer.send({ type: 'pong', id: msg.id });
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
case 'hello': {
|
|
550
|
+
// Re-hello after reconnect would arrive here, but the
|
|
551
|
+
// registration path is owned by the transport layer (it sees
|
|
552
|
+
// the WS first). Ignore.
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
default:
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function health(): HealthReport {
|
|
561
|
+
return {
|
|
562
|
+
status: 'ok',
|
|
563
|
+
mode: 'sandbox',
|
|
564
|
+
project,
|
|
565
|
+
sandboxConnected: peer !== null,
|
|
566
|
+
version,
|
|
567
|
+
startedAt,
|
|
568
|
+
instanceId,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function recordToolEvent(event: BridgeToolEvent): void {
|
|
573
|
+
if (!onToolEvent) return;
|
|
574
|
+
try {
|
|
575
|
+
onToolEvent(event);
|
|
576
|
+
} catch {
|
|
577
|
+
// Audit-log failures must not break tool dispatch.
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
project,
|
|
583
|
+
version,
|
|
584
|
+
startedAt,
|
|
585
|
+
instanceId,
|
|
586
|
+
recordToolEvent,
|
|
587
|
+
registerSandboxPeer,
|
|
588
|
+
isSandboxConnected,
|
|
589
|
+
peerGeneration,
|
|
590
|
+
toolNames,
|
|
591
|
+
dispatch,
|
|
592
|
+
dispatchWorkerOp,
|
|
593
|
+
subscribeWorker,
|
|
594
|
+
handleSandboxMessage,
|
|
595
|
+
health,
|
|
596
|
+
};
|
|
597
|
+
}
|