synabun 2.0.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +115 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +68 -4
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/launcher.mjs +9 -3
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +43 -5
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3680 -206
- package/neural-interface/i18n/pt-BR.json +3628 -157
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +148 -35
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +122 -44
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +59 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +97 -0
- package/neural-interface/lib/assistant-catalog.js +23 -9
- package/neural-interface/lib/assistant-dispatch.js +874 -156
- package/neural-interface/lib/assistant-envelope.js +22 -2
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +183 -26
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-model-catalog.js +64 -14
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-settings-schema.js +109 -47
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +164 -67
- package/neural-interface/lib/exec-loop-command.js +4 -1
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +565 -50
- package/neural-interface/lib/native-loop-runtime.js +287 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +58 -0
- package/neural-interface/lib/opencode-history.js +49 -204
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +310 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +32 -15
- package/neural-interface/lib/opencode-serve-records.js +624 -0
- package/neural-interface/lib/opencode-v2-client.js +53 -131
- package/neural-interface/lib/opencode-v2-ws-requests.js +35 -195
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +796 -0
- package/neural-interface/lib/opencode2-proxy.js +76 -0
- package/neural-interface/lib/opencode2-translate.js +1057 -0
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/process-identity.js +361 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +18 -10
- package/neural-interface/lib/session-title-generator.js +58 -18
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/style-guide/api.js +4 -3
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/untrusted-file-headers.js +27 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +1354 -50
- package/neural-interface/package.json +6 -2
- package/neural-interface/public/index.html +5 -3
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +76 -16
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +205 -19
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +241 -112
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +84 -12
- package/neural-interface/public/shared/assistant/asst-panel.js +303 -44
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +93 -58
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +770 -100
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +530 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +25 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +102 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +165 -13
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +550 -0
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +58 -150
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +62 -32
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +201 -74
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +35 -5
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +78 -86
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/launch-prefetch.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +9 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +140 -254
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +97 -203
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -22
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +20 -13
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +53 -82
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -40
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +205 -62
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +452 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +90 -0
- package/neural-interface/public/shared/settings/settings-ia.js +23 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +2065 -2150
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +155 -152
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +153 -13
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +21 -9
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +56 -52
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +229 -40
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +9 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +2267 -953
- package/neural-interface/public/shared/ui-sessions.js +20 -7
- package/neural-interface/public/shared/ui-settings.js +679 -496
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +56 -5
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +42 -2781
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +22 -5
- package/neural-interface/public/shared/ui-terminal.js +159 -199
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +14 -5
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -61
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -499
- package/neural-interface/public/shared/ui-update.js +121 -16
- package/neural-interface/public/shared/ui-whiteboard.js +313 -59
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +36 -2
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +72 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +116 -3
- package/neural-interface/server.js +4834 -2541
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +5 -5
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -17,25 +17,60 @@
|
|
|
17
17
|
// out. mascot() names the character on screen for the panel's director
|
|
18
18
|
// (asst-mascot.js), which may hold its pose; stills are painted SVGs, never
|
|
19
19
|
// rigs.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// A dispatched run's card opens a stage while its worker works
|
|
21
|
+
// (asst-run-stage.js): the run's own mascot and scene, independent of the
|
|
22
|
+
// Assistant's turn, so a worker that outlives the turn keeps moving. Run
|
|
23
|
+
// cards are not journaled: a reload restores them from the runs the agents
|
|
24
|
+
// tray lists (restoreRuns), next to the call that dispatched them.
|
|
25
|
+
// The Assistant's own work on a direct route (an approved "do it here" route
|
|
26
|
+
// of a task class: research, browser, quick…) uses the same scenes: its
|
|
27
|
+
// racks' stages show the class's scene under the mascot, and the scene the
|
|
28
|
+
// bubble's call implies (a browser tool the browser, a web search research),
|
|
29
|
+
// for the turn the route runs in (noteDirectRoute, paintRackScene). A
|
|
30
|
+
// conversation opened again learns its route from the journal like any other
|
|
31
|
+
// event (no rack of the history moves: none is live) and lends it to the
|
|
32
|
+
// resumed work once its turn runs (lendRouteScene).
|
|
33
|
+
// What a turn leaves for the user to click sits under the row it belongs to:
|
|
34
|
+
// the follow-ups a message offers (its ```synabun-followups block, or best
|
|
35
|
+
// effort its prose: asst-followups.js) and, under an error that is a usage
|
|
36
|
+
// limit, the waiting card with its countdown (asst-limit-wait.js); a limited
|
|
37
|
+
// run's card shows the same waiting card. The user's next prompt answers
|
|
38
|
+
// them or leaves them stale, and so does a new turn (settleOffers). A card
|
|
39
|
+
// that asks and is still open is lifted to the transcript's bottom while it
|
|
40
|
+
// waits (asst-ask-tail.js), so text and tool output streaming in after it
|
|
41
|
+
// never bury it; once answered it goes back under its row.
|
|
42
|
+
// A follow-ups card and the question cards the panel appends (appendNode with
|
|
43
|
+
// an `_ask`) are presented by the mascot (asst-ask.js): the newest one still
|
|
44
|
+
// open is the character on screen, its face a live rig (liveAsk,
|
|
45
|
+
// activateRigs); every other card's face is a painted still.
|
|
46
|
+
|
|
47
|
+
import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
|
|
22
48
|
import { createRouteLine } from './asst-route.js';
|
|
23
49
|
import { closeMenu, isMenuOpen } from './asst-menu.js';
|
|
24
50
|
import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
|
|
25
51
|
import * as mascotLib from '../synabun-mascot.js';
|
|
26
52
|
import { isReducedMotion, subscribe } from '../synabun-ticker.js';
|
|
27
|
-
import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText } from './asst-tool-kinds.js';
|
|
53
|
+
import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, headEllipsis, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText, wrapRuns } from './asst-tool-kinds.js';
|
|
28
54
|
import { injectRackStyles } from './asst-rack-styles.js';
|
|
55
|
+
import { injectSceneStyles } from './asst-scene-styles.js';
|
|
56
|
+
import { activityScene, compactActivity, isLivePhase, runChip, sceneOfClass, sceneSvg, stagePhase } from './asst-scenes.js';
|
|
57
|
+
import { STAGE_TIMING, createWorkerStage } from './asst-run-stage.js';
|
|
29
58
|
import { formatRunTokens } from './asst-usage.js';
|
|
30
59
|
import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
|
|
31
60
|
import { automationView } from './asst-automation.js';
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
import { guessFollowups, splitFollowups } from './asst-followups.js';
|
|
62
|
+
import { createFollowupsCard } from './asst-followups-card.js';
|
|
63
|
+
import { createAskTail } from './asst-ask-tail.js';
|
|
64
|
+
import { limitFromText } from './asst-limit.js';
|
|
65
|
+
import { createLimitDock } from './asst-limit-dock.js';
|
|
66
|
+
import { createLimitWait } from './asst-limit-wait.js';
|
|
67
|
+
|
|
68
|
+
// ── Markdown (lazy marked, the vendored build the sidepanels use too) ───────────────
|
|
34
69
|
let _marked = null;
|
|
35
70
|
let _markedLoading = null;
|
|
36
71
|
function loadMarked() {
|
|
37
72
|
if (_marked || _markedLoading) return _markedLoading;
|
|
38
|
-
_markedLoading = import('
|
|
73
|
+
_markedLoading = import('/vendor/marked/marked-14.1.4.esm.js')
|
|
39
74
|
.then((m) => {
|
|
40
75
|
_marked = m?.marked || m?.default || null;
|
|
41
76
|
if (_marked?.setOptions) _marked.setOptions({ breaks: true, gfm: true });
|
|
@@ -126,10 +161,6 @@ function memoryTitle(memory) {
|
|
|
126
161
|
|
|
127
162
|
// ── Run cards (shared with the agents dock) ────────────────────────────────
|
|
128
163
|
|
|
129
|
-
function runWaitingForRoute(run) {
|
|
130
|
-
return run?.state === 'awaiting_route' || run?.turnState === 'awaiting_route' || run?.route?.status === 'pending';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
164
|
/**
|
|
134
165
|
* buildRunCard(run, { t, providerIcon, providerColor, onFocus, onStop, onRead, onEscalate, compact, result, index })
|
|
135
166
|
* Returns a card element; call updateRunCard(el, run, opts) to refresh it in place.
|
|
@@ -191,20 +222,13 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
191
222
|
const title = card.querySelector('.asst-run-title');
|
|
192
223
|
const label = run.title || run.task || run.runId || '';
|
|
193
224
|
if (title) { title.textContent = label; title.title = label; }
|
|
194
|
-
const waitingRoute = runWaitingForRoute(run);
|
|
195
225
|
const pill = card.querySelector('.asst-pill');
|
|
196
226
|
if (pill) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
: run.queued && run.status !== 'running' ? t('assistant.run.queued', 'queued')
|
|
203
|
-
// A warm worker between turns reports what its last turn ended with, not "running".
|
|
204
|
-
: isRunIdle(run) ? (run.outcome || run.lastResult?.status
|
|
205
|
-
? t('assistant.run.idleWith', '{outcome} · idle', { outcome: run.outcome || run.lastResult.status })
|
|
206
|
-
: t('assistant.run.idle', 'idle'))
|
|
207
|
-
: (run.status || tone);
|
|
227
|
+
// What it waits on (a route card, your answer: the stage's own words) > its approval > queued > what a warm
|
|
228
|
+
// worker's last turn ended with > its status (asst-scenes.js runChip).
|
|
229
|
+
const chip = runChip(run, t);
|
|
230
|
+
pill.dataset.tone = chip.tone;
|
|
231
|
+
(pill.querySelector('.asst-pill-label') || pill).textContent = chip.label;
|
|
208
232
|
}
|
|
209
233
|
const meta = card.querySelector('.asst-run-meta');
|
|
210
234
|
if (meta) {
|
|
@@ -254,12 +278,15 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
254
278
|
const readBtn = card.querySelector('[data-action="read"]');
|
|
255
279
|
if (readBtn) readBtn.disabled = run.status === 'queued';
|
|
256
280
|
|
|
281
|
+
// A provider's usage limit stopped it: the waiting card, whose Retry now stands in for "Retry on" the same model.
|
|
282
|
+
const limitShown = syncRunLimit(card, run, opts, t);
|
|
283
|
+
|
|
257
284
|
// Escalation offer ("Escalate to ‹label›", or "Retry on ‹label›" after a
|
|
258
285
|
// temporary failure) when the server proposes one; a note without a button
|
|
259
286
|
// when a stronger model would hit the same wall (access, the user's input).
|
|
260
287
|
let esc8 = card.querySelector('.asst-run-escalate');
|
|
261
288
|
const escalationRaw = run.escalation && typeof run.escalation === 'object' ? run.escalation : null;
|
|
262
|
-
const escalation = escalationRaw?.to ? escalationRaw : null;
|
|
289
|
+
const escalation = escalationRaw?.to && !(limitShown && escalationRaw.kind === 'retry') ? escalationRaw : null;
|
|
263
290
|
const needsNote = escalationRaw && escalationRaw.kind === 'none' ? escalationRaw : null;
|
|
264
291
|
const onEscalate = opts.onEscalate || card._runOpts?.onEscalate;
|
|
265
292
|
if (esc8 && esc8.dataset.kind !== (needsNote ? 'note' : 'offer')) { esc8.remove(); esc8 = null; card._escalation = null; }
|
|
@@ -317,6 +344,49 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
317
344
|
updateRunMedia(card, run, { t, lightboxT: opts.t || card._runOpts?.t });
|
|
318
345
|
}
|
|
319
346
|
|
|
347
|
+
/** What Retry now runs a limited run on: the server's same-model retry, else the run's own provider and model. */
|
|
348
|
+
function limitRetryTarget(run) {
|
|
349
|
+
const esc = run?.escalation && typeof run.escalation === 'object' ? run.escalation : null;
|
|
350
|
+
if (esc?.kind === 'retry' && esc.to?.provider) return esc.to;
|
|
351
|
+
return { provider: run.provider || 'claude-code', model: run.model || null, effort: run.effort || null, accountId: run.accountId || null };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* A run a provider's usage limit stopped (run.limit, lib/assistant-dispatch.js): the waiting card
|
|
356
|
+
* is registered with the conversation's single bottom dock; a standalone card can own its wait.
|
|
357
|
+
* Retry now runs it again on the same model. Never while running or in the compact agents tray.
|
|
358
|
+
*/
|
|
359
|
+
function syncRunLimit(card, run, opts, t) {
|
|
360
|
+
const limit = run.limit && typeof run.limit === 'object' ? run.limit : null;
|
|
361
|
+
const tone = runTone(run);
|
|
362
|
+
const show = !opts.compact && !!limit && tone !== 'running' && tone !== 'queued' && !run.removed;
|
|
363
|
+
let wait = card._limitWait || null;
|
|
364
|
+
if (!show) {
|
|
365
|
+
(opts.onLimitWait || card._runOpts?.onLimitWait)?.(run, null);
|
|
366
|
+
if (wait) { wait.destroy(); wait.el.remove(); card._limitWait = null; }
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
const shared = opts.onLimitWait || card._runOpts?.onLimitWait;
|
|
370
|
+
if (shared) { shared(run, show && !limit.retriedAt && !run.escalation?.started ? limit : null); return show; }
|
|
371
|
+
if (!wait || !wait.el.isConnected) {
|
|
372
|
+
const onEscalate = opts.onEscalate || card._runOpts?.onEscalate;
|
|
373
|
+
wait = createLimitWait({
|
|
374
|
+
t,
|
|
375
|
+
resetsAt: limit.resetsAt || null,
|
|
376
|
+
since: Date.parse(limit.detectedAt || run.completedAt || '') || Date.now(),
|
|
377
|
+
provider: providerShortLabel(run.provider || 'claude-code'),
|
|
378
|
+
onRetry: typeof onEscalate === 'function' ? () => onEscalate(card.dataset.runId, limitRetryTarget(card._usageRun || run)) : null,
|
|
379
|
+
});
|
|
380
|
+
card._limitWait = wait;
|
|
381
|
+
const meta = card.querySelector('.asst-run-meta');
|
|
382
|
+
if (meta) meta.after(wait.el); else card.appendChild(wait.el);
|
|
383
|
+
} else if (limit.resetsAt) {
|
|
384
|
+
wait.update({ resetsAt: limit.resetsAt });
|
|
385
|
+
}
|
|
386
|
+
if (limit.retriedAt || run.escalation?.started) wait.retried();
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
|
|
320
390
|
const MAX_RUN_MEDIA = 8;
|
|
321
391
|
|
|
322
392
|
/**
|
|
@@ -419,7 +489,7 @@ const PREVIEW_LINES = 5;
|
|
|
419
489
|
const THINK_PREVIEW_LINES = 10;
|
|
420
490
|
const MAX_DIFF_LINES = 400; // an edit's mini diff draws this many lines; "Show full input" draws the rest
|
|
421
491
|
// The empty state's suggestions (asst-panel.js ids) and the kind each one is about.
|
|
422
|
-
const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedule: 'plan', remember: 'memory' };
|
|
492
|
+
const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', skill: 'skill', schedule: 'plan', remember: 'memory' };
|
|
423
493
|
|
|
424
494
|
/**
|
|
425
495
|
* createRenderer(transcriptEl, hooks)
|
|
@@ -428,18 +498,29 @@ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedu
|
|
|
428
498
|
* emptySuggestions() → [{ id, label, template }], onSuggestion(template),
|
|
429
499
|
* brainLabel() → string, onChangeRoutes(), onRouteEvent(ev) → bool (true = handled by an open card),
|
|
430
500
|
* heroOrigin() → DOMRect | null (where the empty state's character flies in from: the host's placeholder),
|
|
431
|
-
* onMascotChange() (the character on screen changed: mascot())
|
|
501
|
+
* onMascotChange() (the character on screen changed: mascot()),
|
|
502
|
+
* onFollowup(text, { set, indices }) (a follow-up was clicked: send `text` as the user's message),
|
|
503
|
+
* onFollowupOther() ("Something else…": the composer), limitInfo() → the session's limit notice
|
|
504
|
+
* ({ status, resetsAt } | null), brainProvider() → the brain's provider id, onLimitRetry(text)
|
|
505
|
+
* (Retry now on the Assistant's own limit card: send `text`) }
|
|
432
506
|
*/
|
|
433
507
|
export function createRenderer(transcriptEl, hooks = {}) {
|
|
434
508
|
loadMarked();
|
|
435
509
|
injectRackStyles();
|
|
510
|
+
injectSceneStyles();
|
|
436
511
|
const $msgs = transcriptEl;
|
|
437
512
|
const scopes = new Map(); // parent_tool_use_id → { container, stream, key }
|
|
438
513
|
const runCards = new Map(); // runId → card element
|
|
514
|
+
const stages = new Map(); // runId → its card's worker stage (asst-run-stage.js)
|
|
515
|
+
const runActivity = new Map(); // runId → what its worker does now, compact ({ kind, name, step, at }), while the run lasts
|
|
516
|
+
let unsubStageTick = null;
|
|
439
517
|
const routeLines = new Map(); // routeId → line handle
|
|
440
518
|
const autoRows = new Map(); // traceId → the turn's automation trace row
|
|
441
519
|
let working = null;
|
|
442
520
|
let workingFace = null; // the "Thinking" row's character: { still, rig?, el, target? } (syncWorkingFace)
|
|
521
|
+
// The open card's slot under every row (asst-ask-tail.js): a card that waits on the user is drawn
|
|
522
|
+
// there, above the "Working…" row, until it is answered; rows appended meanwhile go above it.
|
|
523
|
+
const askTail = createAskTail($msgs, { anchor: () => (working?.parentNode === $msgs ? working : null) });
|
|
443
524
|
let followTail = true;
|
|
444
525
|
let emptyEl = null;
|
|
445
526
|
let hero = null; // the empty state's character: { rig, el, box, parked, leaving, held, dock, egg, target }
|
|
@@ -451,6 +532,25 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
451
532
|
let turn = { calls: 0, last: null, errored: false, answerRow: null }; // what the turn did: the mascot's verdict, the announced answer
|
|
452
533
|
let grace = null; // the stage on screen when the result arrived: it waits for the turn's verdict (finishTurn)
|
|
453
534
|
let waiting = null; // a card waits on the user: { id: the tool_use id it holds or null, since } (setWaiting)
|
|
535
|
+
// An approved direct route (synabun.route auto / decided, target.kind 'direct', a class with a scene) lends its
|
|
536
|
+
// class's scene to the racks of the turn it runs in (noteDirectRoute): { routeId, taskClass, turn, rackAt } with
|
|
537
|
+
// `turn` the turnSeq it belongs to, null until a turn takes it (a pick made while no turn runs, or a route the
|
|
538
|
+
// journal's last turn was still running), and `rackAt` the last rack made before it (its racks come after).
|
|
539
|
+
// A route the journal records (`journal`) belongs to the journal's last turn only: the turn the reattach finds
|
|
540
|
+
// running takes it; with none running, that turn is over (it ended with no result) and nothing later takes it.
|
|
541
|
+
let directRoute = null;
|
|
542
|
+
let lastDirect = null; // the last one approved since the user's prompt: a "do it here" continuation turn takes it again
|
|
543
|
+
let turnSeq = 0; // turns this renderer has seen start (showWorking)
|
|
544
|
+
let reattachRunning = null; // what the last reattach said: a turn runs (true), none (false), not asked yet (null)
|
|
545
|
+
// What a turn left for the user to click: follow-up cards under its messages (asst-followups-card.js).
|
|
546
|
+
// The user's next prompt answers them or they go
|
|
547
|
+
// stale, and so does a new turn; live and from the journal alike, so a reload draws the same states.
|
|
548
|
+
const offers = new Set(); // { el, set, settle(text) → answered, stale() }
|
|
549
|
+
// The cards the mascot presents (asst-ask.js): follow-ups cards and the question cards the panel appends.
|
|
550
|
+
// The newest one still open is the character on screen (above any stage); see liveAsk / activateRigs.
|
|
551
|
+
const askCards = new Set(); // { el, presenter, isOpen(), onChange, seq }
|
|
552
|
+
let askSeq = 0;
|
|
553
|
+
let lastTextRow = null; // the newest root message since the user's last prompt: the one a guess reads
|
|
454
554
|
|
|
455
555
|
// ── Activity racks ──
|
|
456
556
|
// Every burst of agent work (tool calls, thinking, short narration) is one
|
|
@@ -463,6 +563,27 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
463
563
|
let rackSeq = 0;
|
|
464
564
|
let stepSeq = 0;
|
|
465
565
|
let unsubTick = null;
|
|
566
|
+
// A staged rack's bubble is cut to its stage's width (subjectBudget, fitSubject): when that width
|
|
567
|
+
// changes, the bubble is cut again once it has held still (one pass when a drag ends, never one per
|
|
568
|
+
// frame of it), instead of on the next tick. Heights are not watched.
|
|
569
|
+
const RECUT_SETTLE_MS = 120;
|
|
570
|
+
const stageWidths = new WeakMap(); // stage element → { rack, width }
|
|
571
|
+
const toRecut = new Set();
|
|
572
|
+
let recutTimer = 0;
|
|
573
|
+
const stageResizes = typeof ResizeObserver === 'function' ? new ResizeObserver((entries) => {
|
|
574
|
+
for (const entry of entries) {
|
|
575
|
+
const seen = stageWidths.get(entry.target);
|
|
576
|
+
if (!seen) continue;
|
|
577
|
+
const width = Math.round(entry.contentRect.width);
|
|
578
|
+
if (seen.width === null) { seen.width = width; continue; } // the first report: the width it was cut at
|
|
579
|
+
if (width === seen.width) continue;
|
|
580
|
+
seen.width = width;
|
|
581
|
+
toRecut.add(seen.rack);
|
|
582
|
+
}
|
|
583
|
+
if (!toRecut.size) return;
|
|
584
|
+
clearTimeout(recutTimer);
|
|
585
|
+
recutTimer = setTimeout(recutBubbles, RECUT_SETTLE_MS);
|
|
586
|
+
}) : null;
|
|
466
587
|
let muted = false; // inside handleEvent(…, { silent: true }): no motion, no announcements
|
|
467
588
|
let replayDraw = false; // inside handleEvent(…, { replay: true }): a reattach's missed packets (drawn like the journal)
|
|
468
589
|
let clockAt = null; // the journal `at` of the entry being replayed
|
|
@@ -499,6 +620,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
499
620
|
if (step.ghost && step.type === 'call' && !Object.keys(step.input || {}).length) { say.text = '…'; say.detail = '…'; say.segments = null; say.mono = false; say.meta = []; }
|
|
500
621
|
return say;
|
|
501
622
|
};
|
|
623
|
+
const limitDock = createLimitDock($msgs, { t });
|
|
502
624
|
const nowMs = () => (clockAt ?? Date.now());
|
|
503
625
|
const isTimed = () => !muted || clockAt != null;
|
|
504
626
|
const locale = () => (typeof document !== 'undefined' && document.documentElement?.lang) || 'en';
|
|
@@ -699,12 +821,46 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
699
821
|
return best;
|
|
700
822
|
}
|
|
701
823
|
|
|
702
|
-
/** The mascot on screen: the hero, else the newest stage, else the "Thinking" row's, else none. */
|
|
824
|
+
/** The mascot on screen: the hero, else the newest open card's, else the newest stage, else the "Thinking" row's, else none. */
|
|
703
825
|
function visibleMascot() {
|
|
704
826
|
if (hero?.rig) return hero.target;
|
|
827
|
+
const ask = liveAsk();
|
|
828
|
+
if (ask?.presenter?.isLive?.()) return ask.presenter.target;
|
|
705
829
|
return newestStage()?.target || workingFace?.target || null;
|
|
706
830
|
}
|
|
707
831
|
|
|
832
|
+
// ── Cards the mascot presents ──
|
|
833
|
+
/**
|
|
834
|
+
* A follow-ups card or a question card (its `_ask`): its face may be the character on screen while it is
|
|
835
|
+
* open. Every ask — a route card too, which has no face — waits at the transcript's bottom until answered.
|
|
836
|
+
*/
|
|
837
|
+
function registerAsk(handle) {
|
|
838
|
+
if (!handle?.el || askCards.has(handle)) return;
|
|
839
|
+
handle.seq = (askSeq += 1);
|
|
840
|
+
handle.onChange = () => { askTail.sync(); mascotChanged(); };
|
|
841
|
+
askCards.add(handle);
|
|
842
|
+
askTail.pin(handle);
|
|
843
|
+
mascotChanged();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** The newest card with a face that is still open (answers can still be picked): the one whose face is live. */
|
|
847
|
+
function liveAsk() {
|
|
848
|
+
let best = null;
|
|
849
|
+
for (const handle of [...askCards]) {
|
|
850
|
+
if (!handle.el?.isConnected) { askCards.delete(handle); handle.presenter?.destroy?.(); continue; }
|
|
851
|
+
// A route card asks, but has no face of its own: it waits at the bottom without taking the character.
|
|
852
|
+
if (!handle.presenter || !handle.isOpen() || (best && best.seq > handle.seq)) continue;
|
|
853
|
+
best = handle;
|
|
854
|
+
}
|
|
855
|
+
return best;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function dropAsks() {
|
|
859
|
+
for (const handle of askCards) { try { handle.presenter?.destroy?.(); } catch { /* gone */ } }
|
|
860
|
+
askCards.clear();
|
|
861
|
+
askTail.clear();
|
|
862
|
+
}
|
|
863
|
+
|
|
708
864
|
function mascotChanged() {
|
|
709
865
|
activateRigs();
|
|
710
866
|
try { hooks.onMascotChange?.(); } catch { /* the director is gone */ }
|
|
@@ -715,12 +871,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
715
871
|
while (rows.length > MAX_MESSAGES && rows[0] !== working) rows[0].remove();
|
|
716
872
|
}
|
|
717
873
|
|
|
718
|
-
/** Insert a row before "Working…" so the indicator stays the last row. */
|
|
874
|
+
/** Insert a row before the open cards (if any) and before "Working…", so the indicator stays the last row. */
|
|
719
875
|
function append(node, scope = rootScope, { park = false } = {}) {
|
|
720
876
|
hideEmpty({ park });
|
|
721
|
-
if (scope.container === $msgs
|
|
877
|
+
if (scope.container === $msgs) askTail.insert(node);
|
|
722
878
|
else scope.container.appendChild(node);
|
|
723
|
-
if (scope === rootScope) prune();
|
|
879
|
+
if (scope === rootScope) { prune(); askTail.sync(); }
|
|
724
880
|
// Text, a card, a notice…: the parked hero would sit on it. The prompt and a rack leave it to settleHero().
|
|
725
881
|
if (hero?.parked && scope === rootScope && !node._rack && !node.classList.contains('msg-user')) dismissHero();
|
|
726
882
|
if (racks.size) syncRacks();
|
|
@@ -785,6 +941,99 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
785
941
|
linkify(body);
|
|
786
942
|
}
|
|
787
943
|
|
|
944
|
+
/**
|
|
945
|
+
* A message's text without its follow-ups block (asst-followups.js). While it streams, a block
|
|
946
|
+
* being typed stays hidden; once the text is final, the block becomes the card under the row
|
|
947
|
+
* (root messages only: a subagent's offers are not the user's to answer).
|
|
948
|
+
*/
|
|
949
|
+
function paintMessage(row, body, text, { streaming = false, scope = rootScope } = {}) {
|
|
950
|
+
const split = splitFollowups(text, { streaming });
|
|
951
|
+
renderMarkdownInto(body, split.body);
|
|
952
|
+
if (!streaming && split.block && scope === rootScope) attachFollowups(row, split.block);
|
|
953
|
+
return split;
|
|
954
|
+
}
|
|
955
|
+
/** What a streaming text shows: '' while it is nothing but the start of a block. */
|
|
956
|
+
const shownText = (text) => splitFollowups(text, { streaming: true }).body;
|
|
957
|
+
|
|
958
|
+
// ── Offers ──
|
|
959
|
+
/**
|
|
960
|
+
* The follow-ups card under a message row: once per row, registered as an open offer, and presented by
|
|
961
|
+
* the mascot (its face is live while it is the newest open card: activateRigs).
|
|
962
|
+
*/
|
|
963
|
+
function attachFollowups(row, set) {
|
|
964
|
+
if (!row || !set?.items?.length) return null;
|
|
965
|
+
if (row._followups?.el.isConnected) return row._followups;
|
|
966
|
+
const card = createFollowupsCard(set, {
|
|
967
|
+
t,
|
|
968
|
+
// A card drawn from the journal comes in quietly; a live one with a nod.
|
|
969
|
+
entrance: calm() ? null : 'nod',
|
|
970
|
+
onSend: (text, meta) => hooks.onFollowup?.(text, meta),
|
|
971
|
+
onOther: () => hooks.onFollowupOther?.(),
|
|
972
|
+
onFocusComposer: () => hooks.onFollowupOther?.(),
|
|
973
|
+
});
|
|
974
|
+
row._followups = card;
|
|
975
|
+
(row.querySelector('.asst-msg-content') || row).appendChild(card.el);
|
|
976
|
+
offers.add(card);
|
|
977
|
+
registerAsk(card);
|
|
978
|
+
enterRow(card.el);
|
|
979
|
+
scrollEnd();
|
|
980
|
+
return card;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* A finished turn whose last message still ends in a list of options or an "I can X, or Y" offer,
|
|
985
|
+
* with no block: best-effort items read from the prose. Once per message.
|
|
986
|
+
*/
|
|
987
|
+
function guessOffer() {
|
|
988
|
+
const row = lastTextRow;
|
|
989
|
+
if (!row?.isConnected || row._followups || row._guessed || row.classList.contains('asst-msg-error')) return;
|
|
990
|
+
row._guessed = true;
|
|
991
|
+
const set = guessFollowups(row._text || '');
|
|
992
|
+
if (set) attachFollowups(row, set);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The user's next prompt reached the transcript (`text`), or a new turn started (no text): every open
|
|
997
|
+
* offer is answered by it or goes stale. A guessed set nobody took leaves: its message says it in words.
|
|
998
|
+
*/
|
|
999
|
+
function settleOffers(text = null) {
|
|
1000
|
+
for (const offer of [...offers]) {
|
|
1001
|
+
offers.delete(offer);
|
|
1002
|
+
if (!offer.el?.isConnected) continue;
|
|
1003
|
+
const answered = text !== null ? offer.settle(text) : (offer.stale(), false);
|
|
1004
|
+
if (!answered && offer.set?.source === 'heuristic') offer.el.remove();
|
|
1005
|
+
}
|
|
1006
|
+
if (text !== null) lastTextRow = null;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const sameText = (a, b) => String(a ?? '').replace(/\s+/g, ' ').trim().toLowerCase() === String(b ?? '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* A provider's usage limit (asst-limit.js) updates the conversation's single bottom countdown.
|
|
1013
|
+
* The reset time is the session's own limit report while live (hooks.limitInfo: the Claude SDK's
|
|
1014
|
+
* rate_limit_info), else the one the message names, read from when it was written. Retry now sends one
|
|
1015
|
+
* message that retries the blocked work.
|
|
1016
|
+
*/
|
|
1017
|
+
function offerLimitWait(text, { brainError = false, limit = null } = {}) {
|
|
1018
|
+
const read = limitFromText(text, { now: nowMs() });
|
|
1019
|
+
let info = limit;
|
|
1020
|
+
if (!info && !muted) { try { info = hooks.limitInfo?.() || null; } catch { info = null; } }
|
|
1021
|
+
const reported = info?.status === 'rejected';
|
|
1022
|
+
// The brain's own error while the session's report says the limit holds is that limit, whatever its words.
|
|
1023
|
+
if (!read && !(brainError && reported)) return null;
|
|
1024
|
+
const resetsAt = reported ? info.resetsAt : read?.resetsAt;
|
|
1025
|
+
const retryText = t('assistant.limitWait.continue', 'The usage limit has reset. Retry what was blocked and continue where you left off.');
|
|
1026
|
+
let provider = '';
|
|
1027
|
+
try { provider = providerShortLabel(hooks.brainProvider?.() || ''); } catch { /* unknown */ }
|
|
1028
|
+
return limitDock.set('brain', { resetsAt, since: nowMs(), provider, onRetry: () => { limitDock.set('brain', null); hooks.onLimitRetry?.(retryText); } });
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/** The session's limit report changed (assistant:limit): this turn's waiting card counts to its reset time. */
|
|
1032
|
+
function noteLimit(limit) {
|
|
1033
|
+
if (limit?.status === 'rejected') offerLimitWait('', { brainError: true, limit });
|
|
1034
|
+
else if (limit?.status && limit.status !== 'rejected') limitDock.set('brain', null);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
788
1037
|
function copyText(text) {
|
|
789
1038
|
const value = String(text ?? '');
|
|
790
1039
|
if (!value) return;
|
|
@@ -817,10 +1066,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
817
1066
|
if (!s) return;
|
|
818
1067
|
if (s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; }
|
|
819
1068
|
if (s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; }
|
|
820
|
-
if (s.bodyEl && s.textBuf.trim())
|
|
1069
|
+
if (s.bodyEl && s.textBuf.trim()) paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
|
|
821
1070
|
if (s.think) refreshStep(s.think);
|
|
822
1071
|
}
|
|
823
1072
|
|
|
1073
|
+
/** A streamed row that stays as the message (no final message replaced it): its text is final now. */
|
|
1074
|
+
function sealStreamRow(scope) {
|
|
1075
|
+
const s = scope.stream;
|
|
1076
|
+
if (!s?.row || !s.bodyEl || !s.textBuf.trim()) return;
|
|
1077
|
+
s.row.classList.remove('streaming');
|
|
1078
|
+
paintMessage(s.row, s.bodyEl, s.textBuf, { scope });
|
|
1079
|
+
}
|
|
1080
|
+
|
|
824
1081
|
/** The stream's text row goes (the final message renders the text again). */
|
|
825
1082
|
function dropStreamText(scope) {
|
|
826
1083
|
const s = scope.stream;
|
|
@@ -890,10 +1147,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
890
1147
|
if (!s.thinkTimer) s.thinkTimer = setTimeout(() => { s.thinkTimer = null; refreshStep(step); if (step.rack.live) paintStage(step.rack); }, STREAM_TICK_MS);
|
|
891
1148
|
} else if (delta.type === 'text_delta' && delta.text != null) {
|
|
892
1149
|
s.textBuf += delta.text;
|
|
893
|
-
|
|
1150
|
+
// A row once there is something to show: a follow-ups block on its own is the card, later.
|
|
1151
|
+
if (!s.bodyEl && shownText(s.textBuf).trim()) {
|
|
894
1152
|
const { row, wrap } = assistantRow(scope, { _text: s.textBuf, _msgId: s.msgId, _claude: s.claude });
|
|
895
1153
|
row.classList.add('streaming');
|
|
896
1154
|
noteAnswer(scope, row);
|
|
1155
|
+
if (scope === rootScope) lastTextRow = row;
|
|
897
1156
|
s.row = row;
|
|
898
1157
|
s.wrap = wrap;
|
|
899
1158
|
s.bodyEl = el('div', 'asst-md');
|
|
@@ -904,7 +1163,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
904
1163
|
s.mdTimer = setTimeout(() => {
|
|
905
1164
|
s.mdTimer = null;
|
|
906
1165
|
if (!s.bodyEl) return;
|
|
907
|
-
|
|
1166
|
+
paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
|
|
908
1167
|
scrollEnd();
|
|
909
1168
|
}, STREAM_TICK_MS);
|
|
910
1169
|
}
|
|
@@ -913,7 +1172,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
913
1172
|
}
|
|
914
1173
|
if (type === 'content_block_stop') {
|
|
915
1174
|
const s = scope.stream;
|
|
916
|
-
if (s?.bodyEl && s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null;
|
|
1175
|
+
if (s?.bodyEl && s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope }); }
|
|
917
1176
|
if (s?.think && s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; refreshStep(s.think); }
|
|
918
1177
|
}
|
|
919
1178
|
}
|
|
@@ -983,9 +1242,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
983
1242
|
function textRow(scope, text, ctx = {}) {
|
|
984
1243
|
const { row, wrap } = assistantRow(scope, { _text: text, _msgId: ctx.msgId || null, _claude: !!ctx.claude });
|
|
985
1244
|
const body = el('div', 'asst-md');
|
|
986
|
-
renderMarkdownInto(body, text);
|
|
987
1245
|
wrap.appendChild(body);
|
|
1246
|
+
paintMessage(row, body, text, { scope });
|
|
988
1247
|
noteAnswer(scope, row);
|
|
1248
|
+
if (scope === rootScope) lastTextRow = row;
|
|
989
1249
|
return row;
|
|
990
1250
|
}
|
|
991
1251
|
|
|
@@ -1012,7 +1272,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1012
1272
|
else if (s.row) {
|
|
1013
1273
|
s.row.classList.remove('streaming');
|
|
1014
1274
|
if (!s.wrap.children.length) s.row.remove();
|
|
1015
|
-
else if (s.textBuf.trim()) (scope.kept ||= new Set()).add(textKey(s.textBuf));
|
|
1275
|
+
else if (s.textBuf.trim()) { (scope.kept ||= new Set()).add(textKey(s.textBuf)); sealStreamRow(scope); }
|
|
1016
1276
|
}
|
|
1017
1277
|
if (streamedThink && !thinks.length) finishThink(streamedThink);
|
|
1018
1278
|
scope.stream = null;
|
|
@@ -1173,10 +1433,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1173
1433
|
return prev;
|
|
1174
1434
|
}
|
|
1175
1435
|
|
|
1176
|
-
/** The scope's last row, skipping "Working…" and rows folding into a rack. */
|
|
1436
|
+
/** The scope's last row, skipping "Working…", the open card's slot and rows folding into a rack. */
|
|
1177
1437
|
function tailRow(scope) {
|
|
1178
1438
|
let node = scope.container.lastElementChild;
|
|
1179
|
-
while (node && (node === working || node.classList.contains('asst-folding'))) node = node.previousElementSibling;
|
|
1439
|
+
while (node && (node === working || node === askTail.slot || node.classList.contains('asst-folding'))) node = node.previousElementSibling;
|
|
1180
1440
|
return node;
|
|
1181
1441
|
}
|
|
1182
1442
|
|
|
@@ -1256,6 +1516,25 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1256
1516
|
}
|
|
1257
1517
|
|
|
1258
1518
|
// ── Racks ──
|
|
1519
|
+
/**
|
|
1520
|
+
* The class whose scene a new rack of this turn shows: an approved direct route this turn runs, else ''. Live, or
|
|
1521
|
+
* a reattach's replay of the running turn; never the journal (no turn runs while it is drawn).
|
|
1522
|
+
*/
|
|
1523
|
+
function directSceneClass() {
|
|
1524
|
+
return (!muted || replayDraw) && turnActive && directRoute && directRoute.turn === turnSeq ? directRoute.taskClass : '';
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* A route a turn takes when it starts (a conversation opened again mid-turn, a pick made while no turn ran): the
|
|
1529
|
+
* open racks made after it are that turn's work, and show its scene from now on. Settled racks never move.
|
|
1530
|
+
*/
|
|
1531
|
+
function lendRouteScene(route) {
|
|
1532
|
+
for (const rack of racks) {
|
|
1533
|
+
if (rack.nested || rack.closed || rack.sceneClass || !(rack.id > (route.rackAt ?? Infinity))) continue;
|
|
1534
|
+
rack.sceneClass = route.taskClass;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1259
1538
|
function createRack(scope) {
|
|
1260
1539
|
const id = ++rackSeq;
|
|
1261
1540
|
const nested = scope.container !== $msgs;
|
|
@@ -1265,9 +1544,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1265
1544
|
row.setAttribute('role', 'group');
|
|
1266
1545
|
const ledgerId = `asst-rack-${id}-ledger`;
|
|
1267
1546
|
row.innerHTML = `<div class="asst-rack-stage">
|
|
1268
|
-
<div class="asst-rack-face" aria-hidden="true"></div>
|
|
1547
|
+
<div class="asst-rack-set"><div class="asst-rack-face" aria-hidden="true"></div><div class="asst-rack-scene" aria-hidden="true"></div></div>
|
|
1269
1548
|
<div class="asst-rack-caption"></div>
|
|
1270
|
-
<div class="asst-bubble" aria-live="off" hidden><span class="asst-bubble-verb"></span><span class="asst-bubble-subject" role="button" tabindex="-1" aria-expanded="false"><span class="asst-bubble-text"></span
|
|
1549
|
+
<div class="asst-bubble" aria-live="off" hidden><span class="asst-bubble-line"><span class="asst-bubble-verb"></span><span class="asst-bubble-subject" role="button" tabindex="-1" aria-expanded="false"><span class="asst-bubble-text"></span></span><span class="asst-bubble-meta"></span></span><span class="asst-bubble-err" hidden></span></div>
|
|
1271
1550
|
<div class="asst-rack-sweep" aria-hidden="true"></div>
|
|
1272
1551
|
</div>
|
|
1273
1552
|
<button type="button" class="asst-rack-receipt" aria-expanded="false" aria-controls="${ledgerId}"><span class="asst-rack-receipt-face" aria-hidden="true"></span><span class="asst-rack-receipt-text"></span><span class="asst-rack-receipt-failed" hidden></span><span class="asst-rack-receipt-clock"></span><span class="asst-rack-chev" aria-hidden="true">›</span></button>
|
|
@@ -1278,7 +1557,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1278
1557
|
const rack = {
|
|
1279
1558
|
id, row, scope, nested, closed: false, live: false, staged: false, open: false, view: 'kind', everAwake: false,
|
|
1280
1559
|
stations: new Map(), steps: [], timeline: [], said: [], firstAt: 0,
|
|
1281
|
-
|
|
1560
|
+
// The direct route's class whose scene the stage shows: set here, or once when a reopened conversation's route
|
|
1561
|
+
// resumes (lendRouteScene); a rack never loses its scene.
|
|
1562
|
+
sceneClass: nested ? '' : directSceneClass(), sceneId: '', sceneAt: -Infinity,
|
|
1563
|
+
stage: { el: q('.asst-rack-stage'), set: q('.asst-rack-set'), face: q('.asst-rack-face'), scene: q('.asst-rack-scene'), caption: q('.asst-rack-caption'), bubble: q('.asst-bubble'), line: q('.asst-bubble-line'), verb: q('.asst-bubble-verb'), subject: q('.asst-bubble-subject'), text: q('.asst-bubble-text'), meta: q('.asst-bubble-meta'), err: q('.asst-bubble-err') },
|
|
1282
1564
|
receipt: { btn: q('.asst-rack-receipt'), face: q('.asst-rack-receipt-face'), text: q('.asst-rack-receipt-text'), failed: q('.asst-rack-receipt-failed'), clock: q('.asst-rack-receipt-clock'), faceBuilt: false, pose: '', svg: null },
|
|
1283
1565
|
ledger: q('.asst-rack-ledger'), statusEl: q('.asst-rack-status'), alertEl: q('.asst-rack-alert'), timelineEl: null, timelineSig: '',
|
|
1284
1566
|
rig: null, target: null, held: false, holdUntil: 0, holdTimer: 0, pose: null, awake: null, awakeKey: null,
|
|
@@ -1454,6 +1736,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1454
1736
|
renderRackLabel(rack, sentence);
|
|
1455
1737
|
if (live) { if (rack.staged) paintStage(rack, now); scheduleStage(rack, now); needTicker(); }
|
|
1456
1738
|
else if (was) settle(rack);
|
|
1739
|
+
// The scene moves while the rack is live, as its sweep does: never while a card holds it, nor once it settled.
|
|
1740
|
+
rack.stage.scene.firstElementChild?.toggleAttribute('data-live', live && !rack.waiting);
|
|
1457
1741
|
if (rack.open && rack.view === 'time') renderTimeline(rack);
|
|
1458
1742
|
if (working?.isConnected) syncWorkingFace();
|
|
1459
1743
|
}
|
|
@@ -1506,6 +1790,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1506
1790
|
clearTimeout(rack.clearTimer);
|
|
1507
1791
|
rack.clearTimer = 0;
|
|
1508
1792
|
rack.row.classList.add('is-staged');
|
|
1793
|
+
watchStageWidth(rack, true);
|
|
1509
1794
|
if (!rack.rig) {
|
|
1510
1795
|
try { rack.rig = mascotLib.createMascot(rack.stage.face, { width: 72, height: 36, rangeX: 10, rangeY: 7, pose: 'idle' }); } catch { rack.rig = null; }
|
|
1511
1796
|
rack.pose = null;
|
|
@@ -1544,6 +1829,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1544
1829
|
/** A folded stage keeps nothing: its rig goes, its texts empty (a settled rack is the same live or replayed). */
|
|
1545
1830
|
function clearStage(rack) {
|
|
1546
1831
|
if (rack.staged) return;
|
|
1832
|
+
watchStageWidth(rack, false);
|
|
1547
1833
|
const had = !!rack.rig;
|
|
1548
1834
|
rack.rig?.destroy?.();
|
|
1549
1835
|
rack.rig = null;
|
|
@@ -1553,6 +1839,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1553
1839
|
if (had) mascotChanged();
|
|
1554
1840
|
const s = rack.stage;
|
|
1555
1841
|
s.face.textContent = '';
|
|
1842
|
+
s.scene.textContent = '';
|
|
1843
|
+
delete s.el.dataset.scene;
|
|
1844
|
+
rack.sceneId = '';
|
|
1845
|
+
rack.sceneAt = -Infinity;
|
|
1556
1846
|
s.caption.textContent = '';
|
|
1557
1847
|
s.bubble.hidden = true;
|
|
1558
1848
|
delete s.bubble.dataset.state;
|
|
@@ -1561,6 +1851,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1561
1851
|
s.text.textContent = '';
|
|
1562
1852
|
s.subject.removeAttribute('title');
|
|
1563
1853
|
s.subject.className = 'asst-bubble-subject';
|
|
1854
|
+
s.subject.hidden = false;
|
|
1564
1855
|
s.subject.setAttribute('aria-expanded', 'false');
|
|
1565
1856
|
s.meta.textContent = '';
|
|
1566
1857
|
s.err.textContent = '';
|
|
@@ -1570,15 +1861,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1570
1861
|
}
|
|
1571
1862
|
|
|
1572
1863
|
/**
|
|
1573
|
-
* One rig of this transcript animates: the hero, else the newest
|
|
1574
|
-
* the
|
|
1575
|
-
*
|
|
1864
|
+
* One rig of this transcript animates: the hero, else the newest open card
|
|
1865
|
+
* the mascot presents (a follow-ups card, a question card: its face is a rig
|
|
1866
|
+
* only then, and never while calm), else the newest stage, else the
|
|
1867
|
+
* "Thinking" row's. The rule ends at this renderer: every other panel runs
|
|
1868
|
+
* its own, so N live sessions show N moving characters.
|
|
1576
1869
|
*/
|
|
1577
1870
|
function activateRigs() {
|
|
1578
|
-
const
|
|
1871
|
+
const ask = hero?.rig ? null : liveAsk();
|
|
1872
|
+
const askLive = !!ask && !calm();
|
|
1873
|
+
for (const handle of askCards) handle.presenter?.setLive?.(askLive && handle === ask);
|
|
1874
|
+
const asking = askLive && !!ask.presenter?.isLive?.();
|
|
1875
|
+
const newest = hero?.rig || asking ? null : newestStage();
|
|
1579
1876
|
for (const rack of racks) if (rack.rig) rack.rig.setActive?.(rack === newest);
|
|
1580
1877
|
hero?.rig?.setActive?.(true);
|
|
1581
|
-
workingFace?.rig?.setActive?.(!hero?.rig && !newest);
|
|
1878
|
+
workingFace?.rig?.setActive?.(!hero?.rig && !asking && !newest);
|
|
1582
1879
|
}
|
|
1583
1880
|
|
|
1584
1881
|
/** The bubble's call: the awake one, else the newest; it holds 900 ms (3 s for thinking) before the next wins. */
|
|
@@ -1638,11 +1935,32 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1638
1935
|
if (!rack.waiting && shown && shown.rack === rack && now - rack.bubble.since < hold) rack.bubble.pendingAt = rack.bubble.since + hold;
|
|
1639
1936
|
else { rack.bubble.step = target; rack.bubble.since = now; }
|
|
1640
1937
|
}
|
|
1938
|
+
paintRackScene(rack, now); // before the bubble: the set it sits beside takes the scene's width
|
|
1641
1939
|
poseStage(rack);
|
|
1642
1940
|
paintBubble(rack, now, { swap: true });
|
|
1643
1941
|
paintCaption(rack);
|
|
1644
1942
|
}
|
|
1645
1943
|
|
|
1944
|
+
/**
|
|
1945
|
+
* A direct route's rack: the scene under the mascot is the route class's
|
|
1946
|
+
* (asst-scenes.js), or the one the bubble's call implies (a browser tool the
|
|
1947
|
+
* browser, a computer tool the computer, a web search or a page read
|
|
1948
|
+
* research); a scene a call picked holds STAGE_TIMING.sceneDwellMs, as on a
|
|
1949
|
+
* run's stage (tickRacks tries again). Nothing for a rack without a route.
|
|
1950
|
+
*/
|
|
1951
|
+
function paintRackScene(rack, now) {
|
|
1952
|
+
if (!rack.sceneClass || !rack.staged) return;
|
|
1953
|
+
const step = rack.bubble.step;
|
|
1954
|
+
const call = step?.type === 'call' ? { kind: step.kind, name: step.name } : null;
|
|
1955
|
+
const want = activityScene(call, rack.sceneClass) || sceneOfClass(rack.sceneClass);
|
|
1956
|
+
if (want === rack.sceneId) return;
|
|
1957
|
+
if (rack.sceneId && now - rack.sceneAt < STAGE_TIMING.sceneDwellMs) return;
|
|
1958
|
+
rack.sceneId = want;
|
|
1959
|
+
rack.sceneAt = now;
|
|
1960
|
+
rack.stage.scene.innerHTML = sceneSvg(want, { live: rack.live && !rack.waiting });
|
|
1961
|
+
rack.stage.el.dataset.scene = want;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1646
1964
|
function paintCaption(rack) {
|
|
1647
1965
|
const step = rack.bubble.step;
|
|
1648
1966
|
const say = step ? sayOf(step) : null;
|
|
@@ -1667,25 +1985,39 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1667
1985
|
const held = rack.waiting && step === waitingStep(rack);
|
|
1668
1986
|
const verb = held ? t('assistant.rack.waiting', 'Waiting for you') : verbText(say.verb, running, t);
|
|
1669
1987
|
const grouped = groupedSubject(rack, step);
|
|
1670
|
-
|
|
1988
|
+
// A thought with no words yet says only "Thinking" (the mascot's thought dots are the one busy sign):
|
|
1989
|
+
// the subject's "…" placeholder stays for a call whose input is still streaming.
|
|
1990
|
+
const bare = step.type === 'think' && say.text === '…';
|
|
1991
|
+
// A command as the stage says it: one "…" however many paths its summary shortens (say.stage).
|
|
1992
|
+
const full = bare ? '' : grouped || say.stage || say.text;
|
|
1671
1993
|
const literal = !grouped && say.full ? say.full : full; // the command as written: the title and the opened bubble
|
|
1672
1994
|
const open = rack.bubble.open;
|
|
1673
|
-
// Narrow stages cut from the head, so the file name (the tail) survives.
|
|
1674
|
-
const cut = subjectCut(rack);
|
|
1675
|
-
const budget = subjectBudget(rack, verb, cut);
|
|
1676
|
-
const shownText = open ? literal : cut === 'head' ? headEllipsis(full, budget) : middleEllipsis(full, budget, 24);
|
|
1677
|
-
const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}`;
|
|
1678
1995
|
s.bubble.hidden = !full && !verb;
|
|
1679
1996
|
s.bubble.dataset.state = held ? 'waiting' : running ? 'running' : step.state;
|
|
1680
1997
|
s.bubble.dataset.kind = step.kind;
|
|
1998
|
+
// The numbers first: they share the subject's line, so the cut below measures the room they leave.
|
|
1999
|
+
paintBubbleMeta(rack, step, say, now);
|
|
2000
|
+
// Narrow stages cut from the head, so the file name (the tail) survives. A small budget keeps a
|
|
2001
|
+
// shorter tail (24 chars of it would not fit beside the verb). A subject already cut from its head
|
|
2002
|
+
// (a streamed thought's tail) is cut from the head again: one "…", never two.
|
|
2003
|
+
const cut = full.startsWith('…') ? 'head' : subjectCut(rack);
|
|
2004
|
+
const cutTo = (n) => (open ? literal : cut === 'head' ? headEllipsis(full, n) : middleEllipsis(full, n, Math.min(24, Math.max(8, n - 6))));
|
|
2005
|
+
const shownText = bare ? '' : cutTo(subjectBudget(rack, verb, cut));
|
|
2006
|
+
const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}|${bare ? 1 : 0}`;
|
|
1681
2007
|
if (key !== rack.bubble.key) {
|
|
1682
2008
|
const changedStep = !rack.bubble.key.startsWith(`${step.id}|`);
|
|
1683
2009
|
rack.bubble.key = key;
|
|
1684
2010
|
const write = () => {
|
|
1685
2011
|
s.verb.textContent = verb;
|
|
1686
2012
|
s.subject.className = `asst-bubble-subject${say.mono ? ' is-mono' : ''}${say.muted ? ' is-muted' : ''}`;
|
|
1687
|
-
|
|
1688
|
-
|
|
2013
|
+
s.subject.hidden = bare;
|
|
2014
|
+
const segments = grouped || open ? null : say.segments;
|
|
2015
|
+
writeSubject(s.text, segments, shownText);
|
|
2016
|
+
// The budget is an estimate (a long token wraps whole to the next line): what the two-line
|
|
2017
|
+
// clamp would hide is cut here instead, so the subject is never cut a second time.
|
|
2018
|
+
const fitted = open || bare ? shownText : fitSubject(s, shownText, cutTo, segments);
|
|
2019
|
+
s.subject.classList.toggle('is-cut', fitted.startsWith('…'));
|
|
2020
|
+
if (literal !== fitted || open) s.subject.title = literal; else s.subject.removeAttribute('title');
|
|
1689
2021
|
};
|
|
1690
2022
|
if (swap && changedStep && !calm() && typeof s.subject.animate === 'function' && s.text.textContent) {
|
|
1691
2023
|
s.subject.getAnimations?.().forEach(a => a.cancel());
|
|
@@ -1697,7 +2029,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1697
2029
|
};
|
|
1698
2030
|
} else write();
|
|
1699
2031
|
}
|
|
1700
|
-
paintBubbleMeta(rack, step, say, now);
|
|
1701
2032
|
const err = step.state === 'error' ? (step.detail || exitText(step)) : '';
|
|
1702
2033
|
s.err.hidden = !err;
|
|
1703
2034
|
if (s.err.textContent !== err) s.err.textContent = err;
|
|
@@ -1708,24 +2039,32 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1708
2039
|
}
|
|
1709
2040
|
|
|
1710
2041
|
/**
|
|
1711
|
-
* How many characters of subject fit the bubble: two lines of
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
2042
|
+
* How many characters of subject fit the bubble: two lines of its one
|
|
2043
|
+
* text flow, less the verb at the head and the numbers at the end,
|
|
2044
|
+
* measured on the laid-out stage. The text is cut in the middle so its
|
|
2045
|
+
* tail (the file, the last argument) stays; on a narrow stage from the
|
|
2046
|
+
* head (subjectCut).
|
|
1715
2047
|
*/
|
|
1716
2048
|
function subjectBudget(rack, verb, cut = 'middle') {
|
|
1717
2049
|
const s = rack.stage;
|
|
1718
2050
|
const width = s.el.clientWidth;
|
|
1719
2051
|
if (!width) return 96;
|
|
1720
|
-
const face = s.face.getBoundingClientRect();
|
|
2052
|
+
const face = (s.set || s.face).getBoundingClientRect(); // the mascot's column (with a scene under it, as wide as the scene)
|
|
1721
2053
|
const bubble = s.bubble.hidden ? null : s.bubble.getBoundingClientRect();
|
|
1722
2054
|
const beside = !bubble || bubble.left >= face.right - 1;
|
|
1723
2055
|
const verbPx = Math.max(s.verb.getBoundingClientRect().width, verb.length * 7);
|
|
1724
|
-
|
|
2056
|
+
// The numbers at the end of the line (painted first): a running call keeps a clock's room before it shows.
|
|
2057
|
+
const metaPx = s.meta.textContent ? s.meta.getBoundingClientRect().width : 0;
|
|
2058
|
+
const numbersPx = Math.max(metaPx, rack.bubble.step?.state === 'running' ? 28 : 0);
|
|
2059
|
+
// The bubble's text column: the stage's 14px sides, the notch's 8px, the bubble's 10px sides.
|
|
2060
|
+
const column = width - 28 - 8 - 20 - (beside ? face.width + 8 : 0);
|
|
1725
2061
|
const perLine = Math.max(12, Math.floor(column / 7));
|
|
1726
|
-
// Two lines, less what wrapping at token boundaries wastes (a head cut
|
|
1727
|
-
|
|
1728
|
-
|
|
2062
|
+
// Two lines, less what wrapping at token boundaries wastes (a head cut is one tail: close to two full
|
|
2063
|
+
// lines, a path's pieces wrapping at its separators); a guess on the generous side, since fitSubject
|
|
2064
|
+
// cuts what the clamp would hide or a piece would push past the edge.
|
|
2065
|
+
const lines = getComputedStyle(s.line).whiteSpace === 'nowrap' ? 1 : cut === 'head' ? 2 : 1.85;
|
|
2066
|
+
const taken = Math.ceil((verbPx + 7 + (numbersPx ? numbersPx + 7 : 0)) / 7);
|
|
2067
|
+
return Math.max(24, Math.min(240, Math.floor(perLine * lines) - taken - 1));
|
|
1729
2068
|
}
|
|
1730
2069
|
|
|
1731
2070
|
/** 'head' where the stylesheet says so (≤320 px: keep the tail), else 'middle'. */
|
|
@@ -1733,21 +2072,59 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1733
2072
|
try { return getComputedStyle(rack.stage.subject).getPropertyValue('--rk-cut').trim() === 'head' ? 'head' : 'middle'; } catch { return 'middle'; }
|
|
1734
2073
|
}
|
|
1735
2074
|
|
|
1736
|
-
/**
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
if (cut > 0 && cut < tail.length * 0.4) tail = tail.slice(tail[cut] === '/' ? cut : cut + 1);
|
|
1743
|
-
return `…${tail}`;
|
|
1744
|
-
}
|
|
1745
|
-
|
|
2075
|
+
/**
|
|
2076
|
+
* The subject's text, in its segments' spans (a read's dim folder, its
|
|
2077
|
+
* bold name). A URL or path in it is one `.asst-bubble-piece` (nowrap) per
|
|
2078
|
+
* piece with a <wbr> between them (wrapRuns): it wraps after "/" and at
|
|
2079
|
+
* its other separators, never mid-word.
|
|
2080
|
+
*/
|
|
1746
2081
|
function writeSubject(node, segments, text) {
|
|
1747
2082
|
node.textContent = '';
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
2083
|
+
const segs = Array.isArray(segments) && segments.length && segments.map(x => x.text).join('') === text ? segments : null;
|
|
2084
|
+
let end = 0;
|
|
2085
|
+
const bounds = segs ? segs.map(seg => [end += seg.text.length, seg.cls]) : [[text.length, null]];
|
|
2086
|
+
// text[from, to) into `host`, one span per segment it crosses.
|
|
2087
|
+
const put = (host, from, to) => {
|
|
2088
|
+
for (const [stop, cls] of bounds) {
|
|
2089
|
+
if (from >= to) break;
|
|
2090
|
+
if (stop <= from) continue;
|
|
2091
|
+
const slice = text.slice(from, Math.min(stop, to));
|
|
2092
|
+
host.appendChild(segs ? el('span', cls ? `is-${cls}` : '', slice) : document.createTextNode(slice));
|
|
2093
|
+
from += slice.length;
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
let at = 0;
|
|
2097
|
+
for (const run of wrapRuns(text)) {
|
|
2098
|
+
if (!run.pieces) { put(node, at, at += run.text.length); continue; }
|
|
2099
|
+
run.pieces.forEach((piece, i) => {
|
|
2100
|
+
if (i) node.appendChild(document.createElement('wbr'));
|
|
2101
|
+
put(node.appendChild(el('span', 'asst-bubble-piece')), at, at += piece.length);
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
/** A path piece wider than what is left of its line: it runs past the line's edge, where the line's overflow hides it. */
|
|
2107
|
+
function pieceOverflows(s) {
|
|
2108
|
+
const pieces = s.text.getElementsByClassName('asst-bubble-piece');
|
|
2109
|
+
if (!pieces.length) return false;
|
|
2110
|
+
const right = s.line.getBoundingClientRect().right + 0.5;
|
|
2111
|
+
for (const piece of pieces) for (const r of piece.getClientRects()) if (r.right > right) return true;
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/**
|
|
2116
|
+
* The bubble's subject cut shorter (`cutTo`, six steps at most) until the line's clamp hides none of it
|
|
2117
|
+
* (nor the numbers after it) and no path piece runs past its edge; the text it shows.
|
|
2118
|
+
*/
|
|
2119
|
+
function fitSubject(s, text, cutTo, segments) {
|
|
2120
|
+
let shown = text;
|
|
2121
|
+
let budget = shown.length;
|
|
2122
|
+
for (let i = 0; i < 6 && budget > 12 && (s.line.scrollHeight > s.line.clientHeight + 1 || pieceOverflows(s)); i += 1) {
|
|
2123
|
+
budget = Math.floor(budget * 0.85);
|
|
2124
|
+
shown = cutTo(budget);
|
|
2125
|
+
writeSubject(s.text, segments, shown);
|
|
2126
|
+
}
|
|
2127
|
+
return shown;
|
|
1751
2128
|
}
|
|
1752
2129
|
|
|
1753
2130
|
/** How many calls of the rack act on the bubble call's target (itself included): a re-run or re-read reads ×N. */
|
|
@@ -1771,7 +2148,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1771
2148
|
// A subagent's bubble follows its child: the step the agent is on now.
|
|
1772
2149
|
if (step.kind === 'subagent' && step.children?.length) {
|
|
1773
2150
|
const child = step.children.filter(c => c.type === 'call' || (c.state === 'running' && !c.stale)).reduce((a, b) => (!a || b.startedAt >= a.startedAt ? b : a), null);
|
|
1774
|
-
if (child) parts.push(['m', middleEllipsis(stepSentence(child, child.state === 'running' && !child.stale), 60, 24)]);
|
|
2151
|
+
if (child) parts.push(['m', middleEllipsis(stepSentence(child, child.state === 'running' && !child.stale, { stage: true }), 60, 24)]);
|
|
1775
2152
|
}
|
|
1776
2153
|
// ×N only for the same target again (a re-run, a re-read): a number the ledger agrees with.
|
|
1777
2154
|
// Other calls in the same 800 ms burst read as a sentence instead.
|
|
@@ -2065,10 +2442,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2065
2442
|
return t('assistant.rack.state.done', 'done');
|
|
2066
2443
|
}
|
|
2067
2444
|
|
|
2068
|
-
/** "Running npm test" / "Ran npm test". */
|
|
2069
|
-
function stepSentence(step, running) {
|
|
2445
|
+
/** "Running npm test" / "Ran npm test"; `stage`: as the stage bubble says it (say.stage). */
|
|
2446
|
+
function stepSentence(step, running, { stage = false } = {}) {
|
|
2070
2447
|
const say = sayOf(step);
|
|
2071
|
-
return `${verbText(say.verb, running, t)} ${String(say.text || '').replace(/^\$ /, '')}`.trim();
|
|
2448
|
+
return `${verbText(say.verb, running, t)} ${String((stage && say.stage) || say.text || '').replace(/^\$ /, '')}`.trim();
|
|
2072
2449
|
}
|
|
2073
2450
|
|
|
2074
2451
|
/** The receipt: one call says what it did; a burst says it in one sentence. */
|
|
@@ -2462,6 +2839,30 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2462
2839
|
unsubTick = null;
|
|
2463
2840
|
}
|
|
2464
2841
|
|
|
2842
|
+
/** The bubbles whose stage changed width: cut and fit again (the same text may fit differently now). */
|
|
2843
|
+
function recutBubbles() {
|
|
2844
|
+
recutTimer = 0;
|
|
2845
|
+
for (const rack of toRecut) {
|
|
2846
|
+
if (!rack.staged || !rack.bubble.step || !rack.row.isConnected) continue;
|
|
2847
|
+
rack.bubble.key = '';
|
|
2848
|
+
paintBubble(rack, Date.now(), { swap: false });
|
|
2849
|
+
}
|
|
2850
|
+
toRecut.clear();
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function watchStageWidth(rack, on) {
|
|
2854
|
+
if (!stageResizes) return;
|
|
2855
|
+
const stage = rack.stage.el;
|
|
2856
|
+
if (on && !stageWidths.has(stage)) {
|
|
2857
|
+
stageWidths.set(stage, { rack, width: null });
|
|
2858
|
+
stageResizes.observe(stage);
|
|
2859
|
+
} else if (!on && stageWidths.has(stage)) {
|
|
2860
|
+
stageWidths.delete(stage);
|
|
2861
|
+
stageResizes.unobserve(stage);
|
|
2862
|
+
toRecut.delete(rack);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2465
2866
|
/** Once a second while a rack is live: its clocks (and any wake-up the timers have not caught). */
|
|
2466
2867
|
function tickRacks() {
|
|
2467
2868
|
let any = false;
|
|
@@ -2470,7 +2871,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2470
2871
|
if (!rack.live) continue;
|
|
2471
2872
|
any = true;
|
|
2472
2873
|
const now = Date.now();
|
|
2473
|
-
if (rack.staged && rack.
|
|
2874
|
+
if (rack.staged && rack.sceneClass) paintRackScene(rack, now); // a scene swap the dwell held back
|
|
2875
|
+
if (rack.staged && rack.bubble.step) paintBubble(rack, now, { swap: false }); // its clocks (a resize re-cuts it: recutBubbles)
|
|
2474
2876
|
for (const st of rack.stations.values()) { const time = stationTime(st, true, rackNow(rack)); if (st.time.textContent !== time) st.time.textContent = time; }
|
|
2475
2877
|
for (const step of rack.steps) if (step.state === 'running' && step.type === 'call' && step.progress >= 1) refreshStep(step);
|
|
2476
2878
|
}
|
|
@@ -2478,6 +2880,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2478
2880
|
}
|
|
2479
2881
|
|
|
2480
2882
|
function dropRack(rack) {
|
|
2883
|
+
watchStageWidth(rack, false);
|
|
2481
2884
|
clearTimeout(rack.stageTimer);
|
|
2482
2885
|
clearTimeout(rack.clearTimer);
|
|
2483
2886
|
clearTimeout(rack.sr.timer);
|
|
@@ -2544,7 +2947,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2544
2947
|
}
|
|
2545
2948
|
|
|
2546
2949
|
function appendUser(text, { images = [], files = [], memories = [], origin = null, imageCount = 0 } = {}) {
|
|
2950
|
+
// The prompt answers what the last turn offered (one of its follow-ups, a retry), or it leaves it behind.
|
|
2951
|
+
settleOffers(String(text ?? ''));
|
|
2952
|
+
limitDock.set('brain', null);
|
|
2547
2953
|
turnFrames = [];
|
|
2954
|
+
directRoute = null; // a new prompt is a new task: its own route decides
|
|
2955
|
+
lastDirect = null;
|
|
2548
2956
|
rootScope.kept = null;
|
|
2549
2957
|
lastErrorText = '';
|
|
2550
2958
|
const row = el('div', 'asst-msg msg-user');
|
|
@@ -2581,17 +2989,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2581
2989
|
}
|
|
2582
2990
|
|
|
2583
2991
|
function appendError(text) {
|
|
2584
|
-
|
|
2992
|
+
const row = appendStatus(text || t('assistant.errorGeneric', 'Something went wrong'), 'error');
|
|
2993
|
+
if (text) offerLimitWait(text); // a usage limit: the waiting card under it
|
|
2994
|
+
return row;
|
|
2585
2995
|
}
|
|
2586
2996
|
|
|
2587
2997
|
function appendDivider(label) {
|
|
2588
2998
|
return append(el('div', 'asst-divider', label || ''));
|
|
2589
2999
|
}
|
|
2590
3000
|
|
|
2591
|
-
/** Place an arbitrary node (control / route card) as a transcript row. */
|
|
3001
|
+
/** Place an arbitrary node (control / route card) as a transcript row; a card that asks is presented by the mascot. */
|
|
2592
3002
|
function appendNode(node) {
|
|
2593
3003
|
if (!node) return null;
|
|
2594
|
-
|
|
3004
|
+
const row = append(node);
|
|
3005
|
+
if (node._ask) registerAsk(node._ask);
|
|
3006
|
+
return row;
|
|
2595
3007
|
}
|
|
2596
3008
|
|
|
2597
3009
|
function showWorking(label) {
|
|
@@ -2604,7 +3016,17 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2604
3016
|
working.querySelector('.asst-working-label').textContent = label || t('assistant.rack.verb.think.now', 'Thinking');
|
|
2605
3017
|
hideEmpty({ park: true });
|
|
2606
3018
|
$msgs.appendChild(working);
|
|
2607
|
-
if (!turnActive)
|
|
3019
|
+
if (!turnActive) {
|
|
3020
|
+
// A new turn: what the last one offered and nobody took is no longer clickable.
|
|
3021
|
+
settleOffers();
|
|
3022
|
+
limitDock.set('brain', null);
|
|
3023
|
+
turn = { calls: 0, last: null, errored: false, answerRow: null };
|
|
3024
|
+
turnSeq += 1;
|
|
3025
|
+
if (directRoute && directRoute.turn == null) { // a route picked while no turn ran, or one the journal left running, is this turn's
|
|
3026
|
+
directRoute.turn = turnSeq;
|
|
3027
|
+
lendRouteScene(directRoute);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
2608
3030
|
turnActive = true;
|
|
2609
3031
|
if (racks.size) syncRacks();
|
|
2610
3032
|
syncWorkingFace();
|
|
@@ -2685,33 +3107,178 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2685
3107
|
onStop: hooks.onStopRun,
|
|
2686
3108
|
onRead: hooks.onReadRun,
|
|
2687
3109
|
onEscalate: hooks.onEscalateRun,
|
|
3110
|
+
onLimitWait: (run, limit) => limitDock.set(`run:${run.runId}`, limit ? {
|
|
3111
|
+
resetsAt: limit.resetsAt || null,
|
|
3112
|
+
since: Date.parse(limit.detectedAt || run.completedAt || '') || nowMs(),
|
|
3113
|
+
provider: providerShortLabel(run.provider || 'claude-code'),
|
|
3114
|
+
onRetry: () => { limitDock.set(`run:${run.runId}`, null); hooks.onEscalateRun?.(run.runId, limitRetryTarget(run)); },
|
|
3115
|
+
} : null),
|
|
2688
3116
|
result,
|
|
2689
3117
|
keepSummary: true,
|
|
2690
3118
|
};
|
|
2691
3119
|
}
|
|
2692
3120
|
|
|
2693
|
-
|
|
2694
|
-
|
|
3121
|
+
/** What the renderer knows of a run (titles for the stage bubble, the route line's live state). */
|
|
3122
|
+
function noteRun(run) {
|
|
2695
3123
|
const prev = runInfo.get(run.runId);
|
|
2696
3124
|
const status = run.status || prev?.state;
|
|
2697
3125
|
const tone = status ? runTone({ ...run, status }) : '';
|
|
2698
3126
|
const routeId = run.route?.routeId || prev?.routeId || '';
|
|
2699
3127
|
runInfo.set(run.runId, { ...prev, title: run.title || run.task || prev?.title, state: status, provider: run.provider, routeId, working: tone === 'running' || tone === 'queued' });
|
|
3128
|
+
if (isTerminalRunStatus(status)) runActivity.delete(run.runId); // the run is over: so is what it was doing
|
|
2700
3129
|
if (routeId) syncRouteLive(routeId);
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
function upsertRunCard(run, { result = null } = {}) {
|
|
3133
|
+
if (!run?.runId) return null;
|
|
3134
|
+
noteRun(run);
|
|
2701
3135
|
const cardOpts = runCardOpts(result);
|
|
2702
3136
|
let card = runCards.get(run.runId);
|
|
2703
3137
|
if (card && card.isConnected) {
|
|
2704
3138
|
updateRunCard(card, run, cardOpts);
|
|
3139
|
+
syncStage(run.runId, card, run);
|
|
2705
3140
|
for (const rack of racks) if (rack.live) renderRack(rack); // "Waiting on <title>" learns the title
|
|
2706
3141
|
return card;
|
|
2707
3142
|
}
|
|
3143
|
+
dropStage(run.runId); // a card that left the transcript took its stage with it
|
|
2708
3144
|
card = buildRunCard(run, cardOpts);
|
|
2709
3145
|
runCards.set(run.runId, card);
|
|
2710
3146
|
const { wrap } = assistantRow(rootScope);
|
|
2711
3147
|
wrap.appendChild(card);
|
|
3148
|
+
syncStage(run.runId, card, run);
|
|
2712
3149
|
return card;
|
|
2713
3150
|
}
|
|
2714
3151
|
|
|
3152
|
+
/**
|
|
3153
|
+
* A run update from anywhere (the agents tray's sources: the sync bus, its
|
|
3154
|
+
* fetch): the card and its stage update in place, the route line learns
|
|
3155
|
+
* whether its run works. Never adds a card.
|
|
3156
|
+
*/
|
|
3157
|
+
function syncRun(run) {
|
|
3158
|
+
if (!run?.runId) return null;
|
|
3159
|
+
noteRun(run);
|
|
3160
|
+
const card = runCards.get(run.runId);
|
|
3161
|
+
if (!card?.isConnected) { limitDock.set(`run:${run.runId}`, null); return null; }
|
|
3162
|
+
updateRunCard(card, run, runCardOpts()); // fresh options: the run's own lastResult, never a result the card was built with
|
|
3163
|
+
syncStage(run.runId, card, run);
|
|
3164
|
+
return card;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
// ── Worker stages (asst-run-stage.js) ──
|
|
3168
|
+
/** The card's stage: made when the run first works, then updated in place for as long as the card lives. */
|
|
3169
|
+
function syncStage(runId, card, run) {
|
|
3170
|
+
let stage = stages.get(runId);
|
|
3171
|
+
if (!stage) {
|
|
3172
|
+
if (!isLivePhase(stagePhase(run))) return;
|
|
3173
|
+
stage = createWorkerStage(card, { t, calm: () => reducedMotion() });
|
|
3174
|
+
stages.set(runId, stage);
|
|
3175
|
+
stage.setActivity(runActivity.get(runId) || null);
|
|
3176
|
+
}
|
|
3177
|
+
stage.update(run);
|
|
3178
|
+
if (stage.live()) needStageTicker();
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
function dropStage(runId) {
|
|
3182
|
+
const stage = stages.get(runId);
|
|
3183
|
+
if (!stage) return;
|
|
3184
|
+
stages.delete(runId);
|
|
3185
|
+
try { stage.destroy(); } catch { /* gone */ }
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
function needStageTicker() {
|
|
3189
|
+
if (unsubStageTick) return;
|
|
3190
|
+
try { unsubStageTick = subscribe(tickStages, { fps: 1 }); } catch { unsubStageTick = null; }
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
function stopStageTicker() {
|
|
3194
|
+
if (!unsubStageTick) return;
|
|
3195
|
+
try { unsubStageTick(); } catch { /* already gone */ }
|
|
3196
|
+
unsubStageTick = null;
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
/** Once a second while a stage is open: its beat, and the card's elapsed time (the run's one clock: the details line). */
|
|
3200
|
+
function tickStages() {
|
|
3201
|
+
let any = false;
|
|
3202
|
+
const now = Date.now();
|
|
3203
|
+
for (const [runId, stage] of [...stages]) {
|
|
3204
|
+
const card = runCards.get(runId);
|
|
3205
|
+
if (!card?.isConnected) { dropStage(runId); continue; }
|
|
3206
|
+
if (!stage.live()) continue;
|
|
3207
|
+
any = true;
|
|
3208
|
+
stage.tick(now);
|
|
3209
|
+
if (card._usageRun) tickRunCard(card, card._usageRun);
|
|
3210
|
+
}
|
|
3211
|
+
if (!any) stopStageTicker();
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
/** What a run's worker does now (its provider events: Claude and Codex workers). Kept compact, and only while the run lasts. */
|
|
3215
|
+
function setRunActivity(runId, activity) {
|
|
3216
|
+
if (!runId) return;
|
|
3217
|
+
const next = isTerminalRunStatus(runInfo.get(runId)?.state) ? null : compactActivity(activity, t);
|
|
3218
|
+
if (next) runActivity.set(runId, next); else runActivity.delete(runId);
|
|
3219
|
+
while (runActivity.size > 200) runActivity.delete(runActivity.keys().next().value);
|
|
3220
|
+
stages.get(runId)?.setActivity(next);
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
/** The row a restored card sits after: its dispatch call's rack, else its route line; null when neither is drawn. */
|
|
3224
|
+
function dispatchAnchor(run) {
|
|
3225
|
+
const routeId = run.route?.routeId || '';
|
|
3226
|
+
let byRoute = null;
|
|
3227
|
+
for (const step of stepsById.values()) {
|
|
3228
|
+
if (step.type !== 'call' || step.kind !== 'runs' || step.rack.nested || !/agent_dispatch$/i.test(String(step.name))) continue;
|
|
3229
|
+
if (step.result && step.result.includes(run.runId)) return step.rack.row;
|
|
3230
|
+
if (!byRoute && routeId && (step.input?.route_id === routeId || step.input?.routeId === routeId)) byRoute = step.rack.row;
|
|
3231
|
+
}
|
|
3232
|
+
if (byRoute?.isConnected) return byRoute;
|
|
3233
|
+
const line = routeId ? routeLines.get(routeId) : null;
|
|
3234
|
+
return line?.el?.isConnected ? line.el : null;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
const startMs = (run) => new Date(run.startedAt || run.createdAt || 0).getTime() || 0;
|
|
3238
|
+
|
|
3239
|
+
/**
|
|
3240
|
+
* After a reload (run cards are not journaled): each run's card comes back
|
|
3241
|
+
* next to the call that dispatched it (runs of one call in the order they
|
|
3242
|
+
* started), live when its worker still works. A finished run with nothing
|
|
3243
|
+
* in the transcript to sit by is left to the tray; one still working is
|
|
3244
|
+
* added at the end. A card already drawn is only updated.
|
|
3245
|
+
*/
|
|
3246
|
+
function restoreRuns(list) {
|
|
3247
|
+
const placed = new Map(); // anchor row → the last card row put after it
|
|
3248
|
+
const runs = (Array.isArray(list) ? list : []).filter(run => run?.runId).sort((a, b) => startMs(a) - startMs(b));
|
|
3249
|
+
for (const run of runs) {
|
|
3250
|
+
if (runCards.get(run.runId)?.isConnected) { syncRun(run); continue; }
|
|
3251
|
+
const anchor = dispatchAnchor(run);
|
|
3252
|
+
if (!anchor && isTerminalRunStatus(run.status)) { noteRun(run); continue; }
|
|
3253
|
+
const row = upsertRunCard(run)?.closest('.asst-msg');
|
|
3254
|
+
if (!row || !anchor?.isConnected || anchor === row) continue;
|
|
3255
|
+
const after = placed.get(anchor);
|
|
3256
|
+
(after?.isConnected ? after : anchor).after(row);
|
|
3257
|
+
placed.set(anchor, row);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
/** Bring a run's card into view and hand it focus (the agents tray's mini scenes and lanes). */
|
|
3262
|
+
function revealRun(runId) {
|
|
3263
|
+
const card = runCards.get(runId);
|
|
3264
|
+
if (!card?.isConnected) return false;
|
|
3265
|
+
const top = card.getBoundingClientRect().top - $msgs.getBoundingClientRect().top + $msgs.scrollTop - 16;
|
|
3266
|
+
try { $msgs.scrollTo({ top: Math.max(0, top), behavior: calm() ? 'auto' : 'smooth' }); } catch { $msgs.scrollTop = Math.max(0, top); }
|
|
3267
|
+
card.classList.remove('is-revealed');
|
|
3268
|
+
void card.offsetWidth; // restart the highlight
|
|
3269
|
+
card.classList.add('is-revealed');
|
|
3270
|
+
clearTimeout(card._revealTimer);
|
|
3271
|
+
card._revealTimer = setTimeout(() => card.classList.remove('is-revealed'), 1600);
|
|
3272
|
+
if (!card.hasAttribute('tabindex')) card.tabIndex = -1;
|
|
3273
|
+
try { card.focus({ preventScroll: true }); } catch { /* not focusable */ }
|
|
3274
|
+
return true;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
function dropStages() {
|
|
3278
|
+
for (const runId of [...stages.keys()]) dropStage(runId);
|
|
3279
|
+
stopStageTicker();
|
|
3280
|
+
}
|
|
3281
|
+
|
|
2715
3282
|
function patchRunUsage(runId, tokens, fidelity) {
|
|
2716
3283
|
const card = runCards.get(runId);
|
|
2717
3284
|
if (!card?.isConnected || !card._usageRun) return;
|
|
@@ -2749,6 +3316,43 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2749
3316
|
}
|
|
2750
3317
|
|
|
2751
3318
|
// ── Route lines ──
|
|
3319
|
+
/**
|
|
3320
|
+
* A decided route says what the Assistant's own racks show. A direct one
|
|
3321
|
+
* ("do it here") whose class has a scene lends it to the racks of the turn
|
|
3322
|
+
* it runs in: this turn, or the next one when it was picked while no turn
|
|
3323
|
+
* ran (a late card answer). A dispatch or chat route takes it back, and so
|
|
3324
|
+
* does the end of its card (declined, expired, cancelled). The scene goes
|
|
3325
|
+
* with its turn (finishTurn; in a journal or a replay, the turn's `result`) and with a new prompt (appendUser).
|
|
3326
|
+
*/
|
|
3327
|
+
function noteDirectRoute(route, phase) {
|
|
3328
|
+
const id = String(route?.routeId || '');
|
|
3329
|
+
if (phase !== 'auto' && phase !== 'decided') {
|
|
3330
|
+
if (directRoute && directRoute.routeId === id && (phase === 'declined' || phase === 'expired' || phase === 'cancelled')) directRoute = null;
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
const target = route?.target && typeof route.target === 'object' ? route.target : null;
|
|
3334
|
+
const cls = sceneOfClass(route?.taskClass);
|
|
3335
|
+
const direct = !!target && String(target.kind || 'direct') === 'direct' && cls !== 'generic';
|
|
3336
|
+
// Drawn from the journal (not a live event, not a reattach's replay of the running turn): its turn is the journal's last.
|
|
3337
|
+
const journal = muted && !replayDraw;
|
|
3338
|
+
// That turn is known to be over (the reattach found nothing running, e.g. the journal was drawn again after it):
|
|
3339
|
+
// nothing takes its route.
|
|
3340
|
+
const over = journal && !turnActive && reattachRunning === false;
|
|
3341
|
+
directRoute = direct && !over ? { routeId: id, taskClass: cls, turn: turnActive ? turnSeq : null, rackAt: rackSeq, journal } : null;
|
|
3342
|
+
lastDirect = direct ? { routeId: id, taskClass: cls } : null;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/**
|
|
3346
|
+
* What a reattach said (asst-panel.js reattach_result). With no turn running, a route the journal left without its
|
|
3347
|
+
* turn's result belonged to a turn that is over: a later turn (one the CLI starts on its own, a mailbox turn) is
|
|
3348
|
+
* something else and never takes its scene. With one running, that turn is the journal's last: it takes the route
|
|
3349
|
+
* when it starts (showWorking).
|
|
3350
|
+
*/
|
|
3351
|
+
function reattached(running) {
|
|
3352
|
+
reattachRunning = !!running;
|
|
3353
|
+
if (!running && directRoute?.journal && directRoute.turn == null) directRoute = null;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
2752
3356
|
function routeLineOpts(phase) {
|
|
2753
3357
|
return { phase, t: hooks.t, providerIcon: hooks.providerIcon, brainLabel: hooks.brainLabel, onChangeRoutes: hooks.onChangeRoutes };
|
|
2754
3358
|
}
|
|
@@ -2894,8 +3498,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2894
3498
|
// Under the reply once the turn has ended; a later snapshot (learning) never moves it again.
|
|
2895
3499
|
const ended = ev.phase !== 'sent';
|
|
2896
3500
|
if (fresh || (ended && !row.atEnd)) {
|
|
2897
|
-
// Placed like any row (before "Working…"), but a status line of the harness never dismisses the hero.
|
|
2898
|
-
if (working && working.parentNode === $msgs)
|
|
3501
|
+
// Placed like any row (above the open cards and before "Working…"), but a status line of the harness never dismisses the hero.
|
|
3502
|
+
if (askTail.slot || (working && working.parentNode === $msgs)) askTail.insert(row.el);
|
|
2899
3503
|
else $msgs.appendChild(row.el);
|
|
2900
3504
|
if (ended) row.atEnd = true;
|
|
2901
3505
|
prune();
|
|
@@ -2909,13 +3513,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2909
3513
|
function showEmpty() {
|
|
2910
3514
|
if (emptyEl || $msgs.querySelector(':scope > :not(.asst-working)')) return;
|
|
2911
3515
|
emptyEl = el('div', 'asst-empty');
|
|
3516
|
+
// One column, one hierarchy: the character and the heading, the greeting when there is one,
|
|
3517
|
+
// the starters, then the context line (Project · Budget). No lead: the starters say what it can do.
|
|
2912
3518
|
emptyEl.innerHTML = `
|
|
2913
3519
|
<div class="asst-empty-mascot" aria-hidden="true"></div>
|
|
2914
3520
|
<div class="asst-empty-title" role="heading" aria-level="2">${esc(t('assistant.empty.heading', 'What should we do?'))}</div>
|
|
2915
|
-
<div class="asst-empty-body">${esc(t('assistant.empty.lead', 'I can use your Mac, look things up on the web, edit files and run commands, and remember or schedule things.'))}</div>
|
|
2916
3521
|
<div class="asst-empty-greet"></div>
|
|
2917
|
-
<div class="asst-
|
|
2918
|
-
<div class="asst-
|
|
3522
|
+
<div class="asst-suggestions" role="list" aria-label="${esc(t('assistant.empty.starters', 'Starters'))}"></div>
|
|
3523
|
+
<div class="asst-empty-project" role="group" aria-label="${esc(t('assistant.empty.context', 'Context'))}" hidden></div>`;
|
|
2919
3524
|
const box = emptyEl.querySelector('.asst-empty-mascot');
|
|
2920
3525
|
try {
|
|
2921
3526
|
const rig = mascotLib.createMascot(box, { width: 120, height: 60 });
|
|
@@ -2923,8 +3528,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2923
3528
|
hero.target = heroTarget(hero);
|
|
2924
3529
|
wireEgg(hero);
|
|
2925
3530
|
} catch { hero = null; }
|
|
2926
|
-
// The project the conversation works in, under the
|
|
2927
|
-
// picker's project field), so it and the toolbar's Working project are one control.
|
|
3531
|
+
// The project the conversation works in, on the context line under the starters: the host draws
|
|
3532
|
+
// the picker (the brain picker's project field), so it and the toolbar's Working project are one control.
|
|
2928
3533
|
const projectSlot = emptyEl.querySelector('.asst-empty-project');
|
|
2929
3534
|
let picker = null;
|
|
2930
3535
|
try { picker = typeof hooks.mountEmptyProject === 'function' ? hooks.mountEmptyProject(projectSlot) : null; } catch { picker = null; }
|
|
@@ -2941,8 +3546,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2941
3546
|
}
|
|
2942
3547
|
|
|
2943
3548
|
/**
|
|
2944
|
-
* The greeting SynaBun prepared for this return (public/shared/greeting/)
|
|
2945
|
-
* The panel calls it once it knows the conversation is new: a restored one is never greeted.
|
|
3549
|
+
* The greeting SynaBun prepared for this return (public/shared/greeting/) sits between the heading
|
|
3550
|
+
* and the starters. The panel calls it once it knows the conversation is new: a restored one is never greeted.
|
|
2946
3551
|
*/
|
|
2947
3552
|
function offerGreeting() {
|
|
2948
3553
|
const owner = emptyEl;
|
|
@@ -2960,12 +3565,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2960
3565
|
host.innerHTML = '';
|
|
2961
3566
|
let list = [];
|
|
2962
3567
|
try { list = typeof hooks.emptySuggestions === 'function' ? (hooks.emptySuggestions() || []) : []; } catch { list = []; }
|
|
3568
|
+
let i = 0;
|
|
2963
3569
|
for (const s of list) {
|
|
2964
|
-
//
|
|
3570
|
+
// A ghost row: the kind's prop and the label (no capsule, no seam); it comes in staggered by --i,
|
|
3571
|
+
// and the character acts the kind out while the suggestion is hovered or focused.
|
|
2965
3572
|
const chip = el('button', 'asst-chip asst-suggestion');
|
|
2966
3573
|
chip.type = 'button';
|
|
2967
3574
|
chip.setAttribute('role', 'listitem');
|
|
2968
3575
|
chip.dataset.suggestion = s.id || '';
|
|
3576
|
+
chip.style.setProperty('--i', String(i + 1));
|
|
3577
|
+
i += 1;
|
|
2969
3578
|
const kind = s.kind || SUGGESTION_KINDS[s.id] || null;
|
|
2970
3579
|
if (kind) {
|
|
2971
3580
|
chip.dataset.kind = kind;
|
|
@@ -2979,10 +3588,28 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2979
3588
|
chip.addEventListener('blur', () => previewHero(null));
|
|
2980
3589
|
}
|
|
2981
3590
|
chip.appendChild(el('span', 'asst-chip-text', s.label));
|
|
2982
|
-
chip.
|
|
3591
|
+
if (s.meta) chip.appendChild(el('span', 'asst-chip-meta', s.meta));
|
|
3592
|
+
chip.addEventListener('click', () => {
|
|
3593
|
+
// The picked starter keeps the gold on-state while its text is the draft (setDrafting clears it);
|
|
3594
|
+
// a row with an action (the Workspace tour) fills nothing, so it is never "picked".
|
|
3595
|
+
if (!s.action) {
|
|
3596
|
+
for (const other of host.querySelectorAll('.is-picked')) other.classList.remove('is-picked');
|
|
3597
|
+
chip.classList.add('is-picked');
|
|
3598
|
+
}
|
|
3599
|
+
hooks.onSuggestion?.(s.template, s);
|
|
3600
|
+
});
|
|
2983
3601
|
host.appendChild(chip);
|
|
2984
3602
|
}
|
|
2985
3603
|
host.hidden = !list.length;
|
|
3604
|
+
// The context line comes in after the last starter.
|
|
3605
|
+
emptyEl.querySelector('.asst-empty-project')?.style.setProperty('--i', String(list.length + 1));
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
/** The composer holds a draft: the starters recede (the picked one stays lit) until it is cleared. */
|
|
3609
|
+
function setDrafting(on) {
|
|
3610
|
+
if (!emptyEl) return;
|
|
3611
|
+
emptyEl.classList.toggle('is-drafting', !!on);
|
|
3612
|
+
if (!on) for (const chip of emptyEl.querySelectorAll('.asst-suggestion.is-picked')) chip.classList.remove('is-picked');
|
|
2986
3613
|
}
|
|
2987
3614
|
|
|
2988
3615
|
/** The empty state's character tries a suggestion on (null: back to idle); never over a pose the director holds. */
|
|
@@ -3187,6 +3814,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3187
3814
|
const content = Array.isArray(ev.message.content) ? ev.message.content : [];
|
|
3188
3815
|
lastErrorText = typeof ev.message.content === 'string' ? ev.message.content.trim()
|
|
3189
3816
|
: content.filter((b) => b?.type === 'text').map((b) => b.text).join('\n').trim();
|
|
3817
|
+
if (!parentId) offerLimitWait(lastErrorText, { brainError: true });
|
|
3190
3818
|
}
|
|
3191
3819
|
return true;
|
|
3192
3820
|
}
|
|
@@ -3212,6 +3840,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3212
3840
|
if (!parentId && !muted) grace = newestStage();
|
|
3213
3841
|
if (grace) grace.holdUntil = Infinity;
|
|
3214
3842
|
if (!parentId) endBursts(rootScope);
|
|
3843
|
+
// A journal's or a replay's turn ended here (live, finishTurn ends it): so did the route it ran.
|
|
3844
|
+
if (!parentId && muted) directRoute = null;
|
|
3845
|
+
// A turn that ended well: its last message's options in prose become items (no block of its own).
|
|
3846
|
+
if (!parentId && !ev.is_error && !String(ev.subtype || '').startsWith('error')) guessOffer();
|
|
3215
3847
|
hooks.onResult?.(ev);
|
|
3216
3848
|
if (errorText && errorText !== lastErrorText) appendError(errorText);
|
|
3217
3849
|
if (!parentId) { lastErrorText = ''; rootScope.kept = null; releaseGrace(); }
|
|
@@ -3270,11 +3902,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3270
3902
|
return true;
|
|
3271
3903
|
case 'synabun.route': {
|
|
3272
3904
|
const phase = ev.phase || 'decided';
|
|
3905
|
+
noteDirectRoute(ev.route, phase); // the journal's too: a conversation opened again keeps the route of its last turn
|
|
3273
3906
|
if (hooks.onRouteEvent?.(ev)) return true; // an open card represents it
|
|
3274
3907
|
if (phase === 'card' && silent) return true; // replayed card: the pending control re-sends it
|
|
3275
3908
|
upsertRouteLine(ev.route || {}, phase);
|
|
3276
3909
|
return true;
|
|
3277
3910
|
}
|
|
3911
|
+
case 'synabun.continuation':
|
|
3912
|
+
// A "do it here" pick on another model runs as a turn of its own: the route it approved goes on there.
|
|
3913
|
+
if (lastDirect) directRoute = { ...lastDirect, turn: turnActive ? turnSeq : null, rackAt: rackSeq };
|
|
3914
|
+
return true;
|
|
3278
3915
|
case 'synabun.clarify':
|
|
3279
3916
|
// The card itself arrives as a control_request; its outcome is a line (the panel's card shows it live).
|
|
3280
3917
|
if (ev.phase !== 'card') appendStatus(clarifyLine(ev), 'info');
|
|
@@ -3313,14 +3950,19 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3313
3950
|
/**
|
|
3314
3951
|
* The turn ended. `holdMs`: the panel plays a one-shot (success, error) on
|
|
3315
3952
|
* the character on screen, so its stage stays up that long before it folds.
|
|
3953
|
+
* `guess`: the turn ended well, so options its last message offers in prose may become items.
|
|
3316
3954
|
*/
|
|
3317
|
-
function finishTurn({ holdMs = 0 } = {}) {
|
|
3955
|
+
function finishTurn({ holdMs = 0, guess = true } = {}) {
|
|
3318
3956
|
flushStream(rootScope);
|
|
3957
|
+
sealStreamRow(rootScope);
|
|
3319
3958
|
if (rootScope.stream?.row) rootScope.stream.row.classList.remove('streaming');
|
|
3959
|
+
// The `result` usually guessed already; a turn that ended well without one gets it here (never a stopped or failed one).
|
|
3960
|
+
if (guess && !turn.errored) guessOffer();
|
|
3320
3961
|
for (const scope of scopes.values()) flushStream(scope);
|
|
3321
3962
|
closeStreamThinks();
|
|
3322
3963
|
hideWorking();
|
|
3323
3964
|
turnActive = false;
|
|
3965
|
+
if (directRoute && directRoute.turn === turnSeq) directRoute = null; // its turn is over
|
|
3324
3966
|
const stage = grace || newestStage();
|
|
3325
3967
|
grace = null;
|
|
3326
3968
|
const hold = holdMs > 0 && !calm();
|
|
@@ -3356,11 +3998,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3356
3998
|
}
|
|
3357
3999
|
if (failed) console.warn(`[assistant] ${failed} of ${list.length} transcript entries could not be drawn`, firstError);
|
|
3358
4000
|
flushStream(rootScope);
|
|
4001
|
+
sealStreamRow(rootScope);
|
|
3359
4002
|
if (rootScope.stream?.row) rootScope.stream.row.classList.remove('streaming');
|
|
3360
4003
|
for (const scope of scopes.values()) flushStream(scope);
|
|
3361
4004
|
hideWorking();
|
|
3362
4005
|
turnActive = false;
|
|
3363
4006
|
for (const rack of [...racks]) renderRack(rack);
|
|
4007
|
+
// Drawn quietly: a card the history leaves open (nobody answered it yet) comes to life now.
|
|
4008
|
+
mascotChanged();
|
|
3364
4009
|
scrollEnd(true);
|
|
3365
4010
|
}
|
|
3366
4011
|
|
|
@@ -3402,6 +4047,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3402
4047
|
if (rootScope.stream?.mdTimer) clearTimeout(rootScope.stream.mdTimer);
|
|
3403
4048
|
if (rootScope.stream?.thinkTimer) clearTimeout(rootScope.stream.thinkTimer);
|
|
3404
4049
|
scopes.clear();
|
|
4050
|
+
dropStages();
|
|
4051
|
+
limitDock.clear();
|
|
4052
|
+
for (const offer of offers) { try { offer.wait?.destroy(); } catch { /* gone */ } }
|
|
4053
|
+
offers.clear();
|
|
4054
|
+
dropAsks();
|
|
4055
|
+
lastTextRow = null;
|
|
4056
|
+
for (const card of runCards.values()) { try { card._limitWait?.destroy(); } catch { /* gone */ } }
|
|
4057
|
+
runActivity.clear();
|
|
3405
4058
|
runCards.clear();
|
|
3406
4059
|
routeLines.clear();
|
|
3407
4060
|
autoRows.clear();
|
|
@@ -3417,6 +4070,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3417
4070
|
dropHero();
|
|
3418
4071
|
grace = null;
|
|
3419
4072
|
turn = { calls: 0, last: null, errored: false, answerRow: null };
|
|
4073
|
+
directRoute = null;
|
|
4074
|
+
lastDirect = null;
|
|
3420
4075
|
mcpWarned.clear();
|
|
3421
4076
|
seen.clear();
|
|
3422
4077
|
snapshot = null;
|
|
@@ -3430,7 +4085,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3430
4085
|
return {
|
|
3431
4086
|
el: $msgs,
|
|
3432
4087
|
clear,
|
|
3433
|
-
destroy: () => {
|
|
4088
|
+
destroy: () => { clear(); limitDock.destroy(); },
|
|
3434
4089
|
appendUser,
|
|
3435
4090
|
appendAssistantMarkdown,
|
|
3436
4091
|
appendStatus,
|
|
@@ -3441,6 +4096,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3441
4096
|
appendMemorySaved,
|
|
3442
4097
|
appendMailbox,
|
|
3443
4098
|
upsertRunCard,
|
|
4099
|
+
/** A run update that never adds a card (the tray's sources): the card, its stage and the route line follow it. */
|
|
4100
|
+
syncRun,
|
|
4101
|
+
/** After a reload: these runs' cards again, next to the calls that dispatched them (see restoreRuns). */
|
|
4102
|
+
restoreRuns,
|
|
4103
|
+
/** What a run's worker does now: its stage's scene and step follow it. */
|
|
4104
|
+
setRunActivity,
|
|
4105
|
+
/** Scroll a run's card into view and focus it. */
|
|
4106
|
+
revealRun,
|
|
4107
|
+
/** For tests and devtools: a run card's stage ({ open, phase, scene, final, pose, step, label }) or null. */
|
|
4108
|
+
stageOf: (runId) => stages.get(runId)?.state() || null,
|
|
3444
4109
|
patchRunUsage,
|
|
3445
4110
|
upsertRouteLine,
|
|
3446
4111
|
registerRouteLine,
|
|
@@ -3453,10 +4118,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3453
4118
|
claimEvent,
|
|
3454
4119
|
/** A reattach's buffered packets minus what is on screen (see planReplay). */
|
|
3455
4120
|
planReplay,
|
|
4121
|
+
/** What the reattach said (a turn runs or not): a route the journal left open goes only to that turn (see reattached). */
|
|
4122
|
+
reattached,
|
|
3456
4123
|
/** Forget what was drawn (a brain switch starts a new id space). */
|
|
3457
4124
|
forgetSeen: () => { seen.clear(); snapshot = null; },
|
|
3458
4125
|
/** A card waits on the user: `toolUseId` names the call it holds (the newest live stage when unknown); false clears it. */
|
|
3459
4126
|
setWaiting,
|
|
4127
|
+
/** The session's usage-limit report changed: this turn's waiting card counts to its reset time. */
|
|
4128
|
+
noteLimit,
|
|
3460
4129
|
finishTurn,
|
|
3461
4130
|
renderTranscript,
|
|
3462
4131
|
showWorking,
|
|
@@ -3465,6 +4134,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3465
4134
|
showEmpty,
|
|
3466
4135
|
offerGreeting,
|
|
3467
4136
|
refreshEmpty: renderSuggestions,
|
|
4137
|
+
setDrafting,
|
|
3468
4138
|
isEmpty: () => !!emptyEl,
|
|
3469
4139
|
scrollEnd,
|
|
3470
4140
|
isNearBottom,
|