synabun 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -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 +501 -39
- package/neural-interface/i18n/pt-BR.json +501 -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-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 +207 -49
- 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 +13 -4
- 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 +420 -21
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +11 -2
- 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 +32 -2
- 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 +1 -1
- 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-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-panel.js +45 -8
- package/neural-interface/public/shared/assistant/asst-render.js +51 -12
- 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-scenes.js +54 -1
- 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-styles.js +34 -0
- 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-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/ocp-v2/ocp-v2-panel.js +27 -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 +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
- 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/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +354 -34
- 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 +299 -77
- 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-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 +644 -426
- 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
|
@@ -5,7 +5,7 @@ import { readStoredToolListRecord } from './tool-lists.js';
|
|
|
5
5
|
// ═══════════════════════════════════════════
|
|
6
6
|
//
|
|
7
7
|
// A "dispatch" is one worker run launched by the central assistant: a single
|
|
8
|
-
// provider conversation (Claude / Codex / OpenCode) that executes one task,
|
|
8
|
+
// provider conversation (Claude / Codex / OpenCode / Antigravity) that executes one task,
|
|
9
9
|
// then stays warm for follow-up turns until the assistant completes it or the
|
|
10
10
|
// idle timeout fires. It rides on NativeLoopRuntime (ledger, claims, sidepanel
|
|
11
11
|
// attach) through the per-launch buildPrompt/wrapAdapter overrides, so the
|
|
@@ -43,16 +43,19 @@ import { loadPlaybook } from './assistant-playbooks.js';
|
|
|
43
43
|
import { loadStyleGuideForRun } from './style-guide/store.js';
|
|
44
44
|
import { claimCheckWaived, normalizeTestDensity, testingApplies } from './assistant-test-density.js';
|
|
45
45
|
import { normalizeEffort } from './effort-levels.js';
|
|
46
|
-
import { createTurnEvidence, recordProviderEvent, evidenceSnapshot, claimEvidenceLine } from './assistant-evidence.js';
|
|
46
|
+
import { acpCommandOf, createTurnEvidence, recordProviderEvent, evidenceSnapshot, claimEvidenceLine } from './assistant-evidence.js';
|
|
47
47
|
import { BUDGET_MAX_USD, codexUsageCostUsd, floorCents } from './assistant-budget.js';
|
|
48
|
-
import { MIN_SPEND_USD, PROVIDER_LABELS, isFreePrice } from './assistant-budget-split.js';
|
|
48
|
+
import { MIN_SPEND_USD, PROVIDER_LABELS, UNPRICED_PROVIDER_REASON, isFreePrice, isUnpricedProvider } from './assistant-budget-split.js';
|
|
49
49
|
import { createBudgetPlanner } from './assistant-budget-planner.js';
|
|
50
|
-
import { CLAUDE_ESTIMATE_SOURCE, CODEX_USAGE_KEYS, TOKEN_KEYS, addTokens, codexUsageTokens, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
|
|
50
|
+
import { CLAUDE_ESTIMATE_SOURCE, CODEX_USAGE_KEYS, TOKEN_KEYS, addTokens, antigravityUsageTokens, codexUsageTokens, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
|
|
51
51
|
import { claudeTokensCostUsd } from './assistant-pricing.js';
|
|
52
52
|
import { RemotePolicyError, clampDispatchSpec, getSessionPolicy, readRegisteredProjects } from './remote-policy.js';
|
|
53
|
+
import { validateWorkerReply } from './worker-question.js';
|
|
53
54
|
import { limitFromText } from '../public/shared/assistant/asst-limit.js';
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
// Antigravity (Google's `agy`, over ACP: lib/native-loop-providers.js createAntigravityNativeLoopAdapter) is a worker
|
|
57
|
+
// provider only: never a brain, no accounts, its usage reported in tokens and never priced.
|
|
58
|
+
export const NATIVE_PROVIDERS = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
|
|
56
59
|
// awaiting_route: an un-routed dispatch held until the user picks a model on
|
|
57
60
|
// the route card (ask modes). It is active but consumes no rails.
|
|
58
61
|
// suspended: a native worker the server suspended as it went down (its CLI
|
|
@@ -85,7 +88,7 @@ export function runStatusFromState(state) {
|
|
|
85
88
|
export const MIN_MAX_MINUTES = 20;
|
|
86
89
|
|
|
87
90
|
export const DEFAULT_LIMITS = Object.freeze({
|
|
88
|
-
perProvider: Object.freeze({ 'claude-code': 3, codex: 4, opencode: 3 }),
|
|
91
|
+
perProvider: Object.freeze({ 'claude-code': 3, codex: 4, opencode: 3, antigravity: 3 }),
|
|
89
92
|
perCodexAccount: 2,
|
|
90
93
|
perSession: 6,
|
|
91
94
|
queuedPerSession: 20,
|
|
@@ -298,9 +301,55 @@ export function summarizeProviderEvent(payload = {}) {
|
|
|
298
301
|
if (/question\.asked/i.test(eventType)) return `question asked: ${clip((event.questions || []).map((q) => q.question).join(' | '), 200)}`;
|
|
299
302
|
return null;
|
|
300
303
|
}
|
|
304
|
+
if (payload.provider === 'antigravity') {
|
|
305
|
+
// eventType is the ACP session/update kind; event the update as the agent sent it.
|
|
306
|
+
if (eventType === 'agent_message_chunk') {
|
|
307
|
+
const text = event.content?.type === 'text' ? event.content.text : '';
|
|
308
|
+
return text && String(text).trim() ? `assistant: ${clip(text, 200)}` : null;
|
|
309
|
+
}
|
|
310
|
+
if (eventType === 'tool_call') return `${acpToolNoun(event)} started: ${acpToolDetail(event)}`;
|
|
311
|
+
if (eventType === 'tool_call_update') {
|
|
312
|
+
// Only the end of a call: its start was the tool_call line, its progress says nothing new.
|
|
313
|
+
if (event.status !== 'completed' && event.status !== 'failed') return null;
|
|
314
|
+
return `${acpToolNoun(event)} ${event.status === 'failed' ? 'failed' : 'done'}: ${acpToolDetail(event)}`;
|
|
315
|
+
}
|
|
316
|
+
if (eventType === 'plan') {
|
|
317
|
+
const entries = Array.isArray(event.entries) ? event.entries : [];
|
|
318
|
+
if (!entries.length) return null;
|
|
319
|
+
const done = entries.filter((step) => step?.status === 'completed').length;
|
|
320
|
+
const current = entries.find((step) => step?.status === 'in_progress');
|
|
321
|
+
return `plan: ${done}/${entries.length} done${current?.content ? `, now ${clip(current.content, 120)}` : ''}`;
|
|
322
|
+
}
|
|
323
|
+
if (eventType === 'synabun.provider_exit') {
|
|
324
|
+
const how = event.signal ? ` (${event.signal})` : event.code !== null && event.code !== undefined ? ` (code ${event.code})` : '';
|
|
325
|
+
return `error: Antigravity exited${how}${event.error ? `: ${clip(event.error, 160)}` : ''}`;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
301
329
|
return null;
|
|
302
330
|
}
|
|
303
331
|
|
|
332
|
+
/** What an ACP tool call is, for a transcript line: command, file_change or tool (Codex's words). */
|
|
333
|
+
function acpToolNoun(call = {}) {
|
|
334
|
+
const kind = String(call.kind || '');
|
|
335
|
+
// agy names an MCP call call_mcp_tool, kind `execute`: it is a tool, never a command.
|
|
336
|
+
if (call.name === 'call_mcp_tool' || (call.rawInput && typeof call.rawInput === 'object' && call.rawInput.ServerName && call.rawInput.ToolName)) return 'tool';
|
|
337
|
+
if (kind === 'execute') return 'command';
|
|
338
|
+
if (kind === 'edit' || kind === 'delete' || kind === 'move') return 'file_change';
|
|
339
|
+
return 'tool';
|
|
340
|
+
}
|
|
341
|
+
/** The command, the paths or the title of an ACP tool call; its id when an update carries none of them. */
|
|
342
|
+
function acpToolDetail(call = {}) {
|
|
343
|
+
const input = call.rawInput && typeof call.rawInput === 'object' ? call.rawInput : {};
|
|
344
|
+
if (input.ServerName && input.ToolName) return clip(`${input.ServerName}_${input.ToolName}`, 140);
|
|
345
|
+
const command = call.kind === 'execute' ? acpCommandOf(call) : '';
|
|
346
|
+
if (command) return clip(command, 140);
|
|
347
|
+
const paths = (Array.isArray(call.locations) ? call.locations : []).map((location) => location?.path).filter((path) => typeof path === 'string' && path);
|
|
348
|
+
if (paths.length && call.kind !== 'execute' && call.kind !== 'fetch') return clip(paths.join(', '), 160);
|
|
349
|
+
if (call.title) return clip(call.title, 140);
|
|
350
|
+
return call.toolCallId ? `call ${String(call.toolCallId).slice(0, 12)}` : (call.kind || 'tool');
|
|
351
|
+
}
|
|
352
|
+
|
|
304
353
|
// The old five-cap keys a config file may still hold: never limits (the budget planner owns the money).
|
|
305
354
|
const LEGACY_BUDGET_LIMITS = new Set(['defaultBudgetUsd', 'maxBudgetUsd', 'sessionWarnUsd', 'sessionSoftBudgetUsd', 'sessionHardBudgetUsd', 'brainBudgetUsd', 'budgetVersion', 'budgetUpdatedAt']);
|
|
306
355
|
function mergeLimits(overrides = {}) {
|
|
@@ -479,8 +528,10 @@ export function createAssistantDispatcher({
|
|
|
479
528
|
let closing = false;
|
|
480
529
|
|
|
481
530
|
// ── persistence ────────────────────────────────────────────────────────────
|
|
482
|
-
/** Why the original provider account of a run cannot be used now ('' when it can). */
|
|
531
|
+
/** Why the original provider account of a run cannot be used now ('' when it can, or the provider has no accounts). */
|
|
483
532
|
function providerAccountUnusable(accountId, provider = 'claude-code', entry = null) {
|
|
533
|
+
// OpenCode and Antigravity sign in inside their own CLI: SynaBun keeps no account of theirs.
|
|
534
|
+
if (provider !== 'codex' && provider !== 'claude-code') return '';
|
|
484
535
|
if (provider === 'codex') {
|
|
485
536
|
const home = codexAccounts?.homeFor?.(accountId);
|
|
486
537
|
if (home && entry?.codexHome && resolve(home) !== resolve(entry.codexHome)) return 'its CODEX_HOME changed';
|
|
@@ -658,9 +709,11 @@ export function createAssistantDispatcher({
|
|
|
658
709
|
status: runtimeRun.status, turnState: runtimeRun.turnState, stoppedReason: runtimeRun.stoppedReason,
|
|
659
710
|
error: runtimeRun.error, claimedBy: runtimeRun.claimedBy, providerSessionId: runtimeRun.providerSessionId,
|
|
660
711
|
providerThreadId: runtimeRun.providerThreadId,
|
|
712
|
+
...((runtimeRun.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome) ? { antigravityHome: runtimeRun.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome } : {}),
|
|
661
713
|
} : (entry.runtime || null),
|
|
662
714
|
providerSessionId: runtimeRun?.providerSessionId || entry.providerSessionId || null,
|
|
663
715
|
providerThreadId: runtimeRun?.providerThreadId || entry.providerThreadId || null,
|
|
716
|
+
...((runtimeRun?.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome) ? { antigravityHome: runtimeRun?.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome } : {}),
|
|
664
717
|
};
|
|
665
718
|
}
|
|
666
719
|
function safeRuntime() { try { return getRuntime(); } catch { return null; } }
|
|
@@ -771,7 +824,7 @@ export function createAssistantDispatcher({
|
|
|
771
824
|
}
|
|
772
825
|
|
|
773
826
|
/** `taskClass`: the run's class (its route's, else the spec's); image / video creation needs a model that makes the medium. */
|
|
774
|
-
function normalizeSpec(spec = {}, { catalogValue = null,
|
|
827
|
+
function normalizeSpec(spec = {}, { catalogValue = null, taskClass = null } = {}) {
|
|
775
828
|
const provider = String(spec.provider || '').trim().toLowerCase();
|
|
776
829
|
if (!NATIVE_PROVIDERS.has(provider)) throw new DispatchError('INVALID_PROVIDER', `provider must be one of ${[...NATIVE_PROVIDERS].join(', ')}`);
|
|
777
830
|
const task = String(spec.task || '').trim();
|
|
@@ -782,12 +835,6 @@ export function createAssistantDispatcher({
|
|
|
782
835
|
let permissionPolicy = normalizePermissionPolicy(spec.permissionPolicy);
|
|
783
836
|
let capability = normalizeCapability(spec.capability || 'full');
|
|
784
837
|
const notes = [];
|
|
785
|
-
if (provider === 'codex' && permissionPolicy === 'ask') {
|
|
786
|
-
// A route card may move an 'ask' dispatch onto Codex: degrade instead of failing.
|
|
787
|
-
if (!codexAskFallback) throw new DispatchError('PERMISSION_POLICY_UNSUPPORTED', 'Codex dispatches cannot use permissionPolicy "ask" (the Codex SDK has no approval channel). Use "restricted" for a workspace-write sandbox or "auto".', { suggested: 'restricted' });
|
|
788
|
-
permissionPolicy = 'restricted';
|
|
789
|
-
notes.push('permission policy "ask" became "restricted" (Codex has no approval channel)');
|
|
790
|
-
}
|
|
791
838
|
// "auto" (or nothing) = let routing pick / use the provider default.
|
|
792
839
|
let model = spec.model && String(spec.model).trim() && String(spec.model).trim().toLowerCase() !== 'auto' ? String(spec.model).trim() : null;
|
|
793
840
|
let modelInfo = null;
|
|
@@ -1070,10 +1117,11 @@ export function createAssistantDispatcher({
|
|
|
1070
1117
|
/**
|
|
1071
1118
|
* Why a run's spend could not be held to the budget (no price for its model), else null. Enforced
|
|
1072
1119
|
* only with a budget and a price source (`pricing`): Claude reports cost, Codex needs a list price,
|
|
1073
|
-
* OpenCode a catalog price (a free model is fine).
|
|
1120
|
+
* OpenCode a catalog price (a free model is fine), Antigravity never has one (tokens, not prices).
|
|
1074
1121
|
*/
|
|
1075
1122
|
function unpricedReason(entry) {
|
|
1076
1123
|
if (!pricing || entry.provider === 'claude-code' || !limitedNow()) return null;
|
|
1124
|
+
if (isUnpricedProvider(entry.provider)) return UNPRICED_PROVIDER_REASON;
|
|
1077
1125
|
const model = entry.pricingModel || entry.model || null;
|
|
1078
1126
|
if (entry.provider === 'codex') {
|
|
1079
1127
|
let price = null;
|
|
@@ -1085,7 +1133,10 @@ export function createAssistantDispatcher({
|
|
|
1085
1133
|
function refuseUnpriced(entry) {
|
|
1086
1134
|
const why = unpricedReason(entry);
|
|
1087
1135
|
if (!why) return;
|
|
1088
|
-
|
|
1136
|
+
const next = isUnpricedProvider(entry.provider)
|
|
1137
|
+
? `Dispatch it to another provider (agent_catalog shows prices), or ask the user to switch the session budget to No limit (Assistant → Budget) to run it on ${providerName(entry.provider)}.`
|
|
1138
|
+
: 'Pick a priced model or a free one (agent_catalog shows prices).';
|
|
1139
|
+
throw new DispatchError('BUDGET_UNPRICED', `Not dispatched: ${why}, so the session budget could not hold its spend. ${next}`, { status: 409, provider: entry.provider, model: entry.pricingModel || entry.model || null });
|
|
1089
1140
|
}
|
|
1090
1141
|
const providerName = (provider) => PROVIDER_LABELS[provider] || provider;
|
|
1091
1142
|
/** The session budget's words for a provider whose share is used up ("codex share used up: $4.00 of $4.00 spent"). */
|
|
@@ -1522,7 +1573,7 @@ export function createAssistantDispatcher({
|
|
|
1522
1573
|
emit(entry, 'route_pending', { routeId: routed.routeId });
|
|
1523
1574
|
return {
|
|
1524
1575
|
ok: true, queued: true, awaitingRoute: true, routeId: routed.routeId, run: view(entry),
|
|
1525
|
-
next: 'This run is waiting for the user to pick its model on the route card. Do not dispatch it again. It starts automatically after the pick; the decision arrives as a [SynaBun Mailbox] route_decided event.',
|
|
1576
|
+
next: 'This run is waiting for the user to pick its model on the route card. Do not dispatch it again. It starts automatically after the pick; the decision arrives as a [SynaBun Mailbox] route_decided event. If the user picks "here" instead, this run never starts and that event asks you to do the task yourself.',
|
|
1526
1577
|
};
|
|
1527
1578
|
}
|
|
1528
1579
|
|
|
@@ -1555,7 +1606,7 @@ export function createAssistantDispatcher({
|
|
|
1555
1606
|
let spec;
|
|
1556
1607
|
// The picked provider may need another clamp (Codex under strict worker approvals), or the level fell meanwhile.
|
|
1557
1608
|
const remoteNotes = [];
|
|
1558
|
-
try { input = clampRemote(input, entry.assistantSessionId, remoteNotes); spec = normalizeSpec(input, { catalogValue,
|
|
1609
|
+
try { input = clampRemote(input, entry.assistantSessionId, remoteNotes); spec = normalizeSpec(input, { catalogValue, taskClass: entry.taskClass || null }); }
|
|
1559
1610
|
catch (error) {
|
|
1560
1611
|
entry.state = 'failed'; entry.completionReason = 'route_invalid'; entry.finishedAt = iso(now()); entry.turnState = 'terminal';
|
|
1561
1612
|
entry.error = error?.message || String(error);
|
|
@@ -1581,7 +1632,10 @@ export function createAssistantDispatcher({
|
|
|
1581
1632
|
const entry = entries.get(runId);
|
|
1582
1633
|
if (!entry || entry.state !== 'awaiting_route') return false;
|
|
1583
1634
|
entry.state = 'stopped'; entry.completionReason = reason; entry.finishedAt = iso(now()); entry.turnState = 'terminal'; entry.pending = [];
|
|
1584
|
-
|
|
1635
|
+
// route_here: the user picked "here" on its card, so the assistant does the task itself and this worker never starts.
|
|
1636
|
+
const status = reason === 'route_expired' ? 'expired' : reason === 'route_here' ? 'here' : 'declined';
|
|
1637
|
+
entry.route = { ...(entry.route || {}), status, decidedBy: reason === 'route_expired' ? 'timeout' : 'user', decidedAt: iso(now()) };
|
|
1638
|
+
if (reason === 'route_here') entry.notes.push('the user chose to do this task here, in the conversation: this worker was not started');
|
|
1585
1639
|
delete entry.rawSpec;
|
|
1586
1640
|
emit(entry, 'stopped');
|
|
1587
1641
|
return true;
|
|
@@ -1605,7 +1659,7 @@ export function createAssistantDispatcher({
|
|
|
1605
1659
|
accountId: to.accountId || (to.provider === entry.provider ? entry.accountId : null),
|
|
1606
1660
|
title: clip(`${entry.title} ${retry ? '↻' : '↑'}`, 80), tags: entry.tags, workflowId: entry.workflowId, parentRunId: runId,
|
|
1607
1661
|
tools: entry.tools?.some(item => item.startsWith('mcp:')) ? retainExternalRefs(entry.toolsRequested ?? dispatchTools({}).items, entry.tools) : entry.toolsRequested ?? undefined, capability: entry.capability,
|
|
1608
|
-
permissionPolicy:
|
|
1662
|
+
permissionPolicy: entry.permissionPolicy,
|
|
1609
1663
|
usesBrowser: entry.usesBrowser, usesComputer: entry.usesComputer, maxMinutes: entry.maxMinutes, budgetUsd: entry.budgetUsd,
|
|
1610
1664
|
outputSchema: entry.outputSchema, taskClass: entry.taskClass || null,
|
|
1611
1665
|
}, { assistantSessionId: entry.assistantSessionId, origin: 'ui', brief: entry.brief || null });
|
|
@@ -1808,7 +1862,7 @@ export function createAssistantDispatcher({
|
|
|
1808
1862
|
} else entry.scratchDir = null;
|
|
1809
1863
|
const desktopGrant = mintDesktopGrant(entry);
|
|
1810
1864
|
const state = runState(entry, { browserSessionId, browserTabId, desktopGrant, startedAt: iso(now()) });
|
|
1811
|
-
|
|
1865
|
+
state.permissionBroker = createBroker(entry.runId);
|
|
1812
1866
|
// A class's rules (design), read now so an edited override applies without a restart;
|
|
1813
1867
|
// they go to the prompt only, not into the loop state file.
|
|
1814
1868
|
const playbook = playbookFor(entry);
|
|
@@ -1892,8 +1946,13 @@ export function createAssistantDispatcher({
|
|
|
1892
1946
|
adminSandbox: entry.resumeInfo?.adminSandbox === true,
|
|
1893
1947
|
onCodexSandbox: (value) => { resumeInfoOf(entry).adminSandbox = value === true; persist({ force: true }); },
|
|
1894
1948
|
onSandboxPlan: (plan) => { resumeInfoOf(entry).sandboxPlan = plan; persist({ force: true }); },
|
|
1949
|
+
// Antigravity counts tokens per session (ACP usage): what its session had counted, kept for a resume after a
|
|
1950
|
+
// restart, so the resumed run's first turn books only its own.
|
|
1951
|
+
sessionUsageBase: entry.provider === 'antigravity' ? entry.resumeInfo?.acpSessionUsage || null : null,
|
|
1952
|
+
onSessionUsage: (value) => { if (entry.provider === 'antigravity' && value && typeof value === 'object') resumeInfoOf(entry).acpSessionUsage = value; },
|
|
1895
1953
|
guardMode: entry.guardMode, scratchDir: entry.scratchDir || null, serverPort: Number(serverPort) > 0 ? Number(serverPort) : null,
|
|
1896
1954
|
localPortRange: localPortRange && Number(localPortRange.from) > 0 && Number(localPortRange.to) >= Number(localPortRange.from) ? { from: Number(localPortRange.from), to: Number(localPortRange.to) } : null,
|
|
1955
|
+
questionTimeoutMs: limits.questionTimeoutMs,
|
|
1897
1956
|
idleTimeoutMs: entry.idleTimeoutMs, maxTurns: entry.maxTurns, outputSchema: entry.outputSchema, project: entry.project,
|
|
1898
1957
|
title: entry.title, tags: entry.tags, workflowId: entry.workflowId, parentRunId: entry.parentRunId,
|
|
1899
1958
|
// The user's brief after a clarification (assistant-task-prompt.js briefBlock).
|
|
@@ -1914,6 +1973,8 @@ export function createAssistantDispatcher({
|
|
|
1914
1973
|
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
1915
1974
|
resumeInfoOf(entry).pid = pid;
|
|
1916
1975
|
entry.resumeInfo.pidStartedAt = info.startedAt || iso(now());
|
|
1976
|
+
// An Antigravity run's own home (lib/antigravity/home.js): where its conversation is, for a resume.
|
|
1977
|
+
if (typeof info.home === 'string' && info.home) { entry.resumeInfo.antigravityHome = info.home; entry.antigravityHome = info.home; }
|
|
1917
1978
|
persist({ force: true });
|
|
1918
1979
|
},
|
|
1919
1980
|
...extra,
|
|
@@ -1990,36 +2051,38 @@ export function createAssistantDispatcher({
|
|
|
1990
2051
|
return account?.home || CODEX_DEFAULT_HOME || null;
|
|
1991
2052
|
}
|
|
1992
2053
|
|
|
1993
|
-
// ──
|
|
2054
|
+
// ── request broker (questions in every policy; tool approvals under 'ask') ──────────────────────────────────────
|
|
1994
2055
|
function createBroker(runId) {
|
|
1995
2056
|
return {
|
|
1996
2057
|
request: (request = {}) => new Promise((resolvePermission) => {
|
|
1997
2058
|
const entry = entries.get(runId);
|
|
1998
2059
|
const liveState = live.get(runId);
|
|
1999
|
-
if (!entry || !liveState) { resolvePermission({ behavior: 'deny', message: 'Run is no longer tracked.' }); return; }
|
|
2060
|
+
if (!entry || !liveState || liveState.dead || liveState.suspending || TERMINAL_STATES.has(entry.state) || request.signal?.aborted) { resolvePermission({ behavior: 'deny', message: 'Run is no longer tracked.' }); return; }
|
|
2000
2061
|
const requestId = `perm-${randomBytes(6).toString('hex')}`;
|
|
2001
2062
|
const kind = request.kind === 'ask' ? 'question' : 'permission';
|
|
2002
2063
|
const timeoutMs = kind === 'question' ? limits.questionTimeoutMs : limits.permissionTimeoutMs;
|
|
2003
2064
|
const pending = {
|
|
2004
2065
|
requestId, kind, toolName: request.toolName || null, input: request.input ?? null,
|
|
2066
|
+
protocol: request.protocol || null,
|
|
2005
2067
|
questions: Array.isArray(request.questions) ? request.questions : [], createdAt: iso(now()), expiresAt: iso(now() + timeoutMs),
|
|
2006
2068
|
};
|
|
2007
2069
|
entry.pending = [...(entry.pending || []), pending];
|
|
2008
2070
|
entry.state = 'awaiting_permission';
|
|
2009
2071
|
entry.turnState = 'awaiting_permission';
|
|
2072
|
+
// Install the resolver before publishing: a synchronous listener can answer at once.
|
|
2073
|
+
const timer = setTimeout(() => finishPending(runId, requestId, { behavior: 'deny', message: `No decision within ${Math.round(timeoutMs / 60000)} minutes.`, resolvedBy: 'timeout' }), timeoutMs);
|
|
2074
|
+
timer.unref?.();
|
|
2075
|
+
const abortSignal = request.signal;
|
|
2076
|
+
const onAbort = () => finishPending(runId, requestId, { behavior: 'deny', message: 'Run aborted.', resolvedBy: 'abort' });
|
|
2077
|
+
liveState.pendingResolvers.set(requestId, { resolve: resolvePermission, timer, cleanup: () => abortSignal?.removeEventListener?.('abort', onAbort) });
|
|
2078
|
+
abortSignal?.addEventListener?.('abort', onAbort, { once: true });
|
|
2010
2079
|
// Waiting on the answer is not work: the request's own timeout bounds it.
|
|
2011
2080
|
pauseWork(entry, liveState);
|
|
2081
|
+
liveState.ctx?.setPendingRequests?.(entry.pending);
|
|
2012
2082
|
liveState.ctx?.setTurnState?.('awaiting_permission');
|
|
2013
2083
|
liveState.ctx?.emit?.({ type: 'synabun.permission_request', requestId, kind, toolName: pending.toolName, input: pending.input, questions: pending.questions });
|
|
2014
|
-
const timer = setTimeout(() => finishPending(runId, requestId, { behavior: 'deny', message: `No decision within ${Math.round(timeoutMs / 60000)} minutes.`, resolvedBy: 'timeout' }), timeoutMs);
|
|
2015
|
-
timer.unref?.();
|
|
2016
|
-
liveState.pendingResolvers.set(requestId, { resolve: resolvePermission, timer });
|
|
2017
2084
|
try { broadcastSync({ type: 'assistant:permission-request', runId, assistantSessionId: entry.assistantSessionId, provider: entry.provider, request_id: requestId, request: pending }); } catch {}
|
|
2018
2085
|
emit(entry, kind === 'question' ? 'needs_input' : 'permission_request', { request: pending });
|
|
2019
|
-
const abortSignal = request.signal;
|
|
2020
|
-
if (abortSignal && typeof abortSignal.addEventListener === 'function') {
|
|
2021
|
-
abortSignal.addEventListener('abort', () => finishPending(runId, requestId, { behavior: 'deny', message: 'Run aborted.', resolvedBy: 'abort' }), { once: true });
|
|
2022
|
-
}
|
|
2023
2086
|
}),
|
|
2024
2087
|
};
|
|
2025
2088
|
}
|
|
@@ -2030,10 +2093,12 @@ export function createAssistantDispatcher({
|
|
|
2030
2093
|
const resolver = liveState.pendingResolvers.get(requestId);
|
|
2031
2094
|
if (!resolver) return false;
|
|
2032
2095
|
clearTimeout(resolver.timer);
|
|
2096
|
+
resolver.cleanup?.();
|
|
2033
2097
|
liveState.pendingResolvers.delete(requestId);
|
|
2034
2098
|
const request = (entry.pending || []).find((item) => item.requestId === requestId) || null;
|
|
2035
2099
|
entry.pending = (entry.pending || []).filter((item) => item.requestId !== requestId);
|
|
2036
|
-
|
|
2100
|
+
liveState.ctx?.setPendingRequests?.(entry.pending);
|
|
2101
|
+
if (entry.state === 'awaiting_permission' && entry.pending.length === 0 && !liveState.dead && !liveState.suspending && !liveState.abortReason) {
|
|
2037
2102
|
entry.state = 'running';
|
|
2038
2103
|
entry.turnState = 'running';
|
|
2039
2104
|
startWork(entry, liveState);
|
|
@@ -2056,9 +2121,14 @@ export function createAssistantDispatcher({
|
|
|
2056
2121
|
throw new DispatchError(known ? 'PERMISSION_ALREADY_RESOLVED' : 'NO_PENDING_REQUEST', known ? `Request ${requestId} was already resolved` : `No pending request ${requestId} on run ${runId}`, { status: 409 });
|
|
2057
2122
|
}
|
|
2058
2123
|
const behavior = response.behavior === 'allow' || response.decision === 'allow' || response.decision === 'answer' ? 'allow' : 'deny';
|
|
2124
|
+
// Antigravity saves an "always" answer for every later session of the user's, outside the run: a worker's
|
|
2125
|
+
// answer is for this request only.
|
|
2126
|
+
const always = entry.provider === 'antigravity' ? false : !!response.always;
|
|
2127
|
+
try { validateWorkerReply((entry.pending || []).find((p) => p.requestId === requestId), { ...response, behavior }); }
|
|
2128
|
+
catch (error) { throw new DispatchError('INVALID_QUESTION_ANSWER', error.message); }
|
|
2059
2129
|
finishPending(runId, requestId, {
|
|
2060
|
-
behavior, updatedInput: response.updatedInput, message: response.message || response.note, always
|
|
2061
|
-
answers: response.answers, resolvedBy: origin,
|
|
2130
|
+
behavior, updatedInput: response.updatedInput, message: response.message || response.note, always,
|
|
2131
|
+
answers: response.answers ?? response.updatedInput?.answers, result: response.result, content: response.content, action: response.action, error: response.error, resolvedBy: origin,
|
|
2062
2132
|
}, { origin });
|
|
2063
2133
|
return view(entry);
|
|
2064
2134
|
}
|
|
@@ -2105,6 +2175,7 @@ export function createAssistantDispatcher({
|
|
|
2105
2175
|
liveState.turnMeteredUsd = 0;
|
|
2106
2176
|
let raw = await inner.runTurn(prompt, { ...meta, turn: turnNumber, outputSchema: entry.outputSchema || undefined });
|
|
2107
2177
|
if (entry.provider === 'codex') settleCodexTurn(entry, liveState, raw);
|
|
2178
|
+
else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, raw);
|
|
2108
2179
|
let text = typeof raw?.text === 'string' ? raw.text : '';
|
|
2109
2180
|
const evalRun = isEvalRun(entry);
|
|
2110
2181
|
let parsed = evalRun ? { found: false } : parseResultContract(text);
|
|
@@ -2142,6 +2213,7 @@ export function createAssistantDispatcher({
|
|
|
2142
2213
|
if (retryParsed.found) { parsed = { ...retryParsed, source: 'retry' }; text = `${text}\n\n${retryText}`; }
|
|
2143
2214
|
retryCost = accumulateCost(entry, retry);
|
|
2144
2215
|
if (entry.provider === 'codex') settleCodexTurn(entry, liveState, retry);
|
|
2216
|
+
else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, retry);
|
|
2145
2217
|
} catch (error) {
|
|
2146
2218
|
// Stopped during the retry: the run ends here (handleTurnError books the retry), with no
|
|
2147
2219
|
// turn_completed or follow-up after it. The first provider turn was paid for: book it first.
|
|
@@ -2149,6 +2221,7 @@ export function createAssistantDispatcher({
|
|
|
2149
2221
|
if (liveState.abortReason || liveState.suspending) { accumulateCost(entry, raw); throw error; }
|
|
2150
2222
|
retryCost = accumulateCost(entry, error);
|
|
2151
2223
|
if (entry.provider === 'codex') settleCodexTurn(entry, liveState, error);
|
|
2224
|
+
else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, error);
|
|
2152
2225
|
entry.notes.push(`result retry failed: ${error?.message || error}`);
|
|
2153
2226
|
}
|
|
2154
2227
|
}
|
|
@@ -2174,7 +2247,8 @@ export function createAssistantDispatcher({
|
|
|
2174
2247
|
liveState.turnOpen = false;
|
|
2175
2248
|
stopUsagePolling(liveState);
|
|
2176
2249
|
let turnUsage = raw?.usage || null;
|
|
2177
|
-
|
|
2250
|
+
// Codex and Antigravity: what their provider turns (the retry included) reported, in provider keys.
|
|
2251
|
+
if (entry.provider === 'codex' || entry.provider === 'antigravity') turnUsage = liveState.turnUsage || null;
|
|
2178
2252
|
else if (entry.provider === 'opencode') turnUsage = (await settleOpenCodeTurn(entry, liveState)) || turnUsage;
|
|
2179
2253
|
// A reported cost below an earlier turn's estimate can make this negative: a turn never reads below $0.
|
|
2180
2254
|
const turnCost = Math.max(0, accumulateCost(entry, raw) + retryCost + (Number(liveState.turnMeteredUsd) || 0));
|
|
@@ -2383,6 +2457,8 @@ export function createAssistantDispatcher({
|
|
|
2383
2457
|
if (entry.provider === 'codex') settleCodexTurn(entry, liveState, error);
|
|
2384
2458
|
// OpenCode: what streamed before the failure, as it stands (its serve may be gone: no reconcile).
|
|
2385
2459
|
else if (entry.provider === 'opencode') bookOpenCodeTurn(entry, liveState, { reconciled: false });
|
|
2460
|
+
// Antigravity: a stopped turn's answer still carried its tokens (the adapter puts them on the abort).
|
|
2461
|
+
else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, error);
|
|
2386
2462
|
announceUnpriced(entry);
|
|
2387
2463
|
checkSession(entry.assistantSessionId, { source: entry });
|
|
2388
2464
|
if (budgetStopReason(entry.completionReason)) return { providerSessionId: null };
|
|
@@ -2465,7 +2541,8 @@ export function createAssistantDispatcher({
|
|
|
2465
2541
|
if (!raw) return 0;
|
|
2466
2542
|
const cost = Number(raw.costUsd);
|
|
2467
2543
|
const added = bookCost(entry, cost, raw.basis || 'reported');
|
|
2468
|
-
|
|
2544
|
+
// Codex and Antigravity usage reaches entry.usage when it is settled (settleCodexTurn, settleAntigravityTurn).
|
|
2545
|
+
if (entry.provider !== 'codex' && entry.provider !== 'antigravity') addLegacyUsage(entry, raw.usage);
|
|
2469
2546
|
return added;
|
|
2470
2547
|
}
|
|
2471
2548
|
|
|
@@ -2752,6 +2829,38 @@ export function createAssistantDispatcher({
|
|
|
2752
2829
|
persist({ force: true });
|
|
2753
2830
|
}
|
|
2754
2831
|
|
|
2832
|
+
// Antigravity: each provider turn's answer carries that turn's own ACP usage (the adapter takes it off the
|
|
2833
|
+
// session's running totals; a failed or stopped turn has none, so the next answer carries it). Tokens only:
|
|
2834
|
+
// Antigravity is billed by its Google sign-in and reports no price, so every row is unpriced, never $0.
|
|
2835
|
+
/**
|
|
2836
|
+
* Book one Antigravity provider turn: its five token classes into the ledger (exact when the agent's own total
|
|
2837
|
+
* agrees with one reading of its classes, else partial: 'antigravity-total-mismatch'), its usage in provider keys
|
|
2838
|
+
* into the run's and the turn's legacy totals, and the run marked unpriced. Nothing without usage.
|
|
2839
|
+
*/
|
|
2840
|
+
function settleAntigravityTurn(entry, liveState, raw) {
|
|
2841
|
+
if (entry.usageState?.closed) return;
|
|
2842
|
+
const usage = raw?.usage && typeof raw.usage === 'object' ? raw.usage : null;
|
|
2843
|
+
if (!usage) return;
|
|
2844
|
+
const { tokens, mismatch } = antigravityUsageTokens(usage);
|
|
2845
|
+
if (totalTokens(tokens) === 0) return;
|
|
2846
|
+
const used = { input_tokens: tokens.input, output_tokens: tokens.output, reasoning_tokens: tokens.reasoning, cache_read_input_tokens: tokens.cacheRead, cache_write_input_tokens: tokens.cacheWrite };
|
|
2847
|
+
addLegacyUsage(entry, used);
|
|
2848
|
+
if (liveState) {
|
|
2849
|
+
const prev = liveState.turnUsage || {};
|
|
2850
|
+
liveState.turnUsage = Object.fromEntries(Object.entries(used).map(([key, value]) => [key, (Number(prev[key]) || 0) + value]));
|
|
2851
|
+
}
|
|
2852
|
+
markUnpriced(entry, UNPRICED_PROVIDER_REASON);
|
|
2853
|
+
// No model named: the one the session runs (the adapter reads it off the session's config options).
|
|
2854
|
+
let model = entry.model || null;
|
|
2855
|
+
if (!model) { try { model = liveState?.inner?.describe?.()?.model || null; } catch { model = null; } }
|
|
2856
|
+
settleUsage(entry, {
|
|
2857
|
+
model, part: 'main', tokens, costUsd: null, costBasis: 'unpriced',
|
|
2858
|
+
fidelity: mismatch ? 'partial' : 'exact', reason: mismatch ? 'antigravity-total-mismatch' : null, source: 'antigravity-acp-usage',
|
|
2859
|
+
});
|
|
2860
|
+
// At once, not debounced: a kill must not leave the saved run behind the ledger.
|
|
2861
|
+
persist({ force: true });
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2755
2864
|
// OpenCode: every session of the run's own serve (the root and the `task` tool's child sessions).
|
|
2756
2865
|
function openCodeMeterOf(entry) {
|
|
2757
2866
|
const held = metersOf(entry);
|
|
@@ -3054,6 +3163,7 @@ export function createAssistantDispatcher({
|
|
|
3054
3163
|
entry.suspendReason = String(reason || 'server_shutdown');
|
|
3055
3164
|
delete entry.crashed;
|
|
3056
3165
|
// An open permission request goes with the process: the resumed worker asks again.
|
|
3166
|
+
for (const requestId of [...liveState.pendingResolvers.keys()]) finishPending(entry.runId, requestId, { behavior: 'deny', message: 'Server restarting; ask again after resume.', resolvedBy: 'server_restart' });
|
|
3057
3167
|
entry.pending = [];
|
|
3058
3168
|
// What the cut-off turn spent so far is booked now (an estimate per call the stream showed: no result will
|
|
3059
3169
|
// count them); the meter takes the resumed CLI's counters as its new baseline (rebaseClaudeUsage).
|
|
@@ -3245,14 +3355,14 @@ export function createAssistantDispatcher({
|
|
|
3245
3355
|
const state = runState(entry, {
|
|
3246
3356
|
browserSessionId, browserTabId, desktopGrant,
|
|
3247
3357
|
startedAt: entry.startedAt || iso(now()),
|
|
3248
|
-
extra: { resumeSessionId: entry.providerSessionId, resumeThreadId: entry.providerThreadId, costBaseUsd, resumingWorker: true },
|
|
3358
|
+
extra: { resumeSessionId: entry.providerSessionId, resumeThreadId: entry.providerThreadId, costBaseUsd, resumingWorker: true, antigravityHome: entry.resumeInfo?.antigravityHome || null },
|
|
3249
3359
|
});
|
|
3250
3360
|
// The CLI's spend stop (--max-budget-usd) counts from the total the resumed CLI restores (costBaseUsd): what the run
|
|
3251
3361
|
// may still spend (its ceiling less what it spent, never more than the session has left) goes on top of that. A
|
|
3252
3362
|
// backstop only: the dispatcher holds the run to its ceiling and its share between calls.
|
|
3253
3363
|
const allowanceUsd = claudeRunCap(entry, { spentUsd: entry.costUsd });
|
|
3254
3364
|
if (Number(allowanceUsd) > 0) state.maxBudgetUsd = Number((Number(allowanceUsd) + Math.max(0, Number(costBaseUsd) || 0)).toFixed(2));
|
|
3255
|
-
|
|
3365
|
+
state.permissionBroker = createBroker(entry.runId);
|
|
3256
3366
|
const at = clockTime(entry.suspendedAt);
|
|
3257
3367
|
const selfRestarts = isInside(entry.cwd, PACKAGE_ROOT);
|
|
3258
3368
|
const playbook = mode === 'fresh' ? playbookFor(entry) : null;
|
|
@@ -3511,6 +3621,57 @@ export function createAssistantDispatcher({
|
|
|
3511
3621
|
}
|
|
3512
3622
|
processQueue().catch(() => {});
|
|
3513
3623
|
}
|
|
3624
|
+
/** One transcript line into a live run's ring (bounded by lines and characters). */
|
|
3625
|
+
function pushRingLine(liveState, line) {
|
|
3626
|
+
liveState.ring.push(line);
|
|
3627
|
+
liveState.ringChars += line.length;
|
|
3628
|
+
// The message line still growing is no longer the last one.
|
|
3629
|
+
liveState.messageLine = null;
|
|
3630
|
+
while (liveState.ring.length > limits.ringSize || liveState.ringChars > limits.ringChars) {
|
|
3631
|
+
const removed = liveState.ring.shift();
|
|
3632
|
+
liveState.ringChars -= removed?.length || 0;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* An Antigravity message chunk (ACP agent_message_chunk): the text joins the message's line when that line is
|
|
3637
|
+
* still the ring's last one (same messageId), else it starts a new "assistant:" line. Clipped as every line is.
|
|
3638
|
+
*/
|
|
3639
|
+
function growMessageLine(liveState, update = {}) {
|
|
3640
|
+
const text = update?.content?.type === 'text' ? String(update.content.text || '') : '';
|
|
3641
|
+
if (!text) return;
|
|
3642
|
+
const id = update.messageId ?? null;
|
|
3643
|
+
const open = liveState.messageLine;
|
|
3644
|
+
if (open && open.id === id && liveState.ring.length && liveState.ring[liveState.ring.length - 1] === open.line) {
|
|
3645
|
+
open.text += text;
|
|
3646
|
+
const line = `${open.stamp} assistant: ${clip(open.text, 200)}`;
|
|
3647
|
+
if (line !== open.line) {
|
|
3648
|
+
liveState.ring[liveState.ring.length - 1] = line;
|
|
3649
|
+
liveState.ringChars += line.length - open.line.length;
|
|
3650
|
+
open.line = line;
|
|
3651
|
+
}
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
if (!text.trim()) return;
|
|
3655
|
+
const stamp = iso(now()).slice(11, 19);
|
|
3656
|
+
const line = `${stamp} assistant: ${clip(text, 200)}`;
|
|
3657
|
+
pushRingLine(liveState, line);
|
|
3658
|
+
liveState.messageLine = { id, text, stamp, line };
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* An ACP tool_call_update carries only what changed: the call it updates, as the run has seen it so far, so its
|
|
3662
|
+
* end is said with the call's kind, command and title. Other payloads pass as they are. Bounded per run.
|
|
3663
|
+
*/
|
|
3664
|
+
function withAcpCall(liveState, payload) {
|
|
3665
|
+
if ((payload.eventType !== 'tool_call' && payload.eventType !== 'tool_call_update') || !payload.event?.toolCallId) return payload;
|
|
3666
|
+
const calls = liveState.acpCalls || (liveState.acpCalls = new Map());
|
|
3667
|
+
const id = String(payload.event.toolCallId);
|
|
3668
|
+
const call = { ...(calls.get(id) || {}) };
|
|
3669
|
+
for (const [key, value] of Object.entries(payload.event)) if (value !== undefined && value !== null) call[key] = value;
|
|
3670
|
+
calls.delete(id);
|
|
3671
|
+
calls.set(id, call);
|
|
3672
|
+
if (calls.size > 200) calls.delete(calls.keys().next().value);
|
|
3673
|
+
return { ...payload, event: call };
|
|
3674
|
+
}
|
|
3514
3675
|
function onRuntimeMessage(message) {
|
|
3515
3676
|
const runId = message?.run?.runId;
|
|
3516
3677
|
if (!runId || !entries.has(runId)) return;
|
|
@@ -3529,7 +3690,9 @@ export function createAssistantDispatcher({
|
|
|
3529
3690
|
// trigger reading (lib/skills/eval-trace.js; read with transcript format 'tools').
|
|
3530
3691
|
if (isEvalRun(entry)) { try { recordEvalCalls(liveState, message.event || {}); } catch {} }
|
|
3531
3692
|
if (entry.provider === 'opencode') { try { meterOpenCode(entry, liveState, message.event || {}); } catch (error) { log(runId, 'assistant:meter-error', error?.message || String(error)); } }
|
|
3532
|
-
|
|
3693
|
+
// Antigravity streams each message in small chunks: one transcript line per message, grown as it streams.
|
|
3694
|
+
if (entry.provider === 'antigravity' && message.event?.eventType === 'agent_message_chunk') { growMessageLine(liveState, message.event.event); return; }
|
|
3695
|
+
const summary = summarizeProviderEvent(entry.provider === 'antigravity' ? withAcpCall(liveState, message.event || {}) : message.event || {});
|
|
3533
3696
|
// A guard decision (lib/worker-confinement.js) is also a note of the run: what was refused or warned about,
|
|
3534
3697
|
// with its reason, so the brain and the panel see it without the transcript. The first twenty.
|
|
3535
3698
|
if ((message.event?.eventType || message.event?.event?.type) === 'synabun.confinement') {
|
|
@@ -3537,15 +3700,7 @@ export function createAssistantDispatcher({
|
|
|
3537
3700
|
if (count <= 20) entry.notes.push(`guard ${message.event.event?.action || 'refused'}: ${clip(summary, 300)}`);
|
|
3538
3701
|
else if (count === 21) entry.notes.push('guard: more decisions follow in the transcript');
|
|
3539
3702
|
}
|
|
3540
|
-
if (summary) {
|
|
3541
|
-
const line = `${iso(now()).slice(11, 19)} ${summary}`;
|
|
3542
|
-
liveState.ring.push(line);
|
|
3543
|
-
liveState.ringChars += line.length;
|
|
3544
|
-
while (liveState.ring.length > limits.ringSize || liveState.ringChars > limits.ringChars) {
|
|
3545
|
-
const removed = liveState.ring.shift();
|
|
3546
|
-
liveState.ringChars -= removed?.length || 0;
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3703
|
+
if (summary) pushRingLine(liveState, `${iso(now()).slice(11, 19)} ${summary}`);
|
|
3549
3704
|
}
|
|
3550
3705
|
return;
|
|
3551
3706
|
}
|
|
@@ -3556,13 +3711,16 @@ export function createAssistantDispatcher({
|
|
|
3556
3711
|
persist({ force: true });
|
|
3557
3712
|
emit(entry, 'tools_changed');
|
|
3558
3713
|
}
|
|
3559
|
-
|
|
3714
|
+
const runHome = run.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome || null;
|
|
3715
|
+
entry.runtime = { status: run.status, turnState: run.turnState, stoppedReason: run.stoppedReason, error: run.error, claimedBy: run.claimedBy, providerSessionId: run.providerSessionId, providerThreadId: run.providerThreadId, ...(runHome ? { antigravityHome: runHome } : {}) };
|
|
3560
3716
|
// A new provider identity is saved at once: a crash before the run's next write (its first alive mark comes up to
|
|
3561
3717
|
// a minute later) would otherwise leave no session to resume.
|
|
3562
3718
|
const newIdentity = (run.providerSessionId && run.providerSessionId !== entry.providerSessionId)
|
|
3563
|
-
|| (run.providerThreadId && run.providerThreadId !== entry.providerThreadId)
|
|
3719
|
+
|| (run.providerThreadId && run.providerThreadId !== entry.providerThreadId)
|
|
3720
|
+
|| (run.antigravityHome && run.antigravityHome !== entry.antigravityHome);
|
|
3564
3721
|
if (run.providerSessionId) entry.providerSessionId = run.providerSessionId;
|
|
3565
3722
|
if (run.providerThreadId) entry.providerThreadId = run.providerThreadId;
|
|
3723
|
+
if (run.antigravityHome) entry.antigravityHome = run.antigravityHome;
|
|
3566
3724
|
if (newIdentity) persist({ force: true });
|
|
3567
3725
|
if (message.type === 'sidepanel:run-updated' && message.reason === 'started' && entry.state === 'starting') {
|
|
3568
3726
|
entry.state = 'running';
|