@deepseek-harness-tui/dsh-tui 0.9.0 → 0.9.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 +69 -256
- package/bin/dsh-tui.js +172 -5
- package/cordis.patch.yml +16 -4
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
- package/dsh-ecosystem-spec/registry/README.md +14 -14
- package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
- package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
- package/lib/types/cc/hyperlink.d.ts +7 -1
- package/lib/types/cc/hyperlink.d.ts.map +1 -1
- package/lib/types/cc/hyperlink.js +3 -2
- package/lib/types/cc/markdown.d.ts.map +1 -1
- package/lib/types/cc/markdown.js +33 -3
- package/lib/types/cc/sessionColors.d.ts +27 -0
- package/lib/types/cc/sessionColors.d.ts.map +1 -0
- package/lib/types/cc/sessionColors.js +39 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +6 -0
- package/lib/types/components/AutoRecapRow.d.ts +23 -0
- package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
- package/lib/types/components/AutoRecapRow.js +23 -0
- package/lib/types/components/ColorPicker.d.ts +17 -0
- package/lib/types/components/ColorPicker.d.ts.map +1 -0
- package/lib/types/components/ColorPicker.js +20 -0
- package/lib/types/components/CommandSuggestions.d.ts +3 -1
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.d.ts +33 -0
- package/lib/types/components/ContextBarView.d.ts.map +1 -0
- package/lib/types/components/ContextBarView.js +46 -0
- package/lib/types/components/EffortInputBorder.d.ts +12 -1
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +22 -5
- package/lib/types/components/FileActionsPanel.d.ts +22 -0
- package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
- package/lib/types/components/FileActionsPanel.js +28 -0
- package/lib/types/components/FileSuggestions.d.ts +3 -1
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +13 -6
- package/lib/types/components/ModelPicker.d.ts +26 -4
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +26 -6
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +365 -57
- package/lib/types/components/RecapPanel.d.ts +25 -0
- package/lib/types/components/RecapPanel.d.ts.map +1 -0
- package/lib/types/components/RecapPanel.js +55 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
- package/lib/types/components/ScrollbarGutter.js +55 -4
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -4
- package/lib/types/components/SuggestionCard.d.ts +4 -2
- package/lib/types/components/SuggestionCard.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.js +9 -2
- package/lib/types/components/design-system/ThemeProvider.d.ts +7 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +2 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +36 -8
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +7 -3
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +41 -9
- package/lib/types/dsh-adapter/channel.d.ts +101 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +969 -93
- package/lib/types/dsh-adapter/compat/index.d.ts +8 -6
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +8 -6
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +150 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +518 -41
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/extension-events.d.ts +1 -1
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.d.ts +10 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +5 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +317 -8
- package/lib/types/dsh-adapter/presets.d.ts +18 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +31 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts +29 -1
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +89 -5
- package/lib/types/dsh-adapter/questions.d.ts +19 -2
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions.js +53 -10
- package/lib/types/dsh-adapter/recap.d.ts +49 -0
- package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
- package/lib/types/dsh-adapter/recap.js +121 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts +314 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessionTree.js +954 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts +15 -44
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
- package/lib/types/dsh-adapter/shortcuts.js +28 -165
- package/lib/types/dsh-adapter/types.d.ts +1 -1
- package/lib/types/dsh-adapter/types.d.ts.map +1 -1
- package/lib/types/herdr.js +1 -1
- package/lib/types/i18n.d.ts +604 -176
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +206 -72
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +21 -0
- package/lib/types/migrationPrefs.d.ts +60 -0
- package/lib/types/migrationPrefs.d.ts.map +1 -0
- package/lib/types/migrationPrefs.js +97 -0
- package/lib/types/modelGroups.d.ts +72 -0
- package/lib/types/modelGroups.d.ts.map +1 -0
- package/lib/types/modelGroups.js +97 -0
- package/lib/types/modelRecents.d.ts +39 -0
- package/lib/types/modelRecents.d.ts.map +1 -0
- package/lib/types/modelRecents.js +97 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts +11 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
- package/lib/types/native-ts/yoga-layout/index.js +31 -1
- package/lib/types/oauth.d.ts +15 -0
- package/lib/types/oauth.d.ts.map +1 -0
- package/lib/types/oauth.js +14 -0
- package/lib/types/plugin-spec/registry.d.ts +1 -1
- package/lib/types/plugin-spec/registry.js +1 -1
- package/lib/types/reload.d.ts +109 -0
- package/lib/types/reload.d.ts.map +1 -0
- package/lib/types/reload.js +118 -0
- package/lib/types/screens/Chat.d.ts +3 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +588 -64
- package/lib/types/screens/SessionTree.d.ts +27 -0
- package/lib/types/screens/SessionTree.d.ts.map +1 -0
- package/lib/types/screens/SessionTree.js +555 -0
- package/lib/types/screens/StatusLine.d.ts +0 -9
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +215 -52
- package/lib/types/screens/StatusMetrics.d.ts +35 -3
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +63 -8
- package/lib/types/screens/chatOverlay.d.ts +17 -1
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +3 -1
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +22 -4
- package/lib/types/update.d.ts +159 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +496 -30
- package/lib/types/utils/debug.d.ts +1 -1
- package/lib/types/utils/debug.js +3 -3
- package/lib/types/utils/fileTarget.d.ts +46 -0
- package/lib/types/utils/fileTarget.d.ts.map +1 -0
- package/lib/types/utils/fileTarget.js +178 -0
- package/lib/types/utils/keymap.d.ts +134 -0
- package/lib/types/utils/keymap.d.ts.map +1 -0
- package/lib/types/utils/keymap.js +311 -0
- package/lib/types/utils/mentions.d.ts +25 -2
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +69 -10
- package/lib/types/utils/openExternal.d.ts +50 -0
- package/lib/types/utils/openExternal.d.ts.map +1 -0
- package/lib/types/utils/openExternal.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +140 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +11 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.d.ts +14 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.js +67 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +64 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +79 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +196 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +193 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.d.ts +32 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.js +101 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +31 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.d.ts +69 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.js +129 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +70 -0
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/command/LICENSE +21 -21
- package/node_modules/@dsh-std/command/README.md +26 -26
- package/node_modules/@dsh-std/command/README.zh.md +28 -28
- package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/command/package.json +22 -22
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/node_modules/@dsh-std/connection/README.md +42 -42
- package/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/node_modules/@dsh-std/connection/package.json +20 -20
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/core/LICENSE +21 -21
- package/node_modules/@dsh-std/core/README.md +13 -13
- package/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/core/package.json +21 -21
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/node_modules/@dsh-std/manifest/README.md +5 -5
- package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/manifest/package.json +20 -20
- package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/messages/LICENSE +21 -21
- package/node_modules/@dsh-std/messages/README.md +5 -5
- package/node_modules/@dsh-std/messages/README.zh.md +7 -7
- package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/messages/package.json +20 -20
- package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/node_modules/@dsh-std/presentation/README.md +27 -27
- package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/presentation/package.json +25 -25
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/storage/LICENSE +21 -21
- package/node_modules/@dsh-std/storage/README.md +5 -5
- package/node_modules/@dsh-std/storage/README.zh.md +7 -7
- package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/storage/package.json +20 -20
- package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
- package/package.json +17 -5
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/instruction-hint.mjs +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { t, getLang, setLang, isLang, writeLangPref, subscribeLang, LANGS } from '../i18n.js';
|
|
3
|
+
import { t, getLang, setLang, isLang, writeLangPref, readLangPref, subscribeLang, LANGS } from '../i18n.js';
|
|
4
|
+
import { readThemePref } from '../themePrefs.js';
|
|
5
|
+
import { readPresetPref } from '../presetPrefs.js';
|
|
6
|
+
import { readModelPref } from '../modelPrefs.js';
|
|
7
|
+
import { readActivityFrames } from '../activityPrefs.js';
|
|
8
|
+
import { envThemeOverride } from '../components/design-system/ThemeProvider.js';
|
|
9
|
+
import { hasPath } from '../dsh-adapter/settingsEditor.js';
|
|
10
|
+
import { planReload } from '../reload.js';
|
|
4
11
|
import { AlternateScreen, Box, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
|
|
5
12
|
import * as tuiKit from '../ui.js';
|
|
6
13
|
import { POINTER } from '../cc/figures.js';
|
|
7
|
-
import {
|
|
14
|
+
import { isPlainReturnInput, modLabel } from '../utils/modifiers.js';
|
|
15
|
+
import { actionMatches } from '../utils/keymap.js';
|
|
8
16
|
import { formatTokens } from '../cc/format.js';
|
|
9
17
|
import { homeDir } from '../utils/paths.js';
|
|
18
|
+
import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
|
|
19
|
+
import { readModelRecents, recordModelUse } from '../modelRecents.js';
|
|
10
20
|
import { sessionCwdMatches } from '../dsh-adapter/channel.js';
|
|
11
21
|
import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
|
|
12
22
|
import { TuiStatusStore } from '../dsh-adapter/status.js';
|
|
@@ -28,6 +38,7 @@ import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
|
|
|
28
38
|
import { OverlayAbove } from '../components/OverlayAbove.js';
|
|
29
39
|
import { PromptInput } from '../components/PromptInput.js';
|
|
30
40
|
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
41
|
+
import { AutoRecapRow } from '../components/AutoRecapRow.js';
|
|
31
42
|
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
32
43
|
import { StatusLine } from './StatusLine.js';
|
|
33
44
|
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
@@ -36,11 +47,13 @@ import { ModelPicker } from '../components/ModelPicker.js';
|
|
|
36
47
|
import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
|
|
37
48
|
import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
|
|
38
49
|
import { SessionBrowser } from './SessionBrowser.js';
|
|
50
|
+
import { SessionTree } from './SessionTree.js';
|
|
39
51
|
import { Settings } from './Settings.js';
|
|
40
52
|
import { WorkspacePicker } from '../components/WorkspacePicker.js';
|
|
41
53
|
import { WorkspaceMenuPicker } from '../components/WorkspaceMenuPicker.js';
|
|
42
54
|
import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
|
|
43
55
|
import { ActivityPicker } from '../components/ActivityPicker.js';
|
|
56
|
+
import { ColorPicker } from '../components/ColorPicker.js';
|
|
44
57
|
import { EffortSlider } from '../components/EffortSlider.js';
|
|
45
58
|
import { PresetPicker } from '../components/PresetPicker.js';
|
|
46
59
|
import { PermissionsPicker, PERMISSION_PRESET_IDS } from '../components/PermissionsPicker.js';
|
|
@@ -53,9 +66,15 @@ import { ThinkingToggle } from '../components/ThinkingToggle.js';
|
|
|
53
66
|
import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
|
|
54
67
|
import { RewindPicker } from '../components/RewindPicker.js';
|
|
55
68
|
import { BtwPanel } from '../components/BtwPanel.js';
|
|
69
|
+
import { RecapPanel } from '../components/RecapPanel.js';
|
|
70
|
+
import { isValidSessionColor, SESSION_COLOR_NAMES } from '../cc/sessionColors.js';
|
|
56
71
|
import { TipsPanel } from '../components/TipsPanel.js';
|
|
57
72
|
import { SubagentDashboard } from '../components/SubagentDashboard.js';
|
|
58
73
|
import { SubagentDetailScene } from '../components/SubagentDetailScene.js';
|
|
74
|
+
import { FileActionsPanel, FILE_ACTION_COUNT } from '../components/FileActionsPanel.js';
|
|
75
|
+
import { openExternal, openFile, revealInFileManager } from '../utils/openExternal.js';
|
|
76
|
+
import { fileUrlToPath, parseFileLinkUrl, resolveTargetPath } from '../utils/fileTarget.js';
|
|
77
|
+
import { statSync } from 'node:fs';
|
|
59
78
|
import { setClipboard } from '../ink/termio/osc.js';
|
|
60
79
|
import { TerminalWriteContext } from '../ink/useTerminalNotification.js';
|
|
61
80
|
import instances from '../ink/instances.js';
|
|
@@ -148,7 +167,7 @@ let fallbackApprovalStore;
|
|
|
148
167
|
*/
|
|
149
168
|
let fallbackDialogStore;
|
|
150
169
|
let fallbackStatusStore;
|
|
151
|
-
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, onExit, onUpdate, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
|
|
170
|
+
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
|
|
152
171
|
const writeRaw = React.useContext(TerminalWriteContext);
|
|
153
172
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
154
173
|
React.useSyncExternalStore(channel.subscribe, () => channel.version);
|
|
@@ -233,11 +252,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
233
252
|
*/
|
|
234
253
|
const [overlay, dispatchOverlay] = React.useReducer(chatOverlayReducer, NO_OVERLAY);
|
|
235
254
|
const [models, setModels] = React.useState([]);
|
|
255
|
+
/** Provider display identities for the /model group level; refreshed alongside `models`. */
|
|
256
|
+
const [providerInfos, setProviderInfos] = React.useState([]);
|
|
257
|
+
/** /model 最近使用分组:成功切换即记录(去重置顶,上限 10),重启保留。 */
|
|
258
|
+
const [modelRecents, setModelRecents] = React.useState(() => readModelRecents());
|
|
259
|
+
/** Two-level /model: the drilled-in provider route; undefined = group level.
|
|
260
|
+
* Reset on open; stale ids resolve back to the group level via `activeModelGroup`. */
|
|
261
|
+
const [modelGroup, setModelGroup] = React.useState(undefined);
|
|
262
|
+
/** True while the picker sits in the single-provider fast path (drilled in
|
|
263
|
+
* at open, the group level never shown): Esc closes directly and no back
|
|
264
|
+
* hint renders — a pinned recents pseudo-group must not fake a two-level
|
|
265
|
+
* walk the user never saw (issue #527 regression: repro-picker-windowing). */
|
|
266
|
+
const [modelPickerDirect, setModelPickerDirect] = React.useState(false);
|
|
267
|
+
/** Group rows over the current catalog, first-appearance (registry) order,
|
|
268
|
+
* with the pinned recents pseudo-group first when any entry is catalogued. */
|
|
269
|
+
const modelGroups = React.useMemo(() => deriveModelGroups(models, providerInfos, modelRecents), [models, providerInfos, modelRecents]);
|
|
270
|
+
/** The drilled-in group, but only while it still exists in the catalog. */
|
|
271
|
+
const activeModelGroup = modelGroup !== undefined && modelGroups.some(group => group.provider === modelGroup)
|
|
272
|
+
? modelGroup
|
|
273
|
+
: undefined;
|
|
274
|
+
const groupModels = React.useMemo(() => {
|
|
275
|
+
if (activeModelGroup === undefined)
|
|
276
|
+
return [];
|
|
277
|
+
if (activeModelGroup === RECENTS_GROUP_PROVIDER)
|
|
278
|
+
return recentCatalogModels(modelRecents, models);
|
|
279
|
+
return models.filter(model => model.provider === activeModelGroup);
|
|
280
|
+
}, [models, modelRecents, activeModelGroup]);
|
|
281
|
+
/** Switch + record: every successful switch feeds the /model recents group
|
|
282
|
+
* (picker Enter/click, `/model provider/id`, the wizard's live switch,
|
|
283
|
+
* and /reload's applied model all ride this one path). */
|
|
284
|
+
const switchModelRecorded = (provider, id, name) => {
|
|
285
|
+
if (name !== undefined)
|
|
286
|
+
channel.notify(t('model-switching', { name }));
|
|
287
|
+
return channel.switchModel(provider, id).then((ok) => {
|
|
288
|
+
if (!ok)
|
|
289
|
+
return ok;
|
|
290
|
+
if (name !== undefined)
|
|
291
|
+
channel.notify(t('model-switched', { name }));
|
|
292
|
+
setModelRecents(recordModelUse({ provider, id }));
|
|
293
|
+
return ok;
|
|
294
|
+
});
|
|
295
|
+
};
|
|
236
296
|
/** `/skills` 技能目录(issue #204):null = 注册表快照在途。 */
|
|
237
297
|
const [skillsList, setSkillsList] = React.useState(null);
|
|
238
298
|
/** `/resume` opens the session browser, a screen rather than a panel. It
|
|
239
299
|
* owns its own selection, filters and keyboard — Chat only opens it. */
|
|
240
300
|
const [browserOpen, setBrowserOpen] = React.useState(false);
|
|
301
|
+
/** `/tree` opens the session family tree (pi's Session Tree): every rewind
|
|
302
|
+
* fork stitched back onto the message it diverged from, hover previews,
|
|
303
|
+
* and per-node rewind/fork/adopt actions. Like the browser, a screen. */
|
|
304
|
+
const [treeOpen, setTreeOpen] = React.useState(false);
|
|
241
305
|
/** `/settings` opens the plugin settings screen (issue #165) — like the
|
|
242
306
|
* browser, a screen rather than a panel: it owns its own focus, staged
|
|
243
307
|
* drafts and keyboard; Chat only opens it. */
|
|
@@ -271,6 +335,68 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
271
335
|
btwAbortRef.current = null;
|
|
272
336
|
setBtw(null);
|
|
273
337
|
};
|
|
338
|
+
/** /recap overlay (pi-recap semantics): pure UI state like /btw — the
|
|
339
|
+
* summary never enters the transcript or session log; applying the
|
|
340
|
+
* proposed title goes through the normal /rename path. `auto` marks the
|
|
341
|
+
* recapOnOpen-triggered run (rendered as the dim AutoRecapRow until
|
|
342
|
+
* expanded); `expanded` lifts an auto recap into the full RecapPanel;
|
|
343
|
+
* `rowsAtTrigger` is the last user-row id when the auto run started —
|
|
344
|
+
* a newer user row (the user starts a new message) retires the recap. */
|
|
345
|
+
const [recap, setRecap] = React.useState(null);
|
|
346
|
+
const recapAbortRef = React.useRef(null);
|
|
347
|
+
const closeRecap = () => {
|
|
348
|
+
recapAbortRef.current?.abort();
|
|
349
|
+
recapAbortRef.current = null;
|
|
350
|
+
setRecap(null);
|
|
351
|
+
};
|
|
352
|
+
// Auto-recap (`dsh-tui.recapOnOpen`): every time the session switches
|
|
353
|
+
// (mount = open/resume, rewind/fork included), summarize its tail into
|
|
354
|
+
// the dim AutoRecapRow. Failures stay silent in auto mode — `/recap`
|
|
355
|
+
// surfaces them; the summary never enters the transcript or session log.
|
|
356
|
+
const autoRecapSessionId = channel.agentId;
|
|
357
|
+
React.useEffect(() => {
|
|
358
|
+
// A session switch retires the previous recap outright — an old
|
|
359
|
+
// session's 回顾 has no place above a new conversation.
|
|
360
|
+
setRecap(null);
|
|
361
|
+
if (!channel.autoRecapOnOpen)
|
|
362
|
+
return;
|
|
363
|
+
// No conversation yet (/new): nothing to recap, don't even fire.
|
|
364
|
+
if (!channel.rows.some(row => row.kind === 'user' || row.kind === 'assistant'))
|
|
365
|
+
return;
|
|
366
|
+
recapAbortRef.current?.abort();
|
|
367
|
+
const controller = new AbortController();
|
|
368
|
+
recapAbortRef.current = controller;
|
|
369
|
+
const lastUserId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
|
|
370
|
+
setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: true, expanded: false, rowsAtTrigger: lastUserId });
|
|
371
|
+
void channel.recapRecent({
|
|
372
|
+
signal: controller.signal,
|
|
373
|
+
onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
|
|
374
|
+
}).then(result => {
|
|
375
|
+
if (controller.signal.aborted)
|
|
376
|
+
return;
|
|
377
|
+
setRecap(prev => {
|
|
378
|
+
if (prev === null || !prev.auto)
|
|
379
|
+
return prev;
|
|
380
|
+
// Auto mode stays quiet on failure (no activity / llm missing / error).
|
|
381
|
+
if (result.summary === null)
|
|
382
|
+
return null;
|
|
383
|
+
return { ...prev, summary: result.summary, title: result.title, error: result.error, done: true };
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
return () => controller.abort();
|
|
387
|
+
}, [autoRecapSessionId]);
|
|
388
|
+
// The user starts a new message → the auto recap has served its purpose
|
|
389
|
+
// (catching them up) and bows out. A newer user row is the signal; the
|
|
390
|
+
// assistant's own streamed rows don't count.
|
|
391
|
+
const lastUserRowId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
|
|
392
|
+
React.useEffect(() => {
|
|
393
|
+
if (recap !== null &&
|
|
394
|
+
recap.auto &&
|
|
395
|
+
recap.rowsAtTrigger !== undefined &&
|
|
396
|
+
lastUserRowId > recap.rowsAtTrigger) {
|
|
397
|
+
closeRecap();
|
|
398
|
+
}
|
|
399
|
+
}, [lastUserRowId, recap]);
|
|
274
400
|
/** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
|
|
275
401
|
const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
|
|
276
402
|
/** Detail view for a specific subagent (opened from dashboard). */
|
|
@@ -281,6 +407,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
281
407
|
*/
|
|
282
408
|
const [logoNonce, setLogoNonce] = React.useState(0);
|
|
283
409
|
React.useEffect(() => () => btwAbortRef.current?.abort(), []);
|
|
410
|
+
React.useEffect(() => () => recapAbortRef.current?.abort(), []);
|
|
284
411
|
/**
|
|
285
412
|
* The trajectory scene (issue #80 evolution). Unlike every other overlay
|
|
286
413
|
* here it is not a panel but a whole screen: while open, Chat renders the
|
|
@@ -325,6 +452,68 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
325
452
|
ink?.invalidatePrevFrame();
|
|
326
453
|
ink?.reanchorViewport();
|
|
327
454
|
}, []);
|
|
455
|
+
/**
|
|
456
|
+
* Click-to-act targets: the Ink instance's hyperlink-open callback (wired
|
|
457
|
+
* in the effect below) resolves every clickable target the transcript
|
|
458
|
+
* renders — http(s) links open the browser, `dsh-file:`/`file://` paths
|
|
459
|
+
* open the file-action menu. `dsh-file:` payloads are RAW display paths
|
|
460
|
+
* (possibly relative), so they resolve against the CURRENT channel cwd
|
|
461
|
+
* at click time (read through a ref so this callback keeps a stable
|
|
462
|
+
* identity — it is threaded into memoized row components).
|
|
463
|
+
*/
|
|
464
|
+
const cwdRef = React.useRef(channel.cwd);
|
|
465
|
+
React.useEffect(() => {
|
|
466
|
+
cwdRef.current = channel.cwd;
|
|
467
|
+
}, [channel.cwd]);
|
|
468
|
+
const openFileActions = React.useCallback((rawPath) => {
|
|
469
|
+
const resolved = resolveTargetPath(rawPath, cwdRef.current);
|
|
470
|
+
// Whether the target is a directory decides the first menu row's label
|
|
471
|
+
// ("open file" vs "open folder"). Missing paths count as files.
|
|
472
|
+
let isDir = false;
|
|
473
|
+
try {
|
|
474
|
+
isDir = statSync(resolved).isDirectory();
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
isDir = false;
|
|
478
|
+
}
|
|
479
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'file-actions', path: resolved, index: 0, isDir } });
|
|
480
|
+
}, []);
|
|
481
|
+
/** Run one file-action menu row: 0 = open file, 1 = reveal in file
|
|
482
|
+
* manager, 2 = copy absolute path. */
|
|
483
|
+
const runFileAction = React.useCallback((index, path) => {
|
|
484
|
+
if (index === 0)
|
|
485
|
+
openFile(path);
|
|
486
|
+
else if (index === 1)
|
|
487
|
+
revealInFileManager(path);
|
|
488
|
+
else
|
|
489
|
+
void setClipboard(path);
|
|
490
|
+
}, []);
|
|
491
|
+
const handleOpenTarget = React.useCallback((url) => {
|
|
492
|
+
const rawPath = parseFileLinkUrl(url);
|
|
493
|
+
if (rawPath !== undefined) {
|
|
494
|
+
openFileActions(rawPath);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const filePath = fileUrlToPath(url);
|
|
498
|
+
if (filePath !== undefined) {
|
|
499
|
+
openFileActions(filePath);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
openExternal(url);
|
|
503
|
+
}, [openFileActions]);
|
|
504
|
+
// Wire the click-to-open callback into the Ink instance (the field is
|
|
505
|
+
// otherwise never set — clicking links was a no-op). Re-wired whenever
|
|
506
|
+
// the handler changes (cwd moves), cleared on unmount.
|
|
507
|
+
React.useEffect(() => {
|
|
508
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
509
|
+
if (ink)
|
|
510
|
+
ink.onHyperlinkClick = handleOpenTarget;
|
|
511
|
+
return () => {
|
|
512
|
+
const current = instances.get(process.stdout) ?? instances.values().next().value;
|
|
513
|
+
if (current)
|
|
514
|
+
current.onHyperlinkClick = undefined;
|
|
515
|
+
};
|
|
516
|
+
}, [handleOpenTarget]);
|
|
328
517
|
/** `/` transcript search (less-style incsearch, ported from CC's REPL).
|
|
329
518
|
* Only the bar's open/closed mode lives in `overlay`; the query and match
|
|
330
519
|
* counters persist past the bar closing so n/N keep walking the matches. */
|
|
@@ -574,6 +763,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
574
763
|
}
|
|
575
764
|
channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
|
|
576
765
|
};
|
|
766
|
+
/** Localized label of one /reload surface, for the change report. */
|
|
767
|
+
const reloadKindLabel = (kind) => {
|
|
768
|
+
switch (kind) {
|
|
769
|
+
case 'theme': return t('reload-kind-theme');
|
|
770
|
+
case 'lang': return t('reload-kind-lang');
|
|
771
|
+
case 'preset': return t('reload-kind-preset');
|
|
772
|
+
case 'model': return t('reload-kind-model');
|
|
773
|
+
case 'activity': return t('reload-kind-activity');
|
|
774
|
+
}
|
|
775
|
+
};
|
|
577
776
|
const runCommand = (name, rawInput = '') => {
|
|
578
777
|
switch (name) {
|
|
579
778
|
case 'activity': {
|
|
@@ -775,11 +974,50 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
775
974
|
});
|
|
776
975
|
return true;
|
|
777
976
|
}
|
|
977
|
+
case 'color': {
|
|
978
|
+
// `/color`(CC accent,按会话持久化):无参打开调色板选择器,
|
|
979
|
+
// `/color <name>` 直接设置,`/color status` 显示当前,`/color
|
|
980
|
+
// reset` 清除回主题默认。颜色经 `session/color` 事件按会话保存
|
|
981
|
+
// ——resume/rewind 后仍是这个会话自己的颜色(见 channel.ts)。
|
|
982
|
+
setHelpOpen(false);
|
|
983
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
984
|
+
if (parts.length === 0) {
|
|
985
|
+
dispatchOverlay({
|
|
986
|
+
type: 'open',
|
|
987
|
+
overlay: {
|
|
988
|
+
kind: 'color',
|
|
989
|
+
index: Math.max(0, SESSION_COLOR_NAMES.indexOf(channel.sessionColor)),
|
|
990
|
+
},
|
|
991
|
+
});
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
if (parts[0] === 'status') {
|
|
995
|
+
channel.pushLocal('/color', [
|
|
996
|
+
channel.sessionColor === ''
|
|
997
|
+
? t('color-current-none')
|
|
998
|
+
: t('color-current', { name: channel.sessionColor }),
|
|
999
|
+
t('color-usage', { list: SESSION_COLOR_NAMES.join('/') }),
|
|
1000
|
+
]);
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
if (parts[0] === 'reset') {
|
|
1004
|
+
channel.setSessionColor('');
|
|
1005
|
+
channel.notify(t('color-reset'));
|
|
1006
|
+
return true;
|
|
1007
|
+
}
|
|
1008
|
+
const colorName = parts[0].toLowerCase();
|
|
1009
|
+
if (!isValidSessionColor(colorName)) {
|
|
1010
|
+
channel.notify(t('color-unknown', { name: colorName, list: SESSION_COLOR_NAMES.join(' · ') }), { color: 'error' });
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
channel.setSessionColor(colorName);
|
|
1014
|
+
channel.notify(t('color-set', { name: colorName }), { color: 'success' });
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
778
1017
|
case 'new': {
|
|
779
1018
|
// One-shot `/new` (issue #25): the old session stays persisted and
|
|
780
1019
|
// is recoverable via /resume, so discarding the live view is
|
|
781
|
-
// non-destructive — no CC-style "press /new again" confirmation.
|
|
782
|
-
setHelpOpen(false);
|
|
1020
|
+
// non-destructive — no CC-style "press /new again" confirmation. setHelpOpen(false)
|
|
783
1021
|
void channel.newSession().then((ok) => {
|
|
784
1022
|
if (!ok)
|
|
785
1023
|
return;
|
|
@@ -854,11 +1092,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
854
1092
|
channel.notify(t('model-unknown', { spec }), { color: 'error', timeoutMs: 8000 });
|
|
855
1093
|
return;
|
|
856
1094
|
}
|
|
857
|
-
|
|
858
|
-
void channel.switchModel(provider, id).then((ok) => {
|
|
859
|
-
if (ok)
|
|
860
|
-
channel.notify(t('model-switched', { name: model.name }));
|
|
861
|
-
});
|
|
1095
|
+
void switchModelRecorded(provider, id, model.name);
|
|
862
1096
|
});
|
|
863
1097
|
return true;
|
|
864
1098
|
}
|
|
@@ -866,18 +1100,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
866
1100
|
// Opens over the cached catalog (empty cache shows the loading
|
|
867
1101
|
// pane); the fresh list lands with the authoritative focus, and the
|
|
868
1102
|
// kind-guarded set-index cannot re-focus a picker the user left.
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1103
|
+
// Seed-on-open: the model in use IS a use — recording it here means
|
|
1104
|
+
// the recents group exists before the first post-update switch, and
|
|
1105
|
+
// switching A→B keeps A in the list (the file records what was
|
|
1106
|
+
// used, not only switches made after the file appeared).
|
|
1107
|
+
let recentsNow = modelRecents;
|
|
1108
|
+
if (channel.provider !== '' && channel.model !== ''
|
|
1109
|
+
&& !recentsNow.some(ref => ref.provider === channel.provider && ref.id === channel.model)) {
|
|
1110
|
+
recentsNow = recordModelUse({ provider: channel.provider, id: channel.model });
|
|
1111
|
+
setModelRecents(recentsNow);
|
|
1112
|
+
}
|
|
1113
|
+
// Two-level landing: recents (when catalogued) focus their pinned
|
|
1114
|
+
// row; else multi-provider catalogs focus the current provider's
|
|
1115
|
+
// group row; a single-provider catalog without a meaningful recents
|
|
1116
|
+
// list drills straight into its model list (pre-grouping UX).
|
|
1117
|
+
{
|
|
1118
|
+
const landing = modelPickerLanding(models, channel.provider, channel.model, recentsNow);
|
|
1119
|
+
setModelGroup(landing.group);
|
|
1120
|
+
setModelPickerDirect(landing.group !== undefined);
|
|
1121
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'model', index: landing.index } });
|
|
1122
|
+
}
|
|
876
1123
|
void channel.listModels().then((list) => {
|
|
877
1124
|
setModels(list);
|
|
878
|
-
const
|
|
879
|
-
|
|
1125
|
+
const landing = modelPickerLanding(list, channel.provider, channel.model, recentsNow);
|
|
1126
|
+
setModelGroup(landing.group);
|
|
1127
|
+
setModelPickerDirect(landing.group !== undefined);
|
|
1128
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
880
1129
|
});
|
|
1130
|
+
void channel.listProviders().then(setProviderInfos).catch(() => setProviderInfos([]));
|
|
881
1131
|
return true;
|
|
882
1132
|
}
|
|
883
1133
|
case 'skills': {
|
|
@@ -934,7 +1184,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
934
1184
|
notify: (text, options) => channel.notify(text, options),
|
|
935
1185
|
pushLocal: (title, lines) => channel.pushLocal(title, lines),
|
|
936
1186
|
working: () => channel.working,
|
|
937
|
-
switchModel: (provider, model) =>
|
|
1187
|
+
switchModel: (provider, model) => switchModelRecorded(provider, model),
|
|
938
1188
|
}).catch(() => {
|
|
939
1189
|
// The wizard notifies on every handled failure; this only swallows
|
|
940
1190
|
// an unexpected reject so it never surfaces as an unhandled promise.
|
|
@@ -1027,6 +1277,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1027
1277
|
setHelpOpen(false);
|
|
1028
1278
|
openRewind();
|
|
1029
1279
|
return true;
|
|
1280
|
+
case 'tree': {
|
|
1281
|
+
// The session family tree (pi's Session Tree): every fork branch
|
|
1282
|
+
// stitched back, hover previews, per-node rewind/fork/adopt.
|
|
1283
|
+
setHelpOpen(false);
|
|
1284
|
+
setTreeOpen(true);
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
case 'fork': {
|
|
1288
|
+
// Tip fork (kimi-code semantics): a persisted copy of the whole
|
|
1289
|
+
// conversation the user enters via /resume — the live session and
|
|
1290
|
+
// its running turn stay untouched.
|
|
1291
|
+
setHelpOpen(false);
|
|
1292
|
+
void channel.forkSession();
|
|
1293
|
+
return true;
|
|
1294
|
+
}
|
|
1030
1295
|
case 'exit':
|
|
1031
1296
|
case 'quit':
|
|
1032
1297
|
case 'q':
|
|
@@ -1130,12 +1395,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1130
1395
|
setHelpOpen(false);
|
|
1131
1396
|
void channel.describeCredential('DEEPSEEK_API_KEY')
|
|
1132
1397
|
.catch(() => undefined)
|
|
1133
|
-
.then(status => {
|
|
1398
|
+
.then(async (status) => {
|
|
1134
1399
|
const keyStatus = status === undefined
|
|
1135
1400
|
? t('login-credentials-unavailable')
|
|
1136
1401
|
: status.configured
|
|
1137
1402
|
? t('login-key-configured', { ref: 'DEEPSEEK_API_KEY' })
|
|
1138
1403
|
: t('login-key-missing');
|
|
1404
|
+
// OAuth account states ride along only while a dsh-auth-style
|
|
1405
|
+
// plugin is mounted; absent it the lines below are exactly the
|
|
1406
|
+
// pre-plugin set.
|
|
1407
|
+
const oauth = await channel.oauthProviderStatuses().catch(() => undefined);
|
|
1139
1408
|
channel.pushLocal('/login', [
|
|
1140
1409
|
t('login-api-key', { status: keyStatus }),
|
|
1141
1410
|
...(status === undefined
|
|
@@ -1147,6 +1416,20 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1147
1416
|
}),
|
|
1148
1417
|
]),
|
|
1149
1418
|
t('login-base-url', { url: process.env.DEEPSEEK_BASE_URL ?? t('login-official-endpoint') }),
|
|
1419
|
+
...(oauth === undefined
|
|
1420
|
+
? []
|
|
1421
|
+
: [
|
|
1422
|
+
t('login-oauth-heading'),
|
|
1423
|
+
...oauth.map(row => t('login-oauth-row', {
|
|
1424
|
+
provider: row.provider,
|
|
1425
|
+
state: row.signedIn
|
|
1426
|
+
? t('login-oauth-in', { time: new Date(row.expiresAt ?? 0).toISOString() })
|
|
1427
|
+
: row.expired
|
|
1428
|
+
? t('login-oauth-expired')
|
|
1429
|
+
: t('login-oauth-signed-out'),
|
|
1430
|
+
})),
|
|
1431
|
+
t('login-oauth-hint'),
|
|
1432
|
+
]),
|
|
1150
1433
|
]);
|
|
1151
1434
|
});
|
|
1152
1435
|
return true;
|
|
@@ -1261,6 +1544,100 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1261
1544
|
onUpdate();
|
|
1262
1545
|
}
|
|
1263
1546
|
return true;
|
|
1547
|
+
case 'reload': {
|
|
1548
|
+
// pi-style soft reload: re-read the persisted preference files
|
|
1549
|
+
// (~/.dsh-tui/{theme,lang,agent-preset,model,working-activity}.json)
|
|
1550
|
+
// and re-apply live, honoring the boot-time precedence (env >
|
|
1551
|
+
// cordis.yml > settings user layer > pref). The dsh-tui settings
|
|
1552
|
+
// namespace is NOT re-read here — its watch applies edits live and
|
|
1553
|
+
// the platform watcher hot-reloads settings.yaml itself. What no
|
|
1554
|
+
// reload can re-read (cordis.yml root config, frozen fullscreen,
|
|
1555
|
+
// newly built code) is listed in the footer and served by /restart.
|
|
1556
|
+
setHelpOpen(false);
|
|
1557
|
+
const tuiNamespace = channel.settingsHost()
|
|
1558
|
+
?.listNamespaces()
|
|
1559
|
+
.find(entry => entry.ns === 'dsh-tui');
|
|
1560
|
+
const plan = planReload({
|
|
1561
|
+
envTheme: envThemeOverride(),
|
|
1562
|
+
envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
|
|
1563
|
+
themePref: readThemePref(),
|
|
1564
|
+
currentTheme: themeName,
|
|
1565
|
+
langPref: readLangPref(),
|
|
1566
|
+
currentLang: getLang(),
|
|
1567
|
+
langOverriddenBySettings: tuiNamespace !== undefined && hasPath(tuiNamespace.user, ['lang']),
|
|
1568
|
+
configuredLang: channel.configuredLang,
|
|
1569
|
+
configuredPreset: channel.configuredPreset,
|
|
1570
|
+
presetPref: readPresetPref(),
|
|
1571
|
+
currentPreset: channel.agentPreset,
|
|
1572
|
+
configuredModel: {
|
|
1573
|
+
provider: channel.configuredProvider,
|
|
1574
|
+
model: channel.configuredModel,
|
|
1575
|
+
},
|
|
1576
|
+
modelPref: readModelPref(),
|
|
1577
|
+
currentModel: { provider: channel.provider, model: channel.model },
|
|
1578
|
+
configuredActivity: channel.configuredActivityFrames,
|
|
1579
|
+
activityPref: readActivityFrames(),
|
|
1580
|
+
currentActivity: channel.activityFrames,
|
|
1581
|
+
});
|
|
1582
|
+
for (const item of plan.apply) {
|
|
1583
|
+
switch (item.kind) {
|
|
1584
|
+
case 'theme':
|
|
1585
|
+
setTheme(item.to);
|
|
1586
|
+
break;
|
|
1587
|
+
case 'lang':
|
|
1588
|
+
applyLang(item.to);
|
|
1589
|
+
break;
|
|
1590
|
+
case 'preset':
|
|
1591
|
+
void channel.switchPreset(item.to);
|
|
1592
|
+
break;
|
|
1593
|
+
case 'model':
|
|
1594
|
+
if (item.route !== undefined) {
|
|
1595
|
+
void switchModelRecorded(item.route.provider, item.route.model);
|
|
1596
|
+
}
|
|
1597
|
+
break;
|
|
1598
|
+
case 'activity':
|
|
1599
|
+
channel.setActivityFrames(item.to);
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const lines = [t('reload-header')];
|
|
1604
|
+
for (const item of plan.apply) {
|
|
1605
|
+
lines.push(t('reload-applied', {
|
|
1606
|
+
kind: reloadKindLabel(item.kind),
|
|
1607
|
+
from: item.from,
|
|
1608
|
+
to: item.to,
|
|
1609
|
+
}));
|
|
1610
|
+
}
|
|
1611
|
+
for (const kind of plan.unchanged) {
|
|
1612
|
+
lines.push(t('reload-unchanged', { kind: reloadKindLabel(kind) }));
|
|
1613
|
+
}
|
|
1614
|
+
for (const skip of plan.skipped) {
|
|
1615
|
+
const key = skip.reason === 'env-wins'
|
|
1616
|
+
? 'reload-skipped-env'
|
|
1617
|
+
: skip.reason === 'config-wins' ? 'reload-skipped-config' : 'reload-skipped-invalid';
|
|
1618
|
+
lines.push(t(key, { kind: reloadKindLabel(skip.kind) }));
|
|
1619
|
+
}
|
|
1620
|
+
lines.push(t('reload-footer'));
|
|
1621
|
+
channel.pushLocal('/reload', lines);
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
case 'restart':
|
|
1625
|
+
// pi-style reload tail: /reload cannot re-read boot-time-only state
|
|
1626
|
+
// (cordis.yml root config, frozen fullscreen layout, newly built
|
|
1627
|
+
// code), so /restart respawns the process with the original argv and
|
|
1628
|
+
// resumes this session — the /update handoff minus the pnpm step.
|
|
1629
|
+
setHelpOpen(false);
|
|
1630
|
+
if (onRestart === undefined) {
|
|
1631
|
+
channel.notify(t('restart-unavailable'), { color: 'warning' });
|
|
1632
|
+
}
|
|
1633
|
+
else if (channel.working) {
|
|
1634
|
+
channel.notify(t('update-working'), { color: 'warning' });
|
|
1635
|
+
}
|
|
1636
|
+
else {
|
|
1637
|
+
channel.notify(t('restart-starting'));
|
|
1638
|
+
onRestart();
|
|
1639
|
+
}
|
|
1640
|
+
return true;
|
|
1264
1641
|
case 'vim':
|
|
1265
1642
|
channel.notify(t('vim-not-implemented'));
|
|
1266
1643
|
return true;
|
|
@@ -1271,6 +1648,33 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1271
1648
|
t('terminal-paste-hint', { mod: modLabel }),
|
|
1272
1649
|
]);
|
|
1273
1650
|
return true;
|
|
1651
|
+
case 'recap': {
|
|
1652
|
+
// `/recap`(pi-recap 语义):对会话最近活动做一次无工具单轮
|
|
1653
|
+
// 调用,生成一行摘要 + 建议标题。摘要是纯 UI 状态(不进 transcript
|
|
1654
|
+
// 也不进 session log);建议标题经「应用」按钮走 /rename 路径。
|
|
1655
|
+
setHelpOpen(false);
|
|
1656
|
+
recapAbortRef.current?.abort();
|
|
1657
|
+
const controller = new AbortController();
|
|
1658
|
+
recapAbortRef.current = controller;
|
|
1659
|
+
setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: false, expanded: true });
|
|
1660
|
+
void channel.recapRecent({
|
|
1661
|
+
signal: controller.signal,
|
|
1662
|
+
onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
|
|
1663
|
+
}).then(result => {
|
|
1664
|
+
if (controller.signal.aborted)
|
|
1665
|
+
return;
|
|
1666
|
+
setRecap(prev => (prev
|
|
1667
|
+
? {
|
|
1668
|
+
...prev,
|
|
1669
|
+
summary: result.summary ?? prev.raw,
|
|
1670
|
+
title: result.title,
|
|
1671
|
+
error: result.error,
|
|
1672
|
+
done: true,
|
|
1673
|
+
}
|
|
1674
|
+
: prev));
|
|
1675
|
+
});
|
|
1676
|
+
return true;
|
|
1677
|
+
}
|
|
1274
1678
|
case 'btw': {
|
|
1275
1679
|
// CC /btw:单轮无工具侧问,overlay 态纯 UI,不打断主回合、不写
|
|
1276
1680
|
// 会话历史。空参数只提示用法。
|
|
@@ -1621,6 +2025,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1621
2025
|
// search box, which Chat would otherwise route into the prompt.
|
|
1622
2026
|
if (browserOpen)
|
|
1623
2027
|
return;
|
|
2028
|
+
// Same for the session tree: plain letters drive its search, clicks and
|
|
2029
|
+
// Enter drive its action menu.
|
|
2030
|
+
if (treeOpen)
|
|
2031
|
+
return;
|
|
1624
2032
|
// Same for the settings screen: plain letters (s save / d discard) and
|
|
1625
2033
|
// the field draft editor belong to it alone.
|
|
1626
2034
|
if (settingsOpen)
|
|
@@ -1881,29 +2289,55 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1881
2289
|
return;
|
|
1882
2290
|
}
|
|
1883
2291
|
if (overlay.kind === 'model') {
|
|
2292
|
+
// Two-level picker: group rows at the top (Enter drills in), one
|
|
2293
|
+
// provider's models below (Enter switches, the same live-fork path as
|
|
2294
|
+
// the flat picker always had). Esc/⌫ climbs one level and only closes
|
|
2295
|
+
// at the top; a single-group catalog never shows the group level, so
|
|
2296
|
+
// Esc there closes directly.
|
|
2297
|
+
const rowCount = activeModelGroup === undefined ? modelGroups.length : groupModels.length;
|
|
1884
2298
|
if (key.upArrow || key.downArrow) {
|
|
1885
|
-
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count:
|
|
2299
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rowCount });
|
|
1886
2300
|
}
|
|
1887
2301
|
else if (plainReturn) {
|
|
1888
|
-
|
|
2302
|
+
if (activeModelGroup === undefined) {
|
|
2303
|
+
const group = modelGroups[overlay.index];
|
|
2304
|
+
if (!group) {
|
|
2305
|
+
dispatchOverlay({ type: 'close' });
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
setModelGroup(group.provider);
|
|
2309
|
+
// The recents group opens on its most-recent entry; a provider
|
|
2310
|
+
// group on its current model when it owns one, else its first row.
|
|
2311
|
+
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
2312
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
|
|
2316
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
const model = groupModels[overlay.index];
|
|
1889
2320
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
1890
2321
|
if (model) {
|
|
1891
2322
|
// Enter switches the live model right away: the conversation is
|
|
1892
2323
|
// forked at its end and continued with an agent routed to the new
|
|
1893
|
-
// model (history replays unchanged).
|
|
2324
|
+
// model (history replays unchanged) — and feeds the recents group.
|
|
1894
2325
|
dispatchOverlay({ type: 'close' });
|
|
1895
|
-
|
|
1896
|
-
void channel.switchModel(model.provider, model.id).then((ok) => {
|
|
1897
|
-
if (ok)
|
|
1898
|
-
channel.notify(t('model-switched', { name: model.name }));
|
|
1899
|
-
});
|
|
2326
|
+
void switchModelRecorded(model.provider, model.id, model.name);
|
|
1900
2327
|
}
|
|
1901
2328
|
else {
|
|
1902
2329
|
dispatchOverlay({ type: 'close' });
|
|
1903
2330
|
}
|
|
1904
2331
|
}
|
|
1905
|
-
else if (key.escape) {
|
|
1906
|
-
|
|
2332
|
+
else if (key.escape || key.backspace) {
|
|
2333
|
+
if (activeModelGroup !== undefined && modelGroups.length > 1 && !modelPickerDirect) {
|
|
2334
|
+
setModelGroup(undefined);
|
|
2335
|
+
const groupIndex = Math.max(0, modelGroups.findIndex(group => group.provider === activeModelGroup));
|
|
2336
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: groupIndex });
|
|
2337
|
+
}
|
|
2338
|
+
else {
|
|
2339
|
+
dispatchOverlay({ type: 'close' });
|
|
2340
|
+
}
|
|
1907
2341
|
}
|
|
1908
2342
|
return;
|
|
1909
2343
|
}
|
|
@@ -1942,6 +2376,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1942
2376
|
}
|
|
1943
2377
|
return;
|
|
1944
2378
|
}
|
|
2379
|
+
if (overlay.kind === 'color') {
|
|
2380
|
+
if (key.upArrow || key.downArrow) {
|
|
2381
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: SESSION_COLOR_NAMES.length });
|
|
2382
|
+
}
|
|
2383
|
+
else if (plainReturn) {
|
|
2384
|
+
const name = SESSION_COLOR_NAMES[overlay.index];
|
|
2385
|
+
dispatchOverlay({ type: 'close' });
|
|
2386
|
+
if (name) {
|
|
2387
|
+
channel.setSessionColor(name);
|
|
2388
|
+
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
else if (key.escape) {
|
|
2392
|
+
dispatchOverlay({ type: 'close' });
|
|
2393
|
+
}
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
1945
2396
|
if (overlay.kind === 'effort') {
|
|
1946
2397
|
if (key.leftArrow || key.rightArrow) {
|
|
1947
2398
|
const delta = key.leftArrow ? -1 : 1;
|
|
@@ -2065,8 +2516,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2065
2516
|
dispatchOverlay({ type: 'move', delta: -1, count: historyMatches.length });
|
|
2066
2517
|
}
|
|
2067
2518
|
}
|
|
2068
|
-
else if (key.downArrow || (
|
|
2069
|
-
// CC's historySearch:next — ↓ and
|
|
2519
|
+
else if (key.downArrow || actionMatches('history', input, key)) {
|
|
2520
|
+
// CC's historySearch:next — ↓ and the history key (default Ctrl+R)
|
|
2521
|
+
// walk to the next match.
|
|
2070
2522
|
if (historyMatches.length > 0) {
|
|
2071
2523
|
dispatchOverlay({ type: 'move', delta: 1, count: historyMatches.length });
|
|
2072
2524
|
}
|
|
@@ -2172,24 +2624,41 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2172
2624
|
}
|
|
2173
2625
|
return;
|
|
2174
2626
|
}
|
|
2175
|
-
if (
|
|
2176
|
-
//
|
|
2627
|
+
if (overlay.kind === 'file-actions') {
|
|
2628
|
+
// Click-to-act file menu: ↑/↓ move, Enter runs the focused action,
|
|
2629
|
+
// Esc closes.
|
|
2630
|
+
if (key.upArrow || key.downArrow) {
|
|
2631
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: FILE_ACTION_COUNT });
|
|
2632
|
+
}
|
|
2633
|
+
else if (plainReturn) {
|
|
2634
|
+
const path = overlay.path;
|
|
2635
|
+
dispatchOverlay({ type: 'close' });
|
|
2636
|
+
runFileAction(overlay.index, path);
|
|
2637
|
+
}
|
|
2638
|
+
else if (key.escape) {
|
|
2639
|
+
dispatchOverlay({ type: 'close' });
|
|
2640
|
+
}
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
if (actionMatches('trajectory', input, key)) {
|
|
2644
|
+
// The trajectory scene key (default Ctrl+T) opens it at any point in
|
|
2645
|
+
// the session.
|
|
2177
2646
|
openScene();
|
|
2178
2647
|
return;
|
|
2179
2648
|
}
|
|
2180
|
-
if (
|
|
2181
|
-
// Ctrl+A opens the
|
|
2649
|
+
if (actionMatches('dashboard', input, key)) {
|
|
2650
|
+
// The subagent dashboard key (default Ctrl+A) opens the dashboard.
|
|
2182
2651
|
setSubagentDashboardOpen(true);
|
|
2183
2652
|
return;
|
|
2184
2653
|
}
|
|
2185
|
-
if (
|
|
2186
|
-
//
|
|
2187
|
-
// screen (transcript still empty); once rows take
|
|
2188
|
-
// panel disappears the key has nothing left to do.
|
|
2654
|
+
if (actionMatches('contextPanel', input, key) && loadedContextVisible) {
|
|
2655
|
+
// The loaded-context panel key (default Ctrl+P) toggles the startup
|
|
2656
|
+
// panel while it is on screen (transcript still empty); once rows take
|
|
2657
|
+
// over and the panel disappears the key has nothing left to do.
|
|
2189
2658
|
toggleLoadedContext();
|
|
2190
2659
|
return;
|
|
2191
2660
|
}
|
|
2192
|
-
if (
|
|
2661
|
+
if (actionMatches('history', input, key) && !helpOpen) {
|
|
2193
2662
|
setHistoryEntries(loadHistory());
|
|
2194
2663
|
dispatchOverlay({
|
|
2195
2664
|
type: 'open',
|
|
@@ -2231,11 +2700,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2231
2700
|
}
|
|
2232
2701
|
event.stopImmediatePropagation();
|
|
2233
2702
|
}
|
|
2234
|
-
else if (
|
|
2235
|
-
// Leaving transcript mode (Ctrl+O) — search was already
|
|
2236
|
-
// Help is modal: toggling this state behind the
|
|
2237
|
-
// then the next `/` unexpectedly opens
|
|
2238
|
-
// slash-command completion after Help
|
|
2703
|
+
else if (actionMatches('transcript', input, key) && !helpOpen) {
|
|
2704
|
+
// Leaving transcript mode (default Ctrl+O) — search was already
|
|
2705
|
+
// handled above. Help is modal: toggling this state behind the
|
|
2706
|
+
// overlay is invisible, then the next `/` unexpectedly opens
|
|
2707
|
+
// transcript search instead of slash-command completion after Help
|
|
2708
|
+
// closes.
|
|
2239
2709
|
setExpanded(previous => !previous);
|
|
2240
2710
|
// The toggle rewrites every thinking row's layout at once. The
|
|
2241
2711
|
// ordinary scroll-based diff pushes rows into terminal scrollback on
|
|
@@ -2280,17 +2750,18 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2280
2750
|
requestExit();
|
|
2281
2751
|
}
|
|
2282
2752
|
}
|
|
2283
|
-
else if (
|
|
2284
|
-
// CC's app:redraw — clear the physical terminal and
|
|
2753
|
+
else if (actionMatches('redraw', input, key)) {
|
|
2754
|
+
// CC's app:redraw (default Ctrl+L) — clear the physical terminal and
|
|
2755
|
+
// repaint.
|
|
2285
2756
|
instances.get(process.stdout)?.forceRedraw();
|
|
2286
2757
|
}
|
|
2287
|
-
else if (
|
|
2758
|
+
else if (actionMatches('showAll', input, key)) {
|
|
2288
2759
|
setShowAllMessages(previous => !previous);
|
|
2289
2760
|
}
|
|
2290
|
-
else if (
|
|
2291
|
-
// Fold/unfold the GoalTodoPanel todo section — works
|
|
2292
|
-
// the collapsed line keeps the done/total count and the
|
|
2293
|
-
// preview, so long todo lists stop crowding the prompt.
|
|
2761
|
+
else if (actionMatches('todoFold', input, key)) {
|
|
2762
|
+
// Fold/unfold the GoalTodoPanel todo section (default Ctrl+Q) — works
|
|
2763
|
+
// mid-turn too: the collapsed line keeps the done/total count and the
|
|
2764
|
+
// live task preview, so long todo lists stop crowding the prompt.
|
|
2294
2765
|
setTodoCollapsed(previous => !previous);
|
|
2295
2766
|
}
|
|
2296
2767
|
else if (plainReturn && !isSticky) {
|
|
@@ -2356,6 +2827,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2356
2827
|
// hosts are already in it and must not nest a second one.
|
|
2357
2828
|
return fullscreen ? browser : _jsx(AlternateScreen, { children: browser });
|
|
2358
2829
|
}
|
|
2830
|
+
// The session tree follows the browser's rule exactly: it REPLACES the
|
|
2831
|
+
// conversation (an early return after every hook above has run), so there
|
|
2832
|
+
// is no transcript underneath to be repainted or bled through. The dropped
|
|
2833
|
+
// turn's prompt returns through the same fill path a rewind picker uses.
|
|
2834
|
+
if (treeOpen) {
|
|
2835
|
+
const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
|
|
2836
|
+
setHistoryFill(text);
|
|
2837
|
+
} }));
|
|
2838
|
+
return fullscreen ? tree : _jsx(AlternateScreen, { children: tree });
|
|
2839
|
+
}
|
|
2359
2840
|
// The settings screen follows the browser's rule exactly: it REPLACES the
|
|
2360
2841
|
// conversation (an early return after every hook above has run), so there
|
|
2361
2842
|
// is no transcript underneath to be repainted or bled through.
|
|
@@ -2444,7 +2925,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2444
2925
|
// transcript mount batches (and the first wheel events) for the
|
|
2445
2926
|
// frame budget right when the user wants to read history. Fresh
|
|
2446
2927
|
// sessions keep the full intro; restored ones settle instantly.
|
|
2447
|
-
skipIntro: 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: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamFoldedRows: streamFoldedRows, onToggleStreamFold: toggleStreamFolded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, 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: (agentId) => setSubagentDetailId(agentId) })] }), (() => {
|
|
2928
|
+
skipIntro: 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: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamFoldedRows: streamFoldedRows, onToggleStreamFold: toggleStreamFolded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, 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: (agentId) => setSubagentDetailId(agentId), onOpenFile: openFileActions })] }), (() => {
|
|
2448
2929
|
// Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
|
|
2449
2930
|
// rail (default), the proportional scrollbar, or nothing. The
|
|
2450
2931
|
// slot keeps its 2 columns in both rendered modes (Qwen's
|
|
@@ -2467,11 +2948,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2467
2948
|
// the animated chars/4 estimate, matching the classic
|
|
2468
2949
|
// spinner's counter (the suffix used raw chars before,
|
|
2469
2950
|
// inflating the reading next to a real upload number).
|
|
2470
|
-
suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : 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' }) }) })) :
|
|
2951
|
+
suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _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() })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : 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: () => {
|
|
2952
|
+
// An expanded auto recap collapses back to its dim row;
|
|
2953
|
+
// a manual /recap closes outright.
|
|
2954
|
+
if (recap.auto) {
|
|
2955
|
+
setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
|
|
2956
|
+
}
|
|
2957
|
+
else {
|
|
2958
|
+
closeRecap();
|
|
2959
|
+
}
|
|
2960
|
+
}, onCopy: () => {
|
|
2961
|
+
void setClipboard(recap.summary ?? '').then(raw => { if (raw)
|
|
2962
|
+
writeRaw?.(raw); });
|
|
2963
|
+
channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
|
|
2964
|
+
}, onApplyTitle: () => {
|
|
2965
|
+
if (recap.title === undefined || recap.titleApplied)
|
|
2966
|
+
return;
|
|
2967
|
+
channel.renameSession(recap.title);
|
|
2968
|
+
setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
|
|
2969
|
+
channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
|
|
2970
|
+
} }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
|
|
2471
2971
|
void setClipboard(btw.answer ?? '').then(raw => { if (raw)
|
|
2472
2972
|
writeRaw?.(raw); });
|
|
2473
2973
|
channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
|
|
2474
|
-
} }) })) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent()
|
|
2974
|
+
} }) })) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
|
|
2975
|
+
? draft => questionStore.backCurrent(draft)
|
|
2976
|
+
: undefined }, questionSnapshot.key)) : (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
|
|
2475
2977
|
? undefined
|
|
2476
2978
|
: {
|
|
2477
2979
|
band: wakeBand,
|
|
@@ -2500,17 +3002,27 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2500
3002
|
return;
|
|
2501
3003
|
dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
|
|
2502
3004
|
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
2503
|
-
} }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, {
|
|
3005
|
+
} }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : activeModelGroup === undefined ? (_jsx(ModelPicker, { groups: modelGroups, focusIndex: overlay.index, currentProvider: channel.provider, onPick: (index) => {
|
|
3006
|
+
// 点击分组行 = 进入该组(与 Enter 同一条路径)
|
|
3007
|
+
const group = modelGroups[index];
|
|
3008
|
+
if (!group)
|
|
3009
|
+
return;
|
|
3010
|
+
setModelGroup(group.provider);
|
|
3011
|
+
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
3012
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
|
|
3016
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
3017
|
+
} })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
|
|
3018
|
+
? t('picker-group-recent')
|
|
3019
|
+
: modelGroups.find(group => group.provider === activeModelGroup)?.label, showBack: modelGroups.length > 1 && !modelPickerDirect, showProviderPrefix: activeModelGroup === RECENTS_GROUP_PROVIDER, focusIndex: overlay.index, currentModel: `${channel.provider}/${channel.model}`, onPick: (index) => {
|
|
2504
3020
|
// 点击行 = 应用该行模型(与 Enter 同一条路径)
|
|
2505
|
-
const model =
|
|
3021
|
+
const model = groupModels[index];
|
|
2506
3022
|
if (!model)
|
|
2507
3023
|
return;
|
|
2508
3024
|
dispatchOverlay({ type: 'close' });
|
|
2509
|
-
|
|
2510
|
-
void channel.switchModel(model.provider, model.id).then((ok) => {
|
|
2511
|
-
if (ok)
|
|
2512
|
-
channel.notify(t('model-switched', { name: model.name }));
|
|
2513
|
-
});
|
|
3025
|
+
void switchModelRecorded(model.provider, model.id, model.name);
|
|
2514
3026
|
} })) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
|
|
2515
3027
|
const skill = skillsList[index];
|
|
2516
3028
|
if (!skill)
|
|
@@ -2523,6 +3035,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2523
3035
|
const name = PRESET_NAMES[index];
|
|
2524
3036
|
if (name)
|
|
2525
3037
|
channel.setActivityFrames(name);
|
|
3038
|
+
} }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
|
|
3039
|
+
dispatchOverlay({ type: 'close' });
|
|
3040
|
+
const name = SESSION_COLOR_NAMES[index];
|
|
3041
|
+
if (name) {
|
|
3042
|
+
channel.setSessionColor(name);
|
|
3043
|
+
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
3044
|
+
}
|
|
2526
3045
|
} }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
|
|
2527
3046
|
// 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
|
|
2528
3047
|
onPick: (index) => {
|
|
@@ -2590,6 +3109,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2590
3109
|
const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
|
|
2591
3110
|
dispatchOverlay({ type: 'close' });
|
|
2592
3111
|
void performRewind(row, mode);
|
|
3112
|
+
} }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
|
|
3113
|
+
// 点击行直接执行该动作(与 Enter 同路径)
|
|
3114
|
+
const path = overlay.path;
|
|
3115
|
+
dispatchOverlay({ type: 'close' });
|
|
3116
|
+
runFileAction(index, path);
|
|
2593
3117
|
} }) })), overlay.kind === 'search' && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
|
|
2594
3118
|
}
|
|
2595
3119
|
/**
|