synabun 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fetchToolCatalog } from './agent-runtime-options.js';
|
|
2
|
+
import { configureToolPickerTranslation, toolPickerHTML, readToolPicker, toolWord } from './tool-list-picker.js';
|
|
1
3
|
import { mountMemoryMaintenance } from './ui-memory-maintenance.js';
|
|
2
4
|
import { buildJudgmentsTab, wireJudgmentsTab } from './ui-judgments.js';
|
|
3
5
|
import { buildWhatsAppTab, wireWhatsAppTab } from './ui-whatsapp.js';
|
|
@@ -6,14 +8,19 @@ import { buildAboutTab, wireAboutTab } from './settings/settings-about-pane.js';
|
|
|
6
8
|
import { buildAppTab, wireAppTab } from './settings/settings-app-pane.js';
|
|
7
9
|
import { SETTINGS_GROUPS, SETTINGS_PAGES, DEFAULT_SETTINGS_PAGE, pageKeys, getSettingsPage, resolveSettingsTarget, homeOfControl } from './settings/settings-ia.js';
|
|
8
10
|
import * as kit from './settings/settings-kit.js';
|
|
11
|
+
import { skillFoldersHTML, wireSkillFolders, refreshSkillChips, chipState, installedToast } from './settings/settings-skill-folders-pane.js';
|
|
9
12
|
import { RULES_HOST_LABELS, describeRulesHost, describeRulesInstall, describeRulesInstallAll } from './settings/settings-rules.js';
|
|
10
13
|
import { readUserContextResponse, describeUserContext, describeUserContextSaveFailure } from './settings/settings-user-context.js';
|
|
14
|
+
import { readWorkerGuardResponse, describeWorkerGuard, describeWorkerGuardSaveFailure } from './settings/settings-worker-guard.js';
|
|
15
|
+
import { CLAUDE_HOOK_ROWS, readClaudeHooksResponse, describeClaudeHooks, describeClaudeHooksSaveFailure } from './settings/settings-claude-hooks.js';
|
|
11
16
|
import { readGreetingStatus, describeGreeting, describeGreetingSaveFailure } from './settings/settings-greeting.js';
|
|
17
|
+
import { describeOpencodeInstall } from './settings/settings-opencode.js';
|
|
12
18
|
import { renderGreeting, clearGreeting } from './greeting/greeting-card.js';
|
|
13
19
|
import { createTunnelCard, drawInto, mcpKeyNeededText, readTunnel } from './ui-tunnel.js';
|
|
14
20
|
import { remoteMcpAddress } from './tunnel-view.js';
|
|
15
21
|
import { recheckCliStatus, getCliStatus, addCliToPath } from './cli-status.js';
|
|
16
22
|
import { cliInstallText, setCliInstallTranslator } from './cli-install-view.js';
|
|
23
|
+
import { dropdownHTML, wireDropdowns } from './cc-dropdown.js';
|
|
17
24
|
setCliInstallTranslator((key, params) => kit.tx(`cliInstall.${key}`, params));
|
|
18
25
|
import { setGitBashTranslator } from './git-bash-view.js';
|
|
19
26
|
import { gitBashRowHtml, wireGitBashRow } from './ui-git-bash.js';
|
|
@@ -37,6 +44,7 @@ import { state, emit, on } from './state.js';
|
|
|
37
44
|
import { getSettingsTabs } from './registry.js';
|
|
38
45
|
import { escapeHtml } from './utils.js';
|
|
39
46
|
import { storage } from './storage.js';
|
|
47
|
+
import { normalizeViewConfig } from './view-variant.js';
|
|
40
48
|
import { KEYS } from './constants.js';
|
|
41
49
|
import { registerAction } from './ui-keybinds.js';
|
|
42
50
|
import { CLI_PROFILES, ensureModelsForProfile, getModelsForProfile } from './agent-runtime-options.js';
|
|
@@ -47,6 +55,7 @@ import { getNotifSettings, playTestSound, sendTestBanner, SOUND_PRESETS } from '
|
|
|
47
55
|
import {
|
|
48
56
|
fetchRulesStatus, fetchRulesTextWithFallback, installRules, removeRules, installAllRules, setRulesAutoUpdate, removeLegacyRules,
|
|
49
57
|
} from './api.js';
|
|
58
|
+
import { centeredInFrame } from './app-frame.js';
|
|
50
59
|
|
|
51
60
|
// ── SVG icon constants ──
|
|
52
61
|
|
|
@@ -113,6 +122,7 @@ function showCCToast(msg, duration = 3500) {
|
|
|
113
122
|
// INTERFACE CUSTOMIZATION — config, presets, apply
|
|
114
123
|
// ═══════════════════════════════════════════
|
|
115
124
|
|
|
125
|
+
// Optional legacy view preferences must never reintroduce a 2D selection.
|
|
116
126
|
const IFACE_DEFAULTS = {
|
|
117
127
|
visualizationEnabled: true,
|
|
118
128
|
scale: 1,
|
|
@@ -161,7 +171,7 @@ const IFACE_SLIDERS = {
|
|
|
161
171
|
export function loadIfaceConfig() {
|
|
162
172
|
try {
|
|
163
173
|
const raw = storage.getItem('neural-interface-config');
|
|
164
|
-
if (raw) return { ...IFACE_DEFAULTS, ...JSON.parse(raw) };
|
|
174
|
+
if (raw) return { ...IFACE_DEFAULTS, ...normalizeViewConfig(JSON.parse(raw)) };
|
|
165
175
|
// Migrate legacy ui-scale if present
|
|
166
176
|
const legacyScale = storage.getItem('neural-ui-scale');
|
|
167
177
|
if (legacyScale) {
|
|
@@ -174,7 +184,7 @@ export function loadIfaceConfig() {
|
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
export function saveIfaceConfig(cfg) {
|
|
177
|
-
storage.setItem('neural-interface-config', JSON.stringify(cfg));
|
|
187
|
+
storage.setItem('neural-interface-config', JSON.stringify(normalizeViewConfig(cfg)));
|
|
178
188
|
// Keep legacy key in sync for backwards compat
|
|
179
189
|
storage.setItem('neural-ui-scale', cfg.scale);
|
|
180
190
|
}
|
|
@@ -551,7 +561,7 @@ function buildSearchHTML() {
|
|
|
551
561
|
}
|
|
552
562
|
|
|
553
563
|
// What search reads from the panel beside its index (settings-search.js): the labelled rows of the panes that draw
|
|
554
|
-
// themselves, and the names of what the panes load (projects, providers, tools, tool
|
|
564
|
+
// themselves, and the names of what the panes load (projects, providers, tools, tool lists and servers, skills,
|
|
555
565
|
// browser profiles, themes, the linked WhatsApp account). Labels and names only: a source names label elements and
|
|
556
566
|
// the shell reads their text, never a value; `help` is given only where it is the app's own copy. `named` rows may
|
|
557
567
|
// sit inside an inventory control (a list it tags).
|
|
@@ -957,7 +967,7 @@ function buildSetupTab(setupStatus) {
|
|
|
957
967
|
{ id: 'claude', name: 'Claude', icon: ANTHROPIC_ICON, file: '~/.claude.json', ids: { toggle: 'SET004', cli: 'SET005', primary: 'SET006', remove: 'SET007', view: 'SET008', copy: 'SET009' } },
|
|
958
968
|
{ id: 'gemini', name: 'Gemini', icon: GEMINI_ICON, file: '~/.gemini/settings.json', ids: { toggle: 'SET010', config: 'SET011', primary: 'SET012', remove: 'SET013', view: 'SET014', copy: 'SET015' } },
|
|
959
969
|
{ id: 'codex', name: 'Codex', icon: OPENAI_ICON, file: '~/.codex/config.toml', ids: { toggle: 'SET016', cli: 'SET017', config: 'SET018', primary: 'SET019', remove: 'SET020', view: 'SET021', copy: 'SET022' } },
|
|
960
|
-
{ id: 'opencode', name: 'OpenCode', icon: OPENCODE_ICON, file: '~/.config/opencode/
|
|
970
|
+
{ id: 'opencode', name: 'OpenCode', icon: OPENCODE_ICON, file: '~/.config/opencode/opencode.json', ids: { toggle: 'SET023', config: 'SET024', primary: 'SET025', remove: 'SET026', view: 'SET027', copy: 'SET028' } },
|
|
961
971
|
].map((p) => ({ ...p, on: setupStatus[p.id]?.connected || false }));
|
|
962
972
|
|
|
963
973
|
const copyLabel = (invId) => `${COPY_ICON} ${kit.L(invId)}`;
|
|
@@ -1175,23 +1185,23 @@ function buildMcpTab() {
|
|
|
1175
1185
|
return `
|
|
1176
1186
|
<div class="stg-pane" data-stg-pane="mcp">
|
|
1177
1187
|
<div class="stg-mcp-page-tabs" role="tablist" aria-label="${kit.L('MCP001')}" ${kit.inv('MCP001')}>
|
|
1178
|
-
${tab('stg-mcp-page-tab', 'data-pane', '
|
|
1188
|
+
${tab('stg-mcp-page-tab', 'data-pane', 'tools', m('tab.tools'), true)}
|
|
1179
1189
|
${tab('stg-mcp-page-tab', 'data-pane', 'installed', m('tab.installed'), false)}
|
|
1180
1190
|
</div>
|
|
1181
1191
|
|
|
1182
|
-
<div class="stg-mcp-pane" data-pane="
|
|
1192
|
+
<div class="stg-mcp-pane" data-pane="tools" data-stg-reveal='.stg-mcp-page-tab[data-pane="tools"]'>
|
|
1183
1193
|
${kit.card({
|
|
1184
|
-
id: 'stg-sec-
|
|
1194
|
+
id: 'stg-sec-default-tools', section: 'connected_tools.default_tools',
|
|
1185
1195
|
body: `
|
|
1186
1196
|
<div class="stg-mcp-intro">
|
|
1187
|
-
<div class="stg-help" style="margin-top:0">${m('
|
|
1197
|
+
<div class="stg-help" style="margin-top:0">${m('defaultToolsHelp')}</div>
|
|
1188
1198
|
<div class="stg-mcp-note">
|
|
1189
1199
|
<strong>${m('noteTitle')}</strong>
|
|
1190
1200
|
<span>${m('noteBody')}</span>
|
|
1191
1201
|
</div>
|
|
1192
1202
|
</div>
|
|
1193
|
-
<div id="stg-mcp-
|
|
1194
|
-
<div class="stg-mcp-
|
|
1203
|
+
<div id="stg-mcp-default-tools" class="stg-mcp-default-tools stg-mcp-block" ${kit.inv('MCP022')} role="group" aria-label="${kit.L('MCP022')}">
|
|
1204
|
+
<div class="stg-mcp-empty-note">${m('loading')}</div>
|
|
1195
1205
|
</div>
|
|
1196
1206
|
<div id="stg-mcp-always-on" class="stg-mcp-subsection" ${kit.inv('MCP024')}></div>
|
|
1197
1207
|
<div class="stg-mcp-subsection stg-mcp-servers-section">
|
|
@@ -1303,6 +1313,7 @@ function buildOpencodeTab() {
|
|
|
1303
1313
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>
|
|
1304
1314
|
</button>
|
|
1305
1315
|
</span>`, { forId: 'stg-ocp-port', className: 'ocp-server-bar' })}
|
|
1316
|
+
<div class="stg-help ocp-install-note" id="stg-ocp-install" role="status" aria-live="polite" hidden></div>
|
|
1306
1317
|
</div>
|
|
1307
1318
|
|
|
1308
1319
|
<div class="ocp-group-head">
|
|
@@ -1328,9 +1339,10 @@ function buildOpencodeTab() {
|
|
|
1328
1339
|
<div class="ocp-group">
|
|
1329
1340
|
${kit.advanced(`
|
|
1330
1341
|
<div class="ocp-config-footer" ${kit.inv('OCP019')}>
|
|
1331
|
-
<span class="ocp-config-path">${kit.th('settings.redesign.opencode.configPath', { path: '<code class="stg-code">~/.config/opencode/
|
|
1342
|
+
<span class="ocp-config-path">${kit.th('settings.redesign.opencode.configPath', { path: '<code class="stg-code" id="stg-ocp-config-path">~/.config/opencode/opencode.json</code>' })}</span>
|
|
1332
1343
|
<button type="button" class="ocp-config-link" id="stg-ocp-open-config" aria-describedby="stg-ocp-open-config-help">${kit.L('OCP019')}</button>
|
|
1333
1344
|
</div>
|
|
1345
|
+
<p class="stg-help" id="stg-ocp-config-jsonc" hidden></p>
|
|
1334
1346
|
${kit.help('OCP019', { forId: 'stg-ocp-open-config' })}`)}
|
|
1335
1347
|
</div>`,
|
|
1336
1348
|
})}
|
|
@@ -1341,8 +1353,7 @@ function buildOpencodeTab() {
|
|
|
1341
1353
|
body: `
|
|
1342
1354
|
<div class="ocp-compact-group ocp-rows">
|
|
1343
1355
|
${check('OCP013', 'stg-ocp-compact-auto')}
|
|
1344
|
-
${
|
|
1345
|
-
${kit.field('OCP015', `<input type="number" class="ocp-reserved-input" id="stg-ocp-compact-reserved" value="10000" min="1000" max="50000" step="1000" aria-describedby="stg-ocp-compact-reserved-help">`, { forId: 'stg-ocp-compact-reserved' })}
|
|
1356
|
+
${kit.field('OCP015', `<input type="number" class="ocp-reserved-input" id="stg-ocp-compact-reserved" value="" placeholder="${kit.te('settings.redesign.opencode.bufferDefault')}" min="0" max="1000000" step="1000" aria-describedby="stg-ocp-compact-reserved-help">`, { forId: 'stg-ocp-compact-reserved' })}
|
|
1346
1357
|
${kit.toggleField('OCP016', `<button type="button" class="cc-toggle on" id="stg-ocp-snapshot-toggle" aria-labelledby="stg-ocp-snapshot-toggle-label" aria-describedby="stg-ocp-snapshot-toggle-help"></button>`, { forId: 'stg-ocp-snapshot-toggle', className: 'ocp-snapshot-row' })}
|
|
1347
1358
|
</div>
|
|
1348
1359
|
<p class="ocp-footnote">${kit.te('settings.redesign.opencode.snapshotDetail')}</p>`,
|
|
@@ -1668,14 +1679,19 @@ function buildSkillsTab(ccSkills) {
|
|
|
1668
1679
|
const row = (skill) => {
|
|
1669
1680
|
const installed = skill.installedTargets || { claude: !!skill.installed, codex: false, opencode: false };
|
|
1670
1681
|
const anyOn = targets.some(t => installed[t.id]);
|
|
1671
|
-
|
|
1672
|
-
|
|
1682
|
+
// What each runtime has of it: a native skill folder, an old bridge not moved yet, or a folder of that name
|
|
1683
|
+
// SynaBun did not install (settings/settings-skill-folders.js chipState).
|
|
1684
|
+
const chips = targets.map(t => {
|
|
1685
|
+
const chip = chipState(skill.targetStates?.[t.id], installed[t.id]);
|
|
1686
|
+
return `
|
|
1687
|
+
<button type="button" class="cc-skill-target${chip.on ? ' on' : ''}"
|
|
1673
1688
|
data-cc-skill="${kit.esc(skill.dirName)}"
|
|
1674
1689
|
data-cc-target="${t.id}"
|
|
1675
|
-
data-state="${
|
|
1676
|
-
${kit.inv('SKL001')} data-stg-pressed="on" aria-pressed="${
|
|
1677
|
-
title="${kit.te(
|
|
1678
|
-
|
|
1690
|
+
data-state="${chip.on ? 'on' : 'off'}" data-install-state="${chip.state}"
|
|
1691
|
+
${kit.inv('SKL001')} data-stg-pressed="on" aria-pressed="${chip.on ? 'true' : 'false'}"
|
|
1692
|
+
title="${kit.te(chip.key, { skill: skill.name, assistant: t.label })}">${t.label}</button>
|
|
1693
|
+
`;
|
|
1694
|
+
}).join('');
|
|
1679
1695
|
return `
|
|
1680
1696
|
<div class="cc-skill-row${anyOn ? ' installed' : ''}" data-skill-name="${kit.esc(skill.dirName)}">
|
|
1681
1697
|
<div class="cc-skill-info">
|
|
@@ -1690,10 +1706,14 @@ function buildSkillsTab(ccSkills) {
|
|
|
1690
1706
|
<div class="stg-pane" data-stg-pane="skills">
|
|
1691
1707
|
${kit.card({
|
|
1692
1708
|
id: 'stg-sec-skills', section: 'tool_access.skills',
|
|
1693
|
-
body: skills.length
|
|
1709
|
+
body: (skills.length
|
|
1694
1710
|
? `<div ${kit.inv('SKL004')} role="group" aria-label="${kit.L('SKL004')}">${skills.map(row).join('')}</div>
|
|
1695
1711
|
${kit.help('SKL001')}`
|
|
1696
|
-
: `<div class="stg-help" style="margin-top:0">${kit.te('settings.redesign.skills.none')}</div
|
|
1712
|
+
: `<div class="stg-help" style="margin-top:0">${kit.te('settings.redesign.skills.none')}</div>`)
|
|
1713
|
+
// Skills Studio is where a skill is edited, versioned and published; these chips stay for what SynaBun ships.
|
|
1714
|
+
+ `<div class="cc-skill-studio"><button type="button" class="stg-action-btn compact" data-cc-open-studio="1">${kit.te('settings.redesign.skills.openStudio')}</button>
|
|
1715
|
+
<span class="stg-help">${kit.te('settings.redesign.skills.openStudioHelp')}</span></div>`
|
|
1716
|
+
+ skillFoldersHTML(),
|
|
1697
1717
|
})}
|
|
1698
1718
|
</div>`;
|
|
1699
1719
|
}
|
|
@@ -1704,7 +1724,7 @@ function buildPermissionsTab(toolCategories, toolPermissions) {
|
|
|
1704
1724
|
// The inventory ids of the "all tools of this group" switches, by group.
|
|
1705
1725
|
const ALL_SWITCH = {
|
|
1706
1726
|
memory: 'PER001', browser: 'PER003', whiteboard: 'PER004', cards: 'PER005', automation: 'PER006',
|
|
1707
|
-
|
|
1727
|
+
images: 'PER008', gsc: 'PER009', youtube: 'PER010', thirdparty: 'PER011',
|
|
1708
1728
|
};
|
|
1709
1729
|
|
|
1710
1730
|
let totalOn = 0, totalAll = 0;
|
|
@@ -1824,6 +1844,52 @@ const userContextDigestHTML = (digest) => (digest.rows.length
|
|
|
1824
1844
|
? digest.rows.map(row => `<button type="button" class="uc-digest-row" role="listitem" data-uc-memory="${kit.esc(row.id)}" title="${kit.te('settings.redesign.hooks.uc.openMemory')}"><span class="uc-digest-statement">${kit.esc(row.statement)}</span><span class="uc-digest-facet">${kit.esc(row.facet)}${row.seed ? ` · ${kit.te('settings.redesign.hooks.uc.seed')}` : ''}</span></button>`).join('')
|
|
1825
1845
|
: `<div class="stg-help uc-digest-message" role="listitem" style="margin:0">${kit.esc(digest.message)}</div>`);
|
|
1826
1846
|
|
|
1847
|
+
/**
|
|
1848
|
+
* Put a view (describeClaudeHooks) on the section: the line above, the switch, its notes and Repair, and the
|
|
1849
|
+
* per-moment rows and their count in the Hooks card, which show nothing on or off that was not read.
|
|
1850
|
+
*/
|
|
1851
|
+
function drawClaudeHooks(overlay, view) {
|
|
1852
|
+
const section = overlay.querySelector('#stg-sec-automation-behavior');
|
|
1853
|
+
if (!section) return;
|
|
1854
|
+
const note = section.querySelector('#ch-status-note');
|
|
1855
|
+
if (note) { note.hidden = !view.note; note.textContent = view.note?.text || ''; note.dataset.chState = view.state; }
|
|
1856
|
+
const rowBox = section.querySelector('#ch-row');
|
|
1857
|
+
if (rowBox) { rowBox.dataset.chReady = view.ready ? '1' : '0'; rowBox.dataset.chState = view.state; }
|
|
1858
|
+
const toggle = section.querySelector('.cc-toggle[data-ch-master]');
|
|
1859
|
+
if (toggle) {
|
|
1860
|
+
toggle.classList.toggle('on', view.on);
|
|
1861
|
+
toggle.setAttribute('aria-pressed', view.on ? 'true' : 'false');
|
|
1862
|
+
toggle.disabled = view.disabled;
|
|
1863
|
+
toggle.setAttribute('aria-describedby', `ch-master-help ch-notes${view.note ? ' ch-status-note' : ''}`);
|
|
1864
|
+
const item = toggle.closest('.cc-integration-item');
|
|
1865
|
+
item?.classList.toggle('enabled', view.on);
|
|
1866
|
+
item?.classList.toggle('uc-disabled', view.disabled);
|
|
1867
|
+
}
|
|
1868
|
+
const notes = section.querySelector('#ch-notes');
|
|
1869
|
+
if (notes) notes.innerHTML = userContextNotesHTML(view.notes);
|
|
1870
|
+
const repair = section.querySelector('#ch-repair');
|
|
1871
|
+
if (repair) repair.hidden = !(view.repair && !view.disabled);
|
|
1872
|
+
const applies = section.querySelector('#ch-applies');
|
|
1873
|
+
if (applies) applies.textContent = view.applies;
|
|
1874
|
+
const locked = !view.ready || view.disabled;
|
|
1875
|
+
for (const row of CLAUDE_HOOK_ROWS) {
|
|
1876
|
+
const rowToggle = section.querySelector(`.cc-toggle[data-cc-hook="${row.key}"]`);
|
|
1877
|
+
if (!rowToggle) continue;
|
|
1878
|
+
const on = !!view.rows?.[row.key];
|
|
1879
|
+
rowToggle.classList.toggle('on', on);
|
|
1880
|
+
rowToggle.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
1881
|
+
rowToggle.disabled = locked;
|
|
1882
|
+
const item = rowToggle.closest('.cc-integration-item');
|
|
1883
|
+
item?.classList.toggle('enabled', on);
|
|
1884
|
+
item?.classList.toggle('uc-disabled', locked);
|
|
1885
|
+
}
|
|
1886
|
+
const badge = section.querySelector('#cc-hooks-badge');
|
|
1887
|
+
if (badge) {
|
|
1888
|
+
badge.textContent = view.ready ? `${view.onRows}/${view.totalRows}` : '—';
|
|
1889
|
+
badge.classList.toggle('all-on', view.ready && view.onRows === view.totalRows);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1827
1893
|
/** Put a view (describeUserContext) on the section: the note, each switch and its notes, the digest. */
|
|
1828
1894
|
function drawUserContext(overlay, view) {
|
|
1829
1895
|
const section = overlay.querySelector('#stg-sec-automation-behavior');
|
|
@@ -1900,16 +1966,19 @@ function tunnelExtraHtml(view, mcpKeyInfo) {
|
|
|
1900
1966
|
].filter(Boolean).join('');
|
|
1901
1967
|
}
|
|
1902
1968
|
|
|
1903
|
-
function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, greeting, toolPermissions, toolCategories, userContext = null) {
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
const
|
|
1908
|
-
const
|
|
1909
|
-
const
|
|
1910
|
-
const
|
|
1911
|
-
const
|
|
1912
|
-
const
|
|
1969
|
+
function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, greeting, toolPermissions, toolCategories, userContext = null, workerGuard = null, claudeHooks = null) {
|
|
1970
|
+
// SynaBun's Claude Code hooks (settings-claude-hooks.js): the switch and the rows below show what is really in the
|
|
1971
|
+
// settings files, and nothing (disabled, with the reason) when that could not be read: never "off" by default.
|
|
1972
|
+
const ch = claudeHooks || describeClaudeHooks(null, { tx: kit.tx });
|
|
1973
|
+
const rowOn = (key) => !!ch.rows?.[key];
|
|
1974
|
+
const ssOn = rowOn('SessionStart');
|
|
1975
|
+
const psOn = rowOn('UserPromptSubmit');
|
|
1976
|
+
const pcOn = rowOn('PreCompact');
|
|
1977
|
+
const stOn = rowOn('Stop');
|
|
1978
|
+
const prOn = rowOn('PreToolUse');
|
|
1979
|
+
const ptOn = rowOn('PostToolUse');
|
|
1980
|
+
const subStartOn = rowOn('SubagentStart');
|
|
1981
|
+
const subStopOn = rowOn('SubagentStop');
|
|
1913
1982
|
const hf = ccIntegrations.hookFeatures || {};
|
|
1914
1983
|
const cmOn = hf.conversationMemory !== false;
|
|
1915
1984
|
const ulOn = hf.userLearning !== false;
|
|
@@ -1926,7 +1995,8 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
|
|
|
1926
1995
|
{ key: 'SubagentStart', on: subStartOn, inv: 'AUT034' }, { key: 'SubagentStop', on: subStopOn, inv: 'AUT035' },
|
|
1927
1996
|
];
|
|
1928
1997
|
const onCount = hookRows.filter(r => r.on).length;
|
|
1929
|
-
const allOn = onCount === hookRows.length;
|
|
1998
|
+
const allOn = ch.ready && onCount === hookRows.length;
|
|
1999
|
+
const hookRowsLocked = !ch.ready || ch.disabled;
|
|
1930
2000
|
|
|
1931
2001
|
const copyIcon = COPY_ICON;
|
|
1932
2002
|
|
|
@@ -2051,6 +2121,44 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
|
|
|
2051
2121
|
<button type="button" class="conn-add-btn" id="uc-digest-all" ${kit.inv('AUT057')} title="${kit.H('AUT057')}" style="margin:0;width:auto;padding:4px 10px;border-style:solid">${kit.L('AUT057')}</button>
|
|
2052
2122
|
</div>`;
|
|
2053
2123
|
|
|
2124
|
+
// Protect workers (lib/worker-guard-setting.js): what capability read-only / workspace means for every worker the
|
|
2125
|
+
// Assistant dispatches. `workerGuard` is the view of what the server answered (describeWorkerGuard): a server
|
|
2126
|
+
// started before the setting shows the default, disabled, under a line that says a restart is missing.
|
|
2127
|
+
const wg = workerGuard || describeWorkerGuard(null, { tx: kit.tx });
|
|
2128
|
+
const workerGuardBlock = `
|
|
2129
|
+
<div class="cc-hook-toggles" id="wg-row" data-wg-ready="${wg.ready ? '1' : '0'}">
|
|
2130
|
+
<div class="cc-integration-item enabled${wg.disabled ? ' uc-disabled' : ''}" data-wg-row>
|
|
2131
|
+
<div class="cc-integration-info">
|
|
2132
|
+
<div class="cc-integration-label" id="wg-mode-label">${kit.L('AUT061')}</div>
|
|
2133
|
+
<div class="cc-integration-path" id="wg-mode-help">${kit.H('AUT061')}</div>
|
|
2134
|
+
<div class="uc-layer-notes" id="wg-notes" data-stg-data>${userContextNotesHTML(wg.note ? [wg.note] : [])}</div>
|
|
2135
|
+
<div class="stg-help" id="wg-mode-detail" data-stg-data style="margin:4px 0 0">${kit.esc(wg.options.find(o => o.value === wg.mode)?.help || '')}</div>
|
|
2136
|
+
</div>
|
|
2137
|
+
${kit.dropdown('wg-mode', wg.options.map(o => ({ value: o.value, label: kit.esc(o.label) })), { value: wg.mode, classes: 'stg-dropdown stg-dropdown-inline', label: kit.L('AUT061'), attrs: `${kit.inv('AUT061')} aria-describedby="wg-mode-help wg-notes"`, disabled: wg.disabled })}
|
|
2138
|
+
</div>
|
|
2139
|
+
</div>
|
|
2140
|
+
<div class="stg-help uc-save-status" id="wg-save-status" role="alert" data-stg-data hidden></div>
|
|
2141
|
+
<div class="stg-help" id="wg-applies">${kit.esc(wg.applies)}</div>`;
|
|
2142
|
+
|
|
2143
|
+
// SynaBun's hooks in Claude Code: one switch for all of them (POST / DELETE /api/claude-code/integrations, target
|
|
2144
|
+
// global), what is really installed under it, and Repair when the files are out of step. Outside the collapsed
|
|
2145
|
+
// card below, so it can be found; the per-moment rows stay in that card.
|
|
2146
|
+
const claudeHooksBlock = `
|
|
2147
|
+
<div class="stg-help uc-status-note" id="ch-status-note" role="status" data-ch-state="${ch.state}" data-stg-data${ch.note ? '' : ' hidden'}>${ch.note ? kit.esc(ch.note.text) : ''}</div>
|
|
2148
|
+
<div class="cc-hook-toggles" id="ch-row" data-ch-ready="${ch.ready ? '1' : '0'}" data-ch-state="${ch.state}">
|
|
2149
|
+
<div class="cc-integration-item${ch.on ? ' enabled' : ''}${ch.disabled ? ' uc-disabled' : ''}" data-ch-master-row>
|
|
2150
|
+
<div class="cc-integration-info">
|
|
2151
|
+
<div class="cc-integration-label" id="ch-master-label">${kit.L('AUT062')}</div>
|
|
2152
|
+
<div class="cc-integration-path" id="ch-master-help">${kit.H('AUT062')}</div>
|
|
2153
|
+
<div class="uc-layer-notes" id="ch-notes" data-stg-data>${userContextNotesHTML(ch.notes)}</div>
|
|
2154
|
+
<button type="button" class="cc-copy-btn stg-text-btn ch-repair" id="ch-repair" title="${kit.te('settings.redesign.hooks.claudeHooks.repair.title')}"${ch.repair && !ch.disabled ? '' : ' hidden'}>${kit.te('settings.redesign.hooks.claudeHooks.repair.action')}</button>
|
|
2155
|
+
</div>
|
|
2156
|
+
<button type="button" class="cc-toggle${ch.on ? ' on' : ''}" data-ch-master ${kit.inv('AUT062')} aria-pressed="${ch.on ? 'true' : 'false'}" aria-labelledby="ch-master-label" aria-describedby="ch-master-help ch-notes${ch.note ? ' ch-status-note' : ''}"${ch.disabled ? ' disabled' : ''}></button>
|
|
2157
|
+
</div>
|
|
2158
|
+
</div>
|
|
2159
|
+
<div class="stg-help uc-save-status" id="ch-save-status" role="alert" data-stg-data hidden></div>
|
|
2160
|
+
<div class="stg-help uc-applies" id="ch-applies">${kit.esc(ch.applies)}</div>`;
|
|
2161
|
+
|
|
2054
2162
|
return `
|
|
2055
2163
|
<div class="stg-pane" data-stg-pane="hooks">
|
|
2056
2164
|
|
|
@@ -2065,11 +2173,16 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
|
|
|
2065
2173
|
body: `
|
|
2066
2174
|
${userContextBlock}
|
|
2067
2175
|
|
|
2176
|
+
${workerGuardBlock}
|
|
2177
|
+
|
|
2178
|
+
${claudeHooksBlock}
|
|
2179
|
+
|
|
2068
2180
|
${kit.card({
|
|
2069
2181
|
id: 'stg-sec-hooks', level: 'h4', collapsible: true, collapsed: true, advanced: true, attrs: 'data-cc-target="global"',
|
|
2070
2182
|
title: h('hooksTitle'), purpose: h('hooksPurpose'),
|
|
2071
2183
|
// These hooks are Claude Code's own (~/.claude/settings.json, hooks/claude-code): no other assistant runs them.
|
|
2072
|
-
|
|
2184
|
+
// A count only from a state that was read: "—" otherwise, never "0/8".
|
|
2185
|
+
status: `<span class="cc-hooks-badge${allOn ? ' all-on' : ''}" id="cc-hooks-badge">${ch.ready ? `${onCount}/${hookRows.length}` : '—'}</span>${providerBadge({ cli: true, vscode: true, web: false, cowork: false, codex: false })}`,
|
|
2073
2186
|
body: `
|
|
2074
2187
|
<div class="stg-help" style="margin:0 0 10px">${h('claudeOnly')}</div>
|
|
2075
2188
|
<div class="cc-hook-toggles">
|
|
@@ -2088,12 +2201,12 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
|
|
|
2088
2201
|
</div>
|
|
2089
2202
|
<div class="cc-hook-toggles" style="margin-top:10px">
|
|
2090
2203
|
${hookRows.map(r => `
|
|
2091
|
-
<div class="cc-integration-item${r.on ? ' enabled' : ''}" data-hook="${r.key}">
|
|
2204
|
+
<div class="cc-integration-item${r.on ? ' enabled' : ''}${hookRowsLocked ? ' uc-disabled' : ''}" data-hook="${r.key}">
|
|
2092
2205
|
<div class="cc-integration-info">
|
|
2093
2206
|
<div class="cc-integration-label" id="cc-hook-${r.key}-label">${kit.L(r.inv)}</div>
|
|
2094
2207
|
<div class="cc-integration-path" id="cc-hook-${r.key}-help">${kit.H(r.inv)}</div>
|
|
2095
2208
|
</div>
|
|
2096
|
-
<button type="button" class="cc-toggle${r.on ? ' on' : ''}" data-cc-hook="${r.key}" data-cc-scope="global" ${kit.inv(r.inv)} aria-labelledby="cc-hook-${r.key}-label" aria-describedby="cc-hook-${r.key}-help"></button>
|
|
2209
|
+
<button type="button" class="cc-toggle${r.on ? ' on' : ''}" data-cc-hook="${r.key}" data-cc-scope="global" ${kit.inv(r.inv)} aria-pressed="${r.on ? 'true' : 'false'}" aria-labelledby="cc-hook-${r.key}-label" aria-describedby="cc-hook-${r.key}-help"${hookRowsLocked ? ' disabled' : ''}></button>
|
|
2097
2210
|
</div>`).join('')}
|
|
2098
2211
|
</div>`,
|
|
2099
2212
|
})}`,
|
|
@@ -3245,6 +3358,10 @@ async function openSettingsModalNow(options = {}) {
|
|
|
3245
3358
|
// The Assistant's user context (lib/assistant-user-context.js): what its endpoint answered, as a reading.
|
|
3246
3359
|
// No answer is a reading too: the section says what is true instead of showing switches that do nothing.
|
|
3247
3360
|
let userContextReading = readUserContextResponse({ status: 0 });
|
|
3361
|
+
// Protect workers (lib/worker-guard-setting.js): what its endpoint answered, as a reading.
|
|
3362
|
+
let workerGuardReading = readWorkerGuardResponse({ status: 0 });
|
|
3363
|
+
// SynaBun's Claude Code hooks (lib/claude-hooks-status.js): the `claudeHooks` of the integrations read, as a reading.
|
|
3364
|
+
let claudeHooksReading = readClaudeHooksResponse({ status: 0 });
|
|
3248
3365
|
|
|
3249
3366
|
try {
|
|
3250
3367
|
// Read through the page's data cache (settings/settings-data.js): kept answers at once (each open its own
|
|
@@ -3254,15 +3371,19 @@ async function openSettingsModalNow(options = {}) {
|
|
|
3254
3371
|
const readingOf = (entry, make) => ({ status: 'fulfilled', value: entry?.status === 'fulfilled' ? make(entry.value) : make({ status: 0, error: entry?.reason?.message }) });
|
|
3255
3372
|
const greetRes = readingOf(reads.greeting, readGreetingStatus);
|
|
3256
3373
|
const userContextRes = readingOf(reads.userContext, readUserContextResponse);
|
|
3374
|
+
const workerGuardRes = readingOf(reads.workerGuard, readWorkerGuardResponse);
|
|
3257
3375
|
const settingsRes = reads.settings, connRes = reads.connections, ccRes = reads.integrations, skillsRes = reads.skills;
|
|
3258
3376
|
const tunnelRes = reads.tunnel, keyRes = reads.mcpKey, bridgeRes = reads.openclaw, setupRes = reads.setup, cliRes = reads.cli;
|
|
3259
3377
|
const toolPermsRes = reads.toolPermissions, toolCatsRes = reads.toolCategories, discordRes = reads.discord, skinsRes = reads.skins;
|
|
3260
3378
|
const iconsRes = reads.icons, youtubeRes = reads.youtube, typesafeRes = reads.typesafe;
|
|
3261
3379
|
if (typesafeRes.status === 'fulfilled' && typesafeRes.value.ok) typesafeConfig = typesafeRes.value;
|
|
3262
3380
|
if (userContextRes.status === 'fulfilled') userContextReading = userContextRes.value;
|
|
3381
|
+
if (workerGuardRes.status === 'fulfilled') workerGuardReading = workerGuardRes.value;
|
|
3263
3382
|
if (settingsRes.status === 'fulfilled') settings = settingsRes.value;
|
|
3264
3383
|
if (connRes.status === 'fulfilled' && connRes.value.connections) connections = connRes.value.connections;
|
|
3265
|
-
|
|
3384
|
+
// Read as it came (status and body): a refusal or an old server is said as what it is, never as hooks that are off.
|
|
3385
|
+
if (ccRes.status === 'fulfilled' && ccRes.value?.status >= 200 && ccRes.value.status < 300 && ccRes.value.body?.ok) ccIntegrations = ccRes.value.body;
|
|
3386
|
+
claudeHooksReading = readingOf(ccRes, readClaudeHooksResponse).value;
|
|
3266
3387
|
if (skillsRes.status === 'fulfilled' && skillsRes.value.ok) ccSkills = skillsRes.value.skills || [];
|
|
3267
3388
|
if (tunnelRes.status === 'fulfilled' && tunnelRes.value.ok) tunnelStatus = tunnelRes.value;
|
|
3268
3389
|
if (keyRes.status === 'fulfilled' && keyRes.value.ok) mcpKeyInfo = keyRes.value;
|
|
@@ -3295,9 +3416,10 @@ async function openSettingsModalNow(options = {}) {
|
|
|
3295
3416
|
overlay.className = 'settings-panel glass resizable';
|
|
3296
3417
|
overlay.id = 'settings-panel';
|
|
3297
3418
|
|
|
3298
|
-
// Always open centered at default size
|
|
3299
|
-
|
|
3300
|
-
overlay.style.
|
|
3419
|
+
// Always open centered at default size, in the app frame
|
|
3420
|
+
const at = centeredInFrame(900, 650);
|
|
3421
|
+
overlay.style.left = at.left + 'px';
|
|
3422
|
+
overlay.style.top = at.top + 'px';
|
|
3301
3423
|
|
|
3302
3424
|
// ── Assemble HTML ──
|
|
3303
3425
|
overlay.innerHTML = `
|
|
@@ -3330,7 +3452,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
3330
3452
|
<div class="settings-content">
|
|
3331
3453
|
${buildPagesHTML({
|
|
3332
3454
|
server: buildServerTab(settings, connections),
|
|
3333
|
-
hooks: buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, describeGreeting(greetingReading, null, { tx: kit.tx, t: kit.tx }), toolPermissions, toolCategories, describeUserContext(userContextReading, { tx: kit.tx })),
|
|
3455
|
+
hooks: buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, describeGreeting(greetingReading, null, { tx: kit.tx, t: kit.tx }), toolPermissions, toolCategories, describeUserContext(userContextReading, { tx: kit.tx }), describeWorkerGuard(workerGuardReading, { tx: kit.tx }), describeClaudeHooks(claudeHooksReading, { tx: kit.tx })),
|
|
3334
3456
|
terminal: buildTerminalTab(cliConfig),
|
|
3335
3457
|
opencode: buildOpencodeTab(),
|
|
3336
3458
|
notifications: buildNotificationsTab(),
|
|
@@ -4639,9 +4761,14 @@ async function openSettingsModalNow(options = {}) {
|
|
|
4639
4761
|
});
|
|
4640
4762
|
const data = await res.json();
|
|
4641
4763
|
showPathAction(profileId, data);
|
|
4764
|
+
const refusal = profileId === 'opencode' && data.support ? describeOpencodeInstall(data.support, { th: kit.th, tx: kit.tx }) : null;
|
|
4642
4765
|
if (data.ok && data.found && data.path && data.state === 'broken') {
|
|
4643
4766
|
// Found, but it does not start: not put in the field, so a broken path is never saved by accident.
|
|
4644
4767
|
showCCToast(kit.tx('settings.redesign.terminal.detectionFailed', { message: data.error ? `${data.path}: ${data.error}` : data.path }));
|
|
4768
|
+
} else if (data.ok && data.found && data.path && refusal?.status === 'notSupported') {
|
|
4769
|
+
// An OpenCode SynaBun does not drive (OpenCode 1, say): put in the field, with the sentence that says why it will not start.
|
|
4770
|
+
input.value = data.path;
|
|
4771
|
+
showCCToast(refusal.text, 12000);
|
|
4645
4772
|
} else if (data.ok && data.found && data.path) {
|
|
4646
4773
|
input.value = data.path;
|
|
4647
4774
|
showCCToast(kit.tx('settings.redesign.terminal.found', { path: data.version ? `${data.path} (${data.version})` : data.path }));
|
|
@@ -4899,9 +5026,15 @@ async function openSettingsModalNow(options = {}) {
|
|
|
4899
5026
|
if (data.ok) {
|
|
4900
5027
|
const nowOn = !isOn;
|
|
4901
5028
|
toggleBtn.classList.toggle('on');
|
|
5029
|
+
toggleBtn.setAttribute('aria-pressed', nowOn ? 'true' : 'false');
|
|
4902
5030
|
const row = toggleBtn.closest('.cc-integration-item');
|
|
4903
5031
|
if (row) row.classList.toggle('enabled', nowOn);
|
|
4904
|
-
if (target === 'global') {
|
|
5032
|
+
if (target === 'global') {
|
|
5033
|
+
updateGlobalHookBadge();
|
|
5034
|
+
// The answer's state of every hook (a server from before it sends none): the switch above follows it.
|
|
5035
|
+
if (data.claudeHooks) { claudeHooksReading = readClaudeHooksResponse({ status: res.status, body: { ok: true, claudeHooks: data.claudeHooks } }); redrawClaudeHooks(); }
|
|
5036
|
+
refreshSettingsData(['integrations']);
|
|
5037
|
+
}
|
|
4905
5038
|
else { toggleBtn.textContent = nowOn ? kit.tx('settings.redesign.hooks.enabled') : kit.tx('settings.redesign.hooks.enable'); if (panel) panel.classList.toggle('enabled'); const badge = panel?.querySelector('.cc-panel-status'); if (badge) { badge.textContent = nowOn ? kit.tx('settings.redesign.hooks.active') : kit.tx('settings.redesign.hooks.off'); badge.className = 'cc-panel-status ' + (nowOn ? 'active' : 'inactive'); } updateProjectCount(); }
|
|
4906
5039
|
} else { alert(data.error || kit.tx('settings.redesign.hooks.failedToToggleHook')); }
|
|
4907
5040
|
} catch (err) { alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message })); }
|
|
@@ -4913,6 +5046,41 @@ async function openSettingsModalNow(options = {}) {
|
|
|
4913
5046
|
toggle.addEventListener('click', () => { ccToggleHook('global', null, toggle, overlay.querySelector('.iface-section[data-cc-target="global"]'), toggle.dataset.ccHook); });
|
|
4914
5047
|
});
|
|
4915
5048
|
|
|
5049
|
+
// ── SynaBun hooks in Claude Code: one switch for all of them, and Repair ──
|
|
5050
|
+
// Each change is the server's: the row is drawn again from the state its answer carries (what the files hold now),
|
|
5051
|
+
// and a change that failed leaves the row as it was, with the reason under it. One change at a time.
|
|
5052
|
+
function redrawClaudeHooks() { drawClaudeHooks(overlay, describeClaudeHooks(claudeHooksReading, { tx: kit.tx })); }
|
|
5053
|
+
const setClaudeHooksSaveStatus = (text = '') => {
|
|
5054
|
+
const status = overlay.querySelector('#ch-save-status');
|
|
5055
|
+
if (status) { status.hidden = !text; status.textContent = text; }
|
|
5056
|
+
};
|
|
5057
|
+
let claudeHooksBusy = false;
|
|
5058
|
+
async function changeClaudeHooks(action) {
|
|
5059
|
+
if (claudeHooksBusy || claudeHooksReading.state !== 'ready') return;
|
|
5060
|
+
claudeHooksBusy = true;
|
|
5061
|
+
setClaudeHooksSaveStatus('');
|
|
5062
|
+
const row = overlay.querySelector('[data-ch-master-row]');
|
|
5063
|
+
if (row) row.style.opacity = '0.6';
|
|
5064
|
+
let failure = null;
|
|
5065
|
+
try {
|
|
5066
|
+
// On and Repair ask for the hooks to run (unblock: Claude Code's own disableAllHooks in the same file goes too).
|
|
5067
|
+
const payload = action === 'off' ? { target: 'global' } : { target: 'global', unblock: true };
|
|
5068
|
+
const res = await fetch('/api/claude-code/integrations', { method: action === 'off' ? 'DELETE' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
|
5069
|
+
const body = await res.json().catch(() => null);
|
|
5070
|
+
if (res.ok && body?.ok) {
|
|
5071
|
+
claudeHooksReading = readClaudeHooksResponse({ status: res.status, body: { ok: true, claudeHooks: body.claudeHooks } });
|
|
5072
|
+
refreshSettingsData(['integrations']);
|
|
5073
|
+
} else failure = { status: res.status, body };
|
|
5074
|
+
} catch (err) { failure = { status: 0, error: err?.message }; }
|
|
5075
|
+
finally { claudeHooksBusy = false; if (row) row.style.opacity = ''; }
|
|
5076
|
+
redrawClaudeHooks();
|
|
5077
|
+
if (failure) setClaudeHooksSaveStatus(describeClaudeHooksSaveFailure({ action, ...failure }, { tx: kit.tx }));
|
|
5078
|
+
}
|
|
5079
|
+
overlay.querySelector('.cc-toggle[data-ch-master]')?.addEventListener('click', (e) => {
|
|
5080
|
+
changeClaudeHooks(e.currentTarget.classList.contains('on') ? 'off' : 'on');
|
|
5081
|
+
});
|
|
5082
|
+
overlay.querySelector('#ch-repair')?.addEventListener('click', () => changeClaudeHooks('repair'));
|
|
5083
|
+
|
|
4916
5084
|
// ── The Assistant's user context: layers and the digest preview ──
|
|
4917
5085
|
const ucRowsEl = overlay.querySelector('#uc-digest-rows');
|
|
4918
5086
|
const redrawUserContext = (reading = userContextReading) => drawUserContext(overlay, describeUserContext(reading, { tx: kit.tx }));
|
|
@@ -4939,6 +5107,47 @@ async function openSettingsModalNow(options = {}) {
|
|
|
4939
5107
|
if (!link) return;
|
|
4940
5108
|
try { applySettingsOpenOptions(overlay, JSON.parse(link.dataset.ucGoto)); } catch { /* a malformed target goes nowhere */ }
|
|
4941
5109
|
});
|
|
5110
|
+
// Protect workers: the choice is saved at once and follows the server's answer; a save that failed puts the
|
|
5111
|
+
// dropdown back where it was, with the reason under it. Saves go one after the other, in the order of the picks.
|
|
5112
|
+
{
|
|
5113
|
+
const input = overlay.querySelector('#wg-mode');
|
|
5114
|
+
const dd = overlay.querySelector('.cc-dropdown[data-for="wg-mode"]');
|
|
5115
|
+
const status = overlay.querySelector('#wg-save-status');
|
|
5116
|
+
const detail = overlay.querySelector('#wg-mode-detail');
|
|
5117
|
+
const setStatus = (text) => { if (status) { status.textContent = text; status.hidden = !text; } };
|
|
5118
|
+
const showMode = (mode) => {
|
|
5119
|
+
if (input) input.value = mode;
|
|
5120
|
+
const view = describeWorkerGuard(workerGuardReading, { tx: kit.tx });
|
|
5121
|
+
const option = view.options.find(o => o.value === mode);
|
|
5122
|
+
dd?.querySelector('.cc-dropdown-value') && (dd.querySelector('.cc-dropdown-value').textContent = option?.label || mode);
|
|
5123
|
+
dd?.querySelectorAll('.cc-dropdown-item').forEach(item => item.classList.toggle('active', item.dataset.value === mode));
|
|
5124
|
+
if (detail) detail.textContent = option?.help || '';
|
|
5125
|
+
};
|
|
5126
|
+
let saves = Promise.resolve();
|
|
5127
|
+
input?.addEventListener('change', () => {
|
|
5128
|
+
if (workerGuardReading.state !== 'ready') return;
|
|
5129
|
+
const mode = String(input.value || '');
|
|
5130
|
+
if (!mode || mode === workerGuardReading.mode) { showMode(workerGuardReading.mode); return; }
|
|
5131
|
+
setStatus('');
|
|
5132
|
+
showMode(mode);
|
|
5133
|
+
if (dd) dd.style.opacity = '0.4';
|
|
5134
|
+
saves = saves.then(async () => {
|
|
5135
|
+
let failure = null;
|
|
5136
|
+
try {
|
|
5137
|
+
const res = await fetch('/api/assistant/worker-guard', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode }) });
|
|
5138
|
+
const body = await res.json().catch(() => null);
|
|
5139
|
+
const reading = readWorkerGuardResponse({ status: res.status, body });
|
|
5140
|
+
if (res.ok && reading.state === 'ready') workerGuardReading = reading;
|
|
5141
|
+
else failure = { status: res.status, body };
|
|
5142
|
+
} catch (err) {
|
|
5143
|
+
failure = { status: 0, error: err?.message };
|
|
5144
|
+
}
|
|
5145
|
+
if (dd) dd.style.opacity = '';
|
|
5146
|
+
showMode(workerGuardReading.mode);
|
|
5147
|
+
if (failure) setStatus(describeWorkerGuardSaveFailure({ ...failure, mode }, { tx: kit.tx }));
|
|
5148
|
+
});
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
4942
5151
|
// The switch moves at once and follows the server's answer: the stored settings when the save went through,
|
|
4943
5152
|
// back where it was (with the reason next to the switches) when it did not. Saves go one after the other, in
|
|
4944
5153
|
// the order of the clicks, so a second click made before the first was answered is kept, not dropped.
|
|
@@ -5192,6 +5401,12 @@ async function openSettingsModalNow(options = {}) {
|
|
|
5192
5401
|
btn.addEventListener('click', () => { const idx = btn.dataset.ccProjectToggle; const panel = overlay.querySelector(`.cc-panel[data-cc-idx="${idx}"]`); const projectPath = panel?.dataset.ccPath; if (projectPath) ccToggleHook('project', projectPath, btn, panel); });
|
|
5193
5402
|
});
|
|
5194
5403
|
|
|
5404
|
+
// Skills Studio, from Settings → Skills: Settings closes, the Studio opens on its Library.
|
|
5405
|
+
overlay.querySelector('[data-cc-open-studio]')?.addEventListener('click', () => {
|
|
5406
|
+
close();
|
|
5407
|
+
emit('skills:open', { place: 'library' });
|
|
5408
|
+
});
|
|
5409
|
+
|
|
5195
5410
|
// Skill target chips — one skill can be installed independently into Claude / Codex / OpenCode
|
|
5196
5411
|
const runtimeLabels = { claude: 'Claude Code', codex: 'Codex CLI', opencode: 'OpenCode CLI' };
|
|
5197
5412
|
overlay.querySelectorAll('.cc-skill-target[data-cc-skill]').forEach(chip => {
|
|
@@ -5201,28 +5416,48 @@ async function openSettingsModalNow(options = {}) {
|
|
|
5201
5416
|
const isOn = chip.classList.contains('on');
|
|
5202
5417
|
try {
|
|
5203
5418
|
chip.style.opacity = '0.4'; chip.style.pointerEvents = 'none';
|
|
5204
|
-
const
|
|
5419
|
+
const send = (extra = {}) => fetch('/api/skills/install', {
|
|
5205
5420
|
method: isOn ? 'DELETE' : 'POST',
|
|
5206
5421
|
headers: { 'Content-Type': 'application/json' },
|
|
5207
|
-
body: JSON.stringify({ name: skillName, target }),
|
|
5208
|
-
});
|
|
5209
|
-
|
|
5422
|
+
body: JSON.stringify({ name: skillName, target, ...extra }),
|
|
5423
|
+
}).then((res) => res.json());
|
|
5424
|
+
let data = await send();
|
|
5425
|
+
// A skill of that name that SynaBun did not install, or that was changed since, is neither replaced nor
|
|
5426
|
+
// removed unasked: the server says what it is, and only a yes sends its digest back (a backup is kept).
|
|
5427
|
+
if (!data.ok && data.currentDigest && (data.code === 'skill_unowned' || data.code === 'skill_modified')) {
|
|
5428
|
+
if (!confirm(data.question || data.error)) return;
|
|
5429
|
+
data = await send({ expectedDestinationDigest: data.currentDigest });
|
|
5430
|
+
}
|
|
5210
5431
|
if (data.ok) {
|
|
5211
5432
|
chip.classList.toggle('on');
|
|
5212
5433
|
chip.dataset.state = isOn ? 'off' : 'on';
|
|
5434
|
+
// (a server from before the skill folders answers without `destination`: the chip then only says it is installed)
|
|
5435
|
+
const now = chipState(isOn ? null : (data.destination ? (data.shared ? { native: 'absent', shared: 'native' } : { native: 'native', bridge: !!data.bridge }) : undefined), !isOn);
|
|
5436
|
+
chip.dataset.installState = now.state;
|
|
5437
|
+
chip.setAttribute('aria-pressed', now.on ? 'true' : 'false');
|
|
5438
|
+
chip.title = kit.tx(now.key, { skill: skillName, assistant: chip.textContent.trim() });
|
|
5213
5439
|
const row = chip.closest('.cc-skill-row');
|
|
5214
5440
|
if (row) {
|
|
5215
5441
|
const anyOn = !!row.querySelector('.cc-skill-target.on');
|
|
5216
5442
|
row.classList.toggle('installed', anyOn);
|
|
5217
5443
|
}
|
|
5218
5444
|
const runtime = runtimeLabels[target] || target;
|
|
5219
|
-
|
|
5445
|
+
// The shared ~/.agents/skills is read by Codex and OpenCode both: the other chips of the skill follow.
|
|
5446
|
+
if (data.shared) refreshSkillChips(overlay);
|
|
5447
|
+
// An install is a native skill folder now; the server says when an old bridge of it is still there.
|
|
5448
|
+
if (isOn) showCCToast(kit.tx('settings.redesign.hooks.skillUninstalledRestartToApply', { runtime: runtime }));
|
|
5449
|
+
else if (data.destination) showCCToast(kit.tx(installedToast(data), { skill: skillName, runtime: runtime }), 6000);
|
|
5450
|
+
else showCCToast(kit.tx('settings.redesign.hooks.skillInstalledRestartToApply', { runtime: runtime }));
|
|
5220
5451
|
} else { alert(data.error || kit.tx('settings.redesign.hooks.failedToToggleSkill')); }
|
|
5221
5452
|
} catch (err) { alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message })); }
|
|
5222
5453
|
finally { chip.style.opacity = ''; chip.style.pointerEvents = ''; }
|
|
5223
5454
|
});
|
|
5224
5455
|
});
|
|
5225
5456
|
|
|
5457
|
+
// Skill folders: the move of the old bridges to native skill folders, and the shared ~/.agents/skills switch.
|
|
5458
|
+
// After a move, an undo, or a change made anywhere else, the chips are read again in place.
|
|
5459
|
+
wireSkillFolders(overlay, { toast: (message, ms) => showCCToast(message, ms), onChanged: () => refreshSkillChips(overlay), signal: panelLife.signal });
|
|
5460
|
+
|
|
5226
5461
|
// ── Setup tab: per-provider MCP toggles, CLI copy, config copy, SynaBun rules ──
|
|
5227
5462
|
{
|
|
5228
5463
|
const checkIcon = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
@@ -5378,7 +5613,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
5378
5613
|
// An edited copy is removed only on an explicit yes (force). A file without SynaBun's markers is the
|
|
5379
5614
|
// user's own and stays whatever is sent, so there is nothing to confirm for it.
|
|
5380
5615
|
const edited = info?.state === 'modified' && info.replaceable !== false;
|
|
5381
|
-
// OpenCode: a line in
|
|
5616
|
+
// OpenCode: a line in opencode.json that SynaBun did not add keeps the file on a plain remove (the answer
|
|
5382
5617
|
// says so, and the host is then no longer managed). Asked again, the confirm sends force, which takes that line too.
|
|
5383
5618
|
const listed = info?.entry === 'user';
|
|
5384
5619
|
const force = edited || (listed && info.managed === false);
|
|
@@ -5521,8 +5756,8 @@ async function openSettingsModalNow(options = {}) {
|
|
|
5521
5756
|
wireRulesControls('codex', 'codex');
|
|
5522
5757
|
|
|
5523
5758
|
// ── OpenCode ──
|
|
5524
|
-
wireSetupMcpToggle('opencode', '/api/opencode/mcp', '~/.config/opencode/
|
|
5525
|
-
// Config preview — OpenCode
|
|
5759
|
+
wireSetupMcpToggle('opencode', '/api/opencode/mcp', '~/.config/opencode/opencode.json');
|
|
5760
|
+
// Config preview — OpenCode 2 keeps MCP servers under "mcp" → "servers" of opencode.json
|
|
5526
5761
|
{
|
|
5527
5762
|
const preview = overlay.querySelector('#setup-opencode-config-preview');
|
|
5528
5763
|
let cachedConfig = '';
|
|
@@ -5530,15 +5765,18 @@ async function openSettingsModalNow(options = {}) {
|
|
|
5530
5765
|
const ep = setupStatus.paths?.envPath || '<path-to>/synabun/.env';
|
|
5531
5766
|
fetch('/api/opencode/mcp').then(r => r.json()).then(data => {
|
|
5532
5767
|
if (data.ok && data.data && Object.keys(data.data).length) {
|
|
5533
|
-
cachedConfig = JSON.stringify({ mcp: data.data }, null, 2);
|
|
5768
|
+
cachedConfig = JSON.stringify({ mcp: { servers: data.data } }, null, 2);
|
|
5534
5769
|
preview.textContent = cachedConfig;
|
|
5535
5770
|
} else if (data.ok) {
|
|
5771
|
+
const launch = mcpLaunch();
|
|
5536
5772
|
cachedConfig = JSON.stringify({
|
|
5537
5773
|
mcp: {
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5774
|
+
servers: {
|
|
5775
|
+
SynaBun: {
|
|
5776
|
+
type: 'local',
|
|
5777
|
+
command: [launch.command, ...launch.args],
|
|
5778
|
+
environment: { DOTENV_PATH: ep },
|
|
5779
|
+
},
|
|
5542
5780
|
},
|
|
5543
5781
|
},
|
|
5544
5782
|
}, null, 2);
|
|
@@ -7315,6 +7553,9 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7315
7553
|
const ocpToolsEl = overlay.querySelector('#stg-ocp-tools');
|
|
7316
7554
|
const ocpHistoryMetaEl = overlay.querySelector('#stg-ocp-history-meta');
|
|
7317
7555
|
const ocpHistoryClearBtn = overlay.querySelector('#stg-ocp-clear-history');
|
|
7556
|
+
const ocpInstallEl = overlay.querySelector('#stg-ocp-install');
|
|
7557
|
+
const ocpConfigPathEl = overlay.querySelector('#stg-ocp-config-path');
|
|
7558
|
+
const ocpConfigJsoncEl = overlay.querySelector('#stg-ocp-config-jsonc');
|
|
7318
7559
|
const esc = s => String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
7319
7560
|
|
|
7320
7561
|
// ── Helpers ──
|
|
@@ -7337,6 +7578,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7337
7578
|
let _ollamaConfiguring = false;
|
|
7338
7579
|
let _ollamaPollTimer = null;
|
|
7339
7580
|
let _ollamaLastModelNames = '';
|
|
7581
|
+
let _ollamaConfigureError = '';
|
|
7340
7582
|
let _ocpHistoryStats = null;
|
|
7341
7583
|
|
|
7342
7584
|
function formatBytes(bytes) {
|
|
@@ -7367,7 +7609,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7367
7609
|
ocpHistoryMetaEl.textContent = kit.tx('settings.redesign.opencode.historyWipeInProgress');
|
|
7368
7610
|
return;
|
|
7369
7611
|
}
|
|
7370
|
-
const totalBytes =
|
|
7612
|
+
const totalBytes = Number(data.databaseBytes) || 0;
|
|
7371
7613
|
ocpHistoryMetaEl.textContent = kit.tx('settings.redesign.opencode.stored', { formatCount: formatCount(data.sessions, 'session'), formatBytes: formatBytes(totalBytes) });
|
|
7372
7614
|
ocpHistoryClearBtn.disabled = false;
|
|
7373
7615
|
} catch (error) {
|
|
@@ -7379,8 +7621,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7379
7621
|
if (document.querySelector('.ocp-history-clear-overlay')) return;
|
|
7380
7622
|
const confirmation = _ocpHistoryStats?.confirmationPhrase || 'DELETE OPENCODE HISTORY';
|
|
7381
7623
|
const sessionCount = Number(_ocpHistoryStats?.sessions) || 0;
|
|
7382
|
-
const totalBytes =
|
|
7383
|
-
+ (Number(_ocpHistoryStats?.artifactBytes) || 0);
|
|
7624
|
+
const totalBytes = Number(_ocpHistoryStats?.databaseBytes) || 0;
|
|
7384
7625
|
const modal = document.createElement('div');
|
|
7385
7626
|
modal.className = 'tag-delete-overlay ocp-history-clear-overlay';
|
|
7386
7627
|
modal.innerHTML = `
|
|
@@ -7394,9 +7635,9 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7394
7635
|
<p class="ocp-history-modal-subtitle">${formatCount(sessionCount, 'session')} ${kit.th('settings.redesign.opencode.currentlyStored', { formatBytes: formatBytes(totalBytes) })}</p>
|
|
7395
7636
|
</div>
|
|
7396
7637
|
</div>
|
|
7397
|
-
<div class="ocp-history-warning"><strong>${kit.te('settings.redesign.opencode.thisCannotBeUndone')}</strong> ${kit.te('settings.redesign.opencode.
|
|
7638
|
+
<div class="ocp-history-warning"><strong>${kit.te('settings.redesign.opencode.thisCannotBeUndone')}</strong> ${kit.te('settings.redesign.opencode.historyClearSteps')}</div>
|
|
7398
7639
|
<ul class="ocp-history-scope">
|
|
7399
|
-
<li>${kit.te('settings.redesign.opencode.
|
|
7640
|
+
<li>${kit.te('settings.redesign.opencode.historyClearDeletes')}</li>
|
|
7400
7641
|
<li>${kit.te('settings.redesign.opencode.deletesSynabunSDerivedOpencodeResume')}</li>
|
|
7401
7642
|
<li>${kit.te('settings.redesign.opencode.keepsProviderCredentialsAccountConfigSet')}</li>
|
|
7402
7643
|
</ul>
|
|
@@ -7444,7 +7685,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7444
7685
|
confirmButton.disabled = true;
|
|
7445
7686
|
confirmButton.textContent = kit.tx('settings.redesign.opencode.clearing');
|
|
7446
7687
|
feedback.dataset.state = 'working';
|
|
7447
|
-
feedback.textContent = kit.tx('settings.redesign.opencode.
|
|
7688
|
+
feedback.textContent = kit.tx('settings.redesign.opencode.historyClearWorking');
|
|
7448
7689
|
const requestId = crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`;
|
|
7449
7690
|
sessionStorage.setItem('synabun-opencode-history-clear-request', requestId);
|
|
7450
7691
|
try {
|
|
@@ -7457,9 +7698,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7457
7698
|
if (!response.ok || !data.ok) throw new Error(data.error || kit.tx('settings.redesign.opencode.historyWipeFailedHttp', { status: response.status }));
|
|
7458
7699
|
|
|
7459
7700
|
const result = data.result || {};
|
|
7460
|
-
const reclaimed = Math.max(0,
|
|
7461
|
-
(Number(result.databaseBytesBefore) || 0) - (Number(result.databaseBytesAfter) || 0)
|
|
7462
|
-
+ (Number(result.artifactBytesDeleted) || 0));
|
|
7701
|
+
const reclaimed = Math.max(0, (Number(result.databaseBytesBefore) || 0) - (Number(result.databaseBytesAfter) || 0));
|
|
7463
7702
|
const warnings = Array.isArray(result.warnings) ? result.warnings.filter(Boolean) : [];
|
|
7464
7703
|
feedback.dataset.state = warnings.length ? 'warning' : 'working';
|
|
7465
7704
|
feedback.textContent = warnings.length
|
|
@@ -7500,7 +7739,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7500
7739
|
dotColor = '#facc15';
|
|
7501
7740
|
statusText = kit.tx('settings.redesign.opencode.configuring');
|
|
7502
7741
|
statusCls = 'stg-provider-status-key';
|
|
7503
|
-
bodyHtml = '<div class="stg-provider-note">' + kit.te('settings.redesign.opencode.
|
|
7742
|
+
bodyHtml = '<div class="stg-provider-note">' + kit.te('settings.redesign.opencode.writingProviderConfig') + '</div>';
|
|
7504
7743
|
} else if (!_ollamaRunning) {
|
|
7505
7744
|
dotColor = 'rgba(248,113,113,0.7)';
|
|
7506
7745
|
statusText = kit.tx('settings.redesign.opencode.notDetected');
|
|
@@ -7511,6 +7750,11 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7511
7750
|
statusText = kit.tx('settings.redesign.opencode.runningVersion', { version: _ollamaVersion ? ' v' + _ollamaVersion : '' });
|
|
7512
7751
|
statusCls = 'stg-provider-status-key';
|
|
7513
7752
|
bodyHtml = '<div class="stg-provider-note">' + kit.te('settings.redesign.opencode.noModelsRun') + ' <code class="stg-code">ollama pull <model></code></div>';
|
|
7753
|
+
} else if (_ollamaConfigureError) {
|
|
7754
|
+
dotColor = 'rgba(248,113,113,0.7)';
|
|
7755
|
+
statusText = kit.tx('settings.redesign.opencode.runningVersion', { version: _ollamaVersion ? ' v' + _ollamaVersion : '' });
|
|
7756
|
+
statusCls = 'stg-provider-status-disconnected';
|
|
7757
|
+
bodyHtml = '<div class="stg-provider-note">' + esc(kit.tx('settings.redesign.opencode.error2', { message: _ollamaConfigureError })) + '</div>';
|
|
7514
7758
|
} else if (_ollamaConfigured) {
|
|
7515
7759
|
dotColor = '#4ade80';
|
|
7516
7760
|
statusText = kit.tx('settings.redesign.opencode.runningVersion', { version: _ollamaVersion ? ' v' + _ollamaVersion : '' });
|
|
@@ -7546,27 +7790,16 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7546
7790
|
const btn = existing.querySelector('.stg-ollama-remove');
|
|
7547
7791
|
if (btn) { btn.disabled = true; btn.textContent = kit.tx('settings.redesign.opencode.removing'); }
|
|
7548
7792
|
try {
|
|
7549
|
-
await fetch('/api/ollama/configure', { method: 'DELETE' });
|
|
7793
|
+
const resp = await fetch('/api/ollama/configure', { method: 'DELETE' });
|
|
7794
|
+
const data = await resp.json().catch(() => ({}));
|
|
7795
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
7550
7796
|
_ollamaConfigured = false;
|
|
7551
7797
|
_ollamaConfiguredCount = 0;
|
|
7552
7798
|
_ollamaUserRemoved = true;
|
|
7553
7799
|
renderOllamaCard();
|
|
7554
|
-
showCCToast(kit.tx('settings.redesign.opencode.
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
const pollId = setInterval(async () => {
|
|
7558
|
-
polls++;
|
|
7559
|
-
if (polls > 8) { clearInterval(pollId); return; }
|
|
7560
|
-
try {
|
|
7561
|
-
const sr = await fetch('/api/opencode/status');
|
|
7562
|
-
const sd = await sr.json();
|
|
7563
|
-
if (sd.running) {
|
|
7564
|
-
clearInterval(pollId);
|
|
7565
|
-
await refreshOcpProviders();
|
|
7566
|
-
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
7567
|
-
}
|
|
7568
|
-
} catch {}
|
|
7569
|
-
}, 2000);
|
|
7800
|
+
showCCToast(kit.tx('settings.redesign.opencode.ollamaProviderRemoved'));
|
|
7801
|
+
await refreshOcpProviders();
|
|
7802
|
+
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
7570
7803
|
} catch (e) {
|
|
7571
7804
|
showCCToast(kit.tx('settings.redesign.opencode.error2', { message: e.message }));
|
|
7572
7805
|
}
|
|
@@ -7600,7 +7833,9 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7600
7833
|
|
|
7601
7834
|
// Auto-configure if: running + has models + not configured + not user-removed
|
|
7602
7835
|
// Also re-configure if models changed while already configured
|
|
7603
|
-
|
|
7836
|
+
// A refusal (an opencode.json SynaBun cannot change now) is asked again only when the models change.
|
|
7837
|
+
if (modelsChanged) _ollamaConfigureError = '';
|
|
7838
|
+
const shouldConfigure = _ollamaRunning && _ollamaModels.length > 0 && !_ollamaUserRemoved && !_ollamaConfigureError
|
|
7604
7839
|
&& (!_ollamaConfigured || modelsChanged);
|
|
7605
7840
|
|
|
7606
7841
|
if (shouldConfigure && !_ollamaConfiguring) {
|
|
@@ -7608,31 +7843,16 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7608
7843
|
renderOllamaCard();
|
|
7609
7844
|
try {
|
|
7610
7845
|
const cfgResp = await fetch('/api/ollama/configure', { method: 'POST' });
|
|
7611
|
-
const cfgData = await cfgResp.json();
|
|
7612
|
-
|
|
7846
|
+
const cfgData = await cfgResp.json().catch(() => ({}));
|
|
7847
|
+
_ollamaConfiguring = false;
|
|
7848
|
+
if (cfgResp.ok && cfgData.ok) {
|
|
7849
|
+
// OpenCode reads the provider at once: its models are listed now.
|
|
7613
7850
|
_ollamaConfigured = true;
|
|
7614
7851
|
_ollamaConfiguredCount = cfgData.modelsConfigured;
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
let polls = 0;
|
|
7618
|
-
const pollId = setInterval(async () => {
|
|
7619
|
-
polls++;
|
|
7620
|
-
if (polls > 8) { clearInterval(pollId); _ollamaConfiguring = false; renderOllamaCard(); return; }
|
|
7621
|
-
try {
|
|
7622
|
-
const sr = await fetch('/api/opencode/status');
|
|
7623
|
-
const sd = await sr.json();
|
|
7624
|
-
if (sd.running) {
|
|
7625
|
-
clearInterval(pollId);
|
|
7626
|
-
_ollamaConfiguring = false;
|
|
7627
|
-
await refreshOcpProviders();
|
|
7628
|
-
renderOllamaCard();
|
|
7629
|
-
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
7630
|
-
}
|
|
7631
|
-
} catch {}
|
|
7632
|
-
}, 2000);
|
|
7633
|
-
}, 1000);
|
|
7852
|
+
await refreshOcpProviders();
|
|
7853
|
+
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
7634
7854
|
} else {
|
|
7635
|
-
|
|
7855
|
+
_ollamaConfigureError = cfgData.error || `HTTP ${cfgResp.status}`;
|
|
7636
7856
|
}
|
|
7637
7857
|
} catch {
|
|
7638
7858
|
_ollamaConfiguring = false;
|
|
@@ -7884,7 +8104,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7884
8104
|
}
|
|
7885
8105
|
for (const om of oauthMethods) {
|
|
7886
8106
|
const label = om.label || om.name || kit.tx('settings.redesign.opencode.reconnectProvider', { name });
|
|
7887
|
-
btns.push(`<button type="button" class="stg-action-btn compact stg-text-btn stg-ocp-oauth-btn" data-provider="${esc(id)}" data-method="${om._idx}">${esc(label)}</button>`);
|
|
8107
|
+
btns.push(`<button type="button" class="stg-action-btn compact stg-text-btn stg-ocp-oauth-btn" data-provider="${esc(id)}" data-method="${om._idx}" data-method-id="${esc(om.id || '')}">${esc(label)}</button>`);
|
|
7888
8108
|
}
|
|
7889
8109
|
authHtml = btns.join('');
|
|
7890
8110
|
removeHtml = `<button type="button" class="stg-action-btn compact stg-text-btn stg-text-btn-danger stg-ocp-remove-btn" data-provider="${esc(id)}" data-env="${esc(primaryEnv)}">${kit.te('settings.redesign.opencode.removeConfirm')}</button>`;
|
|
@@ -7898,7 +8118,7 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7898
8118
|
}
|
|
7899
8119
|
for (const om of oauthMethods) {
|
|
7900
8120
|
const label = om.label || om.name || kit.tx('settings.redesign.opencode.loginWith', { name: name });
|
|
7901
|
-
btns.push(`<button type="button" class="stg-action-btn compact stg-ocp-oauth-btn" data-provider="${esc(id)}" data-method="${om._idx}">${esc(label)}</button>`);
|
|
8121
|
+
btns.push(`<button type="button" class="stg-action-btn compact stg-ocp-oauth-btn" data-provider="${esc(id)}" data-method="${om._idx}" data-method-id="${esc(om.id || '')}">${esc(label)}</button>`);
|
|
7902
8122
|
}
|
|
7903
8123
|
authHtml = btns.join('');
|
|
7904
8124
|
}
|
|
@@ -7914,9 +8134,16 @@ async function openSettingsModalNow(options = {}) {
|
|
|
7914
8134
|
</div>`;
|
|
7915
8135
|
|
|
7916
8136
|
// OAuth wait indicator (hidden via inline style — JS removes it to show)
|
|
8137
|
+
// OAuth wait (hidden via inline style — JS removes it to show): OpenCode's instructions (a device code),
|
|
8138
|
+
// and, for a sign-in that ends with a code the provider shows, the field it is pasted in.
|
|
7917
8139
|
const oauthWaitHtml = `<div class="stg-ocp-oauth-wait" data-provider="${esc(id)}" style="display:none">
|
|
7918
|
-
|
|
8140
|
+
<span class="stg-ocp-oauth-status">${kit.te('settings.redesign.opencode.waitingForAuthorization')}</span>
|
|
7919
8141
|
<span class="stg-ocp-oauth-code" style="display:none"></span>
|
|
8142
|
+
<div class="stg-ocp-key-row stg-ocp-oauth-code-row" style="display:none">
|
|
8143
|
+
<input type="text" class="stg-input stg-input-sm stg-ocp-oauth-code-input" placeholder="${kit.te('settings.redesign.opencode.oauthCodePlaceholder')}" aria-label="${kit.te('settings.redesign.opencode.oauthCodeFor', { name })}" autocomplete="off" spellcheck="false" style="flex:1">
|
|
8144
|
+
<button type="button" class="stg-action-btn compact stg-ocp-oauth-code-submit">${kit.te('settings.redesign.opencode.oauthConnect')}</button>
|
|
8145
|
+
</div>
|
|
8146
|
+
<div class="stg-ocp-key-error"></div>
|
|
7920
8147
|
</div>`;
|
|
7921
8148
|
|
|
7922
8149
|
const hiddenSet = getHiddenModels();
|
|
@@ -8112,28 +8339,15 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8112
8339
|
try {
|
|
8113
8340
|
const resp = await fetch(`/api/opencode/auth/${encodeURIComponent(provId)}`, {
|
|
8114
8341
|
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
8115
|
-
body: JSON.stringify({
|
|
8342
|
+
body: JSON.stringify({ key }),
|
|
8116
8343
|
});
|
|
8117
|
-
const data = await resp.json();
|
|
8344
|
+
const data = await resp.json().catch(() => ({}));
|
|
8118
8345
|
if (resp.ok && data.ok !== false) {
|
|
8119
8346
|
form.style.display = 'none';
|
|
8120
8347
|
input.value = '';
|
|
8121
|
-
showCCToast(kit.tx('settings.redesign.opencode.
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
const pollId = setInterval(async () => {
|
|
8125
|
-
polls++;
|
|
8126
|
-
if (polls > 8) { clearInterval(pollId); return; }
|
|
8127
|
-
try {
|
|
8128
|
-
const sr = await fetch('/api/opencode/status');
|
|
8129
|
-
const sd = await sr.json();
|
|
8130
|
-
if (sd.running) {
|
|
8131
|
-
clearInterval(pollId);
|
|
8132
|
-
await refreshOcpProviders();
|
|
8133
|
-
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8134
|
-
}
|
|
8135
|
-
} catch {}
|
|
8136
|
-
}, 2000);
|
|
8348
|
+
showCCToast(kit.tx('settings.redesign.opencode.apiKeySaved', { provId: provId }));
|
|
8349
|
+
await refreshOcpProviders();
|
|
8350
|
+
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8137
8351
|
} else {
|
|
8138
8352
|
const msg = data.error || kit.tx('settings.redesign.opencode.failedToSaveKey');
|
|
8139
8353
|
if (errEl) { errEl.textContent = msg; errEl.style.display = ''; }
|
|
@@ -8166,24 +8380,11 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8166
8380
|
method: 'DELETE',
|
|
8167
8381
|
headers: { 'Content-Type': 'application/json' },
|
|
8168
8382
|
});
|
|
8169
|
-
const data = await resp.json();
|
|
8383
|
+
const data = await resp.json().catch(() => ({}));
|
|
8170
8384
|
if (resp.ok && data.ok !== false) {
|
|
8171
|
-
showCCToast(kit.tx('settings.redesign.opencode.
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
const pollId = setInterval(async () => {
|
|
8175
|
-
polls++;
|
|
8176
|
-
if (polls > 8) { clearInterval(pollId); return; }
|
|
8177
|
-
try {
|
|
8178
|
-
const sr = await fetch('/api/opencode/status');
|
|
8179
|
-
const sd = await sr.json();
|
|
8180
|
-
if (sd.running) {
|
|
8181
|
-
clearInterval(pollId);
|
|
8182
|
-
await refreshOcpProviders();
|
|
8183
|
-
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8184
|
-
}
|
|
8185
|
-
} catch {}
|
|
8186
|
-
}, 2000);
|
|
8385
|
+
showCCToast(kit.tx('settings.redesign.opencode.apiKeyRemoved', { provId: provId }));
|
|
8386
|
+
await refreshOcpProviders();
|
|
8387
|
+
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8187
8388
|
} else {
|
|
8188
8389
|
showCCToast(data.error || kit.tx('settings.redesign.opencode.failedToRemoveKey'));
|
|
8189
8390
|
}
|
|
@@ -8200,43 +8401,22 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8200
8401
|
btn.addEventListener('click', async () => {
|
|
8201
8402
|
const provId = btn.dataset.provider;
|
|
8202
8403
|
const methodIdx = parseInt(btn.dataset.method, 10);
|
|
8404
|
+
const card = btn.closest('.stg-provider-card');
|
|
8405
|
+
const providerName = card?.querySelector('.stg-provider-name')?.textContent?.trim() || provId;
|
|
8203
8406
|
btn.disabled = true; btn.textContent = kit.tx('settings.redesign.opencode.connecting');
|
|
8204
|
-
const waitEl = ocpProvidersEl.querySelector(`.stg-ocp-oauth-wait[data-provider="${provId}"]`);
|
|
8205
|
-
const codeEl = waitEl?.querySelector('.stg-ocp-oauth-code');
|
|
8206
8407
|
try {
|
|
8207
8408
|
const resp = await fetch(`/api/opencode/oauth/${encodeURIComponent(provId)}/authorize`, {
|
|
8208
8409
|
method: 'POST',
|
|
8209
8410
|
headers: { 'Content-Type': 'application/json' },
|
|
8210
|
-
body: JSON.stringify({ method: isNaN(methodIdx) ? 0 : methodIdx }),
|
|
8411
|
+
body: JSON.stringify({ methodID: btn.dataset.methodId || undefined, method: isNaN(methodIdx) ? 0 : methodIdx }),
|
|
8211
8412
|
});
|
|
8212
|
-
const data = await resp.json();
|
|
8213
|
-
const
|
|
8214
|
-
if (
|
|
8215
|
-
window.open(
|
|
8216
|
-
|
|
8217
|
-
if (authData.code && codeEl) { codeEl.textContent = authData.code; codeEl.style.display = ''; }
|
|
8218
|
-
if (authData.instructions) showCCToast(authData.instructions);
|
|
8219
|
-
// Poll for completion
|
|
8220
|
-
let attempts = 0;
|
|
8221
|
-
const poll = setInterval(async () => {
|
|
8222
|
-
attempts++;
|
|
8223
|
-
if (attempts > 60) { clearInterval(poll); if (waitEl) waitEl.style.display = 'none'; return; }
|
|
8224
|
-
try {
|
|
8225
|
-
// Only `connected` is read: the compact view (connected providers), not the whole catalog every 2 s.
|
|
8226
|
-
const pr = await fetch('/api/opencode/providers/full?view=compact');
|
|
8227
|
-
const pd = await pr.json();
|
|
8228
|
-
const connected = pd.data?.connected || [];
|
|
8229
|
-
if (connected.includes(provId)) {
|
|
8230
|
-
clearInterval(poll);
|
|
8231
|
-
if (waitEl) waitEl.style.display = 'none';
|
|
8232
|
-
showCCToast(kit.tx('settings.redesign.opencode.connected', { provId: provId }));
|
|
8233
|
-
await refreshOcpProviders();
|
|
8234
|
-
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8235
|
-
}
|
|
8236
|
-
} catch {}
|
|
8237
|
-
}, 2000);
|
|
8413
|
+
const data = await resp.json().catch(() => ({}));
|
|
8414
|
+
const auth = data.data || {};
|
|
8415
|
+
if (resp.ok && data.ok !== false && auth.url) {
|
|
8416
|
+
window.open(auth.url, '_blank', 'noopener');
|
|
8417
|
+
showOcpOAuthWait(provId, providerName, auth);
|
|
8238
8418
|
} else {
|
|
8239
|
-
showCCToast(
|
|
8419
|
+
showCCToast(data.error || kit.tx('settings.redesign.opencode.oauthFailedNoAuthorizationUrlReturned'));
|
|
8240
8420
|
}
|
|
8241
8421
|
} catch (e) {
|
|
8242
8422
|
showCCToast(kit.tx('settings.redesign.opencode.oauthError', { message: e.message }));
|
|
@@ -8246,6 +8426,81 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8246
8426
|
});
|
|
8247
8427
|
}
|
|
8248
8428
|
|
|
8429
|
+
// ── A sign-in under way (OpenCode 2's attempt: `auto` finishes by itself, `code` with the code pasted) ──
|
|
8430
|
+
const _ocpOAuthPolls = new Map(); // provider id → timer
|
|
8431
|
+
function endOcpOAuth(provId) {
|
|
8432
|
+
clearInterval(_ocpOAuthPolls.get(provId));
|
|
8433
|
+
_ocpOAuthPolls.delete(provId);
|
|
8434
|
+
const waitEl = ocpProvidersEl?.querySelector(`.stg-ocp-oauth-wait[data-provider="${CSS.escape(provId)}"]`);
|
|
8435
|
+
if (waitEl) waitEl.style.display = 'none';
|
|
8436
|
+
}
|
|
8437
|
+
async function finishOcpOAuth(provId) {
|
|
8438
|
+
endOcpOAuth(provId);
|
|
8439
|
+
showCCToast(kit.tx('settings.redesign.opencode.connected', { provId: provId }));
|
|
8440
|
+
await refreshOcpProviders();
|
|
8441
|
+
document.dispatchEvent(new CustomEvent('ocp-providers-changed'));
|
|
8442
|
+
}
|
|
8443
|
+
function showOcpOAuthWait(provId, providerName, auth) {
|
|
8444
|
+
endOcpOAuth(provId);
|
|
8445
|
+
const waitEl = ocpProvidersEl?.querySelector(`.stg-ocp-oauth-wait[data-provider="${CSS.escape(provId)}"]`);
|
|
8446
|
+
const codeEl = waitEl?.querySelector('.stg-ocp-oauth-code');
|
|
8447
|
+
const codeRow = waitEl?.querySelector('.stg-ocp-oauth-code-row');
|
|
8448
|
+
const errEl = waitEl?.querySelector('.stg-ocp-key-error');
|
|
8449
|
+
if (waitEl) waitEl.style.display = '';
|
|
8450
|
+
if (codeEl) { codeEl.textContent = auth.instructions || ''; codeEl.style.display = auth.instructions ? '' : 'none'; }
|
|
8451
|
+
if (errEl) errEl.textContent = '';
|
|
8452
|
+
if (auth.method === 'code') {
|
|
8453
|
+
if (!codeRow) return;
|
|
8454
|
+
codeRow.style.display = '';
|
|
8455
|
+
const input = codeRow.querySelector('.stg-ocp-oauth-code-input');
|
|
8456
|
+
const submit = codeRow.querySelector('.stg-ocp-oauth-code-submit');
|
|
8457
|
+
input?.focus();
|
|
8458
|
+
const send = async () => {
|
|
8459
|
+
const code = input?.value?.trim();
|
|
8460
|
+
if (!code) return;
|
|
8461
|
+
submit.disabled = true;
|
|
8462
|
+
try {
|
|
8463
|
+
const resp = await fetch(`/api/opencode/oauth/${encodeURIComponent(provId)}/callback`, {
|
|
8464
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
8465
|
+
body: JSON.stringify({ attemptID: auth.attemptID, code }),
|
|
8466
|
+
});
|
|
8467
|
+
const data = await resp.json().catch(() => ({}));
|
|
8468
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
8469
|
+
input.value = '';
|
|
8470
|
+
await finishOcpOAuth(provId);
|
|
8471
|
+
} catch (e) {
|
|
8472
|
+
if (errEl) errEl.textContent = kit.tx('settings.redesign.opencode.oauthFailed', { message: e.message });
|
|
8473
|
+
} finally {
|
|
8474
|
+
submit.disabled = false;
|
|
8475
|
+
}
|
|
8476
|
+
};
|
|
8477
|
+
if (submit) submit.onclick = send;
|
|
8478
|
+
if (input) input.onkeydown = (event) => { if (event.key === 'Enter') { event.preventDefault(); send(); } };
|
|
8479
|
+
return;
|
|
8480
|
+
}
|
|
8481
|
+
if (codeRow) codeRow.style.display = 'none';
|
|
8482
|
+
if (!auth.attemptID) return;
|
|
8483
|
+
// Every 2 s for five minutes: then the attempt is let go (OpenCode's own attempt ends by itself).
|
|
8484
|
+
let attempts = 0;
|
|
8485
|
+
_ocpOAuthPolls.set(provId, setInterval(async () => {
|
|
8486
|
+
attempts++;
|
|
8487
|
+
if (attempts > 150) { endOcpOAuth(provId); showCCToast(kit.tx('settings.redesign.opencode.oauthGaveUp')); return; }
|
|
8488
|
+
try {
|
|
8489
|
+
const resp = await fetch(`/api/opencode/oauth/${encodeURIComponent(provId)}/status?attemptID=${encodeURIComponent(auth.attemptID)}`);
|
|
8490
|
+
const data = await resp.json().catch(() => ({}));
|
|
8491
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
8492
|
+
const status = data.data?.status;
|
|
8493
|
+
if (status === 'complete') await finishOcpOAuth(provId);
|
|
8494
|
+
else if (status === 'expired') { endOcpOAuth(provId); showCCToast(kit.tx('settings.redesign.opencode.oauthExpired')); }
|
|
8495
|
+
else if (status === 'failed') { endOcpOAuth(provId); showCCToast(kit.tx('settings.redesign.opencode.oauthFailed', { message: data.data?.message || status })); }
|
|
8496
|
+
} catch (e) {
|
|
8497
|
+
endOcpOAuth(provId);
|
|
8498
|
+
showCCToast(kit.tx('settings.redesign.opencode.oauthFailed', { message: e.message }));
|
|
8499
|
+
}
|
|
8500
|
+
}, 2000));
|
|
8501
|
+
}
|
|
8502
|
+
panelLife.signal.addEventListener('abort', () => { for (const provId of [..._ocpOAuthPolls.keys()]) endOcpOAuth(provId); });
|
|
8503
|
+
|
|
8249
8504
|
// ── Delegated card click-to-expand (survives re-renders) ──
|
|
8250
8505
|
if (ocpProvidersEl) {
|
|
8251
8506
|
ocpProvidersEl.addEventListener('click', (e) => {
|
|
@@ -8263,64 +8518,60 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8263
8518
|
}
|
|
8264
8519
|
|
|
8265
8520
|
// ── Tools/Permissions rendering ──
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
const
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8521
|
+
// What each of OpenCode's own tools may do: the rules of OpenCode's global opencode.json
|
|
8522
|
+
// (GET /api/opencode/config: `actions`, `tools`), read and written there whether or not OpenCode runs.
|
|
8523
|
+
const OCP_EFFECTS = ['allow', 'ask', 'deny'];
|
|
8524
|
+
if (ocpToolsEl) {
|
|
8525
|
+
wireDropdowns(ocpToolsEl, { signal: panelLife.signal });
|
|
8526
|
+
ocpToolsEl.addEventListener('change', async (event) => {
|
|
8527
|
+
const input = event.target;
|
|
8528
|
+
const action = input?.dataset?.tool;
|
|
8529
|
+
if (!action || !OCP_EFFECTS.includes(input.value)) return;
|
|
8530
|
+
try {
|
|
8531
|
+
const resp = await fetch('/api/opencode/config', {
|
|
8532
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
|
8533
|
+
body: JSON.stringify({ tools: { [action]: input.value } }),
|
|
8534
|
+
});
|
|
8535
|
+
const data = await resp.json().catch(() => ({}));
|
|
8536
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
8537
|
+
applyOcpConfig(data.data);
|
|
8538
|
+
} catch (err) {
|
|
8539
|
+
showCCToast(kit.tx('settings.redesign.opencode.saveFailed', { message: err.message }));
|
|
8540
|
+
renderToolPermissions();
|
|
8283
8541
|
}
|
|
8542
|
+
}, { signal: panelLife.signal });
|
|
8543
|
+
}
|
|
8284
8544
|
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8545
|
+
function renderToolPermissionsFrom(view) {
|
|
8546
|
+
if (!ocpToolsEl) return;
|
|
8547
|
+
const actions = Array.isArray(view?.actions) ? view.actions : [];
|
|
8548
|
+
if (!actions.length) {
|
|
8549
|
+
ocpToolsEl.innerHTML = '<div class="settings-hint" style="opacity:0.4">' + kit.te('settings.redesign.opencode.noToolsAvailable') + '</div>';
|
|
8550
|
+
return;
|
|
8551
|
+
}
|
|
8552
|
+
const tools = view.tools || {};
|
|
8553
|
+
const labels = { allow: kit.tx('settings.redesign.opencode.allow'), ask: kit.tx('settings.redesign.opencode.ask'), deny: kit.tx('settings.redesign.opencode.deny') };
|
|
8554
|
+
ocpToolsEl.innerHTML = `<div class="stg-tool-grid">${actions.map((action) => {
|
|
8555
|
+
const current = OCP_EFFECTS.includes(tools[action]) ? tools[action] : 'allow';
|
|
8556
|
+
const id = `stg-ocp-tool-${String(action).replace(/[^a-z0-9_-]/gi, '-')}`;
|
|
8557
|
+
return `<div class="stg-tool-row">
|
|
8558
|
+
<span class="stg-tool-name">${esc(action)}</span>
|
|
8559
|
+
${dropdownHTML(id, OCP_EFFECTS.map((effect) => ({ value: effect, label: labels[effect], selected: effect === current })), {
|
|
8560
|
+
classes: 'stg-dropdown stg-dropdown-inline stg-ocp-tool-perm',
|
|
8561
|
+
label: kit.tx('settings.redesign.opencode.permissionFor', { tool: action }),
|
|
8562
|
+
inputAttrs: `data-tool="${esc(action)}"`,
|
|
8563
|
+
})}
|
|
8298
8564
|
</div>`;
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
if (val === 'deny') { newTools[tid] = false; delete newPerms[tid]; }
|
|
8310
|
-
else if (val === 'ask') { delete newTools[tid]; newPerms[tid] = 'ask'; }
|
|
8311
|
-
else { delete newTools[tid]; delete newPerms[tid]; }
|
|
8312
|
-
patchBody.permission = newPerms;
|
|
8313
|
-
patchBody.tools = newTools;
|
|
8314
|
-
try {
|
|
8315
|
-
await fetch('/api/opencode/config', {
|
|
8316
|
-
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
|
8317
|
-
body: JSON.stringify(patchBody),
|
|
8318
|
-
});
|
|
8319
|
-
_ocpConfig.permission = newPerms;
|
|
8320
|
-
_ocpConfig.tools = newTools;
|
|
8321
|
-
} catch {}
|
|
8322
|
-
});
|
|
8323
|
-
});
|
|
8565
|
+
}).join('')}</div>`;
|
|
8566
|
+
}
|
|
8567
|
+
|
|
8568
|
+
async function renderToolPermissions() {
|
|
8569
|
+
if (!ocpToolsEl) return;
|
|
8570
|
+
try {
|
|
8571
|
+
const resp = await fetch('/api/opencode/config');
|
|
8572
|
+
const data = await resp.json();
|
|
8573
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
8574
|
+
applyOcpConfig(data.data);
|
|
8324
8575
|
} catch {
|
|
8325
8576
|
ocpToolsEl.innerHTML = '<div class="settings-hint" style="opacity:0.4">' + kit.te('settings.redesign.opencode.failedToLoadTools') + '</div>';
|
|
8326
8577
|
}
|
|
@@ -8338,155 +8589,37 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8338
8589
|
return null;
|
|
8339
8590
|
}
|
|
8340
8591
|
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
const
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
const isActive = name === active;
|
|
8359
|
-
thead += `<th class="stg-mx-profile-th${isActive ? ' active' : ''}" data-profile="${esc(name)}">
|
|
8360
|
-
<div class="stg-mx-profile-head" data-profile="${esc(name)}">
|
|
8361
|
-
<span class="stg-mx-profile-dot${isActive ? ' on' : ''}"></span>
|
|
8362
|
-
<span class="stg-mx-profile-name">${esc(prof.label || name)}</span>
|
|
8363
|
-
${isActive ? '' : `<button class="stg-mx-profile-del" data-profile="${esc(name)}" title="${kit.te('settings.redesign.mcp.deleteProfile')}">\u00d7</button>`}
|
|
8364
|
-
</div>
|
|
8365
|
-
</th>`;
|
|
8366
|
-
}
|
|
8367
|
-
thead += '<th class="stg-mx-add-th"><button class="stg-mx-add-btn" title="' + kit.te('settings.redesign.mcp.newProfile') + '">+</button></th></tr>';
|
|
8368
|
-
|
|
8369
|
-
// Build table body — switchable groups
|
|
8370
|
-
let tbody = '';
|
|
8371
|
-
for (const [key, info] of switchable) {
|
|
8372
|
-
tbody += `<tr>`;
|
|
8373
|
-
tbody += `<td class="stg-mx-label-td"><div class="stg-mx-label-inner">
|
|
8374
|
-
<span class="stg-mx-grp-name">${esc(info.label)}</span>
|
|
8375
|
-
<span class="stg-mx-grp-count">${info.tools}</span>
|
|
8376
|
-
</div></td>`;
|
|
8377
|
-
for (const [profName, prof] of profEntries) {
|
|
8378
|
-
const isOn = (prof.groups || []).includes(key);
|
|
8379
|
-
tbody += `<td class="stg-mx-cell" data-profile="${esc(profName)}" data-group="${esc(key)}" data-kind="group">
|
|
8380
|
-
<span class="stg-mx-toggle${isOn ? ' on' : ''}"></span>
|
|
8381
|
-
</td>`;
|
|
8382
|
-
}
|
|
8383
|
-
tbody += '<td class="stg-mx-add-spacer"></td></tr>';
|
|
8384
|
-
}
|
|
8385
|
-
|
|
8386
|
-
wrap.innerHTML = `<div class="stg-mcp-matrix-scroll"><table id="stg-mcp-matrix"><thead>${thead}</thead><tbody>${tbody}</tbody></table></div>`;
|
|
8387
|
-
|
|
8388
|
-
// --- Event handlers ---
|
|
8389
|
-
|
|
8390
|
-
// Toggle cells
|
|
8391
|
-
wrap.querySelectorAll('.stg-mx-cell').forEach(cell => {
|
|
8392
|
-
cell.addEventListener('click', async () => {
|
|
8393
|
-
const profName = cell.dataset.profile;
|
|
8394
|
-
const prof = _mcpRegistry.profiles[profName];
|
|
8395
|
-
if (!prof) return;
|
|
8396
|
-
const kind = cell.dataset.kind;
|
|
8397
|
-
if (kind === 'group') {
|
|
8398
|
-
const g = cell.dataset.group;
|
|
8399
|
-
if ((prof.groups || []).includes(g)) {
|
|
8400
|
-
prof.groups = prof.groups.filter(x => x !== g);
|
|
8401
|
-
} else {
|
|
8402
|
-
prof.groups = [...(prof.groups || []), g];
|
|
8403
|
-
}
|
|
8404
|
-
} else {
|
|
8405
|
-
const s = cell.dataset.server;
|
|
8406
|
-
if ((prof.servers || []).includes(s)) {
|
|
8407
|
-
prof.servers = prof.servers.filter(x => x !== s);
|
|
8408
|
-
} else {
|
|
8409
|
-
prof.servers = [...(prof.servers || []), s];
|
|
8410
|
-
}
|
|
8411
|
-
}
|
|
8412
|
-
await fetch(`/api/mcp/registry/profiles/${encodeURIComponent(profName)}`, {
|
|
8413
|
-
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
8414
|
-
body: JSON.stringify(prof),
|
|
8415
|
-
});
|
|
8416
|
-
await refreshMcpTab();
|
|
8417
|
-
});
|
|
8418
|
-
});
|
|
8419
|
-
|
|
8420
|
-
// Activate profile on name click
|
|
8421
|
-
wrap.querySelectorAll('.stg-mx-profile-head').forEach(head => {
|
|
8422
|
-
head.addEventListener('click', async (e) => {
|
|
8423
|
-
if (e.target.closest('.stg-mx-profile-del')) return;
|
|
8424
|
-
const name = head.dataset.profile;
|
|
8425
|
-
if (name === _mcpRegistry.activeProfile) return;
|
|
8426
|
-
await fetch('/api/mcp/registry/activate', {
|
|
8427
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
8428
|
-
body: JSON.stringify({ profile: name }),
|
|
8429
|
-
});
|
|
8430
|
-
showCCToast(kit.tx('settings.redesign.mcp.profileSwitchedTo', { name: name }));
|
|
8431
|
-
await refreshMcpTab();
|
|
8432
|
-
});
|
|
8433
|
-
});
|
|
8434
|
-
|
|
8435
|
-
// Delete profile
|
|
8436
|
-
wrap.querySelectorAll('.stg-mx-profile-del').forEach(btn => {
|
|
8437
|
-
btn.addEventListener('click', async (e) => {
|
|
8438
|
-
e.stopPropagation();
|
|
8439
|
-
await fetch(`/api/mcp/registry/profiles/${encodeURIComponent(btn.dataset.profile)}`, { method: 'DELETE' });
|
|
8440
|
-
await refreshMcpTab();
|
|
8441
|
-
});
|
|
8592
|
+
let _settingsToolCatalog = null;
|
|
8593
|
+
const defaultToolsWrap = overlay.querySelector('#stg-mcp-default-tools');
|
|
8594
|
+
if (defaultToolsWrap) wireDropdowns(defaultToolsWrap, { signal: panelLife.signal });
|
|
8595
|
+
function renderDefaultTools() {
|
|
8596
|
+
const wrap = overlay.querySelector('#stg-mcp-default-tools');
|
|
8597
|
+
if (!wrap) return;
|
|
8598
|
+
wrap.innerHTML = _settingsToolCatalog ? toolPickerHTML('stg-default-tools', _settingsToolCatalog.defaultTools, { catalog: _settingsToolCatalog, provider: 'all', label: toolWord('defaultTools') }) : '';
|
|
8599
|
+
wrap.querySelector('#stg-default-tools')?.addEventListener('change', async (event) => {
|
|
8600
|
+
const tools = readToolPicker(event.target, { inherit: false });
|
|
8601
|
+
try {
|
|
8602
|
+
const response = await fetch('/api/mcp/tools/default', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tools }) });
|
|
8603
|
+
const result = await response.json();
|
|
8604
|
+
if (!response.ok || !result.ok) throw new Error(result.error || toolWord('saveFailed'));
|
|
8605
|
+
_settingsToolCatalog.defaultTools = result.tools;
|
|
8606
|
+
showCCToast(toolWord('saved'));
|
|
8607
|
+
} catch (error) { showCCToast(error.message || toolWord('saveFailed')); }
|
|
8608
|
+
renderDefaultTools();
|
|
8442
8609
|
});
|
|
8443
|
-
|
|
8444
|
-
// Inline add profile
|
|
8445
|
-
const addBtn = wrap.querySelector('.stg-mx-add-btn');
|
|
8446
|
-
if (addBtn) {
|
|
8447
|
-
addBtn.addEventListener('click', () => {
|
|
8448
|
-
const th = addBtn.closest('th');
|
|
8449
|
-
th.innerHTML = `<input class="stg-mx-inline-input" type="text" placeholder="${kit.te('settings.redesign.mcp.name')}" autofocus>`;
|
|
8450
|
-
const input = th.querySelector('input');
|
|
8451
|
-
input.focus();
|
|
8452
|
-
const create = async () => {
|
|
8453
|
-
const name = input.value.trim();
|
|
8454
|
-
if (!name) { await refreshMcpTab(); return; }
|
|
8455
|
-
await fetch(`/api/mcp/registry/profiles/${encodeURIComponent(name)}`, {
|
|
8456
|
-
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
8457
|
-
body: JSON.stringify({ label: name, groups: [], servers: [] }),
|
|
8458
|
-
});
|
|
8459
|
-
await refreshMcpTab();
|
|
8460
|
-
};
|
|
8461
|
-
input.addEventListener('keydown', (e) => {
|
|
8462
|
-
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); create(); }
|
|
8463
|
-
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); refreshMcpTab(); }
|
|
8464
|
-
});
|
|
8465
|
-
input.addEventListener('blur', create);
|
|
8466
|
-
});
|
|
8467
|
-
}
|
|
8468
8610
|
}
|
|
8469
8611
|
|
|
8470
8612
|
function renderAlwaysOn() {
|
|
8471
8613
|
const el = overlay.querySelector('#stg-mcp-always-on');
|
|
8472
|
-
if (!el
|
|
8473
|
-
|
|
8474
|
-
const alwaysOn = Object.entries(tg).filter(([, info]) => info.alwaysOn);
|
|
8475
|
-
if (!alwaysOn.length) { el.innerHTML = ''; return; }
|
|
8476
|
-
el.innerHTML = `
|
|
8477
|
-
<div class="stg-mcp-section-header">${kit.te('settings.redesign.mcp.alwaysOn')}</div>
|
|
8478
|
-
<div class="stg-mx-always-on-row">
|
|
8479
|
-
${alwaysOn.map(([, info]) =>
|
|
8480
|
-
`<span class="stg-mx-always-chip">${esc(info.label)}<span class="stg-mx-grp-count">${info.tools}</span></span>`
|
|
8481
|
-
).join('')}
|
|
8482
|
-
</div>`;
|
|
8614
|
+
if (!el) return;
|
|
8615
|
+
el.innerHTML = _settingsToolCatalog ? `<div class="stg-mcp-section-header">${esc(toolWord('alwaysOn'))}</div><div class="stg-mx-always-on-row">${_settingsToolCatalog.alwaysOnTools.map(name => `<span class="stg-mx-always-chip">${esc(name)}</span>`).join('')}</div>` : '';
|
|
8483
8616
|
}
|
|
8484
8617
|
|
|
8485
8618
|
function renderExtServerList() {
|
|
8486
8619
|
const el = overlay.querySelector('#stg-mcp-ext-servers');
|
|
8487
8620
|
if (!el || !_mcpRegistry) return;
|
|
8488
|
-
const servers = _mcpRegistry.servers || {};
|
|
8489
|
-
const
|
|
8621
|
+
const servers = { ...(_mcpRegistry.servers || {}) };
|
|
8622
|
+
for (const row of _settingsToolCatalog?.servers || []) { const name = row.ref.slice(4); if (!servers[name]) servers[name] = { type: toolWord('configuredExternally'), discovered: true }; }
|
|
8490
8623
|
const entries = Object.entries(servers).filter(([, info]) => !info.builtin);
|
|
8491
8624
|
if (!entries.length) {
|
|
8492
8625
|
el.innerHTML = '<div class="stg-mcp-empty-note">' + kit.te('settings.redesign.mcp.noExternalServersConfigured') + '</div>';
|
|
@@ -8501,38 +8634,31 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8501
8634
|
<span class="stg-mx-ext-name">${esc(name)}</span>
|
|
8502
8635
|
<span class="stg-mx-ext-type">${esc(info.type || '?')}</span>
|
|
8503
8636
|
</div>
|
|
8504
|
-
<div class="stg-mx-ext-sub">${
|
|
8637
|
+
<div class="stg-mx-ext-sub">${esc(toolWord('agentAccessHelp'))}</div>
|
|
8505
8638
|
</div>
|
|
8506
|
-
|
|
8639
|
+
${info.discovered ? '' : `<button class="stg-mx-srv-del stg-mx-ext-remove" ${kit.inv('MCP004')} data-server="${esc(name)}" title="${kit.te('settings.redesign.mcp.removeServer')}">\u00d7</button>`}
|
|
8507
8640
|
</div>
|
|
8508
|
-
<div class="stg-
|
|
8509
|
-
${
|
|
8510
|
-
const isOn = (prof.servers || []).includes(name);
|
|
8511
|
-
return `<button class="stg-mx-ext-toggle${isOn ? ' on' : ''}" data-profile="${esc(profName)}" data-server="${esc(name)}" title="${isOn ? kit.te('settings.redesign.mcp.disable') : kit.te('settings.redesign.mcp.enable')} ${esc(name)} ${kit.te('settings.redesign.mcp.for')} ${esc(prof.label || profName)}">
|
|
8512
|
-
<span class="stg-mx-ext-toggle-name">${esc(prof.label || profName)}</span>
|
|
8513
|
-
</button>`;
|
|
8514
|
-
}).join('')}
|
|
8641
|
+
<div class="stg-field"><span>${esc(toolWord('agentAccess'))}</span>
|
|
8642
|
+
<button type="button" class="cc-toggle${_mcpRegistry.agentAccess?.[name] === false ? '' : ' on'}" role="switch" ${kit.inv('MCP005')} data-agent-server="${esc(name)}" aria-label="${esc(toolWord('agentAccess'))}: ${esc(name)}" aria-checked="${_mcpRegistry.agentAccess?.[name] !== false}"></button>
|
|
8515
8643
|
</div>
|
|
8516
8644
|
</div>`
|
|
8517
8645
|
).join('');
|
|
8518
8646
|
|
|
8519
|
-
el.querySelectorAll('
|
|
8520
|
-
|
|
8521
|
-
const
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
});
|
|
8535
|
-
await refreshMcpTab();
|
|
8647
|
+
el.querySelectorAll('[data-agent-server]').forEach(input => {
|
|
8648
|
+
input.addEventListener('click', async () => {
|
|
8649
|
+
const enabled = input.getAttribute('aria-checked') !== 'true';
|
|
8650
|
+
input.disabled = true;
|
|
8651
|
+
try {
|
|
8652
|
+
const response = await fetch(`/api/mcp/registry/agent-access/${encodeURIComponent(input.dataset.agentServer)}`, {
|
|
8653
|
+
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled }),
|
|
8654
|
+
});
|
|
8655
|
+
const result = await response.json();
|
|
8656
|
+
if (!response.ok || !result.ok) throw new Error(result.error || toolWord('saveFailed'));
|
|
8657
|
+
_mcpRegistry.agentAccess = result.agentAccess;
|
|
8658
|
+
input.classList.toggle('on', enabled);
|
|
8659
|
+
input.setAttribute('aria-checked', String(enabled));
|
|
8660
|
+
} catch (error) { showCCToast(error.message || toolWord('saveFailed')); }
|
|
8661
|
+
finally { input.disabled = false; }
|
|
8536
8662
|
});
|
|
8537
8663
|
});
|
|
8538
8664
|
|
|
@@ -8687,7 +8813,8 @@ async function openSettingsModalNow(options = {}) {
|
|
|
8687
8813
|
|
|
8688
8814
|
async function refreshMcpTab() {
|
|
8689
8815
|
await fetchMcpRegistry();
|
|
8690
|
-
|
|
8816
|
+
_settingsToolCatalog = await fetchToolCatalog(true);
|
|
8817
|
+
renderDefaultTools();
|
|
8691
8818
|
renderAlwaysOn();
|
|
8692
8819
|
renderExtServerList();
|
|
8693
8820
|
renderPluginsList();
|
|
@@ -9199,55 +9326,62 @@ async function openSettingsModalNow(options = {}) {
|
|
|
9199
9326
|
});
|
|
9200
9327
|
|
|
9201
9328
|
// ── Config save helpers ──
|
|
9329
|
+
// PATCH /api/opencode/config (OpenCode 2's global opencode.json): the answer is the configuration as it is now.
|
|
9202
9330
|
async function saveOcpConfig(patch) {
|
|
9203
9331
|
try {
|
|
9204
|
-
await fetch('/api/opencode/config', {
|
|
9332
|
+
const resp = await fetch('/api/opencode/config', {
|
|
9205
9333
|
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
|
9206
9334
|
body: JSON.stringify(patch),
|
|
9207
9335
|
});
|
|
9208
|
-
|
|
9336
|
+
const data = await resp.json().catch(() => ({}));
|
|
9337
|
+
if (!resp.ok || data.ok === false) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
9338
|
+
applyOcpConfig(data.data);
|
|
9339
|
+
return true;
|
|
9340
|
+
} catch (err) {
|
|
9341
|
+
showCCToast(kit.tx('settings.redesign.opencode.saveFailed', { message: err.message }));
|
|
9342
|
+
loadOcpConfig();
|
|
9343
|
+
return false;
|
|
9344
|
+
}
|
|
9209
9345
|
}
|
|
9210
9346
|
|
|
9211
9347
|
// ── Compaction handlers ──
|
|
9212
9348
|
const compactAutoEl = overlay.querySelector('#stg-ocp-compact-auto');
|
|
9213
|
-
const compactPruneEl = overlay.querySelector('#stg-ocp-compact-prune');
|
|
9214
9349
|
const compactReservedEl = overlay.querySelector('#stg-ocp-compact-reserved');
|
|
9215
9350
|
|
|
9351
|
+
// An emptied field is OpenCode's own default again (buffer: null).
|
|
9216
9352
|
function saveCompaction() {
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
reserved: Number(compactReservedEl?.value) || 10000,
|
|
9222
|
-
},
|
|
9223
|
-
});
|
|
9353
|
+
const raw = String(compactReservedEl?.value ?? '').trim();
|
|
9354
|
+
const buffer = raw === '' ? null : Math.round(Number(raw));
|
|
9355
|
+
if (buffer !== null && !(Number.isFinite(buffer) && buffer >= 0 && buffer <= 1_000_000)) { loadOcpConfig(); return; }
|
|
9356
|
+
saveOcpConfig({ compaction: { auto: compactAutoEl?.checked ?? true, buffer } });
|
|
9224
9357
|
}
|
|
9225
9358
|
compactAutoEl?.addEventListener('change', saveCompaction);
|
|
9226
|
-
compactPruneEl?.addEventListener('change', saveCompaction);
|
|
9227
9359
|
compactReservedEl?.addEventListener('change', saveCompaction);
|
|
9228
9360
|
|
|
9229
|
-
// ── Snapshot toggle (
|
|
9361
|
+
// ── Snapshot toggle (OpenCode's `snapshots`, read at once) ──
|
|
9230
9362
|
const snapshotToggle = overlay.querySelector('#stg-ocp-snapshot-toggle');
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9363
|
+
snapshotToggle?.addEventListener('click', () => {
|
|
9364
|
+
const next = !snapshotToggle.classList.contains('on');
|
|
9365
|
+
snapshotToggle.classList.toggle('on', next); // optimistic; the answer (or a failed save's reload) sets it
|
|
9366
|
+
saveOcpConfig({ snapshots: next });
|
|
9367
|
+
});
|
|
9368
|
+
|
|
9369
|
+
// The configuration as GET / PATCH /api/opencode/config give it: the file SynaBun edits, compaction,
|
|
9370
|
+
// snapshots and the tools' permissions.
|
|
9371
|
+
function applyOcpConfig(view) {
|
|
9372
|
+
if (!view || typeof view !== 'object') return;
|
|
9373
|
+
_ocpConfig = view;
|
|
9374
|
+
const comp = view.compaction || {};
|
|
9375
|
+
if (compactAutoEl) compactAutoEl.checked = comp.auto !== false;
|
|
9376
|
+
if (compactReservedEl && document.activeElement !== compactReservedEl) compactReservedEl.value = Number.isFinite(comp.buffer) ? String(comp.buffer) : '';
|
|
9377
|
+
snapshotToggle?.classList.toggle('on', view.snapshots !== false);
|
|
9378
|
+
if (ocpConfigPathEl && view.file) ocpConfigPathEl.textContent = view.file;
|
|
9379
|
+
const jsonc = (Array.isArray(view.files) ? view.files : []).find((file) => file?.exists && /\.jsonc$/i.test(String(file.path || '')));
|
|
9380
|
+
if (ocpConfigJsoncEl) {
|
|
9381
|
+
ocpConfigJsoncEl.hidden = !jsonc;
|
|
9382
|
+
ocpConfigJsoncEl.innerHTML = jsonc ? kit.th('settings.redesign.opencode.jsoncWins', { path: `<code class="stg-code">${esc(jsonc.path)}</code>` }) : '';
|
|
9383
|
+
}
|
|
9384
|
+
renderToolPermissionsFrom(view);
|
|
9251
9385
|
}
|
|
9252
9386
|
|
|
9253
9387
|
// Capability filter pills — use CSS vars, not inline color
|
|
@@ -9285,28 +9419,16 @@ async function openSettingsModalNow(options = {}) {
|
|
|
9285
9419
|
});
|
|
9286
9420
|
|
|
9287
9421
|
// ── Open config file in Synabun's editor ──
|
|
9422
|
+
// OpenCode 2's global opencode.json (made with its $schema when there is none yet).
|
|
9288
9423
|
overlay.querySelector('#stg-ocp-open-config')?.addEventListener('click', async () => {
|
|
9289
9424
|
try {
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
if (revealResp.ok && revealData?.path) {
|
|
9297
|
-
const basePath = String(revealData.path).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
9298
|
-
filePath = basePath.endsWith('/config.json') ? basePath : `${basePath}/config.json`;
|
|
9299
|
-
} else {
|
|
9300
|
-
const fileResp = await fetch('/api/opencode/config/file');
|
|
9301
|
-
const fileData = await fileResp.json();
|
|
9302
|
-
if (!fileResp.ok || !fileData?.path) {
|
|
9303
|
-
showCCToast(fileData?.error || revealData?.error || kit.tx('settings.redesign.opencode.failedToOpenOpencodeConfig'));
|
|
9304
|
-
return;
|
|
9305
|
-
}
|
|
9306
|
-
filePath = fileData.path;
|
|
9425
|
+
const fileResp = await fetch('/api/opencode/config/file');
|
|
9426
|
+
const fileData = await fileResp.json().catch(() => ({}));
|
|
9427
|
+
if (!fileResp.ok || !fileData?.path) {
|
|
9428
|
+
showCCToast(fileData?.error || kit.tx('settings.redesign.opencode.failedToOpenOpencodeConfig'));
|
|
9429
|
+
return;
|
|
9307
9430
|
}
|
|
9308
|
-
|
|
9309
|
-
emit('open-file-editor', { filePath });
|
|
9431
|
+
emit('open-file-editor', { filePath: fileData.path });
|
|
9310
9432
|
} catch (err) {
|
|
9311
9433
|
console.error('[settings] Failed to open OpenCode config:', err);
|
|
9312
9434
|
showCCToast(kit.tx('settings.redesign.opencode.failedToOpenOpencodeConfig'));
|
|
@@ -9334,38 +9456,46 @@ async function openSettingsModalNow(options = {}) {
|
|
|
9334
9456
|
} catch {}
|
|
9335
9457
|
}
|
|
9336
9458
|
|
|
9337
|
-
// ── Load config values into UI ──
|
|
9459
|
+
// ── Load config values into UI (read from the files: OpenCode need not run) ──
|
|
9338
9460
|
async function loadOcpConfig() {
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9461
|
+
await renderToolPermissions();
|
|
9462
|
+
}
|
|
9463
|
+
|
|
9464
|
+
// The OpenCode SynaBun found: its version, or why SynaBun does not drive it and how to get OpenCode 2.
|
|
9465
|
+
function renderOcpInstall(installed) {
|
|
9466
|
+
const note = describeOpencodeInstall(installed, { th: kit.th, tx: kit.tx });
|
|
9467
|
+
if (ocpInstallEl) {
|
|
9468
|
+
ocpInstallEl.hidden = !note.html;
|
|
9469
|
+
ocpInstallEl.dataset.tone = note.tone;
|
|
9470
|
+
ocpInstallEl.innerHTML = note.html;
|
|
9471
|
+
}
|
|
9472
|
+
return note;
|
|
9348
9473
|
}
|
|
9349
9474
|
|
|
9350
9475
|
// ── Master refresh ──
|
|
9351
9476
|
async function refreshOcpStatus() {
|
|
9352
9477
|
setOcpStatus(kit.tx('settings.redesign.opencode.checking'), 'dim');
|
|
9478
|
+
loadOcpConfig();
|
|
9353
9479
|
try {
|
|
9354
9480
|
const resp = await fetch('/api/opencode/status');
|
|
9355
9481
|
const data = await resp.json();
|
|
9482
|
+
const note = renderOcpInstall(data.installed);
|
|
9356
9483
|
if (data.clearingHistory) {
|
|
9357
9484
|
setOcpStatus(kit.tx('settings.redesign.opencode.clearingHistory'), 'warn');
|
|
9358
9485
|
} else if (data.running) {
|
|
9359
9486
|
setOcpStatus(kit.tx('settings.redesign.opencode.runningVersion', { version: data.version ? ' v' + data.version : '' }), 'ok');
|
|
9360
|
-
await
|
|
9361
|
-
refreshOcpProviders(),
|
|
9362
|
-
loadOcpConfig(),
|
|
9363
|
-
renderToolPermissions(),
|
|
9364
|
-
]);
|
|
9487
|
+
await refreshOcpProviders();
|
|
9365
9488
|
} else {
|
|
9366
|
-
setOcpStatus(kit.tx('settings.redesign.opencode.
|
|
9489
|
+
if (note.status === 'notSupported') setOcpStatus(kit.tx('settings.redesign.opencode.notSupportedStatus'), 'err');
|
|
9490
|
+
else if (note.status === 'notInstalled') setOcpStatus(kit.tx('settings.redesign.opencode.notInstalledStatus'), 'dim');
|
|
9491
|
+
else setOcpStatus(kit.tx('settings.redesign.opencode.offline'), 'dim');
|
|
9367
9492
|
if (ocpProvidersEl) ocpProvidersEl.innerHTML = '<div class="settings-hint" style="opacity:0.4">' + kit.te('settings.redesign.opencode.startTheServerToSeeProviders') + '</div>';
|
|
9368
|
-
|
|
9493
|
+
// Something else stands in the way (another serve on the port, a serve that did not answer): the server's sentence.
|
|
9494
|
+
if (!note.html && data.problem && ocpInstallEl) {
|
|
9495
|
+
ocpInstallEl.hidden = false;
|
|
9496
|
+
ocpInstallEl.dataset.tone = 'warn';
|
|
9497
|
+
ocpInstallEl.textContent = data.problem;
|
|
9498
|
+
}
|
|
9369
9499
|
}
|
|
9370
9500
|
} catch (err) {
|
|
9371
9501
|
setOcpStatus(kit.tx('settings.redesign.opencode.error'), 'err');
|
|
@@ -9635,3 +9765,5 @@ export function initSettings() {
|
|
|
9635
9765
|
window.checkSyncStatus = checkSyncStatus;
|
|
9636
9766
|
window.copySyncPrompt = copySyncPrompt;
|
|
9637
9767
|
}
|
|
9768
|
+
|
|
9769
|
+
configureToolPickerTranslation((key, params) => kit.tx(key, params));
|