synabun 2.1.0 → 2.2.1
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/CHANGELOG.md +139 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +504 -39
- package/neural-interface/i18n/pt-BR.json +504 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-brains/codex.js +6 -1
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +211 -50
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +40 -7
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +423 -23
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +165 -22
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +80 -6
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +15 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +46 -9
- package/neural-interface/public/shared/assistant/asst-render.js +112 -18
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
- package/neural-interface/public/shared/assistant/asst-scenes.js +279 -80
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +58 -4
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/loading-teardown.js +34 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +28 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +17 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +101 -68
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/storage-retry.js +47 -0
- package/neural-interface/public/shared/storage.js +25 -4
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +362 -35
- package/neural-interface/public/shared/synabun-mascot.js +27 -8
- package/neural-interface/public/shared/synabun-ticker.js +147 -12
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +374 -134
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-loading.js +5 -4
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +731 -466
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
// Auto-attach pauses new pages, OOPIFs and workers so
|
|
7
7
|
// their FIRST request is guarded too. Closing this client releases its sessions;
|
|
8
8
|
// it never closes a user's target or browser.
|
|
9
|
+
//
|
|
10
|
+
// Faults are per target first. A target is protected once its Fetch rules are in
|
|
11
|
+
// place; releasing it (Runtime.runIfWaitingForDebugger) is not part of that, and its
|
|
12
|
+
// reply comes from the target itself once it actually runs: Chromium holds a new
|
|
13
|
+
// service worker until EVERY auto-attached CDP client has released it, and does not
|
|
14
|
+
// answer a stopped worker's Runtime commands until it runs again. A slow or silent
|
|
15
|
+
// reply is therefore never a fault. A paused service/shared worker whose rules cannot
|
|
16
|
+
// be installed stays paused and is retried; pages, frames, dedicated workers and
|
|
17
|
+
// targets already running still fail closed for the whole context, and a faulted
|
|
18
|
+
// context re-arms with a fresh attachment (recoverBrowserNetworkGuard).
|
|
9
19
|
import { connectGuardCdp } from './browser-guard-cdp.js';
|
|
10
20
|
const guards = new WeakMap();
|
|
11
21
|
const endpoints = new WeakMap();
|
|
@@ -15,35 +25,68 @@ const AUTO = { autoAttach: true, waitForDebuggerOnStart: true, flatten: true, fi
|
|
|
15
25
|
const gone = error => /closed|detached|No session|No target|Target.*not found|Session with given id not found/i.test(error?.message || '');
|
|
16
26
|
const expiredRequest = error => /^Fetch\.(?:continueRequest|failRequest)$/.test(error?.method || '')
|
|
17
27
|
&& /Invalid InterceptionId|Invalid interception id/i.test(error.message);
|
|
28
|
+
// Workers whose own requests only their own Fetch rules intercept. Dedicated workers
|
|
29
|
+
// are intercepted by their owning page or frame instead.
|
|
30
|
+
const OWN_FETCH_WORKERS = new Set(['service_worker', 'shared_worker']);
|
|
31
|
+
const TIMING = {
|
|
32
|
+
quarantineRetryMs: [1000, 5000, 15000, 60000], // the last delay repeats while the worker exists
|
|
33
|
+
rearmBackoffMs: [2000, 5000, 15000, 30000, 60000], // after a failed re-arm; the last repeats
|
|
34
|
+
recoverTimeoutMs: 10000, // the longest a caller waits for one re-arm
|
|
35
|
+
};
|
|
18
36
|
export const NETWORK_GUARD_FAULT_EVENT = 'synabun-network-guard-fault';
|
|
37
|
+
export const NETWORK_GUARD_RECOVERED_EVENT = 'synabun-network-guard-recovered';
|
|
19
38
|
|
|
20
39
|
/** Register only externally owned Chromium contexts with an existing endpoint. */
|
|
21
|
-
export function registerBrowserNetworkEndpoint(context, endpoint, { connect = connectGuardCdp } = {}) {
|
|
22
|
-
endpoints.set(context, { endpoint, connect });
|
|
40
|
+
export function registerBrowserNetworkEndpoint(context, endpoint, { connect = connectGuardCdp, timing } = {}) {
|
|
41
|
+
endpoints.set(context, { endpoint, connect, timing: { ...TIMING, ...timing } });
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
export function usesNativeBrowserNetworkGuard(context) { return endpoints.has(context); }
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this._fault = error;
|
|
32
|
-
if (error) {
|
|
33
|
-
this.context.__browserNetworkGuardError = error.message;
|
|
34
|
-
this.context.emit?.(NETWORK_GUARD_FAULT_EVENT, error.message);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
constructor(context) {
|
|
38
|
-
this.context = context;
|
|
39
|
-
this.rules = new Map();
|
|
46
|
+
/** One flattened CDP connection and every session it holds. Re-arming replaces it whole. */
|
|
47
|
+
class GuardLink {
|
|
48
|
+
constructor(guard) {
|
|
49
|
+
this.guard = guard;
|
|
40
50
|
this.children = new Map();
|
|
41
51
|
this.installing = new Set();
|
|
52
|
+
this.timers = new Set();
|
|
42
53
|
this.fault = null;
|
|
43
|
-
this.
|
|
54
|
+
this.closed = false;
|
|
44
55
|
}
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
/** A fault of this link. It is the context's fault only while this link is the current one. */
|
|
57
|
+
failed(error) {
|
|
58
|
+
if (this.closed) return;
|
|
59
|
+
this.fault ||= error;
|
|
60
|
+
if (this.guard.link === this) this.guard.fault = error;
|
|
61
|
+
}
|
|
62
|
+
later(delay, work) {
|
|
63
|
+
if (this.closed) return;
|
|
64
|
+
const timer = setTimeout(() => { this.timers.delete(timer); if (!this.closed) work(); }, delay);
|
|
65
|
+
timer.unref?.();
|
|
66
|
+
this.timers.add(timer);
|
|
67
|
+
}
|
|
68
|
+
async open() {
|
|
69
|
+
const { endpoint, connect } = endpoints.get(this.guard.context);
|
|
70
|
+
this.connecting = connect(endpoint);
|
|
71
|
+
this.root = await this.connecting;
|
|
72
|
+
if (this.closed) { await this.root.detach(); throw new Error('Network guard disposed during setup'); }
|
|
73
|
+
this.root.on('event', event => this.dispatch(event.sessionId ? this.children.get(event.sessionId) : this.root, event.method, event.params));
|
|
74
|
+
this.root.on('close', () => {
|
|
75
|
+
if (this.closed) return;
|
|
76
|
+
this.failed(new Error('Network guard CDP disconnected; reconnect the browser session'));
|
|
77
|
+
// Cancel pending automation and refuse subsequent actions until a fresh
|
|
78
|
+
// protected attachment exists. Never reinstate context.route on MoreLogin:
|
|
79
|
+
// it would mutate unrelated native traffic and cache even during teardown.
|
|
80
|
+
console.error('[browser-network-guard] CDP disconnected; browser automation refused until the guard re-arms');
|
|
81
|
+
});
|
|
82
|
+
// A page-target attachment can arrive after a popup's initial navigation
|
|
83
|
+
// has started (another CDP client may resume it). The browser Fetch domain
|
|
84
|
+
// closes that gap for selective Document requests only. Attribute each to
|
|
85
|
+
// this profile before deciding; never apply rules to a foreign context.
|
|
86
|
+
await this.configure(this.root);
|
|
87
|
+
await this.root.send('Target.setAutoAttach', AUTO);
|
|
88
|
+
while (this.installing.size) await Promise.all([...this.installing]);
|
|
89
|
+
if (this.fault) throw this.fault;
|
|
47
90
|
}
|
|
48
91
|
async configure(child) {
|
|
49
92
|
// Dedicated workers have no Fetch domain in Chromium. Their requests are
|
|
@@ -51,68 +94,89 @@ class ContextGuard {
|
|
|
51
94
|
// Playwright's CRNetworkManager workerFrame path. Never resume a worker
|
|
52
95
|
// until that parent's current selective guard is installed.
|
|
53
96
|
if (child.target?.type === 'worker') {
|
|
54
|
-
if (!child.parent?.guarded || !child.parent.configuration) {
|
|
97
|
+
if (!child.parent?.guarded || child.parent.quarantined || !child.parent.configuration) {
|
|
55
98
|
throw new Error(`Network guard worker ${child.target.targetId} has no protected network parent`);
|
|
56
99
|
}
|
|
57
100
|
await child.parent.configuration;
|
|
58
101
|
return;
|
|
59
102
|
}
|
|
60
|
-
const patterns = this.patterns().map(pattern => child === this.root ? { ...pattern, resourceType: 'Document' } : pattern);
|
|
103
|
+
const patterns = this.guard.patterns().map(pattern => child === this.root ? { ...pattern, resourceType: 'Document' } : pattern);
|
|
61
104
|
await child.send(patterns.length ? 'Fetch.enable' : 'Fetch.disable', patterns.length ? { patterns } : {});
|
|
62
105
|
}
|
|
106
|
+
/** Rules in place for the target and for what it starts next. */
|
|
107
|
+
async protect(child) {
|
|
108
|
+
await (child.configuration = this.configure(child));
|
|
109
|
+
if (this.closed) return;
|
|
110
|
+
await child.send('Target.setAutoAttach', AUTO);
|
|
111
|
+
child.protected = true;
|
|
112
|
+
}
|
|
113
|
+
/** Release a target this client holds paused. Never awaited and never a fault: the
|
|
114
|
+
* reply comes from the target once it actually runs, which another CDP client of the
|
|
115
|
+
* profile, or a stopped service worker, can delay indefinitely. The target's rules are
|
|
116
|
+
* already in place, so its requests are guarded whenever it runs. */
|
|
117
|
+
resume(child) {
|
|
118
|
+
child.send('Runtime.runIfWaitingForDebugger').catch(error => {
|
|
119
|
+
if (this.closed || gone(error)) return;
|
|
120
|
+
console.warn(`[browser-network-guard] ${error.message}; the target keeps its rules and runs when Chromium releases it`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Whether a failed command's target is gone, which is no fault. A reply can be lost
|
|
124
|
+
* while its target is destroyed, so a timeout asks the browser before deciding. */
|
|
125
|
+
async vanished(child, error) {
|
|
126
|
+
if (this.closed || gone(error) || this.children.get(child.id) !== child) return true;
|
|
127
|
+
try {
|
|
128
|
+
const { targetInfo } = await this.root.send('Target.getTargetInfo', { targetId: child.target.targetId });
|
|
129
|
+
return !targetInfo;
|
|
130
|
+
} catch (probe) {
|
|
131
|
+
return /No target with given id/i.test(probe.protocolMessage || probe.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** A paused service/shared worker whose rules could not be installed. Nothing else
|
|
135
|
+
* intercepts its requests, so it must not run unprotected: keep holding it paused
|
|
136
|
+
* (no other target is affected), retry, and release it once its rules are in place. */
|
|
137
|
+
quarantine(child, error, attempt = 0) {
|
|
138
|
+
child.quarantined = error;
|
|
139
|
+
const delays = this.guard.timing.quarantineRetryMs;
|
|
140
|
+
const delay = delays[Math.min(attempt, delays.length - 1)];
|
|
141
|
+
if (!attempt) console.error(`[browser-network-guard] ${error.message}; this worker stays paused, retrying its protection`);
|
|
142
|
+
this.later(delay, async () => {
|
|
143
|
+
if (this.children.get(child.id) !== child) return;
|
|
144
|
+
try {
|
|
145
|
+
await this.protect(child);
|
|
146
|
+
} catch (retryError) {
|
|
147
|
+
if (await this.vanished(child, retryError)) return;
|
|
148
|
+
this.quarantine(child, retryError, attempt + 1);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (this.closed) return;
|
|
152
|
+
child.quarantined = null;
|
|
153
|
+
console.warn(`[browser-network-guard] worker ${child.target.targetId} protected after ${attempt + 1} retr${attempt ? 'ies' : 'y'}; releasing it`);
|
|
154
|
+
this.resume(child);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
63
157
|
async ownsDocument(frameId) {
|
|
64
158
|
try {
|
|
65
159
|
const { targetInfo } = await this.root.send('Target.getTargetInfo', { targetId: frameId });
|
|
66
|
-
return targetInfo.browserContextId === this.contextId;
|
|
160
|
+
return targetInfo.browserContextId === this.guard.contextId;
|
|
67
161
|
} catch (error) {
|
|
68
162
|
// Same-process subframes have frame IDs, but no separate Target. Find
|
|
69
163
|
// their owning protected page; unrelated incognito frames stay native.
|
|
70
164
|
if (!/No target with given id found/i.test(error.message)) throw error;
|
|
71
165
|
const contains = tree => tree.frame.id === frameId || tree.childFrames?.some(contains);
|
|
166
|
+
let uncertain = false;
|
|
72
167
|
for (const child of this.children.values()) {
|
|
73
168
|
if (!child.guarded || !['page', 'iframe'].includes(child.target.type)) continue;
|
|
74
169
|
try {
|
|
75
170
|
if (contains((await child.send('Page.getFrameTree')).frameTree)) return true;
|
|
76
|
-
} catch (
|
|
171
|
+
} catch (treeError) { if (!gone(treeError)) uncertain = true; }
|
|
77
172
|
}
|
|
78
|
-
|
|
173
|
+
// A protected page that cannot list its frames may hold this one. Decide it as
|
|
174
|
+
// the profile's (the stricter answer) instead of faulting every tab.
|
|
175
|
+
return uncertain;
|
|
79
176
|
}
|
|
80
177
|
}
|
|
81
|
-
async start() {
|
|
82
|
-
this.onContextClose = () => { void this.dispose(); };
|
|
83
|
-
this.onBrowserDisconnect = () => { void this.dispose(); };
|
|
84
|
-
this.context.once('close', this.onContextClose);
|
|
85
|
-
this.context.browser().once('disconnected', this.onBrowserDisconnect);
|
|
86
|
-
const page = this.context.pages().find(page => !page.isClosed());
|
|
87
|
-
if (!page) throw new Error('Network guard requires the attachment tab');
|
|
88
|
-
const probe = await this.context.newCDPSession(page);
|
|
89
|
-
try { this.contextId = (await probe.send('Target.getTargetInfo')).targetInfo.browserContextId; }
|
|
90
|
-
finally { await probe.detach(); }
|
|
91
|
-
if (this.disposed) throw new Error('Network guard disposed during setup');
|
|
92
|
-
const { endpoint, connect } = endpoints.get(this.context);
|
|
93
|
-
this.connecting = connect(endpoint);
|
|
94
|
-
this.root = await this.connecting;
|
|
95
|
-
if (this.disposed) { await this.root.detach(); throw new Error('Network guard disposed during setup'); }
|
|
96
|
-
this.root.on('event', event => this.dispatch(event.sessionId ? this.children.get(event.sessionId) : this.root, event.method, event.params));
|
|
97
|
-
this.root.on('close', () => {
|
|
98
|
-
if (this.disposed) return;
|
|
99
|
-
this.fault = new Error('Network guard CDP disconnected; reconnect the browser session');
|
|
100
|
-
// Cancel pending automation and refuse subsequent actions until a fresh
|
|
101
|
-
// protected attachment exists. Never reinstate context.route on MoreLogin:
|
|
102
|
-
// it would mutate unrelated native traffic and cache even during teardown.
|
|
103
|
-
console.error('[browser-network-guard] CDP disconnected; browser automation disabled until reconnection');
|
|
104
|
-
});
|
|
105
|
-
// A page-target attachment can arrive after a popup's initial navigation
|
|
106
|
-
// has started (another CDP client may resume it). The browser Fetch domain
|
|
107
|
-
// closes that gap for selective Document requests only. Attribute each to
|
|
108
|
-
// this profile before deciding; never apply rules to a foreign context.
|
|
109
|
-
await this.configure(this.root);
|
|
110
|
-
await this.root.send('Target.setAutoAttach', AUTO);
|
|
111
|
-
while (this.installing.size) await Promise.all([...this.installing]);
|
|
112
|
-
if (this.fault) throw this.fault;
|
|
113
|
-
}
|
|
114
178
|
dispatch(parent, method, event) {
|
|
115
|
-
if (this.
|
|
179
|
+
if (this.closed) return;
|
|
116
180
|
if (method === 'Target.attachedToTarget') {
|
|
117
181
|
const child = {
|
|
118
182
|
id: event.sessionId, parent, target: event.targetInfo,
|
|
@@ -130,9 +194,9 @@ class ContextGuard {
|
|
|
130
194
|
this.children.set(event.sessionId, child);
|
|
131
195
|
const installing = this.attach(child, event).catch(error => {
|
|
132
196
|
if (!gone(error)) {
|
|
133
|
-
this.
|
|
197
|
+
this.failed(error);
|
|
134
198
|
// Do not resume an unguarded new target. Existing user tabs are never
|
|
135
|
-
// closed.
|
|
199
|
+
// closed. Tool operations fail until the guard re-arms.
|
|
136
200
|
console.error('[browser-network-guard] target protection failed:', error.message);
|
|
137
201
|
}
|
|
138
202
|
}).finally(() => this.installing.delete(installing));
|
|
@@ -143,35 +207,51 @@ class ContextGuard {
|
|
|
143
207
|
} else if (method === 'Fetch.requestPaused') {
|
|
144
208
|
if (!parent) return; // event queued before this target's detach arrived
|
|
145
209
|
void this.request(parent, event).catch(error => {
|
|
146
|
-
if (!gone(error)) { this.
|
|
210
|
+
if (!gone(error)) { this.failed(error); console.error('[browser-network-guard] request refused after guard failure:', error.message); }
|
|
147
211
|
});
|
|
212
|
+
} else if (method === 'Inspector.targetReloadedAfterCrash') {
|
|
213
|
+
// A stopped service worker that runs again keeps this session and its rules.
|
|
214
|
+
// Release it as Playwright does, so this client never holds it.
|
|
215
|
+
if (parent?.protected && !parent.quarantined) this.resume(parent);
|
|
148
216
|
}
|
|
149
217
|
}
|
|
150
218
|
async attach(child, event) {
|
|
151
|
-
if (this.
|
|
219
|
+
if (this.closed) return;
|
|
220
|
+
const waiting = !!event.waitingForDebugger;
|
|
152
221
|
const inherited = !event.targetInfo.browserContextId && child.parent?.guarded;
|
|
153
|
-
if (event.targetInfo.browserContextId !== this.contextId && !inherited) {
|
|
154
|
-
// A browser-level CDP connection also sees other incognito contexts.
|
|
155
|
-
|
|
156
|
-
|
|
222
|
+
if (event.targetInfo.browserContextId !== this.guard.contextId && !inherited) {
|
|
223
|
+
// A browser-level CDP connection also sees other incognito contexts. Our
|
|
224
|
+
// session there intercepts nothing: releasing it is never this profile's fault.
|
|
225
|
+
if (waiting) this.resume(child);
|
|
226
|
+
await child.parent.send('Target.detachFromTarget', { sessionId: child.id }).catch(error => {
|
|
227
|
+
if (!gone(error)) console.warn('[browser-network-guard] could not release a foreign target:', error.message);
|
|
228
|
+
});
|
|
157
229
|
return;
|
|
158
230
|
}
|
|
159
231
|
child.guarded = true;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
232
|
+
try {
|
|
233
|
+
await this.protect(child);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (await this.vanished(child, error)) return;
|
|
236
|
+
if (waiting && OWN_FETCH_WORKERS.has(child.target.type)) { this.quarantine(child, error); return; }
|
|
237
|
+
// Pages, frames, dedicated workers and anything already running fail closed.
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
if (this.closed) return;
|
|
241
|
+
// Only a target that waits for us is released; a running or stopped one is not.
|
|
242
|
+
if (waiting) this.resume(child);
|
|
164
243
|
}
|
|
165
244
|
async request(child, event) {
|
|
166
245
|
const { requestId, request } = event;
|
|
167
246
|
try {
|
|
168
|
-
|
|
247
|
+
const fault = this.fault || this.guard.fault;
|
|
248
|
+
if (fault) throw fault;
|
|
169
249
|
if (child === this.root && !await this.ownsDocument(event.frameId)) {
|
|
170
250
|
await child.send('Fetch.continueRequest', { requestId });
|
|
171
251
|
return;
|
|
172
252
|
}
|
|
173
253
|
let headers;
|
|
174
|
-
for (const rule of this.rules.values()) {
|
|
254
|
+
for (const rule of this.guard.rules.values()) {
|
|
175
255
|
const decision = rule.decide(request);
|
|
176
256
|
if (decision?.abort) {
|
|
177
257
|
await child.send('Fetch.failRequest', { requestId, errorReason: 'BlockedByClient' });
|
|
@@ -195,36 +275,148 @@ class ContextGuard {
|
|
|
195
275
|
throw error;
|
|
196
276
|
}
|
|
197
277
|
}
|
|
278
|
+
/** Current rules onto every protected target of this link. */
|
|
279
|
+
async reconfigure() {
|
|
280
|
+
await this.configure(this.root);
|
|
281
|
+
// A quarantined worker takes the current rules on its next retry.
|
|
282
|
+
await Promise.all([...this.children.values()].filter(child => child.guarded && !child.quarantined).map(async child => {
|
|
283
|
+
try { await (child.configuration = this.configure(child)); }
|
|
284
|
+
catch (error) { if (!await this.vanished(child, error)) throw error; }
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
close() {
|
|
288
|
+
if (this.closing) return this.closing;
|
|
289
|
+
this.closed = true;
|
|
290
|
+
for (const timer of this.timers) clearTimeout(timer);
|
|
291
|
+
this.timers.clear();
|
|
292
|
+
// Detaching our browser CDP session also detaches its child sessions and
|
|
293
|
+
// releases paused targets. No Target.closeTarget or Browser.close.
|
|
294
|
+
this.closing = (async () => {
|
|
295
|
+
// Closing may race a handshake. Await and detach that late connection
|
|
296
|
+
// as well; returning early would briefly leave a new client attached.
|
|
297
|
+
if (!this.root && this.connecting) this.root = await this.connecting.catch(() => null);
|
|
298
|
+
await this.root?.detach().catch(() => {});
|
|
299
|
+
for (const child of this.children.values()) child.close();
|
|
300
|
+
this.children.clear();
|
|
301
|
+
})();
|
|
302
|
+
return this.closing;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
class ContextGuard {
|
|
307
|
+
get fault() { return this._fault; }
|
|
308
|
+
set fault(error) {
|
|
309
|
+
if (this.disposed) return;
|
|
310
|
+
this._fault = error;
|
|
311
|
+
if (error) {
|
|
312
|
+
this.context.__browserNetworkGuardError = error.message;
|
|
313
|
+
this.context.emit?.(NETWORK_GUARD_FAULT_EVENT, error.message);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
constructor(context) {
|
|
317
|
+
this.context = context;
|
|
318
|
+
this.timing = endpoints.get(context)?.timing || TIMING;
|
|
319
|
+
this.rules = new Map();
|
|
320
|
+
this.fault = null;
|
|
321
|
+
this.queue = Promise.resolve();
|
|
322
|
+
this.rearmFailures = 0;
|
|
323
|
+
this.rearmAt = 0;
|
|
324
|
+
}
|
|
325
|
+
patterns() {
|
|
326
|
+
return [...new Set([...this.rules.values()].flatMap(rule => rule.patterns))].map(urlPattern => ({ urlPattern, requestStage: 'Request' }));
|
|
327
|
+
}
|
|
328
|
+
async start() {
|
|
329
|
+
this.onContextClose = () => { void this.dispose(); };
|
|
330
|
+
this.onBrowserDisconnect = () => { void this.dispose(); };
|
|
331
|
+
this.context.once('close', this.onContextClose);
|
|
332
|
+
this.context.browser().once('disconnected', this.onBrowserDisconnect);
|
|
333
|
+
const page = this.context.pages().find(page => !page.isClosed());
|
|
334
|
+
if (!page) throw new Error('Network guard requires the attachment tab');
|
|
335
|
+
const probe = await this.context.newCDPSession(page);
|
|
336
|
+
try { this.contextId = (await probe.send('Target.getTargetInfo')).targetInfo.browserContextId; }
|
|
337
|
+
finally { await probe.detach(); }
|
|
338
|
+
if (this.disposed) throw new Error('Network guard disposed during setup');
|
|
339
|
+
this.link = new GuardLink(this);
|
|
340
|
+
await this.link.open();
|
|
341
|
+
}
|
|
198
342
|
async update(key, rule) {
|
|
199
343
|
if (this.disposed) throw new Error('Network guard disposed');
|
|
200
344
|
if (this.fault) throw this.fault;
|
|
201
345
|
if (rule) this.rules.set(key, rule); else this.rules.delete(key);
|
|
202
346
|
if (!this.ready) this.ready = this.start().catch(async error => { await this.dispose(); throw error; });
|
|
203
347
|
await this.ready;
|
|
204
|
-
await this.
|
|
205
|
-
|
|
206
|
-
|
|
348
|
+
await this.link.reconfigure();
|
|
349
|
+
}
|
|
350
|
+
/** Re-arm a faulted guard; resolves true once the context is protected again. One
|
|
351
|
+
* attempt at a time, in order with rule updates, and none during the backoff that
|
|
352
|
+
* follows a failed one. */
|
|
353
|
+
rearm() {
|
|
354
|
+
if (this.disposed || !this.link) return Promise.resolve(false);
|
|
355
|
+
if (!this.fault) return Promise.resolve(true);
|
|
356
|
+
if (this.rearming) return this.rearming;
|
|
357
|
+
if (Date.now() < this.rearmAt) return Promise.resolve(false);
|
|
358
|
+
const attempt = this.queue.then(() => this.replaceLink());
|
|
359
|
+
this.queue = attempt.catch(() => {});
|
|
360
|
+
this.rearming = attempt.catch(() => false).finally(() => { this.rearming = null; });
|
|
361
|
+
return this.rearming;
|
|
362
|
+
}
|
|
363
|
+
async replaceLink() {
|
|
364
|
+
if (this.disposed) return false;
|
|
365
|
+
if (!this.fault) return true;
|
|
366
|
+
// The faulted link stays attached (failing its own paused requests closed) until
|
|
367
|
+
// the fresh one has every current target protected, so no target is ever left
|
|
368
|
+
// without an attachment holding its rules.
|
|
369
|
+
const fresh = this.pendingLink = new GuardLink(this);
|
|
370
|
+
try {
|
|
371
|
+
await fresh.open();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
await fresh.close();
|
|
374
|
+
const delays = this.timing.rearmBackoffMs;
|
|
375
|
+
this.rearmAt = Date.now() + delays[Math.min(this.rearmFailures++, delays.length - 1)];
|
|
376
|
+
console.error('[browser-network-guard] re-arm failed; actions stay refused:', error.message);
|
|
377
|
+
return false;
|
|
378
|
+
} finally { this.pendingLink = null; }
|
|
379
|
+
if (this.disposed) { await fresh.close(); return false; }
|
|
380
|
+
const old = this.link;
|
|
381
|
+
this.link = fresh;
|
|
382
|
+
this.rearmFailures = 0;
|
|
383
|
+
this.rearmAt = 0;
|
|
384
|
+
this._fault = null;
|
|
385
|
+
delete this.context.__browserNetworkGuardError;
|
|
386
|
+
this.context.emit?.(NETWORK_GUARD_RECOVERED_EVENT);
|
|
387
|
+
console.warn('[browser-network-guard] re-armed with a fresh protected attachment');
|
|
388
|
+
await old.close();
|
|
389
|
+
return true;
|
|
207
390
|
}
|
|
208
391
|
dispose() {
|
|
209
392
|
if (this.disposal) return this.disposal;
|
|
210
393
|
this.disposed = true;
|
|
211
394
|
if (this.onContextClose) this.context.removeListener('close', this.onContextClose);
|
|
212
395
|
if (this.onBrowserDisconnect) this.context.browser().removeListener('disconnected', this.onBrowserDisconnect);
|
|
213
|
-
// Detaching our browser CDP session also detaches its child sessions and
|
|
214
|
-
// releases paused targets. No Target.closeTarget or Browser.close.
|
|
215
396
|
this.disposal = (async () => {
|
|
216
|
-
|
|
217
|
-
// as well; returning early would briefly leave a new client attached.
|
|
218
|
-
if (!this.root && this.connecting) this.root = await this.connecting.catch(() => null);
|
|
219
|
-
await this.root?.detach().catch(() => {});
|
|
220
|
-
for (const child of this.children.values()) child.close();
|
|
221
|
-
this.children.clear();
|
|
397
|
+
await Promise.all([this.link?.close(), this.pendingLink?.close()]);
|
|
222
398
|
if (guards.get(this.context) === this) guards.delete(this.context);
|
|
223
399
|
})();
|
|
224
400
|
return this.disposal;
|
|
225
401
|
}
|
|
226
402
|
}
|
|
227
403
|
|
|
404
|
+
/** Re-arm a faulted native guard: a fresh protected attachment replaces the broken one,
|
|
405
|
+
* so the context needs no server restart. Resolves true when the context is protected
|
|
406
|
+
* (again), false while its actions stay refused. Never rejects. */
|
|
407
|
+
export async function recoverBrowserNetworkGuard(context, { timeoutMs } = {}) {
|
|
408
|
+
const guard = guards.get(context);
|
|
409
|
+
if (!guard) return !context?.__browserNetworkGuardError;
|
|
410
|
+
let timer;
|
|
411
|
+
try {
|
|
412
|
+
return await Promise.race([
|
|
413
|
+
guard.rearm(),
|
|
414
|
+
new Promise(resolve => { timer = setTimeout(() => resolve(false), timeoutMs ?? guard.timing.recoverTimeoutMs); timer.unref?.(); }),
|
|
415
|
+
]);
|
|
416
|
+
} catch { return false; }
|
|
417
|
+
finally { clearTimeout(timer); }
|
|
418
|
+
}
|
|
419
|
+
|
|
228
420
|
/** Explicitly release only this context's safety transport, including failed setup. */
|
|
229
421
|
export async function disposeBrowserNetworkGuard(context) {
|
|
230
422
|
await guards.get(context)?.dispose();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SynaBun — Browser tool policy: every page through the SynaBun browser
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
//
|
|
5
|
-
// The Assistant's brain (Claude, Codex, OpenCode) and the workers it
|
|
5
|
+
// The Assistant's brain (Claude, Codex, OpenCode, Antigravity) and the workers it
|
|
6
6
|
// dispatches look at pages — public or localhost — only through SynaBun's
|
|
7
7
|
// browser tools (browser_*), which open the browser configured in SynaBun
|
|
8
8
|
// (MoreLogin). They never start a browser of their own, and when the SynaBun
|
|
@@ -48,13 +48,14 @@ const MAX_DEPTH = 4;
|
|
|
48
48
|
|
|
49
49
|
// ── the rule text ───────────────────────────────────────────────────────────
|
|
50
50
|
|
|
51
|
-
const HOSTS = { claude: 'claude', 'claude-code': 'claude', codex: 'codex', opencode: 'opencode' };
|
|
51
|
+
const HOSTS = { claude: 'claude', 'claude-code': 'claude', codex: 'codex', opencode: 'opencode', antigravity: 'antigravity' };
|
|
52
52
|
const hostKey = (host) => HOSTS[String(host || '').toLowerCase()] || null;
|
|
53
53
|
// Each host's own web search / fetch (the rule names the host's; unknown: all of them).
|
|
54
54
|
const WEB_TOOL_WORDS = {
|
|
55
55
|
claude: 'WebSearch / WebFetch',
|
|
56
56
|
codex: 'Codex\'s web_search',
|
|
57
57
|
opencode: 'webfetch / websearch',
|
|
58
|
+
antigravity: 'Antigravity\'s own web search, URL reading or browser',
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -78,7 +79,7 @@ const TEST_RUN_RULE = 'Automated tests run through their test runner (npm test,
|
|
|
78
79
|
/**
|
|
79
80
|
* The browser rule, as bullet lines: the brain's persona (its host's tool
|
|
80
81
|
* names in `tools`), the worker prompts of browser runs. `host`: claude /
|
|
81
|
-
* codex / opencode (names that host's own web tools), else all of them.
|
|
82
|
+
* codex / opencode / antigravity (names that host's own web tools), else all of them.
|
|
82
83
|
*/
|
|
83
84
|
export function browserPolicyText({ tools = '', host = null } = {}) {
|
|
84
85
|
const t = browserToolNames(tools);
|
|
@@ -114,9 +115,11 @@ const OPENCODE_BROWSER_TOOL = new RegExp(`^([a-z0-9-]*?(?:${BROWSER_SERVER_WORD}
|
|
|
114
115
|
const PAGE_TOOL = /^(?:browser_.+|navigate(?:_(?:page|to|to_url|url|back|forward))?|go_?to(?:_(?:url|page))?|go_(?:back|forward)|open_(?:url|page|link|website|webpage|browser|in_browser)|new_page|launch_browser|(?:take|capture)_(?:page_|full_page_|web_?page_|website_)?screenshot|(?:page|full_page|web_?page|website)_screenshot|screenshot(?:_(?:page|url|website|webpage))?)$/;
|
|
115
116
|
const snakeName = (text) => String(text || '').replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[-.\s]+/g, '_').toLowerCase();
|
|
116
117
|
// Web search / fetch outside the SynaBun browser: Claude's, OpenCode's, Codex's.
|
|
117
|
-
const WEB_TOOLS = new Set(['websearch', 'webfetch', 'web_search', 'web_fetch', 'web_search_preview']);
|
|
118
|
+
const WEB_TOOLS = new Set(['websearch', 'webfetch', 'web_search', 'web_fetch', 'web_search_preview', 'search_web', 'read_url_content', 'execute_url']);
|
|
119
|
+
// Antigravity's own browser and browser subagent (its tools' own names, lib/antigravity/tool-call.js).
|
|
120
|
+
const ANTIGRAVITY_BROWSER_TOOLS = new Set(['open_browser_url', 'read_browser_page', 'browser_subagent', 'list_browser_pages', 'capture_browser_screenshot', 'capture_browser_console_logs', 'execute_browser_javascript', 'click_browser_pixel']);
|
|
118
121
|
// Shell tools: Claude's Bash / PowerShell, Codex's shell family, OpenCode's bash.
|
|
119
|
-
const SHELL_TOOLS = new Set(['bash', 'powershell', 'shell', 'shell_command', 'exec_command', 'local_shell', 'container.exec', 'unified_exec']);
|
|
122
|
+
const SHELL_TOOLS = new Set(['bash', 'powershell', 'shell', 'shell_command', 'exec_command', 'local_shell', 'container.exec', 'unified_exec', 'run_command']);
|
|
120
123
|
|
|
121
124
|
/**
|
|
122
125
|
* What another MCP server's tool does to a browser (for the refusal), else null:
|
|
@@ -983,8 +986,8 @@ function argvDenial(argv, ctx) {
|
|
|
983
986
|
}
|
|
984
987
|
|
|
985
988
|
// Where each host puts a shell tool's command and its working directory.
|
|
986
|
-
const COMMAND_KEYS = ['command', 'cmd', 'commands', 'script'];
|
|
987
|
-
const WORKDIR_KEYS = ['workdir', 'cwd', 'working_directory', 'workingDirectory', 'dir'];
|
|
989
|
+
const COMMAND_KEYS = ['command', 'cmd', 'commands', 'script', 'CommandLine'];
|
|
990
|
+
const WORKDIR_KEYS = ['workdir', 'cwd', 'working_directory', 'workingDirectory', 'dir', 'Cwd'];
|
|
988
991
|
|
|
989
992
|
function shellDenial(input, ctx) {
|
|
990
993
|
const args = input && typeof input === 'object' ? input : {};
|
|
@@ -1043,6 +1046,8 @@ export function browserToolDenial(tool, input = {}, { host = null, cwd = null, r
|
|
|
1043
1046
|
if (WEB_TOOLS.has(lower)) return refusal(name, 'web search / fetch outside the SynaBun browser');
|
|
1044
1047
|
// OpenCode's own browser tool drives a browser that is not SynaBun's.
|
|
1045
1048
|
if (kind === 'opencode' && lower === 'browser') return refusal(name, 'OpenCode\'s own browser');
|
|
1049
|
+
// Antigravity's own browser and browser subagent (its built-in tools carry no server: never SynaBun's browser_*).
|
|
1050
|
+
if (kind === 'antigravity' && (ANTIGRAVITY_BROWSER_TOOLS.has(lower) || /^browser_/.test(lower))) return refusal(name, 'Antigravity\'s own browser');
|
|
1046
1051
|
if (!SHELL_TOOLS.has(lower)) return null;
|
|
1047
1052
|
const project = cwd ? resolve(String(cwd)) : process.cwd();
|
|
1048
1053
|
const why = shellDenial(args, { roots: testRoots(project, roots), cwd: project, readFile: typeof readFile === 'function' ? readFile : defaultReadFile, depth: 0 });
|
|
@@ -104,6 +104,7 @@ export const PANEL_CAPABILITIES = Object.freeze([
|
|
|
104
104
|
'ask_annotations', // AskUserQuestion answers keep their `annotations`
|
|
105
105
|
'tool_policy', // query `toolPolicy`: 'full' | 'read-only' | 'no-web'
|
|
106
106
|
'elicitation', // MCP elicitation as control_request subtype `elicitation` (for clients that declare the feature)
|
|
107
|
+
'effort_off', // query `effort: 'off'`: thinking disabled, over any effortLevel in the user's settings
|
|
107
108
|
'session_settings', // query `session`: fallback model, limits, directories, plugins, strict MCP, instructions, sandbox, thinking, fast mode, output style, agent
|
|
108
109
|
'reload_plugins', // inbound `reload_plugins` → `reload_result` + fresh commands_list / mcp_status / plugins_list
|
|
109
110
|
'message_flags', // session.hookEvents / promptSuggestions / subagentText / agentSummaries → the matching SDK options
|
|
@@ -754,6 +755,9 @@ export class ClaudeSession {
|
|
|
754
755
|
if (this.sessionId && this._resumeAt) { options.resumeSessionAt = this._resumeAt; this._resumeAt = null; }
|
|
755
756
|
}
|
|
756
757
|
if (this.model) options.model = this.model;
|
|
758
|
+
// Off is the user's pick, not the model's default: without it an
|
|
759
|
+
// effortLevel in the user's settings would still apply.
|
|
760
|
+
if (this.panel && this.effort === 'off') options.thinking = { type: 'disabled' };
|
|
757
761
|
if (this.effort && CLAUDE_EFFORT_LEVELS.includes(this.effort)) {
|
|
758
762
|
// Panel sessions use the typed option; every other caller keeps the flag it had.
|
|
759
763
|
if (this.panel) options.effort = this.effort;
|
|
@@ -2966,6 +2970,7 @@ export class ClaudeSession {
|
|
|
2966
2970
|
if (cwd) this.cwd = cwd;
|
|
2967
2971
|
// A panel tab that sends no effort means "the model's default": a level that
|
|
2968
2972
|
// was set goes back to it, live when a session exists.
|
|
2973
|
+
const prevEffort = this.effort;
|
|
2969
2974
|
const effortCleared = this.panel && !effort && !!this.effort;
|
|
2970
2975
|
if (effort) this.effort = effort;
|
|
2971
2976
|
else if (effortCleared) this.effort = null;
|
|
@@ -3006,7 +3011,9 @@ export class ClaudeSession {
|
|
|
3006
3011
|
|
|
3007
3012
|
// Effort alone switches on the live CLI. A restart would cold-start the
|
|
3008
3013
|
// session and kill any background agents it is running.
|
|
3014
|
+
// (Off disables thinking, a start-up option: to or from it the session restarts.)
|
|
3009
3015
|
if (this.q && effortChanged && !cwdChanged && !sessionChanged && !newChat
|
|
3016
|
+
&& prevEffort !== 'off' && effort !== 'off'
|
|
3010
3017
|
&& typeof this.q.applyFlagSettings === 'function') {
|
|
3011
3018
|
const effortLevel = CLAUDE_EFFORT_LEVELS.includes(effort) ? effort : null;
|
|
3012
3019
|
this.q.applyFlagSettings({ effortLevel }).catch(err => log('applyFlagSettings(effort) failed:', err.message));
|