@deepseek-harness-tui/dsh-tui 0.11.1 → 0.11.2
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/README.md +50 -20
- package/assets/whale-girl/whale-girl-dark.png +0 -0
- package/assets/whale-girl/whale-girl-happy.png +0 -0
- package/assets/whale-girl/whale-girl.png +0 -0
- package/bin/dsh-tui.js +63 -3
- package/cordis.patch.yml +39 -17
- package/cordis.yml +3 -2
- package/lib/settings.json +1272 -0
- package/lib/types/adapter/channel/ui-policy.d.ts +9 -2
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui-policy.js +13 -0
- package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
- package/lib/types/adapter/kernel/replay.js +2 -0
- package/lib/types/adapter/ports/channel-display.d.ts +12 -0
- package/lib/types/adapter/ports/channel-display.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-session.d.ts +38 -0
- package/lib/types/adapter/ports/channel-session.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-settings.d.ts +12 -0
- package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-ui.d.ts +46 -4
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +43 -0
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +2 -0
- package/lib/types/components/BalanceReportRow.d.ts +10 -2
- package/lib/types/components/BalanceReportRow.d.ts.map +1 -1
- package/lib/types/components/BalanceReportRow.js +18 -4
- package/lib/types/components/CompactionStatusRow.d.ts +24 -0
- package/lib/types/components/CompactionStatusRow.d.ts.map +1 -0
- package/lib/types/components/CompactionStatusRow.js +72 -0
- package/lib/types/components/GoalTodoPanel.js +2 -2
- package/lib/types/components/HelpMenu.d.ts +2 -2
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +5 -4
- package/lib/types/components/InlineMathParagraph.d.ts +22 -0
- package/lib/types/components/InlineMathParagraph.d.ts.map +1 -0
- package/lib/types/components/InlineMathParagraph.js +178 -0
- package/lib/types/components/LogoV2.d.ts +41 -3
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +148 -24
- package/lib/types/components/Markdown.d.ts +10 -3
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +47 -14
- package/lib/types/components/MathBlock.d.ts +13 -0
- package/lib/types/components/MathBlock.d.ts.map +1 -0
- package/lib/types/components/MathBlock.js +136 -0
- package/lib/types/components/MessageList.d.ts +10 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +13 -4
- package/lib/types/components/MigratePicker.d.ts +30 -0
- package/lib/types/components/MigratePicker.d.ts.map +1 -0
- package/lib/types/components/MigratePicker.js +38 -0
- package/lib/types/components/PromptInput.d.ts +17 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +48 -7
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +2 -1
- package/lib/types/components/StarPrompt.d.ts +53 -0
- package/lib/types/components/StarPrompt.d.ts.map +1 -0
- package/lib/types/components/StarPrompt.js +281 -0
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +20 -3
- package/lib/types/components/TimelineRail.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.js +9 -1
- package/lib/types/components/Whale.d.ts +17 -8
- package/lib/types/components/Whale.d.ts.map +1 -1
- package/lib/types/components/Whale.js +25 -15
- package/lib/types/components/WhaleGirl.d.ts +29 -0
- package/lib/types/components/WhaleGirl.d.ts.map +1 -0
- package/lib/types/components/WhaleGirl.js +32 -0
- package/lib/types/components/WorkingSpinner.d.ts +4 -1
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
- package/lib/types/components/WorkingSpinner.js +2 -2
- package/lib/types/components/bigfont.d.ts +47 -18
- package/lib/types/components/bigfont.d.ts.map +1 -1
- package/lib/types/components/bigfont.js +55 -40
- package/lib/types/components/design-system/ThemeProvider.d.ts +6 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +40 -1
- package/lib/types/components/maidPortrait.d.ts +44 -0
- package/lib/types/components/maidPortrait.d.ts.map +1 -0
- package/lib/types/components/maidPortrait.js +178 -0
- package/lib/types/components/mathPreview.d.ts +25 -0
- package/lib/types/components/mathPreview.d.ts.map +1 -0
- package/lib/types/components/mathPreview.js +48 -0
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +2 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +3 -2
- package/lib/types/components/messages/TranscriptImages.d.ts.map +1 -1
- package/lib/types/components/messages/TranscriptImages.js +11 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +14 -2
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +80 -38
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +3 -3
- package/lib/types/components/sessions/ForeignSessionRow.d.ts +34 -0
- package/lib/types/components/sessions/ForeignSessionRow.d.ts.map +1 -0
- package/lib/types/components/sessions/ForeignSessionRow.js +43 -0
- package/lib/types/components/sessions/SourceTabs.d.ts +52 -0
- package/lib/types/components/sessions/SourceTabs.d.ts.map +1 -0
- package/lib/types/components/sessions/SourceTabs.js +70 -0
- package/lib/types/components/splashEggs.d.ts +56 -0
- package/lib/types/components/splashEggs.d.ts.map +1 -0
- package/lib/types/components/splashEggs.js +67 -0
- package/lib/types/components/splashFonts.d.ts +119 -0
- package/lib/types/components/splashFonts.d.ts.map +1 -0
- package/lib/types/components/splashFonts.js +267 -0
- package/lib/types/components/splashLayout.d.ts +41 -0
- package/lib/types/components/splashLayout.d.ts.map +1 -0
- package/lib/types/components/splashLayout.js +40 -0
- package/lib/types/components/whaleGirlSprite.d.ts +17 -0
- package/lib/types/components/whaleGirlSprite.d.ts.map +1 -0
- package/lib/types/components/whaleGirlSprite.js +47 -0
- package/lib/types/deepseekPricing.d.ts +74 -0
- package/lib/types/deepseekPricing.d.ts.map +1 -1
- package/lib/types/deepseekPricing.js +110 -11
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.js +6 -1
- package/lib/types/dsh-adapter/channel/background-action.d.ts +1 -0
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/background-action.js +1 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +8 -9
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +69 -21
- package/lib/types/dsh-adapter/channel/compaction.d.ts +2 -1
- package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/compaction.js +35 -2
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/local-actions.js +6 -1
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +5 -1
- package/lib/types/dsh-adapter/channel/paths.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/paths.js +7 -1
- package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/preferences.js +16 -0
- package/lib/types/dsh-adapter/channel/projection.d.ts +1 -2
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +127 -17
- package/lib/types/dsh-adapter/channel/question-record.d.ts +76 -0
- package/lib/types/dsh-adapter/channel/question-record.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/question-record.js +107 -0
- package/lib/types/dsh-adapter/channel/session-lineage.d.ts +42 -0
- package/lib/types/dsh-adapter/channel/session-lineage.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-lineage.js +51 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +2 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-live-adoption.js +9 -3
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts +2 -1
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +39 -7
- package/lib/types/dsh-adapter/channel/session-reset.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-reset.js +7 -0
- package/lib/types/dsh-adapter/channel/session-resume.d.ts +1 -0
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-resume.js +4 -2
- package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/settings-host.js +8 -0
- package/lib/types/dsh-adapter/channel/state.d.ts +14 -1
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +6 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts +30 -35
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +138 -4
- package/lib/types/dsh-adapter/channel/types.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +19 -4
- package/lib/types/dsh-adapter/compat/messages.d.ts +6 -0
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/messages.js +9 -0
- package/lib/types/dsh-adapter/contract.d.ts +5 -5
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +8 -6
- package/lib/types/dsh-adapter/index.d.ts +36 -1
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +22 -6
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.js +131 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.d.ts +17 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.js +329 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.js +102 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.parse.d.ts +16 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.parse.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.parse.js +289 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.js +117 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.d.ts +13 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.js +178 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.js +133 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan-fs.d.ts +60 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan-fs.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan-fs.js +178 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts +8 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.js +40 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.js +93 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.d.ts +9 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.js +89 -0
- package/lib/types/dsh-adapter/migrate/bin-path.d.ts +6 -0
- package/lib/types/dsh-adapter/migrate/bin-path.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/bin-path.js +30 -0
- package/lib/types/dsh-adapter/migrate/browse.d.ts +42 -0
- package/lib/types/dsh-adapter/migrate/browse.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/browse.js +90 -0
- package/lib/types/dsh-adapter/migrate/cli.d.ts +9 -0
- package/lib/types/dsh-adapter/migrate/cli.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/cli.js +106 -0
- package/lib/types/dsh-adapter/migrate/import-one.d.ts +89 -0
- package/lib/types/dsh-adapter/migrate/import-one.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/import-one.js +133 -0
- package/lib/types/dsh-adapter/migrate/index.d.ts +35 -0
- package/lib/types/dsh-adapter/migrate/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/index.js +107 -0
- package/lib/types/dsh-adapter/migrate/parse/injection.d.ts +40 -0
- package/lib/types/dsh-adapter/migrate/parse/injection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/parse/injection.js +127 -0
- package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts +28 -0
- package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/parse/jsonl.js +40 -0
- package/lib/types/dsh-adapter/migrate/parse/role-turns.d.ts +28 -0
- package/lib/types/dsh-adapter/migrate/parse/role-turns.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/parse/role-turns.js +50 -0
- package/lib/types/dsh-adapter/migrate/parse/title.d.ts +18 -0
- package/lib/types/dsh-adapter/migrate/parse/title.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/parse/title.js +29 -0
- package/lib/types/dsh-adapter/migrate/parse/tools.d.ts +62 -0
- package/lib/types/dsh-adapter/migrate/parse/tools.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/parse/tools.js +110 -0
- package/lib/types/dsh-adapter/migrate/picker.d.ts +64 -0
- package/lib/types/dsh-adapter/migrate/picker.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/picker.js +86 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.d.ts +40 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.js +100 -0
- package/lib/types/dsh-adapter/migrate/sessionize.d.ts +39 -0
- package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/sessionize.js +271 -0
- package/lib/types/dsh-adapter/migrate/types.d.ts +176 -0
- package/lib/types/dsh-adapter/migrate/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/types.js +11 -0
- package/lib/types/dsh-adapter/migrate/uuid.d.ts +7 -0
- package/lib/types/dsh-adapter/migrate/uuid.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/uuid.js +25 -0
- package/lib/types/dsh-adapter/plugin.d.ts +4 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +143 -364
- package/lib/types/dsh-adapter/providerWizard.d.ts +17 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +254 -66
- package/lib/types/dsh-adapter/questions.d.ts +22 -15
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions.js +47 -45
- package/lib/types/dsh-adapter/sessions/digest.d.ts +11 -6
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/digest.js +20 -13
- package/lib/types/dsh-adapter/sessions/frames.d.ts +3 -1
- package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/frames.js +5 -4
- package/lib/types/dsh-adapter/sessions/list.d.ts +6 -1
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/list.js +188 -164
- package/lib/types/dsh-adapter/sessions/recovery.d.ts +16 -0
- package/lib/types/dsh-adapter/sessions/recovery.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/recovery.js +108 -0
- package/lib/types/dsh-adapter/sessions/snapshot.d.ts +7 -0
- package/lib/types/dsh-adapter/sessions/snapshot.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/snapshot.js +90 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts +10 -4
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/store.js +62 -25
- package/lib/types/dsh-adapter/sideQuestion.d.ts +41 -2
- package/lib/types/dsh-adapter/sideQuestion.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sideQuestion.js +98 -2
- package/lib/types/dsh-adapter/subagents.d.ts +28 -3
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +77 -2
- package/lib/types/herdr.d.ts +2 -0
- package/lib/types/herdr.d.ts.map +1 -1
- package/lib/types/herdr.js +9 -3
- package/lib/types/i18n.d.ts +424 -42
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +145 -20
- package/lib/types/ink/components/App.d.ts +2 -2
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +5 -1
- package/lib/types/ink/components/Image.d.ts +14 -1
- package/lib/types/ink/components/Image.d.ts.map +1 -1
- package/lib/types/ink/components/Image.js +2 -2
- package/lib/types/ink/events/input-event.js +3 -3
- package/lib/types/ink/hooks/use-terminal-images.d.ts +12 -0
- package/lib/types/ink/hooks/use-terminal-images.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-terminal-images.js +13 -0
- package/lib/types/ink/ink.d.ts +6 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +16 -1
- package/lib/types/ink/kitty-graphics.d.ts +44 -3
- package/lib/types/ink/kitty-graphics.d.ts.map +1 -1
- package/lib/types/ink/kitty-graphics.js +184 -23
- package/lib/types/ink/output.d.ts +24 -2
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +83 -5
- package/lib/types/ink/parse-keypress.d.ts +26 -1
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +156 -13
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +8 -1
- package/lib/types/ink/screen.d.ts +24 -0
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/screen.js +89 -0
- package/lib/types/ink/selection.d.ts +40 -18
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +142 -30
- package/lib/types/ink/sixel-codec.d.ts +12 -1
- package/lib/types/ink/sixel-codec.d.ts.map +1 -1
- package/lib/types/ink/sixel-codec.js +29 -5
- package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
- package/lib/types/ink/sixel-graphics.js +5 -3
- package/lib/types/ink/styles.d.ts +9 -0
- package/lib/types/ink/styles.d.ts.map +1 -1
- package/lib/types/ink/terminal-image.d.ts +8 -0
- package/lib/types/ink/terminal-image.d.ts.map +1 -1
- package/lib/types/ink/terminal-querier.d.ts +7 -1
- package/lib/types/ink/terminal-querier.d.ts.map +1 -1
- package/lib/types/ink/terminal-querier.js +12 -2
- package/lib/types/ink/timeline-rail.d.ts +5 -0
- package/lib/types/ink/timeline-rail.d.ts.map +1 -1
- package/lib/types/math/inline-layout.d.ts +29 -0
- package/lib/types/math/inline-layout.d.ts.map +1 -0
- package/lib/types/math/inline-layout.js +106 -0
- package/lib/types/math/layout.d.ts +58 -0
- package/lib/types/math/layout.d.ts.map +1 -0
- package/lib/types/math/layout.js +59 -0
- package/lib/types/math/renderer.d.ts +64 -0
- package/lib/types/math/renderer.d.ts.map +1 -0
- package/lib/types/math/renderer.js +256 -0
- package/lib/types/screens/Chat.d.ts +14 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +606 -53
- package/lib/types/screens/SessionSupervisor.d.ts +2 -1
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/SessionSupervisor.js +156 -19
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +49 -11
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +45 -15
- package/lib/types/screens/chatOverlay.d.ts +5 -0
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +1 -0
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts +47 -0
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.js +65 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts +12 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/model.js +26 -0
- package/lib/types/screens/sessionSupervisor/useForeignSessions.d.ts +107 -0
- package/lib/types/screens/sessionSupervisor/useForeignSessions.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/useForeignSessions.js +312 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +4 -1
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +97 -14
- package/lib/types/sessionHistory.d.ts +2 -2
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessionHistory.js +31 -5
- package/lib/types/settings/definitions.d.ts +681 -0
- package/lib/types/settings/definitions.d.ts.map +1 -0
- package/lib/types/settings/definitions.js +511 -0
- package/lib/types/starAction.d.ts +44 -0
- package/lib/types/starAction.d.ts.map +1 -0
- package/lib/types/starAction.js +52 -0
- package/lib/types/terminal-utils/latex.d.ts +10 -0
- package/lib/types/terminal-utils/latex.d.ts.map +1 -0
- package/lib/types/terminal-utils/latex.js +1417 -0
- package/lib/types/terminal-utils/markdown.d.ts +3 -1
- package/lib/types/terminal-utils/markdown.d.ts.map +1 -1
- package/lib/types/terminal-utils/markdown.js +26 -1
- package/lib/types/terminal-utils/math.d.ts +66 -0
- package/lib/types/terminal-utils/math.d.ts.map +1 -0
- package/lib/types/terminal-utils/math.js +337 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +6 -0
- package/lib/types/tuiDisplayPrefs.d.ts +67 -0
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +59 -0
- package/lib/types/ui.d.ts +1 -1
- package/lib/types/ui.d.ts.map +1 -1
- package/lib/types/ui.js +1 -1
- package/lib/types/usageStats.d.ts +98 -0
- package/lib/types/usageStats.d.ts.map +1 -0
- package/lib/types/usageStats.js +169 -0
- package/lib/types/utils/bmp.d.ts +17 -0
- package/lib/types/utils/bmp.d.ts.map +1 -0
- package/lib/types/utils/bmp.js +154 -0
- package/lib/types/utils/clipboard.d.ts +14 -2
- package/lib/types/utils/clipboard.d.ts.map +1 -1
- package/lib/types/utils/clipboard.js +143 -8
- package/lib/types/utils/fold-long-lines.d.ts.map +1 -1
- package/lib/types/utils/fold-long-lines.js +2 -1
- package/lib/types/utils/keymap.d.ts +4 -1
- package/lib/types/utils/keymap.d.ts.map +1 -1
- package/lib/types/utils/keymap.js +8 -0
- package/lib/types/utils/modifiers.d.ts +4 -3
- package/lib/types/utils/modifiers.d.ts.map +1 -1
- package/lib/types/utils/modifiers.js +4 -3
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/LICENSE +202 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/NOTICE +20 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/index.d.ts +16 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/bundled-packages.json +4 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/index.mjs +20 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/index.mjs.LEGAL.txt +35 -0
- package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/package.json +29 -0
- package/package.json +111 -97
|
@@ -11,16 +11,25 @@ import { planReload } from '../reload.js';
|
|
|
11
11
|
import { AlternateScreen, Box, Image, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
|
|
12
12
|
import { usePageInset } from '../components/PageMargin.js';
|
|
13
13
|
import { POINTER } from '../terminal-utils/figures.js';
|
|
14
|
-
import { isPlainReturnInput
|
|
15
|
-
import { actionMatches } from '../utils/keymap.js';
|
|
14
|
+
import { isPlainReturnInput } from '../utils/modifiers.js';
|
|
15
|
+
import { actionMatches, effectiveComboDisplay, primaryComboString } from '../utils/keymap.js';
|
|
16
16
|
import { formatTokens } from '../terminal-utils/format.js';
|
|
17
17
|
import { homeDir } from '../utils/paths.js';
|
|
18
|
+
import { execFileNoThrow } from '../utils/execFileNoThrow.js';
|
|
18
19
|
import { cleanRenderText, cleanScalarText } from '../dsh-adapter/sanitize.js';
|
|
19
20
|
import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
|
|
20
21
|
import { readModelRecents, recordModelUse } from '../modelRecents.js';
|
|
21
22
|
import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
|
|
22
23
|
import { TuiStatusStore } from '../dsh-adapter/status.js';
|
|
23
24
|
import { ActivityStore, useActivity } from '../dsh-adapter/activity-store.js';
|
|
25
|
+
import { rasterToPng, setMathPreviewOpener } from '../components/mathPreview.js';
|
|
26
|
+
import { renderMathRaster } from '../math/renderer.js';
|
|
27
|
+
/** Formula preview rasters are re-typeset at this cell scale (2× the
|
|
28
|
+
* transcript's) so the card's 100–800% zoom stays sharp instead of upscaling
|
|
29
|
+
* the inline pixels. Bounded like the other image budgets. */
|
|
30
|
+
const MATH_PREVIEW_SCALE = 2;
|
|
31
|
+
const MATH_PREVIEW_MAX_COLUMNS = 480;
|
|
32
|
+
const MATH_PREVIEW_MAX_ROWS = 64;
|
|
24
33
|
import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
|
|
25
34
|
import { ApprovalStore } from '../dsh-adapter/approvals.js';
|
|
26
35
|
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
@@ -33,6 +42,9 @@ import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
|
|
|
33
42
|
import { useSelection } from '../ink/hooks/use-selection.js';
|
|
34
43
|
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
35
44
|
import { LogoHeader, MessageList } from '../components/MessageList.js';
|
|
45
|
+
import { splashFontIdOf } from '../components/splashFonts.js';
|
|
46
|
+
import { StarPrompt } from '../components/StarPrompt.js';
|
|
47
|
+
import { dueStarModal, markStarAsked, STAR_MILESTONES } from '../usageStats.js';
|
|
36
48
|
import { TimelineRail } from '../components/TimelineRail.js';
|
|
37
49
|
import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
|
|
38
50
|
import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
|
|
@@ -42,7 +54,9 @@ import { PromptInput } from '../components/PromptInput.js';
|
|
|
42
54
|
import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
|
|
43
55
|
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
44
56
|
import { AutoRecapRow } from '../components/AutoRecapRow.js';
|
|
57
|
+
import { CompactionStatusRow } from '../components/CompactionStatusRow.js';
|
|
45
58
|
import { BalanceReportRow } from '../components/BalanceReportRow.js';
|
|
59
|
+
import { estimateSessionCostSnapshotCny } from '../deepseekPricing.js';
|
|
46
60
|
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
47
61
|
import { StatusLine } from './StatusLine.js';
|
|
48
62
|
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
@@ -52,6 +66,10 @@ import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
|
|
|
52
66
|
import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
|
|
53
67
|
import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
|
|
54
68
|
import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
|
|
69
|
+
import { MigrateConfirm, MigratePicker } from '../components/MigratePicker.js';
|
|
70
|
+
import { collectMigratePickerRows, MIGRATE_SCAN_SPECS, parseImportSummary, resolveMigrateCommand } from '../dsh-adapter/migrate/picker.js';
|
|
71
|
+
import { collectActivitySamples, recentAgentsFrom } from '../dsh-adapter/migrate/recent-agents.js';
|
|
72
|
+
import { MIGRATION_ADAPTERS } from '../dsh-adapter/migrate/index.js';
|
|
55
73
|
import { SessionSupervisor } from './SessionSupervisor.js';
|
|
56
74
|
import { SessionTree } from './SessionTree.js';
|
|
57
75
|
import { Settings } from './Settings.js';
|
|
@@ -89,7 +107,6 @@ import instances from '../ink/instances.js';
|
|
|
89
107
|
import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
90
108
|
import { useExternalVersion } from '../hooks/useExternalVersion.js';
|
|
91
109
|
import { TrajectoryScene } from './TrajectoryScene.js';
|
|
92
|
-
import { resumeFailureText } from '../sessions/resumeFailure.js';
|
|
93
110
|
import { markHomeSeen } from '../homePrefs.js';
|
|
94
111
|
import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
|
|
95
112
|
import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
|
|
@@ -119,6 +136,10 @@ const STATUS_VIEW_UI = Object.freeze({
|
|
|
119
136
|
/** Shared empty snapshot for hosts whose channel has no event log. */
|
|
120
137
|
const NO_EVENTS = [];
|
|
121
138
|
const COMMAND_RESULT_CELLS = 200;
|
|
139
|
+
/** Ceiling for one `dsh-tui migrate` child run. Discovery parses every source
|
|
140
|
+
* file, but a healthy import of thousands of conversations finishes well
|
|
141
|
+
* inside this; without a cap a wedged child would hang the loop forever. */
|
|
142
|
+
const MIGRATE_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
122
143
|
function cleanCommandError(error) {
|
|
123
144
|
try {
|
|
124
145
|
if (error instanceof Error) {
|
|
@@ -207,7 +228,7 @@ let fallbackActivityStore;
|
|
|
207
228
|
function peekKey(image, title) {
|
|
208
229
|
return `${title ?? ''} ${image.id}`;
|
|
209
230
|
}
|
|
210
|
-
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, }) {
|
|
231
|
+
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, starPrompt, }) {
|
|
211
232
|
const writeRaw = React.useContext(TerminalWriteContext);
|
|
212
233
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
213
234
|
// DEFAULT lane on purpose (useExternalVersion): the channel version bumps
|
|
@@ -258,18 +279,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
258
279
|
channel.notify(t('ext-shortcut-failed', { combo }), { color: 'error', timeoutMs: 4000 });
|
|
259
280
|
});
|
|
260
281
|
}, [extensionShortcuts, channel]);
|
|
261
|
-
// When a questionnaire batch completes, fold a Q&A summary into the
|
|
262
|
-
// transcript (the tool card itself is hidden from the message list).
|
|
263
|
-
const questionOpenRef = React.useRef(questionSnapshot !== null);
|
|
264
|
-
React.useEffect(() => {
|
|
265
|
-
const wasOpen = questionOpenRef.current;
|
|
266
|
-
questionOpenRef.current = questionSnapshot !== null;
|
|
267
|
-
if (wasOpen && questionSnapshot === null) {
|
|
268
|
-
for (const summary of questionStore.takeSummaries()) {
|
|
269
|
-
channel.pushLocal(summary.title, summary.lines);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}, [channel, questionSnapshot, questionStore]);
|
|
273
282
|
const [expanded, setExpanded] = React.useState(false);
|
|
274
283
|
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
275
284
|
const [handle, setHandle] = React.useState(null);
|
|
@@ -285,6 +294,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
285
294
|
const [timeline, setTimeline] = React.useState({
|
|
286
295
|
turns: [],
|
|
287
296
|
activeId: null,
|
|
297
|
+
pinnedId: null,
|
|
288
298
|
upId: null,
|
|
289
299
|
downId: null,
|
|
290
300
|
});
|
|
@@ -306,6 +316,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
306
316
|
* list while the fresh one loads, exactly as the boolean era did.
|
|
307
317
|
*/
|
|
308
318
|
const [overlay, dispatchOverlay] = React.useReducer(chatOverlayReducer, NO_OVERLAY);
|
|
319
|
+
// `/migrate` picker rows (null = collecting in the background; the picker
|
|
320
|
+
// shows its empty state until the sub-second scan lands).
|
|
321
|
+
const [migrateRows, setMigrateRows] = React.useState(null);
|
|
322
|
+
// Multi-select state (PRD): checked agent ids + the confirmation layer's
|
|
323
|
+
// frozen snapshot of the checked rows.
|
|
324
|
+
const [migrateChecked, setMigrateChecked] = React.useState(new Set());
|
|
325
|
+
const [migratePending, setMigratePending] = React.useState([]);
|
|
326
|
+
// Smart-hint arming: while the migration hint notification is up, a bare
|
|
327
|
+
// Enter (empty prompt, no overlay) jumps straight into the picker with
|
|
328
|
+
// that source pre-checked (PRD #4). Any other key disarms.
|
|
329
|
+
const [migrateHintAgent, setMigrateHintAgent] = React.useState(null);
|
|
309
330
|
// Chat and PromptInput both receive one parsed stdin batch. Keep the
|
|
310
331
|
// permission focus synchronous so arrow+Enter in the same batch uses the
|
|
311
332
|
// post-arrow row rather than the previous render's index.
|
|
@@ -418,6 +439,152 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
418
439
|
* browser, a screen rather than a panel: it owns its own focus, staged
|
|
419
440
|
* drafts and keyboard; Chat only opens it. */
|
|
420
441
|
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
|
442
|
+
/** 99h / 999 次的"求 star"开屏弹窗(`usageStats` 记账,一档只弹一次):
|
|
443
|
+
* 只在启动时判定一次——回合进行中、或已有整屏界面在开(如开机首页),
|
|
444
|
+
* 这一轮不弹也**不记账**,留给下一次启动。`starPrompt` 是测试缝:传
|
|
445
|
+
* `null` 显式关闭,传 actions 覆写两个按钮(不跑真 gh、不开真浏览器)。 */
|
|
446
|
+
/** 本次会话是否已经 star 成功(开屏彩蛋标题切「捡到小星星啦」)。 */
|
|
447
|
+
const [starred, setStarred] = React.useState(false);
|
|
448
|
+
const [starModal, setStarModal] = React.useState(null);
|
|
449
|
+
// 单发闩:只在第一个"安静的开屏视口"上武装定时器。700ms 窗口内整屏
|
|
450
|
+
// 界面打开 → cleanup 掐掉定时器且**不再重臂**(记账只发生在回调里,
|
|
451
|
+
// 所以这一档完好留给下一次启动);整屏界面随后关闭也不追到聊天视图
|
|
452
|
+
// 上补弹——开屏求星不追人。
|
|
453
|
+
const starModalArmedRef = React.useRef(false);
|
|
454
|
+
/** 预览缝(`DSH_TUI_STAR_MODAL=1`):启动即弹一次 99h 档的弹窗,**既不
|
|
455
|
+
* 读账本也不记账**——给作者看效果、给回归夹具用;生产不设这个变量。 */
|
|
456
|
+
const starModalPreview = process.env.DSH_TUI_STAR_MODAL === '1';
|
|
457
|
+
React.useEffect(() => {
|
|
458
|
+
if (starModalArmedRef.current)
|
|
459
|
+
return;
|
|
460
|
+
if (starPrompt === null)
|
|
461
|
+
return;
|
|
462
|
+
if (supervisorOpen || treeOpen || settingsOpen || channel.working)
|
|
463
|
+
return;
|
|
464
|
+
starModalArmedRef.current = true;
|
|
465
|
+
// 让开屏先画半秒:弹窗压在介绍动画之上,而不是同抢第一帧。
|
|
466
|
+
const timer = setTimeout(() => {
|
|
467
|
+
// 到点时回合已经开始的仍不弹(channel 是活对象,读到的是当前值)。
|
|
468
|
+
if (channel.working)
|
|
469
|
+
return;
|
|
470
|
+
if (starModalPreview) {
|
|
471
|
+
const preview = STAR_MILESTONES.findIndex(milestone => milestone.hours === 99);
|
|
472
|
+
if (preview >= 0)
|
|
473
|
+
setStarModal({ index: preview, phase: 'ask' });
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const index = dueStarModal(starPrompt?.dir);
|
|
477
|
+
if (index === null)
|
|
478
|
+
return;
|
|
479
|
+
markStarAsked(index, starPrompt?.dir);
|
|
480
|
+
setStarModal({ index, phase: 'ask' });
|
|
481
|
+
}, 700);
|
|
482
|
+
return () => { clearTimeout(timer); };
|
|
483
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- 只在整屏界面开合时重判;闩保证只武装一次
|
|
484
|
+
}, [supervisorOpen, treeOpen, settingsOpen]);
|
|
485
|
+
/** `/star` 命令、开屏标语的点击/`Alt+S` 共用的一键动作:异步跑 gh,界面
|
|
486
|
+
* 全程不阻塞,结果回来按四类各报一句(成功 / 没装 gh / 没登录 / 失败)。
|
|
487
|
+
* `starPrompt.onStar` 存在时走同一条测试缝(夹具因此不会真的去 star)。 */
|
|
488
|
+
/** 打开仓库页(走 `starPrompt.onOpen` 测试缝——夹具里不会真的拉起浏览器)。 */
|
|
489
|
+
const openStarPage = React.useCallback(() => {
|
|
490
|
+
const seam = starPrompt?.onOpen;
|
|
491
|
+
if (seam !== undefined) {
|
|
492
|
+
seam();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
void import('../starAction.js').then(({ STAR_REPO }) => {
|
|
496
|
+
openExternal(`https://github.com/${STAR_REPO}`);
|
|
497
|
+
});
|
|
498
|
+
}, [starPrompt]);
|
|
499
|
+
const runStarAction = React.useCallback(() => {
|
|
500
|
+
const seam = starPrompt?.onStar;
|
|
501
|
+
void (seam !== undefined
|
|
502
|
+
? Promise.resolve(seam())
|
|
503
|
+
: import('../starAction.js').then(async ({ starRepo, STAR_REPO }) => {
|
|
504
|
+
const url = `https://github.com/${STAR_REPO}`;
|
|
505
|
+
const outcome = await starRepo();
|
|
506
|
+
if (outcome.kind === 'starred')
|
|
507
|
+
return { kind: 'starred' };
|
|
508
|
+
if (outcome.kind === 'no-gh')
|
|
509
|
+
return { kind: 'no-gh', url };
|
|
510
|
+
if (outcome.kind === 'not-authed')
|
|
511
|
+
return { kind: 'not-authed', url };
|
|
512
|
+
return { kind: 'failed', detail: outcome.detail, url };
|
|
513
|
+
})).then(attempt => {
|
|
514
|
+
if (attempt.kind === 'starred') {
|
|
515
|
+
setStarred(true);
|
|
516
|
+
// 成功就演一段庆祝(女仆娘接住星星)——`/star`、`Alt+S`、标语点击
|
|
517
|
+
// 都是这一条路。整屏界面开着或回合进行中时弹窗放不下,退回一句
|
|
518
|
+
// 通知,用户至少知道 star 点上了。
|
|
519
|
+
const blocked = channel.working || supervisorOpen || treeOpen || settingsOpen;
|
|
520
|
+
const index = STAR_MILESTONES.findIndex(milestone => milestone.hours === 99);
|
|
521
|
+
if (!blocked && index >= 0) {
|
|
522
|
+
setStarModal({ index, phase: 'done' });
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
channel.notify(t('star-ok'), { color: 'success' });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (attempt.kind === 'no-gh') {
|
|
529
|
+
// 本机没法一键(没装 gh / 没登录)→ **自动**打开仓库页让用户自己点,
|
|
530
|
+
// 通知里说明原因(浏览器没拉起来时 URL 也还在文案里)。
|
|
531
|
+
openStarPage();
|
|
532
|
+
channel.notify(t('star-no-gh', { url: attempt.url }), { color: 'warning' });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (attempt.kind === 'not-authed') {
|
|
536
|
+
openStarPage();
|
|
537
|
+
channel.notify(t('star-not-authed', { url: attempt.url }), { color: 'warning' });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
channel.notify(t('star-failed', { detail: attempt.detail, url: attempt.url }), { color: 'error' });
|
|
541
|
+
});
|
|
542
|
+
}, [channel, starPrompt, openStarPage, supervisorOpen, treeOpen, settingsOpen]);
|
|
543
|
+
const starModalActions = React.useMemo(() => ({
|
|
544
|
+
// 弹窗自己演结果(成功→庆祝、失败→留在卡里说明原因),所以这里把
|
|
545
|
+
// 结局**回传**给它;`/star` 命令那条路仍走 runStarAction 的 notify。
|
|
546
|
+
onStar: () => {
|
|
547
|
+
const seam = starPrompt?.onStar;
|
|
548
|
+
// 成功把开屏彩蛋切成「捡到星星」版;gh 缺失/未登录**自动**打开仓库页
|
|
549
|
+
// (与一键路径同一套兜底)。注意**不要**给返回值再包一层 `.then()`——
|
|
550
|
+
// 多一个微任务会让弹窗"庆祝那一帧"被紧随其后的 Enter 关窗批掉
|
|
551
|
+
//(夹具 C8/C9 实测)。这里只挂副作用、原样返回。
|
|
552
|
+
const afterAttempt = (attempt) => {
|
|
553
|
+
if (attempt.kind === 'starred')
|
|
554
|
+
setStarred(true);
|
|
555
|
+
else if (attempt.kind === 'no-gh' || attempt.kind === 'not-authed')
|
|
556
|
+
openStarPage();
|
|
557
|
+
return attempt;
|
|
558
|
+
};
|
|
559
|
+
if (seam !== undefined) {
|
|
560
|
+
const result = seam();
|
|
561
|
+
if (result instanceof Promise) {
|
|
562
|
+
void result.then(afterAttempt);
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
return afterAttempt(result);
|
|
566
|
+
}
|
|
567
|
+
const run = import('../starAction.js').then(async ({ starRepo, STAR_REPO }) => {
|
|
568
|
+
const url = `https://github.com/${STAR_REPO}`;
|
|
569
|
+
const outcome = await starRepo();
|
|
570
|
+
if (outcome.kind === 'starred')
|
|
571
|
+
return { kind: 'starred' };
|
|
572
|
+
if (outcome.kind === 'no-gh')
|
|
573
|
+
return { kind: 'no-gh', url };
|
|
574
|
+
if (outcome.kind === 'not-authed')
|
|
575
|
+
return { kind: 'not-authed', url };
|
|
576
|
+
return { kind: 'failed', detail: outcome.detail, url };
|
|
577
|
+
});
|
|
578
|
+
void run.then(afterAttempt);
|
|
579
|
+
return run;
|
|
580
|
+
},
|
|
581
|
+
onOpen: () => {
|
|
582
|
+
setStarModal(null);
|
|
583
|
+
openStarPage();
|
|
584
|
+
},
|
|
585
|
+
}), [starPrompt, openStarPage]);
|
|
586
|
+
/** 弹窗关闭回调:稳定引用(见渲染处的注释)。 */
|
|
587
|
+
const closeStarModal = React.useCallback(() => { setStarModal(null); }, []);
|
|
421
588
|
const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
|
|
422
589
|
const workspaceFlowRequestRef = React.useRef(0);
|
|
423
590
|
const workspaceFlowAbortRef = React.useRef(null);
|
|
@@ -554,14 +721,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
554
721
|
* renders with stale folds/expansion/selection — the "entered a freshly
|
|
555
722
|
* dispatched session and it renders wrong" bug. Reset the same set `/new`
|
|
556
723
|
* resets, plus the search overlay and the side question, and repaint the
|
|
557
|
-
* transcript
|
|
724
|
+
* transcript pinned to the bottom so rewinds and model switches can continue.
|
|
558
725
|
*/
|
|
559
726
|
const repaintTranscript = () => {
|
|
560
727
|
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
561
728
|
// Wait one task so React commits the new session's tree before the
|
|
562
729
|
// scrollback clear repaints (same pattern as `/new`).
|
|
563
730
|
setTimeout(() => {
|
|
564
|
-
handle?.
|
|
731
|
+
handle?.scrollToBottom();
|
|
565
732
|
ink?.clearScrollbackAndRedraw();
|
|
566
733
|
}, 0);
|
|
567
734
|
};
|
|
@@ -741,6 +908,39 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
741
908
|
overlay: { kind: 'image-preview', image, gallery, index, ...(title === undefined ? {} : { title }) },
|
|
742
909
|
});
|
|
743
910
|
}, [channel]);
|
|
911
|
+
/** A clicked formula opens the same card as a transcript image. The raster
|
|
912
|
+
* is re-typeset at double the cell size, so 100% is a sharper formula
|
|
913
|
+
* rather than an upscaled one; when that re-render fails (too wide, TeX
|
|
914
|
+
* rejected) the pixels already on screen stand in. */
|
|
915
|
+
const openMathPreview = React.useCallback((preview) => {
|
|
916
|
+
const scaled = {
|
|
917
|
+
...preview.request,
|
|
918
|
+
cellSize: {
|
|
919
|
+
width: preview.request.cellSize.width * MATH_PREVIEW_SCALE,
|
|
920
|
+
height: preview.request.cellSize.height * MATH_PREVIEW_SCALE,
|
|
921
|
+
},
|
|
922
|
+
maxColumns: Math.min(preview.request.maxColumns * MATH_PREVIEW_SCALE, MATH_PREVIEW_MAX_COLUMNS),
|
|
923
|
+
maxRows: Math.min(preview.request.maxRows * MATH_PREVIEW_SCALE, MATH_PREVIEW_MAX_ROWS),
|
|
924
|
+
};
|
|
925
|
+
const image = {
|
|
926
|
+
id: `math:${preview.tex}`,
|
|
927
|
+
width: preview.source.width,
|
|
928
|
+
height: preview.source.height,
|
|
929
|
+
name: preview.tex,
|
|
930
|
+
mediaType: 'image/png',
|
|
931
|
+
read: async () => {
|
|
932
|
+
const rendered = await renderMathRaster(scaled);
|
|
933
|
+
return rasterToPng(rendered.ok ? rendered.raster.source : preview.source);
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
openImagePreview(image, preview.tex);
|
|
937
|
+
}, [openImagePreview]);
|
|
938
|
+
// The math components live deep inside the transcript, so the opener is
|
|
939
|
+
// published rather than threaded through every message row's props.
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
setMathPreviewOpener(openMathPreview);
|
|
942
|
+
return () => setMathPreviewOpener(undefined);
|
|
943
|
+
}, [openMathPreview]);
|
|
744
944
|
// Agent-binding generation is monotonic across every agent replacement
|
|
745
945
|
// and bumps before the replacement emit, closing the ABA hole where a
|
|
746
946
|
// resumed session reuses the same id. Partial test/embed channels fall
|
|
@@ -1107,6 +1307,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1107
1307
|
// fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
|
|
1108
1308
|
// subscription in inline mode, where selection belongs to the terminal.
|
|
1109
1309
|
// The copy clears the highlight and posts a transient notification.
|
|
1310
|
+
// Smart migration hint (product ask): ~12s after mount, one background
|
|
1311
|
+
// pass over the foreign-agent stores; when a source was active inside the
|
|
1312
|
+
// 20-minute window, surface the user's own wording once per session. The
|
|
1313
|
+
// file-level mtime scan is the counter's walk shape (sub-second) and runs
|
|
1314
|
+
// off the render path; failures read as "no data" and stay silent.
|
|
1315
|
+
const migrateHintShownRef = React.useRef(false);
|
|
1316
|
+
React.useEffect(() => {
|
|
1317
|
+
if (migrateHintShownRef.current)
|
|
1318
|
+
return;
|
|
1319
|
+
const timer = setTimeout(() => {
|
|
1320
|
+
migrateHintShownRef.current = true;
|
|
1321
|
+
void (async () => {
|
|
1322
|
+
const newest = await new Promise(resolve => {
|
|
1323
|
+
setImmediate(() => resolve(collectActivitySamples(MIGRATION_ADAPTERS, adapter => MIGRATE_SCAN_SPECS[adapter.id])));
|
|
1324
|
+
});
|
|
1325
|
+
const top = recentAgentsFrom(newest, Date.now())[0];
|
|
1326
|
+
if (top !== undefined) {
|
|
1327
|
+
channel.notify(t('migrate-hint-notify', { agent: top.label }), { timeoutMs: 10000 });
|
|
1328
|
+
// PRD #4: while the hint is up, a bare Enter (empty prompt, no
|
|
1329
|
+
// overlay) jumps into the picker with this source pre-checked;
|
|
1330
|
+
// the global key layer below consumes it, anything else disarms.
|
|
1331
|
+
setMigrateHintAgent(top.agentId);
|
|
1332
|
+
setTimeout(() => setMigrateHintAgent(current => current === top.agentId ? null : current), 10_000);
|
|
1333
|
+
}
|
|
1334
|
+
})();
|
|
1335
|
+
}, 12_000);
|
|
1336
|
+
return () => clearTimeout(timer);
|
|
1337
|
+
}, [channel]);
|
|
1110
1338
|
useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }),
|
|
1111
1339
|
// Stale-selection refusal: the highlighted rows were replaced in place
|
|
1112
1340
|
// (streaming overwrite), so nothing was copied — say why instead of
|
|
@@ -1307,10 +1535,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1307
1535
|
const ok = writeLangPref(lang);
|
|
1308
1536
|
setLang(lang);
|
|
1309
1537
|
const settingsHost = channel.settingsHost();
|
|
1310
|
-
|
|
1538
|
+
// This mount's own namespace (custom Loader ids exist): looking up the
|
|
1539
|
+
// literal 'dsh-tui' skipped the mirror entirely on such mounts.
|
|
1540
|
+
const tuiView = settingsHost?.listNamespaces().find(entry => entry.ns === channel.settingsNamespace);
|
|
1311
1541
|
if (settingsHost !== undefined && tuiView !== undefined) {
|
|
1312
1542
|
void settingsHost
|
|
1313
|
-
.write(
|
|
1543
|
+
.write(channel.settingsNamespace, [{ op: 'set', path: ['lang'], value: lang }], tuiView.revision)
|
|
1314
1544
|
.catch(() => { });
|
|
1315
1545
|
}
|
|
1316
1546
|
channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
|
|
@@ -1325,6 +1555,82 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1325
1555
|
case 'activity': return t('reload-kind-activity');
|
|
1326
1556
|
}
|
|
1327
1557
|
};
|
|
1558
|
+
/** Collect picker rows off the current turn: the scan is synchronous FS
|
|
1559
|
+
* work (name-only walk + per-file stat), so it is deferred by one macrotask
|
|
1560
|
+
* to let the overlay paint its loading state first. */
|
|
1561
|
+
const collectMigrateRows = () => new Promise(resolve => {
|
|
1562
|
+
setImmediate(() => resolve(collectMigratePickerRows(Date.now())));
|
|
1563
|
+
});
|
|
1564
|
+
/** Run `dsh-tui migrate <args>` in a child process through the package
|
|
1565
|
+
* bin; resolves with the exit code and the combined output. Uses the
|
|
1566
|
+
* shared no-throw runner (bounded capture, timeout, windowsHide): a wedged
|
|
1567
|
+
* child would otherwise hang the sequential per-source loop forever. */
|
|
1568
|
+
const runMigrateChild = async (parts) => {
|
|
1569
|
+
const { dirname } = await import('node:path');
|
|
1570
|
+
const { fileURLToPath } = await import('node:url');
|
|
1571
|
+
const { resolveOwnBin } = await import('../dsh-adapter/migrate/bin-path.js');
|
|
1572
|
+
// This file sits at a different depth per layout (src/screens vs
|
|
1573
|
+
// lib/types/screens), so the bin resolves by upward probe — see
|
|
1574
|
+
// bin-path.ts; a fixed dirname count fails on real installs.
|
|
1575
|
+
const bin = resolveOwnBin(dirname(fileURLToPath(import.meta.url)));
|
|
1576
|
+
if (bin === undefined) {
|
|
1577
|
+
channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
|
|
1578
|
+
return { code: -1, out: '' };
|
|
1579
|
+
}
|
|
1580
|
+
const result = await execFileNoThrow(process.execPath, [bin, 'migrate', ...parts], {
|
|
1581
|
+
timeout: MIGRATE_CHILD_TIMEOUT_MS,
|
|
1582
|
+
});
|
|
1583
|
+
// A killed child reports `code: null` and whatever it managed to print; put
|
|
1584
|
+
// the reason on the record so the transcript does not read as a silent
|
|
1585
|
+
// failure. Nothing at all (no code, no output) means it never really ran.
|
|
1586
|
+
if (result.code === null) {
|
|
1587
|
+
return {
|
|
1588
|
+
code: null,
|
|
1589
|
+
out: `${result.stdout}${result.stderr}${t('migrate-child-timeout', { minutes: MIGRATE_CHILD_TIMEOUT_MS / 60_000 })}\n`,
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
if (result.code === 1 && result.stdout === '' && result.stderr === '') {
|
|
1593
|
+
channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
|
|
1594
|
+
}
|
|
1595
|
+
// stdout carries the per-source report, stderr the usage/error lines;
|
|
1596
|
+
// both belong in the /migrate transcript row.
|
|
1597
|
+
return { code: result.code, out: `${result.stdout}${result.stderr}` };
|
|
1598
|
+
};
|
|
1599
|
+
/** Orchestrate the confirmation layer's confirmed rows (PRD #3): one child
|
|
1600
|
+
* per source, sequential; per-source progress notifications (throttled by
|
|
1601
|
+
* the source boundary — no intra-source spam), real per-source counters
|
|
1602
|
+
* parsed from each child's report, and a final summary that NEVER claims
|
|
1603
|
+
* success for a source that did not run (the P2 fix). */
|
|
1604
|
+
const spawnMigrateSources = (rows, dryRun) => {
|
|
1605
|
+
const allOut = [];
|
|
1606
|
+
let failures = 0;
|
|
1607
|
+
void (async () => {
|
|
1608
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1609
|
+
const row = rows[i];
|
|
1610
|
+
channel.notify(t(dryRun ? 'migrate-previewing-source' : 'migrate-importing-source', { label: row.label, i: i + 1, n: rows.length }), { timeoutMs: 4000 });
|
|
1611
|
+
const { code, out } = await runMigrateChild(dryRun ? [row.agentId, '--dry-run'] : [row.agentId]);
|
|
1612
|
+
allOut.push(...out.split('\n').map(line => cleanRenderText(line, 400)).filter(Boolean));
|
|
1613
|
+
if (code !== 0)
|
|
1614
|
+
failures += 1;
|
|
1615
|
+
// Per-source real counters straight from the child's report line.
|
|
1616
|
+
const summary = parseImportSummary(out).find(entry => entry.agentId === row.agentId);
|
|
1617
|
+
if (!dryRun && summary !== undefined) {
|
|
1618
|
+
channel.notify(t('migrate-source-done', { label: row.label, imported: summary.imported, existing: summary.existing }), { timeoutMs: 6000 });
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
// The transcript row is this run's record. When a source failed AND no
|
|
1622
|
+
// child output was captured at all (killed by the timeout, or never
|
|
1623
|
+
// spawned), the success wording would contradict the notification right
|
|
1624
|
+
// above it — report the failure here too.
|
|
1625
|
+
const fallbackLine = failures > 0
|
|
1626
|
+
? t('migrate-failed', { n: failures })
|
|
1627
|
+
: t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length });
|
|
1628
|
+
channel.pushLocal('/migrate', allOut.length > 0 ? allOut : [fallbackLine]);
|
|
1629
|
+
channel.notify(failures === 0
|
|
1630
|
+
? t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length })
|
|
1631
|
+
: t('migrate-failed', { n: failures }), failures === 0 ? { timeoutMs: 6000 } : { color: 'error', timeoutMs: 10000 });
|
|
1632
|
+
})();
|
|
1633
|
+
};
|
|
1328
1634
|
const runCommand = (name, rawInput = '', images = []) => {
|
|
1329
1635
|
switch (name) {
|
|
1330
1636
|
case 'activity': {
|
|
@@ -1923,7 +2229,28 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1923
2229
|
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
1924
2230
|
lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
1925
2231
|
}
|
|
1926
|
-
|
|
2232
|
+
// 金额与拆解:主会话按模型分桶 + 子代理按各自 (provider, model) 分桶;
|
|
2233
|
+
// 全部未计价时只报 token 并标注未计价,不显示 ¥0.00 金额行(DESIGN D4/D6)。
|
|
2234
|
+
const estimate = estimateSessionCostSnapshotCny({
|
|
2235
|
+
provider: channel.provider,
|
|
2236
|
+
main: channel.mainCost,
|
|
2237
|
+
subagents: channel.subagentCost,
|
|
2238
|
+
fallbackTokens: channel.tokens,
|
|
2239
|
+
fallbackModel: channel.model,
|
|
2240
|
+
});
|
|
2241
|
+
// 金额行与末尾口径共用同一判定:有已计价金额才显示金额行与"估算非账单"
|
|
2242
|
+
// 文案;无金额(无用量 / 全部未计价)只解释 token(#1089)。
|
|
2243
|
+
const hasAmount = estimate !== undefined && estimate.total > 0;
|
|
2244
|
+
if (estimate !== undefined) {
|
|
2245
|
+
if (hasAmount) {
|
|
2246
|
+
lines.push(t('cost-session-estimate', { cost: estimate.total.toFixed(2) }));
|
|
2247
|
+
lines.push(`${t('cost-split-main', { cost: estimate.main.toFixed(2) })} · ${t('cost-split-subagent', { cost: estimate.subagent.toFixed(2) })}`);
|
|
2248
|
+
}
|
|
2249
|
+
if (estimate.unpricedTokens > 0) {
|
|
2250
|
+
lines.push(t('cost-unpriced', { tokens: formatTokens(estimate.unpricedTokens) }));
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
lines.push(t(hasAmount ? 'cost-note' : 'cost-note-no-amount'));
|
|
1927
2254
|
setHelpOpen(false);
|
|
1928
2255
|
channel.pushLocal('/cost', lines);
|
|
1929
2256
|
return true;
|
|
@@ -1944,6 +2271,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1944
2271
|
setSettingsOpen(true);
|
|
1945
2272
|
return true;
|
|
1946
2273
|
}
|
|
2274
|
+
case 'star': {
|
|
2275
|
+
// 一键 star:**只有用户主动敲 /star 才会跑**(绝不自动)。动作用
|
|
2276
|
+
// runStarAction(与开屏弹窗共用),异步执行、结果用 notify 报。
|
|
2277
|
+
setHelpOpen(false);
|
|
2278
|
+
runStarAction();
|
|
2279
|
+
return true;
|
|
2280
|
+
}
|
|
1947
2281
|
case 'config': {
|
|
1948
2282
|
const userHome = process.env.USERPROFILE ?? '';
|
|
1949
2283
|
const lines = [
|
|
@@ -1961,6 +2295,60 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1961
2295
|
setHelpOpen(false);
|
|
1962
2296
|
channel.pushLocal('/doctor', channel.doctorInfo());
|
|
1963
2297
|
return true;
|
|
2298
|
+
case 'migrate': {
|
|
2299
|
+
// Double entry points with the CLI. BARE `/migrate` opens the
|
|
2300
|
+
// multi-select picker; `/migrate <agent>` opens the CONFIRMATION
|
|
2301
|
+
// layer for that single source (PRD #2 — a bulk import is never one
|
|
2302
|
+
// keystroke away). Validity is decided against the adapter REGISTRY,
|
|
2303
|
+
// never the picker's row cache: on a fresh mount that cache is empty,
|
|
2304
|
+
// and deriving the answer from it made every `/migrate <agent>` report
|
|
2305
|
+
// an unknown source until the picker had been opened once.
|
|
2306
|
+
setHelpOpen(false);
|
|
2307
|
+
const command = resolveMigrateCommand(rawInput, MIGRATION_ADAPTERS.map(adapter => adapter.id));
|
|
2308
|
+
if (command.kind === 'unknown') {
|
|
2309
|
+
channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
if (command.kind === 'usage') {
|
|
2313
|
+
channel.notify(t('migrate-usage'), { color: 'error', timeoutMs: 8000 });
|
|
2314
|
+
return true;
|
|
2315
|
+
}
|
|
2316
|
+
if (command.kind === 'dry-run-needs-source') {
|
|
2317
|
+
channel.notify(t('migrate-dry-run-needs-source'), { color: 'error', timeoutMs: 8000 });
|
|
2318
|
+
return true;
|
|
2319
|
+
}
|
|
2320
|
+
if (command.kind === 'import') {
|
|
2321
|
+
void (async () => {
|
|
2322
|
+
// Rows carry the scannable count the confirmation line shows; a
|
|
2323
|
+
// warm cache from an earlier picker visit is reused as is.
|
|
2324
|
+
const rows = migrateRows ?? await collectMigrateRows();
|
|
2325
|
+
const row = rows.find(candidate => candidate.agentId === command.agentId);
|
|
2326
|
+
if (row === undefined) {
|
|
2327
|
+
channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
if (command.dryRun) {
|
|
2331
|
+
spawnMigrateSources([row], true);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
setMigratePending([row]);
|
|
2335
|
+
// Pin the checked set to the source this confirmation is about:
|
|
2336
|
+
// Esc returns to the picker, and a stale set from an earlier visit
|
|
2337
|
+
// would there contradict what the confirmation just showed.
|
|
2338
|
+
setMigrateChecked(new Set([row.agentId]));
|
|
2339
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
|
|
2340
|
+
})();
|
|
2341
|
+
return true;
|
|
2342
|
+
}
|
|
2343
|
+
// Bare `/migrate` starts a NEW flow: drop the previous run's checks
|
|
2344
|
+
// (only Esc-out-of-confirm keeps them) and let the picker show its
|
|
2345
|
+
// scanning state until the rows land.
|
|
2346
|
+
setMigrateRows(null);
|
|
2347
|
+
setMigrateChecked(new Set());
|
|
2348
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
2349
|
+
void collectMigrateRows().then(setMigrateRows);
|
|
2350
|
+
return true;
|
|
2351
|
+
}
|
|
1964
2352
|
case 'plugins':
|
|
1965
2353
|
// Plugin diagnostics (C-070): trust banner first, then descriptor /
|
|
1966
2354
|
// grant matrix / ledger tail — or validate+negotiate for
|
|
@@ -2172,7 +2560,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2172
2560
|
setHelpOpen(false);
|
|
2173
2561
|
const tuiNamespace = channel.settingsHost()
|
|
2174
2562
|
?.listNamespaces()
|
|
2175
|
-
.find(entry => entry.ns ===
|
|
2563
|
+
.find(entry => entry.ns === channel.settingsNamespace);
|
|
2176
2564
|
const plan = planReload({
|
|
2177
2565
|
envTheme: envThemeOverride(),
|
|
2178
2566
|
envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
|
|
@@ -2267,7 +2655,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2267
2655
|
setHelpOpen(false);
|
|
2268
2656
|
channel.pushLocal('/terminal-setup', [
|
|
2269
2657
|
t('terminal-setup-hint'),
|
|
2270
|
-
t('terminal-paste-hint', {
|
|
2658
|
+
t('terminal-paste-hint', { keys: effectiveComboDisplay('paste') }),
|
|
2271
2659
|
]);
|
|
2272
2660
|
return true;
|
|
2273
2661
|
case 'recap': {
|
|
@@ -2627,6 +3015,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2627
3015
|
/** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
|
|
2628
3016
|
const lastModalEnterAtRef = React.useRef(0);
|
|
2629
3017
|
useInput((input, key, event) => {
|
|
3018
|
+
// 开屏"求 star"弹窗开着时键盘全归它(↑/↓/Enter/Esc 由它自己的
|
|
3019
|
+
// useInput 处理),滚轮也不许滚动它身后的转录——和下面的整屏界面
|
|
3020
|
+
// 同一套让位规则。
|
|
3021
|
+
if (starModal !== null)
|
|
3022
|
+
return;
|
|
2630
3023
|
// Prompt-slot panels own the keyboard while visible. Their own useInput
|
|
2631
3024
|
// handles the relevant keys; Chat registered first, so yielding here
|
|
2632
3025
|
// still lets the panel receive them. PromptInput now stays mounted but
|
|
@@ -3058,6 +3451,81 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3058
3451
|
}
|
|
3059
3452
|
return;
|
|
3060
3453
|
}
|
|
3454
|
+
// Armed migration hint (PRD #4): bare Enter while the hint notification
|
|
3455
|
+
// is up — no overlay and an EMPTY prompt — jumps into the picker with the
|
|
3456
|
+
// hinted source pre-checked; every other key disarms silently. The empty
|
|
3457
|
+
// check is explicit because the composer owns that state: an Enter that
|
|
3458
|
+
// submitted a written message must not also open the picker, and this
|
|
3459
|
+
// listener runs before PromptInput's, so the event is consumed here too.
|
|
3460
|
+
if (migrateHintAgent !== null && overlay.kind === 'none') {
|
|
3461
|
+
if (plainReturn && !(promptControllerRef.current?.hasText() ?? false)) {
|
|
3462
|
+
const agent = migrateHintAgent;
|
|
3463
|
+
event.stopImmediatePropagation();
|
|
3464
|
+
setMigrateHintAgent(null);
|
|
3465
|
+
setMigrateRows(null);
|
|
3466
|
+
setMigrateChecked(new Set([agent]));
|
|
3467
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
3468
|
+
void collectMigrateRows().then(setMigrateRows);
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
setMigrateHintAgent(null);
|
|
3472
|
+
}
|
|
3473
|
+
if (overlay.kind === 'migrate') {
|
|
3474
|
+
const rows = migrateRows ?? [];
|
|
3475
|
+
if (key.upArrow || key.downArrow) {
|
|
3476
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rows.length });
|
|
3477
|
+
}
|
|
3478
|
+
else if (key.escape) {
|
|
3479
|
+
dispatchOverlay({ type: 'close' });
|
|
3480
|
+
}
|
|
3481
|
+
else if (rows.length > 0) {
|
|
3482
|
+
const row = rows[overlay.index];
|
|
3483
|
+
if (input === ' ' && row !== undefined) {
|
|
3484
|
+
setMigrateChecked(current => {
|
|
3485
|
+
const next = new Set(current);
|
|
3486
|
+
if (next.has(row.agentId))
|
|
3487
|
+
next.delete(row.agentId);
|
|
3488
|
+
else
|
|
3489
|
+
next.add(row.agentId);
|
|
3490
|
+
return next;
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
else if (input === 'a' && !key.ctrl && !key.meta) {
|
|
3494
|
+
// All/none toggle: a checked-everything state collapses to none.
|
|
3495
|
+
setMigrateChecked(current => current.size >= rows.length ? new Set() : new Set(rows.map(candidate => candidate.agentId)));
|
|
3496
|
+
}
|
|
3497
|
+
else if (plainReturn) {
|
|
3498
|
+
// Checked set wins; the focused row acts as a single selection
|
|
3499
|
+
// when nothing is checked (PRD #1).
|
|
3500
|
+
const chosen = migrateChecked.size > 0
|
|
3501
|
+
? rows.filter(candidate => migrateChecked.has(candidate.agentId))
|
|
3502
|
+
: row !== undefined ? [row] : [];
|
|
3503
|
+
if (chosen.length > 0) {
|
|
3504
|
+
setMigratePending(chosen);
|
|
3505
|
+
dispatchOverlay({ type: 'close' });
|
|
3506
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
if (overlay.kind === 'migrate-confirm') {
|
|
3513
|
+
if (key.escape) {
|
|
3514
|
+
// Back to the picker with the checked set preserved (PRD #2's
|
|
3515
|
+
// "cancel" reads cheapest as "let me change the selection").
|
|
3516
|
+
dispatchOverlay({ type: 'close' });
|
|
3517
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
3518
|
+
}
|
|
3519
|
+
else if (plainReturn) {
|
|
3520
|
+
dispatchOverlay({ type: 'close' });
|
|
3521
|
+
spawnMigrateSources(migratePending, false);
|
|
3522
|
+
}
|
|
3523
|
+
else if (input === 'd' && !key.ctrl && !key.meta) {
|
|
3524
|
+
dispatchOverlay({ type: 'close' });
|
|
3525
|
+
spawnMigrateSources(migratePending, true);
|
|
3526
|
+
}
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3061
3529
|
if (overlay.kind === 'activity') {
|
|
3062
3530
|
if (key.upArrow || key.downArrow) {
|
|
3063
3531
|
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: PRESET_NAMES.length });
|
|
@@ -3410,6 +3878,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3410
3878
|
}
|
|
3411
3879
|
event.stopImmediatePropagation();
|
|
3412
3880
|
}
|
|
3881
|
+
else if (key.escape
|
|
3882
|
+
&& !helpOpen
|
|
3883
|
+
&& !channel.working
|
|
3884
|
+
&& channel.compaction?.cancellable === true
|
|
3885
|
+
&& !promptControllerRef.current?.vimActive()) {
|
|
3886
|
+
// Idle Esc otherwise falls through to the prompt's double-tap-clear;
|
|
3887
|
+
// while a manual compaction runs, stopping it is what the status row
|
|
3888
|
+
// promises (and the host closes the bracket cleanly on abort).
|
|
3889
|
+
channel.cancelCompact();
|
|
3890
|
+
event.stopImmediatePropagation();
|
|
3891
|
+
}
|
|
3413
3892
|
else if (actionMatches('transcript', input, key) && !helpOpen) {
|
|
3414
3893
|
// Leaving transcript mode (default Ctrl+O) — search was already
|
|
3415
3894
|
// handled above. Help is modal: toggling this state behind the
|
|
@@ -3446,7 +3925,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3446
3925
|
// CLEARS a non-empty prompt (single press) and only arms the
|
|
3447
3926
|
// double-press exit when the input is empty; ctrl+d keeps the
|
|
3448
3927
|
// time-based double-press exit regardless.
|
|
3449
|
-
if (channel.working) {
|
|
3928
|
+
if (input === 'c' && !channel.working && channel.compaction?.cancellable === true) {
|
|
3929
|
+
// Ctrl+C during a manual compaction stops the compaction instead of
|
|
3930
|
+
// arming the double-press exit: exiting the process mid-bracket is how
|
|
3931
|
+
// a session log ends up with an unmatched `compaction/start`. Ctrl+D
|
|
3932
|
+
// keeps its exit meaning, and the next Ctrl+C behaves normally again.
|
|
3933
|
+
channel.cancelCompact();
|
|
3934
|
+
exitPendingRef.current = false;
|
|
3935
|
+
if (exitTimerRef.current)
|
|
3936
|
+
clearTimeout(exitTimerRef.current);
|
|
3937
|
+
}
|
|
3938
|
+
else if (channel.working) {
|
|
3450
3939
|
// First press while working only interrupts. If that abort is still
|
|
3451
3940
|
// converging (cancelPending) the next press is the user insisting on
|
|
3452
3941
|
// leaving: go straight to the exit funnel. Without this, a stuck turn
|
|
@@ -3505,6 +3994,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3505
3994
|
// Consume: same readline-shadowing rule as dashboard/showAll above.
|
|
3506
3995
|
event.stopImmediatePropagation();
|
|
3507
3996
|
}
|
|
3997
|
+
else if (actionMatches('star', input, key)) {
|
|
3998
|
+
// 一键 star(默认 Alt+S)——与 `/star`、开屏标语点击同一个动作。
|
|
3999
|
+
// 消费事件:alt 组合不该再落进输入框当普通字符。
|
|
4000
|
+
runStarAction();
|
|
4001
|
+
event.stopImmediatePropagation();
|
|
4002
|
+
}
|
|
3508
4003
|
else if (plainReturn && !isSticky) {
|
|
3509
4004
|
// Enter while scrolled up returns to the bottom: the
|
|
3510
4005
|
// affordance now exists whenever the view is off the bottom, not
|
|
@@ -3532,6 +4027,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3532
4027
|
// Working-activity line (spinner slot): context-pressure prefix shares the
|
|
3533
4028
|
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
3534
4029
|
const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
|
|
4030
|
+
// Who owns the spinner slot: with the working-activity line on, that slot
|
|
4031
|
+
// draws the user's `/activity` preset, so the compaction row borrows the same
|
|
4032
|
+
// indicator instead of answering with the classic dot.
|
|
4033
|
+
const activitySlot = channel.activityEnabled && !channel.minimal;
|
|
4034
|
+
// An automatic compaction runs INSIDE the turn, so it rides whichever spinner
|
|
4035
|
+
// the slot shows as a badge instead of a second row (the spinner's timer is
|
|
4036
|
+
// the turn's, not the compaction's).
|
|
4037
|
+
const compactionBadge = channel.compaction === undefined ? undefined : t('compact-badge');
|
|
3535
4038
|
// ── Interrupt lane ─────────────────────────────────────────────────────
|
|
3536
4039
|
// The approval and ask_user_question panels park the agent until the user
|
|
3537
4040
|
// answers, but they render inside the conversation layout — every screen
|
|
@@ -3545,8 +4048,38 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3545
4048
|
// The panel elements are shared with the prompt-slot chain below so the
|
|
3546
4049
|
// two mount sites cannot drift.
|
|
3547
4050
|
const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, background: approvalSnapshot.agentId !== channel.agentId, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
|
|
3548
|
-
const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection =>
|
|
3549
|
-
|
|
4051
|
+
const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => {
|
|
4052
|
+
if (!questionStore.stillCurrent(questionSnapshot.key))
|
|
4053
|
+
return;
|
|
4054
|
+
questionStore.answerCurrent(selection);
|
|
4055
|
+
}, onCancel: () => questionStore.cancelCurrent(), onEscape: draft => {
|
|
4056
|
+
// Esc means "back" once a later question is showing, including when
|
|
4057
|
+
// → and Esc share one stdin batch and this panel was mounted for
|
|
4058
|
+
// question 1 (no onBack). Ctrl+C stays on onCancel: it cancels the
|
|
4059
|
+
// whole ask from any question, so a same-batch → must not swallow it.
|
|
4060
|
+
const live = questionStore.getSnapshot();
|
|
4061
|
+
if (live?.canGoBack) {
|
|
4062
|
+
// A same-batch → already saved this panel's draft on the question
|
|
4063
|
+
// it left. Passing that draft into backCurrent would write it onto
|
|
4064
|
+
// the question → just opened.
|
|
4065
|
+
questionStore.backCurrent(questionStore.stillCurrent(questionSnapshot.key) ? draft : undefined);
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (live !== null && questionStore.stillCurrent(questionSnapshot.key)) {
|
|
4069
|
+
questionStore.cancelCurrent();
|
|
4070
|
+
}
|
|
4071
|
+
}, onBack: questionSnapshot.canGoBack
|
|
4072
|
+
? draft => {
|
|
4073
|
+
if (!questionStore.stillCurrent(questionSnapshot.key))
|
|
4074
|
+
return;
|
|
4075
|
+
questionStore.backCurrent(draft);
|
|
4076
|
+
}
|
|
4077
|
+
: undefined, onForward: questionSnapshot.canGoForward
|
|
4078
|
+
? draft => {
|
|
4079
|
+
if (!questionStore.stillCurrent(questionSnapshot.key))
|
|
4080
|
+
return;
|
|
4081
|
+
questionStore.forwardCurrent(draft);
|
|
4082
|
+
}
|
|
3550
4083
|
: undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
|
|
3551
4084
|
const interruptPanel = approvalPanelNode ?? questionPanelNode;
|
|
3552
4085
|
const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
|
|
@@ -3601,19 +4134,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3601
4134
|
*/
|
|
3602
4135
|
const agentRowOf = (sessionId) => agentViewRows.find(row => row.id === sessionId);
|
|
3603
4136
|
const supervisorNode = (_jsx(SessionSupervisor, { channel: channel, home: homeDir(), onClose: closeHome, approval: approvalSnapshot, onApprove: outcome => approvals.decide(outcome), onOpenSession: async (sessionId) => {
|
|
4137
|
+
// A refusal is reported by the screen itself (see `openSession`):
|
|
4138
|
+
// the composer that draws channel notifications is not mounted here.
|
|
3604
4139
|
const result = await channel.resumeTo(sessionId);
|
|
3605
|
-
if (!result.ok)
|
|
3606
|
-
|
|
3607
|
-
if (text !== undefined)
|
|
3608
|
-
channel.notify(text, { color: 'error', timeoutMs: 8000 });
|
|
3609
|
-
return false;
|
|
3610
|
-
}
|
|
4140
|
+
if (!result.ok)
|
|
4141
|
+
return result;
|
|
3611
4142
|
channel.notify(t('resume-resumed'));
|
|
3612
4143
|
suppressLogoIntroRef.current = true;
|
|
3613
4144
|
setAgentViewReturnId(undefined);
|
|
3614
4145
|
setSupervisorOpen(false);
|
|
3615
4146
|
repaintTranscript();
|
|
3616
|
-
return
|
|
4147
|
+
return result;
|
|
3617
4148
|
}, onNewSession: async (target) => {
|
|
3618
4149
|
const ok = await channel.switchWorkspace(target);
|
|
3619
4150
|
if (ok) {
|
|
@@ -3703,7 +4234,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3703
4234
|
|| overlay.kind === 'tips'
|
|
3704
4235
|
|| (recap !== null && (!recap.auto || recap.expanded))
|
|
3705
4236
|
|| btw !== null
|
|
3706
|
-
|| questionPanelNode !== null
|
|
4237
|
+
|| questionPanelNode !== null
|
|
4238
|
+
|| starModal !== null;
|
|
3707
4239
|
// The trajectory scene replaces the conversation for as long as it is open.
|
|
3708
4240
|
// Rendering it INSTEAD of (not above) the transcript is what makes it a
|
|
3709
4241
|
// screen rather than an overlay: it owns the full viewport, and the
|
|
@@ -3728,11 +4260,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3728
4260
|
presetOptionCount: presetOptions.length,
|
|
3729
4261
|
}) && !(overlay.kind === 'permission'
|
|
3730
4262
|
&& (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
|
|
3731
|
-
// The sticky header pins the turn owning the viewport top row
|
|
3732
|
-
// (timeline.
|
|
3733
|
-
// turn, it carries THAT turn's
|
|
4263
|
+
// The sticky header pins the turn owning the viewport top row once its
|
|
4264
|
+
// prompt has scrolled out above it (timeline.pinnedId, reported by
|
|
4265
|
+
// MessageList) — scrolled up to an old turn, it carries THAT turn's
|
|
4266
|
+
// prompt, not the latest one. The row stays while scrolled up and goes
|
|
4267
|
+
// blank when nothing is pinned, so the viewport never shifts under it.
|
|
3734
4268
|
// channel.rows is a live in-place array, so the lookup is per-render.
|
|
3735
|
-
const anchorUserRowId = timeline.
|
|
4269
|
+
const anchorUserRowId = timeline.pinnedId;
|
|
3736
4270
|
const anchorUserText = anchorUserRowId === null
|
|
3737
4271
|
? null
|
|
3738
4272
|
: channel.rows.find(row => row.id === anchorUserRowId)?.text ?? null;
|
|
@@ -3756,7 +4290,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3756
4290
|
? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
|
|
3757
4291
|
: () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
|
|
3758
4292
|
: null;
|
|
3759
|
-
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky &&
|
|
4293
|
+
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && timeline.activeId !== null && (_jsx(PinnedTurnHeader, { text: anchorUserText || null, onClick: () => {
|
|
3760
4294
|
// Click snaps the pinned prompt to the viewport top. Jump by the
|
|
3761
4295
|
// SAME content coordinate the
|
|
3762
4296
|
// rail's tick uses (timeline turn top = the prompt TEXT top):
|
|
@@ -3771,7 +4305,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3771
4305
|
seekRow(anchorUserRowId);
|
|
3772
4306
|
else
|
|
3773
4307
|
handle?.scrollToBottom();
|
|
3774
|
-
} })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd,
|
|
4308
|
+
} })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd,
|
|
4309
|
+
// 大字字面(设置项 `dsh-tui.splashFont`):`daily` 交回按天轮换
|
|
4310
|
+
// (`undefined`),其余 pin 住一款。
|
|
4311
|
+
fontId: splashFontIdOf(channel.splashFont), whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, whaleGirl: channel.whaleGirl, starred: starred, onStarClick: runStarAction, working: channel.working,
|
|
3775
4312
|
// Resuming a long session skips the ~3.4s opening animation: it
|
|
3776
4313
|
// keeps firing low-frequency React commits that compete with the
|
|
3777
4314
|
// transcript mount batches (and the first wheel events) for the
|
|
@@ -3779,7 +4316,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3779
4316
|
// sessions keep the full intro; restored ones settle instantly.
|
|
3780
4317
|
// A remount after a whole screen closed also settles instantly
|
|
3781
4318
|
// (see suppressLogoIntroRef).
|
|
3782
|
-
skipIntro: suppressLogoIntroRef.current || channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key:
|
|
4319
|
+
skipIntro: suppressLogoIntroRef.current || channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: primaryComboString('trajectory') }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
|
|
3783
4320
|
// Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
|
|
3784
4321
|
// rail (default), the proportional scrollbar, or nothing. The
|
|
3785
4322
|
// slot keeps its 2 columns in both rendered modes (Qwen's
|
|
@@ -3793,16 +4330,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3793
4330
|
}
|
|
3794
4331
|
return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: terminalColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
|
|
3795
4332
|
})(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
|
|
3796
|
-
(
|
|
3797
|
-
!channel.minimal &&
|
|
4333
|
+
(activitySlot &&
|
|
3798
4334
|
workingActivity !== undefined &&
|
|
3799
4335
|
workingActivity.line !== '' &&
|
|
3800
4336
|
workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
|
|
3801
4337
|
// Upload = real tokens of the last request; download =
|
|
3802
4338
|
// the animated chars/4 estimate, matching the classic
|
|
3803
4339
|
// spinner's counter (the suffix used raw chars before,
|
|
3804
|
-
// inflating the reading next to a real upload number).
|
|
3805
|
-
|
|
4340
|
+
// inflating the reading next to a real upload number). An
|
|
4341
|
+
// automatic compaction mid-turn badges THIS line too — it is
|
|
4342
|
+
// the spinner slot whenever real activity data exists.
|
|
4343
|
+
suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens${compactionBadge === undefined ? '' : ` · ${compactionBadge}`}` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus, suffix: compactionBadge }))), !channel.working && channel.compaction !== undefined && (_jsx(CompactionStatusRow, { compaction: channel.compaction, activityPreset: activitySlot ? channel.activityFrames : undefined })), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, provider: channel.provider, mainCost: channel.mainCost, subagentCost: channel.subagentCost, onRefresh: runBalance, onDismiss: () => setBalance(null) })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), activePreview === null && statusViews.map(view => (_jsx(PluginStatusViewBoundary, { viewKey: view.key, onError: (key, error) => statusContributions.reportViewError(key, error), children: _jsx(Box, { flexDirection: "column", flexShrink: 0, maxHeight: view.maxRows, overflow: "hidden", children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: React.createElement(view.component, {
|
|
3806
4344
|
React,
|
|
3807
4345
|
ui: STATUS_VIEW_UI,
|
|
3808
4346
|
}) }) }) }, `${view.key}:${view.registrationId}`))), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [approvalPanelNode !== null ? (approvalPanelNode) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : overlay.kind === 'tips' ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => dispatchOverlay({ type: 'close-if', kind: 'tips' }) }) })) : recap !== null && (!recap.auto || recap.expanded) ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RecapPanel, { summary: recap.summary, title: recap.title, error: recap.error, streaming: !recap.done, titleApplied: recap.titleApplied, onClose: () => {
|
|
@@ -3852,7 +4390,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3852
4390
|
? undefined
|
|
3853
4391
|
: {
|
|
3854
4392
|
band: wakeBand,
|
|
3855
|
-
hint: trajectorySeen ? undefined :
|
|
4393
|
+
hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
|
|
3856
4394
|
tick: Math.floor(wakeTime / 120),
|
|
3857
4395
|
} }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
|
|
3858
4396
|
// 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
|
|
@@ -3898,7 +4436,19 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3898
4436
|
return;
|
|
3899
4437
|
dispatchOverlay({ type: 'close' });
|
|
3900
4438
|
void switchModelRecorded(model.provider, model.id, model.name);
|
|
3901
|
-
} })) })), overlay.kind === '
|
|
4439
|
+
} })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
|
|
4440
|
+
const row = (migrateRows ?? [])[index];
|
|
4441
|
+
if (!row)
|
|
4442
|
+
return;
|
|
4443
|
+
setMigrateChecked(current => {
|
|
4444
|
+
const next = new Set(current);
|
|
4445
|
+
if (next.has(row.agentId))
|
|
4446
|
+
next.delete(row.agentId);
|
|
4447
|
+
else
|
|
4448
|
+
next.add(row.agentId);
|
|
4449
|
+
return next;
|
|
4450
|
+
});
|
|
4451
|
+
} }) })), overlay.kind === 'migrate-confirm' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigrateConfirm, { rows: migratePending }) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
|
|
3902
4452
|
const skill = skillsList[index];
|
|
3903
4453
|
if (!skill)
|
|
3904
4454
|
return;
|
|
@@ -3984,18 +4534,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3984
4534
|
const path = overlay.path;
|
|
3985
4535
|
dispatchOverlay({ type: 'close' });
|
|
3986
4536
|
runFileAction(index, path);
|
|
3987
|
-
} }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode] }));
|
|
4537
|
+
} }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode, starModal !== null && STAR_MILESTONES[starModal.index] !== undefined && (_jsx(StarPrompt, { milestone: STAR_MILESTONES[starModal.index], actions: starModalActions, onClose: closeStarModal, initialPhase: starModal.phase }))] }));
|
|
3988
4538
|
}
|
|
3989
4539
|
/**
|
|
3990
4540
|
* The pinned prompt header shown above the ScrollBox while the user has
|
|
3991
|
-
* scrolled up. It pins the
|
|
3992
|
-
*
|
|
3993
|
-
*
|
|
3994
|
-
*
|
|
3995
|
-
*
|
|
4541
|
+
* scrolled up. It pins the prompt of the turn the viewport top is showing
|
|
4542
|
+
* once that prompt has scrolled out above it, so it tracks which turn the
|
|
4543
|
+
* user is reading instead of always carrying the latest prompt. With no
|
|
4544
|
+
* such prompt (it still sits on the top row, or the logo owns the top) the
|
|
4545
|
+
* row renders blank rather than repeat on-screen text. Fixed at 1 row so
|
|
4546
|
+
* the ScrollBox never shifts when the text changes or goes blank.
|
|
3996
4547
|
*/
|
|
3997
4548
|
function PinnedTurnHeader({ text, onClick, }) {
|
|
3998
4549
|
const { columns } = useTerminalSize();
|
|
4550
|
+
if (text === null)
|
|
4551
|
+
return _jsx(Box, { flexShrink: 0, width: "100%", height: 1 });
|
|
3999
4552
|
// A one-row Box does not clip its children. Flatten hard line breaks before
|
|
4000
4553
|
// truncating, otherwise later prompt lines paint down the transcript gutter.
|
|
4001
4554
|
const label = cleanRenderText(`${POINTER} ${text}`, Math.max(1, columns - 1));
|