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
|
@@ -16,6 +16,10 @@ import { dirname, resolve } from 'node:path';
|
|
|
16
16
|
import { createClaudeBrain } from './assistant-brains/claude.js';
|
|
17
17
|
import { createCodexBrain } from './assistant-brains/codex.js';
|
|
18
18
|
import { createOpenCodeBrain } from './assistant-brains/opencode.js';
|
|
19
|
+
import { createAntigravityBrain } from './assistant-brains/antigravity.js';
|
|
20
|
+
import { acpPolicyInput, acpReadOnlyKind, acpToolName } from './antigravity/tool-call.js';
|
|
21
|
+
import { antigravityUsageTokens } from './assistant-usage.js';
|
|
22
|
+
import { UNPRICED_PROVIDER_REASON } from './assistant-budget-split.js';
|
|
19
23
|
import { buildAssistantPersona, formatMailbox, reconcileRouteItems } from './assistant-persona.js';
|
|
20
24
|
import { normalizeTestDensity, testDensityStamp } from './assistant-test-density.js';
|
|
21
25
|
import { codexUsageCostUsd, floorCents } from './assistant-budget.js';
|
|
@@ -35,8 +39,9 @@ import { clampBrainModes, clampRouteMode, defaultRemotePolicyRegistry, effective
|
|
|
35
39
|
import { browserToolDenial } from './browser-tool-policy.js';
|
|
36
40
|
import { browserPolicyRoots } from './browser-policy-roots.js';
|
|
37
41
|
import { stripFollowups } from '../public/shared/assistant/asst-followups.js';
|
|
42
|
+
import { readJsonStrict } from './rulesets/fs-safe.js';
|
|
38
43
|
|
|
39
|
-
export const BRAIN_PROVIDERS = new Set(['claude-code', 'codex', 'opencode']);
|
|
44
|
+
export const BRAIN_PROVIDERS = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
|
|
40
45
|
// Every tab is called SynaBun; the session title (the history list) comes from
|
|
41
46
|
// the first prompt. Legacy "Assistant N" titles still count as unnamed.
|
|
42
47
|
export const DEFAULT_SESSION_TITLE = 'SynaBun';
|
|
@@ -135,21 +140,33 @@ function readJson(path, fallback) { try { return JSON.parse(readFileSync(path, '
|
|
|
135
140
|
function writeJsonAtomic(path, value) {
|
|
136
141
|
mkdirSync(dirname(path), { recursive: true });
|
|
137
142
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
try {
|
|
144
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
145
|
+
renameSync(tmp, path);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
try { rmSync(tmp, { force: true }); } catch { /* held too: the next write replaces it */ }
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
140
150
|
}
|
|
151
|
+
// What Windows answers, now and then, to a rename over a file another process holds open for a
|
|
152
|
+
// moment (an antivirus or indexer scanning it, a backup, the desktop app's pre-update snapshot).
|
|
153
|
+
const HELD_FILE_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
|
|
154
|
+
// A write of the sessions index refused that way is tried again after these waits.
|
|
155
|
+
const INDEX_RETRY_MS = [200, 500, 1000, 2000, 5000, 10_000];
|
|
141
156
|
function clip(value, max = 200) { const text = String(value ?? '').replace(/\s+/g, ' ').trim(); return text.length > max ? `${text.slice(0, max - 1)}…` : text; }
|
|
142
157
|
|
|
143
158
|
/**
|
|
144
159
|
* A brain effort checked against its model (effort-levels.js). "off" (or a
|
|
145
160
|
* model with no levels) stays "off" for Claude and Codex, which reset to the
|
|
146
|
-
* model's default on it; OpenCode has no such variant
|
|
161
|
+
* model's default on it; OpenCode has no such variant and Antigravity's thinking
|
|
162
|
+
* level no "off" (its session keeps the model's own level), so they get null.
|
|
147
163
|
*/
|
|
148
164
|
export function checkBrainEffort(provider, model, effort, catalog) {
|
|
149
165
|
if (!effort) return null;
|
|
166
|
+
if (provider === 'antigravity' && /^(?:off|default|auto|none)$/i.test(String(effort))) return null;
|
|
150
167
|
if (!catalog) return String(effort);
|
|
151
168
|
const next = normalizeEffort({ provider, model, effort, catalog }).effort;
|
|
152
|
-
return next || (provider === 'opencode' ? null : 'off');
|
|
169
|
+
return next || (provider === 'opencode' || provider === 'antigravity' ? null : 'off');
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
/** `catalog` (a catalog value) checks the effort against the brain's model (effort-levels.js). */
|
|
@@ -164,7 +181,8 @@ export function normalizeBrain(input = {}, defaults = {}, { catalog = null, cust
|
|
|
164
181
|
model,
|
|
165
182
|
effort: checkBrainEffort(provider, model, effort, catalog),
|
|
166
183
|
agent: input?.agent ? String(input.agent) : null,
|
|
167
|
-
|
|
184
|
+
// OpenCode and Antigravity sign in inside their own CLI: SynaBun keeps no account of theirs.
|
|
185
|
+
accountId: provider === 'antigravity' ? null : input?.accountId ? String(input.accountId) : (provider === 'opencode' ? null : 'default'),
|
|
168
186
|
cwd: input?.cwd ? String(input.cwd) : (input?.project ? String(input.project) : (defaults.cwd || null)),
|
|
169
187
|
tools: provider === 'opencode' ? (storedToolList(input, { customPresets }) ?? storedToolList(defaults, { customPresets }) ?? [...OPENCODE_BRAIN_TOOLS]) : ['all'],
|
|
170
188
|
permissionMode: input?.permissionMode && !legacyPlan ? String(input.permissionMode) : 'default',
|
|
@@ -206,6 +224,10 @@ export function createAssistantRuntime({
|
|
|
206
224
|
ensureIsolatedServe = null,
|
|
207
225
|
stopIsolatedServe = null,
|
|
208
226
|
setupOpencodeSidepanelConfig = null,
|
|
227
|
+
// Antigravity brains (assistant-brains/antigravity.js): the CLI's launch spec, the environment its process gets,
|
|
228
|
+
// the brain's home (lib/antigravity/home.js), SynaBun's own MCP entry there, and the policy registry and loopback
|
|
229
|
+
// address its hook asks (lib/antigravity/policy.js).
|
|
230
|
+
antigravity = null,
|
|
209
231
|
broadcastSync = () => {},
|
|
210
232
|
dataDir,
|
|
211
233
|
memory = null,
|
|
@@ -322,12 +344,43 @@ export function createAssistantRuntime({
|
|
|
322
344
|
|| !!row.remote || !!row.channel || Number(row.costUsd) > 0;
|
|
323
345
|
return holds ? 'unstarted' : 'empty';
|
|
324
346
|
}
|
|
347
|
+
// Whether the index file may be written: it was read, or there was none. A file that could not be
|
|
348
|
+
// read (Windows: held by an antivirus or a backup) is never replaced by what this process holds;
|
|
349
|
+
// reading it is tried again (INDEX_RETRY_MS), and sessions made meanwhile join what it held.
|
|
350
|
+
let indexReady = false;
|
|
351
|
+
let indexReadTries = 0;
|
|
352
|
+
let indexBlockedLogged = false;
|
|
353
|
+
function readIndexRows() {
|
|
354
|
+
try {
|
|
355
|
+
const rows = readJsonStrict(indexPath);
|
|
356
|
+
if (Array.isArray(rows)) return rows;
|
|
357
|
+
const error = new Error(`${indexPath} does not hold a list`);
|
|
358
|
+
error.code = 'INVALID_JSON';
|
|
359
|
+
throw error;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (error?.code === 'ENOENT') return [];
|
|
362
|
+
if (error?.code !== 'INVALID_JSON') throw error;
|
|
363
|
+
// A corrupt file is moved aside, never deleted, and the index starts again from what is live.
|
|
364
|
+
const aside = `${indexPath}.corrupt-${Date.now()}`;
|
|
365
|
+
renameSync(indexPath, aside);
|
|
366
|
+
log('assistant:index-corrupt', `${error.message}; moved to ${aside}`);
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
325
370
|
function loadIndex() {
|
|
326
|
-
|
|
327
|
-
|
|
371
|
+
let rows;
|
|
372
|
+
try { rows = readIndexRows(); } catch (error) {
|
|
373
|
+
log('assistant:index-unreadable', `${error?.code || ''} ${error?.message || String(error)}`.trim());
|
|
374
|
+
if (indexReadTries < INDEX_RETRY_MS.length) {
|
|
375
|
+
setTimeout(loadIndex, INDEX_RETRY_MS[indexReadTries++]).unref?.();
|
|
376
|
+
} else log('assistant:index-unreadable', `${indexPath} is left as it is; sessions of this run stay in memory only, until a restart reads it`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
indexReady = true;
|
|
380
|
+
const late = sessions.size > 0; // sessions made while the file could not be read
|
|
328
381
|
let dropped = 0;
|
|
329
382
|
for (const row of rows) {
|
|
330
|
-
if (!row?.id) continue;
|
|
383
|
+
if (!row?.id || sessions.has(row.id)) continue;
|
|
331
384
|
try { row.brain = normalizeSessionBrain(row.brain); }
|
|
332
385
|
catch (error) { row.brain = { ...row.brain, tools: [], toolListError: { code: error.code, message: error.message, original: row.brain?.tools ?? row.brain?.mcpProfile } }; delete row.brain.mcpProfile; }
|
|
333
386
|
if (settleLegacyRow(row) === 'empty') { dropped += 1; continue; }
|
|
@@ -339,16 +392,34 @@ export function createAssistantRuntime({
|
|
|
339
392
|
}
|
|
340
393
|
}
|
|
341
394
|
// The index is written again without them (and with the started marks).
|
|
342
|
-
if (dropped)
|
|
395
|
+
if (dropped) log('assistant:index', `${dropped} session record(s) without a conversation dropped`);
|
|
396
|
+
if (dropped || late) persist();
|
|
343
397
|
}
|
|
344
398
|
let persistTimer = null;
|
|
399
|
+
let persistRetries = 0; // index writes Windows refused in a row (INDEX_RETRY_MS)
|
|
345
400
|
function persist({ force = false } = {}) {
|
|
346
401
|
const write = () => {
|
|
402
|
+
if (!indexReady) {
|
|
403
|
+
if (!indexBlockedLogged) { indexBlockedLogged = true; log('assistant:index', 'not written: the sessions index has not been read'); }
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
347
406
|
// A draft is never written: it exists on disk only once its first prompt was sent (startChat).
|
|
348
407
|
const rows = [...sessions.values()].filter((live) => !live.draft).map((live) => publicRecord(live.record, { withLive: false, persisted: true }))
|
|
349
408
|
.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')))
|
|
350
409
|
.slice(0, settings.retention);
|
|
351
|
-
try {
|
|
410
|
+
try {
|
|
411
|
+
writeJsonAtomic(indexPath, rows);
|
|
412
|
+
persistRetries = 0;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
log('assistant:index-error', error?.message || String(error));
|
|
415
|
+
// The records changed (a rename already answered as saved, among them) and the file did not: on
|
|
416
|
+
// Windows it is written again shortly, with whatever the records hold by then, instead of waiting
|
|
417
|
+
// for the next change (a restart before it would bring the old records back).
|
|
418
|
+
if (process.platform === 'win32' && HELD_FILE_CODES.has(error?.code) && persistRetries < INDEX_RETRY_MS.length && !persistTimer) {
|
|
419
|
+
persistTimer = setTimeout(() => { persistTimer = null; write(); }, INDEX_RETRY_MS[persistRetries++]);
|
|
420
|
+
persistTimer.unref?.();
|
|
421
|
+
} else persistRetries = 0;
|
|
422
|
+
}
|
|
352
423
|
};
|
|
353
424
|
if (force) { if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; } write(); return; }
|
|
354
425
|
if (persistTimer) return;
|
|
@@ -1827,6 +1898,8 @@ export function createAssistantRuntime({
|
|
|
1827
1898
|
function brainCostBasis(live) {
|
|
1828
1899
|
const brain = live.record.brain || {};
|
|
1829
1900
|
if (brain.provider === 'claude-code') return 'reported';
|
|
1901
|
+
// Antigravity reports tokens, not prices: its spend has no dollar figure.
|
|
1902
|
+
if (brain.provider === 'antigravity') return null;
|
|
1830
1903
|
const info = brainCapabilities(live.record) || {};
|
|
1831
1904
|
if (brain.provider === 'codex') {
|
|
1832
1905
|
if (!pricing?.codexPrice) return null;
|
|
@@ -1868,6 +1941,10 @@ export function createAssistantRuntime({
|
|
|
1868
1941
|
const money = sessionMoney(live.record.id);
|
|
1869
1942
|
if (!money || money.unlimited) return null;
|
|
1870
1943
|
// Fail closed: a brain whose spend has no price could spend past the budget.
|
|
1944
|
+
// Antigravity has no price at all (whatever the pricing tables): a dollar budget cannot hold its turns.
|
|
1945
|
+
if (live.record.brain?.provider === 'antigravity') {
|
|
1946
|
+
return { code: 'BRAIN_UNPRICED', message: `${UNPRICED_PROVIDER_REASON}, so the session budget cannot hold an Antigravity brain's spend. Switch the budget to No limit in ⋯ → Budget…, or pick another provider for the brain.` };
|
|
1947
|
+
}
|
|
1871
1948
|
if (pricing && brainCostBasis(live) === null) {
|
|
1872
1949
|
const model = live.record.brain?.model || brainCapabilities(live.record)?.id || 'its default model';
|
|
1873
1950
|
return { code: 'BRAIN_UNPRICED', message: `No price is known for the ${live.record.brain?.provider || ''} brain model ${model}, so the session budget cannot hold its spend. Pick a priced or free model for the brain, or switch the budget to No limit.` };
|
|
@@ -2055,6 +2132,8 @@ export function createAssistantRuntime({
|
|
|
2055
2132
|
remoteReadOnly: !!remote && remoteBrainLimited(brain.provider),
|
|
2056
2133
|
// The engine keeps this conversation's thread record: pre-compact.mjs asks for no conversation memory.
|
|
2057
2134
|
userContextThreads: brain.provider === 'claude-code' && userSuffix.nudges === true,
|
|
2135
|
+
// Antigravity (assistant-brains/antigravity.js): where it runs, and what its policy hook asks of this session.
|
|
2136
|
+
...(brain.provider === 'antigravity' ? antigravityBrainDeps(live) : {}),
|
|
2058
2137
|
};
|
|
2059
2138
|
const gate = ensureGate(live);
|
|
2060
2139
|
// A remote session's hook / plugin refuses a call it could not check (fail closed).
|
|
@@ -2064,7 +2143,7 @@ export function createAssistantRuntime({
|
|
|
2064
2143
|
? () => codexGateBootstrap({ url: gateUrl, session: live.record.id, token: live.gateToken, codexBin: typeof codexBin === 'function' ? codexBin() : codexBin, ...(remote ? { remote: true } : {}) })
|
|
2065
2144
|
: async () => null;
|
|
2066
2145
|
}
|
|
2067
|
-
const factories = brainFactories || { 'claude-code': createClaudeBrain, codex: createCodexBrain, opencode: createOpenCodeBrain };
|
|
2146
|
+
const factories = brainFactories || { 'claude-code': createClaudeBrain, codex: createCodexBrain, opencode: createOpenCodeBrain, antigravity: createAntigravityBrain };
|
|
2068
2147
|
const factory = factories[brain.provider];
|
|
2069
2148
|
if (!factory) throw new Error(`Unsupported brain provider ${brain.provider}`);
|
|
2070
2149
|
const memoryHooks = brain.provider === 'claude-code' && memory?.claudeHooks ? memory.claudeHooks({
|
|
@@ -2715,6 +2794,7 @@ export function createAssistantRuntime({
|
|
|
2715
2794
|
if (message.type === 'assistant:permission-request') {
|
|
2716
2795
|
sendToSockets(live, { type: 'event', event: { type: 'synabun.dispatch_control_request', runId: message.runId, request_id: message.request?.requestId, provider: dispatcher?.get?.(message.runId)?.provider || null, request: message.request } });
|
|
2717
2796
|
} else if (message.type === 'assistant:permission-resolved') {
|
|
2797
|
+
live.mailbox = live.mailbox.filter((item) => !(item.run?.runId === message.runId && item.request?.requestId === message.requestId));
|
|
2718
2798
|
sendToSockets(live, { type: 'event', event: { type: 'synabun.dispatch_control_resolved', runId: message.runId, request_id: message.requestId, resolvedBy: message.resolvedBy, behavior: message.behavior } });
|
|
2719
2799
|
}
|
|
2720
2800
|
}
|
|
@@ -3693,6 +3773,69 @@ export function createAssistantRuntime({
|
|
|
3693
3773
|
const decision = decide(live, tool);
|
|
3694
3774
|
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason, refusals: decision.refusals, stopped: !!decision.abort };
|
|
3695
3775
|
}
|
|
3776
|
+
/**
|
|
3777
|
+
* An Antigravity brain's tool call (its policy hook, lib/antigravity/policy.js), checked in this process the way
|
|
3778
|
+
* gateCheck checks the OpenCode plugin's and the Codex hook's: plan mode, a remote session's read-only rules,
|
|
3779
|
+
* the browser policy (every session, plan mode and subagents included), then the route gate. → { decision:
|
|
3780
|
+
* 'deny', reason } or null (no objection: the brain's approval mode decides). `call`: lib/antigravity/tool-call.js
|
|
3781
|
+
* acpCall. Antigravity's own bookkeeping steps (task boundaries, notes to the user) are never routed.
|
|
3782
|
+
*/
|
|
3783
|
+
function antigravityGateCheck(live, call) {
|
|
3784
|
+
const name = acpToolName(call);
|
|
3785
|
+
const input = acpPolicyInput(call);
|
|
3786
|
+
const planned = planGate(live, 'antigravity', name, input);
|
|
3787
|
+
if (planned) return { decision: 'deny', reason: planned.reason };
|
|
3788
|
+
// A remote (WhatsApp) session's Antigravity brain is read-only for good, at every level.
|
|
3789
|
+
if (live.brainRemoteReadOnly && readOnlyToolDecision(name, input, { host: 'antigravity' }) === 'deny') {
|
|
3790
|
+
log('assistant:remote-policy', `${live.record.id}: refused ${name} (read-only)`);
|
|
3791
|
+
return { decision: 'deny', reason: `${readOnlyDenyMessage(name)} Tell the user to do this in SynaBun on their computer if they need it.` };
|
|
3792
|
+
}
|
|
3793
|
+
if (call.mcp) {
|
|
3794
|
+
const remoteDenied = remoteGate(live, name, input, { argsComplete: true, host: 'antigravity' });
|
|
3795
|
+
if (remoteDenied) return { decision: 'deny', reason: remoteDenied.reason };
|
|
3796
|
+
}
|
|
3797
|
+
const browserDenied = browserGate(live, name, input, { host: 'antigravity' });
|
|
3798
|
+
if (browserDenied) return { decision: 'deny', reason: browserDenied.reason };
|
|
3799
|
+
if (!live.gate || call.kind === 'think') return null;
|
|
3800
|
+
const decision = decide(live, name);
|
|
3801
|
+
return decision.allow ? null : { decision: 'deny', reason: decision.reason };
|
|
3802
|
+
}
|
|
3803
|
+
/** What an Antigravity brain needs from this session (assistant-brains/antigravity.js deps). */
|
|
3804
|
+
function antigravityBrainDeps(live) {
|
|
3805
|
+
const ag = antigravity || {};
|
|
3806
|
+
const saved = live.record.usageState?.antigravity;
|
|
3807
|
+
return {
|
|
3808
|
+
antigravityLaunch: typeof ag.launch === 'function' ? ag.launch : () => null,
|
|
3809
|
+
childEnv: typeof ag.childEnv === 'function' ? ag.childEnv : undefined,
|
|
3810
|
+
antigravityHome: typeof ag.home === 'function' ? (sessionId) => ag.home(sessionId) : null,
|
|
3811
|
+
synabunMcpEntry: typeof ag.synabunMcpEntry === 'function' ? ag.synabunMcpEntry : null,
|
|
3812
|
+
policy: ag.policy || null,
|
|
3813
|
+
policyUrl: typeof ag.policyUrl === 'function' ? ag.policyUrl() : (ag.policyUrl || null),
|
|
3814
|
+
connect: ag.connect || undefined,
|
|
3815
|
+
userGemini: ag.userGemini || undefined,
|
|
3816
|
+
policyCheck: (call) => antigravityGateCheck(live, call),
|
|
3817
|
+
// ACP counts tokens per session: what the session the record names had counted (a resume books only what follows).
|
|
3818
|
+
sessionUsageBase: saved && saved.sessionId && saved.sessionId === live.record.providerSessionId ? saved.usage || null : null,
|
|
3819
|
+
onAntigravitySession: (sessionId) => { if (sessionId && live.record.providerSessionId !== sessionId) { live.record.providerSessionId = sessionId; touch(live); } },
|
|
3820
|
+
onAntigravitySessionUsage: (sessionId, total) => { live.record.usageState ||= {}; live.record.usageState.antigravity = { sessionId, usage: total }; },
|
|
3821
|
+
onAntigravityUsage: (info) => settleAntigravityBrain(live, info),
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
/** One Antigravity brain turn's tokens into the ledger: unpriced (tokens, not prices), exact when its total agrees. */
|
|
3825
|
+
function settleAntigravityBrain(live, { usage: turnUsage = null, model = null } = {}) {
|
|
3826
|
+
if (!usage || !turnUsage) return;
|
|
3827
|
+
metered('antigravity-settle', () => {
|
|
3828
|
+
const { tokens, mismatch } = antigravityUsageTokens(turnUsage);
|
|
3829
|
+
if (totalTokens(tokens) === 0) return;
|
|
3830
|
+
const taskId = currentTask(live.record.id) || ensureTaskId(live.record.id);
|
|
3831
|
+
usage.settle({
|
|
3832
|
+
sessionId: live.record.id, taskId, scope: 'brain', runId: null, turn: live.record.turns, provider: 'antigravity',
|
|
3833
|
+
model: model || live.record.brain?.model || null, part: 'main', tokens, costUsd: null, costBasis: 'unpriced',
|
|
3834
|
+
fidelity: mismatch ? 'partial' : 'exact', reason: mismatch ? 'antigravity-total-mismatch' : null, source: 'antigravity-acp-usage',
|
|
3835
|
+
});
|
|
3836
|
+
});
|
|
3837
|
+
persist({ force: true });
|
|
3838
|
+
}
|
|
3696
3839
|
/**
|
|
3697
3840
|
* A remote session's hard refusal for one tool call (every level), else null.
|
|
3698
3841
|
* `argsComplete: false`: the host did not pass the call's arguments. An
|
|
@@ -3913,6 +4056,9 @@ export function createAssistantRuntime({
|
|
|
3913
4056
|
return {
|
|
3914
4057
|
handleWebSocket, listSessions, getSession, createSession, updateSession, closeSession, destroySession, removeSession,
|
|
3915
4058
|
resolveTerminal, accountInUse, ownsIsolatedServe, noteDispatch, subscribe, shutdown,
|
|
4059
|
+
// The sessions index written now, synchronously: the server's exit handler runs it on every way out,
|
|
4060
|
+
// since an exit through process.exit() never reaches shutdown()'s own write.
|
|
4061
|
+
flushIndex: () => persist({ force: true }),
|
|
3916
4062
|
observeBroadcast: onPermissionBroadcast,
|
|
3917
4063
|
testDensityFor, routerSession, routerSend, routerEvent, routerMailbox, routerContinue, routerSinks, routerRouted, routerRouteFailed, routerComputerApproved, gateCheck, isPlanning, isPlanReadOnly,
|
|
3918
4064
|
clarifySession, clarifyEvent, clarifyGate, clarifySinks,
|
|
@@ -25,6 +25,7 @@ const PROVIDER_LABELS = {
|
|
|
25
25
|
'claude-code': 'Claude Code',
|
|
26
26
|
codex: 'Codex',
|
|
27
27
|
opencode: 'OpenCode',
|
|
28
|
+
antigravity: 'Antigravity',
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
function providerLabel(provider) {
|
|
@@ -57,7 +58,7 @@ function topKeywords(task, title) {
|
|
|
57
58
|
/** The provider's host name for browserPolicyText (its own web tools). */
|
|
58
59
|
function policyHost(provider) {
|
|
59
60
|
const value = String(provider || '').toLowerCase();
|
|
60
|
-
return value === 'claude-code' ? 'claude' : value === 'codex' || value === 'opencode' ? value : null;
|
|
61
|
+
return value === 'claude-code' ? 'claude' : value === 'codex' || value === 'opencode' || value === 'antigravity' ? value : null;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/**
|
|
@@ -197,12 +198,12 @@ export function styleGuideBlock(state = {}) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
function policyLine(policy) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
+ '
|
|
201
|
+
const permissions = String(policy) === 'ask'
|
|
202
|
+
? 'Permission requests are answered by the orchestrator or the user; keep them minimal and batch related actions. '
|
|
203
|
+
: 'Continue autonomously within your permissions. Make reasonable assumptions and state them in your result. ';
|
|
204
|
+
return permissions + 'Ask only when genuinely blocked on a decision only the user can make. Use your question tool, never prose or a final needs_input result to ask: the user answers in this run’s live sidepanel and this SAME turn continues. '
|
|
205
|
+
+ 'Claude Code: load the deferred AskUserQuestion schema with ToolSearch (select:AskUserQuestion) before calling it. Codex: use SynaBun choice in Default mode (request_user_input only in Plan mode). OpenCode: use the native question tool. Antigravity: use SynaBun choice through ACP form elicitation. '
|
|
206
|
+
+ 'Do not enter plan mode just to ask. While a question is pending, wait for its tool result; do not end the turn or re-dispatch. If no question tool works, report blocked with the missing capability.';
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
/**
|
|
@@ -223,7 +224,7 @@ function capabilityLine(capability, state = {}) {
|
|
|
223
224
|
case 'read-only':
|
|
224
225
|
return 'CAPABILITY: read-only. You may read, search and analyze anywhere; you must not modify files, run state-changing shell commands, or publish anything. The shell runs read-only commands only (rg, grep, ls, cat, head, tail, find, stat, diff, jq, git log / diff / status / show / blame, node --check, npm ls, curl without an output file); programs, test runners and writes to files are not available.' + enforced;
|
|
225
226
|
case 'workspace':
|
|
226
|
-
return `CAPABILITY: workspace. You may edit files inside the working directory${scratch ? `, the OS temp folder and your scratch folder (${scratch})` : ' and the OS temp folder'} and run the commands the task needs; nothing is written anywhere else (not under the home folder, another project or a tool's configuration such as ~/.claude, ~/.codex, ~/.agents, ~/.config/opencode or ~/.synabun). Shell commands that write elsewhere (redirections, cp, mv, rm, ln, tee, sed -i, git -C another folder, a global install) are refused.` + enforced;
|
|
227
|
+
return `CAPABILITY: workspace. You may edit files inside the working directory${scratch ? `, the OS temp folder and your scratch folder (${scratch})` : ' and the OS temp folder'} and run the commands the task needs; nothing is written anywhere else (not under the home folder, another project or a tool's configuration such as ~/.claude, ~/.codex, ~/.agents, ~/.config/opencode, ~/.gemini or ~/.synabun). Shell commands that write elsewhere (redirections, cp, mv, rm, ln, tee, sed -i, git -C another folder, a global install) are refused.` + enforced;
|
|
227
228
|
default:
|
|
228
229
|
return 'CAPABILITY: full access as configured by the user.';
|
|
229
230
|
}
|
|
@@ -418,6 +419,7 @@ export function buildResumePrompt(state = {}, { at = '', selfRestarts = false }
|
|
|
418
419
|
const lines = [
|
|
419
420
|
`SERVER RESTART (same run ${runId}, project "${project}"): SynaBun's server restarted${clean(at) ? ` at ${clean(at)}` : ''} while you were working on this task, and this conversation was resumed in a new process.`,
|
|
420
421
|
'- A tool call that was running then was cut off: its result is lost, and a command it started may or may not have finished.',
|
|
422
|
+
'- A question pending before the restart was cancelled with its live tool call, not answered. If it is still needed, ask again with the question tool; never infer an answer from the cancellation.',
|
|
421
423
|
'- Check the actual state before you go on instead of assuming it: git status and git log, the files you were changing, test or build output, processes you started.',
|
|
422
424
|
'- Do not repeat a side effect that already happened (a commit, a push, a message or post sent, a memory stored, a deploy): check first.',
|
|
423
425
|
];
|
|
@@ -35,7 +35,7 @@ export function launchTools(selection) {
|
|
|
35
35
|
export function toolsView(entry) {
|
|
36
36
|
const selected = dispatchTools({ tools: entry.toolsRequested ?? undefined, provider: entry.provider, usesBrowser: entry.usesBrowser, taskClass: entry.taskClass });
|
|
37
37
|
const current = requireToolList(resolveToolList(entry.tools ?? launchTools(selected), { source: 'runtime', provider: entry.provider }));
|
|
38
|
-
const label = entry.provider === 'claude-code' ? 'Claude Code' : entry.provider === 'codex' ? 'Codex' : entry.provider || 'This worker';
|
|
38
|
+
const label = entry.provider === 'claude-code' ? 'Claude Code' : entry.provider === 'codex' ? 'Codex' : entry.provider === 'opencode' ? 'OpenCode' : entry.provider === 'antigravity' ? 'Antigravity' : entry.provider || 'This worker';
|
|
39
39
|
return { requested: entry.toolsRequested ?? null, groups: current.groups, added: selected.added, effect: current.effect, count: current.count, servers: entry.externalServers?.names || [], disabledServers: entry.externalServers?.disabled || [],
|
|
40
40
|
note: current.effect === 'focus' ? `${label} reaches every SynaBun tool on demand; this list is its focus.` : 'Loads exactly these SynaBun groups, plus always-on tools and role/grant tools.' };
|
|
41
41
|
}
|
|
@@ -47,5 +47,5 @@ export function dispatchInput(body) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function workerToolsLine(catalog = toolCatalogView()) {
|
|
50
|
-
return `SynaBun tools for workers: pass tools: [group ids] on agent_dispatch. Always there: ${catalog.alwaysOnTools.join(', ')}. With no tools a worker gets ${catalog.default.join(', ')}. Groups: ${catalog.groups.map(group => `${group.id} (${group.count})`).join(' · ')}. Host effect: ${Object.entries(catalog.effect).map(([host, effect]) => `${host}: ${effect}`).join(', ')}. Strict hosts load exactly those groups; Claude Code and Codex always reach every SynaBun tool on demand and the list only sets their focus. Never assume a Claude Code or Codex worker lacks a group you left out. Your MCP servers: name them as mcp:<name> in tools (or mcp:* for all allowed servers). Every unnamed external server is off for that worker on
|
|
50
|
+
return `SynaBun tools for workers: pass tools: [group ids] on agent_dispatch. Always there: ${catalog.alwaysOnTools.join(', ')}. With no tools a worker gets ${catalog.default.join(', ')}. Groups: ${catalog.groups.map(group => `${group.id} (${group.count})`).join(' · ')}. Host effect: ${Object.entries(catalog.effect).map(([host, effect]) => `${host}: ${effect}`).join(', ')}. Strict hosts load exactly those groups; Claude Code and Codex always reach every SynaBun tool on demand and the list only sets their focus. Never assume a Claude Code or Codex worker lacks a group you left out. Your MCP servers: name them as mcp:<name> in tools (or mcp:* for all allowed servers). Every unnamed external server is off for that worker on Claude Code, Codex, OpenCode and Antigravity. Available server refs: ${(catalog.servers || []).filter(row => row.selectable !== false).map(row => row.ref).join(', ') || 'none listed'}.`;
|
|
51
51
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// claudeModelUsageTokens(entry) one SDK ModelUsage entry
|
|
21
21
|
// codexUsageTokens(usage) → { tokens, mismatch } (Codex input includes cached, output includes reasoning)
|
|
22
22
|
// openCodeTokens(tokens) already five classes
|
|
23
|
+
// antigravityUsageTokens(usage) → { tokens, mismatch } (an ACP usage block; which classes nest is read off its total)
|
|
23
24
|
//
|
|
24
25
|
// 2. METERS one per provider session. Each keeps a small JSON-safe state()
|
|
25
26
|
// the caller persists and hands back as { state }.
|
|
@@ -203,6 +204,34 @@ export function codexUsageTokens(usage) {
|
|
|
203
204
|
return { tokens, mismatch: raw.total_tokens !== null && raw.total_tokens !== totalTokens(tokens) };
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Antigravity usage: the ACP `usage` block of a prompt answer ({ inputTokens, outputTokens, thoughtTokens,
|
|
209
|
+
* cachedReadTokens, cachedWriteTokens, totalTokens }, camelCase or snake_case). ACP defines totalTokens as the sum
|
|
210
|
+
* of every type, so the classes are taken as disjoint; when the agent's own total says the cached tokens sit inside
|
|
211
|
+
* the input, or the thought tokens inside the output (the Gemini API's own convention), they are taken out. Which
|
|
212
|
+
* reading applied is the one whose sum is the total; `mismatch` when none is.
|
|
213
|
+
* @returns {{ tokens: object, mismatch: boolean }}
|
|
214
|
+
*/
|
|
215
|
+
export function antigravityUsageTokens(usage) {
|
|
216
|
+
const u = usage && typeof usage === 'object' ? usage : {};
|
|
217
|
+
const input = count(u.inputTokens ?? u.input_tokens);
|
|
218
|
+
const output = count(u.outputTokens ?? u.output_tokens);
|
|
219
|
+
const thought = count(u.thoughtTokens ?? u.thought_tokens ?? u.thinking_tokens ?? u.reasoningTokens);
|
|
220
|
+
const cacheRead = count(u.cachedReadTokens ?? u.cached_read_tokens ?? u.cache_read_tokens);
|
|
221
|
+
const cacheWrite = count(u.cachedWriteTokens ?? u.cached_write_tokens ?? u.cache_write_tokens);
|
|
222
|
+
const rawTotal = u.totalTokens ?? u.total_tokens;
|
|
223
|
+
const total = rawTotal === undefined || rawTotal === null || rawTotal === '' || !Number.isFinite(Number(rawTotal)) ? null : count(rawTotal);
|
|
224
|
+
const disjoint = { input, cacheWrite, cacheRead, output, reasoning: thought };
|
|
225
|
+
if (total === null) return { tokens: disjoint, mismatch: false };
|
|
226
|
+
const cachedInside = { ...disjoint, input: Math.max(0, input - cacheRead - cacheWrite) };
|
|
227
|
+
const thoughtInside = { ...disjoint, output: Math.max(0, output - thought) };
|
|
228
|
+
const bothInside = { ...cachedInside, output: thoughtInside.output };
|
|
229
|
+
for (const reading of [disjoint, cachedInside, thoughtInside, bothInside]) {
|
|
230
|
+
if (totalTokens(reading) === total) return { tokens: reading, mismatch: false };
|
|
231
|
+
}
|
|
232
|
+
return { tokens: disjoint, mismatch: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
206
235
|
/** OpenCode message tokens ({ input, output, reasoning, cache:{ read, write } }): already five additive classes. */
|
|
207
236
|
export function openCodeTokens(tokens) {
|
|
208
237
|
const t = tokens && typeof tokens === 'object' ? tokens : {};
|
|
@@ -38,6 +38,11 @@ export const DEFAULT_BACKUP_RETENTION = Object.freeze({
|
|
|
38
38
|
dailyDays: 30,
|
|
39
39
|
weeklyWeeks: 12,
|
|
40
40
|
maxBytes: 10 * 1024 * 1024 * 1024,
|
|
41
|
+
// How many pre-update/pre-migration snapshots this generic sweep keeps in a shared folder; the
|
|
42
|
+
// dedicated pruner for the updater's own folder (lib/update-snapshot.js prunePreUpdateSnapshots)
|
|
43
|
+
// reads the same retention.preUpdateKeep through the settings mechanism (GET/PUT
|
|
44
|
+
// /api/system/auto-backup), with its own PRE_UPDATE_KEEP_ENV override.
|
|
45
|
+
preUpdateKeep: 1,
|
|
41
46
|
});
|
|
42
47
|
|
|
43
48
|
/** The DATA_HOME folders every backup copies (createVerifiedBackup); DATA_HOME/.env goes too, as a file. */
|
|
@@ -1322,7 +1327,7 @@ export function applyBackupRetention({
|
|
|
1322
1327
|
keep.add(latestPreUpdate.path);
|
|
1323
1328
|
protectedPaths.add(latestPreUpdate.path);
|
|
1324
1329
|
}
|
|
1325
|
-
for (const item of snapshots.filter(item => ['pre-update', 'pre-migration'].includes(item.kind)).slice(0,
|
|
1330
|
+
for (const item of snapshots.filter(item => ['pre-update', 'pre-migration'].includes(item.kind)).slice(0, Math.max(0, Number(policy.preUpdateKeep) || 0))) keep.add(item.path);
|
|
1326
1331
|
|
|
1327
1332
|
const daily = new Set();
|
|
1328
1333
|
const weekly = new Set();
|
|
@@ -5,9 +5,10 @@ import { EventEmitter } from 'node:events';
|
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
6
|
|
|
7
7
|
export class GuardCdpConnection extends EventEmitter {
|
|
8
|
-
constructor(socket) {
|
|
8
|
+
constructor(socket, { timeoutMs = 5000 } = {}) {
|
|
9
9
|
super();
|
|
10
10
|
this.socket = socket;
|
|
11
|
+
this.timeoutMs = timeoutMs;
|
|
11
12
|
this.pending = new Map();
|
|
12
13
|
this.sequence = 0;
|
|
13
14
|
this.closed = false;
|
|
@@ -33,7 +34,7 @@ export class GuardCdpConnection extends EventEmitter {
|
|
|
33
34
|
if (this.closed || this.closing) return Promise.reject(new Error('Network guard CDP transport closed'));
|
|
34
35
|
const id = ++this.sequence;
|
|
35
36
|
return new Promise((resolve, reject) => {
|
|
36
|
-
const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Network guard: ${method} timed out`)); },
|
|
37
|
+
const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Network guard: ${method} timed out`)); }, this.timeoutMs);
|
|
37
38
|
this.pending.set(id, { resolve, reject, timer, sessionId, method });
|
|
38
39
|
const failed = error => {
|
|
39
40
|
if (!error) return;
|
|
@@ -67,7 +68,7 @@ export class GuardCdpConnection extends EventEmitter {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export async function connectGuardCdp(endpoint) {
|
|
71
|
+
export async function connectGuardCdp(endpoint, { timeoutMs } = {}) {
|
|
71
72
|
let address = endpoint;
|
|
72
73
|
if (!/^wss?:\/\//.test(address)) {
|
|
73
74
|
// The owned DevTools JSON API, not page retrieval. No cookies or profile
|
|
@@ -79,5 +80,5 @@ export async function connectGuardCdp(endpoint) {
|
|
|
79
80
|
if (typeof address !== 'string' || !/^wss?:\/\//.test(address)) throw new Error('Network guard: no DevTools WebSocket endpoint');
|
|
80
81
|
const socket = new WebSocket(address, { handshakeTimeout: 5000, maxPayload: 16 * 1024 * 1024 });
|
|
81
82
|
await new Promise((resolve, reject) => { socket.once('open', resolve); socket.once('error', reject); });
|
|
82
|
-
return new GuardCdpConnection(socket);
|
|
83
|
+
return new GuardCdpConnection(socket, { timeoutMs });
|
|
83
84
|
}
|