synabun 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -30,6 +30,19 @@
|
|
|
30
30
|
// conversation opened again learns its route from the journal like any other
|
|
31
31
|
// event (no rack of the history moves: none is live) and lends it to the
|
|
32
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.
|
|
33
46
|
|
|
34
47
|
import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
|
|
35
48
|
import { createRouteLine } from './asst-route.js';
|
|
@@ -37,7 +50,7 @@ import { closeMenu, isMenuOpen } from './asst-menu.js';
|
|
|
37
50
|
import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
|
|
38
51
|
import * as mascotLib from '../synabun-mascot.js';
|
|
39
52
|
import { isReducedMotion, subscribe } from '../synabun-ticker.js';
|
|
40
|
-
import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject,
|
|
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';
|
|
41
54
|
import { injectRackStyles } from './asst-rack-styles.js';
|
|
42
55
|
import { injectSceneStyles } from './asst-scene-styles.js';
|
|
43
56
|
import { activityScene, compactActivity, isLivePhase, runChip, sceneOfClass, sceneSvg, stagePhase } from './asst-scenes.js';
|
|
@@ -45,6 +58,12 @@ import { STAGE_TIMING, createWorkerStage } from './asst-run-stage.js';
|
|
|
45
58
|
import { formatRunTokens } from './asst-usage.js';
|
|
46
59
|
import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
|
|
47
60
|
import { automationView } from './asst-automation.js';
|
|
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';
|
|
48
67
|
|
|
49
68
|
// ── Markdown (lazy marked, the vendored build the sidepanels use too) ───────────────
|
|
50
69
|
let _marked = null;
|
|
@@ -259,12 +278,15 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
259
278
|
const readBtn = card.querySelector('[data-action="read"]');
|
|
260
279
|
if (readBtn) readBtn.disabled = run.status === 'queued';
|
|
261
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
|
+
|
|
262
284
|
// Escalation offer ("Escalate to ‹label›", or "Retry on ‹label›" after a
|
|
263
285
|
// temporary failure) when the server proposes one; a note without a button
|
|
264
286
|
// when a stronger model would hit the same wall (access, the user's input).
|
|
265
287
|
let esc8 = card.querySelector('.asst-run-escalate');
|
|
266
288
|
const escalationRaw = run.escalation && typeof run.escalation === 'object' ? run.escalation : null;
|
|
267
|
-
const escalation = escalationRaw?.to ? escalationRaw : null;
|
|
289
|
+
const escalation = escalationRaw?.to && !(limitShown && escalationRaw.kind === 'retry') ? escalationRaw : null;
|
|
268
290
|
const needsNote = escalationRaw && escalationRaw.kind === 'none' ? escalationRaw : null;
|
|
269
291
|
const onEscalate = opts.onEscalate || card._runOpts?.onEscalate;
|
|
270
292
|
if (esc8 && esc8.dataset.kind !== (needsNote ? 'note' : 'offer')) { esc8.remove(); esc8 = null; card._escalation = null; }
|
|
@@ -322,6 +344,49 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
322
344
|
updateRunMedia(card, run, { t, lightboxT: opts.t || card._runOpts?.t });
|
|
323
345
|
}
|
|
324
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
|
+
|
|
325
390
|
const MAX_RUN_MEDIA = 8;
|
|
326
391
|
|
|
327
392
|
/**
|
|
@@ -424,7 +489,7 @@ const PREVIEW_LINES = 5;
|
|
|
424
489
|
const THINK_PREVIEW_LINES = 10;
|
|
425
490
|
const MAX_DIFF_LINES = 400; // an edit's mini diff draws this many lines; "Show full input" draws the rest
|
|
426
491
|
// The empty state's suggestions (asst-panel.js ids) and the kind each one is about.
|
|
427
|
-
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' };
|
|
428
493
|
|
|
429
494
|
/**
|
|
430
495
|
* createRenderer(transcriptEl, hooks)
|
|
@@ -433,7 +498,11 @@ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedu
|
|
|
433
498
|
* emptySuggestions() → [{ id, label, template }], onSuggestion(template),
|
|
434
499
|
* brainLabel() → string, onChangeRoutes(), onRouteEvent(ev) → bool (true = handled by an open card),
|
|
435
500
|
* heroOrigin() → DOMRect | null (where the empty state's character flies in from: the host's placeholder),
|
|
436
|
-
* 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`) }
|
|
437
506
|
*/
|
|
438
507
|
export function createRenderer(transcriptEl, hooks = {}) {
|
|
439
508
|
loadMarked();
|
|
@@ -449,6 +518,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
449
518
|
const autoRows = new Map(); // traceId → the turn's automation trace row
|
|
450
519
|
let working = null;
|
|
451
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) });
|
|
452
524
|
let followTail = true;
|
|
453
525
|
let emptyEl = null;
|
|
454
526
|
let hero = null; // the empty state's character: { rig, el, box, parked, leaving, held, dock, egg, target }
|
|
@@ -470,6 +542,15 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
470
542
|
let lastDirect = null; // the last one approved since the user's prompt: a "do it here" continuation turn takes it again
|
|
471
543
|
let turnSeq = 0; // turns this renderer has seen start (showWorking)
|
|
472
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
|
|
473
554
|
|
|
474
555
|
// ── Activity racks ──
|
|
475
556
|
// Every burst of agent work (tool calls, thinking, short narration) is one
|
|
@@ -539,6 +620,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
539
620
|
if (step.ghost && step.type === 'call' && !Object.keys(step.input || {}).length) { say.text = '…'; say.detail = '…'; say.segments = null; say.mono = false; say.meta = []; }
|
|
540
621
|
return say;
|
|
541
622
|
};
|
|
623
|
+
const limitDock = createLimitDock($msgs, { t });
|
|
542
624
|
const nowMs = () => (clockAt ?? Date.now());
|
|
543
625
|
const isTimed = () => !muted || clockAt != null;
|
|
544
626
|
const locale = () => (typeof document !== 'undefined' && document.documentElement?.lang) || 'en';
|
|
@@ -739,12 +821,46 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
739
821
|
return best;
|
|
740
822
|
}
|
|
741
823
|
|
|
742
|
-
/** 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. */
|
|
743
825
|
function visibleMascot() {
|
|
744
826
|
if (hero?.rig) return hero.target;
|
|
827
|
+
const ask = liveAsk();
|
|
828
|
+
if (ask?.presenter?.isLive?.()) return ask.presenter.target;
|
|
745
829
|
return newestStage()?.target || workingFace?.target || null;
|
|
746
830
|
}
|
|
747
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
|
+
|
|
748
864
|
function mascotChanged() {
|
|
749
865
|
activateRigs();
|
|
750
866
|
try { hooks.onMascotChange?.(); } catch { /* the director is gone */ }
|
|
@@ -755,12 +871,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
755
871
|
while (rows.length > MAX_MESSAGES && rows[0] !== working) rows[0].remove();
|
|
756
872
|
}
|
|
757
873
|
|
|
758
|
-
/** 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. */
|
|
759
875
|
function append(node, scope = rootScope, { park = false } = {}) {
|
|
760
876
|
hideEmpty({ park });
|
|
761
|
-
if (scope.container === $msgs
|
|
877
|
+
if (scope.container === $msgs) askTail.insert(node);
|
|
762
878
|
else scope.container.appendChild(node);
|
|
763
|
-
if (scope === rootScope) prune();
|
|
879
|
+
if (scope === rootScope) { prune(); askTail.sync(); }
|
|
764
880
|
// Text, a card, a notice…: the parked hero would sit on it. The prompt and a rack leave it to settleHero().
|
|
765
881
|
if (hero?.parked && scope === rootScope && !node._rack && !node.classList.contains('msg-user')) dismissHero();
|
|
766
882
|
if (racks.size) syncRacks();
|
|
@@ -825,6 +941,99 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
825
941
|
linkify(body);
|
|
826
942
|
}
|
|
827
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
|
+
|
|
828
1037
|
function copyText(text) {
|
|
829
1038
|
const value = String(text ?? '');
|
|
830
1039
|
if (!value) return;
|
|
@@ -857,10 +1066,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
857
1066
|
if (!s) return;
|
|
858
1067
|
if (s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; }
|
|
859
1068
|
if (s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; }
|
|
860
|
-
if (s.bodyEl && s.textBuf.trim())
|
|
1069
|
+
if (s.bodyEl && s.textBuf.trim()) paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
|
|
861
1070
|
if (s.think) refreshStep(s.think);
|
|
862
1071
|
}
|
|
863
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
|
+
|
|
864
1081
|
/** The stream's text row goes (the final message renders the text again). */
|
|
865
1082
|
function dropStreamText(scope) {
|
|
866
1083
|
const s = scope.stream;
|
|
@@ -930,10 +1147,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
930
1147
|
if (!s.thinkTimer) s.thinkTimer = setTimeout(() => { s.thinkTimer = null; refreshStep(step); if (step.rack.live) paintStage(step.rack); }, STREAM_TICK_MS);
|
|
931
1148
|
} else if (delta.type === 'text_delta' && delta.text != null) {
|
|
932
1149
|
s.textBuf += delta.text;
|
|
933
|
-
|
|
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()) {
|
|
934
1152
|
const { row, wrap } = assistantRow(scope, { _text: s.textBuf, _msgId: s.msgId, _claude: s.claude });
|
|
935
1153
|
row.classList.add('streaming');
|
|
936
1154
|
noteAnswer(scope, row);
|
|
1155
|
+
if (scope === rootScope) lastTextRow = row;
|
|
937
1156
|
s.row = row;
|
|
938
1157
|
s.wrap = wrap;
|
|
939
1158
|
s.bodyEl = el('div', 'asst-md');
|
|
@@ -944,7 +1163,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
944
1163
|
s.mdTimer = setTimeout(() => {
|
|
945
1164
|
s.mdTimer = null;
|
|
946
1165
|
if (!s.bodyEl) return;
|
|
947
|
-
|
|
1166
|
+
paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
|
|
948
1167
|
scrollEnd();
|
|
949
1168
|
}, STREAM_TICK_MS);
|
|
950
1169
|
}
|
|
@@ -953,7 +1172,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
953
1172
|
}
|
|
954
1173
|
if (type === 'content_block_stop') {
|
|
955
1174
|
const s = scope.stream;
|
|
956
|
-
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 }); }
|
|
957
1176
|
if (s?.think && s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; refreshStep(s.think); }
|
|
958
1177
|
}
|
|
959
1178
|
}
|
|
@@ -1023,9 +1242,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1023
1242
|
function textRow(scope, text, ctx = {}) {
|
|
1024
1243
|
const { row, wrap } = assistantRow(scope, { _text: text, _msgId: ctx.msgId || null, _claude: !!ctx.claude });
|
|
1025
1244
|
const body = el('div', 'asst-md');
|
|
1026
|
-
renderMarkdownInto(body, text);
|
|
1027
1245
|
wrap.appendChild(body);
|
|
1246
|
+
paintMessage(row, body, text, { scope });
|
|
1028
1247
|
noteAnswer(scope, row);
|
|
1248
|
+
if (scope === rootScope) lastTextRow = row;
|
|
1029
1249
|
return row;
|
|
1030
1250
|
}
|
|
1031
1251
|
|
|
@@ -1052,7 +1272,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1052
1272
|
else if (s.row) {
|
|
1053
1273
|
s.row.classList.remove('streaming');
|
|
1054
1274
|
if (!s.wrap.children.length) s.row.remove();
|
|
1055
|
-
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); }
|
|
1056
1276
|
}
|
|
1057
1277
|
if (streamedThink && !thinks.length) finishThink(streamedThink);
|
|
1058
1278
|
scope.stream = null;
|
|
@@ -1213,10 +1433,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1213
1433
|
return prev;
|
|
1214
1434
|
}
|
|
1215
1435
|
|
|
1216
|
-
/** 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. */
|
|
1217
1437
|
function tailRow(scope) {
|
|
1218
1438
|
let node = scope.container.lastElementChild;
|
|
1219
|
-
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;
|
|
1220
1440
|
return node;
|
|
1221
1441
|
}
|
|
1222
1442
|
|
|
@@ -1326,7 +1546,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1326
1546
|
row.innerHTML = `<div class="asst-rack-stage">
|
|
1327
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>
|
|
1328
1548
|
<div class="asst-rack-caption"></div>
|
|
1329
|
-
<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>
|
|
1330
1550
|
<div class="asst-rack-sweep" aria-hidden="true"></div>
|
|
1331
1551
|
</div>
|
|
1332
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>
|
|
@@ -1340,7 +1560,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1340
1560
|
// The direct route's class whose scene the stage shows: set here, or once when a reopened conversation's route
|
|
1341
1561
|
// resumes (lendRouteScene); a rack never loses its scene.
|
|
1342
1562
|
sceneClass: nested ? '' : directSceneClass(), sceneId: '', sceneAt: -Infinity,
|
|
1343
|
-
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'), 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') },
|
|
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') },
|
|
1344
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 },
|
|
1345
1565
|
ledger: q('.asst-rack-ledger'), statusEl: q('.asst-rack-status'), alertEl: q('.asst-rack-alert'), timelineEl: null, timelineSig: '',
|
|
1346
1566
|
rig: null, target: null, held: false, holdUntil: 0, holdTimer: 0, pose: null, awake: null, awakeKey: null,
|
|
@@ -1631,6 +1851,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1631
1851
|
s.text.textContent = '';
|
|
1632
1852
|
s.subject.removeAttribute('title');
|
|
1633
1853
|
s.subject.className = 'asst-bubble-subject';
|
|
1854
|
+
s.subject.hidden = false;
|
|
1634
1855
|
s.subject.setAttribute('aria-expanded', 'false');
|
|
1635
1856
|
s.meta.textContent = '';
|
|
1636
1857
|
s.err.textContent = '';
|
|
@@ -1640,15 +1861,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1640
1861
|
}
|
|
1641
1862
|
|
|
1642
1863
|
/**
|
|
1643
|
-
* One rig of this transcript animates: the hero, else the newest
|
|
1644
|
-
* the
|
|
1645
|
-
*
|
|
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.
|
|
1646
1869
|
*/
|
|
1647
1870
|
function activateRigs() {
|
|
1648
|
-
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();
|
|
1649
1876
|
for (const rack of racks) if (rack.rig) rack.rig.setActive?.(rack === newest);
|
|
1650
1877
|
hero?.rig?.setActive?.(true);
|
|
1651
|
-
workingFace?.rig?.setActive?.(!hero?.rig && !newest);
|
|
1878
|
+
workingFace?.rig?.setActive?.(!hero?.rig && !asking && !newest);
|
|
1652
1879
|
}
|
|
1653
1880
|
|
|
1654
1881
|
/** The bubble's call: the awake one, else the newest; it holds 900 ms (3 s for thinking) before the next wins. */
|
|
@@ -1758,29 +1985,38 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1758
1985
|
const held = rack.waiting && step === waitingStep(rack);
|
|
1759
1986
|
const verb = held ? t('assistant.rack.waiting', 'Waiting for you') : verbText(say.verb, running, t);
|
|
1760
1987
|
const grouped = groupedSubject(rack, step);
|
|
1761
|
-
|
|
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;
|
|
1762
1993
|
const literal = !grouped && say.full ? say.full : full; // the command as written: the title and the opened bubble
|
|
1763
1994
|
const open = rack.bubble.open;
|
|
1764
|
-
// Narrow stages cut from the head, so the file name (the tail) survives. A small budget keeps a
|
|
1765
|
-
// shorter tail (24 chars of it would not fit beside the verb).
|
|
1766
|
-
const cut = subjectCut(rack);
|
|
1767
|
-
const cutTo = (n) => (open ? literal : cut === 'head' ? headEllipsis(full, n) : middleEllipsis(full, n, Math.min(24, Math.max(8, n - 6))));
|
|
1768
|
-
const shownText = cutTo(subjectBudget(rack, verb, cut));
|
|
1769
|
-
const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}`;
|
|
1770
1995
|
s.bubble.hidden = !full && !verb;
|
|
1771
1996
|
s.bubble.dataset.state = held ? 'waiting' : running ? 'running' : step.state;
|
|
1772
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}`;
|
|
1773
2007
|
if (key !== rack.bubble.key) {
|
|
1774
2008
|
const changedStep = !rack.bubble.key.startsWith(`${step.id}|`);
|
|
1775
2009
|
rack.bubble.key = key;
|
|
1776
2010
|
const write = () => {
|
|
1777
2011
|
s.verb.textContent = verb;
|
|
1778
2012
|
s.subject.className = `asst-bubble-subject${say.mono ? ' is-mono' : ''}${say.muted ? ' is-muted' : ''}`;
|
|
2013
|
+
s.subject.hidden = bare;
|
|
1779
2014
|
const segments = grouped || open ? null : say.segments;
|
|
1780
2015
|
writeSubject(s.text, segments, shownText);
|
|
1781
2016
|
// The budget is an estimate (a long token wraps whole to the next line): what the two-line
|
|
1782
2017
|
// clamp would hide is cut here instead, so the subject is never cut a second time.
|
|
1783
|
-
const fitted = open ? shownText : fitSubject(s, shownText, cutTo, segments);
|
|
2018
|
+
const fitted = open || bare ? shownText : fitSubject(s, shownText, cutTo, segments);
|
|
2019
|
+
s.subject.classList.toggle('is-cut', fitted.startsWith('…'));
|
|
1784
2020
|
if (literal !== fitted || open) s.subject.title = literal; else s.subject.removeAttribute('title');
|
|
1785
2021
|
};
|
|
1786
2022
|
if (swap && changedStep && !calm() && typeof s.subject.animate === 'function' && s.text.textContent) {
|
|
@@ -1793,7 +2029,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1793
2029
|
};
|
|
1794
2030
|
} else write();
|
|
1795
2031
|
}
|
|
1796
|
-
paintBubbleMeta(rack, step, say, now);
|
|
1797
2032
|
const err = step.state === 'error' ? (step.detail || exitText(step)) : '';
|
|
1798
2033
|
s.err.hidden = !err;
|
|
1799
2034
|
if (s.err.textContent !== err) s.err.textContent = err;
|
|
@@ -1804,10 +2039,11 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1804
2039
|
}
|
|
1805
2040
|
|
|
1806
2041
|
/**
|
|
1807
|
-
* How many characters of subject fit the bubble: two lines of
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
*
|
|
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).
|
|
1811
2047
|
*/
|
|
1812
2048
|
function subjectBudget(rack, verb, cut = 'middle') {
|
|
1813
2049
|
const s = rack.stage;
|
|
@@ -1817,11 +2053,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1817
2053
|
const bubble = s.bubble.hidden ? null : s.bubble.getBoundingClientRect();
|
|
1818
2054
|
const beside = !bubble || bubble.left >= face.right - 1;
|
|
1819
2055
|
const verbPx = Math.max(s.verb.getBoundingClientRect().width, verb.length * 7);
|
|
1820
|
-
|
|
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);
|
|
1821
2061
|
const perLine = Math.max(12, Math.floor(column / 7));
|
|
1822
|
-
// Two lines, less what wrapping at token boundaries wastes (a head cut
|
|
1823
|
-
|
|
1824
|
-
|
|
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));
|
|
1825
2068
|
}
|
|
1826
2069
|
|
|
1827
2070
|
/** 'head' where the stylesheet says so (≤320 px: keep the tail), else 'middle'. */
|
|
@@ -1830,30 +2073,53 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1830
2073
|
}
|
|
1831
2074
|
|
|
1832
2075
|
/**
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
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.
|
|
1835
2080
|
*/
|
|
1836
|
-
function headEllipsis(text, max) {
|
|
1837
|
-
const s = String(text ?? '');
|
|
1838
|
-
if (s.length <= max) return s;
|
|
1839
|
-
let tail = s.slice(s.length - Math.max(8, max - 1));
|
|
1840
|
-
const cut = tail.search(/[\s/]/);
|
|
1841
|
-
if (cut > 0 && cut < tail.length * 0.4) tail = tail.slice(tail[cut] === '/' ? cut : cut + 1);
|
|
1842
|
-
return joinCut('', tail);
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
2081
|
function writeSubject(node, segments, text) {
|
|
1846
2082
|
node.textContent = '';
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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;
|
|
1850
2113
|
}
|
|
1851
2114
|
|
|
1852
|
-
/**
|
|
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
|
+
*/
|
|
1853
2119
|
function fitSubject(s, text, cutTo, segments) {
|
|
1854
2120
|
let shown = text;
|
|
1855
2121
|
let budget = shown.length;
|
|
1856
|
-
for (let i = 0; i < 6 && budget > 12 && s.
|
|
2122
|
+
for (let i = 0; i < 6 && budget > 12 && (s.line.scrollHeight > s.line.clientHeight + 1 || pieceOverflows(s)); i += 1) {
|
|
1857
2123
|
budget = Math.floor(budget * 0.85);
|
|
1858
2124
|
shown = cutTo(budget);
|
|
1859
2125
|
writeSubject(s.text, segments, shown);
|
|
@@ -1882,7 +2148,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1882
2148
|
// A subagent's bubble follows its child: the step the agent is on now.
|
|
1883
2149
|
if (step.kind === 'subagent' && step.children?.length) {
|
|
1884
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);
|
|
1885
|
-
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)]);
|
|
1886
2152
|
}
|
|
1887
2153
|
// ×N only for the same target again (a re-run, a re-read): a number the ledger agrees with.
|
|
1888
2154
|
// Other calls in the same 800 ms burst read as a sentence instead.
|
|
@@ -2176,10 +2442,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2176
2442
|
return t('assistant.rack.state.done', 'done');
|
|
2177
2443
|
}
|
|
2178
2444
|
|
|
2179
|
-
/** "Running npm test" / "Ran npm test". */
|
|
2180
|
-
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 } = {}) {
|
|
2181
2447
|
const say = sayOf(step);
|
|
2182
|
-
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();
|
|
2183
2449
|
}
|
|
2184
2450
|
|
|
2185
2451
|
/** The receipt: one call says what it did; a burst says it in one sentence. */
|
|
@@ -2681,6 +2947,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2681
2947
|
}
|
|
2682
2948
|
|
|
2683
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);
|
|
2684
2953
|
turnFrames = [];
|
|
2685
2954
|
directRoute = null; // a new prompt is a new task: its own route decides
|
|
2686
2955
|
lastDirect = null;
|
|
@@ -2720,17 +2989,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2720
2989
|
}
|
|
2721
2990
|
|
|
2722
2991
|
function appendError(text) {
|
|
2723
|
-
|
|
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;
|
|
2724
2995
|
}
|
|
2725
2996
|
|
|
2726
2997
|
function appendDivider(label) {
|
|
2727
2998
|
return append(el('div', 'asst-divider', label || ''));
|
|
2728
2999
|
}
|
|
2729
3000
|
|
|
2730
|
-
/** 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. */
|
|
2731
3002
|
function appendNode(node) {
|
|
2732
3003
|
if (!node) return null;
|
|
2733
|
-
|
|
3004
|
+
const row = append(node);
|
|
3005
|
+
if (node._ask) registerAsk(node._ask);
|
|
3006
|
+
return row;
|
|
2734
3007
|
}
|
|
2735
3008
|
|
|
2736
3009
|
function showWorking(label) {
|
|
@@ -2744,6 +3017,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2744
3017
|
hideEmpty({ park: true });
|
|
2745
3018
|
$msgs.appendChild(working);
|
|
2746
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);
|
|
2747
3023
|
turn = { calls: 0, last: null, errored: false, answerRow: null };
|
|
2748
3024
|
turnSeq += 1;
|
|
2749
3025
|
if (directRoute && directRoute.turn == null) { // a route picked while no turn ran, or one the journal left running, is this turn's
|
|
@@ -2831,6 +3107,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2831
3107
|
onStop: hooks.onStopRun,
|
|
2832
3108
|
onRead: hooks.onReadRun,
|
|
2833
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),
|
|
2834
3116
|
result,
|
|
2835
3117
|
keepSummary: true,
|
|
2836
3118
|
};
|
|
@@ -2876,7 +3158,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2876
3158
|
if (!run?.runId) return null;
|
|
2877
3159
|
noteRun(run);
|
|
2878
3160
|
const card = runCards.get(run.runId);
|
|
2879
|
-
if (!card?.isConnected) return null;
|
|
3161
|
+
if (!card?.isConnected) { limitDock.set(`run:${run.runId}`, null); return null; }
|
|
2880
3162
|
updateRunCard(card, run, runCardOpts()); // fresh options: the run's own lastResult, never a result the card was built with
|
|
2881
3163
|
syncStage(run.runId, card, run);
|
|
2882
3164
|
return card;
|
|
@@ -3216,8 +3498,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3216
3498
|
// Under the reply once the turn has ended; a later snapshot (learning) never moves it again.
|
|
3217
3499
|
const ended = ev.phase !== 'sent';
|
|
3218
3500
|
if (fresh || (ended && !row.atEnd)) {
|
|
3219
|
-
// Placed like any row (before "Working…"), but a status line of the harness never dismisses the hero.
|
|
3220
|
-
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);
|
|
3221
3503
|
else $msgs.appendChild(row.el);
|
|
3222
3504
|
if (ended) row.atEnd = true;
|
|
3223
3505
|
prune();
|
|
@@ -3231,13 +3513,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3231
3513
|
function showEmpty() {
|
|
3232
3514
|
if (emptyEl || $msgs.querySelector(':scope > :not(.asst-working)')) return;
|
|
3233
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.
|
|
3234
3518
|
emptyEl.innerHTML = `
|
|
3235
3519
|
<div class="asst-empty-mascot" aria-hidden="true"></div>
|
|
3236
3520
|
<div class="asst-empty-title" role="heading" aria-level="2">${esc(t('assistant.empty.heading', 'What should we do?'))}</div>
|
|
3237
|
-
<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>
|
|
3238
3521
|
<div class="asst-empty-greet"></div>
|
|
3239
|
-
<div class="asst-
|
|
3240
|
-
<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>`;
|
|
3241
3524
|
const box = emptyEl.querySelector('.asst-empty-mascot');
|
|
3242
3525
|
try {
|
|
3243
3526
|
const rig = mascotLib.createMascot(box, { width: 120, height: 60 });
|
|
@@ -3245,8 +3528,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3245
3528
|
hero.target = heroTarget(hero);
|
|
3246
3529
|
wireEgg(hero);
|
|
3247
3530
|
} catch { hero = null; }
|
|
3248
|
-
// The project the conversation works in, under the
|
|
3249
|
-
// 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.
|
|
3250
3533
|
const projectSlot = emptyEl.querySelector('.asst-empty-project');
|
|
3251
3534
|
let picker = null;
|
|
3252
3535
|
try { picker = typeof hooks.mountEmptyProject === 'function' ? hooks.mountEmptyProject(projectSlot) : null; } catch { picker = null; }
|
|
@@ -3263,8 +3546,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3263
3546
|
}
|
|
3264
3547
|
|
|
3265
3548
|
/**
|
|
3266
|
-
* The greeting SynaBun prepared for this return (public/shared/greeting/)
|
|
3267
|
-
* 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.
|
|
3268
3551
|
*/
|
|
3269
3552
|
function offerGreeting() {
|
|
3270
3553
|
const owner = emptyEl;
|
|
@@ -3282,12 +3565,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3282
3565
|
host.innerHTML = '';
|
|
3283
3566
|
let list = [];
|
|
3284
3567
|
try { list = typeof hooks.emptySuggestions === 'function' ? (hooks.emptySuggestions() || []) : []; } catch { list = []; }
|
|
3568
|
+
let i = 0;
|
|
3285
3569
|
for (const s of list) {
|
|
3286
|
-
//
|
|
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.
|
|
3287
3572
|
const chip = el('button', 'asst-chip asst-suggestion');
|
|
3288
3573
|
chip.type = 'button';
|
|
3289
3574
|
chip.setAttribute('role', 'listitem');
|
|
3290
3575
|
chip.dataset.suggestion = s.id || '';
|
|
3576
|
+
chip.style.setProperty('--i', String(i + 1));
|
|
3577
|
+
i += 1;
|
|
3291
3578
|
const kind = s.kind || SUGGESTION_KINDS[s.id] || null;
|
|
3292
3579
|
if (kind) {
|
|
3293
3580
|
chip.dataset.kind = kind;
|
|
@@ -3301,10 +3588,28 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3301
3588
|
chip.addEventListener('blur', () => previewHero(null));
|
|
3302
3589
|
}
|
|
3303
3590
|
chip.appendChild(el('span', 'asst-chip-text', s.label));
|
|
3304
|
-
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
|
+
});
|
|
3305
3601
|
host.appendChild(chip);
|
|
3306
3602
|
}
|
|
3307
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');
|
|
3308
3613
|
}
|
|
3309
3614
|
|
|
3310
3615
|
/** The empty state's character tries a suggestion on (null: back to idle); never over a pose the director holds. */
|
|
@@ -3509,6 +3814,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3509
3814
|
const content = Array.isArray(ev.message.content) ? ev.message.content : [];
|
|
3510
3815
|
lastErrorText = typeof ev.message.content === 'string' ? ev.message.content.trim()
|
|
3511
3816
|
: content.filter((b) => b?.type === 'text').map((b) => b.text).join('\n').trim();
|
|
3817
|
+
if (!parentId) offerLimitWait(lastErrorText, { brainError: true });
|
|
3512
3818
|
}
|
|
3513
3819
|
return true;
|
|
3514
3820
|
}
|
|
@@ -3536,6 +3842,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3536
3842
|
if (!parentId) endBursts(rootScope);
|
|
3537
3843
|
// A journal's or a replay's turn ended here (live, finishTurn ends it): so did the route it ran.
|
|
3538
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();
|
|
3539
3847
|
hooks.onResult?.(ev);
|
|
3540
3848
|
if (errorText && errorText !== lastErrorText) appendError(errorText);
|
|
3541
3849
|
if (!parentId) { lastErrorText = ''; rootScope.kept = null; releaseGrace(); }
|
|
@@ -3642,10 +3950,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3642
3950
|
/**
|
|
3643
3951
|
* The turn ended. `holdMs`: the panel plays a one-shot (success, error) on
|
|
3644
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.
|
|
3645
3954
|
*/
|
|
3646
|
-
function finishTurn({ holdMs = 0 } = {}) {
|
|
3955
|
+
function finishTurn({ holdMs = 0, guess = true } = {}) {
|
|
3647
3956
|
flushStream(rootScope);
|
|
3957
|
+
sealStreamRow(rootScope);
|
|
3648
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();
|
|
3649
3961
|
for (const scope of scopes.values()) flushStream(scope);
|
|
3650
3962
|
closeStreamThinks();
|
|
3651
3963
|
hideWorking();
|
|
@@ -3686,11 +3998,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3686
3998
|
}
|
|
3687
3999
|
if (failed) console.warn(`[assistant] ${failed} of ${list.length} transcript entries could not be drawn`, firstError);
|
|
3688
4000
|
flushStream(rootScope);
|
|
4001
|
+
sealStreamRow(rootScope);
|
|
3689
4002
|
if (rootScope.stream?.row) rootScope.stream.row.classList.remove('streaming');
|
|
3690
4003
|
for (const scope of scopes.values()) flushStream(scope);
|
|
3691
4004
|
hideWorking();
|
|
3692
4005
|
turnActive = false;
|
|
3693
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();
|
|
3694
4009
|
scrollEnd(true);
|
|
3695
4010
|
}
|
|
3696
4011
|
|
|
@@ -3733,6 +4048,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3733
4048
|
if (rootScope.stream?.thinkTimer) clearTimeout(rootScope.stream.thinkTimer);
|
|
3734
4049
|
scopes.clear();
|
|
3735
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 */ } }
|
|
3736
4057
|
runActivity.clear();
|
|
3737
4058
|
runCards.clear();
|
|
3738
4059
|
routeLines.clear();
|
|
@@ -3764,7 +4085,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3764
4085
|
return {
|
|
3765
4086
|
el: $msgs,
|
|
3766
4087
|
clear,
|
|
3767
|
-
destroy: () => {
|
|
4088
|
+
destroy: () => { clear(); limitDock.destroy(); },
|
|
3768
4089
|
appendUser,
|
|
3769
4090
|
appendAssistantMarkdown,
|
|
3770
4091
|
appendStatus,
|
|
@@ -3803,6 +4124,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3803
4124
|
forgetSeen: () => { seen.clear(); snapshot = null; },
|
|
3804
4125
|
/** A card waits on the user: `toolUseId` names the call it holds (the newest live stage when unknown); false clears it. */
|
|
3805
4126
|
setWaiting,
|
|
4127
|
+
/** The session's usage-limit report changed: this turn's waiting card counts to its reset time. */
|
|
4128
|
+
noteLimit,
|
|
3806
4129
|
finishTurn,
|
|
3807
4130
|
renderTranscript,
|
|
3808
4131
|
showWorking,
|
|
@@ -3811,6 +4134,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3811
4134
|
showEmpty,
|
|
3812
4135
|
offerGreeting,
|
|
3813
4136
|
refreshEmpty: renderSuggestions,
|
|
4137
|
+
setDrafting,
|
|
3814
4138
|
isEmpty: () => !!emptyEl,
|
|
3815
4139
|
scrollEnd,
|
|
3816
4140
|
isNearBottom,
|