synabun 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +501 -39
- package/neural-interface/i18n/pt-BR.json +501 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +207 -49
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +13 -4
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +420 -21
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +11 -2
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +32 -2
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +1 -1
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-panel.js +45 -8
- package/neural-interface/public/shared/assistant/asst-render.js +51 -12
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scenes.js +54 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-styles.js +34 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +27 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +354 -34
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +299 -77
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +644 -426
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -3,7 +3,7 @@ import { storedToolList, toolListRecord, readStoredToolListRecord } from './tool
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, resolve } from 'node:path';
|
|
5
5
|
|
|
6
|
-
export const NATIVE_LOOP_PROFILES = new Set(['claude-code', 'codex', 'opencode']);
|
|
6
|
+
export const NATIVE_LOOP_PROFILES = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
|
|
7
7
|
|
|
8
8
|
export function isNativeLoopProfile(profile) {
|
|
9
9
|
return NATIVE_LOOP_PROFILES.has(String(profile || ''));
|
|
@@ -22,6 +22,7 @@ function cleanDescriptor(record) {
|
|
|
22
22
|
provider: record.provider,
|
|
23
23
|
providerSessionId: record.providerSessionId || null,
|
|
24
24
|
providerThreadId: record.providerThreadId || null,
|
|
25
|
+
...(record.antigravityHome ? { antigravityHome: record.antigravityHome } : {}),
|
|
25
26
|
source: record.source,
|
|
26
27
|
focus: !!record.focus,
|
|
27
28
|
status: record.status,
|
|
@@ -53,6 +54,7 @@ function cleanDescriptor(record) {
|
|
|
53
54
|
// existing consumers (router, sidepanels, schedules) are unaffected.
|
|
54
55
|
runMode: record.runMode || 'loop',
|
|
55
56
|
turnState: record.turnState || null,
|
|
57
|
+
pending: record.status === 'running' ? (record.pendingRequests || []) : [],
|
|
56
58
|
// What the last turn's result said (task mode: done, blocked or needs_input; the dispatcher's
|
|
57
59
|
// lastResult.status): an idle warm worker whose turn ended asking waits on the user.
|
|
58
60
|
outcome: record.outcome || null,
|
|
@@ -184,6 +186,14 @@ export class NativeLoopRuntime {
|
|
|
184
186
|
return this._emit('sidepanel:run-updated', record, { reason: 'turn-state' });
|
|
185
187
|
}
|
|
186
188
|
|
|
189
|
+
/** A live worker's requests, durable for a late attach or page reload. Resolvers stay in the dispatcher. */
|
|
190
|
+
setPendingRequests(runId, requests) {
|
|
191
|
+
const record = this.records.get(runId);
|
|
192
|
+
if (!record) return null;
|
|
193
|
+
record.pendingRequests = structuredClone(requests || []);
|
|
194
|
+
return this._emit('sidepanel:run-updated', record, { reason: 'pending-requests' });
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
/**
|
|
188
198
|
* Task-mode helper: records what the wrapper's last turn reported ('done' |
|
|
189
199
|
* 'blocked' | 'needs_input', the dispatcher's lastResult.status) and
|
|
@@ -224,6 +234,7 @@ export class NativeLoopRuntime {
|
|
|
224
234
|
record.suspendedAt = record.suspendedAt || iso(this.now());
|
|
225
235
|
record.suspendReason = record.suspendReason || 'server_restart';
|
|
226
236
|
record.turnState = 'suspended';
|
|
237
|
+
record.pendingRequests = [];
|
|
227
238
|
record.stopping = false;
|
|
228
239
|
this.records.set(record.runId, record);
|
|
229
240
|
this._hold(record);
|
|
@@ -314,7 +325,8 @@ export class NativeLoopRuntime {
|
|
|
314
325
|
}
|
|
315
326
|
|
|
316
327
|
_writeState(runId, state) {
|
|
317
|
-
|
|
328
|
+
const { permissionBroker: _broker, ...stored } = state;
|
|
329
|
+
writeJsonAtomic(this._statePath(runId), toolListRecord(stored, this.toolListOptions()));
|
|
318
330
|
}
|
|
319
331
|
|
|
320
332
|
_writeTerminalState(record, patch) {
|
|
@@ -344,6 +356,7 @@ export class NativeLoopRuntime {
|
|
|
344
356
|
_setIdentity(record, identity = {}) {
|
|
345
357
|
const providerSessionId = identity.providerSessionId || identity.sessionId || null;
|
|
346
358
|
const providerThreadId = identity.providerThreadId || identity.threadId || null;
|
|
359
|
+
const antigravityHome = typeof identity.antigravityHome === "string" && identity.antigravityHome ? identity.antigravityHome : null;
|
|
347
360
|
let changed = false;
|
|
348
361
|
if (providerSessionId && providerSessionId !== record.providerSessionId) {
|
|
349
362
|
record.providerSessionId = providerSessionId;
|
|
@@ -353,6 +366,10 @@ export class NativeLoopRuntime {
|
|
|
353
366
|
record.providerThreadId = providerThreadId;
|
|
354
367
|
changed = true;
|
|
355
368
|
}
|
|
369
|
+
if (antigravityHome && antigravityHome !== record.antigravityHome) {
|
|
370
|
+
record.antigravityHome = antigravityHome;
|
|
371
|
+
changed = true;
|
|
372
|
+
}
|
|
356
373
|
if (changed) this._emit('sidepanel:run-updated', record, { reason: 'identity' });
|
|
357
374
|
if ((record.providerSessionId || record.providerThreadId) && record._resolveIdentity) {
|
|
358
375
|
record._resolveIdentity(cleanDescriptor(record));
|
|
@@ -397,6 +414,7 @@ export class NativeLoopRuntime {
|
|
|
397
414
|
provider: state.profile,
|
|
398
415
|
providerSessionId: null,
|
|
399
416
|
providerThreadId: null,
|
|
417
|
+
antigravityHome: typeof state.antigravityHome === "string" && state.antigravityHome ? state.antigravityHome : null,
|
|
400
418
|
source,
|
|
401
419
|
focus,
|
|
402
420
|
status: 'starting',
|
|
@@ -544,6 +562,7 @@ export class NativeLoopRuntime {
|
|
|
544
562
|
runId,
|
|
545
563
|
emit: (event) => this._emitProviderEvent(record, event),
|
|
546
564
|
setTurnState: (turnState) => this.setTurnState(runId, turnState),
|
|
565
|
+
setPendingRequests: (requests) => this.setPendingRequests(runId, requests),
|
|
547
566
|
setOutcome: (outcome) => this.setOutcome(runId, outcome),
|
|
548
567
|
readState: () => this._readState(runId),
|
|
549
568
|
})
|
|
@@ -639,6 +658,7 @@ export class NativeLoopRuntime {
|
|
|
639
658
|
browserTabId: state.browserTabId || null,
|
|
640
659
|
suspendedAt: null,
|
|
641
660
|
suspendReason: null,
|
|
661
|
+
...(typeof state.antigravityHome === "string" && state.antigravityHome ? { antigravityHome: state.antigravityHome } : {}),
|
|
642
662
|
resumes: (Number(record.resumes) || 0) + 1,
|
|
643
663
|
resumedAt: iso(this.now()),
|
|
644
664
|
buildPrompt,
|
|
@@ -830,6 +850,7 @@ export class NativeLoopRuntime {
|
|
|
830
850
|
record.startupAbortController = null;
|
|
831
851
|
record.buildPrompt = null;
|
|
832
852
|
record.wrapAdapter = null;
|
|
853
|
+
record.pendingRequests = [];
|
|
833
854
|
if (turnState === 'suspended') record.turnState = 'suspended';
|
|
834
855
|
else if (record.turnState && record.turnState !== 'terminal') record.turnState = 'terminal';
|
|
835
856
|
}
|
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
// listen time. This is a capability allowlist, not a generic OpenCode version
|
|
3
3
|
// check: 2.1.4's HTTP/upgrade authentication and stdout password path were
|
|
4
4
|
// inspected and tested. Unknown binaries/command wrappers keep the old guard.
|
|
5
|
+
// 2.2.5 (Homebrew anomalyco/tap/opencode-v2) checked 2026-11-01 against throwaway
|
|
6
|
+
// serves on 127.0.0.1: no password variable → it prints "server password <secret>"
|
|
7
|
+
// on stdout; every /api route (session, config, event, pty, shell, file, mcp, and
|
|
8
|
+
// the new tunnel and pairing) answers 401 without it, both plain and as a
|
|
9
|
+
// WebSocket upgrade, and 200 with it; one listener, bound to 127.0.0.1 only.
|
|
10
|
+
// Non-/api paths serve the static web UI (HTML), as before.
|
|
11
|
+
export const NATIVE_SERVE_VERSIONS = Object.freeze(['2.1.4', '2.2.5']);
|
|
12
|
+
|
|
5
13
|
export function nativeServeCapability({ version, commandLine = false, passwordFrom } = {}) {
|
|
6
|
-
|
|
14
|
+
const bare = String(version || '').trim().replace(/^opencode\s+/, '').replace(/^v/, '');
|
|
15
|
+
return !commandLine && passwordFrom === 'stdout' && NATIVE_SERVE_VERSIONS.includes(bare);
|
|
7
16
|
}
|
|
8
17
|
|
|
9
18
|
export function createNativeServeBoundary() {
|
|
10
19
|
const processes = new Map();
|
|
11
20
|
const contexts = new Map();
|
|
12
|
-
const error = () => new Error(
|
|
21
|
+
const error = () => new Error(`Native browser WebSockets require authenticated OpenCode ${NATIVE_SERVE_VERSIONS.join(' or ')} serves with private stdout credentials; wait for startup, stop unverified serves or use the verified executable path.`);
|
|
13
22
|
return {
|
|
14
23
|
assertLaunch(safe) { if (!safe && contexts.size) throw error(); },
|
|
15
24
|
track(proc, { port, safe = false }) {
|
|
@@ -74,7 +74,7 @@ export const AGENT_CALL_KINDS = Object.freeze(['agent', 'synabun-mcp']);
|
|
|
74
74
|
*/
|
|
75
75
|
export const OWNER_READ_PREFIXES = Object.freeze([...ADMIN_ONLY_PREFIXES.filter((prefix) => prefix !== '/api/hook-'), '/api/git']);
|
|
76
76
|
/** The owner's sockets: a terminal, the agent panels, the Assistant, the browser panel. */
|
|
77
|
-
export const OWNER_SOCKET_PREFIXES = Object.freeze(['/ws/terminal/', '/ws/claude-skin', '/ws/codex-skin', '/ws/opencode-v2', '/ws/assistant/', '/ws/browser/']);
|
|
77
|
+
export const OWNER_SOCKET_PREFIXES = Object.freeze(['/ws/terminal/', '/ws/claude-skin', '/ws/codex-skin', '/ws/opencode-v2', '/ws/antigravity', '/ws/assistant/', '/ws/browser/']);
|
|
78
78
|
/** Sockets that stay open for the app's pages to draw, read-only for a peer the owner's powers are refused to. */
|
|
79
79
|
export const READ_ONLY_SOCKET_PATHS = Object.freeze(['/ws/sync']);
|
|
80
80
|
|
|
@@ -19,13 +19,15 @@ export class PathConfineError extends Error {
|
|
|
19
19
|
export const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
20
|
export const isSessionId = (id) => typeof id === 'string' && SESSION_ID_RE.test(id);
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const foldForPlatform = (platform = process.platform) => (platform === 'win32' ? (s) => s.toLowerCase() : (s) => s);
|
|
23
|
+
const fold = foldForPlatform();
|
|
23
24
|
|
|
24
25
|
/** True when `target` is below `root` (or is `root` itself, with allowRoot). Lexical: both are resolved first. */
|
|
25
|
-
export function isInside(root, target, { allowRoot = false } = {}) {
|
|
26
|
+
export function isInside(root, target, { allowRoot = false, platform = process.platform } = {}) {
|
|
26
27
|
if (!root || !target) return false;
|
|
27
|
-
const
|
|
28
|
-
const
|
|
28
|
+
const f = platform === process.platform ? fold : foldForPlatform(platform);
|
|
29
|
+
const r = f(resolve(String(root)));
|
|
30
|
+
const t = f(resolve(String(target)));
|
|
29
31
|
if (t === r) return allowRoot;
|
|
30
32
|
return t.startsWith(r.endsWith(sep) ? r : r + sep);
|
|
31
33
|
}
|
|
@@ -83,28 +85,29 @@ export function isInsideReal(root, target, { allowRoot = false, fs = realFs } =
|
|
|
83
85
|
* @param o.projectsDir <config dir>/projects of the account
|
|
84
86
|
* @param o.pathToKey (projectPath) => the folder name Claude Code uses for it
|
|
85
87
|
*/
|
|
86
|
-
export function findSessionTranscript({ sessionId, project = '', projects = [], projectsDir, pathToKey, exists = existsSync, realpath = realpathSync } = {}) {
|
|
88
|
+
export function findSessionTranscript({ sessionId, project = '', projects = [], projectsDir, pathToKey, exists = existsSync, realpath = realpathSync, platform = process.platform } = {}) {
|
|
87
89
|
if (!isSessionId(sessionId)) throw new PathConfineError(400, 'Not a session id');
|
|
88
90
|
if (!projectsDir || typeof pathToKey !== 'function') return null;
|
|
91
|
+
const foldPath = platform === process.platform ? fold : foldForPlatform(platform);
|
|
89
92
|
const known = (Array.isArray(projects) ? projects : []).filter(p => p?.path);
|
|
90
|
-
const wanted = typeof project === 'string' && project ? resolve(project) : '';
|
|
93
|
+
const wanted = typeof project === 'string' && project ? foldPath(resolve(project)) : '';
|
|
91
94
|
let order = known;
|
|
92
95
|
if (wanted) {
|
|
93
|
-
const named = known.find(p => resolve(p.path) === wanted);
|
|
96
|
+
const named = known.find(p => foldPath(resolve(p.path)) === wanted);
|
|
94
97
|
if (!named) throw new PathConfineError(400, 'Not a registered project');
|
|
95
98
|
order = [named];
|
|
96
99
|
}
|
|
97
100
|
for (const proj of order) {
|
|
98
101
|
const key = String(pathToKey(proj.path) || '');
|
|
99
102
|
// Claude Code has written the drive letter in both cases on Windows.
|
|
100
|
-
const keys = [...new Set([key.replace(/^([A-Z])/, m => m.toLowerCase()), key])].filter(isPlainName);
|
|
103
|
+
const keys = [...new Set([key.replace(/^([A-Z])/, m => m.toLowerCase()), key.replace(/^([a-z])/, m => m.toUpperCase()), key])].filter(isPlainName);
|
|
101
104
|
for (const k of keys) {
|
|
102
105
|
const folder = join(projectsDir, k);
|
|
103
106
|
const candidate = join(folder, `${sessionId}.jsonl`);
|
|
104
107
|
if (!exists(candidate)) continue;
|
|
105
108
|
let realFile; let realFolder;
|
|
106
109
|
try { realFile = realpath(candidate); realFolder = realpath(folder); } catch { continue; }
|
|
107
|
-
if (!isInside(realFolder, realFile)) continue;
|
|
110
|
+
if (!isInside(realFolder, realFile, { platform })) continue;
|
|
108
111
|
// `realPath` / `realFolder`: the canonical paths that were just checked.
|
|
109
112
|
// The transcript is read from those (lib/confined-fs.js), not from the
|
|
110
113
|
// name it was found under, which could be swapped for a link afterwards.
|
|
@@ -244,7 +244,11 @@ export const RELAY_NAMES = Object.freeze([
|
|
|
244
244
|
/** Proxy / tunnel apps found by their bundle (their helpers carry other names). */
|
|
245
245
|
const RELAY_BUNDLE_RE = /\/(?:charles|proxyman|surge(?: mac)?|clashx[^/]*|clash verge|clash for windows|clash nyanpasu|flclash|shadowrocket|shadowsocksx[^/]*|v2rayu|v2rayx|qv2ray|v2rayn|stash|quantumult x|loon|proxifier|mitmproxy|ngrok|tailscale|cloudflare warp)\.app\//i;
|
|
246
246
|
/** The agents whose process trees never hold the owner's authority (argv[0]'s name). */
|
|
247
|
-
|
|
247
|
+
// Antigravity's CLI runs as `antigravity` (the kernel's name for the binary Homebrew links as `agy`). Gemini CLI stays on
|
|
248
|
+
// the list though SynaBun no longer drives it: an agent the user still runs never holds the owner's authority either.
|
|
249
|
+
export const AGENT_NAMES = Object.freeze(['claude', 'codex', 'opencode', 'agy', 'antigravity', 'gemini', 'cursor-agent', 'aider', 'goose', 'qwen', 'crush']);
|
|
250
|
+
/** The agent backend of the Antigravity app and IDE (a `language_server` inside their bundles: Codeium's name, so only by its place). */
|
|
251
|
+
const ANTIGRAVITY_SERVER_RE = /\/antigravity(?: ide)?\.app\/contents\/resources\/(?:app\/extensions\/[^/]+\/)?bin\/language_server/i;
|
|
248
252
|
/** Browsers the owner browses with (their bundle or their network process): never taken for a relay because they
|
|
249
253
|
* happen to hold a listening socket; an automation flag on them is still refused. */
|
|
250
254
|
const BROWSER_BUNDLE_RE = /\/(?:safari|google chrome(?: beta| dev| canary)?|chromium|firefox(?: developer edition| nightly)?|arc|brave browser(?: beta| nightly)?|microsoft edge(?: beta| dev| canary)?|opera(?: gx)?|vivaldi|orion|zen(?: browser)?|thorium|comet|dia|sigmaos|yandex|duckduckgo|sidekick|wavebox|island)\.app\/|\/synabun\.app\/contents\/(?:macos\/synabun(?:\s|$)|frameworks\/synabun helper)/i;
|
|
@@ -275,10 +279,12 @@ export function processKinds({ ucomm = '', comm = '', args = '' } = {}) {
|
|
|
275
279
|
else if (/(?:^|\s)-juggler(?:-pipe)?(?:\s|$)/.test(la) || /(?:^|\s)--?marionette(?:\s|$)/.test(la)) automation = 'Firefox automation';
|
|
276
280
|
|
|
277
281
|
let agent = has(AGENT_NAMES);
|
|
282
|
+
if (agent === 'agy') agent = 'antigravity';
|
|
278
283
|
if (!agent) {
|
|
279
284
|
if (/\/claude\/versions\/[^/\s]+(?:\s|$)/.test(la) || /@anthropic-ai\/claude-code\/cli\.m?js(?:\s|$)/.test(la) || /@anthropic-ai\/claude-agent-sdk[^/\s]*\/claude(?:\s|$)/.test(la)) agent = 'claude';
|
|
280
285
|
else if (/@openai\/codex\/bin\/codex(?:\.js)?(?:\s|$)/.test(la) || /(?:^|\s|\/)codex\s+(?:app-server|exec|mcp(?:-server)?|proto)\b/.test(la)) agent = 'codex';
|
|
281
286
|
else if (/(?:^|\s|\/)opencode\s+serve\b/.test(la)) agent = 'opencode';
|
|
287
|
+
else if (/(?:^|\s|\/)(?:agy|antigravity)\s+acp\b/.test(la) || ANTIGRAVITY_SERVER_RE.test(c || a)) agent = 'antigravity';
|
|
282
288
|
else if (/@google\/gemini-cli\//.test(la)) agent = 'gemini';
|
|
283
289
|
}
|
|
284
290
|
|
|
@@ -402,6 +408,27 @@ export function isSynabunMcpServer(proc, { roots = [], cwd = null, realpath = nu
|
|
|
402
408
|
return typeof real === 'string' && targets.has(real);
|
|
403
409
|
}
|
|
404
410
|
|
|
411
|
+
// ── Processes SynaBun starts for an agent ────────────────────────────────────
|
|
412
|
+
//
|
|
413
|
+
// A command an agent runs through SynaBun (the Antigravity panel's client terminals, lib/antigravity/acp-client-host.js)
|
|
414
|
+
// is a child of this server, not of the agent, so no agent is in its process tree by name. Its pid is registered here
|
|
415
|
+
// while it runs, and a chain that passes through one is an agent's tree. Like the name rule, a command that detaches
|
|
416
|
+
// from its parent (and so from the chain) is not seen.
|
|
417
|
+
const AGENT_PROCESSES = new Map(); // pid -> label
|
|
418
|
+
|
|
419
|
+
/** A process SynaBun started on an agent's behalf: its tree never holds the owner's authority while it runs. */
|
|
420
|
+
export function registerAgentProcess(pid, label = 'agent command') {
|
|
421
|
+
if (Number.isInteger(pid) && pid > 1) AGENT_PROCESSES.set(pid, String(label || 'agent command').slice(0, 80));
|
|
422
|
+
}
|
|
423
|
+
/** That process ended (its pid can be reused). */
|
|
424
|
+
export function forgetAgentProcess(pid) {
|
|
425
|
+
AGENT_PROCESSES.delete(pid);
|
|
426
|
+
}
|
|
427
|
+
/** The registered processes (for tests and diagnostics). */
|
|
428
|
+
export function agentProcesses() {
|
|
429
|
+
return new Map(AGENT_PROCESSES);
|
|
430
|
+
}
|
|
431
|
+
|
|
405
432
|
/**
|
|
406
433
|
* Whether the process at the client end of a connection, and its parents, may hold the owner's authority.
|
|
407
434
|
* @param {Array<{pid:number, ppid:number, ucomm:string, comm:string, args:string}>} chain the peer first, then its
|
|
@@ -409,9 +436,10 @@ export function isSynabunMcpServer(proc, { roots = [], cwd = null, realpath = nu
|
|
|
409
436
|
* @param {{ selfPid:number, listening:Set<number>, synabunMcp?:number }} o `synabunMcp`: the peer's pid when it is
|
|
410
437
|
* SynaBun's own MCP server over HTTP (isSynabunMcpServer): not refused as a relay, refused like an agent's tree
|
|
411
438
|
* (kind 'synabun-mcp': lib/owner-authority.js takes its pinned calls as an agent's own)
|
|
439
|
+
* `agentPids`: processes SynaBun started for an agent (registerAgentProcess), refused like an agent's tree
|
|
412
440
|
* @returns {{ self:boolean, refuse: null | { why:string, kind:string, pid:number, name:string } }}
|
|
413
441
|
*/
|
|
414
|
-
export function classifyPeerChain(chain, { selfPid = process.pid, listening = new Set(), synabunMcp = 0 } = {}) {
|
|
442
|
+
export function classifyPeerChain(chain, { selfPid = process.pid, listening = new Set(), synabunMcp = 0, agentPids = AGENT_PROCESSES } = {}) {
|
|
415
443
|
const list = Array.isArray(chain) ? chain : [];
|
|
416
444
|
if (!list.length) return { self: false, refuse: null };
|
|
417
445
|
if (list[0].pid === selfPid) return { self: true, refuse: null };
|
|
@@ -425,6 +453,8 @@ export function classifyPeerChain(chain, { selfPid = process.pid, listening = ne
|
|
|
425
453
|
const k = i === 0 ? peerKinds : processKinds(p);
|
|
426
454
|
if (k.automation) return refuse(p, 'automation-browser', `a browser started for automation (${k.automation})`);
|
|
427
455
|
if (k.agent) return refuse(p, 'agent', `an AI agent's process tree (${k.agent})`);
|
|
456
|
+
const startedFor = agentPids?.get?.(p.pid);
|
|
457
|
+
if (startedFor) return refuse(p, 'agent', `an AI agent's process tree (${startedFor}, started by SynaBun)`);
|
|
428
458
|
if (k.synabun) return refuse(p, 'synabun-instance', 'another SynaBun server');
|
|
429
459
|
if (k.remoteLogin) return refuse(p, 'remote-login', 'a remote login session (sshd)');
|
|
430
460
|
// SynaBun's own MCP server over HTTP relays for its MCP clients by design: not refused as a relay or a listener
|
|
@@ -19,6 +19,7 @@ import { evaluateFlow } from './flow.js';
|
|
|
19
19
|
import { EV, OP, HOST_DEFAULTS } from './protocol.js';
|
|
20
20
|
import { createEventLoopMonitor } from '../event-loop-monitor.js';
|
|
21
21
|
import { withPathEnv } from '../augmented-path.js';
|
|
22
|
+
import { killOwnedPty } from './lifetime.js';
|
|
22
23
|
|
|
23
24
|
const IS_WIN = process.platform === 'win32';
|
|
24
25
|
const IS_LINUX = process.platform === 'linux';
|
|
@@ -39,8 +40,14 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
39
40
|
let pty = injectedPty;
|
|
40
41
|
let ptyError = null;
|
|
41
42
|
let shuttingDown = false;
|
|
43
|
+
let shutdownTimer = null;
|
|
42
44
|
|
|
43
45
|
const emit = (msg) => { try { send(msg); } catch {} };
|
|
46
|
+
// Covers process.exit(), including host.js's bounded shutdown, and the
|
|
47
|
+
// in-process transport. This must be synchronous: exit cannot await PTYs.
|
|
48
|
+
const onOwnerExit = () => {
|
|
49
|
+
for (const s of sessions.values()) if (!s.exited) killOwnedPty(s.proc.pid);
|
|
50
|
+
};
|
|
44
51
|
|
|
45
52
|
// ── Startup ──
|
|
46
53
|
async function init() {
|
|
@@ -65,6 +72,10 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
65
72
|
// ── Spawn ──
|
|
66
73
|
function spawnSession(spec) {
|
|
67
74
|
const { id } = spec;
|
|
75
|
+
if (shuttingDown) {
|
|
76
|
+
emit({ t: EV.SPAWN_ERROR, id, message: 'Terminal host is shutting down' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
68
79
|
if (!id || sessions.has(id)) {
|
|
69
80
|
emit({ t: EV.SPAWN_ERROR, id, message: 'Duplicate or missing session id' });
|
|
70
81
|
return;
|
|
@@ -133,6 +144,7 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
133
144
|
graceTimer: null,
|
|
134
145
|
exited: false,
|
|
135
146
|
};
|
|
147
|
+
if (!sessions.size) process.once('exit', onOwnerExit);
|
|
136
148
|
sessions.set(id, s);
|
|
137
149
|
counters.spawns++;
|
|
138
150
|
|
|
@@ -490,6 +502,11 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
490
502
|
for (const f of s.tempFiles.splice(0)) unlink(f).catch(() => {});
|
|
491
503
|
try { s.model.dispose(); } catch {}
|
|
492
504
|
sessions.delete(s.id);
|
|
505
|
+
if (!sessions.size) {
|
|
506
|
+
process.removeListener('exit', onOwnerExit);
|
|
507
|
+
clearTimeout(shutdownTimer);
|
|
508
|
+
shutdownTimer = null;
|
|
509
|
+
}
|
|
493
510
|
}
|
|
494
511
|
|
|
495
512
|
function killSession(s, signal) {
|
|
@@ -571,7 +588,11 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
571
588
|
return out;
|
|
572
589
|
}
|
|
573
590
|
|
|
574
|
-
function shutdownAll() {
|
|
591
|
+
function shutdownAll({ force = false } = {}) {
|
|
592
|
+
// The server's synchronous exit handler kills itself before later exit
|
|
593
|
+
// listeners can run. Its killNow() path must reap the PTYs right here.
|
|
594
|
+
if (force) onOwnerExit();
|
|
595
|
+
if (shuttingDown) return;
|
|
575
596
|
shuttingDown = true;
|
|
576
597
|
for (const s of sessions.values()) {
|
|
577
598
|
s.draining = true;
|
|
@@ -581,6 +602,12 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
|
|
|
581
602
|
for (const s of sessions.values()) {
|
|
582
603
|
for (const c of s.clients) { try { c.ws.close(1001, 'terminal host shutting down'); } catch {} }
|
|
583
604
|
}
|
|
605
|
+
// HUP can be ignored, or arrive while the macOS helper is attaching.
|
|
606
|
+
// Bound the grace period in both transports, even if the owner stays up.
|
|
607
|
+
if (sessions.size) {
|
|
608
|
+
shutdownTimer = setTimeout(onOwnerExit, 100);
|
|
609
|
+
shutdownTimer.unref?.();
|
|
610
|
+
}
|
|
584
611
|
}
|
|
585
612
|
|
|
586
613
|
function dispose() {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// node-pty's macOS helper starts a new session before exec. If its host dies
|
|
2
|
+
// before the helper acquires the controlling terminal, closing the master
|
|
3
|
+
// does not hang it up. Kill the known PTY process group as well as its leader.
|
|
4
|
+
// Callers must forget a PID on EXIT: these are owned children, never a sweep
|
|
5
|
+
// of unrelated processes or a list retained across host restarts.
|
|
6
|
+
export function killOwnedPty(pid, { kill = process.kill, platform = process.platform } = {}) {
|
|
7
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return;
|
|
8
|
+
if (platform !== 'win32') {
|
|
9
|
+
try { kill(-pid, 'SIGKILL'); } catch {}
|
|
10
|
+
}
|
|
11
|
+
try { kill(pid, 'SIGKILL'); } catch {}
|
|
12
|
+
}
|
|
@@ -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
|