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
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
],
|
|
84
84
|
"skillMd": [
|
|
85
85
|
"78ebb9f149ff",
|
|
86
|
-
"
|
|
86
|
+
"870138a74151",
|
|
87
|
+
"bb2df5d23b7d"
|
|
87
88
|
],
|
|
88
89
|
"firstCommit": "ae9acec31b71",
|
|
89
90
|
"firstDate": "2026-10-23"
|
|
@@ -96,7 +97,8 @@
|
|
|
96
97
|
],
|
|
97
98
|
"skillMd": [
|
|
98
99
|
"78ebb9f149ff",
|
|
99
|
-
"
|
|
100
|
+
"870138a74151",
|
|
101
|
+
"bb2df5d23b7d"
|
|
100
102
|
],
|
|
101
103
|
"firstCommit": "ae9acec31b71",
|
|
102
104
|
"firstDate": "2026-10-23"
|
|
@@ -147,6 +149,7 @@
|
|
|
147
149
|
"g3"
|
|
148
150
|
],
|
|
149
151
|
"skillMd": [
|
|
152
|
+
"68a8323dfd0d",
|
|
150
153
|
"8d262753dd16",
|
|
151
154
|
"a9ac8e43510e",
|
|
152
155
|
"ea38ed171e73",
|
|
@@ -163,6 +166,7 @@
|
|
|
163
166
|
"g3"
|
|
164
167
|
],
|
|
165
168
|
"skillMd": [
|
|
169
|
+
"68a8323dfd0d",
|
|
166
170
|
"8d262753dd16",
|
|
167
171
|
"a9ac8e43510e",
|
|
168
172
|
"ea38ed171e73",
|
|
@@ -182,7 +186,8 @@
|
|
|
182
186
|
"1019fc5ef755",
|
|
183
187
|
"2a5ae45388b6",
|
|
184
188
|
"740fdb3985fd",
|
|
185
|
-
"b5380300a1f5"
|
|
189
|
+
"b5380300a1f5",
|
|
190
|
+
"f6a41a558efe"
|
|
186
191
|
],
|
|
187
192
|
"firstCommit": "636f18afbe6b",
|
|
188
193
|
"firstDate": "2026-07-21"
|
|
@@ -198,7 +203,8 @@
|
|
|
198
203
|
"1019fc5ef755",
|
|
199
204
|
"2a5ae45388b6",
|
|
200
205
|
"740fdb3985fd",
|
|
201
|
-
"b5380300a1f5"
|
|
206
|
+
"b5380300a1f5",
|
|
207
|
+
"f6a41a558efe"
|
|
202
208
|
],
|
|
203
209
|
"firstCommit": "636f18afbe6b",
|
|
204
210
|
"firstDate": "2026-07-21"
|
|
@@ -246,6 +252,30 @@
|
|
|
246
252
|
"firstCommit": "ae9acec31b71",
|
|
247
253
|
"firstDate": "2026-10-23"
|
|
248
254
|
},
|
|
255
|
+
{
|
|
256
|
+
"digest": "b052a36caf1bbbebe994c5b8c029696306c64824837ae632968a94f0d97a8888",
|
|
257
|
+
"sep": "any",
|
|
258
|
+
"generators": [
|
|
259
|
+
"g3"
|
|
260
|
+
],
|
|
261
|
+
"skillMd": [
|
|
262
|
+
"870138a74151"
|
|
263
|
+
],
|
|
264
|
+
"firstCommit": "0a8f77317114",
|
|
265
|
+
"firstDate": "2026-10-25"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"digest": "7da57892ab385c2668a1afdb2da670ddcc732927103812c46a635eb476d34781",
|
|
269
|
+
"sep": "any",
|
|
270
|
+
"generators": [
|
|
271
|
+
"g3"
|
|
272
|
+
],
|
|
273
|
+
"skillMd": [
|
|
274
|
+
"bb2df5d23b7d"
|
|
275
|
+
],
|
|
276
|
+
"firstCommit": "52d9d1712c34",
|
|
277
|
+
"firstDate": "2026-10-31"
|
|
278
|
+
},
|
|
249
279
|
{
|
|
250
280
|
"digest": "4b619a432fc38e95310399f2d59a0f1501a3984b67007febd8ff92634afffa8f",
|
|
251
281
|
"sep": "any",
|
|
@@ -380,6 +410,30 @@
|
|
|
380
410
|
],
|
|
381
411
|
"firstCommit": "6776e42b885c",
|
|
382
412
|
"firstDate": "2026-10-23"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"digest": "6765902e8854b8d4a859e96aa9233ba61cc0fe6c33d197b9815c01668a92612c",
|
|
416
|
+
"sep": "any",
|
|
417
|
+
"generators": [
|
|
418
|
+
"g3"
|
|
419
|
+
],
|
|
420
|
+
"skillMd": [
|
|
421
|
+
"68a8323dfd0d"
|
|
422
|
+
],
|
|
423
|
+
"firstCommit": "2be15e00f29b",
|
|
424
|
+
"firstDate": "2026-11-02"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"digest": "9f95ee55bf80bd711b191e1e94c74bd2808bcf82291b02bb20f52cab91f14c3b",
|
|
428
|
+
"sep": "any",
|
|
429
|
+
"generators": [
|
|
430
|
+
"g3"
|
|
431
|
+
],
|
|
432
|
+
"skillMd": [
|
|
433
|
+
"f6a41a558efe"
|
|
434
|
+
],
|
|
435
|
+
"firstCommit": "20ad54882a26",
|
|
436
|
+
"firstDate": "2026-11-03"
|
|
383
437
|
}
|
|
384
438
|
]
|
|
385
439
|
}
|
|
@@ -272,7 +272,7 @@ export function createSkillInstaller({ dataDir, now = () => new Date().toISOStri
|
|
|
272
272
|
/**
|
|
273
273
|
* Install `payload` at `destination` (a plain name in `root`).
|
|
274
274
|
* @param {object} o
|
|
275
|
-
* @param {'claude'|'codex'|'opencode'} o.target @param {string} o.name @param {string} o.root @param {string} o.destination
|
|
275
|
+
* @param {'claude'|'codex'|'opencode'|'antigravity'|'agents'} o.target @param {string} o.name @param {string} o.root @param {string} o.destination
|
|
276
276
|
* @param {object} o.payload from payloadFromFolder / payloadFromText
|
|
277
277
|
* @param {object} [o.source] where the payload came from ({ kind: 'bundled', name, digest })
|
|
278
278
|
* @param {{ expectedDestinationDigest?: string }} [o.decision] the person confirmed replacing what is there now
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
// <config>/opencode skill folders stay listed as read by no runtime.
|
|
24
24
|
// OpenCode also reads ~/.claude/skills and both .agents roots (not in SynaBun's own OpenCode panel,
|
|
25
25
|
// which turns the Claude ones off).
|
|
26
|
+
// Antigravity ~/.gemini/config/skills (Google Antigravity's `agy`; where SynaBun installs) and a project's
|
|
27
|
+
// .agents/skills (the Codex project folder, which Antigravity reads too). Not fixture-tested.
|
|
26
28
|
// Shared ~/.agents/skills: Codex and OpenCode (and other agents) read it. SynaBun writes there only when
|
|
27
29
|
// the owner turned that on (`sharedAgentsRoot`).
|
|
28
30
|
// SynaBun PACKAGE_ROOT/skills, the bundled skills: read-only.
|
|
@@ -34,7 +36,7 @@ import { lstatSync, readdirSync, realpathSync } from 'node:fs';
|
|
|
34
36
|
import { homedir } from 'node:os';
|
|
35
37
|
import { join, resolve } from 'node:path';
|
|
36
38
|
|
|
37
|
-
export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode']);
|
|
39
|
+
export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
|
|
38
40
|
|
|
39
41
|
const realFs = { lstat: lstatSync, realpath: realpathSync, readdir: readdirSync };
|
|
40
42
|
|
|
@@ -186,6 +188,11 @@ export function createRootResolver({
|
|
|
186
188
|
add({ family: 'opencode', kind: 'commands', scope: 'user', sourceKind: 'legacy-bridge', path: join(home, '.opencode', 'command'), runtimes: ['opencode'],
|
|
187
189
|
policy: { write: 'migration', reason: 'An old place SynaBun wrote bridges to.' }, discovery: { contract: '~/.opencode/command/<name>.md', verified: 'unverified' } });
|
|
188
190
|
|
|
191
|
+
// Antigravity (Google's `agy`): its own skills folder under ~/.gemini/config. It needs no refresh: it reads the folder again by itself.
|
|
192
|
+
add({ family: 'antigravity', kind: 'skills', scope: 'user', sourceKind: 'native', path: join(home, '.gemini', 'config', 'skills'), runtimes: ['antigravity'],
|
|
193
|
+
policy: owner('Antigravity\'s own skills folder.'), discovery: { contract: '~/.gemini/config/skills/<name>/SKILL.md', verified: 'docs' },
|
|
194
|
+
installTarget: { runtime: 'antigravity', scope: 'user' } });
|
|
195
|
+
|
|
189
196
|
// Shared.
|
|
190
197
|
add({ family: 'shared', kind: 'skills', scope: 'shared', sourceKind: 'shared', path: join(home, '.agents', 'skills'), runtimes: ['codex', 'opencode'],
|
|
191
198
|
policy: sharedOn ? owner('Shared by Codex, OpenCode and other agents; turned on by the owner.') : { write: 'owner-opt-in', reason: 'Shared by Codex, OpenCode and other agents: SynaBun writes here only when you turn it on.' },
|
|
@@ -206,8 +213,8 @@ export function createRootResolver({
|
|
|
206
213
|
policy: owner('The project\'s commands.'), discovery: { contract: '.claude/commands/<ns>/<name>.md → /ns:name', verified: 'docs' } });
|
|
207
214
|
add({ family: 'claude', kind: 'agents', scope: 'project', sourceKind: 'native', path: at('.claude', 'agents'), runtimes: ['claude'], ...p,
|
|
208
215
|
policy: owner('The project\'s agents.'), discovery: { contract: '.claude/agents/<name>.md', verified: 'docs' } });
|
|
209
|
-
add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'native', path: at('.agents', 'skills'), runtimes: ['codex', 'opencode'], ...p,
|
|
210
|
-
policy: owner('The project\'s shared skills (Codex\'s repository folder; OpenCode
|
|
216
|
+
add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'native', path: at('.agents', 'skills'), runtimes: ['codex', 'opencode', 'antigravity'], ...p,
|
|
217
|
+
policy: owner('The project\'s shared skills (Codex\'s repository folder; OpenCode and Antigravity read it too).'), discovery: { contract: '.agents/skills/<name>/SKILL.md', verified: 'fixture codex-cli 0.163.0, opencode 2.0.32; Antigravity: docs' },
|
|
211
218
|
installTarget: { runtime: 'codex', scope: 'project' } });
|
|
212
219
|
add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'compat', path: at('.codex', 'skills'), runtimes: ['codex'], ...p,
|
|
213
220
|
policy: never('Read by Codex 0.163.0 but not in its guide: new skills go to .agents/skills.'), discovery: { contract: '.codex/skills/<name>/SKILL.md', verified: 'fixture codex-cli 0.163.0' } });
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// "off", a deny rule on Skill(name)), OpenCode's opencode.json and opencode.jsonc (the last rule of
|
|
7
7
|
// action `skill` whose resource matches the name: OpenCode 2's `permissions`, an OpenCode 1
|
|
8
8
|
// `permission.skill` read as OpenCode reads it). 'enabled', 'disabled', or 'unknown' when the
|
|
9
|
-
// settings cannot be read.
|
|
9
|
+
// settings cannot be read (Antigravity's are not read: always 'unknown', with no reason).
|
|
10
10
|
// probe asked of the runtime itself, only when the owner asks (Verify in the Studio): Codex lists its
|
|
11
11
|
// skills through `codex app-server` (skills/list, forceReload); OpenCode through SynaBun's own
|
|
12
|
-
// running serve. Claude Code
|
|
13
|
-
// changes no setting.
|
|
12
|
+
// running serve. Claude Code and Antigravity have no listing to ask: they say null with the reason.
|
|
13
|
+
// A probe reads; it changes no setting.
|
|
14
14
|
// enable the one write here, on the owner's click only (POST /api/skills-studio/v2/eligibility/enable):
|
|
15
15
|
// the setting that turns the skill off is changed to let it through, the file is backed up first
|
|
16
16
|
// (DATA_HOME/data/skills/backups/settings/), and the answer names the file, the backup and the lines
|
|
@@ -160,6 +160,8 @@ export function createSkillsRuntimeChecks({ codexHome, claudeHome, opencodeConfi
|
|
|
160
160
|
if (decided?.effect !== 'deny') return { state: 'enabled', reason: null };
|
|
161
161
|
return { state: 'disabled', reason: { kind: 'opencode-permission', file: decided.file, pattern: decided.resource, action: 'deny' } };
|
|
162
162
|
}
|
|
163
|
+
// Antigravity: SynaBun reads none of its settings, so nothing is said of them (no reason: no "could not be read").
|
|
164
|
+
if (runtime === 'antigravity') return { state: 'unknown', reason: null };
|
|
163
165
|
return { state: 'unknown', reason: { kind: 'unknown-runtime' } };
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -273,6 +275,7 @@ export function createSkillsRuntimeChecks({ codexHome, claudeHome, opencodeConfi
|
|
|
273
275
|
} catch (err) { return { discovered: null, code: 'no-answer', method: 'opencode serve', reason: err?.message || String(err) }; }
|
|
274
276
|
}
|
|
275
277
|
if (runtime === 'claude') return { discovered: null, code: 'no-list', method: 'none', reason: 'Claude Code has no list to ask; its folder rules were checked instead.' };
|
|
278
|
+
if (runtime === 'antigravity') return { discovered: null, code: 'no-list', method: 'none', reason: 'Antigravity has no list SynaBun can ask; its folder rules were checked instead.' };
|
|
276
279
|
return { discovered: null, method: 'none', reason: 'Unknown runtime.' };
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -50,7 +50,7 @@ export const SERVICE_CAPABILITIES = Object.freeze([
|
|
|
50
50
|
'evals', 'usage', 'health', 'discover', 'packages', 'external-scanner-status',
|
|
51
51
|
]);
|
|
52
52
|
|
|
53
|
-
export const ASSISTANT_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode']);
|
|
53
|
+
export const ASSISTANT_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
|
|
54
54
|
|
|
55
55
|
export class ServiceError extends PathConfineError {
|
|
56
56
|
constructor(status, message, code, extra = {}) {
|
|
@@ -68,7 +68,7 @@ export class ServiceError extends PathConfineError {
|
|
|
68
68
|
* @param {string|null} [o.bundledDir] PACKAGE_ROOT/skills
|
|
69
69
|
* @param {string} [o.appVersion]
|
|
70
70
|
* @param {(event) => void} [o.broadcast]
|
|
71
|
-
* @param {() => ({ claude?: boolean, codex?: boolean, opencode?: boolean }|null)} [o.installedRuntimes] which CLIs are
|
|
71
|
+
* @param {() => ({ claude?: boolean, codex?: boolean, opencode?: boolean, antigravity?: boolean }|null)} [o.installedRuntimes] which CLIs are
|
|
72
72
|
* installed on this computer (server.js: the CLI resolver); null when not known
|
|
73
73
|
* @param {object} [o.runtimeChecks] lib/skills/runtime-checks.js (the owner's enable)
|
|
74
74
|
*/
|
|
@@ -116,11 +116,13 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
|
|
|
116
116
|
const used = usedRuntimes();
|
|
117
117
|
const detected = ASSISTANT_RUNTIMES.filter((r) => (installed ? installed[r] !== false : true) && used[r]);
|
|
118
118
|
const fallback = detected.length ? detected : ['claude'];
|
|
119
|
-
|
|
119
|
+
// `installed` leaves out only a runtime the host says is missing (false): one it says nothing of (a host from before
|
|
120
|
+
// Antigravity) is never called "not installed".
|
|
121
|
+
return { chosen: config.assistants, detected: fallback, effective: config.assistants && config.assistants.length ? config.assistants : fallback, installed: installed ? ASSISTANT_RUNTIMES.filter((r) => installed[r] !== false) : null };
|
|
120
122
|
}
|
|
121
123
|
/** Which runtimes have a home on this computer (their skills folder or its parent exists), from the roots. */
|
|
122
124
|
function usedRuntimes() {
|
|
123
|
-
const out = { claude: false, codex: false, opencode: false };
|
|
125
|
+
const out = { claude: false, codex: false, opencode: false, antigravity: false };
|
|
124
126
|
let list = [];
|
|
125
127
|
try { list = roots.listRoots(); } catch { list = []; }
|
|
126
128
|
for (const r of list) {
|
|
@@ -181,7 +183,7 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
|
|
|
181
183
|
const binaryHead = head ? !isEditableText(head.bytes) : false;
|
|
182
184
|
const base = lintText({ type: 'skill', text, folderName, binary: binaryHead }).lint;
|
|
183
185
|
const sidecar = readSidecar(files);
|
|
184
|
-
const ids = Array.isArray(profiles) && profiles.length ? profiles.filter((p) => getProfile(p)) : ['portable-six-field', 'claude-code', 'codex-native', 'opencode-native'];
|
|
186
|
+
const ids = Array.isArray(profiles) && profiles.length ? profiles.filter((p) => getProfile(p)) : ['portable-six-field', 'claude-code', 'codex-native', 'opencode-native', 'antigravity-native'];
|
|
185
187
|
const byProfile = {};
|
|
186
188
|
for (const id of ids) {
|
|
187
189
|
const lint = lintForProfile(id, { parsed, folderName, files: files.map((f) => ({ path: f.path, size: f.bytes.length })), sidecar });
|
|
@@ -281,6 +283,11 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
|
|
|
281
283
|
const entry = await catalog.find(artifactId);
|
|
282
284
|
if (!entry) throw new ServiceError(404, 'There is no such entry in the catalog.', 'artifact_not_found');
|
|
283
285
|
if (entry.kind !== 'skill') throw new ServiceError(400, 'Only a skill folder becomes a library skill.', 'not_a_skill');
|
|
286
|
+
// A folder SynaBun installed a library skill into is that skill already: adopting it again would make a second
|
|
287
|
+
// library skill of the same bytes (two Library rows, each flagged "same description" against the other).
|
|
288
|
+
const installed = deployer.activeAt(entry.path) || (entry.realPath && entry.realPath !== entry.path ? deployer.activeAt(entry.realPath) : null);
|
|
289
|
+
const owner = installed ? library.readSkill(installed.skillId) : null;
|
|
290
|
+
if (owner) return { skill: skillSummary(library.getSkill(owner.skillId)), revision: null, created: false, unchanged: true, installed: true, excluded: [] };
|
|
284
291
|
const { files, dirs, excluded } = collectFolder(entry.realPath);
|
|
285
292
|
const existing = library.findSkill((s) => s.origin?.kind === 'adopted' && s.origin?.artifactId === artifactId);
|
|
286
293
|
const source = { kind: 'adopted', path: entry.path, rootId: entry.rootId, artifactId, name: entry.folderName };
|
|
@@ -475,7 +482,7 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
|
|
|
475
482
|
* backup, and the answer says what changed (lib/skills/runtime-checks.js enable).
|
|
476
483
|
*/
|
|
477
484
|
function enableEligibility({ runtime, name, skillMd, accountId = 'default', projectPath = null, confirmDenyAll = false }) {
|
|
478
|
-
if (!['claude', 'codex', 'opencode'].includes(runtime)) throw new ServiceError(400, 'runtime is claude, codex or
|
|
485
|
+
if (!['claude', 'codex', 'opencode', 'antigravity'].includes(runtime)) throw new ServiceError(400, 'runtime is claude, codex, opencode or antigravity.', 'invalid_target');
|
|
479
486
|
if (typeof name !== 'string' || !isPlainName(name)) throw new ServiceError(400, 'name is a skill\'s folder name.', 'invalid_name');
|
|
480
487
|
if (typeof skillMd !== 'string' || !skillMd || !/[\\/]SKILL\.md$/.test(skillMd)) throw new ServiceError(400, 'skillMd is the path of the skill\'s SKILL.md.', 'invalid_path');
|
|
481
488
|
// Only a skill at a place SynaBun installs to (one of its install roots, or a folder a runtime reads): never an
|
|
@@ -492,7 +499,8 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
|
|
|
492
499
|
/** The legacy chips' view of one bundled skill: what each runtime has of it (native folder, old bridge, neither). */
|
|
493
500
|
function legacyTargetStates(name) {
|
|
494
501
|
const out = {};
|
|
495
|
-
|
|
502
|
+
// Antigravity installs to ~/.gemini/config/skills (lib/skills/roots.js), like the others to their own folders.
|
|
503
|
+
for (const runtime of ['claude', 'codex', 'opencode', 'antigravity']) {
|
|
496
504
|
const root = roots.installRoot({ runtime, scope: 'user', accountId: 'default' });
|
|
497
505
|
const native = root ? deployer.activeAt(join(root.path, name)) : null;
|
|
498
506
|
let nativeState = 'absent';
|
|
@@ -9,6 +9,7 @@ export const USAGE_COVERAGE = Object.freeze({
|
|
|
9
9
|
claude: { native: true, method: 'Skill tool request + successful tool result', limit: 'SynaBun dispatched runs only. Discovery is not invocation; task outcome is not skill quality.' },
|
|
10
10
|
codex: { native: false, method: 'Successful exact cat of an installed SKILL.md', limit: 'No native invocation event. File reads are inferred loads, never invocation counts. Complex shell commands are not classified.' },
|
|
11
11
|
opencode: { native: true, method: 'Completed skill tool part', limit: 'SynaBun dispatched runs only. Other terminals and historical transcripts are not mined.' },
|
|
12
|
+
antigravity: { native: false, method: 'Successful read of an installed SKILL.md', limit: 'No native invocation event: Antigravity reads a skill\'s SKILL.md itself. File reads are inferred loads, never invocation counts. SynaBun dispatched runs only.' },
|
|
12
13
|
});
|
|
13
14
|
const hash = (s) => createHash('sha256').update(s).digest('hex');
|
|
14
15
|
// Native item IDs can restart on a follow-up turn; the dispatcher retains this turn across restarts.
|
|
@@ -65,6 +66,11 @@ export function createSkillTelemetry({ dataDir, deployer, settings, now = Date.n
|
|
|
65
66
|
} else if (c.ok === true) add(run, i, 'invoked', 'native_skill_tool', c.id);
|
|
66
67
|
else if (c.ok === false) add(run, i, 'failed', 'native_skill_tool', c.id);
|
|
67
68
|
}
|
|
69
|
+
// Antigravity reads an installed skill's SKILL.md with its own file tool: a completed read of that exact file.
|
|
70
|
+
if (run.provider === 'antigravity' && (c.tool === 'read_file' || c.tool === 'view_file') && c.ok === true && typeof c.input.path === 'string') {
|
|
71
|
+
const matches = installs.filter((i) => resolve(c.input.path) === join(i.destination.path, 'SKILL.md'));
|
|
72
|
+
if (matches.length === 1) add(run, matches[0], 'loaded', 'successful_skill_file_read', c.id, 'inferred');
|
|
73
|
+
}
|
|
68
74
|
if (run.provider === 'codex' && c.tool === 'shell' && c.phase === 'done' && c.ok === true) {
|
|
69
75
|
// A conservative grammar: no pipes, substitutions, multiple commands, or merely mentioning a path.
|
|
70
76
|
let command = c.input.command || '';
|
|
@@ -121,7 +121,7 @@ export function semverNewer(a, b) {
|
|
|
121
121
|
// versions of the same scheme compare by compareSemver.
|
|
122
122
|
//
|
|
123
123
|
// Only SynaBun's own versions go through these functions. The CLI tools
|
|
124
|
-
// SynaBun watches (Claude Code, Codex,
|
|
124
|
+
// SynaBun watches (Claude Code, Codex, Antigravity, OpenCode) keep plain
|
|
125
125
|
// compareSemver / semverNewer.
|
|
126
126
|
|
|
127
127
|
/** True for a date-style SynaBun version (`2026.9.5`, `v.2026.07.34`, `2026.4.26-1`). */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
/** The kinds an answer may name, in the order the interface lists them. */
|
|
16
16
|
export const ACTIVITY_KINDS = Object.freeze([
|
|
17
|
-
'assistant', 'claude-panel', 'codex-panel', 'opencode-panel', 'claude-cli', 'loop', 'schedule', 'backup', 'maintenance', 'unknown',
|
|
17
|
+
'assistant', 'claude-panel', 'codex-panel', 'opencode-panel', 'antigravity-panel', 'claude-cli', 'loop', 'schedule', 'backup', 'maintenance', 'unknown',
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
20
|
/** A Claude Code session that took a prompt this recently may still be working on it. */
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
import { findModel, hiddenModelId } from '../assistant-catalog.js';
|
|
14
14
|
import { modelEfforts } from '../effort-levels.js';
|
|
15
|
-
import { providerLabel, remoteBrainLimited } from '../remote-policy.js';
|
|
15
|
+
import { providerLabel as remoteProviderLabel, remoteBrainLimited } from '../remote-policy.js';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Every provider the Assistant can run on: Antigravity too (lib/assistant-brains/antigravity.js). Like Codex and
|
|
18
|
+
// OpenCode it holds a WhatsApp conversation read-only (lib/remote-policy.js remoteBrainLimited).
|
|
19
|
+
export const BRAIN_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode', 'antigravity']);
|
|
20
|
+
// The provider's name in what the owner reads (remote-policy.js names the first three).
|
|
21
|
+
const providerLabel = (provider) => (provider === 'antigravity' ? 'Antigravity' : remoteProviderLabel(provider));
|
|
18
22
|
const EFFORT_RE = /^[A-Za-z0-9][\w.-]{0,39}$/;
|
|
19
23
|
const MODEL_MAX = 200;
|
|
20
24
|
|
|
@@ -55,7 +59,7 @@ function refusal(message, { code = 'CONFIG_INVALID', status = 400 } = {}) {
|
|
|
55
59
|
*/
|
|
56
60
|
export function checkBrainChoice(choice, catalog) {
|
|
57
61
|
const clean = cleanBrainChoice(choice);
|
|
58
|
-
if (clean === undefined) return refusal('brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or
|
|
62
|
+
if (clean === undefined) return refusal('brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex, opencode or antigravity');
|
|
59
63
|
if (clean === null) return { ok: true, brain: null };
|
|
60
64
|
if (!catalog || typeof catalog !== 'object') {
|
|
61
65
|
return refusal('The model list is not available right now, so this model could not be checked. Try again in a few seconds.', { code: 'CATALOG_UNAVAILABLE', status: 503 });
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
import { CONTROL_KINDS, buildControlResponse, describeToolInput, normalizeAnswers, normalizeControlRequest } from '../../public/shared/assistant/asst-control.js';
|
|
51
51
|
import { buildRouteResponse, normalizeRouteRequest, routeOptions } from '../../public/shared/assistant/asst-route.js';
|
|
52
|
-
import { isComputerTool, isDeniedRemoteTool } from '../remote-policy.js';
|
|
52
|
+
import { isComputerTool, isDeniedRemoteTool, providerLabel } from '../remote-policy.js';
|
|
53
53
|
|
|
54
54
|
// Codes a person can read out loud: no 0/O, 1/I/L, 2/Z, 5/S, 8/B.
|
|
55
55
|
const TAG_ALPHABET = 'ACDEFGHJKMNPQRTUVWXY34679';
|
|
@@ -266,9 +266,10 @@ function choiceName(choice) {
|
|
|
266
266
|
const strip = (value) => clean(value).replace(/^do it here with /i, '');
|
|
267
267
|
return strip(choice.label) || strip(choice.title) || 'that model';
|
|
268
268
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
/** A choice that runs in this conversation: "here" on its model, or on another model of its provider. */
|
|
270
|
+
const runsHere = (choice) => !!choice?.here || choice?.kind === 'direct';
|
|
271
|
+
function routePhrase(choice, { it = 'it' } = {}) {
|
|
272
|
+
return runsHere(choice) ? `do ${it} here with ${choiceName(choice)}` : `hand ${it} to ${choiceName(choice)}`;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
/**
|
|
@@ -294,7 +295,9 @@ export function renderCard(card, { formatPlan = (text) => text } = {}) {
|
|
|
294
295
|
lines.push(`I can't take that choice from the phone (${card.desktopOnly}): pick it in SynaBun on your computer, or say no and I'll drop it.`);
|
|
295
296
|
return lines.join('\n');
|
|
296
297
|
}
|
|
297
|
-
|
|
298
|
+
// The other choices, the ones done here first, then the workers: "do it here with …, hand it to … or hand it to …".
|
|
299
|
+
const rest = [...others.filter(runsHere), ...others.filter((choice) => !runsHere(choice))].slice(0, 4);
|
|
300
|
+
if (rest.length) lines.push(`I could also ${joinOr(rest.map((choice) => routePhrase(choice)))}: just name it.${mac ? ' Then I ask about the Mac separately.' : ''}`);
|
|
298
301
|
return `${lines.join('\n')}${tagHint(card)}`;
|
|
299
302
|
}
|
|
300
303
|
if (card.kind === CONTROL_KINDS.CLARIFY) {
|
|
@@ -328,7 +331,8 @@ export function renderCard(card, { formatPlan = (text) => text } = {}) {
|
|
|
328
331
|
if (card.desktopOnly) return `I'd need to control your Mac for this, and I can't take a yes for that from the phone (${card.desktopOnly}): answer in SynaBun on your computer, or say no and I'll skip it.`;
|
|
329
332
|
return `${COMPUTER_ASK_TEXT}${tagHint(card)}`;
|
|
330
333
|
}
|
|
331
|
-
|
|
334
|
+
// The worker's provider by its name ("Codex", "Antigravity"), not its id; Claude Code's is the default and goes unsaid.
|
|
335
|
+
const worker = card.source === 'dispatch' ? `One of the agents${card.provider && card.provider !== 'claude-code' ? ` (${providerLabel(card.provider)})` : ''}` : null;
|
|
332
336
|
const lines = [worker ? `${worker} needs your OK to run this:` : 'I need your OK to run this:', ...toolLines(card)];
|
|
333
337
|
if (n.message && n.message !== n.detail) lines.push(clip(n.message, 200));
|
|
334
338
|
if (card.desktopOnly) lines.push(`I can't take a yes for this one from the phone (${card.desktopOnly}): approve it in SynaBun on your computer, or say no and I'll skip it.`);
|
|
@@ -426,12 +430,21 @@ function matchRouteChoice(card, text) {
|
|
|
426
430
|
said = said.replace(PICK_LEAD, '').replace(/^the /, '');
|
|
427
431
|
for (let i = 0; i < 3; i += 1) said = said.replace(/ (?:please|instead|then|thanks|model)$/, '');
|
|
428
432
|
if (said.length < 2) return null;
|
|
433
|
+
// "Here" names the option on the Assistant's own model (else the first done here); "dispatch" / "a worker"
|
|
434
|
+
// names the first worker option, the one the card suggests for the worker side.
|
|
435
|
+
const firstHere = card.routeChoices.find((choice) => choice.here) || card.routeChoices.find(runsHere) || null;
|
|
436
|
+
const firstWorker = card.routeChoices.find((choice) => !runsHere(choice)) || null;
|
|
429
437
|
const names = (choice) => {
|
|
430
438
|
const list = [choice.label, choiceName(choice), choice.model, choice.title, String(choice.model || '').split('/').pop()].map(plain).filter(Boolean);
|
|
431
|
-
if (choice
|
|
439
|
+
if (choice === firstHere) list.push('here', 'do it here', 'do it yourself', 'yourself', 'you do it', `here with ${plain(choice.label)}`);
|
|
440
|
+
if (choice === firstWorker) list.push('dispatch', 'dispatch it', 'a worker', 'worker', 'hand it off', 'delegate', 'delegate it');
|
|
432
441
|
return [...new Set(list)];
|
|
433
442
|
};
|
|
434
|
-
|
|
443
|
+
// A whole name first: "do it here" is the "here" option even when another option's label starts with it
|
|
444
|
+
// ("Do it here with Haiku 4.5"); only then the start of a name ("sonnet" for "Sonnet 5").
|
|
445
|
+
const exact = card.routeChoices.filter((choice) => names(choice).includes(said));
|
|
446
|
+
if (exact.length) return exact.length === 1 ? exact[0] : null;
|
|
447
|
+
const hits = card.routeChoices.filter((choice) => names(choice).some((name) => said.length >= 3 && name.startsWith(`${said} `)));
|
|
435
448
|
return hits.length === 1 ? hits[0] : null;
|
|
436
449
|
}
|
|
437
450
|
|
|
@@ -472,7 +485,7 @@ export function answerCard(card, reply, { quoted = false } = {}) {
|
|
|
472
485
|
if (!tag.ok) return needTag();
|
|
473
486
|
if (card.desktopOnly || !choice) return { action: 'desktop', message: 'I can\'t take that choice from the phone: pick it in SynaBun on your computer, or say no and I\'ll drop it.' };
|
|
474
487
|
const onMac = routeControlsMac(card) && choice.id === card.routeChoices[0].id;
|
|
475
|
-
return respond(card, buildRouteResponse(card.route, { optionId: choice.id, remember: false }), onMac ? `doing it on your Mac, here with ${choiceName(choice)}. Esc on the Mac stops me` : choice
|
|
488
|
+
return respond(card, buildRouteResponse(card.route, { optionId: choice.id, remember: false }), onMac ? `doing it on your Mac, here with ${choiceName(choice)}. Esc on the Mac stops me` : runsHere(choice) ? `doing it here with ${choiceName(choice)}` : `handing it to ${choiceName(choice)}`);
|
|
476
489
|
}
|
|
477
490
|
if (stop || parsed.type === 'no' || (numbers.length === 1 && numbers[0] === 2)) return deny(card, parsed.rest);
|
|
478
491
|
const yes = parsed.type === 'yes' || (numbers.length === 1 && numbers[0] === 1 && !parsed.rest);
|
|
@@ -164,7 +164,7 @@ const MESSAGES = {
|
|
|
164
164
|
activityText: 'activityText must be true or false',
|
|
165
165
|
strictWorkerApprovals: 'strictWorkerApprovals must be true or false',
|
|
166
166
|
computerUse: 'computerUse must be true or false',
|
|
167
|
-
brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or
|
|
167
|
+
brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex, opencode or antigravity',
|
|
168
168
|
transcriptionEnabled: 'transcriptionEnabled must be true or false',
|
|
169
169
|
transcriptionLanguage: "transcriptionLanguage must be 'auto' or a 2-3 letter lowercase language code, such as en or pt",
|
|
170
170
|
transcriptionEnginePath: 'transcriptionEnginePath must be empty (find whisper-cli by itself) or the absolute path of the whisper-cli program',
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
40
40
|
import { homedir, tmpdir } from 'node:os';
|
|
41
41
|
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
42
|
+
import { acpCall, acpToolPaths } from './antigravity/tool-call.js';
|
|
42
43
|
|
|
43
44
|
export const WORKER_GUARD_MODES = Object.freeze(['enforce', 'warn', 'off']);
|
|
44
45
|
export const WORKER_GUARD_DEFAULT = 'enforce';
|
|
@@ -46,7 +47,7 @@ export const WORKER_CONFINEMENT = 'worker';
|
|
|
46
47
|
/** The capabilities the guard applies to (never 'full'). */
|
|
47
48
|
export const GUARDED_CAPABILITIES = Object.freeze(['read-only', 'workspace']);
|
|
48
49
|
/** Folders under the home a worker must never write into (documented; the rule itself is "outside the writable roots"). */
|
|
49
|
-
export const PROTECTED_HOME_DIRS = Object.freeze(['.claude', '.codex', '.agents', '.config/opencode', '.synabun', '.local/share/opencode', '.ssh', '.gnupg', '.aws', '.config']);
|
|
50
|
+
export const PROTECTED_HOME_DIRS = Object.freeze(['.claude', '.codex', '.agents', '.config/opencode', '.synabun', '.local/share/opencode', '.gemini', '.ssh', '.gnupg', '.aws', '.config']);
|
|
50
51
|
|
|
51
52
|
export function normalizeWorkerGuardMode(value) {
|
|
52
53
|
const text = String(value ?? '').trim().toLowerCase();
|
|
@@ -1163,6 +1164,81 @@ export function openCodePermissionDecision(request, confinement, { toolName = nu
|
|
|
1163
1164
|
}
|
|
1164
1165
|
}
|
|
1165
1166
|
|
|
1167
|
+
// The ACP tool kinds that change files (session/request_permission's toolCall.kind).
|
|
1168
|
+
const ACP_WRITE_KINDS = new Set(['edit', 'delete', 'move']);
|
|
1169
|
+
// Preserve the public import while using Antigravity's canonical path reader.
|
|
1170
|
+
export { acpToolPaths };
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* An ACP permission request (session/request_permission: its toolCall { kind, title, locations, rawInput }) under
|
|
1174
|
+
* a confinement, the way Antigravity's `agy acp` asks before an edit, a command or work outside the workspace:
|
|
1175
|
+
* null when it may go ahead, else a refusal. `execute` is read through the shell heuristic, `edit` / `delete` /
|
|
1176
|
+
* `move` by every path the call names (all of them must be writable; read-only refuses them all), `read` /
|
|
1177
|
+
* `search` pass unless they reach one of SynaBun's protected home folders, `fetch` passes unless it calls one of
|
|
1178
|
+
* SynaBun's own local ports, and anything else (an MCP tool) goes through workerToolDecision by its title.
|
|
1179
|
+
*/
|
|
1180
|
+
export function acpPermissionDecision(rawCall, confinement) {
|
|
1181
|
+
if (!confinement) return null;
|
|
1182
|
+
try {
|
|
1183
|
+
// Either shape Antigravity sends (a permission request's toolCall, a hook payload's { name, args }), read as one
|
|
1184
|
+
// (lib/antigravity/tool-call.js): its kind from its name when it has none, an MCP call as its server and tool.
|
|
1185
|
+
const call = rawCall?.args && 'mcp' in rawCall ? rawCall : acpCall(rawCall);
|
|
1186
|
+
const toolCall = { ...call, rawInput: call.args };
|
|
1187
|
+
// An MCP call is the tool it calls (agy reports it as kind `execute` with no command line).
|
|
1188
|
+
if (call.mcp) return workerToolDecision(`mcp__${call.mcp.server}__${call.mcp.tool}`, call.mcp.args, confinement);
|
|
1189
|
+
const kind = String(call.kind || 'other').toLowerCase();
|
|
1190
|
+
const input = call.args;
|
|
1191
|
+
const label = String(call.title || call.name || kind).slice(0, 120);
|
|
1192
|
+
if (kind === 'execute') {
|
|
1193
|
+
const command = typeof input.command === 'string' ? input.command
|
|
1194
|
+
: Array.isArray(input.command) ? input.command.join(' ')
|
|
1195
|
+
: typeof input.CommandLine === 'string' ? input.CommandLine
|
|
1196
|
+
: typeof input.cmd === 'string' ? input.cmd : '';
|
|
1197
|
+
if (!command) return refusal('unknown-command', `${label} names no command SynaBun could read, so it is refused; this run writes inside ${rootsText(confinement)} only.`);
|
|
1198
|
+
return workerShellDecision(command, confinement);
|
|
1199
|
+
}
|
|
1200
|
+
if (ACP_WRITE_KINDS.has(kind)) {
|
|
1201
|
+
if (confinement.capability === 'read-only') return refusal('read-only', `This run has capability read-only: ${label} (${kind}) is not available. Report what you found instead of changing files.`);
|
|
1202
|
+
const paths = acpToolPaths(toolCall);
|
|
1203
|
+
if (!paths.length) return refusal('unknown-path', `${label} (${kind}) names no path SynaBun could read; this run writes inside ${rootsText(confinement)} only.`);
|
|
1204
|
+
for (const raw of paths) {
|
|
1205
|
+
const place = placeOf(raw, { cwd: confinement.cwd, home: confinement.home });
|
|
1206
|
+
if (!place) return refusal('unknown-path', `${label} names a path SynaBun could not read ("${String(raw).slice(0, 200)}"); this run writes inside ${rootsText(confinement)} only.`);
|
|
1207
|
+
if (!mayWrite(place, confinement)) return refusal('write-outside', `${label} (${kind}) to ${place} is outside this run's workspace: this run writes inside ${rootsText(confinement)} only.`, place);
|
|
1208
|
+
}
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
if (kind === 'read' || kind === 'search') {
|
|
1212
|
+
for (const raw of acpToolPaths(toolCall)) {
|
|
1213
|
+
const place = placeOf(raw, { cwd: confinement.cwd, home: confinement.home });
|
|
1214
|
+
if (place && isProtectedPlace(place, confinement)) {
|
|
1215
|
+
return refusal('protected-path', `${label} reaches ${place}, one of SynaBun's protected home folders (its data home or a runtime's credential store): a worker does not read there, whatever its capability.`, place);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
if (kind === 'fetch') {
|
|
1221
|
+
const url = typeof input.url === 'string' ? input.url : typeof input.Url === 'string' ? input.Url : '';
|
|
1222
|
+
if (url) {
|
|
1223
|
+
let parsed = null;
|
|
1224
|
+
try { parsed = new URL(url); } catch { /* not a URL: nothing to compare */ }
|
|
1225
|
+
if (parsed && isLoopbackHost(parsed.hostname)) {
|
|
1226
|
+
const port = Number(parsed.port || (parsed.protocol === 'https:' ? 443 : 80));
|
|
1227
|
+
const kindOfPort = controlPortOf(port, confinement);
|
|
1228
|
+
if (kindOfPort) return refusal('control-port', `${label}: ${controlPortText(kindOfPort, parsed.hostname, port, confinement)}; a worker does not call it.`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
if (kind === 'think' || kind === 'switch_mode') return null;
|
|
1234
|
+
// MCP and other tools: by the name the call carries ("mcp__server__tool" when the agent says so).
|
|
1235
|
+
const name = typeof input.toolName === 'string' ? input.toolName : typeof toolCall?.title === 'string' ? toolCall.title : '';
|
|
1236
|
+
return name ? workerToolDecision(name, input, confinement) : null;
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
return refusal('check-failed', `The request could not be checked (${err?.message || err}), so it is refused.`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1166
1242
|
/** What the run's transcript records for a decision: { type: 'synabun.confinement', action, tool, rule, reason, target? }. */
|
|
1167
1243
|
export function confinementEvent(decision, { tool, confinement, provider = null } = {}) {
|
|
1168
1244
|
return {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Provider wire answers for the dispatcher's single, first-wins pending request.
|
|
2
|
+
import { codexControlRequestFromServerRequest, codexControlResponse } from './assistant-envelope.js';
|
|
3
|
+
import { validateCodexServerResponse } from './codex-compat-rpc.js';
|
|
4
|
+
|
|
5
|
+
export const CODEX_QUESTIONS = new Set(['item/tool/requestUserInput', 'tool/requestUserInput', 'mcpServer/elicitation/request']);
|
|
6
|
+
|
|
7
|
+
export function workerQuestions(params = {}) {
|
|
8
|
+
if (Array.isArray(params.questions)) return params.questions;
|
|
9
|
+
if (String(params.message || '').startsWith('[SYNABUN_CHOICE_V1]')) {
|
|
10
|
+
try { return JSON.parse(params.message.slice('[SYNABUN_CHOICE_V1]'.length)).questions || []; } catch { /* generic form */ }
|
|
11
|
+
}
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function elicitationReply(params, reply = {}) {
|
|
16
|
+
const action = reply.action || reply.result?.action || (reply.behavior === 'allow' ? 'accept' : 'cancel');
|
|
17
|
+
if (reply.behavior !== 'allow' || action !== 'accept') return { action: action === 'decline' ? 'decline' : 'cancel' };
|
|
18
|
+
const supplied = reply.content ?? reply.result?.content ?? reply.answers ?? {};
|
|
19
|
+
if (!supplied || typeof supplied !== 'object' || Array.isArray(supplied)) throw new Error('Question answers must be an object');
|
|
20
|
+
const content = {};
|
|
21
|
+
for (const [key, value] of Object.entries(supplied)) {
|
|
22
|
+
const answers = value?.answers;
|
|
23
|
+
const property = params.requestedSchema?.properties?.[key];
|
|
24
|
+
content[key] = Array.isArray(answers) ? (property?.type === 'array' ? answers : answers.join(', ')) : value;
|
|
25
|
+
// An Assistant answer may give free text instead of choice's wire sentinel.
|
|
26
|
+
if (property?.type === 'array' && !Array.isArray(content[key])) content[key] = [String(content[key])];
|
|
27
|
+
if (property?.oneOf && !property.oneOf.some((o) => o.const === content[key]) && params.requestedSchema.properties[`${key}__other`]) {
|
|
28
|
+
content[`${key}__other`] = String(content[key]); content[key] = '__synabun_other__';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const key of params.requestedSchema?.required || []) {
|
|
32
|
+
if (content[key] == null || content[key] === '' || (Array.isArray(content[key]) && !content[key].length)) throw new Error(`Missing answer for ${key}`);
|
|
33
|
+
}
|
|
34
|
+
for (const [key, value] of Object.entries(content)) {
|
|
35
|
+
const schema = params.requestedSchema?.properties?.[key];
|
|
36
|
+
if (!schema) continue;
|
|
37
|
+
if (schema.type === 'array') {
|
|
38
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== 'string')) throw new Error(`Invalid choices for ${key}`);
|
|
39
|
+
} else if (schema.type && typeof value !== schema.type.replace('integer', 'number')) throw new Error(`Invalid answer for ${key}`);
|
|
40
|
+
if ((value === '__synabun_other__' || Array.isArray(value) && value.includes('__synabun_other__')) && !content[`${key}__other`]?.trim()) throw new Error(`Missing custom answer for ${key}`);
|
|
41
|
+
}
|
|
42
|
+
return { action: 'accept', content };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function codexWorkerReply(packet, reply) {
|
|
46
|
+
if (packet.method === 'mcpServer/elicitation/request') return { result: elicitationReply(packet.params || {}, reply) };
|
|
47
|
+
if (reply.result?.answers && !reply.answers) reply = { ...reply, answers: reply.result.answers };
|
|
48
|
+
const result = codexControlResponse(codexControlRequestFromServerRequest({ ...packet, requestId: packet.id }), reply);
|
|
49
|
+
if (result.result) validateCodexServerResponse(packet.method, packet.params, result.result);
|
|
50
|
+
return result.error ? { error: result.error } : { result: result.result };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function openCodeAnswers(questions, answers) {
|
|
54
|
+
const list = Array.isArray(answers) ? answers : questions.map((q, i) => answers?.[q.id || q.question] ?? answers?.[q.header] ?? answers?.[i]);
|
|
55
|
+
return questions.map((q, i) => {
|
|
56
|
+
const value = list[i]?.answers ?? list[i];
|
|
57
|
+
const result = (Array.isArray(value) ? value : value == null ? [] : [value]).map(String);
|
|
58
|
+
if (!result.length || !result.some((v) => v.trim())) throw new Error(`Missing answer for ${q.header || q.question || i}`);
|
|
59
|
+
return result;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Validate before consuming the resolver, so a malformed answer leaves a retryable card. */
|
|
64
|
+
export function validateWorkerReply(pending, reply) {
|
|
65
|
+
if (reply.behavior !== 'allow' || pending.kind !== 'question') return;
|
|
66
|
+
const protocol = pending.protocol;
|
|
67
|
+
if (protocol?.method === 'elicitation/create') elicitationReply(pending.input || {}, reply);
|
|
68
|
+
else if (protocol?.provider === 'codex') codexWorkerReply({ id: protocol.id, method: protocol.method, params: pending.input || {} }, reply);
|
|
69
|
+
else if (protocol?.provider === 'opencode') openCodeAnswers(pending.questions || [], reply.answers);
|
|
70
|
+
else if (pending.toolName === 'AskUserQuestion') {
|
|
71
|
+
const answers = reply.answers ?? reply.updatedInput?.answers;
|
|
72
|
+
for (const q of pending.questions || []) if (!answers?.[q.question]) throw new Error(`Missing answer for ${q.question}`);
|
|
73
|
+
}
|
|
74
|
+
}
|