synabun 2.0.10 → 2.1.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 +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -84,8 +84,6 @@ export function buildExecInvocation({
|
|
|
84
84
|
mcpEnvOverrides = [],
|
|
85
85
|
// Codex: launch overrides as `-c` words (the Windows sandbox of the Admin sandbox setting).
|
|
86
86
|
codexConfigArgs = [],
|
|
87
|
-
// OpenCode: the major of the CLI (lib/opencode-protocol.js); 2 has other `run` flags.
|
|
88
|
-
opencodeMajor = 1,
|
|
89
87
|
platform = process.platform,
|
|
90
88
|
commandExists = existsSync,
|
|
91
89
|
} = {}) {
|
|
@@ -113,12 +111,10 @@ export function buildExecInvocation({
|
|
|
113
111
|
if (model) args.push('--model', String(model));
|
|
114
112
|
break;
|
|
115
113
|
case 'opencode':
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// configuration) and `--auto` (approve what is not denied)
|
|
119
|
-
|
|
120
|
-
if (Number(opencodeMajor) >= 2) args.push('run', '--standalone', '--auto');
|
|
121
|
-
else args.push('run', '--dangerously-skip-permissions');
|
|
114
|
+
// A private server for this run (`--standalone`; without it the CLI uses
|
|
115
|
+
// the machine's background service, which has none of the loop's
|
|
116
|
+
// configuration) and `--auto` (approve what is not denied).
|
|
117
|
+
args.push('run', '--standalone', '--auto');
|
|
122
118
|
if (model) args.push('--model', String(model));
|
|
123
119
|
args.push('--format', 'default');
|
|
124
120
|
break;
|
|
@@ -407,7 +407,10 @@ export function isOwnerOrigin(origin, { port, machineName = thisMachine.names, o
|
|
|
407
407
|
* proxy's (`publicOrigins`, left out of the listed names), never a listed name
|
|
408
408
|
* on another port, whatever Sec-Fetch-Site says or leaves out: BAD_ORIGIN.
|
|
409
409
|
* `localAddresses` gives this computer's addresses (thisMachineAddresses when
|
|
410
|
-
* it is left out). A request without Origin still passes
|
|
410
|
+
* it is left out). A request without Origin still passes, unless `requireOrigin`
|
|
411
|
+
* is set: then a write must carry one (every browser sends it on a write; a
|
|
412
|
+
* local process that fetched a page for its cookie and sends none is refused,
|
|
413
|
+
* ORIGIN_REQUIRED). First users: the Skills Studio's owner-only writes.
|
|
411
414
|
*
|
|
412
415
|
* `allowedOrigins` (an array or a function) are the tunnel's and the invite
|
|
413
416
|
* proxy's public URLs: their host names count as ours, as for a WebSocket.
|
|
@@ -418,7 +421,7 @@ export function isOwnerOrigin(origin, { port, machineName = thisMachine.names, o
|
|
|
418
421
|
*/
|
|
419
422
|
export function ownerRouteGuard({
|
|
420
423
|
isGuestRequest = null, port, allowedOrigins = [], machineName, refuseCrossSite = false,
|
|
421
|
-
ownOriginsOnly = false, publicOrigins = [], localAddresses = thisMachineAddresses,
|
|
424
|
+
ownOriginsOnly = false, publicOrigins = [], localAddresses = thisMachineAddresses, requireOrigin = false,
|
|
422
425
|
} = {}) {
|
|
423
426
|
// The names the owner listed for this computer: the listed ones, without the tunnel's and the invite proxy's.
|
|
424
427
|
const ownNames = () => { const theirs = listedNames(publicOrigins); return listedNames(allowedOrigins).filter((name) => !theirs.includes(name)); };
|
|
@@ -426,6 +429,9 @@ export function ownerRouteGuard({
|
|
|
426
429
|
|| isOwnerOrigin(req.headers.origin, { port, machineName, ownNames, addresses: localAddresses });
|
|
427
430
|
function refusal(req) {
|
|
428
431
|
if (!isAllowedHost(req?.headers?.host, { allowedHosts: allowedOrigins, machineName })) return { status: 403, code: 'BAD_HOST', error: 'Unexpected Host header.' };
|
|
432
|
+
if (requireOrigin && !SAFE_METHODS.has(req.method) && typeof req?.headers?.origin !== 'string') {
|
|
433
|
+
return { status: 403, code: 'ORIGIN_REQUIRED', error: 'This change is made from SynaBun\'s own page, which sends its Origin.' };
|
|
434
|
+
}
|
|
429
435
|
if (!SAFE_METHODS.has(req.method) && !(ownOriginsOnly ? ownerOrigin(req) : isAllowedWebSocketOrigin(req, { port, allowedOrigins, machineName }))) {
|
|
430
436
|
return { status: 403, code: 'BAD_ORIGIN', error: 'Unexpected Origin header.' };
|
|
431
437
|
}
|
|
@@ -14,6 +14,7 @@ import { execFile as nodeExecFile } from 'node:child_process';
|
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { redactDeep, redactSecrets } from '../../lib/log-redact.js';
|
|
17
|
+
import { mcpDistBuilt } from '../../lib/mcp-dist.js';
|
|
17
18
|
|
|
18
19
|
const COMMAND_TIMEOUT_MS = 8000;
|
|
19
20
|
const OUTPUT_MAX = 2000;
|
|
@@ -111,6 +112,7 @@ export async function collectInstallDetails({
|
|
|
111
112
|
browserExecutable = null,
|
|
112
113
|
exists = existsSync,
|
|
113
114
|
readJson = readJsonFile,
|
|
115
|
+
readText = null,
|
|
114
116
|
version = runVersion,
|
|
115
117
|
} = {}) {
|
|
116
118
|
const windows = platform === 'win32';
|
|
@@ -135,7 +137,8 @@ export async function collectInstallDetails({
|
|
|
135
137
|
const packages = installed ? packagesInLock(installed) : installedFromLock(shrinkwrap, packageRoot, { exists });
|
|
136
138
|
return { name, folder, packages, dependencies: directDependencies(dir, { readJson, stopAt: packageRoot }) };
|
|
137
139
|
});
|
|
138
|
-
|
|
140
|
+
// Built: dist/index.js and every module it imports (lib/mcp-dist.js), not the entry file alone.
|
|
141
|
+
const mcpBuilt = exists(path.join(packageRoot, 'mcp-server', 'dist', 'index.js')) && mcpDistBuilt(packageRoot, { exists, ...(readText ? { readText } : {}) });
|
|
139
142
|
|
|
140
143
|
return redactDeep({
|
|
141
144
|
kind: packaged ? 'app' : 'source',
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Registry conversion is read-only. An explicit switch is authoritative; missing switches are on.
|
|
2
|
+
function asObject(value) {
|
|
3
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function agentAccessFrom(registry) {
|
|
7
|
+
const reg = asObject(registry);
|
|
8
|
+
if (reg.agentAccess && typeof reg.agentAccess === 'object' && !Array.isArray(reg.agentAccess)) return { ...reg.agentAccess };
|
|
9
|
+
const profile = asObject(asObject(reg.profiles)[reg.activeProfile]);
|
|
10
|
+
if (!Array.isArray(profile.servers)) return {};
|
|
11
|
+
const granted = new Set(profile.servers);
|
|
12
|
+
// The old reconciliation marker distinguishes a de-selection from a genuinely new server.
|
|
13
|
+
const known = new Set(Array.isArray(reg.profileDefaults?.knownServers)
|
|
14
|
+
? reg.profileDefaults.knownServers : Object.keys(asObject(reg.servers)));
|
|
15
|
+
return Object.fromEntries(Object.entries(asObject(reg.servers))
|
|
16
|
+
.filter(([name, entry]) => !entry?.builtin && known.has(name) && !granted.has(name))
|
|
17
|
+
.map(([name]) => [name, false]));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function agentServerNames(registry) {
|
|
21
|
+
const reg = asObject(registry);
|
|
22
|
+
const access = agentAccessFrom(reg);
|
|
23
|
+
return Object.entries(asObject(reg.servers))
|
|
24
|
+
.filter(([name, entry]) => !entry?.builtin && access[name] !== false)
|
|
25
|
+
.map(([name]) => name);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tools that mutate ad state or spend budget, keyed by MCP server name.
|
|
30
|
+
*
|
|
31
|
+
* FleetView agents run with `--permission-mode bypassPermissions`, so the read-only
|
|
32
|
+
* allowlist in .claude/settings.json does not apply to them. These are pushed as
|
|
33
|
+
* `--disallowedTools` instead: agents keep full read/reporting access, while anything
|
|
34
|
+
* that creates, edits, activates, or deletes stays with a human.
|
|
35
|
+
*/
|
|
36
|
+
export const MCP_SERVER_WRITE_TOOLS = {
|
|
37
|
+
'meta-ads': [
|
|
38
|
+
'ads_create_campaign', 'ads_create_ad_set', 'ads_create_ad', 'ads_create_creative',
|
|
39
|
+
'ads_update_entity', 'ads_activate_entity', 'ads_boost_ig_post',
|
|
40
|
+
'ads_create_custom_audience', 'ads_update_custom_audience',
|
|
41
|
+
'ads_update_custom_audience_users', 'ads_delete_custom_audience',
|
|
42
|
+
'ads_catalog_create', 'ads_catalog_update_catalog',
|
|
43
|
+
'ads_catalog_product_create', 'ads_catalog_update_product', 'ads_catalog_delete_product',
|
|
44
|
+
'ads_catalog_create_product_set', 'ads_catalog_update_product_set', 'ads_catalog_product_set_delete',
|
|
45
|
+
'ads_catalog_create_product_feed', 'ads_catalog_update_product_feed',
|
|
46
|
+
'ads_catalog_create_product_feed_upload_session', 'ads_catalog_product_feed_delete',
|
|
47
|
+
'ads_catalog_create_feed_rule', 'ads_catalog_product_feed_delete_rule',
|
|
48
|
+
'ads_catalog_event_source_connect', 'ads_catalog_event_source_disconnect',
|
|
49
|
+
'ads_pixel_event_create', 'ads_pixel_event_update', 'ads_pixel_event_delete',
|
|
50
|
+
'ads_pixel_parameter_create', 'ads_pixel_parameter_update', 'ads_pixel_parameter_delete',
|
|
51
|
+
'ads_experiment_abtest_create_test', 'ads_experiment_abtest_update_test',
|
|
52
|
+
'ads_experiment_lift_create_test',
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Fully-qualified deny list for whichever servers an agent's --mcp-config actually grants. */
|
|
57
|
+
export function buildAgentDisallowedTools(mcpConfigJson) {
|
|
58
|
+
let serverNames = [];
|
|
59
|
+
try {
|
|
60
|
+
serverNames = Object.keys(asObject(JSON.parse(mcpConfigJson || '{}')).mcpServers || {});
|
|
61
|
+
} catch { return []; }
|
|
62
|
+
const deny = [];
|
|
63
|
+
for (const server of serverNames) {
|
|
64
|
+
for (const tool of MCP_SERVER_WRITE_TOOLS[server] || []) deny.push(`mcp__${server}__${tool}`);
|
|
65
|
+
}
|
|
66
|
+
return deny;
|
|
67
|
+
}
|
|
@@ -102,32 +102,3 @@ export function mergeGeminiMcpConfig(existing, definition) {
|
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
export function toOpenCodeMcpEntry(mcpConfig) {
|
|
107
|
-
const source = asObject(mcpConfig);
|
|
108
|
-
const { type: _type, command, args, env, environment, headers, oauth: _oauth, url, ...rest } = source;
|
|
109
|
-
if (isRemoteMcp(source)) {
|
|
110
|
-
// OpenCode names the URL-addressed transport "remote"; there is no http/sse split.
|
|
111
|
-
const entry = { type: 'remote', url, ...rest };
|
|
112
|
-
const hdrs = hasKeys(headers);
|
|
113
|
-
if (hdrs) entry.headers = { ...hdrs };
|
|
114
|
-
if (entry.enabled === undefined) entry.enabled = true;
|
|
115
|
-
return entry;
|
|
116
|
-
}
|
|
117
|
-
const entry = { type: 'local', ...rest };
|
|
118
|
-
if (command) entry.command = Array.isArray(command) ? [...command] : [command, ...(args || [])];
|
|
119
|
-
const envObj = environment || env;
|
|
120
|
-
if (envObj && Object.keys(envObj).length > 0) entry.environment = { ...envObj };
|
|
121
|
-
return entry;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function mergeOpenCodeMcpConfig(existing, name, mcpConfig) {
|
|
125
|
-
const config = asObject(existing);
|
|
126
|
-
return {
|
|
127
|
-
...config,
|
|
128
|
-
mcp: {
|
|
129
|
-
...asObject(config.mcp),
|
|
130
|
-
[name]: toOpenCodeMcpEntry(mcpConfig),
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { buildClaudeMcpEntry } from './mcp-client-config.js';
|
|
2
|
+
// External definitions stay at the launch boundary. Public views contain names and availability only.
|
|
3
|
+
import { existsSync, realpathSync, readFileSync, mkdirSync, writeFileSync, renameSync, chmodSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { parseJsonc, mcpServersOf, mergedOpencodeConfig, readOpencodeConfigDocuments } from './opencode-config.js';
|
|
7
|
+
import { agentAccessFrom } from './mcp-agent-servers.js';
|
|
8
|
+
|
|
9
|
+
const object = value => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
10
|
+
const canonical = path => { try { return realpathSync(path); } catch { return resolve(path); } };
|
|
11
|
+
const claudeProject = (user, cwd) => user.projects?.[canonical(cwd)] || user.projects?.[resolve(cwd)] || {};
|
|
12
|
+
const external = name => name !== 'SynaBun';
|
|
13
|
+
export const EXTERNAL_SERVER_LIMITATIONS = Object.freeze({ codex: 'Plugin-provided MCP servers cannot be enumerated from config.toml', 'claude-code': 'claude.ai connectors are unavailable under strict MCP configuration' });
|
|
14
|
+
export const externalServerName = name => typeof name === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(name) && external(name);
|
|
15
|
+
function read(path, fallback = null) {
|
|
16
|
+
try { return readFileSync(path, 'utf8'); }
|
|
17
|
+
catch (error) { if (error.code === 'ENOENT') return fallback; throw Object.assign(new Error('Could not read an MCP configuration source'), { code: 'MCP_DISCOVERY_FAILED' }); }
|
|
18
|
+
}
|
|
19
|
+
function document(path) {
|
|
20
|
+
const text = read(path);
|
|
21
|
+
if (text === null) return {};
|
|
22
|
+
try { return object(parseJsonc(text)); }
|
|
23
|
+
catch { throw Object.assign(new Error('Could not parse an MCP configuration source'), { code: 'MCP_DISCOVERY_FAILED' }); }
|
|
24
|
+
}
|
|
25
|
+
/** Names/kinds/enabled only: Codex keeps definitions and secrets in its own TOML. Quoted dotted names stay whole. */
|
|
26
|
+
export function parseCodexMcpServers(text = '') {
|
|
27
|
+
// Structural TOML reader: supports tables, dotted/quoted keys and inline maps without copying definitions.
|
|
28
|
+
const tokens = []; let at = 0;
|
|
29
|
+
const input = String(text);
|
|
30
|
+
const fail = () => Object.assign(new Error('Could not parse Codex MCP configuration'), { code: 'MCP_DISCOVERY_FAILED' });
|
|
31
|
+
while (at < input.length) {
|
|
32
|
+
const ch = input[at];
|
|
33
|
+
if (ch === '#') { while (at < input.length && input[at] !== '\n') at++; continue; }
|
|
34
|
+
if (ch === '\n') { tokens.push({ type: 'nl', value: ch }); at++; continue; }
|
|
35
|
+
if (/\s/.test(ch)) { at++; continue; }
|
|
36
|
+
if ('[]{}=.,'.includes(ch)) { tokens.push({ type: 'punct', value: ch }); at++; continue; }
|
|
37
|
+
if (ch === '"' || ch === "'") {
|
|
38
|
+
const start = at, triple = input.slice(at, at + 3) === ch.repeat(3); at += triple ? 3 : 1;
|
|
39
|
+
let closed = false;
|
|
40
|
+
while (at < input.length) {
|
|
41
|
+
if (ch === '"' && input[at] === '\\') { at += 2; continue; }
|
|
42
|
+
if (input.slice(at, at + (triple ? 3 : 1)) === ch.repeat(triple ? 3 : 1)) { at += triple ? 3 : 1; closed = true; break; }
|
|
43
|
+
at++;
|
|
44
|
+
}
|
|
45
|
+
if (!closed) throw fail();
|
|
46
|
+
const raw = input.slice(start, at); let value;
|
|
47
|
+
if (triple) value = raw.slice(3, -3);
|
|
48
|
+
else if (ch === "'") value = raw.slice(1, -1);
|
|
49
|
+
else {
|
|
50
|
+
try { value = JSON.parse(raw.replace(/\\U([0-9a-fA-F]{8})/g, (_, code) => String.fromCodePoint(parseInt(code, 16)))); }
|
|
51
|
+
catch { throw fail(); }
|
|
52
|
+
}
|
|
53
|
+
tokens.push({ type: 'string', value }); continue;
|
|
54
|
+
}
|
|
55
|
+
const start = at;
|
|
56
|
+
while (at < input.length && !/[\s\[\]{}=.,#"']/.test(input[at])) at++;
|
|
57
|
+
if (at === start) throw fail();
|
|
58
|
+
tokens.push({ type: 'word', value: input.slice(start, at) });
|
|
59
|
+
}
|
|
60
|
+
let index = 0;
|
|
61
|
+
const peek = () => tokens[index]?.value;
|
|
62
|
+
const skipLines = () => { while (tokens[index]?.type === 'nl') index++; };
|
|
63
|
+
function keys() {
|
|
64
|
+
const out = [];
|
|
65
|
+
while (tokens[index] && ['word', 'string'].includes(tokens[index].type)) {
|
|
66
|
+
const token = tokens[index++];
|
|
67
|
+
if (token.type === 'word' && !/^[A-Za-z0-9_-]+$/.test(token.value)) throw fail();
|
|
68
|
+
out.push(token.value); if (peek() !== '.') break; index++;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function put(root, path, value) {
|
|
73
|
+
let target = root;
|
|
74
|
+
for (const key of path.slice(0, -1)) { if (!target[key] || typeof target[key] !== 'object') target[key] = Object.create(null); target = target[key]; }
|
|
75
|
+
target[path.at(-1)] = value;
|
|
76
|
+
}
|
|
77
|
+
function value() {
|
|
78
|
+
if (peek() === '{') {
|
|
79
|
+
index++; const out = Object.create(null); skipLines();
|
|
80
|
+
while (peek() !== '}') {
|
|
81
|
+
const path = keys(); if (!path.length || peek() !== '=') throw fail(); index++;
|
|
82
|
+
put(out, path, value()); skipLines();
|
|
83
|
+
if (peek() === ',') { index++; skipLines(); } else if (peek() !== '}') throw fail();
|
|
84
|
+
}
|
|
85
|
+
index++; return out;
|
|
86
|
+
}
|
|
87
|
+
if (peek() === '[') {
|
|
88
|
+
index++; const out = []; skipLines();
|
|
89
|
+
while (peek() !== ']') {
|
|
90
|
+
if (!tokens[index]) throw fail(); out.push(value()); skipLines();
|
|
91
|
+
if (peek() === ',') { index++; skipLines(); } else if (peek() !== ']') throw fail();
|
|
92
|
+
}
|
|
93
|
+
index++; return out;
|
|
94
|
+
}
|
|
95
|
+
const first = tokens[index++]; if (!first) throw fail();
|
|
96
|
+
if (first.type === 'string') return first.value;
|
|
97
|
+
let raw = first.value;
|
|
98
|
+
while (tokens[index] && tokens[index].type !== 'nl' && ![',', '}', ']'].includes(peek())) raw += tokens[index++].value;
|
|
99
|
+
return raw === 'false' ? false : raw === 'true' ? true : raw;
|
|
100
|
+
}
|
|
101
|
+
const rows = new Map(); let table = [];
|
|
102
|
+
function observe(path, item) {
|
|
103
|
+
if (path[0] !== 'mcp_servers') return;
|
|
104
|
+
if (path.length === 1 && item && typeof item === 'object') { for (const [name, entry] of Object.entries(item)) observe(['mcp_servers', name], entry); return; }
|
|
105
|
+
const name = path[1]; if (name === undefined) return;
|
|
106
|
+
if (!rows.has(name)) rows.set(name, { kind: 'stdio', enabled: true });
|
|
107
|
+
const info = rows.get(name);
|
|
108
|
+
if (path.length === 2 && item && typeof item === 'object') { if (typeof item.url === 'string') info.kind = 'http'; if (item.enabled === false) info.enabled = false; }
|
|
109
|
+
if (path.length === 3 && path[2] === 'url') info.kind = 'http';
|
|
110
|
+
if (path.length === 3 && path[2] === 'enabled') info.enabled = item !== false;
|
|
111
|
+
}
|
|
112
|
+
while (index < tokens.length) {
|
|
113
|
+
skipLines(); if (!tokens[index]) break;
|
|
114
|
+
if (peek() === '[') {
|
|
115
|
+
index++; const arrayTable = peek() === '['; if (arrayTable) index++;
|
|
116
|
+
table = keys(); if (peek() !== ']') throw fail(); index++;
|
|
117
|
+
if (arrayTable) { if (peek() !== ']') throw fail(); index++; }
|
|
118
|
+
observe(table); continue;
|
|
119
|
+
}
|
|
120
|
+
const path = keys(); if (!path.length || peek() !== '=') throw fail(); index++;
|
|
121
|
+
observe([...table, ...path], value());
|
|
122
|
+
while (tokens[index] && tokens[index].type !== 'nl') index++;
|
|
123
|
+
}
|
|
124
|
+
return rows;
|
|
125
|
+
}
|
|
126
|
+
export function codexMcpServerNames(codexHome) { if (!codexHome) return []; return [...parseCodexMcpServers(read(join(codexHome, 'config.toml'), '')).keys()]; }
|
|
127
|
+
export function projectOpenCodeConfigs(cwd) {
|
|
128
|
+
const dirs = []; let dir = canonical(cwd);
|
|
129
|
+
while (true) { dirs.unshift(dir); const parent = dirname(dir); if (parent === dir) break; dir = parent; }
|
|
130
|
+
// Every direct file, then every .opencode directory, farthest ancestor first (OpenCode 2 discovery).
|
|
131
|
+
return [...dirs, ...dirs.map(path => join(path, '.opencode'))].flatMap(path => ['opencode.json', 'opencode.jsonc'].map(name => join(path, name)));
|
|
132
|
+
}
|
|
133
|
+
export function claudePluginDefinitions(pluginRoot, manifest = document(join(pluginRoot, '.claude-plugin', 'plugin.json'))) {
|
|
134
|
+
const out = {};
|
|
135
|
+
const expand = value => typeof value === 'string' ? value.replaceAll('${CLAUDE_PLUGIN_ROOT}', pluginRoot) : Array.isArray(value) ? value.map(expand) : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, expand(v)])) : value;
|
|
136
|
+
const add = data => Object.assign(out, object(data.mcpServers || data));
|
|
137
|
+
add(document(join(pluginRoot, '.mcp.json')));
|
|
138
|
+
const declared = manifest.mcpServers;
|
|
139
|
+
if (typeof declared === 'string') add(document(resolve(pluginRoot, declared)));
|
|
140
|
+
else if (Array.isArray(declared)) { for (const path of declared) if (typeof path === 'string') add(document(resolve(pluginRoot, path))); }
|
|
141
|
+
else Object.assign(out, object(declared));
|
|
142
|
+
return expand(out);
|
|
143
|
+
}
|
|
144
|
+
function claudeEntry(entry) { return structuredClone(entry); }
|
|
145
|
+
|
|
146
|
+
/** This is the only filesystem discovery boundary; definitions never escape through servers/catalogView. */
|
|
147
|
+
export function externalServerInventory({ provider, cwd = process.cwd(), codexHome, home = homedir(), dataHome, registry, opencodeConfig, readProject = true, env = process.env } = {}) {
|
|
148
|
+
const reg = registry || (dataHome ? document(join(dataHome, 'data', 'mcp-registry.json')) : {});
|
|
149
|
+
const access = agentAccessFrom(reg), definitions = new Map(), rows = new Map();
|
|
150
|
+
function add(name, entry, source, deliverable = true, reason = null, kind = null) {
|
|
151
|
+
if (!external(name)) return;
|
|
152
|
+
const enabled = entry.enabled !== false && entry.disabled !== true;
|
|
153
|
+
const transport = provider === 'codex' || (entry.url ? typeof entry.url === 'string' : Array.isArray(entry.command) ? entry.command.length > 0 : typeof entry.command === 'string' && !!entry.command);
|
|
154
|
+
const supported = provider !== 'claude-code' || (entry.url ? ['http', 'streamable-http', 'sse', 'ws'].includes(entry.type) : entry.type === undefined || entry.type === 'stdio');
|
|
155
|
+
const reserved = provider === 'claude-code' && Object.hasOwn(Object.prototype, name);
|
|
156
|
+
const available = deliverable && enabled && transport && supported && !reserved;
|
|
157
|
+
rows.set(name, { name, provider, source, kind: kind || (entry.url ? entry.type === 'ws' ? 'ws' : entry.type === 'sse' ? 'sse' : 'http' : 'stdio'), deliverable: available, selectable: externalServerName(name), ...(available ? {} : { reason: reason || (!enabled ? 'disabled-in-provider' : 'unsupported-definition') }), blocked: access[name] === false });
|
|
158
|
+
definitions.set(name, entry);
|
|
159
|
+
}
|
|
160
|
+
if (provider === 'codex') {
|
|
161
|
+
for (const [path, source] of [[join(codexHome || env.CODEX_HOME || join(home, '.codex'), 'config.toml'), 'user'], [join(cwd, '.codex', 'config.toml'), 'project']]) {
|
|
162
|
+
for (const [name, info] of parseCodexMcpServers(read(path, ''))) add(name, info, source, true, null, info.kind);
|
|
163
|
+
}
|
|
164
|
+
} else if (provider === 'opencode') {
|
|
165
|
+
const configDir = env.OPENCODE_CONFIG_DIR || join(env.XDG_CONFIG_HOME || join(home, '.config'), 'opencode');
|
|
166
|
+
let base = opencodeConfig;
|
|
167
|
+
if (base == null) {
|
|
168
|
+
const docs = readOpencodeConfigDocuments(configDir);
|
|
169
|
+
if (docs.some(row => row.error)) throw Object.assign(new Error('Could not parse an MCP configuration source'), { code: 'MCP_DISCOVERY_FAILED' });
|
|
170
|
+
base = mergedOpencodeConfig(docs);
|
|
171
|
+
}
|
|
172
|
+
for (const [name, entry] of Object.entries(mcpServersOf(base))) add(name, entry, 'user');
|
|
173
|
+
if (env.OPENCODE_CONFIG) for (const [name, entry] of Object.entries(mcpServersOf(document(resolve(env.OPENCODE_CONFIG))))) add(name, entry, 'user-override');
|
|
174
|
+
if (readProject) for (const path of projectOpenCodeConfigs(cwd)) for (const [name, entry] of Object.entries(mcpServersOf(document(path)))) add(name, entry, 'project');
|
|
175
|
+
} else if (provider === 'claude-code') {
|
|
176
|
+
const configDir = env.CLAUDE_CONFIG_DIR || join(home, '.claude');
|
|
177
|
+
const settings = { ...document(join(configDir, 'settings.json')), ...document(join(cwd, '.claude', 'settings.json')), ...document(join(cwd, '.claude', 'settings.local.json')) };
|
|
178
|
+
// Registry entries are a compatibility fallback for FleetView's pre-Phase-2 registry-only servers.
|
|
179
|
+
for (const [name, entry] of Object.entries(object(reg.servers))) if (!entry.builtin) {
|
|
180
|
+
add(name, buildClaudeMcpEntry(entry), 'registry');
|
|
181
|
+
}
|
|
182
|
+
const user = document(env.CLAUDE_CONFIG_DIR ? join(configDir, '.claude.json') : join(home, '.claude.json'));
|
|
183
|
+
for (const [name, entry] of Object.entries(object(user.mcpServers))) add(name, claudeEntry(entry), 'user');
|
|
184
|
+
const disabled = new Set([...(settings.disabledMcpjsonServers || []), ...(claudeProject(user, cwd)?.disabledMcpjsonServers || [])]);
|
|
185
|
+
for (const [name, entry] of Object.entries(object(document(join(cwd, '.mcp.json')).mcpServers))) {
|
|
186
|
+
const approved = !disabled.has(name) && (settings.enableAllProjectMcpServers === true || claudeProject(user, cwd)?.enableAllProjectMcpServers === true || settings.enabledMcpjsonServers?.includes(name) || claudeProject(user, cwd)?.enabledMcpjsonServers?.includes(name));
|
|
187
|
+
add(name, claudeEntry(entry), 'project', !!approved, 'project-not-approved');
|
|
188
|
+
}
|
|
189
|
+
for (const [name, entry] of Object.entries(object(claudeProject(user, cwd)?.mcpServers))) add(name, claudeEntry(entry), 'local');
|
|
190
|
+
const installed = document(join(configDir, 'plugins', 'installed_plugins.json'));
|
|
191
|
+
for (const [key, installs] of Object.entries(object(installed.plugins))) {
|
|
192
|
+
if (settings.enabledPlugins?.[key] === false) continue;
|
|
193
|
+
const install = (Array.isArray(installs) ? installs : [installs]).filter(row => row?.installPath && (!row.projectPath || canonical(row.projectPath) === canonical(cwd))).at(-1);
|
|
194
|
+
if (!install) continue;
|
|
195
|
+
const manifest = document(join(install.installPath, '.claude-plugin', 'plugin.json'));
|
|
196
|
+
if (!manifest.name) continue;
|
|
197
|
+
for (const [name, entry] of Object.entries(claudePluginDefinitions(install.installPath, manifest))) add(`plugin:${manifest.name}:${name}`, claudeEntry(entry), 'plugin');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (provider === 'claude-code') {
|
|
201
|
+
const configDir = env.CLAUDE_CONFIG_DIR || join(home, '.claude');
|
|
202
|
+
const user = document(env.CLAUDE_CONFIG_DIR ? join(configDir, '.claude.json') : join(home, '.claude.json'));
|
|
203
|
+
const disabled = new Set([...(user.disabledMcpServers || []), ...(claudeProject(user, cwd)?.disabledMcpServers || [])]);
|
|
204
|
+
for (const name of disabled) if (rows.has(name)) Object.assign(rows.get(name), { deliverable: false, reason: 'disabled-in-provider' });
|
|
205
|
+
}
|
|
206
|
+
const servers = [...rows.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
207
|
+
return { provider, servers, definitionFor(name) {
|
|
208
|
+
const definition = definitions.get(name);
|
|
209
|
+
if (!definition) return undefined;
|
|
210
|
+
const entry = structuredClone(definition);
|
|
211
|
+
if (provider === 'claude-code' && dataHome && object(reg.servers)[name] && !entry.url) entry.env = { ...object(entry.env), ...document(join(dataHome, 'data', 'mcp', name, 'env.json')) };
|
|
212
|
+
return entry;
|
|
213
|
+
}, isBlocked(name) { return access[name] === false; } };
|
|
214
|
+
}
|
|
215
|
+
export function discoverExternalServers(options) { return externalServerInventory(options).servers; }
|
|
216
|
+
|
|
217
|
+
/** Codex CLI splits paths on literal dots; quoted TOML key syntax is not supported. */
|
|
218
|
+
export function codexMcpEnabledOverride(name) {
|
|
219
|
+
if (!name || /[.=\x00-\x1f\x7f]/.test(name)) throw Object.assign(new Error(`Codex cannot disable MCP server ${JSON.stringify(name)} for this run: its CLI override paths do not support dots, equals signs or control characters in server names. Rename the server in Codex configuration before retrying.`), { code: 'MCP_SCOPE_UNENFORCED', status: 400 });
|
|
220
|
+
return `mcp_servers.${name}.enabled`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function selectExternalServers(tools, inventory) {
|
|
224
|
+
const requested = [...new Set((tools || []).filter(item => item.startsWith('mcp:')))];
|
|
225
|
+
const selected = new Set(), problems = [];
|
|
226
|
+
for (const ref of requested) {
|
|
227
|
+
if (ref === 'mcp:*') { for (const row of inventory.servers) if (row.deliverable && !row.blocked) selected.add(row.name); continue; }
|
|
228
|
+
const name = ref.slice(4), row = inventory.servers.find(row => row.name === name);
|
|
229
|
+
// A names-only cross-provider catalog distinguishes a typo from a server
|
|
230
|
+
// configured on a different provider. Resolve it only for a missing name.
|
|
231
|
+
const known = !row ? (typeof inventory.catalog === 'function' ? inventory.catalog() : inventory.catalog || []).find(item => item.ref === ref) : null;
|
|
232
|
+
const provider = inventory.provider || row?.provider;
|
|
233
|
+
const context = provider ? ` for ${provider}` : '';
|
|
234
|
+
const reason = row?.blocked || known?.blocked || inventory.isBlocked?.(name) === true ? 'blocked'
|
|
235
|
+
: row?.selectable === false || !externalServerName(name) ? 'non-selectable'
|
|
236
|
+
: !row ? known ? 'absent-for-provider' : 'unknown-server' : !row.deliverable ? 'unavailable' : null;
|
|
237
|
+
const hint = reason === 'blocked' ? `${ref} is blocked for workers by the owner's MCP access setting${context}.`
|
|
238
|
+
: reason === 'non-selectable' ? `${ref} cannot be selected by name; external names must start with a letter or digit and contain only letters, digits, dots, underscores, colons or hyphens (at most 64 characters).`
|
|
239
|
+
: reason === 'absent-for-provider' ? `${ref} is not configured${context}. Configure it there or choose a provider where it is available.`
|
|
240
|
+
: reason === 'unknown-server' ? `Unknown external MCP server ${ref}${context}; use an exact, case-sensitive name from tools.servers.`
|
|
241
|
+
: reason === 'unavailable' ? `${ref} is configured${context} but unavailable (${row.reason || 'disabled or unsupported definition'}).` : null;
|
|
242
|
+
if (reason) problems.push({ item: ref, reason, provider: provider || null, hint, suggestions: inventory.servers.filter(row => row.deliverable && !row.blocked && row.selectable !== false && externalServerName(row.name) && row.name.toLowerCase().includes(name.toLowerCase())).map(row => `mcp:${row.name}`).slice(0, 3) });
|
|
243
|
+
else selected.add(name);
|
|
244
|
+
}
|
|
245
|
+
if (problems.length) throw Object.assign(new Error(problems.map(problem => problem.hint).join(' ')), { code: 'TOOLS_INVALID', status: 400, problems });
|
|
246
|
+
return { names: [...selected].sort(), disabled: inventory.servers.filter(row => !selected.has(row.name)).map(row => row.name) };
|
|
247
|
+
}
|
|
248
|
+
export function externalCatalog(options = {}) {
|
|
249
|
+
const rows = new Map();
|
|
250
|
+
for (const provider of ['codex', 'opencode', 'claude-code']) for (const row of discoverExternalServers({ ...options, provider })) {
|
|
251
|
+
if (!rows.has(row.name)) rows.set(row.name, { ref: `mcp:${row.name}`, label: row.name, selectable: row.selectable, providers: { codex: 'absent', opencode: 'absent', 'claude-code': 'absent' }, blocked: row.blocked });
|
|
252
|
+
rows.get(row.name).providers[provider] = row.blocked ? 'blocked' : row.deliverable ? 'deliverable' : 'unavailable';
|
|
253
|
+
}
|
|
254
|
+
return [...rows.values()].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
255
|
+
}
|
|
256
|
+
/** Complete validated disabled entries, proved on 2.1.4. No owner env/header/URL payload in the disabled copy. */
|
|
257
|
+
export function scopeOpenCodeServers(config, tools, inventory, { node = process.execPath } = {}) {
|
|
258
|
+
const scope = selectExternalServers(tools, inventory);
|
|
259
|
+
const servers = new Map(Object.entries(mcpServersOf(config)));
|
|
260
|
+
for (const row of inventory.servers) servers.set(row.name, scope.names.includes(row.name) ? inventory.definitionFor(row.name) : { type: 'local', command: [node, '-e', 'process.exit(0)'], disabled: true });
|
|
261
|
+
return { scope, config: { ...config, mcp: { ...object(config.mcp), servers: Object.fromEntries(servers) } }, overlay: { mcp: { servers: Object.fromEntries(inventory.servers.filter(row => !scope.names.includes(row.name)).map(row => [row.name, servers.get(row.name)])) } } };
|
|
262
|
+
}
|
|
263
|
+
/** Secret values go only into the CLI environment; the SDK serializes reference strings into argv. */
|
|
264
|
+
export function referenceClaudeMap(mcpServers, baseEnv = {}) {
|
|
265
|
+
const env = {}; let n = 0;
|
|
266
|
+
const reference = value => {
|
|
267
|
+
// Existing references must be expanded by the CLI, which applies its own credential/managed-policy rules.
|
|
268
|
+
if (String(value).includes('${')) return String(value);
|
|
269
|
+
const key = `SYNABUN_MCP_${++n}`;
|
|
270
|
+
env[key] = String(value);
|
|
271
|
+
return '${' + key + '}';
|
|
272
|
+
};
|
|
273
|
+
const map = {};
|
|
274
|
+
for (const [name, definition] of Object.entries(mcpServers)) {
|
|
275
|
+
const entry = structuredClone(definition);
|
|
276
|
+
for (const field of ['env', 'headers']) if (entry[field]) entry[field] = Object.fromEntries(Object.entries(entry[field]).map(([key, value]) => [key, reference(value)]));
|
|
277
|
+
if (entry.headersHelper) entry.headersHelper = reference(entry.headersHelper);
|
|
278
|
+
if (entry.oauth) entry.oauth = Object.fromEntries(Object.entries(entry.oauth).map(([key, value]) => [key, typeof value === 'string' ? reference(value) : value]));
|
|
279
|
+
if (entry.url) entry.url = reference(entry.url);
|
|
280
|
+
if (Array.isArray(entry.args)) entry.args = entry.args.map(reference);
|
|
281
|
+
Object.defineProperty(map, name, { value: entry, enumerable: true, configurable: true, writable: true });
|
|
282
|
+
}
|
|
283
|
+
return { mcpServers: map, env };
|
|
284
|
+
}
|
|
285
|
+
export function assertClaudeMcpScope(status, names) {
|
|
286
|
+
if (!Array.isArray(status) || status.some(row => !names.includes(row.name)) || names.some(name => !status.some(row => row.name === name))) throw Object.assign(new Error('Claude MCP scope could not be enforced before the first turn'), { code: 'MCP_SCOPE_UNENFORCED' });
|
|
287
|
+
}
|
|
288
|
+
export function writePrivateMcpConfig(path, mcpServers) {
|
|
289
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
290
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
291
|
+
writeFileSync(temporary, JSON.stringify({ mcpServers }) + '\n', { mode: 0o600 }); chmodSync(temporary, 0o600); renameSync(temporary, path); chmodSync(path, 0o600);
|
|
292
|
+
return path;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Keep external grants fixed when an MCP child reports only SynaBun group changes. */
|
|
296
|
+
export function retainExternalRefs(groups, previous) {
|
|
297
|
+
return [...new Set([...(groups || []).filter(item => !item.startsWith('mcp:')), ...(previous || []).filter(item => item.startsWith('mcp:'))])];
|
|
298
|
+
}
|
|
299
|
+
export async function waitForClaudeMcpScope(q, names, { timeoutMs = 30000 } = {}) {
|
|
300
|
+
const end = Date.now() + timeoutMs;
|
|
301
|
+
while (Date.now() < end) {
|
|
302
|
+
if (typeof q.mcpServerStatus !== 'function') break;
|
|
303
|
+
const remaining = end - Date.now(); let timer;
|
|
304
|
+
let rows;
|
|
305
|
+
try { rows = await Promise.race([q.mcpServerStatus(), new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('MCP scope check timed out')), remaining); })]); }
|
|
306
|
+
finally { clearTimeout(timer); }
|
|
307
|
+
if (!Array.isArray(rows) || rows.some(row => !names.includes(row.name))) break;
|
|
308
|
+
if (names.every(name => rows.some(row => row.name === name))) return;
|
|
309
|
+
await new Promise(done => setTimeout(done, Math.min(100, Math.max(0, end - Date.now()))));
|
|
310
|
+
}
|
|
311
|
+
throw Object.assign(new Error('Claude MCP scope could not be enforced before the first turn'), { code: 'MCP_SCOPE_UNENFORCED' });
|
|
312
|
+
}
|
|
313
|
+
export function redactMcpValues(value, values) {
|
|
314
|
+
if (typeof value === 'string') { for (const secret of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) value = value.split(secret).join('[MCP value]'); return value; }
|
|
315
|
+
if (Array.isArray(value)) return value.map(item => redactMcpValues(item, values));
|
|
316
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactMcpValues(item, values)]));
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function mcpDefinitionValues(servers) {
|
|
321
|
+
const strings = value => typeof value === 'string' ? [value] : Array.isArray(value) ? value.flatMap(strings) : value && typeof value === 'object' ? Object.values(value).flatMap(strings) : [];
|
|
322
|
+
return [...new Set(Object.values(servers || {}).flatMap(entry => ['command', 'args', 'cwd', 'env', 'environment', 'headers', 'url', 'oauth', 'headersHelper', 'authToken'].flatMap(field => strings(entry[field]))))].filter(Boolean);
|
|
323
|
+
}
|
package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js}
RENAMED
|
@@ -3,9 +3,9 @@ import { randomUUID } from 'crypto';
|
|
|
3
3
|
/**
|
|
4
4
|
* Serializes and coalesces post-tool-call MCP refreshes per managed runtime.
|
|
5
5
|
* Different sidepanels remain independent; repeated selections in one panel
|
|
6
|
-
* collapse to the newest pending
|
|
6
|
+
* collapse to the newest pending tools and never reconnect concurrently.
|
|
7
7
|
*/
|
|
8
|
-
export class
|
|
8
|
+
export class McpToolsRefreshCoordinator {
|
|
9
9
|
constructor({
|
|
10
10
|
delayMs = 250,
|
|
11
11
|
setTimer = setTimeout,
|
|
@@ -21,7 +21,7 @@ export class McpProfileRefreshCoordinator {
|
|
|
21
21
|
this.states = new Map();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
schedule(runtimeId,
|
|
24
|
+
schedule(runtimeId, tools, runtimeKind, refresh) {
|
|
25
25
|
const refreshCorrelationId = this.correlationId();
|
|
26
26
|
let state = this.states.get(runtimeId);
|
|
27
27
|
if (!state) {
|
|
@@ -29,7 +29,7 @@ export class McpProfileRefreshCoordinator {
|
|
|
29
29
|
this.states.set(runtimeId, state);
|
|
30
30
|
}
|
|
31
31
|
state.cancelled = false;
|
|
32
|
-
state.pending = {
|
|
32
|
+
state.pending = { tools, runtimeKind, correlationId: refreshCorrelationId, refresh };
|
|
33
33
|
if (state.timer) this.clearTimer(state.timer);
|
|
34
34
|
if (!state.running) this.#arm(runtimeId, state);
|
|
35
35
|
return refreshCorrelationId;
|
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
import { createHash } from 'node:crypto';
|
|
22
22
|
import { existsSync } from 'node:fs';
|
|
23
23
|
import { spawn } from 'node:child_process';
|
|
24
|
+
import { registerBrowserNetworkEndpoint } from './browser-network-guard.js';
|
|
24
25
|
|
|
25
26
|
export const DEFAULT_MORELOGIN_PORT = 40000;
|
|
27
|
+
// MoreLogin's documented local-API ports. 40000 is the HTTP API SynaBun drives; 15523 is the companion port its
|
|
28
|
+
// desktop app also listens on. A guarded worker's sandbox denies both (a worker with the API could start another
|
|
29
|
+
// profile with its own CDP). Fixed, so they are denied even when nothing is listening at profile-generation time.
|
|
30
|
+
export const MORELOGIN_FIXED_API_PORTS = Object.freeze([40000, 15523]);
|
|
26
31
|
|
|
27
32
|
/** Resolve the MoreLogin local-API port: env → saved config → default. */
|
|
28
33
|
export function getMoreLoginPort(savedCfg) {
|
|
@@ -271,6 +276,31 @@ export async function startProfile(envId, opts = {}) {
|
|
|
271
276
|
return { debugPort: port, webdriver: webdriver || null, raw: data };
|
|
272
277
|
}
|
|
273
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Attach to the profile's existing context, preserving MoreLogin's identity.
|
|
281
|
+
* A new Playwright context is incognito: it does not inherit the profile's
|
|
282
|
+
* cookies/fingerprint, even though it belongs to the same Chromium process.
|
|
283
|
+
* Never substitute one when the profile context is missing. On setup failure,
|
|
284
|
+
* close only this connectOverCDP transport; MoreLogin owns the running browser.
|
|
285
|
+
*/
|
|
286
|
+
export async function attachProfileBrowser(chromium, endpoint, openPage) {
|
|
287
|
+
const browser = await chromium.connectOverCDP(endpoint);
|
|
288
|
+
try {
|
|
289
|
+
const context = browser.contexts()[0];
|
|
290
|
+
if (!context) {
|
|
291
|
+
throw new Error('MoreLogin did not expose its profile context over CDP; refusing to create an isolated context with different cookies and fingerprint settings');
|
|
292
|
+
}
|
|
293
|
+
const page = await openPage(browser, context);
|
|
294
|
+
registerBrowserNetworkEndpoint(context, endpoint);
|
|
295
|
+
return { browser, context, page };
|
|
296
|
+
} catch (error) {
|
|
297
|
+
// Browser.close on a connectOverCDP browser disconnects this client. Do not
|
|
298
|
+
// call context.close or /api/env/close: either would close the user's profile.
|
|
299
|
+
try { await browser.close(); } catch { /* retain the original setup error */ }
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
274
304
|
/** Stop (close) a profile. */
|
|
275
305
|
export async function stopProfile(envId, opts = {}) {
|
|
276
306
|
return request('/api/env/close', { envId: String(envId) }, opts);
|