@deepseek-harness-tui/dsh-tui 0.11.0 → 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 +55 -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 +43 -20
- package/cordis.yml +6 -4
- package/lib/settings.json +1272 -0
- package/lib/types/activityPrefs.d.ts +24 -0
- package/lib/types/activityPrefs.d.ts.map +1 -1
- package/lib/types/activityPrefs.js +50 -1
- package/lib/types/adapter/channel/state.d.ts.map +1 -1
- package/lib/types/adapter/channel/state.js +3 -1
- 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 +14 -1
- 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 +53 -7
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +43 -12
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel.d.ts +4 -1
- package/lib/types/adapter/ports/channel.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/ActivityLine.d.ts +7 -2
- package/lib/types/components/ActivityLine.d.ts.map +1 -1
- package/lib/types/components/ActivityLine.js +0 -8
- 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 +15 -5
- 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 +96 -9
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +3 -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/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +12 -6
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
- package/lib/types/components/SubagentDetailScene.js +8 -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/HintLine.d.ts +1 -2
- package/lib/types/components/design-system/HintLine.d.ts.map +1 -1
- package/lib/types/components/design-system/HintLine.js +1 -2
- 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 +2 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +4 -2
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -2
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +31 -26
- 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/activity-store.d.ts +221 -0
- package/lib/types/dsh-adapter/activity-store.d.ts.map +1 -0
- package/lib/types/dsh-adapter/activity-store.js +359 -0
- 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 +2 -1
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/background-action.js +2 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +15 -14
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +70 -21
- package/lib/types/dsh-adapter/channel/compaction.d.ts +2 -2
- package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/compaction.js +35 -4
- 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 +1 -2
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +6 -2
- 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-adoption.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/session-adoption.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-adoption.js +1 -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 +3 -1
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-live-adoption.js +10 -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 +43 -9
- 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 -1
- package/lib/types/dsh-adapter/channel/session-resume.d.ts +2 -1
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-resume.js +5 -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 +19 -1
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +8 -3
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts +32 -17
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +309 -8
- 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 +1 -1
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +34 -18
- package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +1 -1
- 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/compat/sessionLog.d.ts +18 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +18 -3
- package/lib/types/dsh-adapter/compat/settings.d.ts +2 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/settings.js +14 -0
- package/lib/types/dsh-adapter/contract.d.ts +5 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +9 -5
- 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 +5 -3
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +168 -372
- 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/settings-sections.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settings-sections.js +7 -4
- 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 +44 -6
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +163 -26
- 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/history.d.ts +13 -1
- package/lib/types/history.d.ts.map +1 -1
- package/lib/types/history.js +15 -2
- package/lib/types/i18n.d.ts +512 -41
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +199 -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/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +19 -1
- 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 +68 -18
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +178 -31
- 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 +17 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +618 -57
- 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 +5 -1
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +47 -17
- 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 +1 -1
- package/lib/types/utils/fold-long-lines.d.ts.map +1 -1
- package/lib/types/utils/fold-long-lines.js +3 -2
- 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/lib/types/working-activity.d.ts +10 -9
- package/lib/types/working-activity.d.ts.map +1 -1
- package/lib/types/working-activity.js +17 -10
- 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 +117 -99
- package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +0 -19
- package/lib/types/components/design-system/KeyboardShortcutHint.d.ts.map +0 -1
- package/lib/types/components/design-system/KeyboardShortcutHint.js +0 -14
- package/lib/types/dsh-adapter/channel/activity.d.ts +0 -20
- package/lib/types/dsh-adapter/channel/activity.d.ts.map +0 -1
- package/lib/types/dsh-adapter/channel/activity.js +0 -113
- package/lib/types/terminal-utils/terminal.d.ts +0 -18
- package/lib/types/terminal-utils/terminal.d.ts.map +0 -1
- package/lib/types/terminal-utils/terminal.js +0 -96
|
@@ -11,15 +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';
|
|
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;
|
|
23
33
|
import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
|
|
24
34
|
import { ApprovalStore } from '../dsh-adapter/approvals.js';
|
|
25
35
|
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
@@ -32,6 +42,9 @@ import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
|
|
|
32
42
|
import { useSelection } from '../ink/hooks/use-selection.js';
|
|
33
43
|
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
34
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';
|
|
35
48
|
import { TimelineRail } from '../components/TimelineRail.js';
|
|
36
49
|
import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
|
|
37
50
|
import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
|
|
@@ -41,7 +54,9 @@ import { PromptInput } from '../components/PromptInput.js';
|
|
|
41
54
|
import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
|
|
42
55
|
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
43
56
|
import { AutoRecapRow } from '../components/AutoRecapRow.js';
|
|
57
|
+
import { CompactionStatusRow } from '../components/CompactionStatusRow.js';
|
|
44
58
|
import { BalanceReportRow } from '../components/BalanceReportRow.js';
|
|
59
|
+
import { estimateSessionCostSnapshotCny } from '../deepseekPricing.js';
|
|
45
60
|
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
46
61
|
import { StatusLine } from './StatusLine.js';
|
|
47
62
|
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
@@ -51,6 +66,10 @@ import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
|
|
|
51
66
|
import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
|
|
52
67
|
import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
|
|
53
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';
|
|
54
73
|
import { SessionSupervisor } from './SessionSupervisor.js';
|
|
55
74
|
import { SessionTree } from './SessionTree.js';
|
|
56
75
|
import { Settings } from './Settings.js';
|
|
@@ -88,7 +107,6 @@ import instances from '../ink/instances.js';
|
|
|
88
107
|
import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
89
108
|
import { useExternalVersion } from '../hooks/useExternalVersion.js';
|
|
90
109
|
import { TrajectoryScene } from './TrajectoryScene.js';
|
|
91
|
-
import { resumeFailureText } from '../sessions/resumeFailure.js';
|
|
92
110
|
import { markHomeSeen } from '../homePrefs.js';
|
|
93
111
|
import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
|
|
94
112
|
import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
|
|
@@ -118,6 +136,10 @@ const STATUS_VIEW_UI = Object.freeze({
|
|
|
118
136
|
/** Shared empty snapshot for hosts whose channel has no event log. */
|
|
119
137
|
const NO_EVENTS = [];
|
|
120
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;
|
|
121
143
|
function cleanCommandError(error) {
|
|
122
144
|
try {
|
|
123
145
|
if (error instanceof Error) {
|
|
@@ -199,12 +221,14 @@ let fallbackApprovalStore;
|
|
|
199
221
|
*/
|
|
200
222
|
let fallbackDialogStore;
|
|
201
223
|
let fallbackStatusStore;
|
|
224
|
+
/** Standalone mounts (tests, bare embeds) without the composition root's store. */
|
|
225
|
+
let fallbackActivityStore;
|
|
202
226
|
/** Identity of one caret-preview dismissal: the token (its title) on the
|
|
203
227
|
* image, so the same image staged twice is dismissed per token. */
|
|
204
228
|
function peekKey(image, title) {
|
|
205
229
|
return `${title ?? ''} ${image.id}`;
|
|
206
230
|
}
|
|
207
|
-
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, 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, }) {
|
|
208
232
|
const writeRaw = React.useContext(TerminalWriteContext);
|
|
209
233
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
210
234
|
// DEFAULT lane on purpose (useExternalVersion): the channel version bumps
|
|
@@ -237,6 +261,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
237
261
|
// Plugin status contributions: text keys join into one line; bounded rich
|
|
238
262
|
// views keep their own rows immediately above the prompt.
|
|
239
263
|
const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
|
|
264
|
+
// The working line: the working-activity plugin's published value for THIS
|
|
265
|
+
// session, read from its session projection. No projection value (plugin
|
|
266
|
+
// absent, or nothing published yet) simply means the classic spinner below.
|
|
267
|
+
const activityValues = activityStore ?? (fallbackActivityStore ??= new ActivityStore());
|
|
268
|
+
const workingActivity = useActivity(activityValues, channel.sessionId);
|
|
240
269
|
const subscribeStatus = React.useCallback((listener) => statusContributions.subscribe(listener), [statusContributions]);
|
|
241
270
|
const statusEntries = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getSnapshot());
|
|
242
271
|
const statusViews = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getViewSnapshot());
|
|
@@ -250,18 +279,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
250
279
|
channel.notify(t('ext-shortcut-failed', { combo }), { color: 'error', timeoutMs: 4000 });
|
|
251
280
|
});
|
|
252
281
|
}, [extensionShortcuts, channel]);
|
|
253
|
-
// When a questionnaire batch completes, fold a Q&A summary into the
|
|
254
|
-
// transcript (the tool card itself is hidden from the message list).
|
|
255
|
-
const questionOpenRef = React.useRef(questionSnapshot !== null);
|
|
256
|
-
React.useEffect(() => {
|
|
257
|
-
const wasOpen = questionOpenRef.current;
|
|
258
|
-
questionOpenRef.current = questionSnapshot !== null;
|
|
259
|
-
if (wasOpen && questionSnapshot === null) {
|
|
260
|
-
for (const summary of questionStore.takeSummaries()) {
|
|
261
|
-
channel.pushLocal(summary.title, summary.lines);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}, [channel, questionSnapshot, questionStore]);
|
|
265
282
|
const [expanded, setExpanded] = React.useState(false);
|
|
266
283
|
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
267
284
|
const [handle, setHandle] = React.useState(null);
|
|
@@ -277,6 +294,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
277
294
|
const [timeline, setTimeline] = React.useState({
|
|
278
295
|
turns: [],
|
|
279
296
|
activeId: null,
|
|
297
|
+
pinnedId: null,
|
|
280
298
|
upId: null,
|
|
281
299
|
downId: null,
|
|
282
300
|
});
|
|
@@ -298,6 +316,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
298
316
|
* list while the fresh one loads, exactly as the boolean era did.
|
|
299
317
|
*/
|
|
300
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);
|
|
301
330
|
// Chat and PromptInput both receive one parsed stdin batch. Keep the
|
|
302
331
|
// permission focus synchronous so arrow+Enter in the same batch uses the
|
|
303
332
|
// post-arrow row rather than the previous render's index.
|
|
@@ -410,6 +439,152 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
410
439
|
* browser, a screen rather than a panel: it owns its own focus, staged
|
|
411
440
|
* drafts and keyboard; Chat only opens it. */
|
|
412
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); }, []);
|
|
413
588
|
const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
|
|
414
589
|
const workspaceFlowRequestRef = React.useRef(0);
|
|
415
590
|
const workspaceFlowAbortRef = React.useRef(null);
|
|
@@ -546,14 +721,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
546
721
|
* renders with stale folds/expansion/selection — the "entered a freshly
|
|
547
722
|
* dispatched session and it renders wrong" bug. Reset the same set `/new`
|
|
548
723
|
* resets, plus the search overlay and the side question, and repaint the
|
|
549
|
-
* transcript
|
|
724
|
+
* transcript pinned to the bottom so rewinds and model switches can continue.
|
|
550
725
|
*/
|
|
551
726
|
const repaintTranscript = () => {
|
|
552
727
|
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
553
728
|
// Wait one task so React commits the new session's tree before the
|
|
554
729
|
// scrollback clear repaints (same pattern as `/new`).
|
|
555
730
|
setTimeout(() => {
|
|
556
|
-
handle?.
|
|
731
|
+
handle?.scrollToBottom();
|
|
557
732
|
ink?.clearScrollbackAndRedraw();
|
|
558
733
|
}, 0);
|
|
559
734
|
};
|
|
@@ -733,6 +908,39 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
733
908
|
overlay: { kind: 'image-preview', image, gallery, index, ...(title === undefined ? {} : { title }) },
|
|
734
909
|
});
|
|
735
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]);
|
|
736
944
|
// Agent-binding generation is monotonic across every agent replacement
|
|
737
945
|
// and bumps before the replacement emit, closing the ABA hole where a
|
|
738
946
|
// resumed session reuses the same id. Partial test/embed channels fall
|
|
@@ -1099,6 +1307,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1099
1307
|
// fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
|
|
1100
1308
|
// subscription in inline mode, where selection belongs to the terminal.
|
|
1101
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]);
|
|
1102
1338
|
useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }),
|
|
1103
1339
|
// Stale-selection refusal: the highlighted rows were replaced in place
|
|
1104
1340
|
// (streaming overwrite), so nothing was copied — say why instead of
|
|
@@ -1299,10 +1535,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1299
1535
|
const ok = writeLangPref(lang);
|
|
1300
1536
|
setLang(lang);
|
|
1301
1537
|
const settingsHost = channel.settingsHost();
|
|
1302
|
-
|
|
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);
|
|
1303
1541
|
if (settingsHost !== undefined && tuiView !== undefined) {
|
|
1304
1542
|
void settingsHost
|
|
1305
|
-
.write(
|
|
1543
|
+
.write(channel.settingsNamespace, [{ op: 'set', path: ['lang'], value: lang }], tuiView.revision)
|
|
1306
1544
|
.catch(() => { });
|
|
1307
1545
|
}
|
|
1308
1546
|
channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
|
|
@@ -1317,6 +1555,82 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1317
1555
|
case 'activity': return t('reload-kind-activity');
|
|
1318
1556
|
}
|
|
1319
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
|
+
};
|
|
1320
1634
|
const runCommand = (name, rawInput = '', images = []) => {
|
|
1321
1635
|
switch (name) {
|
|
1322
1636
|
case 'activity': {
|
|
@@ -1915,7 +2229,28 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1915
2229
|
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
1916
2230
|
lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
1917
2231
|
}
|
|
1918
|
-
|
|
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'));
|
|
1919
2254
|
setHelpOpen(false);
|
|
1920
2255
|
channel.pushLocal('/cost', lines);
|
|
1921
2256
|
return true;
|
|
@@ -1936,6 +2271,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1936
2271
|
setSettingsOpen(true);
|
|
1937
2272
|
return true;
|
|
1938
2273
|
}
|
|
2274
|
+
case 'star': {
|
|
2275
|
+
// 一键 star:**只有用户主动敲 /star 才会跑**(绝不自动)。动作用
|
|
2276
|
+
// runStarAction(与开屏弹窗共用),异步执行、结果用 notify 报。
|
|
2277
|
+
setHelpOpen(false);
|
|
2278
|
+
runStarAction();
|
|
2279
|
+
return true;
|
|
2280
|
+
}
|
|
1939
2281
|
case 'config': {
|
|
1940
2282
|
const userHome = process.env.USERPROFILE ?? '';
|
|
1941
2283
|
const lines = [
|
|
@@ -1953,6 +2295,60 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1953
2295
|
setHelpOpen(false);
|
|
1954
2296
|
channel.pushLocal('/doctor', channel.doctorInfo());
|
|
1955
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
|
+
}
|
|
1956
2352
|
case 'plugins':
|
|
1957
2353
|
// Plugin diagnostics (C-070): trust banner first, then descriptor /
|
|
1958
2354
|
// grant matrix / ledger tail — or validate+negotiate for
|
|
@@ -2164,7 +2560,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2164
2560
|
setHelpOpen(false);
|
|
2165
2561
|
const tuiNamespace = channel.settingsHost()
|
|
2166
2562
|
?.listNamespaces()
|
|
2167
|
-
.find(entry => entry.ns ===
|
|
2563
|
+
.find(entry => entry.ns === channel.settingsNamespace);
|
|
2168
2564
|
const plan = planReload({
|
|
2169
2565
|
envTheme: envThemeOverride(),
|
|
2170
2566
|
envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
|
|
@@ -2259,7 +2655,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2259
2655
|
setHelpOpen(false);
|
|
2260
2656
|
channel.pushLocal('/terminal-setup', [
|
|
2261
2657
|
t('terminal-setup-hint'),
|
|
2262
|
-
t('terminal-paste-hint', {
|
|
2658
|
+
t('terminal-paste-hint', { keys: effectiveComboDisplay('paste') }),
|
|
2263
2659
|
]);
|
|
2264
2660
|
return true;
|
|
2265
2661
|
case 'recap': {
|
|
@@ -2619,6 +3015,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2619
3015
|
/** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
|
|
2620
3016
|
const lastModalEnterAtRef = React.useRef(0);
|
|
2621
3017
|
useInput((input, key, event) => {
|
|
3018
|
+
// 开屏"求 star"弹窗开着时键盘全归它(↑/↓/Enter/Esc 由它自己的
|
|
3019
|
+
// useInput 处理),滚轮也不许滚动它身后的转录——和下面的整屏界面
|
|
3020
|
+
// 同一套让位规则。
|
|
3021
|
+
if (starModal !== null)
|
|
3022
|
+
return;
|
|
2622
3023
|
// Prompt-slot panels own the keyboard while visible. Their own useInput
|
|
2623
3024
|
// handles the relevant keys; Chat registered first, so yielding here
|
|
2624
3025
|
// still lets the panel receive them. PromptInput now stays mounted but
|
|
@@ -3050,6 +3451,81 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3050
3451
|
}
|
|
3051
3452
|
return;
|
|
3052
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
|
+
}
|
|
3053
3529
|
if (overlay.kind === 'activity') {
|
|
3054
3530
|
if (key.upArrow || key.downArrow) {
|
|
3055
3531
|
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: PRESET_NAMES.length });
|
|
@@ -3402,6 +3878,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3402
3878
|
}
|
|
3403
3879
|
event.stopImmediatePropagation();
|
|
3404
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
|
+
}
|
|
3405
3892
|
else if (actionMatches('transcript', input, key) && !helpOpen) {
|
|
3406
3893
|
// Leaving transcript mode (default Ctrl+O) — search was already
|
|
3407
3894
|
// handled above. Help is modal: toggling this state behind the
|
|
@@ -3438,7 +3925,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3438
3925
|
// CLEARS a non-empty prompt (single press) and only arms the
|
|
3439
3926
|
// double-press exit when the input is empty; ctrl+d keeps the
|
|
3440
3927
|
// time-based double-press exit regardless.
|
|
3441
|
-
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) {
|
|
3442
3939
|
// First press while working only interrupts. If that abort is still
|
|
3443
3940
|
// converging (cancelPending) the next press is the user insisting on
|
|
3444
3941
|
// leaving: go straight to the exit funnel. Without this, a stuck turn
|
|
@@ -3497,6 +3994,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3497
3994
|
// Consume: same readline-shadowing rule as dashboard/showAll above.
|
|
3498
3995
|
event.stopImmediatePropagation();
|
|
3499
3996
|
}
|
|
3997
|
+
else if (actionMatches('star', input, key)) {
|
|
3998
|
+
// 一键 star(默认 Alt+S)——与 `/star`、开屏标语点击同一个动作。
|
|
3999
|
+
// 消费事件:alt 组合不该再落进输入框当普通字符。
|
|
4000
|
+
runStarAction();
|
|
4001
|
+
event.stopImmediatePropagation();
|
|
4002
|
+
}
|
|
3500
4003
|
else if (plainReturn && !isSticky) {
|
|
3501
4004
|
// Enter while scrolled up returns to the bottom: the
|
|
3502
4005
|
// affordance now exists whenever the view is off the bottom, not
|
|
@@ -3524,6 +4027,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3524
4027
|
// Working-activity line (spinner slot): context-pressure prefix shares the
|
|
3525
4028
|
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
3526
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');
|
|
3527
4038
|
// ── Interrupt lane ─────────────────────────────────────────────────────
|
|
3528
4039
|
// The approval and ask_user_question panels park the agent until the user
|
|
3529
4040
|
// answers, but they render inside the conversation layout — every screen
|
|
@@ -3537,8 +4048,38 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3537
4048
|
// The panel elements are shared with the prompt-slot chain below so the
|
|
3538
4049
|
// two mount sites cannot drift.
|
|
3539
4050
|
const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, background: approvalSnapshot.agentId !== channel.agentId, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
|
|
3540
|
-
const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection =>
|
|
3541
|
-
|
|
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
|
+
}
|
|
3542
4083
|
: undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
|
|
3543
4084
|
const interruptPanel = approvalPanelNode ?? questionPanelNode;
|
|
3544
4085
|
const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
|
|
@@ -3593,19 +4134,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3593
4134
|
*/
|
|
3594
4135
|
const agentRowOf = (sessionId) => agentViewRows.find(row => row.id === sessionId);
|
|
3595
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.
|
|
3596
4139
|
const result = await channel.resumeTo(sessionId);
|
|
3597
|
-
if (!result.ok)
|
|
3598
|
-
|
|
3599
|
-
if (text !== undefined)
|
|
3600
|
-
channel.notify(text, { color: 'error', timeoutMs: 8000 });
|
|
3601
|
-
return false;
|
|
3602
|
-
}
|
|
4140
|
+
if (!result.ok)
|
|
4141
|
+
return result;
|
|
3603
4142
|
channel.notify(t('resume-resumed'));
|
|
3604
4143
|
suppressLogoIntroRef.current = true;
|
|
3605
4144
|
setAgentViewReturnId(undefined);
|
|
3606
4145
|
setSupervisorOpen(false);
|
|
3607
4146
|
repaintTranscript();
|
|
3608
|
-
return
|
|
4147
|
+
return result;
|
|
3609
4148
|
}, onNewSession: async (target) => {
|
|
3610
4149
|
const ok = await channel.switchWorkspace(target);
|
|
3611
4150
|
if (ok) {
|
|
@@ -3695,7 +4234,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3695
4234
|
|| overlay.kind === 'tips'
|
|
3696
4235
|
|| (recap !== null && (!recap.auto || recap.expanded))
|
|
3697
4236
|
|| btw !== null
|
|
3698
|
-
|| questionPanelNode !== null
|
|
4237
|
+
|| questionPanelNode !== null
|
|
4238
|
+
|| starModal !== null;
|
|
3699
4239
|
// The trajectory scene replaces the conversation for as long as it is open.
|
|
3700
4240
|
// Rendering it INSTEAD of (not above) the transcript is what makes it a
|
|
3701
4241
|
// screen rather than an overlay: it owns the full viewport, and the
|
|
@@ -3720,11 +4260,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3720
4260
|
presetOptionCount: presetOptions.length,
|
|
3721
4261
|
}) && !(overlay.kind === 'permission'
|
|
3722
4262
|
&& (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
|
|
3723
|
-
// The sticky header pins the turn owning the viewport top row
|
|
3724
|
-
// (timeline.
|
|
3725
|
-
// 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.
|
|
3726
4268
|
// channel.rows is a live in-place array, so the lookup is per-render.
|
|
3727
|
-
const anchorUserRowId = timeline.
|
|
4269
|
+
const anchorUserRowId = timeline.pinnedId;
|
|
3728
4270
|
const anchorUserText = anchorUserRowId === null
|
|
3729
4271
|
? null
|
|
3730
4272
|
: channel.rows.find(row => row.id === anchorUserRowId)?.text ?? null;
|
|
@@ -3748,7 +4290,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3748
4290
|
? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
|
|
3749
4291
|
: () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
|
|
3750
4292
|
: null;
|
|
3751
|
-
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: () => {
|
|
3752
4294
|
// Click snaps the pinned prompt to the viewport top. Jump by the
|
|
3753
4295
|
// SAME content coordinate the
|
|
3754
4296
|
// rail's tick uses (timeline turn top = the prompt TEXT top):
|
|
@@ -3763,7 +4305,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3763
4305
|
seekRow(anchorUserRowId);
|
|
3764
4306
|
else
|
|
3765
4307
|
handle?.scrollToBottom();
|
|
3766
|
-
} })), _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,
|
|
3767
4312
|
// Resuming a long session skips the ~3.4s opening animation: it
|
|
3768
4313
|
// keeps firing low-frequency React commits that compete with the
|
|
3769
4314
|
// transcript mount batches (and the first wheel events) for the
|
|
@@ -3771,7 +4316,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3771
4316
|
// sessions keep the full intro; restored ones settle instantly.
|
|
3772
4317
|
// A remount after a whole screen closed also settles instantly
|
|
3773
4318
|
// (see suppressLogoIntroRef).
|
|
3774
|
-
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 })] }), (() => {
|
|
3775
4320
|
// Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
|
|
3776
4321
|
// rail (default), the proportional scrollbar, or nothing. The
|
|
3777
4322
|
// slot keeps its 2 columns in both rendered modes (Qwen's
|
|
@@ -3785,16 +4330,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3785
4330
|
}
|
|
3786
4331
|
return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: terminalColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
|
|
3787
4332
|
})(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
|
|
3788
|
-
(
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
|
|
4333
|
+
(activitySlot &&
|
|
4334
|
+
workingActivity !== undefined &&
|
|
4335
|
+
workingActivity.line !== '' &&
|
|
4336
|
+
workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
|
|
3793
4337
|
// Upload = real tokens of the last request; download =
|
|
3794
4338
|
// the animated chars/4 estimate, matching the classic
|
|
3795
4339
|
// spinner's counter (the suffix used raw chars before,
|
|
3796
|
-
// inflating the reading next to a real upload number).
|
|
3797
|
-
|
|
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, {
|
|
3798
4344
|
React,
|
|
3799
4345
|
ui: STATUS_VIEW_UI,
|
|
3800
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: () => {
|
|
@@ -3840,11 +4386,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3840
4386
|
// clock and shifting every row-count layout invariant.
|
|
3841
4387
|
channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
|
|
3842
4388
|
? backgroundAgentsNeedingInput
|
|
3843
|
-
: undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
|
|
4389
|
+
: undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, activity: workingActivity, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
|
|
3844
4390
|
? undefined
|
|
3845
4391
|
: {
|
|
3846
4392
|
band: wakeBand,
|
|
3847
|
-
hint: trajectorySeen ? undefined :
|
|
4393
|
+
hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
|
|
3848
4394
|
tick: Math.floor(wakeTime / 120),
|
|
3849
4395
|
} }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
|
|
3850
4396
|
// 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
|
|
@@ -3890,7 +4436,19 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3890
4436
|
return;
|
|
3891
4437
|
dispatchOverlay({ type: 'close' });
|
|
3892
4438
|
void switchModelRecorded(model.provider, model.id, model.name);
|
|
3893
|
-
} })) })), 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) => {
|
|
3894
4452
|
const skill = skillsList[index];
|
|
3895
4453
|
if (!skill)
|
|
3896
4454
|
return;
|
|
@@ -3976,18 +4534,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3976
4534
|
const path = overlay.path;
|
|
3977
4535
|
dispatchOverlay({ type: 'close' });
|
|
3978
4536
|
runFileAction(index, path);
|
|
3979
|
-
} }) })), 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 }))] }));
|
|
3980
4538
|
}
|
|
3981
4539
|
/**
|
|
3982
4540
|
* The pinned prompt header shown above the ScrollBox while the user has
|
|
3983
|
-
* scrolled up. It pins the
|
|
3984
|
-
*
|
|
3985
|
-
*
|
|
3986
|
-
*
|
|
3987
|
-
*
|
|
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.
|
|
3988
4547
|
*/
|
|
3989
4548
|
function PinnedTurnHeader({ text, onClick, }) {
|
|
3990
4549
|
const { columns } = useTerminalSize();
|
|
4550
|
+
if (text === null)
|
|
4551
|
+
return _jsx(Box, { flexShrink: 0, width: "100%", height: 1 });
|
|
3991
4552
|
// A one-row Box does not clip its children. Flatten hard line breaks before
|
|
3992
4553
|
// truncating, otherwise later prompt lines paint down the transcript gutter.
|
|
3993
4554
|
const label = cleanRenderText(`${POINTER} ${text}`, Math.max(1, columns - 1));
|