@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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Channel } from '../dsh-adapter/channel.js';
|
|
3
|
+
/**
|
|
4
|
+
* The session family tree — `/tree` as a screen of its own (pi's Session
|
|
5
|
+
* Tree over DSH's cross-session fork model).
|
|
6
|
+
*
|
|
7
|
+
* The tree shows every branch the conversation ever took: each rewind fork
|
|
8
|
+
* is stitched back onto the message it diverged from, the live path is
|
|
9
|
+
* marked with `•`, and every other branch stays reachable. Picking a node
|
|
10
|
+
* offers the ways DSH can act on it — rewind (drop the turn, edit its
|
|
11
|
+
* prompt), fork (keep the entry, branch here), adopt (switch to a dead
|
|
12
|
+
* branch whole).
|
|
13
|
+
*
|
|
14
|
+
* Interaction is mouse-first but keyboard-complete: hovering a row shows its
|
|
15
|
+
* full text in the preview pane, clicking it opens the action menu, and the
|
|
16
|
+
* same actions sit on Enter / Ctrl+F / Ctrl+B for the keyboard path. The
|
|
17
|
+
* confirm seat stands between every high-risk action and the fork.
|
|
18
|
+
*/
|
|
19
|
+
export declare function SessionTree({ channel, currentSessionId, onClose, onRestoreText, }: {
|
|
20
|
+
channel: Channel;
|
|
21
|
+
/** Live session id at open time (adopt-live refusal, menu labels). */
|
|
22
|
+
currentSessionId: string;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
/** The dropped turn's prompt goes back into the input for re-editing. */
|
|
25
|
+
onRestoreText: (text: string) => void;
|
|
26
|
+
}): React.ReactNode;
|
|
27
|
+
//# sourceMappingURL=SessionTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionTree.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAqIxD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,gBAAgB,EAChB,OAAO,EACP,aAAa,GACd,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,yEAAyE;IACzE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC,GAAG,KAAK,CAAC,SAAS,CAselB"}
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput, useTerminalSize } from '../ui.js';
|
|
4
|
+
import { Divider } from '../components/design-system/Divider.js';
|
|
5
|
+
import { HintLine } from '../components/design-system/HintLine.js';
|
|
6
|
+
import { SearchBox } from '../components/SearchBox.js';
|
|
7
|
+
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
8
|
+
import { isMod, isPlainReturn, modLabel } from '../utils/modifiers.js';
|
|
9
|
+
import { spreadRow, tailWidth, truncateWidth, wrapWidth } from '../sessions/format.js';
|
|
10
|
+
import { stringWidth } from '../ink/stringWidth.js';
|
|
11
|
+
import { t } from '../i18n.js';
|
|
12
|
+
import { droppedTurnInfo, filterTree, flattenTree, nearestVisibleIndex, TREE_FILTERS, } from '../dsh-adapter/sessionTree.js';
|
|
13
|
+
/** Local HH:MM (or MM/DD HH:MM when older than this year). */
|
|
14
|
+
function formatTime(at, now) {
|
|
15
|
+
const date = new Date(at);
|
|
16
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
17
|
+
const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
18
|
+
const sameYear = new Date(now).getFullYear() === date.getFullYear();
|
|
19
|
+
return sameYear ? time : `${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${time}`;
|
|
20
|
+
}
|
|
21
|
+
/** A thrown value's message, for a notification that has to say something. */
|
|
22
|
+
function message(error) {
|
|
23
|
+
return error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
25
|
+
/** Rendered width of a hint: `HintLine` strips the `**` emphasis markers. */
|
|
26
|
+
function hintWidth(text) {
|
|
27
|
+
return stringWidth(text.replace(/\*\*/gu, ''));
|
|
28
|
+
}
|
|
29
|
+
/** The widest hint that fits; cut only when even the shortest will not. */
|
|
30
|
+
function fitHint(candidates, budget) {
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (hintWidth(candidate) <= budget)
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
return truncateWidth((candidates[candidates.length - 1] ?? '').replace(/\*\*/gu, ''), budget);
|
|
36
|
+
}
|
|
37
|
+
/** Terminal cells of each kind prefix shown before the entry text. */
|
|
38
|
+
const KIND_PREFIX = {
|
|
39
|
+
user: { text: 'user: ', color: 'suggestion' },
|
|
40
|
+
assistant: { text: 'assistant: ', color: 'success' },
|
|
41
|
+
tool: { text: '', color: 'subtle' },
|
|
42
|
+
compact: { text: '[compact] ', color: 'planMode' },
|
|
43
|
+
interrupt: { text: '', color: 'warning' },
|
|
44
|
+
notice: { text: '', color: 'warning' },
|
|
45
|
+
};
|
|
46
|
+
/** Tool outcome glyph appended to a tool row. */
|
|
47
|
+
function toolGlyph(entry) {
|
|
48
|
+
if (entry.toolStatus === 'ok')
|
|
49
|
+
return { glyph: ' ✓', color: 'success' };
|
|
50
|
+
if (entry.toolStatus === 'error')
|
|
51
|
+
return { glyph: ' ✗', color: 'error' };
|
|
52
|
+
if (entry.toolStatus === 'running')
|
|
53
|
+
return { glyph: ' …', color: 'warning' };
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* pi's tree prefix: gutters at recorded positions, connector at indent-1.
|
|
58
|
+
* Each indent level is 3 cells (`│ `, `├─ `, `└─ `).
|
|
59
|
+
*/
|
|
60
|
+
function treePrefix(flatNode) {
|
|
61
|
+
const displayIndent = flatNode.multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
|
|
62
|
+
const hasConnector = flatNode.showConnector && !flatNode.isVirtualRootChild;
|
|
63
|
+
const connectorPosition = hasConnector ? displayIndent - 1 : -1;
|
|
64
|
+
const chars = [];
|
|
65
|
+
for (let i = 0; i < displayIndent * 3; i++) {
|
|
66
|
+
const level = Math.floor(i / 3);
|
|
67
|
+
const posInLevel = i % 3;
|
|
68
|
+
const gutter = flatNode.gutters.find(g => g.position === level);
|
|
69
|
+
if (gutter !== undefined) {
|
|
70
|
+
chars.push(posInLevel === 0 ? (gutter.show ? '│' : ' ') : ' ');
|
|
71
|
+
}
|
|
72
|
+
else if (level === connectorPosition) {
|
|
73
|
+
if (posInLevel === 0)
|
|
74
|
+
chars.push(flatNode.isLast ? '└' : '├');
|
|
75
|
+
else if (posInLevel === 1)
|
|
76
|
+
chars.push('─');
|
|
77
|
+
else
|
|
78
|
+
chars.push(' ');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
chars.push(' ');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return chars.join('');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Keep the LAST `budget` display cells of a prefix, prefixing '…' when any
|
|
88
|
+
* level was cut — narrow terminals sacrifice the oldest ancestors, never the
|
|
89
|
+
* branch geometry next to the entry.
|
|
90
|
+
*/
|
|
91
|
+
function clampPrefix(prefix, budget) {
|
|
92
|
+
if (stringWidth(prefix) <= budget)
|
|
93
|
+
return prefix;
|
|
94
|
+
if (budget <= 1)
|
|
95
|
+
return '…';
|
|
96
|
+
return `…${tailWidth(prefix, budget - 1)}`;
|
|
97
|
+
}
|
|
98
|
+
/** Rows the layout cannot do without: header, search box, hints. */
|
|
99
|
+
const MANDATORY_LINES = 3;
|
|
100
|
+
/** Both dividers, in the order they earn their row (header rule first). */
|
|
101
|
+
const RULE_PRIORITY = [0, 1];
|
|
102
|
+
/** Width from which the preview pane sits beside the list instead of below. */
|
|
103
|
+
const SPLIT_MIN_COLUMNS = 100;
|
|
104
|
+
/** Lines the bottom preview block may take on narrow terminals. */
|
|
105
|
+
const PREVIEW_BLOCK_LINES = 5;
|
|
106
|
+
/**
|
|
107
|
+
* The session family tree — `/tree` as a screen of its own (pi's Session
|
|
108
|
+
* Tree over DSH's cross-session fork model).
|
|
109
|
+
*
|
|
110
|
+
* The tree shows every branch the conversation ever took: each rewind fork
|
|
111
|
+
* is stitched back onto the message it diverged from, the live path is
|
|
112
|
+
* marked with `•`, and every other branch stays reachable. Picking a node
|
|
113
|
+
* offers the ways DSH can act on it — rewind (drop the turn, edit its
|
|
114
|
+
* prompt), fork (keep the entry, branch here), adopt (switch to a dead
|
|
115
|
+
* branch whole).
|
|
116
|
+
*
|
|
117
|
+
* Interaction is mouse-first but keyboard-complete: hovering a row shows its
|
|
118
|
+
* full text in the preview pane, clicking it opens the action menu, and the
|
|
119
|
+
* same actions sit on Enter / Ctrl+F / Ctrl+B for the keyboard path. The
|
|
120
|
+
* confirm seat stands between every high-risk action and the fork.
|
|
121
|
+
*/
|
|
122
|
+
export function SessionTree({ channel, currentSessionId, onClose, onRestoreText, }) {
|
|
123
|
+
const { columns, rows } = useTerminalSize();
|
|
124
|
+
const isTerminalFocused = useTerminalFocus();
|
|
125
|
+
const [data, setData] = React.useState(null);
|
|
126
|
+
const [loading, setLoading] = React.useState(true);
|
|
127
|
+
/** A rewind/fork/adopt is in flight — the seat stays up until it settles. */
|
|
128
|
+
const [busy, setBusy] = React.useState(false);
|
|
129
|
+
const [notice, setNotice] = React.useState();
|
|
130
|
+
// The cursor is a node id, not an index: filters and searches rebuild the
|
|
131
|
+
// row list, and an index would silently land on a different node.
|
|
132
|
+
const [selectedId, setSelectedId] = React.useState(null);
|
|
133
|
+
const [filter, setFilter] = React.useState('default');
|
|
134
|
+
const [query, setQuery] = React.useState('');
|
|
135
|
+
/** The hovered node id — drives the preview pane alongside the cursor. */
|
|
136
|
+
const [hoverId, setHoverId] = React.useState(null);
|
|
137
|
+
const [menuNodeId, setMenuNodeId] = React.useState(null);
|
|
138
|
+
const [menuIndex, setMenuIndex] = React.useState(0);
|
|
139
|
+
const [confirmHovered, setConfirmHovered] = React.useState(false);
|
|
140
|
+
const [confirmState, setConfirmState] = React.useState(null);
|
|
141
|
+
// React batches every parsed key from one stdin chunk; actions lock in a
|
|
142
|
+
// ref so a repeated Enter cannot start the same async operation twice.
|
|
143
|
+
const actionPendingRef = React.useRef(false);
|
|
144
|
+
// ── Load ────────────────────────────────────────────────────────────────
|
|
145
|
+
// A build that settles null/undefined closes the screen: the channel
|
|
146
|
+
// already toasted the reason (persistence absent) or the live session
|
|
147
|
+
// swapped mid-build (silent by design — the reopened tree rebuilds on the
|
|
148
|
+
// new session).
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
let live = true;
|
|
151
|
+
void channel
|
|
152
|
+
.buildSessionTree()
|
|
153
|
+
.then(result => (live ? result : undefined))
|
|
154
|
+
.catch(() => undefined)
|
|
155
|
+
.then(result => {
|
|
156
|
+
if (!live)
|
|
157
|
+
return;
|
|
158
|
+
if (result === null || result === undefined) {
|
|
159
|
+
onClose();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
setData(result);
|
|
163
|
+
setLoading(false);
|
|
164
|
+
});
|
|
165
|
+
return () => {
|
|
166
|
+
live = false;
|
|
167
|
+
};
|
|
168
|
+
}, [channel, onClose]);
|
|
169
|
+
// ── Derived view ────────────────────────────────────────────────────────
|
|
170
|
+
const full = React.useMemo(() => (data === null ? [] : flattenTree(data.roots, data.activeLeafId)), [data]);
|
|
171
|
+
const visible = React.useMemo(() => (data === null ? [] : filterTree(full, data.activeLeafId, filter, query)), [data, full, filter, query]);
|
|
172
|
+
const focusIndex = React.useMemo(() => {
|
|
173
|
+
if (visible.length === 0)
|
|
174
|
+
return 0;
|
|
175
|
+
return nearestVisibleIndex(visible, full, selectedId ?? data?.activeLeafId ?? null);
|
|
176
|
+
}, [visible, full, selectedId, data?.activeLeafId]);
|
|
177
|
+
const focusedNode = visible[focusIndex];
|
|
178
|
+
const focusedEntry = focusedNode?.node.entry ?? null;
|
|
179
|
+
// The menu rides the PICKED node id (not the cursor): moving the cursor
|
|
180
|
+
// while the menu is open must not retarget an action.
|
|
181
|
+
const menuNode = menuNodeId === null
|
|
182
|
+
? undefined
|
|
183
|
+
: visible.find(n => n.node.id === menuNodeId) ?? full.find(n => n.node.id === menuNodeId);
|
|
184
|
+
const menuEntry = menuNode?.node.entry ?? null;
|
|
185
|
+
const menuOpen = menuNode !== undefined && menuEntry !== null;
|
|
186
|
+
const seat = confirmState !== null ? 'confirm' : menuOpen ? 'menu' : 'tree';
|
|
187
|
+
// Preview target: the hovered row wins (that is what the eye is asking
|
|
188
|
+
// about), the focused row backs it up so keyboard users see one too.
|
|
189
|
+
const previewNode = (hoverId !== null ? visible.find(n => n.node.id === hoverId) : undefined) ?? focusedNode;
|
|
190
|
+
const previewEntry = previewNode?.node.entry ?? null;
|
|
191
|
+
// ── Actions ─────────────────────────────────────────────────────────────
|
|
192
|
+
/** Pre-validate a rewind pick the way rewindToNode will refuse it. */
|
|
193
|
+
const rewindBlockedReason = (entry) => {
|
|
194
|
+
if (entry.firstTurn === true && entry.kind === 'user')
|
|
195
|
+
return t('tree-first-message');
|
|
196
|
+
return undefined;
|
|
197
|
+
};
|
|
198
|
+
/** Build the click menu's options for one entry. */
|
|
199
|
+
const menuOptionsFor = (entry) => {
|
|
200
|
+
const options = [];
|
|
201
|
+
const blocked = rewindBlockedReason(entry);
|
|
202
|
+
options.push({
|
|
203
|
+
id: 'rewind',
|
|
204
|
+
label: t('tree-menu-rewind'),
|
|
205
|
+
detail: blocked ?? t('tree-menu-rewind-detail'),
|
|
206
|
+
...(blocked !== undefined ? { disabled: true } : {}),
|
|
207
|
+
});
|
|
208
|
+
options.push({ id: 'fork', label: t('tree-menu-fork'), detail: t('tree-menu-fork-detail') });
|
|
209
|
+
const tip = data?.rewindFacts.get(entry.sessionId)?.tipBoundary;
|
|
210
|
+
if (entry.sessionId !== currentSessionId) {
|
|
211
|
+
options.push(tip === undefined
|
|
212
|
+
? { id: 'adopt', label: t('tree-menu-adopt'), detail: t('tree-adopt-unavailable'), disabled: true }
|
|
213
|
+
: { id: 'adopt', label: t('tree-menu-adopt'), detail: t('tree-menu-adopt-detail') });
|
|
214
|
+
}
|
|
215
|
+
options.push({ id: 'cancel', label: t('tree-menu-cancel'), detail: '' });
|
|
216
|
+
return options;
|
|
217
|
+
};
|
|
218
|
+
const options = menuOpen && menuEntry !== null ? menuOptionsFor(menuEntry) : [];
|
|
219
|
+
const runAction = (action) => {
|
|
220
|
+
if (actionPendingRef.current)
|
|
221
|
+
return;
|
|
222
|
+
actionPendingRef.current = true;
|
|
223
|
+
void action().finally(() => {
|
|
224
|
+
actionPendingRef.current = false;
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Execute a rewind/fork at an entry (menu rows, Ctrl+F). The seat stays up
|
|
229
|
+
* while the swap runs; success closes the screen and hands the dropped
|
|
230
|
+
* turn's prompt back to the input.
|
|
231
|
+
*/
|
|
232
|
+
const executeAt = (entry, mode) => {
|
|
233
|
+
runAction(async () => {
|
|
234
|
+
setBusy(true);
|
|
235
|
+
try {
|
|
236
|
+
const text = await channel.rewindToNode(entry.sessionId, entry.seq, mode);
|
|
237
|
+
if (text === null) {
|
|
238
|
+
// The channel notified the transcript (hidden behind this screen),
|
|
239
|
+
// so repeat the refusal on screen and stay put.
|
|
240
|
+
setNotice({ text: t('tree-refused'), tone: 'error' });
|
|
241
|
+
setBusy(false);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (text !== '')
|
|
245
|
+
onRestoreText(text);
|
|
246
|
+
channel.notify(mode === 'fork' ? t('tree-forked') : t('tree-rewound'));
|
|
247
|
+
onClose();
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
setNotice({ text: t('tree-rewind-failed', { message: message(error) }), tone: 'error' });
|
|
251
|
+
setBusy(false);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
/** Adopt = fork the branch at its tip with the whole log kept. */
|
|
256
|
+
const executeAdopt = (entry, tipSeq) => {
|
|
257
|
+
runAction(async () => {
|
|
258
|
+
setBusy(true);
|
|
259
|
+
try {
|
|
260
|
+
const text = await channel.rewindToNode(entry.sessionId, tipSeq, 'rewind');
|
|
261
|
+
if (text === null) {
|
|
262
|
+
setNotice({ text: t('tree-refused'), tone: 'error' });
|
|
263
|
+
setBusy(false);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
channel.notify(t('tree-adopted'));
|
|
267
|
+
onClose();
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
setNotice({ text: t('tree-rewind-failed', { message: message(error) }), tone: 'error' });
|
|
271
|
+
setBusy(false);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
/** A rewind pick (Enter path and the menu's rewind option): confirm seat. */
|
|
276
|
+
const askRewind = (entry) => {
|
|
277
|
+
const blocked = rewindBlockedReason(entry);
|
|
278
|
+
if (blocked !== undefined) {
|
|
279
|
+
setNotice({ text: blocked, tone: 'error' });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const drop = data === null ? undefined : droppedTurnInfo(data, entry);
|
|
283
|
+
setConfirmState({
|
|
284
|
+
kind: 'rewind',
|
|
285
|
+
entry,
|
|
286
|
+
...(drop?.coversBranch === true ? { dropsBranch: true } : {}),
|
|
287
|
+
...(drop !== undefined ? { droppedEntries: drop.droppedEntries } : {}),
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
/** Ctrl+B (and the menu's adopt option): the confirm seat. */
|
|
291
|
+
const askAdopt = (entry) => {
|
|
292
|
+
if (entry.sessionId === currentSessionId) {
|
|
293
|
+
setNotice({ text: t('tree-adopt-live'), tone: 'info' });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const tip = data?.rewindFacts.get(entry.sessionId)?.tipBoundary;
|
|
297
|
+
if (tip === undefined) {
|
|
298
|
+
setNotice({ text: t('tree-adopt-unavailable'), tone: 'error' });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
setConfirmState({ kind: 'adopt', entry, tipSeq: tip });
|
|
302
|
+
};
|
|
303
|
+
const performConfirm = (confirm) => {
|
|
304
|
+
setConfirmState(null);
|
|
305
|
+
if (confirm.kind === 'adopt') {
|
|
306
|
+
if (confirm.tipSeq !== undefined)
|
|
307
|
+
executeAdopt(confirm.entry, confirm.tipSeq);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
executeAt(confirm.entry, 'rewind');
|
|
311
|
+
};
|
|
312
|
+
/** Run one menu option (click path and Enter path share this). */
|
|
313
|
+
const runMenuOption = (option) => {
|
|
314
|
+
if (option.disabled === true || !menuOpen || menuEntry === null)
|
|
315
|
+
return;
|
|
316
|
+
const entry = menuEntry;
|
|
317
|
+
setMenuNodeId(null);
|
|
318
|
+
if (option.id === 'cancel')
|
|
319
|
+
return;
|
|
320
|
+
if (option.id === 'rewind')
|
|
321
|
+
askRewind(entry);
|
|
322
|
+
else if (option.id === 'fork')
|
|
323
|
+
executeAt(entry, 'fork');
|
|
324
|
+
else
|
|
325
|
+
askAdopt(entry);
|
|
326
|
+
};
|
|
327
|
+
// ── Keyboard ────────────────────────────────────────────────────────────
|
|
328
|
+
// Keys are read through refs for the same reason SessionBrowser does it:
|
|
329
|
+
// several key events can arrive in one stdin chunk before React re-renders.
|
|
330
|
+
const focusIndexRef = React.useRef(focusIndex);
|
|
331
|
+
focusIndexRef.current = focusIndex;
|
|
332
|
+
const visibleRef = React.useRef(visible);
|
|
333
|
+
visibleRef.current = visible;
|
|
334
|
+
const listHeightRef = React.useRef(1);
|
|
335
|
+
const step = (by, times = 1, wrap = true) => {
|
|
336
|
+
const list = visibleRef.current;
|
|
337
|
+
if (list.length === 0)
|
|
338
|
+
return;
|
|
339
|
+
let at = focusIndexRef.current;
|
|
340
|
+
for (let taken = 0; taken < times; taken++) {
|
|
341
|
+
at = at + by;
|
|
342
|
+
if (at < 0)
|
|
343
|
+
at = wrap ? list.length - 1 : 0;
|
|
344
|
+
if (at >= list.length)
|
|
345
|
+
at = wrap ? 0 : list.length - 1;
|
|
346
|
+
}
|
|
347
|
+
focusIndexRef.current = at;
|
|
348
|
+
setSelectedId(list[at]?.node.id ?? null);
|
|
349
|
+
};
|
|
350
|
+
useInput((input, key) => {
|
|
351
|
+
// While an action is in flight the seat swallows every key — closing now
|
|
352
|
+
// would drop the user back into a half-swapped session.
|
|
353
|
+
if (busy)
|
|
354
|
+
return;
|
|
355
|
+
if (notice !== undefined)
|
|
356
|
+
setNotice(undefined);
|
|
357
|
+
if (seat === 'confirm' && confirmState !== null) {
|
|
358
|
+
if (isPlainReturn(key))
|
|
359
|
+
performConfirm(confirmState);
|
|
360
|
+
else if (key.escape)
|
|
361
|
+
setConfirmState(null);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (seat === 'menu') {
|
|
365
|
+
if (key.upArrow)
|
|
366
|
+
setMenuIndex(index => wrapIndex(index, -1, options.length));
|
|
367
|
+
else if (key.downArrow)
|
|
368
|
+
setMenuIndex(index => wrapIndex(index, 1, options.length));
|
|
369
|
+
else if (key.escape)
|
|
370
|
+
setMenuNodeId(null);
|
|
371
|
+
else if (isPlainReturn(key)) {
|
|
372
|
+
const option = options[menuIndex];
|
|
373
|
+
if (option !== undefined)
|
|
374
|
+
runMenuOption(option);
|
|
375
|
+
}
|
|
376
|
+
else if (!isMod(key) && !key.meta && input) {
|
|
377
|
+
const lower = input.toLowerCase();
|
|
378
|
+
const hit = options.find(option => option.id !== 'cancel' && option.id.startsWith(lower));
|
|
379
|
+
if (hit !== undefined)
|
|
380
|
+
runMenuOption(hit);
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (key.upArrow)
|
|
385
|
+
step(-1);
|
|
386
|
+
else if (key.downArrow)
|
|
387
|
+
step(1);
|
|
388
|
+
else if (key.wheelUp)
|
|
389
|
+
step(-1, 1, false);
|
|
390
|
+
else if (key.wheelDown)
|
|
391
|
+
step(1, 1, false);
|
|
392
|
+
else if (key.pageUp || key.leftArrow)
|
|
393
|
+
step(-1, Math.max(1, Math.floor(listHeightRef.current / 2)));
|
|
394
|
+
else if (key.pageDown || key.rightArrow)
|
|
395
|
+
step(1, Math.max(1, Math.floor(listHeightRef.current / 2)));
|
|
396
|
+
else if (key.escape) {
|
|
397
|
+
// Esc backs out one layer at a time: a live query first, the screen
|
|
398
|
+
// second — closing on the first Esc would discard a search still being
|
|
399
|
+
// refined.
|
|
400
|
+
if (query !== '')
|
|
401
|
+
setQuery('');
|
|
402
|
+
else
|
|
403
|
+
onClose();
|
|
404
|
+
}
|
|
405
|
+
else if (isPlainReturn(key)) {
|
|
406
|
+
if (focusedEntry !== null && focusedNode !== undefined) {
|
|
407
|
+
setSelectedId(focusedNode.node.id);
|
|
408
|
+
setMenuNodeId(focusedNode.node.id);
|
|
409
|
+
setMenuIndex(0);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else if (isMod(key) && input === 'o') {
|
|
413
|
+
const index = TREE_FILTERS.indexOf(filter);
|
|
414
|
+
setFilter(TREE_FILTERS[(index + 1) % TREE_FILTERS.length] ?? 'default');
|
|
415
|
+
}
|
|
416
|
+
else if (isMod(key) && input === 'f' && focusedEntry !== null) {
|
|
417
|
+
executeAt(focusedEntry, 'fork');
|
|
418
|
+
}
|
|
419
|
+
else if (isMod(key) && input === 'b' && focusedEntry !== null) {
|
|
420
|
+
askAdopt(focusedEntry);
|
|
421
|
+
}
|
|
422
|
+
else if (key.backspace || key.delete) {
|
|
423
|
+
setQuery(text => text.slice(0, -1));
|
|
424
|
+
}
|
|
425
|
+
else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
|
|
426
|
+
// Only real characters reach the query — anything else the terminal
|
|
427
|
+
// delivers would be typed into the search invisibly.
|
|
428
|
+
const typed = input.replace(/\p{Cc}/gu, '');
|
|
429
|
+
if (typed.length > 0)
|
|
430
|
+
setQuery(text => text + typed);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
// ── Layout arithmetic ───────────────────────────────────────────────────
|
|
434
|
+
const inputBudget = Math.max(0, columns - 2);
|
|
435
|
+
const menuLines = seat === 'menu' ? 1 + options.length : 0;
|
|
436
|
+
const confirmLines = confirmState === null ? 0 : 1 + (confirmState.dropsBranch === true ? 1 : 0);
|
|
437
|
+
const previewBlock = seat === 'tree' && previewEntry !== null && columns < SPLIT_MIN_COLUMNS && hoverId !== null
|
|
438
|
+
? PREVIEW_BLOCK_LINES
|
|
439
|
+
: 0;
|
|
440
|
+
const extraLines = (notice === undefined ? 0 : 1) + menuLines + confirmLines + previewBlock;
|
|
441
|
+
const ruleBudget = Math.max(0, Math.min(RULE_PRIORITY.length, rows - MANDATORY_LINES - extraLines));
|
|
442
|
+
const rules = new Set(RULE_PRIORITY.slice(0, ruleBudget));
|
|
443
|
+
const listHeight = Math.max(0, rows - MANDATORY_LINES - extraLines - rules.size);
|
|
444
|
+
listHeightRef.current = listHeight;
|
|
445
|
+
const splitPreview = seat === 'tree' && previewEntry !== null && columns >= SPLIT_MIN_COLUMNS;
|
|
446
|
+
const previewWidth = splitPreview ? Math.min(52, Math.floor(columns * 0.38)) : columns;
|
|
447
|
+
const listWidth = Math.max(20, columns - (splitPreview ? previewWidth : 0));
|
|
448
|
+
// Cursor-centered window over single-line rows.
|
|
449
|
+
const windowStart = visible.length <= listHeight
|
|
450
|
+
? 0
|
|
451
|
+
: Math.max(0, Math.min(focusIndex - Math.floor(listHeight / 2), visible.length - listHeight));
|
|
452
|
+
const windowed = visible.slice(windowStart, windowStart + Math.max(0, listHeight));
|
|
453
|
+
const heading = ` ${t('tree-title')}`;
|
|
454
|
+
const right = [
|
|
455
|
+
`${focusIndex + 1}/${visible.length}`,
|
|
456
|
+
t(`tree-filter-${filter}`),
|
|
457
|
+
`${data?.sessionCount ?? 0} ${t('tree-sessions')}`,
|
|
458
|
+
...(data?.truncated === true ? [t('tree-truncated')] : []),
|
|
459
|
+
].join(' · ');
|
|
460
|
+
const header = spreadRow(heading, right, Math.max(0, columns - 1));
|
|
461
|
+
const hint = seat === 'confirm'
|
|
462
|
+
? fitHint([t('tree-hint-confirm')], inputBudget)
|
|
463
|
+
: seat === 'menu'
|
|
464
|
+
? fitHint([t('tree-hint-menu')], inputBudget)
|
|
465
|
+
: fitHint([t('tree-hint', { mod: modLabel }), t('tree-hint-short')], inputBudget);
|
|
466
|
+
// ── Loading / busy seats ────────────────────────────────────────────────
|
|
467
|
+
if (loading || busy) {
|
|
468
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: "remember", bold: true, children: truncateWidth(` ${t('tree-title')}`, inputBudget) }) }), rules.has(0) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(busy ? t('tree-rewinding') : t('tree-loading'), inputBudget)}` }) })] }));
|
|
469
|
+
}
|
|
470
|
+
// ── Render ──────────────────────────────────────────────────────────────
|
|
471
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), rules.has(0) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(query, inputBudget), isFocused: seat === 'tree', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('tree-search'), inputBudget), borderless: true }) }), _jsxs(Box, { flexGrow: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [visible.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('tree-empty'), Math.max(0, listWidth - 2))}` })), windowed.map((flatNode, index) => (_jsx(TreeRow, { flatNode: flatNode, meta: data?.sessions.get(flatNode.node.sessionId), liveSessionId: currentSessionId, width: listWidth, focused: windowStart + index === focusIndex, onActivePath: data?.activePath.has(flatNode.node.id) === true, onHover: setHoverId, onClick: () => {
|
|
472
|
+
// Click = focus + open the action menu (the mouse path to
|
|
473
|
+
// every action this screen offers); the menu's own rows
|
|
474
|
+
// execute.
|
|
475
|
+
setSelectedId(flatNode.node.id);
|
|
476
|
+
setMenuNodeId(flatNode.node.id);
|
|
477
|
+
setMenuIndex(0);
|
|
478
|
+
} }, flatNode.node.id)))] }), splitPreview && previewEntry !== null && (_jsx(PreviewPane, { entry: previewEntry, meta: data?.sessions.get(previewEntry.sessionId), width: previewWidth, height: listHeight, liveSessionId: currentSessionId, now: Date.now() }))] }), previewBlock > 0 && previewEntry !== null && (_jsx(PreviewPane, { entry: previewEntry, meta: data?.sessions.get(previewEntry.sessionId), width: columns, height: previewBlock, liveSessionId: currentSessionId, now: Date.now(), compact: true })), notice !== undefined && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: ` ${truncateWidth(notice.text, inputBudget)}` }) })), seat === 'menu' && menuEntry !== null && (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: "remember", children: ` ${truncateWidth(`❰ ${menuEntry.text}`, inputBudget)}` }) }), options.map((option, index) => (_jsx(MenuOptionRow, { option: option, highlighted: index === menuIndex, width: inputBudget, onClick: () => {
|
|
479
|
+
setMenuIndex(index);
|
|
480
|
+
runMenuOption(option);
|
|
481
|
+
}, onHover: () => setMenuIndex(index) }, option.id)))] })), seat === 'confirm' && confirmState !== null && (_jsxs(Box, { flexShrink: 0, flexDirection: "column",
|
|
482
|
+
// 点击确认行 = 确认执行(与 Enter 同路径);取消保留键盘 Esc,防误点
|
|
483
|
+
onClick: () => performConfirm(confirmState), onMouseEnter: () => setConfirmHovered(true), onMouseLeave: () => setConfirmHovered(false), backgroundColor: confirmHovered ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: "warning", children: ` ${truncateWidth(confirmState.kind === 'adopt'
|
|
484
|
+
? t('tree-confirm-adopt', { text: confirmState.entry.text })
|
|
485
|
+
: confirmState.droppedEntries !== undefined && confirmState.droppedEntries > 0
|
|
486
|
+
? t('tree-confirm-drop', { text: confirmState.entry.text, n: confirmState.droppedEntries })
|
|
487
|
+
: t('tree-confirm-rewind', { text: confirmState.entry.text }), inputBudget)}` }), confirmState.dropsBranch === true && (_jsx(Text, { color: "error", children: ` ${truncateWidth(t('tree-confirm-drops-branch'), inputBudget)}` }))] })), rules.has(1) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: hint }) }) })] }));
|
|
488
|
+
}
|
|
489
|
+
/** Index wrap helper for the menu cursor. */
|
|
490
|
+
function wrapIndex(current, by, count) {
|
|
491
|
+
if (count <= 0)
|
|
492
|
+
return 0;
|
|
493
|
+
return (current + by + count) % count;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* One tree row: focus pointer, tree-drawing prefix, active-path bullet,
|
|
497
|
+
* branch badge, then the entry text in its kind's colours. Hover highlights
|
|
498
|
+
* the row and drives the preview pane; click opens the action menu.
|
|
499
|
+
*/
|
|
500
|
+
function TreeRow({ flatNode, meta, liveSessionId, width, focused, onActivePath, onHover, onClick, }) {
|
|
501
|
+
const [hovered, setHovered] = React.useState(false);
|
|
502
|
+
const entry = flatNode.node.entry;
|
|
503
|
+
if (entry === null) {
|
|
504
|
+
// Placeholder rows: an empty fork, an unreadable log, an unloaded one.
|
|
505
|
+
// They carry no action, but keep the branch structure visible.
|
|
506
|
+
const label = meta?.unreadable === true
|
|
507
|
+
? t('tree-unreadable')
|
|
508
|
+
: meta?.unloaded === true
|
|
509
|
+
? t('tree-unloaded')
|
|
510
|
+
: t('tree-empty-fork');
|
|
511
|
+
return (_jsxs(Box, { flexShrink: 0, onClick: onClick, onMouseEnter: () => {
|
|
512
|
+
setHovered(true);
|
|
513
|
+
onHover(null);
|
|
514
|
+
}, onMouseLeave: () => setHovered(false), backgroundColor: hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: "subtle", children: ' ' }), _jsx(Text, { dimColor: true, children: clampPrefix(treePrefix(flatNode), Math.max(0, width - 4)) }), _jsx(Text, { dimColor: true, italic: true, children: truncateWidth(`⎇ ${label}`, Math.max(0, width - 6)) })] }));
|
|
515
|
+
}
|
|
516
|
+
const kind = KIND_PREFIX[entry.kind];
|
|
517
|
+
const glyph = entry.kind === 'tool' ? toolGlyph(entry) : undefined;
|
|
518
|
+
// Branch badge: the first row of a session chain names the branch it
|
|
519
|
+
// starts — the live session wears its own mark.
|
|
520
|
+
const badge = flatNode.node.branchHead
|
|
521
|
+
? `${flatNode.node.sessionId === liveSessionId ? '●' : '⎇'} ${truncateWidth(meta?.title ?? flatNode.node.sessionId.slice(0, 8), 16)} › `
|
|
522
|
+
: '';
|
|
523
|
+
const budget = Math.max(4, width - 2 - stringWidth(clampPrefix(treePrefix(flatNode), Math.max(0, width - 8))) - (onActivePath ? 2 : 0) - stringWidth(badge) - stringWidth(kind.text) - (glyph === undefined ? 0 : 2));
|
|
524
|
+
return (_jsxs(Box, { flexShrink: 0, onClick: onClick, onMouseEnter: () => {
|
|
525
|
+
setHovered(true);
|
|
526
|
+
onHover(flatNode.node.id);
|
|
527
|
+
}, onMouseLeave: () => {
|
|
528
|
+
setHovered(false);
|
|
529
|
+
onHover(null);
|
|
530
|
+
}, backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: focused ? '❯ ' : ' ' }), _jsx(Text, { dimColor: true, children: clampPrefix(treePrefix(flatNode), Math.max(0, width - 8)) }), onActivePath && _jsx(Text, { color: "suggestion", children: '• ' }), badge !== '' && _jsx(Text, { color: flatNode.node.sessionId === liveSessionId ? 'remember' : 'planMode', dimColor: true, children: badge }), kind.text !== '' && (_jsx(Text, { color: entry.label === 'aborted' ? 'warning' : kind.color, children: kind.text })), _jsx(Text, { color: entry.kind === 'interrupt' || entry.kind === 'notice' ? 'warning' : entry.kind === 'tool' ? 'subtle' : undefined, bold: focused, dimColor: entry.kind === 'tool', children: truncateWidth(`${entry.text}${entry.label === 'aborted' ? ` (${entry.label})` : ''}`, budget) }), glyph !== undefined && _jsx(Text, { color: glyph.color, children: glyph.glyph })] }));
|
|
531
|
+
}
|
|
532
|
+
/** One option row of the click menu: marker, label, detail. */
|
|
533
|
+
function MenuOptionRow({ option, highlighted, width, onClick, onHover, }) {
|
|
534
|
+
const dim = option.disabled === true;
|
|
535
|
+
const labelWidth = 14;
|
|
536
|
+
const detailBudget = Math.max(0, width - 4 - labelWidth);
|
|
537
|
+
return (_jsxs(Box, { flexShrink: 0, onClick: dim ? undefined : onClick, onMouseEnter: dim ? undefined : onHover, backgroundColor: highlighted && !dim ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: highlighted ? 'suggestion' : 'subtle', children: highlighted ? '❯ ' : ' ' }), _jsx(Text, { color: dim ? 'inactive' : option.id === 'rewind' ? 'warning' : option.id === 'fork' ? 'remember' : option.id === 'adopt' ? 'planMode' : 'subtle', bold: highlighted && !dim, children: truncateWidth(option.label, labelWidth) }), _jsx(Text, { dimColor: true, children: ` ${truncateWidth(option.detail, detailBudget)}` })] }));
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* The preview pane: the hovered (or focused) entry's full text, wrapped,
|
|
541
|
+
* with the facts that place it — kind, time, branch, seq.
|
|
542
|
+
*/
|
|
543
|
+
function PreviewPane({ entry, meta, width, height, liveSessionId, now, compact, }) {
|
|
544
|
+
const bodyWidth = Math.max(8, width - 2);
|
|
545
|
+
const kindLabel = t(`tree-kind-${entry.kind}`);
|
|
546
|
+
const head = `${kindLabel} · ${formatTime(entry.time, now)}`;
|
|
547
|
+
const lines = wrapWidth(entry.text, bodyWidth).slice(0, Math.max(0, height - (compact === true ? 2 : 4)));
|
|
548
|
+
const cut = wrapWidth(entry.text, bodyWidth).length > lines.length;
|
|
549
|
+
const branch = entry.sessionId === liveSessionId
|
|
550
|
+
? t('tree-branch-live')
|
|
551
|
+
: meta?.title !== undefined
|
|
552
|
+
? truncateWidth(meta.title, Math.max(0, bodyWidth - head.length - 3))
|
|
553
|
+
: entry.sessionId.slice(0, 8);
|
|
554
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, flexShrink: 0, children: [!compact && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: "remember", bold: true, children: ` ${truncateWidth(t('tree-preview-title'), Math.max(0, width - 1))}` }) })), _jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", children: ` ${truncateWidth(head, Math.max(0, width - 1))}` }), _jsx(Text, { dimColor: true, children: ` ${truncateWidth(branch, Math.max(0, width - 1 - stringWidth(head)))}` })] }), !compact && _jsx(Divider, { width: width }), lines.map((line, index) => (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { children: ` ${truncateWidth(line, bodyWidth)}` }) }, index))), cut && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: ` ${'…'}` }) }))] }));
|
|
555
|
+
}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Channel } from '../dsh-adapter/channel.js';
|
|
3
3
|
import type { WaveBand } from '../dsh-adapter/types.js';
|
|
4
|
-
/**
|
|
5
|
-
* The footer under the prompt input, in Claude Code's PromptInputFooter
|
|
6
|
-
* layout: the segmented context progress bar on its own first line, the
|
|
7
|
-
* status line below (left group: model · tokens · think level · cache · tps
|
|
8
|
-
* gauge/sparkline; right group: git · cwd · title · short session id,
|
|
9
|
-
* right-aligned), and the
|
|
10
|
-
* mode/hint line last. The right side of the footer shows the latest
|
|
11
|
-
* transient notification (errors in red, warnings in amber — CC style).
|
|
12
|
-
*/
|
|
13
4
|
export declare function StatusLine({ channel, selectionActive, helpOpen, wake, }: {
|
|
14
5
|
channel: Channel;
|
|
15
6
|
selectionActive?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAQzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAQzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAYxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AA6EvD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBA8VA;AAED,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAyHD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvF"}
|