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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Antigravity panel: tabs, attachments and dispatched-run events (no DOM)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// A tab is one ACP session of Antigravity's in one project folder. What is saved
|
|
6
|
+
// per window (`synabun-antigravity-panel-tabs-<windowId>`, read by the side panel
|
|
7
|
+
// tray too) is `{ tabs, activeIdx }` with the fields below; the transcript is not
|
|
8
|
+
// saved: a tab with a session id replays it from Antigravity (session/load).
|
|
9
|
+
// `bypass` is the tab's Bypass (agy-bypass.js): off unless the owner turned it on.
|
|
10
|
+
|
|
11
|
+
export const TAB_FIELDS = Object.freeze(['id', 'sessionId', 'project', 'geminiDir', 'label', 'title', 'titleByUser', 'additionalDirectories', 'draft',
|
|
12
|
+
'running', 'needsYou', 'automationRunId', 'automationActive', 'automationOwnerId', 'releasedRunId', 'createdAt', 'bypass']);
|
|
13
|
+
export const MAX_TABS = 12;
|
|
14
|
+
export const ATTACH_LIMITS = Object.freeze({ maxAttachments: 8, maxBytes: 15 * 1024 * 1024 });
|
|
15
|
+
const ATTACHABLE = /^(image\/(png|jpeg|gif|webp)|application\/pdf|text\/[a-z0-9.+-]+|application\/(json|xml|x-yaml|yaml)|audio\/(wav|x-wav|mpeg|mp4|aac|flac|ogg|opus|webm))$/;
|
|
16
|
+
const TEXT_EXTENSIONS = /\.(txt|md|markdown|json|ya?ml|xml|csv|log|js|mjs|cjs|ts|tsx|jsx|py|rb|go|rs|java|kt|swift|c|cc|cpp|h|hpp|cs|php|sh|zsh|bash|css|scss|html|sql|toml|ini|env)$/i;
|
|
17
|
+
|
|
18
|
+
let counter = 0;
|
|
19
|
+
const newId = () => (globalThis.crypto?.randomUUID?.() || `agy-${Date.now().toString(36)}-${(counter += 1)}`);
|
|
20
|
+
|
|
21
|
+
export function newTab({ project = '', sessionId = null, title = null, geminiDir = null } = {}) {
|
|
22
|
+
return {
|
|
23
|
+
id: newId(), sessionId, project: String(project || ''), geminiDir: typeof geminiDir === 'string' && geminiDir.trim() ? geminiDir.trim() : null,
|
|
24
|
+
label: 'New session', title, titleByUser: null, additionalDirectories: [], draft: '',
|
|
25
|
+
running: false, needsYou: false, automationRunId: null, automationActive: false, automationOwnerId: null,
|
|
26
|
+
releasedRunId: null, createdAt: Date.now(), bypass: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What the tray and the panel show for a tab: its session's title, else a short id, else "New session". `words`
|
|
32
|
+
* gives the page's language for the last three (the saved label stays English when none is given).
|
|
33
|
+
*/
|
|
34
|
+
export function tabLabel(tab, words = {}) {
|
|
35
|
+
const title = typeof tab?.title === 'string' ? tab.title.trim() : '';
|
|
36
|
+
if (title) return title.length > 60 ? `${title.slice(0, 57)}…` : title;
|
|
37
|
+
if (tab?.automationRunId) return `${words.run || 'Dispatched run'} · ${String(tab.automationRunId).slice(0, 8)}`;
|
|
38
|
+
if (tab?.sessionId) return `Antigravity · ${String(tab.sessionId).slice(0, 8)}`;
|
|
39
|
+
return words.newSession || 'New session';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The saved form: `{ tabs, activeIdx }`, each tab its persisted fields only. */
|
|
43
|
+
export function serializeTabs(tabs, activeIdx) {
|
|
44
|
+
const list = (Array.isArray(tabs) ? tabs : []).slice(0, MAX_TABS).map((tab) => {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const key of TAB_FIELDS) if (tab?.[key] !== undefined) out[key] = tab[key];
|
|
47
|
+
out.label = tabLabel(tab);
|
|
48
|
+
return out;
|
|
49
|
+
});
|
|
50
|
+
const idx = Number.isInteger(activeIdx) ? Math.min(Math.max(activeIdx, 0), Math.max(list.length - 1, 0)) : 0;
|
|
51
|
+
return { tabs: list, activeIdx: idx };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Saved tabs as the panel restores them: invalid rows dropped, nothing marked running (the page that ran it is
|
|
56
|
+
* gone: a live run is reattached by the native-loop router, not by a flag), an id for every tab.
|
|
57
|
+
*/
|
|
58
|
+
export function restoreTabs(raw) {
|
|
59
|
+
let data = raw;
|
|
60
|
+
if (typeof raw === 'string') { try { data = JSON.parse(raw); } catch { data = null; } }
|
|
61
|
+
const rows = Array.isArray(data?.tabs) ? data.tabs : [];
|
|
62
|
+
const tabs = [];
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
if (!row || typeof row !== 'object') continue;
|
|
66
|
+
const tab = { ...newTab(), ...Object.fromEntries(TAB_FIELDS.filter((key) => row[key] !== undefined).map((key) => [key, row[key]])) };
|
|
67
|
+
if (typeof tab.id !== 'string' || !tab.id || seen.has(tab.id)) tab.id = newId();
|
|
68
|
+
seen.add(tab.id);
|
|
69
|
+
if (tab.sessionId !== null && (typeof tab.sessionId !== 'string' || !/^[A-Za-z0-9._:@-]{1,200}$/.test(tab.sessionId))) tab.sessionId = null;
|
|
70
|
+
tab.project = typeof tab.project === 'string' ? tab.project : '';
|
|
71
|
+
tab.geminiDir = typeof tab.geminiDir === 'string' && tab.geminiDir.trim() ? tab.geminiDir.trim() : null;
|
|
72
|
+
tab.draft = typeof tab.draft === 'string' ? tab.draft : '';
|
|
73
|
+
tab.titleByUser = typeof tab.titleByUser === 'string' && tab.titleByUser.trim() ? tab.titleByUser.trim().slice(0, 120) : null;
|
|
74
|
+
tab.additionalDirectories = Array.isArray(tab.additionalDirectories)
|
|
75
|
+
? [...new Set(tab.additionalDirectories.filter((p) => typeof p === 'string' && p && p !== tab.project))].slice(0, 8)
|
|
76
|
+
: [];
|
|
77
|
+
// Bypass comes back only as the owner's own pick (a literal true), never from anything else in storage.
|
|
78
|
+
tab.bypass = tab.bypass === true;
|
|
79
|
+
tab.running = false;
|
|
80
|
+
tab.needsYou = false;
|
|
81
|
+
tabs.push(tab);
|
|
82
|
+
if (tabs.length >= MAX_TABS) break;
|
|
83
|
+
}
|
|
84
|
+
const activeIdx = Number.isInteger(data?.activeIdx) && data.activeIdx >= 0 && data.activeIdx < tabs.length ? data.activeIdx : 0;
|
|
85
|
+
return { tabs, activeIdx };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The MIME type a dropped or pasted file is sent as (a browser leaves it empty for many text files). */
|
|
89
|
+
export function attachmentType(file) {
|
|
90
|
+
const declared = String(file?.type || '').toLowerCase();
|
|
91
|
+
if (declared) return declared;
|
|
92
|
+
return TEXT_EXTENSIONS.test(String(file?.name || '')) ? 'text/plain' : '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Whether a file may be attached now: null when it may, else { key, params } for agyText. */
|
|
96
|
+
export function attachmentProblem(file, staged = [], limits = ATTACH_LIMITS) {
|
|
97
|
+
const name = String(file?.name || 'file');
|
|
98
|
+
if (staged.length >= limits.maxAttachments) return { key: 'attachmentTooMany', params: { count: limits.maxAttachments } };
|
|
99
|
+
if (!ATTACHABLE.test(attachmentType(file))) return { key: 'attachmentType', params: { name } };
|
|
100
|
+
if (Number(file?.size) > limits.maxBytes) return { key: 'attachmentTooLarge', params: { name, size: Math.round(limits.maxBytes / 1048576) } };
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A picture another part of the app hands the panel (the whiteboard's Send to Panel, also through a detached window)
|
|
106
|
+
* as a staged attachment: `{ file: { name, mimeType, dataUrl } }`, `{ problem: { key, params } }` when it may not
|
|
107
|
+
* be attached now (the same limits as a pasted file), or null when it is not a PNG, JPEG, GIF or WebP data URL.
|
|
108
|
+
*/
|
|
109
|
+
export function stagedPicture(dataUrl, staged = [], { name = 'whiteboard', now = Date.now(), limits = ATTACH_LIMITS } = {}) {
|
|
110
|
+
const match = /^data:(image\/(png|jpeg|gif|webp));base64,([A-Za-z0-9+/]+={0,2})$/.exec(String(dataUrl || ''));
|
|
111
|
+
if (!match) return null;
|
|
112
|
+
const body = match[3];
|
|
113
|
+
const size = Math.floor((body.length * 3) / 4) - (body.endsWith('==') ? 2 : body.endsWith('=') ? 1 : 0);
|
|
114
|
+
const file = { name: `${name}-${now}.${match[2] === 'jpeg' ? 'jpg' : match[2]}`, type: match[1], size };
|
|
115
|
+
const problem = attachmentProblem(file, staged, limits);
|
|
116
|
+
return problem ? { problem } : { file: { name: file.name, mimeType: match[1], dataUrl } };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A data URL's base64 body, or '' for anything else. */
|
|
120
|
+
export function base64Of(dataUrl) {
|
|
121
|
+
const match = /^data:[^;,]*(?:;[^;,]*)*;base64,([A-Za-z0-9+/]+={0,2})$/.exec(String(dataUrl || ''));
|
|
122
|
+
return match ? match[1] : '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The bridge's `prompt` request for a tab: its text and staged attachments ({ name, mimeType, dataUrl }). */
|
|
126
|
+
export function promptRequest(tab, text, staged = []) {
|
|
127
|
+
return {
|
|
128
|
+
sessionId: tab.sessionId,
|
|
129
|
+
text: String(text || ''),
|
|
130
|
+
attachments: staged.map((file) => ({ name: file.name, mimeType: file.mimeType, data: base64Of(file.dataUrl) })).filter((file) => file.data),
|
|
131
|
+
paths: staged.filter((file) => file.path).map((file) => file.path),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* One event of a dispatched run (the native-loop router's `sync:sidepanel:provider-event` payload, or its `event`)
|
|
137
|
+
* as what the transcript does with it: { kind: 'user', text, iteration } | { kind: 'update', update } |
|
|
138
|
+
* { kind: 'notice', text, severity } | null.
|
|
139
|
+
*/
|
|
140
|
+
export function automationEvent(payload) {
|
|
141
|
+
const message = payload?.event && typeof payload.event === 'object' && (payload.eventType || payload.provider) ? payload : (payload?.event || payload);
|
|
142
|
+
const eventType = message?.eventType || message?.event?.type || message?.type;
|
|
143
|
+
const event = message?.event ?? message;
|
|
144
|
+
if (!event || typeof event !== 'object') return null;
|
|
145
|
+
if (event.type === 'synabun.user_prompt' || eventType === 'synabun.user_prompt') return { kind: 'user', text: String(event.text || ''), iteration: message.iteration ?? null };
|
|
146
|
+
if (event.type === 'synabun.confinement') return { kind: 'notice', text: `${event.action === 'warned' ? 'Guard warning' : 'Refused'}: ${event.reason || event.rule || ''}`.trim(), severity: event.action === 'warned' ? 'warning' : 'error' };
|
|
147
|
+
if (event.type === 'synabun.provider_exit') return { kind: 'notice', text: `Antigravity stopped${event.error ? `: ${event.error}` : ''}`, severity: 'error' };
|
|
148
|
+
if (event.type === 'synabun.error') return { kind: 'notice', text: String(event.message || event.error || 'Error'), severity: 'error' };
|
|
149
|
+
if (typeof event.sessionUpdate === 'string') return { kind: 'update', update: event };
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The per-run Antigravity home (`--gemini-dir`) a dispatched run executed in, or null when none is named. */
|
|
154
|
+
export function runGeminiDir(run) {
|
|
155
|
+
const raw = run?.antigravityHome ?? run?.geminiDir ?? run?.resumeInfo?.antigravityHome ?? run?.runtime?.antigravityHome ?? null;
|
|
156
|
+
return typeof raw === 'string' && raw.trim() ? raw.trim() : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The bridge request (`session:load` or `session:resume`) that opens an existing tab's session in the panel. */
|
|
160
|
+
export function openSessionRequest(tab, { resume = false, folders = [] } = {}) {
|
|
161
|
+
const dirs = Array.isArray(folders) ? folders.filter((p) => typeof p === 'string' && p) : [];
|
|
162
|
+
const geminiDir = typeof tab?.geminiDir === 'string' && tab.geminiDir ? tab.geminiDir : null;
|
|
163
|
+
return {
|
|
164
|
+
type: resume ? 'session:resume' : 'session:load',
|
|
165
|
+
payload: {
|
|
166
|
+
sessionId: tab.sessionId,
|
|
167
|
+
cwd: tab.project,
|
|
168
|
+
tabId: tab.id,
|
|
169
|
+
bypass: tab?.bypass === true,
|
|
170
|
+
...(geminiDir ? { geminiDir } : {}),
|
|
171
|
+
...(dirs.length ? { additionalDirectories: dirs } : {}),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The notice shown when loading or resuming a tab's session fails (null when sign-in handles it instead). */
|
|
177
|
+
export function sessionLoadNotice(error, textOf) {
|
|
178
|
+
if (error?.authRequired) return null;
|
|
179
|
+
if (error?.code === 'HOME_PRUNED') return { text: textOf('runHomePruned'), severity: 'error' };
|
|
180
|
+
return { text: textOf('historyFailed', { error: error?.message || String(error ?? '') }), severity: 'error' };
|
|
181
|
+
}
|
|
@@ -881,15 +881,15 @@ export async function fetchOpencodeSessions({ project, limit, offset, search } =
|
|
|
881
881
|
return jsonFetch(`/api/opencode/sessions?${params}`);
|
|
882
882
|
}
|
|
883
883
|
|
|
884
|
-
// ───
|
|
884
|
+
// ─── Antigravity CLI Sessions (Resume) ─────
|
|
885
885
|
|
|
886
|
-
export async function
|
|
886
|
+
export async function fetchAntigravitySessions({ project, limit, offset, search } = {}) {
|
|
887
887
|
const params = new URLSearchParams();
|
|
888
888
|
if (project) params.set('project', project);
|
|
889
889
|
if (limit) params.set('limit', String(limit));
|
|
890
890
|
if (offset) params.set('offset', String(offset));
|
|
891
891
|
if (search) params.set('search', search);
|
|
892
|
-
return jsonFetch(`/api/
|
|
892
|
+
return jsonFetch(`/api/antigravity/sessions?${params}`);
|
|
893
893
|
}
|
|
894
894
|
|
|
895
895
|
// ─── Hybrid session content search (FTS5 + semantic for Claude) ─────
|
|
@@ -1143,7 +1143,7 @@ export async function fetchRulesStatus() {
|
|
|
1143
1143
|
}
|
|
1144
1144
|
}
|
|
1145
1145
|
|
|
1146
|
-
/** The rendered text for a host (claude, codex, opencode,
|
|
1146
|
+
/** The rendered text for a host (claude, codex, opencode, antigravity, cursor) or the copy-only `coexistence` snippet. */
|
|
1147
1147
|
export async function fetchRulesText(host) {
|
|
1148
1148
|
return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}/text`);
|
|
1149
1149
|
}
|
|
@@ -23,13 +23,12 @@
|
|
|
23
23
|
// last card, so one stray click or key there must not end the session's work.
|
|
24
24
|
|
|
25
25
|
import { on } from '../state.js';
|
|
26
|
-
import { getProviderMeta } from '../provider-icons.js';
|
|
27
26
|
import { listAssistantRuns } from './asst-api.js';
|
|
28
27
|
import { buildRunCard, tickRunCard, updateRunCard } from './asst-render.js';
|
|
29
28
|
import { awaitsAnswer, compactActivity, isLivePhase, phaseLine, sceneFor, sceneSvg, stageLabel, stagePhase, stepLine } from './asst-scenes.js';
|
|
30
29
|
import { injectSceneStyles } from './asst-scene-styles.js';
|
|
31
30
|
import { plural } from './asst-tool-kinds.js';
|
|
32
|
-
import { isRemovableRun, isRunIdle, isTerminalRunStatus, mergeRunDescriptor, runFromPayload, runNeedsAttention, sortRuns } from './asst-state.js';
|
|
31
|
+
import { isRemovableRun, isRunIdle, isTerminalRunStatus, mergeRunDescriptor, providerMeta, runFromPayload, runNeedsAttention, sortRuns } from './asst-state.js';
|
|
33
32
|
import { createConfirmGate } from './asst-stop.js';
|
|
34
33
|
|
|
35
34
|
const TICK_MS = 5000;
|
|
@@ -166,8 +165,8 @@ export function createAgentsDock(hostEl, hooks = {}) {
|
|
|
166
165
|
return {
|
|
167
166
|
t: hooks.t,
|
|
168
167
|
compact: true,
|
|
169
|
-
providerIcon: (p) =>
|
|
170
|
-
providerColor: (p) =>
|
|
168
|
+
providerIcon: (p) => providerMeta(p).icon,
|
|
169
|
+
providerColor: (p) => providerMeta(p).color,
|
|
171
170
|
onFocus: hooks.onFocus,
|
|
172
171
|
onStop: hooks.onStop,
|
|
173
172
|
onRead: hooks.onRead,
|
|
@@ -48,7 +48,7 @@ export function fetchAssistantCatalog({ force = false } = {}) {
|
|
|
48
48
|
|
|
49
49
|
// ── Hidden models (Assistant → Models) ───────
|
|
50
50
|
|
|
51
|
-
/** → { ok, providers: { 'claude-code': [], codex: [], opencode: [] } } */
|
|
51
|
+
/** → { ok, providers: { 'claude-code': [], codex: [], opencode: [], antigravity: [] } } */
|
|
52
52
|
export function getHiddenModelLists() {
|
|
53
53
|
return jsonFetch('/api/assistant/hidden-models');
|
|
54
54
|
}
|
|
@@ -98,8 +98,20 @@ export function escalateAssistantRun(runId, target) {
|
|
|
98
98
|
|
|
99
99
|
// ── Desktop (computer use) ───────────────────
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
/** How long the status check may take before it counts as failed (the tray must never spin forever). */
|
|
102
|
+
export const DESKTOP_STATUS_TIMEOUT_MS = 10_000;
|
|
103
|
+
|
|
104
|
+
export async function getDesktopStatus({ timeoutMs = DESKTOP_STATUS_TIMEOUT_MS } = {}) {
|
|
105
|
+
const ctrl = typeof AbortController === 'function' ? new AbortController() : null;
|
|
106
|
+
const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
|
|
107
|
+
try {
|
|
108
|
+
return await jsonFetch('/api/desktop/status', ctrl ? { signal: ctrl.signal } : {});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (ctrl?.signal.aborted) throw Object.assign(new Error('The status check timed out'), { code: 'TIMEOUT', timedOut: true });
|
|
111
|
+
throw err;
|
|
112
|
+
} finally {
|
|
113
|
+
if (timer) clearTimeout(timer);
|
|
114
|
+
}
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
/** step: 'start'|'recheck'|'install_toolchain'|'request_screen'|'request_accessibility'|'open_settings' (+ { pane }). */
|
|
@@ -42,6 +42,7 @@ import { createMascotControl } from './asst-mascot.js';
|
|
|
42
42
|
import { modelChipForms, modelChipName, modelChipTag } from './asst-model-chip.js';
|
|
43
43
|
import {
|
|
44
44
|
ACCOUNT_PROVIDERS,
|
|
45
|
+
BRAIN_PROVIDERS,
|
|
45
46
|
brainRequiresSwitch,
|
|
46
47
|
emptyProjectView,
|
|
47
48
|
modelShortName,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
PLAN_MODE_PROVIDERS,
|
|
51
52
|
permissionModeLabel,
|
|
52
53
|
PROVIDERS,
|
|
54
|
+
providerMeta,
|
|
53
55
|
projectLabel,
|
|
54
56
|
resolveBrainDefaults,
|
|
55
57
|
TEST_DENSITIES,
|
|
@@ -113,6 +115,7 @@ const PLAN_DESC = {
|
|
|
113
115
|
'claude-code': 'Plan first; approving the plan continues in the approval mode below',
|
|
114
116
|
codex: 'Native Codex plan mode (read-only sandbox)',
|
|
115
117
|
opencode: 'OpenCode plan agent (no file edits)',
|
|
118
|
+
antigravity: 'Antigravity explores and proposes a plan; code changes wait for your approval',
|
|
116
119
|
};
|
|
117
120
|
|
|
118
121
|
function agentName(agent) {
|
|
@@ -194,7 +197,7 @@ function hiddenOpencodeIds(remote) {
|
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
// The models the user hid (Assistant → Models), per provider, as the last catalog load saw them.
|
|
197
|
-
let _hidden = { 'claude-code': new Set(), codex: new Set(), opencode: new Set() };
|
|
200
|
+
let _hidden = { 'claude-code': new Set(), codex: new Set(), opencode: new Set(), antigravity: new Set() };
|
|
198
201
|
|
|
199
202
|
function hiddenSets(remote) {
|
|
200
203
|
const out = {};
|
|
@@ -295,7 +298,7 @@ function catalogSaysNone(remote, provider, local) {
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
/**
|
|
298
|
-
* { 'claude-code': [...], codex: [...], opencode: [...] }: the models of every
|
|
301
|
+
* { 'claude-code': [...], codex: [...], opencode: [...], antigravity: [...] }: the models of every
|
|
299
302
|
* Assistant model menu and of the Routes editor, from GET /api/assistant/catalog
|
|
300
303
|
* (see mergeProviderModels). Models the user hid (Assistant → Models, and
|
|
301
304
|
* Settings → OpenCode for OpenCode) never appear. `force` asks the server for a
|
|
@@ -316,6 +319,7 @@ export async function loadModelCatalog({ force = false } = {}) {
|
|
|
316
319
|
// No catalog (request failed): the last known disabled lists still apply.
|
|
317
320
|
const sets = remote ? hiddenSets(remote) : { ..._hidden, opencode: new Set([...(_hidden.opencode || []), ...hiddenOpencodeIds(null)]) };
|
|
318
321
|
_hidden = sets;
|
|
322
|
+
if (remote?.providers) _providerInfo = remote.providers;
|
|
319
323
|
for (const provider of PROVIDERS) {
|
|
320
324
|
const local = getModelsForProfile(provider) || [];
|
|
321
325
|
// An empty list the catalog vouches for is its answer: the window's own rows do not stand in for it.
|
|
@@ -366,6 +370,33 @@ function keepCatalog(catalog, sets) {
|
|
|
366
370
|
/** The last loadModelCatalog() result, synchronously (null before the first load). */
|
|
367
371
|
export function cachedModelCatalog() { return _catalog; }
|
|
368
372
|
|
|
373
|
+
// The catalog's provider entries ([{ id, brain, accounts, priced, signedIn?, tier? }]) as the last load saw them:
|
|
374
|
+
// Antigravity's sign-in state is read from there (it signs in inside its own CLI; SynaBun keeps no account of it).
|
|
375
|
+
let _providerInfo = null;
|
|
376
|
+
|
|
377
|
+
/** One provider's catalog entry ({ id, brain, signedIn?, tier?, … }), from an array or a map; null when unknown. */
|
|
378
|
+
export function catalogProviderInfo(provider, providers = _providerInfo) {
|
|
379
|
+
if (!provider || !providers) return null;
|
|
380
|
+
if (Array.isArray(providers)) return providers.find(p => p && p.id === provider) || null;
|
|
381
|
+
const entry = providers[provider];
|
|
382
|
+
return entry && typeof entry === 'object' ? entry : null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* What the model menu and the model chip say about Antigravity's sign-in (`info`: its catalog entry):
|
|
387
|
+
* { state: 'signedIn' | 'signedOut' | 'unknown', text }. Antigravity signs in with Google inside its own panel;
|
|
388
|
+
* the tier is the plan its session reported ("Google AI Pro").
|
|
389
|
+
*/
|
|
390
|
+
export function antigravitySignInView(info, t = null) {
|
|
391
|
+
const tr = tf(t);
|
|
392
|
+
const tier = typeof info?.tier === 'string' && info.tier.trim() ? info.tier.trim() : (typeof info?.tier?.name === 'string' ? info.tier.name.trim() : '');
|
|
393
|
+
if (info?.signedIn === true) {
|
|
394
|
+
return { state: 'signedIn', text: tier ? tr('assistant.brain.antigravitySignedInTier', 'Signed in with Google ({tier})', { tier }) : tr('assistant.brain.antigravitySignedIn', 'Signed in with Google') };
|
|
395
|
+
}
|
|
396
|
+
if (info?.signedIn === false) return { state: 'signedOut', text: tr('assistant.brain.antigravitySignedOut', 'Not signed in: open the Antigravity panel to sign in') };
|
|
397
|
+
return { state: 'unknown', text: tr('assistant.brain.antigravitySignInUnknown', 'Sign-in not checked yet: Antigravity signs in inside its own panel') };
|
|
398
|
+
}
|
|
399
|
+
|
|
369
400
|
/** UI metadata from the same catalog load; no second list or extra endpoint. */
|
|
370
401
|
export function modelCatalogStatus() {
|
|
371
402
|
return { partial: _catalogPartial, hiddenCounts: Object.fromEntries(PROVIDERS.map(provider => [provider, _hidden[provider]?.size || 0])) };
|
|
@@ -474,7 +505,8 @@ function tierTag(m, tr) {
|
|
|
474
505
|
/**
|
|
475
506
|
* Model menu grouped by provider, filterable, with "Custom model id…".
|
|
476
507
|
* opts: { brain, onSelect(target), includeHere, hereLabel, title, t, placement,
|
|
477
|
-
* current:{provider, model}|null, warnSwitch, providers
|
|
508
|
+
* current:{provider, model}|null, warnSwitch, providers (default: the brain
|
|
509
|
+
* providers, so a worker-only one is listed only when named), onToast(text),
|
|
478
510
|
* accept(provider, model) → bool (only those models), emptyLabel (when none
|
|
479
511
|
* passes), allowCustom (default true), zIndex (see asst-menu.js),
|
|
480
512
|
* accountItems (menu items listed first, under accountTitle) }
|
|
@@ -546,13 +578,15 @@ export async function openModelMenu(anchor, opts = {}) {
|
|
|
546
578
|
if (includeHere) {
|
|
547
579
|
items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
|
|
548
580
|
}
|
|
549
|
-
|
|
581
|
+
// Without `providers` the menu is a brain's (the toolbar's model field, the WhatsApp brain): every provider the
|
|
582
|
+
// Assistant can run on (BRAIN_PROVIDERS, Antigravity included); a route or a dispatch names its own providers.
|
|
583
|
+
const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : BRAIN_PROVIDERS;
|
|
550
584
|
let listed = 0;
|
|
551
585
|
for (const provider of list) {
|
|
552
586
|
const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
|
|
553
587
|
if (!models.length && !opts.routingEditor) continue;
|
|
554
588
|
listed += models.length;
|
|
555
|
-
const meta =
|
|
589
|
+
const meta = providerMeta(provider);
|
|
556
590
|
const hiddenCount = metadata.hiddenCounts[provider] || 0;
|
|
557
591
|
const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
|
|
558
592
|
items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
|
|
@@ -780,6 +814,8 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
780
814
|
// The composer chip's account cue: the account menu's initial avatar when the provider has more than one
|
|
781
815
|
// account (or the account is signed out), "Auto" for Auto; null otherwise. Its full words are the tooltip's.
|
|
782
816
|
function accountCue(brain) {
|
|
817
|
+
// Antigravity signed out: a warning cue on the chip (its words are the tooltip's and the Model menu's).
|
|
818
|
+
if (brain.provider === 'antigravity') return catalogProviderInfo('antigravity')?.signedIn === false ? { text: '!', tone: 'warn' } : null;
|
|
783
819
|
if (!ACCOUNT_PROVIDERS.has(brain.provider)) return null;
|
|
784
820
|
if (brain.accountId === 'auto') return { text: t('assistant.brain.accountAutoShort', 'Auto'), tone: 'muted' };
|
|
785
821
|
const accounts = brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts();
|
|
@@ -804,7 +840,8 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
804
840
|
effortSupported: brain.provider === 'opencode' || hasEfforts,
|
|
805
841
|
effort: brain.provider === 'opencode'
|
|
806
842
|
? (brain.agent || t('assistant.brain.agentDefault', 'default'))
|
|
807
|
-
|
|
843
|
+
// Antigravity has no "off" level: no level is the model's own default ("Default").
|
|
844
|
+
: (level ? level.label : (brain.effort || (brain.provider === 'antigravity' ? (efforts.find(e => e.id === 'off')?.label || t('assistant.brain.effortOff', 'off')) : t('assistant.brain.effortOff', 'off')))),
|
|
808
845
|
variantSupported: brain.provider === 'opencode' && hasEfforts,
|
|
809
846
|
variant: level ? level.label : (brain.effort || t('assistant.brain.variantDefault', 'default variant')),
|
|
810
847
|
project: brain.project ? projectLabel(brain.project, _projectsCache || []) : t('assistant.brain.projectHome', 'Home'),
|
|
@@ -888,6 +925,7 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
888
925
|
const tip = [
|
|
889
926
|
v.meta.label, model, who ? t('assistant.brain.asAccount', 'as {account}', { account: who }) : '',
|
|
890
927
|
acc ? accountIdentity(acc) : '', acc ? accountBadge(acc, p).text : '',
|
|
928
|
+
brain.provider === 'antigravity' ? antigravitySignInView(catalogProviderInfo('antigravity'), hooks.t).text : '',
|
|
891
929
|
].filter(Boolean).join(' · ');
|
|
892
930
|
setBtn('model', { label: '', icon: v.meta.icon, color: v.meta.color, tooltip: tip, aria: `${t('assistant.brain.model', 'Model')}: ${tip}` });
|
|
893
931
|
|
|
@@ -1111,10 +1149,12 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1111
1149
|
const brain = currentBrain();
|
|
1112
1150
|
if (field === 'model') {
|
|
1113
1151
|
// The account rows come from the kept lists; they are read again in the background once loaded.
|
|
1114
|
-
|
|
1152
|
+
// Antigravity has no SynaBun account: its sign-in state comes with the catalog.
|
|
1153
|
+
if (ACCOUNT_PROVIDERS.has(brain.provider) || brain.provider === 'antigravity') {
|
|
1115
1154
|
if (_warmProviders.has(brain.provider)) ensureLoaded(brain.provider).catch(() => {});
|
|
1116
1155
|
else await ensureLoaded(brain.provider);
|
|
1117
1156
|
}
|
|
1157
|
+
const antigravity = brain.provider === 'antigravity';
|
|
1118
1158
|
await openModelMenu(anchor, {
|
|
1119
1159
|
brain,
|
|
1120
1160
|
shouldOpen,
|
|
@@ -1122,8 +1162,8 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1122
1162
|
placement,
|
|
1123
1163
|
current: { provider: brain.provider, model: brain.model },
|
|
1124
1164
|
warnSwitch: true,
|
|
1125
|
-
accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : [],
|
|
1126
|
-
accountTitle: accountText('menuTitle', {}, accountProvider(brain)),
|
|
1165
|
+
accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : antigravity ? antigravitySignInItems() : [],
|
|
1166
|
+
accountTitle: antigravity ? t('assistant.brain.antigravitySignIn', 'Antigravity sign-in') : accountText('menuTitle', {}, accountProvider(brain)),
|
|
1127
1167
|
onToast: hooks.onToast,
|
|
1128
1168
|
onManageModels: hooks.onManageModels,
|
|
1129
1169
|
onSelect: (target) => {
|
|
@@ -1298,6 +1338,12 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1298
1338
|
};
|
|
1299
1339
|
}
|
|
1300
1340
|
|
|
1341
|
+
// Antigravity's place for accounts in the Model menu: its sign-in state, one line (antigravitySignInView).
|
|
1342
|
+
function antigravitySignInItems() {
|
|
1343
|
+
const view = antigravitySignInView(catalogProviderInfo('antigravity'), hooks.t);
|
|
1344
|
+
return [{ kind: 'info', id: '__antigravity_signin', label: view.text, className: `asst-agy-signin asst-agy-signin-${view.state}` }];
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1301
1347
|
// The composer's Model menu lists the brain's accounts too (Claude or Codex), as the cog's account menu
|
|
1302
1348
|
// does (asst-account-menu.js): Auto, every account once (a signed-out one opens its sign-in), the Uses
|
|
1303
1349
|
// switch of an account holding both credentials, Add account… and Manage….
|
|
@@ -1369,6 +1415,7 @@ export function createBrainPicker(host, hooks = {}) {
|
|
|
1369
1415
|
refresh,
|
|
1370
1416
|
snapshot,
|
|
1371
1417
|
openField,
|
|
1418
|
+
switchProvider,
|
|
1372
1419
|
fieldSummary,
|
|
1373
1420
|
modelLabel: () => modelLabel(),
|
|
1374
1421
|
mountEmptyProject,
|
|
@@ -228,10 +228,11 @@ export function renderBudgetSession(view, { t } = {}) {
|
|
|
228
228
|
? tr('assistant.budget.providerSpend', '{spent} spent', { spent: money(row.spentUsd) })
|
|
229
229
|
: tr('assistant.budget.providerAmounts', 'Share {share} · spent {spent} · left for tasks {left}', { share: row.shareUsd == null ? '—' : money(row.shareUsd), spent: money(row.spentUsd), left: row.workerLeftUsd == null ? '—' : money(row.workerLeftUsd) })));
|
|
230
230
|
if (!view.unlimited && row.isBrain && view.split) item.append(el('div', 'asst-modal-note', tr('assistant.budget.reserve', '{reserve} reserved for the brain within this share.', { reserve: money(view.split.brainReserveLeftUsd) })));
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
const reason = !row.eligible && row.reason ? String(row.reason).replace(/^brain:/, '') : '';
|
|
232
|
+
// A provider that reports no prices (Antigravity) gets no share under a budget: nothing of it was "used up".
|
|
233
|
+
if (!view.unlimited && (view.dry || []).includes(row.provider) && reason !== 'unpriced') item.append(el('div', 'asst-budget-dry', tr('assistant.budget.dry', 'Share used up')));
|
|
234
|
+
if (reason) {
|
|
235
|
+
item.append(el('div', 'asst-modal-note', tr(`assistant.budget.ineligible.${reason}`, { no_models: 'No available models', signed_out: 'Signed out', limited: 'Provider usage limit reached', unpriced: 'Reports tokens, not prices' }[reason] || reason)));
|
|
235
236
|
}
|
|
236
237
|
providers.append(item);
|
|
237
238
|
}
|
|
@@ -290,6 +290,14 @@ export function requestDesktopStop(body = { scope: 'all' }) {
|
|
|
290
290
|
const ICON_COMPUTER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg>';
|
|
291
291
|
const ICON_X = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
292
292
|
const POLL_MS = 3000;
|
|
293
|
+
|
|
294
|
+
/** What a failed status check was: { kind: 'forbidden' | 'timeout' | 'error', message }. */
|
|
295
|
+
export function computerCheckFailure(err) {
|
|
296
|
+
const message = str(err?.message) || 'The status check failed';
|
|
297
|
+
if (err?.status === 401 || err?.status === 403 || err?.forbidden) return { kind: 'forbidden', message };
|
|
298
|
+
if (err?.timedOut || err?.code === 'TIMEOUT' || err?.name === 'AbortError') return { kind: 'timeout', message };
|
|
299
|
+
return { kind: 'error', message };
|
|
300
|
+
}
|
|
293
301
|
const THUMB_MIN_INTERVAL_MS = 500; // ≤ 2 live-thumbnail updates per second
|
|
294
302
|
|
|
295
303
|
function el(tag, className, text) {
|
|
@@ -411,6 +419,7 @@ export function createComputerPanel(hostEl, hooks = {}) {
|
|
|
411
419
|
thumbTimer: null,
|
|
412
420
|
thumbPending: null,
|
|
413
421
|
view: '',
|
|
422
|
+
checkFailed: null,
|
|
414
423
|
};
|
|
415
424
|
|
|
416
425
|
function isEnabled() { try { return !!hooks.isEnabled?.(); } catch { return false; } }
|
|
@@ -575,7 +584,17 @@ export function createComputerPanel(hostEl, hooks = {}) {
|
|
|
575
584
|
const note = (text) => body.appendChild(el('div', 'asst-computer-tray-note', text));
|
|
576
585
|
const spinner = () => head.insertBefore(el('span', 'asst-spinner'), head.firstChild);
|
|
577
586
|
|
|
578
|
-
|
|
587
|
+
const viewState = info.state === 'loading' && state.checkFailed ? 'check_failed' : info.state;
|
|
588
|
+
switch (viewState) {
|
|
589
|
+
case 'check_failed':
|
|
590
|
+
title.textContent = t('assistant.computer.checkFailed', 'Could not check computer use');
|
|
591
|
+
note(state.checkFailed.kind === 'forbidden'
|
|
592
|
+
? t('assistant.computer.checkFailedForbidden', 'This window is not allowed to read computer use.')
|
|
593
|
+
: state.checkFailed.kind === 'timeout'
|
|
594
|
+
? t('assistant.computer.checkFailedTimeout', 'The check did not answer in time.')
|
|
595
|
+
: state.checkFailed.message);
|
|
596
|
+
button(t('assistant.computer.checkAgain', 'Check again'), 'secondary', () => { state.checkFailed = null; render(); refresh().catch(() => {}); }, 'recheck');
|
|
597
|
+
break;
|
|
579
598
|
case 'loading':
|
|
580
599
|
spinner();
|
|
581
600
|
title.textContent = t('assistant.computer.checking', 'Checking computer use…');
|
|
@@ -670,6 +689,7 @@ export function createComputerPanel(hostEl, hooks = {}) {
|
|
|
670
689
|
|
|
671
690
|
function applyStatus(status) {
|
|
672
691
|
if (!status || typeof status !== 'object') return;
|
|
692
|
+
state.checkFailed = null;
|
|
673
693
|
state.status = status;
|
|
674
694
|
state.info = computerSetupState(status);
|
|
675
695
|
if (status.control && typeof status.control === 'object') {
|
|
@@ -688,7 +708,9 @@ export function createComputerPanel(hostEl, hooks = {}) {
|
|
|
688
708
|
if (!state.destroyed) applyStatus(status);
|
|
689
709
|
return status;
|
|
690
710
|
} catch (err) {
|
|
691
|
-
|
|
711
|
+
// A check that failed is a settled state (Check again), never "Checking…" forever. A status already known
|
|
712
|
+
// stays on screen; only a tray that never got one shows the failure.
|
|
713
|
+
if (!state.destroyed) { state.error = ''; state.checkFailed = computerCheckFailure(err); render(); }
|
|
692
714
|
throw err;
|
|
693
715
|
}
|
|
694
716
|
}
|
|
@@ -735,6 +757,8 @@ export function createComputerPanel(hostEl, hooks = {}) {
|
|
|
735
757
|
stop,
|
|
736
758
|
isActive: desktopActive,
|
|
737
759
|
status: () => state.status,
|
|
760
|
+
/** A status check made elsewhere failed: settle the tray instead of leaving it on "Checking…". */
|
|
761
|
+
checkFailed(err) { if (state.destroyed || state.status) return; state.checkFailed = computerCheckFailure(err); render(); },
|
|
738
762
|
info: () => state.info,
|
|
739
763
|
destroy() {
|
|
740
764
|
state.destroyed = true;
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// Claude → cp/cp-permissions.js { behavior, always, updatedInput, updatedPermissions, message, planDecision }
|
|
11
11
|
// Codex → cdx/cdx-requests.js result:{ decision } | result:{ permissions, scope } | result:{ answers } | result:{ action, content }
|
|
12
12
|
// OpenCode → ocp-v2/ocp-v2-render.js reply:'once'|'always'|'reject' | answers:[[label]]
|
|
13
|
+
// Antigravity (a dispatched worker's ACP session/request_permission, relayed by the broker as
|
|
14
|
+
// { toolName, input: <the ACP toolCall> }) → { behavior } once or deny, never "always"
|
|
13
15
|
// Every response additionally carries `behavior`, `provider` and `kind` so the
|
|
14
16
|
// server-side envelope translator can route without re-deriving them; Codex
|
|
15
17
|
// replies repeat `answers` / `content` and OpenCode replies repeat `always` at
|
|
@@ -192,6 +194,9 @@ export function describeToolInput(toolName, input = {}) {
|
|
|
192
194
|
function inferProvider(req, packet) {
|
|
193
195
|
const explicit = normalizeProvider(packet?.provider ?? req?.provider);
|
|
194
196
|
if (explicit) return explicit;
|
|
197
|
+
// An ACP permission request (Antigravity): a tool call with its id, options of allow_* / reject_* kinds.
|
|
198
|
+
if (req?.brain_native?.toolCall?.toolCallId || req?.toolCall?.toolCallId) return 'antigravity';
|
|
199
|
+
if (Array.isArray(req?.options) && req.options.some(o => typeof o?.optionId === 'string' && /^(allow|reject)_(once|always)$/.test(String(o?.kind || '')))) return 'antigravity';
|
|
195
200
|
if (typeof req?.method === 'string' && req.method.includes('/')) return 'codex';
|
|
196
201
|
if (typeof req?.brain_native?.method === 'string' && req.brain_native.method.includes('/')) return 'codex';
|
|
197
202
|
if (req?.subtype === 'can_use_tool' || req?.subtype === 'permission_request' || req?.tool_name) return 'claude-code';
|
|
@@ -296,9 +301,49 @@ export function normalizeControlRequest(packet, { origin = null } = {}) {
|
|
|
296
301
|
if (firstStr(req.tool_name, req.toolName) === 'ExitPlanMode') return normalizeClaude(req, base);
|
|
297
302
|
if (provider === 'codex') return normalizeCodex(req, base);
|
|
298
303
|
if (provider === 'opencode') return normalizeOpenCode(req, base);
|
|
304
|
+
if (provider === 'antigravity') return normalizeAntigravity(req, base);
|
|
299
305
|
return normalizeClaude(req, base);
|
|
300
306
|
}
|
|
301
307
|
|
|
308
|
+
// The path-like fields of an Antigravity tool call's arguments (lib/worker-confinement.js reads the same ones).
|
|
309
|
+
const ACP_PATH_KEYS = ['path', 'file_path', 'filePath', 'Path', 'AbsolutePath', 'TargetFile', 'DirectoryPath', 'SearchPath'];
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* An Antigravity worker's permission request: the broker relays the ACP tool call
|
|
313
|
+
* ({ toolCallId, title, kind, status, locations, rawInput }) as `input`. The card shows
|
|
314
|
+
* the call's own arguments, and its command (execute) or the paths it touches as the detail.
|
|
315
|
+
*/
|
|
316
|
+
function normalizeAntigravity(req, base) {
|
|
317
|
+
// A worker's relay sends the ACP tool call as `input`; the brain sends the call's arguments as `input` and the
|
|
318
|
+
// call itself as `brain_native.toolCall`. Either way: the call, then its arguments.
|
|
319
|
+
const given = obj(req.input) || {};
|
|
320
|
+
const looksLikeCall = given.rawInput !== undefined || given.toolCallId !== undefined || Array.isArray(given.locations);
|
|
321
|
+
const call = obj(req.brain_native?.toolCall) || obj(req.toolCall) || (looksLikeCall ? given : {});
|
|
322
|
+
const args = obj(call.rawInput) || (looksLikeCall ? {} : given);
|
|
323
|
+
const kind = str(call.kind) || 'other';
|
|
324
|
+
// An MCP call names its server and tool in its arguments ({ ServerName, ToolName, Arguments }).
|
|
325
|
+
const mcpServer = firstStr(args.ServerName, args.serverName);
|
|
326
|
+
const mcpTool = firstStr(args.ToolName, args.toolName);
|
|
327
|
+
const mcpArgs = obj(args.Arguments) || obj(args.arguments);
|
|
328
|
+
const command = Array.isArray(args.command) ? args.command.map(str).join(' ') : firstStr(args.command, args.CommandLine, args.cmd);
|
|
329
|
+
const paths = [...new Set([
|
|
330
|
+
...(Array.isArray(call.locations) ? call.locations : []).map(location => str(location?.path)),
|
|
331
|
+
...ACP_PATH_KEYS.map(key => (typeof args[key] === 'string' ? str(args[key]) : '')),
|
|
332
|
+
].filter(Boolean))];
|
|
333
|
+
let mcpDetail = '';
|
|
334
|
+
if (mcpServer && mcpTool && mcpArgs) { try { mcpDetail = JSON.stringify(mcpArgs).slice(0, 400); } catch { mcpDetail = ''; } }
|
|
335
|
+
const detail = mcpServer && mcpTool ? (mcpDetail || `${mcpServer} · ${mcpTool}`)
|
|
336
|
+
: kind === 'execute' && command ? command : paths.length ? paths.join('\n') : str(call.title);
|
|
337
|
+
return {
|
|
338
|
+
...base,
|
|
339
|
+
kind: CONTROL_KINDS.PERMISSION,
|
|
340
|
+
toolName: (mcpServer && mcpTool ? `${mcpServer}_${mcpTool}` : '') || firstStr(req.toolName, req.tool_name, call.title, kind) || 'tool',
|
|
341
|
+
title: 'Permission required',
|
|
342
|
+
detail,
|
|
343
|
+
input: mcpServer && mcpTool && mcpArgs ? mcpArgs : args,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
302
347
|
function normalizeClaude(req, base) {
|
|
303
348
|
const toolName = firstStr(req.tool_name, req.toolName) || 'Unknown';
|
|
304
349
|
const input = obj(req.input) || {};
|
|
@@ -543,6 +588,8 @@ export function buildControlResponse(normalized, decision = {}) {
|
|
|
543
588
|
if (normalized.kind === CONTROL_KINDS.PLAN) return buildClaude(normalized, decision, base, allow, always);
|
|
544
589
|
if (normalized.provider === 'codex') return buildCodex(normalized, decision, base, allow, always);
|
|
545
590
|
if (normalized.provider === 'opencode') return buildOpenCode(normalized, decision, base, allow, always);
|
|
591
|
+
// Antigravity: allowed once or denied; an "always" is never sent (see controlActions).
|
|
592
|
+
if (normalized.provider === 'antigravity') return buildClaude(normalized, decision, base, allow, false);
|
|
546
593
|
return buildClaude(normalized, decision, base, allow, always);
|
|
547
594
|
}
|
|
548
595
|
|
|
@@ -651,6 +698,7 @@ export function controlActions(normalized) {
|
|
|
651
698
|
];
|
|
652
699
|
}
|
|
653
700
|
const actions = [];
|
|
701
|
+
// Never for Antigravity: its "always" option is saved by Antigravity for later sessions too, beyond this run.
|
|
654
702
|
const canAlways = n.provider === 'claude-code'
|
|
655
703
|
|| n.provider === 'opencode'
|
|
656
704
|
|| (n.provider === 'codex' && (n.method === 'item/permissions/requestApproval'
|