@deepseek-harness-tui/dsh-tui 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -14
- package/bin/dsh-tui.js +200 -34
- package/cordis.patch.yml +16 -52
- package/cordis.yml +6 -2
- package/guide/dsh-tui-guide/claude-backend.en.md +44 -7
- package/guide/dsh-tui-guide/claude-backend.md +28 -6
- package/guide/dsh-tui-guide/codex-backend.en.md +34 -7
- package/guide/dsh-tui-guide/codex-backend.md +17 -2
- package/guide/dsh-tui-guide/configuration.en.md +129 -16
- package/guide/dsh-tui-guide/configuration.md +110 -14
- package/guide/dsh-tui-guide/interaction.en.md +57 -24
- package/guide/dsh-tui-guide/interaction.md +42 -19
- package/guide/dsh-tui-guide/plugins.en.md +36 -3
- package/guide/dsh-tui-guide/plugins.md +21 -2
- package/guide/dsh-tui-guide/themes.en.md +23 -14
- package/guide/dsh-tui-guide/themes.md +19 -12
- package/guide/dsh-tui-guide/user-guide.en.md +51 -20
- package/guide/dsh-tui-guide/user-guide.md +41 -21
- package/lib/settings.json +103 -9
- package/lib/types/adapter/channel/ui-policy.d.ts +1 -1
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui-policy.js +3 -0
- package/lib/types/adapter/channel/ui.d.ts +2 -0
- package/lib/types/adapter/channel/ui.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui.js +6 -2
- package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
- package/lib/types/adapter/kernel/runtime.js +2 -0
- package/lib/types/adapter/ports/channel-settings.d.ts +9 -0
- package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-ui.d.ts +50 -12
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +21 -0
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/adapter/spec/backend-capabilities.d.ts +22 -0
- package/lib/types/adapter/spec/backend-capabilities.d.ts.map +1 -0
- package/lib/types/adapter/spec/backend-capabilities.js +49 -0
- package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
- package/lib/types/adapter/spec/protocol-constants.js +10 -3
- package/lib/types/adapter/spec/tui-contributions.d.ts +3 -0
- package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
- package/lib/types/adapter/spec/tui-contributions.js +7 -0
- package/lib/types/adapter/standard/registry.d.ts +6 -0
- package/lib/types/adapter/standard/registry.d.ts.map +1 -1
- package/lib/types/adapter/standard/registry.js +65 -15
- package/lib/types/adapter/standard/tui-extension.d.ts.map +1 -1
- package/lib/types/adapter/standard/tui-extension.js +7 -1
- package/lib/types/adapter/standard/types.d.ts +10 -1
- package/lib/types/adapter/standard/types.d.ts.map +1 -1
- package/lib/types/adapter/upstream/host-descriptor-driver.d.ts.map +1 -1
- package/lib/types/adapter/upstream/host-descriptor-driver.js +52 -0
- package/lib/types/agent/backend-manifest.d.ts +152 -0
- package/lib/types/agent/backend-manifest.d.ts.map +1 -0
- package/lib/types/agent/backend-manifest.js +14 -0
- package/lib/types/agent/backend.d.ts +71 -5
- package/lib/types/agent/backend.d.ts.map +1 -1
- package/lib/types/agent/capabilities.d.ts +6 -0
- package/lib/types/agent/capabilities.d.ts.map +1 -1
- package/lib/types/agent/index.d.ts +2 -0
- package/lib/types/agent/index.d.ts.map +1 -1
- package/lib/types/agent/refs.d.ts +11 -1
- package/lib/types/agent/refs.d.ts.map +1 -1
- package/lib/types/agent/starting-session.d.ts +4 -0
- package/lib/types/agent/starting-session.d.ts.map +1 -0
- package/lib/types/agent/starting-session.js +19 -0
- package/lib/types/backends/claude/activity.d.ts +1 -1
- package/lib/types/backends/claude/activity.d.ts.map +1 -1
- package/lib/types/backends/claude/activity.js +5 -5
- package/lib/types/backends/claude/auth.d.ts.map +1 -1
- package/lib/types/backends/claude/auth.js +10 -3
- package/lib/types/backends/claude/backend.d.ts.map +1 -1
- package/lib/types/backends/claude/backend.js +103 -29
- package/lib/types/backends/claude/catalog.d.ts +2 -1
- package/lib/types/backends/claude/catalog.d.ts.map +1 -1
- package/lib/types/backends/claude/catalog.js +15 -5
- package/lib/types/backends/claude/channels.d.ts +5 -3
- package/lib/types/backends/claude/channels.d.ts.map +1 -1
- package/lib/types/backends/claude/channels.js +5 -4
- package/lib/types/backends/claude/contract.d.ts +4 -0
- package/lib/types/backends/claude/contract.d.ts.map +1 -1
- package/lib/types/backends/claude/contract.js +4 -0
- package/lib/types/backends/claude/controls.d.ts +4 -0
- package/lib/types/backends/claude/controls.d.ts.map +1 -1
- package/lib/types/backends/claude/controls.js +34 -42
- package/lib/types/backends/claude/dialogs.d.ts +8 -2
- package/lib/types/backends/claude/dialogs.d.ts.map +1 -1
- package/lib/types/backends/claude/dialogs.js +24 -16
- package/lib/types/backends/claude/images.d.ts +1 -1
- package/lib/types/backends/claude/images.d.ts.map +1 -1
- package/lib/types/backends/claude/images.js +7 -7
- package/lib/types/backends/claude/manifest.d.ts +21 -0
- package/lib/types/backends/claude/manifest.d.ts.map +1 -0
- package/lib/types/backends/claude/manifest.js +29 -0
- package/lib/types/backends/claude/older-history.js +2 -2
- package/lib/types/backends/claude/permissions.js +11 -11
- package/lib/types/backends/claude/prefs.d.ts +4 -2
- package/lib/types/backends/claude/prefs.d.ts.map +1 -1
- package/lib/types/backends/claude/prefs.js +4 -3
- package/lib/types/backends/claude/process.d.ts +16 -4
- package/lib/types/backends/claude/process.d.ts.map +1 -1
- package/lib/types/backends/claude/process.js +100 -37
- package/lib/types/backends/claude/send-message.d.ts +1 -1
- package/lib/types/backends/claude/send-message.d.ts.map +1 -1
- package/lib/types/backends/claude/session/auth.js +22 -22
- package/lib/types/backends/claude/session/channels.d.ts +4 -0
- package/lib/types/backends/claude/session/channels.d.ts.map +1 -1
- package/lib/types/backends/claude/session/channels.js +5 -3
- package/lib/types/backends/claude/session/lifecycle.d.ts.map +1 -1
- package/lib/types/backends/claude/session/lifecycle.js +31 -23
- package/lib/types/backends/claude/session/store.js +8 -8
- package/lib/types/backends/claude/session/subagents.js +2 -2
- package/lib/types/backends/claude/session/types.d.ts +7 -0
- package/lib/types/backends/claude/session/types.d.ts.map +1 -1
- package/lib/types/backends/claude/side-query.js +3 -3
- package/lib/types/backends/claude/task-output.js +5 -5
- package/lib/types/backends/claude/text.d.ts +778 -0
- package/lib/types/backends/claude/text.d.ts.map +1 -0
- package/lib/types/backends/claude/text.js +298 -0
- package/lib/types/backends/claude/transcript-file.d.ts +1 -1
- package/lib/types/backends/claude/transcript-file.d.ts.map +1 -1
- package/lib/types/backends/claude/transcript-file.js +4 -4
- package/lib/types/backends/claude/translate/attempts.js +2 -2
- package/lib/types/backends/claude/translate/core.js +3 -3
- package/lib/types/backends/claude/translate/messages.js +4 -4
- package/lib/types/backends/claude/translate/system.js +14 -14
- package/lib/types/backends/claude/translate/tasks.js +2 -2
- package/lib/types/backends/claude/translate/types.d.ts +1 -1
- package/lib/types/backends/claude/translate/types.d.ts.map +1 -1
- package/lib/types/backends/codex/auth/external-tokens.js +16 -16
- package/lib/types/backends/codex/auth/status.js +14 -14
- package/lib/types/backends/codex/backend.d.ts +4 -19
- package/lib/types/backends/codex/backend.d.ts.map +1 -1
- package/lib/types/backends/codex/backend.js +118 -25
- package/lib/types/backends/codex/catalog.d.ts +2 -1
- package/lib/types/backends/codex/catalog.d.ts.map +1 -1
- package/lib/types/backends/codex/catalog.js +63 -18
- package/lib/types/backends/codex/channels.d.ts +6 -3
- package/lib/types/backends/codex/channels.d.ts.map +1 -1
- package/lib/types/backends/codex/channels.js +15 -13
- package/lib/types/backends/codex/detect.d.ts.map +1 -1
- package/lib/types/backends/codex/detect.js +9 -6
- package/lib/types/backends/codex/manifest.d.ts +30 -0
- package/lib/types/backends/codex/manifest.d.ts.map +1 -0
- package/lib/types/backends/codex/manifest.js +23 -0
- package/lib/types/backends/codex/prefs.d.ts +4 -2
- package/lib/types/backends/codex/prefs.d.ts.map +1 -1
- package/lib/types/backends/codex/prefs.js +4 -3
- package/lib/types/backends/codex/rpc/hub.js +2 -2
- package/lib/types/backends/codex/session/activity.d.ts +1 -1
- package/lib/types/backends/codex/session/activity.d.ts.map +1 -1
- package/lib/types/backends/codex/session/activity.js +13 -13
- package/lib/types/backends/codex/session/approvals.js +30 -30
- package/lib/types/backends/codex/session/commands.d.ts +8 -2
- package/lib/types/backends/codex/session/commands.d.ts.map +1 -1
- package/lib/types/backends/codex/session/commands.js +71 -23
- package/lib/types/backends/codex/session/controls.d.ts +1 -1
- package/lib/types/backends/codex/session/controls.d.ts.map +1 -1
- package/lib/types/backends/codex/session/controls.js +21 -16
- package/lib/types/backends/codex/session/goals.d.ts.map +1 -1
- package/lib/types/backends/codex/session/goals.js +3 -3
- package/lib/types/backends/codex/session/history.js +2 -2
- package/lib/types/backends/codex/session/images.js +8 -8
- package/lib/types/backends/codex/session/input.d.ts +6 -2
- package/lib/types/backends/codex/session/input.d.ts.map +1 -1
- package/lib/types/backends/codex/session/input.js +55 -13
- package/lib/types/backends/codex/session/plan-review.js +6 -6
- package/lib/types/backends/codex/session/session.d.ts.map +1 -1
- package/lib/types/backends/codex/session/session.js +66 -34
- package/lib/types/backends/codex/session/side-query.d.ts.map +1 -1
- package/lib/types/backends/codex/session/side-query.js +27 -9
- package/lib/types/backends/codex/session/state.d.ts +6 -1
- package/lib/types/backends/codex/session/state.d.ts.map +1 -1
- package/lib/types/backends/codex/session/subagents.js +5 -5
- package/lib/types/backends/codex/session/tasks.js +4 -4
- package/lib/types/backends/codex/text.d.ts +933 -0
- package/lib/types/backends/codex/text.d.ts.map +1 -0
- package/lib/types/backends/codex/text.js +313 -0
- package/lib/types/backends/codex/translate/items.js +4 -4
- package/lib/types/backends/codex/translate/live.d.ts.map +1 -1
- package/lib/types/backends/codex/translate/live.js +11 -8
- package/lib/types/backends/codex/translate/notices.js +10 -10
- package/lib/types/backends/shared/channel-slug.d.ts +4 -0
- package/lib/types/backends/shared/channel-slug.d.ts.map +1 -0
- package/lib/types/backends/shared/channel-slug.js +25 -0
- package/lib/types/backends/shared/channel-tokens.d.ts.map +1 -1
- package/lib/types/backends/shared/channel-tokens.js +30 -23
- package/lib/types/{channel → backends/shared}/elicitation.d.ts +78 -21
- package/lib/types/backends/shared/elicitation.d.ts.map +1 -0
- package/lib/types/{channel → backends/shared}/elicitation.js +64 -66
- package/lib/types/backends/shared/image-size.d.ts +33 -0
- package/lib/types/backends/shared/image-size.d.ts.map +1 -0
- package/lib/types/backends/shared/image-size.js +134 -0
- package/lib/types/{utils → backends/shared}/jsonl.d.ts +5 -1
- package/lib/types/backends/shared/jsonl.d.ts.map +1 -0
- package/lib/types/{utils → backends/shared}/jsonl.js +5 -1
- package/lib/types/backends/shared/localized-text.d.ts +30 -0
- package/lib/types/backends/shared/localized-text.d.ts.map +1 -0
- package/lib/types/backends/shared/localized-text.js +24 -0
- package/lib/types/backends/shared/platform-text.d.ts +88 -0
- package/lib/types/backends/shared/platform-text.d.ts.map +1 -0
- package/lib/types/backends/shared/platform-text.js +33 -0
- package/lib/types/branding.d.ts +13 -0
- package/lib/types/branding.d.ts.map +1 -1
- package/lib/types/branding.js +18 -0
- package/lib/types/channel/channel-slug.d.ts +3 -1
- package/lib/types/channel/channel-slug.d.ts.map +1 -1
- package/lib/types/channel/channel-slug.js +3 -1
- package/lib/types/channel/sanitize.d.ts +7 -0
- package/lib/types/channel/sanitize.d.ts.map +1 -1
- package/lib/types/channel/sanitize.js +9 -0
- package/lib/types/channel/usage.d.ts.map +1 -1
- package/lib/types/channel/usage.js +5 -0
- package/lib/types/commands.d.ts +5 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +47 -8
- package/lib/types/components/AgentMessageComposer.d.ts.map +1 -1
- package/lib/types/components/AgentMessageComposer.js +3 -2
- package/lib/types/components/BtwPanel.d.ts.map +1 -1
- package/lib/types/components/BtwPanel.js +4 -2
- package/lib/types/components/Chat/JobCard.js +3 -3
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +20 -6
- package/lib/types/components/ContextBarView.d.ts +5 -1
- package/lib/types/components/ContextBarView.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.js +6 -3
- package/lib/types/components/ExtensionDialog.d.ts.map +1 -1
- package/lib/types/components/ExtensionDialog.js +2 -3
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +14 -3
- package/lib/types/components/InlineMathParagraph.js +1 -1
- package/lib/types/components/KernelPicker.d.ts.map +1 -1
- package/lib/types/components/KernelPicker.js +1 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +9 -6
- package/lib/types/components/MarkdownTable.d.ts.map +1 -1
- package/lib/types/components/MarkdownTable.js +4 -3
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +18 -5
- package/lib/types/components/ModelPicker.d.ts +24 -31
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +115 -40
- package/lib/types/components/PromptEditor.d.ts +3 -3
- package/lib/types/components/PromptInput.d.ts +21 -3
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +58 -118
- package/lib/types/components/SdkInstallWizard.d.ts +3 -1
- package/lib/types/components/SdkInstallWizard.d.ts.map +1 -1
- package/lib/types/components/SdkInstallWizard.js +8 -1
- package/lib/types/components/SearchBox.d.ts +13 -15
- package/lib/types/components/SearchBox.d.ts.map +1 -1
- package/lib/types/components/SearchBox.js +58 -27
- package/lib/types/components/ThemePicker.d.ts +1 -1
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +14 -11
- package/lib/types/components/Tooltip.js +1 -1
- package/lib/types/components/WorkingSpinner.js +1 -1
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
- package/lib/types/components/WorkspaceFlowPicker.js +4 -3
- package/lib/types/components/composerImageTokens.d.ts +24 -0
- package/lib/types/components/composerImageTokens.d.ts.map +1 -0
- package/lib/types/components/composerImageTokens.js +37 -0
- package/lib/types/components/design-system/InputCaret.d.ts +7 -0
- package/lib/types/components/design-system/InputCaret.d.ts.map +1 -0
- package/lib/types/components/design-system/InputCaret.js +9 -0
- package/lib/types/components/design-system/ListItem.d.ts +13 -2
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +11 -5
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +6 -8
- package/lib/types/components/kernelCatalog.d.ts +55 -11
- package/lib/types/components/kernelCatalog.d.ts.map +1 -1
- package/lib/types/components/kernelCatalog.js +42 -23
- package/lib/types/components/launchpadActions.d.ts +4 -0
- package/lib/types/components/launchpadActions.d.ts.map +1 -1
- package/lib/types/components/launchpadActions.js +5 -4
- package/lib/types/components/launchpadLayout.d.ts.map +1 -1
- package/lib/types/components/launchpadLayout.js +2 -2
- package/lib/types/components/messages/UserPromptMessage.d.ts +4 -3
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +4 -3
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +8 -3
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +4 -3
- package/lib/types/components/questions/ProviderModelEditor.d.ts.map +1 -1
- package/lib/types/components/questions/ProviderModelEditor.js +3 -13
- package/lib/types/components/sessions/ForeignSessionRow.d.ts +3 -1
- package/lib/types/components/sessions/ForeignSessionRow.d.ts.map +1 -1
- package/lib/types/components/sessions/ForeignSessionRow.js +5 -2
- package/lib/types/components/sessions/SessionListRow.d.ts +3 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +5 -2
- package/lib/types/components/sidePanel/PanelBar.d.ts +1 -0
- package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -1
- package/lib/types/components/sidePanel/PanelBar.js +21 -17
- package/lib/types/components/sidePanel/SidePanelColumn.d.ts +1 -1
- package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -1
- package/lib/types/components/sidePanel/SidePanelColumn.js +2 -1
- package/lib/types/components/sidePanel/SidePanelLayout.d.ts +6 -4
- package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -1
- package/lib/types/components/sidePanel/SidePanelLayout.js +33 -7
- package/lib/types/components/sidePanel/btw/BtwComposer.d.ts +4 -2
- package/lib/types/components/sidePanel/btw/BtwComposer.d.ts.map +1 -1
- package/lib/types/components/sidePanel/btw/BtwComposer.js +12 -4
- package/lib/types/components/sidePanel/btw/BtwPanelAdapter.d.ts.map +1 -1
- package/lib/types/components/sidePanel/btw/BtwPanelAdapter.js +59 -15
- package/lib/types/components/sidePanel/btw/BtwThreadScene.d.ts.map +1 -1
- package/lib/types/components/sidePanel/btw/BtwThreadScene.js +4 -2
- package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts +1 -1
- package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts.map +1 -1
- package/lib/types/components/sidePanel/btw/BtwThreadView.js +12 -6
- package/lib/types/components/sidePanel/companion/CompanionPanel.js +2 -2
- package/lib/types/components/sidePanel/types.d.ts +1 -1
- package/lib/types/components/sidePanel/types.d.ts.map +1 -1
- package/lib/types/components/sidePanel/useSidePanel.d.ts +2 -0
- package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -1
- package/lib/types/components/sidePanel/useSidePanel.js +11 -1
- package/lib/types/components/splashFonts.d.ts +24 -1
- package/lib/types/components/splashFonts.d.ts.map +1 -1
- package/lib/types/components/splashFonts.js +40 -5
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
- package/lib/types/components/workspaces/HomeWorkspaceRow.js +5 -2
- package/lib/types/dsh-adapter/admission-loader.d.ts +20 -0
- package/lib/types/dsh-adapter/admission-loader.d.ts.map +1 -0
- package/lib/types/dsh-adapter/admission-loader.js +256 -0
- package/lib/types/dsh-adapter/backend/translate.d.ts.map +1 -1
- package/lib/types/dsh-adapter/backend/translate.js +3 -0
- package/lib/types/dsh-adapter/backend-admission.d.ts +64 -0
- package/lib/types/dsh-adapter/backend-admission.d.ts.map +1 -0
- package/lib/types/dsh-adapter/backend-admission.js +34 -0
- package/lib/types/dsh-adapter/backend-contribution.d.ts +26 -0
- package/lib/types/dsh-adapter/backend-contribution.d.ts.map +1 -0
- package/lib/types/dsh-adapter/backend-contribution.js +37 -0
- package/lib/types/dsh-adapter/backend-registry.d.ts +136 -0
- package/lib/types/dsh-adapter/backend-registry.d.ts.map +1 -0
- package/lib/types/dsh-adapter/backend-registry.js +285 -0
- package/lib/types/dsh-adapter/backends.d.ts +67 -24
- package/lib/types/dsh-adapter/backends.d.ts.map +1 -1
- package/lib/types/dsh-adapter/backends.generated.d.ts +12 -0
- package/lib/types/dsh-adapter/backends.generated.d.ts.map +1 -0
- package/lib/types/dsh-adapter/backends.generated.js +6 -0
- package/lib/types/dsh-adapter/backends.js +92 -46
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts +2 -0
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.js +9 -3
- package/lib/types/dsh-adapter/channel/agent-view-projection.js +2 -2
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/background-action.js +8 -3
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +16 -3
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +18 -46
- package/lib/types/dsh-adapter/channel/channel-host.d.ts +45 -0
- package/lib/types/dsh-adapter/channel/channel-host.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/channel-host.js +1 -0
- package/lib/types/dsh-adapter/channel/command-completions.d.ts +5 -0
- package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/command-completions.js +16 -0
- package/lib/types/dsh-adapter/channel/composer-images.d.ts +4 -2
- package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/composer-images.js +36 -5
- package/lib/types/dsh-adapter/channel/cordis-host.d.ts +14 -0
- package/lib/types/dsh-adapter/channel/cordis-host.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/cordis-host.js +35 -0
- package/lib/types/dsh-adapter/channel/core/actions.d.ts +3 -3
- package/lib/types/dsh-adapter/channel/core/actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/actions.js +15 -6
- package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts +7 -3
- package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/binding-feed.js +3 -0
- package/lib/types/dsh-adapter/channel/core/compose.d.ts +12 -28
- package/lib/types/dsh-adapter/channel/core/compose.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/compose.js +318 -117
- package/lib/types/dsh-adapter/channel/core/files.d.ts +7 -2
- package/lib/types/dsh-adapter/channel/core/files.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/files.js +1 -0
- package/lib/types/dsh-adapter/channel/core/host.d.ts +12 -6
- package/lib/types/dsh-adapter/channel/core/host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/host.js +69 -35
- package/lib/types/dsh-adapter/channel/core/local-actions.d.ts +5 -3
- package/lib/types/dsh-adapter/channel/core/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/local-actions.js +5 -2
- package/lib/types/dsh-adapter/channel/core/session-controls.js +2 -2
- package/lib/types/dsh-adapter/channel/core/session-switch.d.ts +7 -4
- package/lib/types/dsh-adapter/channel/core/session-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/session-switch.js +8 -4
- package/lib/types/dsh-adapter/channel/core/sessions.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/core/sessions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/core/sessions.js +75 -16
- package/lib/types/dsh-adapter/channel/extensions.d.ts +4 -3
- package/lib/types/dsh-adapter/channel/extensions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/extensions.js +41 -22
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +2 -2
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/input-delivery.js +14 -5
- package/lib/types/dsh-adapter/channel/job-projection.d.ts +4 -0
- package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/job-projection.js +205 -24
- package/lib/types/dsh-adapter/channel/mentions.d.ts +3 -3
- package/lib/types/dsh-adapter/channel/mentions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +1 -0
- package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/mode-permission-actions.js +138 -8
- package/lib/types/dsh-adapter/channel/model-actions.d.ts +5 -1
- package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-actions.js +40 -28
- package/lib/types/dsh-adapter/channel/model-selection.d.ts +17 -0
- package/lib/types/dsh-adapter/channel/model-selection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/model-selection.js +109 -0
- package/lib/types/dsh-adapter/channel/model-switch.d.ts +12 -21
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +39 -102
- package/lib/types/dsh-adapter/channel/projection.js +1 -1
- package/lib/types/dsh-adapter/channel/reports.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/reports.js +11 -3
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-fork.js +33 -20
- package/lib/types/dsh-adapter/channel/session-listing.d.ts +20 -0
- package/lib/types/dsh-adapter/channel/session-listing.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/session-listing.js +51 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts +3 -1
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +56 -27
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-resume.js +15 -8
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +38 -24
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +38 -24
- package/lib/types/dsh-adapter/channel/settings-host.d.ts +2 -2
- package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/settings-host.js +1 -0
- package/lib/types/dsh-adapter/channel/state.d.ts +37 -1
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +14 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +66 -6
- package/lib/types/dsh-adapter/channel/task-completion.d.ts +6 -0
- package/lib/types/dsh-adapter/channel/task-completion.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/task-completion.js +114 -0
- package/lib/types/dsh-adapter/channel/transcript.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/transcript.js +1 -1
- package/lib/types/dsh-adapter/channel/upstream-retry.d.ts +81 -0
- package/lib/types/dsh-adapter/channel/upstream-retry.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/upstream-retry.js +138 -0
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.js +9 -8
- package/lib/types/dsh-adapter/channel-ui.d.ts +3 -1
- package/lib/types/dsh-adapter/channel-ui.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel-ui.js +4 -2
- package/lib/types/dsh-adapter/channel.d.ts +2 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +19 -5
- package/lib/types/dsh-adapter/context-occupancy.d.ts +81 -4
- package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -1
- package/lib/types/dsh-adapter/context-occupancy.js +108 -17
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +15 -0
- package/lib/types/dsh-adapter/entry-slot.d.ts +36 -0
- package/lib/types/dsh-adapter/entry-slot.d.ts.map +1 -0
- package/lib/types/dsh-adapter/entry-slot.js +28 -0
- package/lib/types/dsh-adapter/fresh-agent.d.ts +22 -0
- package/lib/types/dsh-adapter/fresh-agent.d.ts.map +1 -0
- package/lib/types/dsh-adapter/fresh-agent.js +221 -0
- package/lib/types/dsh-adapter/host-access.d.ts +16 -1
- package/lib/types/dsh-adapter/host-access.d.ts.map +1 -1
- package/lib/types/dsh-adapter/host-access.js +53 -2
- package/lib/types/dsh-adapter/host-contract.d.ts +103 -0
- package/lib/types/dsh-adapter/host-contract.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-contract.js +76 -0
- package/lib/types/dsh-adapter/host-dsh.d.ts +106 -0
- package/lib/types/dsh-adapter/host-dsh.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-dsh.js +466 -0
- package/lib/types/dsh-adapter/host-entry.d.ts +9 -0
- package/lib/types/dsh-adapter/host-entry.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-entry.js +228 -0
- package/lib/types/dsh-adapter/index.d.ts +55 -18
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +59 -7
- package/lib/types/dsh-adapter/install/executors.d.ts +44 -0
- package/lib/types/dsh-adapter/install/executors.d.ts.map +1 -0
- package/lib/types/dsh-adapter/install/executors.js +15 -0
- package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts +3 -0
- package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts.map +1 -0
- package/lib/types/dsh-adapter/install/pnpm-profile-add.js +150 -0
- package/lib/types/dsh-adapter/jobs.d.ts +7 -0
- package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
- package/lib/types/dsh-adapter/jobs.js +13 -0
- package/lib/types/dsh-adapter/lite-profile.d.ts +50 -0
- package/lib/types/dsh-adapter/lite-profile.d.ts.map +1 -0
- package/lib/types/dsh-adapter/lite-profile.js +49 -0
- package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts +1 -1
- package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts.map +1 -1
- package/lib/types/dsh-adapter/migrate/parse/jsonl.js +4 -3
- package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -1
- package/lib/types/dsh-adapter/oauth/profiles.js +5 -1
- package/lib/types/dsh-adapter/panels.d.ts +7 -0
- package/lib/types/dsh-adapter/panels.d.ts.map +1 -1
- package/lib/types/dsh-adapter/panels.js +35 -2
- package/lib/types/dsh-adapter/plugin-completions.d.ts +32 -0
- package/lib/types/dsh-adapter/plugin-completions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-completions.js +80 -0
- package/lib/types/dsh-adapter/plugin-declaration.d.ts +21 -0
- package/lib/types/dsh-adapter/plugin-declaration.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-declaration.js +53 -0
- package/lib/types/dsh-adapter/plugin-host.d.ts +13 -2
- package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin-host.js +34 -8
- package/lib/types/dsh-adapter/plugin-manager.d.ts +68 -0
- package/lib/types/dsh-adapter/plugin-manager.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-manager.js +51 -0
- package/lib/types/dsh-adapter/plugin.d.ts +50 -1
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +870 -233
- package/lib/types/dsh-adapter/plugins-info.d.ts +44 -8
- package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugins-info.js +354 -34
- package/lib/types/dsh-adapter/process-exit.d.ts +43 -0
- package/lib/types/dsh-adapter/process-exit.d.ts.map +1 -0
- package/lib/types/dsh-adapter/process-exit.js +60 -0
- package/lib/types/dsh-adapter/root-dispose.d.ts +18 -0
- package/lib/types/dsh-adapter/root-dispose.d.ts.map +1 -0
- package/lib/types/dsh-adapter/root-dispose.js +28 -0
- package/lib/types/dsh-adapter/session-list-metadata.d.ts +20 -0
- package/lib/types/dsh-adapter/session-list-metadata.d.ts.map +1 -0
- package/lib/types/dsh-adapter/session-list-metadata.js +56 -0
- package/lib/types/dsh-adapter/session-seed.d.ts +11 -0
- package/lib/types/dsh-adapter/session-seed.d.ts.map +1 -0
- package/lib/types/dsh-adapter/session-seed.js +47 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts +3 -1
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/list.js +11 -5
- package/lib/types/dsh-adapter/sessions/snapshot.d.ts +0 -2
- package/lib/types/dsh-adapter/sessions/snapshot.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/snapshot.js +7 -70
- package/lib/types/dsh-adapter/startup-args.d.ts +2 -0
- package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -1
- package/lib/types/dsh-adapter/startup-args.js +5 -0
- package/lib/types/dsh-adapter/subagents.d.ts +33 -1
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +110 -8
- package/lib/types/dsh-adapter/tui-settings.d.ts +57 -0
- package/lib/types/dsh-adapter/tui-settings.d.ts.map +1 -0
- package/lib/types/dsh-adapter/tui-settings.js +199 -0
- package/lib/types/effortPrefs.d.ts +6 -3
- package/lib/types/effortPrefs.d.ts.map +1 -1
- package/lib/types/effortPrefs.js +7 -4
- package/lib/types/hostEntryRoute.d.ts +18 -0
- package/lib/types/hostEntryRoute.d.ts.map +1 -0
- package/lib/types/hostEntryRoute.js +57 -0
- package/lib/types/i18n.d.ts +363 -1432
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +155 -385
- package/lib/types/ink/components/AlternateScreen.js +2 -2
- package/lib/types/ink/components/App.d.ts +1 -0
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +16 -4
- package/lib/types/ink/components/CursorDeclarationContext.d.ts +9 -0
- package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -1
- package/lib/types/ink/components/CursorDeclarationContext.js +2 -0
- package/lib/types/ink/hooks/use-copy-on-select.d.ts +2 -2
- package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-copy-on-select.js +13 -7
- package/lib/types/ink/hooks/use-declared-cursor.d.ts +11 -3
- package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-declared-cursor.js +30 -10
- package/lib/types/ink/ink.d.ts +22 -17
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +151 -40
- package/lib/types/ink/kitty-graphics.d.ts +2 -0
- package/lib/types/ink/kitty-graphics.d.ts.map +1 -1
- package/lib/types/ink/kitty-graphics.js +6 -1
- package/lib/types/ink/node-cache.d.ts +2 -1
- package/lib/types/ink/node-cache.d.ts.map +1 -1
- package/lib/types/ink/output.d.ts +10 -4
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +14 -10
- package/lib/types/ink/render-node-to-output.d.ts +5 -2
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +27 -9
- package/lib/types/ink/renderer.d.ts +1 -0
- package/lib/types/ink/renderer.d.ts.map +1 -1
- package/lib/types/ink/renderer.js +4 -3
- package/lib/types/ink/root.d.ts +4 -1
- package/lib/types/ink/root.d.ts.map +1 -1
- package/lib/types/ink/screen.d.ts +4 -2
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/selection.d.ts +10 -1
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +94 -25
- package/lib/types/ink/stdin-handoff.d.ts +11 -0
- package/lib/types/ink/stdin-handoff.d.ts.map +1 -0
- package/lib/types/ink/stdin-handoff.js +20 -0
- package/lib/types/ink/styles.d.ts +2 -1
- package/lib/types/ink/styles.d.ts.map +1 -1
- package/lib/types/ink/terminal-image.d.ts +6 -4
- package/lib/types/ink/terminal-image.d.ts.map +1 -1
- package/lib/types/ink/terminal-image.js +16 -39
- package/lib/types/ink/terminal-notification.d.ts +8 -0
- package/lib/types/ink/terminal-notification.d.ts.map +1 -0
- package/lib/types/ink/terminal-notification.js +83 -0
- package/lib/types/ink/terminal.d.ts +9 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +34 -1
- package/lib/types/ink/termio/csi.d.ts +11 -0
- package/lib/types/ink/termio/csi.d.ts.map +1 -1
- package/lib/types/ink/termio/csi.js +13 -0
- package/lib/types/ink/update-overflow-guard.d.ts +6 -0
- package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
- package/lib/types/ink/update-overflow-guard.js +17 -0
- package/lib/types/ink/wrap-text.d.ts +6 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -1
- package/lib/types/ink/wrap-text.js +17 -0
- package/lib/types/kernelPrefs.d.ts +129 -16
- package/lib/types/kernelPrefs.d.ts.map +1 -1
- package/lib/types/kernelPrefs.js +98 -35
- package/lib/types/math/inline-layout.d.ts +2 -0
- package/lib/types/math/inline-layout.d.ts.map +1 -1
- package/lib/types/math/inline-layout.js +5 -27
- package/lib/types/modelGroups.d.ts +15 -27
- package/lib/types/modelGroups.d.ts.map +1 -1
- package/lib/types/modelGroups.js +17 -31
- package/lib/types/modelPrefs.d.ts +5 -4
- package/lib/types/modelPrefs.d.ts.map +1 -1
- package/lib/types/modelPrefs.js +5 -4
- package/lib/types/modelRoute.d.ts +23 -9
- package/lib/types/modelRoute.d.ts.map +1 -1
- package/lib/types/modelRoute.js +25 -11
- package/lib/types/permissionPrefs.d.ts +34 -0
- package/lib/types/permissionPrefs.d.ts.map +1 -0
- package/lib/types/permissionPrefs.js +61 -0
- package/lib/types/plugin-host.d.ts +1 -0
- package/lib/types/plugin-host.d.ts.map +1 -1
- package/lib/types/presetPrefs.d.ts +17 -3
- package/lib/types/presetPrefs.d.ts.map +1 -1
- package/lib/types/presetPrefs.js +20 -3
- package/lib/types/reload.d.ts +6 -10
- package/lib/types/reload.d.ts.map +1 -1
- package/lib/types/reload.js +14 -14
- package/lib/types/screens/Chat.d.ts +22 -15
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +442 -272
- package/lib/types/screens/Launchpad.d.ts +27 -17
- package/lib/types/screens/Launchpad.d.ts.map +1 -1
- package/lib/types/screens/Launchpad.js +241 -78
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/SessionSupervisor.js +53 -12
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +6 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +24 -7
- package/lib/types/screens/StatusMetrics.d.ts +46 -1
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +72 -0
- package/lib/types/screens/chat/useKernelPicker.d.ts +8 -4
- package/lib/types/screens/chat/useKernelPicker.d.ts.map +1 -1
- package/lib/types/screens/chat/useKernelPicker.js +61 -21
- package/lib/types/screens/chat/useModelPicker.d.ts +42 -0
- package/lib/types/screens/chat/useModelPicker.d.ts.map +1 -0
- package/lib/types/screens/chat/useModelPicker.js +182 -0
- package/lib/types/screens/chatOverlay.d.ts +11 -4
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +0 -1
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts +4 -1
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.js +26 -5
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +1 -1
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +51 -29
- package/lib/types/sessionHistory.d.ts +5 -0
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessionHistory.js +5 -0
- package/lib/types/sessions/listSnapshot.d.ts +6 -0
- package/lib/types/sessions/listSnapshot.d.ts.map +1 -0
- package/lib/types/sessions/listSnapshot.js +67 -0
- package/lib/types/settings/definitions.d.ts +50 -0
- package/lib/types/settings/definitions.d.ts.map +1 -1
- package/lib/types/settings/definitions.js +42 -8
- package/lib/types/terminal-utils/cjk-emphasis.d.ts +28 -0
- package/lib/types/terminal-utils/cjk-emphasis.d.ts.map +1 -0
- package/lib/types/terminal-utils/cjk-emphasis.js +31 -0
- package/lib/types/terminal-utils/markdown.d.ts +3 -1
- package/lib/types/terminal-utils/markdown.d.ts.map +1 -1
- package/lib/types/terminal-utils/markdown.js +7 -2
- package/lib/types/theme.d.ts +29 -12
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +310 -23
- package/lib/types/themeCatalog.d.ts.map +1 -1
- package/lib/types/themeCatalog.js +21 -2
- package/lib/types/tips.js +4 -4
- package/lib/types/tuiSettingsFile.d.ts +56 -0
- package/lib/types/tuiSettingsFile.d.ts.map +1 -0
- package/lib/types/tuiSettingsFile.js +206 -0
- package/lib/types/ui.d.ts +2 -0
- package/lib/types/ui.d.ts.map +1 -1
- package/lib/types/ui.js +2 -0
- package/lib/types/update.d.ts +72 -26
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +131 -43
- package/lib/types/utils/bmp.d.ts +7 -0
- package/lib/types/utils/bmp.d.ts.map +1 -1
- package/lib/types/utils/bmp.js +27 -0
- package/lib/types/utils/bootTrace.d.ts +2 -0
- package/lib/types/utils/bootTrace.d.ts.map +1 -0
- package/lib/types/utils/bootTrace.js +20 -0
- package/lib/types/utils/clipboard.d.ts.map +1 -1
- package/lib/types/utils/clipboard.js +11 -3
- package/lib/types/utils/credentials.d.ts +14 -2
- package/lib/types/utils/credentials.d.ts.map +1 -1
- package/lib/types/utils/credentials.js +34 -11
- package/lib/types/utils/imageResize.d.ts +10 -20
- package/lib/types/utils/imageResize.d.ts.map +1 -1
- package/lib/types/utils/imageResize.js +61 -134
- package/lib/types/utils/inputPaste.d.ts +2 -2
- package/lib/types/utils/inputPaste.js +2 -2
- package/lib/types/utils/pastedImagePath.d.ts +5 -0
- package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
- package/lib/types/utils/pastedImagePath.js +33 -0
- package/package.json +36 -6
- package/presets/winbash.mjs +716 -0
- package/tui-profile/protocols/profile-definitions.d.ts +1 -0
- package/tui-profile/protocols/profile-definitions.js +14 -0
- package/tui-profile/protocols/tui-contributions.d.ts +55 -0
- package/tui-profile/protocols/tui-contributions.js +141 -0
- package/tui-profile/registry/contracts/backend-v1alpha1.json +13 -0
- package/tui-profile/registry/registry-0.15.json +12 -0
- package/lib/types/backends/claude/install.d.ts +0 -15
- package/lib/types/backends/claude/install.d.ts.map +0 -1
- package/lib/types/backends/claude/install.js +0 -91
- package/lib/types/channel/elicitation.d.ts.map +0 -1
- package/lib/types/dsh-adapter/channel/session-lineage.d.ts +0 -42
- package/lib/types/dsh-adapter/channel/session-lineage.d.ts.map +0 -1
- package/lib/types/dsh-adapter/channel/session-lineage.js +0 -51
- package/lib/types/utils/jsonl.d.ts.map +0 -1
|
@@ -4,12 +4,17 @@ import { SessionId } from '@deepseek-ai/dsh-session';
|
|
|
4
4
|
import UserQuestionService from '@deepseek-ai/dsh-user-questions';
|
|
5
5
|
import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user';
|
|
6
6
|
import Schema from '@deepseek-ai/schemastery';
|
|
7
|
-
import { Config
|
|
7
|
+
import { Config } from './index.js';
|
|
8
8
|
import { configValues, createSettingsScope, resolveSettingsNamespace } from './compat/settings.js';
|
|
9
9
|
import { createChannel } from './channel.js';
|
|
10
10
|
import { createDshSession } from './backend/session.js';
|
|
11
|
-
import {
|
|
11
|
+
import { createTuiSettingsService } from './tui-settings.js';
|
|
12
|
+
import { EDITABLE_CONFIG_KEYS } from '../settings/definitions.js';
|
|
13
|
+
import { kernelEntriesOf } from '../components/kernelCatalog.js';
|
|
14
|
+
import { openBackendStartup, prepareBackendStartup, probeKernels, installSurfaceFor } from './backends.js';
|
|
15
|
+
import { backendLabel, isBackendIdSyntax, isRegisteredBackend, listBackends, listOfferedBackends, loadBackend, parseBackendChoice, unloadBackends } from './backend-registry.js';
|
|
12
16
|
import { formatSessionRef } from '../agent/refs.js';
|
|
17
|
+
import { createStartingSession } from '../agent/starting-session.js';
|
|
13
18
|
import { createChannelSceneOutlet } from './channel-scene-outlet.js';
|
|
14
19
|
import { mountChannelUi } from './channel-ui.js';
|
|
15
20
|
import { bindChannelCommands } from './channel/commands.js';
|
|
@@ -18,6 +23,7 @@ import { createChildStderrReporter, installChildStderrGuard } from './childStder
|
|
|
18
23
|
import { removeClipboardImageDir } from '../utils/clipboard.js';
|
|
19
24
|
import { appendCrashLog, serializeCrashDetail, unserializableCrashDetail } from '../utils/crashDetail.js';
|
|
20
25
|
import { logForDebugging } from '../utils/debug.js';
|
|
26
|
+
import { createTerminalNotifier } from '../ink/terminal-notification.js';
|
|
21
27
|
import { isEnvTruthy } from '../utils/envUtils.js';
|
|
22
28
|
import { QuestionStore, bindQuestionStore } from './questions.js';
|
|
23
29
|
import { prepareQuestionAnswerer } from './questions-answerer.js';
|
|
@@ -29,23 +35,24 @@ import { readActivityFrames } from '../activityPrefs.js';
|
|
|
29
35
|
import { commitFullscreenFactoryMigration, planFullscreenFactoryMigration, readAppliedMigrations } from '../migrationPrefs.js';
|
|
30
36
|
import { readModelPref } from '../modelPrefs.js';
|
|
31
37
|
import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
|
|
32
|
-
import { migratePresetPref, readPresetPref } from '../presetPrefs.js';
|
|
38
|
+
import { migratePresetPref, presetOverrideFromEnv, readPresetPref } from '../presetPrefs.js';
|
|
33
39
|
import { readEffortPref } from '../effortPrefs.js';
|
|
34
40
|
import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, resolvePersistedRoute, runningPresetOf } from './presets.js';
|
|
41
|
+
import { createFreshAgent, isUnstoredFreshSession } from './fresh-agent.js';
|
|
35
42
|
import { ensurePackagedPresets } from './packaged-presets.js';
|
|
36
43
|
import { registerBundledPresets } from './bundled-presets.js';
|
|
37
44
|
import { ensureLegacySessionEventTypes, snapshotLiveSessionEvents } from './compat/index.js';
|
|
38
|
-
import { clearResumeTarget, resumeTargetFromArgv, writeResumeTarget } from '../sessionHistory.js';
|
|
39
|
-
import { initialPromptFromCmdlineArgs } from './startup-args.js';
|
|
45
|
+
import { clearResumeTarget, resumeTargetFromArgv, stripResumeArgs, writeResumeTarget } from '../sessionHistory.js';
|
|
46
|
+
import { cmdlineArgsOf, initialPromptFromCmdlineArgs } from './startup-args.js';
|
|
40
47
|
import { readHomePrefs } from '../homePrefs.js';
|
|
41
48
|
import { handoffEventTag, formatHandoffNotice } from '../handoffEvents.js';
|
|
42
49
|
import { armFirstFrameAck, beginHandoffAck, handoffAttemptId, ownsAltScreenExit } from '../handoffAck.js';
|
|
43
|
-
import {
|
|
50
|
+
import { DSH_BACKEND_ID, HOST_NOTICE_ENV, KERNEL_SWITCH_HANDOFF_ENV, RESUME_BACKEND_ENV, RESUME_RETRY_ENV, parseBackendId, readKernelPrefs, resolveRememberedBackend, resolveResumeTarget, writeKernelPrefs } from '../kernelPrefs.js';
|
|
44
51
|
import { shouldOfferOnboarding } from '../onboardingPrefs.js';
|
|
45
52
|
import { resolveSessionCwd } from '../utils/workspaceRoot.js';
|
|
46
|
-
import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
|
|
53
|
+
import { LAUNCH_PROMPT_SENT_ENV, beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
|
|
47
54
|
import { getLang, isLang, resolveStartupLang, setLang, t, writeLangPref } from '../i18n.js';
|
|
48
|
-
import { applyBtwContextBudget, applyBtwContextTurns, applyCodeFrameStyle, applyCompanionSkin, applyImageBacking, applyMathImageBacking, applyMathImageScale, applyMathRendering, applyMermaidDiagrams, applyPageMargin, applySidePanelOpen,
|
|
55
|
+
import { applyBtwContextBudget, applyBtwContextTurns, applyCodeFrameStyle, applyCompanionSkin, applyImageBacking, applyMathImageBacking, applyMathImageScale, applyMathRendering, applyMermaidDiagrams, applyPageMargin, applySidePanelOpen, applySidePanelRatio, applySidePanelSplitEnabled, BTW_CONTEXT_BUDGET_MAX, BTW_CONTEXT_BUDGET_MIN, BTW_CONTEXT_TURNS_MAX, BTW_CONTEXT_TURNS_MIN, DEFAULT_PAGE_MARGIN, DEFAULT_SIDE_PANEL_IDS, DEFAULT_STATUS_BAR, isPageMarginMode, normalizeJobGroupFold, normalizePageMargin, normalizeScrollGutter, normalizeSidePanelPanels, normalizeStatusBar, normalizeToolBackground, parsePageMarginSpec, resolveMathRendering, SIDE_PANEL_ID_PATTERN } from '../tuiDisplayPrefs.js';
|
|
49
56
|
import { draftComboConflicts, effectiveComboString, parseComboDraft, setKeymapOverrides, SHORTCUT_ACTIONS, } from '../utils/keymap.js';
|
|
50
57
|
import { attachHerdrIntegration } from '../herdr.js';
|
|
51
58
|
import { logMouseDebug } from '../utils/debug.js';
|
|
@@ -55,6 +62,7 @@ import { startSessionMountHeartbeat } from './session-mount-heartbeat.js';
|
|
|
55
62
|
import { reserveMount } from '../sessionMounts.js';
|
|
56
63
|
import { getHostDialogStore } from './dialogs.js';
|
|
57
64
|
import { getHostStatusStore } from './status.js';
|
|
65
|
+
import { attachSessionListMetadata } from './session-list-metadata.js';
|
|
58
66
|
import { createActivityStore } from './activity-store.js';
|
|
59
67
|
import { createContextOccupancyStore } from './context-occupancy.js';
|
|
60
68
|
import { getHostToastStore } from './toast.js';
|
|
@@ -64,6 +72,9 @@ import { attachSessionToWorkspace } from './workspace.js';
|
|
|
64
72
|
import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
|
|
65
73
|
import { getHostSettingsSections, getLocalSettingsSectionsHost } from './settings-sections.js';
|
|
66
74
|
import { compositionRoot, withHostRootCapability } from './host-access.js';
|
|
75
|
+
import { armAdmissionLoader } from './admission-loader.js';
|
|
76
|
+
import { applyConfiguredSidePanelPanels, applySidePanelPanelsFromSettings } from './panels.js';
|
|
77
|
+
import { disposeRootSettled } from './root-dispose.js';
|
|
67
78
|
import { render, ThemeProvider, AlternateScreen } from '../ui.js';
|
|
68
79
|
import { PageMargin } from '../components/PageMargin.js';
|
|
69
80
|
import { normalizeSplashFont } from '../components/splashFonts.js';
|
|
@@ -73,7 +84,11 @@ import instances from '../ink/instances.js';
|
|
|
73
84
|
import { cursorMove, DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DISABLE_WIN32_INPUT_MODE } from '../ink/termio/csi.js';
|
|
74
85
|
import { DBP, DFE, DISABLE_MOUSE_TRACKING, EXIT_ALT_SCREEN, SHOW_CURSOR } from '../ink/termio/dec.js';
|
|
75
86
|
import { CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, supportsTabStatus, wrapForMultiplexer } from '../ink/termio/osc.js';
|
|
76
|
-
import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
|
|
87
|
+
import { addProcessErrorAbsorber, fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
|
|
88
|
+
import { CHANNEL_UI_LIFETIME_ENDED } from '../adapter/channel/ui.js';
|
|
89
|
+
import { markBoot } from '../utils/bootTrace.js';
|
|
90
|
+
import { TERMINATION_SIGNALS, dieBySignal } from './process-exit.js';
|
|
91
|
+
import { StartupOpenError } from './channel/state.js';
|
|
77
92
|
/**
|
|
78
93
|
* Interactive TUI front door for DeepSeek Harness agents.
|
|
79
94
|
*
|
|
@@ -98,6 +113,11 @@ import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update
|
|
|
98
113
|
let lastBootedFullscreen;
|
|
99
114
|
// Image preferences also stay fixed across host recomposes until /restart.
|
|
100
115
|
let lastBootedTerminalImages;
|
|
116
|
+
// A replacement's first process already sent the launch prompt. Read once per
|
|
117
|
+
// process (a recompose must not resend it) and dropped from the environment,
|
|
118
|
+
// so a `dsh-tui "prompt"` run from inside the session keeps its own prompt.
|
|
119
|
+
const launchPromptSent = process.env[LAUNCH_PROMPT_SENT_ENV] === '1';
|
|
120
|
+
delete process.env[LAUNCH_PROMPT_SENT_ENV];
|
|
101
121
|
// Kept importable from here: the startup parser moved to its own dependency-free
|
|
102
122
|
// module so argv probes can load it without the whole plugin graph.
|
|
103
123
|
export { initialPromptFromCmdlineArgs };
|
|
@@ -128,8 +148,10 @@ export function resolveTuiHostMode(stdoutIsTTY = process.stdout.isTTY === true,
|
|
|
128
148
|
const explicitTuiLaunch = env.DSH_TUI_LAUNCHER_VERSION !== undefined || isStandaloneRuntime();
|
|
129
149
|
return explicitTuiLaunch ? 'invalid-explicit-launch' : 'headless-host';
|
|
130
150
|
}
|
|
131
|
-
export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
151
|
+
export async function apply(ctx, runtimeConfig, configOwner = ctx, runtimeOptions = {}) {
|
|
132
152
|
const config = configValues(runtimeConfig);
|
|
153
|
+
const taskNotifier = createTerminalNotifier(process.stdout);
|
|
154
|
+
taskNotifier.configure(config.taskNotifications, config.notificationMethod);
|
|
133
155
|
// /restart handoff diagnosis: the replacement process is marked by env and
|
|
134
156
|
// logs its boot progress to ~/.dsh-tui/restart.log (ordinary launches stay
|
|
135
157
|
// silent). First line lands before anything in this function can throw.
|
|
@@ -203,21 +225,40 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
203
225
|
}
|
|
204
226
|
// Validate settings before creating an agent or taking over the terminal.
|
|
205
227
|
const tuiSettingsNs = resolveSettingsNamespace(configOwner, Config);
|
|
228
|
+
// Register before agent creation so its first checkpoint includes web list metadata.
|
|
229
|
+
attachSessionListMetadata(ctx);
|
|
230
|
+
// The TUI's own settings layer (~/.dsh-tui/settings.json): the same
|
|
231
|
+
// document on every kernel and entry. Other namespaces still go
|
|
232
|
+
// to the host's settings service (absent in the standalone entry).
|
|
233
|
+
const tuiSettings = createTuiSettingsService({
|
|
234
|
+
ns: tuiSettingsNs,
|
|
235
|
+
profile: resolveDshProfileName() ?? runtimeOptions.profile ?? 'dsh-tui',
|
|
236
|
+
keys: EDITABLE_CONFIG_KEYS,
|
|
237
|
+
delegate: () => ctx.get('settings'),
|
|
238
|
+
});
|
|
239
|
+
// The in-process DSH kernel's screen mounts before DSH has composed: what
|
|
240
|
+
// needs DSH's services runs later, in `attachDsh`.
|
|
241
|
+
const entrySlot = runtimeOptions.entrySlot;
|
|
242
|
+
const dshInEntry = entrySlot?.dsh === true;
|
|
206
243
|
// Modern hosts own a declarative registry; old hosts discover directories.
|
|
207
244
|
// A modern bundle failure must not silently fall back to obsolete files.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
245
|
+
const installPresets = async (presetCtx) => {
|
|
246
|
+
if (!await registerBundledPresets(presetCtx))
|
|
247
|
+
try {
|
|
248
|
+
for (const result of ensurePackagedPresets()) {
|
|
249
|
+
if (result.status === 'conflict') {
|
|
250
|
+
presetCtx.logger.warn(`dsh-tui: packaged preset "${result.id}" was not installed because an unmanaged preset already uses that id`);
|
|
251
|
+
}
|
|
213
252
|
}
|
|
214
253
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
254
|
+
catch (error) {
|
|
255
|
+
// A read-only home must not make the whole terminal unusable; the other
|
|
256
|
+
// official and user presets remain available.
|
|
257
|
+
presetCtx.logger.warn(`dsh-tui: unable to install packaged presets (${error instanceof Error ? error.message : String(error)})`);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
if (!dshInEntry)
|
|
261
|
+
await installPresets(ctx);
|
|
221
262
|
// UI language resolution: DSH_TUI_LANG env var wins, then the
|
|
222
263
|
// settings.yaml `dsh-tui.lang` user layer (applied once the settings
|
|
223
264
|
// namespace registers below), then cordis.yml `lang`, then the
|
|
@@ -263,6 +304,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
263
304
|
}
|
|
264
305
|
}
|
|
265
306
|
}
|
|
307
|
+
// The questionnaire store every backend's questions park in (a non-DSH
|
|
308
|
+
// session's `question.request` events too); Chat renders it.
|
|
309
|
+
const questionStore = new QuestionStore(adapterRuntimeFor(ctx));
|
|
310
|
+
bindQuestionStore(ctx, questionStore);
|
|
311
|
+
// One store, one teardown effect on both API lines. The compatibility
|
|
312
|
+
// adapter binds either registration to this Cordis fiber; this separate
|
|
313
|
+
// effect rejects asks still parked in the UI during teardown.
|
|
314
|
+
ctx.effect(() => () => questionStore.rejectAll());
|
|
266
315
|
// DSH user-interaction seam: the model's ask_user_question tool parks on
|
|
267
316
|
// the userQuestions service until a UI answerer responds. Mount the
|
|
268
317
|
// service when the composition doesn't (the official dsh-base
|
|
@@ -272,46 +321,45 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
272
321
|
// seat is registered below; the 0.1.2 line's agent-aware waterfall needs the
|
|
273
322
|
// channel owner and is therefore registered immediately after the channel
|
|
274
323
|
// is created. Optional-service access goes through `ctx.get`, not the
|
|
275
|
-
// inject proxy.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
324
|
+
// inject proxy. The in-process DSH kernel mounts these from its dsh-tui row
|
|
325
|
+
// once DSH has composed: mounting them on the entry's root first would
|
|
326
|
+
// take the `userQuestions` seat the profile's own row registers.
|
|
327
|
+
const mountDshQuestionSeams = (dshCtx) => {
|
|
328
|
+
const userQuestions = dshCtx.get('userQuestions') ?? new UserQuestionService(dshCtx);
|
|
329
|
+
dshCtx.plugin(toolAskUser);
|
|
330
|
+
// The host-level tool mount above is intentional for the TUI and for user
|
|
331
|
+
// presets, but the official Minimal preset is a single-tool trajectory (one
|
|
332
|
+
// persistent shell: bash on POSIX, pwsh on Windows). Filter only that preset
|
|
333
|
+
// at the final assembly boundary. Reading the session on every assembly also
|
|
334
|
+
// makes blank-session /preset switches and resumed sessions behave correctly.
|
|
335
|
+
dshCtx.on('system-prompt/assemble', async (_assembly, context, next) => {
|
|
336
|
+
const assembled = await next();
|
|
337
|
+
const presetId = context.agent === undefined ? undefined : runningPresetOf(context.agent.session);
|
|
338
|
+
return filterMinimalPresetTools(assembled, presetId);
|
|
339
|
+
});
|
|
340
|
+
// `/debug-prompt` snapshots the final provider-neutral request at the
|
|
341
|
+
// llm/stream boundary, after every prompt and tool contributor has run.
|
|
342
|
+
registerPromptDebug(dshCtx);
|
|
343
|
+
// API selection and registration live behind one adapter boundary. The UI
|
|
344
|
+
// bootstrap only translates a legacy seat conflict into its visible notice.
|
|
345
|
+
const registration = prepareQuestionAnswerer(dshCtx, userQuestions, questionStore);
|
|
346
|
+
const seatDecision = registration.kind === 'legacy' ? registration.yieldDecision : undefined;
|
|
347
|
+
let notice;
|
|
348
|
+
if (seatDecision?.action === 'alert-unverified') {
|
|
349
|
+
dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a component self-reporting as ${seatDecision.incumbentId} ` +
|
|
350
|
+
'(identity not host-verified); this TUI will not register its questionnaire and model questions may be answered by it');
|
|
351
|
+
notice = t('question-provider-occupied-unverified', { id: seatDecision.incumbentId ?? '' });
|
|
352
|
+
}
|
|
353
|
+
else if (seatDecision?.action === 'alert') {
|
|
354
|
+
const displayId = seatDecision.incumbentId ?? t('question-provider-occupied-unknown');
|
|
355
|
+
dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a non-host component (${displayId}); ` +
|
|
356
|
+
'this TUI will not register its questionnaire and model questions may be answered by it');
|
|
357
|
+
notice = t('question-provider-occupied', { id: displayId });
|
|
358
|
+
}
|
|
359
|
+
return { registration, notice };
|
|
360
|
+
};
|
|
361
|
+
const questionSeams = dshInEntry ? undefined : mountDshQuestionSeams(ctx);
|
|
362
|
+
let questionSeatNotice = questionSeams?.notice;
|
|
315
363
|
// Child-process stderr guard (issue #17): MCP servers spawned with an
|
|
316
364
|
// inherited stderr (the MCP SDK's stdio default) write straight to the
|
|
317
365
|
// terminal device from the child process, bypassing the renderer's own
|
|
@@ -344,20 +392,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
344
392
|
provider: config.provider,
|
|
345
393
|
model: config.model,
|
|
346
394
|
};
|
|
347
|
-
// Atomic route resolution (issue #67):
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
// without
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
&& configuredDefault.provider.length > 0
|
|
356
|
-
&& typeof configuredDefault.model === 'string'
|
|
357
|
-
&& configuredDefault.model.length > 0
|
|
358
|
-
? { provider: configuredDefault.provider, model: configuredDefault.model }
|
|
359
|
-
: undefined;
|
|
360
|
-
const startupRoute = resolveModelRoute(configuredRoute, readModelPref(), harnessDefault);
|
|
395
|
+
// Atomic route resolution (issue #67): the persisted `/model` choice — the
|
|
396
|
+
// route the last session ran — wins whole; else a complete cordis.yml route
|
|
397
|
+
// wins whole as the deployment DEFAULT (it decides the first run, never a
|
|
398
|
+
// later one); else Harness's provider-neutral agent-default-model
|
|
399
|
+
// selection. The local DeepSeek pair remains the final fallback for bare
|
|
400
|
+
// embedders without that service. This lets optional provider bundles supply
|
|
401
|
+
// the same default to Web and TUI without patching this front door by name.
|
|
402
|
+
const { startupRoute, deploymentRoute } = startupRoutes(ctx, configuredRoute);
|
|
361
403
|
// Session cwd (issue #96): explicit cordis.yml `cwd` wins; otherwise the
|
|
362
404
|
// git worktree root containing the launch directory (the launch directory
|
|
363
405
|
// itself outside any worktree), so `@` completion and mention expansion
|
|
@@ -413,11 +455,16 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
413
455
|
ctx.logger.warn('dsh-tui: tuiPluginHost service is not mounted; plugin grant store, runtime generation and Host Descriptor are unavailable. ' +
|
|
414
456
|
'The bundle patch is older than the installed dsh-tui package — update the globally installed dsh-tui launcher to match the profile (issue #183).');
|
|
415
457
|
}
|
|
458
|
+
// Admit third-party activations from their package-root `dsh-plugin.json`.
|
|
459
|
+
armAdmissionLoader(ctx);
|
|
460
|
+
// The in-process DSH kernel mounts before the profile's workspace providers
|
|
461
|
+
// exist: a provider URI resolves in `attachDsh`, and an unresolvable one
|
|
462
|
+
// fails the startup session there instead of the mount.
|
|
416
463
|
const initialWorkspace = requestedWorkspace === undefined
|
|
417
464
|
? undefined
|
|
418
465
|
: await workspaceService.resolve(requestedWorkspace);
|
|
419
|
-
if (requestedWorkspace !== undefined && initialWorkspace === undefined) {
|
|
420
|
-
throw new Error(
|
|
466
|
+
if (requestedWorkspace !== undefined && initialWorkspace === undefined && !dshInEntry) {
|
|
467
|
+
throw new Error(unsupportedWorkspaceTarget(requestedWorkspace));
|
|
421
468
|
}
|
|
422
469
|
const sessionCwd = initialWorkspace?.cwd ?? resolveSessionCwd(config.cwd);
|
|
423
470
|
const meta = { cwd: sessionCwd };
|
|
@@ -425,8 +472,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
425
472
|
// `dsh --profile tui` forwards `--resume` verbatim instead, so fall back to
|
|
426
473
|
// the same app-argv snapshot as the initial prompt. Raw process.argv also
|
|
427
474
|
// contains the DSH launcher's own -- and is only a legacy embedder fallback.
|
|
428
|
-
const
|
|
429
|
-
|
|
475
|
+
const cmdlineArgs = cmdlineArgsOf(ctx);
|
|
476
|
+
// The raw request, deliberately self-contained: `scripts/verify-startup-argv.mjs`
|
|
477
|
+
// replays this very statement (picked out of the compiled `apply` by name) to check
|
|
478
|
+
// the launcher's argv interception end to end, so it has to stay computable from
|
|
479
|
+
// `config` and `cmdlineArgs` alone. Whether the target may actually be used is
|
|
480
|
+
// decided further down, once the backend this boot lands on is known.
|
|
430
481
|
const launchSessionId = config.sessionId ?? resumeTargetFromArgv(cmdlineArgs ?? process.argv.slice(2));
|
|
431
482
|
// The session's backend (Config `backend`, `dsh-tui --backend`). A non-DSH
|
|
432
483
|
// backend opens its own session here and skips everything DSH-specific
|
|
@@ -447,21 +498,109 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
447
498
|
const handoffBackendRaw = process.env[KERNEL_SWITCH_HANDOFF_ENV];
|
|
448
499
|
if (handoffBackendRaw !== undefined)
|
|
449
500
|
delete process.env[KERNEL_SWITCH_HANDOFF_ENV];
|
|
450
|
-
const handoffBackend =
|
|
501
|
+
const handoffBackend = parseBackendChoice(handoffBackendRaw);
|
|
451
502
|
// Fullscreen kernel switch: the old process spawned this one with an ACK
|
|
452
503
|
// pipe on fd 3 and the alternate screen still open (see handoffAck.ts).
|
|
453
504
|
const handoffAck = beginHandoffAck();
|
|
454
505
|
if (handoffAck !== undefined) {
|
|
455
506
|
logRestartEvent('handoff/boot: ack armed', { attemptId: handoffAttemptId() ?? '' });
|
|
456
507
|
}
|
|
457
|
-
|
|
508
|
+
// Every source is parsed in two stages (P0 D1): syntax first, then the
|
|
509
|
+
// registry. An id that is syntactically valid but not installed must behave
|
|
510
|
+
// exactly like an unknown value did before the set was open — dsh plus a
|
|
511
|
+
// warning, never a crashed boot. `envKnown` carries that into the resolver for
|
|
512
|
+
// the one source that arrives raw; the other three are parsed above/beside.
|
|
513
|
+
const configuredBackend = parseBackendChoice(config.backend);
|
|
514
|
+
const rememberedBackend = readKernelPrefs().backend;
|
|
515
|
+
const entryKernel = runtimeOptions.entryKernel;
|
|
516
|
+
const backendChoice = entryKernel ?? (dshInEntry ? DSH_BACKEND_ID : resolveRememberedBackend({
|
|
458
517
|
...(handoffBackend === undefined ? {} : { handoff: handoffBackend }),
|
|
459
|
-
configured:
|
|
518
|
+
configured: configuredBackend,
|
|
460
519
|
envRaw: rawBackend,
|
|
461
|
-
|
|
520
|
+
envKnown: isRegisteredBackend,
|
|
521
|
+
memory: isRegisteredBackend(rememberedBackend) ? rememberedBackend : undefined,
|
|
522
|
+
}));
|
|
523
|
+
const rawBackendGiven = rawBackend === undefined ? '' : rawBackend.trim();
|
|
524
|
+
// The launcher marks a *derived* resume target with the backend it read it from
|
|
525
|
+
// (RESUME_BACKEND_ENV); a target the user placed carries no mark. Read and
|
|
526
|
+
// deleted right away, like the kernel handoff above, so no child inherits it.
|
|
527
|
+
// The mark is parsed with the syntax gate alone: the source is typically a
|
|
528
|
+
// backend that is NOT registered here, which is the whole reason to check.
|
|
529
|
+
// A marked target belongs to that one backend, and this boot may have landed
|
|
530
|
+
// elsewhere — an uninstalled plugin id falls back to dsh (P0 D1), an unset
|
|
531
|
+
// DSH_TUI_BACKEND follows the remembered kernel instead. Handing it over either
|
|
532
|
+
// fails or resumes an unrelated session that happens to share the id, so it is
|
|
533
|
+
// revoked rather than carried across (PR #1380 review R2) — and a revocation is
|
|
534
|
+
// a refusal, not a cold start (roadmap §6 item 11, B-2a).
|
|
535
|
+
const resumeBackendRaw = process.env[RESUME_BACKEND_ENV];
|
|
536
|
+
if (resumeBackendRaw !== undefined)
|
|
537
|
+
delete process.env[RESUME_BACKEND_ENV];
|
|
538
|
+
// A safe-mode retry carries a licence to survive a revoked target (the launcher
|
|
539
|
+
// sets RESUME_RETRY_ENV; see resolveResumeTarget). Read and deleted right away,
|
|
540
|
+
// like the mark above: no child may inherit it.
|
|
541
|
+
const resumeRetry = process.env[RESUME_RETRY_ENV] !== undefined;
|
|
542
|
+
delete process.env[RESUME_RETRY_ENV];
|
|
543
|
+
const resumeTarget = resolveResumeTarget({
|
|
544
|
+
sessionId: launchSessionId,
|
|
545
|
+
sourceBackend: parseBackendId(resumeBackendRaw),
|
|
546
|
+
backendChoice,
|
|
547
|
+
retry: resumeRetry,
|
|
462
548
|
});
|
|
463
|
-
|
|
464
|
-
|
|
549
|
+
const effectiveSessionId = resumeTarget.kind === 'usable' ? resumeTarget.sessionId : undefined;
|
|
550
|
+
// The non-DSH path below opens `backendChoice`'s own session — a revoked target
|
|
551
|
+
// must not reach it either.
|
|
552
|
+
const configuredSessionId = resumeTarget.kind === 'revoked' ? undefined : config.sessionId;
|
|
553
|
+
// Safe-mode retries replay the original argv. Revocation must also remove its
|
|
554
|
+
// resume flags, or either startup path can reopen the replacement backend's
|
|
555
|
+
// own last session after promising a cold start. Prompt parsing keeps raw argv.
|
|
556
|
+
const startupArgv = resumeTarget.kind === 'revoked'
|
|
557
|
+
? stripResumeArgs(cmdlineArgs ?? process.argv.slice(2))
|
|
558
|
+
: cmdlineArgs ?? process.argv.slice(2);
|
|
559
|
+
if (resumeTarget.kind === 'revoked') {
|
|
560
|
+
// Fail closed (roadmap §6 item 11): the target belongs to another backend, so
|
|
561
|
+
// this boot neither carries it over nor quietly starts a session nobody asked
|
|
562
|
+
// for. A fatal refusal is thrown into the startup funnel, which reports it on
|
|
563
|
+
// stderr and exits non-zero after restoring the terminal; the only tolerated
|
|
564
|
+
// case is the safe-mode retry, whose target comes from last-run.json and whose
|
|
565
|
+
// failure would close the user's last way back (RESUME_RETRY_ENV).
|
|
566
|
+
// The advice must be executable (PR #1449 review R2): when the source backend
|
|
567
|
+
// is not registered here, `--backend <from> --resume` would hit this same
|
|
568
|
+
// refusal, so that branch says the backend is unavailable instead. The other
|
|
569
|
+
// branch names the id (`kernelId`), never the display label — `--backend`
|
|
570
|
+
// takes an id.
|
|
571
|
+
const refusalKey = !resumeTarget.fatal
|
|
572
|
+
? 'resume-target-revoked-retry'
|
|
573
|
+
: isRegisteredBackend(resumeTarget.from) ? 'resume-target-revoked' : 'resume-target-revoked-uninstalled';
|
|
574
|
+
const refusal = t(refusalKey, {
|
|
575
|
+
from: resumeTarget.from,
|
|
576
|
+
kernel: backendLabel(backendChoice),
|
|
577
|
+
kernelId: backendChoice,
|
|
578
|
+
});
|
|
579
|
+
if (resumeTarget.fatal)
|
|
580
|
+
throw new Error(refusal);
|
|
581
|
+
ctx.logger.warn(refusal);
|
|
582
|
+
}
|
|
583
|
+
// Config and handoff outrank env: an ignored env value cannot invalidate the
|
|
584
|
+
// selected backend's resume target or claim that this boot fell back to DSH.
|
|
585
|
+
if (handoffBackend === undefined && configuredBackend === undefined
|
|
586
|
+
&& rawBackendGiven !== '' && !isRegisteredBackend(rawBackendGiven.toLowerCase())) {
|
|
587
|
+
// Two cases, two sentences: a typo is not an uninstalled plugin (P0 D1).
|
|
588
|
+
const installed = listBackends().map(entry => entry.manifest.id).join(', ');
|
|
589
|
+
// The plain fallback (no resume request) keeps P0 D1's shape: dsh plus a
|
|
590
|
+
// warning, exit code 0. With a resume target in hand it must not: dsh would
|
|
591
|
+
// be asked to open an id that belongs to the backend the user named, which
|
|
592
|
+
// either fails or resumes a session of another backend (roadmap §6 item 11).
|
|
593
|
+
// Nothing is resumed, nothing is created — the boot stops here instead.
|
|
594
|
+
if (effectiveSessionId !== undefined) {
|
|
595
|
+
throw new Error(t('resume-backend-unavailable', {
|
|
596
|
+
backend: rawBackendGiven,
|
|
597
|
+
session: effectiveSessionId,
|
|
598
|
+
installed,
|
|
599
|
+
}));
|
|
600
|
+
}
|
|
601
|
+
ctx.logger.warn(isBackendIdSyntax(rawBackendGiven)
|
|
602
|
+
? `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" names no installed backend (registered: ${installed}); starting on dsh`
|
|
603
|
+
: `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" is not a valid backend id (lowercase letters, digits and dashes); starting on dsh`);
|
|
465
604
|
}
|
|
466
605
|
/**
|
|
467
606
|
* Whether a Config row or DSH_TUI_BACKEND overrides the selector's
|
|
@@ -477,27 +616,74 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
477
616
|
// Explicit choices (flag/Config/handoff) and explicit resume targets keep
|
|
478
617
|
// the hard failure: silently swapping what the user named would be worse.
|
|
479
618
|
let backendStart;
|
|
619
|
+
/** The startup session still opening (`deferBackendOpen`); the channel adopts it. */
|
|
620
|
+
let backendStartup;
|
|
621
|
+
/** Once set, the channel's binding owns the session. */
|
|
622
|
+
let backendStartupAdopted = false;
|
|
480
623
|
let backendFallbackNotice;
|
|
624
|
+
const deferBackendOpen = runtimeOptions.deferBackendOpen === true;
|
|
625
|
+
if (deferBackendOpen && backendChoice === 'dsh' && !dshInEntry) {
|
|
626
|
+
throw new Error('dsh-tui: the standalone entry cannot host the DSH kernel; start it through the profile launcher');
|
|
627
|
+
}
|
|
628
|
+
/** The in-process DSH kernel's startup session: settled by `attachDsh`
|
|
629
|
+
* (the profile's dsh-tui row) or by a failed composition. */
|
|
630
|
+
let settleDshStartup;
|
|
631
|
+
let dshStartupSettled = false;
|
|
632
|
+
const dshStartup = dshInEntry
|
|
633
|
+
? new Promise((resolve, reject) => {
|
|
634
|
+
settleDshStartup = {
|
|
635
|
+
resolve: startup => { dshStartupSettled = true; resolve(startup); },
|
|
636
|
+
reject: error => { dshStartupSettled = true; reject(error); },
|
|
637
|
+
};
|
|
638
|
+
})
|
|
639
|
+
: undefined;
|
|
640
|
+
// A boot that fails before the channel holds the startup must not leak an
|
|
641
|
+
// unhandled rejection.
|
|
642
|
+
dshStartup?.catch(() => undefined);
|
|
481
643
|
if (backendChoice !== 'dsh') {
|
|
482
644
|
try {
|
|
483
|
-
|
|
645
|
+
const startupInput = {
|
|
484
646
|
cwd: sessionCwd,
|
|
485
|
-
stderr: line => {
|
|
647
|
+
stderr: (line) => {
|
|
486
648
|
logForDebugging(`[${backendChoice}-stderr] ${line}`);
|
|
487
649
|
stderrReporter.push(line);
|
|
488
650
|
},
|
|
489
|
-
...(
|
|
490
|
-
argv:
|
|
491
|
-
}
|
|
651
|
+
...(configuredSessionId === undefined ? {} : { configuredSessionId }),
|
|
652
|
+
argv: startupArgv,
|
|
653
|
+
};
|
|
654
|
+
// One load per launch, shared by the deferred path (the entry mounts
|
|
655
|
+
// between `prepare` and `start`) and the eager one.
|
|
656
|
+
const backendModule = await loadBackend(backendChoice);
|
|
657
|
+
if (deferBackendOpen) {
|
|
658
|
+
const prepared = await prepareBackendStartup(ctx, backendModule, startupInput);
|
|
659
|
+
const startup = prepared.start().then(opened => ({ session: opened.session, history: opened.initialHistory }));
|
|
660
|
+
backendStartup = startup;
|
|
661
|
+
// As for dshStartup above; the effect closes an unadopted session.
|
|
662
|
+
startup.catch(() => undefined);
|
|
663
|
+
ctx.effect(() => () => {
|
|
664
|
+
if (backendStartupAdopted)
|
|
665
|
+
return;
|
|
666
|
+
void startup.then(opened => opened.session.dispose(), () => undefined).catch(() => undefined);
|
|
667
|
+
}, 'dsh-tui deferred backend startup');
|
|
668
|
+
backendStart = { ...prepared, session: createStartingSession(prepared.backendId, sessionCwd), initialHistory: [] };
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
backendStart = await openBackendStartup(ctx, backendModule, startupInput);
|
|
672
|
+
}
|
|
492
673
|
}
|
|
493
674
|
catch (error) {
|
|
494
|
-
if (backendPinned || handoffBackend !== undefined ||
|
|
675
|
+
if (deferBackendOpen || backendPinned || handoffBackend !== undefined || effectiveSessionId !== undefined)
|
|
495
676
|
throw error;
|
|
496
677
|
const reason = error instanceof Error ? error.message : String(error);
|
|
497
678
|
logForDebugging(`dsh-tui: remembered backend "${backendChoice}" failed to open (${reason}); falling back to dsh`);
|
|
498
|
-
backendFallbackNotice = t('kernel-memory-fallback', { name:
|
|
679
|
+
backendFallbackNotice = t('kernel-memory-fallback', { name: backendLabel(backendChoice), reason });
|
|
499
680
|
}
|
|
500
681
|
}
|
|
682
|
+
// Resolve a surviving bare request on the backend that actually opened.
|
|
683
|
+
// This also covers a remembered backend whose startup fell back to DSH.
|
|
684
|
+
const bootSessionId = backendStart === undefined
|
|
685
|
+
? effectiveSessionId ?? resumeTargetFromArgv(startupArgv)
|
|
686
|
+
: backendStart.resumedSessionId;
|
|
501
687
|
// The backend session (and its child process) belongs to this fiber until the
|
|
502
688
|
// channel adopts it: a boot that throws before then disposes the fiber's
|
|
503
689
|
// effects, and this one stops the child instead of leaking it. Dispose is
|
|
@@ -515,11 +701,15 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
515
701
|
const backendPermissions = backendStart === undefined ? undefined : new PermissionStore();
|
|
516
702
|
if (backendPermissions !== undefined)
|
|
517
703
|
ctx.effect(() => () => backendPermissions.settleAll());
|
|
518
|
-
const
|
|
704
|
+
const resolved = backendStart !== undefined || dshInEntry
|
|
519
705
|
? { agent: undefined, handle: undefined, agentPreset: undefined, route: undefined }
|
|
520
|
-
: await resolveAgent(ctx,
|
|
706
|
+
: await resolveAgent(ctx, bootSessionId, configuredRoute, startupRoute, deploymentRoute, meta, config.preset);
|
|
707
|
+
let agent = resolved.agent;
|
|
708
|
+
const { handle, agentPreset, route: createdRoute } = resolved;
|
|
521
709
|
// Workspace ownership is a DSH session-store fact (skipped off DSH).
|
|
522
|
-
|
|
710
|
+
// `ctx` here is the context that owns the session: this plugin's, or the
|
|
711
|
+
// dsh-tui row's for the in-process DSH kernel (`attachDsh`).
|
|
712
|
+
const attachWorkspaceOwnership = async (ctx, agent) => {
|
|
523
713
|
try {
|
|
524
714
|
// Opening a persisted TUI session is an explicit ownership action too.
|
|
525
715
|
// Older TUI versions only wrote the Session log, so attaching on every
|
|
@@ -556,6 +746,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
556
746
|
// loud instead of silently leaving the conversation Ungrouped.
|
|
557
747
|
ctx.logger.warn(`dsh-tui: session "${agent.session.id}" workspace attachment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
558
748
|
}
|
|
749
|
+
};
|
|
750
|
+
if (agent !== undefined)
|
|
751
|
+
await attachWorkspaceOwnership(ctx, agent);
|
|
559
752
|
// Status-line route: the exact route the agent runs with — on create the
|
|
560
753
|
// validated startup resolution, on resume the route the target session's
|
|
561
754
|
// own records carry (a complete cordis.yml pin wins over them).
|
|
@@ -586,16 +779,36 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
586
779
|
startupSession = backendStart.session;
|
|
587
780
|
else if (agent !== undefined)
|
|
588
781
|
startupSession = createDshSession(ctx, { agent, handle });
|
|
782
|
+
else if (dshInEntry)
|
|
783
|
+
startupSession = createStartingSession('dsh', sessionCwd);
|
|
589
784
|
else
|
|
590
785
|
throw new Error('dsh-tui: no startup session was opened');
|
|
786
|
+
// The in-process DSH kernel's route and Config pins from its dsh-tui row,
|
|
787
|
+
// set in `attachDsh` before adoption; the getters below serve them.
|
|
788
|
+
const rowFacts = {};
|
|
789
|
+
/**
|
|
790
|
+
* The in-process DSH kernel's `/new` while no DSH session was adopted (its
|
|
791
|
+
* startup open failed). Installed by `attachDsh`.
|
|
792
|
+
*/
|
|
793
|
+
let openDshSession;
|
|
794
|
+
let compositionFailed = false;
|
|
795
|
+
/** How the in-process DSH kernel's composition ended: a failing startup open
|
|
796
|
+
* waits for it, so a failed composition (the root cause) is reported. */
|
|
797
|
+
let settleComposition = () => undefined;
|
|
798
|
+
const compositionSettled = new Promise(resolve => { settleComposition = resolve; });
|
|
591
799
|
const rawChannel = createChannel(ctx, startupSession, {
|
|
592
800
|
// The namespace this boot actually registered the settings section under
|
|
593
801
|
// (the Config owner's Loader id; custom ids are supported). Chat and the
|
|
594
802
|
// channel's own settings reads look the section up by it.
|
|
595
803
|
settingsNs: tuiSettingsNs,
|
|
804
|
+
onTaskComplete: preview => {
|
|
805
|
+
if (!shadow)
|
|
806
|
+
taskNotifier.notify(t('task-complete-notification'), preview);
|
|
807
|
+
},
|
|
808
|
+
settingsService: tuiSettings,
|
|
596
809
|
// The backend reports its model with its first turn (`system/init`);
|
|
597
810
|
// until then the status line names the backend.
|
|
598
|
-
model
|
|
811
|
+
get model() { return rowFacts.route?.model ?? (backendStart !== undefined ? backendStart.label : displayRoute.model); },
|
|
599
812
|
...(backendStart === undefined || backendPermissions === undefined ? {} : {
|
|
600
813
|
backendLabel: backendStart.label,
|
|
601
814
|
openSession: backendStart.open,
|
|
@@ -605,6 +818,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
605
818
|
sessionPrefs: backendStart.sessionPrefs,
|
|
606
819
|
initialHistory: backendStart.initialHistory,
|
|
607
820
|
resumeCommand: backendStart.resumeCommand,
|
|
821
|
+
...(backendStartup === undefined ? {} : { startup: backendStartup }),
|
|
822
|
+
}),
|
|
823
|
+
// The in-process DSH kernel: a DSH placeholder until the row's session.
|
|
824
|
+
...(dshStartup === undefined ? {} : {
|
|
825
|
+
backendLabel: backendLabel('dsh'),
|
|
826
|
+
startup: dshStartup,
|
|
827
|
+
openSession: async (target) => {
|
|
828
|
+
if (target.kind !== 'create' || openDshSession === undefined)
|
|
829
|
+
throw new Error(t('new-session-unavailable'));
|
|
830
|
+
return await openDshSession(target.cwd);
|
|
831
|
+
},
|
|
608
832
|
}),
|
|
609
833
|
// The activity projection only pushes on change; read the current value as
|
|
610
834
|
// soon as this session binds so a resumed or reattached session renders its
|
|
@@ -626,17 +850,19 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
626
850
|
// sessions record sessionCwd at creation, so both agree there.
|
|
627
851
|
// A resumed backend session runs where it was recorded.
|
|
628
852
|
cwd: backendStart?.session.cwd ?? agent?.session.header.cwd ?? sessionCwd,
|
|
629
|
-
provider
|
|
853
|
+
get provider() { return rowFacts.route?.provider ?? (backendStart !== undefined ? backendStart.backendId : displayRoute.provider); },
|
|
630
854
|
// Raw cordis.yml route (undefined when unset): the channel's
|
|
631
855
|
// new-session path re-resolves prefs against these, and resume passes
|
|
632
856
|
// only explicit values so the target session's own record wins.
|
|
633
|
-
configuredModel: config.model,
|
|
634
|
-
configuredProvider: config.provider,
|
|
857
|
+
get configuredModel() { return rowFacts.config === undefined ? config.model : rowFacts.config.model; },
|
|
858
|
+
get configuredProvider() { return rowFacts.config === undefined ? config.provider : rowFacts.config.provider; },
|
|
635
859
|
// Raw cordis.yml `lang` / `activityFrames`: /reload must not override a
|
|
636
860
|
// static deployment choice with the persisted preference.
|
|
637
861
|
configuredLang: config.lang,
|
|
638
862
|
configuredActivityFrames: config.activityFrames,
|
|
639
|
-
|
|
863
|
+
// Read when the DSH extensions attach (the adoption), after the row's
|
|
864
|
+
// facts are in; the display fields below are fixed at the first frame.
|
|
865
|
+
get effort() { return rowFacts.config === undefined ? config.effort : rowFacts.config.effort; },
|
|
640
866
|
activity: config.activity,
|
|
641
867
|
// Explicit cordis.yml value (static deployment choice) wins over the
|
|
642
868
|
// runtime `/activity` preference, which wins over the default.
|
|
@@ -645,14 +871,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
645
871
|
contextBar: config.contextBar,
|
|
646
872
|
// Same precedence for the agent preset: cordis.yml `preset` over the
|
|
647
873
|
// persisted `/preset` choice; undefined adopts the roster default.
|
|
648
|
-
configuredPreset: config.preset,
|
|
874
|
+
get configuredPreset() { return rowFacts.config === undefined ? config.preset : rowFacts.config.preset; },
|
|
649
875
|
agentPreset,
|
|
650
876
|
// Shift+Tab session-mode cycle (undefined → the built-in default/
|
|
651
877
|
// plan/full cycle in sessionModes.ts).
|
|
652
|
-
modes: config.modes,
|
|
878
|
+
get modes() { return rowFacts.config === undefined ? config.modes : rowFacts.config.modes; },
|
|
653
879
|
// Edit/Write diff presentation (schema default 'auto'); the /settings
|
|
654
880
|
// screen edits this key live through the dsh-tui namespace.
|
|
655
881
|
diffLayout: config.diffLayout,
|
|
882
|
+
// Upstream auto-retry seeding (cordis.yml upstreamRetry, default on):
|
|
883
|
+
// only an explicit opt-out disables it.
|
|
884
|
+
get upstreamRetry() { return rowFacts.config === undefined ? config.upstreamRetry : rowFacts.config.upstreamRetry; },
|
|
656
885
|
thinkingFold: config.thinkingFold,
|
|
657
886
|
jobGroupFold: config.jobGroupFold,
|
|
658
887
|
toolBackground: config.toolBackground,
|
|
@@ -674,6 +903,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
674
903
|
// 的改动由 applyBrand 实时接上(branding.ts 负责解析)。
|
|
675
904
|
brand: config.brand,
|
|
676
905
|
});
|
|
906
|
+
backendStartupAdopted = backendStartup !== undefined;
|
|
677
907
|
// Register the live Channel for the adapter Kernel. The Channel driver
|
|
678
908
|
// resolves it lazily from the composition root, so this can be called after
|
|
679
909
|
// the plugin-host Kernel started without requiring a re-mount.
|
|
@@ -682,9 +912,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
682
912
|
// child activation context.
|
|
683
913
|
const unregisterTuiChannel = registerTuiChannel(compositionRoot(ctx), rawChannel);
|
|
684
914
|
ctx.effect(() => () => { unregisterTuiChannel(); });
|
|
685
|
-
const pluginHost = ctx.get('tuiPluginHost');
|
|
686
915
|
const adapterRuntime = adapterRuntimeFor(ctx);
|
|
687
|
-
|
|
916
|
+
// Looked up per probe: the plugin-host row can compose after the mount.
|
|
917
|
+
const uiMount = mountChannelUi(ctx, rawChannel, ctx.get('tuiPluginHost'), adapterRuntime.mode, () => ctx.get('tuiPluginHost'));
|
|
688
918
|
const channel = uiMount.channel;
|
|
689
919
|
bindChannelCommands(rawChannel, channel);
|
|
690
920
|
// last-run.json for the launcher's crash retry (see writeLastRunRecord):
|
|
@@ -692,6 +922,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
692
922
|
// at that point. The kernel-switch branch skips the refresh because the
|
|
693
923
|
// replacement writes its own record when it boots.
|
|
694
924
|
const bootAttemptId = `${process.pid.toString(36)}-${Date.now().toString(36)}`;
|
|
925
|
+
/** None before DSH has composed (the in-process DSH kernel) or off DSH. */
|
|
926
|
+
const liveDshAgent = () => dshAgentOn(ctx, channel.agentId);
|
|
695
927
|
const refreshLastRunRecord = () => {
|
|
696
928
|
// An observational composition (replay/embedding) is not "the instance the
|
|
697
929
|
// user ran last" — it must not overwrite the interactive record.
|
|
@@ -701,7 +933,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
701
933
|
? backendStart.persisted(channel.agentId, channel.rows)
|
|
702
934
|
: isExitResumable({
|
|
703
935
|
pendingCount: channel.pending.length,
|
|
704
|
-
liveAgent:
|
|
936
|
+
liveAgent: liveDshAgent(),
|
|
705
937
|
startupAgent: agent,
|
|
706
938
|
});
|
|
707
939
|
writeLastRunRecord({
|
|
@@ -725,6 +957,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
725
957
|
// lands once the channel can actually show it.
|
|
726
958
|
if (backendFallbackNotice !== undefined)
|
|
727
959
|
notifyChannel(backendFallbackNotice, { color: 'warning' });
|
|
960
|
+
// The entry could not use the installed dsh and fell back (this process,
|
|
961
|
+
// or the `dsh --profile` it handed the launch to): say why, once.
|
|
962
|
+
const hostNotice = process.env[HOST_NOTICE_ENV];
|
|
963
|
+
delete process.env[HOST_NOTICE_ENV];
|
|
964
|
+
if (hostNotice !== undefined && hostNotice !== '') {
|
|
965
|
+
notifyChannel(t('host-dsh-unavailable', { reason: hostNotice }), { color: 'warning', timeoutMs: 10000 });
|
|
966
|
+
}
|
|
728
967
|
const submitChannel = text => {
|
|
729
968
|
if (!shadow)
|
|
730
969
|
channel.submit(text);
|
|
@@ -744,7 +983,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
744
983
|
applySidePanelSplitEnabled(config.sidePanel?.splitEnabled);
|
|
745
984
|
applySidePanelOpen(config.sidePanel?.open);
|
|
746
985
|
applySidePanelRatio(config.sidePanel?.ratio);
|
|
747
|
-
|
|
986
|
+
// Registered ids survive: on the profile path a third-party row may have
|
|
987
|
+
// registered its panel before this row applied (./panels.ts).
|
|
988
|
+
applyConfiguredSidePanelPanels(config.sidePanel?.panels);
|
|
748
989
|
applyCompanionSkin(config.companion?.skin);
|
|
749
990
|
applyBtwContextTurns(config.btw?.contextTurns);
|
|
750
991
|
applyBtwContextBudget(config.btw?.contextBudget);
|
|
@@ -759,14 +1000,16 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
759
1000
|
// Without the extensions row (tuiToast absent) plugin toasts are dropped
|
|
760
1001
|
// by the runtime itself — same soft-degrade contract as the other seams.
|
|
761
1002
|
// Delivery uses the same owner-bound UI capability as all renderer actions.
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1003
|
+
const bindToastSink = () => {
|
|
1004
|
+
getHostToastStore(ctx.get('tuiToast'))?.setSink(delivery => {
|
|
1005
|
+
notifyChannel(delivery.text, { color: delivery.color, timeoutMs: delivery.timeoutMs });
|
|
1006
|
+
});
|
|
1007
|
+
};
|
|
1008
|
+
bindToastSink();
|
|
1009
|
+
if (questionSeams?.registration.kind === 'waterfall') {
|
|
767
1010
|
// Ownership follows the mutable channel; registration cleanup belongs to
|
|
768
1011
|
// this Cordis fiber.
|
|
769
|
-
|
|
1012
|
+
questionSeams.registration.register(channel);
|
|
770
1013
|
}
|
|
771
1014
|
// Fullscreen layout decision: the settings user layer (edited through the
|
|
772
1015
|
// /settings screen) overrides cordis.yml when set. The settings injection
|
|
@@ -777,6 +1020,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
777
1020
|
// (swapping layouts requires re-mounting the whole tree).
|
|
778
1021
|
let bootedFullscreen = config.fullscreen === true;
|
|
779
1022
|
let bootedTerminalImages = lastBootedTerminalImages ?? config.terminalImages ?? true;
|
|
1023
|
+
// The launchpad boot decision (settings `launchpad`): same
|
|
1024
|
+
// restart-to-apply pattern as fullscreen — resolved before the tree mounts
|
|
1025
|
+
// from the settings user layer over cordis.yml; a mid-session edit is for
|
|
1026
|
+
// the next boot, the running session keeps what it booted with.
|
|
1027
|
+
let bootedLaunchpad = config.launchpad !== false;
|
|
780
1028
|
let rendererSettingsFrozen = false;
|
|
781
1029
|
const terminalImagesDisabledByEnv = isEnvTruthy(process.env.DSH_TUI_DISABLE_TERMINAL_IMAGES);
|
|
782
1030
|
// The settings service may come up AFTER this plugin's apply: the cordis
|
|
@@ -791,10 +1039,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
791
1039
|
resolveSettingsReady = () => resolve();
|
|
792
1040
|
setTimeout(resolve, 300);
|
|
793
1041
|
});
|
|
794
|
-
|
|
795
|
-
// volatile Config fields instead; both paths apply edits without remounting.
|
|
796
|
-
ctx.inject(['settings'], (settingsCtx) => {
|
|
797
|
-
// Loader targets the Config owner's fiber, not the injected child fiber.
|
|
1042
|
+
((settingsCtx) => {
|
|
798
1043
|
const scope = createSettingsScope(configOwner, settingsCtx.settings, tuiSettingsNs, Schema.object({
|
|
799
1044
|
diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
|
|
800
1045
|
thinkingFold: Schema.union(['preview', 'full']).default('preview'),
|
|
@@ -822,6 +1067,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
822
1067
|
expandEditor: Schema.boolean(),
|
|
823
1068
|
// Same no-default rule: applyDisplay resolves `?? config.smoothStreaming ?? true`.
|
|
824
1069
|
smoothStreaming: Schema.boolean(),
|
|
1070
|
+
taskNotifications: Schema.union(['off', 'unfocused', 'always']),
|
|
1071
|
+
notificationMethod: Schema.union(['auto', 'osc9', 'bel']),
|
|
825
1072
|
// Same no-default rule: applyDisplay resolves `?? config.mermaidDiagrams ?? true`.
|
|
826
1073
|
mermaidDiagrams: Schema.boolean(),
|
|
827
1074
|
// Code-frame shape; unset keeps the light rail frame.
|
|
@@ -913,6 +1160,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
913
1160
|
// Same no-default rule: unset keeps cordis.yml's `fullscreen`
|
|
914
1161
|
// decisive; set overrides it from the next boot on.
|
|
915
1162
|
fullscreen: Schema.boolean(),
|
|
1163
|
+
// Same no-default rule as fullscreen: the launchpad boot gate reads
|
|
1164
|
+
// `value.launchpad ?? config.launchpad ?? true`, so cordis.yml stays
|
|
1165
|
+
// decisive while the user layer is unset.
|
|
1166
|
+
launchpad: Schema.boolean(),
|
|
916
1167
|
// Unset inherits cordis.yml; a saved choice takes effect after restart.
|
|
917
1168
|
terminalImages: Schema.boolean(),
|
|
918
1169
|
// Built-in action-shortcut overrides, one optional combo string per
|
|
@@ -969,8 +1220,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
969
1220
|
if (typeof value.fullscreen === 'boolean') {
|
|
970
1221
|
bootedFullscreen = value.fullscreen;
|
|
971
1222
|
}
|
|
1223
|
+
if (typeof value.launchpad === 'boolean') {
|
|
1224
|
+
bootedLaunchpad = value.launchpad;
|
|
1225
|
+
}
|
|
972
1226
|
bootedTerminalImages = lastBootedTerminalImages ?? value.terminalImages ?? config.terminalImages ?? true;
|
|
973
1227
|
};
|
|
1228
|
+
/** False only during the boot application below: a registered panel absent
|
|
1229
|
+
* from its CSV is not a user removal (./panels.ts). */
|
|
1230
|
+
let settingsEditsLive = true;
|
|
974
1231
|
// The /settings language field writes `lang` through the settings
|
|
975
1232
|
// service (user layer): apply it live and mirror it to lang.json so
|
|
976
1233
|
// the /lang command and next-boot resolution agree. DSH_TUI_LANG
|
|
@@ -1002,6 +1259,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1002
1259
|
channel.setPromptSessionLabel(value.promptSessionLabel ?? config.promptSessionLabel ?? false);
|
|
1003
1260
|
channel.setExpandEditor(value.expandEditor ?? config.expandEditor ?? true);
|
|
1004
1261
|
channel.setSmoothStreaming(value.smoothStreaming ?? config.smoothStreaming ?? true);
|
|
1262
|
+
taskNotifier.configure(value.taskNotifications ?? config.taskNotifications, value.notificationMethod ?? config.notificationMethod);
|
|
1005
1263
|
applyMermaidDiagrams(value.mermaidDiagrams ?? config.mermaidDiagrams);
|
|
1006
1264
|
applyCodeFrameStyle(value.codeFrameStyle ?? config.codeFrameStyle);
|
|
1007
1265
|
applyMathRendering(resolveMathRendering(value, config));
|
|
@@ -1016,7 +1274,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1016
1274
|
applySidePanelSplitEnabled(value.sidePanel?.splitEnabled ?? config.sidePanel?.splitEnabled);
|
|
1017
1275
|
applySidePanelOpen(value.sidePanel?.open ?? config.sidePanel?.open);
|
|
1018
1276
|
applySidePanelRatio(value.sidePanel?.ratio ?? config.sidePanel?.ratio);
|
|
1019
|
-
|
|
1277
|
+
// A registered panel missing from a live edit was unticked in /settings.
|
|
1278
|
+
const panelsCsv = value.sidePanel?.panels ?? config.sidePanel?.panels;
|
|
1279
|
+
if (settingsEditsLive)
|
|
1280
|
+
applySidePanelPanelsFromSettings(panelsCsv);
|
|
1281
|
+
else
|
|
1282
|
+
applyConfiguredSidePanelPanels(panelsCsv);
|
|
1020
1283
|
applyCompanionSkin(value.companion?.skin ?? config.companion?.skin);
|
|
1021
1284
|
applyBtwContextTurns(value.btw?.contextTurns ?? config.btw?.contextTurns);
|
|
1022
1285
|
applyBtwContextBudget(value.btw?.contextBudget ?? config.btw?.contextBudget);
|
|
@@ -1090,8 +1353,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1090
1353
|
notifyChannel(t('settings-fullscreen-migrated'), { color: 'warning' });
|
|
1091
1354
|
}
|
|
1092
1355
|
const { fullscreen: staleFullscreen, ...migratedSettings } = bootSettings;
|
|
1356
|
+
settingsEditsLive = false;
|
|
1093
1357
|
apply(fullscreenMigration === 'unset' ? migratedSettings : bootSettings);
|
|
1358
|
+
settingsEditsLive = true;
|
|
1094
1359
|
let lastTerminalImages = bootSettings.terminalImages ?? config.terminalImages ?? true;
|
|
1360
|
+
let lastLaunchpad = bootSettings.launchpad ?? config.launchpad ?? true;
|
|
1095
1361
|
settingsCtx.effect(() => scope.watch(next => {
|
|
1096
1362
|
apply(next);
|
|
1097
1363
|
if (typeof next.fullscreen === 'boolean' && next.fullscreen !== bootedFullscreen) {
|
|
@@ -1102,9 +1368,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1102
1368
|
channel.notify(t('settings-terminal-images-restart'), { color: 'warning' });
|
|
1103
1369
|
}
|
|
1104
1370
|
lastTerminalImages = terminalImages;
|
|
1371
|
+
const launchpad = next.launchpad ?? config.launchpad ?? true;
|
|
1372
|
+
if (launchpad !== lastLaunchpad && launchpad !== bootedLaunchpad) {
|
|
1373
|
+
notifyChannel(t('settings-launchpad-restart'), { color: 'warning' });
|
|
1374
|
+
}
|
|
1375
|
+
lastLaunchpad = launchpad;
|
|
1105
1376
|
}));
|
|
1106
1377
|
resolveSettingsReady?.();
|
|
1107
|
-
});
|
|
1378
|
+
})({ settings: tuiSettings, effect: ctx.effect.bind(ctx) });
|
|
1108
1379
|
// The /settings screen's own section: the dsh-tui namespace comes from
|
|
1109
1380
|
// the settings registration above, and the declared selects write `lang`
|
|
1110
1381
|
// and `diffLayout` back through the settings service's revision-fenced
|
|
@@ -1147,9 +1418,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1147
1418
|
// left this registration silently skipped and /settings read-only.
|
|
1148
1419
|
// channel.ts reads through the same fallback, so both sides meet in the
|
|
1149
1420
|
// same registry either way.
|
|
1421
|
+
// The in-process DSH kernel registers into the local host first and moves
|
|
1422
|
+
// the section to the composition's service once it exists.
|
|
1423
|
+
let rehomeSettingsSection = () => undefined;
|
|
1150
1424
|
{
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1425
|
+
let settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost(ctx);
|
|
1426
|
+
const tuiSection = {
|
|
1153
1427
|
ns: tuiSettingsNs,
|
|
1154
1428
|
title: 'dsh-tui',
|
|
1155
1429
|
groups: [...SETTING_GROUPS],
|
|
@@ -1258,6 +1532,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1258
1532
|
return String(typeof value === 'boolean' ? value : config.mermaidDiagrams !== false);
|
|
1259
1533
|
},
|
|
1260
1534
|
},
|
|
1535
|
+
{
|
|
1536
|
+
...settingField('taskNotifications'),
|
|
1537
|
+
format: value => String(value ?? config.taskNotifications ?? 'unfocused'),
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
...settingField('notificationMethod'),
|
|
1541
|
+
format: value => String(value ?? config.notificationMethod ?? 'auto'),
|
|
1542
|
+
},
|
|
1261
1543
|
{
|
|
1262
1544
|
...settingField('codeFrameStyle'),
|
|
1263
1545
|
},
|
|
@@ -1284,10 +1566,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1284
1566
|
...settingField('effortDefault'),
|
|
1285
1567
|
format(value) {
|
|
1286
1568
|
// Unset in settings.yaml: show what a boot would actually start
|
|
1287
|
-
// on (the
|
|
1569
|
+
// on (the persisted /effort choice → the cordis effort default)
|
|
1288
1570
|
// instead of a misleading blank.
|
|
1289
1571
|
if (value === undefined || value === null || value === 'auto') {
|
|
1290
|
-
return config.effort ??
|
|
1572
|
+
return readEffortPref() ?? config.effort ?? 'auto';
|
|
1291
1573
|
}
|
|
1292
1574
|
return String(value);
|
|
1293
1575
|
},
|
|
@@ -1440,6 +1722,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1440
1722
|
return { kind: 'set', value: budget };
|
|
1441
1723
|
},
|
|
1442
1724
|
},
|
|
1725
|
+
{
|
|
1726
|
+
...settingField('launchpad'),
|
|
1727
|
+
format(value) {
|
|
1728
|
+
// Unset in settings.yaml: the effective default is on — same
|
|
1729
|
+
// rule as expandEditor's field.
|
|
1730
|
+
return String(typeof value === 'boolean' ? value : config.launchpad !== false);
|
|
1731
|
+
},
|
|
1732
|
+
},
|
|
1443
1733
|
{
|
|
1444
1734
|
...settingField('companion.skin'),
|
|
1445
1735
|
},
|
|
@@ -1471,8 +1761,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1471
1761
|
...settingField('minimal'),
|
|
1472
1762
|
},
|
|
1473
1763
|
],
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1764
|
+
};
|
|
1765
|
+
let unregister = settingsSections.register(tuiSection);
|
|
1766
|
+
ctx.effect(() => () => { unregister(); });
|
|
1767
|
+
rehomeSettingsSection = () => {
|
|
1768
|
+
const service = getHostSettingsSections(ctx.get('tuiSettingsSections'));
|
|
1769
|
+
if (service === undefined || service === settingsSections)
|
|
1770
|
+
return;
|
|
1771
|
+
unregister();
|
|
1772
|
+
settingsSections = service;
|
|
1773
|
+
unregister = service.register(tuiSection);
|
|
1774
|
+
};
|
|
1476
1775
|
}
|
|
1477
1776
|
// DSH approval seam: the permission layer asks ApprovalService.request(),
|
|
1478
1777
|
// which dispatches an `approval/request` waterfall. With no answerer the
|
|
@@ -1486,19 +1785,24 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1486
1785
|
bindApprovalStore(ctx, approvalStore);
|
|
1487
1786
|
// A non-DSH backend answers its own permission prompts: the DSH answerer
|
|
1488
1787
|
// is not registered for it.
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1788
|
+
// The in-process DSH kernel installs it in `attachDsh`.
|
|
1789
|
+
const mountDshApprovals = (dshCtx) => {
|
|
1790
|
+
if (dshCtx.get('approval') !== undefined && backendStart === undefined) {
|
|
1791
|
+
dshCtx.on('approval/request', (req, next) => approvalStore.park(req).catch(() => next()));
|
|
1792
|
+
// Badge-flip push (P-4): React does not know the session log appended —
|
|
1793
|
+
// a source-badge verdict that only flips inside getSnapshot() surfaces
|
|
1794
|
+
// solely when something else re-renders. Feed the session firehose to
|
|
1795
|
+
// the store: it reacts only to tool/result (the sole verdict-flipping
|
|
1796
|
+
// event type), and its internal log-length memo skips appends from any
|
|
1797
|
+
// session other than the active ask's, so no agent filtering is needed
|
|
1798
|
+
// here. The firehose fires post-commit, after the event entered
|
|
1799
|
+
// session.events, so the recheck sees the settled result.
|
|
1800
|
+
dshCtx.on('session/event', (session, event) => approvalStore.noteSessionEvent(session.id, event));
|
|
1801
|
+
dshCtx.effect(() => () => approvalStore.settleAll('cancelled'));
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
if (!dshInEntry)
|
|
1805
|
+
mountDshApprovals(ctx);
|
|
1502
1806
|
// The agent view reads parked ask ids for its "needs input" state.
|
|
1503
1807
|
rawChannel.bindApprovalStore(approvalStore);
|
|
1504
1808
|
// The panel source Chat renders: the backend's own prompts off DSH.
|
|
@@ -1518,9 +1822,25 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1518
1822
|
// Submit once the channel exists; delivery goes through the normal pending/inbox
|
|
1519
1823
|
// chain, so no special timing is needed. The parser separates startup flags
|
|
1520
1824
|
// from literal prompt text.
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1825
|
+
// The standalone entry has no `ctx.cmdlineArgs` (its argv is the app's); a
|
|
1826
|
+
// replacement keeps the arguments but the first process sent their prompt.
|
|
1827
|
+
const initialPrompt = launchPromptSent ? '' : initialPromptFromCmdlineArgs(cmdlineArgs ?? process.argv.slice(2));
|
|
1828
|
+
if (initialPrompt) {
|
|
1829
|
+
// A placeholder session refuses sends and nothing replays them after
|
|
1830
|
+
// adoption: hold the prompt until the channel is ready.
|
|
1831
|
+
if (channel.ready) {
|
|
1832
|
+
submitChannel(initialPrompt);
|
|
1833
|
+
}
|
|
1834
|
+
else {
|
|
1835
|
+
const unsubscribe = channel.subscribe(() => {
|
|
1836
|
+
if (!channel.ready)
|
|
1837
|
+
return;
|
|
1838
|
+
unsubscribe();
|
|
1839
|
+
submitChannel(initialPrompt);
|
|
1840
|
+
});
|
|
1841
|
+
ctx.effect(() => unsubscribe, 'dsh-tui initial prompt after startup');
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1524
1844
|
// Attach the stderr reporter to the live channel and flush anything a
|
|
1525
1845
|
// startup-spawned server produced while the channel didn't exist yet.
|
|
1526
1846
|
notifyStderr = (text, options) => notifyChannel(text, options);
|
|
@@ -1554,10 +1874,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1554
1874
|
// accepted; the exit funnel then respawns onto that kernel (a NEW session —
|
|
1555
1875
|
// no resume markers at all).
|
|
1556
1876
|
let backendSwitchRequested;
|
|
1877
|
+
// A signal or `ctx.appExit` via the entry's exit seam: the clean-exit branch
|
|
1878
|
+
// runs as for `/quit`, then ends the process by the signal / with the code.
|
|
1879
|
+
let processExitRequest;
|
|
1557
1880
|
// The profile this process was booted with (`dsh --profile <name>`); dsh
|
|
1558
1881
|
// exposes it nowhere else, and /update must update the installation the
|
|
1559
1882
|
// user is actually running, not a hard-coded one.
|
|
1560
|
-
const profile = resolveDshProfileName();
|
|
1883
|
+
const profile = resolveDshProfileName() ?? runtimeOptions.profile;
|
|
1561
1884
|
// Single exit funnel: `/exit` and double Ctrl+C land here, and so does
|
|
1562
1885
|
// the unmount triggered by a cordis context teardown — but the two must
|
|
1563
1886
|
// not share a fate (issue #12). Teardown only unmounts the UI; user exit
|
|
@@ -1565,7 +1888,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1565
1888
|
// update handoff or resume hint).
|
|
1566
1889
|
/** The session id for a restart or update handoff, or empty when the backend
|
|
1567
1890
|
* has not persisted it and the replacement must start fresh. */
|
|
1568
|
-
const handoffSessionId = () =>
|
|
1891
|
+
const handoffSessionId = () => {
|
|
1892
|
+
if (backendStart !== undefined)
|
|
1893
|
+
return backendStart.persisted(channel.agentId, channel.rows) ? channel.agentId : '';
|
|
1894
|
+
return isUnstoredFreshSession(liveDshAgent()?.session) ? '' : channel.agentId;
|
|
1895
|
+
};
|
|
1569
1896
|
const handoffHint = backendStart === undefined ? undefined : (sessionId) => backendStart.resumeCommand(sessionId);
|
|
1570
1897
|
const funnel = createExitFunnel({
|
|
1571
1898
|
onUserExit: error => {
|
|
@@ -1589,18 +1916,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1589
1916
|
// never CLEARS a marker, so a session the user still has cannot be
|
|
1590
1917
|
// dropped by a failure that happened before the first message landed.
|
|
1591
1918
|
writeResumeMarkers: () => {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1919
|
+
writeCrashResumeMarkers({
|
|
1920
|
+
ctx,
|
|
1921
|
+
backendStart,
|
|
1922
|
+
channel,
|
|
1595
1923
|
startupAgent: agent,
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
}
|
|
1599
|
-
// Non-DSH sessions use their backend preference as the launcher marker.
|
|
1600
|
-
if (backendStart !== undefined && backendStart.persisted(channel.agentId, channel.rows))
|
|
1601
|
-
backendStart.sessionPrefs.setLastSession(channel.agentId);
|
|
1602
|
-
// So the launcher's retry reopens this session, not the boot-time one.
|
|
1603
|
-
refreshLastRunRecord();
|
|
1924
|
+
writeResumeTarget,
|
|
1925
|
+
refreshLastRunRecord,
|
|
1926
|
+
});
|
|
1604
1927
|
},
|
|
1605
1928
|
finish: crashLine => {
|
|
1606
1929
|
void finishExit(ctx, instance, bootedFullscreen, undefined, crashLine, () => disposeRootAndExit(ctx, 1));
|
|
@@ -1611,8 +1934,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1611
1934
|
if (updateRequested) {
|
|
1612
1935
|
try {
|
|
1613
1936
|
// Non-DSH sessions keep their marker in backend prefs, not DSH's `resume.txt`.
|
|
1614
|
-
if (backendStart === undefined)
|
|
1615
|
-
|
|
1937
|
+
if (backendStart === undefined) {
|
|
1938
|
+
const sessionId = handoffSessionId();
|
|
1939
|
+
if (sessionId === '')
|
|
1940
|
+
clearResumeTarget();
|
|
1941
|
+
else
|
|
1942
|
+
writeResumeTarget(sessionId);
|
|
1943
|
+
}
|
|
1616
1944
|
else if (backendStart.persisted(channel.agentId, channel.rows))
|
|
1617
1945
|
backendStart.sessionPrefs.setLastSession(channel.agentId);
|
|
1618
1946
|
}
|
|
@@ -1623,7 +1951,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1623
1951
|
const hintText = isStandaloneRuntime()
|
|
1624
1952
|
? t('update-standalone-starting')
|
|
1625
1953
|
: t('update-starting');
|
|
1626
|
-
void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint));
|
|
1954
|
+
void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint, exitSeam));
|
|
1627
1955
|
return;
|
|
1628
1956
|
}
|
|
1629
1957
|
// The kernel selector's switch: the same respawn machinery, no resume.
|
|
@@ -1633,30 +1961,41 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1633
1961
|
// inherited DSH_TUI_RESUME_SESSION marker from the replacement env.
|
|
1634
1962
|
// kernel.json was already written when the choice was accepted.
|
|
1635
1963
|
if (backendSwitchRequested !== undefined) {
|
|
1636
|
-
|
|
1964
|
+
// Captured: the closure below runs after this block, where the outer
|
|
1965
|
+
// `let` is no longer narrowed.
|
|
1966
|
+
const switchingTo = backendSwitchRequested;
|
|
1967
|
+
logRestartEvent('funnel: backend-switch branch entered', { backend: switchingTo });
|
|
1637
1968
|
// Fullscreen keeps the alternate screen and writes the "switching"
|
|
1638
1969
|
// notice into it until the replacement takes over (see handoffAck.ts);
|
|
1639
1970
|
// inline restores the main screen and writes the notice there.
|
|
1640
|
-
logRestartEvent(handoffEventTag('starting'), { backend:
|
|
1971
|
+
logRestartEvent(handoffEventTag('starting'), { backend: switchingTo });
|
|
1641
1972
|
const keepAlt = bootedFullscreen;
|
|
1642
|
-
void finishExit(ctx, instance, bootedFullscreen, formatHandoffNotice('starting', { name:
|
|
1643
|
-
backend:
|
|
1973
|
+
void finishExit(ctx, instance, bootedFullscreen, formatHandoffNotice('starting', { name: backendLabel(switchingTo), color: process.stdout.isTTY === true }), undefined, () => runRestart(ctx, profile, '', undefined, {
|
|
1974
|
+
backend: switchingTo,
|
|
1975
|
+
// The replacement's parent prints the same name this process just
|
|
1976
|
+
// put on screen (update.ts has no registry to look it up in).
|
|
1977
|
+
backendName: backendLabel(switchingTo),
|
|
1644
1978
|
...(keepAlt ? { handoffScreen: 'alt' } : {}),
|
|
1645
|
-
}), { keepAltScreen: keepAlt });
|
|
1979
|
+
}, exitSeam), { keepAltScreen: keepAlt });
|
|
1646
1980
|
return;
|
|
1647
1981
|
}
|
|
1648
1982
|
// `/restart`: same handoff as the update path, no installation step.
|
|
1649
|
-
//
|
|
1650
|
-
//
|
|
1983
|
+
// A fresh initialization-only session has no log to reopen. Restart
|
|
1984
|
+
// it fresh; saved sessions keep their identity across the handoff.
|
|
1651
1985
|
if (restartRequested) {
|
|
1652
1986
|
beginRestartAttempt(channel.agentId);
|
|
1653
1987
|
logRestartEvent('funnel: /restart branch entered');
|
|
1654
1988
|
try {
|
|
1655
|
-
if (backendStart === undefined)
|
|
1656
|
-
|
|
1989
|
+
if (backendStart === undefined) {
|
|
1990
|
+
const sessionId = handoffSessionId();
|
|
1991
|
+
if (sessionId === '')
|
|
1992
|
+
clearResumeTarget();
|
|
1993
|
+
else
|
|
1994
|
+
writeResumeTarget(sessionId);
|
|
1995
|
+
}
|
|
1657
1996
|
else if (backendStart.persisted(channel.agentId, channel.rows))
|
|
1658
1997
|
backendStart.sessionPrefs.setLastSession(channel.agentId);
|
|
1659
|
-
logRestartEvent('funnel: resume target
|
|
1998
|
+
logRestartEvent('funnel: resume target updated');
|
|
1660
1999
|
}
|
|
1661
2000
|
catch (error) {
|
|
1662
2001
|
// Resume persistence is best effort and must never block a restart.
|
|
@@ -1665,7 +2004,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1665
2004
|
});
|
|
1666
2005
|
}
|
|
1667
2006
|
refreshLastRunRecord();
|
|
1668
|
-
void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }));
|
|
2007
|
+
void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }, exitSeam));
|
|
1669
2008
|
return;
|
|
1670
2009
|
}
|
|
1671
2010
|
// Judge against the live session behind the channel (channel.agentId),
|
|
@@ -1689,7 +2028,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1689
2028
|
else {
|
|
1690
2029
|
const resumable = isExitResumable({
|
|
1691
2030
|
pendingCount: channel.pending.length,
|
|
1692
|
-
liveAgent:
|
|
2031
|
+
liveAgent: liveDshAgent(),
|
|
1693
2032
|
startupAgent: agent,
|
|
1694
2033
|
});
|
|
1695
2034
|
try {
|
|
@@ -1707,10 +2046,39 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1707
2046
|
}
|
|
1708
2047
|
// Same resumability as the markers above.
|
|
1709
2048
|
refreshLastRunRecord();
|
|
1710
|
-
|
|
2049
|
+
const request = processExitRequest;
|
|
2050
|
+
void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => {
|
|
2051
|
+
if (request === undefined)
|
|
2052
|
+
disposeRootAndExit(ctx, 0);
|
|
2053
|
+
else if (request.kind === 'code')
|
|
2054
|
+
disposeRootAndExit(ctx, request.code);
|
|
2055
|
+
// A signal: the process dies by it once the root is down (bounded
|
|
2056
|
+
// as any dispose; the stalled case dies by it too).
|
|
2057
|
+
else
|
|
2058
|
+
disposeRootAndThen(ctx, () => { dieBySignal(request.signal); }, () => { dieBySignal(request.signal); });
|
|
2059
|
+
});
|
|
1711
2060
|
},
|
|
1712
2061
|
});
|
|
1713
2062
|
const handleExit = funnel.handleExit;
|
|
2063
|
+
const exitSeam = runtimeOptions.exitSeam;
|
|
2064
|
+
const requestProcessExit = (request) => {
|
|
2065
|
+
// A handoff is under way: no backstop, which could kill the supervisor of
|
|
2066
|
+
// a replacement already up; superviseReplacement takes the seam over.
|
|
2067
|
+
if (updateRequested || restartRequested || backendSwitchRequested !== undefined)
|
|
2068
|
+
return 'supervising';
|
|
2069
|
+
// A `/quit` or a crash is already ending this process.
|
|
2070
|
+
if (exited)
|
|
2071
|
+
return 'pending';
|
|
2072
|
+
processExitRequest = request;
|
|
2073
|
+
logRestartEvent('funnel: process exit requested', request.kind === 'signal' ? { signal: request.signal } : { code: request.code });
|
|
2074
|
+
if (handleExit())
|
|
2075
|
+
return 'exiting';
|
|
2076
|
+
// The tree is being torn down: the funnel cannot own the process.
|
|
2077
|
+
processExitRequest = undefined;
|
|
2078
|
+
return 'refused';
|
|
2079
|
+
};
|
|
2080
|
+
if (exitSeam !== undefined)
|
|
2081
|
+
exitSeam.request = requestProcessExit;
|
|
1714
2082
|
/** The launchpad kernel selector's accept path (the Chat side passes the
|
|
1715
2083
|
* chosen kernel through onSwitchBackend — the prop wiring lands together
|
|
1716
2084
|
* with the selector's Chat rendering). Persists the choice BEFORE the
|
|
@@ -1724,7 +2092,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1724
2092
|
backendSwitchRequested = backend;
|
|
1725
2093
|
writeKernelPrefs({ backend });
|
|
1726
2094
|
logRestartEvent('command: backend switch accepted', { backend });
|
|
1727
|
-
notifyChannel(t('kernel-switch-restarting', { name:
|
|
2095
|
+
notifyChannel(t('kernel-switch-restarting', { name: backendLabel(backend) }));
|
|
1728
2096
|
handleExit();
|
|
1729
2097
|
};
|
|
1730
2098
|
/** /channel changed the active channel's connection. The running CLI child
|
|
@@ -1818,18 +2186,21 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1818
2186
|
* and opens straight into its conversation.
|
|
1819
2187
|
*/
|
|
1820
2188
|
const dshBoot = backendStart === undefined;
|
|
1821
|
-
const noResume = isLandingLaunch({ launchSessionId, initialPrompt });
|
|
2189
|
+
const noResume = isLandingLaunch({ launchSessionId: bootSessionId, initialPrompt });
|
|
1822
2190
|
const openHomeOnBoot = dshBoot && !homeSeen && noResume && requestedWorkspace === undefined;
|
|
1823
2191
|
/**
|
|
1824
2192
|
* The launchpad is NOT one-shot the way the workspace home is: every
|
|
1825
2193
|
* ordinary launch starts on it, because it is where the first sentence gets
|
|
1826
2194
|
* typed rather than a tutorial that retires itself — on every backend: a
|
|
1827
2195
|
* remembered claude kernel lands here exactly like a dsh one (the `dshBoot`
|
|
1828
|
-
* gate below is deliberately absent). `
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
2196
|
+
* gate below is deliberately absent). The /settings `launchpad` toggle
|
|
2197
|
+
* (settings user layer over cordis.yml, resolved into `bootedLaunchpad`
|
|
2198
|
+
* above) is the persistent off switch — off boots straight into the chat
|
|
2199
|
+
* page. `DSH_TUI_NO_LAUNCHPAD=1` remains the env escape hatch (an
|
|
2200
|
+
* automation that wants the old blank conversation and no dialog in front
|
|
2201
|
+
* of it).
|
|
1831
2202
|
*/
|
|
1832
|
-
const launchpadOnBoot = noResume && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
|
|
2203
|
+
const launchpadOnBoot = noResume && bootedLaunchpad && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
|
|
1833
2204
|
/**
|
|
1834
2205
|
* The first-run guide. Gated on its own preference (not on `homeSeen`): the
|
|
1835
2206
|
* two answer different questions, and an install that already knows its
|
|
@@ -1839,7 +2210,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1839
2210
|
// 品牌镜像初值(branding.ts):首帧渲染前铺好,避免 Claude 后端先画一屏
|
|
1840
2211
|
// 蓝再变橙。Chat 里的 effect 会在品牌解析变化时跟进更新这个镜像。
|
|
1841
2212
|
setActiveBrand(resolveBrand(config.brand, backendChoice));
|
|
1842
|
-
const
|
|
2213
|
+
const liveThemeHost = () => getHostThemes(ctx.get('tuiThemes'));
|
|
2214
|
+
const buildChat = () => React.createElement(Chat, {
|
|
1843
2215
|
channel,
|
|
1844
2216
|
renderScene: createChannelSceneOutlet(() => rawChannel.pluginScene),
|
|
1845
2217
|
questionStore,
|
|
@@ -1859,7 +2231,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1859
2231
|
// no longer runs a second activity tracker of its own.
|
|
1860
2232
|
activityStore,
|
|
1861
2233
|
extensionShortcuts: getHostShortcuts(ctx.get('tuiShortcuts')),
|
|
1862
|
-
themeHost,
|
|
2234
|
+
themeHost: liveThemeHost(),
|
|
1863
2235
|
// Full-screen surfaces inside Chat — the trajectory scene and the session
|
|
1864
2236
|
// browser — enter the alt screen themselves in inline mode; in fullscreen
|
|
1865
2237
|
// the tree is already wrapped below, so they must not nest.
|
|
@@ -1880,12 +2252,15 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1880
2252
|
onSwitchBackend: switchBackend,
|
|
1881
2253
|
// /channel: restart with a new session after the connection changed.
|
|
1882
2254
|
onRestartFreshSession: restartFreshSession,
|
|
2255
|
+
// The backend registry as the picker sees it: the UI takes data, not the
|
|
2256
|
+
// registry (P0). dsh first, then the manifest order — picker order. Entries
|
|
2257
|
+
// that registered without an admission are not offered (B-2).
|
|
2258
|
+
kernelEntries: kernelEntriesOf(listOfferedBackends()),
|
|
1883
2259
|
onProbeKernels: () => probeKernels(ctx, sessionCwd),
|
|
1884
|
-
// The kernel picker's SDK install wizard (
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
sdkInstallPinned: sdkInstall.pinned,
|
|
2260
|
+
// The kernel picker's SDK install wizard (a dim row's Enter): the surface of
|
|
2261
|
+
// whichever row the user picked, looked up by id — the manifest says what to
|
|
2262
|
+
// install and with which executor, this host holds the actions.
|
|
2263
|
+
onResolveSdkInstall: installSurfaceFor,
|
|
1889
2264
|
kernelPinned: backendPinned,
|
|
1890
2265
|
// Only a `dsh --profile <name>` launch has a profile installation for
|
|
1891
2266
|
// `/update` to act on; source checkouts and `--config` overlays get the
|
|
@@ -1957,17 +2332,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1957
2332
|
// the screen border). It must sit INSIDE AlternateScreen: the alt-screen
|
|
1958
2333
|
// box sizes itself to the real terminal rows, while PageMargin reports
|
|
1959
2334
|
// content-box dimensions to everything below it.
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2335
|
+
const buildTree = () => {
|
|
2336
|
+
const chat = buildChat();
|
|
2337
|
+
const marginChildren = bootedFullscreen
|
|
2338
|
+
? React.createElement(AlternateScreen, null, React.createElement(PageMargin, null, chat))
|
|
2339
|
+
: React.createElement(PageMargin, null, chat);
|
|
2340
|
+
return React.createElement(ThemeProvider, {
|
|
2341
|
+
themeHost: liveThemeHost(),
|
|
2342
|
+
children: marginChildren,
|
|
2343
|
+
});
|
|
2344
|
+
};
|
|
2345
|
+
const tree = buildTree();
|
|
1967
2346
|
// Kernel-switch replacement: send the ready ACK once the first frame after
|
|
1968
2347
|
// adoption is flushed. Does nothing on an ordinary boot.
|
|
1969
2348
|
armFirstFrameAck(process.stdout);
|
|
1970
|
-
|
|
2349
|
+
markBoot('render-start');
|
|
2350
|
+
// The entry's composition right after the mount freezes the event loop for
|
|
2351
|
+
// a while: it waits for the first frame to reach the terminal.
|
|
2352
|
+
const awaitsFirstFrame = entrySlot !== undefined;
|
|
2353
|
+
let frameFlushed = () => undefined;
|
|
2354
|
+
const firstFrame = awaitsFirstFrame ? new Promise(resolve => { frameFlushed = resolve; }) : undefined;
|
|
2355
|
+
instance = await render(tree, {
|
|
2356
|
+
exitOnCtrlC: false,
|
|
2357
|
+
terminalImages: bootedTerminalImages,
|
|
2358
|
+
...(awaitsFirstFrame ? { onFrame: () => { frameFlushed(); } } : {}),
|
|
2359
|
+
});
|
|
2360
|
+
markBoot('render-done');
|
|
1971
2361
|
const isRecompose = lastBootedFullscreen !== undefined;
|
|
1972
2362
|
lastBootedFullscreen = bootedFullscreen;
|
|
1973
2363
|
lastBootedTerminalImages = bootedTerminalImages;
|
|
@@ -1981,14 +2371,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
1981
2371
|
// External injection channel (dsh.nvim etc.): expose a per-session local
|
|
1982
2372
|
// socket that appends text into the prompt input and submits it. Optional
|
|
1983
2373
|
// integration — a bind failure degrades to "no channel" and never fails the
|
|
1984
|
-
// session.
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2374
|
+
// session. It follows the bound session: a placeholder still opening (empty
|
|
2375
|
+
// id) binds nothing, and adoption or a session switch rebinds under the new
|
|
2376
|
+
// id and cwd, closing the previous socket and discovery record (a connected
|
|
2377
|
+
// client reconnects through servers.json). Closed on teardown so neither leaks.
|
|
2378
|
+
let injectChannel = null;
|
|
2379
|
+
let injectSessionId = '';
|
|
2380
|
+
const bindInjectChannel = () => {
|
|
2381
|
+
if (channel.agentId === injectSessionId)
|
|
2382
|
+
return;
|
|
2383
|
+
injectChannel?.close();
|
|
2384
|
+
injectChannel = null;
|
|
2385
|
+
injectSessionId = channel.agentId;
|
|
2386
|
+
if (injectSessionId === '')
|
|
2387
|
+
return;
|
|
2388
|
+
injectChannel = openInjectChannel(injectSessionId, channel.cwd, {
|
|
2389
|
+
append: (text) => injectControllerRef.current?.append(text),
|
|
2390
|
+
submit: () => injectControllerRef.current?.submit(),
|
|
2391
|
+
}, (message) => ctx.logger.warn(`dsh-tui: ${message}`));
|
|
2392
|
+
};
|
|
2393
|
+
bindInjectChannel();
|
|
2394
|
+
const unsubscribeInject = channel.subscribe(bindInjectChannel);
|
|
2395
|
+
ctx.effect(() => () => {
|
|
2396
|
+
unsubscribeInject();
|
|
2397
|
+
injectChannel?.close();
|
|
2398
|
+
injectChannel = null;
|
|
2399
|
+
}, 'dsh-tui inject channel');
|
|
1992
2400
|
// Cross-process session mounting: publish the sessions this process has
|
|
1993
2401
|
// mounted so another TUI (a different terminal process on the same machine)
|
|
1994
2402
|
// can see them as occupied and refuse to mount the same log. Two processes
|
|
@@ -2034,9 +2442,138 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
2034
2442
|
// the process, so a later fatal error falls back to Node's default crash
|
|
2035
2443
|
// instead of reaching a disposed ctx.
|
|
2036
2444
|
registerProcessGuardFatalSink(undefined);
|
|
2445
|
+
if (exitSeam?.request === requestProcessExit)
|
|
2446
|
+
exitSeam.request = undefined;
|
|
2037
2447
|
rawChannel.releaseContributions();
|
|
2038
2448
|
instance?.unmount();
|
|
2039
2449
|
});
|
|
2450
|
+
/** The first frame's write, then stdout drained (bounded: a renderer that
|
|
2451
|
+
* never draws must not hold the composition back). */
|
|
2452
|
+
const flushFirstFrame = async () => {
|
|
2453
|
+
await Promise.race([firstFrame ?? Promise.resolve(), new Promise(resolve => { setTimeout(resolve, 1000).unref(); })]);
|
|
2454
|
+
await new Promise(resolve => { process.stdout.write('', () => { resolve(); }); });
|
|
2455
|
+
};
|
|
2456
|
+
/**
|
|
2457
|
+
* Services composed after the mount: re-read what the screen took as values
|
|
2458
|
+
* at mount (Chat's stores, theme host, toast sink, /settings section).
|
|
2459
|
+
*/
|
|
2460
|
+
const refreshHostServices = () => {
|
|
2461
|
+
rehomeSettingsSection();
|
|
2462
|
+
bindToastSink();
|
|
2463
|
+
if (!exited)
|
|
2464
|
+
instance?.rerender(buildTree());
|
|
2465
|
+
};
|
|
2466
|
+
if (entrySlot !== undefined)
|
|
2467
|
+
entrySlot.firstFrameFlushed = flushFirstFrame;
|
|
2468
|
+
// The kernels without a dsh-tui row: no row to refresh the mounted screen,
|
|
2469
|
+
// so the slot does it.
|
|
2470
|
+
if (entrySlot !== undefined && !entrySlot.dsh)
|
|
2471
|
+
entrySlot.composeSucceeded = () => { refreshHostServices(); };
|
|
2472
|
+
if (entrySlot?.dsh === true) {
|
|
2473
|
+
entrySlot.composeSucceeded = () => { settleComposition(true); };
|
|
2474
|
+
entrySlot.composeFailed = (error, logPath) => {
|
|
2475
|
+
settleComposition(false);
|
|
2476
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2477
|
+
logForDebugging(`dsh-tui: host composition failed (${reason})${logPath === undefined ? '' : ` — startup report: ${logPath}`}`);
|
|
2478
|
+
compositionFailed = true;
|
|
2479
|
+
openDshSession = undefined;
|
|
2480
|
+
// No DSH to open on: the failure row names the report and the ways out.
|
|
2481
|
+
const hint = logPath === undefined ? t('startup-compose-failed-hint') : t('startup-compose-failed-hint-log', { path: logPath });
|
|
2482
|
+
if (!dshStartupSettled) {
|
|
2483
|
+
settleDshStartup?.reject(new StartupOpenError(reason.split('\n')[0] ?? reason, hint, { cause: error }));
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (exited)
|
|
2487
|
+
return;
|
|
2488
|
+
// The session may already be adopted (the audit runs after the tree
|
|
2489
|
+
// settled): a row that stays, not a toast.
|
|
2490
|
+
try {
|
|
2491
|
+
channel.pushLocal(t('startup-open-failed', { backend: backendLabel('dsh'), err: reason.split('\n')[0] ?? reason }), [hint]);
|
|
2492
|
+
}
|
|
2493
|
+
catch {
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
entrySlot.attachDsh = async (rowCtx, rowRuntimeConfig) => {
|
|
2497
|
+
const dshCtx = rowCtx;
|
|
2498
|
+
// The composition failed (its row is on screen), or the process is
|
|
2499
|
+
// leaving (the root dispose waited for the Loader, which started this row).
|
|
2500
|
+
if (compositionFailed || exited)
|
|
2501
|
+
return;
|
|
2502
|
+
try {
|
|
2503
|
+
const rowConfig = configValues(rowRuntimeConfig);
|
|
2504
|
+
rowFacts.config = rowConfig;
|
|
2505
|
+
await installPresets(dshCtx);
|
|
2506
|
+
const seams = mountDshQuestionSeams(dshCtx);
|
|
2507
|
+
if (seams.notice !== undefined)
|
|
2508
|
+
notifyChannel(seams.notice, { color: 'error' });
|
|
2509
|
+
if (seams.registration.kind === 'waterfall')
|
|
2510
|
+
seams.registration.register(channel);
|
|
2511
|
+
mountDshApprovals(dshCtx);
|
|
2512
|
+
refreshHostServices();
|
|
2513
|
+
const rowRoute = { provider: rowConfig.provider, model: rowConfig.model };
|
|
2514
|
+
// The same startup/deployment split the plugin's own boot makes (issue #67).
|
|
2515
|
+
const { startupRoute: rowStartupRoute, deploymentRoute: rowDeploymentRoute } = startupRoutes(dshCtx, rowRoute);
|
|
2516
|
+
const openOnRow = (sessionId, openMeta) => resolveAgent(dshCtx, sessionId, rowRoute, rowStartupRoute, rowDeploymentRoute, openMeta, rowConfig.preset);
|
|
2517
|
+
/** The opened agent is this screen's from now on; returns its route. */
|
|
2518
|
+
const takeOpened = async (opened) => {
|
|
2519
|
+
agent = opened.agent;
|
|
2520
|
+
await attachWorkspaceOwnership(dshCtx, opened.agent);
|
|
2521
|
+
rowFacts.route = opened.route ?? rowStartupRoute;
|
|
2522
|
+
return rowFacts.route;
|
|
2523
|
+
};
|
|
2524
|
+
// `/new` after a failed startup open: DSH's create path on this row.
|
|
2525
|
+
openDshSession = async (cwd) => {
|
|
2526
|
+
const created = await openOnRow(undefined, { cwd });
|
|
2527
|
+
await takeOpened(created);
|
|
2528
|
+
refreshLastRunRecord();
|
|
2529
|
+
return createDshSession(dshCtx, { agent: created.agent, handle: created.handle });
|
|
2530
|
+
};
|
|
2531
|
+
// The entry mounted on rebuilt defaults: the row's own `workspace` /
|
|
2532
|
+
// `cwd` decide the session directory, with `apply`'s precedence. A
|
|
2533
|
+
// provider-URI target resolves now that the providers exist.
|
|
2534
|
+
const rowWorkspace = rowConfig.workspace ?? requestedWorkspace;
|
|
2535
|
+
let startupMeta = { cwd: resolveSessionCwd(rowConfig.cwd) };
|
|
2536
|
+
if (rowWorkspace !== undefined) {
|
|
2537
|
+
if (initialWorkspace !== undefined && rowWorkspace === requestedWorkspace) {
|
|
2538
|
+
startupMeta = meta;
|
|
2539
|
+
}
|
|
2540
|
+
else {
|
|
2541
|
+
const workspaces = getHostWorkspaceRuntime(dshCtx.get('tuiWorkspaces'));
|
|
2542
|
+
const target = workspaces === undefined ? undefined : await workspaces.resolve(rowWorkspace);
|
|
2543
|
+
if (target === undefined)
|
|
2544
|
+
throw new Error(unsupportedWorkspaceTarget(rowWorkspace));
|
|
2545
|
+
startupMeta = { cwd: target.cwd };
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
// A revoked target (the safe-mode retry's warning path) cold-starts here too.
|
|
2549
|
+
const rowSessionId = resumeTarget.kind === 'revoked'
|
|
2550
|
+
? undefined
|
|
2551
|
+
: rowConfig.sessionId ?? resumeTargetFromArgv(cmdlineArgsOf(dshCtx) ?? process.argv.slice(2));
|
|
2552
|
+
const opened = await openOnRow(rowSessionId, startupMeta);
|
|
2553
|
+
const session = createDshSession(dshCtx, { agent: opened.agent, handle: opened.handle });
|
|
2554
|
+
// The composition failed while this opened: close the unadoptable session.
|
|
2555
|
+
if (compositionFailed) {
|
|
2556
|
+
await session.dispose().catch(() => undefined);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
const route = await takeOpened(opened);
|
|
2560
|
+
settleDshStartup?.resolve({
|
|
2561
|
+
session,
|
|
2562
|
+
history: [],
|
|
2563
|
+
route,
|
|
2564
|
+
...(opened.agentPreset === undefined ? {} : { agentPreset: opened.agentPreset }),
|
|
2565
|
+
});
|
|
2566
|
+
refreshLastRunRecord();
|
|
2567
|
+
}
|
|
2568
|
+
catch (error) {
|
|
2569
|
+
logForDebugging(`dsh-tui: DSH session failed to open (${error instanceof Error ? error.message : String(error)})`);
|
|
2570
|
+
// A composition that fails reports itself (its row names the report).
|
|
2571
|
+
if (!await compositionSettled)
|
|
2572
|
+
return;
|
|
2573
|
+
settleDshStartup?.reject(error);
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2040
2577
|
// The TUI is the front door: when the user unmounts it (Ctrl+C), dispose
|
|
2041
2578
|
// the app tree and exit the process. The rejection handler covers
|
|
2042
2579
|
// error-driven unmounts — without it a rejected exitPromise became an
|
|
@@ -2044,6 +2581,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
2044
2581
|
// is swallowed by the funnel (issue #12).
|
|
2045
2582
|
void instance.waitUntilExit().then(handleExit, handleExit);
|
|
2046
2583
|
}
|
|
2584
|
+
function unsupportedWorkspaceTarget(target) {
|
|
2585
|
+
return `dsh-tui: unsupported or unavailable workspace target: ${target}`;
|
|
2586
|
+
}
|
|
2047
2587
|
/**
|
|
2048
2588
|
* Attach to an existing agent, resume a persisted session (`dsh-tui --resume`
|
|
2049
2589
|
* feeds the id through `config.sessionId`), or create a fresh one. Resume
|
|
@@ -2053,26 +2593,45 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
|
|
|
2053
2593
|
* without a session id.
|
|
2054
2594
|
*
|
|
2055
2595
|
* Preset composition (issue #8): a create resolves the requested preset
|
|
2056
|
-
* (
|
|
2057
|
-
* default) and mounts it in the factory's setup hook;
|
|
2058
|
-
* preset the session's own log records. Without the
|
|
2059
|
-
* as before presets existed.
|
|
2596
|
+
* (`DSH_TUI_PRESET` over the persisted `/preset` choice over the cordis.yml
|
|
2597
|
+
* default over the roster default) and mounts it in the factory's setup hook;
|
|
2598
|
+
* a resume re-mounts the preset the session's own log records. Without the
|
|
2599
|
+
* roster both paths behave as before presets existed.
|
|
2060
2600
|
*
|
|
2061
2601
|
* Model route (issues #14/#30/#67): a create adopts the caller's atomically
|
|
2062
2602
|
* resolved route (validated against the adapter catalog below); a resume
|
|
2063
|
-
*
|
|
2064
|
-
*
|
|
2603
|
+
* prefers the route the target session's own records carry and falls back to
|
|
2604
|
+
* a COMPLETE cordis.yml route — a provider-only pin must not half-override
|
|
2605
|
+
* the session's route, and the static route is only the deployment default.
|
|
2065
2606
|
*/
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2607
|
+
/**
|
|
2608
|
+
* The startup route and the deployment route a rejected preference falls back
|
|
2609
|
+
* to (issue #67): the persisted `/model` pick wins whole, else a complete
|
|
2610
|
+
* cordis.yml route, else Harness's agent-default-model selection — never the
|
|
2611
|
+
* lock the user's own pick replaced, never a half-pinned config.
|
|
2612
|
+
*/
|
|
2613
|
+
function startupRoutes(ctx, configuredRoute) {
|
|
2614
|
+
const selected = ctx.get('agentDefaultModel')?.currentSelection?.();
|
|
2615
|
+
const harnessDefault = typeof selected?.provider === 'string' && selected.provider.length > 0
|
|
2616
|
+
&& typeof selected.model === 'string' && selected.model.length > 0
|
|
2617
|
+
? { provider: selected.provider, model: selected.model }
|
|
2618
|
+
: undefined;
|
|
2619
|
+
return {
|
|
2620
|
+
startupRoute: resolveModelRoute(configuredRoute, readModelPref(), harnessDefault),
|
|
2621
|
+
deploymentRoute: resolveModelRoute(configuredRoute, undefined, harnessDefault),
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRoute, deploymentRoute, meta, configuredPreset) {
|
|
2625
|
+
// Deployment fallback (issue #67): a resumed session continues on the route
|
|
2626
|
+
// its own request/header records carry (issue #30) — the route it actually
|
|
2627
|
+
// ran is more specific than any static config — and only a session that
|
|
2628
|
+
// records none adopts the complete cordis.yml route. The recorded route is
|
|
2629
|
+
// ALSO fed back into agentOptions (not just the status line): a resume
|
|
2071
2630
|
// whose cordis.yml pins only `provider` would otherwise leave
|
|
2072
2631
|
// agentOptions.model undefined, which breaks the `{{model}}` persona
|
|
2073
2632
|
// variable for the resumed agent's own assembly and for every subagent it
|
|
2074
2633
|
// spawns (dsh-subagent inherits `parent.options.model`).
|
|
2075
|
-
const resumeRoute = explicitModelRoute(configuredRoute);
|
|
2634
|
+
const resumeRoute = explicitModelRoute(configuredRoute) ?? deploymentRoute;
|
|
2076
2635
|
if (requestedSessionId !== undefined) {
|
|
2077
2636
|
const resumeId = SessionId(requestedSessionId);
|
|
2078
2637
|
const existing = ctx.agents.get(resumeId);
|
|
@@ -2125,8 +2684,8 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
|
|
|
2125
2684
|
const composed = await composePreset(ctx, persisted);
|
|
2126
2685
|
const recorded = await resolvePersistedRoute(ctx, resumeId);
|
|
2127
2686
|
const resumeOptions = {
|
|
2128
|
-
provider:
|
|
2129
|
-
model:
|
|
2687
|
+
provider: recorded?.provider ?? resumeRoute?.provider,
|
|
2688
|
+
model: recorded?.model ?? resumeRoute?.model,
|
|
2130
2689
|
};
|
|
2131
2690
|
const resumed = await ctx.agents.resume({
|
|
2132
2691
|
resumeSessionId: resumeId,
|
|
@@ -2134,14 +2693,14 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
|
|
|
2134
2693
|
...(composed.setup === undefined ? {} : { setup: composed.setup }),
|
|
2135
2694
|
});
|
|
2136
2695
|
// Status-line route on resume: the route the session actually
|
|
2137
|
-
// continues on —
|
|
2138
|
-
//
|
|
2139
|
-
//
|
|
2696
|
+
// continues on — its own request/header record, else the complete
|
|
2697
|
+
// cordis.yml deployment route (a bare log that pins none falls back to
|
|
2698
|
+
// the startup resolution, best effort).
|
|
2140
2699
|
return {
|
|
2141
2700
|
agent: resumed.agent,
|
|
2142
2701
|
handle: resumed,
|
|
2143
2702
|
agentPreset: composed.agentPreset,
|
|
2144
|
-
route:
|
|
2703
|
+
route: recordedModelRoute(snapshotLiveSessionEvents(resumed.agent.session)) ?? resumeRoute,
|
|
2145
2704
|
};
|
|
2146
2705
|
}
|
|
2147
2706
|
catch (error) {
|
|
@@ -2165,18 +2724,24 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
|
|
|
2165
2724
|
}
|
|
2166
2725
|
}
|
|
2167
2726
|
const sessionId = SessionId(randomUUID());
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2727
|
+
// Fresh-session preset precedence (issue #8): the DSH_TUI_PRESET launch
|
|
2728
|
+
// instruction, then the persisted `/preset` choice (the standing user
|
|
2729
|
+
// default), then the cordis.yml value as the deployment default, then the
|
|
2730
|
+
// roster default. The pref is migrated only when it was the effective
|
|
2731
|
+
// source — an override must not rewrite the remembered id.
|
|
2732
|
+
const presetOverride = presetOverrideFromEnv();
|
|
2733
|
+
const presetPref = readPresetPref();
|
|
2734
|
+
const composed = await composePreset(ctx, presetOverride ?? presetPref ?? configuredPreset);
|
|
2735
|
+
if (presetOverride === undefined && !migratePresetPref(presetPref, composed.agentPreset)) {
|
|
2171
2736
|
ctx.logger.warn(`dsh-tui: resolved preset preference "${presetPref}" as "${composed.agentPreset}" but could not persist the migrated id`);
|
|
2172
2737
|
}
|
|
2173
2738
|
// Fresh-session route precedence (issues #14/#30/#67): resolved atomically
|
|
2174
|
-
// by the caller (
|
|
2739
|
+
// by the caller (the persisted `/model` choice > a complete cordis.yml route
|
|
2175
2740
|
// > the harness default), then validated against the adapter catalog — a
|
|
2176
|
-
// stale persisted choice falls back to the
|
|
2177
|
-
// of reaching the server as an unknown model name.
|
|
2741
|
+
// stale persisted choice falls back to the deployment route wholesale
|
|
2742
|
+
// instead of reaching the server as an unknown model name.
|
|
2178
2743
|
const llm = ctx.get('llm');
|
|
2179
|
-
const { route, rejected } = await validateModelRoute(llm, startupRoute);
|
|
2744
|
+
const { route, rejected } = await validateModelRoute(llm, startupRoute, deploymentRoute);
|
|
2180
2745
|
if (rejected !== undefined) {
|
|
2181
2746
|
ctx.logger.warn(`dsh-tui: model route ${rejected.provider}/${rejected.model} is not advertised by provider "${rejected.provider}"; falling back to ${route.provider}/${route.model}`);
|
|
2182
2747
|
}
|
|
@@ -2193,7 +2758,7 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
|
|
|
2193
2758
|
const bootReservation = bootReserved.ok ? bootReserved.reservation : undefined;
|
|
2194
2759
|
let created;
|
|
2195
2760
|
try {
|
|
2196
|
-
created = await ctx.agents
|
|
2761
|
+
created = await createFreshAgent(ctx, ctx.agents, {
|
|
2197
2762
|
sessionId,
|
|
2198
2763
|
meta: {
|
|
2199
2764
|
...meta,
|
|
@@ -2276,6 +2841,31 @@ export function runCrashExit(deps) {
|
|
|
2276
2841
|
}
|
|
2277
2842
|
deps.finish(`dsh-tui crashed: ${detail.message}`);
|
|
2278
2843
|
}
|
|
2844
|
+
/** None where the root has no `ctx.agents` (before DSH composes, or a non-DSH kernel). */
|
|
2845
|
+
function dshAgentOn(ctx, agentId) {
|
|
2846
|
+
return ctx.get('agents')?.get(SessionId(agentId));
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* The crash tail's resume markers (exported for scripts/verify-shutdown-fallback).
|
|
2850
|
+
* Leaves the marker a clean exit would, never CLEARS one.
|
|
2851
|
+
*/
|
|
2852
|
+
export function writeCrashResumeMarkers(deps) {
|
|
2853
|
+
const { ctx, backendStart, channel } = deps;
|
|
2854
|
+
if (backendStart === undefined) {
|
|
2855
|
+
if (isExitResumable({
|
|
2856
|
+
pendingCount: channel.pending.length,
|
|
2857
|
+
liveAgent: dshAgentOn(ctx, channel.agentId),
|
|
2858
|
+
startupAgent: deps.startupAgent,
|
|
2859
|
+
})) {
|
|
2860
|
+
deps.writeResumeTarget(channel.agentId);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
else if (backendStart.persisted(channel.agentId, channel.rows)) {
|
|
2864
|
+
// Non-DSH sessions use their backend preference as the launcher marker.
|
|
2865
|
+
backendStart.sessionPrefs.setLastSession(channel.agentId);
|
|
2866
|
+
}
|
|
2867
|
+
deps.refreshLastRunRecord();
|
|
2868
|
+
}
|
|
2279
2869
|
/**
|
|
2280
2870
|
* Whether a user exit should leave the resume marker (and print the resume
|
|
2281
2871
|
* hint). Must be judged against the LIVE session behind the channel, not the
|
|
@@ -2291,6 +2881,8 @@ export function isExitResumable(deps) {
|
|
|
2291
2881
|
const agent = deps.liveAgent ?? deps.startupAgent;
|
|
2292
2882
|
if (agent === undefined)
|
|
2293
2883
|
return false;
|
|
2884
|
+
if (isUnstoredFreshSession(agent.session))
|
|
2885
|
+
return false;
|
|
2294
2886
|
return (deps.pendingCount > 0 ||
|
|
2295
2887
|
snapshotLiveSessionEvents(agent.session).some(event => event.type === 'user/message' && event.data.source.kind === 'user'));
|
|
2296
2888
|
}
|
|
@@ -2336,7 +2928,7 @@ export async function finishExit(ctx, instance, fullscreen, notice, stderrNotice
|
|
|
2336
2928
|
}
|
|
2337
2929
|
const cursor = fullscreen ? '' : cursorMoveToFrameEnd(runtime);
|
|
2338
2930
|
try {
|
|
2339
|
-
runtime?.detachForShutdown?.();
|
|
2931
|
+
await runtime?.detachForShutdown?.();
|
|
2340
2932
|
// The /update continuation spawns children that inherit this stdin;
|
|
2341
2933
|
// strip the readable pump so the parent cannot swallow their input
|
|
2342
2934
|
// (issues #284/#307). Harmless on plain exits — the process exits
|
|
@@ -2466,11 +3058,47 @@ function writeStream(stream, data) {
|
|
|
2466
3058
|
function preservedSessionTail(sessionId, hint) {
|
|
2467
3059
|
return sessionId === '' ? '\n\n' : ` Your session is preserved — resume with:\n${hint(sessionId)}\n\n`;
|
|
2468
3060
|
}
|
|
2469
|
-
|
|
3061
|
+
/**
|
|
3062
|
+
* The entry's signals while following a replacement: SIGTERM is forwarded;
|
|
3063
|
+
* SIGINT/SIGHUP already reach it through the process group. A replacement
|
|
3064
|
+
* that dies by a termination signal ends this process the same way.
|
|
3065
|
+
*/
|
|
3066
|
+
function superviseReplacement(exitSeam, options) {
|
|
3067
|
+
if (exitSeam === undefined)
|
|
3068
|
+
return options;
|
|
3069
|
+
let replacement;
|
|
3070
|
+
exitSeam.request = request => {
|
|
3071
|
+
if (replacement === undefined)
|
|
3072
|
+
return 'refused';
|
|
3073
|
+
const forwarded = request.kind === 'signal' && request.signal === 'SIGTERM';
|
|
3074
|
+
if (forwarded)
|
|
3075
|
+
replacement.kill('SIGTERM');
|
|
3076
|
+
logRestartEvent('supervisor: exit request while following the replacement', request.kind === 'signal' ? { signal: request.signal, forwarded } : { code: request.code });
|
|
3077
|
+
return 'supervising';
|
|
3078
|
+
};
|
|
3079
|
+
return {
|
|
3080
|
+
...options,
|
|
3081
|
+
onSpawn: child => { replacement = child; },
|
|
3082
|
+
onTerminationSignal: signal => {
|
|
3083
|
+
if (TERMINATION_SIGNALS.includes(signal))
|
|
3084
|
+
dieBySignal(signal);
|
|
3085
|
+
},
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile, id), options = {}, exitSeam) {
|
|
2470
3089
|
logRestartEvent('runRestart: entered, disposing cordis root');
|
|
2471
3090
|
disposeRootAndThen(ctx, () => {
|
|
2472
3091
|
logRestartEvent('runRestart: root disposed, starting restartTui');
|
|
2473
|
-
|
|
3092
|
+
const supervision = superviseReplacement(exitSeam, options);
|
|
3093
|
+
// The React tree stays mounted while supervising, so a late UI read of the
|
|
3094
|
+
// ended channel is logged instead of crashing the supervisor.
|
|
3095
|
+
addProcessErrorAbsorber(error => {
|
|
3096
|
+
if (!(error instanceof Error) || error.message !== CHANNEL_UI_LIFETIME_ENDED)
|
|
3097
|
+
return false;
|
|
3098
|
+
logRestartEvent('supervisor: late UI read after dispose ignored', { at: error.stack?.split('\n').slice(1, 4).map(line => line.trim()).join(' | ') });
|
|
3099
|
+
return true;
|
|
3100
|
+
});
|
|
3101
|
+
void restartTui(sessionId, supervision).then(restartCode => {
|
|
2474
3102
|
logRestartEvent('runRestart: restartTui resolved', { restartCode });
|
|
2475
3103
|
if (restartCode !== 0) {
|
|
2476
3104
|
writeHandoffNotice(`\ndsh-tui restart failed to spawn (exit ${restartCode}).${preservedSessionTail(sessionId, hint)}`);
|
|
@@ -2484,13 +3112,13 @@ function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile,
|
|
|
2484
3112
|
});
|
|
2485
3113
|
});
|
|
2486
3114
|
}
|
|
2487
|
-
function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id)) {
|
|
3115
|
+
function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id), exitSeam) {
|
|
2488
3116
|
disposeRootAndThen(ctx, () => {
|
|
2489
3117
|
if (profile === undefined) {
|
|
2490
3118
|
process.stderr.write(`\n${t('update-aborted-no-profile')}\n`);
|
|
2491
3119
|
process.exit(1);
|
|
2492
3120
|
}
|
|
2493
|
-
void updateTuiAndRestart(sessionId, profile, targetVersion, kernel).then(({ updateCode, restartCode }) => {
|
|
3121
|
+
void updateTuiAndRestart(sessionId, profile, targetVersion, kernel, superviseReplacement(exitSeam, {})).then(({ updateCode, restartCode }) => {
|
|
2494
3122
|
if (updateCode !== 0) {
|
|
2495
3123
|
process.stderr.write(`\ndsh-tui update failed (exit ${updateCode}).${preservedSessionTail(sessionId, hint)}`);
|
|
2496
3124
|
}
|
|
@@ -2534,16 +3162,25 @@ function resumeCommand(profile, sessionId) {
|
|
|
2534
3162
|
* handoff (update/restart) may legitimately take longer than the bound, and
|
|
2535
3163
|
* reporting failure on a clean exit would mislead wrapper scripts.
|
|
2536
3164
|
*/
|
|
2537
|
-
function disposeRootAndThen(ctx, done,
|
|
3165
|
+
function disposeRootAndThen(ctx, done, fallback = 1) {
|
|
3166
|
+
const startedAt = Date.now();
|
|
2538
3167
|
const timer = setTimeout(() => {
|
|
2539
3168
|
// Diagnosis for a stalled disposal: without this line the fallback exit
|
|
2540
3169
|
// is indistinguishable from a successful handoff in the field.
|
|
2541
|
-
logRestartEvent('dispose: timeout, taking fallback exit', { fallbackCode });
|
|
2542
|
-
|
|
3170
|
+
logRestartEvent('dispose: timeout, taking fallback exit', typeof fallback === 'number' ? { fallbackCode: fallback } : { fallback: 'signal' });
|
|
3171
|
+
if (typeof fallback === 'number')
|
|
3172
|
+
process.exit(fallback);
|
|
3173
|
+
else
|
|
3174
|
+
fallback();
|
|
2543
3175
|
}, 5000);
|
|
2544
3176
|
timer.unref();
|
|
2545
|
-
|
|
3177
|
+
// A composition still running into this root settles first.
|
|
3178
|
+
// The pooled, process-wide resources of every backend this process actually
|
|
3179
|
+
// loaded are closed here — after the fiber, never before (P0 D4-P2/P3).
|
|
3180
|
+
void disposeRootSettled(ctx, () => withHostRootCapability(() => ctx.root.fiber.dispose())).finally(() => unloadBackends()).then(() => {
|
|
2546
3181
|
clearTimeout(timer);
|
|
3182
|
+
// The teardown's length, to restart.log only (never the terminal).
|
|
3183
|
+
logRestartEvent('dispose: root disposed', { ms: Date.now() - startedAt });
|
|
2547
3184
|
done();
|
|
2548
3185
|
}, () => {
|
|
2549
3186
|
clearTimeout(timer);
|