@deepseek-harness-tui/dsh-tui 0.11.2 → 0.13.0
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 +60 -8
- package/assets/deepy/README.md +60 -0
- package/assets/deepy/frames.json +1 -0
- package/assets/whale-girl/whale-girl-happy.png +0 -0
- package/assets/whaleGirl/README.md +7 -0
- package/assets/whaleGirl/frames.json +1 -0
- package/assets/whaleGirl/img/building/00.png +0 -0
- package/assets/whaleGirl/img/building/01.png +0 -0
- package/assets/whaleGirl/img/building/02.png +0 -0
- package/assets/whaleGirl/img/building/03.png +0 -0
- package/assets/whaleGirl/img/building/04.png +0 -0
- package/assets/whaleGirl/img/building/05.png +0 -0
- package/assets/whaleGirl/img/building/06.png +0 -0
- package/assets/whaleGirl/img/building/07.png +0 -0
- package/assets/whaleGirl/img/building/08.png +0 -0
- package/assets/whaleGirl/img/building/09.png +0 -0
- package/assets/whaleGirl/img/building/10.png +0 -0
- package/assets/whaleGirl/img/building/11.png +0 -0
- package/assets/whaleGirl/img/building/12.png +0 -0
- package/assets/whaleGirl/img/building/13.png +0 -0
- package/assets/whaleGirl/img/building/14.png +0 -0
- package/assets/whaleGirl/img/building/15.png +0 -0
- package/assets/whaleGirl/img/building/16.png +0 -0
- package/assets/whaleGirl/img/building/17.png +0 -0
- package/assets/whaleGirl/img/building/18.png +0 -0
- package/assets/whaleGirl/img/building/19.png +0 -0
- package/assets/whaleGirl/img/building/20.png +0 -0
- package/assets/whaleGirl/img/building/21.png +0 -0
- package/assets/whaleGirl/img/building/22.png +0 -0
- package/assets/whaleGirl/img/building/23.png +0 -0
- package/assets/whaleGirl/img/carrying/00.png +0 -0
- package/assets/whaleGirl/img/carrying/01.png +0 -0
- package/assets/whaleGirl/img/carrying/02.png +0 -0
- package/assets/whaleGirl/img/carrying/03.png +0 -0
- package/assets/whaleGirl/img/carrying/04.png +0 -0
- package/assets/whaleGirl/img/carrying/05.png +0 -0
- package/assets/whaleGirl/img/carrying/06.png +0 -0
- package/assets/whaleGirl/img/carrying/07.png +0 -0
- package/assets/whaleGirl/img/compacting/00.png +0 -0
- package/assets/whaleGirl/img/compacting/01.png +0 -0
- package/assets/whaleGirl/img/compacting/02.png +0 -0
- package/assets/whaleGirl/img/compacting/03.png +0 -0
- package/assets/whaleGirl/img/compacting/04.png +0 -0
- package/assets/whaleGirl/img/compacting/05.png +0 -0
- package/assets/whaleGirl/img/compacting/06.png +0 -0
- package/assets/whaleGirl/img/compacting/07.png +0 -0
- package/assets/whaleGirl/img/compacting/08.png +0 -0
- package/assets/whaleGirl/img/compacting/09.png +0 -0
- package/assets/whaleGirl/img/conducting/00.png +0 -0
- package/assets/whaleGirl/img/conducting/01.png +0 -0
- package/assets/whaleGirl/img/conducting/02.png +0 -0
- package/assets/whaleGirl/img/conducting/03.png +0 -0
- package/assets/whaleGirl/img/conducting/04.png +0 -0
- package/assets/whaleGirl/img/conducting/05.png +0 -0
- package/assets/whaleGirl/img/conducting/06.png +0 -0
- package/assets/whaleGirl/img/conducting/07.png +0 -0
- package/assets/whaleGirl/img/conducting/08.png +0 -0
- package/assets/whaleGirl/img/conducting/09.png +0 -0
- package/assets/whaleGirl/img/conducting/10.png +0 -0
- package/assets/whaleGirl/img/conducting/11.png +0 -0
- package/assets/whaleGirl/img/drag/00.png +0 -0
- package/assets/whaleGirl/img/drag/01.png +0 -0
- package/assets/whaleGirl/img/drag/02.png +0 -0
- package/assets/whaleGirl/img/drag/03.png +0 -0
- package/assets/whaleGirl/img/drag/04.png +0 -0
- package/assets/whaleGirl/img/drag/05.png +0 -0
- package/assets/whaleGirl/img/drag/06.png +0 -0
- package/assets/whaleGirl/img/drag/07.png +0 -0
- package/assets/whaleGirl/img/drag/08.png +0 -0
- package/assets/whaleGirl/img/drag/09.png +0 -0
- package/assets/whaleGirl/img/error/00.png +0 -0
- package/assets/whaleGirl/img/error/01.png +0 -0
- package/assets/whaleGirl/img/error/02.png +0 -0
- package/assets/whaleGirl/img/error/03.png +0 -0
- package/assets/whaleGirl/img/error/04.png +0 -0
- package/assets/whaleGirl/img/error/05.png +0 -0
- package/assets/whaleGirl/img/error/06.png +0 -0
- package/assets/whaleGirl/img/error/07.png +0 -0
- package/assets/whaleGirl/img/error/08.png +0 -0
- package/assets/whaleGirl/img/error/09.png +0 -0
- package/assets/whaleGirl/img/happy/00.png +0 -0
- package/assets/whaleGirl/img/happy/01.png +0 -0
- package/assets/whaleGirl/img/happy/02.png +0 -0
- package/assets/whaleGirl/img/happy/03.png +0 -0
- package/assets/whaleGirl/img/happy/04.png +0 -0
- package/assets/whaleGirl/img/happy/05.png +0 -0
- package/assets/whaleGirl/img/happy/06.png +0 -0
- package/assets/whaleGirl/img/happy/07.png +0 -0
- package/assets/whaleGirl/img/happy/08.png +0 -0
- package/assets/whaleGirl/img/happy/09.png +0 -0
- package/assets/whaleGirl/img/happy/10.png +0 -0
- package/assets/whaleGirl/img/idle/00.png +0 -0
- package/assets/whaleGirl/img/idle/01.png +0 -0
- package/assets/whaleGirl/img/idle/02.png +0 -0
- package/assets/whaleGirl/img/idle/03.png +0 -0
- package/assets/whaleGirl/img/idle/04.png +0 -0
- package/assets/whaleGirl/img/idle/05.png +0 -0
- package/assets/whaleGirl/img/idle/06.png +0 -0
- package/assets/whaleGirl/img/idle/07.png +0 -0
- package/assets/whaleGirl/img/idle/08.png +0 -0
- package/assets/whaleGirl/img/idle/09.png +0 -0
- package/assets/whaleGirl/img/idle/10.png +0 -0
- package/assets/whaleGirl/img/idle/11.png +0 -0
- package/assets/whaleGirl/img/idle/12.png +0 -0
- package/assets/whaleGirl/img/idle/13.png +0 -0
- package/assets/whaleGirl/img/idle/14.png +0 -0
- package/assets/whaleGirl/img/idle/15.png +0 -0
- package/assets/whaleGirl/img/idle/16.png +0 -0
- package/assets/whaleGirl/img/idle/17.png +0 -0
- package/assets/whaleGirl/img/idle/18.png +0 -0
- package/assets/whaleGirl/img/idle/19.png +0 -0
- package/assets/whaleGirl/img/idle/20.png +0 -0
- package/assets/whaleGirl/img/idle-look/00.png +0 -0
- package/assets/whaleGirl/img/idle-look/01.png +0 -0
- package/assets/whaleGirl/img/idle-look/02.png +0 -0
- package/assets/whaleGirl/img/idle-look/03.png +0 -0
- package/assets/whaleGirl/img/idle-look/04.png +0 -0
- package/assets/whaleGirl/img/idle-look/05.png +0 -0
- package/assets/whaleGirl/img/idle-look/06.png +0 -0
- package/assets/whaleGirl/img/idle-look/07.png +0 -0
- package/assets/whaleGirl/img/idle-look/08.png +0 -0
- package/assets/whaleGirl/img/idle-look/09.png +0 -0
- package/assets/whaleGirl/img/idle-look/10.png +0 -0
- package/assets/whaleGirl/img/idle-look/11.png +0 -0
- package/assets/whaleGirl/img/idle-look/12.png +0 -0
- package/assets/whaleGirl/img/idle-spout/00.png +0 -0
- package/assets/whaleGirl/img/idle-spout/01.png +0 -0
- package/assets/whaleGirl/img/idle-spout/02.png +0 -0
- package/assets/whaleGirl/img/idle-spout/03.png +0 -0
- package/assets/whaleGirl/img/idle-spout/04.png +0 -0
- package/assets/whaleGirl/img/idle-spout/05.png +0 -0
- package/assets/whaleGirl/img/idle-spout/06.png +0 -0
- package/assets/whaleGirl/img/idle-spout/07.png +0 -0
- package/assets/whaleGirl/img/idle-spout/08.png +0 -0
- package/assets/whaleGirl/img/idle-spout/09.png +0 -0
- package/assets/whaleGirl/img/idle-spout/10.png +0 -0
- package/assets/whaleGirl/img/music/00.png +0 -0
- package/assets/whaleGirl/img/music/01.png +0 -0
- package/assets/whaleGirl/img/music/02.png +0 -0
- package/assets/whaleGirl/img/music/03.png +0 -0
- package/assets/whaleGirl/img/music/04.png +0 -0
- package/assets/whaleGirl/img/music/05.png +0 -0
- package/assets/whaleGirl/img/music/06.png +0 -0
- package/assets/whaleGirl/img/music/07.png +0 -0
- package/assets/whaleGirl/img/notification/00.png +0 -0
- package/assets/whaleGirl/img/notification/01.png +0 -0
- package/assets/whaleGirl/img/notification/02.png +0 -0
- package/assets/whaleGirl/img/notification/03.png +0 -0
- package/assets/whaleGirl/img/notification/04.png +0 -0
- package/assets/whaleGirl/img/notification/05.png +0 -0
- package/assets/whaleGirl/img/notification/06.png +0 -0
- package/assets/whaleGirl/img/notification/07.png +0 -0
- package/assets/whaleGirl/img/poke-left/00.png +0 -0
- package/assets/whaleGirl/img/poke-left/01.png +0 -0
- package/assets/whaleGirl/img/poke-left/02.png +0 -0
- package/assets/whaleGirl/img/poke-left/03.png +0 -0
- package/assets/whaleGirl/img/poke-left/04.png +0 -0
- package/assets/whaleGirl/img/poke-left/05.png +0 -0
- package/assets/whaleGirl/img/poke-left/06.png +0 -0
- package/assets/whaleGirl/img/poke-left/07.png +0 -0
- package/assets/whaleGirl/img/poke-left/08.png +0 -0
- package/assets/whaleGirl/img/poke-left/09.png +0 -0
- package/assets/whaleGirl/img/poke-left/10.png +0 -0
- package/assets/whaleGirl/img/poke-right/00.png +0 -0
- package/assets/whaleGirl/img/poke-right/01.png +0 -0
- package/assets/whaleGirl/img/poke-right/02.png +0 -0
- package/assets/whaleGirl/img/poke-right/03.png +0 -0
- package/assets/whaleGirl/img/poke-right/04.png +0 -0
- package/assets/whaleGirl/img/poke-right/05.png +0 -0
- package/assets/whaleGirl/img/poke-right/06.png +0 -0
- package/assets/whaleGirl/img/poke-right/07.png +0 -0
- package/assets/whaleGirl/img/poke-right/08.png +0 -0
- package/assets/whaleGirl/img/poke-right/09.png +0 -0
- package/assets/whaleGirl/img/poke-right/10.png +0 -0
- package/assets/whaleGirl/img/sleeping/00.png +0 -0
- package/assets/whaleGirl/img/sleeping/01.png +0 -0
- package/assets/whaleGirl/img/sleeping/02.png +0 -0
- package/assets/whaleGirl/img/sleeping/03.png +0 -0
- package/assets/whaleGirl/img/sleeping/04.png +0 -0
- package/assets/whaleGirl/img/sleeping/05.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/00.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/01.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/02.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/03.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/04.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/05.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/06.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/07.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/08.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/09.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/10.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/11.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/12.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/13.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/14.png +0 -0
- package/assets/whaleGirl/img/smile-hearts/15.png +0 -0
- package/assets/whaleGirl/img/swim/00.png +0 -0
- package/assets/whaleGirl/img/swim/01.png +0 -0
- package/assets/whaleGirl/img/swim/02.png +0 -0
- package/assets/whaleGirl/img/swim/03.png +0 -0
- package/assets/whaleGirl/img/swim/04.png +0 -0
- package/assets/whaleGirl/img/swim/05.png +0 -0
- package/assets/whaleGirl/img/swim/06.png +0 -0
- package/assets/whaleGirl/img/swim/07.png +0 -0
- package/assets/whaleGirl/img/thinking/00.png +0 -0
- package/assets/whaleGirl/img/thinking/01.png +0 -0
- package/assets/whaleGirl/img/thinking/02.png +0 -0
- package/assets/whaleGirl/img/thinking/03.png +0 -0
- package/assets/whaleGirl/img/thinking/04.png +0 -0
- package/assets/whaleGirl/img/thinking/05.png +0 -0
- package/assets/whaleGirl/img/thinking/06.png +0 -0
- package/assets/whaleGirl/img/thinking/07.png +0 -0
- package/assets/whaleGirl/img/thinking/08.png +0 -0
- package/assets/whaleGirl/img/thinking/09.png +0 -0
- package/assets/whaleGirl/img/thinking/10.png +0 -0
- package/assets/whaleGirl/img/thinking/11.png +0 -0
- package/assets/whaleGirl/img/thinking/12.png +0 -0
- package/assets/whaleGirl/img/thinking/13.png +0 -0
- package/assets/whaleGirl/img/thinking/14.png +0 -0
- package/assets/whaleGirl/img/thinking/15.png +0 -0
- package/assets/whaleGirl/img/thinking/16.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/00.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/01.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/02.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/03.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/04.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/05.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/06.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/07.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/08.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/09.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/10.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/11.png +0 -0
- package/assets/whaleGirl/img/thumbs-up/12.png +0 -0
- package/assets/whaleGirl/img/tickle/00.png +0 -0
- package/assets/whaleGirl/img/tickle/01.png +0 -0
- package/assets/whaleGirl/img/tickle/02.png +0 -0
- package/assets/whaleGirl/img/tickle/03.png +0 -0
- package/assets/whaleGirl/img/tickle/04.png +0 -0
- package/assets/whaleGirl/img/tickle/05.png +0 -0
- package/assets/whaleGirl/img/tickle/06.png +0 -0
- package/assets/whaleGirl/img/tickle/07.png +0 -0
- package/assets/whaleGirl/img/tickle/08.png +0 -0
- package/assets/whaleGirl/img/tickle/09.png +0 -0
- package/assets/whaleGirl/img/tickle/10.png +0 -0
- package/assets/whaleGirl/img/tickle/11.png +0 -0
- package/assets/whaleGirl/img/tickle/12.png +0 -0
- package/assets/whaleGirl/img/tickle/13.png +0 -0
- package/assets/whaleGirl/img/timings.json +1 -0
- package/assets/whaleGirl/img/typing/00.png +0 -0
- package/assets/whaleGirl/img/typing/01.png +0 -0
- package/assets/whaleGirl/img/typing/02.png +0 -0
- package/assets/whaleGirl/img/typing/03.png +0 -0
- package/assets/whaleGirl/img/typing/04.png +0 -0
- package/assets/whaleGirl/img/typing/05.png +0 -0
- package/assets/whaleGirl/img/typing/06.png +0 -0
- package/assets/whaleGirl/img/typing/07.png +0 -0
- package/assets/whaleGirl/img/typing/08.png +0 -0
- package/assets/whaleGirl/img/typing/09.png +0 -0
- package/assets/whaleGirl/img/typing/10.png +0 -0
- package/assets/whaleGirl/img/typing/11.png +0 -0
- package/assets/whaleGirl/img/typing/12.png +0 -0
- package/assets/whaleGirl/img/typing/13.png +0 -0
- package/assets/whaleGirl/img/waking/00.png +0 -0
- package/assets/whaleGirl/img/waking/01.png +0 -0
- package/assets/whaleGirl/img/waking/02.png +0 -0
- package/assets/whaleGirl/img/waking/03.png +0 -0
- package/assets/whaleGirl/img/waking/04.png +0 -0
- package/assets/whaleGirl/img/waking/05.png +0 -0
- package/assets/whaleGirl/img/waking/06.png +0 -0
- package/assets/whaleGirl/img/waking/07.png +0 -0
- package/assets/whaleGirl/img/waking/08.png +0 -0
- package/assets/whaleGirl/img/waking/09.png +0 -0
- package/assets/whaleGirl/img/waking/10.png +0 -0
- package/bin/dsh-tui.js +38 -6
- package/cordis.patch.yml +31 -6
- package/guide/dsh-tui-guide/SKILL.md +37 -0
- package/guide/dsh-tui-guide/configuration.en.md +415 -0
- package/guide/dsh-tui-guide/configuration.md +354 -0
- package/guide/dsh-tui-guide/getting-started.en.md +401 -0
- package/guide/dsh-tui-guide/getting-started.md +399 -0
- package/guide/dsh-tui-guide/interaction.en.md +738 -0
- package/guide/dsh-tui-guide/interaction.md +793 -0
- package/guide/dsh-tui-guide/migrate.en.md +166 -0
- package/guide/dsh-tui-guide/migrate.md +132 -0
- package/guide/dsh-tui-guide/plugins.en.md +78 -0
- package/guide/dsh-tui-guide/plugins.md +64 -0
- package/guide/dsh-tui-guide/themes.en.md +197 -0
- package/guide/dsh-tui-guide/themes.md +188 -0
- package/guide/dsh-tui-guide/user-guide.en.md +711 -0
- package/guide/dsh-tui-guide/user-guide.md +666 -0
- package/guide/dsh-tui-guide/vscode.en.md +301 -0
- package/guide/dsh-tui-guide/vscode.md +272 -0
- package/lib/settings.json +219 -5
- package/lib/types/adapter/channel/ui-policy.d.ts +7 -2
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui-policy.js +18 -1
- package/lib/types/adapter/index.d.ts +1 -1
- package/lib/types/adapter/index.js +1 -1
- package/lib/types/adapter/kernel/host-facade.d.ts +4 -1
- package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
- package/lib/types/adapter/kernel/host-facade.js +8 -1
- package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
- package/lib/types/adapter/kernel/runtime.js +13 -0
- package/lib/types/adapter/kernel/slices/index.d.ts +1 -0
- package/lib/types/adapter/kernel/slices/index.d.ts.map +1 -1
- package/lib/types/adapter/kernel/slices/index.js +3 -0
- package/lib/types/adapter/kernel/slices/panels.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/panels.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/panels.js +14 -0
- package/lib/types/adapter/ports/channel-capabilities.d.ts +48 -0
- package/lib/types/adapter/ports/channel-capabilities.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-capabilities.js +2 -0
- package/lib/types/adapter/ports/channel-catalog.d.ts +7 -0
- package/lib/types/adapter/ports/channel-catalog.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-display.d.ts +10 -0
- package/lib/types/adapter/ports/channel-display.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-settings.d.ts +2 -1
- package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-ui.d.ts +91 -4
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +137 -9
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel.d.ts +1 -1
- package/lib/types/adapter/ports/channel.js +1 -1
- package/lib/types/adapter/ports/descriptor.d.ts +1 -1
- package/lib/types/adapter/ports/descriptor.js +1 -1
- package/lib/types/adapter/ports/index.d.ts +1 -1
- package/lib/types/adapter/ports/index.js +1 -1
- package/lib/types/adapter/ports/panels.d.ts +28 -0
- package/lib/types/adapter/ports/panels.d.ts.map +1 -0
- package/lib/types/adapter/ports/panels.js +8 -0
- package/lib/types/adapter/ports/workspace.d.ts +1 -1
- package/lib/types/adapter/ports/workspace.js +1 -1
- package/lib/types/adapter/spec/index.d.ts +2 -2
- package/lib/types/adapter/spec/index.js +2 -2
- package/lib/types/adapter/spec/profile.d.ts +6 -6
- package/lib/types/adapter/spec/profile.d.ts.map +1 -1
- package/lib/types/adapter/spec/profile.js +6 -6
- package/lib/types/adapter/spec/protocol-constants.d.ts +3 -3
- package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
- package/lib/types/adapter/spec/protocol-constants.js +5 -5
- package/lib/types/adapter/spec/tui-contributions.d.ts +4 -4
- package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
- package/lib/types/adapter/spec/tui-contributions.js +5 -5
- package/lib/types/adapter/standard/descriptor.js +1 -1
- package/lib/types/adapter/standard/index.d.ts +3 -3
- package/lib/types/adapter/standard/index.js +3 -3
- package/lib/types/adapter/standard/registry.d.ts +3 -2
- package/lib/types/adapter/standard/registry.d.ts.map +1 -1
- package/lib/types/adapter/standard/registry.js +6 -5
- package/lib/types/adapter/standard/tui-extension.d.ts +1 -1
- package/lib/types/adapter/standard/tui-extension.js +1 -1
- package/lib/types/adapter/upstream/index.d.ts +1 -0
- package/lib/types/adapter/upstream/index.d.ts.map +1 -1
- package/lib/types/adapter/upstream/index.js +1 -0
- package/lib/types/adapter/upstream/panels-driver.d.ts +18 -0
- package/lib/types/adapter/upstream/panels-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/panels-driver.js +159 -0
- package/lib/types/api.d.ts +2 -0
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +3 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +12 -4
- package/lib/types/components/ActivityLine.d.ts +6 -9
- package/lib/types/components/ActivityLine.d.ts.map +1 -1
- package/lib/types/components/ActivityLine.js +7 -8
- package/lib/types/components/Chat/JobCard.d.ts +27 -1
- package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
- package/lib/types/components/Chat/JobCard.js +128 -33
- package/lib/types/components/Chat/JobGroupHeader.d.ts +27 -0
- package/lib/types/components/Chat/JobGroupHeader.d.ts.map +1 -0
- package/lib/types/components/Chat/JobGroupHeader.js +62 -0
- package/lib/types/components/Chat/SubagentMessage.js +6 -6
- package/lib/types/components/GoalTodoPanel.d.ts +13 -1
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +55 -14
- package/lib/types/components/JobsPanel.d.ts +42 -2
- package/lib/types/components/JobsPanel.d.ts.map +1 -1
- package/lib/types/components/JobsPanel.js +220 -40
- package/lib/types/components/LogoV2.d.ts +43 -3
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +42 -15
- package/lib/types/components/MessageList.d.ts +8 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +215 -21
- package/lib/types/components/OverlayAbove.d.ts +11 -1
- package/lib/types/components/OverlayAbove.d.ts.map +1 -1
- package/lib/types/components/OverlayAbove.js +2 -12
- package/lib/types/components/PageMargin.d.ts.map +1 -1
- package/lib/types/components/PageMargin.js +3 -1
- package/lib/types/components/PluginSceneBoundary.d.ts +1 -1
- package/lib/types/components/PluginSceneBoundary.js +1 -1
- package/lib/types/components/PresetPicker.d.ts +4 -0
- package/lib/types/components/PresetPicker.d.ts.map +1 -1
- package/lib/types/components/PresetPicker.js +5 -1
- package/lib/types/components/PromptInput.d.ts +20 -22
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +425 -157
- package/lib/types/components/SearchBox.d.ts +22 -5
- package/lib/types/components/SearchBox.d.ts.map +1 -1
- package/lib/types/components/SearchBox.js +17 -13
- package/lib/types/components/StarPrompt.d.ts +6 -0
- package/lib/types/components/StarPrompt.d.ts.map +1 -1
- package/lib/types/components/StarPrompt.js +45 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +24 -9
- package/lib/types/components/SubagentDashboard.d.ts +18 -3
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
- package/lib/types/components/SubagentDashboard.js +53 -5
- package/lib/types/components/SubagentDetailScene.d.ts +7 -1
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
- package/lib/types/components/SubagentDetailScene.js +160 -13
- package/lib/types/components/SurfaceEdges.d.ts +21 -0
- package/lib/types/components/SurfaceEdges.d.ts.map +1 -0
- package/lib/types/components/SurfaceEdges.js +7 -0
- package/lib/types/components/ThemePicker.d.ts +9 -0
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +74 -10
- package/lib/types/components/ThemePreviewPane.d.ts +26 -0
- package/lib/types/components/ThemePreviewPane.d.ts.map +1 -0
- package/lib/types/components/ThemePreviewPane.js +99 -0
- package/lib/types/components/WhaleGirl.d.ts +6 -0
- package/lib/types/components/WhaleGirl.d.ts.map +1 -1
- package/lib/types/components/WhaleGirl.js +18 -0
- package/lib/types/components/design-system/Divider.d.ts.map +1 -1
- package/lib/types/components/design-system/Divider.js +14 -9
- package/lib/types/components/launchpadActions.d.ts +73 -0
- package/lib/types/components/launchpadActions.d.ts.map +1 -0
- package/lib/types/components/launchpadActions.js +111 -0
- package/lib/types/components/launchpadLayout.d.ts +62 -0
- package/lib/types/components/launchpadLayout.d.ts.map +1 -0
- package/lib/types/components/launchpadLayout.js +176 -0
- package/lib/types/components/messages/AssistantThinkingMessage.js +4 -4
- package/lib/types/components/onboardingModel.d.ts +83 -0
- package/lib/types/components/onboardingModel.d.ts.map +1 -0
- package/lib/types/components/onboardingModel.js +128 -0
- package/lib/types/components/sidePanel/InfoPanel.d.ts +17 -0
- package/lib/types/components/sidePanel/InfoPanel.d.ts.map +1 -0
- package/lib/types/components/sidePanel/InfoPanel.js +124 -0
- package/lib/types/components/sidePanel/PanelBar.d.ts +45 -0
- package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -0
- package/lib/types/components/sidePanel/PanelBar.js +104 -0
- package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts +21 -0
- package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts.map +1 -0
- package/lib/types/components/sidePanel/PanelErrorBoundary.js +30 -0
- package/lib/types/components/sidePanel/PanelHost.d.ts +36 -0
- package/lib/types/components/sidePanel/PanelHost.d.ts.map +1 -0
- package/lib/types/components/sidePanel/PanelHost.js +81 -0
- package/lib/types/components/sidePanel/PanelPicker.d.ts +24 -0
- package/lib/types/components/sidePanel/PanelPicker.d.ts.map +1 -0
- package/lib/types/components/sidePanel/PanelPicker.js +45 -0
- package/lib/types/components/sidePanel/PanelStore.d.ts +55 -0
- package/lib/types/components/sidePanel/PanelStore.d.ts.map +1 -0
- package/lib/types/components/sidePanel/PanelStore.js +109 -0
- package/lib/types/components/sidePanel/SidePanelColumn.d.ts +31 -0
- package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -0
- package/lib/types/components/sidePanel/SidePanelColumn.js +70 -0
- package/lib/types/components/sidePanel/SidePanelLayout.d.ts +54 -0
- package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -0
- package/lib/types/components/sidePanel/SidePanelLayout.js +81 -0
- package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts +43 -0
- package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts.map +1 -0
- package/lib/types/components/sidePanel/SidePanelRuntimeContext.js +26 -0
- package/lib/types/components/sidePanel/TrajectoryPanel.d.ts +7 -0
- package/lib/types/components/sidePanel/TrajectoryPanel.d.ts.map +1 -0
- package/lib/types/components/sidePanel/TrajectoryPanel.js +263 -0
- package/lib/types/components/sidePanel/WorkspacePanel.d.ts +4 -0
- package/lib/types/components/sidePanel/WorkspacePanel.d.ts.map +1 -0
- package/lib/types/components/sidePanel/WorkspacePanel.js +171 -0
- package/lib/types/components/sidePanel/builtinPanels.d.ts +3 -0
- package/lib/types/components/sidePanel/builtinPanels.d.ts.map +1 -0
- package/lib/types/components/sidePanel/builtinPanels.js +229 -0
- package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts +53 -0
- package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/CompanionPanel.js +528 -0
- package/lib/types/components/sidePanel/companion/SplashMascot.d.ts +32 -0
- package/lib/types/components/sidePanel/companion/SplashMascot.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/SplashMascot.js +103 -0
- package/lib/types/components/sidePanel/companion/deepy.d.ts +75 -0
- package/lib/types/components/sidePanel/companion/deepy.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/deepy.js +222 -0
- package/lib/types/components/sidePanel/companion/mood.d.ts +176 -0
- package/lib/types/components/sidePanel/companion/mood.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/mood.js +212 -0
- package/lib/types/components/sidePanel/companion/pose.d.ts +43 -0
- package/lib/types/components/sidePanel/companion/pose.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/pose.js +34 -0
- package/lib/types/components/sidePanel/companion/skins.d.ts +102 -0
- package/lib/types/components/sidePanel/companion/skins.d.ts.map +1 -0
- package/lib/types/components/sidePanel/companion/skins.js +408 -0
- package/lib/types/components/sidePanel/dimensions.d.ts +60 -0
- package/lib/types/components/sidePanel/dimensions.d.ts.map +1 -0
- package/lib/types/components/sidePanel/dimensions.js +75 -0
- package/lib/types/components/sidePanel/jobsFocusStore.d.ts +21 -0
- package/lib/types/components/sidePanel/jobsFocusStore.d.ts.map +1 -0
- package/lib/types/components/sidePanel/jobsFocusStore.js +30 -0
- package/lib/types/components/sidePanel/panelBridge.d.ts +44 -0
- package/lib/types/components/sidePanel/panelBridge.d.ts.map +1 -0
- package/lib/types/components/sidePanel/panelBridge.js +55 -0
- package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts +22 -0
- package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts.map +1 -0
- package/lib/types/components/sidePanel/pluginPanelAdapter.js +148 -0
- package/lib/types/components/sidePanel/types.d.ts +110 -0
- package/lib/types/components/sidePanel/types.d.ts.map +1 -0
- package/lib/types/components/sidePanel/types.js +1 -0
- package/lib/types/components/sidePanel/usePanelInput.d.ts +5 -0
- package/lib/types/components/sidePanel/usePanelInput.d.ts.map +1 -0
- package/lib/types/components/sidePanel/usePanelInput.js +44 -0
- package/lib/types/components/sidePanel/useSidePanel.d.ts +47 -0
- package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -0
- package/lib/types/components/sidePanel/useSidePanel.js +284 -0
- package/lib/types/components/trajectory/MiniWake.d.ts +11 -1
- package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -1
- package/lib/types/components/trajectory/MiniWake.js +9 -3
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
- package/lib/types/components/workspaces/HomeWorkspaceRow.js +3 -2
- package/lib/types/deepseekPricing.d.ts +10 -9
- package/lib/types/deepseekPricing.d.ts.map +1 -1
- package/lib/types/deepseekPricing.js +18 -11
- package/lib/types/dsh-adapter/channel/attached-context.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/attached-context.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/attached-context.js +109 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +6 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +43 -0
- package/lib/types/dsh-adapter/channel/capabilities.d.ts +82 -0
- package/lib/types/dsh-adapter/channel/capabilities.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/capabilities.js +165 -0
- package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/command-completions.js +8 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +4 -12
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/context-bookkeeping.js +9 -4
- package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/external-commands.js +8 -2
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +13 -2
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/input-delivery.js +18 -1
- package/lib/types/dsh-adapter/channel/job-projection.d.ts +1 -0
- package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/job-projection.js +88 -3
- package/lib/types/dsh-adapter/channel/mode-actions.d.ts +13 -3
- package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/mode-actions.js +55 -9
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +8 -1
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/mode-permission-actions.js +25 -10
- package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/preferences.js +26 -8
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +42 -37
- package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/reports.js +13 -4
- package/lib/types/dsh-adapter/channel/session-reset.d.ts +5 -1
- package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-reset.js +1 -0
- package/lib/types/dsh-adapter/channel/state.d.ts +21 -3
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +3 -2
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +5 -0
- package/lib/types/dsh-adapter/channel/types.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/usage.d.ts +18 -1
- package/lib/types/dsh-adapter/channel/usage.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/usage.js +97 -2
- package/lib/types/dsh-adapter/channel.d.ts +1 -1
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +72 -4
- package/lib/types/dsh-adapter/context-occupancy.d.ts +204 -0
- package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -0
- package/lib/types/dsh-adapter/context-occupancy.js +299 -0
- package/lib/types/dsh-adapter/contract.d.ts +19 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +33 -3
- package/lib/types/dsh-adapter/index.d.ts +32 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +20 -1
- package/lib/types/dsh-adapter/jobs.d.ts +82 -16
- package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
- package/lib/types/dsh-adapter/jobs.js +104 -9
- package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -1
- package/lib/types/dsh-adapter/migrate/sessionize.js +11 -12
- package/lib/types/dsh-adapter/oauth/bonus.d.ts +27 -0
- package/lib/types/dsh-adapter/oauth/bonus.d.ts.map +1 -0
- package/lib/types/dsh-adapter/oauth/bonus.js +69 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.d.ts +2 -1
- package/lib/types/dsh-adapter/oauth/command.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.js +7 -4
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.d.ts +13 -10
- package/lib/types/dsh-adapter/oauth/credentials.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.js +61 -23
- package/lib/types/dsh-adapter/oauth/deepseek.d.ts +39 -0
- package/lib/types/dsh-adapter/oauth/deepseek.d.ts.map +1 -0
- package/lib/types/dsh-adapter/oauth/deepseek.js +189 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.d.ts +24 -16
- package/lib/types/dsh-adapter/oauth/index.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.js +34 -20
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/interaction.d.ts +14 -12
- package/lib/types/dsh-adapter/oauth/interaction.d.ts.map +1 -0
- package/lib/types/dsh-adapter/oauth/interaction.js +283 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.d.ts +2 -1
- package/lib/types/dsh-adapter/oauth/opener.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.js +1 -1
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.d.ts +10 -3
- package/lib/types/dsh-adapter/oauth/pi-ai.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.js +8 -3
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.d.ts +6 -3
- package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.js +11 -3
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.d.ts +14 -7
- package/lib/types/dsh-adapter/oauth/service.d.ts.map +1 -0
- package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.js +82 -17
- package/lib/types/dsh-adapter/panel-runtime-registry.d.ts +39 -0
- package/lib/types/dsh-adapter/panel-runtime-registry.d.ts.map +1 -0
- package/lib/types/dsh-adapter/panel-runtime-registry.js +84 -0
- package/lib/types/dsh-adapter/panels.d.ts +251 -0
- package/lib/types/dsh-adapter/panels.d.ts.map +1 -0
- package/lib/types/dsh-adapter/panels.js +407 -0
- package/lib/types/dsh-adapter/plugin-host.js +1 -1
- package/lib/types/dsh-adapter/plugin.d.ts +25 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +241 -44
- package/lib/types/dsh-adapter/presets.d.ts +26 -4
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +29 -5
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +6 -2
- package/lib/types/dsh-adapter/startup-args.d.ts +8 -0
- package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -0
- package/lib/types/dsh-adapter/startup-args.js +34 -0
- package/lib/types/dsh-adapter/subagents.d.ts +1 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +10 -1
- package/lib/types/history.d.ts +24 -7
- package/lib/types/history.d.ts.map +1 -1
- package/lib/types/history.js +77 -18
- package/lib/types/i18n.d.ts +908 -33
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +355 -17
- package/lib/types/ink/components/App.d.ts +14 -1
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +71 -61
- package/lib/types/ink/components/TerminalSizeContext.d.ts +7 -0
- package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-input.d.ts +11 -0
- package/lib/types/ink/hooks/use-input.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-input.js +8 -2
- package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-terminal-viewport.js +3 -1
- package/lib/types/ink/ink.d.ts +18 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +51 -8
- package/lib/types/ink/output.d.ts +10 -1
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +3 -3
- package/lib/types/ink/parse-keypress.d.ts +71 -3
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +815 -133
- package/lib/types/ink/render-node-to-output.d.ts +1 -1
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +37 -5
- package/lib/types/ink/screen.d.ts +26 -1
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/screen.js +96 -1
- package/lib/types/ink/selection.d.ts +23 -1
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +102 -26
- package/lib/types/ink/sixel-codec.d.ts +11 -0
- package/lib/types/ink/sixel-codec.d.ts.map +1 -1
- package/lib/types/ink/sixel-codec.js +42 -8
- package/lib/types/ink/terminal-querier.d.ts +25 -0
- package/lib/types/ink/terminal-querier.d.ts.map +1 -1
- package/lib/types/ink/terminal-querier.js +42 -0
- package/lib/types/ink/update-overflow-guard.d.ts +24 -0
- package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
- package/lib/types/ink/update-overflow-guard.js +25 -3
- package/lib/types/math/renderer.d.ts +2 -0
- package/lib/types/math/renderer.d.ts.map +1 -1
- package/lib/types/math/renderer.js +4 -0
- package/lib/types/minimalUiMode.d.ts +19 -0
- package/lib/types/minimalUiMode.d.ts.map +1 -0
- package/lib/types/minimalUiMode.js +23 -0
- package/lib/types/oauth.d.ts +5 -10
- package/lib/types/oauth.d.ts.map +1 -1
- package/lib/types/oauth.js +5 -10
- package/lib/types/onboardingPrefs.d.ts +52 -0
- package/lib/types/onboardingPrefs.d.ts.map +1 -0
- package/lib/types/onboardingPrefs.js +107 -0
- package/lib/types/panels.d.ts +4 -0
- package/lib/types/panels.d.ts.map +1 -0
- package/lib/types/panels.js +4 -0
- package/lib/types/screens/Chat.d.ts +25 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +1149 -302
- package/lib/types/screens/Launchpad.d.ts +194 -0
- package/lib/types/screens/Launchpad.d.ts.map +1 -0
- package/lib/types/screens/Launchpad.js +607 -0
- package/lib/types/screens/Onboarding.d.ts +37 -0
- package/lib/types/screens/Onboarding.d.ts.map +1 -0
- package/lib/types/screens/Onboarding.js +492 -0
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/SessionSupervisor.js +18 -12
- package/lib/types/screens/StatusLine.d.ts +4 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +43 -25
- package/lib/types/screens/StatusMetrics.d.ts +22 -0
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +20 -0
- package/lib/types/screens/chatOverlay.d.ts +18 -1
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +3 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts +1 -2
- package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/model.js +4 -2
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +7 -3
- package/lib/types/settings/definitions.d.ts +93 -0
- package/lib/types/settings/definitions.d.ts.map +1 -1
- package/lib/types/settings/definitions.js +87 -4
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +13 -7
- package/lib/types/tuiDisplayPrefs.d.ts +42 -2
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +74 -0
- package/lib/types/update.d.ts +53 -9
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +103 -46
- package/lib/types/utils/bmp.d.ts.map +1 -1
- package/lib/types/utils/bmp.js +8 -1
- package/lib/types/utils/clipboard.d.ts.map +1 -1
- package/lib/types/utils/clipboard.js +25 -6
- package/lib/types/utils/credentials.d.ts.map +1 -1
- package/lib/types/utils/credentials.js +5 -1
- package/lib/types/utils/draftWordBoundary.d.ts +23 -0
- package/lib/types/utils/draftWordBoundary.d.ts.map +1 -0
- package/lib/types/utils/draftWordBoundary.js +161 -0
- package/lib/types/utils/fileTarget.d.ts.map +1 -1
- package/lib/types/utils/fileTarget.js +7 -9
- package/lib/types/utils/inputPaste.d.ts +62 -0
- package/lib/types/utils/inputPaste.d.ts.map +1 -0
- package/lib/types/utils/inputPaste.js +111 -0
- package/lib/types/utils/intl.d.ts +8 -0
- package/lib/types/utils/intl.d.ts.map +1 -1
- package/lib/types/utils/intl.js +11 -0
- package/lib/types/utils/keymap.d.ts +13 -3
- package/lib/types/utils/keymap.d.ts.map +1 -1
- package/lib/types/utils/keymap.js +27 -5
- package/lib/types/utils/pastedImagePath.d.ts +8 -0
- package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
- package/lib/types/utils/pastedImagePath.js +29 -5
- package/package.json +128 -106
- package/presets/liangshen/agent.cordis.yml +4 -1
- package/presets/liangshen/preset.yml +1 -1
- package/{dsh-ecosystem-spec → tui-profile}/registry/README.md +1 -1
- package/{dsh-ecosystem-spec → tui-profile}/registry/registry-0.15.json +1 -1
- package/lib/types/minimalMode.d.ts +0 -13
- package/lib/types/minimalMode.d.ts.map +0 -1
- package/lib/types/minimalMode.js +0 -17
- package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +0 -21
- package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +0 -143
- package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +0 -11
- package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +0 -44
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +0 -193
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +0 -69
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.d.ts +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.js +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.d.ts +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.js +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.d.ts +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.js +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.d.ts +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.js +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/decision-events-v1alpha1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/scene-v1alpha1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/settings-section-v1alpha1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/tui-channel-v1alpha1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/workspace-provider-v1alpha1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/host-descriptor.tui.example.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/registry/permissions-0.1.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/schemas/conformance-claim.schema.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/schemas/effect-ledger-record.schema.json +0 -0
- /package/{dsh-ecosystem-spec → tui-profile}/schemas/host-descriptor.schema.json +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { t, getLang, setLang, isLang, writeLangPref, readLangPref, subscribeLang, LANGS } from '../i18n.js';
|
|
4
|
+
import { checkForTuiUpdate, installedTuiVersion } from '../update.js';
|
|
5
|
+
import { installedKernelVersion } from '../dsh-adapter/contract.js';
|
|
4
6
|
import { readThemePref } from '../themePrefs.js';
|
|
5
7
|
import { readPresetPref } from '../presetPrefs.js';
|
|
6
8
|
import { readModelPref } from '../modelPrefs.js';
|
|
@@ -43,8 +45,8 @@ import { useSelection } from '../ink/hooks/use-selection.js';
|
|
|
43
45
|
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
44
46
|
import { LogoHeader, MessageList } from '../components/MessageList.js';
|
|
45
47
|
import { splashFontIdOf } from '../components/splashFonts.js';
|
|
46
|
-
import { StarPrompt } from '../components/StarPrompt.js';
|
|
47
|
-
import { dueStarModal, markStarAsked, STAR_MILESTONES } from '../usageStats.js';
|
|
48
|
+
import { StarPrompt, WhaleCouponPrompt } from '../components/StarPrompt.js';
|
|
49
|
+
import { dueStarModal, markStarAsked, pendingStarMilestone, readUsage, STAR_MILESTONES } from '../usageStats.js';
|
|
48
50
|
import { TimelineRail } from '../components/TimelineRail.js';
|
|
49
51
|
import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
|
|
50
52
|
import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
|
|
@@ -53,15 +55,22 @@ import { TooltipLayer } from '../components/Tooltip.js';
|
|
|
53
55
|
import { PromptInput } from '../components/PromptInput.js';
|
|
54
56
|
import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
|
|
55
57
|
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
58
|
+
import { useSidePanel } from '../components/sidePanel/useSidePanel.js';
|
|
59
|
+
import { jobsFocusStore } from '../components/sidePanel/jobsFocusStore.js';
|
|
60
|
+
import { SidePanelLayout } from '../components/sidePanel/SidePanelLayout.js';
|
|
61
|
+
import { SidePanelColumn } from '../components/sidePanel/SidePanelColumn.js';
|
|
62
|
+
import { PanelPicker, usePanelPickerRows } from '../components/sidePanel/PanelPicker.js';
|
|
56
63
|
import { AutoRecapRow } from '../components/AutoRecapRow.js';
|
|
57
64
|
import { CompactionStatusRow } from '../components/CompactionStatusRow.js';
|
|
58
65
|
import { BalanceReportRow } from '../components/BalanceReportRow.js';
|
|
59
66
|
import { estimateSessionCostSnapshotCny } from '../deepseekPricing.js';
|
|
60
67
|
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
61
68
|
import { StatusLine } from './StatusLine.js';
|
|
69
|
+
import { channelContextOccupancy } from './StatusMetrics.js';
|
|
62
70
|
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
63
71
|
import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
|
|
64
72
|
import { ModelPicker } from '../components/ModelPicker.js';
|
|
73
|
+
import { HelpMenu } from '../components/HelpMenu.js';
|
|
65
74
|
import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
|
|
66
75
|
import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
|
|
67
76
|
import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
|
|
@@ -108,6 +117,12 @@ import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
|
108
117
|
import { useExternalVersion } from '../hooks/useExternalVersion.js';
|
|
109
118
|
import { TrajectoryScene } from './TrajectoryScene.js';
|
|
110
119
|
import { markHomeSeen } from '../homePrefs.js';
|
|
120
|
+
import { markOnboardingDone } from '../onboardingPrefs.js';
|
|
121
|
+
import { Launchpad, launchpadVisible } from './Launchpad.js';
|
|
122
|
+
import { resolveLaunchpadActions } from '../components/launchpadActions.js';
|
|
123
|
+
import { Onboarding } from './Onboarding.js';
|
|
124
|
+
import { appendHistory } from '../history.js';
|
|
125
|
+
import { isHiddenCommandName, isLocalCommandName, parseCommandName } from '../commands.js';
|
|
111
126
|
import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
|
|
112
127
|
import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
|
|
113
128
|
import { readTrajectorySeen, writeTrajectorySeen } from '../trajectoryPrefs.js';
|
|
@@ -140,6 +155,21 @@ const COMMAND_RESULT_CELLS = 200;
|
|
|
140
155
|
* file, but a healthy import of thousands of conversations finishes well
|
|
141
156
|
* inside this; without a cap a wedged child would hang the loop forever. */
|
|
142
157
|
const MIGRATE_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
158
|
+
/**
|
|
159
|
+
* 落地页参数行四段能点开的既有选择器(第五版):模型 → /model、思考深度 →
|
|
160
|
+
* /effort、模式 → /plan、权限预设 → /permission。盖在落地页之上时键盘归
|
|
161
|
+
* 选择器(见 useInput 的 launchpad 分支注释);其余 overlay 类型不在此列,
|
|
162
|
+
* 落地页期间照旧整块让位。
|
|
163
|
+
*/
|
|
164
|
+
const LAUNCHPAD_OVERLAY_KINDS = new Set([
|
|
165
|
+
'model', 'effort', 'plan', 'preset', 'permission',
|
|
166
|
+
// 第七版:左下角工作目录铭牌点开的工作区菜单(及其二级选择器/流程层)
|
|
167
|
+
// 也是「盖在落地页之上」的姿态——同一套 pickerPanels 挂载,Esc 回落地页。
|
|
168
|
+
'workspace-menu', 'workspace-picker', 'workspace-flow',
|
|
169
|
+
// 第八版:帮助入口也走「盖在落地页之上」的浮层姿态(overlay kind 'help',
|
|
170
|
+
// HelpMenu 经 pickerPanels 挂进 OverlayAbove)——不再收掉落地页进对话页。
|
|
171
|
+
'help',
|
|
172
|
+
]);
|
|
143
173
|
function cleanCommandError(error) {
|
|
144
174
|
try {
|
|
145
175
|
if (error instanceof Error) {
|
|
@@ -178,6 +208,27 @@ const NO_ROWS = [];
|
|
|
178
208
|
function capitalize(text) {
|
|
179
209
|
return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
|
|
180
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* The occupancy line `/tokens` and `/status` print.
|
|
213
|
+
*
|
|
214
|
+
* Occupancy has ONE source (see `dsh-adapter/context-occupancy.ts`): this is
|
|
215
|
+
* the same reading the footer's ctx field, the segmented bar and the
|
|
216
|
+
* context-low warning use — never the session's cumulative uncached input,
|
|
217
|
+
* which is a different quantity by orders of magnitude.
|
|
218
|
+
* @param channel - Live channel surface.
|
|
219
|
+
* @returns The localized line, or `undefined` when no window is known.
|
|
220
|
+
*/
|
|
221
|
+
function contextOccupancyLine(channel) {
|
|
222
|
+
const occupancy = channelContextOccupancy(channel);
|
|
223
|
+
if (occupancy === undefined || occupancy.contextWindow === undefined || occupancy.contextWindow <= 0)
|
|
224
|
+
return undefined;
|
|
225
|
+
const percent = Math.max(0, Math.min(100, Math.round((occupancy.usedTokens / occupancy.contextWindow) * 100)));
|
|
226
|
+
return t('context-occupancy', {
|
|
227
|
+
percent,
|
|
228
|
+
used: formatTokens(occupancy.usedTokens),
|
|
229
|
+
window: formatTokens(occupancy.contextWindow),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
181
232
|
/** Terminal-title spinner frames. */
|
|
182
233
|
const TITLE_SPINNER_FRAMES = ['⠂', '⠐'];
|
|
183
234
|
/** Searchable transcript text for one row (`/` incsearch):
|
|
@@ -223,12 +274,14 @@ let fallbackDialogStore;
|
|
|
223
274
|
let fallbackStatusStore;
|
|
224
275
|
/** Standalone mounts (tests, bare embeds) without the composition root's store. */
|
|
225
276
|
let fallbackActivityStore;
|
|
277
|
+
const noCouponSubscription = () => () => undefined;
|
|
278
|
+
const noCouponSnapshot = () => null;
|
|
226
279
|
/** Identity of one caret-preview dismissal: the token (its title) on the
|
|
227
280
|
* image, so the same image staged twice is dismissed per token. */
|
|
228
281
|
function peekKey(image, title) {
|
|
229
282
|
return `${title ?? ''} ${image.id}`;
|
|
230
283
|
}
|
|
231
|
-
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, starPrompt, }) {
|
|
284
|
+
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, bonusNotices, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, launchpadOnBoot, onboardingOnBoot, starPrompt, }) {
|
|
232
285
|
const writeRaw = React.useContext(TerminalWriteContext);
|
|
233
286
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
234
287
|
// DEFAULT lane on purpose (useExternalVersion): the channel version bumps
|
|
@@ -258,6 +311,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
258
311
|
// extensions row share one inert store that never holds a dialog.
|
|
259
312
|
const dialogs = extensionDialogs ?? (fallbackDialogStore ??= new TuiDialogStore());
|
|
260
313
|
const dialogSnapshot = React.useSyncExternalStore(listener => dialogs.subscribe(listener), () => dialogs.getSnapshot());
|
|
314
|
+
const coupon = React.useSyncExternalStore(bonusNotices?.subscribe ?? noCouponSubscription, bonusNotices?.getSnapshot ?? noCouponSnapshot);
|
|
261
315
|
// Plugin status contributions: text keys join into one line; bounded rich
|
|
262
316
|
// views keep their own rows immediately above the prompt.
|
|
263
317
|
const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
|
|
@@ -405,7 +459,111 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
405
459
|
* on" has not been answered yet. Every later launch starts on the chat
|
|
406
460
|
* screen, and the screen stays reachable.
|
|
407
461
|
*/
|
|
408
|
-
|
|
462
|
+
// 第七版:启动页在开时**不再**预开会话浏览器。旧姿态是「先收落地页再开
|
|
463
|
+
// 整屏」,浏览器必须提前藏在下面;现在整屏(会话/设置/任务面板)盖在
|
|
464
|
+
// 落地页**之上**、Esc 退回落地页,按需打开即可——boot 时同时为真反而会
|
|
465
|
+
// 让浏览器盖住落地页(渲染顺序见各 early-return)。
|
|
466
|
+
const [supervisorOpen, setSupervisorOpen] = React.useState(openHomeOnBoot === true && launchpadOnBoot !== true);
|
|
467
|
+
/**
|
|
468
|
+
* The launchpad: the landing page every ordinary launch starts on.
|
|
469
|
+
*
|
|
470
|
+
* Seeded from `launchpadOnBoot`, and the screen that closes it is the one
|
|
471
|
+
* that decides what comes next — a submitted line hands over to the chat
|
|
472
|
+
* screen, an action hands over to whatever surface that action opens.
|
|
473
|
+
*/
|
|
474
|
+
const [launchpadOpen, setLaunchpadOpen] = React.useState(launchpadOnBoot === true);
|
|
475
|
+
/**
|
|
476
|
+
* The launchpad's draft. It lives HERE, not inside the screen, because the
|
|
477
|
+
* screen unmounts the moment the user submits: a draft owned by an
|
|
478
|
+
* unmounting component would be lost in exactly the transition it exists
|
|
479
|
+
* to carry.
|
|
480
|
+
*/
|
|
481
|
+
const [launchpadDraft, setLaunchpadDraft] = React.useState('');
|
|
482
|
+
const [launchpadCaret, setLaunchpadCaret] = React.useState(0);
|
|
483
|
+
const [launchpadFocus, setLaunchpadFocus] = React.useState(-1);
|
|
484
|
+
/**
|
|
485
|
+
* 「整屏盖启动页」的显式授权(第七版防御位,用户实测回归:启动页一闪而过
|
|
486
|
+
* 被顶掉)。整屏分支排在落地页**之前**,任何一处状态在开机后被异步置真
|
|
487
|
+
* (杂散输入/宿主事件/未来新代码)都会把落地页挤掉。授权位只有**从落地页
|
|
488
|
+
* 出发的交互**(入口行/空输入 Esc/目录铭牌/命令面板里的整屏命令、Continue
|
|
489
|
+
* 的兜底浏览器)才置真;覆盖屏全部收起时自动落 false。开机后即便某个整屏
|
|
490
|
+
* 状态被误置真,落地页仍在最上层——「浮层可以盖、整屏必须经授权」。
|
|
491
|
+
*/
|
|
492
|
+
const launchpadCoverRef = React.useRef(false);
|
|
493
|
+
/**
|
|
494
|
+
* 这一帧到底出不出落地页:状态开着还不够,minimal 模式(`dsh-tui.minimal`)
|
|
495
|
+
* 下它整块不存在——`minimalMode.ts` 的标志由 channel 在设置落地后写入,
|
|
496
|
+
* 建 state 时读不到,所以判定必须放在**渲染期**读(与其它 minimal 门同一口径)。
|
|
497
|
+
* 键盘守卫、渲染分支与"有没有整屏界面"的判定都认这一个值,三者不会分叉。
|
|
498
|
+
*/
|
|
499
|
+
/**
|
|
500
|
+
* 右下角铭牌的版本号:`installedTuiVersion()` 每次都要读一遍 package.json,
|
|
501
|
+
* 而它在一个进程里不会变——mount 时读一次就够。
|
|
502
|
+
*/
|
|
503
|
+
const tuiVersion = React.useMemo(() => installedTuiVersion(), []);
|
|
504
|
+
/**
|
|
505
|
+
* 内核(dsh)版本(第七版:落地页右下角双版本铭牌)。真实来源见
|
|
506
|
+
* `contract.installedKernelVersion`(宿主 CLI 的 manifest,回落内核线包);
|
|
507
|
+
* 两级都读不到 = undefined → 铭牌只画 TUI 段,绝不编造。
|
|
508
|
+
*/
|
|
509
|
+
const kernelVersion = React.useMemo(() => installedKernelVersion(), []);
|
|
510
|
+
const launchpadShown = launchpadOpen && launchpadVisible();
|
|
511
|
+
/**
|
|
512
|
+
* 落地页之上是否盖着一个参数选择器(第五版:参数行四段各自可点,点开的
|
|
513
|
+
* 是聊天页同一套 /model · /effort · /plan · /permission overlay)。这时
|
|
514
|
+
* 键盘归选择器(Esc 关它回到落地页),渲染也要把 pickerPanels 带进
|
|
515
|
+
* 落地页分支——不然整屏 early-return 把 overlay 吞了,"点了没反应"。
|
|
516
|
+
*/
|
|
517
|
+
const launchpadOverlayUp = launchpadShown && LAUNCHPAD_OVERLAY_KINDS.has(overlay.kind);
|
|
518
|
+
/**
|
|
519
|
+
* 渲染在**覆盖层**(而不是整屏 early-return)的那几个命令。
|
|
520
|
+
*
|
|
521
|
+
* 落地页的快捷入口与向导招式卡的「试一下」都走 `runCommand`,而 supervisor /
|
|
522
|
+
* settings / help 的 early-return 排在两个界面**之后**:不收掉当前界面就是
|
|
523
|
+
* "点了没反应",状态还滞留着、等界面关掉才突然弹出来。默认收,白名单只留给覆盖层。
|
|
524
|
+
*/
|
|
525
|
+
const overlayCommandNames = React.useMemo(() => new Set(['model', 'effort', 'plan', 'preset', 'permission']), []);
|
|
526
|
+
/**
|
|
527
|
+
* 打开**整屏界面**的命令(第七版):从落地页触发这些命令时**不收掉落地页**
|
|
528
|
+
* ——整屏盖在落地页之上渲染(它们的 early-return 排在落地页分支之前),
|
|
529
|
+
* Esc 退出整屏回到落地页(草稿/参数/焦点原样保留)。这是「从启动页进入
|
|
530
|
+
* 对话页的唯一路径 = Enter 提交一条非命令消息」的落地:任何返回键都不再
|
|
531
|
+
* 把人甩到对话页。其余命令(star / update / help / 转录输出类)的反馈在
|
|
532
|
+
* 对话页,仍按旧约收掉落地页再执行。
|
|
533
|
+
*/
|
|
534
|
+
const launchpadScreenCommands = React.useMemo(() => new Set(['home', 'resume', 'agentview', 'settings', 'jobs', 'tree', 'agents', 'setup', 'bg', 'background']), []);
|
|
535
|
+
/**
|
|
536
|
+
* 落地页条件位②(有新版本):`checkForTuiUpdate()`(与 /update 同一条
|
|
537
|
+
* 判定,src/update.ts)在启动页第一次挂起时后台探一次——registry 延迟
|
|
538
|
+
* 不许拖慢第一帧;失败/离线静默为 false(绝不放假按钮)。
|
|
539
|
+
*/
|
|
540
|
+
const [launchpadUpdateAvailable, setLaunchpadUpdateAvailable] = React.useState(false);
|
|
541
|
+
const launchpadUpdateProbedRef = React.useRef(false);
|
|
542
|
+
React.useEffect(() => {
|
|
543
|
+
if (!launchpadShown || launchpadUpdateProbedRef.current)
|
|
544
|
+
return;
|
|
545
|
+
launchpadUpdateProbedRef.current = true;
|
|
546
|
+
void checkForTuiUpdate().then(update => {
|
|
547
|
+
setLaunchpadUpdateAvailable(update !== undefined);
|
|
548
|
+
}).catch(() => undefined);
|
|
549
|
+
}, [launchpadShown]);
|
|
550
|
+
/**
|
|
551
|
+
* 落地页条件位③(投喂一颗 Star):与开屏求 star 弹窗**同一口径**——
|
|
552
|
+
* `usageStats`(~/.dsh-tui/usage.json)里有未报过的已达档里程碑
|
|
553
|
+
* (`pendingStarMilestone`,首档 24h)且本进程尚未 star 成功。star 成功
|
|
554
|
+
* 后(`starred` 翻真)按钮立即消失;记账过的档不再纠缠。
|
|
555
|
+
*/
|
|
556
|
+
/**
|
|
557
|
+
* The first-run guide. Renders above the launchpad (see the prop docs): a
|
|
558
|
+
* launch that needs setup has not answered the launchpad's question yet.
|
|
559
|
+
*/
|
|
560
|
+
const [onboardingOpen, setOnboardingOpen] = React.useState(onboardingOnBoot === true);
|
|
561
|
+
/**
|
|
562
|
+
* 落地页那条"第一次用?跑一遍引导"的横幅认的是**还欠一次引导**,而不是启动快照:
|
|
563
|
+
* 完成(写进 onboarding.json)之后立刻收掉;跳过刻意保留——没记账,下次启动还会问,
|
|
564
|
+
* 横幅说的正是这件事。
|
|
565
|
+
*/
|
|
566
|
+
const [onboardingPending, setOnboardingPending] = React.useState(onboardingOnBoot === true);
|
|
409
567
|
/** `/tree` opens the session family tree (pi's Session Tree): every rewind
|
|
410
568
|
* fork stitched back onto the message it diverged from, hover previews,
|
|
411
569
|
* and per-node rewind/fork/adopt actions. Like the supervisor, a screen. */
|
|
@@ -445,6 +603,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
445
603
|
* `null` 显式关闭,传 actions 覆写两个按钮(不跑真 gh、不开真浏览器)。 */
|
|
446
604
|
/** 本次会话是否已经 star 成功(开屏彩蛋标题切「捡到小星星啦」)。 */
|
|
447
605
|
const [starred, setStarred] = React.useState(false);
|
|
606
|
+
/**
|
|
607
|
+
* 落地页条件位③(投喂一颗 Star):与开屏求 star 弹窗**同一口径**——
|
|
608
|
+
* `usageStats`(~/.dsh-tui/usage.json)里有未报过的已达档里程碑
|
|
609
|
+
* (`pendingStarMilestone`,首档 24h)且本进程尚未 star 成功。star 成功
|
|
610
|
+
* 后(`starred` 翻真)按钮立即消失;记账过的档不再纠缠。readUsage 是
|
|
611
|
+
* 文件读,只在 starred 翻真或测试缝变化时重算,不逐帧读盘。
|
|
612
|
+
*/
|
|
613
|
+
const launchpadStarDue = React.useMemo(() => !starred && pendingStarMilestone(readUsage(starPrompt?.dir)) !== null,
|
|
614
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- dir 经测试缝注入
|
|
615
|
+
[starred, starPrompt]);
|
|
448
616
|
const [starModal, setStarModal] = React.useState(null);
|
|
449
617
|
// 单发闩:只在第一个"安静的开屏视口"上武装定时器。700ms 窗口内整屏
|
|
450
618
|
// 界面打开 → cleanup 掐掉定时器且**不再重臂**(记账只发生在回调里,
|
|
@@ -459,7 +627,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
459
627
|
return;
|
|
460
628
|
if (starPrompt === null)
|
|
461
629
|
return;
|
|
462
|
-
if (supervisorOpen || treeOpen || settingsOpen || channel.working)
|
|
630
|
+
if (supervisorOpen || treeOpen || settingsOpen || launchpadShown || onboardingOpen || channel.working)
|
|
463
631
|
return;
|
|
464
632
|
starModalArmedRef.current = true;
|
|
465
633
|
// 让开屏先画半秒:弹窗压在介绍动画之上,而不是同抢第一帧。
|
|
@@ -481,7 +649,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
481
649
|
}, 700);
|
|
482
650
|
return () => { clearTimeout(timer); };
|
|
483
651
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- 只在整屏界面开合时重判;闩保证只武装一次
|
|
484
|
-
}, [supervisorOpen, treeOpen, settingsOpen]);
|
|
652
|
+
}, [supervisorOpen, treeOpen, settingsOpen, launchpadShown, onboardingOpen]);
|
|
485
653
|
/** `/star` 命令、开屏标语的点击/`Alt+S` 共用的一键动作:异步跑 gh,界面
|
|
486
654
|
* 全程不阻塞,结果回来按四类各报一句(成功 / 没装 gh / 没登录 / 失败)。
|
|
487
655
|
* `starPrompt.onStar` 存在时走同一条测试缝(夹具因此不会真的去 star)。 */
|
|
@@ -590,10 +758,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
590
758
|
const workspaceFlowAbortRef = React.useRef(null);
|
|
591
759
|
/** `/preset` agent-preset roster (issue #8): loads async, persists. */
|
|
592
760
|
const [presetOptions, setPresetOptions] = React.useState([]);
|
|
761
|
+
/**
|
|
762
|
+
* 落地页参数行的模式段(第六版设计 1)显示 preset 的**显示名**
|
|
763
|
+
* (Standard/PTC/极简…),名册是异步的——落地页出来时顺手预热一次
|
|
764
|
+
* (空名册不写;失败静默,段缺省不画)。/preset 自己的加载路径不动。
|
|
765
|
+
*/
|
|
766
|
+
React.useEffect(() => {
|
|
767
|
+
if (!launchpadShown || presetOptions.length > 0)
|
|
768
|
+
return;
|
|
769
|
+
let cancelled = false;
|
|
770
|
+
channel.listPresets()
|
|
771
|
+
.then(list => { if (!cancelled && list.length > 0)
|
|
772
|
+
setPresetOptions(list); })
|
|
773
|
+
.catch(() => { });
|
|
774
|
+
return () => { cancelled = true; };
|
|
775
|
+
}, [launchpadShown, presetOptions.length, channel]);
|
|
593
776
|
/** `/effort` adapter levels: load async before the slider opens. */
|
|
594
777
|
const [effortOptions, setEffortOptions] = React.useState([]);
|
|
595
778
|
const [themeName, setTheme] = useTheme();
|
|
596
779
|
const { rows: terminalRows } = useTerminalSize();
|
|
780
|
+
/**
|
|
781
|
+
* 帮助盖屏(第八版,overlay kind 'help')的滚动视口:HelpMenu 自带
|
|
782
|
+
* ScrollBox,键盘(↑/↓/PgUp/PgDn/Home/End)由 Chat 的 overlay 分支驱动。
|
|
783
|
+
* 视口预算与 PromptInput 的 helpViewportHeight 同式(PR #446 的口径)。
|
|
784
|
+
*/
|
|
785
|
+
const helpCoverScrollRef = React.useRef(null);
|
|
786
|
+
const helpCoverViewportHeight = Math.max(3, Math.min(terminalRows - 7, 15));
|
|
597
787
|
const [showAllMessages, setShowAllMessages] = React.useState(false);
|
|
598
788
|
/** Scope the fold to this question: an aborted ask can promote its queued
|
|
599
789
|
* successor without ever publishing an idle (null) snapshot. */
|
|
@@ -769,9 +959,40 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
769
959
|
/** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
|
|
770
960
|
const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
|
|
771
961
|
const [jobsPanelOpen, setJobsPanelOpen] = React.useState(false);
|
|
962
|
+
/** Job id the panel should focus on open: set by a transcript card click
|
|
963
|
+
* (open the panel AT that job), cleared on close so the keyboard/command
|
|
964
|
+
* path reopens at the top. */
|
|
965
|
+
const [jobsPanelFocusId, setJobsPanelFocusId] = React.useState(null);
|
|
966
|
+
// Side-panel routing needs the controller, which is created further down;
|
|
967
|
+
// a ref keeps this identity-stable callback fresh anyway.
|
|
968
|
+
const sidePanelRef = React.useRef(null);
|
|
772
969
|
// MessageList forwards these open handlers to every memoized row. Their
|
|
773
970
|
// identities must survive token/metrics updates, including for tool rows.
|
|
774
|
-
const openJobsPanel = React.useCallback(() =>
|
|
971
|
+
const openJobsPanel = React.useCallback((focusId) => {
|
|
972
|
+
const sidePanel = sidePanelRef.current;
|
|
973
|
+
// Split mode: open the jobs side panel at the requested job (the focus
|
|
974
|
+
// lane carries the id; a fresh nonce refocuses even for the same id).
|
|
975
|
+
if (sidePanel !== null && sidePanel.split && sidePanel.enabledPanelIds.includes('jobs')) {
|
|
976
|
+
if (typeof focusId === 'string' && focusId !== '')
|
|
977
|
+
jobsFocusStore.request(focusId);
|
|
978
|
+
sidePanel.openPanel('jobs', { focus: true });
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
// Narrow / inline fallback: the full-screen overlay (unchanged).
|
|
982
|
+
if (typeof focusId === 'string' && focusId !== '')
|
|
983
|
+
setJobsPanelFocusId(focusId);
|
|
984
|
+
setJobsPanelOpen(true);
|
|
985
|
+
}, []);
|
|
986
|
+
/** Ctrl+A / detail 回退:侧栏分栏且 agents 已启用时打开右栏 Panel(内部
|
|
987
|
+
* dashboard ↔ detail 二级路由自己管);窄屏 / inline 保留整屏形态。 */
|
|
988
|
+
const openSubagentDashboard = React.useCallback(() => {
|
|
989
|
+
const sidePanel = sidePanelRef.current;
|
|
990
|
+
if (sidePanel !== null && sidePanel.split && sidePanel.enabledPanelIds.includes('agents')) {
|
|
991
|
+
sidePanel.openPanel('agents', { focus: true });
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
setSubagentDashboardOpen(true);
|
|
995
|
+
}, []);
|
|
775
996
|
/** Detail view for a specific subagent (opened from dashboard). */
|
|
776
997
|
const [subagentDetailId, setSubagentDetailId] = React.useState(null);
|
|
777
998
|
/**
|
|
@@ -799,14 +1020,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
799
1020
|
const closeScene = React.useCallback(() => {
|
|
800
1021
|
setSceneOpen(false);
|
|
801
1022
|
}, []);
|
|
802
|
-
/**
|
|
803
|
-
|
|
1023
|
+
/**
|
|
1024
|
+
* 整屏分支的「盖启动页」闸门(第七版防御位,用户实测回归:启动页一闪
|
|
1025
|
+
* 而过被顶掉)。落地页在屏上时,只有**经 `launchpadCoverRef` 授权**
|
|
1026
|
+
* (= 从落地页出发的交互打开)的整屏才盖它;否则该整屏状态被无视
|
|
1027
|
+
* (渲染落到落地页),启动页永远不被开机期的杂散状态挤掉。落地页不在
|
|
1028
|
+
* 屏上时闸门恒开(普通姿态与从前逐字节一致)。
|
|
1029
|
+
*
|
|
1030
|
+
* ⚠ 这一段必须排在组件**所有** early-return 之前(hooks 规则):曾放在
|
|
1031
|
+
* onboarding/interrupt 分支之后,向导一开就少跑这个 effect,React 直接
|
|
1032
|
+
* 报 hooks 乱序(verify-launchpad-onboarding-chat A2/E5/H2 全红)。
|
|
1033
|
+
*/
|
|
1034
|
+
const launchpadGate = () => !launchpadShown || launchpadCoverRef.current;
|
|
1035
|
+
// 覆盖屏全部收起时收回授权:下一次打开必须再经过落地页自己的交互。
|
|
1036
|
+
const launchpadCoverScreenUp = supervisorOpen || treeOpen || settingsOpen
|
|
1037
|
+
|| jobsPanelOpen || subagentDashboardOpen || subagentDetailId !== null || sceneOpen;
|
|
1038
|
+
React.useEffect(() => {
|
|
1039
|
+
if (!launchpadCoverScreenUp)
|
|
1040
|
+
launchpadCoverRef.current = false;
|
|
1041
|
+
}, [launchpadCoverScreenUp]);
|
|
1042
|
+
/**
|
|
1043
|
+
* 从落地页出发的交互要开整屏前先授权(配合 `launchpadGate`):调用点只在
|
|
1044
|
+
* 落地页自己的回调里(onAction / onCommandPick / onEscape 的会话浏览路)。
|
|
1045
|
+
* 异步误置真的整屏状态没有这道授权 → 闸门挡下,落地页留在最上层。
|
|
1046
|
+
*/
|
|
1047
|
+
const authorizeLaunchpadCover = () => { launchpadCoverRef.current = true; };
|
|
1048
|
+
/**
|
|
1049
|
+
* Open the trajectory, mark failures seen, and retire the key hint for good.
|
|
1050
|
+
*
|
|
1051
|
+
* Ctrl+T and `/trace` share this one entry point, and it is SPLIT-AWARE the
|
|
1052
|
+
* same way `/jobs` and `/agents` already are: while the sidebar is
|
|
1053
|
+
* rendering, the trajectory opens as the `trajectory` panel inside it
|
|
1054
|
+
* instead of taking the whole screen. `options.fullscreen` is the escape
|
|
1055
|
+
* hatch the panel's own ⤢ button uses — without it that button would route
|
|
1056
|
+
* straight back into the panel it is trying to leave.
|
|
1057
|
+
*/
|
|
1058
|
+
const openScene = React.useCallback((options) => {
|
|
804
1059
|
seenFailuresRef.current = trajectoryRef.current?.counts.errors ?? 0;
|
|
805
1060
|
setTrajectorySeen(previous => {
|
|
806
1061
|
if (!previous)
|
|
807
1062
|
writeTrajectorySeen();
|
|
808
1063
|
return true;
|
|
809
1064
|
});
|
|
1065
|
+
const controller = sidePanelRef.current;
|
|
1066
|
+
if (options?.fullscreen !== true
|
|
1067
|
+
&& controller !== null
|
|
1068
|
+
&& controller.split
|
|
1069
|
+
&& controller.enabledPanelIds.includes('trajectory')) {
|
|
1070
|
+
controller.openPanel('trajectory', { focus: true });
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
810
1073
|
setSceneOpen(true);
|
|
811
1074
|
}, []);
|
|
812
1075
|
/**
|
|
@@ -837,6 +1100,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
837
1100
|
}).catch(() => undefined);
|
|
838
1101
|
}
|
|
839
1102
|
}, [agentViewReturnId, channel, repaintTranscript]);
|
|
1103
|
+
/**
|
|
1104
|
+
* Leave the first-run guide.
|
|
1105
|
+
*
|
|
1106
|
+
* `done` writes the one-shot marker; `skipped` deliberately does NOT — the
|
|
1107
|
+
* user has not answered, and a later launch is exactly when they might.
|
|
1108
|
+
* Both cases land on the launchpad rather than the transcript: the wizard
|
|
1109
|
+
* interrupted a launch, so the launch resumes where it left off.
|
|
1110
|
+
*
|
|
1111
|
+
* The write is best-effort by design (`markOnboardingDone` returns false
|
|
1112
|
+
* when the data directory is unwritable); a read-only install gets one more
|
|
1113
|
+
* offer next launch, which is the recoverable end of the trade.
|
|
1114
|
+
*/
|
|
1115
|
+
const closeOnboarding = React.useCallback((outcome) => {
|
|
1116
|
+
setOnboardingOpen(false);
|
|
1117
|
+
if (outcome === 'done') {
|
|
1118
|
+
const written = markOnboardingDone();
|
|
1119
|
+
setOnboardingPending(false);
|
|
1120
|
+
channel.notify(t(written ? 'onboarding-finished' : 'onboarding-write-failed'), {
|
|
1121
|
+
color: written ? 'success' : 'warning',
|
|
1122
|
+
timeoutMs: written ? 3000 : 6000,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
channel.notify(t('onboarding-skipped'), { timeoutMs: 4000 });
|
|
1127
|
+
}
|
|
1128
|
+
}, [channel]);
|
|
840
1129
|
/** The startup summary gives way to transcript rows after the first local command or message. */
|
|
841
1130
|
const loadedContextVisible = channel.rows.length === 0 && channel.loadedContext !== undefined;
|
|
842
1131
|
/** Startup context panel: collapsed by default, toggled with Ctrl+P. */
|
|
@@ -1631,6 +1920,69 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1631
1920
|
: t('migrate-failed', { n: failures }), failures === 0 ? { timeoutMs: 6000 } : { color: 'error', timeoutMs: 10000 });
|
|
1632
1921
|
})();
|
|
1633
1922
|
};
|
|
1923
|
+
/**
|
|
1924
|
+
* Close the launchpad and hand the draft to the chat screen.
|
|
1925
|
+
*
|
|
1926
|
+
* THREE cases, and they are genuinely different:
|
|
1927
|
+
*
|
|
1928
|
+
* - a slash command → `runCommand`, the same dispatch a typed command
|
|
1929
|
+
* takes in the composer. The line is NOT submitted to the model.
|
|
1930
|
+
* Recognition is the composer's OWN rule (第六版 BUG 1 修复): the merged
|
|
1931
|
+
* command list (locals + plugin/registry commands via channel.commandList)
|
|
1932
|
+
* decides whether the line is a command — isLocalCommandName alone missed
|
|
1933
|
+
* registry-only names (e.g. /plan, /goal), which then fell through to
|
|
1934
|
+
* channel.submit and went to the model as a user message.
|
|
1935
|
+
* - ordinary text → SENT DIRECTLY (fifth revision, user-reported bug:
|
|
1936
|
+
* "按了回车就直接进入流式输出"). The line rides the composer's own
|
|
1937
|
+
* submit path (`channel.submit`, which queues through the DSH inbox
|
|
1938
|
+
* while a turn is running) — no draft is parked anywhere, the composer
|
|
1939
|
+
* mounts EMPTY because the content is already gone as the first turn.
|
|
1940
|
+
* - empty → nothing to send; just show the conversation.
|
|
1941
|
+
*
|
|
1942
|
+
* History is appended for the two non-empty cases (matching what PromptInput
|
|
1943
|
+
* does on submit) so the launchpad's first line is reachable with ↑ later.
|
|
1944
|
+
*/
|
|
1945
|
+
const closeLaunchpad = React.useCallback((submit) => {
|
|
1946
|
+
const text = submit.trim();
|
|
1947
|
+
setLaunchpadOpen(false);
|
|
1948
|
+
// 首启时 openHomeOnBoot 与落地页同时为真:会话浏览器已经开着、只是被落地页盖住。
|
|
1949
|
+
// 提交首句后必须把它收掉,否则用户落到浏览器而不是"草稿就在眼前的对话",
|
|
1950
|
+
// 与本函数 doc 承诺的落点直接矛盾。
|
|
1951
|
+
setSupervisorOpen(false);
|
|
1952
|
+
setLaunchpadFocus(-1);
|
|
1953
|
+
setLaunchpadDraft('');
|
|
1954
|
+
setLaunchpadCaret(0);
|
|
1955
|
+
if (text === '')
|
|
1956
|
+
return;
|
|
1957
|
+
void appendHistory(text);
|
|
1958
|
+
const parsed = text.startsWith('/') ? parseCommandName(text) : undefined;
|
|
1959
|
+
// 第八版:/help 在落地页上也是盖屏浮层(补全面板被 Esc 收掉后直接
|
|
1960
|
+
// Enter 的那条路)——不收落地页、不进对话页。聊天页里 /help 的行为
|
|
1961
|
+
// 不变(那边不走这个回调)。
|
|
1962
|
+
if (parsed !== undefined && parsed.name === 'help' && launchpadOpen) {
|
|
1963
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'help' } });
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
// 与 composer 的 tryRunCommand 同一条判定:合并命令表(LOCAL_COMMANDS +
|
|
1967
|
+
// channel.commandList 的插件/registry 命令)里有名字才是命令;hidden
|
|
1968
|
+
// 命令照旧认。判定之外的 / 开头行才走 submit(与聊天页 Enter 行为一致)。
|
|
1969
|
+
if (parsed !== undefined && (isLocalCommandName(parsed.name)
|
|
1970
|
+
|| isHiddenCommandName(parsed.name)
|
|
1971
|
+
|| channel.commandList.some(entry => entry.name === parsed.name))) {
|
|
1972
|
+
void runCommand(parsed.name, parsed.rawInput);
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
// 直接发送:与 composer 回车同一条提交路径。发出去之后输入框是空的
|
|
1976
|
+
// (内容已作为首轮发出,绝不"既发了又留在框里"),也没有交接提示——
|
|
1977
|
+
// 没有草稿要交,一句"已放进输入框"的 toast 反而是假的。
|
|
1978
|
+
channel.submit(text);
|
|
1979
|
+
}, [channel, launchpadOpen]);
|
|
1980
|
+
/**
|
|
1981
|
+
* The screen's command dispatcher. Every entry point reaches this one
|
|
1982
|
+
* closure — PromptInput's `onRunCommand`, the completion menu, this screen's
|
|
1983
|
+
* own `/` cases, and the launchpad handoff above. There is deliberately no
|
|
1984
|
+
* second dispatcher for the landing page.
|
|
1985
|
+
*/
|
|
1634
1986
|
const runCommand = (name, rawInput = '', images = []) => {
|
|
1635
1987
|
switch (name) {
|
|
1636
1988
|
case 'activity': {
|
|
@@ -1913,9 +2265,24 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
1913
2265
|
setSelectedId(null);
|
|
1914
2266
|
setSelectionActive(false);
|
|
1915
2267
|
return true;
|
|
1916
|
-
case 'compact':
|
|
1917
|
-
|
|
2268
|
+
case 'compact': {
|
|
2269
|
+
// The TUI's own transaction is the primary path: it owns the
|
|
2270
|
+
// `tui/compact` decision event, the progress row with live token
|
|
2271
|
+
// count, Esc cancellation, and the settle before a session switch
|
|
2272
|
+
// (issue #1092) — the official `dsh-command-compact` command has none
|
|
2273
|
+
// of that. A composition that mounts no compaction service falls back
|
|
2274
|
+
// to the registry command; one with neither says WHY up front instead
|
|
2275
|
+
// of looking usable and failing on use (channel/capabilities.ts).
|
|
2276
|
+
const compact = channel.capabilities().compact;
|
|
2277
|
+
if (compact.route === 'local') {
|
|
2278
|
+
channel.compact();
|
|
2279
|
+
return true;
|
|
2280
|
+
}
|
|
2281
|
+
if (compact.route === 'registry')
|
|
2282
|
+
return runExternalCommand('compact', rawInput, images);
|
|
2283
|
+
channel.notify(t('capability-unavailable', { name: 'compact', reason: t(compact.reasonKey) }), { color: 'warning', timeoutMs: 8000 });
|
|
1918
2284
|
return true;
|
|
2285
|
+
}
|
|
1919
2286
|
case 'trace':
|
|
1920
2287
|
// `/trace` is kept as the discoverable spelling of Ctrl+T: the
|
|
1921
2288
|
// command menu is where a user finds out the trajectory exists.
|
|
@@ -2075,14 +2442,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2075
2442
|
});
|
|
2076
2443
|
return true;
|
|
2077
2444
|
case 'tokens': {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2445
|
+
// Three separately-labelled facts, never two measures side by side:
|
|
2446
|
+
// what THIS request uploaded (the provider's mutually-exclusive prompt
|
|
2447
|
+
// buckets), what the session has accumulated (the token counters), and
|
|
2448
|
+
// how full the window is (the channel's single occupancy reading — the
|
|
2449
|
+
// same number the footer and the context-low warning show).
|
|
2450
|
+
const usage = channel.lastUsage;
|
|
2451
|
+
const lines = [];
|
|
2452
|
+
if (usage !== undefined) {
|
|
2453
|
+
const upload = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
2454
|
+
const rate = upload > 0 ? ((usage.cacheRead / upload) * 100).toFixed(1) : '0.0';
|
|
2455
|
+
lines.push(t('tokens-request-upload', {
|
|
2456
|
+
upload: formatTokens(upload),
|
|
2457
|
+
input: formatTokens(usage.input),
|
|
2458
|
+
read: formatTokens(usage.cacheRead),
|
|
2459
|
+
write: formatTokens(usage.cacheWrite),
|
|
2460
|
+
rate,
|
|
2461
|
+
}));
|
|
2085
2462
|
}
|
|
2463
|
+
lines.push(t('tokens-session-breakdown', {
|
|
2464
|
+
input: formatTokens(channel.tokens.input),
|
|
2465
|
+
output: formatTokens(channel.tokens.output),
|
|
2466
|
+
read: formatTokens(channel.tokens.cacheRead),
|
|
2467
|
+
write: formatTokens(channel.tokens.cacheWrite),
|
|
2468
|
+
}));
|
|
2469
|
+
const occupancyLine = contextOccupancyLine(channel);
|
|
2470
|
+
if (occupancyLine !== undefined)
|
|
2471
|
+
lines.push(occupancyLine);
|
|
2472
|
+
channel.notify(lines.join('\n'));
|
|
2086
2473
|
return true;
|
|
2087
2474
|
}
|
|
2088
2475
|
case 'resume':
|
|
@@ -2095,7 +2482,20 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2095
2482
|
* commands is about muscle memory, not about three surfaces — every one
|
|
2096
2483
|
* of them lands here, on the same runtime.
|
|
2097
2484
|
*/
|
|
2098
|
-
case 'home':
|
|
2485
|
+
case 'home': {
|
|
2486
|
+
setHelpOpen(false);
|
|
2487
|
+
// Split-aware, like /trace and /jobs: while the sidebar is rendering
|
|
2488
|
+
// and the workspace panel is enabled, the workspace view opens THERE.
|
|
2489
|
+
// The panel's own ⤢ goes back to the full-screen home.
|
|
2490
|
+
const controller = sidePanelRef.current;
|
|
2491
|
+
if (controller !== null && controller.split && controller.enabledPanelIds.includes('workspace')) {
|
|
2492
|
+
controller.openPanel('workspace', { focus: true });
|
|
2493
|
+
return true;
|
|
2494
|
+
}
|
|
2495
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
2496
|
+
setSupervisorOpen(true);
|
|
2497
|
+
return true;
|
|
2498
|
+
}
|
|
2099
2499
|
case 'agentview': {
|
|
2100
2500
|
setHelpOpen(false);
|
|
2101
2501
|
// The screen opens immediately and loads its own list. Waiting for the
|
|
@@ -2196,23 +2596,24 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2196
2596
|
return true;
|
|
2197
2597
|
case 'status': {
|
|
2198
2598
|
const usage = channel.lastUsage;
|
|
2199
|
-
const pct = channel.contextWindow === undefined
|
|
2200
|
-
? undefined
|
|
2201
|
-
: Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
2202
2599
|
const lines = [
|
|
2203
2600
|
`${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
|
|
2204
2601
|
`${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
|
|
2205
2602
|
`${t('status-session', { id: channel.agentId })}`,
|
|
2206
2603
|
`${t('status-dir', { cwd: channel.displayCwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
|
|
2207
|
-
|
|
2604
|
+
t('tokens-session-total', {
|
|
2605
|
+
input: formatTokens(channel.tokens.input),
|
|
2606
|
+
output: formatTokens(channel.tokens.output),
|
|
2607
|
+
}),
|
|
2208
2608
|
];
|
|
2209
2609
|
if (usage !== undefined) {
|
|
2210
2610
|
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
2211
2611
|
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
2212
2612
|
lines.push(t('cost-cache-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
2213
2613
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2614
|
+
const occupancyLine = contextOccupancyLine(channel);
|
|
2615
|
+
if (occupancyLine !== undefined)
|
|
2616
|
+
lines.push(occupancyLine);
|
|
2216
2617
|
if (channel.sessionTitle)
|
|
2217
2618
|
lines.push(t('status-title', { title: channel.sessionTitle }));
|
|
2218
2619
|
setHelpOpen(false);
|
|
@@ -2222,7 +2623,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2222
2623
|
case 'cost': {
|
|
2223
2624
|
const usage = channel.lastUsage;
|
|
2224
2625
|
const lines = [
|
|
2225
|
-
|
|
2626
|
+
t('tokens-session-total', {
|
|
2627
|
+
input: formatTokens(channel.tokens.input),
|
|
2628
|
+
output: formatTokens(channel.tokens.output),
|
|
2629
|
+
}),
|
|
2226
2630
|
];
|
|
2227
2631
|
if (usage !== undefined) {
|
|
2228
2632
|
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
@@ -2271,6 +2675,54 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2271
2675
|
setSettingsOpen(true);
|
|
2272
2676
|
return true;
|
|
2273
2677
|
}
|
|
2678
|
+
case 'continue': {
|
|
2679
|
+
// 落地页第四版的 Continue(最高频动作):继续**最近一条可继续会话**。
|
|
2680
|
+
// 数据是真的——`agentViewRows`(含持久化名册,listing 落地后含全部历史)
|
|
2681
|
+
// 里挑 updatedAt 最新的非当前行;没有可继续的就去会话名册挑,失败也不
|
|
2682
|
+
// 静默(notify + 打开 supervisor 让用户自己挑),绝不点了个没反应。
|
|
2683
|
+
setHelpOpen(false);
|
|
2684
|
+
const candidates = agentViewRows.filter(row => !row.current && row.id !== channel.agentId && row.title.trim() !== '');
|
|
2685
|
+
const latest = candidates.reduce((acc, row) => (acc === undefined || row.updatedAt > acc.updatedAt ? row : acc), undefined);
|
|
2686
|
+
if (latest === undefined) {
|
|
2687
|
+
channel.notify(t('launchpad-continue-none'), { color: 'error', timeoutMs: 6000 });
|
|
2688
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
2689
|
+
// 第七版:无可继续会话时浏览器盖在落地页之上(不收落地页)——Esc
|
|
2690
|
+
// 回启动页,与合并入口「会话与工作区」同一条姿态。
|
|
2691
|
+
setSupervisorOpen(true);
|
|
2692
|
+
return true;
|
|
2693
|
+
}
|
|
2694
|
+
setLaunchpadOpen(false);
|
|
2695
|
+
void channel.resumeTo(latest.id)
|
|
2696
|
+
.then((result) => {
|
|
2697
|
+
if (!result.ok) {
|
|
2698
|
+
channel.notify(t('launchpad-continue-failed'), { color: 'error', timeoutMs: 8000 });
|
|
2699
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
2700
|
+
setSupervisorOpen(true);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
channel.notify(t('resume-resumed'));
|
|
2704
|
+
suppressLogoIntroRef.current = true;
|
|
2705
|
+
setAgentViewReturnId(undefined);
|
|
2706
|
+
repaintTranscript();
|
|
2707
|
+
})
|
|
2708
|
+
.catch(() => {
|
|
2709
|
+
channel.notify(t('launchpad-continue-failed'), { color: 'error', timeoutMs: 8000 });
|
|
2710
|
+
});
|
|
2711
|
+
return true;
|
|
2712
|
+
}
|
|
2713
|
+
case 'setup': {
|
|
2714
|
+
// `/setup` re-runs the first-run guide. Unlike the boot path this is
|
|
2715
|
+
// an EXPLICIT request, so it opens unconditionally — a user who typed
|
|
2716
|
+
// it has already decided the wizard is what they want. It reuses the
|
|
2717
|
+
// same screen and the same `onClose`, so completing it here also
|
|
2718
|
+
// (re)writes the one-shot marker.
|
|
2719
|
+
// 第七版:向导的 early-return 在落地页**之前**,天然盖在落地页之上;
|
|
2720
|
+
// 不再收掉落地页——Esc/跳过/完成都回到启动页(旧姿态收掉落地页后,
|
|
2721
|
+
// 向导关掉就落到对话页,正是用户报的 bug 形态之一)。
|
|
2722
|
+
setHelpOpen(false);
|
|
2723
|
+
setOnboardingOpen(true);
|
|
2724
|
+
return true;
|
|
2725
|
+
}
|
|
2274
2726
|
case 'star': {
|
|
2275
2727
|
// 一键 star:**只有用户主动敲 /star 才会跑**(绝不自动)。动作用
|
|
2276
2728
|
// runStarAction(与开屏弹窗共用),异步执行、结果用 notify 报。
|
|
@@ -2373,10 +2825,52 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2373
2825
|
channel.notify(t('agentsmd-created', { result }));
|
|
2374
2826
|
return true;
|
|
2375
2827
|
}
|
|
2376
|
-
case 'jobs':
|
|
2828
|
+
case 'jobs': {
|
|
2377
2829
|
setHelpOpen(false);
|
|
2378
|
-
|
|
2830
|
+
// Split mode routes to the side panel; narrow terminals keep the
|
|
2831
|
+
// full-screen overlay.
|
|
2832
|
+
if (sidePanel.split && sidePanel.enabledPanelIds.includes('jobs')) {
|
|
2833
|
+
sidePanel.openPanel('jobs', { focus: true });
|
|
2834
|
+
}
|
|
2835
|
+
else {
|
|
2836
|
+
setJobsPanelFocusId(null);
|
|
2837
|
+
setJobsPanelOpen(true);
|
|
2838
|
+
}
|
|
2379
2839
|
return true;
|
|
2840
|
+
}
|
|
2841
|
+
case 'panel': {
|
|
2842
|
+
setHelpOpen(false);
|
|
2843
|
+
// 无参 → 面板选择器(overlay 互斥结构内);有参 → 侧栏命令分发。
|
|
2844
|
+
if (rawInput.trim() === '' && sidePanel.splitAvailable && panelPickerRows.length > 0) {
|
|
2845
|
+
const current = panelPickerRows.findIndex(row => row.id === sidePanel.activePanelId);
|
|
2846
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'panel', index: Math.max(0, current) } });
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
if (sidePanel.command(rawInput))
|
|
2850
|
+
return true;
|
|
2851
|
+
// 分栏不可用(窄屏 / inline / 编辑器展开 / splitEnabled=false)
|
|
2852
|
+
// 或参数未知时的整屏回退与兜底:已知面板走整屏形态,其余消费
|
|
2853
|
+
// 掉并提示——绝不原样返回 false,否则 PromptInput 会把
|
|
2854
|
+
// "/panel …" 当普通消息发给模型。
|
|
2855
|
+
const arg = rawInput.trim().toLowerCase();
|
|
2856
|
+
const target = arg === '' || arg === 'focus' || arg === 'toggle' || arg === 'zoom'
|
|
2857
|
+
? sidePanel.activePanelId
|
|
2858
|
+
: arg;
|
|
2859
|
+
if (target === 'jobs') {
|
|
2860
|
+
setJobsPanelFocusId(null);
|
|
2861
|
+
setJobsPanelOpen(true);
|
|
2862
|
+
return true;
|
|
2863
|
+
}
|
|
2864
|
+
if (target === 'agents') {
|
|
2865
|
+
setSubagentDashboardOpen(true);
|
|
2866
|
+
return true;
|
|
2867
|
+
}
|
|
2868
|
+
if (arg === 'toggle' || arg === 'focus' || arg === 'zoom' || arg === '' || target !== undefined) {
|
|
2869
|
+
channel.notify(t('panel-unavailable-hint'));
|
|
2870
|
+
return true;
|
|
2871
|
+
}
|
|
2872
|
+
return false;
|
|
2873
|
+
}
|
|
2380
2874
|
case 'agents':
|
|
2381
2875
|
setHelpOpen(false);
|
|
2382
2876
|
void channel.listSubagents().then((lines) => {
|
|
@@ -2415,7 +2909,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2415
2909
|
...oauth.map(row => t('login-oauth-row', {
|
|
2416
2910
|
provider: row.provider,
|
|
2417
2911
|
state: row.signedIn
|
|
2418
|
-
?
|
|
2912
|
+
? row.expiresAt === undefined
|
|
2913
|
+
? t('login-oauth-in-no-expiry')
|
|
2914
|
+
: t('login-oauth-in', { time: new Date(row.expiresAt).toISOString() })
|
|
2419
2915
|
: row.expired
|
|
2420
2916
|
? t('login-oauth-expired')
|
|
2421
2917
|
: t('login-oauth-signed-out'),
|
|
@@ -2499,9 +2995,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2499
2995
|
// Registered by dsh-plan-mode: bare `/plan` opens an on/off picker
|
|
2500
2996
|
// marked with the current state instead of toggling blindly; Enter
|
|
2501
2997
|
// dispatches `/plan` or `/plan off`. Arguments pass through verbatim
|
|
2502
|
-
// (`/plan off`)
|
|
2503
|
-
//
|
|
2504
|
-
|
|
2998
|
+
// (`/plan off`). Availability comes from the shared capability facts
|
|
2999
|
+
// (the same read Shift+Tab uses), not from a second command-list
|
|
3000
|
+
// scan; with no registry command the line falls through to the model,
|
|
3001
|
+
// exactly as before.
|
|
3002
|
+
const plan = channel.capabilities().plan;
|
|
3003
|
+
const mounted = plan.route !== 'none';
|
|
2505
3004
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
2506
3005
|
if (mounted && parts.length === 0) {
|
|
2507
3006
|
setHelpOpen(false);
|
|
@@ -2849,7 +3348,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
2849
3348
|
*/
|
|
2850
3349
|
const { columns: terminalColumns } = useTerminalSize();
|
|
2851
3350
|
const pageInsetX = usePageInset().x;
|
|
2852
|
-
|
|
3351
|
+
// 侧栏控制器:几何(chatColumns/panelColumns)、焦点与键盘分发都在
|
|
3352
|
+
// 这个 hook 里(设计文档 §16.5——Chat 只多一次调用、一处键盘让位、
|
|
3353
|
+
// 一条 runCommand case)。编辑器展开时几何强制 collapsed。
|
|
3354
|
+
const sidePanel = useSidePanel({
|
|
3355
|
+
columns: terminalColumns,
|
|
3356
|
+
fullscreen,
|
|
3357
|
+
editorOpen: promptEditorOpen,
|
|
3358
|
+
});
|
|
3359
|
+
/**
|
|
3360
|
+
* 「全屏」出口(PanelBar 的 ⤢):把侧栏里那个面板的内容切到它原本的整屏
|
|
3361
|
+
* 形态。只映射**真的有整屏对应物**的面板 id(能力位 capabilities.fullscreen
|
|
3362
|
+
* 决定按钮画不画,这里决定点了去哪);没映射到的 id 静默无操作——按钮与
|
|
3363
|
+
* 落地页、键盘入口共用同一个状态位,所以从整屏返回时侧栏还在原处。
|
|
3364
|
+
*/
|
|
3365
|
+
const openPanelFullscreen = React.useCallback((panelId) => {
|
|
3366
|
+
if (panelId === 'jobs') {
|
|
3367
|
+
sidePanelRef.current?.focusChat();
|
|
3368
|
+
setJobsPanelOpen(true);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
if (panelId === 'agents') {
|
|
3372
|
+
sidePanelRef.current?.focusChat();
|
|
3373
|
+
setSubagentDashboardOpen(true);
|
|
3374
|
+
return;
|
|
3375
|
+
}
|
|
3376
|
+
if (panelId === 'trajectory') {
|
|
3377
|
+
sidePanelRef.current?.focusChat();
|
|
3378
|
+
// 强制整屏:默认的 openScene 在分屏下会路由回面板(见它的注释)。
|
|
3379
|
+
openScene({ fullscreen: true });
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
if (panelId === 'workspace') {
|
|
3383
|
+
sidePanelRef.current?.focusChat();
|
|
3384
|
+
setSupervisorOpen(true);
|
|
3385
|
+
}
|
|
3386
|
+
}, [openScene]);
|
|
3387
|
+
// openJobsPanel(在上方、identity 稳定)经 ref 读取最新控制器。
|
|
3388
|
+
sidePanelRef.current = sidePanel;
|
|
3389
|
+
/**
|
|
3390
|
+
* 宠物「代言」判定:分屏开着且 companion 是活动面板时,新通知由宠物头顶
|
|
3391
|
+
* 气泡说出,输入框上方的 toast 不再重复同一条。error 色恒不压制(可能
|
|
3392
|
+
* 要行动)。渲染期读控制器状态——与气泡同一提交,无先闪后消的竞态。
|
|
3393
|
+
*/
|
|
3394
|
+
const petSaysNotices = React.useCallback((item) => item.color !== 'error' && sidePanel.split && sidePanel.activePanelId === 'companion', [sidePanel.split, sidePanel.activePanelId]);
|
|
3395
|
+
// 聊天列宽:收起时 = 内容区全宽(与现状逐字节一致),分栏时 = 左栏宽。
|
|
3396
|
+
// 所有显式下传的宽度(gutter / 图片预览区 / wake 条)都改用它;转录
|
|
3397
|
+
// 子树则经 SidePanelLayout 的 TerminalSizeContext 覆盖自动拿到。
|
|
3398
|
+
const chatColumns = sidePanel.chatColumns;
|
|
3399
|
+
const wakeWidth = miniWakeWidth(chatColumns);
|
|
3400
|
+
const panelPickerRows = usePanelPickerRows(sidePanel);
|
|
2853
3401
|
const wakeBand = React.useMemo(() => wakeWidth === 0
|
|
2854
3402
|
? undefined
|
|
2855
3403
|
// `sequence`, not the scene's `compressed`: at sixteen columns an idle
|
|
@@ -3014,11 +3562,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3014
3562
|
}, []);
|
|
3015
3563
|
/** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
|
|
3016
3564
|
const lastModalEnterAtRef = React.useRef(0);
|
|
3565
|
+
const couponVisible = coupon !== null && starModal === null
|
|
3566
|
+
&& approvalSnapshot === null && dialogSnapshot === null && questionSnapshot === null
|
|
3567
|
+
&& overlay.kind === 'none' && btw === null && recap === null
|
|
3568
|
+
&& !supervisorOpen && !treeOpen && !settingsOpen && !jobsPanelOpen
|
|
3569
|
+
&& !sceneOpen && !subagentDashboardOpen && subagentDetailId === null;
|
|
3570
|
+
const markCouponShown = React.useCallback((orderId) => {
|
|
3571
|
+
bonusNotices?.shown(orderId);
|
|
3572
|
+
}, [bonusNotices]);
|
|
3573
|
+
const closeCoupon = React.useCallback(() => {
|
|
3574
|
+
if (coupon !== null)
|
|
3575
|
+
bonusNotices?.dismiss(coupon.orderId);
|
|
3576
|
+
}, [bonusNotices, coupon]);
|
|
3017
3577
|
useInput((input, key, event) => {
|
|
3018
3578
|
// 开屏"求 star"弹窗开着时键盘全归它(↑/↓/Enter/Esc 由它自己的
|
|
3019
3579
|
// useInput 处理),滚轮也不许滚动它身后的转录——和下面的整屏界面
|
|
3020
3580
|
// 同一套让位规则。
|
|
3021
|
-
if (starModal !== null)
|
|
3581
|
+
if (starModal !== null || couponVisible)
|
|
3022
3582
|
return;
|
|
3023
3583
|
// Prompt-slot panels own the keyboard while visible. Their own useInput
|
|
3024
3584
|
// handles the relevant keys; Chat registered first, so yielding here
|
|
@@ -3029,6 +3589,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3029
3589
|
|| overlay.kind === 'tips'
|
|
3030
3590
|
|| (recap !== null && (!recap.auto || recap.expanded)))
|
|
3031
3591
|
return;
|
|
3592
|
+
// The first-run guide owns the whole terminal while it is up: it is a
|
|
3593
|
+
// wizard with its own step navigation, its own focus ring inside the
|
|
3594
|
+
// pickers it borrows, and no free-text input at all. Registered ABOVE the
|
|
3595
|
+
// launchpad because it renders above it.
|
|
3596
|
+
if (onboardingOpen)
|
|
3597
|
+
return;
|
|
3598
|
+
// The launchpad owns the whole terminal while it is up — including the
|
|
3599
|
+
// plain letters that would otherwise reach the composer, which is exactly
|
|
3600
|
+
// the point: it IS the composer on this screen, and its draft is submitted
|
|
3601
|
+
// directly (see closeLaunchpad). ONE exception (fifth revision): a picker
|
|
3602
|
+
// opened from the param row renders ABOVE the launchpad and therefore owns
|
|
3603
|
+
// the keyboard — fall through to the overlay branches below (Esc closes the
|
|
3604
|
+
// picker back onto the launchpad). The launchpad's own useInput is paused
|
|
3605
|
+
// via inputPaused for exactly this window, so keys the picker does not
|
|
3606
|
+
// consume cannot leak into the draft.
|
|
3607
|
+
if (launchpadShown && !launchpadOverlayUp)
|
|
3608
|
+
return;
|
|
3032
3609
|
// The session tree owns the whole terminal while it is up: plain letters
|
|
3033
3610
|
// drive its search, clicks and Enter drive its action menu.
|
|
3034
3611
|
if (treeOpen)
|
|
@@ -3076,7 +3653,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3076
3653
|
// Events only arrive with mouse tracking on; inline mode never sees
|
|
3077
3654
|
// them, so this is a no-op there.
|
|
3078
3655
|
if (key.wheelUp || key.wheelDown) {
|
|
3079
|
-
|
|
3656
|
+
// 焦点在右栏时滚轮不回落到转录:落在面板矩形上的滚轮已由命中
|
|
3657
|
+
// 测试路由给面板自己的 ScrollBox,到达这里的兜底事件不应在
|
|
3658
|
+
// 用户操作右栏时误滚左栏。
|
|
3659
|
+
if (helpOpen || sidePanel.focus === 'panel')
|
|
3080
3660
|
return;
|
|
3081
3661
|
// Any open transient dialog is modal to the wheel; the one exception
|
|
3082
3662
|
// mirrors the render gate — a workspace picker whose target list has
|
|
@@ -3113,7 +3693,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3113
3693
|
// pending. The panels bind ↑/↓/Space/Tab/Enter/Esc and never these keys,
|
|
3114
3694
|
// so paging cannot steal anything from them.
|
|
3115
3695
|
if ((key.pageUp || key.pageDown) && fullscreen) {
|
|
3116
|
-
|
|
3696
|
+
// 焦点在右栏时 PgUp/PgDn 属于活动面板(经侧栏键盘分发)。
|
|
3697
|
+
if (helpOpen || sidePanel.focus === 'panel')
|
|
3117
3698
|
return;
|
|
3118
3699
|
const overlayModal = overlay.kind !== 'none' &&
|
|
3119
3700
|
(overlay.kind !== 'workspace-picker' || workspaceTargets.length > 0);
|
|
@@ -3152,6 +3733,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3152
3733
|
}
|
|
3153
3734
|
return;
|
|
3154
3735
|
}
|
|
3736
|
+
// 侧栏键盘分发(v2.1 优先级):上面的审批 / 问卷 / 对话框守卫仍然
|
|
3737
|
+
// 最先,其次是侧栏全局快捷键(Ctrl+B / Alt+Z,两种焦点都生效),
|
|
3738
|
+
// 然后焦点在右栏时一切按键归侧栏——活动面板的业务键优先,宿主
|
|
3739
|
+
// 回退键(←/→ 切面板、z、+/-、Esc 回聊天)兜底。
|
|
3740
|
+
if (sidePanel.handleKey(input, key, event))
|
|
3741
|
+
return;
|
|
3155
3742
|
const returnCandidate = isPlainReturnInput(input, key);
|
|
3156
3743
|
const returnNow = Date.now();
|
|
3157
3744
|
const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
|
|
@@ -3246,6 +3833,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3246
3833
|
event.stopImmediatePropagation();
|
|
3247
3834
|
return;
|
|
3248
3835
|
}
|
|
3836
|
+
if (overlay.kind === 'help') {
|
|
3837
|
+
// 帮助盖屏(第八版):Esc/Ctrl+C/Enter 收起回落地页;纵向导航归这个
|
|
3838
|
+
// 视口(与 PromptInput 的 helpOpen 分支同一套键位),其余按键模态吞掉
|
|
3839
|
+
// ——绝不漏进落地页草稿。
|
|
3840
|
+
const page = Math.max(1, helpCoverViewportHeight - 2);
|
|
3841
|
+
if (key.upArrow || key.wheelUp) {
|
|
3842
|
+
helpCoverScrollRef.current?.scrollBy(key.wheelUp ? -3 : -1);
|
|
3843
|
+
}
|
|
3844
|
+
else if (key.downArrow || key.wheelDown) {
|
|
3845
|
+
helpCoverScrollRef.current?.scrollBy(key.wheelDown ? 3 : 1);
|
|
3846
|
+
}
|
|
3847
|
+
else if (key.pageUp || key.pageDown) {
|
|
3848
|
+
helpCoverScrollRef.current?.scrollBy(key.pageUp ? -page : page);
|
|
3849
|
+
}
|
|
3850
|
+
else if (key.home) {
|
|
3851
|
+
helpCoverScrollRef.current?.scrollTo(0);
|
|
3852
|
+
}
|
|
3853
|
+
else if (key.end) {
|
|
3854
|
+
helpCoverScrollRef.current?.scrollTo(Number.MAX_SAFE_INTEGER);
|
|
3855
|
+
}
|
|
3856
|
+
else if (key.escape || (key.ctrl && (input === 'c' || input === 'd')) || plainReturn) {
|
|
3857
|
+
dispatchOverlay({ type: 'close' });
|
|
3858
|
+
}
|
|
3859
|
+
event.stopImmediatePropagation();
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3249
3862
|
if (overlay.kind === 'thinking') {
|
|
3250
3863
|
if (key.upArrow || key.downArrow) {
|
|
3251
3864
|
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: 2 });
|
|
@@ -3644,6 +4257,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3644
4257
|
}
|
|
3645
4258
|
return;
|
|
3646
4259
|
}
|
|
4260
|
+
if (overlay.kind === 'panel') {
|
|
4261
|
+
if (key.upArrow || key.downArrow) {
|
|
4262
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: panelPickerRows.length });
|
|
4263
|
+
}
|
|
4264
|
+
else if (plainReturn) {
|
|
4265
|
+
const row = panelPickerRows[overlay.index];
|
|
4266
|
+
dispatchOverlay({ type: 'close' });
|
|
4267
|
+
if (row !== undefined)
|
|
4268
|
+
sidePanel.openPanel(row.id, { focus: true });
|
|
4269
|
+
}
|
|
4270
|
+
else if (key.escape) {
|
|
4271
|
+
dispatchOverlay({ type: 'close' });
|
|
4272
|
+
}
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
3647
4275
|
if (overlay.kind === 'theme') {
|
|
3648
4276
|
const options = getThemeOptions(themeHost);
|
|
3649
4277
|
if (key.upArrow || key.downArrow) {
|
|
@@ -3815,11 +4443,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3815
4443
|
return;
|
|
3816
4444
|
}
|
|
3817
4445
|
if (actionMatches('dashboard', input, key)) {
|
|
3818
|
-
// The subagent dashboard key (default Ctrl+A) opens the dashboard
|
|
3819
|
-
//
|
|
3820
|
-
//
|
|
3821
|
-
//
|
|
3822
|
-
|
|
4446
|
+
// The subagent dashboard key (default Ctrl+A) opens the dashboard —
|
|
4447
|
+
// split mode routes it to the agents side panel instead. Consume the
|
|
4448
|
+
// key: without the stop the prompt editor's readline binding ALSO
|
|
4449
|
+
// fires (Ctrl+A moves the caret to line start), so one press both
|
|
4450
|
+
// opens the view and jumps the cursor.
|
|
4451
|
+
openSubagentDashboard();
|
|
3823
4452
|
event.stopImmediatePropagation();
|
|
3824
4453
|
return;
|
|
3825
4454
|
}
|
|
@@ -3831,7 +4460,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3831
4460
|
return;
|
|
3832
4461
|
}
|
|
3833
4462
|
if (actionMatches('history', input, key) && !helpOpen) {
|
|
3834
|
-
setHistoryEntries(loadHistory());
|
|
4463
|
+
setHistoryEntries(loadHistory(channel.cwd));
|
|
3835
4464
|
dispatchOverlay({
|
|
3836
4465
|
type: 'open',
|
|
3837
4466
|
overlay: { kind: 'history', query: '', cursor: 0, focus: 0 },
|
|
@@ -3856,6 +4485,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
3856
4485
|
setSelectedId(null);
|
|
3857
4486
|
}
|
|
3858
4487
|
}
|
|
4488
|
+
else if (key.escape && promptControllerRef.current?.consumeEscape()) {
|
|
4489
|
+
// Selection/editor Esc stays with the composer even though Chat's
|
|
4490
|
+
// global listener runs first. Consume it before interrupting the turn.
|
|
4491
|
+
event.stopImmediatePropagation();
|
|
4492
|
+
}
|
|
3859
4493
|
else if (key.escape && channel.working && !helpOpen && !promptControllerRef.current?.vimActive()) {
|
|
3860
4494
|
// Esc interrupts a running turn (the prompt input
|
|
3861
4495
|
// only sees esc when idle, where it has the double-tap-clear meaning).
|
|
@@ -4023,14 +4657,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4023
4657
|
// (wired to the toast below).
|
|
4024
4658
|
event.stopImmediatePropagation();
|
|
4025
4659
|
}
|
|
4660
|
+
}, {
|
|
4661
|
+
// Chat's global layer must see every key before the composer and the
|
|
4662
|
+
// panels it hosts (the handler's yield guards and readline shadowing
|
|
4663
|
+
// both assume it), whether or not they mounted in the same
|
|
4664
|
+
// commit — child effects run first, so append order would put a
|
|
4665
|
+
// first-mount PromptInput ahead of Chat (#1155).
|
|
4666
|
+
prepend: true,
|
|
4026
4667
|
});
|
|
4027
4668
|
// Working-activity line (spinner slot): context-pressure prefix shares the
|
|
4028
|
-
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
4029
|
-
const activityWarnPct = contextPressurePct(channel
|
|
4669
|
+
// StatusLine thresholds (amber ≥ 80, red ≥ 95) and its occupancy source.
|
|
4670
|
+
const activityWarnPct = contextPressurePct(channelContextOccupancy(channel));
|
|
4030
4671
|
// Who owns the spinner slot: with the working-activity line on, that slot
|
|
4031
4672
|
// draws the user's `/activity` preset, so the compaction row borrows the same
|
|
4032
4673
|
// indicator instead of answering with the classic dot.
|
|
4033
|
-
const activitySlot = channel.activityEnabled && !channel.
|
|
4674
|
+
const activitySlot = channel.activityEnabled && !channel.minimalUi;
|
|
4034
4675
|
// An automatic compaction runs INSIDE the turn, so it rides whichever spinner
|
|
4035
4676
|
// the slot shows as a badge instead of a second row (the spinner's timer is
|
|
4036
4677
|
// the turn's, not the compaction's).
|
|
@@ -4082,8 +4723,171 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4082
4723
|
}
|
|
4083
4724
|
: undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
|
|
4084
4725
|
const interruptPanel = approvalPanelNode ?? questionPanelNode;
|
|
4726
|
+
/**
|
|
4727
|
+
* Transient picker panels (pickers/dialogs) - this JSX feeds TWO mount
|
|
4728
|
+
* points since the fifth launchpad revision: the chat page OverlayAbove
|
|
4729
|
+
* (above the input cluster) and the launchpad itself (above its input
|
|
4730
|
+
* card, see the launchpad branch). Defined once so the two never drift.
|
|
4731
|
+
*/
|
|
4732
|
+
const pickerPanels = (_jsxs(_Fragment, { children: [overlay.kind === 'help' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpCoverViewportHeight, viewportWidth: chatColumns, scrollRef: helpCoverScrollRef, onCommandPick: (name) => {
|
|
4733
|
+
// 点击命令行 = 把 /name 填进落地页草稿(聊天页 Tab 补全的
|
|
4734
|
+
// 鼠标等价),浮层收起、人还在启动页(第八版:不许进对话页)。
|
|
4735
|
+
dispatchOverlay({ type: 'close' });
|
|
4736
|
+
const filled = '/' + name + ' ';
|
|
4737
|
+
setLaunchpadDraft(filled);
|
|
4738
|
+
setLaunchpadCaret(filled.length);
|
|
4739
|
+
setLaunchpadFocus(-1);
|
|
4740
|
+
} }) })), overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
|
|
4741
|
+
// 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
|
|
4742
|
+
const visible = index === 0;
|
|
4743
|
+
setThinkingVisible(visible);
|
|
4744
|
+
dispatchOverlay({ type: 'close' });
|
|
4745
|
+
channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
|
|
4746
|
+
} })), overlay.kind === 'workspace-picker' && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: overlay.index, currentCwd: channel.cwd, onPick: (index) => {
|
|
4747
|
+
// 点击行 = 切换该行目标(与 Enter 同一条路径)
|
|
4748
|
+
const target = workspaceTargets[index];
|
|
4749
|
+
dispatchOverlay({ type: 'close' });
|
|
4750
|
+
if (target !== undefined)
|
|
4751
|
+
void channel.switchWorkspace(target);
|
|
4752
|
+
} }) })), overlay.kind === 'workspace-menu' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceMenuPicker, { options: workspaceMenuOptions, focusIndex: overlay.index, onPick: (index) => {
|
|
4753
|
+
// 点击行 = 执行该行(与 Enter 同一条路径)
|
|
4754
|
+
runWorkspaceMenuOption(workspaceMenuOptions[index]);
|
|
4755
|
+
} }) })), overlay.kind === 'workspace-flow' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: overlay.flow.title, choices: overlay.flow.choices, focusIndex: overlay.index, busy: overlay.busy, input: overlay.input, onPick: (index) => {
|
|
4756
|
+
// 点击行 = 设焦点 + 执行分支(与 Enter 同一条路径);
|
|
4757
|
+
// busy/输入态在组件侧禁点
|
|
4758
|
+
const choice = overlay.flow.choices[index];
|
|
4759
|
+
if (choice === undefined)
|
|
4760
|
+
return;
|
|
4761
|
+
dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
|
|
4762
|
+
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
4763
|
+
} }) })), 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) => {
|
|
4764
|
+
// 点击分组行 = 进入该组(与 Enter 同一条路径)
|
|
4765
|
+
const group = modelGroups[index];
|
|
4766
|
+
if (!group)
|
|
4767
|
+
return;
|
|
4768
|
+
setModelGroup(group.provider);
|
|
4769
|
+
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
4770
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
|
|
4774
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
4775
|
+
} })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
|
|
4776
|
+
? t('picker-group-recent')
|
|
4777
|
+
: 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) => {
|
|
4778
|
+
// 点击行 = 应用该行模型(与 Enter 同一条路径)
|
|
4779
|
+
const model = groupModels[index];
|
|
4780
|
+
if (!model)
|
|
4781
|
+
return;
|
|
4782
|
+
dispatchOverlay({ type: 'close' });
|
|
4783
|
+
void switchModelRecorded(model.provider, model.id, model.name);
|
|
4784
|
+
} })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
|
|
4785
|
+
const row = (migrateRows ?? [])[index];
|
|
4786
|
+
if (!row)
|
|
4787
|
+
return;
|
|
4788
|
+
setMigrateChecked(current => {
|
|
4789
|
+
const next = new Set(current);
|
|
4790
|
+
if (next.has(row.agentId))
|
|
4791
|
+
next.delete(row.agentId);
|
|
4792
|
+
else
|
|
4793
|
+
next.add(row.agentId);
|
|
4794
|
+
return next;
|
|
4795
|
+
});
|
|
4796
|
+
} }) })), overlay.kind === 'migrate-confirm' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigrateConfirm, { rows: migratePending }) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
|
|
4797
|
+
const skill = skillsList[index];
|
|
4798
|
+
if (!skill)
|
|
4799
|
+
return;
|
|
4800
|
+
dispatchOverlay({ type: 'close' });
|
|
4801
|
+
if (skill.userInvocable)
|
|
4802
|
+
setHistoryFill(`/${skill.name} `);
|
|
4803
|
+
} })) })), overlay.kind === 'activity' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: overlay.index, currentPreset: channel.activityFrames, onPick: (index) => {
|
|
4804
|
+
dispatchOverlay({ type: 'close' });
|
|
4805
|
+
const name = PRESET_NAMES[index];
|
|
4806
|
+
if (name)
|
|
4807
|
+
channel.setActivityFrames(name);
|
|
4808
|
+
} }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
|
|
4809
|
+
dispatchOverlay({ type: 'close' });
|
|
4810
|
+
const name = SESSION_COLOR_NAMES[index];
|
|
4811
|
+
if (name) {
|
|
4812
|
+
channel.setSessionColor(name);
|
|
4813
|
+
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
4814
|
+
}
|
|
4815
|
+
} }) })), overlay.kind === 'panel' && panelPickerRows.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PanelPicker, { rows: panelPickerRows, focusIndex: overlay.index, activeId: sidePanel.activePanelId, onPick: (index) => {
|
|
4816
|
+
const row = panelPickerRows[index];
|
|
4817
|
+
dispatchOverlay({ type: 'close' });
|
|
4818
|
+
if (row !== undefined)
|
|
4819
|
+
sidePanel.openPanel(row.id, { focus: true });
|
|
4820
|
+
} }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
|
|
4821
|
+
// 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
|
|
4822
|
+
onPick: (index) => {
|
|
4823
|
+
dispatchOverlay({ type: 'set-index', kind: 'effort', index });
|
|
4824
|
+
const option = effortOptions[index];
|
|
4825
|
+
if (option)
|
|
4826
|
+
void channel.setEffort(option.id);
|
|
4827
|
+
} }) })), overlay.kind === 'preset' && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: overlay.index, currentPreset: channel.agentPreset, onPick: (index) => {
|
|
4828
|
+
dispatchOverlay({ type: 'close' });
|
|
4829
|
+
const option = presetOptions[index];
|
|
4830
|
+
if (option)
|
|
4831
|
+
void channel.switchPreset(option.id);
|
|
4832
|
+
} }) })), overlay.kind === 'permission' && overlay.snapshot.options.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PermissionsPicker, { options: overlay.snapshot.options, focusIndex: overlay.index, currentValue: overlay.snapshot.current?.value, cwd: channel.cwd, onPick: (index) => {
|
|
4833
|
+
if (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null)
|
|
4834
|
+
return;
|
|
4835
|
+
const option = overlay.snapshot.options[index];
|
|
4836
|
+
dispatchOverlay({ type: 'close' });
|
|
4837
|
+
if (option !== undefined)
|
|
4838
|
+
void runPermissionCommand(` ${option.value}`);
|
|
4839
|
+
} }) })), overlay.kind === 'plan' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PlanPicker, { focusIndex: overlay.index, currentOn: channel.mode.plan === true, onPick: (index) => {
|
|
4840
|
+
dispatchOverlay({ type: 'close' });
|
|
4841
|
+
const on = index === 0;
|
|
4842
|
+
void runExternalCommand('plan', on ? '' : ' off');
|
|
4843
|
+
} }) })), overlay.kind === 'lang' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(LangPicker, { focusIndex: overlay.index, currentLang: getLang(), onPick: (index) => {
|
|
4844
|
+
const lang = LANGS[index];
|
|
4845
|
+
if (lang === undefined)
|
|
4846
|
+
return;
|
|
4847
|
+
dispatchOverlay({ type: 'close' });
|
|
4848
|
+
applyLang(lang);
|
|
4849
|
+
} }) })), overlay.kind === 'theme' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: overlay.index, currentTheme: themeName, themeHost: themeHost, onPick: (index) => {
|
|
4850
|
+
dispatchOverlay({ type: 'close' });
|
|
4851
|
+
const name = getThemeOptions(themeHost)[index]?.value;
|
|
4852
|
+
if (name !== undefined) {
|
|
4853
|
+
const ok = setTheme(name);
|
|
4854
|
+
channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
|
|
4855
|
+
}
|
|
4856
|
+
} }) })), overlay.kind === 'history' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: overlay.query, cursorOffset: overlay.cursor, matches: historyMatches, focusIndex: overlay.focus, onPick: (index) => {
|
|
4857
|
+
// 点击行 = 填入该历史命令(与 Enter 同路径)
|
|
4858
|
+
const entry = historyMatches[index];
|
|
4859
|
+
if (entry) {
|
|
4860
|
+
setHistoryFill(entry.text);
|
|
4861
|
+
dispatchOverlay({ type: 'close' });
|
|
4862
|
+
}
|
|
4863
|
+
} }) })), overlay.kind === 'rewind' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: overlay.index, confirmRow: overlay.confirm, modes: overlay.modes, modeIndex: overlay.modeIndex, busy: overlay.busy, onPickRow: (index) => {
|
|
4864
|
+
// 列表页点击只选中:进入确认态保留键盘 Enter 显式触发
|
|
4865
|
+
dispatchOverlay({ type: 'set-index', kind: 'rewind', index });
|
|
4866
|
+
}, onConfirm: () => {
|
|
4867
|
+
// 确认页即显式确认层,点击直接执行(与 Enter 同路径)
|
|
4868
|
+
const row = overlay.confirm;
|
|
4869
|
+
if (row === null)
|
|
4870
|
+
return;
|
|
4871
|
+
dispatchOverlay({ type: 'close' });
|
|
4872
|
+
void performRewind(row);
|
|
4873
|
+
}, onPickMode: (index) => {
|
|
4874
|
+
// 模式列表点击直接执行该模式(与 Enter 同路径)
|
|
4875
|
+
const row = overlay.confirm;
|
|
4876
|
+
if (row === null)
|
|
4877
|
+
return;
|
|
4878
|
+
// 模式页仅当 modes 非空才渲染,这里空安全取值
|
|
4879
|
+
const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
|
|
4880
|
+
dispatchOverlay({ type: 'close' });
|
|
4881
|
+
void performRewind(row, mode);
|
|
4882
|
+
} }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
|
|
4883
|
+
// 点击行直接执行该动作(与 Enter 同路径)
|
|
4884
|
+
const path = overlay.path;
|
|
4885
|
+
dispatchOverlay({ type: 'close' });
|
|
4886
|
+
runFileAction(index, path);
|
|
4887
|
+
} }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }));
|
|
4085
4888
|
const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
|
|
4086
|
-
|| subagentDetailId !== null || subagentDashboardOpen || sceneOpen
|
|
4889
|
+
|| subagentDetailId !== null || subagentDashboardOpen || sceneOpen
|
|
4890
|
+
|| launchpadShown || onboardingOpen;
|
|
4087
4891
|
if (interruptPanel !== null && screenOpen) {
|
|
4088
4892
|
const node = (_jsx(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: interruptPanel }));
|
|
4089
4893
|
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
@@ -4109,6 +4913,29 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4109
4913
|
}, children: renderScene ? renderScene(pluginScene.id, channel) : _jsxs(Text, { children: ["Scene unavailable: ", pluginScene.id] }) }));
|
|
4110
4914
|
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
4111
4915
|
}
|
|
4916
|
+
/**
|
|
4917
|
+
* The first-run guide — the FIRST frame of a fresh installation.
|
|
4918
|
+
*
|
|
4919
|
+
* It sits above every other screen because it answers a question that comes
|
|
4920
|
+
* before all of them: "is this thing wired up at all". A launch that needs
|
|
4921
|
+
* setup has nothing useful to show behind a browser or a landing page.
|
|
4922
|
+
*
|
|
4923
|
+
* `activeModel` is deliberately undefined here: the wizard's model step
|
|
4924
|
+
* switches models through `channel.switchModel`, which is the same path the
|
|
4925
|
+
* chat screen uses, so a switch made inside the wizard is already live when
|
|
4926
|
+
* the user lands on the launcher.
|
|
4927
|
+
*/
|
|
4928
|
+
if (onboardingOpen) {
|
|
4929
|
+
const node = (_jsx(Onboarding, { channel: channel, themeHost: themeHost, onClose: closeOnboarding, onApplyLang: applyLang, onRunCommand: (name) => {
|
|
4930
|
+
// 与落地页的 onAction 同一条规则:打开整屏界面的命令要先把向导收掉。
|
|
4931
|
+
// 走 `closeOnboarding('skipped')` 而不是直接置 false——用户是去试命令、
|
|
4932
|
+
// 没答完引导,按 skipped 的口径不记账(下次启动还会问)。
|
|
4933
|
+
if (!overlayCommandNames.has(name))
|
|
4934
|
+
closeOnboarding('skipped');
|
|
4935
|
+
void runCommand(name, '');
|
|
4936
|
+
} }));
|
|
4937
|
+
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
4938
|
+
}
|
|
4112
4939
|
/**
|
|
4113
4940
|
* The session supervisor: a screen in the same sense as the tree — an early
|
|
4114
4941
|
* return after every hook above has run, so there is no transcript
|
|
@@ -4125,7 +4952,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4125
4952
|
* while they read the list, which is why closing the screen only repaints
|
|
4126
4953
|
* the transcript when the attached session actually changed.
|
|
4127
4954
|
*/
|
|
4128
|
-
if (supervisorOpen) {
|
|
4955
|
+
if (supervisorOpen && launchpadGate()) {
|
|
4129
4956
|
/**
|
|
4130
4957
|
* Live state per session, from the channel's own agent-view projection.
|
|
4131
4958
|
* Reading the projection rather than a parallel source is what keeps this
|
|
@@ -4143,6 +4970,15 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4143
4970
|
suppressLogoIntroRef.current = true;
|
|
4144
4971
|
setAgentViewReturnId(undefined);
|
|
4145
4972
|
setSupervisorOpen(false);
|
|
4973
|
+
// 第七版:明确选中一个会话 = 有意导航(与 Esc「退出」相对)——浏览页
|
|
4974
|
+
// 与盖在它底下的落地页**一起收**,人落在那个会话的聊天页。只收浏览页
|
|
4975
|
+
// 会露出启动页,正是用户实测的「选完会话还是回到启动页」。落地页的
|
|
4976
|
+
// 草稿/焦点也按 closeLaunchpad 同一口径清掉(进入的是别的会话,旧草稿
|
|
4977
|
+
// 不该跟过去)。
|
|
4978
|
+
setLaunchpadOpen(false);
|
|
4979
|
+
setLaunchpadFocus(-1);
|
|
4980
|
+
setLaunchpadDraft('');
|
|
4981
|
+
setLaunchpadCaret(0);
|
|
4146
4982
|
repaintTranscript();
|
|
4147
4983
|
return result;
|
|
4148
4984
|
}, onNewSession: async (target) => {
|
|
@@ -4151,6 +4987,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4151
4987
|
suppressLogoIntroRef.current = true;
|
|
4152
4988
|
setAgentViewReturnId(undefined);
|
|
4153
4989
|
setSupervisorOpen(false);
|
|
4990
|
+
// 同 onOpenSession:新建/切工作区会话也是有意导航,落地页一并收。
|
|
4991
|
+
setLaunchpadOpen(false);
|
|
4992
|
+
setLaunchpadFocus(-1);
|
|
4993
|
+
setLaunchpadDraft('');
|
|
4994
|
+
setLaunchpadCaret(0);
|
|
4154
4995
|
repaintTranscript();
|
|
4155
4996
|
}
|
|
4156
4997
|
return ok;
|
|
@@ -4168,7 +5009,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4168
5009
|
// conversation (an early return after every hook above has run), so there
|
|
4169
5010
|
// is no transcript underneath to be repainted or bled through. The dropped
|
|
4170
5011
|
// turn's prompt returns through the same fill path a rewind picker uses.
|
|
4171
|
-
if (treeOpen) {
|
|
5012
|
+
if (treeOpen && launchpadGate()) {
|
|
4172
5013
|
const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
|
|
4173
5014
|
// The tree rewound to a node and is handing that turn's prompt back,
|
|
4174
5015
|
// exactly like the picker does. It belongs to the binding the tree
|
|
@@ -4181,18 +5022,18 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4181
5022
|
// The settings screen follows the browser's rule exactly: it REPLACES the
|
|
4182
5023
|
// conversation (an early return after every hook above has run), so there
|
|
4183
5024
|
// is no transcript underneath to be repainted or bled through.
|
|
4184
|
-
if (settingsOpen) {
|
|
5025
|
+
if (settingsOpen && launchpadGate()) {
|
|
4185
5026
|
const screen = _jsx(Settings, { channel: channel, onClose: () => setSettingsOpen(false) });
|
|
4186
5027
|
return fullscreen ? screen : _jsx(AlternateScreen, { children: screen });
|
|
4187
5028
|
}
|
|
4188
5029
|
// Subagent detail scene: displays detailed view of a specific subagent.
|
|
4189
5030
|
// Like the browser and settings, it replaces the conversation entirely.
|
|
4190
|
-
if (subagentDetailId !== null) {
|
|
5031
|
+
if (subagentDetailId !== null && launchpadGate()) {
|
|
4191
5032
|
const subagent = channel.subagents.find(s => s.agentId === subagentDetailId);
|
|
4192
5033
|
if (!subagent) {
|
|
4193
|
-
// Agent not found, go back to dashboard
|
|
5034
|
+
// Agent not found, go back to the dashboard (side panel when split).
|
|
4194
5035
|
setSubagentDetailId(null);
|
|
4195
|
-
|
|
5036
|
+
openSubagentDashboard();
|
|
4196
5037
|
return null;
|
|
4197
5038
|
}
|
|
4198
5039
|
const scene = (_jsx(SubagentDetailScene, { subagent: subagent, onInterrupt: (id) => channel.subagentControl.interrupt(id), onBack: () => {
|
|
@@ -4203,8 +5044,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4203
5044
|
}
|
|
4204
5045
|
// Jobs panel: background jobs (running/killed) with kill/inspect actions.
|
|
4205
5046
|
// Like the browser and settings, it replaces the conversation entirely.
|
|
4206
|
-
if (jobsPanelOpen) {
|
|
4207
|
-
const panel = (_jsx(JobsPanel, { jobs: channel.backgroundJobs ?? [], onClose: () => setJobsPanelOpen(false), onKill: (id) => {
|
|
5047
|
+
if (jobsPanelOpen && launchpadGate()) {
|
|
5048
|
+
const panel = (_jsx(JobsPanel, { jobs: channel.backgroundJobs ?? [], initialFocusId: jobsPanelFocusId ?? undefined, onClose: () => { setJobsPanelOpen(false); setJobsPanelFocusId(null); }, onKill: (id) => {
|
|
4208
5049
|
// Stub channels (verify harnesses) have no jobControl — surface
|
|
4209
5050
|
// the same failure toast as a refused kill instead of throwing.
|
|
4210
5051
|
if (channel.jobControl?.kill(id) !== true) {
|
|
@@ -4215,7 +5056,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4215
5056
|
}
|
|
4216
5057
|
// Subagent dashboard: displays all active and completed subagents.
|
|
4217
5058
|
// Like the browser and settings, it replaces the conversation entirely.
|
|
4218
|
-
if (subagentDashboardOpen) {
|
|
5059
|
+
if (subagentDashboardOpen && launchpadGate()) {
|
|
4219
5060
|
const dashboard = (_jsx(SubagentDashboard, { subagents: [...channel.subagents], onSelect: (id) => {
|
|
4220
5061
|
setSubagentDashboardOpen(false);
|
|
4221
5062
|
setSubagentDetailId(id);
|
|
@@ -4235,7 +5076,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4235
5076
|
|| (recap !== null && (!recap.auto || recap.expanded))
|
|
4236
5077
|
|| btw !== null
|
|
4237
5078
|
|| questionPanelNode !== null
|
|
4238
|
-
|| starModal !== null
|
|
5079
|
+
|| starModal !== null
|
|
5080
|
+
|| couponVisible;
|
|
4239
5081
|
// The trajectory scene replaces the conversation for as long as it is open.
|
|
4240
5082
|
// Rendering it INSTEAD of (not above) the transcript is what makes it a
|
|
4241
5083
|
// screen rather than an overlay: it owns the full viewport, and the
|
|
@@ -4244,10 +5086,146 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4244
5086
|
// `<AlternateScreen>` is skipped when the app is already fullscreen —
|
|
4245
5087
|
// nesting it would emit a second DEC 1049, and its unmount would drop the
|
|
4246
5088
|
// whole app back to the main screen.
|
|
4247
|
-
if (sceneOpen) {
|
|
5089
|
+
if (sceneOpen && launchpadGate()) {
|
|
4248
5090
|
const scene = _jsx(TrajectoryScene, { channel: channel, build: trajectory, onClose: closeScene });
|
|
4249
5091
|
return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
|
|
4250
5092
|
}
|
|
5093
|
+
/**
|
|
5094
|
+
* The launchpad — the landing page an ordinary launch starts on.
|
|
5095
|
+
*
|
|
5096
|
+
* 第七版渲染姿态:early-return 排在**所有整屏界面(会话浏览器 / 设置 /
|
|
5097
|
+
* 任务面板 / 家谱 / 子代理 / 轨迹场景)之后**——从落地页打开的整屏盖在
|
|
5098
|
+
* 落地页之上,Esc 退出整屏回到这里(草稿/参数/焦点原样保留)。硬约束:
|
|
5099
|
+
* 从启动页进入对话页的唯一路径是**提交一条非命令消息**(Enter 发送);
|
|
5100
|
+
* 任何 Esc/返回都回到启动页。参数行点开的选择器经 `pickerPanels` +
|
|
5101
|
+
* `inputPaused` 渲染在本屏之上,Esc 关掉它也回到这里。
|
|
5102
|
+
*/
|
|
5103
|
+
if (launchpadShown) {
|
|
5104
|
+
// 参数行的「模式/权限」两段(第三版):模式看 channel.mode.plan;权限看
|
|
5105
|
+
// permissionPresets() 的当前身份——只认 runtime 名册,legacy/unavailable 与
|
|
5106
|
+
// 抛错都按「拿不到」处理(缺省不画那一段,两段都可选)。
|
|
5107
|
+
let launchpadPermission;
|
|
5108
|
+
try {
|
|
5109
|
+
const snapshot = channel.permissionPresets();
|
|
5110
|
+
launchpadPermission = snapshot.availability === 'runtime' ? snapshot.current?.name : undefined;
|
|
5111
|
+
}
|
|
5112
|
+
catch {
|
|
5113
|
+
launchpadPermission = undefined;
|
|
5114
|
+
}
|
|
5115
|
+
// 第七版动作表:状态快照全部来自既有数据源(详见 launchpadActions.ts)——
|
|
5116
|
+
// - lastSessionTitle = agentViewRows(含持久化名册)里最近一条非当前会话
|
|
5117
|
+
// 的标题——listing 是异步的,落地前没有 Continue、落地后自动长出来;
|
|
5118
|
+
// - jobsRunning / updateAvailable / starDue = 条件位三连(后台任务面 /
|
|
5119
|
+
// checkForTuiUpdate / usageStats 里程碑口径)。
|
|
5120
|
+
const resumableRows = agentViewRows
|
|
5121
|
+
.filter(row => !row.current && row.id !== channel.agentId && row.title.trim() !== '');
|
|
5122
|
+
const latestRow = resumableRows.reduce((best, row) => (best === undefined || row.updatedAt > best.updatedAt ? row : best), undefined);
|
|
5123
|
+
const launchpadActions = resolveLaunchpadActions({
|
|
5124
|
+
lastSessionTitle: latestRow?.title,
|
|
5125
|
+
// 条件位①:后台任务面是真数据(JobsPanel 同一个 channel.backgroundJobs),
|
|
5126
|
+
// running/stopping 都算「在跑」。
|
|
5127
|
+
jobsRunning: (channel.backgroundJobs ?? []).some(job => job.status === 'running' || job.status === 'stopping'),
|
|
5128
|
+
updateAvailable: launchpadUpdateAvailable,
|
|
5129
|
+
starDue: launchpadStarDue,
|
|
5130
|
+
});
|
|
5131
|
+
const node = (_jsx(Launchpad, { query: launchpadDraft, cursorOffset: launchpadCaret, focusIndex: launchpadFocus, isTerminalFocused: terminalFocused, whale: channel.whale, whaleIdle: channel.whaleIdle, whaleGirl: channel.whaleGirl, fontId: splashFontIdOf(channel.splashFont), starred: starred, onStarClick: runStarAction, firstRun: onboardingPending, actions: launchpadActions,
|
|
5132
|
+
// 参数行四段点开的既有选择器(第五版):pickerPanels 与聊天页共用
|
|
5133
|
+
// 同一份 JSX,盖在落地页之上;选择器开着时落地页键盘让位(inputPaused)。
|
|
5134
|
+
overlayPanel: launchpadOverlayUp ? pickerPanels : undefined, inputPaused: launchpadOverlayUp, onParamPick: (segment) => {
|
|
5135
|
+
// 四段 → 既有命令:model→/model、effort→/effort、preset→/preset、
|
|
5136
|
+
// permission→/permission。全部在 overlayCommandNames 白名单里,
|
|
5137
|
+
// 落地页不收,选择器盖上来。
|
|
5138
|
+
// 第八版切换式(用户原话「点一下是展开 再点一下收起来」):**同一段**
|
|
5139
|
+
// 再点/再按 Enter = 收起(展开 ↔ 收起,键盘与鼠标同一条 onParamPick)。
|
|
5140
|
+
// 点另一段仍直接切换,点空白/Esc 仍收起(上一版契约不回退)。
|
|
5141
|
+
if (overlay.kind === segment) {
|
|
5142
|
+
dispatchOverlay({ type: 'close' });
|
|
5143
|
+
return;
|
|
5144
|
+
}
|
|
5145
|
+
// BUG 3(点另一段直接切换):/effort 的选择器是异步 open-if(when:
|
|
5146
|
+
// ['none']),盖着别的选择器时会被丢弃——这就是"点了没反应"的根因。
|
|
5147
|
+
// 先把屏上的参数选择器收掉再开新的,一个在屏、且就是点的那段。
|
|
5148
|
+
if (overlay.kind !== 'none') {
|
|
5149
|
+
dispatchOverlay({ type: 'close' });
|
|
5150
|
+
}
|
|
5151
|
+
void runCommand(segment, '');
|
|
5152
|
+
}, onQueryChange: (text, cursor) => {
|
|
5153
|
+
setLaunchpadDraft(text);
|
|
5154
|
+
setLaunchpadCaret(cursor);
|
|
5155
|
+
}, onSubmit: closeLaunchpad, onFocusChange: setLaunchpadFocus, onAction: (action) => {
|
|
5156
|
+
// 第八版(用户实测:「刚点帮助,不知道为什么直接进入聊天页面了」):
|
|
5157
|
+
// 帮助**不属于**离开启动页的两条路(Enter 发非命令消息 / 会话浏览
|
|
5158
|
+
// 里选中会话)——它盖在落地页之上(overlay kind 'help'),再点同一
|
|
5159
|
+
// 入口 = 收起(与参数段同一条切换语义)。
|
|
5160
|
+
if (action.command === 'help') {
|
|
5161
|
+
dispatchOverlay(overlay.kind === 'help'
|
|
5162
|
+
? { type: 'close' }
|
|
5163
|
+
: { type: 'open', overlay: { kind: 'help' } });
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
// 第七版姿态:覆盖层命令(overlayCommandNames)盖在落地页之上;
|
|
5167
|
+
// **整屏命令**(launchpadScreenCommands:会话与工作区 / 设置 / 后台
|
|
5168
|
+
// 任务 / 家谱 / 子代理 / 引导)也盖在落地页之上——落地页不收,
|
|
5169
|
+
// Esc 退出整屏回到落地页(草稿/参数/焦点都在)。整屏要先过
|
|
5170
|
+
// launchpadGate 的授权位(防御:异步误置的整屏状态盖不走启动页)。
|
|
5171
|
+
// 其余命令(star / update——反馈在对话页的转录/通知)仍收掉落地页
|
|
5172
|
+
// 再执行,这是用户主动执行命令,不是「返回」。
|
|
5173
|
+
if (launchpadScreenCommands.has(action.command)) {
|
|
5174
|
+
authorizeLaunchpadCover();
|
|
5175
|
+
}
|
|
5176
|
+
else if (!overlayCommandNames.has(action.command)) {
|
|
5177
|
+
setLaunchpadOpen(false);
|
|
5178
|
+
}
|
|
5179
|
+
void runCommand(action.command, '');
|
|
5180
|
+
}, onEscape: (intent) => {
|
|
5181
|
+
if (intent === 'exit') {
|
|
5182
|
+
requestExit();
|
|
5183
|
+
return;
|
|
5184
|
+
}
|
|
5185
|
+
// 空输入按 Esc:这一屏的"下一步"通常是去挑工作区/会话。第七版:
|
|
5186
|
+
// 会话浏览器**盖在落地页之上**(落地页不收)——Esc 退出浏览器回到
|
|
5187
|
+
// 落地页,绝不落到对话页。这是从落地页出发的交互:过闸门授权。
|
|
5188
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
5189
|
+
authorizeLaunchpadCover();
|
|
5190
|
+
setSupervisorOpen(true);
|
|
5191
|
+
}, onBlankClick: () => {
|
|
5192
|
+
// BUG 3(点别处关掉选择器):沿用"点空白收回焦点"的兜底路径——
|
|
5193
|
+
// 有参数选择器盖在落地页之上时,空白点击先把选择器收掉(焦点照旧
|
|
5194
|
+
// 收回输入框)。选择器内部的点击已被 Launchpad 的浮层包装拦住
|
|
5195
|
+
// 冒泡,不会走到这里。
|
|
5196
|
+
if (overlay.kind !== 'none')
|
|
5197
|
+
dispatchOverlay({ type: 'close' });
|
|
5198
|
+
setLaunchpadFocus(-1);
|
|
5199
|
+
}, model: channel.model, effort: channel.reasoningEffort, preset: channel.agentPreset === undefined
|
|
5200
|
+
? undefined
|
|
5201
|
+
: presetOptions.find(option => option.id === channel.agentPreset)?.name ?? channel.agentPreset, permission: launchpadPermission, commands: launchpadOverlayUp || !launchpadDraft.startsWith('/')
|
|
5202
|
+
? undefined
|
|
5203
|
+
: channel.commandCompletions(launchpadDraft), onCommandPick: (commandLine) => {
|
|
5204
|
+
// 补全面板选中(Enter/Tab/点击):走 runCommand,与快捷入口同一条
|
|
5205
|
+
// 白名单口径——覆盖层与整屏命令(第七版 launchpadScreenCommands)
|
|
5206
|
+
// 不收落地页(盖在它之上),其余收掉再执行。/help 与快捷入口同一条
|
|
5207
|
+
// 拦截:盖屏浮层,不进对话页(第八版)。
|
|
5208
|
+
const parsed = parseCommandName(commandLine);
|
|
5209
|
+
if (parsed === undefined)
|
|
5210
|
+
return;
|
|
5211
|
+
if (parsed.name === 'help') {
|
|
5212
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'help' } });
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
if (launchpadScreenCommands.has(parsed.name)) {
|
|
5216
|
+
authorizeLaunchpadCover();
|
|
5217
|
+
}
|
|
5218
|
+
else if (!overlayCommandNames.has(parsed.name)) {
|
|
5219
|
+
setLaunchpadOpen(false);
|
|
5220
|
+
}
|
|
5221
|
+
void runCommand(parsed.name, parsed.rawInput);
|
|
5222
|
+
}, cwd: channel.displayCwd, branch: channel.gitBranch, tuiVersion: tuiVersion, kernelVersion: kernelVersion,
|
|
5223
|
+
// 左下角工作目录铭牌(第七版):点开/回车开既有 /workspace 菜单——
|
|
5224
|
+
// workspace-menu 在 LAUNCHPAD_OVERLAY_KINDS 里,选择器盖在落地页之上,
|
|
5225
|
+
// Esc 回落地页(与参数行选择器同一姿态),不新造面板。
|
|
5226
|
+
onOpenWorkspace: () => { void runCommand('workspace', ''); } }));
|
|
5227
|
+
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
5228
|
+
}
|
|
4251
5229
|
// 浮层整体挂载条件:与内部各面板的可见条件同值(数据门在
|
|
4252
5230
|
// dialogOverlayVisible 里逐面板镜像)。关闭时把整个 absolute 浮层从树里
|
|
4253
5231
|
// 移除——渲染器的"移除 absolute 节点"检测只看被移除子树自身的
|
|
@@ -4258,6 +5236,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4258
5236
|
workspaceTargetCount: workspaceTargets.length,
|
|
4259
5237
|
effortOptionCount: effortOptions.length,
|
|
4260
5238
|
presetOptionCount: presetOptions.length,
|
|
5239
|
+
panelCount: panelPickerRows.length,
|
|
4261
5240
|
}) && !(overlay.kind === 'permission'
|
|
4262
5241
|
&& (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
|
|
4263
5242
|
// The sticky header pins the turn owning the viewport top row once its
|
|
@@ -4280,8 +5259,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4280
5259
|
// the transcript viewport height from the ScrollBox handle and the content
|
|
4281
5260
|
// column width. The full-screen (editor-open) placement uses the terminal.
|
|
4282
5261
|
const imagePreviewRegion = promptEditorOpen
|
|
4283
|
-
? { columns:
|
|
4284
|
-
: { columns:
|
|
5262
|
+
? { columns: chatColumns, rows: terminalRows }
|
|
5263
|
+
: { columns: chatColumns, rows: handle?.getViewportHeight() ?? terminalRows };
|
|
4285
5264
|
const imagePreviewNode = activePreview !== null && (activePreview.peek || imagePreviewOwned)
|
|
4286
5265
|
? (_jsx(ImagePreviewOverlay, { image: activePreview.image, title: activePreview.title, navigation: previewGallery.length > 1 && previewIndex >= 0 ? {
|
|
4287
5266
|
index: previewIndex, total: previewGallery.length,
|
|
@@ -4290,251 +5269,119 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
|
|
|
4290
5269
|
? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
|
|
4291
5270
|
: () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
|
|
4292
5271
|
: null;
|
|
4293
|
-
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
if (recap.auto) {
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
}
|
|
4355
|
-
}, onCopy: () => {
|
|
4356
|
-
void setClipboard(recap.summary ?? '').then(raw => { if (raw)
|
|
4357
|
-
writeRaw?.(raw); });
|
|
4358
|
-
channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
|
|
4359
|
-
}, onApplyTitle: () => {
|
|
4360
|
-
if (recap.title === undefined || recap.titleApplied)
|
|
4361
|
-
return;
|
|
4362
|
-
channel.renameSession(recap.title);
|
|
4363
|
-
setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
|
|
4364
|
-
channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
|
|
4365
|
-
} }) })) : 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: () => {
|
|
4366
|
-
void setClipboard(btw.answer ?? '').then(raw => { if (raw)
|
|
4367
|
-
writeRaw?.(raw); });
|
|
4368
|
-
channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
|
|
4369
|
-
} }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen, draftCache: promptDraftRef.current, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => setHistoryFill(null), onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView,
|
|
4370
|
-
// The 🏠 at the head of the input row opens the same session screen
|
|
4371
|
-
// `/resume` and `/agentview` open — one surface, three doors. It is
|
|
4372
|
-
// gated on this prop rather than a setting, so hosts that mount the
|
|
4373
|
-
// prompt without a session screen (and the layout regressions that
|
|
4374
|
-
// pin the row's column budget) keep the row they had.
|
|
4375
|
-
onOpenSessions: () => {
|
|
4376
|
-
agentViewOpenSessionRef.current = channel.agentId;
|
|
4377
|
-
setSupervisorOpen(true);
|
|
4378
|
-
}, backgroundAgentsNeedingInput:
|
|
4379
|
-
// Only the real channel supplies the seam; pre-agent-view test
|
|
4380
|
-
// stubs must not grow the footer row (layout-dependent
|
|
4381
|
-
// regressions pin the visible row count). The footer only
|
|
4382
|
-
// renders while some session actually waits (N > 0): a
|
|
4383
|
-
// permanent idle row would steal a transcript row on every
|
|
4384
|
-
// real channel — one row is enough to scroll the startup
|
|
4385
|
-
// header fully off a short terminal, pausing its viewport
|
|
4386
|
-
// clock and shifting every row-count layout invariant.
|
|
4387
|
-
channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
|
|
4388
|
-
? backgroundAgentsNeedingInput
|
|
4389
|
-
: undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, activity: workingActivity, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
|
|
4390
|
-
? undefined
|
|
4391
|
-
: {
|
|
4392
|
-
band: wakeBand,
|
|
4393
|
-
hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
|
|
4394
|
-
tick: Math.floor(wakeTime / 120),
|
|
4395
|
-
} }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
|
|
4396
|
-
// 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
|
|
4397
|
-
const visible = index === 0;
|
|
4398
|
-
setThinkingVisible(visible);
|
|
4399
|
-
dispatchOverlay({ type: 'close' });
|
|
4400
|
-
channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
|
|
4401
|
-
} })), overlay.kind === 'workspace-picker' && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: overlay.index, currentCwd: channel.cwd, onPick: (index) => {
|
|
4402
|
-
// 点击行 = 切换该行目标(与 Enter 同一条路径)
|
|
4403
|
-
const target = workspaceTargets[index];
|
|
4404
|
-
dispatchOverlay({ type: 'close' });
|
|
4405
|
-
if (target !== undefined)
|
|
4406
|
-
void channel.switchWorkspace(target);
|
|
4407
|
-
} }) })), overlay.kind === 'workspace-menu' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceMenuPicker, { options: workspaceMenuOptions, focusIndex: overlay.index, onPick: (index) => {
|
|
4408
|
-
// 点击行 = 执行该行(与 Enter 同一条路径)
|
|
4409
|
-
runWorkspaceMenuOption(workspaceMenuOptions[index]);
|
|
4410
|
-
} }) })), overlay.kind === 'workspace-flow' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: overlay.flow.title, choices: overlay.flow.choices, focusIndex: overlay.index, busy: overlay.busy, input: overlay.input, onPick: (index) => {
|
|
4411
|
-
// 点击行 = 设焦点 + 执行分支(与 Enter 同一条路径);
|
|
4412
|
-
// busy/输入态在组件侧禁点
|
|
4413
|
-
const choice = overlay.flow.choices[index];
|
|
4414
|
-
if (choice === undefined)
|
|
4415
|
-
return;
|
|
4416
|
-
dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
|
|
4417
|
-
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
4418
|
-
} }) })), 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) => {
|
|
4419
|
-
// 点击分组行 = 进入该组(与 Enter 同一条路径)
|
|
4420
|
-
const group = modelGroups[index];
|
|
4421
|
-
if (!group)
|
|
4422
|
-
return;
|
|
4423
|
-
setModelGroup(group.provider);
|
|
4424
|
-
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
4425
|
-
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
4426
|
-
return;
|
|
5272
|
+
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [_jsxs(SidePanelLayout, { geometry: sidePanel.geometry, focus: sidePanel.focus, onActivateChat: sidePanel.focusChat, onActivatePanel: sidePanel.focusPanel, side: _jsx(SidePanelColumn, { width: sidePanel.panelColumns, controller: sidePanel, channel: channel, activity: workingActivity, attention: {
|
|
5273
|
+
approvals: approvalSnapshot !== null ? 1 : 0,
|
|
5274
|
+
questions: questionSnapshot !== null ? 1 : 0,
|
|
5275
|
+
}, trajectory: trajectory, onExpand: openPanelFullscreen }), children: [!isSticky && timeline.activeId !== null && (_jsx(PinnedTurnHeader, { text: anchorUserText || null, onClick: () => {
|
|
5276
|
+
// Click snaps the pinned prompt to the viewport top. Jump by the
|
|
5277
|
+
// SAME content coordinate the
|
|
5278
|
+
// rail's tick uses (timeline turn top = the prompt TEXT top):
|
|
5279
|
+
// the element-based seek lands the row wrapper's margin at the
|
|
5280
|
+
// top instead — one row shy of the text top the anchor rule
|
|
5281
|
+
// compares against — and the header would flip to the previous
|
|
5282
|
+
// turn immediately after the click.
|
|
5283
|
+
const turn = timeline.turns.find(t => t.id === anchorUserRowId);
|
|
5284
|
+
if (turn)
|
|
5285
|
+
handle?.scrollTo(turn.top);
|
|
5286
|
+
else if (anchorUserRowId !== null)
|
|
5287
|
+
seekRow(anchorUserRowId);
|
|
5288
|
+
else
|
|
5289
|
+
handle?.scrollToBottom();
|
|
5290
|
+
} })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: sidePanel.split ? 0 : -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd,
|
|
5291
|
+
// 大字字面(设置项 `dsh-tui.splashFont`):`daily` 交回按天轮换
|
|
5292
|
+
// (`undefined`),其余 pin 住一款。
|
|
5293
|
+
fontId: splashFontIdOf(channel.splashFont), whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, whaleGirl: channel.whaleGirl, starred: starred, onStarClick: runStarAction, working: channel.working,
|
|
5294
|
+
// Resuming a long session skips the ~3.4s opening animation: it
|
|
5295
|
+
// keeps firing low-frequency React commits that compete with the
|
|
5296
|
+
// transcript mount batches (and the first wheel events) for the
|
|
5297
|
+
// frame budget right when the user wants to read history. Fresh
|
|
5298
|
+
// sessions keep the full intro; restored ones settle instantly.
|
|
5299
|
+
// A remount after a whole screen closed also settles instantly
|
|
5300
|
+
// (see suppressLogoIntroRef).
|
|
5301
|
+
skipIntro: suppressLogoIntroRef.current || channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: primaryComboString('trajectory') }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, jobGroupFold: channel.jobGroupFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
|
|
5302
|
+
// Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
|
|
5303
|
+
// rail (default), the proportional scrollbar, or nothing. The
|
|
5304
|
+
// slot keeps its 2 columns in both rendered modes (Qwen's
|
|
5305
|
+
// permanent-gutter rule — an appearing/disappearing gutter
|
|
5306
|
+
// changes the transcript width and rewraps everything).
|
|
5307
|
+
const gutter = normalizeScrollGutter(channel.scrollGutter);
|
|
5308
|
+
if (gutter === 'hidden')
|
|
5309
|
+
return null;
|
|
5310
|
+
if (gutter === 'scrollbar') {
|
|
5311
|
+
return _jsx(ScrollbarGutter, { handle: handle, terminalWidth: chatColumns });
|
|
5312
|
+
}
|
|
5313
|
+
return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: chatColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
|
|
5314
|
+
})(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
|
|
5315
|
+
(activitySlot &&
|
|
5316
|
+
workingActivity !== undefined &&
|
|
5317
|
+
workingActivity.line !== '' &&
|
|
5318
|
+
workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
|
|
5319
|
+
// Upload = real tokens of the last request; download =
|
|
5320
|
+
// the animated chars/4 estimate, matching the classic
|
|
5321
|
+
// spinner's counter (the suffix used raw chars before,
|
|
5322
|
+
// inflating the reading next to a real upload number). An
|
|
5323
|
+
// automatic compaction mid-turn badges THIS line too — it is
|
|
5324
|
+
// the spinner slot whenever real activity data exists.
|
|
5325
|
+
suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens${compactionBadge === undefined ? '' : ` · ${compactionBadge}`}` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus, suffix: compactionBadge }))), !channel.working && channel.compaction !== undefined && (_jsx(CompactionStatusRow, { compaction: channel.compaction, activityPreset: activitySlot ? channel.activityFrames : undefined })), !(sidePanel.split && sidePanel.enabledPanelIds.includes('todo')) && (_jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) })), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, provider: channel.provider, mainCost: channel.mainCost, subagentCost: channel.subagentCost, onRefresh: runBalance, onDismiss: () => setBalance(null) })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), activePreview === null && statusViews.map(view => (_jsx(PluginStatusViewBoundary, { viewKey: view.key, onError: (key, error) => statusContributions.reportViewError(key, error), children: _jsx(Box, { flexDirection: "column", flexShrink: 0, maxHeight: view.maxRows, overflow: "hidden", children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: React.createElement(view.component, {
|
|
5326
|
+
React,
|
|
5327
|
+
ui: STATUS_VIEW_UI,
|
|
5328
|
+
}) }) }) }, `${view.key}:${view.registrationId}`))), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [approvalPanelNode !== null ? (approvalPanelNode) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : overlay.kind === 'tips' ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => dispatchOverlay({ type: 'close-if', kind: 'tips' }) }) })) : recap !== null && (!recap.auto || recap.expanded) ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RecapPanel, { summary: recap.summary, title: recap.title, error: recap.error, streaming: !recap.done, titleApplied: recap.titleApplied, onClose: () => {
|
|
5329
|
+
// An expanded auto recap collapses back to its dim row;
|
|
5330
|
+
// a manual /recap closes outright.
|
|
5331
|
+
if (recap.auto) {
|
|
5332
|
+
setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
|
|
4427
5333
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
} })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
|
|
4431
|
-
? t('picker-group-recent')
|
|
4432
|
-
: 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) => {
|
|
4433
|
-
// 点击行 = 应用该行模型(与 Enter 同一条路径)
|
|
4434
|
-
const model = groupModels[index];
|
|
4435
|
-
if (!model)
|
|
4436
|
-
return;
|
|
4437
|
-
dispatchOverlay({ type: 'close' });
|
|
4438
|
-
void switchModelRecorded(model.provider, model.id, model.name);
|
|
4439
|
-
} })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
|
|
4440
|
-
const row = (migrateRows ?? [])[index];
|
|
4441
|
-
if (!row)
|
|
4442
|
-
return;
|
|
4443
|
-
setMigrateChecked(current => {
|
|
4444
|
-
const next = new Set(current);
|
|
4445
|
-
if (next.has(row.agentId))
|
|
4446
|
-
next.delete(row.agentId);
|
|
4447
|
-
else
|
|
4448
|
-
next.add(row.agentId);
|
|
4449
|
-
return next;
|
|
4450
|
-
});
|
|
4451
|
-
} }) })), overlay.kind === 'migrate-confirm' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigrateConfirm, { rows: migratePending }) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
|
|
4452
|
-
const skill = skillsList[index];
|
|
4453
|
-
if (!skill)
|
|
4454
|
-
return;
|
|
4455
|
-
dispatchOverlay({ type: 'close' });
|
|
4456
|
-
if (skill.userInvocable)
|
|
4457
|
-
setHistoryFill(`/${skill.name} `);
|
|
4458
|
-
} })) })), overlay.kind === 'activity' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: overlay.index, currentPreset: channel.activityFrames, onPick: (index) => {
|
|
4459
|
-
dispatchOverlay({ type: 'close' });
|
|
4460
|
-
const name = PRESET_NAMES[index];
|
|
4461
|
-
if (name)
|
|
4462
|
-
channel.setActivityFrames(name);
|
|
4463
|
-
} }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
|
|
4464
|
-
dispatchOverlay({ type: 'close' });
|
|
4465
|
-
const name = SESSION_COLOR_NAMES[index];
|
|
4466
|
-
if (name) {
|
|
4467
|
-
channel.setSessionColor(name);
|
|
4468
|
-
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
4469
|
-
}
|
|
4470
|
-
} }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
|
|
4471
|
-
// 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
|
|
4472
|
-
onPick: (index) => {
|
|
4473
|
-
dispatchOverlay({ type: 'set-index', kind: 'effort', index });
|
|
4474
|
-
const option = effortOptions[index];
|
|
4475
|
-
if (option)
|
|
4476
|
-
void channel.setEffort(option.id);
|
|
4477
|
-
} }) })), overlay.kind === 'preset' && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: overlay.index, currentPreset: channel.agentPreset, onPick: (index) => {
|
|
4478
|
-
dispatchOverlay({ type: 'close' });
|
|
4479
|
-
const option = presetOptions[index];
|
|
4480
|
-
if (option)
|
|
4481
|
-
void channel.switchPreset(option.id);
|
|
4482
|
-
} }) })), overlay.kind === 'permission' && overlay.snapshot.options.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PermissionsPicker, { options: overlay.snapshot.options, focusIndex: overlay.index, currentValue: overlay.snapshot.current?.value, cwd: channel.cwd, onPick: (index) => {
|
|
4483
|
-
if (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null)
|
|
4484
|
-
return;
|
|
4485
|
-
const option = overlay.snapshot.options[index];
|
|
4486
|
-
dispatchOverlay({ type: 'close' });
|
|
4487
|
-
if (option !== undefined)
|
|
4488
|
-
void runPermissionCommand(` ${option.value}`);
|
|
4489
|
-
} }) })), overlay.kind === 'plan' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PlanPicker, { focusIndex: overlay.index, currentOn: channel.mode.plan === true, onPick: (index) => {
|
|
4490
|
-
dispatchOverlay({ type: 'close' });
|
|
4491
|
-
const on = index === 0;
|
|
4492
|
-
void runExternalCommand('plan', on ? '' : ' off');
|
|
4493
|
-
} }) })), overlay.kind === 'lang' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(LangPicker, { focusIndex: overlay.index, currentLang: getLang(), onPick: (index) => {
|
|
4494
|
-
const lang = LANGS[index];
|
|
4495
|
-
if (lang === undefined)
|
|
4496
|
-
return;
|
|
4497
|
-
dispatchOverlay({ type: 'close' });
|
|
4498
|
-
applyLang(lang);
|
|
4499
|
-
} }) })), overlay.kind === 'theme' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: overlay.index, currentTheme: themeName, themeHost: themeHost, onPick: (index) => {
|
|
4500
|
-
dispatchOverlay({ type: 'close' });
|
|
4501
|
-
const name = getThemeOptions(themeHost)[index]?.value;
|
|
4502
|
-
if (name !== undefined) {
|
|
4503
|
-
const ok = setTheme(name);
|
|
4504
|
-
channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
|
|
5334
|
+
else {
|
|
5335
|
+
closeRecap();
|
|
4505
5336
|
}
|
|
4506
|
-
}
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
}
|
|
4513
|
-
} }) })), overlay.kind === 'rewind' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: overlay.index, confirmRow: overlay.confirm, modes: overlay.modes, modeIndex: overlay.modeIndex, busy: overlay.busy, onPickRow: (index) => {
|
|
4514
|
-
// 列表页点击只选中:进入确认态保留键盘 Enter 显式触发
|
|
4515
|
-
dispatchOverlay({ type: 'set-index', kind: 'rewind', index });
|
|
4516
|
-
}, onConfirm: () => {
|
|
4517
|
-
// 确认页即显式确认层,点击直接执行(与 Enter 同路径)
|
|
4518
|
-
const row = overlay.confirm;
|
|
4519
|
-
if (row === null)
|
|
4520
|
-
return;
|
|
4521
|
-
dispatchOverlay({ type: 'close' });
|
|
4522
|
-
void performRewind(row);
|
|
4523
|
-
}, onPickMode: (index) => {
|
|
4524
|
-
// 模式列表点击直接执行该模式(与 Enter 同路径)
|
|
4525
|
-
const row = overlay.confirm;
|
|
4526
|
-
if (row === null)
|
|
5337
|
+
}, onCopy: () => {
|
|
5338
|
+
void setClipboard(recap.summary ?? '').then(raw => { if (raw)
|
|
5339
|
+
writeRaw?.(raw); });
|
|
5340
|
+
channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
|
|
5341
|
+
}, onApplyTitle: () => {
|
|
5342
|
+
if (recap.title === undefined || recap.titleApplied)
|
|
4527
5343
|
return;
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
5344
|
+
channel.renameSession(recap.title);
|
|
5345
|
+
setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
|
|
5346
|
+
channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
|
|
5347
|
+
} }) })) : 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: () => {
|
|
5348
|
+
void setClipboard(btw.answer ?? '').then(raw => { if (raw)
|
|
5349
|
+
writeRaw?.(raw); });
|
|
5350
|
+
channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
|
|
5351
|
+
} }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen,
|
|
5352
|
+
// 宠物面板是活动面板时,通知由它的头顶气泡「说出来」,输入框上方
|
|
5353
|
+
// 不再重复弹同一条(error 色除外——可能要行动的信号永远走 toast)。
|
|
5354
|
+
// 渲染期判定(split + activePanelId),与气泡同一次提交切换,不会
|
|
5355
|
+
// 先闪一帧 toast 再消失。
|
|
5356
|
+
toastSuppressed: petSaysNotices, draftCache: promptDraftRef.current, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => setHistoryFill(null), onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView,
|
|
5357
|
+
// The 🏠 at the head of the input row opens the same session screen
|
|
5358
|
+
// `/resume` and `/agentview` open — one surface, three doors. It is
|
|
5359
|
+
// gated on this prop rather than a setting, so hosts that mount the
|
|
5360
|
+
// prompt without a session screen (and the layout regressions that
|
|
5361
|
+
// pin the row's column budget) keep the row they had.
|
|
5362
|
+
onOpenSessions: () => {
|
|
5363
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
5364
|
+
setSupervisorOpen(true);
|
|
5365
|
+
}, backgroundAgentsNeedingInput:
|
|
5366
|
+
// Only the real channel supplies the seam; pre-agent-view test
|
|
5367
|
+
// stubs must not grow the footer row (layout-dependent
|
|
5368
|
+
// regressions pin the visible row count). The footer only
|
|
5369
|
+
// renders while some session actually waits (N > 0): a
|
|
5370
|
+
// permanent idle row would steal a transcript row on every
|
|
5371
|
+
// real channel — one row is enough to scroll the startup
|
|
5372
|
+
// header fully off a short terminal, pausing its viewport
|
|
5373
|
+
// clock and shifting every row-count layout invariant.
|
|
5374
|
+
channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
|
|
5375
|
+
? backgroundAgentsNeedingInput
|
|
5376
|
+
: undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, activity: workingActivity, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
|
|
5377
|
+
? undefined
|
|
5378
|
+
: {
|
|
5379
|
+
band: wakeBand,
|
|
5380
|
+
hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
|
|
5381
|
+
tick: Math.floor(wakeTime / 120),
|
|
5382
|
+
onOpen: openScene,
|
|
5383
|
+
hoverHint: primaryComboString('trajectory'),
|
|
5384
|
+
} }), dialogOverlayOpen && (_jsx(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: pickerPanels }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation })] }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode, starModal !== null && STAR_MILESTONES[starModal.index] !== undefined && (_jsx(StarPrompt, { milestone: STAR_MILESTONES[starModal.index], actions: starModalActions, onClose: closeStarModal, initialPhase: starModal.phase })), couponVisible && coupon !== null && (_jsx(WhaleCouponPrompt, { notice: coupon, onShown: markCouponShown, onClose: closeCoupon }))] }));
|
|
4538
5385
|
}
|
|
4539
5386
|
/**
|
|
4540
5387
|
* The pinned prompt header shown above the ScrollBox while the user has
|