synabun 2.1.0 → 2.2.1
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 +139 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +504 -39
- package/neural-interface/i18n/pt-BR.json +504 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-brains/codex.js +6 -1
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +211 -50
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +40 -7
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +423 -23
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +165 -22
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +80 -6
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +15 -3
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +46 -9
- package/neural-interface/public/shared/assistant/asst-render.js +112 -18
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
- package/neural-interface/public/shared/assistant/asst-scenes.js +279 -80
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +58 -4
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/loading-teardown.js +34 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +28 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +17 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +101 -68
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/storage-retry.js +47 -0
- package/neural-interface/public/shared/storage.js +25 -4
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +362 -35
- package/neural-interface/public/shared/synabun-mascot.js +27 -8
- package/neural-interface/public/shared/synabun-ticker.js +147 -12
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +374 -134
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-loading.js +5 -4
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +731 -466
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { t } from './i18n.js';
|
|
2
|
+
import { answerWorkerQuestion, syncWorkerQuestionQueue } from './worker-questions.js';
|
|
1
3
|
import { providerAuthSnapshot } from './ui-provider-auth.js';
|
|
2
4
|
import { mountProviderTerminalAuth, providerApiKeyRequest, startProviderTerminalLogin } from './ui-provider-terminal-auth.js';
|
|
3
5
|
import { accountName, accountSwitchPlan, accountText, tabAccountId, serverAccountId } from './account-view.js';
|
|
@@ -52,7 +54,7 @@ import { sanitizeStoredHtml } from './assistant/asst-sanitize.js';
|
|
|
52
54
|
import { escapeHtml, classToken } from './cp/cp-markdown.js';
|
|
53
55
|
import { rehydrateStoredTranscript } from './cp/cp-rehydrate.js';
|
|
54
56
|
import { compactHolds, compactQueue, isCompactCommand, newConversation, nextQueued, ownsQueued, queuedRefusal, restoredQueue, QUEUED_REFUSAL_LINES } from './cp/cp-compose.js';
|
|
55
|
-
import { scrubSavedTab, historyProjectOf, shownUuids, shownTail, snapshotVerdict, withStartConfig, historyOutcome, transcriptOwner, historyNotFoundText } from './cp/cp-restore.js';
|
|
57
|
+
import { scrubSavedTab, historyProjectOf, shownUuids, shownTail, snapshotVerdict, withStartConfig, historyOutcome, transcriptOwner, historyNotFoundText, splitHistoryTail, replayTodosFromMessages, markHistoryRows, earlierHistoryRows, HISTORY_ROW as HISTORY_ROW_KEY, liveRows, queueHistoryLoad, historyGate, renderHistoryRowsInto, holdPendingResult, takePendingResult } from './cp/cp-restore.js';
|
|
56
58
|
import { hasCapability, slashCommandRoute, bypassesPromptBuffer, idleEndsTurn, describeResult, describeTurnFooter, localCommandOutput, splitAssistantBlocks, normalizeSlashCommands, matchSlashCommands, listSlashCommands, slashMenuMaxHeight, splitCommandsByCapability, readInit, statusRows } from './cp/cp-events.js';
|
|
57
59
|
import {
|
|
58
60
|
renderSdkEvent, noteUnhandledEvent, applyEngineHello, renderCompactBoundary, markDeniedFromResult, endTurnRows,
|
|
@@ -282,7 +284,7 @@ const _snapshotsReady = fetchBlobNamespace('claude-snapshots')
|
|
|
282
284
|
const EFFORT_LEVELS = ['off', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
283
285
|
const EFFORT_LABELS = { off: 'Think', low: 'lo', medium: 'med', high: 'hi', xhigh: 'xhi', max: 'max' };
|
|
284
286
|
const EFFORT_TITLES = {
|
|
285
|
-
off: '
|
|
287
|
+
off: 'Thinking off',
|
|
286
288
|
low: 'Thinking: low',
|
|
287
289
|
medium: 'Thinking: medium',
|
|
288
290
|
high: 'Thinking: high',
|
|
@@ -417,6 +419,7 @@ function buildPanel() {
|
|
|
417
419
|
</div>
|
|
418
420
|
<div class="cp-input-area">
|
|
419
421
|
<div class="cp-input-wrap">
|
|
422
|
+
<span class="cp-spin-ring" aria-hidden="true"><i></i></span>
|
|
420
423
|
<div class="cp-image-preview" id="cp-image-preview"></div>
|
|
421
424
|
<div class="cp-input-inner">
|
|
422
425
|
<textarea class="cp-input" id="cp-input" placeholder="Message SynaBun..." rows="1" autocomplete="off" spellcheck="false"></textarea>
|
|
@@ -483,6 +486,10 @@ function injectStyles() {
|
|
|
483
486
|
opacity: 1;
|
|
484
487
|
overflow: visible;
|
|
485
488
|
}
|
|
489
|
+
/* Windows: the shell is 92% opaque, so the blur behind it barely shows, yet Chromium recomputes it on every repaint
|
|
490
|
+
inside the panel (a transcript rendering, a stream). There it goes; the colour stays. */
|
|
491
|
+
html.platform-win32 .claude-panel,
|
|
492
|
+
html.desktop-titlebar--win32 .claude-panel { backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
486
493
|
/* Raise panel above floating terminal windows while a dropdown is open
|
|
487
494
|
so project/branch/recall menus aren't clipped behind them. */
|
|
488
495
|
.claude-panel:has(.cp-dropdown.open) { z-index: 99999; }
|
|
@@ -1055,26 +1062,17 @@ function injectStyles() {
|
|
|
1055
1062
|
margin-top: 8px; transition: opacity 0.3s, border-color 0.2s, box-shadow 0.3s;
|
|
1056
1063
|
}
|
|
1057
1064
|
.perm-card { position: relative; }
|
|
1058
|
-
.
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
padding: 1px;
|
|
1063
|
-
pointer-events: none;
|
|
1065
|
+
/* A card that waits on you turns an amber ring inside its edge: the shared .cp-spin-ring (with the
|
|
1066
|
+
composer's, below), turning only while the card is .active-perm. */
|
|
1067
|
+
.perm-card > .cp-spin-ring { border-radius: 8px; }
|
|
1068
|
+
.perm-card > .cp-spin-ring > i {
|
|
1064
1069
|
background: conic-gradient(
|
|
1065
|
-
from var(--cp-border-angle, 0deg),
|
|
1066
1070
|
rgba(245,180,60,0.0) 0%,
|
|
1067
1071
|
rgba(245,180,60,0.55) 25%,
|
|
1068
1072
|
rgba(245,180,60,0.1) 50%,
|
|
1069
1073
|
rgba(245,180,60,0.55) 75%,
|
|
1070
1074
|
rgba(245,180,60,0.0) 100%
|
|
1071
1075
|
);
|
|
1072
|
-
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1073
|
-
-webkit-mask-composite: xor;
|
|
1074
|
-
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1075
|
-
mask-composite: exclude;
|
|
1076
|
-
opacity: 0;
|
|
1077
|
-
transition: opacity 0.4s;
|
|
1078
1076
|
}
|
|
1079
1077
|
.perm-card.active-perm {
|
|
1080
1078
|
border-color: rgba(245,180,60,0.25);
|
|
@@ -1082,9 +1080,10 @@ function injectStyles() {
|
|
|
1082
1080
|
box-shadow: 0 0 16px rgba(245,180,60,0.08), 0 0 40px rgba(245,180,60,0.04);
|
|
1083
1081
|
animation: cp-perm-glow 2.5s ease-in-out infinite;
|
|
1084
1082
|
}
|
|
1085
|
-
.perm-card.active-perm
|
|
1086
|
-
|
|
1087
|
-
animation: cp-
|
|
1083
|
+
.perm-card.active-perm > .cp-spin-ring { opacity: 1; }
|
|
1084
|
+
.perm-card.active-perm > .cp-spin-ring > i {
|
|
1085
|
+
animation: cp-spin-ring-turn 3s linear infinite;
|
|
1086
|
+
will-change: rotate;
|
|
1088
1087
|
}
|
|
1089
1088
|
.perm-card:hover { border-color: var(--b-light); }
|
|
1090
1089
|
.perm-card.active-perm:hover { border-color: rgba(245,180,60,0.4); }
|
|
@@ -1228,47 +1227,64 @@ function injectStyles() {
|
|
|
1228
1227
|
transition: background 0.4s;
|
|
1229
1228
|
min-width: 0;
|
|
1230
1229
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1230
|
+
/* ── Spinning border ring (composer focus, a waiting permission card) ──
|
|
1231
|
+
A conic gradient turning inside the 1px edge, the mask keeping only the ring. The gradient is a
|
|
1232
|
+
square box that turns (rotate, run by the compositor) under the mask, and only while its owner's
|
|
1233
|
+
state is on; an animated custom property (the old --cp-border-angle, @property + a conic gradient
|
|
1234
|
+
from it) restyled and repainted the owner on the main thread 60 times a second. The ring is a size
|
|
1235
|
+
container and the square 142cqmax + 4px, wider than the owner's diagonal at any shape, so what
|
|
1236
|
+
the mask shows is the same conic gradient at every angle. Markup: <span class="cp-spin-ring"
|
|
1237
|
+
aria-hidden="true"><i></i></span>, the owner's first child. */
|
|
1238
|
+
.cp-spin-ring {
|
|
1233
1239
|
position: absolute; inset: 0;
|
|
1234
|
-
border-radius: 14px;
|
|
1235
1240
|
padding: 1px;
|
|
1241
|
+
overflow: hidden;
|
|
1236
1242
|
pointer-events: none;
|
|
1243
|
+
container-type: size;
|
|
1244
|
+
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1245
|
+
-webkit-mask-composite: xor;
|
|
1246
|
+
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1247
|
+
mask-composite: exclude;
|
|
1248
|
+
opacity: 0;
|
|
1249
|
+
transition: opacity 0.4s;
|
|
1250
|
+
}
|
|
1251
|
+
.cp-spin-ring > i {
|
|
1252
|
+
position: absolute; left: 50%; top: 50%;
|
|
1253
|
+
width: calc(142cqmax + 4px);
|
|
1254
|
+
aspect-ratio: 1;
|
|
1255
|
+
translate: -50% -50%;
|
|
1256
|
+
}
|
|
1257
|
+
@keyframes cp-spin-ring-turn {
|
|
1258
|
+
to { rotate: 360deg; }
|
|
1259
|
+
}
|
|
1260
|
+
body.ui-interacting .cp-spin-ring > i { animation-play-state: paused !important; }
|
|
1261
|
+
/* Reduced motion: the ring holds still where the turn starts (the focus / waiting look stays). */
|
|
1262
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1263
|
+
.cp-spin-ring > i { animation: none !important; will-change: auto !important; }
|
|
1264
|
+
}
|
|
1265
|
+
.cp-input-wrap > .cp-spin-ring { border-radius: 14px; }
|
|
1266
|
+
.cp-input-wrap > .cp-spin-ring > i {
|
|
1237
1267
|
background: conic-gradient(
|
|
1238
|
-
from var(--cp-border-angle, 0deg),
|
|
1239
1268
|
rgba(212,162,127,0.0) 0%,
|
|
1240
1269
|
rgba(212,162,127,0.35) 25%,
|
|
1241
1270
|
rgba(212,162,127,0.10) 50%,
|
|
1242
1271
|
rgba(212,162,127,0.35) 75%,
|
|
1243
1272
|
rgba(212,162,127,0.0) 100%
|
|
1244
1273
|
);
|
|
1245
|
-
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1246
|
-
-webkit-mask-composite: xor;
|
|
1247
|
-
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
1248
|
-
mask-composite: exclude;
|
|
1249
|
-
opacity: 0;
|
|
1250
|
-
transition: opacity 0.4s;
|
|
1251
1274
|
}
|
|
1252
|
-
.cp-input-wrap:focus-within
|
|
1253
|
-
|
|
1254
|
-
animation: cp-
|
|
1275
|
+
.cp-input-wrap:focus-within > .cp-spin-ring { opacity: 1; }
|
|
1276
|
+
.cp-input-wrap:focus-within > .cp-spin-ring > i {
|
|
1277
|
+
animation: cp-spin-ring-turn 3s linear infinite;
|
|
1278
|
+
will-change: rotate;
|
|
1255
1279
|
}
|
|
1256
1280
|
.cp-input-wrap:focus-within {
|
|
1257
1281
|
background: rgba(212,162,127,0.03);
|
|
1258
1282
|
box-shadow: 0 0 20px rgba(212,162,127,0.04), 0 0 60px rgba(212,162,127,0.015);
|
|
1259
1283
|
}
|
|
1260
|
-
@keyframes cp-border-spin {
|
|
1261
|
-
to { --cp-border-angle: 360deg; }
|
|
1262
|
-
}
|
|
1263
1284
|
@keyframes cp-perm-glow {
|
|
1264
1285
|
0%, 100% { box-shadow: 0 0 16px rgba(245,180,60,0.08), 0 0 40px rgba(245,180,60,0.04); }
|
|
1265
1286
|
50% { box-shadow: 0 0 20px rgba(245,180,60,0.14), 0 0 50px rgba(245,180,60,0.07); }
|
|
1266
1287
|
}
|
|
1267
|
-
@property --cp-border-angle {
|
|
1268
|
-
syntax: '<angle>';
|
|
1269
|
-
initial-value: 0deg;
|
|
1270
|
-
inherits: false;
|
|
1271
|
-
}
|
|
1272
1288
|
|
|
1273
1289
|
.cp-input-inner {
|
|
1274
1290
|
display: flex; align-items: flex-end;
|
|
@@ -1768,8 +1784,8 @@ function injectStyles() {
|
|
|
1768
1784
|
pointer-events: auto;
|
|
1769
1785
|
width: 0; opacity: 0; overflow: hidden;
|
|
1770
1786
|
background: rgba(18, 18, 20, 0.92);
|
|
1771
|
-
backdrop-filter:
|
|
1772
|
-
-webkit-backdrop-filter:
|
|
1787
|
+
backdrop-filter: none;
|
|
1788
|
+
-webkit-backdrop-filter: none;
|
|
1773
1789
|
border: 0.5px solid rgba(255,255,255,0.06);
|
|
1774
1790
|
border-radius: 14px;
|
|
1775
1791
|
box-shadow:
|
|
@@ -1782,7 +1798,19 @@ function injectStyles() {
|
|
|
1782
1798
|
transition: width 0.22s cubic-bezier(0.2,0,0.2,1), opacity 0.18s;
|
|
1783
1799
|
max-height: 100%;
|
|
1784
1800
|
}
|
|
1785
|
-
.cp-todo-dock.is-open .cp-todo-drawer {
|
|
1801
|
+
.cp-todo-dock.is-open .cp-todo-drawer {
|
|
1802
|
+
width: 260px; opacity: 1;
|
|
1803
|
+
backdrop-filter: blur(60px) saturate(1.5);
|
|
1804
|
+
-webkit-backdrop-filter: blur(60px) saturate(1.5);
|
|
1805
|
+
}
|
|
1806
|
+
html.platform-win32 .cp-todo-tag,
|
|
1807
|
+
html.desktop-titlebar--win32 .cp-todo-tag,
|
|
1808
|
+
html.platform-win32 .cp-todo-dock.is-open .cp-todo-drawer,
|
|
1809
|
+
html.desktop-titlebar--win32 .cp-todo-dock.is-open .cp-todo-drawer {
|
|
1810
|
+
background: rgba(18, 18, 20, 0.96);
|
|
1811
|
+
backdrop-filter: none;
|
|
1812
|
+
-webkit-backdrop-filter: none;
|
|
1813
|
+
}
|
|
1786
1814
|
.cp-todo-dock.is-open .cp-todo-tag { display: none; }
|
|
1787
1815
|
|
|
1788
1816
|
.cp-todo-drawer .cp-todo-head {
|
|
@@ -2283,52 +2311,53 @@ function injectStyles() {
|
|
|
2283
2311
|
}
|
|
2284
2312
|
|
|
2285
2313
|
/* ── Claude session pills in shared tray ── */
|
|
2286
|
-
@property --cp-pill-angle {
|
|
2287
|
-
syntax: '<angle>';
|
|
2288
|
-
initial-value: 0deg;
|
|
2289
|
-
inherits: false;
|
|
2290
|
-
}
|
|
2291
2314
|
.cp-session-pill {
|
|
2292
2315
|
position: relative;
|
|
2293
2316
|
overflow: hidden;
|
|
2294
2317
|
border-color: rgba(212, 162, 127, 0.14);
|
|
2295
2318
|
transition: border-color 0.2s;
|
|
2296
2319
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2320
|
+
/* A working pill's ring: a conic gradient turning inside the 1px border (the mask keeps only the
|
|
2321
|
+
ring). The gradient is a square box that turns (rotate, run by the compositor) under the mask; an
|
|
2322
|
+
animated custom property (the old --cp-pill-angle) repainted the pill on the main thread every
|
|
2323
|
+
frame for as long as a session worked. The square is wider than the pill's diagonal, so the ring
|
|
2324
|
+
is the same conic gradient at every angle. */
|
|
2325
|
+
.cp-pill-ring {
|
|
2299
2326
|
position: absolute; inset: 0;
|
|
2327
|
+
display: none;
|
|
2300
2328
|
border-radius: 8px;
|
|
2301
2329
|
padding: 1px;
|
|
2330
|
+
overflow: hidden;
|
|
2302
2331
|
pointer-events: none;
|
|
2303
|
-
background: transparent;
|
|
2304
2332
|
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
2305
2333
|
-webkit-mask-composite: xor;
|
|
2306
2334
|
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
2307
2335
|
mask-composite: exclude;
|
|
2308
|
-
opacity: 0;
|
|
2309
2336
|
}
|
|
2310
|
-
.cp-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2337
|
+
.cp-pill-ring > i {
|
|
2338
|
+
position: absolute; left: 50%; top: 50%;
|
|
2339
|
+
width: calc(100% + 32px);
|
|
2340
|
+
aspect-ratio: 1;
|
|
2341
|
+
translate: -50% -50%;
|
|
2314
2342
|
background: conic-gradient(
|
|
2315
|
-
from var(--cp-pill-angle, 0deg),
|
|
2316
2343
|
rgba(212,162,127,0.0) 0%,
|
|
2317
2344
|
rgba(212,162,127,0.6) 25%,
|
|
2318
2345
|
rgba(212,162,127,0.12) 50%,
|
|
2319
2346
|
rgba(212,162,127,0.6) 75%,
|
|
2320
2347
|
rgba(212,162,127,0.0) 100%
|
|
2321
2348
|
);
|
|
2322
|
-
animation: cp-pill-
|
|
2323
|
-
|
|
2349
|
+
animation: cp-pill-ring-turn 2.4s linear infinite;
|
|
2350
|
+
}
|
|
2351
|
+
.cp-session-pill.cp-pill-running {
|
|
2352
|
+
border-color: transparent;
|
|
2324
2353
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2354
|
+
.cp-session-pill.cp-pill-running .cp-pill-ring { display: block; }
|
|
2355
|
+
@keyframes cp-pill-ring-turn {
|
|
2356
|
+
to { rotate: 360deg; }
|
|
2327
2357
|
}
|
|
2328
2358
|
.cp-session-pill:hover { border-color: rgba(212, 162, 127, 0.28); }
|
|
2329
|
-
.cp-session-pill:hover.cp-pill-
|
|
2359
|
+
.cp-session-pill:hover .cp-pill-ring > i {
|
|
2330
2360
|
background: conic-gradient(
|
|
2331
|
-
from var(--cp-pill-angle, 0deg),
|
|
2332
2361
|
rgba(212,162,127,0.0) 0%,
|
|
2333
2362
|
rgba(212,162,127,0.8) 25%,
|
|
2334
2363
|
rgba(212,162,127,0.20) 50%,
|
|
@@ -2336,6 +2365,19 @@ function injectStyles() {
|
|
|
2336
2365
|
rgba(212,162,127,0.0) 100%
|
|
2337
2366
|
);
|
|
2338
2367
|
}
|
|
2368
|
+
html.platform-win32 .cp-session-pill.cp-pill-running,
|
|
2369
|
+
html.desktop-titlebar--win32 .cp-session-pill.cp-pill-running {
|
|
2370
|
+
border-color: rgba(212, 162, 127, 0.42);
|
|
2371
|
+
}
|
|
2372
|
+
html.platform-win32 .cp-session-pill.cp-pill-running:hover,
|
|
2373
|
+
html.desktop-titlebar--win32 .cp-session-pill.cp-pill-running:hover {
|
|
2374
|
+
border-color: rgba(212, 162, 127, 0.62);
|
|
2375
|
+
}
|
|
2376
|
+
html.platform-win32 .cp-session-pill.cp-pill-running .cp-pill-ring,
|
|
2377
|
+
html.desktop-titlebar--win32 .cp-session-pill.cp-pill-running .cp-pill-ring {
|
|
2378
|
+
display: none;
|
|
2379
|
+
}
|
|
2380
|
+
body.ui-interacting .cp-pill-ring > i { animation-play-state: paused; }
|
|
2339
2381
|
.cp-session-pill .term-minimized-pill-icon { color: rgba(212, 162, 127, 0.45); transition: color 0.2s; }
|
|
2340
2382
|
.cp-session-pill.cp-pill-running .term-minimized-pill-icon { color: rgba(212, 162, 127, 1); }
|
|
2341
2383
|
.cp-session-pill:hover .term-minimized-pill-icon { color: rgba(212, 162, 127, 0.85); }
|
|
@@ -2370,6 +2412,12 @@ function injectStyles() {
|
|
|
2370
2412
|
pointer-events: none;
|
|
2371
2413
|
}
|
|
2372
2414
|
@keyframes cp-pill-pulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
|
|
2415
|
+
/* Reduced motion: the ring, the dot and the edge hold still (the working look stays). */
|
|
2416
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2417
|
+
.cp-pill-ring > i,
|
|
2418
|
+
.cp-session-pill.cp-pill-running .term-minimized-pill-label::before,
|
|
2419
|
+
.cp-session-pill.cp-pill-running::after { animation: none; }
|
|
2420
|
+
}
|
|
2373
2421
|
|
|
2374
2422
|
/* ── Think intensity toggle ── */
|
|
2375
2423
|
/* ── Shared toggle base ── */
|
|
@@ -2802,6 +2850,13 @@ function _effortLevelsForModel() {
|
|
|
2802
2850
|
return ['off', ...advertised.filter(id => id !== 'off')];
|
|
2803
2851
|
}
|
|
2804
2852
|
|
|
2853
|
+
// Whether the tab's model (the selector's, for the active tab) has effort levels.
|
|
2854
|
+
function _modelSupportsEffort(tab) {
|
|
2855
|
+
if (tab === activeTab()) return _effortLevelsForModel().length > 1;
|
|
2856
|
+
const model = tab?.model ? _models.find(m => m.id === tab.model) : null;
|
|
2857
|
+
return !Array.isArray(model?.effortLevels) || model.effortLevels.some(id => id !== 'off');
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2805
2860
|
// Some models have no thinking at all (Haiku). Disable the toggle and force 'off'
|
|
2806
2861
|
// rather than passing a --effort the CLI will ignore.
|
|
2807
2862
|
function _syncThinkAvailability() {
|
|
@@ -2922,7 +2977,7 @@ function newTabState(sessionId = null, label = 'New chat', id = null) {
|
|
|
2922
2977
|
};
|
|
2923
2978
|
}
|
|
2924
2979
|
|
|
2925
|
-
function createTab(sessionId = null, label = 'New chat', { autoSwitch = true, id = null } = {}) {
|
|
2980
|
+
function createTab(sessionId = null, label = 'New chat', { autoSwitch = true, id = null, deferHistory = false } = {}) {
|
|
2926
2981
|
if (_tabs.length >= MAX_TABS) return null;
|
|
2927
2982
|
const tab = newTabState(sessionId, label, id);
|
|
2928
2983
|
// Create messages div — kept DETACHED until the tab activates. Inactive
|
|
@@ -2937,14 +2992,17 @@ function createTab(sessionId = null, label = 'New chat', { autoSwitch = true, id
|
|
|
2937
2992
|
msgDiv.innerHTML = `<div class="cp-empty"><div class="cp-empty-logo">${CLAUDE_ICON}</div><span class="cp-empty-name">Claude</span><span class="cp-empty-hint">Send a message to start</span></div>`;
|
|
2938
2993
|
tab.messagesEl = msgDiv;
|
|
2939
2994
|
tab.scrollController = createStickyScrollController(msgDiv, { active: false });
|
|
2940
|
-
// Connect WebSocket
|
|
2995
|
+
// Connect WebSocket (every tab, active or not: a restored tab reattaches to the background agents,
|
|
2996
|
+
// shells and wakeups its session kept on the server).
|
|
2941
2997
|
connectTab(tab);
|
|
2942
2998
|
// Create pill (rendered in shared tray, not inside panel)
|
|
2943
2999
|
tab.pillEl = _createTrayPill(tab);
|
|
2944
3000
|
_paintTemporary(tab);
|
|
2945
3001
|
_tabs.push(tab);
|
|
2946
3002
|
if (autoSwitch) switchTab(_tabs.length - 1);
|
|
2947
|
-
|
|
3003
|
+
// A restored background tab loads its transcript when it is first shown (switchTab), not all at once at panel open.
|
|
3004
|
+
if (sessionId && deferHistory) tab._historyDeferred = sessionId;
|
|
3005
|
+
if (sessionId && !deferHistory) loadSessionHistory(sessionId, tab.messagesEl);
|
|
2948
3006
|
renderPills();
|
|
2949
3007
|
if (autoSwitch) saveTabs();
|
|
2950
3008
|
return tab;
|
|
@@ -3095,7 +3153,9 @@ function switchTab(idx) {
|
|
|
3095
3153
|
const items = _models.map(m => ({ value: m.id, label: m.label }));
|
|
3096
3154
|
const modelVal = _resolveModelValue(items, tab.model || _getDefaultModel());
|
|
3097
3155
|
ddPopulateModels($model, items, modelVal);
|
|
3098
|
-
|
|
3156
|
+
// The unverified fallback list (the CLI not asked yet: /api/claude/config answers at once) never
|
|
3157
|
+
// rewrites a tab's model; refreshModels() puts the real list in and settles it.
|
|
3158
|
+
if (modelVal && modelVal !== tab.model && _modelsSource !== 'fallback') { tab.model = modelVal; saveTabs(); }
|
|
3099
3159
|
}
|
|
3100
3160
|
if ($think && tab.effort) _setEffort($think, tab.effort);
|
|
3101
3161
|
_syncThinkAvailability();
|
|
@@ -3117,6 +3177,10 @@ function switchTab(idx) {
|
|
|
3117
3177
|
populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
|
|
3118
3178
|
_renderAccountMenu();
|
|
3119
3179
|
_paintTemporary(tab);
|
|
3180
|
+
if (tab._historyDeferred && tab._historyDeferred === tab.sessionId && !tab.automationRunId) {
|
|
3181
|
+
tab._historyDeferred = false;
|
|
3182
|
+
loadSessionHistory(tab.sessionId, tab.messagesEl);
|
|
3183
|
+
}
|
|
3120
3184
|
saveTabs();
|
|
3121
3185
|
}
|
|
3122
3186
|
|
|
@@ -3191,6 +3255,7 @@ function _createTrayPill(tab) {
|
|
|
3191
3255
|
pill.className = 'term-minimized-pill cp-session-pill';
|
|
3192
3256
|
pill.dataset.tabId = tab.id;
|
|
3193
3257
|
pill.innerHTML = `
|
|
3258
|
+
<span class="cp-pill-ring" aria-hidden="true"><i></i></span>
|
|
3194
3259
|
<span class="term-minimized-pill-icon">${_CP_ICON_SVG}</span>
|
|
3195
3260
|
<span class="term-minimized-pill-label">${escH(tab.label)}</span>
|
|
3196
3261
|
<button class="term-minimized-pill-close" data-tooltip="Close" data-tooltip-pos="top">×</button>
|
|
@@ -3414,8 +3479,23 @@ function _scrubStaleTabState(key) {
|
|
|
3414
3479
|
}
|
|
3415
3480
|
}
|
|
3416
3481
|
|
|
3482
|
+
// A restored tab that was working (a turn, a question, background agents or shells) can get live rows
|
|
3483
|
+
// as soon as its socket reattaches: its transcript loads at once, ahead of them. An idle one waits until
|
|
3484
|
+
// it is shown (switchTab), so opening the panel does not fetch and render every tab's transcript.
|
|
3485
|
+
function _loadHistoryIfWorking(tab, saved) {
|
|
3486
|
+
if (!tab._historyDeferred || tab._historyDeferred !== tab.sessionId) return;
|
|
3487
|
+
if (!(saved?.running || saved?.needsYou || Number(saved?.backgroundWork) > 0)) return;
|
|
3488
|
+
tab._historyDeferred = false;
|
|
3489
|
+
loadSessionHistory(tab.sessionId, tab.messagesEl);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3417
3492
|
function restoreTabs() {
|
|
3418
3493
|
_cleanStaleWindows();
|
|
3494
|
+
const pickRestoredTabIdx = (activeIdx) => {
|
|
3495
|
+
const pendingId = globalThis.__synabunPendingTab?.provider === 'claude' ? globalThis.__synabunPendingTab.tabId : '';
|
|
3496
|
+
const pendingIdx = pendingId ? _tabs.findIndex(t => t.id === pendingId || t.sessionId === pendingId) : -1;
|
|
3497
|
+
return pendingIdx >= 0 ? pendingIdx : Math.min(activeIdx || 0, _tabs.length - 1);
|
|
3498
|
+
};
|
|
3419
3499
|
// Try window-scoped tabs first
|
|
3420
3500
|
try {
|
|
3421
3501
|
const raw = storage.getItem(STOR.tabs);
|
|
@@ -3424,7 +3504,7 @@ function restoreTabs() {
|
|
|
3424
3504
|
if (data.tabs?.length) {
|
|
3425
3505
|
for (const saved of data.tabs) {
|
|
3426
3506
|
if (saved.automationActive && saved.automationOwnerId !== nativeLoopWindowId) continue;
|
|
3427
|
-
const t = createTab(saved.automationRunId ? null : saved.sessionId, saved.label, { autoSwitch: false, id: saved.id });
|
|
3507
|
+
const t = createTab(saved.automationRunId ? null : saved.sessionId, saved.label, { autoSwitch: false, id: saved.id, deferHistory: true });
|
|
3428
3508
|
if (t && saved.automationRunId) t.sessionId = saved.sessionId || null;
|
|
3429
3509
|
if (t && saved.sessionCost) t.sessionCost = saved.sessionCost;
|
|
3430
3510
|
if (t && saved.running && !saved.automationRunId) t._wasRunning = true; // trigger reattach on WS open
|
|
@@ -3448,9 +3528,10 @@ function restoreTabs() {
|
|
|
3448
3528
|
if (t && saved.automationRunId) t.automationActive = !!saved.automationActive;
|
|
3449
3529
|
if (t && saved.automationRunId) t.automationOwnerId = saved.automationOwnerId || null;
|
|
3450
3530
|
if (t && saved.releasedRunId && !saved.automationRunId) t.releasedRunId = saved.releasedRunId;
|
|
3531
|
+
if (t) _loadHistoryIfWorking(t, saved);
|
|
3451
3532
|
}
|
|
3452
3533
|
if (_tabs.length) {
|
|
3453
|
-
switchTab(
|
|
3534
|
+
switchTab(pickRestoredTabIdx(data.activeIdx));
|
|
3454
3535
|
return;
|
|
3455
3536
|
}
|
|
3456
3537
|
}
|
|
@@ -3464,7 +3545,7 @@ function restoreTabs() {
|
|
|
3464
3545
|
if (data.tabs?.length) {
|
|
3465
3546
|
for (const saved of data.tabs) {
|
|
3466
3547
|
if (saved.automationActive && saved.automationOwnerId !== nativeLoopWindowId) continue;
|
|
3467
|
-
const t = createTab(saved.automationRunId ? null : saved.sessionId, saved.label, { autoSwitch: false, id: saved.id });
|
|
3548
|
+
const t = createTab(saved.automationRunId ? null : saved.sessionId, saved.label, { autoSwitch: false, id: saved.id, deferHistory: true });
|
|
3468
3549
|
if (t && saved.automationRunId) t.sessionId = saved.sessionId || null;
|
|
3469
3550
|
if (t && saved.sessionCost) t.sessionCost = saved.sessionCost;
|
|
3470
3551
|
if (t && saved.running && !saved.automationRunId) t._wasRunning = true;
|
|
@@ -3475,9 +3556,10 @@ function restoreTabs() {
|
|
|
3475
3556
|
if (t && saved.automationRunId) t.automationRunId = saved.automationRunId;
|
|
3476
3557
|
if (t && saved.automationRunId) t.automationActive = !!saved.automationActive;
|
|
3477
3558
|
if (t && saved.automationRunId) t.automationOwnerId = saved.automationOwnerId || null;
|
|
3559
|
+
if (t) _loadHistoryIfWorking(t, saved);
|
|
3478
3560
|
}
|
|
3479
3561
|
if (_tabs.length) {
|
|
3480
|
-
switchTab(
|
|
3562
|
+
switchTab(pickRestoredTabIdx(data.activeIdx));
|
|
3481
3563
|
// Don't remove legacy key — other windows may still need it during rollout
|
|
3482
3564
|
return;
|
|
3483
3565
|
}
|
|
@@ -4512,6 +4594,8 @@ function writeSessionSnapshot(tab) {
|
|
|
4512
4594
|
const sid = tab?.sessionId || null;
|
|
4513
4595
|
const $msgs = tab?.messagesEl || null;
|
|
4514
4596
|
if (!sid || !$msgs) return;
|
|
4597
|
+
// Not loaded yet (a restored tab not shown so far), or half rebuilt: what is on screen is not the transcript.
|
|
4598
|
+
if (tab._historyDeferred || tab._historyRendering || tab._historyHold) return;
|
|
4515
4599
|
const html = providerAuthSnapshot($msgs);
|
|
4516
4600
|
if (!html.trim()) return;
|
|
4517
4601
|
// Skip snapshot if the transcript only contains the empty-state placeholder
|
|
@@ -4686,9 +4770,14 @@ function _renderHistoryRow(tab, $msgs, m) {
|
|
|
4686
4770
|
if (typeof t === 'object' && t.name) {
|
|
4687
4771
|
// A question or a plan that was already answered: a record of it, not
|
|
4688
4772
|
// the interactive card (and not a JSON dump).
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
else
|
|
4773
|
+
let card;
|
|
4774
|
+
if (t.name === 'AskUserQuestion') card = buildHistoryAskCard(t);
|
|
4775
|
+
else if (t.name === 'ExitPlanMode') card = buildHistoryPlanCard(t);
|
|
4776
|
+
else card = buildTool(t, tab);
|
|
4777
|
+
if (card) {
|
|
4778
|
+
if (t.id && $msgs._replayCards instanceof Map) $msgs._replayCards.set(String(t.id), card);
|
|
4779
|
+
wrap.appendChild(card);
|
|
4780
|
+
}
|
|
4692
4781
|
} else {
|
|
4693
4782
|
// Legacy: tool name string only
|
|
4694
4783
|
const tName = String(t);
|
|
@@ -4704,20 +4793,33 @@ function _renderHistoryRow(tab, $msgs, m) {
|
|
|
4704
4793
|
}
|
|
4705
4794
|
}
|
|
4706
4795
|
el.appendChild(wrap); $msgs.appendChild(el);
|
|
4796
|
+
// Live results that arrived for these cards while the replay had not built them.
|
|
4797
|
+
if (live && tab._pendingResults instanceof Map && tab._pendingResults.size) {
|
|
4798
|
+
for (const t of m.tools || []) {
|
|
4799
|
+
const ev = t?.id ? takePendingResult(tab, t.id) : null;
|
|
4800
|
+
if (ev) { try { updateToolResult(tab, ev); } catch {} }
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4707
4803
|
return;
|
|
4708
4804
|
}
|
|
4709
4805
|
if (m.role === 'tool_result' && m.toolUseId) {
|
|
4710
4806
|
if (fillHistoryCard($msgs, m)) return;
|
|
4807
|
+
const indexedCard = $msgs._replayCards instanceof Map ? $msgs._replayCards.get(String(m.toolUseId)) : null;
|
|
4711
4808
|
if (live) {
|
|
4712
|
-
|
|
4809
|
+
// Marked as a replayed result: it is not kept as pending, and its todos are already in the precomputed list.
|
|
4810
|
+
tab._replayRow = true;
|
|
4811
|
+
try { updateToolResult(tab, { type: 'tool_result', tool_use_id: m.toolUseId, content: m.text || '', is_error: !!m.isError, tool_use_result: m.structured }); }
|
|
4812
|
+
finally { tab._replayRow = false; }
|
|
4713
4813
|
// A subagent of a past session: its own transcript loads when the card is opened.
|
|
4714
4814
|
const agentId = m.structured?.agentId;
|
|
4715
|
-
const agentCard = agentId && sessionActionsAvailable(tab)
|
|
4815
|
+
const agentCard = agentId && sessionActionsAvailable(tab)
|
|
4816
|
+
? ((indexedCard?.classList?.contains('cp-agent-card') ? indexedCard : null) || $msgs.querySelector(`.cp-agent-card[data-tool-id="${CSS.escape(m.toolUseId)}"]`))
|
|
4817
|
+
: null;
|
|
4716
4818
|
if (agentCard) _wireSubagentTranscript(tab, agentCard, String(agentId));
|
|
4717
4819
|
return;
|
|
4718
4820
|
}
|
|
4719
4821
|
// No tab behind this container: plain text into the card.
|
|
4720
|
-
const card = $msgs.querySelector(`.tool-card[data-tool-id="${CSS.escape(m.toolUseId)}"]`);
|
|
4822
|
+
const card = (indexedCard?.matches?.('.tool-card') ? indexedCard : null) || $msgs.querySelector(`.tool-card[data-tool-id="${CSS.escape(m.toolUseId)}"]`);
|
|
4721
4823
|
const rLbl = card?.querySelector('.tool-result-label');
|
|
4722
4824
|
const rSec = card?.querySelector('.tool-result-content');
|
|
4723
4825
|
if (rSec && m.text) {
|
|
@@ -4822,12 +4924,19 @@ function _addHistoryPager(tab, $msgs, sid, project, data) {
|
|
|
4822
4924
|
// Render at the end (the live renderers append), then move the block up.
|
|
4823
4925
|
const tail = $msgs.lastElementChild;
|
|
4824
4926
|
tab._replaying = (tab._replaying || 0) + 1;
|
|
4927
|
+
const replayCards = new Map();
|
|
4928
|
+
$msgs._replayCards = replayCards;
|
|
4825
4929
|
try { for (const m of page.messages) _renderHistoryRow(tab, $msgs, m); }
|
|
4826
|
-
finally {
|
|
4930
|
+
finally {
|
|
4931
|
+
if ($msgs._replayCards === replayCards) delete $msgs._replayCards;
|
|
4932
|
+
tab._replaying = Math.max(0, (tab._replaying || 1) - 1);
|
|
4933
|
+
}
|
|
4827
4934
|
const added = [];
|
|
4828
4935
|
for (let n = tail?.nextElementSibling; n; n = n.nextElementSibling) added.push(n);
|
|
4936
|
+
markHistoryRows(added, 'earlier'); // history: a later rebuild replaces it
|
|
4829
4937
|
const anchor = note.nextElementSibling;
|
|
4830
4938
|
for (const n of added) $msgs.insertBefore(n, anchor);
|
|
4939
|
+
try { collapseAdjacentMcpTools($msgs); } catch {}
|
|
4831
4940
|
try { settleReplayedCards($msgs, tab); } catch {}
|
|
4832
4941
|
// What the user asked to see must survive the next prune.
|
|
4833
4942
|
$msgs._pruneCap = $msgs.childElementCount + MAX_MSG_CHILDREN;
|
|
@@ -4860,20 +4969,42 @@ function _dropSessionSnapshot(sid) {
|
|
|
4860
4969
|
try { delete _sessionSnapshots[sid]; deleteBlob('claude-snapshots', sid); } catch {}
|
|
4861
4970
|
}
|
|
4862
4971
|
|
|
4863
|
-
|
|
4972
|
+
// Loads of one container run one at a time, the newest asked for wins (queueHistoryLoad, cp/cp-restore.js).
|
|
4973
|
+
// The tab that owns the container is taken once, here: per-tab state (todos, usage, the snapshot) never goes
|
|
4974
|
+
// to another tab, and a tab closed meanwhile stops the load. While any load of a tab runs, its live
|
|
4975
|
+
// transcript messages wait in its buffer (historyGate in handleTabMsg) and play after the history.
|
|
4976
|
+
function loadSessionHistory(sid, $msgs) {
|
|
4864
4977
|
if (!$msgs) $msgs = activeTab()?.messagesEl;
|
|
4865
|
-
if (!$msgs) return;
|
|
4978
|
+
if (!$msgs) return Promise.resolve();
|
|
4979
|
+
const ownerTab = _tabs.find(t => t.messagesEl === $msgs) || null;
|
|
4980
|
+
if (ownerTab && ownerTab._historyDeferred === sid) ownerTab._historyDeferred = false;
|
|
4981
|
+
if (ownerTab) ownerTab._historyHold = (ownerTab._historyHold || 0) + 1;
|
|
4982
|
+
const job = queueHistoryLoad($msgs, (isStale, ticket) => {
|
|
4983
|
+
const stale = () => isStale() || !!(ownerTab && (ownerTab.closed || ownerTab.messagesEl !== $msgs));
|
|
4984
|
+
if (stale()) return;
|
|
4985
|
+
return _loadSessionHistoryNow(sid, $msgs, ownerTab, stale, ticket);
|
|
4986
|
+
});
|
|
4987
|
+
return job.finally(() => {
|
|
4988
|
+
if (!ownerTab) return;
|
|
4989
|
+
ownerTab._historyHold = Math.max(0, (ownerTab._historyHold || 1) - 1);
|
|
4990
|
+
if (!ownerTab._historyHold && ownerTab._msgBuffer?.length && !ownerTab.closed) _flushMsgBuffer(ownerTab);
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
async function _loadSessionHistoryNow(sid, $msgs, ownerTab, stale, ticket) {
|
|
4866
4995
|
// Try local snapshot first — exact replay of the live render, lossless.
|
|
4867
4996
|
// Snapshot hydration is async (blob store); wait for it so a fresh page
|
|
4868
4997
|
// load doesn't miss its own snapshots and rebuild from JSONL needlessly.
|
|
4869
4998
|
try { await _snapshotsReady; } catch {}
|
|
4999
|
+
if (stale()) return;
|
|
4870
5000
|
const snap = getSessionSnapshot(sid);
|
|
4871
5001
|
let restored = null; // the rows the snapshot put back, when a rebuild follows it
|
|
4872
5002
|
if (snap && snap.html) {
|
|
4873
|
-
const tab =
|
|
5003
|
+
const tab = ownerTab;
|
|
4874
5004
|
$msgs._historySid = sid; // the session this transcript shows (a restored pager loads its earlier pages)
|
|
4875
5005
|
if (renderStoredSession(snap, $msgs, tab)) {
|
|
4876
5006
|
restored = [...$msgs.children];
|
|
5007
|
+
markHistoryRows(restored, ticket); // the next rebuild replaces them
|
|
4877
5008
|
scrollEnd();
|
|
4878
5009
|
// Background refresh: pull latest JSONL in case the session continued in
|
|
4879
5010
|
// another window, or was rewound there. The probe reads what the owning
|
|
@@ -4889,6 +5020,7 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
4889
5020
|
if (tab?.accountId) params.set('account', tab.accountId);
|
|
4890
5021
|
const probe = await fetch(`/api/claude-code/sessions/${encodeURIComponent(sid)}/messages?${params}`);
|
|
4891
5022
|
const probeData = await probe.json();
|
|
5023
|
+
if (stale()) return;
|
|
4892
5024
|
// `visible` counts prompts and replies only (a server that predates it sends `total`).
|
|
4893
5025
|
if ((probeData?.visible ?? probeData?.total ?? 0) > (snap.itemCount || 0) + 2) {
|
|
4894
5026
|
// Upstream has materially more content — fall through to full rebuild
|
|
@@ -4904,8 +5036,8 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
4904
5036
|
} catch { return; }
|
|
4905
5037
|
}
|
|
4906
5038
|
}
|
|
4907
|
-
const accountTab =
|
|
4908
|
-
let project = _historyProject(
|
|
5039
|
+
const accountTab = ownerTab;
|
|
5040
|
+
let project = _historyProject(ownerTab || activeTab()) || undefined;
|
|
4909
5041
|
const readHistory = async (from) => {
|
|
4910
5042
|
const params = new URLSearchParams({ limit: '500' });
|
|
4911
5043
|
if (from) params.set('project', from);
|
|
@@ -4916,6 +5048,7 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
4916
5048
|
};
|
|
4917
5049
|
try {
|
|
4918
5050
|
let data = await readHistory(project);
|
|
5051
|
+
if (stale()) return;
|
|
4919
5052
|
// The tab's Claude account was removed in Settings: say so. Its transcripts
|
|
4920
5053
|
// are not looked for under the default account (another identity's folder).
|
|
4921
5054
|
if (data.code === 'account_unavailable') {
|
|
@@ -4932,6 +5065,7 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
4932
5065
|
if (historyOutcome(data) === 'not_in_project') {
|
|
4933
5066
|
const owner = await _transcriptOwner(sid, project, accountTab?.accountId || '');
|
|
4934
5067
|
const moved = owner ? await readHistory(owner) : null;
|
|
5068
|
+
if (stale()) return;
|
|
4935
5069
|
if (moved && historyOutcome(moved) !== 'not_in_project' && historyOutcome(moved) !== 'not_found' && historyOutcome(moved) !== 'refused') {
|
|
4936
5070
|
data = moved;
|
|
4937
5071
|
project = owner;
|
|
@@ -4963,29 +5097,66 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
4963
5097
|
return;
|
|
4964
5098
|
}
|
|
4965
5099
|
// Chunked rebuild: rendering 500 messages in one task froze the UI during
|
|
4966
|
-
// cold restore / orphan resync.
|
|
4967
|
-
//
|
|
4968
|
-
//
|
|
4969
|
-
|
|
4970
|
-
const
|
|
4971
|
-
// The rebuild replaces the snapshot that was put back above (only those
|
|
4972
|
-
// rows: what a live turn appended meanwhile stays).
|
|
5100
|
+
// cold restore / orphan resync. A newer load of this container (reattach resync,
|
|
5101
|
+
// in-tab session switch) or the tab closing stops this one at its next yield.
|
|
5102
|
+
// What an earlier rebuild of this container left (finished, stopped half way, or
|
|
5103
|
+
// the snapshot put back above) is taken out: never a second copy after it.
|
|
5104
|
+
for (const node of earlierHistoryRows($msgs.children, ticket)) node.remove();
|
|
4973
5105
|
if (restored) for (const node of restored) node.remove();
|
|
5106
|
+
// Rows that came live before the load (the history goes before them: it is older).
|
|
5107
|
+
const live = liveRows($msgs.children);
|
|
5108
|
+
const liveAnchor = live[0] || null;
|
|
5109
|
+
const liveSet = new Set(live);
|
|
5110
|
+
const { head, tail } = !restored ? splitHistoryTail(data.messages, 40) : { head: [], tail: data.messages };
|
|
5111
|
+
// Renders rows at the end, marks them as this load's, and moves them before `before`.
|
|
5112
|
+
const renderRows = (rows, before) => renderHistoryRowsInto($msgs, rows, (m) => _renderHistoryRow(ownerTab, $msgs, m), { before, ticket, skip: liveSet });
|
|
4974
5113
|
// Replay goes through the live renderers (tool cards, typed results), marked
|
|
4975
5114
|
// so it leaves no activity behind: no hook-strip entries, no statusline verb.
|
|
4976
5115
|
if (ownerTab) ownerTab._replaying = (ownerTab._replaying || 0) + 1;
|
|
5116
|
+
if (ownerTab) ownerTab._historyRendering = (ownerTab._historyRendering || 0) + 1;
|
|
5117
|
+
if (ownerTab && head.length) {
|
|
5118
|
+
const precomputedTodos = replayTodosFromMessages(data.messages, ownerTab.todos);
|
|
5119
|
+
if (precomputedTodos) ownerTab.todos = precomputedTodos;
|
|
5120
|
+
ownerTab._skipReplayTodos = true;
|
|
5121
|
+
}
|
|
5122
|
+
const replayCards = new Map();
|
|
5123
|
+
$msgs._replayCards = replayCards;
|
|
4977
5124
|
try {
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
5125
|
+
if (head.length) {
|
|
5126
|
+
const tailNodes = renderRows(tail, liveAnchor);
|
|
5127
|
+
const firstTailNode = tailNodes[0] || liveAnchor;
|
|
5128
|
+
scrollEnd();
|
|
5129
|
+
const CHUNK = 50;
|
|
5130
|
+
for (let offset = 0; offset < head.length; offset += CHUNK) {
|
|
4981
5131
|
await new Promise(r => setTimeout(r, 0));
|
|
4982
|
-
if (
|
|
5132
|
+
if (stale()) return;
|
|
5133
|
+
renderRows(head.slice(offset, offset + CHUNK), firstTailNode);
|
|
5134
|
+
// Following the bottom: stay there. Scrolled up: the browser's scroll anchoring keeps the rows in view.
|
|
5135
|
+
if (ownerTab === activeTab()) scrollEnd();
|
|
5136
|
+
}
|
|
5137
|
+
} else {
|
|
5138
|
+
const CHUNK = 40;
|
|
5139
|
+
for (let offset = 0; offset < data.messages.length; offset += CHUNK) {
|
|
5140
|
+
if (offset) {
|
|
5141
|
+
await new Promise(r => setTimeout(r, 0));
|
|
5142
|
+
if (stale()) return;
|
|
5143
|
+
}
|
|
5144
|
+
renderRows(data.messages.slice(offset, offset + CHUNK), liveAnchor);
|
|
4983
5145
|
}
|
|
4984
|
-
_renderHistoryRow(ownerTab, $msgs, m);
|
|
4985
5146
|
}
|
|
4986
5147
|
} finally {
|
|
4987
|
-
if (
|
|
5148
|
+
if ($msgs._replayCards === replayCards) delete $msgs._replayCards;
|
|
5149
|
+
if (ownerTab) {
|
|
5150
|
+
delete ownerTab._skipReplayTodos;
|
|
5151
|
+
ownerTab._historyRendering = Math.max(0, (ownerTab._historyRendering || 1) - 1);
|
|
5152
|
+
ownerTab._replaying = Math.max(0, (ownerTab._replaying || 1) - 1);
|
|
5153
|
+
// Live results kept for cards this page never built (cut by the page) are dropped.
|
|
5154
|
+
if (!ownerTab._historyRendering) ownerTab._pendingResults = null;
|
|
5155
|
+
}
|
|
4988
5156
|
}
|
|
5157
|
+
try { collapseAdjacentMcpTools($msgs); } catch {}
|
|
5158
|
+
// Always for the tab on screen: a list the page emptied must clear the dock too.
|
|
5159
|
+
if (ownerTab && ownerTab === activeTab()) { try { renderTodoWidget(ownerTab); } catch {} }
|
|
4989
5160
|
// Calls whose result is not in this page (interrupted, or cut by the page) stop looking busy.
|
|
4990
5161
|
try { settleReplayedCards($msgs, ownerTab); } catch {}
|
|
4991
5162
|
pruneMessages($msgs);
|
|
@@ -5001,6 +5172,8 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
5001
5172
|
$msgs.appendChild(note);
|
|
5002
5173
|
}
|
|
5003
5174
|
if (ownerTab) ownerTab._historyResults = historyHasResultSupport(data);
|
|
5175
|
+
// The pager and the notes are this load's too (a later rebuild replaces them).
|
|
5176
|
+
markHistoryRows([...$msgs.children].filter(n => !liveSet.has(n) && n[HISTORY_ROW_KEY] == null), ticket);
|
|
5004
5177
|
// Populate context gauge from session history usage
|
|
5005
5178
|
const tab = ownerTab;
|
|
5006
5179
|
if (tab && data.usage) {
|
|
@@ -5013,10 +5186,10 @@ async function loadSessionHistory(sid, $msgs) {
|
|
|
5013
5186
|
renderGauge(tab);
|
|
5014
5187
|
}
|
|
5015
5188
|
scrollEnd();
|
|
5016
|
-
// Cache the rebuilt transcript so
|
|
5017
|
-
|
|
5018
|
-
if (tab && tab.sessionId === sid) writeSessionSnapshot(tab);
|
|
5189
|
+
// Cache the rebuilt transcript (debounced so DOM serialization never blocks session open).
|
|
5190
|
+
if (tab && tab.sessionId === sid) scheduleSessionSnapshotSave(tab);
|
|
5019
5191
|
} catch {
|
|
5192
|
+
if (stale()) return;
|
|
5020
5193
|
$msgs.innerHTML = '<div class="msg-status">Failed to load history</div>';
|
|
5021
5194
|
}
|
|
5022
5195
|
}
|
|
@@ -5270,6 +5443,14 @@ function handleTabMsg(tab, msg) {
|
|
|
5270
5443
|
// An MCP server's elicitation_complete resolves the URL card that is waiting
|
|
5271
5444
|
// (bypassesPromptBuffer in cp/cp-events.js lists what passes).
|
|
5272
5445
|
const _bufferBypass = bypassesPromptBuffer(msg);
|
|
5446
|
+
// A restored tab whose history is not loaded yet gets a live row (a wakeup, a background turn): its
|
|
5447
|
+
// history loads first and the message waits for it; any running history load holds them the same way.
|
|
5448
|
+
const gate = historyGate(tab, msg, _bufferBypass);
|
|
5449
|
+
if (gate === 'load') { tab._historyDeferred = false; loadSessionHistory(tab.sessionId, tab.messagesEl); }
|
|
5450
|
+
if (gate !== 'pass') {
|
|
5451
|
+
if (tab._msgBuffer.length < 500) tab._msgBuffer.push(msg);
|
|
5452
|
+
return;
|
|
5453
|
+
}
|
|
5273
5454
|
// Also buffer while a chunked drain is in progress (or older messages still
|
|
5274
5455
|
// queue) — without this, live events overtake buffered ones between slices.
|
|
5275
5456
|
if ((tab._activePerm || tab.pendingAskRequestId || tab._draining || tab._msgBuffer.length) && !_bufferBypass) {
|
|
@@ -5295,10 +5476,11 @@ function _flushMsgBuffer(tab) {
|
|
|
5295
5476
|
while (tab._msgBuffer.length && n++ < budget) {
|
|
5296
5477
|
// A drained message can re-arm a permission/ask prompt — stop and let
|
|
5297
5478
|
// that flow's resolution resume the drain (its handlers call us again).
|
|
5298
|
-
if (tab._activePerm || tab.pendingAskRequestId) break;
|
|
5479
|
+
if (tab._activePerm || tab.pendingAskRequestId || tab._historyHold) break;
|
|
5299
5480
|
_processTabMsg(tab, tab._msgBuffer.shift());
|
|
5300
5481
|
}
|
|
5301
|
-
|
|
5482
|
+
// A history load holds the rest: it drains them when it is done.
|
|
5483
|
+
if (tab._msgBuffer.length && !tab._activePerm && !tab.pendingAskRequestId && !tab._historyHold) {
|
|
5302
5484
|
setTimeout(step, 0);
|
|
5303
5485
|
} else {
|
|
5304
5486
|
tab._draining = false;
|
|
@@ -5413,6 +5595,11 @@ function _processTabMsg(tab, msg) {
|
|
|
5413
5595
|
_sessionSaysMode(tab, msg.mode, 'reattach', msg);
|
|
5414
5596
|
// Orphan buffer overflowed while detached — rebuild transcript from JSONL
|
|
5415
5597
|
if (msg.resynced && tab.sessionId) {
|
|
5598
|
+
tab._historyDeferred = false;
|
|
5599
|
+
try { loadSessionHistory(tab.sessionId, tab.messagesEl); } catch {}
|
|
5600
|
+
} else if (tab._historyDeferred && tab._historyDeferred === tab.sessionId) {
|
|
5601
|
+
// A restored tab not shown yet whose session is still live on the server: its history first.
|
|
5602
|
+
tab._historyDeferred = false;
|
|
5416
5603
|
try { loadSessionHistory(tab.sessionId, tab.messagesEl); } catch {}
|
|
5417
5604
|
}
|
|
5418
5605
|
if (msg.running) {
|
|
@@ -6688,6 +6875,8 @@ function renderAssistant(tab, msg) {
|
|
|
6688
6875
|
|
|
6689
6876
|
/** Build an interactive AskUserQuestion card from a tool_use block */
|
|
6690
6877
|
function buildAskFromToolUse(tab, block) {
|
|
6878
|
+
// A task worker’s broker owns the interactive card, even if this tool event arrives first.
|
|
6879
|
+
if (tab.automationRunId) { const placeholder = document.createElement('div'); placeholder.hidden = true; return placeholder; }
|
|
6691
6880
|
const input = block.input || {};
|
|
6692
6881
|
const toolUseId = block.id || '';
|
|
6693
6882
|
tab.pendingAskToolUseId = toolUseId;
|
|
@@ -6879,7 +7068,7 @@ function buildAskFromToolUse(tab, block) {
|
|
|
6879
7068
|
// Submit button — sends all answers as a batch
|
|
6880
7069
|
submitBtn.addEventListener('click', () => {
|
|
6881
7070
|
// An answer is shown as given only once it can be sent: until then the card stays as it is.
|
|
6882
|
-
if (tab.ws?.readyState !== WebSocket.OPEN) { appendStatus(tab, ASK_ANSWER_UNSENT); return; }
|
|
7071
|
+
if (!tab.automationRunId && tab.ws?.readyState !== WebSocket.OPEN) { appendStatus(tab, ASK_ANSWER_UNSENT); return; }
|
|
6883
7072
|
container.querySelectorAll('.ask-option').forEach(b => { b.disabled = true; });
|
|
6884
7073
|
container.querySelectorAll('.ask-text-input, .ask-notes').forEach(i => { i.disabled = true; });
|
|
6885
7074
|
submitBtn.disabled = true;
|
|
@@ -6897,6 +7086,21 @@ function buildAskFromToolUse(tab, block) {
|
|
|
6897
7086
|
|
|
6898
7087
|
// Returns whether the answer left: false while the tab's socket is closed.
|
|
6899
7088
|
function sendAskAnswer(tab, questions, answers, annotations = null) {
|
|
7089
|
+
if (tab.automationRunId) {
|
|
7090
|
+
const requestId = tab.pendingAskRequestId;
|
|
7091
|
+
if (!tab._workerQuestions?.has(requestId)) return false;
|
|
7092
|
+
answerWorkerQuestion(tab.automationRunId, requestId, { decision: 'answer', answers }).catch((error) => {
|
|
7093
|
+
appendStatus(tab, error.message);
|
|
7094
|
+
// Keep the native card retryable after a delivery failure.
|
|
7095
|
+
if (tab._workerQuestions?.has(requestId)) {
|
|
7096
|
+
const card = tab.messagesEl?.querySelector(`[data-worker-question-id="${CSS.escape(requestId)}"]`);
|
|
7097
|
+
card?.querySelectorAll('button, input, textarea').forEach((el) => { el.disabled = false; });
|
|
7098
|
+
const submit = card?.querySelector('.ask-submit');
|
|
7099
|
+
if (submit) submit.textContent = t('common.retry');
|
|
7100
|
+
}
|
|
7101
|
+
});
|
|
7102
|
+
return true;
|
|
7103
|
+
}
|
|
6900
7104
|
if (!tab.ws || tab.ws.readyState !== WebSocket.OPEN) return false;
|
|
6901
7105
|
|
|
6902
7106
|
const requestId = tab.pendingAskRequestId;
|
|
@@ -7348,9 +7552,15 @@ function updateToolResult(tab, ev) {
|
|
|
7348
7552
|
if (!$msgs) return;
|
|
7349
7553
|
// Background task output (BashOutput results) routes back to the origin card
|
|
7350
7554
|
try { handleBgToolResult(ev, tab); } catch {}
|
|
7351
|
-
|
|
7352
|
-
const
|
|
7353
|
-
|
|
7555
|
+
// During a replay the cards it built are indexed by id (no selector scan of the transcript per result).
|
|
7556
|
+
const indexed = $msgs._replayCards instanceof Map ? $msgs._replayCards.get(String(ev.tool_use_id)) : null;
|
|
7557
|
+
let card = indexed?.matches?.('.tool-card, .plan-card') ? indexed : null;
|
|
7558
|
+
if (!card) {
|
|
7559
|
+
const escapedId = CSS.escape(ev.tool_use_id);
|
|
7560
|
+
card = $msgs.querySelector(`.tool-card[data-tool-id="${escapedId}"]`) || $msgs.querySelector(`.plan-card[data-tool-id="${escapedId}"]`);
|
|
7561
|
+
}
|
|
7562
|
+
// A live result for a card the tail-first replay has not built yet: applied when it is built.
|
|
7563
|
+
if (!card) { holdPendingResult(tab, ev); return; }
|
|
7354
7564
|
// Synthetic PostToolUse hook event + SubagentStop
|
|
7355
7565
|
const toolNm = card.dataset.toolName || '';
|
|
7356
7566
|
recordHookEvent(tab, ev.is_error ? 'PostToolUseFailure' : 'PostToolUse', toolNm);
|
|
@@ -7375,8 +7585,10 @@ function updateToolResult(tab, ev) {
|
|
|
7375
7585
|
finalizeDiffCard(card, ev, typed?.view);
|
|
7376
7586
|
if (!ev.is_error) return; // success: stats in the header say it all — skip noisy RESULT text
|
|
7377
7587
|
}
|
|
7378
|
-
// Collapse adjacent repeated MCP tool calls (
|
|
7379
|
-
|
|
7588
|
+
// Collapse adjacent repeated MCP tool calls inside this turn (replays batch once at the end)
|
|
7589
|
+
if (!tab._replaying && toolNm.startsWith('mcp__')) {
|
|
7590
|
+
try { collapseAdjacentMcpTools(card.parentElement || $msgs); } catch {}
|
|
7591
|
+
}
|
|
7380
7592
|
// Plan cards don't have result sections — just mark success/error via border
|
|
7381
7593
|
if (card.classList.contains('plan-card')) {
|
|
7382
7594
|
card.style.borderColor = ev.is_error ? 'rgba(255,82,82,0.3)' : 'rgba(100,200,120,0.3)';
|
|
@@ -7558,6 +7770,9 @@ function _applySessionOptions(tab, msg) {
|
|
|
7558
7770
|
if (hasCapability(tab, 'tool_policy')) msg.toolPolicy = tab.toolPolicy || 'full';
|
|
7559
7771
|
// The tab's session settings (/session): the bridge validates them again.
|
|
7560
7772
|
if (hasCapability(tab, 'session_settings')) msg.session = normalizeSession(tab.session);
|
|
7773
|
+
// Think off on a model with effort levels is a pick of its own: sent as 'off'
|
|
7774
|
+
// so the bridge disables thinking, or the user's settings' effortLevel applies.
|
|
7775
|
+
if (!msg.effort && hasCapability(tab, 'effort_off') && _modelSupportsEffort(tab)) msg.effort = 'off';
|
|
7561
7776
|
// The Claude account this tab runs under (the default account sends nothing).
|
|
7562
7777
|
if (hasCapability(tab, 'accounts')) msg.accountId = tab.accountId || 'default';
|
|
7563
7778
|
// A name typed before the first message becomes the new session's title.
|
|
@@ -8057,6 +8272,7 @@ function renderAskUserQuestion(tab, requestId, input) {
|
|
|
8057
8272
|
const allQuestions = input.questions || questions;
|
|
8058
8273
|
const el = document.createElement('div');
|
|
8059
8274
|
el.className = 'msg msg-assistant';
|
|
8275
|
+
el.dataset.workerQuestionId = requestId;
|
|
8060
8276
|
|
|
8061
8277
|
const avatar = document.createElement('div');
|
|
8062
8278
|
avatar.className = 'msg-avatar';
|
|
@@ -8238,7 +8454,7 @@ function renderAskUserQuestion(tab, requestId, input) {
|
|
|
8238
8454
|
// Submit button — sends all answers as a batch
|
|
8239
8455
|
submitBtn.addEventListener('click', () => {
|
|
8240
8456
|
// An answer is shown as given only once it can be sent: until then the card stays as it is.
|
|
8241
|
-
if (tab.ws?.readyState !== WebSocket.OPEN) { appendStatus(tab, ASK_ANSWER_UNSENT); return; }
|
|
8457
|
+
if (!tab.automationRunId && tab.ws?.readyState !== WebSocket.OPEN) { appendStatus(tab, ASK_ANSWER_UNSENT); return; }
|
|
8242
8458
|
wrap.querySelectorAll('.ask-option').forEach(b => { b.disabled = true; });
|
|
8243
8459
|
wrap.querySelectorAll('.ask-text-input, .ask-notes').forEach(i => { i.disabled = true; });
|
|
8244
8460
|
submitBtn.disabled = true;
|
|
@@ -8308,8 +8524,12 @@ function appendUser(tab, text, images, files) {
|
|
|
8308
8524
|
function _stampUserMessageUuid(tab, uuid) {
|
|
8309
8525
|
const $msgs = tab.messagesEl;
|
|
8310
8526
|
if (!$msgs || !uuid) return;
|
|
8311
|
-
const
|
|
8312
|
-
|
|
8527
|
+
const last = $msgs.lastElementChild;
|
|
8528
|
+
let row = (last?.classList?.contains('msg-user') && !last.dataset.uuid) ? last : null;
|
|
8529
|
+
if (!row) {
|
|
8530
|
+
const rows = [...$msgs.querySelectorAll('.msg-user')].reverse();
|
|
8531
|
+
row = rows.find(r => !r.dataset.uuid);
|
|
8532
|
+
}
|
|
8313
8533
|
if (!row) return;
|
|
8314
8534
|
row.dataset.uuid = uuid;
|
|
8315
8535
|
if (!tab.sdkMode) return;
|
|
@@ -10349,29 +10569,36 @@ const _fileLinkOptions = {
|
|
|
10349
10569
|
};
|
|
10350
10570
|
|
|
10351
10571
|
// ── Public API ──
|
|
10572
|
+
let _readyPromise = null;
|
|
10352
10573
|
async function _ensureClaudePanelReady() {
|
|
10574
|
+
if (_readyPromise) return _readyPromise;
|
|
10353
10575
|
if (!_panel) {
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10576
|
+
_readyPromise = (async () => {
|
|
10577
|
+
injectStyles();
|
|
10578
|
+
_panel = buildPanel();
|
|
10579
|
+
document.body.appendChild(_panel);
|
|
10580
|
+
registerSidepanel({
|
|
10581
|
+
owner: PANEL_OWNER, provider: 'claude', element: _panel,
|
|
10582
|
+
header: _panel.querySelector('.cp-header'), actions: _panel.querySelector('.cp-header-actions'),
|
|
10583
|
+
buttonClass: 'cp-header-btn', dockHandle: _panel.querySelector('.cp-resize-handle'),
|
|
10584
|
+
applyVisibility: applyClaudeVisibility,
|
|
10585
|
+
});
|
|
10586
|
+
wireEvents();
|
|
10587
|
+
_panel.querySelector('#cp-messages-container')?.addEventListener('click', _onMessagesClick);
|
|
10588
|
+
wireFileLinks(_panel.querySelector('#cp-messages-container'), _fileLinkOptions);
|
|
10589
|
+
await loadConfig(); // must complete before restoreTabs — checks server boot ID
|
|
10590
|
+
loadSkills();
|
|
10591
|
+
restoreTabs();
|
|
10592
|
+
setTimeout(() => {
|
|
10593
|
+
loadMonthlyCost();
|
|
10594
|
+
syncSessionCosts();
|
|
10595
|
+
}, 400);
|
|
10596
|
+
_wireBrowserSync();
|
|
10597
|
+
_startHeartbeat();
|
|
10598
|
+
window.dispatchEvent(new CustomEvent('sidepanel-tray:provider-loaded', { detail: { provider: 'claude' } }));
|
|
10599
|
+
_ensureCliSubscription();
|
|
10600
|
+
})();
|
|
10601
|
+
return _readyPromise;
|
|
10375
10602
|
}
|
|
10376
10603
|
}
|
|
10377
10604
|
|
|
@@ -10495,6 +10722,19 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
|
|
|
10495
10722
|
tab._automationLifecycleError = run.error;
|
|
10496
10723
|
appendError(tab, run.error);
|
|
10497
10724
|
}
|
|
10725
|
+
syncWorkerQuestionQueue(tab, run, {
|
|
10726
|
+
open: (id, pending) => handleControlRequest(tab, { request_id: id, request: { tool_name: 'AskUserQuestion', input: pending.input || { questions: pending.questions } } }),
|
|
10727
|
+
close: (id) => {
|
|
10728
|
+
const card = tab.messagesEl?.querySelector(`[data-worker-question-id="${CSS.escape(id)}"]`);
|
|
10729
|
+
card?.querySelectorAll('button, input, textarea').forEach((el) => { el.disabled = true; });
|
|
10730
|
+
const submit = card?.querySelector('.ask-submit');
|
|
10731
|
+
if (submit) submit.textContent = t('workerQuestion.resolved');
|
|
10732
|
+
tab.pendingAskRequestId = null; tab.pendingAskToolUseId = null;
|
|
10733
|
+
tab.pendingAskBufferedAnswer = null; tab.askRenderedViaControl = false;
|
|
10734
|
+
// The same provider turn resumes; release messages buffered while its question was open.
|
|
10735
|
+
_flushMsgBuffer(tab);
|
|
10736
|
+
},
|
|
10737
|
+
});
|
|
10498
10738
|
trackWorkerEnd(run, { apply: (view) => showWorkerEnd(tab, view), busy: hasLiveBackgroundWork(tab) });
|
|
10499
10739
|
// The mode control is hidden while the run is active and back when it ended.
|
|
10500
10740
|
if (tab === activeTab()) populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
|
|
@@ -11997,11 +12237,11 @@ function wireEvents() {
|
|
|
11997
12237
|
// Side panel browser launch removed — all automations use floating browsers only.
|
|
11998
12238
|
|
|
11999
12239
|
// Show panel without toggling it closed (used by notification click-through)
|
|
12000
|
-
on('claude-panel:show', (data) => {
|
|
12001
|
-
if (!_visible) toggleClaudePanel();
|
|
12240
|
+
on('claude-panel:show', async (data) => {
|
|
12241
|
+
if (!_visible) await toggleClaudePanel();
|
|
12002
12242
|
if (data?.tabId) {
|
|
12003
|
-
const idx = _tabs.findIndex(t => t.id === data.tabId);
|
|
12004
|
-
if (idx >= 0) switchTab(idx);
|
|
12243
|
+
const idx = _tabs.findIndex(t => t.id === data.tabId || t.sessionId === data.tabId);
|
|
12244
|
+
if (idx >= 0 && idx !== _activeTabIdx) switchTab(idx);
|
|
12005
12245
|
}
|
|
12006
12246
|
});
|
|
12007
12247
|
|