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
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// open. `synabun.route` events (phase card|auto|decided|pending|expired|
|
|
17
17
|
// declined|cancelled) update the line in place by routeId.
|
|
18
18
|
|
|
19
|
+
import { fetchClaudeAccounts, fetchCodexAccounts, getEffortLevelsForModel } from '../agent-runtime-options.js';
|
|
19
20
|
import { getProviderMeta } from '../provider-icons.js';
|
|
20
21
|
import { fetchManageCatalog, patchHiddenModels } from './asst-api.js';
|
|
21
22
|
import { catalogModelRow, fmtModelPrice, hiddenModelFor, ICON_CARET, invalidateModelCatalog, openModelMenu } from './asst-brain-picker.js';
|
|
@@ -107,7 +108,7 @@ function normalizeRouteOption(o) {
|
|
|
107
108
|
provider: normalizeProvider(src.provider, str(src.provider) || null),
|
|
108
109
|
model: str(src.model),
|
|
109
110
|
effort: str(src.effort) || null,
|
|
110
|
-
...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, account: obj(src.account), accountError: obj(src.accountError) } : {}),
|
|
111
|
+
...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, accountChoice: src.accountChoice || null, account: obj(src.account), accountError: obj(src.accountError) } : {}),
|
|
111
112
|
label: str(src.label),
|
|
112
113
|
badge: str(src.badge) || null,
|
|
113
114
|
tier: str(src.tier) || null,
|
|
@@ -303,6 +304,7 @@ export function buildRouteResponse(n, choice = {}) {
|
|
|
303
304
|
provider: normalizeProvider(target.provider, str(target.provider) || null),
|
|
304
305
|
model: str(target.model),
|
|
305
306
|
effort: str(target.effort) || null,
|
|
307
|
+
accountId: str(target.accountId) || null,
|
|
306
308
|
};
|
|
307
309
|
}
|
|
308
310
|
return out;
|
|
@@ -804,11 +806,12 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
804
806
|
row.classList.toggle('selected', on);
|
|
805
807
|
row.tabIndex = on ? 0 : -1;
|
|
806
808
|
}
|
|
809
|
+
syncFields();
|
|
807
810
|
runBtn.disabled = state.pending || state.resolved || state.expired || !state.selectedId;
|
|
808
811
|
}
|
|
809
812
|
|
|
810
813
|
function select(id, { focus = false } = {}) {
|
|
811
|
-
if (state.pending || state.resolved) return;
|
|
814
|
+
if (state.pending || state.resolved || state.expired) return;
|
|
812
815
|
const o = allOptions().find(x => x.id === id);
|
|
813
816
|
if (!o || o.disabled) return;
|
|
814
817
|
state.selectedId = id;
|
|
@@ -816,8 +819,62 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
816
819
|
if (focus) list.querySelector(`.asst-route-option[data-option-id="${CSS.escape(id)}"]`)?.focus();
|
|
817
820
|
}
|
|
818
821
|
|
|
819
|
-
//
|
|
820
|
-
const
|
|
822
|
+
// Task-local controls; these never edit the routing preference table.
|
|
823
|
+
const fields = el('div', 'asst-route-fields');
|
|
824
|
+
function taskField(className) {
|
|
825
|
+
const button = el('button', `asst-dd ${className}`);
|
|
826
|
+
button.type = 'button';
|
|
827
|
+
button.setAttribute('aria-haspopup', 'menu');
|
|
828
|
+
button.innerHTML = `<span class="asst-dd-label"></span><span class="asst-dd-caret" aria-hidden="true">${ICON_CARET}</span>`;
|
|
829
|
+
return button;
|
|
830
|
+
}
|
|
831
|
+
const effortBtn = taskField('asst-route-effort');
|
|
832
|
+
const accountBtn = taskField('asst-route-account');
|
|
833
|
+
const setFieldLabel = (button, text) => { button.querySelector('.asst-dd-label').textContent = text; button.title = text; };
|
|
834
|
+
const selected = () => allOptions().find(o => o.id === state.selectedId);
|
|
835
|
+
function editTarget(patch) {
|
|
836
|
+
if (state.pending || state.resolved || state.expired) return;
|
|
837
|
+
state.other = { ...selected(), accountId: selected()?.accountChoice || selected()?.accountId || null, accountChoice: Object.hasOwn(patch, 'accountId') ? patch.accountId : selected()?.accountChoice || null, disabled: false, disabledReason: null, accountError: null, ...patch, id: 'other', number: options.length + 1, badge: null, badgeText: tr('assistant.routes.badge.other', 'Your choice') };
|
|
838
|
+
state.selectedId = 'other';
|
|
839
|
+
renderOptions();
|
|
840
|
+
}
|
|
841
|
+
function syncFields() {
|
|
842
|
+
const target = selected();
|
|
843
|
+
setFieldLabel(otherBtn, target ? routeTargetLabel(target) : tr('assistant.routes.other', 'Other model…'));
|
|
844
|
+
otherBtn.setAttribute('aria-label', tr('assistant.brain.model', 'Model'));
|
|
845
|
+
otherBtn.disabled = state.pending || state.resolved || state.expired;
|
|
846
|
+
setFieldLabel(effortBtn, target?.effort || tr('assistant.routes.editorEffortAuto', 'Auto'));
|
|
847
|
+
effortBtn.setAttribute('aria-label', tr('assistant.brain.effort', 'Thinking effort'));
|
|
848
|
+
setFieldLabel(accountBtn, routeDisplayAccount(target, hooks.t) || tr('assistant.routes.account.workersDefault', 'Workers default'));
|
|
849
|
+
accountBtn.setAttribute('aria-label', tr('assistant.brain.account', 'Account profile'));
|
|
850
|
+
const levels = target ? getEffortLevelsForModel(target.provider, catalogModelRow(target.provider, target.model)) : [];
|
|
851
|
+
effortBtn.disabled = state.pending || state.resolved || state.expired || !levels.some(e => e.id !== 'off');
|
|
852
|
+
accountBtn.hidden = !['claude-code', 'codex'].includes(target?.provider);
|
|
853
|
+
accountBtn.disabled = state.pending || state.resolved || state.expired;
|
|
854
|
+
}
|
|
855
|
+
effortBtn.addEventListener('click', () => {
|
|
856
|
+
const target = selected();
|
|
857
|
+
const levels = getEffortLevelsForModel(target.provider, catalogModelRow(target.provider, target.model));
|
|
858
|
+
openMenu(effortBtn, { title: tr('assistant.brain.effort', 'Thinking effort'), placement: 'auto', items: [
|
|
859
|
+
{ kind: 'radio', label: tr('assistant.routes.editorEffortAuto', 'Auto'), selected: !target.effort, onSelect: () => editTarget({ effort: null }) },
|
|
860
|
+
...levels.filter(e => e.id !== 'off').map(e => ({ kind: 'radio', label: e.label || e.id, selected: target.effort === e.id, onSelect: () => editTarget({ effort: e.id }) })),
|
|
861
|
+
] });
|
|
862
|
+
});
|
|
863
|
+
accountBtn.addEventListener('click', async () => {
|
|
864
|
+
const target = selected();
|
|
865
|
+
try {
|
|
866
|
+
const rows = await (target.provider === 'codex' ? fetchCodexAccounts() : fetchClaudeAccounts());
|
|
867
|
+
if (!card.isConnected || state.pending || state.resolved || state.expired || selected() !== target) return;
|
|
868
|
+
openMenu(accountBtn, { title: tr('assistant.brain.account', 'Account profile'), placement: 'auto', items: [
|
|
869
|
+
{ kind: 'radio', label: tr('assistant.routes.account.workersDefault', 'Workers default'), selected: !(target.accountChoice || target.accountId), onSelect: () => editTarget({ accountId: null, account: null }) },
|
|
870
|
+
{ kind: 'radio', label: autoAccountLabel(hooks.t), selected: (target.accountChoice || target.accountId) === 'auto', onSelect: () => editTarget({ accountId: 'auto', account: null }) },
|
|
871
|
+
...(Array.isArray(rows) ? rows : []).map(a => ({ kind: 'radio', label: accountSummary(a, target.provider === 'codex' ? 'codex' : 'claude'), disabled: a.signedIn === false, selected: (target.accountChoice || target.accountId) === a.id, onSelect: () => editTarget({ accountId: a.id, account: a }) })),
|
|
872
|
+
] });
|
|
873
|
+
} catch (error) { hooks.onToast?.(error.message); }
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Model menu, scoped to this task.
|
|
877
|
+
const otherBtn = taskField('asst-route-other');
|
|
821
878
|
otherBtn.type = 'button';
|
|
822
879
|
otherBtn.setAttribute('aria-haspopup', 'menu');
|
|
823
880
|
otherBtn.hidden = !n.other.allowed;
|
|
@@ -827,21 +884,24 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
827
884
|
const brain = normalizeBrain(hooks.getBrain?.() || { provider: n.brain.provider || 'claude-code', model: n.brain.model });
|
|
828
885
|
openModelMenu(otherBtn, {
|
|
829
886
|
brain,
|
|
887
|
+
shouldOpen: () => card.isConnected && !state.pending && !state.resolved && !state.expired,
|
|
830
888
|
t: hooks.t,
|
|
831
889
|
title: tr('assistant.routes.otherTitle', 'Run on another model'),
|
|
832
890
|
placement: 'auto',
|
|
833
891
|
providers: n.other.providers.length ? n.other.providers.map(p => normalizeProvider(p, p)) : null,
|
|
834
|
-
current:
|
|
892
|
+
current: selected() ? { provider: selected().provider, model: selected().model } : null,
|
|
835
893
|
onToast: hooks.onToast,
|
|
836
894
|
// Image / video creation: only models that generate the medium (no custom id: its output is unknown).
|
|
837
895
|
...(n.requires ? { accept: (_p, m) => m?.outputs?.[n.requires] === true, allowCustom: false, emptyLabel: tr('assistant.routes.noCapableModel', 'No model in your catalog can make {medium} yet.', { medium: mediumWords(n.requires, tr) }) } : {}),
|
|
838
896
|
// Design: only models that can see.
|
|
839
897
|
...(!n.requires && n.visionOnly ? { accept: (_p, m) => m?.vision === true && m?.status !== 'unavailable', allowCustom: false, emptyLabel: tr('assistant.routes.noSeeingModel', 'No model in your catalog can see images yet.') } : {}),
|
|
840
898
|
onSelect: (target) => {
|
|
841
|
-
if (!target?.model) return;
|
|
842
|
-
|
|
899
|
+
if (!target?.model || !card.isConnected) return;
|
|
900
|
+
if (state.pending || state.resolved || state.expired) return;
|
|
901
|
+
const previous = selected();
|
|
902
|
+
const kind = n.dispatchOnly || previous?.kind === 'dispatch' || (target.provider && target.provider !== brain.provider) ? 'dispatch' : 'direct';
|
|
843
903
|
state.other = {
|
|
844
|
-
id: 'other', kind, provider: target.provider, model: target.model, effort: null,
|
|
904
|
+
id: 'other', kind, provider: target.provider, model: target.model, effort: null, accountId: previous?.provider === target.provider ? previous.accountChoice || previous.accountId || null : null, account: previous?.provider === target.provider ? previous.account || null : null,
|
|
845
905
|
label: target.label || '', badge: null, number: options.length + 1, here: false,
|
|
846
906
|
title: target.label || target.model, badgeText: tr('assistant.routes.badge.other', 'Your choice'),
|
|
847
907
|
vision: target.vision === true, price: target.price || null, reason: '', warning: null, disabled: false,
|
|
@@ -852,7 +912,8 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
852
912
|
},
|
|
853
913
|
});
|
|
854
914
|
});
|
|
855
|
-
|
|
915
|
+
fields.append(otherBtn, effortBtn, accountBtn);
|
|
916
|
+
card.appendChild(fields);
|
|
856
917
|
|
|
857
918
|
// Remember for ‹class› tasks
|
|
858
919
|
let rememberInput = null;
|
|
@@ -906,7 +967,7 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
906
967
|
decidedBy: 'user',
|
|
907
968
|
optionId: state.selectedId,
|
|
908
969
|
remembered: state.remember,
|
|
909
|
-
target: o ? { kind: o.kind || 'direct', provider: o.provider, model: o.model, effort: o.effort, label: o.label || (o.here ? routeTargetLabel(o) : o.title) } : null,
|
|
970
|
+
target: o ? { kind: o.kind || 'direct', provider: o.provider, model: o.model, effort: o.effort, accountId: o.accountId || null, label: o.label || (o.here ? routeTargetLabel(o) : o.title) } : null,
|
|
910
971
|
alternatives: allOptions().filter(x => x.id !== state.selectedId).map(x => ({ provider: x.provider, model: x.model, effort: x.effort, label: x.label })),
|
|
911
972
|
};
|
|
912
973
|
}
|
|
@@ -926,6 +987,9 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
926
987
|
state.line = line;
|
|
927
988
|
state.collapsed = true;
|
|
928
989
|
state.resolved = true;
|
|
990
|
+
// No longer waits: the renderer puts it back from the open card's slot at the transcript's bottom
|
|
991
|
+
// (asst-ask-tail.js) before it becomes the route line in its own place.
|
|
992
|
+
try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
|
|
929
993
|
if (card.isConnected) card.replaceWith(line.el);
|
|
930
994
|
try { hooks.onCollapse?.(n.routeId, line); } catch { /* ignore */ }
|
|
931
995
|
if (hadFocus) { try { hooks.onDone?.(); } catch { /* ignore */ } }
|
|
@@ -937,6 +1001,8 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
937
1001
|
if (state.line?.el?.isConnected) state.line.el.replaceWith(card);
|
|
938
1002
|
state.collapsed = false;
|
|
939
1003
|
state.resolved = false;
|
|
1004
|
+
// Open again (ROUTE_INVALID): it waits on the user, so it goes back to the bottom slot.
|
|
1005
|
+
try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
|
|
940
1006
|
}
|
|
941
1007
|
|
|
942
1008
|
function clearTimers() {
|
|
@@ -955,7 +1021,7 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
955
1021
|
}
|
|
956
1022
|
|
|
957
1023
|
function cancel() {
|
|
958
|
-
if (state.pending || state.resolved) return;
|
|
1024
|
+
if (state.pending || state.resolved || state.expired) return;
|
|
959
1025
|
if (!n.decline.allowed) {
|
|
960
1026
|
// Nothing to cancel on the server: leave the card and hand focus back.
|
|
961
1027
|
try { hooks.onCancel?.(buildRouteCancel(n, 'user'), n); } catch { /* ignore */ }
|
|
@@ -1021,11 +1087,16 @@ export function renderRouteCard(container, input, hooks = {}) {
|
|
|
1021
1087
|
state.timer = null;
|
|
1022
1088
|
countdown.textContent = tr('assistant.routes.state.expired', 'route expired');
|
|
1023
1089
|
syncSelection();
|
|
1090
|
+
// No answer is coming from the user any more: it goes back to its own place (asst-ask-tail.js).
|
|
1091
|
+
try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
|
|
1024
1092
|
}
|
|
1025
1093
|
}
|
|
1026
|
-
if (expires && Number.isFinite(expires)) { tick(); state.timer = setInterval(tick, 1000); }
|
|
1094
|
+
if (expires && Number.isFinite(expires)) { tick(); if (!state.expired) state.timer = setInterval(tick, 1000); }
|
|
1027
1095
|
|
|
1028
1096
|
renderOptions();
|
|
1097
|
+
// The renderer lifts a card that waits on the user to the transcript's bottom while it is open
|
|
1098
|
+
// (asst-ask-tail.js): a route card waits like a follow-ups set or a question does.
|
|
1099
|
+
card._ask = { el: card, presenter: null, isOpen: () => !state.collapsed && !state.resolved && !state.expired, onChange: null };
|
|
1029
1100
|
if (container) container.appendChild(card);
|
|
1030
1101
|
try { hooks.scrollEnd?.(); } catch { /* ignore */ }
|
|
1031
1102
|
|
|
@@ -118,6 +118,8 @@ export function runChip(run, t = tFallback) {
|
|
|
118
118
|
const tone = runTone(run);
|
|
119
119
|
if (run?.turnState === 'awaiting_permission') return { tone, label: t('assistant.run.needsApproval', 'needs approval') };
|
|
120
120
|
if (run?.turnState === 'needs_input') return { tone, label: t('assistant.run.needsInput', 'needs input') };
|
|
121
|
+
// A Claude Code worker a server restart cut off, until the next start has resumed it (lib/assistant-dispatch.js).
|
|
122
|
+
if (run?.state === 'suspended' || run?.state === 'resuming') return { tone, label: t('assistant.run.resuming', 'resuming after a server restart') };
|
|
121
123
|
if (run?.queued && run.status !== 'running') return { tone, label: t('assistant.run.queued', 'queued') };
|
|
122
124
|
if (isRunIdle(run)) {
|
|
123
125
|
const outcome = run.outcome || run.lastResult?.status;
|
|
@@ -30,6 +30,7 @@ import { brainLabel, readStoredBrain, restoredSessionName } from './asst-state.j
|
|
|
30
30
|
import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
|
|
31
31
|
import { injectAssistantSidepanelStyles } from './asst-sidepanel-styles.js';
|
|
32
32
|
import { createMascot, mascotCameoSvg } from '../synabun-mascot.js';
|
|
33
|
+
import { createTutorialPreview } from '../ui-tutorial-preview.js';
|
|
33
34
|
|
|
34
35
|
const PANEL_OWNER = 'assistant-sidepanel';
|
|
35
36
|
const HOST_ID = 'sidepanel';
|
|
@@ -55,6 +56,7 @@ let _toastTimer = null;
|
|
|
55
56
|
let _renaming = false;
|
|
56
57
|
const _tabs = []; // { sessionId, viewport, toolbar, ctl, pill, label, userRenamed, status, brain }
|
|
57
58
|
let _activeIdx = -1;
|
|
59
|
+
let _tourPresenting = false;
|
|
58
60
|
|
|
59
61
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
60
62
|
|
|
@@ -408,13 +410,13 @@ function focusSession(sessionId, { prompt = '' } = {}) {
|
|
|
408
410
|
if (prompt) _tabs[idx].ctl?.send(prompt);
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
async function startSession() {
|
|
413
|
+
async function startSession(prompt = '') {
|
|
412
414
|
if (_starting || _tabs.length) return;
|
|
413
415
|
_starting = true;
|
|
414
416
|
_startError = '';
|
|
415
417
|
renderPlaceholder('starting');
|
|
416
418
|
try {
|
|
417
|
-
const res = await openAssistant({ host: HOST_ID });
|
|
419
|
+
const res = await openAssistant(prompt ? { host: HOST_ID, prompt } : { host: HOST_ID });
|
|
418
420
|
if (!_tabs.length) renderPlaceholder(res ? null : 'failed');
|
|
419
421
|
} finally {
|
|
420
422
|
_starting = false;
|
|
@@ -563,8 +565,8 @@ function applyVisibility(nextVisible) {
|
|
|
563
565
|
const tab = activeTab();
|
|
564
566
|
if (_visible) {
|
|
565
567
|
state.lastActivePanel = 'assistant';
|
|
566
|
-
if (!_tabs.length) startSession();
|
|
567
|
-
else if (tab) {
|
|
568
|
+
if (!_tabs.length && !_tourPresenting) startSession();
|
|
569
|
+
else if (tab && !_tourPresenting) {
|
|
568
570
|
if (tab.status === 'done') tab.status = 'idle';
|
|
569
571
|
try { tab.ctl?.onShown(); } catch { /* ignore */ }
|
|
570
572
|
noteAssistantFocus(tab.sessionId, HOST_ID);
|
|
@@ -617,6 +619,67 @@ export function assistantPanelPeekable() {
|
|
|
617
619
|
return !assistantBlocked() && _tabs.length > 0;
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Present the panel for the Workspace tour (ui-tutorial.js), reversibly: it never
|
|
624
|
+
* creates a session. The tour plays on the active tab when its empty state shows
|
|
625
|
+
* its character (the tab's own controls, composer and director: tourHost());
|
|
626
|
+
* otherwise (no tab, or a conversation on screen) on a session-free preview laid
|
|
627
|
+
* over the tab. Returns { host, live, restore({ reason }) } or null when the
|
|
628
|
+
* Assistant is blocked. restore() puts back the panel's visibility, the docked
|
|
629
|
+
* peers and the last-active panels, unless the tour ended on its hand-off to a
|
|
630
|
+
* live tab ('done'): then the panel stays and the tab's composer takes the focus.
|
|
631
|
+
*/
|
|
632
|
+
export function beginAssistantTour({ onPreviewSend = () => {} } = {}) {
|
|
633
|
+
if (assistantBlocked()) return null;
|
|
634
|
+
ensurePanel();
|
|
635
|
+
const wasVisible = _visible;
|
|
636
|
+
const previousPanel = state.lastActivePanel;
|
|
637
|
+
const previousSidepanel = state.lastActiveSidepanel;
|
|
638
|
+
const peers = ['#topright-claude-panel-btn', '#topright-codex-panel-btn', '#topright-opencode-panel-btn']
|
|
639
|
+
.map(s => document.querySelector(s)).filter(b => b?.classList.contains('active'));
|
|
640
|
+
_tourPresenting = true;
|
|
641
|
+
const tab = activeTab();
|
|
642
|
+
let liveHost = null;
|
|
643
|
+
try { liveHost = tab?.ctl?.tourHost?.() || null; } catch { liveHost = null; }
|
|
644
|
+
const preview = liveHost ? null : createTutorialPreview(_panel, { onSend: (text) => onPreviewSend(text) });
|
|
645
|
+
if (preview) _panel.classList.add('tour-covering');
|
|
646
|
+
setVisible(true);
|
|
647
|
+
let restored = false;
|
|
648
|
+
const host = liveHost || preview.host;
|
|
649
|
+
return {
|
|
650
|
+
host,
|
|
651
|
+
live: !!liveHost,
|
|
652
|
+
/**
|
|
653
|
+
* The user sent `text` from the preview's composer (after restore({ reason: 'sent' })):
|
|
654
|
+
* with no session, it starts one with that message (the user's own send); with a
|
|
655
|
+
* conversation on screen, it becomes that conversation's draft, never sent for them.
|
|
656
|
+
*/
|
|
657
|
+
deliver(text) {
|
|
658
|
+
const message = String(text || '').trim();
|
|
659
|
+
if (!message) return;
|
|
660
|
+
const current = activeTab();
|
|
661
|
+
if (current?.ctl) { focusSession(current.sessionId); current.ctl.insertText?.(message); current.ctl.focus?.(); return; }
|
|
662
|
+
startSession(message);
|
|
663
|
+
},
|
|
664
|
+
restore({ reason = 'skip' } = {}) {
|
|
665
|
+
if (restored) return;
|
|
666
|
+
restored = true;
|
|
667
|
+
closeMenu();
|
|
668
|
+
preview?.destroy();
|
|
669
|
+
_panel.classList.remove('tour-covering');
|
|
670
|
+
_tourPresenting = false;
|
|
671
|
+
const keep = (reason === 'done' && !!liveHost) || reason === 'sent';
|
|
672
|
+
if (keep) {
|
|
673
|
+
if (tab && findTab(tab.sessionId)) { try { tab.ctl?.onShown(); } catch { /* ignore */ } }
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (!wasVisible) setVisible(false);
|
|
677
|
+
for (const button of peers) if (!button.classList.contains('active')) button.click();
|
|
678
|
+
state.lastActivePanel = previousPanel; state.lastActiveSidepanel = previousSidepanel;
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
620
683
|
/** The top-right SynaBun button. */
|
|
621
684
|
export async function toggleAssistantPanel() {
|
|
622
685
|
if (assistantBlocked()) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { normalizeComputerMode, normalizeProvider, normalizeRouteMode } from './asst-state.js';
|
|
8
8
|
|
|
9
9
|
export const SLASH_COMMANDS = [
|
|
10
|
-
{ name: 'dispatch', usage: '/dispatch <provider> [--model m] [--effort e] [--agent a] [--project p] [--
|
|
10
|
+
{ name: 'dispatch', usage: '/dispatch <provider> [--model m] [--effort e] [--agent a] [--project p] [--tools a,b] [--account id] [--ask] [--title "t"] <task>', descKey: 'assistant.slash.dispatch', desc: 'Dispatch a task to a sidepanel agent' },
|
|
11
11
|
{ name: 'agents', usage: '/agents [all]', descKey: 'assistant.slash.agents', desc: 'List dispatched runs' },
|
|
12
12
|
{ name: 'recall', usage: '/recall <query>', descKey: 'assistant.slash.recall', desc: 'Search memories' },
|
|
13
13
|
{ name: 'remember', usage: '/remember <text>', descKey: 'assistant.slash.remember', desc: 'Store a memory' },
|
|
@@ -17,7 +17,7 @@ export const SLASH_COMMANDS = [
|
|
|
17
17
|
{ name: 'models', usage: '/models [list | archived | hide <id…> | show <id…>]', descKey: 'assistant.slash.models', desc: 'Switch models off or on for the Assistant (or open the Models list)' },
|
|
18
18
|
{ name: 'budget', usage: '/budget', descKey: 'assistant.slash.budget', desc: 'Set the session budget and view provider shares' },
|
|
19
19
|
{ name: 'computer', usage: '/computer [on|off]', descKey: 'assistant.slash.computer', desc: 'Let the assistant use your Mac' },
|
|
20
|
-
{ name: 'stop', usage: '/stop [runId|all]', descKey: 'assistant.slash.stop', desc: 'Stop a run, everything, or the current turn' },
|
|
20
|
+
{ name: 'stop', usage: '/stop [runId|title|all]', descKey: 'assistant.slash.stop', desc: 'Stop a run, everything, or the current turn' },
|
|
21
21
|
{ name: 'new', usage: '/new [provider]', descKey: 'assistant.slash.new', desc: 'Start a fresh session' },
|
|
22
22
|
{ name: 'resume', usage: '/resume [sessionId]', descKey: 'assistant.slash.resume', desc: 'Resume a previous session' },
|
|
23
23
|
{ name: 'compact', usage: '/compact', descKey: 'assistant.slash.compact', desc: 'Compact the context' },
|
|
@@ -33,7 +33,7 @@ export const DISPATCH_FLAGS = {
|
|
|
33
33
|
effort: 'effort', e: 'effort',
|
|
34
34
|
agent: 'agent',
|
|
35
35
|
project: 'project', p: 'project', cwd: 'project',
|
|
36
|
-
|
|
36
|
+
tools: 'tools',
|
|
37
37
|
account: 'account', a: 'account',
|
|
38
38
|
title: 'title', t: 'title',
|
|
39
39
|
budget: 'budget',
|
|
@@ -219,10 +219,10 @@ export function matchSlashCommands(query) {
|
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
221
|
* Build the POST /api/assistant/dispatch body from a parsed /dispatch command.
|
|
222
|
-
* Only explicit flags set model/effort — the brain's project
|
|
222
|
+
* Only explicit flags set model/effort — the brain's project and
|
|
223
223
|
* (same-provider) account are inherited, models are left to the server default.
|
|
224
224
|
*/
|
|
225
|
-
export function buildDispatchSpec(parsed, brain = {}, { assistantSessionId = null } = {}) {
|
|
225
|
+
export function buildDispatchSpec(parsed, brain = {}, { assistantSessionId = null, toolCatalog = null } = {}) {
|
|
226
226
|
if (!parsed || parsed.name !== 'dispatch' || parsed.error) return null;
|
|
227
227
|
const o = parsed.options || {};
|
|
228
228
|
const spec = {
|
|
@@ -236,8 +236,7 @@ export function buildDispatchSpec(parsed, brain = {}, { assistantSessionId = nul
|
|
|
236
236
|
if (o.model) spec.model = o.model;
|
|
237
237
|
if (o.effort) spec.effort = o.effort;
|
|
238
238
|
if (o.agent) spec.agent = o.agent;
|
|
239
|
-
|
|
240
|
-
if (mcp) spec.mcpProfile = mcp;
|
|
239
|
+
if (o.tools !== undefined) spec.tools = String(o.tools).split(',').map(id => id.trim()).filter(Boolean);
|
|
241
240
|
const account = o.account || (brain.provider === parsed.provider ? brain.accountId : null);
|
|
242
241
|
if (account) {
|
|
243
242
|
if (parsed.provider === 'codex') spec.codexAccountId = account;
|
|
@@ -247,6 +246,9 @@ export function buildDispatchSpec(parsed, brain = {}, { assistantSessionId = nul
|
|
|
247
246
|
if (o.budget !== undefined && Number.isFinite(Number(o.budget))) spec.budgetUsd = Number(o.budget);
|
|
248
247
|
if (o.minutes !== undefined && Number.isFinite(Number(o.minutes))) spec.maxMinutes = Number(o.minutes);
|
|
249
248
|
if (o.browser) spec.usesBrowser = true;
|
|
249
|
+
// A browser-reading slash launch uses the workspace guard, just like a routed browser task.
|
|
250
|
+
// Group metadata comes from the catalog, never from a second list of browser ids.
|
|
251
|
+
if (o.browser || (spec.tools || []).some(id => id === 'all' || toolCatalog?.groups?.some(group => group.id === id && group.browser))) spec.capability = 'workspace';
|
|
250
252
|
if (assistantSessionId) spec.assistantSessionId = assistantSessionId;
|
|
251
253
|
return spec;
|
|
252
254
|
}
|
|
@@ -64,7 +64,7 @@ export const DEFAULT_BRAIN = Object.freeze({
|
|
|
64
64
|
effort: null,
|
|
65
65
|
agent: null,
|
|
66
66
|
project: null,
|
|
67
|
-
|
|
67
|
+
tools: Object.freeze(['all']),
|
|
68
68
|
accountId: null,
|
|
69
69
|
permissionMode: 'default',
|
|
70
70
|
planMode: false,
|
|
@@ -105,7 +105,7 @@ export function normalizeBrain(raw, fallback = DEFAULT_BRAIN) {
|
|
|
105
105
|
effort: str(src.effort) || null,
|
|
106
106
|
agent: provider === 'opencode' ? (str(src.agent) || null) : null,
|
|
107
107
|
project: str(src.project ?? src.cwd) || null,
|
|
108
|
-
|
|
108
|
+
tools: provider === 'opencode' ? (Array.isArray(src.tools) ? [...src.tools] : null) : ['all'],
|
|
109
109
|
accountId: ACCOUNT_PROVIDERS.has(provider) ? (str(src.accountId) || null) : null,
|
|
110
110
|
permissionMode: modes.some(m => m.id === mode) ? mode : 'default',
|
|
111
111
|
planMode: PLAN_MODE_PROVIDERS.has(provider) && (src.planMode === true || legacyPlan),
|
|
@@ -145,7 +145,7 @@ function pickDefault(list) {
|
|
|
145
145
|
* efforts: { [provider]: [{ id, tier? }] },
|
|
146
146
|
* accounts: { [provider]: [{ id, label }] },
|
|
147
147
|
* agents: [{ name, mode? }], // OpenCode
|
|
148
|
-
*
|
|
148
|
+
* toolCatalog: { default, groups },
|
|
149
149
|
* projects: [{ path, label }],
|
|
150
150
|
* }
|
|
151
151
|
*/
|
|
@@ -188,14 +188,8 @@ export function resolveBrainDefaults(brain, catalog = {}) {
|
|
|
188
188
|
b.accountId = null;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const ids = Object.keys(profiles);
|
|
194
|
-
if (ids.length && !ids.includes(b.mcpProfile)) {
|
|
195
|
-
b.mcpProfile = ids.includes(catalog.defaultMcpProfile) ? catalog.defaultMcpProfile : (ids.includes('full') ? 'full' : ids[0]);
|
|
196
|
-
}
|
|
197
|
-
} else if (!b.mcpProfile && catalog.defaultMcpProfile) {
|
|
198
|
-
b.mcpProfile = catalog.defaultMcpProfile;
|
|
191
|
+
if (b.provider === 'opencode' && b.tools == null && catalog.toolCatalog) {
|
|
192
|
+
b.tools = [...new Set([...(catalog.toolCatalog.default || []).filter(id => id !== 'git'), 'browser'])];
|
|
199
193
|
}
|
|
200
194
|
|
|
201
195
|
if (!b.project && Array.isArray(catalog.projects) && catalog.projects.length === 1) {
|
|
@@ -285,7 +279,7 @@ export function emptyProjectView(project, projects = [], tr = (_key, fallback, p
|
|
|
285
279
|
export function brainRequiresSwitch(a, b) {
|
|
286
280
|
const x = normalizeBrain(a);
|
|
287
281
|
const y = normalizeBrain(b);
|
|
288
|
-
return x.provider !== y.provider || x.accountId !== y.accountId || x.
|
|
282
|
+
return x.provider !== y.provider || x.accountId !== y.accountId || JSON.stringify(x.tools) !== JSON.stringify(y.tools);
|
|
289
283
|
}
|
|
290
284
|
|
|
291
285
|
// ── Runs ────────────────────────────────────────────────────────────────────
|
|
@@ -295,9 +289,11 @@ export function isTerminalRunStatus(status) {
|
|
|
295
289
|
}
|
|
296
290
|
|
|
297
291
|
// Dispatcher views carry `state` (queued, awaiting_route, starting, running,
|
|
298
|
-
// idle, awaiting_permission, completed, failed, stopped,
|
|
299
|
-
// descriptors (sidepanel:run-*) carry `status`. The tray
|
|
300
|
-
|
|
292
|
+
// idle, awaiting_permission, suspended, resuming, completed, failed, stopped,
|
|
293
|
+
// interrupted); runtime descriptors (sidepanel:run-*) carry `status`. The tray
|
|
294
|
+
// reads `status`. A run a server restart suspended is still running; one being
|
|
295
|
+
// resumed is starting.
|
|
296
|
+
const STATE_STATUS = { queued: 'queued', awaiting_route: 'queued', starting: 'starting', running: 'running', idle: 'running', awaiting_permission: 'running', suspended: 'running', resuming: 'starting' };
|
|
301
297
|
|
|
302
298
|
export function runStatusFromState(state) {
|
|
303
299
|
const value = str(state);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Assistant — what a stop names
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// Pure module (no DOM, no i18n import) so node:test can exercise it.
|
|
5
|
+
//
|
|
6
|
+
// A stop ends the work the user named and nothing else:
|
|
7
|
+
// resolveStopTarget `/stop <target>`: the one run of this session the target names (its id, the
|
|
8
|
+
// start of its id, its title or the words of its title), or why it names none.
|
|
9
|
+
// Two candidates stop nothing: the user picks one.
|
|
10
|
+
// createConfirmGate the agents tray's "Stop all": the first press arms it, a second press inside
|
|
11
|
+
// the window stops every run of the session at once. A single stray click, a
|
|
12
|
+
// double click or a held key never does (2026-10-24: one "Stop all" press
|
|
13
|
+
// ended both runs of a session while the user meant one of them).
|
|
14
|
+
// Words the user types to stop everything ("stop", "cancel") go to the brain, which stops runs
|
|
15
|
+
// with agent_stop (assistant-persona.js): nothing here reads a message.
|
|
16
|
+
|
|
17
|
+
import { isTerminalRunStatus } from './asst-state.js';
|
|
18
|
+
|
|
19
|
+
/** How long an armed "Stop all" waits for its second press, and the least time between the two. */
|
|
20
|
+
export const STOP_ALL_CONFIRM_MS = 4000;
|
|
21
|
+
export const STOP_ALL_MIN_GAP_MS = 300;
|
|
22
|
+
|
|
23
|
+
// Words that say "a run" without saying which one: "/stop the OpenCode settings run" names it by the rest.
|
|
24
|
+
const FILLER = new Set(['the', 'a', 'an', 'that', 'this', 'my', 'our', 'one', 'run', 'runs', 'agent', 'agents', 'worker', 'workers', 'task', 'job', 'please', 'o', 'os', 'as', 'do', 'da', 'de']);
|
|
25
|
+
const FULL_RUN_ID = /^[0-9a-f]{32}$/i;
|
|
26
|
+
const ID_PREFIX = /^[0-9a-f][0-9a-f-]{3,}$/i;
|
|
27
|
+
|
|
28
|
+
function words(text) {
|
|
29
|
+
return String(text ?? '').toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function unquote(text) {
|
|
33
|
+
const s = String(text ?? '').trim();
|
|
34
|
+
const m = s.match(/^(["'“‘])([\s\S]*)(["'”’])$/);
|
|
35
|
+
return m ? m[2].trim() : s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The run `/stop <target>` names among `runs` (the tray's runs of this session).
|
|
40
|
+
* → { run, by: 'id' | 'prefix' | 'title' | 'words' }
|
|
41
|
+
* | { error: 'empty' | 'none' | 'finished' | 'ambiguous', matches?: run[], run? }
|
|
42
|
+
* An exact id names that run whatever its state (the server says when it already ended). Every
|
|
43
|
+
* other way names a run that still works; one that ended is `finished`. A full id the tray does not
|
|
44
|
+
* know is handed on as `{ error: 'none', passthrough: id }`: the server knows more runs than a tray.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveStopTarget(target, runs = []) {
|
|
47
|
+
const raw = unquote(target);
|
|
48
|
+
if (!raw) return { error: 'empty' };
|
|
49
|
+
const list = (Array.isArray(runs) ? runs : []).filter((run) => run && run.runId && run.removed !== true);
|
|
50
|
+
const live = list.filter((run) => !isTerminalRunStatus(run.status));
|
|
51
|
+
const one = (matches, by) => {
|
|
52
|
+
if (matches.length === 1) return isTerminalRunStatus(matches[0].status) ? { error: 'finished', run: matches[0] } : { run: matches[0], by };
|
|
53
|
+
if (matches.length > 1) return { error: 'ambiguous', matches };
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
const lower = raw.toLowerCase();
|
|
57
|
+
|
|
58
|
+
const exact = list.find((run) => String(run.runId).toLowerCase() === lower);
|
|
59
|
+
if (exact) return { run: exact, by: 'id' };
|
|
60
|
+
if (ID_PREFIX.test(raw)) {
|
|
61
|
+
const byPrefix = one(list.filter((run) => String(run.runId).toLowerCase().startsWith(lower)), 'prefix');
|
|
62
|
+
if (byPrefix) return byPrefix;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const title = (run) => words(run.title || run.task || '').join(' ');
|
|
66
|
+
const wanted = words(raw).join(' ');
|
|
67
|
+
if (wanted) {
|
|
68
|
+
const byTitle = one(live.filter((run) => title(run) === wanted), 'title');
|
|
69
|
+
if (byTitle) return byTitle;
|
|
70
|
+
}
|
|
71
|
+
// The words that say which run: each one is a word of its title, or the start of one.
|
|
72
|
+
const keys = words(raw).filter((word) => !FILLER.has(word));
|
|
73
|
+
if (keys.length) {
|
|
74
|
+
const fits = (run) => { const have = words(run.title || run.task || ''); return keys.every((key) => have.some((word) => word === key || (key.length >= 3 && word.startsWith(key)))); };
|
|
75
|
+
const byWords = one(live.filter(fits), 'words');
|
|
76
|
+
if (byWords) return byWords;
|
|
77
|
+
const ended = list.filter((run) => isTerminalRunStatus(run.status) && (title(run) === wanted || fits(run)));
|
|
78
|
+
if (ended.length === 1) return { error: 'finished', run: ended[0] };
|
|
79
|
+
}
|
|
80
|
+
return FULL_RUN_ID.test(raw) ? { error: 'none', passthrough: raw } : { error: 'none' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A press that only counts as the second of two. `press()` → 'armed' (the first: `onChange(true)`),
|
|
85
|
+
* 'confirmed' (the second, inside `windowMs` and at least `minGapMs` after the first:
|
|
86
|
+
* `onChange(false)`, the caller stops everything) or 'ignored' (too soon after the first: a double
|
|
87
|
+
* click, a held key). The arm lapses after `windowMs` by itself (`onChange(false)`).
|
|
88
|
+
*/
|
|
89
|
+
export function createConfirmGate({
|
|
90
|
+
windowMs = STOP_ALL_CONFIRM_MS,
|
|
91
|
+
minGapMs = STOP_ALL_MIN_GAP_MS,
|
|
92
|
+
now = Date.now,
|
|
93
|
+
setTimer = (fn, ms) => setTimeout(fn, ms),
|
|
94
|
+
clearTimer = (id) => clearTimeout(id),
|
|
95
|
+
onChange = () => {},
|
|
96
|
+
} = {}) {
|
|
97
|
+
let armedAt = null;
|
|
98
|
+
let timer = null;
|
|
99
|
+
const notify = (armed) => { try { onChange(armed); } catch { /* the button is gone */ } };
|
|
100
|
+
function reset() {
|
|
101
|
+
if (timer !== null) { clearTimer(timer); timer = null; }
|
|
102
|
+
if (armedAt === null) return false;
|
|
103
|
+
armedAt = null;
|
|
104
|
+
notify(false);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function press() {
|
|
108
|
+
const at = now();
|
|
109
|
+
// An arm whose timer never ran (a throttled background tab) has lapsed all the same.
|
|
110
|
+
if (armedAt !== null && at - armedAt > windowMs) reset();
|
|
111
|
+
if (armedAt === null) {
|
|
112
|
+
armedAt = at;
|
|
113
|
+
timer = setTimer(() => { timer = null; reset(); }, windowMs);
|
|
114
|
+
notify(true);
|
|
115
|
+
return 'armed';
|
|
116
|
+
}
|
|
117
|
+
if (at - armedAt < minGapMs) return 'ignored';
|
|
118
|
+
reset();
|
|
119
|
+
return 'confirmed';
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
press,
|
|
123
|
+
reset,
|
|
124
|
+
isArmed: () => armedAt !== null,
|
|
125
|
+
destroy() { if (timer !== null) { clearTimer(timer); timer = null; } armedAt = null; },
|
|
126
|
+
};
|
|
127
|
+
}
|