synabun 2.0.9 → 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 +115 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +68 -4
- 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/launcher.mjs +9 -3
- 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 +43 -5
- 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 +3680 -206
- package/neural-interface/i18n/pt-BR.json +3628 -157
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +148 -35
- 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 +122 -44
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +59 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +97 -0
- package/neural-interface/lib/assistant-catalog.js +23 -9
- package/neural-interface/lib/assistant-dispatch.js +874 -156
- package/neural-interface/lib/assistant-envelope.js +22 -2
- 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 +183 -26
- 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-model-catalog.js +64 -14
- 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-settings-schema.js +109 -47
- 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 +164 -67
- package/neural-interface/lib/exec-loop-command.js +4 -1
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- 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 +565 -50
- package/neural-interface/lib/native-loop-runtime.js +287 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +58 -0
- package/neural-interface/lib/opencode-history.js +49 -204
- 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 +310 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +32 -15
- package/neural-interface/lib/opencode-serve-records.js +624 -0
- package/neural-interface/lib/opencode-v2-client.js +53 -131
- package/neural-interface/lib/opencode-v2-ws-requests.js +35 -195
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +796 -0
- package/neural-interface/lib/opencode2-proxy.js +76 -0
- package/neural-interface/lib/opencode2-translate.js +1057 -0
- 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/process-identity.js +361 -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 +18 -10
- package/neural-interface/lib/session-title-generator.js +58 -18
- 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/style-guide/api.js +4 -3
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/untrusted-file-headers.js +27 -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 +1354 -50
- package/neural-interface/package.json +6 -2
- package/neural-interface/public/index.html +5 -3
- 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 +76 -16
- 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 +205 -19
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- 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 +241 -112
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- 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-hosts.js +49 -2
- 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 +84 -12
- package/neural-interface/public/shared/assistant/asst-panel.js +303 -44
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +93 -58
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +770 -100
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +530 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- 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 +25 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +102 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +165 -13
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +550 -0
- 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 +58 -150
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +62 -32
- 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 +15 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +201 -74
- 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/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +35 -5
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +78 -86
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/launch-prefetch.js +59 -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-caps.js +3 -2
- 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 +9 -2
- 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-modelpicker.js +54 -17
- 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 +140 -254
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +97 -203
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -22
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +20 -13
- 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 +53 -82
- 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 +48 -40
- 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 +205 -62
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- 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 +452 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +90 -0
- package/neural-interface/public/shared/settings/settings-ia.js +23 -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/sidepanel-session-status.js +49 -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 +2065 -2150
- 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-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +155 -152
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +153 -13
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- 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 +21 -9
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +56 -52
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +229 -40
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +9 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +2267 -953
- package/neural-interface/public/shared/ui-sessions.js +20 -7
- package/neural-interface/public/shared/ui-settings.js +679 -496
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +56 -5
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +42 -2781
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +22 -5
- package/neural-interface/public/shared/ui-terminal.js +159 -199
- 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 +14 -5
- 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 -61
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -499
- package/neural-interface/public/shared/ui-update.js +121 -16
- package/neural-interface/public/shared/ui-whiteboard.js +313 -59
- 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 +36 -2
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +72 -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/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -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 +116 -3
- package/neural-interface/server.js +4834 -2541
- 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 +5 -5
- 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
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { externalServerInventory, selectExternalServers, codexMcpEnabledOverride, referenceClaudeMap, waitForClaudeMcpScope, redactMcpValues, writePrivateMcpConfig, mcpDefinitionValues } from './mcp-external-servers.js';
|
|
4
|
+
import { resolveStoredTools, runtimeToolsValue, readDefaultTools } from './tool-lists.js';
|
|
5
|
+
import { waitForSynabunTools, synabunToolsTimeoutText } from './opencode-mcp-ready.js';
|
|
6
|
+
import { config as memoryConfig, detectProject as memoryProject } from '../../mcp-server/dist/config.js';
|
|
2
7
|
import { Codex } from '@openai/codex-sdk';
|
|
8
|
+
import { CodexWorkerAppServer } from './codex-worker-app-server.js';
|
|
3
9
|
import { query as claudeQuery } from '@anthropic-ai/claude-agent-sdk';
|
|
4
10
|
import { CLAUDE_EFFORT_LEVELS } from './claude-model-catalog.js';
|
|
5
11
|
import { CLAUDE_NOT_INSTALLED, resolveClaudeSdkExecutable } from './claude-executable.js';
|
|
6
12
|
import { extractOpenCodeText } from './session-title-generator.js';
|
|
13
|
+
import { withServeEnv } from './opencode-protocol.js';
|
|
7
14
|
import { patchCodexLineSplitting } from './jsonl-lines.js';
|
|
8
|
-
import { dirname } from 'node:path';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
9
16
|
import { codexReasoningEffort } from './effort-levels.js';
|
|
17
|
+
import { spawn as spawnProcess } from 'node:child_process';
|
|
10
18
|
import { codexStreamMedia, collectCodexMedia, openCodeMediaPart, ownCodexImages } from './assistant-media.js';
|
|
11
19
|
import { browserToolDenial } from './browser-tool-policy.js';
|
|
12
20
|
import { CODEX_BROWSER_POLICY_UNTRUSTED_NOTE, codexBrowserPolicyHook } from './assistant-route-gate-codex.js';
|
|
13
21
|
import { codexWindowsSandbox, codexWindowsSandboxConfig } from './agent-sandbox-elevation.js';
|
|
22
|
+
import { DRAFT_DISALLOWED_TOOLS, DRAFT_TOOLS, codexDraftOptions, draftOpenCodeDecision, draftPreToolUseHook, draftToolDecision, openCodeDraftConfig, resolveDraftConfinement } from './skills/draft-confinement.js';
|
|
23
|
+
import { EVAL_CONFINEMENT, codexEvalOptions, evalClaudeOptions, evalGitSafetyEnv, evalOpenCodeDecision, evalPreToolUseHook, evalToolDecision, openCodeEvalConfig, openCodeEvalEnv, placeOpenCodeEvalSkill, resolveEvalConfinement } from './skills/eval-confinement.js';
|
|
24
|
+
import { WORKER_GUARD_DEFAULT, codexSandboxFor, confinementEvent, openCodeCodeModeDecision, openCodeCodeModeNote, openCodePermissionDecision, openCodePermissionsFor, resolveWorkerConfinement, workerShellDecision, workerToolDecision } from './worker-confinement.js';
|
|
25
|
+
|
|
26
|
+
function nativeToolSelection(options, provider) { return resolveStoredTools(options, { source: options.runMode === 'task' ? 'dispatch' : 'launch', provider, defaultTools: options.runMode === 'task' ? undefined : readDefaultTools(memoryConfig.dataHome) }); }
|
|
14
27
|
|
|
15
28
|
function stringEnv(source) {
|
|
16
29
|
const out = {};
|
|
@@ -112,6 +125,12 @@ export function normalizeOpenCodeModel(model) {
|
|
|
112
125
|
export const PERMISSION_POLICIES = new Set(['auto', 'ask', 'restricted']);
|
|
113
126
|
export const CAPABILITIES = new Set(['read-only', 'workspace', 'full']);
|
|
114
127
|
export const CLAUDE_READ_ONLY_DISALLOWED_TOOLS = ['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'Bash'];
|
|
128
|
+
// The owner's live data home (its real memory DB). A worker's shell must never inherit these: a temp server or a
|
|
129
|
+
// test suite started from the worker would otherwise open the owner's real store (a reviewer's temp boot did,
|
|
130
|
+
// briefly). The worker's own SynaBun MCP child gets the DB through its own env block / DOTENV_PATH (server.js
|
|
131
|
+
// buildCodexMcpEnv, the OpenCode serve config), not from the shell; the Claude worker's MCP is in-process HTTP and
|
|
132
|
+
// needs nothing here. Stripped for every worker (guarded and full alike): a worker never opens the live DB directly.
|
|
133
|
+
export const LIVE_DATA_ENV = Object.freeze(['SQLITE_DB_PATH', 'MEMORY_DATA_DIR']);
|
|
115
134
|
|
|
116
135
|
export function normalizePermissionPolicy(policy) {
|
|
117
136
|
const value = String(policy || 'auto').trim().toLowerCase();
|
|
@@ -136,6 +155,99 @@ function policyUnsupportedError(provider, policy) {
|
|
|
136
155
|
return error;
|
|
137
156
|
}
|
|
138
157
|
|
|
158
|
+
/** A confined run (lib/skills/draft-confinement.js) that cannot be set up on its provider: it never starts unconfined. */
|
|
159
|
+
function confinementUnsupportedError(provider, reason, what = 'a drafting folder') {
|
|
160
|
+
const error = new Error(`${provider} cannot run confined to ${what}${reason ? ` (${reason})` : ''}: the run did not start.`);
|
|
161
|
+
error.code = 'CONFINEMENT_UNSUPPORTED';
|
|
162
|
+
return error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Claude's built-in writers, kept off the tool list of an enforced read-only run (the hook refuses them too; Bash is the hook's). */
|
|
166
|
+
export const CLAUDE_READ_ONLY_WRITE_TOOLS = Object.freeze(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The worker confinement of a run (lib/worker-confinement.js), or null: capability full, the Protect workers
|
|
170
|
+
* setting off, or a skill-drafting run (which has its own, stricter confinement). Throws when the working
|
|
171
|
+
* directory cannot be resolved: the run must not start on a guess.
|
|
172
|
+
*/
|
|
173
|
+
function workerGuardFor({ capability, cwd, guardMode, scratchDir, confinement, serverPort = null, localPorts = null, localPortRange = null }) {
|
|
174
|
+
if (confinement) return null;
|
|
175
|
+
// Never rooted where the server runs: a run without a cwd is rooted at its scratch folder, or does not start.
|
|
176
|
+
return resolveWorkerConfinement({ capability, cwd: cwd || null, mode: guardMode, scratchDir: scratchDir || null, serverPort, localPorts, localPortRange });
|
|
177
|
+
}
|
|
178
|
+
/** The port of SynaBun's own server, from a run's MCP address (a guarded worker's shell may not call it). */
|
|
179
|
+
function serverPortOf(mcpUrl, fallback = null) {
|
|
180
|
+
try { const port = Number(new URL(String(mcpUrl)).port); if (port > 0) return port; } catch { /* no address */ }
|
|
181
|
+
return Number(fallback) > 0 ? Number(fallback) : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* What an OpenCode run's own config carries for its confinement (server.js setupOpencodeLoopConfig): null for an
|
|
186
|
+
* unguarded run (today's rules), else { permissions (OpenCode 2's rules), mcp: 'off'?, directMcp: true? }. A
|
|
187
|
+
* skill-drafting run has no shell, web, subagent or skill tool and no MCP server; a guarded worker has the rules its
|
|
188
|
+
* adapter answers by policy, and under Enforce no Code Mode: `directMcp` sets `codemode: false` on every MCP server of
|
|
189
|
+
* the run's config, which OpenCode 2.1.4 otherwise reaches only through Code Mode's `execute` (denied by the rules).
|
|
190
|
+
*/
|
|
191
|
+
export function openCodeRunConfigFor(state = {}) {
|
|
192
|
+
if (state.confinement?.kind === EVAL_CONFINEMENT) return openCodeEvalConfig({ role: state.confinement.role, shell: state.confinement.shell === true });
|
|
193
|
+
if (state.confinement) return openCodeDraftConfig();
|
|
194
|
+
const guard = workerGuardFor({ capability: normalizeCapability(state.capability), cwd: state.cwd, guardMode: state.guardMode ?? WORKER_GUARD_DEFAULT, scratchDir: state.scratchDir, confinement: null, serverPort: state.serverPort || null });
|
|
195
|
+
if (!guard) return null;
|
|
196
|
+
return { permissions: openCodePermissionsFor(guard), ...(guard.enforced ? { directMcp: true } : {}) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Whether an OpenCode run is a protected worker: an Assistant task worker with capability read-only or workspace
|
|
201
|
+
* under Protect workers = Enforce (never a drafting or evaluation run, which have confinements of their own; never
|
|
202
|
+
* Warn, Off or full). Its serve reads no project configuration (openCodeRunEnvFor) and keeps its password out of every
|
|
203
|
+
* environment (server.js ensureIsolatedServe `hidePassword`: lib/opencode-protocol.js serveSpecFor passwordFrom
|
|
204
|
+
* 'stdout'). Throws like resolveWorkerConfinement when a guarded run has no folder.
|
|
205
|
+
*/
|
|
206
|
+
export function openCodeProtectedWorker(state = {}) {
|
|
207
|
+
if (state.confinement) return false;
|
|
208
|
+
const guard = workerGuardFor({ capability: normalizeCapability(state.capability), cwd: state.cwd, guardMode: state.guardMode ?? WORKER_GUARD_DEFAULT, scratchDir: state.scratchDir, confinement: null, serverPort: state.serverPort || null });
|
|
209
|
+
return !!guard?.enforced;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What a protected worker's serve environment holds (openCodeProtectedWorker): no project configuration at all, so
|
|
214
|
+
* nothing the worker writes in its folder runs in the serve (no opencode.json with plugins or MCP servers, no
|
|
215
|
+
* .opencode plugin, agent, command or tool, no project AGENTS.md: OPENCODE_DISABLE_PROJECT_CONFIG, and
|
|
216
|
+
* OPENCODE_CONFIG_PROJECT_DISABLE too, which OpenCode 2 reads first), and no inherited OPENCODE_CONFIG_DIR (OpenCode
|
|
217
|
+
* reads that folder in place of the run's own configuration, whose rules, `codemode: false` and SynaBun MCP pin would
|
|
218
|
+
* then be gone). Every inherited GIT_* variable is removed too: repository, index and object paths could point git
|
|
219
|
+
* outside the folder, and GIT_CONFIG_PARAMETERS or GIT_CONFIG_COUNT/KEY_n/VALUE_n could run an fsmonitor or pager.
|
|
220
|
+
* `GIT_*: null` clears the prefix before deliberate settings are applied (lib/opencode-protocol.js withServeEnv).
|
|
221
|
+
*/
|
|
222
|
+
export const OPENCODE_PROTECTED_WORKER_ENV = Object.freeze({
|
|
223
|
+
OPENCODE_DISABLE_PROJECT_CONFIG: '1', OPENCODE_CONFIG_PROJECT_DISABLE: '1', OPENCODE_CONFIG_DIR: null,
|
|
224
|
+
'GIT_*': null,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* What an OpenCode run's serve environment carries for its confinement (server.js ensureIsolatedServe): an evaluation
|
|
229
|
+
* run's (no project configuration, no file watcher, git's safety settings: lib/skills/eval-confinement.js
|
|
230
|
+
* openCodeEvalEnv), a protected worker's (OPENCODE_PROTECTED_WORKER_ENV), else null. A value of null removes that
|
|
231
|
+
* variable from the serve's environment. Throws when the evaluation folder cannot be resolved: the serve must not start
|
|
232
|
+
* unconfined.
|
|
233
|
+
*/
|
|
234
|
+
export function openCodeRunEnvFor(state = {}) {
|
|
235
|
+
if (state.confinement?.kind === EVAL_CONFINEMENT) return openCodeEvalEnv(resolveEvalConfinement(state.confinement, state.cwd));
|
|
236
|
+
if (openCodeProtectedWorker(state)) return { ...OPENCODE_PROTECTED_WORKER_ENV };
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* An evaluation trial with the skill: its copy placed in the run's own OpenCode configuration folder (`configDir`,
|
|
242
|
+
* server.js setupOpencodeLoopConfig's, removed with the run's serve), where OpenCode reads it as a global skill
|
|
243
|
+
* (lib/skills/eval-confinement.js placeOpenCodeEvalSkill). → the folder, or null (not an evaluation, a baseline, a
|
|
244
|
+
* grader). Throws when it cannot be placed: the run must not start without the skill it tests.
|
|
245
|
+
*/
|
|
246
|
+
export function openCodeEvalSkillFor(state = {}, configDir) {
|
|
247
|
+
if (state.confinement?.kind !== EVAL_CONFINEMENT) return null;
|
|
248
|
+
return placeOpenCodeEvalSkill(resolveEvalConfinement(state.confinement, state.cwd), configDir);
|
|
249
|
+
}
|
|
250
|
+
|
|
139
251
|
/** Final assistant text carried by a Codex SDK stream event, or null. */
|
|
140
252
|
export function codexAgentMessageText(event) {
|
|
141
253
|
if (!event) return null;
|
|
@@ -241,23 +353,47 @@ function codexStructuredOutput(text, schema) {
|
|
|
241
353
|
export async function createCodexNativeLoopAdapter(options = {}) {
|
|
242
354
|
const {
|
|
243
355
|
runId, cwd, model, effort, codexHome, codexPath, browserSessionId,
|
|
244
|
-
browserTabId,
|
|
356
|
+
browserTabId, onIdentity = () => {}, onEvent = () => {},
|
|
245
357
|
CodexClass = Codex,
|
|
246
358
|
extraEnv = {}, permissionPolicy: rawPolicy = 'auto', capability: rawCapability = 'full',
|
|
247
359
|
outputSchema = null, desktopGrantProvider = null, modelContextWindow = null, contextMode = null,
|
|
248
360
|
// Image creation runs (assistant dispatch): 'image' → each turn returns the images it generated.
|
|
249
361
|
collectMedia = null,
|
|
250
|
-
// Assistant task runs: the browser policy's hook and the run's notes (a function: never in the state file)
|
|
251
|
-
|
|
362
|
+
// Assistant task runs: the browser policy's hook and the run's notes (a function: never in the state file), and
|
|
363
|
+
// the registered code projects its test runners may also run in (lib/browser-policy-roots.js, read as the run starts).
|
|
364
|
+
runMode = 'loop', onNote = null, browserPolicyTrust = null, policyRoots = [],
|
|
252
365
|
// Inherited variables to remove before extraEnv applies (the Codex account's launch:
|
|
253
366
|
// lib/codex-accounts.js launchFor() — inherited OpenAI/Codex credentials never reach the run).
|
|
254
367
|
unsetEnv = [],
|
|
255
368
|
// The Admin sandbox setting when the run started (lib/agent-sandbox-elevation.js): the Windows
|
|
256
369
|
// sandbox every `codex exec` of the thread may use. The run keeps it while it lives.
|
|
257
370
|
adminSandbox = false,
|
|
371
|
+
// A skill-drafting run's confinement (lib/skills/draft-confinement.js codexDraftOptions): the sandbox around the
|
|
372
|
+
// workspace, no network, no MCP server.
|
|
373
|
+
confinement = null,
|
|
374
|
+
// The Protect workers setting as the run starts (lib/worker-confinement.js), the run's own scratch folder and
|
|
375
|
+
// SynaBun's own port (a guarded worker's shell may not call it).
|
|
376
|
+
guardMode = WORKER_GUARD_DEFAULT, scratchDir = null, serverPort = null, localPortRange = null,
|
|
258
377
|
} = options;
|
|
259
378
|
const permissionPolicy = normalizePermissionPolicy(rawPolicy);
|
|
260
379
|
const capability = normalizeCapability(rawCapability);
|
|
380
|
+
// Resolved before anything starts: a confinement that cannot be set up stops the run (never an unconfined start).
|
|
381
|
+
let drafted = null;
|
|
382
|
+
let evaluated = null;
|
|
383
|
+
if (confinement?.kind === EVAL_CONFINEMENT) {
|
|
384
|
+
// A skill-evaluation run (lib/skills/eval-confinement.js): the sandbox around its job folder (read-only for a
|
|
385
|
+
// grader, a judge or a proposer), no network, no web search, every MCP server disabled.
|
|
386
|
+
let held;
|
|
387
|
+
try { held = resolveEvalConfinement(confinement, cwd); } catch (error) { throw confinementUnsupportedError('Codex', error?.message || error, 'an evaluation folder'); }
|
|
388
|
+
evaluated = { held, ...codexEvalOptions(held, { codexHome, projectDir: cwd }) };
|
|
389
|
+
} else if (confinement) {
|
|
390
|
+
let confined;
|
|
391
|
+
try { confined = resolveDraftConfinement(confinement, cwd); } catch (error) { throw confinementUnsupportedError('Codex', error?.message || error); }
|
|
392
|
+
drafted = codexDraftOptions(confined, { codexHome, projectDir: cwd });
|
|
393
|
+
}
|
|
394
|
+
// Either confinement's options (the thread's sandbox and the config's MCP switches): one shape.
|
|
395
|
+
const boxed = drafted || evaluated;
|
|
396
|
+
const guard = boxed ? null : workerGuardFor({ capability, cwd, guardMode, scratchDir, confinement: null, serverPort, localPortRange });
|
|
261
397
|
// "<id>[extended]" (the Assistant's extended-context row) runs <id> with the
|
|
262
398
|
// larger window the dispatcher read from the catalog; never silently at 272k.
|
|
263
399
|
const legacyExtended = /\[extended\]$/i.test(String(model || ''));
|
|
@@ -283,16 +419,37 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
283
419
|
SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
|
|
284
420
|
SYNABUN_BROWSER_SESSION: browserSessionId,
|
|
285
421
|
SYNABUN_BROWSER_TAB: browserTabId,
|
|
286
|
-
|
|
422
|
+
SYNABUN_TOOLS: runtimeToolsValue((() => { const list = nativeToolSelection(options, 'codex'); return list.items.includes('all') ? list.items : [...list.groups, ...list.servers]; })()),
|
|
287
423
|
SYNABUN_TOOL_CATALOG_MODE: 'deferred',
|
|
288
424
|
// Computer-use workers: the grant unlocks the `computer` group in the MCP child.
|
|
289
425
|
SYNABUN_DESKTOP_GRANT: typeof desktopGrantProvider === 'function' ? desktopGrantProvider() || undefined : undefined,
|
|
290
426
|
});
|
|
291
427
|
const inherited = { ...process.env };
|
|
428
|
+
for (const key of ['SYNABUN_PROFILE', 'SYNABUN_RUNTIME_PROFILE_PATH', 'SYNABUN_TOOLS', 'SYNABUN_TOOLS_PATH']) delete inherited[key];
|
|
292
429
|
for (const key of Array.isArray(unsetEnv) ? unsetEnv : []) delete inherited[key];
|
|
293
|
-
|
|
430
|
+
// A worker never holds a brain's launch credential (the Assistant's own identity) or an owner's page secret.
|
|
431
|
+
delete inherited.SYNABUN_BRAIN_TOKEN;
|
|
432
|
+
// The worker's shell must not inherit the owner's live-data paths (its MCP child gets them through its own
|
|
433
|
+
// config: server.js buildCodexMcpEnv). A worker that boots a temp server or runs the suite stays off the live DB.
|
|
434
|
+
for (const key of LIVE_DATA_ENV) delete inherited[key];
|
|
435
|
+
// The hook reads the run's project and the other projects its test runners may run in (a JSON list) from its env.
|
|
436
|
+
const policyEnv = browserPolicy ? { SYNABUN_BROWSER_POLICY_CWD: cwd || process.cwd(), SYNABUN_BROWSER_POLICY_ROOTS: JSON.stringify(Array.isArray(policyRoots) ? policyRoots : []) } : {};
|
|
437
|
+
const env = withServeEnv(stringEnv({ ...inherited, ...pins, CODEX_HOME: codexHome, ...policyEnv, ...stringEnv(extraEnv) }), evaluated ? evalGitSafetyEnv(evaluated.held.root) : null);
|
|
294
438
|
const config = { ...codexWindowsSandboxConfig(adminSandbox) };
|
|
295
439
|
if (Object.keys(pins).length) config.mcp_servers = { SynaBun: { env: pins } };
|
|
440
|
+
if (!boxed) {
|
|
441
|
+
const inventory = externalServerInventory({ provider: 'codex', cwd, codexHome, env, home: env.HOME, dataHome: memoryConfig.dataHome, ...options.externalMcpContext });
|
|
442
|
+
const list = nativeToolSelection(options, 'codex');
|
|
443
|
+
const scope = selectExternalServers(list.servers, inventory);
|
|
444
|
+
// Leaf overrides preserve native transports/env. Codex treats quotes in a
|
|
445
|
+
// path literally; reject unaddressable names rather than launch a wider scope.
|
|
446
|
+
for (const name of scope.disabled) {
|
|
447
|
+
if (inventory.servers.find(row => row.name === name)?.reason === 'disabled-in-provider') continue;
|
|
448
|
+
config[codexMcpEnabledOverride(name)] = false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// A drafting or evaluation run: every MCP server disabled (SynaBun's keeps its pins beside the switch; they never start).
|
|
452
|
+
if (boxed) config.mcp_servers = { ...(config.mcp_servers || {}), ...Object.fromEntries(Object.entries(boxed.config.mcp_servers).map(([name, value]) => [name, { ...(config.mcp_servers?.[name] || {}), ...value }])) };
|
|
296
453
|
if (extended) config.model_context_window = Math.floor(Number(modelContextWindow));
|
|
297
454
|
if (browserPolicy) Object.assign(config, browserPolicy.config);
|
|
298
455
|
// The SDK is always told which Codex to start: the user's own installation.
|
|
@@ -303,7 +460,9 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
303
460
|
error.code = 'CODEX_NOT_INSTALLED';
|
|
304
461
|
throw error;
|
|
305
462
|
}
|
|
306
|
-
const
|
|
463
|
+
const WorkerCodex = task && !boxed && CodexClass === Codex ? CodexWorkerAppServer : CodexClass;
|
|
464
|
+
const codex = new WorkerCodex({
|
|
465
|
+
onProcess: options.onProcess, signal: options.signal, apiKey: options.apiKey, configArgs: options.configArgs,
|
|
307
466
|
...(codexPath ? { codexPathOverride: codexPath } : {}),
|
|
308
467
|
env,
|
|
309
468
|
config,
|
|
@@ -312,23 +471,67 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
312
471
|
// U+2028 / U+2029: a command printing one ended the run with "Failed to parse item".
|
|
313
472
|
patchCodexLineSplitting(codex);
|
|
314
473
|
const parent = dirname(cwd || process.cwd());
|
|
315
|
-
|
|
474
|
+
// The sandbox: a drafting run's (the workspace, no network); an enforced guard's (lib/worker-confinement.js
|
|
475
|
+
// codexSandboxFor: read-only, or workspace-write with the working directory and the scratch folder, never the
|
|
476
|
+
// parent folder); else today's (the capability's mode, the parent folder writable too).
|
|
477
|
+
const sandboxed = guard?.enforced ? codexSandboxFor(guard, { parentDir: parent && parent !== cwd ? parent : null, scratchDir }) : null;
|
|
478
|
+
// A guarded Codex worker (Protect workers = Enforce) runs with network OFF in Codex's own sandbox. Codex's
|
|
479
|
+
// sandbox network is all-or-nothing (no per-host/loopback denial on this CLI), so SynaBun cannot wrap it in an
|
|
480
|
+
// outer Seatbelt (nested sandbox-exec is refused): turning the network off is the only way its shell commands
|
|
481
|
+
// (node -e, curl, a script) cannot reach SynaBun's own control port or any other local endpoint. The owner has
|
|
482
|
+
// accepted that read-only / workspace Codex workers lose internet (HTTPS, npm, git fetch) for their shell. The
|
|
483
|
+
// worker's SynaBun MCP child runs outside this command sandbox, so remember / recall / browser tools still work.
|
|
484
|
+
const codexNetworkOff = !!(boxed || guard?.enforced);
|
|
485
|
+
const threadOptions = {
|
|
316
486
|
workingDirectory: cwd || process.cwd(),
|
|
317
487
|
model: runModel || undefined,
|
|
318
488
|
modelReasoningEffort: normalizeCodexEffort(effort),
|
|
319
489
|
approvalPolicy: 'never',
|
|
320
|
-
sandboxMode: codexSandboxModeFor({ permissionPolicy, capability }),
|
|
490
|
+
sandboxMode: boxed ? boxed.thread.sandboxMode : sandboxed ? sandboxed.sandboxMode : codexSandboxModeFor({ permissionPolicy, capability }),
|
|
321
491
|
skipGitRepoCheck: true,
|
|
322
|
-
networkAccessEnabled:
|
|
323
|
-
...(task ? { webSearchMode: 'disabled' } : {}),
|
|
324
|
-
additionalDirectories: parent && parent !== cwd ? [parent] : undefined,
|
|
325
|
-
}
|
|
492
|
+
networkAccessEnabled: !codexNetworkOff,
|
|
493
|
+
...(task || boxed ? { webSearchMode: 'disabled' } : {}),
|
|
494
|
+
additionalDirectories: boxed ? undefined : sandboxed ? sandboxed.additionalDirectories : parent && parent !== cwd ? [parent] : undefined,
|
|
495
|
+
};
|
|
496
|
+
const thread = options.resumeThreadId ? codex.resumeThread(options.resumeThreadId, threadOptions) : codex.startThread(threadOptions);
|
|
497
|
+
try { await thread.ready?.(); } catch (error) { await codex.dispose?.(); throw error; }
|
|
498
|
+
if (thread.id) onIdentity({ providerThreadId: thread.id, providerSessionId: thread.id });
|
|
326
499
|
let alive = true;
|
|
327
500
|
let activeAbort = null;
|
|
328
501
|
let activeTurn = null;
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
|
|
502
|
+
// A guarded run says at its start what holds it: Codex's sandbox never reports to SynaBun by itself, so this is
|
|
503
|
+
// the one place the note is written before any command (the run's notes, then its events).
|
|
504
|
+
if (guard) {
|
|
505
|
+
try { onNote?.(`worker confinement (${guard.mode}, capability ${guard.capability}): Codex sandbox ${sandboxed ? sandboxed.sandboxMode : codexSandboxModeFor({ permissionPolicy, capability })}${guard.enforced ? `, writable: ${guard.writable.join(', ')}` : ' as before (warn: what Enforce would refuse is noted)'}`); } catch {}
|
|
506
|
+
// Network-off is the one way a Codex worker's shell cannot reach SynaBun's control port (Codex runs its own
|
|
507
|
+
// sandbox, which SynaBun cannot wrap, and its network is all-or-nothing). Say it plainly, once, at the start.
|
|
508
|
+
if (guard.enforced) { try { onNote?.('This protected Codex worker has NO internet for its shell commands: Codex\'s sandbox network is all-or-nothing, so to keep its commands off SynaBun\'s control port (and every local port) the network is off. HTTPS, npm install and git fetch will fail in the shell; your SynaBun MCP tools (remember, recall, browser) still work. A run that needs internet in its shell must use capability "full".'); } catch {} }
|
|
509
|
+
else { try { onNote?.('warn mode: Enforce would also turn this Codex worker\'s shell network off (no internet), so its commands could not reach SynaBun\'s control port.'); } catch {} }
|
|
510
|
+
}
|
|
511
|
+
if (drafted) { try { onNote?.('skill-draft confinement: Codex sandbox workspace-write around the drafting folder, no network, no web search, every MCP server disabled; the shell stays, inside the sandbox'); } catch {} }
|
|
512
|
+
if (evaluated) { try { onNote?.(`skill-eval confinement (${evaluated.held.role}): Codex sandbox ${evaluated.thread.sandboxMode} around the evaluation folder, no network, no web search, every MCP server disabled; the shell stays, inside the sandbox`); } catch {} }
|
|
513
|
+
/**
|
|
514
|
+
* A command Codex ran (item.completed command_execution): under warn, what Enforce would have refused is noted;
|
|
515
|
+
* under enforce, a sandbox denial in its output is explained in SynaBun's words (Codex's sandbox refuses by itself
|
|
516
|
+
* and says nothing to SynaBun).
|
|
517
|
+
*/
|
|
518
|
+
const noteCommand = (item, meta) => {
|
|
519
|
+
if (!guard || item?.type !== 'command_execution') return;
|
|
520
|
+
const command = typeof item.command === 'string' ? item.command : Array.isArray(item.command) ? item.command.join(' ') : '';
|
|
521
|
+
if (!command) return;
|
|
522
|
+
const decision = workerShellDecision(command, guard);
|
|
523
|
+
const output = String(item.aggregated_output || item.output || '');
|
|
524
|
+
const denied = Number(item.exit_code) !== 0 && /operation not permitted|permission denied|read-only file system|sandbox|EPERM|EACCES|EROFS/i.test(output);
|
|
525
|
+
if (!decision && !denied) return;
|
|
526
|
+
const event = decision
|
|
527
|
+
? confinementEvent(decision, { tool: 'shell', confinement: guard, provider: 'codex' })
|
|
528
|
+
: { type: 'synabun.confinement', action: 'refused', capability: guard.capability, tool: 'shell', rule: 'sandbox', reason: `Codex's sandbox refused "${command.slice(0, 160)}": this run (capability ${guard.capability}) writes inside ${guard.writable.join(', ')} only${guard.capability === 'read-only' ? ' (read-only: nothing at all, the temp folder included)' : ''}.`, provider: 'codex' };
|
|
529
|
+
if (decision && guard.enforced && !denied) event.action = 'noted'; // the sandbox decides; the heuristic's view is a note
|
|
530
|
+
onEvent({ provider: 'codex', runId, iteration: meta?.iteration, event });
|
|
531
|
+
};
|
|
532
|
+
// The sidepanel keys a turn's items by this sequence, including across a restart.
|
|
533
|
+
// Scheduled SDK turns restart their item IDs; app-server workers keep their thread IDs.
|
|
534
|
+
let providerTurn = Number(options.providerTurnBase) || 0;
|
|
332
535
|
// Generated images already returned by an earlier turn, and when the thread began (its rollout's day).
|
|
333
536
|
const mediaSeen = new Set();
|
|
334
537
|
let threadStartedMs = 0;
|
|
@@ -337,9 +540,13 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
337
540
|
identity: () => ({ providerThreadId: thread.id, providerSessionId: thread.id }),
|
|
338
541
|
isAlive: () => alive,
|
|
339
542
|
describe: () => ({
|
|
340
|
-
provider: 'codex', permissionPolicy, capability, sandboxMode: codexSandboxModeFor({ permissionPolicy, capability }),
|
|
543
|
+
provider: 'codex', permissionPolicy, capability, sandboxMode: boxed ? boxed.thread.sandboxMode : sandboxed ? sandboxed.sandboxMode : codexSandboxModeFor({ permissionPolicy, capability }),
|
|
544
|
+
networkOff: codexNetworkOff,
|
|
341
545
|
windowsSandbox: codexWindowsSandbox(adminSandbox),
|
|
342
546
|
...(task ? { browserPolicy: browserPolicy ? 'hook' : 'instructions' } : {}),
|
|
547
|
+
...(drafted ? { confinement: 'skill-draft', root: cwd } : {}),
|
|
548
|
+
...(evaluated ? { confinement: EVAL_CONFINEMENT, role: evaluated.held.role, root: cwd } : {}),
|
|
549
|
+
...(guard ? { guard: { mode: guard.mode, capability: guard.capability, writable: [...guard.writable] } } : {}),
|
|
343
550
|
}),
|
|
344
551
|
async runTurn(prompt, meta = {}) {
|
|
345
552
|
if (!alive) throw new Error('Codex native loop is closed');
|
|
@@ -377,6 +584,7 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
377
584
|
if (agentText !== null) text = agentText;
|
|
378
585
|
const saved = codexStreamMedia(event);
|
|
379
586
|
if (saved) media.push(saved);
|
|
587
|
+
if (event?.type === 'item.completed') { try { noteCommand(event.item, meta); } catch { /* a note, never the turn */ } }
|
|
380
588
|
if (event?.type === 'turn.failed') failure = codexFailureMessage(event.error);
|
|
381
589
|
if (event?.type === 'turn.completed') { completed = true; if (event.usage) usage = event.usage; }
|
|
382
590
|
if (event?.type === 'error') failure = codexFailureMessage(event);
|
|
@@ -415,6 +623,7 @@ export async function createCodexNativeLoopAdapter(options = {}) {
|
|
|
415
623
|
async dispose() {
|
|
416
624
|
alive = false;
|
|
417
625
|
if (activeAbort) activeAbort.abort(asAbortError('disposed'));
|
|
626
|
+
await codex.dispose?.();
|
|
418
627
|
},
|
|
419
628
|
};
|
|
420
629
|
}
|
|
@@ -438,28 +647,61 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
438
647
|
unsetEnv = [],
|
|
439
648
|
// The host's last word on the environment: on Windows the Git Bash it names (lib/git-bash/).
|
|
440
649
|
childEnv = null,
|
|
650
|
+
// A skill-drafting run (lib/skills/draft-confinement.js): file tools in its folder only, nothing else.
|
|
651
|
+
confinement = null,
|
|
652
|
+
// The Protect workers setting as the run starts (lib/worker-confinement.js), the run's own scratch folder and
|
|
653
|
+
// SynaBun's own port (a guarded worker's shell may not call it; read from mcpUrl when not given).
|
|
654
|
+
guardMode = WORKER_GUARD_DEFAULT, scratchDir = null, serverPort = null, localPortRange = null,
|
|
655
|
+
// Assistant task runs: the registered code projects the browser policy lets test runners run in besides the
|
|
656
|
+
// run's own (lib/browser-policy-roots.js, read as the run starts).
|
|
657
|
+
policyRoots = [],
|
|
658
|
+
// A run resumed after a server restart (assistant-dispatch.js resumeRun): the Claude session to continue, and the
|
|
659
|
+
// cost counter the resumed CLI starts from (it restores its total from the transcript's last `cost-state` line,
|
|
660
|
+
// lib/worker-resume.js readCostState), so the first turn after it is charged only for itself.
|
|
661
|
+
resumeSessionId = null, costBaseUsd = 0,
|
|
662
|
+
// Told the CLI's process once it is spawned ({ pid, startedAt, command }): a run that outlives this server is
|
|
663
|
+
// found by it (the next start ends an orphan before it resumes the session).
|
|
664
|
+
onProcess = null,
|
|
441
665
|
} = options;
|
|
442
666
|
const permissionPolicy = normalizePermissionPolicy(rawPolicy);
|
|
443
667
|
const capability = normalizeCapability(rawCapability);
|
|
444
|
-
|
|
668
|
+
// Resolved before anything starts: a confinement that cannot be set up stops the run (never an unconfined start).
|
|
669
|
+
let evaluated = null;
|
|
670
|
+
if (confinement?.kind === EVAL_CONFINEMENT) {
|
|
671
|
+
try { evaluated = resolveEvalConfinement(confinement, cwd); } catch (error) { throw confinementUnsupportedError('Claude Code', error?.message || error, 'an evaluation folder'); }
|
|
672
|
+
}
|
|
673
|
+
const confined = confinement && !evaluated ? resolveDraftConfinement(confinement, cwd) : null;
|
|
674
|
+
// Either confinement (drafting, evaluation): no worker guard, nothing asked, never bypassPermissions.
|
|
675
|
+
const held = confined || evaluated;
|
|
676
|
+
const evalOptions = evaluated ? evalClaudeOptions(evaluated) : null;
|
|
677
|
+
// The worker confinement of a read-only / workspace run: a PreToolUse hook on every call (subagents' included),
|
|
678
|
+
// which Claude Code runs before any permission mode, so the run keeps bypassPermissions and nothing waits.
|
|
679
|
+
const guard = workerGuardFor({ capability, cwd, guardMode, scratchDir, confinement: held, serverPort: serverPortOf(mcpUrl, serverPort), localPortRange });
|
|
680
|
+
const askMode = !held && permissionPolicy === 'ask' && typeof permissionBroker?.request === 'function';
|
|
445
681
|
const input = createInputQueue();
|
|
446
682
|
const abortController = new AbortController();
|
|
447
683
|
let alive = true;
|
|
448
684
|
let sessionId = null;
|
|
449
685
|
let activeTurn = null;
|
|
450
686
|
const inherited = cleanClaudeEnvironment();
|
|
687
|
+
for (const key of ['SYNABUN_PROFILE', 'SYNABUN_RUNTIME_PROFILE_PATH', 'SYNABUN_TOOLS', 'SYNABUN_TOOLS_PATH']) delete inherited[key];
|
|
451
688
|
for (const key of Array.isArray(unsetEnv) ? unsetEnv : []) delete inherited[key];
|
|
689
|
+
// A worker never holds a brain's launch credential (the Assistant's own identity) or an owner's page secret.
|
|
690
|
+
delete inherited.SYNABUN_BRAIN_TOKEN;
|
|
691
|
+
// The worker's shell must not inherit the owner's live-data paths: a Claude worker's MCP is in-process HTTP (it
|
|
692
|
+
// reaches the DB through the Neural Interface, never from this env), so nothing here needs them.
|
|
693
|
+
for (const key of LIVE_DATA_ENV) delete inherited[key];
|
|
452
694
|
const ownEnv = stringEnv({
|
|
453
695
|
...inherited,
|
|
454
696
|
SYNABUN_TERMINAL_SESSION: runId,
|
|
455
697
|
SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
|
|
456
|
-
SYNABUN_PROFILE: 'full',
|
|
457
698
|
SYNABUN_BROWSER_SESSION: browserSessionId,
|
|
458
699
|
SYNABUN_BROWSER_TAB: browserTabId,
|
|
459
700
|
SYNABUN_RUN_MODE: runMode === 'task' ? 'task' : undefined,
|
|
460
701
|
...stringEnv(extraEnv),
|
|
461
702
|
});
|
|
462
|
-
|
|
703
|
+
// Apply after every inherited/child environment overlay: drop ALL GIT_* first, then SynaBun's own safety entries.
|
|
704
|
+
const env = withServeEnv(typeof childEnv === 'function' ? stringEnv(childEnv(ownEnv)) : ownEnv, evalOptions?.env);
|
|
463
705
|
const brokerRequest = async (request) => {
|
|
464
706
|
try {
|
|
465
707
|
const reply = await permissionBroker.request({
|
|
@@ -474,6 +716,7 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
474
716
|
'X-Synabun-Terminal': runId,
|
|
475
717
|
'X-Synabun-Project': cwd ? memoryProject(cwd) : 'global',
|
|
476
718
|
'X-Synabun-Memory-Session': runId,
|
|
719
|
+
'X-Synabun-Tools': runtimeToolsValue(resolveStoredTools(options, { source: options.runMode === 'task' ? 'dispatch' : 'launch', provider: 'claude-code' }).groups),
|
|
477
720
|
'X-Synabun-Browser-Session': browserSessionId,
|
|
478
721
|
'X-Synabun-Browser-Tab': browserTabId,
|
|
479
722
|
// Computer-use workers: the grant unlocks the `computer` MCP group.
|
|
@@ -486,15 +729,35 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
486
729
|
const denyMessage = askMode
|
|
487
730
|
? 'Dispatched workers do not use plan mode.'
|
|
488
731
|
: 'This unattended automation cannot wait for interactive input.';
|
|
732
|
+
// A confined run asks canUseTool for every write (permission mode 'default') on top of its PreToolUse hook, and
|
|
733
|
+
// loads no settings file: no user hooks (they would ask it for memories it has no tool to write), no allow rules.
|
|
734
|
+
const bypass = !askMode && !held;
|
|
735
|
+
// The guard's decision on a tool call: refused (enforce) or noted (warn), always written into the run's events.
|
|
736
|
+
const guardHook = guard ? async (input) => {
|
|
737
|
+
const decision = workerToolDecision(input?.tool_name, input?.tool_input || {}, guard);
|
|
738
|
+
if (!decision) return {};
|
|
739
|
+
onEvent({ provider: 'claude-code', runId, event: confinementEvent(decision, { tool: input?.tool_name, confinement: guard, provider: 'claude-code' }) });
|
|
740
|
+
if (!guard.enforced) return {};
|
|
741
|
+
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: decision.reason } };
|
|
742
|
+
} : null;
|
|
489
743
|
const queryOptions = {
|
|
490
744
|
cwd: cwd || process.cwd(),
|
|
491
|
-
permissionMode:
|
|
492
|
-
...(
|
|
745
|
+
permissionMode: bypass ? 'bypassPermissions' : 'default',
|
|
746
|
+
...(bypass ? { allowDangerouslySkipPermissions: true } : {}),
|
|
493
747
|
includePartialMessages: !!includePartialMessages,
|
|
494
748
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
495
|
-
|
|
749
|
+
// A drafting run and every evaluation run read no settings file at all (an evaluation's skill under test comes as
|
|
750
|
+
// its one plugin, below); everything else the user's, the project's and the local ones.
|
|
751
|
+
settingSources: evalOptions ? evalOptions.settingSources : confined ? [] : ['user', 'project', 'local'],
|
|
496
752
|
hooks: {
|
|
497
|
-
PreToolUse: [
|
|
753
|
+
PreToolUse: [
|
|
754
|
+
// A skill-drafting run: every call, before any permission mode (lib/skills/draft-confinement.js).
|
|
755
|
+
...(confined ? [{ hooks: [draftPreToolUseHook(confined)] }] : []),
|
|
756
|
+
// A skill-evaluation run: every call, the same way (lib/skills/eval-confinement.js).
|
|
757
|
+
...(evaluated ? [{ hooks: [evalPreToolUseHook(evaluated)] }] : []),
|
|
758
|
+
// A guarded worker (capability read-only / workspace): every call, subagents' included (lib/worker-confinement.js).
|
|
759
|
+
...(guardHook ? [{ hooks: [guardHook] }] : []),
|
|
760
|
+
{
|
|
498
761
|
matcher: denyMatcher,
|
|
499
762
|
hooks: [async () => ({
|
|
500
763
|
hookSpecificOutput: {
|
|
@@ -508,7 +771,7 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
508
771
|
// on every call, subagents' included, before the bypass and canUseTool.
|
|
509
772
|
...(runMode === 'task' ? [{
|
|
510
773
|
hooks: [async (input) => {
|
|
511
|
-
const reason = browserToolDenial(input?.tool_name, input?.tool_input || {}, { host: 'claude', cwd: cwd || process.cwd() });
|
|
774
|
+
const reason = browserToolDenial(input?.tool_name, input?.tool_input || {}, { host: 'claude', cwd: cwd || process.cwd(), roots: policyRoots });
|
|
512
775
|
return reason ? { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } } : {};
|
|
513
776
|
}],
|
|
514
777
|
}] : [])],
|
|
@@ -516,6 +779,14 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
516
779
|
env,
|
|
517
780
|
abortController,
|
|
518
781
|
canUseTool: async (toolName, toolInput) => {
|
|
782
|
+
if (confined) {
|
|
783
|
+
const reason = draftToolDecision(toolName, toolInput, confined);
|
|
784
|
+
return reason ? { behavior: 'deny', message: reason } : { behavior: 'allow', updatedInput: toolInput };
|
|
785
|
+
}
|
|
786
|
+
if (evaluated) {
|
|
787
|
+
const reason = evalToolDecision(toolName, toolInput, evaluated);
|
|
788
|
+
return reason ? { behavior: 'deny', message: reason } : { behavior: 'allow', updatedInput: toolInput };
|
|
789
|
+
}
|
|
519
790
|
if (toolName === 'ExitPlanMode') return { behavior: 'deny', message: denyMessage };
|
|
520
791
|
if (toolName === 'AskUserQuestion') {
|
|
521
792
|
if (!askMode) return { behavior: 'deny', message: denyMessage };
|
|
@@ -541,12 +812,105 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
541
812
|
return { behavior: 'allow', updatedInput: toolInput };
|
|
542
813
|
},
|
|
543
814
|
};
|
|
544
|
-
if (
|
|
815
|
+
if (confined) {
|
|
816
|
+
// No MCP server at all (SynaBun's or the user's: strict), and only the file tools.
|
|
817
|
+
queryOptions.mcpServers = {};
|
|
818
|
+
queryOptions.strictMcpConfig = true;
|
|
819
|
+
queryOptions.tools = [...DRAFT_TOOLS];
|
|
820
|
+
queryOptions.disallowedTools = [...DRAFT_DISALLOWED_TOOLS];
|
|
821
|
+
} else if (evalOptions) {
|
|
822
|
+
// No MCP server at all, the role's tools only, and the skills it may use: the one under test (a baseline: none),
|
|
823
|
+
// loaded as the run's one local plugin; no hook of any settings file, plugin or skill and no inline shell.
|
|
824
|
+
queryOptions.mcpServers = {};
|
|
825
|
+
queryOptions.strictMcpConfig = true;
|
|
826
|
+
queryOptions.tools = [...evalOptions.tools];
|
|
827
|
+
queryOptions.disallowedTools = [...evalOptions.disallowedTools];
|
|
828
|
+
queryOptions.skills = [...evalOptions.skills];
|
|
829
|
+
queryOptions.settings = { ...evalOptions.settings };
|
|
830
|
+
if (evalOptions.plugins.length) queryOptions.plugins = evalOptions.plugins.map((plugin) => ({ ...plugin }));
|
|
831
|
+
} else if (mcpUrl) queryOptions.mcpServers = { SynaBun: { type: 'http', url: mcpUrl, headers } };
|
|
832
|
+
let externalScopeNames = [];
|
|
833
|
+
let externalValues = [];
|
|
834
|
+
let privateMcpServers = null;
|
|
835
|
+
let privateMcpDir = null;
|
|
836
|
+
if (!held) {
|
|
837
|
+
const inventory = externalServerInventory({ provider: 'claude-code', cwd, env, home: env.HOME, dataHome: memoryConfig.dataHome, ...options.externalMcpContext });
|
|
838
|
+
const list = nativeToolSelection(options, 'claude-code');
|
|
839
|
+
const scope = selectExternalServers(list.servers, inventory);
|
|
840
|
+
const definitions = Object.fromEntries(scope.names.map(name => [name, inventory.definitionFor(name)]));
|
|
841
|
+
if (mcpUrl) definitions.SynaBun = { type: 'http', url: mcpUrl, headers };
|
|
842
|
+
const referenced = referenceClaudeMap(definitions, env);
|
|
843
|
+
queryOptions.mcpServers = referenced.mcpServers;
|
|
844
|
+
queryOptions.strictMcpConfig = true;
|
|
845
|
+
Object.assign(env, referenced.env);
|
|
846
|
+
// OAuth and dynamic header helpers are not expanded by the CLI's generic ${VAR} pass.
|
|
847
|
+
// Keep them intact in a private file; the SDK's JSON is replaced before the OS spawn boundary.
|
|
848
|
+
privateMcpServers = structuredClone(referenced.mcpServers);
|
|
849
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
850
|
+
if (definition.oauth) privateMcpServers[name].oauth = structuredClone(definition.oauth);
|
|
851
|
+
if (definition.headersHelper) privateMcpServers[name].headersHelper = definition.headersHelper;
|
|
852
|
+
}
|
|
853
|
+
externalValues = [...Object.values(referenced.env), ...mcpDefinitionValues(definitions)];
|
|
854
|
+
externalScopeNames = Object.keys(definitions);
|
|
855
|
+
}
|
|
545
856
|
if (model) queryOptions.model = model;
|
|
546
857
|
if (effort && CLAUDE_EFFORT_LEVELS.includes(effort)) queryOptions.effort = effort;
|
|
547
|
-
|
|
858
|
+
// Read-only: with the guard enforced the shell stays, limited to read-only commands by the hook, and only the
|
|
859
|
+
// built-in writers are off the list; otherwise (warn, off) today's list, Bash included.
|
|
860
|
+
if (capability === 'read-only' && !held) queryOptions.disallowedTools = guard?.enforced ? [...CLAUDE_READ_ONLY_WRITE_TOOLS] : [...CLAUDE_READ_ONLY_DISALLOWED_TOOLS];
|
|
548
861
|
if (Number(maxBudgetUsd) > 0) queryOptions.maxBudgetUsd = Number(maxBudgetUsd);
|
|
549
862
|
if (outputSchema && typeof outputSchema === 'object') queryOptions.outputFormat = { type: 'json_schema', schema: outputSchema };
|
|
863
|
+
// A run resumed after a server restart continues its own Claude session. A tool call the restart cut off is in its
|
|
864
|
+
// transcript without a result: the CLI records it as interrupted (outcome unknown) and the next call goes through.
|
|
865
|
+
if (resumeSessionId) queryOptions.resume = String(resumeSessionId);
|
|
866
|
+
// The CLI's process is spawned here, not inside the SDK, so its pid is known: a server shutdown ends it at once
|
|
867
|
+
// (endProcess), and the next start can tell an orphan from another program. Left to the SDK, a CLI whose server
|
|
868
|
+
// went down only gets its stdin closed: it finishes the tool call in progress (a minute of `sleep` measured) and
|
|
869
|
+
// keeps writing to its transcript, because the SDK's SIGTERM waits 2 s on a timer that dies with the server. The
|
|
870
|
+
// SDK reads no stderr from a process it did not spawn: it is drained here, its tail kept for a failure's message.
|
|
871
|
+
let child = null;
|
|
872
|
+
let stderrTail = '';
|
|
873
|
+
let terminating = false;
|
|
874
|
+
queryOptions.spawnClaudeCodeProcess = (spawnOptions) => {
|
|
875
|
+
if (!alive || abortController.signal.aborted) throw Object.assign(new Error('Claude MCP launch was stopped'), { code: 'MCP_SCOPE_UNENFORCED' });
|
|
876
|
+
const args = [...spawnOptions.args];
|
|
877
|
+
if (privateMcpServers) for (let index = 0; index < args.length - 1; index++) if (args[index] === '--mcp-config') {
|
|
878
|
+
privateMcpDir ||= mkdtempSync(join(tmpdir(), 'synabun-claude-mcp-'));
|
|
879
|
+
args[index + 1] = writePrivateMcpConfig(join(privateMcpDir, 'mcp.json'), privateMcpServers);
|
|
880
|
+
}
|
|
881
|
+
const proc = (options.spawnFactory || spawnProcess)(spawnOptions.command, args, {
|
|
882
|
+
cwd: spawnOptions.cwd, env: spawnOptions.env, signal: spawnOptions.signal,
|
|
883
|
+
stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
884
|
+
});
|
|
885
|
+
child = proc;
|
|
886
|
+
proc.stderr?.setEncoding?.('utf8');
|
|
887
|
+
proc.stderr?.on('data', (chunk) => { stderrTail = redactMcpValues(`${stderrTail}${chunk}`, externalValues).slice(-2048); });
|
|
888
|
+
proc.stderr?.on('error', () => {});
|
|
889
|
+
if (proc.pid) { try { onProcess?.({ pid: proc.pid, startedAt: new Date().toISOString(), command: String(spawnOptions.command || '') }); } catch { /* a note, never the run */ } }
|
|
890
|
+
return proc;
|
|
891
|
+
};
|
|
892
|
+
const processEnded = () => !child || child.exitCode !== null || (child.signalCode !== null && child.signalCode !== undefined);
|
|
893
|
+
/**
|
|
894
|
+
* End the CLI now (a server shutdown): SIGTERM, which makes it stop the tool call in progress (its process tree
|
|
895
|
+
* included), record that call's result and its cost state, and exit within a second; SIGKILL when it has not exited
|
|
896
|
+
* by `graceMs` (a killed CLI leaves its tool's processes behind, so it is the fallback only). Windows has no SIGTERM
|
|
897
|
+
* (a signal stops the CLI alone, and a dev server its tool started keeps its port): the tree is ended with taskkill.
|
|
898
|
+
* The CLI writes no cost state then; the resume reads what the CLI restores either way (lib/worker-resume.js).
|
|
899
|
+
*/
|
|
900
|
+
async function endProcess(graceMs = 1500) {
|
|
901
|
+
const proc = child;
|
|
902
|
+
if (!proc || processEnded()) return;
|
|
903
|
+
const exited = new Promise((resolveExit) => { proc.once('exit', () => resolveExit()); });
|
|
904
|
+
if (process.platform === 'win32') {
|
|
905
|
+
try { spawnProcess('taskkill', ['/pid', String(proc.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }).on?.('error', () => {}); } catch { /* gone */ }
|
|
906
|
+
} else {
|
|
907
|
+
try { proc.kill('SIGTERM'); } catch { /* gone */ }
|
|
908
|
+
}
|
|
909
|
+
await Promise.race([exited, new Promise((resolveWait) => setTimeout(resolveWait, Math.max(100, graceMs)))]);
|
|
910
|
+
if (processEnded()) return;
|
|
911
|
+
try { proc.kill('SIGKILL'); } catch { /* gone */ }
|
|
912
|
+
await Promise.race([exited, new Promise((resolveWait) => setTimeout(resolveWait, 500))]);
|
|
913
|
+
}
|
|
550
914
|
// The loop runs the user's own Claude Code: SynaBun carries none, and the SDK
|
|
551
915
|
// is never left to look for one. The SDK uses pathToClaudeCodeExecutable
|
|
552
916
|
// verbatim with no validation, so the path was vetted where it was resolved
|
|
@@ -565,9 +929,11 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
565
929
|
throw error;
|
|
566
930
|
}
|
|
567
931
|
|
|
568
|
-
|
|
932
|
+
let q;
|
|
933
|
+
try { q = queryFactory({ prompt: input, options: queryOptions }); }
|
|
934
|
+
catch (error) { if (privateMcpDir) rmSync(privateMcpDir, { recursive: true, force: true }); throw error; }
|
|
569
935
|
const deltaBuffer = new Map();
|
|
570
|
-
const forwardEvent = (event) => onEvent({ provider: 'claude-code', runId, event });
|
|
936
|
+
const forwardEvent = (event) => onEvent({ provider: 'claude-code', runId, event: redactMcpValues(event, externalValues) });
|
|
571
937
|
const flushDelta = (key) => {
|
|
572
938
|
const slot = deltaBuffer.get(key);
|
|
573
939
|
if (!slot) return;
|
|
@@ -605,8 +971,9 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
605
971
|
};
|
|
606
972
|
// total_cost_usd is this CLI process's running total, not the turn's charge:
|
|
607
973
|
// a warm task run reported $7.87 after turn 1 and $7.99 after a $0.12 turn 2.
|
|
608
|
-
// A turn resolves with its own charge (costUsd) beside the total.
|
|
609
|
-
|
|
974
|
+
// A turn resolves with its own charge (costUsd) beside the total. A resumed
|
|
975
|
+
// session's CLI starts from the total its transcript recorded (costBaseUsd).
|
|
976
|
+
let reportedCostUsd = Math.max(0, Number(costBaseUsd) || 0);
|
|
610
977
|
const turnCost = (event) => {
|
|
611
978
|
const total = Number(event?.total_cost_usd);
|
|
612
979
|
if (!Number.isFinite(total) || total <= 0) return { costUsd: 0, totalCostUsd: reportedCostUsd };
|
|
@@ -618,7 +985,8 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
618
985
|
let pumpError = null;
|
|
619
986
|
const pump = (async () => {
|
|
620
987
|
try {
|
|
621
|
-
for await (const
|
|
988
|
+
for await (const rawEvent of q) {
|
|
989
|
+
const event = redactMcpValues(rawEvent, externalValues);
|
|
622
990
|
if (!alive) break;
|
|
623
991
|
const eventSessionId = event?.session_id || null;
|
|
624
992
|
if (eventSessionId && eventSessionId !== sessionId) {
|
|
@@ -653,6 +1021,12 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
653
1021
|
}
|
|
654
1022
|
}
|
|
655
1023
|
} catch (error) {
|
|
1024
|
+
// A process that exited is reported without what it said on stderr (the SDK keeps that only for a process it
|
|
1025
|
+
// spawned itself): its tail goes with the message.
|
|
1026
|
+
const message = String(error?.message || '');
|
|
1027
|
+
if (stderrTail.trim() && /Claude Code process (exited|terminated)/.test(message) && !/stderr:/.test(message)) {
|
|
1028
|
+
try { error.message = `${message}. stderr: ${stderrTail.trim().slice(-1000)}`; } catch { /* read-only message */ }
|
|
1029
|
+
}
|
|
656
1030
|
pumpError = error;
|
|
657
1031
|
} finally {
|
|
658
1032
|
flushDeltas();
|
|
@@ -670,7 +1044,8 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
670
1044
|
pump.catch((error) => {
|
|
671
1045
|
// dispose() aborts the query when the run ends, however it ended, and the
|
|
672
1046
|
// SDK throws "Claude Code process aborted by user" for it: not a failure.
|
|
673
|
-
|
|
1047
|
+
// Neither is the exit of a CLI a server shutdown ended (dispose with terminate).
|
|
1048
|
+
if (abortController.signal.aborted || terminating) return;
|
|
674
1049
|
onEvent({
|
|
675
1050
|
provider: 'claude-code',
|
|
676
1051
|
runId,
|
|
@@ -681,8 +1056,14 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
681
1056
|
return {
|
|
682
1057
|
identity: () => ({ providerSessionId: sessionId }),
|
|
683
1058
|
isAlive: () => alive,
|
|
684
|
-
|
|
685
|
-
|
|
1059
|
+
// The CLI's process while it runs ({ pid }), else null.
|
|
1060
|
+
processInfo: () => (child?.pid && !processEnded() ? { pid: child.pid } : null),
|
|
1061
|
+
describe: () => ({ provider: 'claude-code', permissionPolicy, capability, askMode, ...(held ? { confinement: held.kind, root: held.root, ...(evaluated ? { role: evaluated.role, skills: [...evalOptions.skills], plugins: evalOptions.plugins.map((plugin) => plugin.path) } : {}) } : {}), ...(guard ? { guard: { mode: guard.mode, capability: guard.capability, writable: [...guard.writable] } } : {}) }),
|
|
1062
|
+
async runTurn(prompt, meta = {}) {
|
|
1063
|
+
if (!held) {
|
|
1064
|
+
try { await waitForClaudeMcpScope(q, externalScopeNames); }
|
|
1065
|
+
catch { input.close(); q.close?.(); await endProcess(); throw Object.assign(new Error('Claude MCP scope could not be enforced before the first turn'), { code: 'MCP_SCOPE_UNENFORCED' }); }
|
|
1066
|
+
}
|
|
686
1067
|
if (!alive) return Promise.reject(new Error('Claude native loop is closed'));
|
|
687
1068
|
if (activeTurn) return Promise.reject(new Error('Claude native loop already has an active turn'));
|
|
688
1069
|
onEvent({
|
|
@@ -718,16 +1099,24 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
|
|
|
718
1099
|
} catch {}
|
|
719
1100
|
return true;
|
|
720
1101
|
},
|
|
721
|
-
|
|
1102
|
+
// `terminate` (a server shutdown, lib/native-loop-runtime.js): the CLI is ended before the query is closed
|
|
1103
|
+
// (endProcess), so nothing of it outlives the server. The events it sends while it stops (the cut-off tool call's
|
|
1104
|
+
// result) still reach the run; its exit is not reported as a failure.
|
|
1105
|
+
async dispose(options = {}) {
|
|
722
1106
|
if (activeTurn) {
|
|
723
1107
|
const turn = activeTurn; activeTurn = null;
|
|
724
1108
|
turn.reject(asAbortError('disposed'));
|
|
725
1109
|
}
|
|
1110
|
+
if (options?.terminate === true) {
|
|
1111
|
+
terminating = true;
|
|
1112
|
+
await endProcess(Number(options.graceMs) > 0 ? Number(options.graceMs) : 1500);
|
|
1113
|
+
}
|
|
726
1114
|
alive = false;
|
|
727
1115
|
input.close();
|
|
728
1116
|
flushDeltas();
|
|
729
1117
|
try { abortController.abort(asAbortError('disposed')); } catch {}
|
|
730
1118
|
await Promise.race([pump, new Promise((resolveWait) => setTimeout(resolveWait, 1000))]).catch(() => {});
|
|
1119
|
+
if (privateMcpDir) { rmSync(privateMcpDir, { recursive: true, force: true }); privateMcpDir = null; }
|
|
731
1120
|
},
|
|
732
1121
|
};
|
|
733
1122
|
}
|
|
@@ -743,23 +1132,101 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
743
1132
|
agent = null, effort = null, runMode = 'loop',
|
|
744
1133
|
// Image / video creation runs: 'image' | 'video' → each turn returns the files its model generated.
|
|
745
1134
|
collectMedia = null,
|
|
1135
|
+
// A skill-drafting run's confinement (lib/skills/draft-confinement.js openCodeDraftConfig): the serve's config has
|
|
1136
|
+
// no shell, web, subagent or skill tool and no MCP server; every edit is asked and answered inside the folder.
|
|
1137
|
+
confinement = null,
|
|
1138
|
+
// The Protect workers setting as the run starts (lib/worker-confinement.js), the run's own scratch folder,
|
|
1139
|
+
// SynaBun's own port, the run's own serve port and the band SynaBun keeps for its serves (a guarded worker's shell
|
|
1140
|
+
// may call none of them).
|
|
1141
|
+
guardMode = WORKER_GUARD_DEFAULT, scratchDir = null, serverPort = null, localPortRange = null, servePort = null,
|
|
1142
|
+
onNote = null,
|
|
746
1143
|
} = options;
|
|
747
1144
|
if (!client?.session?.create || (!client?.session?.promptAsync && !client?.session?.prompt)) {
|
|
748
1145
|
throw new Error('OpenCode native loop requires an isolated SDK client');
|
|
749
1146
|
}
|
|
750
1147
|
const permissionPolicy = normalizePermissionPolicy(rawPolicy);
|
|
751
1148
|
const capability = normalizeCapability(rawCapability);
|
|
752
|
-
|
|
1149
|
+
// Resolved before anything starts: a confinement that cannot be set up stops the run (never an unconfined start).
|
|
1150
|
+
let confined = null;
|
|
1151
|
+
let evaluated = null;
|
|
1152
|
+
if (confinement?.kind === EVAL_CONFINEMENT) {
|
|
1153
|
+
try { evaluated = resolveEvalConfinement(confinement, cwd); } catch (error) { throw confinementUnsupportedError('OpenCode', error?.message || error, 'an evaluation folder'); }
|
|
1154
|
+
} else if (confinement) {
|
|
1155
|
+
try { confined = resolveDraftConfinement(confinement, cwd); } catch (error) { throw confinementUnsupportedError('OpenCode', error?.message || error); }
|
|
1156
|
+
}
|
|
1157
|
+
const held = confined || evaluated;
|
|
1158
|
+
const guard = workerGuardFor({ capability, cwd, guardMode, scratchDir, confinement: held, serverPort, localPorts: servePort ? [servePort] : null, localPortRange });
|
|
1159
|
+
const askMode = !held && permissionPolicy === 'ask' && typeof permissionBroker?.request === 'function';
|
|
1160
|
+
// Code Mode (lib/worker-confinement.js OPENCODE_CODE_MODE_TOOL): off under Enforce, noted call by call under Warn.
|
|
1161
|
+
if (guard) { try { onNote?.(openCodeCodeModeNote(guard)); } catch {} }
|
|
1162
|
+
const codeModeCalls = new Set();
|
|
1163
|
+
const noteCodeModeCall = (part) => {
|
|
1164
|
+
if (!guard || part?.type !== 'tool') return;
|
|
1165
|
+
const decision = openCodeCodeModeDecision(part.tool, guard);
|
|
1166
|
+
const callID = part.callID || part.id || '';
|
|
1167
|
+
if (!decision || codeModeCalls.has(callID)) return;
|
|
1168
|
+
codeModeCalls.add(callID);
|
|
1169
|
+
onEvent({ provider: 'opencode', runId, eventType: 'synabun.confinement', event: confinementEvent(decision, { tool: part.tool, confinement: guard, provider: 'opencode' }) });
|
|
1170
|
+
};
|
|
1171
|
+
// An external_directory ask identifies its originating call, not whether it reads or writes. Resolve that exact
|
|
1172
|
+
// message/call pair from SSE or the message API; never infer a read from the requested path or arbitrary metadata.
|
|
1173
|
+
const permissionTools = new Map();
|
|
1174
|
+
const rememberPermissionTool = (part) => {
|
|
1175
|
+
if (part?.type === 'tool' && part.messageID && part.callID && part.tool) permissionTools.set(`${part.messageID}:${part.callID}`, part.tool);
|
|
1176
|
+
};
|
|
1177
|
+
const permissionToolName = async (event) => {
|
|
1178
|
+
const { messageID, callID } = event?.tool || {};
|
|
1179
|
+
if (!messageID || !callID) return null;
|
|
1180
|
+
const key = `${messageID}:${callID}`;
|
|
1181
|
+
if (!permissionTools.has(key) && typeof client.session?.message === 'function') {
|
|
1182
|
+
try {
|
|
1183
|
+
const row = await settleWithin(client.session.message({ sessionID: sessionId, messageID, directory: cwd || undefined }), 5000);
|
|
1184
|
+
for (const part of row?.data?.parts || []) if (part.messageID === messageID) rememberPermissionTool(part);
|
|
1185
|
+
} catch { /* unknown calls fail closed */ }
|
|
1186
|
+
}
|
|
1187
|
+
return permissionTools.get(key) || null;
|
|
1188
|
+
};
|
|
1189
|
+
/**
|
|
1190
|
+
* A permission ask under the run's confinement: { reply: 'once' | 'reject', message }, the decision written into
|
|
1191
|
+
* the run's events. A refusal carries its reason as the reply's message: OpenCode hands it to the model as the
|
|
1192
|
+
* tool's error (a reject without a message is what OpenCode's own client follows with an interrupt).
|
|
1193
|
+
*/
|
|
1194
|
+
const policyReply = async (event) => {
|
|
1195
|
+
if (evaluated) {
|
|
1196
|
+
const reason = evalOpenCodeDecision(event, evaluated);
|
|
1197
|
+
if (!reason) return { reply: 'once' };
|
|
1198
|
+
onEvent({ provider: 'opencode', runId, eventType: 'synabun.confinement', event: { type: 'synabun.confinement', action: 'refused', capability: 'workspace', tool: event.permission || event.type || 'permission', rule: 'skill-eval', reason, provider: 'opencode' } });
|
|
1199
|
+
return { reply: 'reject', message: reason };
|
|
1200
|
+
}
|
|
1201
|
+
if (confined) {
|
|
1202
|
+
const reason = draftOpenCodeDecision(event, confined);
|
|
1203
|
+
if (!reason) return { reply: 'once' };
|
|
1204
|
+
onEvent({ provider: 'opencode', runId, eventType: 'synabun.confinement', event: { type: 'synabun.confinement', action: 'refused', capability: 'workspace', tool: event.permission || event.type || 'permission', rule: 'skill-draft', reason, provider: 'opencode' } });
|
|
1205
|
+
return { reply: 'reject', message: reason };
|
|
1206
|
+
}
|
|
1207
|
+
if (guard) {
|
|
1208
|
+
const toolName = String(event?.permission || event?.type || '').toLowerCase() === 'external_directory' ? await permissionToolName(event) : null;
|
|
1209
|
+
const decision = openCodePermissionDecision(event, guard, { toolName });
|
|
1210
|
+
if (!decision) return { reply: 'once' };
|
|
1211
|
+
onEvent({ provider: 'opencode', runId, eventType: 'synabun.confinement', event: confinementEvent(decision, { tool: event.permission || event.type || 'permission', confinement: guard, provider: 'opencode' }) });
|
|
1212
|
+
return guard.enforced ? { reply: 'reject', message: decision.reason } : { reply: 'once' };
|
|
1213
|
+
}
|
|
1214
|
+
// Unattended and unguarded: allowed, one call at a time. Never "always": OpenCode 2 saves an "always" answer for
|
|
1215
|
+
// the whole project (GET /api/permission/saved), where it would stop the user's own sessions there from asking.
|
|
1216
|
+
return { reply: 'once' };
|
|
1217
|
+
};
|
|
753
1218
|
let alive = true;
|
|
754
1219
|
let activeAbort = null;
|
|
755
1220
|
let activeTurn = null;
|
|
756
1221
|
const eventSource = client.onEvent ? client : client.event;
|
|
757
1222
|
let sessionId = null;
|
|
758
1223
|
// Assistant text assembled from the SSE stream (message.updated gives roles,
|
|
759
|
-
// message.part.updated gives text parts keyed by message id
|
|
1224
|
+
// message.part.updated gives text parts keyed by message id, message.part.delta
|
|
1225
|
+
// the text a part grows by between them).
|
|
760
1226
|
const messageRoles = new Map();
|
|
761
1227
|
const messageOrder = [];
|
|
762
1228
|
const textParts = new Map(); // messageID → Map(partID → text)
|
|
1229
|
+
const textPartMessages = new Map(); // partID of a text part → its messageID (where a delta goes)
|
|
763
1230
|
const rememberMessage = (info) => {
|
|
764
1231
|
if (!info?.id) return;
|
|
765
1232
|
if (!messageRoles.has(info.id)) messageOrder.push(info.id);
|
|
@@ -780,8 +1247,16 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
780
1247
|
if (!messageRoles.has(part.messageID)) rememberMessage({ id: part.messageID, role: null });
|
|
781
1248
|
let parts = textParts.get(part.messageID);
|
|
782
1249
|
if (!parts) { parts = new Map(); textParts.set(part.messageID, parts); }
|
|
1250
|
+
if (part.id) textPartMessages.set(part.id, part.messageID);
|
|
783
1251
|
parts.set(part.id || `${parts.size}`, part.text);
|
|
784
1252
|
};
|
|
1253
|
+
// A delta of an announced text part extends it (OpenCode 2 streams text only this way until the part ends).
|
|
1254
|
+
const rememberDelta = (event) => {
|
|
1255
|
+
if (!event?.partID || (event.field && event.field !== 'text') || typeof event.delta !== 'string' || !event.delta) return;
|
|
1256
|
+
const messageID = textPartMessages.get(event.partID);
|
|
1257
|
+
if (!messageID || (event.messageID && event.messageID !== messageID)) return;
|
|
1258
|
+
rememberPart({ id: event.partID, messageID, type: 'text', text: `${textParts.get(messageID)?.get(event.partID) || ''}${event.delta}` });
|
|
1259
|
+
};
|
|
785
1260
|
/** { media } with the generated files not returned yet (none: {}). */
|
|
786
1261
|
const turnMedia = (extraParts = []) => {
|
|
787
1262
|
for (const part of extraParts) rememberPart(part);
|
|
@@ -835,7 +1310,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
835
1310
|
};
|
|
836
1311
|
const finishTurnWithText = () => {
|
|
837
1312
|
const turn = activeTurn;
|
|
838
|
-
if (!turn || turn.mode !== 'async' || turn.settled || turn.finishing) return;
|
|
1313
|
+
if (!turn || turn.mode !== 'async' || turn.sent === false || turn.settled || turn.finishing) return;
|
|
839
1314
|
turn.finishing = true;
|
|
840
1315
|
fetchAssistantText()
|
|
841
1316
|
.then((text) => settleActiveTurn(null, { text, result: { text }, ...turnMedia() }))
|
|
@@ -857,7 +1332,8 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
857
1332
|
return;
|
|
858
1333
|
}
|
|
859
1334
|
if (/^message[.:]updated$/i.test(eventType)) rememberMessage(event.info || event.message?.info || null);
|
|
860
|
-
if (/^message[.:]part[.:]updated$/i.test(eventType)) rememberPart(event.part || null);
|
|
1335
|
+
if (/^message[.:]part[.:]updated$/i.test(eventType)) { rememberPart(event.part || null); rememberPermissionTool(event.part); noteCodeModeCall(event.part); }
|
|
1336
|
+
if (/^message[.:]part[.:]delta$/i.test(eventType)) rememberDelta(event);
|
|
861
1337
|
if (/question\.asked/i.test(eventType)) {
|
|
862
1338
|
const requestID = event.id || event.requestID;
|
|
863
1339
|
if (requestID && askMode && client.question?.reply) {
|
|
@@ -883,7 +1359,11 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
883
1359
|
})
|
|
884
1360
|
.catch(() => {});
|
|
885
1361
|
} else if (requestID && client.permission?.reply) {
|
|
886
|
-
|
|
1362
|
+
// Unattended: by the run's confinement (a drafting run, a guarded worker), else allowed once.
|
|
1363
|
+
policyReply(event).then((answer) => {
|
|
1364
|
+
if (!alive) return;
|
|
1365
|
+
return client.permission.reply({ requestID, reply: answer.reply, ...(answer.message ? { message: answer.message } : {}), directory: cwd || undefined });
|
|
1366
|
+
}).catch(() => {});
|
|
887
1367
|
}
|
|
888
1368
|
}
|
|
889
1369
|
// promptAsync acknowledges immediately, so the durable SSE stream is the
|
|
@@ -901,11 +1381,23 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
901
1381
|
}
|
|
902
1382
|
onEvent({ provider: 'opencode', runId, eventType, event });
|
|
903
1383
|
}) || (() => {});
|
|
1384
|
+
// The rules of a confined run ride the session too (lib/opencode2-client.js session.create sends them as the
|
|
1385
|
+
// session's own `permissions`), on top of the serve's config.
|
|
1386
|
+
const sessionRules = confined ? openCodeDraftConfig().permissions : evaluated ? openCodeEvalConfig(evaluated).permissions : guard ? openCodePermissionsFor(guard) : null;
|
|
904
1387
|
try {
|
|
905
1388
|
await client.waitUntilConnected?.(10_000);
|
|
906
|
-
const created =
|
|
1389
|
+
const created = options.resumeSessionId
|
|
1390
|
+
? await client.session.get({ sessionID: options.resumeSessionId })
|
|
1391
|
+
: await client.session.create({ directory: cwd || undefined, title: title || 'SynaBun automation', ...(sessionRules ? { permission: sessionRules } : {}) });
|
|
1392
|
+
if (created?.error) throw new Error(`OpenCode session unavailable: ${providerErrorMessage(created.error)}`);
|
|
907
1393
|
sessionId = created?.data?.id || created?.data?.sessionID || created?.id || created?.sessionID;
|
|
908
1394
|
if (!sessionId) throw new Error('OpenCode did not return a session id');
|
|
1395
|
+
if (options.resumeSessionId && sessionId !== options.resumeSessionId) throw new Error('OpenCode returned a different session on resume');
|
|
1396
|
+
if (options.resumeSessionId && sessionRules) {
|
|
1397
|
+
if (!client.session.update) throw new Error('OpenCode cannot restore the worker permission rules');
|
|
1398
|
+
const updated = await client.session.update({ sessionID: sessionId, permission: sessionRules });
|
|
1399
|
+
if (!updated || updated.error) throw new Error('OpenCode could not restore the worker permission rules');
|
|
1400
|
+
}
|
|
909
1401
|
onIdentity({ providerSessionId: sessionId });
|
|
910
1402
|
} catch (error) {
|
|
911
1403
|
try { unsubscribe(); } catch {}
|
|
@@ -916,9 +1408,13 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
916
1408
|
// What the serve stored, for the usage meter's reconcile at a turn's end (assistant-usage.js).
|
|
917
1409
|
// A call that fails, times out or is missing on an older serve throws: the caller then knows
|
|
918
1410
|
// the turn was not reconciled.
|
|
1411
|
+
// The clients SynaBun builds (lib/opencode-v2-client.js, either major) keep `session` as it was and carry
|
|
1412
|
+
// `children` under `extra.session`.
|
|
919
1413
|
const storedRows = async (method, id) => {
|
|
920
|
-
|
|
921
|
-
|
|
1414
|
+
const group = typeof client.session?.[method] === 'function' ? client.session
|
|
1415
|
+
: typeof client.extra?.session?.[method] === 'function' ? client.extra.session : null;
|
|
1416
|
+
if (!group) throw new Error(`OpenCode session.${method} is not available`);
|
|
1417
|
+
const response = await settleWithin(group[method]({ sessionID: id, directory: cwd || undefined }), 5000);
|
|
922
1418
|
if (!response || response.error) throw new Error(providerErrorMessage(response?.error, `OpenCode session.${method} did not answer`));
|
|
923
1419
|
const rows = Array.isArray(response.data) ? response.data : Array.isArray(response) ? response : null;
|
|
924
1420
|
if (!rows) throw new Error(`OpenCode session.${method} returned no list`);
|
|
@@ -928,7 +1424,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
928
1424
|
return {
|
|
929
1425
|
identity: () => ({ providerSessionId: sessionId }),
|
|
930
1426
|
isAlive: () => alive,
|
|
931
|
-
describe: () => ({ provider: 'opencode', permissionPolicy, capability, askMode }),
|
|
1427
|
+
describe: () => ({ provider: 'opencode', permissionPolicy, capability, askMode, ...(held ? { confinement: held.kind, root: held.root, ...(evaluated ? { role: evaluated.role } : {}) } : {}), ...(guard ? { guard: { mode: guard.mode, capability: guard.capability, writable: [...guard.writable] } } : {}) }),
|
|
932
1428
|
/** { children(sessionId) → sessions, messages(sessionId) → [{ info }] } on this run's own serve. */
|
|
933
1429
|
usageFetchers: () => ({
|
|
934
1430
|
children: (id) => storedRows('children', id),
|
|
@@ -937,13 +1433,29 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
937
1433
|
async runTurn(prompt, meta = {}) {
|
|
938
1434
|
if (!alive) throw new Error('OpenCode native loop is closed');
|
|
939
1435
|
if (activeTurn) throw new Error('OpenCode native loop already has an active turn');
|
|
940
|
-
|
|
941
|
-
activeAbort =
|
|
1436
|
+
const controller = new AbortController();
|
|
1437
|
+
activeAbort = controller;
|
|
1438
|
+
const checkReady = async () => {
|
|
1439
|
+
await client.waitUntilConnected?.(10_000);
|
|
1440
|
+
if (!alive || controller.signal.aborted) throw asAbortError('aborted');
|
|
1441
|
+
const ready = await waitForSynabunTools(client, {
|
|
1442
|
+
...(options.mcpWait || {}), directory: cwd || undefined,
|
|
1443
|
+
expected: options.synabunExpected !== false && !held, signal: controller.signal,
|
|
1444
|
+
stopped: () => !alive,
|
|
1445
|
+
onTimeout: (ms) => {
|
|
1446
|
+
const text = synabunToolsTimeoutText(ms, 'worker');
|
|
1447
|
+
onNote?.(text);
|
|
1448
|
+
onEvent({ provider: 'opencode', runId, eventType: 'synabun.mcp.status', event: { sessionID: sessionId, text } });
|
|
1449
|
+
},
|
|
1450
|
+
});
|
|
1451
|
+
if (!ready || controller.signal.aborted || !alive) throw asAbortError('aborted');
|
|
1452
|
+
};
|
|
942
1453
|
const request = {
|
|
943
1454
|
sessionID: sessionId,
|
|
944
1455
|
parts: [{ type: 'text', text: prompt }],
|
|
945
1456
|
model: normalizeOpenCodeModel(model),
|
|
946
|
-
|
|
1457
|
+
// Capability is enforced by session rules, the adapter and sandbox, not OpenCode's planning workflow.
|
|
1458
|
+
agent: agent ? String(agent) : 'build',
|
|
947
1459
|
directory: cwd || undefined,
|
|
948
1460
|
signal: activeAbort.signal,
|
|
949
1461
|
};
|
|
@@ -979,11 +1491,13 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
979
1491
|
// reported as an unhandled promise before runTurn begins awaiting it.
|
|
980
1492
|
completion.catch(() => {});
|
|
981
1493
|
const turn = {
|
|
982
|
-
mode: 'async', completion,
|
|
1494
|
+
mode: 'async', completion, sent: false,
|
|
983
1495
|
resolve: resolveTurn, reject: rejectTurn, settled: false,
|
|
984
1496
|
};
|
|
985
1497
|
activeTurn = turn;
|
|
986
1498
|
try {
|
|
1499
|
+
await checkReady();
|
|
1500
|
+
turn.sent = true;
|
|
987
1501
|
const response = await client.session.promptAsync(request);
|
|
988
1502
|
validateResponse(response);
|
|
989
1503
|
return await completion;
|
|
@@ -996,6 +1510,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
|
|
|
996
1510
|
const turn = { mode: 'sync' };
|
|
997
1511
|
activeTurn = turn;
|
|
998
1512
|
try {
|
|
1513
|
+
await checkReady();
|
|
999
1514
|
const response = await client.session.prompt(request);
|
|
1000
1515
|
validateResponse(response);
|
|
1001
1516
|
const text = extractOpenCodeText(response);
|