@askdkc/dsh-cli 0.11.3
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/LICENSE +21 -0
- package/README.md +321 -0
- package/bin/dsh-tui.js +1366 -0
- package/cordis.patch.yml +482 -0
- package/cordis.yml +250 -0
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
- package/dsh-ecosystem-spec/registry/README.md +14 -0
- package/dsh-ecosystem-spec/registry/contracts/decision-events-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
- package/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
- package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -0
- package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -0
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
- package/dsh-ecosystem-spec/schemas/conformance-claim.schema.json +15 -0
- package/dsh-ecosystem-spec/schemas/effect-ledger-record.schema.json +16 -0
- package/dsh-ecosystem-spec/schemas/host-descriptor.schema.json +26 -0
- package/lib/types/activityPrefs.d.ts +63 -0
- package/lib/types/activityPrefs.d.ts.map +1 -0
- package/lib/types/activityPrefs.js +126 -0
- package/lib/types/adapter/channel/actions.d.ts +17 -0
- package/lib/types/adapter/channel/actions.d.ts.map +1 -0
- package/lib/types/adapter/channel/actions.js +28 -0
- package/lib/types/adapter/channel/consumer.d.ts +34 -0
- package/lib/types/adapter/channel/consumer.d.ts.map +1 -0
- package/lib/types/adapter/channel/consumer.js +87 -0
- package/lib/types/adapter/channel/features.d.ts +12 -0
- package/lib/types/adapter/channel/features.d.ts.map +1 -0
- package/lib/types/adapter/channel/features.js +41 -0
- package/lib/types/adapter/channel/host-registry.d.ts +40 -0
- package/lib/types/adapter/channel/host-registry.d.ts.map +1 -0
- package/lib/types/adapter/channel/host-registry.js +120 -0
- package/lib/types/adapter/channel/index.d.ts +22 -0
- package/lib/types/adapter/channel/index.d.ts.map +1 -0
- package/lib/types/adapter/channel/index.js +21 -0
- package/lib/types/adapter/channel/internal-token.d.ts +12 -0
- package/lib/types/adapter/channel/internal-token.d.ts.map +1 -0
- package/lib/types/adapter/channel/internal-token.js +11 -0
- package/lib/types/adapter/channel/plugins.d.ts +17 -0
- package/lib/types/adapter/channel/plugins.d.ts.map +1 -0
- package/lib/types/adapter/channel/plugins.js +47 -0
- package/lib/types/adapter/channel/projection.d.ts +15 -0
- package/lib/types/adapter/channel/projection.d.ts.map +1 -0
- package/lib/types/adapter/channel/projection.js +59 -0
- package/lib/types/adapter/channel/provider.d.ts +52 -0
- package/lib/types/adapter/channel/provider.d.ts.map +1 -0
- package/lib/types/adapter/channel/provider.js +231 -0
- package/lib/types/adapter/channel/read-view.d.ts +4 -0
- package/lib/types/adapter/channel/read-view.d.ts.map +1 -0
- package/lib/types/adapter/channel/read-view.js +132 -0
- package/lib/types/adapter/channel/session-projection.d.ts +56 -0
- package/lib/types/adapter/channel/session-projection.d.ts.map +1 -0
- package/lib/types/adapter/channel/session-projection.js +249 -0
- package/lib/types/adapter/channel/state.d.ts +11 -0
- package/lib/types/adapter/channel/state.d.ts.map +1 -0
- package/lib/types/adapter/channel/state.js +57 -0
- package/lib/types/adapter/channel/transcript.d.ts +15 -0
- package/lib/types/adapter/channel/transcript.d.ts.map +1 -0
- package/lib/types/adapter/channel/transcript.js +22 -0
- package/lib/types/adapter/channel/ui-policy.d.ts +109 -0
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -0
- package/lib/types/adapter/channel/ui-policy.js +170 -0
- package/lib/types/adapter/channel/ui.d.ts +16 -0
- package/lib/types/adapter/channel/ui.d.ts.map +1 -0
- package/lib/types/adapter/channel/ui.js +173 -0
- package/lib/types/adapter/index.d.ts +20 -0
- package/lib/types/adapter/index.d.ts.map +1 -0
- package/lib/types/adapter/index.js +19 -0
- package/lib/types/adapter/kernel/contract-catalog.d.ts +12 -0
- package/lib/types/adapter/kernel/contract-catalog.d.ts.map +1 -0
- package/lib/types/adapter/kernel/contract-catalog.js +11 -0
- package/lib/types/adapter/kernel/diagnostics.d.ts +18 -0
- package/lib/types/adapter/kernel/diagnostics.d.ts.map +1 -0
- package/lib/types/adapter/kernel/diagnostics.js +16 -0
- package/lib/types/adapter/kernel/driver-types.d.ts +16 -0
- package/lib/types/adapter/kernel/driver-types.d.ts.map +1 -0
- package/lib/types/adapter/kernel/driver-types.js +8 -0
- package/lib/types/adapter/kernel/host-facade.d.ts +65 -0
- package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -0
- package/lib/types/adapter/kernel/host-facade.js +216 -0
- package/lib/types/adapter/kernel/host-probe-access.d.ts +71 -0
- package/lib/types/adapter/kernel/host-probe-access.d.ts.map +1 -0
- package/lib/types/adapter/kernel/host-probe-access.js +121 -0
- package/lib/types/adapter/kernel/index.d.ts +20 -0
- package/lib/types/adapter/kernel/index.d.ts.map +1 -0
- package/lib/types/adapter/kernel/index.js +19 -0
- package/lib/types/adapter/kernel/kernel-runtime.d.ts +138 -0
- package/lib/types/adapter/kernel/kernel-runtime.d.ts.map +1 -0
- package/lib/types/adapter/kernel/kernel-runtime.js +572 -0
- package/lib/types/adapter/kernel/ledger.d.ts +55 -0
- package/lib/types/adapter/kernel/ledger.d.ts.map +1 -0
- package/lib/types/adapter/kernel/ledger.js +33 -0
- package/lib/types/adapter/kernel/legacy-facade.d.ts +22 -0
- package/lib/types/adapter/kernel/legacy-facade.d.ts.map +1 -0
- package/lib/types/adapter/kernel/legacy-facade.js +34 -0
- package/lib/types/adapter/kernel/lifecycle.d.ts +38 -0
- package/lib/types/adapter/kernel/lifecycle.d.ts.map +1 -0
- package/lib/types/adapter/kernel/lifecycle.js +53 -0
- package/lib/types/adapter/kernel/replay-isolation.d.ts +27 -0
- package/lib/types/adapter/kernel/replay-isolation.d.ts.map +1 -0
- package/lib/types/adapter/kernel/replay-isolation.js +45 -0
- package/lib/types/adapter/kernel/replay.d.ts +132 -0
- package/lib/types/adapter/kernel/replay.d.ts.map +1 -0
- package/lib/types/adapter/kernel/replay.js +428 -0
- package/lib/types/adapter/kernel/runtime-context.d.ts +15 -0
- package/lib/types/adapter/kernel/runtime-context.d.ts.map +1 -0
- package/lib/types/adapter/kernel/runtime-context.js +24 -0
- package/lib/types/adapter/kernel/runtime.d.ts +70 -0
- package/lib/types/adapter/kernel/runtime.d.ts.map +1 -0
- package/lib/types/adapter/kernel/runtime.js +327 -0
- package/lib/types/adapter/kernel/slices/channel.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/channel.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/channel.js +8 -0
- package/lib/types/adapter/kernel/slices/decisions.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/decisions.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/decisions.js +10 -0
- package/lib/types/adapter/kernel/slices/extensions.d.ts +8 -0
- package/lib/types/adapter/kernel/slices/extensions.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/extensions.js +43 -0
- package/lib/types/adapter/kernel/slices/index.d.ts +19 -0
- package/lib/types/adapter/kernel/slices/index.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/index.js +36 -0
- package/lib/types/adapter/kernel/slices/presentation.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/presentation.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/presentation.js +14 -0
- package/lib/types/adapter/kernel/slices/scenes.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/scenes.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/scenes.js +14 -0
- package/lib/types/adapter/kernel/slices/settings.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/settings.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/settings.js +12 -0
- package/lib/types/adapter/kernel/slices/types.d.ts +20 -0
- package/lib/types/adapter/kernel/slices/types.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/types.js +12 -0
- package/lib/types/adapter/kernel/slices/workspace.d.ts +3 -0
- package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -0
- package/lib/types/adapter/kernel/slices/workspace.js +16 -0
- package/lib/types/adapter/ports/channel-catalog.d.ts +90 -0
- package/lib/types/adapter/ports/channel-catalog.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-catalog.js +2 -0
- package/lib/types/adapter/ports/channel-display.d.ts +90 -0
- package/lib/types/adapter/ports/channel-display.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-display.js +2 -0
- package/lib/types/adapter/ports/channel-session.d.ts +229 -0
- package/lib/types/adapter/ports/channel-session.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-session.js +2 -0
- package/lib/types/adapter/ports/channel-settings.d.ts +279 -0
- package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-settings.js +1 -0
- package/lib/types/adapter/ports/channel-ui.d.ts +593 -0
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-ui.js +1 -0
- package/lib/types/adapter/ports/channel-view.d.ts +663 -0
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-view.js +2 -0
- package/lib/types/adapter/ports/channel-workspace.d.ts +68 -0
- package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel-workspace.js +2 -0
- package/lib/types/adapter/ports/channel.d.ts +148 -0
- package/lib/types/adapter/ports/channel.d.ts.map +1 -0
- package/lib/types/adapter/ports/channel.js +22 -0
- package/lib/types/adapter/ports/decisions.d.ts +18 -0
- package/lib/types/adapter/ports/decisions.d.ts.map +1 -0
- package/lib/types/adapter/ports/decisions.js +9 -0
- package/lib/types/adapter/ports/descriptor.d.ts +25 -0
- package/lib/types/adapter/ports/descriptor.d.ts.map +1 -0
- package/lib/types/adapter/ports/descriptor.js +11 -0
- package/lib/types/adapter/ports/extensions.d.ts +86 -0
- package/lib/types/adapter/ports/extensions.d.ts.map +1 -0
- package/lib/types/adapter/ports/extensions.js +9 -0
- package/lib/types/adapter/ports/index.d.ts +23 -0
- package/lib/types/adapter/ports/index.d.ts.map +1 -0
- package/lib/types/adapter/ports/index.js +14 -0
- package/lib/types/adapter/ports/owner.d.ts +19 -0
- package/lib/types/adapter/ports/owner.d.ts.map +1 -0
- package/lib/types/adapter/ports/owner.js +9 -0
- package/lib/types/adapter/ports/presentation.d.ts +81 -0
- package/lib/types/adapter/ports/presentation.d.ts.map +1 -0
- package/lib/types/adapter/ports/presentation.js +10 -0
- package/lib/types/adapter/ports/scenes.d.ts +24 -0
- package/lib/types/adapter/ports/scenes.d.ts.map +1 -0
- package/lib/types/adapter/ports/scenes.js +8 -0
- package/lib/types/adapter/ports/settings.d.ts +44 -0
- package/lib/types/adapter/ports/settings.d.ts.map +1 -0
- package/lib/types/adapter/ports/settings.js +9 -0
- package/lib/types/adapter/ports/workspace.d.ts +62 -0
- package/lib/types/adapter/ports/workspace.d.ts.map +1 -0
- package/lib/types/adapter/ports/workspace.js +8 -0
- package/lib/types/adapter/spec/index.d.ts +11 -0
- package/lib/types/adapter/spec/index.d.ts.map +1 -0
- package/lib/types/adapter/spec/index.js +10 -0
- package/lib/types/adapter/spec/profile.d.ts +10 -0
- package/lib/types/adapter/spec/profile.d.ts.map +1 -0
- package/lib/types/adapter/spec/profile.js +9 -0
- package/lib/types/adapter/spec/protocol-constants.d.ts +60 -0
- package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -0
- package/lib/types/adapter/spec/protocol-constants.js +129 -0
- package/lib/types/adapter/spec/tui-contributions.d.ts +18 -0
- package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -0
- package/lib/types/adapter/spec/tui-contributions.js +19 -0
- package/lib/types/adapter/standard/admission.d.ts +11 -0
- package/lib/types/adapter/standard/admission.d.ts.map +1 -0
- package/lib/types/adapter/standard/admission.js +10 -0
- package/lib/types/adapter/standard/descriptor.d.ts +74 -0
- package/lib/types/adapter/standard/descriptor.d.ts.map +1 -0
- package/lib/types/adapter/standard/descriptor.js +356 -0
- package/lib/types/adapter/standard/grants.d.ts +30 -0
- package/lib/types/adapter/standard/grants.d.ts.map +1 -0
- package/lib/types/adapter/standard/grants.js +219 -0
- package/lib/types/adapter/standard/index.d.ts +25 -0
- package/lib/types/adapter/standard/index.d.ts.map +1 -0
- package/lib/types/adapter/standard/index.js +24 -0
- package/lib/types/adapter/standard/negotiate.d.ts +11 -0
- package/lib/types/adapter/standard/negotiate.d.ts.map +1 -0
- package/lib/types/adapter/standard/negotiate.js +97 -0
- package/lib/types/adapter/standard/permission-scope.d.ts +27 -0
- package/lib/types/adapter/standard/permission-scope.d.ts.map +1 -0
- package/lib/types/adapter/standard/permission-scope.js +68 -0
- package/lib/types/adapter/standard/protocols.d.ts +12 -0
- package/lib/types/adapter/standard/protocols.d.ts.map +1 -0
- package/lib/types/adapter/standard/protocols.js +11 -0
- package/lib/types/adapter/standard/registry.d.ts +23 -0
- package/lib/types/adapter/standard/registry.d.ts.map +1 -0
- package/lib/types/adapter/standard/registry.js +240 -0
- package/lib/types/adapter/standard/schema-check.d.ts +17 -0
- package/lib/types/adapter/standard/schema-check.d.ts.map +1 -0
- package/lib/types/adapter/standard/schema-check.js +167 -0
- package/lib/types/adapter/standard/tui-extension.d.ts +25 -0
- package/lib/types/adapter/standard/tui-extension.d.ts.map +1 -0
- package/lib/types/adapter/standard/tui-extension.js +45 -0
- package/lib/types/adapter/standard/types.d.ts +108 -0
- package/lib/types/adapter/standard/types.d.ts.map +1 -0
- package/lib/types/adapter/standard/types.js +10 -0
- package/lib/types/adapter/standard/validate.d.ts +21 -0
- package/lib/types/adapter/standard/validate.d.ts.map +1 -0
- package/lib/types/adapter/standard/validate.js +139 -0
- package/lib/types/adapter/upstream/channel-driver.d.ts +22 -0
- package/lib/types/adapter/upstream/channel-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/channel-driver.js +424 -0
- package/lib/types/adapter/upstream/decisions-driver.d.ts +15 -0
- package/lib/types/adapter/upstream/decisions-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/decisions-driver.js +141 -0
- package/lib/types/adapter/upstream/detection.d.ts +26 -0
- package/lib/types/adapter/upstream/detection.d.ts.map +1 -0
- package/lib/types/adapter/upstream/detection.js +9 -0
- package/lib/types/adapter/upstream/driver.d.ts +50 -0
- package/lib/types/adapter/upstream/driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/driver.js +8 -0
- package/lib/types/adapter/upstream/extensions-driver.d.ts +24 -0
- package/lib/types/adapter/upstream/extensions-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/extensions-driver.js +697 -0
- package/lib/types/adapter/upstream/host-descriptor-driver.d.ts +45 -0
- package/lib/types/adapter/upstream/host-descriptor-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/host-descriptor-driver.js +606 -0
- package/lib/types/adapter/upstream/index.d.ts +18 -0
- package/lib/types/adapter/upstream/index.d.ts.map +1 -0
- package/lib/types/adapter/upstream/index.js +15 -0
- package/lib/types/adapter/upstream/presentation-driver.d.ts +17 -0
- package/lib/types/adapter/upstream/presentation-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/presentation-driver.js +129 -0
- package/lib/types/adapter/upstream/scenes-driver.d.ts +18 -0
- package/lib/types/adapter/upstream/scenes-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/scenes-driver.js +170 -0
- package/lib/types/adapter/upstream/settings-driver.d.ts +20 -0
- package/lib/types/adapter/upstream/settings-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/settings-driver.js +173 -0
- package/lib/types/adapter/upstream/workspace-driver.d.ts +19 -0
- package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -0
- package/lib/types/adapter/upstream/workspace-driver.js +198 -0
- package/lib/types/api.d.ts +7 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/api.js +1 -0
- package/lib/types/bootstrap/state.d.ts +11 -0
- package/lib/types/bootstrap/state.d.ts.map +1 -0
- package/lib/types/bootstrap/state.js +10 -0
- package/lib/types/command-trees.d.ts +4 -0
- package/lib/types/command-trees.d.ts.map +1 -0
- package/lib/types/command-trees.js +4 -0
- package/lib/types/commands.d.ts +87 -0
- package/lib/types/commands.d.ts.map +1 -0
- package/lib/types/commands.js +218 -0
- package/lib/types/components/ActivityLine.d.ts +34 -0
- package/lib/types/components/ActivityLine.d.ts.map +1 -0
- package/lib/types/components/ActivityLine.js +39 -0
- package/lib/types/components/ActivityPicker.d.ts +16 -0
- package/lib/types/components/ActivityPicker.d.ts.map +1 -0
- package/lib/types/components/ActivityPicker.js +23 -0
- package/lib/types/components/AutoRecapRow.d.ts +23 -0
- package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
- package/lib/types/components/AutoRecapRow.js +23 -0
- package/lib/types/components/BalanceReportRow.d.ts +24 -0
- package/lib/types/components/BalanceReportRow.d.ts.map +1 -0
- package/lib/types/components/BalanceReportRow.js +87 -0
- package/lib/types/components/BtwPanel.d.ts +16 -0
- package/lib/types/components/BtwPanel.d.ts.map +1 -0
- package/lib/types/components/BtwPanel.js +47 -0
- package/lib/types/components/Chat/JobCard.d.ts +23 -0
- package/lib/types/components/Chat/JobCard.d.ts.map +1 -0
- package/lib/types/components/Chat/JobCard.js +93 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts +20 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
- package/lib/types/components/Chat/SubagentMessage.js +83 -0
- package/lib/types/components/ColorPicker.d.ts +17 -0
- package/lib/types/components/ColorPicker.d.ts.map +1 -0
- package/lib/types/components/ColorPicker.js +20 -0
- package/lib/types/components/CommandSuggestions.d.ts +34 -0
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -0
- package/lib/types/components/CommandSuggestions.js +61 -0
- package/lib/types/components/ContextBarView.d.ts +41 -0
- package/lib/types/components/ContextBarView.d.ts.map +1 -0
- package/lib/types/components/ContextBarView.js +50 -0
- package/lib/types/components/EffortChargeGlyph.d.ts +26 -0
- package/lib/types/components/EffortChargeGlyph.d.ts.map +1 -0
- package/lib/types/components/EffortChargeGlyph.js +70 -0
- package/lib/types/components/EffortInputBorder.d.ts +47 -0
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -0
- package/lib/types/components/EffortInputBorder.js +90 -0
- package/lib/types/components/EffortSlider.d.ts +18 -0
- package/lib/types/components/EffortSlider.d.ts.map +1 -0
- package/lib/types/components/EffortSlider.js +19 -0
- package/lib/types/components/EffortTierBadge.d.ts +26 -0
- package/lib/types/components/EffortTierBadge.d.ts.map +1 -0
- package/lib/types/components/EffortTierBadge.js +97 -0
- package/lib/types/components/ExtensionDialog.d.ts +40 -0
- package/lib/types/components/ExtensionDialog.d.ts.map +1 -0
- package/lib/types/components/ExtensionDialog.js +253 -0
- package/lib/types/components/FileActionsPanel.d.ts +22 -0
- package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
- package/lib/types/components/FileActionsPanel.js +28 -0
- package/lib/types/components/FileSuggestions.d.ts +18 -0
- package/lib/types/components/FileSuggestions.d.ts.map +1 -0
- package/lib/types/components/FileSuggestions.js +78 -0
- package/lib/types/components/GoalTodoPanel.d.ts +38 -0
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -0
- package/lib/types/components/GoalTodoPanel.js +131 -0
- package/lib/types/components/HelpMenu.d.ts +29 -0
- package/lib/types/components/HelpMenu.d.ts.map +1 -0
- package/lib/types/components/HelpMenu.js +39 -0
- package/lib/types/components/HistorySearchDialog.d.ts +18 -0
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -0
- package/lib/types/components/HistorySearchDialog.js +47 -0
- package/lib/types/components/ImagePreviewOverlay.d.ts +51 -0
- package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -0
- package/lib/types/components/ImagePreviewOverlay.js +269 -0
- package/lib/types/components/JobsPanel.d.ts +17 -0
- package/lib/types/components/JobsPanel.d.ts.map +1 -0
- package/lib/types/components/JobsPanel.js +111 -0
- package/lib/types/components/LangPicker.d.ts +14 -0
- package/lib/types/components/LangPicker.d.ts.map +1 -0
- package/lib/types/components/LangPicker.js +17 -0
- package/lib/types/components/LoadedContextPanel.d.ts +20 -0
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -0
- package/lib/types/components/LoadedContextPanel.js +32 -0
- package/lib/types/components/LogoV2.d.ts +48 -0
- package/lib/types/components/LogoV2.d.ts.map +1 -0
- package/lib/types/components/LogoV2.js +239 -0
- package/lib/types/components/Markdown.d.ts +46 -0
- package/lib/types/components/Markdown.d.ts.map +1 -0
- package/lib/types/components/Markdown.js +178 -0
- package/lib/types/components/MarkdownTable.d.ts +13 -0
- package/lib/types/components/MarkdownTable.d.ts.map +1 -0
- package/lib/types/components/MarkdownTable.js +155 -0
- package/lib/types/components/MermaidDiagram.d.ts +13 -0
- package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
- package/lib/types/components/MermaidDiagram.js +61 -0
- package/lib/types/components/MessageList.d.ts +144 -0
- package/lib/types/components/MessageList.d.ts.map +1 -0
- package/lib/types/components/MessageList.js +1170 -0
- package/lib/types/components/MigratePicker.d.ts +30 -0
- package/lib/types/components/MigratePicker.d.ts.map +1 -0
- package/lib/types/components/MigratePicker.js +38 -0
- package/lib/types/components/ModelPicker.d.ts +42 -0
- package/lib/types/components/ModelPicker.d.ts.map +1 -0
- package/lib/types/components/ModelPicker.js +51 -0
- package/lib/types/components/OverlayAbove.d.ts +48 -0
- package/lib/types/components/OverlayAbove.d.ts.map +1 -0
- package/lib/types/components/OverlayAbove.js +98 -0
- package/lib/types/components/PageMargin.d.ts +58 -0
- package/lib/types/components/PageMargin.d.ts.map +1 -0
- package/lib/types/components/PageMargin.js +38 -0
- package/lib/types/components/PermissionsPicker.d.ts +20 -0
- package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
- package/lib/types/components/PermissionsPicker.js +33 -0
- package/lib/types/components/PlanPicker.d.ts +15 -0
- package/lib/types/components/PlanPicker.d.ts.map +1 -0
- package/lib/types/components/PlanPicker.js +18 -0
- package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
- package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginSceneBoundary.js +29 -0
- package/lib/types/components/PluginStatusViewBoundary.d.ts +26 -0
- package/lib/types/components/PluginStatusViewBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginStatusViewBoundary.js +22 -0
- package/lib/types/components/PresetPicker.d.ts +20 -0
- package/lib/types/components/PresetPicker.d.ts.map +1 -0
- package/lib/types/components/PresetPicker.js +24 -0
- package/lib/types/components/PromptEditor.d.ts +48 -0
- package/lib/types/components/PromptEditor.d.ts.map +1 -0
- package/lib/types/components/PromptEditor.js +75 -0
- package/lib/types/components/PromptInput.d.ts +163 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -0
- package/lib/types/components/PromptInput.js +3503 -0
- package/lib/types/components/RecapPanel.d.ts +25 -0
- package/lib/types/components/RecapPanel.d.ts.map +1 -0
- package/lib/types/components/RecapPanel.js +55 -0
- package/lib/types/components/RewindPicker.d.ts +37 -0
- package/lib/types/components/RewindPicker.d.ts.map +1 -0
- package/lib/types/components/RewindPicker.js +66 -0
- package/lib/types/components/ScrollbarGutter.d.ts +43 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
- package/lib/types/components/ScrollbarGutter.js +197 -0
- package/lib/types/components/SearchBox.d.ts +24 -0
- package/lib/types/components/SearchBox.d.ts.map +1 -0
- package/lib/types/components/SearchBox.js +132 -0
- package/lib/types/components/Select.d.ts +29 -0
- package/lib/types/components/Select.d.ts.map +1 -0
- package/lib/types/components/Select.js +21 -0
- package/lib/types/components/SkillsPicker.d.ts +21 -0
- package/lib/types/components/SkillsPicker.d.ts.map +1 -0
- package/lib/types/components/SkillsPicker.js +52 -0
- package/lib/types/components/Spinner/GlimmerMessage.d.ts +16 -0
- package/lib/types/components/Spinner/GlimmerMessage.d.ts.map +1 -0
- package/lib/types/components/Spinner/GlimmerMessage.js +57 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +33 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +168 -0
- package/lib/types/components/Spinner/SpinnerGlyph.d.ts +16 -0
- package/lib/types/components/Spinner/SpinnerGlyph.d.ts.map +1 -0
- package/lib/types/components/Spinner/SpinnerGlyph.js +36 -0
- package/lib/types/components/Spinner/spinnerMode.d.ts +2 -0
- package/lib/types/components/Spinner/spinnerMode.d.ts.map +1 -0
- package/lib/types/components/Spinner/spinnerMode.js +1 -0
- package/lib/types/components/Spinner/spinnerUtils.d.ts +36 -0
- package/lib/types/components/Spinner/spinnerUtils.d.ts.map +1 -0
- package/lib/types/components/Spinner/spinnerUtils.js +94 -0
- package/lib/types/components/Spinner/useStalledAnimation.d.ts +16 -0
- package/lib/types/components/Spinner/useStalledAnimation.d.ts.map +1 -0
- package/lib/types/components/Spinner/useStalledAnimation.js +52 -0
- package/lib/types/components/SplitDiffView.d.ts +25 -0
- package/lib/types/components/SplitDiffView.d.ts.map +1 -0
- package/lib/types/components/SplitDiffView.js +279 -0
- package/lib/types/components/StreamingMarkdown.d.ts +6 -0
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -0
- package/lib/types/components/StreamingMarkdown.js +279 -0
- package/lib/types/components/SubagentCard.d.ts +10 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -0
- package/lib/types/components/SubagentCard.js +39 -0
- package/lib/types/components/SubagentDashboard.d.ts +17 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
- package/lib/types/components/SubagentDashboard.js +58 -0
- package/lib/types/components/SubagentDetailScene.d.ts +16 -0
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
- package/lib/types/components/SubagentDetailScene.js +147 -0
- package/lib/types/components/SuggestionCard.d.ts +65 -0
- package/lib/types/components/SuggestionCard.d.ts.map +1 -0
- package/lib/types/components/SuggestionCard.js +77 -0
- package/lib/types/components/SyntaxText.d.ts +15 -0
- package/lib/types/components/SyntaxText.d.ts.map +1 -0
- package/lib/types/components/SyntaxText.js +34 -0
- package/lib/types/components/ThemePicker.d.ts +27 -0
- package/lib/types/components/ThemePicker.d.ts.map +1 -0
- package/lib/types/components/ThemePicker.js +55 -0
- package/lib/types/components/ThinkingToggle.d.ts +13 -0
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -0
- package/lib/types/components/ThinkingToggle.js +25 -0
- package/lib/types/components/TimelineRail.d.ts +53 -0
- package/lib/types/components/TimelineRail.d.ts.map +1 -0
- package/lib/types/components/TimelineRail.js +197 -0
- package/lib/types/components/TipsPanel.d.ts +14 -0
- package/lib/types/components/TipsPanel.d.ts.map +1 -0
- package/lib/types/components/TipsPanel.js +37 -0
- package/lib/types/components/ToolUseLoader.d.ts +18 -0
- package/lib/types/components/ToolUseLoader.d.ts.map +1 -0
- package/lib/types/components/ToolUseLoader.js +51 -0
- package/lib/types/components/Tooltip.d.ts +67 -0
- package/lib/types/components/Tooltip.d.ts.map +1 -0
- package/lib/types/components/Tooltip.js +266 -0
- package/lib/types/components/TurnInterruptedRow.d.ts +6 -0
- package/lib/types/components/TurnInterruptedRow.d.ts.map +1 -0
- package/lib/types/components/TurnInterruptedRow.js +9 -0
- package/lib/types/components/Whale.d.ts +42 -0
- package/lib/types/components/Whale.d.ts.map +1 -0
- package/lib/types/components/Whale.js +108 -0
- package/lib/types/components/WorkingSpinner.d.ts +26 -0
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -0
- package/lib/types/components/WorkingSpinner.js +67 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts +20 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceFlowPicker.js +21 -0
- package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
- package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceMenuPicker.js +19 -0
- package/lib/types/components/WorkspacePicker.d.ts +12 -0
- package/lib/types/components/WorkspacePicker.d.ts.map +1 -0
- package/lib/types/components/WorkspacePicker.js +13 -0
- package/lib/types/components/activityFrames.d.ts +23 -0
- package/lib/types/components/activityFrames.d.ts.map +1 -0
- package/lib/types/components/activityFrames.js +31 -0
- package/lib/types/components/approvals/ApprovalPanel.d.ts +24 -0
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -0
- package/lib/types/components/approvals/ApprovalPanel.js +53 -0
- package/lib/types/components/bigfont.d.ts +28 -0
- package/lib/types/components/bigfont.d.ts.map +1 -0
- package/lib/types/components/bigfont.js +91 -0
- package/lib/types/components/design-system/Byline.d.ts +14 -0
- package/lib/types/components/design-system/Byline.d.ts.map +1 -0
- package/lib/types/components/design-system/Byline.js +17 -0
- package/lib/types/components/design-system/Divider.d.ts +61 -0
- package/lib/types/components/design-system/Divider.d.ts.map +1 -0
- package/lib/types/components/design-system/Divider.js +115 -0
- package/lib/types/components/design-system/HintLine.d.ts +11 -0
- package/lib/types/components/design-system/HintLine.d.ts.map +1 -0
- package/lib/types/components/design-system/HintLine.js +14 -0
- package/lib/types/components/design-system/ListItem.d.ts +42 -0
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -0
- package/lib/types/components/design-system/ListItem.js +84 -0
- package/lib/types/components/design-system/LoadingState.d.ts +19 -0
- package/lib/types/components/design-system/LoadingState.d.ts.map +1 -0
- package/lib/types/components/design-system/LoadingState.js +22 -0
- package/lib/types/components/design-system/Pane.d.ts +17 -0
- package/lib/types/components/design-system/Pane.d.ts.map +1 -0
- package/lib/types/components/design-system/Pane.js +15 -0
- package/lib/types/components/design-system/ProgressBar.d.ts +21 -0
- package/lib/types/components/design-system/ProgressBar.d.ts.map +1 -0
- package/lib/types/components/design-system/ProgressBar.js +26 -0
- package/lib/types/components/design-system/StatusIcon.d.ts +15 -0
- package/lib/types/components/design-system/StatusIcon.d.ts.map +1 -0
- package/lib/types/components/design-system/StatusIcon.js +20 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts +21 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemeProvider.js +256 -0
- package/lib/types/components/design-system/ThemedBox.d.ts +60 -0
- package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemedBox.js +29 -0
- package/lib/types/components/design-system/ThemedText.d.ts +61 -0
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemedText.js +42 -0
- package/lib/types/components/listWindow.d.ts +26 -0
- package/lib/types/components/listWindow.d.ts.map +1 -0
- package/lib/types/components/listWindow.js +47 -0
- package/lib/types/components/messages/AssistantTextMessage.d.ts +20 -0
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantTextMessage.js +18 -0
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +40 -0
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantThinkingMessage.js +84 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +72 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantToolUseMessage.js +517 -0
- package/lib/types/components/messages/MessageMetadata.d.ts +14 -0
- package/lib/types/components/messages/MessageMetadata.d.ts.map +1 -0
- package/lib/types/components/messages/MessageMetadata.js +19 -0
- package/lib/types/components/messages/TranscriptImages.d.ts +21 -0
- package/lib/types/components/messages/TranscriptImages.d.ts.map +1 -0
- package/lib/types/components/messages/TranscriptImages.js +83 -0
- package/lib/types/components/messages/UserPromptMessage.d.ts +18 -0
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -0
- package/lib/types/components/messages/UserPromptMessage.js +28 -0
- package/lib/types/components/messages/imageInspection.d.ts +33 -0
- package/lib/types/components/messages/imageInspection.d.ts.map +1 -0
- package/lib/types/components/messages/imageInspection.js +107 -0
- package/lib/types/components/messages/originalImage.d.ts +5 -0
- package/lib/types/components/messages/originalImage.d.ts.map +1 -0
- package/lib/types/components/messages/originalImage.js +56 -0
- package/lib/types/components/messages/transcriptImageDecode.d.ts +9 -0
- package/lib/types/components/messages/transcriptImageDecode.d.ts.map +1 -0
- package/lib/types/components/messages/transcriptImageDecode.js +144 -0
- package/lib/types/components/messages/useImageInspection.d.ts +10 -0
- package/lib/types/components/messages/useImageInspection.d.ts.map +1 -0
- package/lib/types/components/messages/useImageInspection.js +91 -0
- package/lib/types/components/overlayBudget.d.ts +39 -0
- package/lib/types/components/overlayBudget.d.ts.map +1 -0
- package/lib/types/components/overlayBudget.js +43 -0
- package/lib/types/components/promptDraftCache.d.ts +102 -0
- package/lib/types/components/promptDraftCache.d.ts.map +1 -0
- package/lib/types/components/promptDraftCache.js +76 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +94 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -0
- package/lib/types/components/questions/AskUserQuestionPanel.js +527 -0
- package/lib/types/components/questions/PlanReviewPanel.d.ts +55 -0
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -0
- package/lib/types/components/questions/PlanReviewPanel.js +351 -0
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
- package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
- package/lib/types/components/sessions/SessionListRow.d.ts +58 -0
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionListRow.js +108 -0
- package/lib/types/components/sessions/SessionPreview.d.ts +25 -0
- package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionPreview.js +62 -0
- package/lib/types/components/sessions/WorkspaceListRow.d.ts +17 -0
- package/lib/types/components/sessions/WorkspaceListRow.d.ts.map +1 -0
- package/lib/types/components/sessions/WorkspaceListRow.js +27 -0
- package/lib/types/components/shimmer.d.ts +35 -0
- package/lib/types/components/shimmer.d.ts.map +1 -0
- package/lib/types/components/shimmer.js +43 -0
- package/lib/types/components/smoothReveal.d.ts +57 -0
- package/lib/types/components/smoothReveal.d.ts.map +1 -0
- package/lib/types/components/smoothReveal.js +305 -0
- package/lib/types/components/trajectory/HotspotView.d.ts +20 -0
- package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -0
- package/lib/types/components/trajectory/HotspotView.js +89 -0
- package/lib/types/components/trajectory/Inspector.d.ts +31 -0
- package/lib/types/components/trajectory/Inspector.d.ts.map +1 -0
- package/lib/types/components/trajectory/Inspector.js +67 -0
- package/lib/types/components/trajectory/Ledger.d.ts +28 -0
- package/lib/types/components/trajectory/Ledger.d.ts.map +1 -0
- package/lib/types/components/trajectory/Ledger.js +130 -0
- package/lib/types/components/trajectory/MiniWake.d.ts +24 -0
- package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -0
- package/lib/types/components/trajectory/MiniWake.js +99 -0
- package/lib/types/components/trajectory/WaveBand.d.ts +42 -0
- package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -0
- package/lib/types/components/trajectory/WaveBand.js +150 -0
- package/lib/types/components/whaleFrames.d.ts +81 -0
- package/lib/types/components/whaleFrames.d.ts.map +1 -0
- package/lib/types/components/whaleFrames.js +768 -0
- package/lib/types/components/whaleIdle.d.ts +66 -0
- package/lib/types/components/whaleIdle.d.ts.map +1 -0
- package/lib/types/components/whaleIdle.js +174 -0
- package/lib/types/components/whaleLayers.d.ts +28 -0
- package/lib/types/components/whaleLayers.d.ts.map +1 -0
- package/lib/types/components/whaleLayers.js +90 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
- package/lib/types/customTheme.d.ts +108 -0
- package/lib/types/customTheme.d.ts.map +1 -0
- package/lib/types/customTheme.js +281 -0
- package/lib/types/deepseekBalance.d.ts +19 -0
- package/lib/types/deepseekBalance.d.ts.map +1 -0
- package/lib/types/deepseekBalance.js +72 -0
- package/lib/types/deepseekPricing.d.ts +83 -0
- package/lib/types/deepseekPricing.d.ts.map +1 -0
- package/lib/types/deepseekPricing.js +140 -0
- package/lib/types/dsh-adapter/activity-store.d.ts +221 -0
- package/lib/types/dsh-adapter/activity-store.d.ts.map +1 -0
- package/lib/types/dsh-adapter/activity-store.js +359 -0
- package/lib/types/dsh-adapter/agent-view.d.ts +66 -0
- package/lib/types/dsh-adapter/agent-view.d.ts.map +1 -0
- package/lib/types/dsh-adapter/agent-view.js +153 -0
- package/lib/types/dsh-adapter/approvals.d.ts +225 -0
- package/lib/types/dsh-adapter/approvals.d.ts.map +1 -0
- package/lib/types/dsh-adapter/approvals.js +465 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/bundled-presets.js +80 -0
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts +20 -0
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/action-readiness.js +86 -0
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +64 -0
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/agent-view-projection.js +312 -0
- package/lib/types/dsh-adapter/channel/background-action.d.ts +41 -0
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/background-action.js +110 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +67 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/binding-events.js +170 -0
- package/lib/types/dsh-adapter/channel/binding.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/binding.js +287 -0
- package/lib/types/dsh-adapter/channel/command-completions.d.ts +29 -0
- package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/command-completions.js +94 -0
- package/lib/types/dsh-adapter/channel/commands.d.ts +8 -0
- package/lib/types/dsh-adapter/channel/commands.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/commands.js +9 -0
- package/lib/types/dsh-adapter/channel/compaction.d.ts +18 -0
- package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/compaction.js +114 -0
- package/lib/types/dsh-adapter/channel/composer-images.d.ts +62 -0
- package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/composer-images.js +316 -0
- package/lib/types/dsh-adapter/channel/composer-mentions.d.ts +15 -0
- package/lib/types/dsh-adapter/channel/composer-mentions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/composer-mentions.js +200 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +34 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.js +31 -0
- package/lib/types/dsh-adapter/channel/decisions.d.ts +19 -0
- package/lib/types/dsh-adapter/channel/decisions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/decisions.js +90 -0
- package/lib/types/dsh-adapter/channel/emitter.d.ts +10 -0
- package/lib/types/dsh-adapter/channel/emitter.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/emitter.js +71 -0
- package/lib/types/dsh-adapter/channel/external-commands.d.ts +31 -0
- package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/external-commands.js +187 -0
- package/lib/types/dsh-adapter/channel/file-actions.d.ts +20 -0
- package/lib/types/dsh-adapter/channel/file-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/file-actions.js +45 -0
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
- package/lib/types/dsh-adapter/channel/input-actions.d.ts +13 -0
- package/lib/types/dsh-adapter/channel/input-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/input-actions.js +135 -0
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +42 -0
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/input-delivery.js +313 -0
- package/lib/types/dsh-adapter/channel/job-projection.d.ts +28 -0
- package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/job-projection.js +150 -0
- package/lib/types/dsh-adapter/channel/lifetime-resources.d.ts +9 -0
- package/lib/types/dsh-adapter/channel/lifetime-resources.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/lifetime-resources.js +33 -0
- package/lib/types/dsh-adapter/channel/loaded-context.d.ts +40 -0
- package/lib/types/dsh-adapter/channel/loaded-context.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/loaded-context.js +62 -0
- package/lib/types/dsh-adapter/channel/local-actions.d.ts +54 -0
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/local-actions.js +109 -0
- package/lib/types/dsh-adapter/channel/mentions.d.ts +29 -0
- package/lib/types/dsh-adapter/channel/mentions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/mentions.js +235 -0
- package/lib/types/dsh-adapter/channel/mode-actions.d.ts +30 -0
- package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/mode-actions.js +247 -0
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +35 -0
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/mode-permission-actions.js +199 -0
- package/lib/types/dsh-adapter/channel/mode-permission.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/mode-permission.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/mode-permission.js +240 -0
- package/lib/types/dsh-adapter/channel/mode-roster.d.ts +51 -0
- package/lib/types/dsh-adapter/channel/mode-roster.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/mode-roster.js +119 -0
- package/lib/types/dsh-adapter/channel/model-actions.d.ts +49 -0
- package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/model-actions.js +351 -0
- package/lib/types/dsh-adapter/channel/model-switch.d.ts +31 -0
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/model-switch.js +116 -0
- package/lib/types/dsh-adapter/channel/notifications.d.ts +9 -0
- package/lib/types/dsh-adapter/channel/notifications.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/notifications.js +24 -0
- package/lib/types/dsh-adapter/channel/owner.d.ts +18 -0
- package/lib/types/dsh-adapter/channel/owner.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/owner.js +73 -0
- package/lib/types/dsh-adapter/channel/paths.d.ts +31 -0
- package/lib/types/dsh-adapter/channel/paths.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/paths.js +147 -0
- package/lib/types/dsh-adapter/channel/permissions.d.ts +40 -0
- package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/permissions.js +187 -0
- package/lib/types/dsh-adapter/channel/preferences.d.ts +3 -0
- package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/preferences.js +103 -0
- package/lib/types/dsh-adapter/channel/projection-helpers.d.ts +13 -0
- package/lib/types/dsh-adapter/channel/projection-helpers.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/projection-helpers.js +63 -0
- package/lib/types/dsh-adapter/channel/projection.d.ts +44 -0
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/projection.js +1031 -0
- package/lib/types/dsh-adapter/channel/reports.d.ts +32 -0
- package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/reports.js +163 -0
- package/lib/types/dsh-adapter/channel/session-actions.d.ts +13 -0
- package/lib/types/dsh-adapter/channel/session-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-actions.js +29 -0
- package/lib/types/dsh-adapter/channel/session-adoption.d.ts +32 -0
- package/lib/types/dsh-adapter/channel/session-adoption.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-adoption.js +36 -0
- package/lib/types/dsh-adapter/channel/session-fork.d.ts +19 -0
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-fork.js +109 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +35 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.js +64 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts +91 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-metadata.js +220 -0
- package/lib/types/dsh-adapter/channel/session-reset.d.ts +11 -0
- package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-reset.js +43 -0
- package/lib/types/dsh-adapter/channel/session-resume.d.ts +65 -0
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-resume.js +438 -0
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts +19 -0
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-rewind.js +146 -0
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts +19 -0
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +154 -0
- package/lib/types/dsh-adapter/channel/session-tree.d.ts +10 -0
- package/lib/types/dsh-adapter/channel/session-tree.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-tree.js +503 -0
- package/lib/types/dsh-adapter/channel/settings-host.d.ts +4 -0
- package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/settings-host.js +257 -0
- package/lib/types/dsh-adapter/channel/skill-catalog.d.ts +33 -0
- package/lib/types/dsh-adapter/channel/skill-catalog.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/skill-catalog.js +215 -0
- package/lib/types/dsh-adapter/channel/state.d.ts +51 -0
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/state.js +31 -0
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts +67 -0
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/subagent-projection.js +307 -0
- package/lib/types/dsh-adapter/channel/transcript.d.ts +73 -0
- package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/transcript.js +306 -0
- package/lib/types/dsh-adapter/channel/types.d.ts +167 -0
- package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/types.js +1 -0
- package/lib/types/dsh-adapter/channel/usage.d.ts +13 -0
- package/lib/types/dsh-adapter/channel/usage.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/usage.js +48 -0
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +23 -0
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/workspace-actions.js +96 -0
- package/lib/types/dsh-adapter/channel-facade-actions.d.ts +25 -0
- package/lib/types/dsh-adapter/channel-facade-actions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel-facade-actions.js +55 -0
- package/lib/types/dsh-adapter/channel-scene-outlet.d.ts +6 -0
- package/lib/types/dsh-adapter/channel-scene-outlet.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel-scene-outlet.js +13 -0
- package/lib/types/dsh-adapter/channel-ui.d.ts +9 -0
- package/lib/types/dsh-adapter/channel-ui.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel-ui.js +128 -0
- package/lib/types/dsh-adapter/channel.d.ts +23 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel.js +999 -0
- package/lib/types/dsh-adapter/childStderr.d.ts +63 -0
- package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -0
- package/lib/types/dsh-adapter/childStderr.js +180 -0
- package/lib/types/dsh-adapter/command-attribution.d.ts +51 -0
- package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-attribution.js +93 -0
- package/lib/types/dsh-adapter/command-errors.d.ts +42 -0
- package/lib/types/dsh-adapter/command-errors.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-errors.js +63 -0
- package/lib/types/dsh-adapter/command-trees.d.ts +34 -0
- package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-trees.js +111 -0
- package/lib/types/dsh-adapter/compat/index.d.ts +29 -0
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/index.js +28 -0
- package/lib/types/dsh-adapter/compat/liveSession.d.ts +79 -0
- package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/liveSession.js +223 -0
- package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/messages.js +15 -0
- package/lib/types/dsh-adapter/compat/persistence.d.ts +21 -0
- package/lib/types/dsh-adapter/compat/persistence.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/persistence.js +17 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +309 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/sessionLog.js +1177 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts +35 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/settings.js +57 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/shell.js +11 -0
- package/lib/types/dsh-adapter/component-identity.d.ts +33 -0
- package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -0
- package/lib/types/dsh-adapter/component-identity.js +108 -0
- package/lib/types/dsh-adapter/contract.d.ts +91 -0
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -0
- package/lib/types/dsh-adapter/contract.js +276 -0
- package/lib/types/dsh-adapter/credentialRefGuard.d.ts +40 -0
- package/lib/types/dsh-adapter/credentialRefGuard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/credentialRefGuard.js +43 -0
- package/lib/types/dsh-adapter/decision-guard.d.ts +128 -0
- package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/decision-guard.js +460 -0
- package/lib/types/dsh-adapter/dialogs.d.ts +145 -0
- package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -0
- package/lib/types/dsh-adapter/dialogs.js +329 -0
- package/lib/types/dsh-adapter/effect-ledger.d.ts +96 -0
- package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -0
- package/lib/types/dsh-adapter/effect-ledger.js +290 -0
- package/lib/types/dsh-adapter/extension-events.d.ts +186 -0
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -0
- package/lib/types/dsh-adapter/extension-events.js +187 -0
- package/lib/types/dsh-adapter/extensions.d.ts +36 -0
- package/lib/types/dsh-adapter/extensions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/extensions.js +51 -0
- package/lib/types/dsh-adapter/host-access.d.ts +37 -0
- package/lib/types/dsh-adapter/host-access.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-access.js +652 -0
- package/lib/types/dsh-adapter/host-grants.d.ts +23 -0
- package/lib/types/dsh-adapter/host-grants.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-grants.js +45 -0
- package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
- package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/ide-channel.js +556 -0
- package/lib/types/dsh-adapter/index.d.ts +151 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/index.js +119 -0
- package/lib/types/dsh-adapter/inject-channel.d.ts +108 -0
- package/lib/types/dsh-adapter/inject-channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/inject-channel.js +247 -0
- package/lib/types/dsh-adapter/invariant.d.ts +20 -0
- package/lib/types/dsh-adapter/invariant.d.ts.map +1 -0
- package/lib/types/dsh-adapter/invariant.js +26 -0
- package/lib/types/dsh-adapter/jobs.d.ts +101 -0
- package/lib/types/dsh-adapter/jobs.d.ts.map +1 -0
- package/lib/types/dsh-adapter/jobs.js +177 -0
- package/lib/types/dsh-adapter/message-observer.d.ts +186 -0
- package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -0
- package/lib/types/dsh-adapter/message-observer.js +682 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/claude-code.js +191 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/codex.js +133 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/grok-build.js +175 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/omp.js +132 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts +6 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/scan.js +38 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts +3 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/adapters/zcode.js +107 -0
- package/lib/types/dsh-adapter/migrate/bin-path.d.ts +6 -0
- package/lib/types/dsh-adapter/migrate/bin-path.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/bin-path.js +30 -0
- package/lib/types/dsh-adapter/migrate/cli.d.ts +9 -0
- package/lib/types/dsh-adapter/migrate/cli.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/cli.js +105 -0
- package/lib/types/dsh-adapter/migrate/index.d.ts +29 -0
- package/lib/types/dsh-adapter/migrate/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/index.js +108 -0
- package/lib/types/dsh-adapter/migrate/picker.d.ts +64 -0
- package/lib/types/dsh-adapter/migrate/picker.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/picker.js +86 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.d.ts +40 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/recent-agents.js +100 -0
- package/lib/types/dsh-adapter/migrate/sessionize.d.ts +19 -0
- package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/sessionize.js +101 -0
- package/lib/types/dsh-adapter/migrate/types.d.ts +52 -0
- package/lib/types/dsh-adapter/migrate/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/types.js +11 -0
- package/lib/types/dsh-adapter/migrate/uuid.d.ts +7 -0
- package/lib/types/dsh-adapter/migrate/uuid.d.ts.map +1 -0
- package/lib/types/dsh-adapter/migrate/uuid.js +25 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts +22 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/packaged-presets.js +94 -0
- package/lib/types/dsh-adapter/plugin-host.d.ts +205 -0
- package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-host.js +762 -0
- package/lib/types/dsh-adapter/plugin-storage.d.ts +118 -0
- package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-storage.js +536 -0
- package/lib/types/dsh-adapter/plugin.d.ts +70 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin.js +2140 -0
- package/lib/types/dsh-adapter/plugins-info.d.ts +43 -0
- package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugins-info.js +293 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts +57 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -0
- package/lib/types/dsh-adapter/preset-resolution.js +52 -0
- package/lib/types/dsh-adapter/presets.d.ts +110 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/presets.js +161 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts +4 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
- package/lib/types/dsh-adapter/promptDebug.js +163 -0
- package/lib/types/dsh-adapter/providerGuard.d.ts +75 -0
- package/lib/types/dsh-adapter/providerGuard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/providerGuard.js +104 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts +42 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/providerWizard.js +921 -0
- package/lib/types/dsh-adapter/questions-answerer.d.ts +51 -0
- package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -0
- package/lib/types/dsh-adapter/questions-answerer.js +64 -0
- package/lib/types/dsh-adapter/questions.d.ts +130 -0
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/questions.js +282 -0
- package/lib/types/dsh-adapter/recap.d.ts +41 -0
- package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
- package/lib/types/dsh-adapter/recap.js +111 -0
- package/lib/types/dsh-adapter/renderers.d.ts +58 -0
- package/lib/types/dsh-adapter/renderers.d.ts.map +1 -0
- package/lib/types/dsh-adapter/renderers.js +198 -0
- package/lib/types/dsh-adapter/sanitize.d.ts +45 -0
- package/lib/types/dsh-adapter/sanitize.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sanitize.js +84 -0
- package/lib/types/dsh-adapter/scenes.d.ts +87 -0
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/scenes.js +223 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts +232 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessionTree.js +984 -0
- package/lib/types/dsh-adapter/sessions/digest.d.ts +54 -0
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/digest.js +478 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts +103 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/frames.js +263 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts +56 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/header.js +85 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts +17 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/index.js +15 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts +64 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/list.js +331 -0
- package/lib/types/dsh-adapter/sessions/recovery.d.ts +17 -0
- package/lib/types/dsh-adapter/sessions/recovery.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/recovery.js +109 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts +60 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/store.js +221 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts +21 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/types.js +1 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts +48 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settings-sections.js +253 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts +100 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settingsEditor.js +238 -0
- package/lib/types/dsh-adapter/sharp.d.ts +10 -0
- package/lib/types/dsh-adapter/sharp.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sharp.js +59 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts +82 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -0
- package/lib/types/dsh-adapter/shortcuts.js +252 -0
- package/lib/types/dsh-adapter/sideQuestion.d.ts +36 -0
- package/lib/types/dsh-adapter/sideQuestion.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sideQuestion.js +59 -0
- package/lib/types/dsh-adapter/status.d.ts +148 -0
- package/lib/types/dsh-adapter/status.d.ts.map +1 -0
- package/lib/types/dsh-adapter/status.js +435 -0
- package/lib/types/dsh-adapter/subagents.d.ts +64 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/subagents.js +366 -0
- package/lib/types/dsh-adapter/themes.d.ts +52 -0
- package/lib/types/dsh-adapter/themes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/themes.js +307 -0
- package/lib/types/dsh-adapter/toast.d.ts +80 -0
- package/lib/types/dsh-adapter/toast.d.ts.map +1 -0
- package/lib/types/dsh-adapter/toast.js +171 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +47 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.js +208 -0
- package/lib/types/dsh-adapter/trajectory/guards.d.ts +196 -0
- package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/guards.js +273 -0
- package/lib/types/dsh-adapter/trajectory/index.d.ts +16 -0
- package/lib/types/dsh-adapter/trajectory/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/index.js +15 -0
- package/lib/types/dsh-adapter/trajectory/inspect.d.ts +40 -0
- package/lib/types/dsh-adapter/trajectory/inspect.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/inspect.js +160 -0
- package/lib/types/dsh-adapter/trajectory/projection.d.ts +146 -0
- package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/projection.js +710 -0
- package/lib/types/dsh-adapter/trajectory/types.d.ts +232 -0
- package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/types.js +70 -0
- package/lib/types/dsh-adapter/trajectory/wave.d.ts +45 -0
- package/lib/types/dsh-adapter/trajectory/wave.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/wave.js +210 -0
- package/lib/types/dsh-adapter/transcript-images.d.ts +17 -0
- package/lib/types/dsh-adapter/transcript-images.d.ts.map +1 -0
- package/lib/types/dsh-adapter/transcript-images.js +96 -0
- package/lib/types/dsh-adapter/types.d.ts +23 -0
- package/lib/types/dsh-adapter/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/types.js +1 -0
- package/lib/types/dsh-adapter/workspace.d.ts +22 -0
- package/lib/types/dsh-adapter/workspace.d.ts.map +1 -0
- package/lib/types/dsh-adapter/workspace.js +16 -0
- package/lib/types/dsh-adapter/workspaces.d.ts +125 -0
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -0
- package/lib/types/dsh-adapter/workspaces.js +543 -0
- package/lib/types/effortPrefs.d.ts +48 -0
- package/lib/types/effortPrefs.d.ts.map +1 -0
- package/lib/types/effortPrefs.js +95 -0
- package/lib/types/extensions.d.ts +18 -0
- package/lib/types/extensions.d.ts.map +1 -0
- package/lib/types/extensions.js +27 -0
- package/lib/types/force-production-react.d.ts +1 -0
- package/lib/types/force-production-react.d.ts.map +1 -0
- package/lib/types/force-production-react.js +12 -0
- package/lib/types/herdr.d.ts +28 -0
- package/lib/types/herdr.d.ts.map +1 -0
- package/lib/types/herdr.js +133 -0
- package/lib/types/history.d.ts +39 -0
- package/lib/types/history.d.ts.map +1 -0
- package/lib/types/history.js +177 -0
- package/lib/types/homePrefs.d.ts +15 -0
- package/lib/types/homePrefs.d.ts.map +1 -0
- package/lib/types/homePrefs.js +53 -0
- package/lib/types/hooks/useBlink.d.ts +10 -0
- package/lib/types/hooks/useBlink.d.ts.map +1 -0
- package/lib/types/hooks/useBlink.js +19 -0
- package/lib/types/hooks/useExternalVersion.d.ts +32 -0
- package/lib/types/hooks/useExternalVersion.d.ts.map +1 -0
- package/lib/types/hooks/useExternalVersion.js +53 -0
- package/lib/types/hooks/useRevealVersion.d.ts +21 -0
- package/lib/types/hooks/useRevealVersion.d.ts.map +1 -0
- package/lib/types/hooks/useRevealVersion.js +33 -0
- package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts +10 -0
- package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts.map +1 -0
- package/lib/types/hooks/useRuntimeThemeSnapshot.js +32 -0
- package/lib/types/i18n.d.ts +4433 -0
- package/lib/types/i18n.d.ts.map +1 -0
- package/lib/types/i18n.js +1493 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +4 -0
- package/lib/types/ink/Ansi.d.ts +9 -0
- package/lib/types/ink/Ansi.d.ts.map +1 -0
- package/lib/types/ink/Ansi.js +45 -0
- package/lib/types/ink/bidi.d.ts +18 -0
- package/lib/types/ink/bidi.d.ts.map +1 -0
- package/lib/types/ink/bidi.js +119 -0
- package/lib/types/ink/clearTerminal.d.ts +22 -0
- package/lib/types/ink/clearTerminal.d.ts.map +1 -0
- package/lib/types/ink/clearTerminal.js +72 -0
- package/lib/types/ink/colorize.d.ts +43 -0
- package/lib/types/ink/colorize.d.ts.map +1 -0
- package/lib/types/ink/colorize.js +233 -0
- package/lib/types/ink/components/AlternateScreen.d.ts +8 -0
- package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -0
- package/lib/types/ink/components/AlternateScreen.js +29 -0
- package/lib/types/ink/components/App.d.ts +149 -0
- package/lib/types/ink/components/App.d.ts.map +1 -0
- package/lib/types/ink/components/App.js +1027 -0
- package/lib/types/ink/components/AppContext.d.ts +17 -0
- package/lib/types/ink/components/AppContext.d.ts.map +1 -0
- package/lib/types/ink/components/AppContext.js +12 -0
- package/lib/types/ink/components/Box.d.ts +85 -0
- package/lib/types/ink/components/Box.d.ts.map +1 -0
- package/lib/types/ink/components/Box.js +22 -0
- package/lib/types/ink/components/Button.d.ts +37 -0
- package/lib/types/ink/components/Button.d.ts.map +1 -0
- package/lib/types/ink/components/Button.js +23 -0
- package/lib/types/ink/components/ClockContext.d.ts +17 -0
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -0
- package/lib/types/ink/components/ClockContext.js +99 -0
- package/lib/types/ink/components/CursorDeclarationContext.d.ts +30 -0
- package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -0
- package/lib/types/ink/components/CursorDeclarationContext.js +6 -0
- package/lib/types/ink/components/ErrorOverview.d.ts +7 -0
- package/lib/types/ink/components/ErrorOverview.d.ts.map +1 -0
- package/lib/types/ink/components/ErrorOverview.js +49 -0
- package/lib/types/ink/components/Image.d.ts +28 -0
- package/lib/types/ink/components/Image.d.ts.map +1 -0
- package/lib/types/ink/components/Image.js +44 -0
- package/lib/types/ink/components/Link.d.ts +8 -0
- package/lib/types/ink/components/Link.d.ts.map +1 -0
- package/lib/types/ink/components/Link.js +7 -0
- package/lib/types/ink/components/Newline.d.ts +7 -0
- package/lib/types/ink/components/Newline.d.ts.map +1 -0
- package/lib/types/ink/components/Newline.js +5 -0
- package/lib/types/ink/components/NoSelect.d.ts +9 -0
- package/lib/types/ink/components/NoSelect.d.ts.map +1 -0
- package/lib/types/ink/components/NoSelect.js +6 -0
- package/lib/types/ink/components/RawAnsi.d.ts +9 -0
- package/lib/types/ink/components/RawAnsi.d.ts.map +1 -0
- package/lib/types/ink/components/RawAnsi.js +7 -0
- package/lib/types/ink/components/ScrollBox.d.ts +78 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -0
- package/lib/types/ink/components/ScrollBox.js +309 -0
- package/lib/types/ink/components/Spacer.d.ts +4 -0
- package/lib/types/ink/components/Spacer.d.ts.map +1 -0
- package/lib/types/ink/components/Spacer.js +6 -0
- package/lib/types/ink/components/StdinContext.d.ts +32 -0
- package/lib/types/ink/components/StdinContext.d.ts.map +1 -0
- package/lib/types/ink/components/StdinContext.js +16 -0
- package/lib/types/ink/components/TerminalFocusContext.d.ts +11 -0
- package/lib/types/ink/components/TerminalFocusContext.d.ts.map +1 -0
- package/lib/types/ink/components/TerminalFocusContext.js +11 -0
- package/lib/types/ink/components/TerminalSizeContext.d.ts +6 -0
- package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -0
- package/lib/types/ink/components/TerminalSizeContext.js +2 -0
- package/lib/types/ink/components/Text.d.ts +64 -0
- package/lib/types/ink/components/Text.d.ts.map +1 -0
- package/lib/types/ink/components/Text.js +19 -0
- package/lib/types/ink/constants.d.ts +12 -0
- package/lib/types/ink/constants.d.ts.map +1 -0
- package/lib/types/ink/constants.js +11 -0
- package/lib/types/ink/cursor.d.ts +11 -0
- package/lib/types/ink/cursor.d.ts.map +1 -0
- package/lib/types/ink/cursor.js +1 -0
- package/lib/types/ink/devtools.d.ts +7 -0
- package/lib/types/ink/devtools.d.ts.map +1 -0
- package/lib/types/ink/devtools.js +1 -0
- package/lib/types/ink/dom.d.ts +194 -0
- package/lib/types/ink/dom.d.ts.map +1 -0
- package/lib/types/ink/dom.js +424 -0
- package/lib/types/ink/events/click-event.d.ts +23 -0
- package/lib/types/ink/events/click-event.d.ts.map +1 -0
- package/lib/types/ink/events/click-event.js +33 -0
- package/lib/types/ink/events/context-menu-event.d.ts +19 -0
- package/lib/types/ink/events/context-menu-event.d.ts.map +1 -0
- package/lib/types/ink/events/context-menu-event.js +20 -0
- package/lib/types/ink/events/dispatcher.d.ts +63 -0
- package/lib/types/ink/events/dispatcher.d.ts.map +1 -0
- package/lib/types/ink/events/dispatcher.js +194 -0
- package/lib/types/ink/events/drag-event.d.ts +44 -0
- package/lib/types/ink/events/drag-event.d.ts.map +1 -0
- package/lib/types/ink/events/drag-event.js +39 -0
- package/lib/types/ink/events/emitter.d.ts +20 -0
- package/lib/types/ink/events/emitter.d.ts.map +1 -0
- package/lib/types/ink/events/emitter.js +43 -0
- package/lib/types/ink/events/event-handlers.d.ts +65 -0
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -0
- package/lib/types/ink/events/event-handlers.js +40 -0
- package/lib/types/ink/events/event.d.ts +19 -0
- package/lib/types/ink/events/event.d.ts.map +1 -0
- package/lib/types/ink/events/event.js +22 -0
- package/lib/types/ink/events/focus-event.d.ts +19 -0
- package/lib/types/ink/events/focus-event.d.ts.map +1 -0
- package/lib/types/ink/events/focus-event.js +21 -0
- package/lib/types/ink/events/input-event.d.ts +72 -0
- package/lib/types/ink/events/input-event.d.ts.map +1 -0
- package/lib/types/ink/events/input-event.js +185 -0
- package/lib/types/ink/events/keyboard-event.d.ts +40 -0
- package/lib/types/ink/events/keyboard-event.d.ts.map +1 -0
- package/lib/types/ink/events/keyboard-event.js +65 -0
- package/lib/types/ink/events/paste-event.d.ts +11 -0
- package/lib/types/ink/events/paste-event.d.ts.map +1 -0
- package/lib/types/ink/events/paste-event.js +13 -0
- package/lib/types/ink/events/pointer-event.d.ts +61 -0
- package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
- package/lib/types/ink/events/pointer-event.js +61 -0
- package/lib/types/ink/events/resize-event.d.ts +13 -0
- package/lib/types/ink/events/resize-event.d.ts.map +1 -0
- package/lib/types/ink/events/resize-event.js +16 -0
- package/lib/types/ink/events/terminal-event.d.ts +112 -0
- package/lib/types/ink/events/terminal-event.d.ts.map +1 -0
- package/lib/types/ink/events/terminal-event.js +131 -0
- package/lib/types/ink/events/terminal-focus-event.d.ts +21 -0
- package/lib/types/ink/events/terminal-focus-event.d.ts.map +1 -0
- package/lib/types/ink/events/terminal-focus-event.js +18 -0
- package/lib/types/ink/events/wheel-event.d.ts +17 -0
- package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
- package/lib/types/ink/events/wheel-event.js +20 -0
- package/lib/types/ink/flush-tick.d.ts +26 -0
- package/lib/types/ink/flush-tick.d.ts.map +1 -0
- package/lib/types/ink/flush-tick.js +31 -0
- package/lib/types/ink/focus.d.ts +74 -0
- package/lib/types/ink/focus.d.ts.map +1 -0
- package/lib/types/ink/focus.js +188 -0
- package/lib/types/ink/frame.d.ts +128 -0
- package/lib/types/ink/frame.d.ts.map +1 -0
- package/lib/types/ink/frame.js +43 -0
- package/lib/types/ink/geometry-trace.d.ts +51 -0
- package/lib/types/ink/geometry-trace.d.ts.map +1 -0
- package/lib/types/ink/geometry-trace.js +83 -0
- package/lib/types/ink/get-max-width.d.ts +12 -0
- package/lib/types/ink/get-max-width.d.ts.map +1 -0
- package/lib/types/ink/get-max-width.js +12 -0
- package/lib/types/ink/hit-test.d.ts +181 -0
- package/lib/types/ink/hit-test.d.ts.map +1 -0
- package/lib/types/ink/hit-test.js +540 -0
- package/lib/types/ink/hooks/use-animation-frame.d.ts +27 -0
- package/lib/types/ink/hooks/use-animation-frame.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-animation-frame.js +48 -0
- package/lib/types/ink/hooks/use-app.d.ts +7 -0
- package/lib/types/ink/hooks/use-app.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-app.js +7 -0
- package/lib/types/ink/hooks/use-copy-on-select.d.ts +26 -0
- package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-copy-on-select.js +54 -0
- package/lib/types/ink/hooks/use-declared-cursor.d.ts +31 -0
- package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-declared-cursor.js +65 -0
- package/lib/types/ink/hooks/use-input.d.ts +41 -0
- package/lib/types/ink/hooks/use-input.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-input.js +81 -0
- package/lib/types/ink/hooks/use-interval.d.ts +23 -0
- package/lib/types/ink/hooks/use-interval.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-interval.js +58 -0
- package/lib/types/ink/hooks/use-search-highlight.d.ts +34 -0
- package/lib/types/ink/hooks/use-search-highlight.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-search-highlight.js +35 -0
- package/lib/types/ink/hooks/use-selection.d.ts +44 -0
- package/lib/types/ink/hooks/use-selection.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-selection.js +64 -0
- package/lib/types/ink/hooks/use-stdin.d.ts +8 -0
- package/lib/types/ink/hooks/use-stdin.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-stdin.js +8 -0
- package/lib/types/ink/hooks/use-tab-status.d.ts +18 -0
- package/lib/types/ink/hooks/use-tab-status.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-tab-status.js +58 -0
- package/lib/types/ink/hooks/use-terminal-focus.d.ts +11 -0
- package/lib/types/ink/hooks/use-terminal-focus.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-focus.js +15 -0
- package/lib/types/ink/hooks/use-terminal-images.d.ts +14 -0
- package/lib/types/ink/hooks/use-terminal-images.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-images.js +22 -0
- package/lib/types/ink/hooks/use-terminal-size.d.ts +6 -0
- package/lib/types/ink/hooks/use-terminal-size.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-size.js +12 -0
- package/lib/types/ink/hooks/use-terminal-title.d.ts +14 -0
- package/lib/types/ink/hooks/use-terminal-title.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-title.js +30 -0
- package/lib/types/ink/hooks/use-terminal-viewport.d.ts +32 -0
- package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-viewport.js +79 -0
- package/lib/types/ink/ink.d.ts +571 -0
- package/lib/types/ink/ink.d.ts.map +1 -0
- package/lib/types/ink/ink.js +2818 -0
- package/lib/types/ink/input-suppression.d.ts +20 -0
- package/lib/types/ink/input-suppression.d.ts.map +1 -0
- package/lib/types/ink/input-suppression.js +26 -0
- package/lib/types/ink/instances.d.ts +10 -0
- package/lib/types/ink/instances.d.ts.map +1 -0
- package/lib/types/ink/instances.js +13 -0
- package/lib/types/ink/kitty-graphics.d.ts +41 -0
- package/lib/types/ink/kitty-graphics.d.ts.map +1 -0
- package/lib/types/ink/kitty-graphics.js +229 -0
- package/lib/types/ink/layout/engine.d.ts +7 -0
- package/lib/types/ink/layout/engine.d.ts.map +1 -0
- package/lib/types/ink/layout/engine.js +8 -0
- package/lib/types/ink/layout/geometry.d.ts +86 -0
- package/lib/types/ink/layout/geometry.d.ts.map +1 -0
- package/lib/types/ink/layout/geometry.js +95 -0
- package/lib/types/ink/layout/node.d.ts +153 -0
- package/lib/types/ink/layout/node.d.ts.map +1 -0
- package/lib/types/ink/layout/node.js +72 -0
- package/lib/types/ink/layout/yoga.d.ts +70 -0
- package/lib/types/ink/layout/yoga.d.ts.map +1 -0
- package/lib/types/ink/layout/yoga.js +252 -0
- package/lib/types/ink/line-width-cache.d.ts +9 -0
- package/lib/types/ink/line-width-cache.d.ts.map +1 -0
- package/lib/types/ink/line-width-cache.js +45 -0
- package/lib/types/ink/log-update.d.ts +57 -0
- package/lib/types/ink/log-update.d.ts.map +1 -0
- package/lib/types/ink/log-update.js +1000 -0
- package/lib/types/ink/measure-element.d.ts +19 -0
- package/lib/types/ink/measure-element.d.ts.map +1 -0
- package/lib/types/ink/measure-element.js +10 -0
- package/lib/types/ink/measure-text.d.ts +13 -0
- package/lib/types/ink/measure-text.d.ts.map +1 -0
- package/lib/types/ink/measure-text.js +38 -0
- package/lib/types/ink/node-cache.d.ts +54 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -0
- package/lib/types/ink/node-cache.js +45 -0
- package/lib/types/ink/optimizer.d.ts +18 -0
- package/lib/types/ink/optimizer.d.ts.map +1 -0
- package/lib/types/ink/optimizer.js +83 -0
- package/lib/types/ink/output.d.ts +300 -0
- package/lib/types/ink/output.d.ts.map +1 -0
- package/lib/types/ink/output.js +1205 -0
- package/lib/types/ink/parse-keypress.d.ts +217 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -0
- package/lib/types/ink/parse-keypress.js +1550 -0
- package/lib/types/ink/reconciler.d.ts +43 -0
- package/lib/types/ink/reconciler.d.ts.map +1 -0
- package/lib/types/ink/reconciler.js +414 -0
- package/lib/types/ink/render-border.d.ts +41 -0
- package/lib/types/ink/render-border.d.ts.map +1 -0
- package/lib/types/ink/render-border.js +159 -0
- package/lib/types/ink/render-node-to-output.d.ts +199 -0
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -0
- package/lib/types/ink/render-node-to-output.js +1758 -0
- package/lib/types/ink/render-to-screen.d.ts +65 -0
- package/lib/types/ink/render-to-screen.d.ts.map +1 -0
- package/lib/types/ink/render-to-screen.js +189 -0
- package/lib/types/ink/renderer.d.ts +34 -0
- package/lib/types/ink/renderer.d.ts.map +1 -0
- package/lib/types/ink/renderer.js +181 -0
- package/lib/types/ink/root.d.ts +109 -0
- package/lib/types/ink/root.d.ts.map +1 -0
- package/lib/types/ink/root.js +96 -0
- package/lib/types/ink/screen.d.ts +545 -0
- package/lib/types/ink/screen.d.ts.map +1 -0
- package/lib/types/ink/screen.js +1607 -0
- package/lib/types/ink/selection.d.ts +529 -0
- package/lib/types/ink/selection.d.ts.map +1 -0
- package/lib/types/ink/selection.js +1292 -0
- package/lib/types/ink/sixel-codec.d.ts +41 -0
- package/lib/types/ink/sixel-codec.d.ts.map +1 -0
- package/lib/types/ink/sixel-codec.js +128 -0
- package/lib/types/ink/sixel-graphics.d.ts +74 -0
- package/lib/types/ink/sixel-graphics.d.ts.map +1 -0
- package/lib/types/ink/sixel-graphics.js +498 -0
- package/lib/types/ink/sixel-worker.d.ts +2 -0
- package/lib/types/ink/sixel-worker.d.ts.map +1 -0
- package/lib/types/ink/sixel-worker.js +12 -0
- package/lib/types/ink/squash-text-nodes.d.ts +30 -0
- package/lib/types/ink/squash-text-nodes.d.ts.map +1 -0
- package/lib/types/ink/squash-text-nodes.js +63 -0
- package/lib/types/ink/stringWidth.d.ts +3 -0
- package/lib/types/ink/stringWidth.d.ts.map +1 -0
- package/lib/types/ink/stringWidth.js +248 -0
- package/lib/types/ink/styles.d.ts +346 -0
- package/lib/types/ink/styles.d.ts.map +1 -0
- package/lib/types/ink/styles.js +307 -0
- package/lib/types/ink/supports-hyperlinks.d.ts +20 -0
- package/lib/types/ink/supports-hyperlinks.d.ts.map +1 -0
- package/lib/types/ink/supports-hyperlinks.js +44 -0
- package/lib/types/ink/tabstops.d.ts +10 -0
- package/lib/types/ink/tabstops.d.ts.map +1 -0
- package/lib/types/ink/tabstops.js +47 -0
- package/lib/types/ink/terminal-focus-state.d.ts +35 -0
- package/lib/types/ink/terminal-focus-state.d.ts.map +1 -0
- package/lib/types/ink/terminal-focus-state.js +57 -0
- package/lib/types/ink/terminal-image-protocol.d.ts +4 -0
- package/lib/types/ink/terminal-image-protocol.d.ts.map +1 -0
- package/lib/types/ink/terminal-image-protocol.js +8 -0
- package/lib/types/ink/terminal-image.d.ts +102 -0
- package/lib/types/ink/terminal-image.d.ts.map +1 -0
- package/lib/types/ink/terminal-image.js +223 -0
- package/lib/types/ink/terminal-querier.d.ts +203 -0
- package/lib/types/ink/terminal-querier.d.ts.map +1 -0
- package/lib/types/ink/terminal-querier.js +286 -0
- package/lib/types/ink/terminal.d.ts +192 -0
- package/lib/types/ink/terminal.d.ts.map +1 -0
- package/lib/types/ink/terminal.js +436 -0
- package/lib/types/ink/termio/ansi.d.ts +75 -0
- package/lib/types/ink/termio/ansi.d.ts.map +1 -0
- package/lib/types/ink/termio/ansi.js +79 -0
- package/lib/types/ink/termio/csi.d.ts +263 -0
- package/lib/types/ink/termio/csi.d.ts.map +1 -0
- package/lib/types/ink/termio/csi.js +367 -0
- package/lib/types/ink/termio/dec.d.ts +61 -0
- package/lib/types/ink/termio/dec.d.ts.map +1 -0
- package/lib/types/ink/termio/dec.js +76 -0
- package/lib/types/ink/termio/esc.d.ts +15 -0
- package/lib/types/ink/termio/esc.d.ts.map +1 -0
- package/lib/types/ink/termio/esc.js +57 -0
- package/lib/types/ink/termio/osc.d.ts +198 -0
- package/lib/types/ink/termio/osc.d.ts.map +1 -0
- package/lib/types/ink/termio/osc.js +475 -0
- package/lib/types/ink/termio/parser.d.ts +46 -0
- package/lib/types/ink/termio/parser.d.ts.map +1 -0
- package/lib/types/ink/termio/parser.js +366 -0
- package/lib/types/ink/termio/sgr.d.ts +17 -0
- package/lib/types/ink/termio/sgr.d.ts.map +1 -0
- package/lib/types/ink/termio/sgr.js +300 -0
- package/lib/types/ink/termio/tokenize.d.ts +56 -0
- package/lib/types/ink/termio/tokenize.d.ts.map +1 -0
- package/lib/types/ink/termio/tokenize.js +290 -0
- package/lib/types/ink/termio/types.d.ts +211 -0
- package/lib/types/ink/termio/types.d.ts.map +1 -0
- package/lib/types/ink/termio/types.js +68 -0
- package/lib/types/ink/termio.d.ts +25 -0
- package/lib/types/ink/termio.d.ts.map +1 -0
- package/lib/types/ink/termio.js +24 -0
- package/lib/types/ink/timeline-rail.d.ts +134 -0
- package/lib/types/ink/timeline-rail.d.ts.map +1 -0
- package/lib/types/ink/timeline-rail.js +175 -0
- package/lib/types/ink/transcript-highlight.d.ts +25 -0
- package/lib/types/ink/transcript-highlight.d.ts.map +1 -0
- package/lib/types/ink/transcript-highlight.js +82 -0
- package/lib/types/ink/truncateToWidth.d.ts +7 -0
- package/lib/types/ink/truncateToWidth.d.ts.map +1 -0
- package/lib/types/ink/truncateToWidth.js +18 -0
- package/lib/types/ink/update-overflow-guard.d.ts +62 -0
- package/lib/types/ink/update-overflow-guard.d.ts.map +1 -0
- package/lib/types/ink/update-overflow-guard.js +164 -0
- package/lib/types/ink/useTerminalNotification.d.ts +45 -0
- package/lib/types/ink/useTerminalNotification.d.ts.map +1 -0
- package/lib/types/ink/useTerminalNotification.js +70 -0
- package/lib/types/ink/warn.d.ts +7 -0
- package/lib/types/ink/warn.d.ts.map +1 -0
- package/lib/types/ink/warn.js +15 -0
- package/lib/types/ink/widest-line.d.ts +7 -0
- package/lib/types/ink/widest-line.d.ts.map +1 -0
- package/lib/types/ink/widest-line.js +19 -0
- package/lib/types/ink/wrap-text.d.ts +10 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -0
- package/lib/types/ink/wrap-text.js +103 -0
- package/lib/types/ink/wrapAnsi.d.ts +17 -0
- package/lib/types/ink/wrapAnsi.d.ts.map +1 -0
- package/lib/types/ink/wrapAnsi.js +38 -0
- package/lib/types/jsx-runtime.d.ts +2 -0
- package/lib/types/jsx-runtime.d.ts.map +1 -0
- package/lib/types/jsx-runtime.js +13 -0
- package/lib/types/migrationPrefs.d.ts +60 -0
- package/lib/types/migrationPrefs.d.ts.map +1 -0
- package/lib/types/migrationPrefs.js +97 -0
- package/lib/types/minimalMode.d.ts +13 -0
- package/lib/types/minimalMode.d.ts.map +1 -0
- package/lib/types/minimalMode.js +17 -0
- package/lib/types/modelGroups.d.ts +72 -0
- package/lib/types/modelGroups.d.ts.map +1 -0
- package/lib/types/modelGroups.js +97 -0
- package/lib/types/modelPrefs.d.ts +37 -0
- package/lib/types/modelPrefs.d.ts.map +1 -0
- package/lib/types/modelPrefs.js +66 -0
- package/lib/types/modelRecents.d.ts +39 -0
- package/lib/types/modelRecents.d.ts.map +1 -0
- package/lib/types/modelRecents.js +97 -0
- package/lib/types/modelRoute.d.ts +87 -0
- package/lib/types/modelRoute.d.ts.map +1 -0
- package/lib/types/modelRoute.js +125 -0
- package/lib/types/native-ts/yoga-layout/enums.d.ts +183 -0
- package/lib/types/native-ts/yoga-layout/enums.d.ts.map +1 -0
- package/lib/types/native-ts/yoga-layout/enums.js +150 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts +804 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -0
- package/lib/types/native-ts/yoga-layout/index.js +2614 -0
- package/lib/types/oauth.d.ts +15 -0
- package/lib/types/oauth.d.ts.map +1 -0
- package/lib/types/oauth.js +14 -0
- package/lib/types/plugin-host.d.ts +14 -0
- package/lib/types/plugin-host.d.ts.map +1 -0
- package/lib/types/plugin-host.js +19 -0
- package/lib/types/presetPrefs.d.ts +30 -0
- package/lib/types/presetPrefs.d.ts.map +1 -0
- package/lib/types/presetPrefs.js +66 -0
- package/lib/types/reload.d.ts +109 -0
- package/lib/types/reload.d.ts.map +1 -0
- package/lib/types/reload.js +118 -0
- package/lib/types/scenes.d.ts +4 -0
- package/lib/types/scenes.d.ts.map +1 -0
- package/lib/types/scenes.js +4 -0
- package/lib/types/screens/Chat.d.ts +82 -0
- package/lib/types/screens/Chat.d.ts.map +1 -0
- package/lib/types/screens/Chat.js +4299 -0
- package/lib/types/screens/SessionSupervisor.d.ts +84 -0
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/SessionSupervisor.js +350 -0
- package/lib/types/screens/SessionTree.d.ts +27 -0
- package/lib/types/screens/SessionTree.d.ts.map +1 -0
- package/lib/types/screens/SessionTree.js +583 -0
- package/lib/types/screens/Settings.d.ts +22 -0
- package/lib/types/screens/Settings.d.ts.map +1 -0
- package/lib/types/screens/Settings.js +592 -0
- package/lib/types/screens/StatusLine.d.ts +48 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -0
- package/lib/types/screens/StatusLine.js +453 -0
- package/lib/types/screens/StatusMetrics.d.ts +176 -0
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -0
- package/lib/types/screens/StatusMetrics.js +372 -0
- package/lib/types/screens/TrajectoryScene.d.ts +16 -0
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -0
- package/lib/types/screens/TrajectoryScene.js +374 -0
- package/lib/types/screens/chatOverlay.d.ts +267 -0
- package/lib/types/screens/chatOverlay.d.ts.map +1 -0
- package/lib/types/screens/chatOverlay.js +132 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/model.js +69 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +639 -0
- package/lib/types/sessionHistory.d.ts +59 -0
- package/lib/types/sessionHistory.d.ts.map +1 -0
- package/lib/types/sessionHistory.js +223 -0
- package/lib/types/sessionModes.d.ts +83 -0
- package/lib/types/sessionModes.d.ts.map +1 -0
- package/lib/types/sessionModes.js +141 -0
- package/lib/types/sessionMounts.d.ts +272 -0
- package/lib/types/sessionMounts.d.ts.map +1 -0
- package/lib/types/sessionMounts.js +706 -0
- package/lib/types/sessionPins.d.ts +33 -0
- package/lib/types/sessionPins.d.ts.map +1 -0
- package/lib/types/sessionPins.js +181 -0
- package/lib/types/sessions/format.d.ts +125 -0
- package/lib/types/sessions/format.d.ts.map +1 -0
- package/lib/types/sessions/format.js +276 -0
- package/lib/types/sessions/resumeFailure.d.ts +38 -0
- package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
- package/lib/types/sessions/resumeFailure.js +58 -0
- package/lib/types/sessions/view.d.ts +162 -0
- package/lib/types/sessions/view.d.ts.map +1 -0
- package/lib/types/sessions/view.js +350 -0
- package/lib/types/settings-sections.d.ts +4 -0
- package/lib/types/settings-sections.d.ts.map +1 -0
- package/lib/types/settings-sections.js +4 -0
- package/lib/types/terminal-utils/cliHighlight.d.ts +20 -0
- package/lib/types/terminal-utils/cliHighlight.d.ts.map +1 -0
- package/lib/types/terminal-utils/cliHighlight.js +53 -0
- package/lib/types/terminal-utils/figures.d.ts +26 -0
- package/lib/types/terminal-utils/figures.d.ts.map +1 -0
- package/lib/types/terminal-utils/figures.js +36 -0
- package/lib/types/terminal-utils/format.d.ts +35 -0
- package/lib/types/terminal-utils/format.d.ts.map +1 -0
- package/lib/types/terminal-utils/format.js +63 -0
- package/lib/types/terminal-utils/hash.d.ts +25 -0
- package/lib/types/terminal-utils/hash.d.ts.map +1 -0
- package/lib/types/terminal-utils/hash.js +41 -0
- package/lib/types/terminal-utils/hyperlink.d.ts +27 -0
- package/lib/types/terminal-utils/hyperlink.d.ts.map +1 -0
- package/lib/types/terminal-utils/hyperlink.js +70 -0
- package/lib/types/terminal-utils/markdown.d.ts +58 -0
- package/lib/types/terminal-utils/markdown.d.ts.map +1 -0
- package/lib/types/terminal-utils/markdown.js +441 -0
- package/lib/types/terminal-utils/mermaid.d.ts +44 -0
- package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
- package/lib/types/terminal-utils/mermaid.js +83 -0
- package/lib/types/terminal-utils/sessionColors.d.ts +27 -0
- package/lib/types/terminal-utils/sessionColors.d.ts.map +1 -0
- package/lib/types/terminal-utils/sessionColors.js +39 -0
- package/lib/types/terminal-utils/spinnerVerbs.d.ts +3 -0
- package/lib/types/terminal-utils/spinnerVerbs.d.ts.map +1 -0
- package/lib/types/terminal-utils/spinnerVerbs.js +21 -0
- package/lib/types/terminal-utils/syntaxRuns.d.ts +16 -0
- package/lib/types/terminal-utils/syntaxRuns.d.ts.map +1 -0
- package/lib/types/terminal-utils/syntaxRuns.js +79 -0
- package/lib/types/terminal-utils/syntaxTheme.d.ts +12 -0
- package/lib/types/terminal-utils/syntaxTheme.d.ts.map +1 -0
- package/lib/types/terminal-utils/syntaxTheme.js +76 -0
- package/lib/types/theme.d.ts +201 -0
- package/lib/types/theme.d.ts.map +1 -0
- package/lib/types/theme.js +525 -0
- package/lib/types/themeCatalog.d.ts +36 -0
- package/lib/types/themeCatalog.d.ts.map +1 -0
- package/lib/types/themeCatalog.js +159 -0
- package/lib/types/themePrefs.d.ts +28 -0
- package/lib/types/themePrefs.d.ts.map +1 -0
- package/lib/types/themePrefs.js +59 -0
- package/lib/types/tips.d.ts +30 -0
- package/lib/types/tips.d.ts.map +1 -0
- package/lib/types/tips.js +899 -0
- package/lib/types/trajectory/effortIgnition.d.ts +66 -0
- package/lib/types/trajectory/effortIgnition.d.ts.map +1 -0
- package/lib/types/trajectory/effortIgnition.js +142 -0
- package/lib/types/trajectory/format.d.ts +100 -0
- package/lib/types/trajectory/format.d.ts.map +1 -0
- package/lib/types/trajectory/format.js +213 -0
- package/lib/types/trajectory/motion.d.ts +99 -0
- package/lib/types/trajectory/motion.d.ts.map +1 -0
- package/lib/types/trajectory/motion.js +143 -0
- package/lib/types/trajectory/query.d.ts +83 -0
- package/lib/types/trajectory/query.d.ts.map +1 -0
- package/lib/types/trajectory/query.js +187 -0
- package/lib/types/trajectoryPrefs.d.ts +36 -0
- package/lib/types/trajectoryPrefs.d.ts.map +1 -0
- package/lib/types/trajectoryPrefs.js +65 -0
- package/lib/types/tuiDisplayPrefs.d.ts +49 -0
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -0
- package/lib/types/tuiDisplayPrefs.js +168 -0
- package/lib/types/ui.d.ts +29 -0
- package/lib/types/ui.d.ts.map +1 -0
- package/lib/types/ui.js +25 -0
- package/lib/types/update.d.ts +550 -0
- package/lib/types/update.d.ts.map +1 -0
- package/lib/types/update.js +1751 -0
- package/lib/types/utils/clipboard.d.ts +122 -0
- package/lib/types/utils/clipboard.d.ts.map +1 -0
- package/lib/types/utils/clipboard.js +577 -0
- package/lib/types/utils/credentials.d.ts +13 -0
- package/lib/types/utils/credentials.d.ts.map +1 -0
- package/lib/types/utils/credentials.js +41 -0
- package/lib/types/utils/debug.d.ts +18 -0
- package/lib/types/utils/debug.d.ts.map +1 -0
- package/lib/types/utils/debug.js +37 -0
- package/lib/types/utils/earlyInput.d.ts +6 -0
- package/lib/types/utils/earlyInput.d.ts.map +1 -0
- package/lib/types/utils/earlyInput.js +5 -0
- package/lib/types/utils/env.d.ts +8 -0
- package/lib/types/utils/env.d.ts.map +1 -0
- package/lib/types/utils/env.js +7 -0
- package/lib/types/utils/envUtils.d.ts +8 -0
- package/lib/types/utils/envUtils.d.ts.map +1 -0
- package/lib/types/utils/envUtils.js +13 -0
- package/lib/types/utils/execFileNoThrow.d.ts +17 -0
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -0
- package/lib/types/utils/execFileNoThrow.js +65 -0
- package/lib/types/utils/externalEditor.d.ts +116 -0
- package/lib/types/utils/externalEditor.d.ts.map +1 -0
- package/lib/types/utils/externalEditor.js +270 -0
- package/lib/types/utils/fileSuggestions.d.ts +12 -0
- package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
- package/lib/types/utils/fileSuggestions.js +40 -0
- package/lib/types/utils/fileTarget.d.ts +46 -0
- package/lib/types/utils/fileTarget.d.ts.map +1 -0
- package/lib/types/utils/fileTarget.js +178 -0
- package/lib/types/utils/fold-long-lines.d.ts +38 -0
- package/lib/types/utils/fold-long-lines.d.ts.map +1 -0
- package/lib/types/utils/fold-long-lines.js +91 -0
- package/lib/types/utils/fullscreen.d.ts +7 -0
- package/lib/types/utils/fullscreen.d.ts.map +1 -0
- package/lib/types/utils/fullscreen.js +9 -0
- package/lib/types/utils/imageResize.d.ts +90 -0
- package/lib/types/utils/imageResize.d.ts.map +1 -0
- package/lib/types/utils/imageResize.js +272 -0
- package/lib/types/utils/intl.d.ts +7 -0
- package/lib/types/utils/intl.d.ts.map +1 -0
- package/lib/types/utils/intl.js +9 -0
- package/lib/types/utils/keymap.d.ts +138 -0
- package/lib/types/utils/keymap.d.ts.map +1 -0
- package/lib/types/utils/keymap.js +351 -0
- package/lib/types/utils/loaded-context.d.ts +25 -0
- package/lib/types/utils/loaded-context.d.ts.map +1 -0
- package/lib/types/utils/loaded-context.js +72 -0
- package/lib/types/utils/log.d.ts +7 -0
- package/lib/types/utils/log.d.ts.map +1 -0
- package/lib/types/utils/log.js +9 -0
- package/lib/types/utils/mentions.d.ts +53 -0
- package/lib/types/utils/mentions.d.ts.map +1 -0
- package/lib/types/utils/mentions.js +133 -0
- package/lib/types/utils/modifiers.d.ts +43 -0
- package/lib/types/utils/modifiers.d.ts.map +1 -0
- package/lib/types/utils/modifiers.js +45 -0
- package/lib/types/utils/narration.d.ts +11 -0
- package/lib/types/utils/narration.d.ts.map +1 -0
- package/lib/types/utils/narration.js +16 -0
- package/lib/types/utils/openExternal.d.ts +50 -0
- package/lib/types/utils/openExternal.d.ts.map +1 -0
- package/lib/types/utils/openExternal.js +170 -0
- package/lib/types/utils/pastedImagePath.d.ts +51 -0
- package/lib/types/utils/pastedImagePath.d.ts.map +1 -0
- package/lib/types/utils/pastedImagePath.js +194 -0
- package/lib/types/utils/paths.d.ts +16 -0
- package/lib/types/utils/paths.d.ts.map +1 -0
- package/lib/types/utils/paths.js +19 -0
- package/lib/types/utils/semver.d.ts +9 -0
- package/lib/types/utils/semver.d.ts.map +1 -0
- package/lib/types/utils/semver.js +24 -0
- package/lib/types/utils/shellQuote.d.ts +14 -0
- package/lib/types/utils/shellQuote.d.ts.map +1 -0
- package/lib/types/utils/shellQuote.js +39 -0
- package/lib/types/utils/sliceAnsi.d.ts +12 -0
- package/lib/types/utils/sliceAnsi.d.ts.map +1 -0
- package/lib/types/utils/sliceAnsi.js +79 -0
- package/lib/types/utils/truncateMiddle.d.ts +9 -0
- package/lib/types/utils/truncateMiddle.d.ts.map +1 -0
- package/lib/types/utils/truncateMiddle.js +28 -0
- package/lib/types/utils/urlGuard.d.ts +28 -0
- package/lib/types/utils/urlGuard.d.ts.map +1 -0
- package/lib/types/utils/urlGuard.js +21 -0
- package/lib/types/utils/workspaceRoot.d.ts +21 -0
- package/lib/types/utils/workspaceRoot.d.ts.map +1 -0
- package/lib/types/utils/workspaceRoot.js +47 -0
- package/lib/types/working-activity.d.ts +32 -0
- package/lib/types/working-activity.d.ts.map +1 -0
- package/lib/types/working-activity.js +38 -0
- package/lib/types/workspaces.d.ts +5 -0
- package/lib/types/workspaces.d.ts.map +1 -0
- package/lib/types/workspaces.js +4 -0
- package/node_modules/@askdkc/dsh-auth/LICENSE +21 -0
- package/node_modules/@askdkc/dsh-auth/README.ja.md +164 -0
- package/node_modules/@askdkc/dsh-auth/README.md +163 -0
- package/node_modules/@askdkc/dsh-auth/cordis.patch.yml +11 -0
- package/node_modules/@askdkc/dsh-auth/dsh-plugin.json +44 -0
- package/node_modules/@askdkc/dsh-auth/lib/command.d.ts +14 -0
- package/node_modules/@askdkc/dsh-auth/lib/command.js +70 -0
- package/node_modules/@askdkc/dsh-auth/lib/credentials.d.ts +63 -0
- package/node_modules/@askdkc/dsh-auth/lib/credentials.js +204 -0
- package/node_modules/@askdkc/dsh-auth/lib/custom-profiles.d.ts +26 -0
- package/node_modules/@askdkc/dsh-auth/lib/custom-profiles.js +142 -0
- package/node_modules/@askdkc/dsh-auth/lib/index.d.ts +96 -0
- package/node_modules/@askdkc/dsh-auth/lib/index.js +263 -0
- package/node_modules/@askdkc/dsh-auth/lib/interaction.d.ts +68 -0
- package/node_modules/@askdkc/dsh-auth/lib/interaction.js +193 -0
- package/node_modules/@askdkc/dsh-auth/lib/nous-oauth.d.ts +5 -0
- package/node_modules/@askdkc/dsh-auth/lib/nous-oauth.js +112 -0
- package/node_modules/@askdkc/dsh-auth/lib/opener.d.ts +32 -0
- package/node_modules/@askdkc/dsh-auth/lib/opener.js +101 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-ai.d.ts +23 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-ai.js +27 -0
- package/node_modules/@askdkc/dsh-auth/lib/profiles.d.ts +45 -0
- package/node_modules/@askdkc/dsh-auth/lib/profiles.js +110 -0
- package/node_modules/@askdkc/dsh-auth/lib/service.d.ts +80 -0
- package/node_modules/@askdkc/dsh-auth/lib/service.js +224 -0
- package/node_modules/@askdkc/dsh-auth/package.json +70 -0
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/command/LICENSE +21 -0
- package/node_modules/@dsh-std/command/README.md +26 -0
- package/node_modules/@dsh-std/command/README.zh.md +28 -0
- package/node_modules/@dsh-std/command/lib/index.d.ts +144 -0
- package/node_modules/@dsh-std/command/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/command/lib/index.js +252 -0
- package/node_modules/@dsh-std/command/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/command/package.json +22 -0
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/node_modules/@dsh-std/connection/README.md +42 -0
- package/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.js +258 -0
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/node_modules/@dsh-std/connection/package.json +20 -0
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/core/LICENSE +21 -0
- package/node_modules/@dsh-std/core/README.md +13 -0
- package/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/core/package.json +21 -0
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/manifest/LICENSE +21 -0
- package/node_modules/@dsh-std/manifest/README.md +5 -0
- package/node_modules/@dsh-std/manifest/README.zh.md +5 -0
- package/node_modules/@dsh-std/manifest/lib/index.d.ts +182 -0
- package/node_modules/@dsh-std/manifest/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/manifest/lib/index.js +625 -0
- package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/manifest/package.json +20 -0
- package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -0
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/messages/LICENSE +21 -0
- package/node_modules/@dsh-std/messages/README.md +5 -0
- package/node_modules/@dsh-std/messages/README.zh.md +7 -0
- package/node_modules/@dsh-std/messages/lib/index.d.ts +50 -0
- package/node_modules/@dsh-std/messages/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/messages/lib/index.js +151 -0
- package/node_modules/@dsh-std/messages/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/messages/package.json +20 -0
- package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -0
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/presentation/LICENSE +21 -0
- package/node_modules/@dsh-std/presentation/README.md +27 -0
- package/node_modules/@dsh-std/presentation/README.zh.md +29 -0
- package/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts +262 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/index.js +550 -0
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/interaction.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/interaction.js +2 -0
- package/node_modules/@dsh-std/presentation/lib/operations.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/operations.js +2 -0
- package/node_modules/@dsh-std/presentation/package.json +25 -0
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/storage/LICENSE +21 -0
- package/node_modules/@dsh-std/storage/README.md +5 -0
- package/node_modules/@dsh-std/storage/README.zh.md +7 -0
- package/node_modules/@dsh-std/storage/lib/index.d.ts +48 -0
- package/node_modules/@dsh-std/storage/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/storage/lib/index.js +104 -0
- package/node_modules/@dsh-std/storage/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/storage/package.json +20 -0
- package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -0
- package/package.json +492 -0
- package/presets/liangshen/.dsh-tui-managed.json +5 -0
- package/presets/liangshen/agent.cordis.yml +425 -0
- package/presets/liangshen/compaction-epoch.mjs +83 -0
- package/presets/liangshen/custom-bash.mjs +301 -0
- package/presets/liangshen/instruction-hint.mjs +190 -0
- package/presets/liangshen/preset.yml +3 -0
- package/presets/liangshen/session-events.mjs +8 -0
- package/presets/liangshen/skill-search.mjs +141 -0
- package/presets/liangshen/tool-bootstrap.mjs +300 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +258 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.d.ts +182 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js +625 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +262 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +550 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/interaction.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/interaction.js +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/operations.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/operations.js +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +258 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -0
|
@@ -0,0 +1,4299 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { t, getLang, setLang, isLang, writeLangPref, readLangPref, subscribeLang, LANGS } from '../i18n.js';
|
|
4
|
+
import { readThemePref } from '../themePrefs.js';
|
|
5
|
+
import { readPresetPref } from '../presetPrefs.js';
|
|
6
|
+
import { readModelPref } from '../modelPrefs.js';
|
|
7
|
+
import { readActivityFrames } from '../activityPrefs.js';
|
|
8
|
+
import { envThemeOverride } from '../components/design-system/ThemeProvider.js';
|
|
9
|
+
import { hasPath } from '../dsh-adapter/settingsEditor.js';
|
|
10
|
+
import { planReload } from '../reload.js';
|
|
11
|
+
import { AlternateScreen, Box, Image, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
|
|
12
|
+
import { usePageInset } from '../components/PageMargin.js';
|
|
13
|
+
import { POINTER } from '../terminal-utils/figures.js';
|
|
14
|
+
import { isPlainReturnInput, modLabel } from '../utils/modifiers.js';
|
|
15
|
+
import { actionMatches } from '../utils/keymap.js';
|
|
16
|
+
import { formatTokens } from '../terminal-utils/format.js';
|
|
17
|
+
import { homeDir } from '../utils/paths.js';
|
|
18
|
+
import { execFileNoThrow } from '../utils/execFileNoThrow.js';
|
|
19
|
+
import { cleanRenderText, cleanScalarText } from '../dsh-adapter/sanitize.js';
|
|
20
|
+
import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
|
|
21
|
+
import { readModelRecents, recordModelUse } from '../modelRecents.js';
|
|
22
|
+
import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
|
|
23
|
+
import { TuiStatusStore } from '../dsh-adapter/status.js';
|
|
24
|
+
import { ActivityStore, useActivity } from '../dsh-adapter/activity-store.js';
|
|
25
|
+
import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
|
|
26
|
+
import { ApprovalStore } from '../dsh-adapter/approvals.js';
|
|
27
|
+
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
28
|
+
import { ApprovalPanel } from '../components/approvals/ApprovalPanel.js';
|
|
29
|
+
import { ExtensionDialog } from '../components/ExtensionDialog.js';
|
|
30
|
+
import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
|
|
31
|
+
import { useTerminalTitle } from '../ink/hooks/use-terminal-title.js';
|
|
32
|
+
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
33
|
+
import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
|
|
34
|
+
import { useSelection } from '../ink/hooks/use-selection.js';
|
|
35
|
+
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
36
|
+
import { LogoHeader, MessageList } from '../components/MessageList.js';
|
|
37
|
+
import { TimelineRail } from '../components/TimelineRail.js';
|
|
38
|
+
import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
|
|
39
|
+
import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
|
|
40
|
+
import { OverlayAbove } from '../components/OverlayAbove.js';
|
|
41
|
+
import { TooltipLayer } from '../components/Tooltip.js';
|
|
42
|
+
import { PromptInput } from '../components/PromptInput.js';
|
|
43
|
+
import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
|
|
44
|
+
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
45
|
+
import { AutoRecapRow } from '../components/AutoRecapRow.js';
|
|
46
|
+
import { BalanceReportRow } from '../components/BalanceReportRow.js';
|
|
47
|
+
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
48
|
+
import { StatusLine } from './StatusLine.js';
|
|
49
|
+
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
50
|
+
import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
|
|
51
|
+
import { ModelPicker } from '../components/ModelPicker.js';
|
|
52
|
+
import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
|
|
53
|
+
import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
|
|
54
|
+
import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
|
|
55
|
+
import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
|
|
56
|
+
import { MigrateConfirm, MigratePicker } from '../components/MigratePicker.js';
|
|
57
|
+
import { collectMigratePickerRows, MIGRATE_SCAN_SPECS, parseImportSummary, resolveMigrateCommand } from '../dsh-adapter/migrate/picker.js';
|
|
58
|
+
import { collectActivitySamples, recentAgentsFrom } from '../dsh-adapter/migrate/recent-agents.js';
|
|
59
|
+
import { MIGRATION_ADAPTERS } from '../dsh-adapter/migrate/index.js';
|
|
60
|
+
import { SessionSupervisor } from './SessionSupervisor.js';
|
|
61
|
+
import { SessionTree } from './SessionTree.js';
|
|
62
|
+
import { Settings } from './Settings.js';
|
|
63
|
+
import { WorkspacePicker } from '../components/WorkspacePicker.js';
|
|
64
|
+
import { WorkspaceMenuPicker } from '../components/WorkspaceMenuPicker.js';
|
|
65
|
+
import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
|
|
66
|
+
import { ActivityPicker } from '../components/ActivityPicker.js';
|
|
67
|
+
import { ColorPicker } from '../components/ColorPicker.js';
|
|
68
|
+
import { EffortSlider } from '../components/EffortSlider.js';
|
|
69
|
+
import { PresetPicker } from '../components/PresetPicker.js';
|
|
70
|
+
import { PermissionsPicker } from '../components/PermissionsPicker.js';
|
|
71
|
+
import { PlanPicker } from '../components/PlanPicker.js';
|
|
72
|
+
import { LangPicker } from '../components/LangPicker.js';
|
|
73
|
+
import { ThemePicker, getThemeOptions } from '../components/ThemePicker.js';
|
|
74
|
+
import { AUTO_THEME_NAME, getAutoThemeBase } from '../theme.js';
|
|
75
|
+
import { FRAME_PRESETS, PRESET_NAMES } from '../components/activityFrames.js';
|
|
76
|
+
import { ThinkingToggle } from '../components/ThinkingToggle.js';
|
|
77
|
+
import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
|
|
78
|
+
import { RewindPicker } from '../components/RewindPicker.js';
|
|
79
|
+
import { BtwPanel } from '../components/BtwPanel.js';
|
|
80
|
+
import { RecapPanel } from '../components/RecapPanel.js';
|
|
81
|
+
import { isValidSessionColor, SESSION_COLOR_NAMES } from '../terminal-utils/sessionColors.js';
|
|
82
|
+
import { TipsPanel } from '../components/TipsPanel.js';
|
|
83
|
+
import { SubagentDashboard } from '../components/SubagentDashboard.js';
|
|
84
|
+
import { JobsPanel } from '../components/JobsPanel.js';
|
|
85
|
+
import { SubagentDetailScene } from '../components/SubagentDetailScene.js';
|
|
86
|
+
import { FileActionsPanel, FILE_ACTION_COUNT } from '../components/FileActionsPanel.js';
|
|
87
|
+
import { openExternal, openFile, revealInFileManager } from '../utils/openExternal.js';
|
|
88
|
+
import { resolveTargetPath } from '../utils/fileTarget.js';
|
|
89
|
+
import { classifyOpenTarget } from '../utils/urlGuard.js';
|
|
90
|
+
import { statSync } from 'node:fs';
|
|
91
|
+
import { setClipboard } from '../ink/termio/osc.js';
|
|
92
|
+
import { TerminalWriteContext } from '../ink/useTerminalNotification.js';
|
|
93
|
+
import instances from '../ink/instances.js';
|
|
94
|
+
import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
95
|
+
import { useExternalVersion } from '../hooks/useExternalVersion.js';
|
|
96
|
+
import { TrajectoryScene } from './TrajectoryScene.js';
|
|
97
|
+
import { resumeFailureText } from '../sessions/resumeFailure.js';
|
|
98
|
+
import { markHomeSeen } from '../homePrefs.js';
|
|
99
|
+
import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
|
|
100
|
+
import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
|
|
101
|
+
import { readTrajectorySeen, writeTrajectorySeen } from '../trajectoryPrefs.js';
|
|
102
|
+
import { LoadingState } from '../components/design-system/LoadingState.js';
|
|
103
|
+
import { Pane } from '../components/design-system/Pane.js';
|
|
104
|
+
import { loadHistory } from '../history.js';
|
|
105
|
+
import { formatLoadedContextReport } from '../utils/loaded-context.js';
|
|
106
|
+
import { NO_OVERLAY, chatOverlayReducer, dialogOverlayVisible, wrapIndex, } from './chatOverlay.js';
|
|
107
|
+
/** Strip the focus/global-input surface even from untyped plugins. Local
|
|
108
|
+
* click, hover, and captured drag stay inside the view and are kept. */
|
|
109
|
+
function StatusViewBox({ ref: _ref, tabIndex: _tabIndex, autoFocus: _autoFocus, onContextMenu: _onContextMenu, onFocus: _onFocus, onFocusCapture: _onFocusCapture, onBlur: _onBlur, onBlurCapture: _onBlurCapture, onKeyDown: _onKeyDown, onKeyDownCapture: _onKeyDownCapture, onWheel: _onWheel, ...props }) {
|
|
110
|
+
return _jsx(Box, { ...props });
|
|
111
|
+
}
|
|
112
|
+
/** Text refs would expose the host DOM node; status text is presentation. */
|
|
113
|
+
function StatusViewText({ ref: _ref, ...props }) {
|
|
114
|
+
return _jsx(Text, { ...props });
|
|
115
|
+
}
|
|
116
|
+
/** Rich status views receive pointer-only layout/text primitives, never the
|
|
117
|
+
* input, channel, raw-ANSI, or terminal-write parts of the full UI kit. */
|
|
118
|
+
const STATUS_VIEW_UI = Object.freeze({
|
|
119
|
+
Box: StatusViewBox,
|
|
120
|
+
Image,
|
|
121
|
+
Text: StatusViewText,
|
|
122
|
+
useTerminalSize,
|
|
123
|
+
});
|
|
124
|
+
/** Shared empty snapshot for hosts whose channel has no event log. */
|
|
125
|
+
const NO_EVENTS = [];
|
|
126
|
+
const COMMAND_RESULT_CELLS = 200;
|
|
127
|
+
/** Ceiling for one `dsh-tui migrate` child run. Discovery parses every source
|
|
128
|
+
* file, but a healthy import of thousands of conversations finishes well
|
|
129
|
+
* inside this; without a cap a wedged child would hang the loop forever. */
|
|
130
|
+
const MIGRATE_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
131
|
+
function cleanCommandError(error) {
|
|
132
|
+
try {
|
|
133
|
+
if (error instanceof Error) {
|
|
134
|
+
return typeof error.message === 'string'
|
|
135
|
+
? cleanRenderText(error.message, COMMAND_RESULT_CELLS)
|
|
136
|
+
: '';
|
|
137
|
+
}
|
|
138
|
+
return cleanScalarText(error, COMMAND_RESULT_CELLS);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function clonePermissionPresetSnapshot(snapshot) {
|
|
145
|
+
return {
|
|
146
|
+
availability: snapshot.availability,
|
|
147
|
+
options: snapshot.options.map(option => ({ ...option })),
|
|
148
|
+
...(snapshot.current === undefined ? {} : { current: { ...snapshot.current } }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Row kinds the message-selection cursor can land on. */
|
|
152
|
+
const SELECTABLE_KINDS = new Set([
|
|
153
|
+
'user',
|
|
154
|
+
'assistant',
|
|
155
|
+
'tool',
|
|
156
|
+
'reasoning',
|
|
157
|
+
'interrupt',
|
|
158
|
+
'local',
|
|
159
|
+
'local-output',
|
|
160
|
+
'compact',
|
|
161
|
+
]);
|
|
162
|
+
/** Shared empty list for mode-gated derived rows (stable reference, so
|
|
163
|
+
* downstream consumers never see a changing prop when the mode is off). */
|
|
164
|
+
const NO_ROWS = [];
|
|
165
|
+
/** `max` → `Max` (effort levels arrive lower-case from the adapter). */
|
|
166
|
+
function capitalize(text) {
|
|
167
|
+
return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
|
|
168
|
+
}
|
|
169
|
+
/** Terminal-title spinner frames. */
|
|
170
|
+
const TITLE_SPINNER_FRAMES = ['⠂', '⠐'];
|
|
171
|
+
/** Searchable transcript text for one row (`/` incsearch):
|
|
172
|
+
* user text, assistant text, thinking, tool args/results, local output). */
|
|
173
|
+
function searchableText(row) {
|
|
174
|
+
switch (row.kind) {
|
|
175
|
+
case 'tool':
|
|
176
|
+
return row.tool
|
|
177
|
+
? `${row.tool.name} ${row.tool.argsText} ${row.tool.resultText ?? ''} ${row.tool.errorText ?? ''}`
|
|
178
|
+
: '';
|
|
179
|
+
default:
|
|
180
|
+
return row.text;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Main chat screen: a scrollable transcript
|
|
185
|
+
* (with the user message the viewport is showing pinned above the transcript
|
|
186
|
+
* while scrolled up, and a 1-column minimap scrollbar with one node per
|
|
187
|
+
* user message — the current message's node is highlighted, clicking a node
|
|
188
|
+
* jumps to it), transient notifications, the working spinner, the bordered
|
|
189
|
+
* prompt
|
|
190
|
+
* input (with slash-command overlay) and the status line pinned at the
|
|
191
|
+
* bottom.
|
|
192
|
+
*
|
|
193
|
+
* Ctrl+O toggles expanded detail globally; Shift+↑ enters message-selection
|
|
194
|
+
* mode (↑/↓ move, Enter expands the selected row, Esc exits); Ctrl+C
|
|
195
|
+
* interrupts the running turn, or (when idle) asks for a second Ctrl+C to
|
|
196
|
+
* exit; Enter while scrolled up jumps back to the bottom.
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Shared inert approval store for hosts that render Chat without an
|
|
200
|
+
* approval seam (headless verify scripts). Never parked into, so its
|
|
201
|
+
* snapshot stays null and the approval panel never mounts.
|
|
202
|
+
*/
|
|
203
|
+
let fallbackApprovalStore;
|
|
204
|
+
/**
|
|
205
|
+
* Shared inert extension stores for hosts that render Chat without the
|
|
206
|
+
* dsh-tui-extensions row (headless verify scripts, bare embeds). Never
|
|
207
|
+
* written, so plugin dialogs/status contributions never mount and
|
|
208
|
+
* no shortcut ever matches.
|
|
209
|
+
*/
|
|
210
|
+
let fallbackDialogStore;
|
|
211
|
+
let fallbackStatusStore;
|
|
212
|
+
/** Standalone mounts (tests, bare embeds) without the composition root's store. */
|
|
213
|
+
let fallbackActivityStore;
|
|
214
|
+
/** Identity of one caret-preview dismissal: the token (its title) on the
|
|
215
|
+
* image, so the same image staged twice is dismissed per token. */
|
|
216
|
+
function peekKey(image, title) {
|
|
217
|
+
return `${title ?? ''} ${image.id}`;
|
|
218
|
+
}
|
|
219
|
+
export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, }) {
|
|
220
|
+
const writeRaw = React.useContext(TerminalWriteContext);
|
|
221
|
+
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
222
|
+
// DEFAULT lane on purpose (useExternalVersion): the channel version bumps
|
|
223
|
+
// at streaming cadence (every ~16ms via emitStream), and a useSyncExternalStore
|
|
224
|
+
// wakeup would force a SyncLane render per bump — each such sync commit
|
|
225
|
+
// preempting the in-flight Default render and ending with Default work
|
|
226
|
+
// still pending feeds React's nested-update counter until error #185 kills
|
|
227
|
+
// the process (beta.3; the reveal-store half was PR #680, this is the
|
|
228
|
+
// channel half — the surviving source on Windows timer granularity).
|
|
229
|
+
useExternalVersion(channel.subscribe, () => channel.version);
|
|
230
|
+
// Re-render on language switches so the whole UI hot-swaps its strings.
|
|
231
|
+
React.useSyncExternalStore(subscribeLang, getLang);
|
|
232
|
+
const promptEditorOpen = usePromptEditorOpen();
|
|
233
|
+
// The pending ask-user-question (DSH user-interaction seam): the model's
|
|
234
|
+
// `ask_user_question` tool parks here until the panel is answered.
|
|
235
|
+
const questionSnapshot = React.useSyncExternalStore(listener => questionStore.subscribe(listener), () => questionStore.getSnapshot());
|
|
236
|
+
// The pending tool-approval ask (DSH approval seam): the permission layer
|
|
237
|
+
// parks here until the panel decides; shown with priority over a pending
|
|
238
|
+
// questionnaire since it gates a tool about to run. Hosts that pass no
|
|
239
|
+
// approvalStore share one inert instance that never holds an ask.
|
|
240
|
+
const approvals = approvalStore ?? (fallbackApprovalStore ??= new ApprovalStore());
|
|
241
|
+
const approvalSnapshot = React.useSyncExternalStore(listener => approvals.subscribe(listener), () => approvals.getSnapshot());
|
|
242
|
+
// The pending managed plugin dialog (tuiDialogs seam): a plugin's
|
|
243
|
+
// select/confirm/input request parks here until the panel settles it.
|
|
244
|
+
// Priority sits right below the approval panel (a gated tool outranks a
|
|
245
|
+
// plugin's question) and above the questionnaire. Hosts without the
|
|
246
|
+
// extensions row share one inert store that never holds a dialog.
|
|
247
|
+
const dialogs = extensionDialogs ?? (fallbackDialogStore ??= new TuiDialogStore());
|
|
248
|
+
const dialogSnapshot = React.useSyncExternalStore(listener => dialogs.subscribe(listener), () => dialogs.getSnapshot());
|
|
249
|
+
// Plugin status contributions: text keys join into one line; bounded rich
|
|
250
|
+
// views keep their own rows immediately above the prompt.
|
|
251
|
+
const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
|
|
252
|
+
// The working line: the working-activity plugin's published value for THIS
|
|
253
|
+
// session, read from its session projection. No projection value (plugin
|
|
254
|
+
// absent, or nothing published yet) simply means the classic spinner below.
|
|
255
|
+
const activityValues = activityStore ?? (fallbackActivityStore ??= new ActivityStore());
|
|
256
|
+
const workingActivity = useActivity(activityValues, channel.sessionId);
|
|
257
|
+
const subscribeStatus = React.useCallback((listener) => statusContributions.subscribe(listener), [statusContributions]);
|
|
258
|
+
const statusEntries = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getSnapshot());
|
|
259
|
+
const statusViews = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getViewSnapshot());
|
|
260
|
+
// Shortcut handler failures surface as toasts (the registry also logs
|
|
261
|
+
// them); the hook is re-pointed on every mount so a stale closure never
|
|
262
|
+
// outlives its channel.
|
|
263
|
+
React.useEffect(() => {
|
|
264
|
+
if (extensionShortcuts === undefined)
|
|
265
|
+
return;
|
|
266
|
+
return extensionShortcuts.setErrorHandler(combo => {
|
|
267
|
+
channel.notify(t('ext-shortcut-failed', { combo }), { color: 'error', timeoutMs: 4000 });
|
|
268
|
+
});
|
|
269
|
+
}, [extensionShortcuts, channel]);
|
|
270
|
+
// When a questionnaire batch completes, fold a Q&A summary into the
|
|
271
|
+
// transcript (the tool card itself is hidden from the message list).
|
|
272
|
+
const questionOpenRef = React.useRef(questionSnapshot !== null);
|
|
273
|
+
React.useEffect(() => {
|
|
274
|
+
const wasOpen = questionOpenRef.current;
|
|
275
|
+
questionOpenRef.current = questionSnapshot !== null;
|
|
276
|
+
if (wasOpen && questionSnapshot === null) {
|
|
277
|
+
for (const summary of questionStore.takeSummaries()) {
|
|
278
|
+
channel.pushLocal(summary.title, summary.lines);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, [channel, questionSnapshot, questionStore]);
|
|
282
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
283
|
+
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
284
|
+
const [handle, setHandle] = React.useState(null);
|
|
285
|
+
/**
|
|
286
|
+
* Conversation timeline snapshot (reported by MessageList): one entry
|
|
287
|
+
* per user turn plus the viewport-derived navigation targets. The
|
|
288
|
+
* ACTIVE turn — the one whose content owns the viewport top row — pins
|
|
289
|
+
* the sticky prompt header AND highlights the transcript rail's tick,
|
|
290
|
+
* from one report so the two can never disagree; upId/downId drive the
|
|
291
|
+
* rail's ▲/▼. Null activeId while pinned to the bottom only when there
|
|
292
|
+
* are no turns (header hidden there anyway).
|
|
293
|
+
*/
|
|
294
|
+
const [timeline, setTimeline] = React.useState({
|
|
295
|
+
turns: [],
|
|
296
|
+
activeId: null,
|
|
297
|
+
upId: null,
|
|
298
|
+
downId: null,
|
|
299
|
+
});
|
|
300
|
+
const [selectionActive, setSelectionActive] = React.useState(false);
|
|
301
|
+
const [selectedId, setSelectedId] = React.useState(null);
|
|
302
|
+
const [expandedRows, setExpandedRows] = React.useState(() => new Set());
|
|
303
|
+
/** 流式 reasoning 行相对 thinkingFold 默认值的用户切换。与
|
|
304
|
+
* expandedRows 分开:preview 默认三行、full 默认全文,点击在两者间
|
|
305
|
+
* 翻转;落定后自动回到普通行的折叠语义。 */
|
|
306
|
+
const [streamViewToggledRows, setStreamViewToggledRows] = React.useState(() => new Set());
|
|
307
|
+
/**
|
|
308
|
+
* The transient-dialog layer (every picker/dialog `<OverlayAbove>` hosts,
|
|
309
|
+
* plus /tips) as ONE value: mutual exclusion between the panels is
|
|
310
|
+
* structural instead of emerging from "an open picker makes the prompt
|
|
311
|
+
* inert". Transitions live in the pure reducer (chatOverlay.ts), which
|
|
312
|
+
* scripts/verify-chat-overlay.ts pins without a renderer. Async data the
|
|
313
|
+
* pickers show (model list, preset roster, …) stays in the caches below —
|
|
314
|
+
* it persists across open/close so a reopened picker paints the previous
|
|
315
|
+
* list while the fresh one loads, exactly as the boolean era did.
|
|
316
|
+
*/
|
|
317
|
+
const [overlay, dispatchOverlay] = React.useReducer(chatOverlayReducer, NO_OVERLAY);
|
|
318
|
+
// `/migrate` picker rows (null = collecting in the background; the picker
|
|
319
|
+
// shows its empty state until the sub-second scan lands).
|
|
320
|
+
const [migrateRows, setMigrateRows] = React.useState(null);
|
|
321
|
+
// Multi-select state (PRD): checked agent ids + the confirmation layer's
|
|
322
|
+
// frozen snapshot of the checked rows.
|
|
323
|
+
const [migrateChecked, setMigrateChecked] = React.useState(new Set());
|
|
324
|
+
const [migratePending, setMigratePending] = React.useState([]);
|
|
325
|
+
// Smart-hint arming: while the migration hint notification is up, a bare
|
|
326
|
+
// Enter (empty prompt, no overlay) jumps straight into the picker with
|
|
327
|
+
// that source pre-checked (PRD #4). Any other key disarms.
|
|
328
|
+
const [migrateHintAgent, setMigrateHintAgent] = React.useState(null);
|
|
329
|
+
// Chat and PromptInput both receive one parsed stdin batch. Keep the
|
|
330
|
+
// permission focus synchronous so arrow+Enter in the same batch uses the
|
|
331
|
+
// post-arrow row rather than the previous render's index.
|
|
332
|
+
const permissionOverlayFocusRef = React.useRef(null);
|
|
333
|
+
React.useEffect(() => {
|
|
334
|
+
if (overlay.kind === 'permission') {
|
|
335
|
+
// Seed each concrete picker instance after commit. Keyboard handlers
|
|
336
|
+
// update this ref synchronously; render must remain side-effect free.
|
|
337
|
+
if (permissionOverlayFocusRef.current?.overlay !== overlay) {
|
|
338
|
+
permissionOverlayFocusRef.current = { overlay, index: overlay.index };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
permissionOverlayFocusRef.current = null;
|
|
343
|
+
}
|
|
344
|
+
}, [overlay]);
|
|
345
|
+
const [models, setModels] = React.useState([]);
|
|
346
|
+
/** Provider display identities for the /model group level; refreshed alongside `models`. */
|
|
347
|
+
const [providerInfos, setProviderInfos] = React.useState([]);
|
|
348
|
+
React.useEffect(() => {
|
|
349
|
+
const subscribe = channel.providerSetup?.()?.oauth?.onCredentialChange;
|
|
350
|
+
if (subscribe === undefined)
|
|
351
|
+
return;
|
|
352
|
+
return subscribe(() => {
|
|
353
|
+
channel.invalidateModelCompletion();
|
|
354
|
+
void channel.listModels().then(setModels).catch(() => setModels([]));
|
|
355
|
+
void channel.listProviders().then(setProviderInfos).catch(() => setProviderInfos([]));
|
|
356
|
+
});
|
|
357
|
+
}, [channel]);
|
|
358
|
+
/** /model 最近使用分组:成功切换即记录(去重置顶,上限 10),重启保留。 */
|
|
359
|
+
const [modelRecents, setModelRecents] = React.useState(() => readModelRecents());
|
|
360
|
+
/** Two-level /model: the drilled-in provider route; undefined = group level.
|
|
361
|
+
* Reset on open; stale ids resolve back to the group level via `activeModelGroup`. */
|
|
362
|
+
const [modelGroup, setModelGroup] = React.useState(undefined);
|
|
363
|
+
/** True while the picker sits in the single-provider fast path (drilled in
|
|
364
|
+
* at open, the group level never shown): Esc closes directly and no back
|
|
365
|
+
* hint renders — a pinned recents pseudo-group must not fake a two-level
|
|
366
|
+
* walk the user never saw (issue #527 regression: repro-picker-windowing). */
|
|
367
|
+
const [modelPickerDirect, setModelPickerDirect] = React.useState(false);
|
|
368
|
+
/** Group rows over the current catalog, first-appearance (registry) order,
|
|
369
|
+
* with the pinned recents pseudo-group first when any entry is catalogued. */
|
|
370
|
+
const modelGroups = React.useMemo(() => deriveModelGroups(models, providerInfos, modelRecents), [models, providerInfos, modelRecents]);
|
|
371
|
+
/** The drilled-in group, but only while it still exists in the catalog. */
|
|
372
|
+
const activeModelGroup = modelGroup !== undefined && modelGroups.some(group => group.provider === modelGroup)
|
|
373
|
+
? modelGroup
|
|
374
|
+
: undefined;
|
|
375
|
+
const groupModels = React.useMemo(() => {
|
|
376
|
+
if (activeModelGroup === undefined)
|
|
377
|
+
return [];
|
|
378
|
+
if (activeModelGroup === RECENTS_GROUP_PROVIDER)
|
|
379
|
+
return recentCatalogModels(modelRecents, models);
|
|
380
|
+
return models.filter(model => model.provider === activeModelGroup);
|
|
381
|
+
}, [models, modelRecents, activeModelGroup]);
|
|
382
|
+
/** Switch + record: every successful switch feeds the /model recents group
|
|
383
|
+
* (picker Enter/click, `/model provider/id`, the wizard's live switch,
|
|
384
|
+
* and /reload's applied model all ride this one path). */
|
|
385
|
+
const switchModelRecorded = (provider, id, name) => {
|
|
386
|
+
if (name !== undefined)
|
|
387
|
+
channel.notify(t('model-switching', { name }));
|
|
388
|
+
return channel.switchModel(provider, id).then((ok) => {
|
|
389
|
+
if (!ok)
|
|
390
|
+
return ok;
|
|
391
|
+
if (name !== undefined)
|
|
392
|
+
channel.notify(t('model-switched', { name }));
|
|
393
|
+
setModelRecents(recordModelUse({ provider, id }));
|
|
394
|
+
return ok;
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
/** `/skills` 技能目录(issue #204):null = 注册表快照在途。 */
|
|
398
|
+
const [skillsList, setSkillsList] = React.useState(null);
|
|
399
|
+
/**
|
|
400
|
+
* The session supervisor — the ONE screen behind `/resume`, `/agentview`,
|
|
401
|
+
* `/home`, `/bg` and the composer's 🏠 button.
|
|
402
|
+
*
|
|
403
|
+
* Those were three screens over one domain (a workspace rail here, a
|
|
404
|
+
* search surface there, a live-status overview somewhere else), which is why
|
|
405
|
+
* each new session feature needed patching into all three and why the
|
|
406
|
+
* three disagreed about what switching a session even does. There is now a
|
|
407
|
+
* single surface and a single runtime behind every entry point: this
|
|
408
|
+
* terminal hosts several sessions, leaving one parks it rather than ending
|
|
409
|
+
* it, and a session another terminal holds is visible but not enterable.
|
|
410
|
+
*
|
|
411
|
+
* Seeded from the host's one-shot landing decision (`openHomeOnBoot`): the
|
|
412
|
+
* first ordinary launch of an installation lands here instead of on a blank
|
|
413
|
+
* conversation, because that is the launch where "which project am I working
|
|
414
|
+
* on" has not been answered yet. Every later launch starts on the chat
|
|
415
|
+
* screen, and the screen stays reachable.
|
|
416
|
+
*/
|
|
417
|
+
const [supervisorOpen, setSupervisorOpen] = React.useState(openHomeOnBoot === true);
|
|
418
|
+
/** `/tree` opens the session family tree (pi's Session Tree): every rewind
|
|
419
|
+
* fork stitched back onto the message it diverged from, hover previews,
|
|
420
|
+
* and per-node rewind/fork/adopt actions. Like the supervisor, a screen. */
|
|
421
|
+
const [treeOpen, setTreeOpen] = React.useState(false);
|
|
422
|
+
/**
|
|
423
|
+
* The session backgrounded when the screen opened via ←/`/bg` (the "Esc
|
|
424
|
+
* returns to that conversation" return target), cleared on close.
|
|
425
|
+
*/
|
|
426
|
+
const [agentViewReturnId, setAgentViewReturnId] = React.useState(undefined);
|
|
427
|
+
/** Live agent-view rows: the prompt footer's "← N agents" hint reads the
|
|
428
|
+
* needs-input count from here (cached snapshot in the channel). The
|
|
429
|
+
* `?.()` fallbacks keep pre-agent-view test stubs (channel facades in
|
|
430
|
+
* scripts/*) rendering — the real channel always provides the seams. */
|
|
431
|
+
const EMPTY_AGENT_VIEW_ROWS = [];
|
|
432
|
+
const agentViewRows = React.useSyncExternalStore(listener => channel.subscribeAgentView?.(listener) ?? (() => { }), () => channel.agentViewRows?.() ?? EMPTY_AGENT_VIEW_ROWS);
|
|
433
|
+
const backgroundAgentsNeedingInput = agentViewRows.filter(row => row.status === 'needs-input' && !row.current).length;
|
|
434
|
+
/** Background the attached session and open the supervisor
|
|
435
|
+
* (`/bg`, `/background`, and ← on an empty prompt all land here). The
|
|
436
|
+
* backgrounded session becomes the screen's return target (final Esc
|
|
437
|
+
* attaches back to it). */
|
|
438
|
+
const backgroundToAgentView = React.useCallback(() => {
|
|
439
|
+
void channel.backgroundCurrent().then((result) => {
|
|
440
|
+
if (result.ok) {
|
|
441
|
+
setAgentViewReturnId(result.backgroundedSessionId);
|
|
442
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
443
|
+
setSupervisorOpen(true);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}, [channel]);
|
|
447
|
+
/** `/settings` opens the plugin settings screen (issue #165) — like the
|
|
448
|
+
* browser, a screen rather than a panel: it owns its own focus, staged
|
|
449
|
+
* drafts and keyboard; Chat only opens it. */
|
|
450
|
+
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
|
451
|
+
const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
|
|
452
|
+
const workspaceFlowRequestRef = React.useRef(0);
|
|
453
|
+
const workspaceFlowAbortRef = React.useRef(null);
|
|
454
|
+
/** `/preset` agent-preset roster (issue #8): loads async, persists. */
|
|
455
|
+
const [presetOptions, setPresetOptions] = React.useState([]);
|
|
456
|
+
/** `/effort` adapter levels: load async before the slider opens. */
|
|
457
|
+
const [effortOptions, setEffortOptions] = React.useState([]);
|
|
458
|
+
const [themeName, setTheme] = useTheme();
|
|
459
|
+
const { rows: terminalRows } = useTerminalSize();
|
|
460
|
+
const [showAllMessages, setShowAllMessages] = React.useState(false);
|
|
461
|
+
/** Scope the fold to this question: an aborted ask can promote its queued
|
|
462
|
+
* successor without ever publishing an idle (null) snapshot. */
|
|
463
|
+
const [minimizedQuestionKey, setMinimizedQuestionKey] = React.useState(null);
|
|
464
|
+
const questionMinimized = questionSnapshot !== null && minimizedQuestionKey === questionSnapshot.key;
|
|
465
|
+
/** Fold state for the GoalTodoPanel todo section (ctrl/cmd+q or click). */
|
|
466
|
+
const [todoCollapsed, setTodoCollapsed] = React.useState(false);
|
|
467
|
+
const [thinkingVisible, setThinkingVisible] = React.useState(true);
|
|
468
|
+
/** ctrl+r history-search entries (loaded on open, persists). */
|
|
469
|
+
const [historyEntries, setHistoryEntries] = React.useState([]);
|
|
470
|
+
const [historyFill, setHistoryFill] = React.useState(null);
|
|
471
|
+
/** Monotonic token: only the latest rewind decision may land (a slow
|
|
472
|
+
* plugin answering after the user moved on must not open a confirm for
|
|
473
|
+
* a row they are no longer looking at). */
|
|
474
|
+
const rewindRequestRef = React.useRef(0);
|
|
475
|
+
/** /btw side-question overlay: pure UI state — the answer never
|
|
476
|
+
* enters the transcript or the session log. */
|
|
477
|
+
const [btw, setBtw] = React.useState(null);
|
|
478
|
+
const btwAbortRef = React.useRef(null);
|
|
479
|
+
const closeBtw = () => {
|
|
480
|
+
btwAbortRef.current?.abort();
|
|
481
|
+
btwAbortRef.current = null;
|
|
482
|
+
setBtw(null);
|
|
483
|
+
};
|
|
484
|
+
/** /recap overlay (pi-recap semantics): pure UI state like /btw — the
|
|
485
|
+
* summary never enters the transcript or session log; applying the
|
|
486
|
+
* proposed title goes through the normal /rename path. `auto` marks the
|
|
487
|
+
* recapOnOpen-triggered run (rendered as the dim AutoRecapRow until
|
|
488
|
+
* expanded); `expanded` lifts an auto recap into the full RecapPanel;
|
|
489
|
+
* `rowsAtTrigger` is the last user-row id when the auto run started —
|
|
490
|
+
* a newer user row (the user starts a new message) retires the recap. */
|
|
491
|
+
const [recap, setRecap] = React.useState(null);
|
|
492
|
+
const recapAbortRef = React.useRef(null);
|
|
493
|
+
const closeRecap = () => {
|
|
494
|
+
recapAbortRef.current?.abort();
|
|
495
|
+
recapAbortRef.current = null;
|
|
496
|
+
setRecap(null);
|
|
497
|
+
};
|
|
498
|
+
/** /balance report (`BalanceReportRow`): pure UI state like /recap — the
|
|
499
|
+
* result never enters the transcript or session log. Clicking the row
|
|
500
|
+
* re-queries (refreshing keeps the stale summary visible); a session
|
|
501
|
+
* switch retires the report. */
|
|
502
|
+
const [balance, setBalance] = React.useState(null);
|
|
503
|
+
const balanceSeqRef = React.useRef(0);
|
|
504
|
+
const runBalance = React.useCallback(() => {
|
|
505
|
+
const seq = ++balanceSeqRef.current;
|
|
506
|
+
setBalance(prev => ({ result: prev?.result ?? null, refreshing: true }));
|
|
507
|
+
void channel.balanceInfo().then(result => {
|
|
508
|
+
if (balanceSeqRef.current !== seq)
|
|
509
|
+
return;
|
|
510
|
+
setBalance({ result, refreshing: false });
|
|
511
|
+
});
|
|
512
|
+
}, [channel]);
|
|
513
|
+
const balanceSessionId = channel.agentId;
|
|
514
|
+
React.useEffect(() => {
|
|
515
|
+
// Retire every in-flight /balance completion from the previous binding;
|
|
516
|
+
// the balance seam has no UI session id in its readonly DTO.
|
|
517
|
+
balanceSeqRef.current += 1;
|
|
518
|
+
setBalance(null);
|
|
519
|
+
}, [balanceSessionId]);
|
|
520
|
+
// A side question belongs to its captured session just like a recap. Chat
|
|
521
|
+
// remains mounted across /resume, so explicitly retire its request/UI when
|
|
522
|
+
// the binding changes instead of allowing a former conversation to finish.
|
|
523
|
+
React.useEffect(() => {
|
|
524
|
+
btwAbortRef.current?.abort();
|
|
525
|
+
btwAbortRef.current = null;
|
|
526
|
+
setBtw(null);
|
|
527
|
+
}, [channel.agentId]);
|
|
528
|
+
// Auto-recap (`dsh-tui.recapOnOpen`): every time the session switches
|
|
529
|
+
// (mount = open/resume, rewind/fork included), summarize its tail into
|
|
530
|
+
// the dim AutoRecapRow. Failures stay silent in auto mode — `/recap`
|
|
531
|
+
// surfaces them; the summary never enters the transcript or session log.
|
|
532
|
+
const autoRecapSessionId = channel.agentId;
|
|
533
|
+
React.useEffect(() => {
|
|
534
|
+
// A session switch retires the previous recap outright — an old
|
|
535
|
+
// session's 回顾 has no place above a new conversation.
|
|
536
|
+
setRecap(null);
|
|
537
|
+
if (!channel.autoRecapOnOpen)
|
|
538
|
+
return;
|
|
539
|
+
// No conversation yet (/new): nothing to recap, don't even fire.
|
|
540
|
+
if (!channel.rows.some(row => row.kind === 'user' || row.kind === 'assistant'))
|
|
541
|
+
return;
|
|
542
|
+
recapAbortRef.current?.abort();
|
|
543
|
+
const controller = new AbortController();
|
|
544
|
+
recapAbortRef.current = controller;
|
|
545
|
+
const lastUserId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
|
|
546
|
+
setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: true, expanded: false, rowsAtTrigger: lastUserId });
|
|
547
|
+
void channel.recapRecent({
|
|
548
|
+
signal: controller.signal,
|
|
549
|
+
onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
|
|
550
|
+
}).then(result => {
|
|
551
|
+
if (controller.signal.aborted)
|
|
552
|
+
return;
|
|
553
|
+
setRecap(prev => {
|
|
554
|
+
if (prev === null || !prev.auto)
|
|
555
|
+
return prev;
|
|
556
|
+
// Auto mode stays quiet on failure (no activity / llm missing / error).
|
|
557
|
+
if (result.summary === null)
|
|
558
|
+
return null;
|
|
559
|
+
return { ...prev, summary: result.summary, title: result.title, error: result.error, done: true };
|
|
560
|
+
});
|
|
561
|
+
}).catch(() => {
|
|
562
|
+
if (!controller.signal.aborted)
|
|
563
|
+
setRecap(null);
|
|
564
|
+
});
|
|
565
|
+
return () => controller.abort();
|
|
566
|
+
}, [autoRecapSessionId]);
|
|
567
|
+
// The user starts a new message → the auto recap has served its purpose
|
|
568
|
+
// (catching them up) and bows out. A newer user row is the signal; the
|
|
569
|
+
// assistant's own streamed rows don't count.
|
|
570
|
+
const lastUserRowId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
|
|
571
|
+
React.useEffect(() => {
|
|
572
|
+
if (recap !== null &&
|
|
573
|
+
recap.auto &&
|
|
574
|
+
recap.rowsAtTrigger !== undefined &&
|
|
575
|
+
lastUserRowId > recap.rowsAtTrigger) {
|
|
576
|
+
closeRecap();
|
|
577
|
+
}
|
|
578
|
+
}, [lastUserRowId, recap]);
|
|
579
|
+
/**
|
|
580
|
+
* Session switches that do not go through `/new` (agent-view attach,
|
|
581
|
+
* backgrounding, `/resume`) remount the transcript tree without resetting
|
|
582
|
+
* view-local state. Row-id-based UI would keep pointing at rows of the
|
|
583
|
+
* PREVIOUS session (ids restart at 0 after an adopt), so the new session
|
|
584
|
+
* renders with stale folds/expansion/selection — the "entered a freshly
|
|
585
|
+
* dispatched session and it renders wrong" bug. Reset the same set `/new`
|
|
586
|
+
* resets, plus the search overlay and the side question, and repaint the
|
|
587
|
+
* transcript from the top.
|
|
588
|
+
*/
|
|
589
|
+
const repaintTranscript = () => {
|
|
590
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
591
|
+
// Wait one task so React commits the new session's tree before the
|
|
592
|
+
// scrollback clear repaints (same pattern as `/new`).
|
|
593
|
+
setTimeout(() => {
|
|
594
|
+
handle?.scrollTo(0);
|
|
595
|
+
ink?.clearScrollbackAndRedraw();
|
|
596
|
+
}, 0);
|
|
597
|
+
};
|
|
598
|
+
const lastAgentIdRef = React.useRef(undefined);
|
|
599
|
+
React.useEffect(() => {
|
|
600
|
+
const id = channel.agentId;
|
|
601
|
+
if (lastAgentIdRef.current === undefined) {
|
|
602
|
+
lastAgentIdRef.current = id;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (lastAgentIdRef.current === id)
|
|
606
|
+
return;
|
|
607
|
+
lastAgentIdRef.current = id;
|
|
608
|
+
setExpanded(false);
|
|
609
|
+
setExpandedRows(new Set());
|
|
610
|
+
setSelectedId(null);
|
|
611
|
+
setSelectionActive(false);
|
|
612
|
+
setShowAllMessages(false);
|
|
613
|
+
setLoadedContextOpen(false);
|
|
614
|
+
setSearchQuery('');
|
|
615
|
+
setSearchCursor(0);
|
|
616
|
+
setSearchCount(0);
|
|
617
|
+
setSearchCurrent(0);
|
|
618
|
+
closeBtw();
|
|
619
|
+
repaintTranscript();
|
|
620
|
+
}, [channel.agentId]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
621
|
+
/** The session attached when the agent view opened; a close on a
|
|
622
|
+
* DIFFERENT session means a switch happened inside the view, and the
|
|
623
|
+
* transcript repaint cannot be skipped. */
|
|
624
|
+
const agentViewOpenSessionRef = React.useRef(undefined);
|
|
625
|
+
/**
|
|
626
|
+
* Leaving a whole screen (agent view, browser) remounts the transcript
|
|
627
|
+
* tree, which would replay the ~3.4s whale opening animation on every
|
|
628
|
+
* close — competing with resumed or streaming rows for frame budget.
|
|
629
|
+
* Suppress the intro on those remounts; `/deepseek` re-enables it.
|
|
630
|
+
*/
|
|
631
|
+
const suppressLogoIntroRef = React.useRef(false);
|
|
632
|
+
/** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
|
|
633
|
+
const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
|
|
634
|
+
const [jobsPanelOpen, setJobsPanelOpen] = React.useState(false);
|
|
635
|
+
// MessageList forwards these open handlers to every memoized row. Their
|
|
636
|
+
// identities must survive token/metrics updates, including for tool rows.
|
|
637
|
+
const openJobsPanel = React.useCallback(() => setJobsPanelOpen(true), []);
|
|
638
|
+
/** Detail view for a specific subagent (opened from dashboard). */
|
|
639
|
+
const [subagentDetailId, setSubagentDetailId] = React.useState(null);
|
|
640
|
+
/**
|
|
641
|
+
* Hidden `/deepseek` easter egg: each invocation bumps this key so the
|
|
642
|
+
* logo header remounts and replays the whale spout + text shimmer.
|
|
643
|
+
*/
|
|
644
|
+
const [logoNonce, setLogoNonce] = React.useState(0);
|
|
645
|
+
React.useEffect(() => () => btwAbortRef.current?.abort(), []);
|
|
646
|
+
React.useEffect(() => () => recapAbortRef.current?.abort(), []);
|
|
647
|
+
/**
|
|
648
|
+
* The trajectory scene (issue #80 evolution). Unlike every other overlay
|
|
649
|
+
* here it is not a panel but a whole screen: while open, Chat renders the
|
|
650
|
+
* scene INSTEAD of the conversation (see the early return below) and hands
|
|
651
|
+
* it the keyboard. Chat itself stays mounted, so scroll position, pickers
|
|
652
|
+
* and in-flight turn state survive the round trip untouched.
|
|
653
|
+
*/
|
|
654
|
+
const [sceneOpen, setSceneOpen] = React.useState(false);
|
|
655
|
+
/**
|
|
656
|
+
* Close the scene.
|
|
657
|
+
*
|
|
658
|
+
* Leaving the alternate screen makes the terminal restore the main buffer;
|
|
659
|
+
* Ink restores the matching saved frame and diffs any conversation changes
|
|
660
|
+
* that happened while the scene was open.
|
|
661
|
+
*/
|
|
662
|
+
const closeScene = React.useCallback(() => {
|
|
663
|
+
setSceneOpen(false);
|
|
664
|
+
}, []);
|
|
665
|
+
/** Open the scene, mark failures seen, and retire the key hint for good. */
|
|
666
|
+
const openScene = React.useCallback(() => {
|
|
667
|
+
seenFailuresRef.current = trajectoryRef.current?.counts.errors ?? 0;
|
|
668
|
+
setTrajectorySeen(previous => {
|
|
669
|
+
if (!previous)
|
|
670
|
+
writeTrajectorySeen();
|
|
671
|
+
return true;
|
|
672
|
+
});
|
|
673
|
+
setSceneOpen(true);
|
|
674
|
+
}, []);
|
|
675
|
+
/**
|
|
676
|
+
* Leave the session supervisor for the conversation.
|
|
677
|
+
*
|
|
678
|
+
* The one-shot landing preference is written here rather than at boot: a
|
|
679
|
+
* process that dies before the user ever sees the screen (a config error, a
|
|
680
|
+
* crash during the first render) must not burn the installation's only
|
|
681
|
+
* first-launch landing. Writing on the way OUT means "the user has seen it".
|
|
682
|
+
*
|
|
683
|
+
* Leaving also honours `/bg`'s return target. `/background` moved the
|
|
684
|
+
* session the user was in to the background and opened this screen; a plain
|
|
685
|
+
* Esc out of it re-attaches to that session instead of silently leaving them
|
|
686
|
+
* on the fresh one, which is what "go back to what I was doing" means. Any
|
|
687
|
+
* explicit mount inside the screen clears the target first, so this can
|
|
688
|
+
* never undo a choice the user just made.
|
|
689
|
+
*/
|
|
690
|
+
const closeHome = React.useCallback(() => {
|
|
691
|
+
suppressLogoIntroRef.current = true;
|
|
692
|
+
markHomeSeen();
|
|
693
|
+
const returnTo = agentViewReturnId;
|
|
694
|
+
setAgentViewReturnId(undefined);
|
|
695
|
+
setSupervisorOpen(false);
|
|
696
|
+
if (returnTo !== undefined && returnTo !== channel.agentId) {
|
|
697
|
+
void channel.resumeTo(returnTo).then((result) => {
|
|
698
|
+
if (result.ok)
|
|
699
|
+
repaintTranscript();
|
|
700
|
+
}).catch(() => undefined);
|
|
701
|
+
}
|
|
702
|
+
}, [agentViewReturnId, channel, repaintTranscript]);
|
|
703
|
+
/** The startup summary gives way to transcript rows after the first local command or message. */
|
|
704
|
+
const loadedContextVisible = channel.rows.length === 0 && channel.loadedContext !== undefined;
|
|
705
|
+
/** Startup context panel: collapsed by default, toggled with Ctrl+P. */
|
|
706
|
+
const [loadedContextOpen, setLoadedContextOpen] = React.useState(false);
|
|
707
|
+
const toggleLoadedContext = React.useCallback(() => {
|
|
708
|
+
setLoadedContextOpen(previous => !previous);
|
|
709
|
+
}, []);
|
|
710
|
+
const renderedLoadedContextOpen = React.useRef(loadedContextOpen);
|
|
711
|
+
React.useLayoutEffect(() => {
|
|
712
|
+
if (renderedLoadedContextOpen.current === loadedContextOpen)
|
|
713
|
+
return;
|
|
714
|
+
renderedLoadedContextOpen.current = loadedContextOpen;
|
|
715
|
+
// Reanchor after the new panel geometry commits. Requesting it in the
|
|
716
|
+
// key handler lets a pending paint consume it on the old tall layout,
|
|
717
|
+
// leaving the collapsed summary stranded outside the physical viewport.
|
|
718
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
719
|
+
ink?.invalidatePrevFrame();
|
|
720
|
+
ink?.reanchorViewport();
|
|
721
|
+
}, [loadedContextOpen]);
|
|
722
|
+
/**
|
|
723
|
+
* Click-to-act targets: the Ink instance's hyperlink-open callback (wired
|
|
724
|
+
* in the effect below) resolves every clickable target the transcript
|
|
725
|
+
* renders — http(s) links open the browser, `dsh-file:`/`file://` paths
|
|
726
|
+
* open the file-action menu. `dsh-file:` payloads are RAW display paths
|
|
727
|
+
* (possibly relative), so they resolve against the CURRENT channel cwd
|
|
728
|
+
* at click time (read through a ref so this callback keeps a stable
|
|
729
|
+
* identity — it is threaded into memoized row components).
|
|
730
|
+
*/
|
|
731
|
+
const cwdRef = React.useRef(channel.cwd);
|
|
732
|
+
React.useEffect(() => {
|
|
733
|
+
cwdRef.current = channel.cwd;
|
|
734
|
+
}, [channel.cwd]);
|
|
735
|
+
const openFileActions = React.useCallback((rawPath) => {
|
|
736
|
+
const resolved = resolveTargetPath(rawPath, cwdRef.current);
|
|
737
|
+
// Whether the target is a directory decides the first menu row's label
|
|
738
|
+
// ("open file" vs "open folder"). Missing paths count as files.
|
|
739
|
+
let isDir = false;
|
|
740
|
+
try {
|
|
741
|
+
isDir = statSync(resolved).isDirectory();
|
|
742
|
+
}
|
|
743
|
+
catch {
|
|
744
|
+
isDir = false;
|
|
745
|
+
}
|
|
746
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'file-actions', path: resolved, index: 0, isDir } });
|
|
747
|
+
}, []);
|
|
748
|
+
/** Run one file-action menu row: 0 = open file, 1 = reveal in file
|
|
749
|
+
* manager, 2 = copy absolute path. */
|
|
750
|
+
const runFileAction = React.useCallback((index, path) => {
|
|
751
|
+
if (index === 0)
|
|
752
|
+
openFile(path);
|
|
753
|
+
else if (index === 1)
|
|
754
|
+
revealInFileManager(path);
|
|
755
|
+
else
|
|
756
|
+
void setClipboard(path);
|
|
757
|
+
}, []);
|
|
758
|
+
/** Shared open path for the modal image preview: composer `[Image #N]`
|
|
759
|
+
* tokens and transcript thumbnails both land here. */
|
|
760
|
+
const openImagePreview = React.useCallback((image, title) => {
|
|
761
|
+
// Snapshot only metadata/facades on an explicit open, not on every streamed
|
|
762
|
+
// token. Unvisited attachments stay lazy and duplicate image occurrences stay distinct.
|
|
763
|
+
const gallery = channel.rows.flatMap(row => (row.images ?? []).map(image => ({ image })));
|
|
764
|
+
let index = gallery.findIndex(entry => entry.image === image);
|
|
765
|
+
if (index < 0) {
|
|
766
|
+
index = gallery.length;
|
|
767
|
+
gallery.push({ image, title });
|
|
768
|
+
}
|
|
769
|
+
dispatchOverlay({
|
|
770
|
+
type: 'open',
|
|
771
|
+
overlay: { kind: 'image-preview', image, gallery, index, ...(title === undefined ? {} : { title }) },
|
|
772
|
+
});
|
|
773
|
+
}, [channel]);
|
|
774
|
+
// Agent-binding generation is monotonic across every agent replacement
|
|
775
|
+
// and bumps before the replacement emit, closing the ABA hole where a
|
|
776
|
+
// resumed session reuses the same id. Partial test/embed channels fall
|
|
777
|
+
// back to staged-image generation.
|
|
778
|
+
const previewBindingGeneration = channel.agentBindingGeneration
|
|
779
|
+
?? channel.stagedImageGeneration?.()
|
|
780
|
+
?? 0;
|
|
781
|
+
const previewGenerationRef = React.useRef(previewBindingGeneration);
|
|
782
|
+
const imagePreviewOwned = previewGenerationRef.current === previewBindingGeneration;
|
|
783
|
+
React.useEffect(() => {
|
|
784
|
+
if (previewGenerationRef.current === previewBindingGeneration)
|
|
785
|
+
return;
|
|
786
|
+
previewGenerationRef.current = previewBindingGeneration;
|
|
787
|
+
dispatchOverlay({ type: 'close-if', kind: 'image-preview' });
|
|
788
|
+
}, [previewBindingGeneration]);
|
|
789
|
+
// A questionnaire/approval/plugin dialog owns the keyboard while pending
|
|
790
|
+
// (their guard runs BEFORE the overlay key chain), so a preview left open
|
|
791
|
+
// underneath would be visually on top yet key-dead. Close it instead.
|
|
792
|
+
const previewBlocked = questionSnapshot !== null || approvalSnapshot !== null || dialogSnapshot !== null;
|
|
793
|
+
React.useEffect(() => {
|
|
794
|
+
if (overlay.kind === 'image-preview' &&
|
|
795
|
+
previewBlocked) {
|
|
796
|
+
dispatchOverlay({ type: 'close-if', kind: 'image-preview' });
|
|
797
|
+
}
|
|
798
|
+
}, [overlay.kind, previewBlocked]);
|
|
799
|
+
// Caret-driven preview (Grok Build's chip peek): while the composer caret
|
|
800
|
+
// sits on a staged `[Image #N]` — at its start, the token inverted — the
|
|
801
|
+
// same card shows over the transcript, and it goes away when the caret
|
|
802
|
+
// leaves (the cell just after the token is not "on" it). It is
|
|
803
|
+
// derived state, not an overlay: the prompt keeps the keyboard, so ←/→
|
|
804
|
+
// walk from image to image with the card following. Esc (or a click
|
|
805
|
+
// outside the card) dismisses it for THIS token until the caret leaves and
|
|
806
|
+
// comes back; a click on the token always shows it again.
|
|
807
|
+
const [caretPreview, setCaretPreview] = React.useState(null);
|
|
808
|
+
const [peekSuppressed, setPeekSuppressed] = React.useState(null);
|
|
809
|
+
const handleCaretImage = React.useCallback((image, title, reason) => {
|
|
810
|
+
if (image === undefined) {
|
|
811
|
+
setCaretPreview(null);
|
|
812
|
+
setPeekSuppressed(null);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
setCaretPreview({ image, ...(title === undefined ? {} : { title }) });
|
|
816
|
+
const key = peekKey(image, title);
|
|
817
|
+
setPeekSuppressed(current => reason === 'click' || current !== key ? null : current);
|
|
818
|
+
}, []);
|
|
819
|
+
const peekPreview = !previewBlocked && overlay.kind === 'none' && caretPreview !== null
|
|
820
|
+
&& peekSuppressed !== peekKey(caretPreview.image, caretPreview.title)
|
|
821
|
+
? caretPreview
|
|
822
|
+
: null;
|
|
823
|
+
/** Esc / click-outside on the peek: dismissed for this token until the
|
|
824
|
+
* caret leaves it. PromptInput's Esc arm calls this first — its listener
|
|
825
|
+
* runs before Chat's and the prompt stays live under a peek. */
|
|
826
|
+
const dismissPeek = () => {
|
|
827
|
+
if (peekPreview !== null)
|
|
828
|
+
setPeekSuppressed(peekKey(peekPreview.image, peekPreview.title));
|
|
829
|
+
};
|
|
830
|
+
/** The card on screen, if any: the modal overlay first, else the peek. */
|
|
831
|
+
const activePreview = !previewBlocked && overlay.kind === 'image-preview'
|
|
832
|
+
? { image: overlay.image, ...(overlay.title === undefined ? {} : { title: overlay.title }), peek: false }
|
|
833
|
+
: peekPreview !== null
|
|
834
|
+
? { ...peekPreview, peek: true }
|
|
835
|
+
: null;
|
|
836
|
+
const handleOpenTarget = React.useCallback((url) => {
|
|
837
|
+
const classification = classifyOpenTarget(url);
|
|
838
|
+
if (classification.kind === 'file-actions') {
|
|
839
|
+
openFileActions(classification.path);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (classification.kind === 'external') {
|
|
843
|
+
openExternal(url);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
// Non-http(s) schemes from model/plugin-shaped links are not handed to
|
|
847
|
+
// the OS handler — see urlGuard.ts. Silently ignored: a toast needs
|
|
848
|
+
// channel state the Ink click path does not carry.
|
|
849
|
+
}, [openFileActions]);
|
|
850
|
+
// Wire the click-to-open callback into the Ink instance (the field is
|
|
851
|
+
// otherwise never set — clicking links was a no-op). Re-wired whenever
|
|
852
|
+
// the handler changes (cwd moves), cleared on unmount.
|
|
853
|
+
React.useEffect(() => {
|
|
854
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
855
|
+
if (ink)
|
|
856
|
+
ink.onHyperlinkClick = handleOpenTarget;
|
|
857
|
+
return () => {
|
|
858
|
+
const current = instances.get(process.stdout) ?? instances.values().next().value;
|
|
859
|
+
if (current)
|
|
860
|
+
current.onHyperlinkClick = undefined;
|
|
861
|
+
};
|
|
862
|
+
}, [handleOpenTarget]);
|
|
863
|
+
/** `/` transcript search (less-style incsearch).
|
|
864
|
+
* Only the bar's open/closed mode lives in `overlay`; the query and match
|
|
865
|
+
* counters persist past the bar closing so n/N keep walking the matches. */
|
|
866
|
+
const searchActive = overlay.kind === 'search';
|
|
867
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
868
|
+
const [searchCursor, setSearchCursor] = React.useState(0);
|
|
869
|
+
const [searchCount, setSearchCount] = React.useState(0);
|
|
870
|
+
const [searchCurrent, setSearchCurrent] = React.useState(0);
|
|
871
|
+
const searchAnchorRef = React.useRef(0);
|
|
872
|
+
const rowRefsRef = React.useRef(new Map());
|
|
873
|
+
const { setQuery: setHighlight } = useSearchHighlight();
|
|
874
|
+
// Sticky (pinned-to-bottom) scroll state, subscribed imperatively so
|
|
875
|
+
// wheel events don't re-render React — only the header/pill flip.
|
|
876
|
+
// Deliberately KEPT on useSyncExternalStore despite the SyncLane wakeup
|
|
877
|
+
// cost: the renderer's at-bottom re-pin flips sticky WITHOUT firing the
|
|
878
|
+
// scroll subscribers (see ScrollBox's subscribe doc), so only uSES's
|
|
879
|
+
// every-render getSnapshot check picks that flip up — a pure
|
|
880
|
+
// notification-driven subscription misses it and the new-message pill
|
|
881
|
+
// stops reflecting reality (repro-pill). Wheel cadence is an
|
|
882
|
+
// interaction-rate source (not streaming-rate), the streaming-side
|
|
883
|
+
// #185 sources are all Default-lane now, and the overflow guard
|
|
884
|
+
// backstops the residue.
|
|
885
|
+
const isSticky = React.useSyncExternalStore(cb => (handle ? handle.subscribe(cb) : () => { }), () => (handle ? handle.isSticky() : true));
|
|
886
|
+
// Whale idle gate: the settled header scrolls away with the transcript,
|
|
887
|
+
// and the idle planner is worth nothing the moment its art leaves the
|
|
888
|
+
// viewport — pause it there (timers cleared, the resting pose's cached
|
|
889
|
+
// rows stay painted so scroll geometry never shifts) and re-arm a fresh
|
|
890
|
+
// cycle when the user scrolls back to the top. Same uSES rationale as
|
|
891
|
+
// isSticky above: the renderer's sticky re-pin doesn't fire scroll
|
|
892
|
+
// subscribers, only the every-render snapshot check picks it up.
|
|
893
|
+
const WHALE_ART_CUTOFF_ROWS = 16; // marginTop + the 13-row whale art
|
|
894
|
+
const whaleArtVisible = React.useSyncExternalStore(cb => (handle ? handle.subscribe(cb) : () => { }), () => {
|
|
895
|
+
if (!handle)
|
|
896
|
+
return true;
|
|
897
|
+
// A transcript that fits the viewport always shows the header.
|
|
898
|
+
if (handle.getScrollHeight() <= handle.getViewportHeight())
|
|
899
|
+
return true;
|
|
900
|
+
// Visible while the art block intersects the viewport. A sticky bottom
|
|
901
|
+
// pin with an overflow smaller than the art's height still leaves the
|
|
902
|
+
// art on screen — visibility, not pin state, decides whether the idle
|
|
903
|
+
// planner earns its keep.
|
|
904
|
+
return handle.getScrollTop() < WHALE_ART_CUTOFF_ROWS;
|
|
905
|
+
});
|
|
906
|
+
const subscribeTooltipInvalidation = React.useCallback((listener) => (handle ? handle.subscribe(listener) : () => { }), [handle]);
|
|
907
|
+
// "N new messages" pill: new rows whose top edge is still BELOW the
|
|
908
|
+
// viewport bottom. The count decrements as the user scrolls down through
|
|
909
|
+
// them and hits 0 (pill hides) once every new row has been on screen —
|
|
910
|
+
// no need to wait for the exact-bottom sticky restore. Chat anchors the
|
|
911
|
+
// "seen up to" point by ROW ID (stable across loadOlder prepends, unlike
|
|
912
|
+
// a rows.length index); MessageList owns the row offsets, so it computes
|
|
913
|
+
// how many rows past that anchor lie below the viewport and reports it.
|
|
914
|
+
const lastSeenRowIdRef = React.useRef(null);
|
|
915
|
+
const [unseenCount, setUnseenCount] = React.useState(0);
|
|
916
|
+
React.useEffect(() => {
|
|
917
|
+
if (isSticky) {
|
|
918
|
+
lastSeenRowIdRef.current = null;
|
|
919
|
+
setUnseenCount(0);
|
|
920
|
+
}
|
|
921
|
+
else if (lastSeenRowIdRef.current === null) {
|
|
922
|
+
lastSeenRowIdRef.current = channel.rows.length
|
|
923
|
+
? channel.rows[channel.rows.length - 1].id
|
|
924
|
+
: -1;
|
|
925
|
+
}
|
|
926
|
+
}, [isSticky, channel.rows]);
|
|
927
|
+
// The pill shows whenever the view is off the bottom (one-click return
|
|
928
|
+
// home): with unseen rows it counts them, otherwise it is the plain
|
|
929
|
+
// "return to bottom" affordance (Enter/End/click all land it).
|
|
930
|
+
const showPill = !isSticky;
|
|
931
|
+
// Idle Ctrl+C: first press arms an exit, second press exits. Under
|
|
932
|
+
// Windows ConPTY the key
|
|
933
|
+
// arrives as stdin data (key.ctrl && input === 'c') — the useInput
|
|
934
|
+
// branch below is the only path; SIGINT is not emitted.
|
|
935
|
+
const exitPendingRef = React.useRef(false);
|
|
936
|
+
const exitTimerRef = React.useRef(null);
|
|
937
|
+
// Live view into the prompt's text for the Ctrl+C rule (clears text when
|
|
938
|
+
// non-empty; the double-press exit only arms on an empty input).
|
|
939
|
+
const ownPromptControllerRef = React.useRef(null);
|
|
940
|
+
const promptControllerRef = promptControllerRefProp ?? ownPromptControllerRef;
|
|
941
|
+
/**
|
|
942
|
+
* Owner of the unsent draft. Every screen this component renders INSTEAD of
|
|
943
|
+
* the conversation (the session screen, the tree, settings, the jobs and
|
|
944
|
+
* subagent panels, the trajectory scene) unmounts the composer, and the
|
|
945
|
+
* composer keeps its text in local state — so without this the half-written
|
|
946
|
+
* prompt died on the way in. The slot lives here, outlives that unmount, and
|
|
947
|
+
* is dropped the moment the attached session changes so no draft can follow
|
|
948
|
+
* the user into a different conversation.
|
|
949
|
+
*/
|
|
950
|
+
const promptDraftRef = React.useRef({ current: null });
|
|
951
|
+
/**
|
|
952
|
+
* Latest channel for the unmount release below: that effect must not re-run
|
|
953
|
+
* on a channel identity change, yet its cleanup must release against the
|
|
954
|
+
* channel of the last render.
|
|
955
|
+
*/
|
|
956
|
+
const channelRef = React.useRef(channel);
|
|
957
|
+
channelRef.current = channel;
|
|
958
|
+
/**
|
|
959
|
+
* Release the staged images a WAITING snapshot alone owns.
|
|
960
|
+
*
|
|
961
|
+
* While a draft waits in the slot for the composer to remount, the snapshot
|
|
962
|
+
* is the only owner of the capabilities behind its `[Image #N]` tokens. If
|
|
963
|
+
* Chat itself goes away first (leaving an early-return screen by exiting the
|
|
964
|
+
* TUI), nothing would ever restore or discard them — the session's
|
|
965
|
+
* 128-entry FIFO would evict live entries instead. The `hasStagedImage`
|
|
966
|
+
* guard keeps a capability the channel already recycled a no-op; both calls
|
|
967
|
+
* are idempotent.
|
|
968
|
+
*
|
|
969
|
+
* Scope, deliberately narrow (review round 7): a capability a QUEUED message
|
|
970
|
+
* still references (`channel.pending`) is never revoked here. The real
|
|
971
|
+
* double-hold path is paste an image → queue the draft with Tab while the
|
|
972
|
+
* model works → recall that line from input history with ↑ (same stageId
|
|
973
|
+
* rebound to the draft) → park the composer. Delivery resolves its refs from
|
|
974
|
+
* the enqueue-time capture, so a late revoke would only bite a host that
|
|
975
|
+
* re-resolves them afterwards — this keeps the rule identical to
|
|
976
|
+
* `stageIdIsRetained` instead of relying on that.
|
|
977
|
+
*
|
|
978
|
+
* A composer that is still MOUNTED when Chat unmounts is NOT covered: React
|
|
979
|
+
* runs this parent cleanup BEFORE the child's, so the child then writes its
|
|
980
|
+
* draft into the now-dead ref and those ids ride the channel's lifetime out
|
|
981
|
+
* (the #942 review's remaining P2). Neither unmount path loses anything
|
|
982
|
+
* user-visible — the channel dies with them.
|
|
983
|
+
*/
|
|
984
|
+
React.useEffect(() => {
|
|
985
|
+
return () => {
|
|
986
|
+
const snapshot = promptDraftRef.current.current;
|
|
987
|
+
promptDraftRef.current.current = null;
|
|
988
|
+
if (snapshot === null)
|
|
989
|
+
return;
|
|
990
|
+
const queued = new Set();
|
|
991
|
+
for (const item of channelRef.current.pending) {
|
|
992
|
+
// `?? []`: a foreign/embedded host may hand us a pending entry without
|
|
993
|
+
// images, and a throw inside an unmount cleanup escapes into the exit
|
|
994
|
+
// path — every other reader of this field guards it the same way.
|
|
995
|
+
for (const image of item.images ?? [])
|
|
996
|
+
queued.add(image.stageId);
|
|
997
|
+
}
|
|
998
|
+
for (const [, stageId] of snapshot.images) {
|
|
999
|
+
if (queued.has(stageId))
|
|
1000
|
+
continue;
|
|
1001
|
+
if (channelRef.current.hasStagedImage?.(stageId) === true) {
|
|
1002
|
+
channelRef.current.discardStagedImage(stageId);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}, []);
|
|
1007
|
+
const draftSessionId = channel.agentId;
|
|
1008
|
+
/** Session the effect below last reconciled against; a change is a switch. */
|
|
1009
|
+
const draftSessionRef = React.useRef(draftSessionId);
|
|
1010
|
+
/**
|
|
1011
|
+
* The session a fill Chat itself requested belongs to, if one is in flight.
|
|
1012
|
+
*
|
|
1013
|
+
* A rewind's restored message arrives in the same commit that replaces the
|
|
1014
|
+
* session, and it belongs to the NEW binding — the user picked it. The
|
|
1015
|
+
* composer cannot tell, so Chat says so here, at the two call sites that ask
|
|
1016
|
+
* for a fill.
|
|
1017
|
+
*
|
|
1018
|
+
* Keyed by the session id rather than a bare flag, so it can only ever excuse
|
|
1019
|
+
* the switch it was written for. Do NOT clear it when the composer consumes
|
|
1020
|
+
* the fill: a child's layout effects run before the parent's, so the fill is
|
|
1021
|
+
* consumed in the very commit this effect judges, and clearing it there would
|
|
1022
|
+
* wipe the message the user just got back.
|
|
1023
|
+
*/
|
|
1024
|
+
const pendingFillRef = React.useRef(null);
|
|
1025
|
+
/**
|
|
1026
|
+
* Drop the composer's text when the session underneath it is replaced.
|
|
1027
|
+
*
|
|
1028
|
+
* A LAYOUT effect, not a passive one: the clear has to land in the commit
|
|
1029
|
+
* that swaps the session. A passive effect is flushed later, and anything
|
|
1030
|
+
* typed in between (the tree's hand-off, a fast user) would be wiped with the
|
|
1031
|
+
* old conversation's text. Which DRAFT the slot keeps is a separate question,
|
|
1032
|
+
* answered by the snapshot's owner fields.
|
|
1033
|
+
*/
|
|
1034
|
+
React.useLayoutEffect(() => {
|
|
1035
|
+
if (draftSessionRef.current === draftSessionId)
|
|
1036
|
+
return;
|
|
1037
|
+
draftSessionRef.current = draftSessionId;
|
|
1038
|
+
// A stored draft can only belong to the conversation being replaced: the
|
|
1039
|
+
// composer is the one that writes it, and it writes it on the way out.
|
|
1040
|
+
promptDraftRef.current.current = null;
|
|
1041
|
+
if (pendingFillRef.current === draftSessionId) {
|
|
1042
|
+
pendingFillRef.current = null;
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
promptControllerRef.current?.clear();
|
|
1046
|
+
}, [draftSessionId]);
|
|
1047
|
+
const previewGallery = activePreview === null ? [] : activePreview.peek
|
|
1048
|
+
? promptControllerRef.current?.previewImages?.() ?? [activePreview]
|
|
1049
|
+
: overlay.kind === 'image-preview' ? overlay.gallery ?? [activePreview] : [];
|
|
1050
|
+
// Peek entries are rebuilt from the prompt every render: match by
|
|
1051
|
+
// attachment id + token title, not facade identity.
|
|
1052
|
+
const previewIndex = activePreview?.peek
|
|
1053
|
+
? previewGallery.findIndex(entry => entry.image.id === activePreview.image.id && entry.title === activePreview.title)
|
|
1054
|
+
: overlay.kind === 'image-preview' ? overlay.index ?? 0 : -1;
|
|
1055
|
+
const stepPreview = (delta) => {
|
|
1056
|
+
if (!activePreview?.peek) {
|
|
1057
|
+
dispatchOverlay({ type: 'image-step', delta });
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const index = previewIndex + delta;
|
|
1061
|
+
const entry = previewGallery[index];
|
|
1062
|
+
if (!entry)
|
|
1063
|
+
return;
|
|
1064
|
+
// A gallery click promotes the caret peek to a modal without moving or
|
|
1065
|
+
// editing the draft. Suppress the original peek so Esc really closes it.
|
|
1066
|
+
setPeekSuppressed(peekKey(activePreview.image, activePreview.title));
|
|
1067
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'image-preview', ...entry, gallery: previewGallery, index } });
|
|
1068
|
+
};
|
|
1069
|
+
// Publish the external-injection controller (dsh.nvim etc.) every render so
|
|
1070
|
+
// the adapter-owned socket can append to the prompt and submit. `submit`
|
|
1071
|
+
// mirrors an Enter press: `channel.submit` routes through the DSH inbox
|
|
1072
|
+
// (queued after the current turn while working), then the input is cleared.
|
|
1073
|
+
React.useEffect(() => {
|
|
1074
|
+
if (!injectControllerRef)
|
|
1075
|
+
return;
|
|
1076
|
+
injectControllerRef.current = {
|
|
1077
|
+
append: (text) => {
|
|
1078
|
+
promptControllerRef.current?.append(text);
|
|
1079
|
+
},
|
|
1080
|
+
submit: () => {
|
|
1081
|
+
const controller = promptControllerRef.current;
|
|
1082
|
+
if (!controller)
|
|
1083
|
+
return;
|
|
1084
|
+
const text = controller.append('').trim();
|
|
1085
|
+
if (text === '')
|
|
1086
|
+
return;
|
|
1087
|
+
channel.submit(text);
|
|
1088
|
+
controller.clear();
|
|
1089
|
+
channel.notify(channel.working ? t('input-sent-after-turn') : t('input-injected'), { timeoutMs: 2500 });
|
|
1090
|
+
},
|
|
1091
|
+
};
|
|
1092
|
+
return () => {
|
|
1093
|
+
injectControllerRef.current = null;
|
|
1094
|
+
};
|
|
1095
|
+
});
|
|
1096
|
+
const requestExit = () => {
|
|
1097
|
+
if (exitPendingRef.current) {
|
|
1098
|
+
onExit();
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
exitPendingRef.current = true;
|
|
1102
|
+
channel.notify(t('exit-press-again'));
|
|
1103
|
+
exitTimerRef.current = setTimeout(() => {
|
|
1104
|
+
exitPendingRef.current = false;
|
|
1105
|
+
}, 3000);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
React.useEffect(() => {
|
|
1109
|
+
return () => {
|
|
1110
|
+
if (exitTimerRef.current)
|
|
1111
|
+
clearTimeout(exitTimerRef.current);
|
|
1112
|
+
};
|
|
1113
|
+
}, []);
|
|
1114
|
+
// Spinner timing refs, fed from channel state each render (the spinner
|
|
1115
|
+
// only mounts while working, so values are stable for the mount).
|
|
1116
|
+
const responseLengthRef = React.useRef(0);
|
|
1117
|
+
const uploadTokensRef = React.useRef(0);
|
|
1118
|
+
const loadingStartTimeRef = React.useRef(0);
|
|
1119
|
+
const totalPausedMsRef = React.useRef(0);
|
|
1120
|
+
const pauseStartTimeRef = React.useRef(null);
|
|
1121
|
+
responseLengthRef.current = channel.responseChars;
|
|
1122
|
+
// Most recent request's real upload (input + cache read/write occupy the
|
|
1123
|
+
// wire exactly like the context window); 0 until the first usage event.
|
|
1124
|
+
const lastUploadTokens = channel.lastUsage === undefined
|
|
1125
|
+
? 0
|
|
1126
|
+
: channel.lastUsage.input + channel.lastUsage.cacheRead + channel.lastUsage.cacheWrite;
|
|
1127
|
+
uploadTokensRef.current = lastUploadTokens;
|
|
1128
|
+
loadingStartTimeRef.current = channel.turnStart;
|
|
1129
|
+
const thinkingStatus = useThinkingStatus(channel.spinnerMode === 'thinking');
|
|
1130
|
+
// Terminal tab title: the session
|
|
1131
|
+
// title when set, else "dsh-TUI"; a `⠂/⠐` spinner prefix while a turn is
|
|
1132
|
+
// working (960ms cadence, only while the terminal is focused), a static
|
|
1133
|
+
// `✦` otherwise. dsh-TUI brands the idle prefix with the DeepSeek whale.
|
|
1134
|
+
const [titleFrame, setTitleFrame] = React.useState(0);
|
|
1135
|
+
const terminalFocused = useTerminalFocus();
|
|
1136
|
+
// Mouse text selection auto-copy: active only in
|
|
1137
|
+
// fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
|
|
1138
|
+
// subscription in inline mode, where selection belongs to the terminal.
|
|
1139
|
+
// The copy clears the highlight and posts a transient notification.
|
|
1140
|
+
// Smart migration hint (product ask): ~12s after mount, one background
|
|
1141
|
+
// pass over the foreign-agent stores; when a source was active inside the
|
|
1142
|
+
// 20-minute window, surface the user's own wording once per session. The
|
|
1143
|
+
// file-level mtime scan is the counter's walk shape (sub-second) and runs
|
|
1144
|
+
// off the render path; failures read as "no data" and stay silent.
|
|
1145
|
+
const migrateHintShownRef = React.useRef(false);
|
|
1146
|
+
React.useEffect(() => {
|
|
1147
|
+
if (migrateHintShownRef.current)
|
|
1148
|
+
return;
|
|
1149
|
+
const timer = setTimeout(() => {
|
|
1150
|
+
migrateHintShownRef.current = true;
|
|
1151
|
+
void (async () => {
|
|
1152
|
+
const newest = await new Promise(resolve => {
|
|
1153
|
+
setImmediate(() => resolve(collectActivitySamples(MIGRATION_ADAPTERS, adapter => MIGRATE_SCAN_SPECS[adapter.id])));
|
|
1154
|
+
});
|
|
1155
|
+
const top = recentAgentsFrom(newest, Date.now())[0];
|
|
1156
|
+
if (top !== undefined) {
|
|
1157
|
+
channel.notify(t('migrate-hint-notify', { agent: top.label }), { timeoutMs: 10000 });
|
|
1158
|
+
// PRD #4: while the hint is up, a bare Enter (empty prompt, no
|
|
1159
|
+
// overlay) jumps into the picker with this source pre-checked;
|
|
1160
|
+
// the global key layer below consumes it, anything else disarms.
|
|
1161
|
+
setMigrateHintAgent(top.agentId);
|
|
1162
|
+
setTimeout(() => setMigrateHintAgent(current => current === top.agentId ? null : current), 10_000);
|
|
1163
|
+
}
|
|
1164
|
+
})();
|
|
1165
|
+
}, 12_000);
|
|
1166
|
+
return () => clearTimeout(timer);
|
|
1167
|
+
}, [channel]);
|
|
1168
|
+
useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }),
|
|
1169
|
+
// Stale-selection refusal: the highlighted rows were replaced in place
|
|
1170
|
+
// (streaming overwrite), so nothing was copied — say why instead of
|
|
1171
|
+
// letting the highlight vanish silently.
|
|
1172
|
+
() => channel.notify(t('copy-refused-stale'), { timeoutMs: 2500 }));
|
|
1173
|
+
const { clearSelection: clearMouseSelection, hasSelection: hasMouseSelection } = useSelection();
|
|
1174
|
+
React.useEffect(() => {
|
|
1175
|
+
if (!channel.working || !terminalFocused)
|
|
1176
|
+
return;
|
|
1177
|
+
const interval = setInterval(() => {
|
|
1178
|
+
setTitleFrame(f => (f + 1) % TITLE_SPINNER_FRAMES.length);
|
|
1179
|
+
}, 960);
|
|
1180
|
+
return () => { clearInterval(interval); };
|
|
1181
|
+
}, [channel.working, terminalFocused]);
|
|
1182
|
+
const titlePrefix = channel.working
|
|
1183
|
+
? (TITLE_SPINNER_FRAMES[titleFrame] ?? '✦')
|
|
1184
|
+
: '✦';
|
|
1185
|
+
useTerminalTitle(`${titlePrefix} 🐋 ${channel.sessionTitle}`);
|
|
1186
|
+
const handleWorkspaceResult = (result) => {
|
|
1187
|
+
workspaceFlowAbortRef.current = null;
|
|
1188
|
+
if (result.kind === 'target') {
|
|
1189
|
+
dispatchOverlay({ type: 'close-if', kind: 'workspace-flow' });
|
|
1190
|
+
void channel.switchWorkspace(result.target);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
if (result.choices.length === 0) {
|
|
1194
|
+
dispatchOverlay({ type: 'close-if', kind: 'workspace-flow' });
|
|
1195
|
+
channel.notify(t('workspace-command-empty'));
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
// open-if: 'workspace-flow' stays allowed so an in-flow action can
|
|
1199
|
+
// transition to its next stage; a picker the user opened after leaving
|
|
1200
|
+
// the menu wins over a late command result.
|
|
1201
|
+
dispatchOverlay({
|
|
1202
|
+
type: 'open-if',
|
|
1203
|
+
overlay: { kind: 'workspace-flow', flow: result, index: 0, busy: false, input: null },
|
|
1204
|
+
when: ['none', 'workspace-flow'],
|
|
1205
|
+
});
|
|
1206
|
+
};
|
|
1207
|
+
const runWorkspaceFlowAction = (action) => {
|
|
1208
|
+
const request = ++workspaceFlowRequestRef.current;
|
|
1209
|
+
const controller = new AbortController();
|
|
1210
|
+
workspaceFlowAbortRef.current = controller;
|
|
1211
|
+
dispatchOverlay({ type: 'flow-busy', busy: true });
|
|
1212
|
+
void Promise.resolve()
|
|
1213
|
+
.then(() => action(controller.signal))
|
|
1214
|
+
.then((result) => {
|
|
1215
|
+
if (request === workspaceFlowRequestRef.current)
|
|
1216
|
+
handleWorkspaceResult(result);
|
|
1217
|
+
})
|
|
1218
|
+
.catch((error) => {
|
|
1219
|
+
if (request !== workspaceFlowRequestRef.current)
|
|
1220
|
+
return;
|
|
1221
|
+
workspaceFlowAbortRef.current = null;
|
|
1222
|
+
dispatchOverlay({ type: 'flow-busy', busy: false });
|
|
1223
|
+
channel.notify(t('workspace-command-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
1224
|
+
});
|
|
1225
|
+
};
|
|
1226
|
+
/** Bare `/workspace` menu rows: built-in subcommands first, then the
|
|
1227
|
+
* dynamically registered extensions (same reserved-name filter the Tab
|
|
1228
|
+
* completion applies). Recomputed per render — the extension list is
|
|
1229
|
+
* live. */
|
|
1230
|
+
const workspaceMenuOptions = [
|
|
1231
|
+
{ id: 'resume', label: 'resume', description: t('workspace-menu-resume-desc') },
|
|
1232
|
+
{ id: 'rename', label: 'rename', description: t('workspace-menu-rename-desc') },
|
|
1233
|
+
{ id: 'open', label: 'open', description: t('workspace-menu-open-desc') },
|
|
1234
|
+
// Optional call: verify/repro scripts and embedders stub the channel
|
|
1235
|
+
// without the workspace-commands API — render must not throw for them
|
|
1236
|
+
// (a thrown render unmounts the whole Ink root).
|
|
1237
|
+
...(channel.workspaceCommands?.() ?? [])
|
|
1238
|
+
.filter(command => !['resume', 'rename', 'open'].includes(command.name.toLowerCase()))
|
|
1239
|
+
.map(command => ({ id: command.name, label: command.name, description: command.description })),
|
|
1240
|
+
];
|
|
1241
|
+
const openWorkspaceTarget = (reference) => {
|
|
1242
|
+
void channel.resolveWorkspace(reference).then((target) => {
|
|
1243
|
+
if (target === undefined) {
|
|
1244
|
+
channel.notify(t('workspace-uri-invalid', { uri: reference }), { color: 'error', timeoutMs: 8000 });
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
void channel.switchWorkspace(target);
|
|
1248
|
+
}).catch((error) => {
|
|
1249
|
+
channel.notify(t('workspace-uri-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
1250
|
+
});
|
|
1251
|
+
};
|
|
1252
|
+
const openWorkspaceResume = () => {
|
|
1253
|
+
void channel.listWorkspaces().then((targets) => {
|
|
1254
|
+
if (targets.length === 0) {
|
|
1255
|
+
channel.notify(t('workspace-none'));
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
setWorkspaceTargets(targets);
|
|
1259
|
+
// open-if: the listing is async — whatever the user opened meanwhile wins.
|
|
1260
|
+
dispatchOverlay({
|
|
1261
|
+
type: 'open-if',
|
|
1262
|
+
overlay: {
|
|
1263
|
+
kind: 'workspace-picker',
|
|
1264
|
+
index: Math.max(0, targets.findIndex(target => target.cwd === channel.cwd)),
|
|
1265
|
+
},
|
|
1266
|
+
when: ['none'],
|
|
1267
|
+
});
|
|
1268
|
+
}).catch((error) => {
|
|
1269
|
+
channel.notify(t('workspace-list-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error' });
|
|
1270
|
+
});
|
|
1271
|
+
};
|
|
1272
|
+
/**
|
|
1273
|
+
* Run one /workspace menu row (Enter path, shared with the mouse click):
|
|
1274
|
+
* built-ins dispatch locally, extension commands go through the channel.
|
|
1275
|
+
*/
|
|
1276
|
+
const runWorkspaceMenuOption = (option) => {
|
|
1277
|
+
dispatchOverlay({ type: 'close-if', kind: 'workspace-menu' });
|
|
1278
|
+
if (option === undefined)
|
|
1279
|
+
return;
|
|
1280
|
+
if (option.id === 'resume') {
|
|
1281
|
+
openWorkspaceResume();
|
|
1282
|
+
}
|
|
1283
|
+
else if (option.id === 'rename') {
|
|
1284
|
+
channel.notify(t('workspace-rename-usage'));
|
|
1285
|
+
}
|
|
1286
|
+
else if (option.id === 'open') {
|
|
1287
|
+
channel.notify(t('workspace-open-usage'));
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
void channel.runWorkspaceCommand(option.id, '').then((result) => {
|
|
1291
|
+
if (result !== undefined)
|
|
1292
|
+
handleWorkspaceResult(result);
|
|
1293
|
+
}).catch((error) => {
|
|
1294
|
+
channel.notify(t('workspace-command-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
/**
|
|
1299
|
+
* Dispatch a slash command; false lets the input flow to the model.
|
|
1300
|
+
* Built-in names run the local switch; anything registered by a DSH
|
|
1301
|
+
* plugin (plan/goal/…) dispatches through the command registry, whose
|
|
1302
|
+
* result text lands as a notification. `rawInput` carries the text after
|
|
1303
|
+
* the command name (`/plan off` → ` off`).
|
|
1304
|
+
*/
|
|
1305
|
+
const runExternalCommand = (name, rawInput, images = []) => {
|
|
1306
|
+
const originAgentBinding = channel.agentBindingGeneration;
|
|
1307
|
+
return channel.runExternalCommandOutcome(name, rawInput, images).then((outcome) => {
|
|
1308
|
+
if (channel.agentBindingGeneration !== originAgentBinding)
|
|
1309
|
+
return false;
|
|
1310
|
+
if (outcome === undefined) {
|
|
1311
|
+
channel.notify(t('command-not-found', { name }), { color: 'error' });
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
const cleaned = cleanRenderText(outcome.text, COMMAND_RESULT_CELLS);
|
|
1315
|
+
if (cleaned !== '') {
|
|
1316
|
+
channel.notify(cleaned, outcome.kind === 'error' ? { color: 'error' } : undefined);
|
|
1317
|
+
}
|
|
1318
|
+
return outcome.consumeDraft;
|
|
1319
|
+
}).catch((error) => {
|
|
1320
|
+
if (channel.agentBindingGeneration !== originAgentBinding)
|
|
1321
|
+
return false;
|
|
1322
|
+
const detail = cleanCommandError(error);
|
|
1323
|
+
if (detail !== '')
|
|
1324
|
+
channel.notify(detail, { color: 'error' });
|
|
1325
|
+
return false;
|
|
1326
|
+
});
|
|
1327
|
+
};
|
|
1328
|
+
/** Route every permission switch through the official command path when it
|
|
1329
|
+
* is registered; otherwise fall back to the permission-presets service's
|
|
1330
|
+
* own write path (the same handler the command drives) so the picker and
|
|
1331
|
+
* typed `/permission <preset>` keep working on compositions where the
|
|
1332
|
+
* command row never reaches this agent's registry. The fallback carries no
|
|
1333
|
+
* images: it is not a registry command and has no image grammar. */
|
|
1334
|
+
const runPermissionCommand = (rawInput, images = []) => {
|
|
1335
|
+
const originAgentBinding = channel.agentBindingGeneration;
|
|
1336
|
+
const mounted = channel.commandList.some(command => command.external && command.name === 'permission');
|
|
1337
|
+
const run = mounted
|
|
1338
|
+
? channel.runExternalCommandOutcome('permission', rawInput, images)
|
|
1339
|
+
: channel.runPermissionPreset(rawInput.trim()).then(ok => ok ? { kind: 'success', text: '', consumeDraft: true } : undefined);
|
|
1340
|
+
return run.then((outcome) => {
|
|
1341
|
+
if (channel.agentBindingGeneration !== originAgentBinding)
|
|
1342
|
+
return false;
|
|
1343
|
+
if (outcome === undefined) {
|
|
1344
|
+
channel.notify(t('command-not-found', { name: 'permission' }), { color: 'error' });
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
const cleaned = cleanRenderText(outcome.text, COMMAND_RESULT_CELLS);
|
|
1348
|
+
if (cleaned !== '') {
|
|
1349
|
+
channel.notify(cleaned, outcome.kind === 'error' ? { color: 'error' } : undefined);
|
|
1350
|
+
}
|
|
1351
|
+
return outcome.consumeDraft;
|
|
1352
|
+
}).catch((error) => {
|
|
1353
|
+
if (channel.agentBindingGeneration !== originAgentBinding)
|
|
1354
|
+
return false;
|
|
1355
|
+
const detail = cleanCommandError(error);
|
|
1356
|
+
if (detail !== '')
|
|
1357
|
+
channel.notify(detail, { color: 'error' });
|
|
1358
|
+
return false;
|
|
1359
|
+
});
|
|
1360
|
+
};
|
|
1361
|
+
/** Hot-swap the UI language (`/lang <id>` and the LangPicker both land
|
|
1362
|
+
* here): persist to ~/.dsh-tui/lang.json and mirror into the dsh-tui
|
|
1363
|
+
* settings namespace when it is served (best effort). */
|
|
1364
|
+
const applyLang = (lang) => {
|
|
1365
|
+
const ok = writeLangPref(lang);
|
|
1366
|
+
setLang(lang);
|
|
1367
|
+
const settingsHost = channel.settingsHost();
|
|
1368
|
+
const tuiView = settingsHost?.listNamespaces().find(entry => entry.ns === 'dsh-tui');
|
|
1369
|
+
if (settingsHost !== undefined && tuiView !== undefined) {
|
|
1370
|
+
void settingsHost
|
|
1371
|
+
.write('dsh-tui', [{ op: 'set', path: ['lang'], value: lang }], tuiView.revision)
|
|
1372
|
+
.catch(() => { });
|
|
1373
|
+
}
|
|
1374
|
+
channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
|
|
1375
|
+
};
|
|
1376
|
+
/** Localized label of one /reload surface, for the change report. */
|
|
1377
|
+
const reloadKindLabel = (kind) => {
|
|
1378
|
+
switch (kind) {
|
|
1379
|
+
case 'theme': return t('reload-kind-theme');
|
|
1380
|
+
case 'lang': return t('reload-kind-lang');
|
|
1381
|
+
case 'preset': return t('reload-kind-preset');
|
|
1382
|
+
case 'model': return t('reload-kind-model');
|
|
1383
|
+
case 'activity': return t('reload-kind-activity');
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
/** Collect picker rows off the current turn: the scan is synchronous FS
|
|
1387
|
+
* work (name-only walk + per-file stat), so it is deferred by one macrotask
|
|
1388
|
+
* to let the overlay paint its loading state first. */
|
|
1389
|
+
const collectMigrateRows = () => new Promise(resolve => {
|
|
1390
|
+
setImmediate(() => resolve(collectMigratePickerRows(Date.now())));
|
|
1391
|
+
});
|
|
1392
|
+
/** Run `dsh-tui migrate <args>` in a child process through the package
|
|
1393
|
+
* bin; resolves with the exit code and the combined output. Uses the
|
|
1394
|
+
* shared no-throw runner (bounded capture, timeout, windowsHide): a wedged
|
|
1395
|
+
* child would otherwise hang the sequential per-source loop forever. */
|
|
1396
|
+
const runMigrateChild = async (parts) => {
|
|
1397
|
+
const { dirname } = await import('node:path');
|
|
1398
|
+
const { fileURLToPath } = await import('node:url');
|
|
1399
|
+
const { resolveOwnBin } = await import('../dsh-adapter/migrate/bin-path.js');
|
|
1400
|
+
// This file sits at a different depth per layout (src/screens vs
|
|
1401
|
+
// lib/types/screens), so the bin resolves by upward probe — see
|
|
1402
|
+
// bin-path.ts; a fixed dirname count fails on real installs.
|
|
1403
|
+
const bin = resolveOwnBin(dirname(fileURLToPath(import.meta.url)));
|
|
1404
|
+
if (bin === undefined) {
|
|
1405
|
+
channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
|
|
1406
|
+
return { code: -1, out: '' };
|
|
1407
|
+
}
|
|
1408
|
+
const result = await execFileNoThrow(process.execPath, [bin, 'migrate', ...parts], {
|
|
1409
|
+
timeout: MIGRATE_CHILD_TIMEOUT_MS,
|
|
1410
|
+
});
|
|
1411
|
+
// A killed child reports `code: null` and whatever it managed to print; put
|
|
1412
|
+
// the reason on the record so the transcript does not read as a silent
|
|
1413
|
+
// failure. Nothing at all (no code, no output) means it never really ran.
|
|
1414
|
+
if (result.code === null) {
|
|
1415
|
+
return {
|
|
1416
|
+
code: null,
|
|
1417
|
+
out: `${result.stdout}${result.stderr}${t('migrate-child-timeout', { minutes: MIGRATE_CHILD_TIMEOUT_MS / 60_000 })}\n`,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
if (result.code === 1 && result.stdout === '' && result.stderr === '') {
|
|
1421
|
+
channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
|
|
1422
|
+
}
|
|
1423
|
+
// stdout carries the per-source report, stderr the usage/error lines;
|
|
1424
|
+
// both belong in the /migrate transcript row.
|
|
1425
|
+
return { code: result.code, out: `${result.stdout}${result.stderr}` };
|
|
1426
|
+
};
|
|
1427
|
+
/** Orchestrate the confirmation layer's confirmed rows (PRD #3): one child
|
|
1428
|
+
* per source, sequential; per-source progress notifications (throttled by
|
|
1429
|
+
* the source boundary — no intra-source spam), real per-source counters
|
|
1430
|
+
* parsed from each child's report, and a final summary that NEVER claims
|
|
1431
|
+
* success for a source that did not run (the P2 fix). */
|
|
1432
|
+
const spawnMigrateSources = (rows, dryRun) => {
|
|
1433
|
+
const allOut = [];
|
|
1434
|
+
let failures = 0;
|
|
1435
|
+
void (async () => {
|
|
1436
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1437
|
+
const row = rows[i];
|
|
1438
|
+
channel.notify(t(dryRun ? 'migrate-previewing-source' : 'migrate-importing-source', { label: row.label, i: i + 1, n: rows.length }), { timeoutMs: 4000 });
|
|
1439
|
+
const { code, out } = await runMigrateChild(dryRun ? [row.agentId, '--dry-run'] : [row.agentId]);
|
|
1440
|
+
allOut.push(...out.split('\n').map(line => cleanRenderText(line, 400)).filter(Boolean));
|
|
1441
|
+
if (code !== 0)
|
|
1442
|
+
failures += 1;
|
|
1443
|
+
// Per-source real counters straight from the child's report line.
|
|
1444
|
+
const summary = parseImportSummary(out).find(entry => entry.agentId === row.agentId);
|
|
1445
|
+
if (!dryRun && summary !== undefined) {
|
|
1446
|
+
channel.notify(t('migrate-source-done', { label: row.label, imported: summary.imported, existing: summary.existing }), { timeoutMs: 6000 });
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
// The transcript row is this run's record. When a source failed AND no
|
|
1450
|
+
// child output was captured at all (killed by the timeout, or never
|
|
1451
|
+
// spawned), the success wording would contradict the notification right
|
|
1452
|
+
// above it — report the failure here too.
|
|
1453
|
+
const fallbackLine = failures > 0
|
|
1454
|
+
? t('migrate-failed', { n: failures })
|
|
1455
|
+
: t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length });
|
|
1456
|
+
channel.pushLocal('/migrate', allOut.length > 0 ? allOut : [fallbackLine]);
|
|
1457
|
+
channel.notify(failures === 0
|
|
1458
|
+
? t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length })
|
|
1459
|
+
: t('migrate-failed', { n: failures }), failures === 0 ? { timeoutMs: 6000 } : { color: 'error', timeoutMs: 10000 });
|
|
1460
|
+
})();
|
|
1461
|
+
};
|
|
1462
|
+
const runCommand = (name, rawInput = '', images = []) => {
|
|
1463
|
+
switch (name) {
|
|
1464
|
+
case 'activity': {
|
|
1465
|
+
// Ported from the pi working-activity extension: bare `/activity`
|
|
1466
|
+
// opens the interactive indicator picker; `/activity frames <name>`
|
|
1467
|
+
// switches directly; `/activity frames` lists presets; `/activity
|
|
1468
|
+
// status` shows the current choice. The choice persists to
|
|
1469
|
+
// ~/.dsh-tui/working-activity.json and survives restarts.
|
|
1470
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1471
|
+
if (parts[0] === 'status') {
|
|
1472
|
+
setHelpOpen(false);
|
|
1473
|
+
channel.pushLocal('/activity', [
|
|
1474
|
+
t('activity-current-preset', { name: channel.activityFrames ?? 'moon8' }),
|
|
1475
|
+
t('activity-switch-hint'),
|
|
1476
|
+
t('activity-persist-hint'),
|
|
1477
|
+
]);
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
if (parts[0] === 'frames') {
|
|
1481
|
+
setHelpOpen(false);
|
|
1482
|
+
if (parts[1]) {
|
|
1483
|
+
channel.setActivityFrames(parts[1].toLowerCase());
|
|
1484
|
+
return true;
|
|
1485
|
+
}
|
|
1486
|
+
const current = channel.activityFrames;
|
|
1487
|
+
channel.pushLocal('/activity', [
|
|
1488
|
+
t('activity-current-direct', { name: current ?? 'moon8' }),
|
|
1489
|
+
...PRESET_NAMES.map(name => `${name.padEnd(10)} ${name === 'random' ? t('activity-random-each') : FRAME_PRESETS[name].frames.slice(0, 5).join(' ')}${name === current ? t('activity-current-marker') : ''}`),
|
|
1490
|
+
]);
|
|
1491
|
+
return true;
|
|
1492
|
+
}
|
|
1493
|
+
if (parts.length > 0) {
|
|
1494
|
+
channel.notify(t('activity-usage'), { color: 'warning' });
|
|
1495
|
+
return true;
|
|
1496
|
+
}
|
|
1497
|
+
setHelpOpen(false);
|
|
1498
|
+
dispatchOverlay({
|
|
1499
|
+
type: 'open',
|
|
1500
|
+
overlay: {
|
|
1501
|
+
kind: 'activity',
|
|
1502
|
+
index: Math.max(0, PRESET_NAMES.indexOf(channel.activityFrames ?? 'random')),
|
|
1503
|
+
},
|
|
1504
|
+
});
|
|
1505
|
+
return true;
|
|
1506
|
+
}
|
|
1507
|
+
case 'preset': {
|
|
1508
|
+
// issue #8: bare `/preset` opens the roster picker (standard/ptc/
|
|
1509
|
+
// minimal/cordis plus any user-authored presets); `/preset <id>`
|
|
1510
|
+
// switches directly; `/preset status` shows the current choice. A
|
|
1511
|
+
// blank session swaps composition in place (official blank-only
|
|
1512
|
+
// rule); a started session is locked and the choice persists as the
|
|
1513
|
+
// default for future sessions (~/.dsh-tui/agent-preset.json).
|
|
1514
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1515
|
+
if (parts[0] === 'status') {
|
|
1516
|
+
setHelpOpen(false);
|
|
1517
|
+
channel.pushLocal('/preset', [
|
|
1518
|
+
t('preset-current', { name: channel.agentPreset ?? t('preset-roster-missing') }),
|
|
1519
|
+
t('preset-switch-hint'),
|
|
1520
|
+
t('preset-persist-hint'),
|
|
1521
|
+
t('preset-lock-hint'),
|
|
1522
|
+
]);
|
|
1523
|
+
return true;
|
|
1524
|
+
}
|
|
1525
|
+
if (parts.length > 0) {
|
|
1526
|
+
setHelpOpen(false);
|
|
1527
|
+
void channel.switchPreset(parts[0]);
|
|
1528
|
+
return true;
|
|
1529
|
+
}
|
|
1530
|
+
setHelpOpen(false);
|
|
1531
|
+
// The picker opens immediately over the cached roster (no loading
|
|
1532
|
+
// pane — deliberate contrast with /model); the fresh list lands with
|
|
1533
|
+
// the authoritative focus. Both loader writes are kind-guarded, so a
|
|
1534
|
+
// picker the user already left is not resurrected or re-focused.
|
|
1535
|
+
dispatchOverlay({
|
|
1536
|
+
type: 'open',
|
|
1537
|
+
overlay: {
|
|
1538
|
+
kind: 'preset',
|
|
1539
|
+
index: Math.max(0, presetOptions.findIndex(preset => preset.id === channel.agentPreset)),
|
|
1540
|
+
},
|
|
1541
|
+
});
|
|
1542
|
+
void channel.listPresets().then((list) => {
|
|
1543
|
+
if (list.length === 0) {
|
|
1544
|
+
dispatchOverlay({ type: 'close-if', kind: 'preset' });
|
|
1545
|
+
channel.notify(t('preset-roster-unmounted'), { color: 'warning' });
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
setPresetOptions(list);
|
|
1549
|
+
const index = list.findIndex(preset => preset.id === channel.agentPreset);
|
|
1550
|
+
dispatchOverlay({ type: 'set-index', kind: 'preset', index: index >= 0 ? index : 0 });
|
|
1551
|
+
});
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
case 'effort': {
|
|
1555
|
+
// Bare `/effort` opens the rheostat slider over the live route's
|
|
1556
|
+
// adapter levels (←/→ applies each step immediately); `/effort <id>`
|
|
1557
|
+
// sets directly (validated by the channel); `/effort status` prints
|
|
1558
|
+
// the current level. The choice persists to ~/.dsh-tui/effort.json.
|
|
1559
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1560
|
+
if (parts[0] === 'status') {
|
|
1561
|
+
setHelpOpen(false);
|
|
1562
|
+
channel.pushLocal('/effort', [
|
|
1563
|
+
t('effort-current', { name: channel.reasoningEffort ?? '—' }),
|
|
1564
|
+
t('effort-usage'),
|
|
1565
|
+
]);
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
if (parts.length > 0) {
|
|
1569
|
+
setHelpOpen(false);
|
|
1570
|
+
void channel.setEffort(parts[0]);
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
setHelpOpen(false);
|
|
1574
|
+
void channel.listEfforts().then(({ efforts, defaultEffort }) => {
|
|
1575
|
+
// 0/1-tier routes were already notified by listEfforts.
|
|
1576
|
+
if (efforts.length <= 1)
|
|
1577
|
+
return;
|
|
1578
|
+
setEffortOptions(efforts);
|
|
1579
|
+
const current = channel.reasoningEffort ?? defaultEffort;
|
|
1580
|
+
const index = efforts.findIndex(effort => effort.id === current);
|
|
1581
|
+
// open-if: a picker the user opened during the round trip wins
|
|
1582
|
+
// over this late-arriving slider.
|
|
1583
|
+
dispatchOverlay({
|
|
1584
|
+
type: 'open-if',
|
|
1585
|
+
overlay: { kind: 'effort', index: index >= 0 ? index : 0 },
|
|
1586
|
+
when: ['none'],
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1589
|
+
return true;
|
|
1590
|
+
}
|
|
1591
|
+
case 'lang': {
|
|
1592
|
+
// `/lang` shows the current UI language, `/lang en|zh` switches
|
|
1593
|
+
// (hot-swap, persisted to ~/.dsh-tui/lang.json), bare `/lang` opens
|
|
1594
|
+
// the en/zh picker. Precedence on next launch: DSH_TUI_LANG >
|
|
1595
|
+
// settings.yaml `dsh-tui.lang` > cordis.yml `lang` > the persisted
|
|
1596
|
+
// choice.
|
|
1597
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1598
|
+
if (parts[0] === 'status') {
|
|
1599
|
+
setHelpOpen(false);
|
|
1600
|
+
channel.pushLocal('/lang', [
|
|
1601
|
+
t('lang-current', { lang: getLang() }),
|
|
1602
|
+
t('lang-switch-hint'),
|
|
1603
|
+
t('lang-persist-hint'),
|
|
1604
|
+
]);
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
if (parts.length > 0) {
|
|
1608
|
+
setHelpOpen(false);
|
|
1609
|
+
if (isLang(parts[0])) {
|
|
1610
|
+
applyLang(parts[0]);
|
|
1611
|
+
}
|
|
1612
|
+
else {
|
|
1613
|
+
channel.notify(t('lang-unknown', { lang: parts[0] }), { color: 'error' });
|
|
1614
|
+
}
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
setHelpOpen(false);
|
|
1618
|
+
dispatchOverlay({
|
|
1619
|
+
type: 'open',
|
|
1620
|
+
overlay: { kind: 'lang', index: getLang() === 'zh' ? 0 : 1 },
|
|
1621
|
+
});
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
case 'theme': {
|
|
1625
|
+
// Bare `/theme` opens the interactive color picker (`auto` + built-in
|
|
1626
|
+
// palettes + static/runtime themes); `/theme <name>`
|
|
1627
|
+
// switches directly; `/theme status` shows the current choice.
|
|
1628
|
+
// `auto` follows the terminal background (OSC 11). Selection
|
|
1629
|
+
// persists to ~/.dsh-tui/theme.json and hot swaps via the
|
|
1630
|
+
// ThemeProvider setter (DSH_TUI_THEME still wins on next launch).
|
|
1631
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1632
|
+
if (parts[0] === 'status') {
|
|
1633
|
+
setHelpOpen(false);
|
|
1634
|
+
channel.pushLocal('/theme', [
|
|
1635
|
+
t('theme-current', { name: themeName }),
|
|
1636
|
+
// `auto` resolves through terminal-background detection; show
|
|
1637
|
+
// which palette it currently maps to.
|
|
1638
|
+
...(themeName === AUTO_THEME_NAME
|
|
1639
|
+
? [t('theme-auto-resolved', { name: getAutoThemeBase() })]
|
|
1640
|
+
: []),
|
|
1641
|
+
t('theme-switch-hint'),
|
|
1642
|
+
t('theme-persist-hint'),
|
|
1643
|
+
t('theme-custom-hint'),
|
|
1644
|
+
]);
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
if (parts.length > 0) {
|
|
1648
|
+
setHelpOpen(false);
|
|
1649
|
+
// setTheme rejects unknown names via isThemeAvailable, so pass the
|
|
1650
|
+
// raw argument instead of resolving it against the catalog first.
|
|
1651
|
+
const ok = setTheme(parts[0]);
|
|
1652
|
+
channel.notify(ok ? t('theme-switched-saved', { name: parts[0] }) : t('theme-unknown', { name: parts[0] }), { color: ok ? 'success' : 'error' });
|
|
1653
|
+
return true;
|
|
1654
|
+
}
|
|
1655
|
+
setHelpOpen(false);
|
|
1656
|
+
dispatchOverlay({
|
|
1657
|
+
type: 'open',
|
|
1658
|
+
overlay: {
|
|
1659
|
+
kind: 'theme',
|
|
1660
|
+
index: Math.max(0, getThemeOptions(themeHost).findIndex(option => option.value === themeName)),
|
|
1661
|
+
},
|
|
1662
|
+
});
|
|
1663
|
+
return true;
|
|
1664
|
+
}
|
|
1665
|
+
case 'color': {
|
|
1666
|
+
// `/color`(按会话持久化的 accent):无参打开调色板选择器,
|
|
1667
|
+
// `/color <name>` 直接设置,`/color status` 显示当前,`/color
|
|
1668
|
+
// reset` 清除回主题默认。颜色经 `session/color` 事件按会话保存
|
|
1669
|
+
// ——resume/rewind 后仍是这个会话自己的颜色(见 channel.ts)。
|
|
1670
|
+
setHelpOpen(false);
|
|
1671
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1672
|
+
if (parts.length === 0) {
|
|
1673
|
+
dispatchOverlay({
|
|
1674
|
+
type: 'open',
|
|
1675
|
+
overlay: {
|
|
1676
|
+
kind: 'color',
|
|
1677
|
+
index: Math.max(0, SESSION_COLOR_NAMES.indexOf(channel.sessionColor)),
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
return true;
|
|
1681
|
+
}
|
|
1682
|
+
if (parts[0] === 'status') {
|
|
1683
|
+
channel.pushLocal('/color', [
|
|
1684
|
+
channel.sessionColor === ''
|
|
1685
|
+
? t('color-current-none')
|
|
1686
|
+
: t('color-current', { name: channel.sessionColor }),
|
|
1687
|
+
t('color-usage', { list: SESSION_COLOR_NAMES.join('/') }),
|
|
1688
|
+
]);
|
|
1689
|
+
return true;
|
|
1690
|
+
}
|
|
1691
|
+
if (parts[0] === 'reset') {
|
|
1692
|
+
channel.setSessionColor('');
|
|
1693
|
+
channel.notify(t('color-reset'));
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1696
|
+
const colorName = parts[0].toLowerCase();
|
|
1697
|
+
if (!isValidSessionColor(colorName)) {
|
|
1698
|
+
channel.notify(t('color-unknown', { name: colorName, list: SESSION_COLOR_NAMES.join(' · ') }), { color: 'error' });
|
|
1699
|
+
return true;
|
|
1700
|
+
}
|
|
1701
|
+
channel.setSessionColor(colorName);
|
|
1702
|
+
channel.notify(t('color-set', { name: colorName }), { color: 'success' });
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
case 'new': {
|
|
1706
|
+
// One-shot `/new` (issue #25): the old session stays persisted and
|
|
1707
|
+
// is recoverable via /resume, so discarding the live view is
|
|
1708
|
+
// non-destructive — no second confirmation is required.
|
|
1709
|
+
setHelpOpen(false);
|
|
1710
|
+
void channel.newSession().then((ok) => {
|
|
1711
|
+
if (!ok)
|
|
1712
|
+
return;
|
|
1713
|
+
// A new session is a fresh terminal page, not merely an emptied
|
|
1714
|
+
// transcript. Reset view-local state, return the ScrollBox to the
|
|
1715
|
+
// top, then clear native scrollback and repaint the whale homepage.
|
|
1716
|
+
setExpanded(false);
|
|
1717
|
+
setExpandedRows(new Set());
|
|
1718
|
+
setStreamViewToggledRows(new Set());
|
|
1719
|
+
setSelectedId(null);
|
|
1720
|
+
setSelectionActive(false);
|
|
1721
|
+
setShowAllMessages(false);
|
|
1722
|
+
setLoadedContextOpen(false);
|
|
1723
|
+
handle?.scrollTo(0);
|
|
1724
|
+
channel.notify(t('new-session-started'));
|
|
1725
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
1726
|
+
// Wait one task so React commits the empty transcript/homepage tree;
|
|
1727
|
+
// clearing before that would immediately repaint the old session.
|
|
1728
|
+
setTimeout(() => {
|
|
1729
|
+
handle?.scrollTo(0);
|
|
1730
|
+
ink?.clearScrollbackAndRedraw();
|
|
1731
|
+
}, 0);
|
|
1732
|
+
});
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
case 'clear':
|
|
1736
|
+
channel.clear();
|
|
1737
|
+
// channel.clear() resets row ids to 0; stale expanded/selection
|
|
1738
|
+
// state would mis-highlight fresh rows (known-limitation fix).
|
|
1739
|
+
setExpandedRows(new Set());
|
|
1740
|
+
setStreamViewToggledRows(new Set());
|
|
1741
|
+
setSelectedId(null);
|
|
1742
|
+
setSelectionActive(false);
|
|
1743
|
+
return true;
|
|
1744
|
+
case 'compact':
|
|
1745
|
+
channel.compact();
|
|
1746
|
+
return true;
|
|
1747
|
+
case 'trace':
|
|
1748
|
+
// `/trace` is kept as the discoverable spelling of Ctrl+T: the
|
|
1749
|
+
// command menu is where a user finds out the trajectory exists.
|
|
1750
|
+
setHelpOpen(false);
|
|
1751
|
+
openScene();
|
|
1752
|
+
return true;
|
|
1753
|
+
case 'context': {
|
|
1754
|
+
setHelpOpen(false);
|
|
1755
|
+
const context = channel.loadedContext;
|
|
1756
|
+
if (context === undefined) {
|
|
1757
|
+
channel.notify(t('context-unavailable'), { color: 'warning' });
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
channel.pushLocal('/context', formatLoadedContextReport(context));
|
|
1761
|
+
return true;
|
|
1762
|
+
}
|
|
1763
|
+
case 'help':
|
|
1764
|
+
setHelpOpen(true);
|
|
1765
|
+
return true;
|
|
1766
|
+
case 'model': {
|
|
1767
|
+
// `/model <provider/model>` switches directly (same live-fork path
|
|
1768
|
+
// as the picker's Enter), bare `/model` opens the picker.
|
|
1769
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1770
|
+
if (parts.length > 0) {
|
|
1771
|
+
setHelpOpen(false);
|
|
1772
|
+
const spec = parts[0];
|
|
1773
|
+
const slash = spec.indexOf('/');
|
|
1774
|
+
const provider = slash >= 0 ? spec.slice(0, slash) : undefined;
|
|
1775
|
+
const id = slash >= 0 ? spec.slice(slash + 1) : spec;
|
|
1776
|
+
if (provider === undefined || id.length === 0 || provider.length === 0) {
|
|
1777
|
+
channel.notify(t('model-usage'), { color: 'warning' });
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
void channel.listModels().then((list) => {
|
|
1781
|
+
const model = list.find(m => m.provider === provider && m.id === id);
|
|
1782
|
+
if (model === undefined) {
|
|
1783
|
+
channel.notify(t('model-unknown', { spec }), { color: 'error', timeoutMs: 8000 });
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
void switchModelRecorded(provider, id, model.name);
|
|
1787
|
+
});
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
setHelpOpen(false);
|
|
1791
|
+
// Opens over the cached catalog (empty cache shows the loading
|
|
1792
|
+
// pane); the fresh list lands with the authoritative focus, and the
|
|
1793
|
+
// kind-guarded set-index cannot re-focus a picker the user left.
|
|
1794
|
+
// Seed-on-open: the model in use IS a use — recording it here means
|
|
1795
|
+
// the recents group exists before the first post-update switch, and
|
|
1796
|
+
// switching A→B keeps A in the list (the file records what was
|
|
1797
|
+
// used, not only switches made after the file appeared).
|
|
1798
|
+
let recentsNow = modelRecents;
|
|
1799
|
+
if (channel.provider !== '' && channel.model !== ''
|
|
1800
|
+
&& !recentsNow.some(ref => ref.provider === channel.provider && ref.id === channel.model)) {
|
|
1801
|
+
recentsNow = recordModelUse({ provider: channel.provider, id: channel.model });
|
|
1802
|
+
setModelRecents(recentsNow);
|
|
1803
|
+
}
|
|
1804
|
+
// Two-level landing: recents (when catalogued) focus their pinned
|
|
1805
|
+
// row; else multi-provider catalogs focus the current provider's
|
|
1806
|
+
// group row; a single-provider catalog without a meaningful recents
|
|
1807
|
+
// list drills straight into its model list (pre-grouping UX).
|
|
1808
|
+
{
|
|
1809
|
+
const landing = modelPickerLanding(models, channel.provider, channel.model, recentsNow);
|
|
1810
|
+
setModelGroup(landing.group);
|
|
1811
|
+
setModelPickerDirect(landing.group !== undefined);
|
|
1812
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'model', index: landing.index } });
|
|
1813
|
+
}
|
|
1814
|
+
void channel.listModels().then((list) => {
|
|
1815
|
+
setModels(list);
|
|
1816
|
+
const landing = modelPickerLanding(list, channel.provider, channel.model, recentsNow);
|
|
1817
|
+
setModelGroup(landing.group);
|
|
1818
|
+
setModelPickerDirect(landing.group !== undefined);
|
|
1819
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
1820
|
+
});
|
|
1821
|
+
void channel.listProviders().then(setProviderInfos).catch(() => setProviderInfos([]));
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
case 'skills': {
|
|
1825
|
+
// issue #204: 列出当前 agent 的完整技能目录(名称 + 来源 + 简述),
|
|
1826
|
+
// Enter 把可直调技能以 `/name ` 填回输入行(completion-only 分发的
|
|
1827
|
+
// 同一路径)。注册表读取走 channel(快照 scoped 到 live agent)。
|
|
1828
|
+
// `/skills <name>` 直达同一个填回动作,跳过选择器。
|
|
1829
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
1830
|
+
if (parts.length > 0) {
|
|
1831
|
+
setHelpOpen(false);
|
|
1832
|
+
void channel.listSkills().then((list) => {
|
|
1833
|
+
if (list === undefined) {
|
|
1834
|
+
channel.notify(t('skills-load-failed'), { color: 'error' });
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const skill = list.find(s => s.name === parts[0]);
|
|
1838
|
+
if (skill === undefined) {
|
|
1839
|
+
channel.notify(t('skills-unknown', { name: parts[0] }), { color: 'error' });
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (skill.userInvocable)
|
|
1843
|
+
setHistoryFill(`/${skill.name} `);
|
|
1844
|
+
else
|
|
1845
|
+
channel.notify(t('skills-not-invocable', { name: parts[0] }), { color: 'warning' });
|
|
1846
|
+
});
|
|
1847
|
+
return true;
|
|
1848
|
+
}
|
|
1849
|
+
setHelpOpen(false);
|
|
1850
|
+
setSkillsList(null);
|
|
1851
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'skills', index: 0 } });
|
|
1852
|
+
void channel.listSkills().then((list) => {
|
|
1853
|
+
if (list === undefined) {
|
|
1854
|
+
dispatchOverlay({ type: 'close-if', kind: 'skills' });
|
|
1855
|
+
channel.notify(t('skills-load-failed'), { color: 'error' });
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
setSkillsList(list);
|
|
1859
|
+
});
|
|
1860
|
+
return true;
|
|
1861
|
+
}
|
|
1862
|
+
case 'provider': {
|
|
1863
|
+
// Interactive add-provider wizard (/provider): drives the shared
|
|
1864
|
+
// question panel, persists profile + key via the channel's settings/
|
|
1865
|
+
// credentials seams. No picker state — AskUserQuestionPanel renders it.
|
|
1866
|
+
setHelpOpen(false);
|
|
1867
|
+
const host = channel.providerSetup();
|
|
1868
|
+
if (!host) {
|
|
1869
|
+
channel.notify(t('provider-unavailable'), { color: 'warning', timeoutMs: 8000 });
|
|
1870
|
+
return true;
|
|
1871
|
+
}
|
|
1872
|
+
void runProviderWizard({
|
|
1873
|
+
host,
|
|
1874
|
+
ask: (request, options) => questionStore.ask(request, options),
|
|
1875
|
+
notify: (text, options) => channel.notify(text, options),
|
|
1876
|
+
pushLocal: (title, lines) => channel.pushLocal(title, lines),
|
|
1877
|
+
working: () => channel.working,
|
|
1878
|
+
switchModel: (provider, model) => switchModelRecorded(provider, model),
|
|
1879
|
+
}).then((outcome) => {
|
|
1880
|
+
// A catalog-changing outcome invalidates every cached model surface
|
|
1881
|
+
// so `/model` (picker + completion) reflects it immediately — the
|
|
1882
|
+
// same consistency the picker's per-open refetch provides, minus
|
|
1883
|
+
// the stale flash on the next open. The wizard's live-switch branch
|
|
1884
|
+
// already dropped the completion cache via switchModelRecorded;
|
|
1885
|
+
// this covers keep-current, add, edit, delete and OAuth login/logout.
|
|
1886
|
+
if (outcome === 'added' || outcome === 'updated'
|
|
1887
|
+
|| outcome === 'deleted' || outcome === 'signed-out') {
|
|
1888
|
+
channel.invalidateModelCompletion();
|
|
1889
|
+
void channel.listModels().then(setModels);
|
|
1890
|
+
void channel.listProviders().then(setProviderInfos).catch(() => setProviderInfos([]));
|
|
1891
|
+
}
|
|
1892
|
+
}).catch(() => {
|
|
1893
|
+
// The wizard notifies on every handled failure; this only swallows
|
|
1894
|
+
// an unexpected reject so it never surfaces as an unhandled promise.
|
|
1895
|
+
});
|
|
1896
|
+
return true;
|
|
1897
|
+
}
|
|
1898
|
+
case 'thinking':
|
|
1899
|
+
setHelpOpen(false);
|
|
1900
|
+
dispatchOverlay({
|
|
1901
|
+
type: 'open',
|
|
1902
|
+
overlay: { kind: 'thinking', focus: thinkingVisible ? 0 : 1 },
|
|
1903
|
+
});
|
|
1904
|
+
return true;
|
|
1905
|
+
case 'tokens': {
|
|
1906
|
+
const usage = t('tokens-usage', { in: formatTokens(channel.tokens.input), out: formatTokens(channel.tokens.output) });
|
|
1907
|
+
if (channel.contextWindow === undefined) {
|
|
1908
|
+
channel.notify(usage);
|
|
1909
|
+
}
|
|
1910
|
+
else {
|
|
1911
|
+
const percent = Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
1912
|
+
channel.notify(t('tokens-usage-context', { usage, percent }));
|
|
1913
|
+
}
|
|
1914
|
+
return true;
|
|
1915
|
+
}
|
|
1916
|
+
case 'resume':
|
|
1917
|
+
/**
|
|
1918
|
+
* `/resume`, `/home` and `/agentview` are one screen.
|
|
1919
|
+
*
|
|
1920
|
+
* They were three implementations of one domain and drifted apart: the
|
|
1921
|
+
* same session could be listed by all three, each with its own selection
|
|
1922
|
+
* model and its own idea of what opening one does. Keeping the three
|
|
1923
|
+
* commands is about muscle memory, not about three surfaces — every one
|
|
1924
|
+
* of them lands here, on the same runtime.
|
|
1925
|
+
*/
|
|
1926
|
+
case 'home':
|
|
1927
|
+
case 'agentview': {
|
|
1928
|
+
setHelpOpen(false);
|
|
1929
|
+
// The screen opens immediately and loads its own list. Waiting for the
|
|
1930
|
+
// listing here would make it feel slower the more history a project
|
|
1931
|
+
// has, which is exactly backwards.
|
|
1932
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
1933
|
+
setSupervisorOpen(true);
|
|
1934
|
+
return true;
|
|
1935
|
+
}
|
|
1936
|
+
case 'bg':
|
|
1937
|
+
case 'background': {
|
|
1938
|
+
// `/background`: the attached session moves to the background
|
|
1939
|
+
// (it keeps running in this process), the terminal lands on a fresh
|
|
1940
|
+
// session, and the supervisor opens on top.
|
|
1941
|
+
setHelpOpen(false);
|
|
1942
|
+
backgroundToAgentView();
|
|
1943
|
+
return true;
|
|
1944
|
+
}
|
|
1945
|
+
case 'workspace': {
|
|
1946
|
+
setHelpOpen(false);
|
|
1947
|
+
const trimmed = rawInput.trim();
|
|
1948
|
+
const separator = trimmed.search(/\s/u);
|
|
1949
|
+
const subcommand = (separator < 0 ? trimmed : trimmed.slice(0, separator)).toLowerCase();
|
|
1950
|
+
const input = separator < 0 ? '' : trimmed.slice(separator).trim();
|
|
1951
|
+
if (subcommand === '') {
|
|
1952
|
+
// Bare `/workspace` opens the action menu (resume / rename / open
|
|
1953
|
+
// plus any registered extensions) instead of a text usage line.
|
|
1954
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'workspace-menu', index: 0 } });
|
|
1955
|
+
}
|
|
1956
|
+
else if (subcommand === 'resume') {
|
|
1957
|
+
openWorkspaceResume();
|
|
1958
|
+
}
|
|
1959
|
+
else if (subcommand === 'rename') {
|
|
1960
|
+
if (input.length === 0)
|
|
1961
|
+
channel.notify(t('workspace-rename-usage'));
|
|
1962
|
+
else
|
|
1963
|
+
void channel.renameWorkspace(input);
|
|
1964
|
+
}
|
|
1965
|
+
else if (subcommand === 'open') {
|
|
1966
|
+
if (input.length === 0)
|
|
1967
|
+
channel.notify(t('workspace-open-usage'));
|
|
1968
|
+
else
|
|
1969
|
+
openWorkspaceTarget(input);
|
|
1970
|
+
}
|
|
1971
|
+
else if (channel.workspaceCommands().some(command => command.name.toLowerCase() === subcommand
|
|
1972
|
+
|| command.aliases?.some(alias => alias.toLowerCase() === subcommand))) {
|
|
1973
|
+
void channel.runWorkspaceCommand(subcommand, input).then((result) => {
|
|
1974
|
+
if (result !== undefined)
|
|
1975
|
+
handleWorkspaceResult(result);
|
|
1976
|
+
}).catch((error) => {
|
|
1977
|
+
channel.notify(t('workspace-command-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
else {
|
|
1981
|
+
channel.notify(t('workspace-command-unknown', { command: subcommand }), { color: 'error' });
|
|
1982
|
+
}
|
|
1983
|
+
return true;
|
|
1984
|
+
}
|
|
1985
|
+
case 'rename': {
|
|
1986
|
+
setHelpOpen(false);
|
|
1987
|
+
const title = rawInput.trim();
|
|
1988
|
+
if (title.length === 0) {
|
|
1989
|
+
channel.pushLocal('/rename', [
|
|
1990
|
+
t('rename-current', { title: channel.sessionTitle || '—' }),
|
|
1991
|
+
t('rename-usage'),
|
|
1992
|
+
]);
|
|
1993
|
+
return true;
|
|
1994
|
+
}
|
|
1995
|
+
channel.renameSession(title);
|
|
1996
|
+
channel.notify(t('rename-done', { title }));
|
|
1997
|
+
return true;
|
|
1998
|
+
}
|
|
1999
|
+
case 'rewind':
|
|
2000
|
+
// Same picker as PromptInput's double-Esc on an empty input;
|
|
2001
|
+
// `openRewind` notifies when there is nothing to rewind.
|
|
2002
|
+
setHelpOpen(false);
|
|
2003
|
+
openRewind();
|
|
2004
|
+
return true;
|
|
2005
|
+
case 'tree': {
|
|
2006
|
+
// The session family tree (pi's Session Tree): every fork branch
|
|
2007
|
+
// stitched back, hover previews, per-node rewind/fork/adopt.
|
|
2008
|
+
setHelpOpen(false);
|
|
2009
|
+
setTreeOpen(true);
|
|
2010
|
+
return true;
|
|
2011
|
+
}
|
|
2012
|
+
case 'fork': {
|
|
2013
|
+
// Tip fork (kimi-code semantics): a persisted copy of the whole
|
|
2014
|
+
// conversation the user enters via /resume — the live session and
|
|
2015
|
+
// its running turn stay untouched.
|
|
2016
|
+
setHelpOpen(false);
|
|
2017
|
+
void channel.forkSession();
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
case 'exit':
|
|
2021
|
+
case 'quit':
|
|
2022
|
+
case 'q':
|
|
2023
|
+
onExit();
|
|
2024
|
+
return true;
|
|
2025
|
+
case 'status': {
|
|
2026
|
+
const usage = channel.lastUsage;
|
|
2027
|
+
const pct = channel.contextWindow === undefined
|
|
2028
|
+
? undefined
|
|
2029
|
+
: Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
2030
|
+
const lines = [
|
|
2031
|
+
`${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
|
|
2032
|
+
`${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
|
|
2033
|
+
`${t('status-session', { id: channel.agentId })}`,
|
|
2034
|
+
`${t('status-dir', { cwd: channel.displayCwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
|
|
2035
|
+
`Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
|
|
2036
|
+
];
|
|
2037
|
+
if (usage !== undefined) {
|
|
2038
|
+
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
2039
|
+
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
2040
|
+
lines.push(t('cost-cache-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
2041
|
+
}
|
|
2042
|
+
if (pct !== undefined)
|
|
2043
|
+
lines.push(t('cost-context', { pct }));
|
|
2044
|
+
if (channel.sessionTitle)
|
|
2045
|
+
lines.push(t('status-title', { title: channel.sessionTitle }));
|
|
2046
|
+
setHelpOpen(false);
|
|
2047
|
+
channel.pushLocal('/status', lines);
|
|
2048
|
+
return true;
|
|
2049
|
+
}
|
|
2050
|
+
case 'cost': {
|
|
2051
|
+
const usage = channel.lastUsage;
|
|
2052
|
+
const lines = [
|
|
2053
|
+
`Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
|
|
2054
|
+
];
|
|
2055
|
+
if (usage !== undefined) {
|
|
2056
|
+
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
2057
|
+
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
2058
|
+
lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
2059
|
+
}
|
|
2060
|
+
lines.push(t('cost-note'));
|
|
2061
|
+
setHelpOpen(false);
|
|
2062
|
+
channel.pushLocal('/cost', lines);
|
|
2063
|
+
return true;
|
|
2064
|
+
}
|
|
2065
|
+
case 'balance': {
|
|
2066
|
+
// DeepSeek official account balance (free read-only endpoint): the
|
|
2067
|
+
// channel resolves DEEPSEEK_API_KEY through the credentials seam and
|
|
2068
|
+
// queries api.deepseek.com/user/balance. The result renders as the
|
|
2069
|
+
// interactive BalanceReportRow (hover for details, click to refresh).
|
|
2070
|
+
setHelpOpen(false);
|
|
2071
|
+
runBalance();
|
|
2072
|
+
return true;
|
|
2073
|
+
}
|
|
2074
|
+
case 'settings': {
|
|
2075
|
+
// Plugin settings screen (issue #165): opens immediately; the screen
|
|
2076
|
+
// reads sections + namespaces from the channel itself.
|
|
2077
|
+
setHelpOpen(false);
|
|
2078
|
+
setSettingsOpen(true);
|
|
2079
|
+
return true;
|
|
2080
|
+
}
|
|
2081
|
+
case 'config': {
|
|
2082
|
+
const userHome = process.env.USERPROFILE ?? '';
|
|
2083
|
+
const lines = [
|
|
2084
|
+
t('doctor-example-config', { path: 'dsh --profile dsh-tui' }),
|
|
2085
|
+
t('doctor-user-config', { path: `${userHome}/.dsh/profiles/dsh-tui/cordis.patch.yml` }),
|
|
2086
|
+
'',
|
|
2087
|
+
t('doctor-launch-hint'),
|
|
2088
|
+
t('doctor-route-hint'),
|
|
2089
|
+
];
|
|
2090
|
+
setHelpOpen(false);
|
|
2091
|
+
channel.pushLocal('/config', lines);
|
|
2092
|
+
return true;
|
|
2093
|
+
}
|
|
2094
|
+
case 'doctor':
|
|
2095
|
+
setHelpOpen(false);
|
|
2096
|
+
channel.pushLocal('/doctor', channel.doctorInfo());
|
|
2097
|
+
return true;
|
|
2098
|
+
case 'migrate': {
|
|
2099
|
+
// Double entry points with the CLI. BARE `/migrate` opens the
|
|
2100
|
+
// multi-select picker; `/migrate <agent>` opens the CONFIRMATION
|
|
2101
|
+
// layer for that single source (PRD #2 — a bulk import is never one
|
|
2102
|
+
// keystroke away). Validity is decided against the adapter REGISTRY,
|
|
2103
|
+
// never the picker's row cache: on a fresh mount that cache is empty,
|
|
2104
|
+
// and deriving the answer from it made every `/migrate <agent>` report
|
|
2105
|
+
// an unknown source until the picker had been opened once.
|
|
2106
|
+
setHelpOpen(false);
|
|
2107
|
+
const command = resolveMigrateCommand(rawInput, MIGRATION_ADAPTERS.map(adapter => adapter.id));
|
|
2108
|
+
if (command.kind === 'unknown') {
|
|
2109
|
+
channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
|
|
2110
|
+
return true;
|
|
2111
|
+
}
|
|
2112
|
+
if (command.kind === 'usage') {
|
|
2113
|
+
channel.notify(t('migrate-usage'), { color: 'error', timeoutMs: 8000 });
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
if (command.kind === 'dry-run-needs-source') {
|
|
2117
|
+
channel.notify(t('migrate-dry-run-needs-source'), { color: 'error', timeoutMs: 8000 });
|
|
2118
|
+
return true;
|
|
2119
|
+
}
|
|
2120
|
+
if (command.kind === 'import') {
|
|
2121
|
+
void (async () => {
|
|
2122
|
+
// Rows carry the scannable count the confirmation line shows; a
|
|
2123
|
+
// warm cache from an earlier picker visit is reused as is.
|
|
2124
|
+
const rows = migrateRows ?? await collectMigrateRows();
|
|
2125
|
+
const row = rows.find(candidate => candidate.agentId === command.agentId);
|
|
2126
|
+
if (row === undefined) {
|
|
2127
|
+
channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
if (command.dryRun) {
|
|
2131
|
+
spawnMigrateSources([row], true);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
setMigratePending([row]);
|
|
2135
|
+
// Pin the checked set to the source this confirmation is about:
|
|
2136
|
+
// Esc returns to the picker, and a stale set from an earlier visit
|
|
2137
|
+
// would there contradict what the confirmation just showed.
|
|
2138
|
+
setMigrateChecked(new Set([row.agentId]));
|
|
2139
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
|
|
2140
|
+
})();
|
|
2141
|
+
return true;
|
|
2142
|
+
}
|
|
2143
|
+
// Bare `/migrate` starts a NEW flow: drop the previous run's checks
|
|
2144
|
+
// (only Esc-out-of-confirm keeps them) and let the picker show its
|
|
2145
|
+
// scanning state until the rows land.
|
|
2146
|
+
setMigrateRows(null);
|
|
2147
|
+
setMigrateChecked(new Set());
|
|
2148
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
2149
|
+
void collectMigrateRows().then(setMigrateRows);
|
|
2150
|
+
return true;
|
|
2151
|
+
}
|
|
2152
|
+
case 'plugins':
|
|
2153
|
+
// Plugin diagnostics (C-070): trust banner first, then descriptor /
|
|
2154
|
+
// grant matrix / ledger tail — or validate+negotiate for
|
|
2155
|
+
// `/plugins check <path>` (rawInput carries the subcommand).
|
|
2156
|
+
setHelpOpen(false);
|
|
2157
|
+
channel.pushLocal('/plugins', channel.pluginsInfo(rawInput));
|
|
2158
|
+
return true;
|
|
2159
|
+
case 'export': {
|
|
2160
|
+
const target = channel.exportSession();
|
|
2161
|
+
channel.notify(target === null
|
|
2162
|
+
? t('export-failed')
|
|
2163
|
+
: t('export-saved', { target }), target === null ? { color: 'error', timeoutMs: 8000 } : { timeoutMs: 8000 });
|
|
2164
|
+
return true;
|
|
2165
|
+
}
|
|
2166
|
+
case 'init': {
|
|
2167
|
+
const result = channel.initWorkspace();
|
|
2168
|
+
if (result === null)
|
|
2169
|
+
channel.notify(t('agentsmd-create-failed'), { color: 'error' });
|
|
2170
|
+
else if (result === 'exists')
|
|
2171
|
+
channel.notify(t('agentsmd-exists'));
|
|
2172
|
+
else
|
|
2173
|
+
channel.notify(t('agentsmd-created', { result }));
|
|
2174
|
+
return true;
|
|
2175
|
+
}
|
|
2176
|
+
case 'jobs':
|
|
2177
|
+
setHelpOpen(false);
|
|
2178
|
+
setJobsPanelOpen(true);
|
|
2179
|
+
return true;
|
|
2180
|
+
case 'agents':
|
|
2181
|
+
setHelpOpen(false);
|
|
2182
|
+
void channel.listSubagents().then((lines) => {
|
|
2183
|
+
channel.pushLocal('/agents', lines);
|
|
2184
|
+
});
|
|
2185
|
+
return true;
|
|
2186
|
+
case 'login': {
|
|
2187
|
+
setHelpOpen(false);
|
|
2188
|
+
void channel.describeCredential('DEEPSEEK_API_KEY')
|
|
2189
|
+
.catch(() => undefined)
|
|
2190
|
+
.then(async (status) => {
|
|
2191
|
+
const keyStatus = status === undefined
|
|
2192
|
+
? t('login-credentials-unavailable')
|
|
2193
|
+
: status.configured
|
|
2194
|
+
? t('login-key-configured', { ref: 'DEEPSEEK_API_KEY' })
|
|
2195
|
+
: t('login-key-missing');
|
|
2196
|
+
// OAuth account states ride along only while a dsh-auth-style
|
|
2197
|
+
// plugin is mounted; absent it the lines below are exactly the
|
|
2198
|
+
// pre-plugin set.
|
|
2199
|
+
const oauth = await channel.oauthProviderStatuses().catch(() => undefined);
|
|
2200
|
+
channel.pushLocal('/login', [
|
|
2201
|
+
t('login-api-key', { status: keyStatus }),
|
|
2202
|
+
...(status === undefined
|
|
2203
|
+
? []
|
|
2204
|
+
: [
|
|
2205
|
+
t('login-credential-source', { source: status.source ?? t('login-source-none') }),
|
|
2206
|
+
t('login-credential-storage', {
|
|
2207
|
+
mode: t(status.writable ? 'login-storage-writable' : 'login-storage-read-only'),
|
|
2208
|
+
}),
|
|
2209
|
+
]),
|
|
2210
|
+
t('login-base-url', { url: process.env.DEEPSEEK_BASE_URL ?? t('login-official-endpoint') }),
|
|
2211
|
+
...(oauth === undefined
|
|
2212
|
+
? []
|
|
2213
|
+
: [
|
|
2214
|
+
t('login-oauth-heading'),
|
|
2215
|
+
...oauth.map(row => t('login-oauth-row', {
|
|
2216
|
+
provider: row.provider,
|
|
2217
|
+
state: row.signedIn
|
|
2218
|
+
? row.expiresAt === undefined
|
|
2219
|
+
? t('login-auth-in')
|
|
2220
|
+
: t('login-oauth-in', { time: new Date(row.expiresAt).toISOString() })
|
|
2221
|
+
: row.expired
|
|
2222
|
+
? t('login-oauth-expired')
|
|
2223
|
+
: t('login-oauth-signed-out'),
|
|
2224
|
+
})),
|
|
2225
|
+
t('login-oauth-hint'),
|
|
2226
|
+
]),
|
|
2227
|
+
]);
|
|
2228
|
+
});
|
|
2229
|
+
return true;
|
|
2230
|
+
}
|
|
2231
|
+
case 'logout':
|
|
2232
|
+
channel.notify(t('login-logout-hint'));
|
|
2233
|
+
return true;
|
|
2234
|
+
case 'permission': {
|
|
2235
|
+
// The command itself is registered by the permission-presets row
|
|
2236
|
+
// (dsh-base): bare `/permission` opens the preset picker and Enter
|
|
2237
|
+
// dispatches `/permission <preset>`; `/permission status` prints the
|
|
2238
|
+
// policy explainer; other arguments pass through verbatim.
|
|
2239
|
+
// The row may be mounted as a service without its command ever
|
|
2240
|
+
// reaching this agent's registry (composition-dependent) — when the
|
|
2241
|
+
// service snapshot is usable the TUI still owns the entry and
|
|
2242
|
+
// switches through the service write path (never the model).
|
|
2243
|
+
const mounted = channel.commandList.some(command => command.external && command.name === 'permission');
|
|
2244
|
+
let serviceUsable = false;
|
|
2245
|
+
try {
|
|
2246
|
+
serviceUsable = channel.permissionPresets().availability === 'runtime';
|
|
2247
|
+
}
|
|
2248
|
+
catch {
|
|
2249
|
+
serviceUsable = false;
|
|
2250
|
+
}
|
|
2251
|
+
const reachable = mounted || serviceUsable;
|
|
2252
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
2253
|
+
if (reachable && parts[0] === 'status') {
|
|
2254
|
+
setHelpOpen(false);
|
|
2255
|
+
const snapshot = channel.permissionPresets();
|
|
2256
|
+
if (snapshot.options.some(option => option.value === 'status')) {
|
|
2257
|
+
return runPermissionCommand(rawInput, images);
|
|
2258
|
+
}
|
|
2259
|
+
const currentName = snapshot.availability === 'unavailable'
|
|
2260
|
+
? t('permission-roster-unavailable')
|
|
2261
|
+
: snapshot.current?.name ?? '—';
|
|
2262
|
+
channel.pushLocal('/permission', [
|
|
2263
|
+
t('permission-current', { name: currentName }),
|
|
2264
|
+
t('permission-policy-hint'),
|
|
2265
|
+
t('permission-approval-hint'),
|
|
2266
|
+
t('permission-root-hint', { cwd: channel.cwd }),
|
|
2267
|
+
t('permission-path-hint'),
|
|
2268
|
+
]);
|
|
2269
|
+
return true;
|
|
2270
|
+
}
|
|
2271
|
+
if (reachable && parts.length === 0) {
|
|
2272
|
+
setHelpOpen(false);
|
|
2273
|
+
const snapshot = channel.permissionPresets();
|
|
2274
|
+
if (snapshot.availability === 'unavailable' || snapshot.options.length === 0) {
|
|
2275
|
+
return runPermissionCommand(rawInput, images);
|
|
2276
|
+
}
|
|
2277
|
+
const currentValue = snapshot.current?.kind === 'preset' ? snapshot.current.value : undefined;
|
|
2278
|
+
const currentIndex = currentValue === undefined
|
|
2279
|
+
? -1
|
|
2280
|
+
: snapshot.options.findIndex(option => option.value === currentValue);
|
|
2281
|
+
const index = currentIndex >= 0
|
|
2282
|
+
? currentIndex
|
|
2283
|
+
: Math.min(1, snapshot.options.length - 1);
|
|
2284
|
+
dispatchOverlay({
|
|
2285
|
+
type: 'open',
|
|
2286
|
+
overlay: {
|
|
2287
|
+
kind: 'permission',
|
|
2288
|
+
index,
|
|
2289
|
+
snapshot: clonePermissionPresetSnapshot(snapshot),
|
|
2290
|
+
},
|
|
2291
|
+
});
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
if (reachable) {
|
|
2295
|
+
setHelpOpen(false);
|
|
2296
|
+
return runPermissionCommand(rawInput, images);
|
|
2297
|
+
}
|
|
2298
|
+
return false;
|
|
2299
|
+
}
|
|
2300
|
+
case 'plan': {
|
|
2301
|
+
// Registered by dsh-plan-mode: bare `/plan` opens an on/off picker
|
|
2302
|
+
// marked with the current state instead of toggling blindly; Enter
|
|
2303
|
+
// dispatches `/plan` or `/plan off`. Arguments pass through verbatim
|
|
2304
|
+
// (`/plan off`), and an unmounted row falls back to the default
|
|
2305
|
+
// external path.
|
|
2306
|
+
const mounted = channel.commandList.some(command => command.external && command.name === 'plan');
|
|
2307
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
2308
|
+
if (mounted && parts.length === 0) {
|
|
2309
|
+
setHelpOpen(false);
|
|
2310
|
+
dispatchOverlay({
|
|
2311
|
+
type: 'open',
|
|
2312
|
+
overlay: { kind: 'plan', index: channel.mode.plan === true ? 0 : 1 },
|
|
2313
|
+
});
|
|
2314
|
+
return true;
|
|
2315
|
+
}
|
|
2316
|
+
if (mounted) {
|
|
2317
|
+
setHelpOpen(false);
|
|
2318
|
+
return runExternalCommand('plan', rawInput, images);
|
|
2319
|
+
}
|
|
2320
|
+
return false;
|
|
2321
|
+
}
|
|
2322
|
+
case 'add-dir':
|
|
2323
|
+
setHelpOpen(false);
|
|
2324
|
+
channel.pushLocal('/add-dir', [
|
|
2325
|
+
t('permission-root-hint', { cwd: channel.cwd }),
|
|
2326
|
+
t('permission-path-hint'),
|
|
2327
|
+
]);
|
|
2328
|
+
return true;
|
|
2329
|
+
case 'hooks':
|
|
2330
|
+
setHelpOpen(false);
|
|
2331
|
+
channel.pushLocal('/hooks', [
|
|
2332
|
+
t('hooks-not-mounted'),
|
|
2333
|
+
t('hooks-mount-hint'),
|
|
2334
|
+
]);
|
|
2335
|
+
return true;
|
|
2336
|
+
case 'mcp':
|
|
2337
|
+
setHelpOpen(false);
|
|
2338
|
+
channel.pushLocal('/mcp', channel.mcpStatus());
|
|
2339
|
+
return true;
|
|
2340
|
+
case 'update':
|
|
2341
|
+
setHelpOpen(false);
|
|
2342
|
+
if (onUpdate === undefined) {
|
|
2343
|
+
channel.notify(t('update-unavailable'), { color: 'warning' });
|
|
2344
|
+
}
|
|
2345
|
+
else if (channel.working) {
|
|
2346
|
+
channel.notify(t('update-working'), { color: 'warning' });
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
channel.notify(t('update-starting'));
|
|
2350
|
+
onUpdate();
|
|
2351
|
+
}
|
|
2352
|
+
return true;
|
|
2353
|
+
case 'reload': {
|
|
2354
|
+
// pi-style soft reload: re-read the persisted preference files
|
|
2355
|
+
// (~/.dsh-tui/{theme,lang,agent-preset,model,working-activity}.json)
|
|
2356
|
+
// and re-apply live, honoring the boot-time precedence (env >
|
|
2357
|
+
// cordis.yml > settings user layer > pref). The dsh-tui settings
|
|
2358
|
+
// namespace is NOT re-read here — its watch applies edits live and
|
|
2359
|
+
// the platform watcher hot-reloads settings.yaml itself. What no
|
|
2360
|
+
// reload can re-read (cordis.yml root config, frozen fullscreen,
|
|
2361
|
+
// newly built code) is listed in the footer and served by /restart.
|
|
2362
|
+
setHelpOpen(false);
|
|
2363
|
+
const tuiNamespace = channel.settingsHost()
|
|
2364
|
+
?.listNamespaces()
|
|
2365
|
+
.find(entry => entry.ns === 'dsh-tui');
|
|
2366
|
+
const plan = planReload({
|
|
2367
|
+
envTheme: envThemeOverride(),
|
|
2368
|
+
envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
|
|
2369
|
+
themePref: readThemePref(),
|
|
2370
|
+
currentTheme: themeName,
|
|
2371
|
+
langPref: readLangPref(),
|
|
2372
|
+
currentLang: getLang(),
|
|
2373
|
+
langOverriddenBySettings: tuiNamespace !== undefined && hasPath(tuiNamespace.user, ['lang']),
|
|
2374
|
+
configuredLang: channel.configuredLang,
|
|
2375
|
+
configuredPreset: channel.configuredPreset,
|
|
2376
|
+
presetPref: readPresetPref(),
|
|
2377
|
+
currentPreset: channel.agentPreset,
|
|
2378
|
+
configuredModel: {
|
|
2379
|
+
provider: channel.configuredProvider,
|
|
2380
|
+
model: channel.configuredModel,
|
|
2381
|
+
},
|
|
2382
|
+
modelPref: readModelPref(),
|
|
2383
|
+
currentModel: { provider: channel.provider, model: channel.model },
|
|
2384
|
+
configuredActivity: channel.configuredActivityFrames,
|
|
2385
|
+
activityPref: readActivityFrames(),
|
|
2386
|
+
currentActivity: channel.activityFrames,
|
|
2387
|
+
});
|
|
2388
|
+
for (const item of plan.apply) {
|
|
2389
|
+
switch (item.kind) {
|
|
2390
|
+
case 'theme':
|
|
2391
|
+
setTheme(item.to);
|
|
2392
|
+
break;
|
|
2393
|
+
case 'lang':
|
|
2394
|
+
applyLang(item.to);
|
|
2395
|
+
break;
|
|
2396
|
+
case 'preset':
|
|
2397
|
+
void channel.switchPreset(item.to);
|
|
2398
|
+
break;
|
|
2399
|
+
case 'model':
|
|
2400
|
+
if (item.route !== undefined) {
|
|
2401
|
+
void switchModelRecorded(item.route.provider, item.route.model);
|
|
2402
|
+
}
|
|
2403
|
+
break;
|
|
2404
|
+
case 'activity':
|
|
2405
|
+
channel.setActivityFrames(item.to);
|
|
2406
|
+
break;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
const lines = [t('reload-header')];
|
|
2410
|
+
for (const item of plan.apply) {
|
|
2411
|
+
lines.push(t('reload-applied', {
|
|
2412
|
+
kind: reloadKindLabel(item.kind),
|
|
2413
|
+
from: item.from,
|
|
2414
|
+
to: item.to,
|
|
2415
|
+
}));
|
|
2416
|
+
}
|
|
2417
|
+
for (const kind of plan.unchanged) {
|
|
2418
|
+
lines.push(t('reload-unchanged', { kind: reloadKindLabel(kind) }));
|
|
2419
|
+
}
|
|
2420
|
+
for (const skip of plan.skipped) {
|
|
2421
|
+
const key = skip.reason === 'env-wins'
|
|
2422
|
+
? 'reload-skipped-env'
|
|
2423
|
+
: skip.reason === 'config-wins' ? 'reload-skipped-config' : 'reload-skipped-invalid';
|
|
2424
|
+
lines.push(t(key, { kind: reloadKindLabel(skip.kind) }));
|
|
2425
|
+
}
|
|
2426
|
+
lines.push(t('reload-footer'));
|
|
2427
|
+
channel.pushLocal('/reload', lines);
|
|
2428
|
+
return true;
|
|
2429
|
+
}
|
|
2430
|
+
case 'restart':
|
|
2431
|
+
// pi-style reload tail: /reload cannot re-read boot-time-only state
|
|
2432
|
+
// (cordis.yml root config, frozen fullscreen layout, newly built
|
|
2433
|
+
// code), so /restart respawns the process with the original argv and
|
|
2434
|
+
// resumes this session — the /update handoff minus the pnpm step.
|
|
2435
|
+
setHelpOpen(false);
|
|
2436
|
+
if (onRestart === undefined) {
|
|
2437
|
+
channel.notify(t('restart-unavailable'), { color: 'warning' });
|
|
2438
|
+
}
|
|
2439
|
+
else if (channel.working) {
|
|
2440
|
+
channel.notify(t('update-working'), { color: 'warning' });
|
|
2441
|
+
}
|
|
2442
|
+
else {
|
|
2443
|
+
channel.notify(t('restart-starting'));
|
|
2444
|
+
onRestart();
|
|
2445
|
+
}
|
|
2446
|
+
return true;
|
|
2447
|
+
case 'vim': {
|
|
2448
|
+
// `/vim`:切换输入框的 vim 编辑开关。状态在
|
|
2449
|
+
// PromptInput 内部(controllerRef.toggleVim),每次切换落回 insert
|
|
2450
|
+
// 子模式;Esc 进 normal、i/a/o 回 insert。会话级、不持久化。
|
|
2451
|
+
setHelpOpen(false);
|
|
2452
|
+
const on = promptControllerRef.current?.toggleVim() ?? false;
|
|
2453
|
+
channel.notify(t(on ? 'vim-on' : 'vim-off'));
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
case 'terminal-setup':
|
|
2457
|
+
setHelpOpen(false);
|
|
2458
|
+
channel.pushLocal('/terminal-setup', [
|
|
2459
|
+
t('terminal-setup-hint'),
|
|
2460
|
+
t('terminal-paste-hint', { mod: modLabel }),
|
|
2461
|
+
]);
|
|
2462
|
+
return true;
|
|
2463
|
+
case 'recap': {
|
|
2464
|
+
// `/recap`(pi-recap 语义):对会话最近活动做一次无工具单轮
|
|
2465
|
+
// 调用,生成一行摘要 + 建议标题。摘要是纯 UI 状态(不进 transcript
|
|
2466
|
+
// 也不进 session log);建议标题经「应用」按钮走 /rename 路径。
|
|
2467
|
+
setHelpOpen(false);
|
|
2468
|
+
recapAbortRef.current?.abort();
|
|
2469
|
+
const controller = new AbortController();
|
|
2470
|
+
recapAbortRef.current = controller;
|
|
2471
|
+
setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: false, expanded: true });
|
|
2472
|
+
void channel.recapRecent({
|
|
2473
|
+
signal: controller.signal,
|
|
2474
|
+
onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
|
|
2475
|
+
}).then(result => {
|
|
2476
|
+
if (controller.signal.aborted)
|
|
2477
|
+
return;
|
|
2478
|
+
setRecap(prev => (prev
|
|
2479
|
+
? {
|
|
2480
|
+
...prev,
|
|
2481
|
+
summary: result.summary ?? prev.raw,
|
|
2482
|
+
title: result.title,
|
|
2483
|
+
error: result.error,
|
|
2484
|
+
done: true,
|
|
2485
|
+
}
|
|
2486
|
+
: prev));
|
|
2487
|
+
}).catch(error => {
|
|
2488
|
+
if (controller.signal.aborted)
|
|
2489
|
+
return;
|
|
2490
|
+
setRecap(prev => prev ? { ...prev, error: error instanceof Error ? error.message : String(error), done: true } : prev);
|
|
2491
|
+
});
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
case 'btw': {
|
|
2495
|
+
// `/btw`:单轮无工具侧问,overlay 态纯 UI,不打断主回合、不写
|
|
2496
|
+
// 会话历史。空参数只提示用法。
|
|
2497
|
+
setHelpOpen(false);
|
|
2498
|
+
const question = rawInput.trim();
|
|
2499
|
+
if (!question) {
|
|
2500
|
+
channel.notify(t('btw-usage'), { timeoutMs: 3000 });
|
|
2501
|
+
return true;
|
|
2502
|
+
}
|
|
2503
|
+
btwAbortRef.current?.abort();
|
|
2504
|
+
const controller = new AbortController();
|
|
2505
|
+
btwAbortRef.current = controller;
|
|
2506
|
+
setBtw({ question, answer: '', done: false });
|
|
2507
|
+
void channel.sideQuestion(question, {
|
|
2508
|
+
signal: controller.signal,
|
|
2509
|
+
onText: delta => setBtw(prev => (prev ? { ...prev, answer: prev.answer + delta } : prev)),
|
|
2510
|
+
}).then(result => {
|
|
2511
|
+
if (controller.signal.aborted)
|
|
2512
|
+
return;
|
|
2513
|
+
setBtw(prev => (prev ? { ...prev, answer: result.answer ?? prev.answer, error: result.error, done: true } : prev));
|
|
2514
|
+
}).catch(error => {
|
|
2515
|
+
if (controller.signal.aborted)
|
|
2516
|
+
return;
|
|
2517
|
+
setBtw(prev => prev ? { ...prev, error: error instanceof Error ? error.message : String(error), done: true } : prev);
|
|
2518
|
+
});
|
|
2519
|
+
return true;
|
|
2520
|
+
}
|
|
2521
|
+
case 'deepseek': {
|
|
2522
|
+
// Hidden easter egg: replay the logo header's whale spout + text
|
|
2523
|
+
// shimmer. The command is intentionally not in the suggestion/help
|
|
2524
|
+
// catalogs; PromptInput recognizes it through HIDDEN_COMMAND_NAMES.
|
|
2525
|
+
setHelpOpen(false);
|
|
2526
|
+
suppressLogoIntroRef.current = false;
|
|
2527
|
+
setLogoNonce(n => n + 1);
|
|
2528
|
+
// Bring the logo back into view if the transcript has scrolled.
|
|
2529
|
+
setTimeout(() => {
|
|
2530
|
+
handle?.scrollTo(0);
|
|
2531
|
+
}, 0);
|
|
2532
|
+
return true;
|
|
2533
|
+
}
|
|
2534
|
+
case 'tips':
|
|
2535
|
+
setHelpOpen(false);
|
|
2536
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'tips' } });
|
|
2537
|
+
return true;
|
|
2538
|
+
case 'connect':
|
|
2539
|
+
setHelpOpen(false);
|
|
2540
|
+
channel.pushLocal('/connect', [t('connect-none')]);
|
|
2541
|
+
return true;
|
|
2542
|
+
default: {
|
|
2543
|
+
// Plugin-registered command (DSH command registry): dispatch through
|
|
2544
|
+
// the channel, whose execution logs command/run + command/done (the
|
|
2545
|
+
// plan-mode projection folds those records, so /plan state stays
|
|
2546
|
+
// consistent). Unknown names fall through to the model.
|
|
2547
|
+
const external = channel.commandList.find(command => command.external && command.name === name);
|
|
2548
|
+
if (external) {
|
|
2549
|
+
setHelpOpen(false);
|
|
2550
|
+
return runExternalCommand(name, rawInput, images);
|
|
2551
|
+
}
|
|
2552
|
+
return false;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
// === Message-selection mode (Shift+↑ message actions) ===
|
|
2557
|
+
// NOTE: rows is a live in-place array on the channel (no new reference per
|
|
2558
|
+
// update), so derived lists must be computed per render — a useMemo keyed
|
|
2559
|
+
// on `channel.rows` would freeze at the first empty snapshot forever.
|
|
2560
|
+
// Both lists only feed their respective modes; computing them
|
|
2561
|
+
// unconditionally cost an O(rows) scan + array allocation per render
|
|
2562
|
+
// (every streamed chunk), so they are gated on the consuming mode.
|
|
2563
|
+
const selectableRows = selectionActive
|
|
2564
|
+
? channel.rows.filter(row => SELECTABLE_KINDS.has(row.kind))
|
|
2565
|
+
: NO_ROWS;
|
|
2566
|
+
// ctrl+r history search: substring match on the query, newest first.
|
|
2567
|
+
// The draft lives in the overlay variant; the derived '' while closed
|
|
2568
|
+
// keeps the memo inputs stable (nothing renders the matches then).
|
|
2569
|
+
const historyQuery = overlay.kind === 'history' ? overlay.query : '';
|
|
2570
|
+
const historyMatches = React.useMemo(() => {
|
|
2571
|
+
const q = historyQuery.trim().toLowerCase();
|
|
2572
|
+
return q ? historyEntries.filter(e => e.text.toLowerCase().includes(q)) : historyEntries;
|
|
2573
|
+
}, [historyEntries, historyQuery]);
|
|
2574
|
+
// Double-Esc rewind: the user's own messages, newest first (the list shows
|
|
2575
|
+
// selectable user turns; steering side-questions are excluded). Computed
|
|
2576
|
+
// per render while the picker is open — `channel.rows` is a live in-place
|
|
2577
|
+
// array (see selectableRows).
|
|
2578
|
+
const rewindRows = overlay.kind === 'rewind'
|
|
2579
|
+
? channel.rows
|
|
2580
|
+
.filter(row => row.kind === 'user' && row.label === undefined)
|
|
2581
|
+
.reverse()
|
|
2582
|
+
: NO_ROWS;
|
|
2583
|
+
/** Open the rewind picker (from PromptInput's double-Esc on an empty input). */
|
|
2584
|
+
const openRewind = () => {
|
|
2585
|
+
// The overlay is not 'rewind' yet this render, so rewindRows is empty —
|
|
2586
|
+
// scan directly instead of reading the gated list.
|
|
2587
|
+
const candidates = channel.rows
|
|
2588
|
+
.filter(row => row.kind === 'user' && row.label === undefined)
|
|
2589
|
+
.reverse();
|
|
2590
|
+
if (candidates.length === 0) {
|
|
2591
|
+
channel.notify(t('rewind-none'));
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
rewindRequestRef.current += 1;
|
|
2595
|
+
dispatchOverlay({
|
|
2596
|
+
type: 'open',
|
|
2597
|
+
overlay: { kind: 'rewind', index: 0, confirm: null, modes: null, modeIndex: 0, busy: false },
|
|
2598
|
+
});
|
|
2599
|
+
};
|
|
2600
|
+
/**
|
|
2601
|
+
* Enter on a rewind candidate: ask the plugins first (tui/rewind-prompt).
|
|
2602
|
+
* A veto keeps the list open; offered modes turn the confirm pane into a
|
|
2603
|
+
* choice list; "no opinion" lands on the plain confirm as before.
|
|
2604
|
+
*/
|
|
2605
|
+
const requestRewindConfirm = async (row) => {
|
|
2606
|
+
const token = ++rewindRequestRef.current;
|
|
2607
|
+
dispatchOverlay({ type: 'rewind-busy', busy: true });
|
|
2608
|
+
const decision = await channel.promptRewind(row);
|
|
2609
|
+
if (token !== rewindRequestRef.current)
|
|
2610
|
+
return;
|
|
2611
|
+
if (decision === 'cancel') {
|
|
2612
|
+
dispatchOverlay({ type: 'rewind-busy', busy: false });
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
dispatchOverlay({ type: 'rewind-decision', confirm: row, modes: decision?.modes ?? null });
|
|
2616
|
+
};
|
|
2617
|
+
/** Execute the confirmed rewind; the message comes back into the input. */
|
|
2618
|
+
const performRewind = async (row, mode = null) => {
|
|
2619
|
+
const text = await channel.rewindTo(row, mode);
|
|
2620
|
+
if (text !== null) {
|
|
2621
|
+
// The restored message belongs to the binding `rewindTo` just created,
|
|
2622
|
+
// not to the one it replaced: it is the user's choice, and the switch
|
|
2623
|
+
// effect must not treat it as the old conversation's leftovers.
|
|
2624
|
+
pendingFillRef.current = String(channel.agentId);
|
|
2625
|
+
// Put the restored message back in the prompt for re-editing.
|
|
2626
|
+
setHistoryFill(text);
|
|
2627
|
+
channel.notify(t('rewind-done'));
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
/**
|
|
2631
|
+
* The session's trajectory projection, folded here rather than inside the
|
|
2632
|
+
* scene.
|
|
2633
|
+
*
|
|
2634
|
+
* Two things fall out of owning it at this level: the status-line chip can
|
|
2635
|
+
* show live counters without a second fold, and opening the scene is
|
|
2636
|
+
* instant because the build is already warm. The fold is incremental — it
|
|
2637
|
+
* consumes only events appended since the last render — so an idle
|
|
2638
|
+
* conversation pays nothing for it.
|
|
2639
|
+
*/
|
|
2640
|
+
const trajectoryRef = React.useRef(null);
|
|
2641
|
+
trajectoryRef.current = extendTrajectory(trajectoryRef.current,
|
|
2642
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: headless hosts render Chat with a partial channel
|
|
2643
|
+
channel.traceEvents?.() ?? NO_EVENTS);
|
|
2644
|
+
const trajectory = trajectoryRef.current;
|
|
2645
|
+
/**
|
|
2646
|
+
* The status-line wake.
|
|
2647
|
+
*
|
|
2648
|
+
* Projected onto a dozen-odd columns and memoized against the ledger's row
|
|
2649
|
+
* count, so it recomputes when the session actually grows rather than on
|
|
2650
|
+
* every animation tick. The tick only re-colours the cells it already has.
|
|
2651
|
+
*/
|
|
2652
|
+
const { columns: terminalColumns } = useTerminalSize();
|
|
2653
|
+
const pageInsetX = usePageInset().x;
|
|
2654
|
+
const wakeWidth = miniWakeWidth(terminalColumns);
|
|
2655
|
+
const wakeBand = React.useMemo(() => wakeWidth === 0
|
|
2656
|
+
? undefined
|
|
2657
|
+
// `sequence`, not the scene's `compressed`: at sixteen columns an idle
|
|
2658
|
+
// gap cannot express how long it was, so it only reads as a broken
|
|
2659
|
+
// strip. Equal-width columns give a continuous silhouette, which is
|
|
2660
|
+
// the only thing this size can actually say.
|
|
2661
|
+
// Width is also clamped to the row count: with fewer rows than
|
|
2662
|
+
// columns the strip would be mostly gaps, which reads as broken
|
|
2663
|
+
// rather than as short. It simply grows as the session does.
|
|
2664
|
+
: projectWave(trajectory.nodes, Math.min(wakeWidth, trajectory.nodes.length), 'sequence'),
|
|
2665
|
+
// The node array is mutated in place by the incremental fold, so its
|
|
2666
|
+
// length is the honest dependency; its identity never changes.
|
|
2667
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
2668
|
+
[trajectory.nodes, trajectory.counts.rows, wakeWidth]);
|
|
2669
|
+
const [wakeTickRef, wakeTime] = useAnimationFrame(channel.working ? 120 : null);
|
|
2670
|
+
/**
|
|
2671
|
+
* The key hint beside the strip retires itself once the trajectory has been
|
|
2672
|
+
* opened — teaching belongs in the first minute, not on every frame forever.
|
|
2673
|
+
*/
|
|
2674
|
+
const [trajectorySeen, setTrajectorySeen] = React.useState(() => trajectorySeenProp ?? readTrajectorySeen());
|
|
2675
|
+
/**
|
|
2676
|
+
* The one failure worth pointing at.
|
|
2677
|
+
*
|
|
2678
|
+
* Only the LATEST failed tool row carries the footnote, and only while its
|
|
2679
|
+
* failures are unseen. Repeating it under every historical failure would be
|
|
2680
|
+
* exactly the clutter the whole entry design is trying to avoid — one
|
|
2681
|
+
* pointer, at the newest problem, is enough to find the rest.
|
|
2682
|
+
*/
|
|
2683
|
+
const seenFailuresRef = React.useRef(0);
|
|
2684
|
+
const unreadFailures = Math.max(0, trajectory.counts.errors - seenFailuresRef.current);
|
|
2685
|
+
const failureHintRowId = React.useMemo(() => {
|
|
2686
|
+
if (unreadFailures === 0)
|
|
2687
|
+
return null;
|
|
2688
|
+
for (let index = channel.rows.length - 1; index >= 0; index--) {
|
|
2689
|
+
const row = channel.rows[index];
|
|
2690
|
+
if (row?.kind === 'tool' && row.tool?.status === 'error')
|
|
2691
|
+
return row.id;
|
|
2692
|
+
}
|
|
2693
|
+
return null;
|
|
2694
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
2695
|
+
}, [channel.rows, channel.version, unreadFailures]);
|
|
2696
|
+
// Row seeking under layout virtualization: a mounted row seeks directly;
|
|
2697
|
+
// an unmounted one is force-mounted first, then sought by the completion
|
|
2698
|
+
// effect below once its ref lands.
|
|
2699
|
+
const [forceMountRowId, setForceMountRowId] = React.useState(null);
|
|
2700
|
+
const seekRow = (rowId) => {
|
|
2701
|
+
const el = rowRefsRef.current.get(rowId);
|
|
2702
|
+
if (el) {
|
|
2703
|
+
handle?.scrollToElement(el);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
setForceMountRowId(rowId);
|
|
2707
|
+
};
|
|
2708
|
+
/**
|
|
2709
|
+
* Reveal-and-seek for a row folded behind the recent-rows window (the
|
|
2710
|
+
* rail's tick for an old turn, the doc's revealAndSeekRow): expand the
|
|
2711
|
+
* fold first, then the ordinary seek takes over — the completion effect
|
|
2712
|
+
* below force-mounts the row and scrollToElement lands it once its ref
|
|
2713
|
+
* (and Yoga top) exist. The fold toggle is idempotent, so calling this
|
|
2714
|
+
* for an already-revealed row is harmless.
|
|
2715
|
+
*/
|
|
2716
|
+
const revealAndSeekRow = (rowId) => {
|
|
2717
|
+
if (!showAllMessages)
|
|
2718
|
+
setShowAllMessages(true);
|
|
2719
|
+
seekRow(rowId);
|
|
2720
|
+
};
|
|
2721
|
+
React.useLayoutEffect(() => {
|
|
2722
|
+
if (forceMountRowId === null)
|
|
2723
|
+
return;
|
|
2724
|
+
const el = rowRefsRef.current.get(forceMountRowId);
|
|
2725
|
+
if (el) {
|
|
2726
|
+
handle?.scrollToElement(el);
|
|
2727
|
+
// Clear deferred to a macrotask: clearing here would let React's
|
|
2728
|
+
// synchronous re-render narrow the virtualization window and unmount
|
|
2729
|
+
// the row BEFORE the renderer's deferred pass reads its Yoga top
|
|
2730
|
+
// (scrollAnchor processing runs in a microtask) — the seek would
|
|
2731
|
+
// silently no-op (detached anchor element).
|
|
2732
|
+
setTimeout(() => setForceMountRowId(null), 0);
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
// `/` transcript search: rows whose searchable text contains the query.
|
|
2736
|
+
// Computed per render — `channel.rows` is a live in-place array (see
|
|
2737
|
+
// selectableRows); a useMemo would freeze the match list at mount.
|
|
2738
|
+
const searchMatches = (() => {
|
|
2739
|
+
const q = searchQuery.toLowerCase();
|
|
2740
|
+
if (!q)
|
|
2741
|
+
return [];
|
|
2742
|
+
return channel.rows
|
|
2743
|
+
.map((row, index) => ({ row, index, text: searchableText(row).toLowerCase() }))
|
|
2744
|
+
.filter(m => m.text.includes(q));
|
|
2745
|
+
})();
|
|
2746
|
+
// Incsearch: highlight all matches (screen-space overlay) and keep the
|
|
2747
|
+
// current match row in view as the query changes.
|
|
2748
|
+
React.useEffect(() => {
|
|
2749
|
+
if (!searchActive)
|
|
2750
|
+
return;
|
|
2751
|
+
setHighlight(searchQuery);
|
|
2752
|
+
const count = searchMatches.length;
|
|
2753
|
+
setSearchCount(count);
|
|
2754
|
+
const current = Math.min(searchCurrent, Math.max(0, count - 1));
|
|
2755
|
+
setSearchCurrent(current);
|
|
2756
|
+
const target = searchMatches[current];
|
|
2757
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
|
|
2758
|
+
if (target) {
|
|
2759
|
+
seekRow(target.row.id);
|
|
2760
|
+
}
|
|
2761
|
+
}, [searchQuery, searchActive]);
|
|
2762
|
+
// n/N navigation: move the current match into view.
|
|
2763
|
+
React.useEffect(() => {
|
|
2764
|
+
if (!searchActive)
|
|
2765
|
+
return;
|
|
2766
|
+
const target = searchMatches[searchCurrent];
|
|
2767
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
|
|
2768
|
+
if (target) {
|
|
2769
|
+
seekRow(target.row.id);
|
|
2770
|
+
}
|
|
2771
|
+
}, [searchCurrent]);
|
|
2772
|
+
const enterSelection = () => {
|
|
2773
|
+
setSelectionActive(true);
|
|
2774
|
+
const last = selectableRows[selectableRows.length - 1];
|
|
2775
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: empty selectable list
|
|
2776
|
+
setSelectedId(last ? last.id : null);
|
|
2777
|
+
};
|
|
2778
|
+
const moveSelection = (delta) => {
|
|
2779
|
+
if (selectedId === null)
|
|
2780
|
+
return;
|
|
2781
|
+
const index = selectableRows.findIndex(row => row.id === selectedId);
|
|
2782
|
+
if (index < 0)
|
|
2783
|
+
return;
|
|
2784
|
+
const next = selectableRows[index + delta];
|
|
2785
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index
|
|
2786
|
+
if (next)
|
|
2787
|
+
setSelectedId(next.id);
|
|
2788
|
+
};
|
|
2789
|
+
// useCallback: these feed MessageList → MemoRow's shallow compare; fresh
|
|
2790
|
+
// closures each render would defeat every row's memo.
|
|
2791
|
+
const toggleRowExpanded = React.useCallback((rowId) => {
|
|
2792
|
+
setExpandedRows((previous) => {
|
|
2793
|
+
const next = new Set(previous);
|
|
2794
|
+
if (next.has(rowId))
|
|
2795
|
+
next.delete(rowId);
|
|
2796
|
+
else
|
|
2797
|
+
next.add(rowId);
|
|
2798
|
+
return next;
|
|
2799
|
+
});
|
|
2800
|
+
}, []);
|
|
2801
|
+
const toggleStreamView = React.useCallback((rowId) => {
|
|
2802
|
+
setStreamViewToggledRows((previous) => {
|
|
2803
|
+
const next = new Set(previous);
|
|
2804
|
+
if (next.has(rowId))
|
|
2805
|
+
next.delete(rowId);
|
|
2806
|
+
else
|
|
2807
|
+
next.add(rowId);
|
|
2808
|
+
return next;
|
|
2809
|
+
});
|
|
2810
|
+
}, []);
|
|
2811
|
+
const registerRowRef = React.useCallback((rowId, el) => {
|
|
2812
|
+
if (el)
|
|
2813
|
+
rowRefsRef.current.set(rowId, el);
|
|
2814
|
+
else
|
|
2815
|
+
rowRefsRef.current.delete(rowId);
|
|
2816
|
+
}, []);
|
|
2817
|
+
/** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
|
|
2818
|
+
const lastModalEnterAtRef = React.useRef(0);
|
|
2819
|
+
useInput((input, key, event) => {
|
|
2820
|
+
// Prompt-slot panels own the keyboard while visible. Their own useInput
|
|
2821
|
+
// handles the relevant keys; Chat registered first, so yielding here
|
|
2822
|
+
// still lets the panel receive them. PromptInput now stays mounted but
|
|
2823
|
+
// suspended to preserve async command drafts, making this guard also
|
|
2824
|
+
// essential for Ctrl+C: it must never clear the hidden composer.
|
|
2825
|
+
if (btw !== null
|
|
2826
|
+
|| overlay.kind === 'tips'
|
|
2827
|
+
|| (recap !== null && (!recap.auto || recap.expanded)))
|
|
2828
|
+
return;
|
|
2829
|
+
// The session tree owns the whole terminal while it is up: plain letters
|
|
2830
|
+
// drive its search, clicks and Enter drive its action menu.
|
|
2831
|
+
if (treeOpen)
|
|
2832
|
+
return;
|
|
2833
|
+
// The session supervisor owns the whole terminal while it is up: its rail
|
|
2834
|
+
// and session list bind ↑/↓/Enter/Tab/Esc, its filter box takes the plain
|
|
2835
|
+
// letters that would otherwise reach the prompt, and the directory picker
|
|
2836
|
+
// and menus it opens are its own modal layers.
|
|
2837
|
+
if (supervisorOpen)
|
|
2838
|
+
return;
|
|
2839
|
+
// Same for the settings screen: plain letters (s save / d discard) and
|
|
2840
|
+
// the field draft editor belong to it alone.
|
|
2841
|
+
if (settingsOpen)
|
|
2842
|
+
return;
|
|
2843
|
+
// Subagent dashboard or detail scene: it owns the keyboard while open.
|
|
2844
|
+
if (subagentDashboardOpen || subagentDetailId !== null)
|
|
2845
|
+
return;
|
|
2846
|
+
// The `/jobs` panel replaces the conversation too, so it owns Esc (close)
|
|
2847
|
+
// and k (kill) while open. Unguarded, Esc meant to CLOSE the panel also
|
|
2848
|
+
// reached the chat:cancel branch below whenever a turn was in flight —
|
|
2849
|
+
// dismissing the panel and killing the turn with one key.
|
|
2850
|
+
if (jobsPanelOpen)
|
|
2851
|
+
return;
|
|
2852
|
+
// A plugin scene (dsh-tui-scenes) or the trajectory scene owns the whole
|
|
2853
|
+
// screen while open: every key belongs to it. Unguarded, an Esc meant to
|
|
2854
|
+
// CLOSE the scene also reached the chat:cancel branch below whenever a
|
|
2855
|
+
// turn was in flight — closing the view and killing the turn in one key.
|
|
2856
|
+
if (sceneOpen || channel.pluginScene !== undefined)
|
|
2857
|
+
return;
|
|
2858
|
+
// Mouse wheel scrolls the transcript even while a question/approval/
|
|
2859
|
+
// dialog panel is open — those panels own arrow/Enter/Esc keys, but the
|
|
2860
|
+
// transcript above them should still be scrollable in fullscreen mode.
|
|
2861
|
+
//
|
|
2862
|
+
// Wheel routing is position-first: events landing over a ScrollBox
|
|
2863
|
+
// (transcript, help, subagent panels…) are consumed by that box in
|
|
2864
|
+
// App's input batch (onWheelAt) and never reach this branch. What
|
|
2865
|
+
// arrives here is the fallback: wheel over non-scroll areas (prompt,
|
|
2866
|
+
// status bar) or over floating overlays.
|
|
2867
|
+
// - Help stays yielded: PromptInput's help ScrollBox handles the
|
|
2868
|
+
// remaining global wheel while help is open (both covered layers
|
|
2869
|
+
// must not move).
|
|
2870
|
+
// - Pickers/dialogs are modal: wheel that fell through over them
|
|
2871
|
+
// must NOT scroll the transcript behind (the audit's
|
|
2872
|
+
// pass-through gap), so yield like the keyboard guards above.
|
|
2873
|
+
// Events only arrive with mouse tracking on; inline mode never sees
|
|
2874
|
+
// them, so this is a no-op there.
|
|
2875
|
+
if (key.wheelUp || key.wheelDown) {
|
|
2876
|
+
if (helpOpen)
|
|
2877
|
+
return;
|
|
2878
|
+
// Any open transient dialog is modal to the wheel; the one exception
|
|
2879
|
+
// mirrors the render gate — a workspace picker whose target list has
|
|
2880
|
+
// not landed paints nothing, so wheel-through keeps scrolling.
|
|
2881
|
+
const overlayModal = overlay.kind !== 'none' &&
|
|
2882
|
+
(overlay.kind !== 'workspace-picker' || workspaceTargets.length > 0);
|
|
2883
|
+
if (overlayModal)
|
|
2884
|
+
return;
|
|
2885
|
+
handle?.scrollBy(key.wheelUp ? -3 : 3);
|
|
2886
|
+
event.stopImmediatePropagation();
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
// PgUp/PgDn page the transcript a full viewport at a time — the keyboard
|
|
2890
|
+
// counterpart of the wheel branch above. Without it, a fullscreen session
|
|
2891
|
+
// has no keyboard route to scrollback at all: the alt screen holds no
|
|
2892
|
+
// native scrollback (see MessageList's historyPaint gate), so a mouse-less
|
|
2893
|
+
// user cannot reach an earlier turn.
|
|
2894
|
+
//
|
|
2895
|
+
// Fullscreen only, on purpose. Inline mode paints committed history onto
|
|
2896
|
+
// the main screen, so the terminal's OWN scrollback owns these keys there;
|
|
2897
|
+
// claiming them would break paging that already works, exactly like the
|
|
2898
|
+
// wheel branch above is a no-op inline.
|
|
2899
|
+
//
|
|
2900
|
+
// Routing mirrors the wheel branch: help stays yielded (PromptInput pages
|
|
2901
|
+
// its help viewport with the same keys) and open pickers/dialogs are modal,
|
|
2902
|
+
// so the transcript behind them must not move. Every guard above (session
|
|
2903
|
+
// tree, settings, scenes, dashboards) already claimed the keyboard — those
|
|
2904
|
+
// surfaces page their own lists with these keys.
|
|
2905
|
+
//
|
|
2906
|
+
// The question/approval/dialog panels deliberately do NOT yield: like the
|
|
2907
|
+
// wheel branch above (whose comment spells this out), those panels mount
|
|
2908
|
+
// BELOW the transcript — replacing the prompt, not covering it — so the
|
|
2909
|
+
// transcript above them stays visible and scrollable while a decision is
|
|
2910
|
+
// pending. The panels bind ↑/↓/Space/Tab/Enter/Esc and never these keys,
|
|
2911
|
+
// so paging cannot steal anything from them.
|
|
2912
|
+
if ((key.pageUp || key.pageDown) && fullscreen) {
|
|
2913
|
+
if (helpOpen)
|
|
2914
|
+
return;
|
|
2915
|
+
const overlayModal = overlay.kind !== 'none' &&
|
|
2916
|
+
(overlay.kind !== 'workspace-picker' || workspaceTargets.length > 0);
|
|
2917
|
+
if (overlayModal)
|
|
2918
|
+
return;
|
|
2919
|
+
// One less than the viewport keeps a row of context so a page never
|
|
2920
|
+
// reads as a blank jump; a not-yet-measured handle falls back to a
|
|
2921
|
+
// fixed page rather than paging by 0 (a dead key). The final page
|
|
2922
|
+
// overshoots and the renderer clamps it exactly onto maxScroll, whose
|
|
2923
|
+
// positional at-bottom restore re-pins sticky (the #421/#422 wheel
|
|
2924
|
+
// contract) — so paging back home clears the new-messages pill too.
|
|
2925
|
+
const viewport = handle?.getViewportHeight() ?? 0;
|
|
2926
|
+
const page = viewport > 1 ? viewport - 1 : 12;
|
|
2927
|
+
handle?.scrollBy(key.pageUp ? -page : page);
|
|
2928
|
+
event.stopImmediatePropagation();
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
// Help is modal over Chat. Chat's listener registers before PromptInput's,
|
|
2932
|
+
// so yield every remaining key before any global/custom shortcut, search,
|
|
2933
|
+
// selection, or working-turn cancellation branch can mutate hidden state.
|
|
2934
|
+
// PromptInput then owns Esc, navigation, Tab guards, and ordinary typing.
|
|
2935
|
+
if (helpOpen)
|
|
2936
|
+
return;
|
|
2937
|
+
// The questionnaire / approval panel / managed plugin dialog owns the
|
|
2938
|
+
// keyboard while one is pending (the panel's own useInput handles
|
|
2939
|
+
// ↑/↓/Space/Tab/Enter/Esc; the prompt input is suspended, so nothing
|
|
2940
|
+
// else should see these keys).
|
|
2941
|
+
if (approvalSnapshot !== null || dialogSnapshot !== null)
|
|
2942
|
+
return;
|
|
2943
|
+
if (questionSnapshot !== null) {
|
|
2944
|
+
// Only transcript navigation belongs here. The mounted questionnaire
|
|
2945
|
+
// owns fold/expand keys, including when it interrupts another screen.
|
|
2946
|
+
if (questionMinimized && !isSticky && (isPlainReturnInput(input, key) || key.end)) {
|
|
2947
|
+
handle?.scrollToBottom();
|
|
2948
|
+
event.stopImmediatePropagation();
|
|
2949
|
+
}
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
const returnCandidate = isPlainReturnInput(input, key);
|
|
2953
|
+
const returnNow = Date.now();
|
|
2954
|
+
const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
|
|
2955
|
+
if (plainReturn)
|
|
2956
|
+
lastModalEnterAtRef.current = returnNow;
|
|
2957
|
+
if (overlay.kind === 'image-preview') {
|
|
2958
|
+
// Modal gallery owns plain left/right. Caret peeks below still leave
|
|
2959
|
+
// navigation with PromptInput. Esc/Ctrl+C/Enter keep their close semantics.
|
|
2960
|
+
if (key.escape || (key.ctrl && input === 'c') || plainReturn) {
|
|
2961
|
+
dispatchOverlay({ type: 'close' });
|
|
2962
|
+
}
|
|
2963
|
+
else if (!key.ctrl && !key.meta && !key.shift && (key.leftArrow || key.rightArrow)) {
|
|
2964
|
+
dispatchOverlay({ type: 'image-step', delta: key.leftArrow ? -1 : 1 });
|
|
2965
|
+
}
|
|
2966
|
+
event.stopImmediatePropagation();
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
if (peekPreview !== null && key.escape) {
|
|
2970
|
+
// Caret-driven preview: Esc dismisses it until the caret leaves the
|
|
2971
|
+
// token (PromptInput's own Esc arm normally gets there first; this is
|
|
2972
|
+
// the fallback when the prompt is not listening). Every other key
|
|
2973
|
+
// stays with the prompt, so the caret keeps moving (and the card
|
|
2974
|
+
// follows it) while the preview is up.
|
|
2975
|
+
dismissPeek();
|
|
2976
|
+
event.stopImmediatePropagation();
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
// Esc clears a settled mouse selection before the ordinary chat meanings
|
|
2980
|
+
// below, but never before a top-level modal. Otherwise a preview opened
|
|
2981
|
+
// over selected transcript text needed two Esc presses to close.
|
|
2982
|
+
// hasSelection() is an imperative read — no subscription needed.
|
|
2983
|
+
if (key.escape && hasMouseSelection()) {
|
|
2984
|
+
clearMouseSelection();
|
|
2985
|
+
event.stopImmediatePropagation();
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (overlay.kind === 'search') {
|
|
2989
|
+
// Transcript search bar (less-style): edit the query, Enter commits
|
|
2990
|
+
// (query persists for n/N), Esc/ctrl+c cancels back to the anchor.
|
|
2991
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
2992
|
+
dispatchOverlay({ type: 'close' });
|
|
2993
|
+
setHighlight('');
|
|
2994
|
+
handle?.scrollTo(searchAnchorRef.current);
|
|
2995
|
+
}
|
|
2996
|
+
else if (plainReturn) {
|
|
2997
|
+
// Enter commits; 0-match junk queries don't persist.
|
|
2998
|
+
if (searchCount === 0)
|
|
2999
|
+
setSearchQuery('');
|
|
3000
|
+
dispatchOverlay({ type: 'close' });
|
|
3001
|
+
}
|
|
3002
|
+
else if (key.backspace) {
|
|
3003
|
+
if (searchCursor > 0) {
|
|
3004
|
+
setSearchQuery(searchQuery.slice(0, searchCursor - 1) + searchQuery.slice(searchCursor));
|
|
3005
|
+
setSearchCursor(searchCursor - 1);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
else if (key.delete) {
|
|
3009
|
+
if (searchCursor < searchQuery.length) {
|
|
3010
|
+
setSearchQuery(searchQuery.slice(0, searchCursor) + searchQuery.slice(searchCursor + 1));
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
else if (key.leftArrow) {
|
|
3014
|
+
setSearchCursor(c => Math.max(0, c - 1));
|
|
3015
|
+
}
|
|
3016
|
+
else if (key.rightArrow) {
|
|
3017
|
+
setSearchCursor(c => Math.min(searchQuery.length, c + 1));
|
|
3018
|
+
}
|
|
3019
|
+
else if (key.home) {
|
|
3020
|
+
setSearchCursor(0);
|
|
3021
|
+
}
|
|
3022
|
+
else if (key.end) {
|
|
3023
|
+
setSearchCursor(searchQuery.length);
|
|
3024
|
+
}
|
|
3025
|
+
else if (!key.ctrl && !key.meta && !key.super && input) {
|
|
3026
|
+
const next = searchQuery.slice(0, searchCursor) + input + searchQuery.slice(searchCursor);
|
|
3027
|
+
setSearchQuery(next);
|
|
3028
|
+
setSearchCursor(searchCursor + input.length);
|
|
3029
|
+
}
|
|
3030
|
+
event.stopImmediatePropagation();
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
// After Enter closed the search bar, n/N keep walking the matches
|
|
3034
|
+
// The query persists across bar open/close so n/N keep working.
|
|
3035
|
+
// Transcript mode only — in prompt mode n/N are ordinary input chars.
|
|
3036
|
+
if (expanded && input === 'n' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta && !key.super) {
|
|
3037
|
+
setSearchCurrent(i => (i >= searchCount - 1 ? 0 : i + 1));
|
|
3038
|
+
event.stopImmediatePropagation();
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (expanded && input === 'N' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta && !key.super) {
|
|
3042
|
+
setSearchCurrent(i => (i <= 0 ? searchCount - 1 : i - 1));
|
|
3043
|
+
event.stopImmediatePropagation();
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
if (overlay.kind === 'thinking') {
|
|
3047
|
+
if (key.upArrow || key.downArrow) {
|
|
3048
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: 2 });
|
|
3049
|
+
}
|
|
3050
|
+
else if (plainReturn) {
|
|
3051
|
+
const visible = overlay.focus === 0;
|
|
3052
|
+
setThinkingVisible(visible);
|
|
3053
|
+
dispatchOverlay({ type: 'close' });
|
|
3054
|
+
channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
|
|
3055
|
+
}
|
|
3056
|
+
else if (key.escape) {
|
|
3057
|
+
dispatchOverlay({ type: 'close' });
|
|
3058
|
+
}
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
if (overlay.kind === 'workspace-flow') {
|
|
3062
|
+
const { flow, busy, input: flowInput } = overlay;
|
|
3063
|
+
if (key.escape) {
|
|
3064
|
+
if (flowInput !== null && !busy) {
|
|
3065
|
+
dispatchOverlay({ type: 'flow-input', input: null });
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
workspaceFlowAbortRef.current?.abort();
|
|
3069
|
+
workspaceFlowAbortRef.current = null;
|
|
3070
|
+
workspaceFlowRequestRef.current += 1;
|
|
3071
|
+
dispatchOverlay({ type: 'close' });
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
if (busy)
|
|
3075
|
+
return;
|
|
3076
|
+
if (flowInput !== null) {
|
|
3077
|
+
const choice = flow.choices.find(candidate => candidate.id === flowInput.choiceId);
|
|
3078
|
+
const editor = choice?.input;
|
|
3079
|
+
if (plainReturn) {
|
|
3080
|
+
const value = flowInput.value.trim();
|
|
3081
|
+
if (value.length === 0) {
|
|
3082
|
+
channel.notify(t('workspace-flow-input-empty'), { color: 'warning' });
|
|
3083
|
+
}
|
|
3084
|
+
else if (editor !== undefined) {
|
|
3085
|
+
runWorkspaceFlowAction(signal => editor.submit(value, signal));
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
else if (key.backspace && flowInput.cursor > 0) {
|
|
3089
|
+
dispatchOverlay({
|
|
3090
|
+
type: 'flow-input-edit',
|
|
3091
|
+
value: flowInput.value.slice(0, flowInput.cursor - 1) + flowInput.value.slice(flowInput.cursor),
|
|
3092
|
+
cursor: flowInput.cursor - 1,
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
else if (key.delete && flowInput.cursor < flowInput.value.length) {
|
|
3096
|
+
dispatchOverlay({
|
|
3097
|
+
type: 'flow-input-edit',
|
|
3098
|
+
value: flowInput.value.slice(0, flowInput.cursor) + flowInput.value.slice(flowInput.cursor + 1),
|
|
3099
|
+
cursor: flowInput.cursor,
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
else if (key.leftArrow) {
|
|
3103
|
+
dispatchOverlay({
|
|
3104
|
+
type: 'flow-input-edit',
|
|
3105
|
+
value: flowInput.value,
|
|
3106
|
+
cursor: Math.max(0, flowInput.cursor - 1),
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
else if (key.rightArrow) {
|
|
3110
|
+
dispatchOverlay({
|
|
3111
|
+
type: 'flow-input-edit',
|
|
3112
|
+
value: flowInput.value,
|
|
3113
|
+
cursor: Math.min(flowInput.value.length, flowInput.cursor + 1),
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
else if (input.length > 0 && !key.ctrl && !key.meta && !key.super && !key.tab) {
|
|
3117
|
+
dispatchOverlay({
|
|
3118
|
+
type: 'flow-input-edit',
|
|
3119
|
+
value: flowInput.value.slice(0, flowInput.cursor) + input + flowInput.value.slice(flowInput.cursor),
|
|
3120
|
+
cursor: flowInput.cursor + input.length,
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
if (key.upArrow || key.downArrow) {
|
|
3126
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: flow.choices.length });
|
|
3127
|
+
}
|
|
3128
|
+
else if (key.tab && !key.shift) {
|
|
3129
|
+
const choice = flow.choices[overlay.index];
|
|
3130
|
+
if (choice?.input !== undefined) {
|
|
3131
|
+
const value = choice.input.initialValue ?? '';
|
|
3132
|
+
const flowInputNext = {
|
|
3133
|
+
choiceId: choice.id,
|
|
3134
|
+
value,
|
|
3135
|
+
cursor: value.length,
|
|
3136
|
+
...(choice.input.placeholder === undefined ? {} : { placeholder: choice.input.placeholder }),
|
|
3137
|
+
};
|
|
3138
|
+
dispatchOverlay({ type: 'flow-input', input: flowInputNext });
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
else if (plainReturn) {
|
|
3142
|
+
const choice = flow.choices[overlay.index];
|
|
3143
|
+
if (choice !== undefined) {
|
|
3144
|
+
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
if (overlay.kind === 'workspace-picker') {
|
|
3150
|
+
if (key.upArrow || key.downArrow) {
|
|
3151
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: workspaceTargets.length });
|
|
3152
|
+
}
|
|
3153
|
+
else if (plainReturn) {
|
|
3154
|
+
const target = workspaceTargets[overlay.index];
|
|
3155
|
+
dispatchOverlay({ type: 'close' });
|
|
3156
|
+
if (target !== undefined)
|
|
3157
|
+
void channel.switchWorkspace(target);
|
|
3158
|
+
}
|
|
3159
|
+
else if (key.escape) {
|
|
3160
|
+
dispatchOverlay({ type: 'close' });
|
|
3161
|
+
}
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (overlay.kind === 'workspace-menu') {
|
|
3165
|
+
const menu = workspaceMenuOptions;
|
|
3166
|
+
if (key.upArrow || key.downArrow) {
|
|
3167
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: menu.length });
|
|
3168
|
+
}
|
|
3169
|
+
else if (plainReturn) {
|
|
3170
|
+
const option = menu[overlay.index];
|
|
3171
|
+
runWorkspaceMenuOption(option);
|
|
3172
|
+
}
|
|
3173
|
+
else if (key.escape) {
|
|
3174
|
+
dispatchOverlay({ type: 'close' });
|
|
3175
|
+
}
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
if (overlay.kind === 'model') {
|
|
3179
|
+
// Two-level picker: group rows at the top (Enter drills in), one
|
|
3180
|
+
// provider's models below (Enter switches, the same live-fork path as
|
|
3181
|
+
// the flat picker always had). Esc/⌫ climbs one level and only closes
|
|
3182
|
+
// at the top; a single-group catalog never shows the group level, so
|
|
3183
|
+
// Esc there closes directly.
|
|
3184
|
+
const rowCount = activeModelGroup === undefined ? modelGroups.length : groupModels.length;
|
|
3185
|
+
if (key.upArrow || key.downArrow) {
|
|
3186
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rowCount });
|
|
3187
|
+
}
|
|
3188
|
+
else if (plainReturn) {
|
|
3189
|
+
if (activeModelGroup === undefined) {
|
|
3190
|
+
const group = modelGroups[overlay.index];
|
|
3191
|
+
if (!group) {
|
|
3192
|
+
dispatchOverlay({ type: 'close' });
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
setModelGroup(group.provider);
|
|
3196
|
+
// The recents group opens on its most-recent entry; a provider
|
|
3197
|
+
// group on its current model when it owns one, else its first row.
|
|
3198
|
+
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
3199
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
|
|
3203
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
const model = groupModels[overlay.index];
|
|
3207
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
3208
|
+
if (model) {
|
|
3209
|
+
// Enter switches the live model right away: the conversation is
|
|
3210
|
+
// forked at its end and continued with an agent routed to the new
|
|
3211
|
+
// model (history replays unchanged) — and feeds the recents group.
|
|
3212
|
+
dispatchOverlay({ type: 'close' });
|
|
3213
|
+
void switchModelRecorded(model.provider, model.id, model.name);
|
|
3214
|
+
}
|
|
3215
|
+
else {
|
|
3216
|
+
dispatchOverlay({ type: 'close' });
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
else if (key.escape || key.backspace) {
|
|
3220
|
+
if (activeModelGroup !== undefined && modelGroups.length > 1 && !modelPickerDirect) {
|
|
3221
|
+
setModelGroup(undefined);
|
|
3222
|
+
const groupIndex = Math.max(0, modelGroups.findIndex(group => group.provider === activeModelGroup));
|
|
3223
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: groupIndex });
|
|
3224
|
+
}
|
|
3225
|
+
else {
|
|
3226
|
+
dispatchOverlay({ type: 'close' });
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
if (overlay.kind === 'skills') {
|
|
3232
|
+
const list = skillsList ?? [];
|
|
3233
|
+
if (key.upArrow || key.downArrow) {
|
|
3234
|
+
// count 0 (snapshot still loading) is a no-op inside the reducer.
|
|
3235
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: list.length });
|
|
3236
|
+
}
|
|
3237
|
+
else if (plainReturn) {
|
|
3238
|
+
const skill = list[overlay.index];
|
|
3239
|
+
dispatchOverlay({ type: 'close' });
|
|
3240
|
+
// 可直调技能 Enter 填入 `/name `——与 / 菜单选中技能同一条
|
|
3241
|
+
// completion-only 分发路径;模型专用技能(userInvocable=false)只关闭。
|
|
3242
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
3243
|
+
if (skill?.userInvocable)
|
|
3244
|
+
setHistoryFill(`/${skill.name} `);
|
|
3245
|
+
}
|
|
3246
|
+
else if (key.escape) {
|
|
3247
|
+
dispatchOverlay({ type: 'close' });
|
|
3248
|
+
}
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
// Armed migration hint (PRD #4): bare Enter while the hint notification
|
|
3252
|
+
// is up — no overlay and an EMPTY prompt — jumps into the picker with the
|
|
3253
|
+
// hinted source pre-checked; every other key disarms silently. The empty
|
|
3254
|
+
// check is explicit because the composer owns that state: an Enter that
|
|
3255
|
+
// submitted a written message must not also open the picker, and this
|
|
3256
|
+
// listener runs before PromptInput's, so the event is consumed here too.
|
|
3257
|
+
if (migrateHintAgent !== null && overlay.kind === 'none') {
|
|
3258
|
+
if (plainReturn && !(promptControllerRef.current?.hasText() ?? false)) {
|
|
3259
|
+
const agent = migrateHintAgent;
|
|
3260
|
+
event.stopImmediatePropagation();
|
|
3261
|
+
setMigrateHintAgent(null);
|
|
3262
|
+
setMigrateRows(null);
|
|
3263
|
+
setMigrateChecked(new Set([agent]));
|
|
3264
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
3265
|
+
void collectMigrateRows().then(setMigrateRows);
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
setMigrateHintAgent(null);
|
|
3269
|
+
}
|
|
3270
|
+
if (overlay.kind === 'migrate') {
|
|
3271
|
+
const rows = migrateRows ?? [];
|
|
3272
|
+
if (key.upArrow || key.downArrow) {
|
|
3273
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rows.length });
|
|
3274
|
+
}
|
|
3275
|
+
else if (key.escape) {
|
|
3276
|
+
dispatchOverlay({ type: 'close' });
|
|
3277
|
+
}
|
|
3278
|
+
else if (rows.length > 0) {
|
|
3279
|
+
const row = rows[overlay.index];
|
|
3280
|
+
if (input === ' ' && row !== undefined) {
|
|
3281
|
+
setMigrateChecked(current => {
|
|
3282
|
+
const next = new Set(current);
|
|
3283
|
+
if (next.has(row.agentId))
|
|
3284
|
+
next.delete(row.agentId);
|
|
3285
|
+
else
|
|
3286
|
+
next.add(row.agentId);
|
|
3287
|
+
return next;
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
else if (input === 'a' && !key.ctrl && !key.meta) {
|
|
3291
|
+
// All/none toggle: a checked-everything state collapses to none.
|
|
3292
|
+
setMigrateChecked(current => current.size >= rows.length ? new Set() : new Set(rows.map(candidate => candidate.agentId)));
|
|
3293
|
+
}
|
|
3294
|
+
else if (plainReturn) {
|
|
3295
|
+
// Checked set wins; the focused row acts as a single selection
|
|
3296
|
+
// when nothing is checked (PRD #1).
|
|
3297
|
+
const chosen = migrateChecked.size > 0
|
|
3298
|
+
? rows.filter(candidate => migrateChecked.has(candidate.agentId))
|
|
3299
|
+
: row !== undefined ? [row] : [];
|
|
3300
|
+
if (chosen.length > 0) {
|
|
3301
|
+
setMigratePending(chosen);
|
|
3302
|
+
dispatchOverlay({ type: 'close' });
|
|
3303
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
if (overlay.kind === 'migrate-confirm') {
|
|
3310
|
+
if (key.escape) {
|
|
3311
|
+
// Back to the picker with the checked set preserved (PRD #2's
|
|
3312
|
+
// "cancel" reads cheapest as "let me change the selection").
|
|
3313
|
+
dispatchOverlay({ type: 'close' });
|
|
3314
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
|
|
3315
|
+
}
|
|
3316
|
+
else if (plainReturn) {
|
|
3317
|
+
dispatchOverlay({ type: 'close' });
|
|
3318
|
+
spawnMigrateSources(migratePending, false);
|
|
3319
|
+
}
|
|
3320
|
+
else if (input === 'd' && !key.ctrl && !key.meta) {
|
|
3321
|
+
dispatchOverlay({ type: 'close' });
|
|
3322
|
+
spawnMigrateSources(migratePending, true);
|
|
3323
|
+
}
|
|
3324
|
+
return;
|
|
3325
|
+
}
|
|
3326
|
+
if (overlay.kind === 'activity') {
|
|
3327
|
+
if (key.upArrow || key.downArrow) {
|
|
3328
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: PRESET_NAMES.length });
|
|
3329
|
+
}
|
|
3330
|
+
else if (plainReturn) {
|
|
3331
|
+
const name = PRESET_NAMES[overlay.index];
|
|
3332
|
+
dispatchOverlay({ type: 'close' });
|
|
3333
|
+
if (name)
|
|
3334
|
+
channel.setActivityFrames(name);
|
|
3335
|
+
}
|
|
3336
|
+
else if (key.escape) {
|
|
3337
|
+
dispatchOverlay({ type: 'close' });
|
|
3338
|
+
}
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
if (overlay.kind === 'color') {
|
|
3342
|
+
if (key.upArrow || key.downArrow) {
|
|
3343
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: SESSION_COLOR_NAMES.length });
|
|
3344
|
+
}
|
|
3345
|
+
else if (plainReturn) {
|
|
3346
|
+
const name = SESSION_COLOR_NAMES[overlay.index];
|
|
3347
|
+
dispatchOverlay({ type: 'close' });
|
|
3348
|
+
if (name) {
|
|
3349
|
+
channel.setSessionColor(name);
|
|
3350
|
+
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
else if (key.escape) {
|
|
3354
|
+
dispatchOverlay({ type: 'close' });
|
|
3355
|
+
}
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
if (overlay.kind === 'effort') {
|
|
3359
|
+
if (key.leftArrow || key.rightArrow) {
|
|
3360
|
+
const delta = key.leftArrow ? -1 : 1;
|
|
3361
|
+
// The same wrap rule the reducer applies — computed here too so the
|
|
3362
|
+
// newly focused level is applied in this very keystroke.
|
|
3363
|
+
const next = wrapIndex(overlay.index, delta, effortOptions.length);
|
|
3364
|
+
dispatchOverlay({ type: 'move', delta, count: effortOptions.length });
|
|
3365
|
+
const option = effortOptions[next];
|
|
3366
|
+
// Live-apply: the slider IS the control; Esc does not revert.
|
|
3367
|
+
if (option)
|
|
3368
|
+
void channel.setEffort(option.id);
|
|
3369
|
+
}
|
|
3370
|
+
else if (plainReturn || key.escape) {
|
|
3371
|
+
dispatchOverlay({ type: 'close' });
|
|
3372
|
+
}
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
if (overlay.kind === 'preset') {
|
|
3376
|
+
if (key.upArrow || key.downArrow) {
|
|
3377
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: presetOptions.length });
|
|
3378
|
+
}
|
|
3379
|
+
else if (plainReturn) {
|
|
3380
|
+
const option = presetOptions[overlay.index];
|
|
3381
|
+
dispatchOverlay({ type: 'close' });
|
|
3382
|
+
if (option)
|
|
3383
|
+
void channel.switchPreset(option.id);
|
|
3384
|
+
}
|
|
3385
|
+
else if (key.escape) {
|
|
3386
|
+
dispatchOverlay({ type: 'close' });
|
|
3387
|
+
}
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
if (overlay.kind === 'permission') {
|
|
3391
|
+
if (key.upArrow || key.downArrow) {
|
|
3392
|
+
const currentIndex = permissionOverlayFocusRef.current?.overlay === overlay
|
|
3393
|
+
? permissionOverlayFocusRef.current.index
|
|
3394
|
+
: overlay.index;
|
|
3395
|
+
const nextIndex = wrapIndex(currentIndex, key.upArrow ? -1 : 1, overlay.snapshot.options.length);
|
|
3396
|
+
permissionOverlayFocusRef.current = { overlay, index: nextIndex };
|
|
3397
|
+
dispatchOverlay({ type: 'set-index', kind: 'permission', index: nextIndex });
|
|
3398
|
+
}
|
|
3399
|
+
else if (plainReturn) {
|
|
3400
|
+
const currentIndex = permissionOverlayFocusRef.current?.overlay === overlay
|
|
3401
|
+
? permissionOverlayFocusRef.current.index
|
|
3402
|
+
: overlay.index;
|
|
3403
|
+
const option = overlay.snapshot.options[currentIndex];
|
|
3404
|
+
permissionOverlayFocusRef.current = null;
|
|
3405
|
+
dispatchOverlay({ type: 'close' });
|
|
3406
|
+
if (option !== undefined)
|
|
3407
|
+
void runPermissionCommand(` ${option.value}`);
|
|
3408
|
+
}
|
|
3409
|
+
else if (key.escape) {
|
|
3410
|
+
permissionOverlayFocusRef.current = null;
|
|
3411
|
+
dispatchOverlay({ type: 'close' });
|
|
3412
|
+
}
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
if (overlay.kind === 'plan') {
|
|
3416
|
+
if (key.upArrow || key.downArrow) {
|
|
3417
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: 2 });
|
|
3418
|
+
}
|
|
3419
|
+
else if (plainReturn) {
|
|
3420
|
+
const on = overlay.index === 0;
|
|
3421
|
+
dispatchOverlay({ type: 'close' });
|
|
3422
|
+
void runExternalCommand('plan', on ? '' : ' off');
|
|
3423
|
+
}
|
|
3424
|
+
else if (key.escape) {
|
|
3425
|
+
dispatchOverlay({ type: 'close' });
|
|
3426
|
+
}
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
if (overlay.kind === 'lang') {
|
|
3430
|
+
if (key.upArrow || key.downArrow) {
|
|
3431
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: 2 });
|
|
3432
|
+
}
|
|
3433
|
+
else if (plainReturn) {
|
|
3434
|
+
const lang = LANGS[overlay.index];
|
|
3435
|
+
dispatchOverlay({ type: 'close' });
|
|
3436
|
+
if (lang !== undefined)
|
|
3437
|
+
applyLang(lang);
|
|
3438
|
+
}
|
|
3439
|
+
else if (key.escape) {
|
|
3440
|
+
dispatchOverlay({ type: 'close' });
|
|
3441
|
+
}
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
if (overlay.kind === 'theme') {
|
|
3445
|
+
const options = getThemeOptions(themeHost);
|
|
3446
|
+
if (key.upArrow || key.downArrow) {
|
|
3447
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: options.length });
|
|
3448
|
+
}
|
|
3449
|
+
else if (plainReturn) {
|
|
3450
|
+
dispatchOverlay({ type: 'close' });
|
|
3451
|
+
const name = options[overlay.index]?.value;
|
|
3452
|
+
if (name !== undefined) {
|
|
3453
|
+
const ok = setTheme(name);
|
|
3454
|
+
channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
else if (key.escape) {
|
|
3458
|
+
dispatchOverlay({ type: 'close' });
|
|
3459
|
+
}
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
if (overlay.kind === 'history') {
|
|
3463
|
+
const { query, cursor, focus } = overlay;
|
|
3464
|
+
if (key.escape) {
|
|
3465
|
+
dispatchOverlay({ type: 'close' });
|
|
3466
|
+
}
|
|
3467
|
+
else if (key.ctrl && (input === 'c' || input === 'd')) {
|
|
3468
|
+
// History search cancels on ctrl+c/ctrl+d too.
|
|
3469
|
+
dispatchOverlay({ type: 'close' });
|
|
3470
|
+
}
|
|
3471
|
+
else if (plainReturn) {
|
|
3472
|
+
const entry = historyMatches[focus];
|
|
3473
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty match list
|
|
3474
|
+
if (entry) {
|
|
3475
|
+
setHistoryFill(entry.text);
|
|
3476
|
+
dispatchOverlay({ type: 'close' });
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
else if (key.upArrow) {
|
|
3480
|
+
if (historyMatches.length > 0) {
|
|
3481
|
+
dispatchOverlay({ type: 'move', delta: -1, count: historyMatches.length });
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
else if (key.downArrow || actionMatches('history', input, key)) {
|
|
3485
|
+
// History search next — ↓ and the history key (default Ctrl+R)
|
|
3486
|
+
// walk to the next match.
|
|
3487
|
+
if (historyMatches.length > 0) {
|
|
3488
|
+
dispatchOverlay({ type: 'move', delta: 1, count: historyMatches.length });
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
else if (key.backspace) {
|
|
3492
|
+
if (cursor > 0) {
|
|
3493
|
+
dispatchOverlay({
|
|
3494
|
+
type: 'history-edit',
|
|
3495
|
+
query: query.slice(0, cursor - 1) + query.slice(cursor),
|
|
3496
|
+
cursor: cursor - 1,
|
|
3497
|
+
focus: 0,
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
else if (key.delete) {
|
|
3502
|
+
if (cursor < query.length) {
|
|
3503
|
+
dispatchOverlay({
|
|
3504
|
+
type: 'history-edit',
|
|
3505
|
+
query: query.slice(0, cursor) + query.slice(cursor + 1),
|
|
3506
|
+
focus: 0,
|
|
3507
|
+
});
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
else if (key.leftArrow) {
|
|
3511
|
+
// Step by code point, not UTF-16 unit: an emoji is two units, and
|
|
3512
|
+
// a mid-pair caret offset would split it in the SearchBox render.
|
|
3513
|
+
if (cursor > 0) {
|
|
3514
|
+
const ch = [...query.slice(0, cursor)].pop();
|
|
3515
|
+
dispatchOverlay({ type: 'history-edit', cursor: cursor - ch.length });
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
else if (key.rightArrow) {
|
|
3519
|
+
if (cursor < query.length) {
|
|
3520
|
+
const ch = [...query.slice(cursor)][0];
|
|
3521
|
+
dispatchOverlay({ type: 'history-edit', cursor: cursor + ch.length });
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
else if (key.home) {
|
|
3525
|
+
dispatchOverlay({ type: 'history-edit', cursor: 0 });
|
|
3526
|
+
}
|
|
3527
|
+
else if (key.end) {
|
|
3528
|
+
dispatchOverlay({ type: 'history-edit', cursor: query.length });
|
|
3529
|
+
}
|
|
3530
|
+
else if (!key.ctrl && !key.meta && !key.super && input) {
|
|
3531
|
+
dispatchOverlay({
|
|
3532
|
+
type: 'history-edit',
|
|
3533
|
+
query: query.slice(0, cursor) + input + query.slice(cursor),
|
|
3534
|
+
cursor: cursor + input.length,
|
|
3535
|
+
focus: 0,
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
if (overlay.kind === 'rewind') {
|
|
3541
|
+
// While the plugin decision is in flight the picker is read-only;
|
|
3542
|
+
// Esc abandons the wait (the stale answer is dropped by the token).
|
|
3543
|
+
if (overlay.busy) {
|
|
3544
|
+
if (key.escape) {
|
|
3545
|
+
rewindRequestRef.current += 1;
|
|
3546
|
+
dispatchOverlay({ type: 'rewind-busy', busy: false });
|
|
3547
|
+
}
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (overlay.confirm !== null) {
|
|
3551
|
+
const row = overlay.confirm;
|
|
3552
|
+
if (overlay.modes !== null) {
|
|
3553
|
+
// Plugin offered modes: the confirm pane is a choice list —
|
|
3554
|
+
// option 0 is always the built-in conversation-only rewind.
|
|
3555
|
+
const optionCount = overlay.modes.length + 1;
|
|
3556
|
+
if (key.upArrow || key.downArrow) {
|
|
3557
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: optionCount });
|
|
3558
|
+
}
|
|
3559
|
+
else if (plainReturn) {
|
|
3560
|
+
const mode = overlay.modeIndex === 0 ? null : (overlay.modes[overlay.modeIndex - 1]?.id ?? null);
|
|
3561
|
+
dispatchOverlay({ type: 'close' });
|
|
3562
|
+
void performRewind(row, mode);
|
|
3563
|
+
}
|
|
3564
|
+
else if (key.escape) {
|
|
3565
|
+
dispatchOverlay({ type: 'rewind-back' });
|
|
3566
|
+
}
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
// Confirmation state: Enter rewinds, Esc backs out to the list.
|
|
3570
|
+
if (plainReturn) {
|
|
3571
|
+
dispatchOverlay({ type: 'close' });
|
|
3572
|
+
void performRewind(row);
|
|
3573
|
+
}
|
|
3574
|
+
else if (key.escape) {
|
|
3575
|
+
dispatchOverlay({ type: 'rewind-back' });
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
else if (key.upArrow || key.downArrow) {
|
|
3579
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rewindRows.length });
|
|
3580
|
+
}
|
|
3581
|
+
else if (plainReturn) {
|
|
3582
|
+
const row = rewindRows[overlay.index];
|
|
3583
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
3584
|
+
if (row)
|
|
3585
|
+
void requestRewindConfirm(row);
|
|
3586
|
+
}
|
|
3587
|
+
else if (key.escape) {
|
|
3588
|
+
dispatchOverlay({ type: 'close' });
|
|
3589
|
+
}
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
if (overlay.kind === 'file-actions') {
|
|
3593
|
+
// Click-to-act file menu: ↑/↓ move, Enter runs the focused action,
|
|
3594
|
+
// Esc closes.
|
|
3595
|
+
if (key.upArrow || key.downArrow) {
|
|
3596
|
+
dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: FILE_ACTION_COUNT });
|
|
3597
|
+
}
|
|
3598
|
+
else if (plainReturn) {
|
|
3599
|
+
const path = overlay.path;
|
|
3600
|
+
dispatchOverlay({ type: 'close' });
|
|
3601
|
+
runFileAction(overlay.index, path);
|
|
3602
|
+
}
|
|
3603
|
+
else if (key.escape) {
|
|
3604
|
+
dispatchOverlay({ type: 'close' });
|
|
3605
|
+
}
|
|
3606
|
+
return;
|
|
3607
|
+
}
|
|
3608
|
+
if (actionMatches('trajectory', input, key)) {
|
|
3609
|
+
// The trajectory scene key (default Ctrl+T) opens it at any point in
|
|
3610
|
+
// the session.
|
|
3611
|
+
openScene();
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
if (actionMatches('dashboard', input, key)) {
|
|
3615
|
+
// The subagent dashboard key (default Ctrl+A) opens the dashboard.
|
|
3616
|
+
// Consume the key: without the stop the prompt editor's readline
|
|
3617
|
+
// binding ALSO fires (Ctrl+A moves the caret to line start), so one
|
|
3618
|
+
// press both opens the overlay and jumps the cursor.
|
|
3619
|
+
setSubagentDashboardOpen(true);
|
|
3620
|
+
event.stopImmediatePropagation();
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
if (actionMatches('contextPanel', input, key) && loadedContextVisible) {
|
|
3624
|
+
// The loaded-context panel key (default Ctrl+P) toggles the startup
|
|
3625
|
+
// panel while it is on screen (transcript still empty); once rows take
|
|
3626
|
+
// over and the panel disappears the key has nothing left to do.
|
|
3627
|
+
toggleLoadedContext();
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3630
|
+
if (actionMatches('history', input, key) && !helpOpen) {
|
|
3631
|
+
setHistoryEntries(loadHistory());
|
|
3632
|
+
dispatchOverlay({
|
|
3633
|
+
type: 'open',
|
|
3634
|
+
overlay: { kind: 'history', query: '', cursor: 0, focus: 0 },
|
|
3635
|
+
});
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
if (key.shift && key.upArrow && !selectionActive && !helpOpen) {
|
|
3639
|
+
enterSelection();
|
|
3640
|
+
}
|
|
3641
|
+
else if (selectionActive) {
|
|
3642
|
+
if (key.upArrow) {
|
|
3643
|
+
moveSelection(-1);
|
|
3644
|
+
}
|
|
3645
|
+
else if (key.downArrow) {
|
|
3646
|
+
moveSelection(1);
|
|
3647
|
+
}
|
|
3648
|
+
else if (plainReturn && selectedId !== null) {
|
|
3649
|
+
toggleRowExpanded(selectedId);
|
|
3650
|
+
}
|
|
3651
|
+
else if (key.escape) {
|
|
3652
|
+
setSelectionActive(false);
|
|
3653
|
+
setSelectedId(null);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
else if (key.escape && channel.working && !helpOpen && !promptControllerRef.current?.vimActive()) {
|
|
3657
|
+
// Esc interrupts a running turn (the prompt input
|
|
3658
|
+
// only sees esc when idle, where it has the double-tap-clear meaning).
|
|
3659
|
+
// With messages queued for delivery, interrupt-and-deliver them right
|
|
3660
|
+
// away (Codex behavior); otherwise a plain interrupt parks the queue.
|
|
3661
|
+
// vim mode (either submode) yields: there Esc is a MODE key (INSERT→
|
|
3662
|
+
// NORMAL, NORMAL = no-op/cancel pending d) and the prompt owns it;
|
|
3663
|
+
// interrupting still works via Ctrl+C / Ctrl+Enter.
|
|
3664
|
+
if (channel.pending.length > 0) {
|
|
3665
|
+
const count = channel.interruptAndDeliver(channel.pending.map(item => ({
|
|
3666
|
+
text: item.text,
|
|
3667
|
+
images: item.images ?? [],
|
|
3668
|
+
})));
|
|
3669
|
+
if (count > 0) {
|
|
3670
|
+
channel.notify(t('interrupt-delivered', { n: count }), { timeoutMs: 2500 });
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
else {
|
|
3674
|
+
channel.cancel();
|
|
3675
|
+
}
|
|
3676
|
+
event.stopImmediatePropagation();
|
|
3677
|
+
}
|
|
3678
|
+
else if (actionMatches('transcript', input, key) && !helpOpen) {
|
|
3679
|
+
// Leaving transcript mode (default Ctrl+O) — search was already
|
|
3680
|
+
// handled above. Help is modal: toggling this state behind the
|
|
3681
|
+
// overlay is invisible, then the next `/` unexpectedly opens
|
|
3682
|
+
// transcript search instead of slash-command completion after Help
|
|
3683
|
+
// closes.
|
|
3684
|
+
setExpanded(previous => !previous);
|
|
3685
|
+
// The toggle rewrites every thinking row's layout at once. The
|
|
3686
|
+
// ordinary scroll-based diff pushes rows into terminal scrollback on
|
|
3687
|
+
// each expand and nothing removes them on collapse — rapid toggling
|
|
3688
|
+
// drifts the virtual↔scrollback mapping until writes misland
|
|
3689
|
+
// (garbled transcript, duplicated rows). Re-anchor the next frame:
|
|
3690
|
+
// in-place viewport repaint, nothing added to scrollback. Lookup
|
|
3691
|
+
// falls back to the only live instance for embedders whose stdout
|
|
3692
|
+
// isn't process.stdout (test harnesses).
|
|
3693
|
+
const ink = instances.get(process.stdout) ?? instances.values().next().value;
|
|
3694
|
+
ink?.reanchorViewport();
|
|
3695
|
+
}
|
|
3696
|
+
else if (input === '/' && !key.ctrl && !key.meta && !key.super && !helpOpen) {
|
|
3697
|
+
// `/` in transcript mode (Ctrl+O expanded):
|
|
3698
|
+
// search is active on the transcript screen where `/` isn't a command).
|
|
3699
|
+
if (expanded) {
|
|
3700
|
+
searchAnchorRef.current = handle?.getScrollTop() ?? 0;
|
|
3701
|
+
setSearchQuery('');
|
|
3702
|
+
setSearchCursor(0);
|
|
3703
|
+
setSearchCurrent(0);
|
|
3704
|
+
setSearchCount(0);
|
|
3705
|
+
dispatchOverlay({ type: 'open', overlay: { kind: 'search' } });
|
|
3706
|
+
event.stopImmediatePropagation();
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
else if (key.ctrl && (input === 'c' || input === 'd')) {
|
|
3710
|
+
// Ctrl+C interrupts a running turn; idle Ctrl+C
|
|
3711
|
+
// CLEARS a non-empty prompt (single press) and only arms the
|
|
3712
|
+
// double-press exit when the input is empty; ctrl+d keeps the
|
|
3713
|
+
// time-based double-press exit regardless.
|
|
3714
|
+
if (channel.working) {
|
|
3715
|
+
// First press while working only interrupts. If that abort is still
|
|
3716
|
+
// converging (cancelPending) the next press is the user insisting on
|
|
3717
|
+
// leaving: go straight to the exit funnel. Without this, a stuck turn
|
|
3718
|
+
// (long tool call that never settles, silent stream) swallows every
|
|
3719
|
+
// Ctrl+C forever — raw mode keeps the launcher's SIGINT escape
|
|
3720
|
+
// unreachable until the TUI exits.
|
|
3721
|
+
if (channel.cancelPending) {
|
|
3722
|
+
onExit();
|
|
3723
|
+
}
|
|
3724
|
+
else {
|
|
3725
|
+
channel.cancel();
|
|
3726
|
+
// Interrupt replaces any previously armed exit: the next press
|
|
3727
|
+
// must re-confirm instead of exiting out from under the turn.
|
|
3728
|
+
exitPendingRef.current = false;
|
|
3729
|
+
if (exitTimerRef.current)
|
|
3730
|
+
clearTimeout(exitTimerRef.current);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
else if (input === 'c' && promptControllerRef.current?.consumeSelectionCopy()) {
|
|
3734
|
+
// A mouse selection is active: Ctrl+C copies it to the clipboard
|
|
3735
|
+
// (via the prompt controller — Chat's listener registers first) and
|
|
3736
|
+
// KEEPS the selection for further editing. The key is consumed.
|
|
3737
|
+
exitPendingRef.current = false;
|
|
3738
|
+
if (exitTimerRef.current)
|
|
3739
|
+
clearTimeout(exitTimerRef.current);
|
|
3740
|
+
}
|
|
3741
|
+
else if (input === 'c' && promptControllerRef.current?.hasText()) {
|
|
3742
|
+
promptControllerRef.current.clear();
|
|
3743
|
+
// A pending exit arm no longer makes sense once the user is editing.
|
|
3744
|
+
exitPendingRef.current = false;
|
|
3745
|
+
if (exitTimerRef.current)
|
|
3746
|
+
clearTimeout(exitTimerRef.current);
|
|
3747
|
+
}
|
|
3748
|
+
else {
|
|
3749
|
+
requestExit();
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
else if (actionMatches('redraw', input, key)) {
|
|
3753
|
+
// Redraw (default Ctrl+L) — clear the physical terminal and
|
|
3754
|
+
// repaint.
|
|
3755
|
+
instances.get(process.stdout)?.forceRedraw();
|
|
3756
|
+
// Consume: same readline-shadowing rule as dashboard/showAll below.
|
|
3757
|
+
event.stopImmediatePropagation();
|
|
3758
|
+
}
|
|
3759
|
+
else if (actionMatches('showAll', input, key)) {
|
|
3760
|
+
setShowAllMessages(previous => !previous);
|
|
3761
|
+
// Ctrl+E is also the editor's line-end binding — stop the press from
|
|
3762
|
+
// additionally moving the caret (one press, one meaning).
|
|
3763
|
+
event.stopImmediatePropagation();
|
|
3764
|
+
}
|
|
3765
|
+
else if (actionMatches('todoFold', input, key)) {
|
|
3766
|
+
// Fold/unfold the GoalTodoPanel todo section (default Ctrl+Q) — works
|
|
3767
|
+
// mid-turn too: the collapsed line keeps the done/total count and the
|
|
3768
|
+
// live task preview, so long todo lists stop crowding the prompt.
|
|
3769
|
+
setTodoCollapsed(previous => !previous);
|
|
3770
|
+
// Consume: same readline-shadowing rule as dashboard/showAll above.
|
|
3771
|
+
event.stopImmediatePropagation();
|
|
3772
|
+
}
|
|
3773
|
+
else if (plainReturn && !isSticky) {
|
|
3774
|
+
// Enter while scrolled up returns to the bottom: the
|
|
3775
|
+
// affordance now exists whenever the view is off the bottom, not
|
|
3776
|
+
// only with unseen rows).
|
|
3777
|
+
handle?.scrollToBottom();
|
|
3778
|
+
}
|
|
3779
|
+
else if (key.end && !isSticky) {
|
|
3780
|
+
// End = jump to bottom, less/vim semantics. Global on the chat
|
|
3781
|
+
// screen (search/history overlays consume their own End first —
|
|
3782
|
+
// cursor-to-line-end there). At the bottom already: no-op, so the
|
|
3783
|
+
// key stays harmless in muscle memory.
|
|
3784
|
+
handle?.scrollToBottom();
|
|
3785
|
+
event.stopImmediatePropagation();
|
|
3786
|
+
}
|
|
3787
|
+
else if (extensionShortcuts !== undefined && extensionShortcuts.dispatch(input, key)) {
|
|
3788
|
+
// Plugin shortcut (tuiShortcuts seam): matched only after every
|
|
3789
|
+
// built-in global binding above declined — locals always win, and the
|
|
3790
|
+
// registry additionally refuses the prompt editor's own combos at
|
|
3791
|
+
// registration, so a plugin can never shadow anything. The handler
|
|
3792
|
+
// runs fire-and-forget; its errors arrive via the onError hook
|
|
3793
|
+
// (wired to the toast below).
|
|
3794
|
+
event.stopImmediatePropagation();
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
// Working-activity line (spinner slot): context-pressure prefix shares the
|
|
3798
|
+
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
3799
|
+
const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
|
|
3800
|
+
// ── Interrupt lane ─────────────────────────────────────────────────────
|
|
3801
|
+
// The approval and ask_user_question panels park the agent until the user
|
|
3802
|
+
// answers, but they render inside the conversation layout — every screen
|
|
3803
|
+
// early-return below (plugin scene, browser, settings, subagent, trace)
|
|
3804
|
+
// used to win over them, leaving the session stuck with no visible cause.
|
|
3805
|
+
// While one is pending and a screen is up, the panel takes the whole
|
|
3806
|
+
// terminal INSTEAD of the screen. The screen's open flag survives, so the
|
|
3807
|
+
// decision lands back on the screen (remounted fresh — the same lifecycle
|
|
3808
|
+
// as closing and reopening it); keyboard exclusivity holds because the
|
|
3809
|
+
// covered screen is unmounted, exactly like the chat-state prompt slot.
|
|
3810
|
+
// The panel elements are shared with the prompt-slot chain below so the
|
|
3811
|
+
// two mount sites cannot drift.
|
|
3812
|
+
const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, background: approvalSnapshot.agentId !== channel.agentId, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
|
|
3813
|
+
const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
|
|
3814
|
+
? draft => questionStore.backCurrent(draft)
|
|
3815
|
+
: undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
|
|
3816
|
+
const interruptPanel = approvalPanelNode ?? questionPanelNode;
|
|
3817
|
+
const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
|
|
3818
|
+
|| subagentDetailId !== null || subagentDashboardOpen || sceneOpen;
|
|
3819
|
+
if (interruptPanel !== null && screenOpen) {
|
|
3820
|
+
const node = (_jsx(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: interruptPanel }));
|
|
3821
|
+
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
3822
|
+
}
|
|
3823
|
+
// A plugin scene (dsh-tui-scenes) takes the whole terminal the same way
|
|
3824
|
+
// the trajectory scene does, and sits at the TOP of this return chain:
|
|
3825
|
+
// an open() landing while the session browser or the trajectory scene is
|
|
3826
|
+
// up must still take the screen (and the keyboard, via the useInput guard
|
|
3827
|
+
// above), not queue silently behind them. Closing the plugin scene lands
|
|
3828
|
+
// back on whatever screen was up before, so these early returns read as a
|
|
3829
|
+
// stack. The component comes from the registry, so its identity is stable
|
|
3830
|
+
// across renders and its hook state survives re-renders; it receives the
|
|
3831
|
+
// TUI's own React + ui kit because a plugin importing its own React copy
|
|
3832
|
+
// would die on the first hook call under this reconciler.
|
|
3833
|
+
// The scene is third-party code, so it renders inside a boundary: a render
|
|
3834
|
+
// crash reports to the transcript and closes the scene instead of taking
|
|
3835
|
+
// the whole TUI down through ink's app-level boundary.
|
|
3836
|
+
const pluginScene = channel.pluginScene;
|
|
3837
|
+
if (pluginScene !== undefined) {
|
|
3838
|
+
const node = (_jsx(PluginSceneBoundary, { id: pluginScene.id, onError: (id, error) => {
|
|
3839
|
+
channel.notify(t('plugin-scene-crashed', { id, err: error.message }), { color: 'error' });
|
|
3840
|
+
channel.closePluginScene();
|
|
3841
|
+
}, children: renderScene ? renderScene(pluginScene.id, channel) : _jsxs(Text, { children: ["Scene unavailable: ", pluginScene.id] }) }));
|
|
3842
|
+
return fullscreen ? node : _jsx(AlternateScreen, { children: node });
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3845
|
+
* The session supervisor: a screen in the same sense as the tree — an early
|
|
3846
|
+
* return after every hook above has run, so there is no transcript
|
|
3847
|
+
* underneath to repaint or bled through.
|
|
3848
|
+
*
|
|
3849
|
+
* It sits ABOVE the session tree because it is the surface a launch can
|
|
3850
|
+
* start on (`openHomeOnBoot`): a first launch has no conversation to come
|
|
3851
|
+
* back to, and every action it offers either mounts a session (which closes
|
|
3852
|
+
* it) or starts a new one.
|
|
3853
|
+
*
|
|
3854
|
+
* Behind it, every session this terminal hosts keeps running — that is the
|
|
3855
|
+
* runtime the screen describes, not an implementation detail of it. A turn
|
|
3856
|
+
* that was in flight when the user opened this screen is still in flight
|
|
3857
|
+
* while they read the list, which is why closing the screen only repaints
|
|
3858
|
+
* the transcript when the attached session actually changed.
|
|
3859
|
+
*/
|
|
3860
|
+
if (supervisorOpen) {
|
|
3861
|
+
/**
|
|
3862
|
+
* Live state per session, from the channel's own agent-view projection.
|
|
3863
|
+
* Reading the projection rather than a parallel source is what keeps this
|
|
3864
|
+
* screen and the attention hints in the composer footer from disagreeing
|
|
3865
|
+
* about which session is waiting for input.
|
|
3866
|
+
*/
|
|
3867
|
+
const agentRowOf = (sessionId) => agentViewRows.find(row => row.id === sessionId);
|
|
3868
|
+
const supervisorNode = (_jsx(SessionSupervisor, { channel: channel, home: homeDir(), onClose: closeHome, approval: approvalSnapshot, onApprove: outcome => approvals.decide(outcome), onOpenSession: async (sessionId) => {
|
|
3869
|
+
const result = await channel.resumeTo(sessionId);
|
|
3870
|
+
if (!result.ok) {
|
|
3871
|
+
const text = resumeFailureText(result);
|
|
3872
|
+
if (text !== undefined)
|
|
3873
|
+
channel.notify(text, { color: 'error', timeoutMs: 8000 });
|
|
3874
|
+
return false;
|
|
3875
|
+
}
|
|
3876
|
+
channel.notify(t('resume-resumed'));
|
|
3877
|
+
suppressLogoIntroRef.current = true;
|
|
3878
|
+
setAgentViewReturnId(undefined);
|
|
3879
|
+
setSupervisorOpen(false);
|
|
3880
|
+
repaintTranscript();
|
|
3881
|
+
return true;
|
|
3882
|
+
}, onNewSession: async (target) => {
|
|
3883
|
+
const ok = await channel.switchWorkspace(target);
|
|
3884
|
+
if (ok) {
|
|
3885
|
+
suppressLogoIntroRef.current = true;
|
|
3886
|
+
setAgentViewReturnId(undefined);
|
|
3887
|
+
setSupervisorOpen(false);
|
|
3888
|
+
repaintTranscript();
|
|
3889
|
+
}
|
|
3890
|
+
return ok;
|
|
3891
|
+
}, onStopSession: async (sessionId) => channel.stopBackgroundAgent?.(sessionId) ?? false, liveStateOf: (sessionId) => {
|
|
3892
|
+
const row = agentRowOf(sessionId);
|
|
3893
|
+
return row === undefined
|
|
3894
|
+
? undefined
|
|
3895
|
+
: { status: row.status, live: row.live, current: row.current, summary: row.summary };
|
|
3896
|
+
} }));
|
|
3897
|
+
// Inline hosts enter the alternate screen for the duration; full-screen
|
|
3898
|
+
// hosts are already in it and must not nest a second one.
|
|
3899
|
+
return fullscreen ? supervisorNode : _jsx(AlternateScreen, { children: supervisorNode });
|
|
3900
|
+
}
|
|
3901
|
+
// The session tree follows the browser's rule exactly: it REPLACES the
|
|
3902
|
+
// conversation (an early return after every hook above has run), so there
|
|
3903
|
+
// is no transcript underneath to be repainted or bled through. The dropped
|
|
3904
|
+
// turn's prompt returns through the same fill path a rewind picker uses.
|
|
3905
|
+
if (treeOpen) {
|
|
3906
|
+
const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
|
|
3907
|
+
// The tree rewound to a node and is handing that turn's prompt back,
|
|
3908
|
+
// exactly like the picker does. It belongs to the binding the tree
|
|
3909
|
+
// action just created.
|
|
3910
|
+
pendingFillRef.current = String(channel.agentId);
|
|
3911
|
+
setHistoryFill(text);
|
|
3912
|
+
} }));
|
|
3913
|
+
return fullscreen ? tree : _jsx(AlternateScreen, { children: tree });
|
|
3914
|
+
}
|
|
3915
|
+
// The settings screen follows the browser's rule exactly: it REPLACES the
|
|
3916
|
+
// conversation (an early return after every hook above has run), so there
|
|
3917
|
+
// is no transcript underneath to be repainted or bled through.
|
|
3918
|
+
if (settingsOpen) {
|
|
3919
|
+
const screen = _jsx(Settings, { channel: channel, onClose: () => setSettingsOpen(false) });
|
|
3920
|
+
return fullscreen ? screen : _jsx(AlternateScreen, { children: screen });
|
|
3921
|
+
}
|
|
3922
|
+
// Subagent detail scene: displays detailed view of a specific subagent.
|
|
3923
|
+
// Like the browser and settings, it replaces the conversation entirely.
|
|
3924
|
+
if (subagentDetailId !== null) {
|
|
3925
|
+
const subagent = channel.subagents.find(s => s.agentId === subagentDetailId);
|
|
3926
|
+
if (!subagent) {
|
|
3927
|
+
// Agent not found, go back to dashboard
|
|
3928
|
+
setSubagentDetailId(null);
|
|
3929
|
+
setSubagentDashboardOpen(true);
|
|
3930
|
+
return null;
|
|
3931
|
+
}
|
|
3932
|
+
const scene = (_jsx(SubagentDetailScene, { subagent: subagent, onInterrupt: (id) => channel.subagentControl.interrupt(id), onBack: () => {
|
|
3933
|
+
setSubagentDetailId(null);
|
|
3934
|
+
setSubagentDashboardOpen(true);
|
|
3935
|
+
} }));
|
|
3936
|
+
return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
|
|
3937
|
+
}
|
|
3938
|
+
// Jobs panel: background jobs (running/killed) with kill/inspect actions.
|
|
3939
|
+
// Like the browser and settings, it replaces the conversation entirely.
|
|
3940
|
+
if (jobsPanelOpen) {
|
|
3941
|
+
const panel = (_jsx(JobsPanel, { jobs: channel.backgroundJobs ?? [], onClose: () => setJobsPanelOpen(false), onKill: (id) => {
|
|
3942
|
+
// Stub channels (verify harnesses) have no jobControl — surface
|
|
3943
|
+
// the same failure toast as a refused kill instead of throwing.
|
|
3944
|
+
if (channel.jobControl?.kill(id) !== true) {
|
|
3945
|
+
channel.notify(t('jobs-kill-failed', { id }), { color: 'error' });
|
|
3946
|
+
}
|
|
3947
|
+
} }));
|
|
3948
|
+
return fullscreen ? panel : _jsx(AlternateScreen, { children: panel });
|
|
3949
|
+
}
|
|
3950
|
+
// Subagent dashboard: displays all active and completed subagents.
|
|
3951
|
+
// Like the browser and settings, it replaces the conversation entirely.
|
|
3952
|
+
if (subagentDashboardOpen) {
|
|
3953
|
+
const dashboard = (_jsx(SubagentDashboard, { subagents: [...channel.subagents], onSelect: (id) => {
|
|
3954
|
+
setSubagentDashboardOpen(false);
|
|
3955
|
+
setSubagentDetailId(id);
|
|
3956
|
+
}, onClose: () => setSubagentDashboardOpen(false) }));
|
|
3957
|
+
return fullscreen ? dashboard : _jsx(AlternateScreen, { children: dashboard });
|
|
3958
|
+
}
|
|
3959
|
+
/** Prompt input is inert while a modal dialog owns the keyboard. The
|
|
3960
|
+
* overlay union covers every picker/dialog and /tips in one check;
|
|
3961
|
+
* message-selection mode and the /btw panel live outside it. */
|
|
3962
|
+
const promptSelectionActive = selectionActive || overlay.kind !== 'none' || btw !== null;
|
|
3963
|
+
// These panels replace the visible composer, but PromptInput remains
|
|
3964
|
+
// mounted (suspended) so an async registry command cannot lose its exact
|
|
3965
|
+
// text/image draft while it waits for a user decision.
|
|
3966
|
+
const promptReplacementOpen = approvalPanelNode !== null
|
|
3967
|
+
|| dialogSnapshot !== null
|
|
3968
|
+
|| overlay.kind === 'tips'
|
|
3969
|
+
|| (recap !== null && (!recap.auto || recap.expanded))
|
|
3970
|
+
|| btw !== null
|
|
3971
|
+
|| questionPanelNode !== null;
|
|
3972
|
+
// The trajectory scene replaces the conversation for as long as it is open.
|
|
3973
|
+
// Rendering it INSTEAD of (not above) the transcript is what makes it a
|
|
3974
|
+
// screen rather than an overlay: it owns the full viewport, and the
|
|
3975
|
+
// conversation's own frame is never resized while it is up. Chat stays
|
|
3976
|
+
// mounted, so every hook above has already run and no state is lost.
|
|
3977
|
+
// `<AlternateScreen>` is skipped when the app is already fullscreen —
|
|
3978
|
+
// nesting it would emit a second DEC 1049, and its unmount would drop the
|
|
3979
|
+
// whole app back to the main screen.
|
|
3980
|
+
if (sceneOpen) {
|
|
3981
|
+
const scene = _jsx(TrajectoryScene, { channel: channel, build: trajectory, onClose: closeScene });
|
|
3982
|
+
return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
|
|
3983
|
+
}
|
|
3984
|
+
// 浮层整体挂载条件:与内部各面板的可见条件同值(数据门在
|
|
3985
|
+
// dialogOverlayVisible 里逐面板镜像)。关闭时把整个 absolute 浮层从树里
|
|
3986
|
+
// 移除——渲染器的"移除 absolute 节点"检测只看被移除子树自身的
|
|
3987
|
+
// style.position(dom.ts collectRemovedRects),若浮层常驻、只移除其
|
|
3988
|
+
// 普通子节点,blit 解毒不触发,被覆盖的转录行会在 blit-skip 后留空
|
|
3989
|
+
// (Esc 关 picker 一片空白的根因)。
|
|
3990
|
+
const dialogOverlayOpen = dialogOverlayVisible(overlay, {
|
|
3991
|
+
workspaceTargetCount: workspaceTargets.length,
|
|
3992
|
+
effortOptionCount: effortOptions.length,
|
|
3993
|
+
presetOptionCount: presetOptions.length,
|
|
3994
|
+
}) && !(overlay.kind === 'permission'
|
|
3995
|
+
&& (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
|
|
3996
|
+
// The sticky header pins the turn owning the viewport top row
|
|
3997
|
+
// (timeline.activeId, reported by MessageList) — scrolled up to an old
|
|
3998
|
+
// turn, it carries THAT turn's prompt, not the latest one.
|
|
3999
|
+
// channel.rows is a live in-place array, so the lookup is per-render.
|
|
4000
|
+
const anchorUserRowId = timeline.activeId;
|
|
4001
|
+
const anchorUserText = anchorUserRowId === null
|
|
4002
|
+
? null
|
|
4003
|
+
: channel.rows.find(row => row.id === anchorUserRowId)?.text ?? null;
|
|
4004
|
+
// Modal image preview, shared by the composer's [Image #N] tokens and the
|
|
4005
|
+
// transcript thumbnails. It normally lives INSIDE the transcript row, so
|
|
4006
|
+
// the card centers over the conversation and the sticky header, prompt
|
|
4007
|
+
// and status rows stay visible. While the fullscreen draft editor is open
|
|
4008
|
+
// it moves to the root, after PromptEditorLayer, so it still paints above
|
|
4009
|
+
// the editor (the editor state stays put; closing the preview restores it).
|
|
4010
|
+
// The layer needs its region before its first paint (see the component):
|
|
4011
|
+
// the transcript viewport height from the ScrollBox handle and the content
|
|
4012
|
+
// column width. The full-screen (editor-open) placement uses the terminal.
|
|
4013
|
+
const imagePreviewRegion = promptEditorOpen
|
|
4014
|
+
? { columns: terminalColumns, rows: terminalRows }
|
|
4015
|
+
: { columns: terminalColumns, rows: handle?.getViewportHeight() ?? terminalRows };
|
|
4016
|
+
const imagePreviewNode = activePreview !== null && (activePreview.peek || imagePreviewOwned)
|
|
4017
|
+
? (_jsx(ImagePreviewOverlay, { image: activePreview.image, title: activePreview.title, navigation: previewGallery.length > 1 && previewIndex >= 0 ? {
|
|
4018
|
+
index: previewIndex, total: previewGallery.length,
|
|
4019
|
+
onPrevious: () => stepPreview(-1), onNext: () => stepPreview(1),
|
|
4020
|
+
} : undefined, onClose: activePreview.peek
|
|
4021
|
+
? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
|
|
4022
|
+
: () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
|
|
4023
|
+
: null;
|
|
4024
|
+
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && anchorUserText && (_jsx(PinnedTurnHeader, { text: anchorUserText, onClick: () => {
|
|
4025
|
+
// Click snaps the pinned prompt to the viewport top. Jump by the
|
|
4026
|
+
// SAME content coordinate the
|
|
4027
|
+
// rail's tick uses (timeline turn top = the prompt TEXT top):
|
|
4028
|
+
// the element-based seek lands the row wrapper's margin at the
|
|
4029
|
+
// top instead — one row shy of the text top the anchor rule
|
|
4030
|
+
// compares against — and the header would flip to the previous
|
|
4031
|
+
// turn immediately after the click.
|
|
4032
|
+
const turn = timeline.turns.find(t => t.id === anchorUserRowId);
|
|
4033
|
+
if (turn)
|
|
4034
|
+
handle?.scrollTo(turn.top);
|
|
4035
|
+
else if (anchorUserRowId !== null)
|
|
4036
|
+
seekRow(anchorUserRowId);
|
|
4037
|
+
else
|
|
4038
|
+
handle?.scrollToBottom();
|
|
4039
|
+
} })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd, whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, working: channel.working,
|
|
4040
|
+
// Resuming a long session skips the ~3.4s opening animation: it
|
|
4041
|
+
// keeps firing low-frequency React commits that compete with the
|
|
4042
|
+
// transcript mount batches (and the first wheel events) for the
|
|
4043
|
+
// frame budget right when the user wants to read history. Fresh
|
|
4044
|
+
// sessions keep the full intro; restored ones settle instantly.
|
|
4045
|
+
// A remount after a whole screen closed also settles instantly
|
|
4046
|
+
// (see suppressLogoIntroRef).
|
|
4047
|
+
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: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
|
|
4048
|
+
// Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
|
|
4049
|
+
// rail (default), the proportional scrollbar, or nothing. The
|
|
4050
|
+
// slot keeps its 2 columns in both rendered modes (Qwen's
|
|
4051
|
+
// permanent-gutter rule — an appearing/disappearing gutter
|
|
4052
|
+
// changes the transcript width and rewraps everything).
|
|
4053
|
+
const gutter = normalizeScrollGutter(channel.scrollGutter);
|
|
4054
|
+
if (gutter === 'hidden')
|
|
4055
|
+
return null;
|
|
4056
|
+
if (gutter === 'scrollbar') {
|
|
4057
|
+
return _jsx(ScrollbarGutter, { handle: handle, terminalWidth: terminalColumns });
|
|
4058
|
+
}
|
|
4059
|
+
return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: terminalColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
|
|
4060
|
+
})(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
|
|
4061
|
+
(channel.activityEnabled &&
|
|
4062
|
+
!channel.minimal &&
|
|
4063
|
+
workingActivity !== undefined &&
|
|
4064
|
+
workingActivity.line !== '' &&
|
|
4065
|
+
workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
|
|
4066
|
+
// Upload = real tokens of the last request; download =
|
|
4067
|
+
// the animated chars/4 estimate, matching the classic
|
|
4068
|
+
// spinner's counter (the suffix used raw chars before,
|
|
4069
|
+
// inflating the reading next to a real upload number).
|
|
4070
|
+
suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, 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, {
|
|
4071
|
+
React,
|
|
4072
|
+
ui: STATUS_VIEW_UI,
|
|
4073
|
+
}) }) }) }, `${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: () => {
|
|
4074
|
+
// An expanded auto recap collapses back to its dim row;
|
|
4075
|
+
// a manual /recap closes outright.
|
|
4076
|
+
if (recap.auto) {
|
|
4077
|
+
setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
|
|
4078
|
+
}
|
|
4079
|
+
else {
|
|
4080
|
+
closeRecap();
|
|
4081
|
+
}
|
|
4082
|
+
}, onCopy: () => {
|
|
4083
|
+
void setClipboard(recap.summary ?? '').then(raw => { if (raw)
|
|
4084
|
+
writeRaw?.(raw); });
|
|
4085
|
+
channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
|
|
4086
|
+
}, onApplyTitle: () => {
|
|
4087
|
+
if (recap.title === undefined || recap.titleApplied)
|
|
4088
|
+
return;
|
|
4089
|
+
channel.renameSession(recap.title);
|
|
4090
|
+
setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
|
|
4091
|
+
channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
|
|
4092
|
+
} }) })) : 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: () => {
|
|
4093
|
+
void setClipboard(btw.answer ?? '').then(raw => { if (raw)
|
|
4094
|
+
writeRaw?.(raw); });
|
|
4095
|
+
channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
|
|
4096
|
+
} }) })) : 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,
|
|
4097
|
+
// The 🏠 at the head of the input row opens the same session screen
|
|
4098
|
+
// `/resume` and `/agentview` open — one surface, three doors. It is
|
|
4099
|
+
// gated on this prop rather than a setting, so hosts that mount the
|
|
4100
|
+
// prompt without a session screen (and the layout regressions that
|
|
4101
|
+
// pin the row's column budget) keep the row they had.
|
|
4102
|
+
onOpenSessions: () => {
|
|
4103
|
+
agentViewOpenSessionRef.current = channel.agentId;
|
|
4104
|
+
setSupervisorOpen(true);
|
|
4105
|
+
}, backgroundAgentsNeedingInput:
|
|
4106
|
+
// Only the real channel supplies the seam; pre-agent-view test
|
|
4107
|
+
// stubs must not grow the footer row (layout-dependent
|
|
4108
|
+
// regressions pin the visible row count). The footer only
|
|
4109
|
+
// renders while some session actually waits (N > 0): a
|
|
4110
|
+
// permanent idle row would steal a transcript row on every
|
|
4111
|
+
// real channel — one row is enough to scroll the startup
|
|
4112
|
+
// header fully off a short terminal, pausing its viewport
|
|
4113
|
+
// clock and shifting every row-count layout invariant.
|
|
4114
|
+
channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
|
|
4115
|
+
? backgroundAgentsNeedingInput
|
|
4116
|
+
: 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
|
|
4117
|
+
? undefined
|
|
4118
|
+
: {
|
|
4119
|
+
band: wakeBand,
|
|
4120
|
+
hint: trajectorySeen ? undefined : `${modLabel}t`,
|
|
4121
|
+
tick: Math.floor(wakeTime / 120),
|
|
4122
|
+
} }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
|
|
4123
|
+
// 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
|
|
4124
|
+
const visible = index === 0;
|
|
4125
|
+
setThinkingVisible(visible);
|
|
4126
|
+
dispatchOverlay({ type: 'close' });
|
|
4127
|
+
channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
|
|
4128
|
+
} })), 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) => {
|
|
4129
|
+
// 点击行 = 切换该行目标(与 Enter 同一条路径)
|
|
4130
|
+
const target = workspaceTargets[index];
|
|
4131
|
+
dispatchOverlay({ type: 'close' });
|
|
4132
|
+
if (target !== undefined)
|
|
4133
|
+
void channel.switchWorkspace(target);
|
|
4134
|
+
} }) })), overlay.kind === 'workspace-menu' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceMenuPicker, { options: workspaceMenuOptions, focusIndex: overlay.index, onPick: (index) => {
|
|
4135
|
+
// 点击行 = 执行该行(与 Enter 同一条路径)
|
|
4136
|
+
runWorkspaceMenuOption(workspaceMenuOptions[index]);
|
|
4137
|
+
} }) })), 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) => {
|
|
4138
|
+
// 点击行 = 设焦点 + 执行分支(与 Enter 同一条路径);
|
|
4139
|
+
// busy/输入态在组件侧禁点
|
|
4140
|
+
const choice = overlay.flow.choices[index];
|
|
4141
|
+
if (choice === undefined)
|
|
4142
|
+
return;
|
|
4143
|
+
dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
|
|
4144
|
+
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
4145
|
+
} }) })), 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) => {
|
|
4146
|
+
// 点击分组行 = 进入该组(与 Enter 同一条路径)
|
|
4147
|
+
const group = modelGroups[index];
|
|
4148
|
+
if (!group)
|
|
4149
|
+
return;
|
|
4150
|
+
setModelGroup(group.provider);
|
|
4151
|
+
if (group.provider === RECENTS_GROUP_PROVIDER) {
|
|
4152
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
|
|
4156
|
+
dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
|
|
4157
|
+
} })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
|
|
4158
|
+
? t('picker-group-recent')
|
|
4159
|
+
: 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) => {
|
|
4160
|
+
// 点击行 = 应用该行模型(与 Enter 同一条路径)
|
|
4161
|
+
const model = groupModels[index];
|
|
4162
|
+
if (!model)
|
|
4163
|
+
return;
|
|
4164
|
+
dispatchOverlay({ type: 'close' });
|
|
4165
|
+
void switchModelRecorded(model.provider, model.id, model.name);
|
|
4166
|
+
} })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
|
|
4167
|
+
const row = (migrateRows ?? [])[index];
|
|
4168
|
+
if (!row)
|
|
4169
|
+
return;
|
|
4170
|
+
setMigrateChecked(current => {
|
|
4171
|
+
const next = new Set(current);
|
|
4172
|
+
if (next.has(row.agentId))
|
|
4173
|
+
next.delete(row.agentId);
|
|
4174
|
+
else
|
|
4175
|
+
next.add(row.agentId);
|
|
4176
|
+
return next;
|
|
4177
|
+
});
|
|
4178
|
+
} }) })), 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) => {
|
|
4179
|
+
const skill = skillsList[index];
|
|
4180
|
+
if (!skill)
|
|
4181
|
+
return;
|
|
4182
|
+
dispatchOverlay({ type: 'close' });
|
|
4183
|
+
if (skill.userInvocable)
|
|
4184
|
+
setHistoryFill(`/${skill.name} `);
|
|
4185
|
+
} })) })), overlay.kind === 'activity' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: overlay.index, currentPreset: channel.activityFrames, onPick: (index) => {
|
|
4186
|
+
dispatchOverlay({ type: 'close' });
|
|
4187
|
+
const name = PRESET_NAMES[index];
|
|
4188
|
+
if (name)
|
|
4189
|
+
channel.setActivityFrames(name);
|
|
4190
|
+
} }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
|
|
4191
|
+
dispatchOverlay({ type: 'close' });
|
|
4192
|
+
const name = SESSION_COLOR_NAMES[index];
|
|
4193
|
+
if (name) {
|
|
4194
|
+
channel.setSessionColor(name);
|
|
4195
|
+
channel.notify(t('color-set', { name }), { color: 'success' });
|
|
4196
|
+
}
|
|
4197
|
+
} }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
|
|
4198
|
+
// 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
|
|
4199
|
+
onPick: (index) => {
|
|
4200
|
+
dispatchOverlay({ type: 'set-index', kind: 'effort', index });
|
|
4201
|
+
const option = effortOptions[index];
|
|
4202
|
+
if (option)
|
|
4203
|
+
void channel.setEffort(option.id);
|
|
4204
|
+
} }) })), 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) => {
|
|
4205
|
+
dispatchOverlay({ type: 'close' });
|
|
4206
|
+
const option = presetOptions[index];
|
|
4207
|
+
if (option)
|
|
4208
|
+
void channel.switchPreset(option.id);
|
|
4209
|
+
} }) })), 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) => {
|
|
4210
|
+
if (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null)
|
|
4211
|
+
return;
|
|
4212
|
+
const option = overlay.snapshot.options[index];
|
|
4213
|
+
dispatchOverlay({ type: 'close' });
|
|
4214
|
+
if (option !== undefined)
|
|
4215
|
+
void runPermissionCommand(` ${option.value}`);
|
|
4216
|
+
} }) })), overlay.kind === 'plan' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PlanPicker, { focusIndex: overlay.index, currentOn: channel.mode.plan === true, onPick: (index) => {
|
|
4217
|
+
dispatchOverlay({ type: 'close' });
|
|
4218
|
+
const on = index === 0;
|
|
4219
|
+
void runExternalCommand('plan', on ? '' : ' off');
|
|
4220
|
+
} }) })), overlay.kind === 'lang' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(LangPicker, { focusIndex: overlay.index, currentLang: getLang(), onPick: (index) => {
|
|
4221
|
+
const lang = LANGS[index];
|
|
4222
|
+
if (lang === undefined)
|
|
4223
|
+
return;
|
|
4224
|
+
dispatchOverlay({ type: 'close' });
|
|
4225
|
+
applyLang(lang);
|
|
4226
|
+
} }) })), overlay.kind === 'theme' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: overlay.index, currentTheme: themeName, themeHost: themeHost, onPick: (index) => {
|
|
4227
|
+
dispatchOverlay({ type: 'close' });
|
|
4228
|
+
const name = getThemeOptions(themeHost)[index]?.value;
|
|
4229
|
+
if (name !== undefined) {
|
|
4230
|
+
const ok = setTheme(name);
|
|
4231
|
+
channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
|
|
4232
|
+
}
|
|
4233
|
+
} }) })), 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) => {
|
|
4234
|
+
// 点击行 = 填入该历史命令(与 Enter 同路径)
|
|
4235
|
+
const entry = historyMatches[index];
|
|
4236
|
+
if (entry) {
|
|
4237
|
+
setHistoryFill(entry.text);
|
|
4238
|
+
dispatchOverlay({ type: 'close' });
|
|
4239
|
+
}
|
|
4240
|
+
} }) })), 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) => {
|
|
4241
|
+
// 列表页点击只选中:进入确认态保留键盘 Enter 显式触发
|
|
4242
|
+
dispatchOverlay({ type: 'set-index', kind: 'rewind', index });
|
|
4243
|
+
}, onConfirm: () => {
|
|
4244
|
+
// 确认页即显式确认层,点击直接执行(与 Enter 同路径)
|
|
4245
|
+
const row = overlay.confirm;
|
|
4246
|
+
if (row === null)
|
|
4247
|
+
return;
|
|
4248
|
+
dispatchOverlay({ type: 'close' });
|
|
4249
|
+
void performRewind(row);
|
|
4250
|
+
}, onPickMode: (index) => {
|
|
4251
|
+
// 模式列表点击直接执行该模式(与 Enter 同路径)
|
|
4252
|
+
const row = overlay.confirm;
|
|
4253
|
+
if (row === null)
|
|
4254
|
+
return;
|
|
4255
|
+
// 模式页仅当 modes 非空才渲染,这里空安全取值
|
|
4256
|
+
const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
|
|
4257
|
+
dispatchOverlay({ type: 'close' });
|
|
4258
|
+
void performRewind(row, mode);
|
|
4259
|
+
} }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
|
|
4260
|
+
// 点击行直接执行该动作(与 Enter 同路径)
|
|
4261
|
+
const path = overlay.path;
|
|
4262
|
+
dispatchOverlay({ type: 'close' });
|
|
4263
|
+
runFileAction(index, path);
|
|
4264
|
+
} }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode] }));
|
|
4265
|
+
}
|
|
4266
|
+
/**
|
|
4267
|
+
* The pinned prompt header shown above the ScrollBox while the user has
|
|
4268
|
+
* scrolled up. It pins the user message the transcript viewport is currently
|
|
4269
|
+
* showing — the topmost visible user message, or the nearest one above when only assistant
|
|
4270
|
+
* content fills the view — so it tracks which turn the user is reading
|
|
4271
|
+
* instead of always carrying the latest prompt. Fixed at 1 row so the
|
|
4272
|
+
* ScrollBox never shifts when the text changes.
|
|
4273
|
+
*/
|
|
4274
|
+
function PinnedTurnHeader({ text, onClick, }) {
|
|
4275
|
+
const { columns } = useTerminalSize();
|
|
4276
|
+
// A one-row Box does not clip its children. Flatten hard line breaks before
|
|
4277
|
+
// truncating, otherwise later prompt lines paint down the transcript gutter.
|
|
4278
|
+
const label = cleanRenderText(`${POINTER} ${text}`, Math.max(1, columns - 1));
|
|
4279
|
+
return (_jsx(Box, { flexShrink: 0, width: "100%", height: 1, overflow: "hidden", paddingRight: 1, onClick: onClick, children: _jsx(Text, { color: "userPromptLabel", bold: true, wrap: "truncate-end", children: label }) }));
|
|
4280
|
+
}
|
|
4281
|
+
/** The `↓ N new messages` pill shown while scrolled up with new content. */
|
|
4282
|
+
function NewMessagesPill({ count, onClick, }) {
|
|
4283
|
+
const [hover, setHover] = React.useState(false);
|
|
4284
|
+
return (_jsx(NoSelect, { paddingX: 2, paddingTop: 1, children: _jsx(Box, { backgroundColor: hover ? 'userMessageBackgroundHover' : 'background', onClick: onClick, onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, children: _jsxs(Text, { color: "inverseText", bold: true, children: [' ', count > 0
|
|
4285
|
+
? t(count === 1 ? 'new-message' : 'new-messages', { n: count })
|
|
4286
|
+
: t('back-to-bottom'), ' '] }) }) }));
|
|
4287
|
+
}
|
|
4288
|
+
/** /model while the provider catalog is still loading. */
|
|
4289
|
+
function ModelPickerLoading() {
|
|
4290
|
+
return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children: t('picker-title-model') }), _jsx(LoadingState, { message: t('model-loading'), bold: true, subtitle: t('model-loading-subtitle') })] }) }));
|
|
4291
|
+
}
|
|
4292
|
+
/**
|
|
4293
|
+
* The `/` incsearch bar: a
|
|
4294
|
+
* single row above the prompt input with the query, a block cursor, and the
|
|
4295
|
+
* match counter (`current/count`) or a red `no matches` when nothing hits.
|
|
4296
|
+
*/ function TranscriptSearch({ query, cursorOffset, count, current, }) {
|
|
4297
|
+
const cursorChar = cursorOffset < query.length ? query[cursorOffset] : ' ';
|
|
4298
|
+
return (_jsxs(NoSelect, { borderTopDimColor: true, borderBottom: false, borderLeft: false, borderRight: false, borderStyle: "single", marginTop: 1, paddingLeft: 2, width: "100%", children: [_jsx(Text, { children: "/" }), _jsx(Text, { children: query.slice(0, cursorOffset) }), _jsx(Text, { inverse: true, children: cursorChar }), cursorOffset < query.length && _jsx(Text, { children: query.slice(cursorOffset + 1) }), _jsx(Box, { flexGrow: 1 }), query && count === 0 ? (_jsxs(Text, { color: "error", children: [t('search-no-matches'), " "] })) : count > 0 ? (_jsxs(Text, { dimColor: true, children: [Math.min(current + 1, count), "/", count, ' '] })) : null] }));
|
|
4299
|
+
}
|