synabun 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +504 -39
- package/neural-interface/i18n/pt-BR.json +504 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-brains/codex.js +6 -1
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +211 -50
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +40 -7
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +423 -23
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +165 -22
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +80 -6
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +15 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +46 -9
- package/neural-interface/public/shared/assistant/asst-render.js +112 -18
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
- package/neural-interface/public/shared/assistant/asst-scenes.js +279 -80
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +58 -4
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/loading-teardown.js +34 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +28 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +17 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +101 -68
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/storage-retry.js +47 -0
- package/neural-interface/public/shared/storage.js +25 -4
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +362 -35
- package/neural-interface/public/shared/synabun-mascot.js +27 -8
- package/neural-interface/public/shared/synabun-ticker.js +147 -12
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +374 -134
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-loading.js +5 -4
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +731 -466
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -10,6 +10,7 @@ import { fork } from 'node:child_process';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { EV, OP, UPGRADE_HEADER_WHITELIST } from './protocol.js';
|
|
12
12
|
import { createPtyHostCore } from './core.js';
|
|
13
|
+
import { killOwnedPty } from './lifetime.js';
|
|
13
14
|
|
|
14
15
|
const HOST_PATH = fileURLToPath(new URL('./host.js', import.meta.url));
|
|
15
16
|
const BACKOFF_MS = [0, 1000, 2000, 4000, 8000, 16000, 30000];
|
|
@@ -39,6 +40,7 @@ export function createPtyHostManager({
|
|
|
39
40
|
const handlers = new Map(); // id → { onSpawned, onTap, onResized, onClients, onExit }
|
|
40
41
|
const spawnWaiters = new Map(); // id → waiter
|
|
41
42
|
const preparing = new Map(); // id → operations queued while PATH is read
|
|
43
|
+
const ptyPids = new Map(); // id → live PTY PID owned by the current host
|
|
42
44
|
const requests = new Map(); // reqId → { resolve, reject, timer }
|
|
43
45
|
let reqSeq = 0;
|
|
44
46
|
let child = null;
|
|
@@ -76,6 +78,7 @@ export function createPtyHostManager({
|
|
|
76
78
|
releaseHeldSockets();
|
|
77
79
|
break;
|
|
78
80
|
case EV.SPAWNED:
|
|
81
|
+
ptyPids.set(m.id, m.pid);
|
|
79
82
|
try { handlers.get(m.id)?.onSpawned?.(m); } catch (err) { log.warn?.('[pty-host] onSpawned', err?.message); }
|
|
80
83
|
settleSpawn(m.id, null, m);
|
|
81
84
|
break;
|
|
@@ -97,6 +100,7 @@ export function createPtyHostManager({
|
|
|
97
100
|
try { handlers.get(m.id)?.onClients?.(m); } catch {}
|
|
98
101
|
break;
|
|
99
102
|
case EV.EXIT: {
|
|
103
|
+
ptyPids.delete(m.id);
|
|
100
104
|
const h = handlers.get(m.id);
|
|
101
105
|
handlers.delete(m.id);
|
|
102
106
|
spawnWaiters.delete(m.id);
|
|
@@ -150,6 +154,11 @@ export function createPtyHostManager({
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
function onChildExit(code, signal) {
|
|
157
|
+
// A SIGKILL cannot run host.js's disconnect/shutdown handlers. In
|
|
158
|
+
// particular, a macOS helper still attaching its terminal may survive
|
|
159
|
+
// closing the PTY master. Reap only this host's still-owned children.
|
|
160
|
+
for (const pid of ptyPids.values()) killOwnedPty(pid);
|
|
161
|
+
ptyPids.clear();
|
|
153
162
|
child = null;
|
|
154
163
|
ready = false;
|
|
155
164
|
hostPid = null;
|
|
@@ -358,8 +367,10 @@ export function createPtyHostManager({
|
|
|
358
367
|
for (const id of preparing.keys()) settleSpawn(id, new Error('Terminal host is shutting down'));
|
|
359
368
|
preparing.clear();
|
|
360
369
|
if (restartTimer) { clearTimeout(restartTimer); restartTimer = null; }
|
|
370
|
+
for (const pid of ptyPids.values()) killOwnedPty(pid);
|
|
371
|
+
ptyPids.clear();
|
|
361
372
|
try { child?.kill('SIGTERM'); } catch {}
|
|
362
|
-
try { core?.shutdownAll(); } catch {}
|
|
373
|
+
try { core?.shutdownAll({ force: true }); } catch {}
|
|
363
374
|
}
|
|
364
375
|
|
|
365
376
|
return {
|
|
@@ -49,7 +49,7 @@ const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
|
|
|
49
49
|
|
|
50
50
|
/** Brain providers that can hold a remote session at Ask and Autonomous (the in-process hook). */
|
|
51
51
|
export const REMOTE_ENFORCING_PROVIDERS = Object.freeze(['claude-code']);
|
|
52
|
-
const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode' });
|
|
52
|
+
const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity' });
|
|
53
53
|
export function providerLabel(provider) { return PROVIDER_LABELS[provider] || String(provider || 'this brain'); }
|
|
54
54
|
/** True for a known brain other than Claude: a remote session on it is read-only. */
|
|
55
55
|
export function remoteBrainLimited(provider) {
|
|
@@ -96,6 +96,9 @@ export const ADMIN_ONLY_PREFIXES = Object.freeze([
|
|
|
96
96
|
'/api/project-files',
|
|
97
97
|
'/api/browse-folder',
|
|
98
98
|
'/api/codex/plans',
|
|
99
|
+
// Antigravity's own conversations and models: each uncached answer starts the owner's `agy` (its side panel
|
|
100
|
+
// socket is refused to guests too).
|
|
101
|
+
'/api/antigravity',
|
|
99
102
|
'/api/sidepanel',
|
|
100
103
|
'/api/sidepanel-runs',
|
|
101
104
|
'/api/loop',
|
|
@@ -41,9 +41,10 @@
|
|
|
41
41
|
|
|
42
42
|
import { isRulesetHost, readRulesetManifest, renderCoexistenceSnippet, renderRuleset, RULESET_HOSTS } from './render.js';
|
|
43
43
|
|
|
44
|
-
// The pre-2.0 `format` names, as the onboarding wizard and Settings still send them.
|
|
45
|
-
|
|
46
|
-
const
|
|
44
|
+
// The pre-2.0 `format` names, as the onboarding wizard and Settings still send them. `gemini` is what a page from
|
|
45
|
+
// before Antigravity replaced Gemini CLI asks for: Antigravity's text, which reads the same files.
|
|
46
|
+
const LEGACY_FORMATS = Object.freeze({ claude: 'claude', codex: 'codex', cursor: 'cursor', generic: 'opencode', opencode: 'opencode', antigravity: 'antigravity', gemini: 'antigravity' });
|
|
47
|
+
const LEGACY_VERSION_FORMATS = Object.freeze(['claude', 'codex', 'cursor', 'generic', 'antigravity']);
|
|
47
48
|
// A copy someone edited, damaged markers, a CLI that is not there: the request was fine, the files said no.
|
|
48
49
|
const REFUSED_STATES = new Set(['not-installed', 'modified', 'conflict', 'error']);
|
|
49
50
|
const REFUSED_CODES = new Set(['REMOVE_INCOMPLETE', 'CHANGED_UNDERNEATH', 'BUSY']);
|
|
@@ -133,7 +134,7 @@ export function legacyRulesetPayload(format, { render = renderRuleset } = {}) {
|
|
|
133
134
|
const name = String(format || 'claude').toLowerCase();
|
|
134
135
|
if (name === 'coexistence') return { ok: true, ruleset: renderCoexistenceSnippet().text, format: name };
|
|
135
136
|
const host = LEGACY_FORMATS[name];
|
|
136
|
-
if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic,
|
|
137
|
+
if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic, antigravity, codex, or coexistence.` };
|
|
137
138
|
return { ok: true, ruleset: render(host).text, format: name };
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -155,7 +156,7 @@ export function legacyRulesetVersionsPayload() {
|
|
|
155
156
|
|
|
156
157
|
/** The two endpoints the pre-2.0 UI reads. Same shapes as before, served from the renderer. */
|
|
157
158
|
export function registerLegacyRulesetRoutes(app, { render = renderRuleset } = {}) {
|
|
158
|
-
// Supports ?format=claude (default) | cursor | generic |
|
|
159
|
+
// Supports ?format=claude (default) | cursor | generic | antigravity | codex | coexistence
|
|
159
160
|
app.get('/api/claude-code/ruleset', (req, res) => {
|
|
160
161
|
try {
|
|
161
162
|
const payload = legacyRulesetPayload(req.query.format, { render });
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
//
|
|
5
5
|
// The rules installer edits files that belong to the user and to other CLIs
|
|
6
|
-
// (~/.codex/AGENTS.md, ~/.gemini/GEMINI.md, OpenCode's opencode.json). These
|
|
6
|
+
// (~/.codex/AGENTS.md, ~/.gemini/config/GEMINI.md, OpenCode's opencode.json). These
|
|
7
7
|
// helpers make each write all-or-nothing, keep a symlinked file a symlink,
|
|
8
8
|
// refuse to write through a link that points at nothing, keep a copy of what
|
|
9
9
|
// was there before, never read a corrupt JSON file as `{}`, and never write
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// claude owned file ~/.claude/rules/synabun.md (and each Claude account's config dir)
|
|
9
9
|
// opencode owned file <opencode config dir>/synabun.md + one `instructions` entry in opencode.json
|
|
10
10
|
// codex block ~/.codex/AGENTS.md (account homes link to it, or get their own block)
|
|
11
|
-
//
|
|
11
|
+
// antigravity block ~/.gemini/config/GEMINI.md (Antigravity's global customization root; the block an
|
|
12
|
+
// older SynaBun wrote for Gemini CLI in ~/.gemini/GEMINI.md, which Antigravity also
|
|
13
|
+
// reads, is moved here so the rules are never loaded twice)
|
|
12
14
|
// cursor none User Rules live inside the app: copy and paste ("manual")
|
|
13
15
|
//
|
|
14
16
|
// What it will not do: create the config directory of a CLI that is not
|
|
@@ -83,11 +85,18 @@ const HOSTS = Object.freeze({
|
|
|
83
85
|
claude: { label: 'Claude Code', carrier: 'file' },
|
|
84
86
|
codex: { label: 'Codex CLI', carrier: 'block' },
|
|
85
87
|
opencode: { label: 'OpenCode', carrier: 'file' },
|
|
86
|
-
|
|
88
|
+
// `legacyHosts`: the stamps an earlier SynaBun wrote for what this host replaced (Gemini CLI's rules, host=gemini).
|
|
89
|
+
antigravity: { label: 'Antigravity', carrier: 'block', legacyHosts: Object.freeze(['gemini']) },
|
|
87
90
|
cursor: { label: 'Cursor', carrier: 'none' },
|
|
88
91
|
});
|
|
89
92
|
const MANAGED_HOSTS = Object.freeze(RULESET_HOSTS.filter((host) => HOSTS[host].carrier !== 'none'));
|
|
90
|
-
const PROJECT_FILES = Object.freeze([['CLAUDE.md', 'claude'], ['AGENTS.md', 'codex'], ['GEMINI.md', '
|
|
93
|
+
const PROJECT_FILES = Object.freeze([['CLAUDE.md', 'claude'], ['AGENTS.md', 'codex'], ['GEMINI.md', 'antigravity']]);
|
|
94
|
+
// State rows an earlier SynaBun kept under a host this one replaced: read as the new host's row (readState).
|
|
95
|
+
const RENAMED_HOSTS = Object.freeze({ gemini: 'antigravity' });
|
|
96
|
+
/** Whether a block's stamp is this host's own: its name, or one it replaced. */
|
|
97
|
+
function ownStamp(host, stamped) {
|
|
98
|
+
return stamped === host || (HOSTS[host]?.legacyHosts || []).includes(stamped);
|
|
99
|
+
}
|
|
91
100
|
const RANK = Object.freeze({ missing: 0, current: 1, newer: 2, outdated: 3, modified: 4, conflict: 5, error: 6 });
|
|
92
101
|
const MAX_SCAN_BYTES = 2 * 1024 * 1024;
|
|
93
102
|
|
|
@@ -259,6 +268,17 @@ export function createRulesetInstaller({
|
|
|
259
268
|
return view();
|
|
260
269
|
}
|
|
261
270
|
if (!isPlainObject(saved)) return view();
|
|
271
|
+
// A row kept under a host this SynaBun replaced (Gemini CLI → Antigravity) is the new host's row, marked
|
|
272
|
+
// `movedFrom` until reconcile has put the rules where the new host reads them (see reconcileHosts).
|
|
273
|
+
for (const [old, now] of Object.entries(RENAMED_HOSTS)) {
|
|
274
|
+
const hosts = isPlainObject(saved.hosts) ? saved.hosts : null;
|
|
275
|
+
if (hosts && isPlainObject(hosts[old]) && !isPlainObject(hosts[now])) {
|
|
276
|
+
saved.hosts = { ...hosts, [now]: { ...hosts[old], ...(hosts[old].managed === true ? { movedFrom: old } : {}) } };
|
|
277
|
+
delete saved.hosts[old];
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(saved.offerAcked)) saved.offerAcked = saved.offerAcked.map((host) => (host === old ? now : host));
|
|
280
|
+
if (isPlainObject(saved.notice) && Array.isArray(saved.notice.hosts)) saved.notice = { ...saved.notice, hosts: unique(saved.notice.hosts.map((host) => (host === old ? now : host))) };
|
|
281
|
+
}
|
|
262
282
|
if (Number.isSafeInteger(saved.revision) && saved.revision > 0) state.revision = saved.revision;
|
|
263
283
|
if (typeof saved.autoUpdate === 'boolean') state.autoUpdate = saved.autoUpdate;
|
|
264
284
|
if (isPlainObject(saved.notice)) state.notice = saved.notice;
|
|
@@ -352,7 +372,7 @@ export function createRulesetInstaller({
|
|
|
352
372
|
|
|
353
373
|
// ── Where things live ──────────────────────────────────────────────────────
|
|
354
374
|
// Resolved the way the MCP toggle routes resolve them: Claude, Codex and
|
|
355
|
-
//
|
|
375
|
+
// Antigravity under the home directory, OpenCode in its configuration folder
|
|
356
376
|
// (OPENCODE_CONFIG_DIR, else XDG_CONFIG_HOME/opencode, else ~/.config/opencode),
|
|
357
377
|
// whose opencode.json OpenCode 2 reads (lib/opencode-config.js).
|
|
358
378
|
function layout() {
|
|
@@ -363,6 +383,9 @@ export function createRulesetInstaller({
|
|
|
363
383
|
return {
|
|
364
384
|
claudeDir: join(home, '.claude'),
|
|
365
385
|
codexHome: join(home, '.codex'),
|
|
386
|
+
// Antigravity's global customization root (rules, skills, mcp_config.json), and the folder above it, where
|
|
387
|
+
// Gemini CLI's GEMINI.md lives (Antigravity reads that file too).
|
|
388
|
+
antigravityDir: join(home, '.gemini', 'config'),
|
|
366
389
|
geminiDir: join(home, '.gemini'),
|
|
367
390
|
opencodeDir: dirname(opencodeConfig),
|
|
368
391
|
opencodeConfig,
|
|
@@ -416,7 +439,7 @@ export function createRulesetInstaller({
|
|
|
416
439
|
...codexAccounts().filter((account) => account.kind === 'file').map(({ path, dir }) => ({ path, dir })),
|
|
417
440
|
];
|
|
418
441
|
}
|
|
419
|
-
if (host === '
|
|
442
|
+
if (host === 'antigravity') return [{ path: join(where.antigravityDir, 'GEMINI.md'), dir: where.antigravityDir, legacyBlock: join(where.geminiDir, 'GEMINI.md') }];
|
|
420
443
|
return [];
|
|
421
444
|
}
|
|
422
445
|
function projectPaths() {
|
|
@@ -428,7 +451,10 @@ export function createRulesetInstaller({
|
|
|
428
451
|
const where = layout();
|
|
429
452
|
try {
|
|
430
453
|
if (host === 'claude') return !!JSON.parse(readFileSync(join(home, '.claude.json'), 'utf8'))?.mcpServers?.SynaBun;
|
|
431
|
-
if (host === '
|
|
454
|
+
if (host === 'antigravity') {
|
|
455
|
+
const text = readFileSync(join(where.antigravityDir, 'mcp_config.json'), 'utf8').replace(/^\uFEFF/, '');
|
|
456
|
+
return text.trim() ? !!JSON.parse(text)?.mcpServers?.SynaBun : false;
|
|
457
|
+
}
|
|
432
458
|
if (host === 'codex') return /^\[mcp_servers\.SynaBun\]/m.test(readFileSync(join(where.codexHome, 'config.toml'), 'utf8'));
|
|
433
459
|
if (host === 'opencode') return !!mcpServersOf(mergedOpencodeConfig(readOpencodeConfigDocuments(where.opencodeDir))).SynaBun;
|
|
434
460
|
} catch { /* no config, no registration */ }
|
|
@@ -481,7 +507,7 @@ export function createRulesetInstaller({
|
|
|
481
507
|
}
|
|
482
508
|
const block = blocks[0] || null;
|
|
483
509
|
// A stamp for another tool is not this host's copy, however untouched it is: never updated, never removed.
|
|
484
|
-
if (block && block.host
|
|
510
|
+
if (block && !ownStamp(host, block.host)) return { status: 'conflict', text, bytes, detail: `${foreignBlock(host, block, target.path)} Replace does not apply here: take that block out by hand, then install again.` };
|
|
485
511
|
if (HOSTS[host].carrier === 'file') {
|
|
486
512
|
if (normalise(text) === expected.block) return { status: 'current', text, bytes, block };
|
|
487
513
|
if (!block) return { status: 'modified', text, bytes, block, detail: `${target.path} has no SynaBun markers, so SynaBun treats it as your own file and never replaces it. Move or delete it, then install again.` };
|
|
@@ -756,6 +782,45 @@ export function createRulesetInstaller({
|
|
|
756
782
|
}
|
|
757
783
|
tally.changed = true;
|
|
758
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* The block an earlier SynaBun wrote where this host's tool also reads rules, under a name this host replaced
|
|
787
|
+
* (Gemini CLI's ~/.gemini/GEMINI.md, host=gemini, which Antigravity reads beside its own ~/.gemini/config/GEMINI.md):
|
|
788
|
+
* once the rules are where the new host keeps them, an untouched old block goes, or the tool would load them twice.
|
|
789
|
+
* An edited one is the user's: it stays, with a warning. A file that held nothing else and that SynaBun created is
|
|
790
|
+
* deleted; any other file keeps everything but the block. Never forced.
|
|
791
|
+
*/
|
|
792
|
+
function moveLegacyBlock(host, file, row, tally) {
|
|
793
|
+
let bytes;
|
|
794
|
+
try { bytes = readBytes(file); } catch { return; }
|
|
795
|
+
if (bytes === null) return;
|
|
796
|
+
const text = bytes.toString('utf8');
|
|
797
|
+
const { blocks, conflict } = findBlocks(text);
|
|
798
|
+
if (conflict) {
|
|
799
|
+
tally.warnings.push(`${conflict.message} in ${file}, which ${HOSTS[host].label} also reads, so SynaBun left it as it is: remove the old SynaBun marker lines by hand.`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const block = blocks[0];
|
|
803
|
+
if (!block || !ownStamp(host, block.host)) return;
|
|
804
|
+
if (!isPristine(block)) {
|
|
805
|
+
tally.warnings.push(`The SynaBun block an earlier version wrote in ${file} was edited, so it stays there. ${HOSTS[host].label} reads that file as well: take that block out by hand, or the rules are loaded twice.`);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const next = removeBlock(text);
|
|
809
|
+
const created = Array.isArray(row.created) && row.created.some((path) => samePath(path, file));
|
|
810
|
+
try {
|
|
811
|
+
tally.backup(backedUp(file, host, () => {
|
|
812
|
+
if (created && !isSymlink(file) && withoutBom(next).trim() === '') unlinkUnchanged(file, text);
|
|
813
|
+
else commit(file, next, text);
|
|
814
|
+
}));
|
|
815
|
+
} catch (error) {
|
|
816
|
+
if (error?.code !== 'CHANGED_UNDERNEATH') throw error;
|
|
817
|
+
tally.warnings.push(`${file} changed while SynaBun was moving its old rules out of it, so it was left as it is.`);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
tally.changed = true;
|
|
821
|
+
row.paths = row.paths.filter((path) => !samePath(path, file));
|
|
822
|
+
row.created = row.created.filter((path) => !samePath(path, file));
|
|
823
|
+
}
|
|
759
824
|
/**
|
|
760
825
|
* One carrier. Returns the status it ended in; writes only when the copy is
|
|
761
826
|
* missing, untouched and old, or (forced) one well-formed copy that was
|
|
@@ -951,7 +1016,7 @@ export function createRulesetInstaller({
|
|
|
951
1016
|
};
|
|
952
1017
|
for (const target of targets('claude')) add(target.legacyFile, 'claude', 'global');
|
|
953
1018
|
for (const target of targets('codex')) add(target.path, 'codex', 'global');
|
|
954
|
-
for (const target of targets('
|
|
1019
|
+
for (const target of targets('antigravity')) { add(target.path, 'antigravity', 'global'); add(target.legacyBlock, 'antigravity', 'global'); }
|
|
955
1020
|
for (const target of targets('opencode')) add(target.legacyFile, 'opencode', 'global');
|
|
956
1021
|
for (const dir of projectPaths()) {
|
|
957
1022
|
for (const [name, host] of PROJECT_FILES) add(join(dir, name), host, 'project');
|
|
@@ -1047,6 +1112,7 @@ export function createRulesetInstaller({
|
|
|
1047
1112
|
}
|
|
1048
1113
|
}
|
|
1049
1114
|
if (host === 'opencode' && !moveOpencode1Entry(row, tally)) addOpencodeEntry(row, tally);
|
|
1115
|
+
for (const target of list) if (target.legacyBlock) moveLegacyBlock(host, target.legacyBlock, row, tally);
|
|
1050
1116
|
|
|
1051
1117
|
Object.assign(row, {
|
|
1052
1118
|
managed: true,
|
|
@@ -1181,7 +1247,7 @@ export function createRulesetInstaller({
|
|
|
1181
1247
|
const created = new Set(row.created);
|
|
1182
1248
|
// Every place a copy can be: the host's carriers, the row's paths, and what an unfinished removal recorded.
|
|
1183
1249
|
const recorded = Array.isArray(row.removal?.paths) ? row.removal.paths.filter((path) => typeof path === 'string') : [];
|
|
1184
|
-
const paths = unique([...list.map((target) => target.path), ...row.paths, ...recorded]);
|
|
1250
|
+
const paths = unique([...list.map((target) => target.path), ...list.map((target) => target.legacyBlock).filter(Boolean), ...row.paths, ...recorded]);
|
|
1185
1251
|
/** Intent first: the paths about to go are saved before the first of them is touched. */
|
|
1186
1252
|
const recordIntent = (planned) => {
|
|
1187
1253
|
row.removal = { at: stamp(), error: INTERRUPTED, paths: unique(planned) };
|
|
@@ -1256,7 +1322,7 @@ export function createRulesetInstaller({
|
|
|
1256
1322
|
if (conflict) { refuse(path, 'conflict', `${conflict.message} in ${path}. SynaBun will not guess: remove the SynaBun marker lines by hand, then remove again.`); continue; }
|
|
1257
1323
|
const block = blocks[0];
|
|
1258
1324
|
if (!block) continue;
|
|
1259
|
-
if (block.host
|
|
1325
|
+
if (!ownStamp(host, block.host)) { refuse(path, 'conflict', `${foreignBlock(host, block, path)} Take that block out by hand if you no longer want it.`); continue; }
|
|
1260
1326
|
const pristine = isPristine(block);
|
|
1261
1327
|
if (!force && !pristine) {
|
|
1262
1328
|
left.push({ path, reason: 'edited', message: `The SynaBun block in ${path} was edited, so SynaBun left it in place and no longer manages it.` });
|
|
@@ -1413,6 +1479,19 @@ export function createRulesetInstaller({
|
|
|
1413
1479
|
// config.json does not list any more is not moved: the user took it out, which is the opt-out below.
|
|
1414
1480
|
const older = config && first.status !== 'missing' && !config.hasEntry ? opencode1Record(row) : null;
|
|
1415
1481
|
const moving = older !== null && opencode1Listed(older);
|
|
1482
|
+
if (first.status === 'missing' && row.movedFrom && isDir(primary.dir)) {
|
|
1483
|
+
// A host this SynaBun replaced (Gemini CLI → Antigravity) was managed: its rules go where the new host reads
|
|
1484
|
+
// them, and the old block goes (installHost moves it). Not an opt-out: nothing was deleted by the user.
|
|
1485
|
+
const moved = installHost(state, host, { force: false }, call);
|
|
1486
|
+
if (moved.ok) { delete row.movedFrom; updated.push(host); to = expected.version; from = from || row.installedVersion || null; }
|
|
1487
|
+
states[host] = hostStatus(host, state).state;
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
if (first.status === 'missing' && row.movedFrom) {
|
|
1491
|
+
// The new host's folder is not there yet (Antigravity not installed): nothing to move to; asked again later.
|
|
1492
|
+
states[host] = 'not-installed';
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1416
1495
|
if (first.status === 'missing' || (config && !config.hasEntry && !moving)) {
|
|
1417
1496
|
// The user deleted it: an opt-out, never a reason to put it back. (A copy SynaBun's own removal took is
|
|
1418
1497
|
// not this case: that removal is on record and was dealt with above.)
|
|
@@ -18,7 +18,7 @@ import { normalise, sha12 } from './normalise.js';
|
|
|
18
18
|
|
|
19
19
|
export { normalise, sha12 };
|
|
20
20
|
|
|
21
|
-
export const RULESET_HOSTS = Object.freeze(['claude', 'codex', 'opencode', '
|
|
21
|
+
export const RULESET_HOSTS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'cursor']);
|
|
22
22
|
export const RULESETS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates', 'rulesets');
|
|
23
23
|
|
|
24
24
|
const HOST_SLOT = '<!-- host -->';
|
|
@@ -3,7 +3,7 @@ import { normalizeMemoryEvents, ingestMemoryEvents } from '../../mcp-server/dist
|
|
|
3
3
|
import { detectProject } from '../../mcp-server/dist/config.js';
|
|
4
4
|
/**
|
|
5
5
|
* Session Cache Builder — populates session_cache + session_fts for
|
|
6
|
-
* Claude Code, Codex CLI, and
|
|
6
|
+
* Claude Code, Codex CLI, OpenCode and Antigravity so the Resume surfaces can do
|
|
7
7
|
* full-body keyword search via FTS5.
|
|
8
8
|
*
|
|
9
9
|
* Per-provider rebuilders walk the provider's session storage, extract
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { parseLine } from './session-chunker.js';
|
|
26
26
|
import { listCodexSessionsFromAccounts } from './codex-session-catalog.js';
|
|
27
27
|
import { OPENCODE_SESSION_TABLES, hasOpencodeSessionTables, v2MessageText } from './opencode-db-schema.js';
|
|
28
|
+
import { listAntigravityConversations, parseAntigravityTranscript, readTranscriptText } from './antigravity/transcripts.js';
|
|
28
29
|
|
|
29
30
|
const MAX_BODY = 256 * 1024;
|
|
30
31
|
const MAX_MSG_CHARS = 8 * 1024;
|
|
@@ -493,6 +494,68 @@ export function rebuildOpencodeCache({ getOpencodeDb, onProgress } = {}) {
|
|
|
493
494
|
return { sessions: total, updated };
|
|
494
495
|
}
|
|
495
496
|
|
|
497
|
+
// --- Antigravity ---
|
|
498
|
+
|
|
499
|
+
// agy keeps one transcript.jsonl per conversation (lib/antigravity/transcripts.js). Every conversation it holds,
|
|
500
|
+
// SynaBun's brain and run homes included, is queued for memory capture; the user's own (the panel's and a terminal
|
|
501
|
+
// agy's, the only ones the panel can load again) also go into the Resume index, titled as Antigravity titles them.
|
|
502
|
+
// A conversation unchanged since the last pass (size, time, folder, title) is not read again. `only`: one
|
|
503
|
+
// conversation (a panel turn just ended), the others left as they are. Request/timer paths read at most
|
|
504
|
+
// MAX_BODY bytes per transcript: the index is bounded; full transcripts remain available to memory capture.
|
|
505
|
+
export function rebuildAntigravityCache({ stores = [], only = null, onProgress, list = listAntigravityConversations, read = readTranscriptText } = {}) {
|
|
506
|
+
const emit = onProgress || (() => {});
|
|
507
|
+
let conversations;
|
|
508
|
+
try { conversations = list(stores); } catch { return { sessions: 0, updated: 0 }; }
|
|
509
|
+
if (only) conversations = conversations.filter((c) => c.sessionId === only);
|
|
510
|
+
const listed = new Set(conversations.filter((c) => c.store === 'user').map((c) => c.sessionId));
|
|
511
|
+
const total = listed.size;
|
|
512
|
+
let updated = 0;
|
|
513
|
+
for (const c of conversations) {
|
|
514
|
+
try {
|
|
515
|
+
queueMemoryCapture({ host: 'antigravity', sessionId: c.sessionId, filePath: c.transcript, cwd: c.cwd || undefined, project: c.cwd ? detectProject(c.cwd) : 'global', size: c.size });
|
|
516
|
+
} catch { /* memory capture is best effort */ }
|
|
517
|
+
if (c.store !== 'user') continue;
|
|
518
|
+
const mtimeIso = new Date(c.mtimeMs).toISOString();
|
|
519
|
+
const existing = getSessionCacheEntry(c.sessionId, 'antigravity');
|
|
520
|
+
if (existing && !existing.deleted && Number(existing.file_size) === c.size && existing.file_mtime === mtimeIso
|
|
521
|
+
&& (existing.project_path || null) === (c.cwd || null) && (!c.title || existing.first_prompt === c.title)) continue;
|
|
522
|
+
const parsed = parseAntigravityTranscript(read(c.transcript, { maxBytes: MAX_BODY }));
|
|
523
|
+
let body = '';
|
|
524
|
+
for (const message of parsed.messages) {
|
|
525
|
+
if (message.role !== 'user' && message.role !== 'assistant') continue;
|
|
526
|
+
if (message.text) body = appendBody(body, message.text.slice(0, MAX_MSG_CHARS));
|
|
527
|
+
}
|
|
528
|
+
const firstPrompt = c.title || parsed.firstPrompt;
|
|
529
|
+
const project = c.cwd ? basename(c.cwd) : null;
|
|
530
|
+
upsertSessionCache({
|
|
531
|
+
session_id: c.sessionId,
|
|
532
|
+
provider: 'antigravity',
|
|
533
|
+
project,
|
|
534
|
+
project_path: c.cwd,
|
|
535
|
+
git_branch: null,
|
|
536
|
+
first_prompt: firstPrompt,
|
|
537
|
+
message_count: parsed.messageCount,
|
|
538
|
+
created: parsed.created,
|
|
539
|
+
modified: c.updatedAt || parsed.modified || mtimeIso,
|
|
540
|
+
file_path: c.transcript,
|
|
541
|
+
file_size: c.size,
|
|
542
|
+
file_mtime: mtimeIso,
|
|
543
|
+
body_size: body.length,
|
|
544
|
+
deleted: 0,
|
|
545
|
+
});
|
|
546
|
+
upsertSessionFts({ session_id: c.sessionId, provider: 'antigravity', project, first_prompt: firstPrompt, body });
|
|
547
|
+
updated++;
|
|
548
|
+
emit({ type: 'cache:session', provider: 'antigravity', sessionId: c.sessionId, done: updated, total });
|
|
549
|
+
}
|
|
550
|
+
// A conversation Antigravity no longer has (deleted there, or its folder cleared) leaves Resume.
|
|
551
|
+
if (!only) {
|
|
552
|
+
for (const cached of listSessionCache('antigravity', { limit: 100000 })) {
|
|
553
|
+
if (!listed.has(cached.session_id) && !cached.deleted) markSessionDeleted(cached.session_id, 'antigravity', true);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return { sessions: total, updated };
|
|
557
|
+
}
|
|
558
|
+
|
|
496
559
|
// --- Single-session refresh (for piggyback from session-indexer) ---
|
|
497
560
|
|
|
498
561
|
export function updateSessionCacheEntry(provider, sessionId, opts = {}) {
|
|
@@ -178,8 +178,12 @@ export function createSkillsCatalog({ roots, readHead, describe, ownership = ()
|
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
} else {
|
|
181
|
+
// Antigravity's rule for two skills of one name (its folder and a project's .agents/skills) is not known here.
|
|
182
|
+
const why = runtime === 'antigravity'
|
|
183
|
+
? 'Antigravity sees more than one skill of this name; which one it uses is not certain. Give them different names.'
|
|
184
|
+
: `${runtime === 'codex' ? 'Codex' : 'OpenCode'} lists every skill of this name; which one the model picks is not fixed. Give them different names.`;
|
|
181
185
|
for (const e of list) {
|
|
182
|
-
(e.collisions ||= []).push({ runtime, with: list.filter((x) => x !== e).map((x) => x.id), outcome: 'both-listed', why
|
|
186
|
+
(e.collisions ||= []).push({ runtime, with: list.filter((x) => x !== e).map((x) => x.id), outcome: 'both-listed', why });
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
}
|
|
@@ -82,7 +82,7 @@ const literal = (value, max) => { const s = String(value ?? ''); return JSON.str
|
|
|
82
82
|
*/
|
|
83
83
|
export function buildDraftingBrief({ jobId, request, sources = [] }) {
|
|
84
84
|
const name = request?.name || '';
|
|
85
|
-
const runtimes = [...new Set((request?.targets || []).map((t) => ({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'Codex and OpenCode (shared folder)' }[t.runtime])).filter(Boolean))];
|
|
85
|
+
const runtimes = [...new Set((request?.targets || []).map((t) => ({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'Codex and OpenCode (shared folder)' }[t.runtime])).filter(Boolean))];
|
|
86
86
|
const prompt = request?.prompt ? quoteBounded(request.prompt, PROMPT_IN_BRIEF) : null;
|
|
87
87
|
const example = request?.example ? quoteBounded(request.example, EXAMPLE_IN_BRIEF) : null;
|
|
88
88
|
const lines = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ── AI skill creation: the sources a draft is made from (P3) ──
|
|
2
2
|
// What lib/skills/creation.js puts in a drafting job's workspace (`sources/`) before the worker starts: one session
|
|
3
|
-
// transcript of Claude Code, Codex or
|
|
3
|
+
// transcript of Claude Code, Codex, OpenCode or Antigravity, SynaBun memories, a read-only snapshot of a registered project, the
|
|
4
4
|
// files of an existing skill. Every file opens with a line saying it is untrusted data, secrets are taken out
|
|
5
5
|
// (lib/log-redact.js: before anything is cut short, so half a secret never survives), and every source has a
|
|
6
6
|
// budget: what does not fit is cut, and the manifest says so. A web page is read by the server with the SynaBun
|
|
@@ -91,6 +91,24 @@ export function opencodeRowsToMessages(rows) {
|
|
|
91
91
|
return out;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Antigravity's transcript as lib/antigravity/transcripts.js parses it (parseAntigravityTranscript().messages:
|
|
96
|
+
* { role: user | assistant | tool_result, text, tools }) → messages: the person's words, the model's answers and
|
|
97
|
+
* the tools it called (never its reasoning), and what the tools gave back.
|
|
98
|
+
*/
|
|
99
|
+
export function antigravityMessagesToMessages(messages) {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const m of Array.isArray(messages) ? messages : []) {
|
|
102
|
+
if (!m || typeof m !== 'object') continue;
|
|
103
|
+
if (m.role === 'user' && typeof m.text === 'string' && m.text.trim()) out.push({ role: 'user', text: m.text });
|
|
104
|
+
else if (m.role === 'assistant') {
|
|
105
|
+
if (typeof m.text === 'string' && m.text.trim()) out.push({ role: 'assistant', text: m.text });
|
|
106
|
+
for (const t of Array.isArray(m.tools) ? m.tools : []) out.push({ role: 'tool', tool: String(t?.name || 'tool'), text: safeJson(t?.input) });
|
|
107
|
+
} else if (m.role === 'tool_result' && typeof m.text === 'string' && m.text.trim()) out.push({ role: 'tool_result', text: m.text, error: m.error === true });
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
function safeJson(value) {
|
|
95
113
|
try { return typeof value === 'string' ? value : JSON.stringify(value ?? {}); } catch { return ''; }
|
|
96
114
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// ── Installing library revisions into runtime folders ──
|
|
2
2
|
// An install puts one revision of a library skill (lib/skills/library.js) into one or more runtime folders as a
|
|
3
3
|
// native skill folder: Claude Code (<config dir>/skills or a project's .claude/skills), Codex ($CODEX_HOME/skills
|
|
4
|
-
// or a project's .agents/skills), OpenCode (<config>/opencode/skills or a project's
|
|
5
|
-
// shared ~/.agents/skills only when the owner
|
|
4
|
+
// or a project's .agents/skills, which Antigravity reads too), OpenCode (<config>/opencode/skills or a project's
|
|
5
|
+
// .opencode/skills), Antigravity (~/.gemini/config/skills), and the shared ~/.agents/skills only when the owner
|
|
6
|
+
// turned it on. It is two steps:
|
|
6
7
|
//
|
|
7
8
|
// plan(…) what each target would receive (the export of the revision for that runtime, with its digest),
|
|
8
9
|
// where, which runtimes see that folder (and what else of that name they already see), what is there
|
|
@@ -39,7 +40,7 @@ import { PORTABLE_NAME_RE, TARGET_PROFILE, getProfile, lintForProfile } from '..
|
|
|
39
40
|
import { analyzeResources, argumentDiagnostics } from '../../public/shared/skills/ss-resources.js';
|
|
40
41
|
import { isEditableText } from '../../public/shared/skills/ss-doc.js';
|
|
41
42
|
|
|
42
|
-
export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'agents']);
|
|
43
|
+
export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'agents']);
|
|
43
44
|
export const INSTALLATIONS_SCHEMA = 2;
|
|
44
45
|
const MAX_INSTALLATIONS = 2000;
|
|
45
46
|
|
|
@@ -66,9 +67,11 @@ const REFRESH = {
|
|
|
66
67
|
opencode: (rootExisted, root) => (root?.family === 'claude'
|
|
67
68
|
? { kind: 'terminal-only', message: 'An OpenCode open in a terminal reads Claude Code\'s folder (new session); SynaBun\'s own OpenCode panel and an OpenCode Assistant do not: install for OpenCode for those.' }
|
|
68
69
|
: { kind: 'panel-reload', message: 'SynaBun\'s OpenCode panel lists it now; an OpenCode open in a terminal needs a new session.' }),
|
|
70
|
+
// Antigravity reads its skill folders again by itself: there is no command to run.
|
|
71
|
+
antigravity: () => ({ kind: 'automatic', message: 'Antigravity picks up the change by itself.' }),
|
|
69
72
|
};
|
|
70
73
|
|
|
71
|
-
const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'the shared agents folder' };
|
|
74
|
+
const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'the shared agents folder' };
|
|
72
75
|
|
|
73
76
|
/** A target as a request may name it, checked. */
|
|
74
77
|
export function normalizeTarget(t) {
|
|
@@ -78,6 +81,8 @@ export function normalizeTarget(t) {
|
|
|
78
81
|
const scope = t.scope === undefined ? 'user' : t.scope;
|
|
79
82
|
if (scope !== 'user' && scope !== 'project') throw new DeployError(400, 'A target\'s scope is user or project.', 'invalid_target');
|
|
80
83
|
if (runtime === 'agents' && scope !== 'user') throw new DeployError(400, 'The shared agents folder is a user folder; a project\'s .agents/skills is the codex target with scope project.', 'invalid_target');
|
|
84
|
+
// Antigravity has no project folder of its own: it reads the project's .agents/skills, the Codex project folder.
|
|
85
|
+
if (runtime === 'antigravity' && scope !== 'user') throw new DeployError(400, 'Antigravity reads a project\'s .agents/skills: that is the codex target with scope project.', 'invalid_target');
|
|
81
86
|
const accountId = t.accountId === undefined || t.accountId === null || t.accountId === '' ? 'default' : t.accountId;
|
|
82
87
|
if (typeof accountId !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(accountId)) throw new DeployError(400, 'Not an account id.', 'invalid_target');
|
|
83
88
|
if (scope === 'project' && (typeof t.projectPath !== 'string' || !t.projectPath)) throw new DeployError(400, 'A project target names its project (projectPath).', 'invalid_target');
|
|
@@ -139,7 +139,9 @@ export function draftConfinementSupport(provider, { platform = process.platform
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
if (name === 'opencode') return { ok: true, level: 'rules', note: 'OpenCode with its shell, web, subagent and skill tools switched off, no MCP server, and every edit answered inside its folder only.' };
|
|
142
|
-
|
|
142
|
+
// Antigravity (a worker of the Assistant) has no drafting confinement in SynaBun: a drafting run never starts unconfined.
|
|
143
|
+
if (name === 'antigravity') return { ok: false, reason: 'Antigravity cannot be held to a drafting folder: skill drafting runs on Claude Code, Codex and OpenCode.' };
|
|
144
|
+
return { ok: false, reason: `${name || 'That provider'} cannot be held to a drafting folder: skill drafting runs on Claude Code, Codex and OpenCode.` };
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/** The MCP server names a Codex config.toml declares ([mcp_servers.NAME] tables), for disabling them one by one. */
|
|
@@ -96,7 +96,9 @@ export function evalConfinementSupport(provider, { platform = process.platform }
|
|
|
96
96
|
if (name === 'claude-code') return { ok: true, level: 'strict', shell: platform === 'darwin' ? 'sandbox' : 'never', optIn: false, note: 'Read, Write, Edit, Glob, Grep and Skill inside its folder; no settings file, hook or plugin but the skill under test; a shell only under the evaluation\'s macOS sandbox (no network, no home folder); no web, no MCP tools, no subagents.' };
|
|
97
97
|
if (name === 'codex') return { ok: true, level: 'sandbox', shell: 'own-sandbox', optIn: true, note: `Codex keeps its shell under its ${platform === 'win32' ? 'Windows' : platform === 'darwin' ? 'Seatbelt' : 'Linux'} sandbox: writes inside its folder only, no network, no web search, no MCP server; its shell can read files anywhere on this computer, and what it reads can reach its model provider.` };
|
|
98
98
|
if (name === 'opencode') return { ok: true, level: 'rules', shell: platform === 'darwin' ? 'sandbox' : 'never', optIn: false, note: 'OpenCode with no project configuration or plugin of the folder, its web, subagent and todo tools off, no MCP server, reads and edits inside its folder only; a shell only under the evaluation\'s macOS sandbox (no network, no home folder).' };
|
|
99
|
-
|
|
99
|
+
// Antigravity (a worker of the Assistant) has no evaluation confinement in SynaBun: an evaluation never runs unconfined.
|
|
100
|
+
if (name === 'antigravity') return { ok: false, reason: 'Antigravity cannot be held to an evaluation folder: skill evaluations run on Claude Code, Codex and OpenCode.' };
|
|
101
|
+
return { ok: false, reason: `${name || 'That provider'} cannot be held to an evaluation folder: skill evaluations run on Claude Code, Codex and OpenCode.` };
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
/**
|
|
@@ -60,6 +60,22 @@ export function toolCallsOf(payload = {}) {
|
|
|
60
60
|
}
|
|
61
61
|
return out;
|
|
62
62
|
}
|
|
63
|
+
if (payload.provider === 'antigravity') {
|
|
64
|
+
// An ACP tool call (session/update tool_call, then tool_call_update with its status). Antigravity names its
|
|
65
|
+
// tool (read_file, run_command, call_mcp_tool …) and its arguments in its own words: the path a read names
|
|
66
|
+
// and a command's line are kept under `path` / `command`; an MCP call is <server>.<tool>.
|
|
67
|
+
if ((eventType === 'tool_call' || eventType === 'tool_call_update') && event.toolCallId) {
|
|
68
|
+
const raw = isObject(event.rawInput) ? event.rawInput : {};
|
|
69
|
+
const mcp = String(event.name || '') === 'call_mcp_tool' && raw.ServerName && raw.ToolName ? `${raw.ServerName}.${raw.ToolName}` : '';
|
|
70
|
+
const location = Array.isArray(event.locations) ? event.locations.find((l) => typeof l?.path === 'string' && l.path)?.path : '';
|
|
71
|
+
const path = [raw.AbsolutePath, raw.Path, raw.TargetFile, raw.path, raw.file_path, location].find((v) => typeof v === 'string' && v) || '';
|
|
72
|
+
const command = typeof raw.CommandLine === 'string' ? raw.CommandLine : typeof raw.command === 'string' ? raw.command : '';
|
|
73
|
+
const input = { ...(path ? { path } : {}), ...(command ? { command } : {}) };
|
|
74
|
+
const status = String(event.status || '');
|
|
75
|
+
out.push({ id: String(event.toolCallId), tool: mcp || String(event.name || event.kind || ''), input, phase: status, ok: status === 'completed' ? true : status === 'failed' ? false : null });
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
63
79
|
return out;
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
// - Claude Code: `disable-model-invocation: true` / `user-invocable: false` in SKILL.md (that key only);
|
|
8
8
|
// - Codex: `policy.allow_implicit_invocation: false` in agents/openai.yaml (made when there is none; otherwise
|
|
9
9
|
// that one value is set and every other value, comment and order is kept);
|
|
10
|
-
// - OpenCode: metadata `opencode/autoinvoke: "false"` in SKILL.md (the metadata key only)
|
|
10
|
+
// - OpenCode: metadata `opencode/autoinvoke: "false"` in SKILL.md (the metadata key only);
|
|
11
|
+
// - Antigravity: `disable-slash-command: true` in SKILL.md (that key only) for a skill the person may not start by
|
|
12
|
+
// name. Antigravity has no setting that keeps its model from starting a skill: an export of an explicit-only
|
|
13
|
+
// skill for it is refused (L-08-invocation-unrepresentable), never written wider.
|
|
11
14
|
// A Codex export also carries the harness metadata Codex shows in its skill picker (agents/openai.yaml
|
|
12
15
|
// `interface`: display_name, short_description, default_prompt, from the skill's own name and description; the
|
|
13
16
|
// 02-standards-runtimes report, §6.3). A sidecar that is already there keeps every value it has: only the interface
|
|
@@ -140,7 +143,7 @@ export function buildExport({ files, dirs = [] }, profileId) {
|
|
|
140
143
|
}
|
|
141
144
|
const next = patchHead(headText, changes);
|
|
142
145
|
if (next === null) {
|
|
143
|
-
diagnostics.push({ ruleId: 'L-08-adapt-failed', severity: 'error', message: `The SKILL.md frontmatter could not be changed to keep ${profile?.title || profileId} from ${t.key === 'user-invocable' ? 'showing it to the person' : 'starting it on its own'}: fix the YAML first.`, file: 'SKILL.md', line: null });
|
|
146
|
+
diagnostics.push({ ruleId: 'L-08-adapt-failed', severity: 'error', message: `The SKILL.md frontmatter could not be changed to keep ${profile?.title || profileId} from ${t.key === 'user-invocable' || t.key === 'disable-slash-command' ? 'showing it to the person' : 'starting it on its own'}: fix the YAML first.`, file: 'SKILL.md', line: null });
|
|
144
147
|
continue;
|
|
145
148
|
}
|
|
146
149
|
headText = next;
|