@deepseek-harness-tui/dsh-tui 0.7.3 → 0.8.1
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 +15 -7
- package/bin/dsh-tui.js +19 -3
- package/cordis.patch.yml +43 -11
- package/cordis.yml +20 -1
- package/ecosystem-spec/registry/contracts/decision-events-v1alpha1.json +13 -0
- package/ecosystem-spec/registry/host-descriptor.tui.example.json +56 -0
- package/ecosystem-spec/registry/permissions-0.1.json +13 -0
- package/ecosystem-spec/registry/registry-0.15.json +57 -0
- package/ecosystem-spec/schemas/conformance-claim.schema.json +15 -0
- package/ecosystem-spec/schemas/effect-ledger-record.schema.json +16 -0
- package/ecosystem-spec/schemas/host-descriptor.schema.json +26 -0
- package/lib/types/cc/figures.d.ts +4 -0
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +4 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +4 -1
- package/lib/types/components/ExtensionDialog.d.ts +40 -0
- package/lib/types/components/ExtensionDialog.d.ts.map +1 -0
- package/lib/types/components/ExtensionDialog.js +210 -0
- package/lib/types/components/HelpMenu.js +1 -1
- package/lib/types/components/LoadedContextPanel.d.ts +5 -10
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +7 -18
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +1 -2
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +29 -5
- package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
- package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginSceneBoundary.js +29 -0
- package/lib/types/components/RewindPicker.d.ts +13 -1
- package/lib/types/components/RewindPicker.d.ts.map +1 -1
- package/lib/types/components/RewindPicker.js +26 -2
- package/lib/types/components/SkillsPicker.d.ts +18 -0
- package/lib/types/components/SkillsPicker.d.ts.map +1 -0
- package/lib/types/components/SkillsPicker.js +50 -0
- package/lib/types/components/SplitDiffView.d.ts +28 -0
- package/lib/types/components/SplitDiffView.d.ts.map +1 -0
- package/lib/types/components/SplitDiffView.js +417 -0
- package/lib/types/components/ToolUseLoader.d.ts +8 -3
- package/lib/types/components/ToolUseLoader.d.ts.map +1 -1
- package/lib/types/components/ToolUseLoader.js +43 -9
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
- package/lib/types/dsh-adapter/channel.d.ts +98 -7
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +623 -26
- package/lib/types/dsh-adapter/command-attribution.d.ts +51 -0
- package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-attribution.js +93 -0
- package/lib/types/dsh-adapter/command-errors.d.ts +42 -0
- package/lib/types/dsh-adapter/command-errors.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-errors.js +63 -0
- package/lib/types/dsh-adapter/component-identity.d.ts +28 -0
- package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -0
- package/lib/types/dsh-adapter/component-identity.js +108 -0
- package/lib/types/dsh-adapter/contract.d.ts +2 -2
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +3 -2
- package/lib/types/dsh-adapter/decision-guard.d.ts +84 -0
- package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/decision-guard.js +354 -0
- package/lib/types/dsh-adapter/dialogs.d.ts +146 -0
- package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -0
- package/lib/types/dsh-adapter/dialogs.js +262 -0
- package/lib/types/dsh-adapter/effect-ledger.d.ts +99 -0
- package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -0
- package/lib/types/dsh-adapter/effect-ledger.js +231 -0
- package/lib/types/dsh-adapter/extension-events.d.ts +193 -0
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -0
- package/lib/types/dsh-adapter/extension-events.js +169 -0
- package/lib/types/dsh-adapter/extensions.d.ts +32 -0
- package/lib/types/dsh-adapter/extensions.d.ts.map +1 -0
- package/lib/types/dsh-adapter/extensions.js +42 -0
- package/lib/types/dsh-adapter/grants.d.ts +25 -0
- package/lib/types/dsh-adapter/grants.d.ts.map +1 -0
- package/lib/types/dsh-adapter/grants.js +184 -0
- package/lib/types/dsh-adapter/host-descriptor.d.ts +24 -0
- package/lib/types/dsh-adapter/host-descriptor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-descriptor.js +146 -0
- package/lib/types/dsh-adapter/index.d.ts +4 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +1 -0
- package/lib/types/dsh-adapter/message-observer.d.ts +178 -0
- package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -0
- package/lib/types/dsh-adapter/message-observer.js +509 -0
- package/lib/types/dsh-adapter/plugin-host.d.ts +103 -0
- package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-host.js +270 -0
- package/lib/types/dsh-adapter/plugin-storage.d.ts +113 -0
- package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugin-storage.js +385 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +106 -1
- package/lib/types/dsh-adapter/plugins-info.d.ts +43 -0
- package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -0
- package/lib/types/dsh-adapter/plugins-info.js +276 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
- package/lib/types/dsh-adapter/promptDebug.js +148 -0
- package/lib/types/dsh-adapter/renderers.d.ts +65 -0
- package/lib/types/dsh-adapter/renderers.d.ts.map +1 -0
- package/lib/types/dsh-adapter/renderers.js +138 -0
- package/lib/types/dsh-adapter/sanitize.d.ts +36 -0
- package/lib/types/dsh-adapter/sanitize.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sanitize.js +65 -0
- package/lib/types/dsh-adapter/scenes.d.ts +81 -0
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/scenes.js +93 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settings-sections.js +63 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settingsEditor.js +244 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts +109 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -0
- package/lib/types/dsh-adapter/shortcuts.js +268 -0
- package/lib/types/dsh-adapter/status.d.ts +75 -0
- package/lib/types/dsh-adapter/status.d.ts.map +1 -0
- package/lib/types/dsh-adapter/status.js +168 -0
- package/lib/types/extensions.d.ts +8 -0
- package/lib/types/extensions.d.ts.map +1 -0
- package/lib/types/extensions.js +15 -0
- package/lib/types/i18n.d.ts +299 -26
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +89 -8
- package/lib/types/ink/ink.d.ts +11 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +33 -0
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +101 -51
- package/lib/types/ink/output.d.ts +7 -0
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +125 -4
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +14 -6
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +18 -6
- package/lib/types/jsx-runtime.d.ts +2 -0
- package/lib/types/jsx-runtime.d.ts.map +1 -0
- package/lib/types/jsx-runtime.js +13 -0
- package/lib/types/modelRoute.d.ts +10 -2
- package/lib/types/modelRoute.d.ts.map +1 -1
- package/lib/types/modelRoute.js +33 -4
- package/lib/types/plugin-host.d.ts +13 -0
- package/lib/types/plugin-host.d.ts.map +1 -0
- package/lib/types/plugin-host.js +17 -0
- package/lib/types/plugin-spec/negotiate.d.ts +11 -0
- package/lib/types/plugin-spec/negotiate.d.ts.map +1 -0
- package/lib/types/plugin-spec/negotiate.js +97 -0
- package/lib/types/plugin-spec/permission-scope.d.ts +32 -0
- package/lib/types/plugin-spec/permission-scope.d.ts.map +1 -0
- package/lib/types/plugin-spec/permission-scope.js +90 -0
- package/lib/types/plugin-spec/registry.d.ts +23 -0
- package/lib/types/plugin-spec/registry.d.ts.map +1 -0
- package/lib/types/plugin-spec/registry.js +157 -0
- package/lib/types/plugin-spec/schema-check.d.ts +17 -0
- package/lib/types/plugin-spec/schema-check.d.ts.map +1 -0
- package/lib/types/plugin-spec/schema-check.js +165 -0
- package/lib/types/plugin-spec/tui-extension.d.ts +16 -0
- package/lib/types/plugin-spec/tui-extension.d.ts.map +1 -0
- package/lib/types/plugin-spec/tui-extension.js +77 -0
- package/lib/types/plugin-spec/types.d.ts +114 -0
- package/lib/types/plugin-spec/types.d.ts.map +1 -0
- package/lib/types/plugin-spec/types.js +10 -0
- package/lib/types/plugin-spec/validate.d.ts +21 -0
- package/lib/types/plugin-spec/validate.d.ts.map +1 -0
- package/lib/types/plugin-spec/validate.js +119 -0
- package/lib/types/scenes.d.ts +3 -0
- package/lib/types/scenes.d.ts.map +1 -0
- package/lib/types/scenes.js +4 -0
- package/lib/types/screens/Chat.d.ts +14 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +247 -48
- package/lib/types/screens/Settings.d.ts +20 -0
- package/lib/types/screens/Settings.d.ts.map +1 -0
- package/lib/types/screens/Settings.js +333 -0
- package/lib/types/screens/StatusLine.js +1 -1
- package/lib/types/settings-sections.d.ts +3 -0
- package/lib/types/settings-sections.d.ts.map +1 -0
- package/lib/types/settings-sections.js +4 -0
- package/lib/types/theme.d.ts +17 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +51 -0
- package/lib/types/update.d.ts +20 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +54 -7
- package/lib/types/utils/loaded-context.d.ts +8 -3
- package/lib/types/utils/loaded-context.d.ts.map +1 -1
- package/lib/types/utils/loaded-context.js +39 -3
- package/lib/types/utils/modifiers.d.ts.map +1 -1
- package/lib/types/utils/modifiers.js +3 -2
- package/lib/types/working-activity.d.ts +12 -0
- package/lib/types/working-activity.d.ts.map +1 -1
- package/lib/types/working-activity.js +13 -0
- package/node_modules/@dsh-std/command/LICENSE +21 -0
- package/node_modules/@dsh-std/command/README.md +26 -0
- package/node_modules/@dsh-std/command/README.zh.md +28 -0
- package/node_modules/@dsh-std/command/lib/index.d.ts +144 -0
- package/node_modules/@dsh-std/command/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/command/lib/index.js +252 -0
- package/node_modules/@dsh-std/command/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/command/package.json +22 -0
- package/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/node_modules/@dsh-std/connection/README.md +42 -0
- package/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.js +257 -0
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/node_modules/@dsh-std/connection/package.json +20 -0
- package/node_modules/@dsh-std/core/LICENSE +21 -0
- package/node_modules/@dsh-std/core/README.md +13 -0
- package/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/core/package.json +21 -0
- package/node_modules/@dsh-std/manifest/LICENSE +21 -0
- package/node_modules/@dsh-std/manifest/README.md +5 -0
- package/node_modules/@dsh-std/manifest/README.zh.md +5 -0
- package/node_modules/@dsh-std/manifest/lib/index.d.ts +182 -0
- package/node_modules/@dsh-std/manifest/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/manifest/lib/index.js +625 -0
- package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/manifest/package.json +20 -0
- package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -0
- package/node_modules/@dsh-std/messages/LICENSE +21 -0
- package/node_modules/@dsh-std/messages/README.md +5 -0
- package/node_modules/@dsh-std/messages/README.zh.md +7 -0
- package/node_modules/@dsh-std/messages/lib/index.d.ts +50 -0
- package/node_modules/@dsh-std/messages/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/messages/lib/index.js +151 -0
- package/node_modules/@dsh-std/messages/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/messages/package.json +20 -0
- package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -0
- package/node_modules/@dsh-std/presentation/LICENSE +21 -0
- package/node_modules/@dsh-std/presentation/README.md +26 -0
- package/node_modules/@dsh-std/presentation/README.zh.md +28 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts +219 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/index.js +547 -0
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/interaction.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/interaction.js +2 -0
- package/node_modules/@dsh-std/presentation/lib/operations.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/operations.js +2 -0
- package/node_modules/@dsh-std/presentation/package.json +21 -0
- package/node_modules/@dsh-std/storage/LICENSE +21 -0
- package/node_modules/@dsh-std/storage/README.md +5 -0
- package/node_modules/@dsh-std/storage/README.zh.md +7 -0
- package/node_modules/@dsh-std/storage/lib/index.d.ts +48 -0
- package/node_modules/@dsh-std/storage/lib/index.d.ts.map +1 -0
- package/node_modules/@dsh-std/storage/lib/index.js +104 -0
- package/node_modules/@dsh-std/storage/lib/index.js.map +1 -0
- package/node_modules/@dsh-std/storage/package.json +20 -0
- package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -0
- package/package.json +118 -67
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +257 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.d.ts +182 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js +625 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +26 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +28 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +219 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +547 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/interaction.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/interaction.js +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/operations.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/operations.js +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +21 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts +125 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/connection-CdGcSQnu.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.d.ts +89 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js +158 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +24 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +257 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js +226 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.d.ts +112 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.d.ts.map +1 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js +424 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -0
|
@@ -27,6 +27,102 @@ import { modeDisplayName, resolveSessionModes } from '../sessionModes.js';
|
|
|
27
27
|
import { ActivityTracker } from 'dsh-working-activity/status';
|
|
28
28
|
import { attachSessionToWorkspace } from './workspace.js';
|
|
29
29
|
import { createLocalWorkspaceRuntime } from './workspaces.js';
|
|
30
|
+
import { dispatchTuiDecision, dispatchTuiNotification, normalizeCancelDecision } from './extension-events.js';
|
|
31
|
+
import { installDecisionGuard } from './decision-guard.js';
|
|
32
|
+
import { commandOwner } from './command-attribution.js';
|
|
33
|
+
import { readGrantStore } from './grants.js';
|
|
34
|
+
import { hasCommandErrorCode, mapCommandError } from './command-errors.js';
|
|
35
|
+
import { pluginsInfoLines } from './plugins-info.js';
|
|
36
|
+
import { cleanRenderText, cleanScalarText } from './sanitize.js';
|
|
37
|
+
/** `tui/input` return normalization: transform/handled/cancel or no opinion.
|
|
38
|
+
* A blank `{ text }` rewrite is NOT a decision — it is logged and the chain
|
|
39
|
+
* continues so a later veto listener still runs. */
|
|
40
|
+
function normalizeInputDecision(result, warn) {
|
|
41
|
+
if (result === undefined || result === null || result === false)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (typeof result !== 'object') {
|
|
44
|
+
warn(`a non-object (${typeof result})`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const record = result;
|
|
48
|
+
if (record.cancel === true) {
|
|
49
|
+
const reason = cleanScalarText(record.reason, NOTICE_CELLS);
|
|
50
|
+
return { cancel: true, ...(reason === '' ? {} : { reason }) };
|
|
51
|
+
}
|
|
52
|
+
if (record.handled === true) {
|
|
53
|
+
const notice = cleanScalarText(record.notice, NOTICE_CELLS);
|
|
54
|
+
return { handled: true, ...(notice === '' ? {} : { notice }) };
|
|
55
|
+
}
|
|
56
|
+
if (typeof record.text === 'string') {
|
|
57
|
+
if (record.text.trim() === '') {
|
|
58
|
+
warn('a blank {text} rewrite');
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return { text: record.text };
|
|
62
|
+
}
|
|
63
|
+
warn('an unrecognized decision shape');
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/** `tui/rewind-prompt` return normalization: cancel/modes or no opinion.
|
|
67
|
+
* Modes are COPIED with only validated, sanitized scalar fields — the raw
|
|
68
|
+
* plugin object must never reach the render path (a `description: {}` would
|
|
69
|
+
* crash ListItem's `.replace`, and control chars would corrupt the pane).
|
|
70
|
+
* Notices/reasons are toast-bound plugin text: sanitized too (see
|
|
71
|
+
* ./sanitize.js — the one implementation of the render-path contract). */
|
|
72
|
+
function normalizeRewindPromptDecision(result, warn) {
|
|
73
|
+
if (result === undefined || result === null || result === false)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (typeof result !== 'object') {
|
|
76
|
+
warn(`a non-object (${typeof result})`);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const record = result;
|
|
80
|
+
if (record.cancel === true) {
|
|
81
|
+
const reason = cleanScalarText(record.reason, NOTICE_CELLS);
|
|
82
|
+
return { cancel: true, ...(reason === '' ? {} : { reason }) };
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(record.modes)) {
|
|
85
|
+
const modes = [];
|
|
86
|
+
for (const raw of record.modes) {
|
|
87
|
+
if (modes.length >= 8)
|
|
88
|
+
break;
|
|
89
|
+
if (raw === null || typeof raw !== 'object')
|
|
90
|
+
continue;
|
|
91
|
+
const candidate = raw;
|
|
92
|
+
if (typeof candidate.id !== 'string' || candidate.id.trim() === '')
|
|
93
|
+
continue;
|
|
94
|
+
const label = typeof candidate.label === 'string' ? cleanRenderText(candidate.label, 120) : '';
|
|
95
|
+
if (label === '')
|
|
96
|
+
continue;
|
|
97
|
+
const description = typeof candidate.description === 'string' && candidate.description.trim() !== ''
|
|
98
|
+
? cleanRenderText(candidate.description, 400)
|
|
99
|
+
: undefined;
|
|
100
|
+
modes.push({ id: candidate.id, label, ...(description === undefined ? {} : { description }) });
|
|
101
|
+
}
|
|
102
|
+
if (modes.length === 0) {
|
|
103
|
+
warn('an empty or invalid {modes} list');
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return { modes };
|
|
107
|
+
}
|
|
108
|
+
warn('an unrecognized decision shape');
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/** Toast-bound plugin text (veto reasons, handled notices, rewind summaries)
|
|
112
|
+
* is render-path data too: same sanitization, toast-width cap. */
|
|
113
|
+
const NOTICE_CELLS = 200;
|
|
114
|
+
/** `tui/rewind-done` return normalization: the first non-empty STRING is the
|
|
115
|
+
* summary; anything else is not a decision. */
|
|
116
|
+
function normalizeRewindDoneSummary(result, warn) {
|
|
117
|
+
if (result === undefined || result === null || result === false)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (typeof result === 'string') {
|
|
120
|
+
const summary = cleanRenderText(result, NOTICE_CELLS);
|
|
121
|
+
return summary === '' ? undefined : summary;
|
|
122
|
+
}
|
|
123
|
+
warn('a non-string summary');
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
30
126
|
/**
|
|
31
127
|
* Delay before re-reading a skill catalog that reported an incomplete
|
|
32
128
|
* observation (a provider whose directory watcher is still warming).
|
|
@@ -288,18 +384,50 @@ async function waitForTurnEnd(session, fromSeq, timeoutMs) {
|
|
|
288
384
|
export function createChannel(ctx, initialAgent, options) {
|
|
289
385
|
let agent = initialAgent;
|
|
290
386
|
let currentHandle = options.handle;
|
|
387
|
+
// D-7 backstop: the extensions row installs the decision-subscription
|
|
388
|
+
// gate, but the channel IS the dispatch path — a stale patch without that
|
|
389
|
+
// row (or a bare embed mounting neither) would otherwise leave tui/input
|
|
390
|
+
// & friends subscribable by default, silently voiding the default-deny
|
|
391
|
+
// posture. Idempotent per cordis root, so the full-patch path installs
|
|
392
|
+
// exactly once whichever side runs first. One store instance serves both
|
|
393
|
+
// the gate and the invoke checkpoint below.
|
|
394
|
+
// Keep a private fallback for bare embedders, but resolve the host-owned
|
|
395
|
+
// store on every operation so a plugin-host row mounted later (or a custom
|
|
396
|
+
// live GrantStore) is not shadowed by an early snapshot.
|
|
397
|
+
const fallbackGrantStore = readGrantStore();
|
|
398
|
+
const currentGrantStore = () => ctx.get('tuiPluginHost')?.grants ?? fallbackGrantStore;
|
|
399
|
+
installDecisionGuard(ctx, currentGrantStore());
|
|
291
400
|
// The DSH slash-command registry (optional service): /plan, /goal and
|
|
292
401
|
// friends register here; the TUI merges their descriptors into the slash
|
|
293
402
|
// menu and dispatches through `execute` (which logs the paired
|
|
294
403
|
// command/run + command/done records). Absent the service, only the
|
|
295
404
|
// built-in local commands exist.
|
|
296
405
|
const commandService = ctx.get('commands');
|
|
406
|
+
// messages.observe broker (optional service, C-042): mounted by the
|
|
407
|
+
// dsh-tui-plugin-host row; absent the row, publish is a no-op and nothing
|
|
408
|
+
// else changes (soft degradation, #183).
|
|
409
|
+
const messageObserver = ctx.get('tuiMessageObserver');
|
|
297
410
|
// Workspace registry runtime (optional service, issue #183): mounted by
|
|
298
411
|
// the bundle patch's dsh-tui-workspaces row; absent the row (stale patch
|
|
299
412
|
// or a bare embedder), degrade to the local-only runtime. plugin.ts owns
|
|
300
413
|
// the degraded-boot warning for profile launches.
|
|
301
414
|
const workspaceService = ctx.get('tuiWorkspaces') ?? createLocalWorkspaceRuntime();
|
|
302
415
|
const commandTrees = ctx.get('tuiCommandTrees');
|
|
416
|
+
// The `/settings` screen reads its host on EVERY render, so the host must
|
|
417
|
+
// be a stable object: a fresh literal per call would re-fire the screen's
|
|
418
|
+
// host-keyed effects endlessly (render → new host → effect → state →
|
|
419
|
+
// render). The underlying services are fixed for the channel's lifetime,
|
|
420
|
+
// so compute once and cache.
|
|
421
|
+
let settingsHostCache;
|
|
422
|
+
let settingsHostResolved = false;
|
|
423
|
+
// Plugin scene runtime (optional service, same degradation rule as
|
|
424
|
+
// tuiWorkspaces/tuiCommandTrees): mounted by the bundle patch's
|
|
425
|
+
// dsh-tui-scenes row; absent the row, `pluginScene` simply stays undefined.
|
|
426
|
+
const sceneRuntime = ctx.get('tuiScenes');
|
|
427
|
+
// Custom-entry text renderers (optional service, dsh-tui-extensions row):
|
|
428
|
+
// absent the row, unknown plugin event types stay invisible in the
|
|
429
|
+
// transcript, exactly as before the seam existed.
|
|
430
|
+
const rendererRuntime = ctx.get('tuiRenderers');
|
|
303
431
|
// Shift+Tab session-mode cycle: cordis.yml `modes` wins; absent/empty/
|
|
304
432
|
// atom-less → the built-in default/plan/full cycle (sessionModes.ts).
|
|
305
433
|
const { modes: sessionModes, dropped: droppedModeIds } = resolveSessionModes(options.modes);
|
|
@@ -394,6 +522,142 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
394
522
|
state.notify(t('send-failed', { err: message }), { color: 'error' });
|
|
395
523
|
});
|
|
396
524
|
};
|
|
525
|
+
/**
|
|
526
|
+
* RFC 0005 D-8: a flow parked on a plugin decision must be user-observable.
|
|
527
|
+
* Decisions normally resolve in milliseconds, so the notice only fires
|
|
528
|
+
* once the wait crosses a threshold — a slow plugin (e.g. one showing a
|
|
529
|
+
* managed dialog) then explains the pause instead of looking like the TUI
|
|
530
|
+
* ate the input.
|
|
531
|
+
*/
|
|
532
|
+
const DECISION_PENDING_MS = 400;
|
|
533
|
+
const withDecisionPending = (name, pending) => {
|
|
534
|
+
let dismiss;
|
|
535
|
+
const timer = setTimeout(() => {
|
|
536
|
+
// Sticky (timeoutMs 0), D-8: the indicator must cover the WHOLE wait —
|
|
537
|
+
// an auto-expiring notice would vanish after ~4s while the decision,
|
|
538
|
+
// the delivery and every queued FIFO task behind them stay parked,
|
|
539
|
+
// leaving the user with no sign the flow is still waiting. It comes
|
|
540
|
+
// down only when the decision settles (finally below); a decision
|
|
541
|
+
// that never settles keeps its indicator up, which is the truthful
|
|
542
|
+
// state.
|
|
543
|
+
dismiss = state.notify(t('ext-decision-pending', { event: name }), { timeoutMs: 0 });
|
|
544
|
+
}, DECISION_PENDING_MS);
|
|
545
|
+
// Both exits are covered: a fast decision clears the timer before it
|
|
546
|
+
// fires; a slow one dismisses the indicator it raised.
|
|
547
|
+
return pending.finally(() => {
|
|
548
|
+
clearTimeout(timer);
|
|
549
|
+
dismiss?.();
|
|
550
|
+
});
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* The `tui/input` decision event (pi's `input` seam): the FIRST plugin
|
|
554
|
+
* returning a valid decision wins — transform the text, mark it handled,
|
|
555
|
+
* or cancel it. No listeners (or only crashing/malformed ones) means
|
|
556
|
+
* delivery proceeds unchanged, so a broken plugin can never wedge the
|
|
557
|
+
* input path — and can never skip a later veto listener either
|
|
558
|
+
* (dispatchTuiDecision isolates crashes and normalizes returns per
|
|
559
|
+
* listener instead of bailing on the first object).
|
|
560
|
+
*
|
|
561
|
+
* Decision AND delivery enter one FIFO chain in submission order: a slow
|
|
562
|
+
* listener on A parks A's delivery AND any later submissions behind it —
|
|
563
|
+
* without the chain, B's decision could resolve first and the model would
|
|
564
|
+
* receive B before A. Each submission binds its origin agent AT ENQUEUE,
|
|
565
|
+
* so a session switch landing before OR during its decision drops the
|
|
566
|
+
* stale text with a notice instead of sending the old conversation's
|
|
567
|
+
* words to the new session.
|
|
568
|
+
*/
|
|
569
|
+
let inputChain = Promise.resolve();
|
|
570
|
+
const runUserTextDecision = async (text, placement, originAgent, originAgentId) => {
|
|
571
|
+
// Stale detection compares the AGENT REFERENCE, not the id: session ids
|
|
572
|
+
// are reusable (A → /new → /resume A lands back on the same id with a
|
|
573
|
+
// fresh agent), so an id check has an ABA hole. Both origin values are
|
|
574
|
+
// ENQUEUE-time captures (see dispatchUserText): a decision parked behind
|
|
575
|
+
// a slow predecessor must still be judged against the session its text
|
|
576
|
+
// was typed in, not whichever session is live when it finally runs.
|
|
577
|
+
const decision = await withDecisionPending('tui/input', dispatchTuiDecision(ctx, 'tui/input', {
|
|
578
|
+
text,
|
|
579
|
+
delivery: placement === 'steer' ? 'steer' : 'followup',
|
|
580
|
+
sessionId: originAgentId,
|
|
581
|
+
cwd: state.cwd,
|
|
582
|
+
}, normalizeInputDecision));
|
|
583
|
+
if (decision !== undefined) {
|
|
584
|
+
// Both intercepts toast — a bare {cancel}/{handled} must not make the
|
|
585
|
+
// typed line vanish silently (the host-localized fallback mirrors the
|
|
586
|
+
// other decision events' ext-action-cancelled handling).
|
|
587
|
+
if ('cancel' in decision) {
|
|
588
|
+
state.notify(decision.reason ?? t('ext-action-cancelled'), { color: 'warning', timeoutMs: 4000 });
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if ('handled' in decision) {
|
|
592
|
+
state.notify(decision.notice ?? t('ext-action-handled'), { timeoutMs: 4000 });
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
text = decision.text.trim();
|
|
596
|
+
}
|
|
597
|
+
if (agent !== originAgent) {
|
|
598
|
+
state.notify(t('ext-stale-dropped'), { color: 'warning', timeoutMs: 4000 });
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
deliverUserText(text, placement);
|
|
602
|
+
};
|
|
603
|
+
const dispatchUserText = (text, placement) => {
|
|
604
|
+
// D-6: bind the submission to the session it was typed in AT ENQUEUE
|
|
605
|
+
// TIME. The FIFO chain may park this task behind a slow predecessor
|
|
606
|
+
// while the user /new's away — capturing the agent at run time would
|
|
607
|
+
// adopt the NEW session as this text's origin and deliver the old
|
|
608
|
+
// conversation's words into it.
|
|
609
|
+
const originAgent = agent;
|
|
610
|
+
const originAgentId = state.agentId;
|
|
611
|
+
inputChain = inputChain.then(() => runUserTextDecision(text, placement, originAgent, originAgentId)).catch((error) => {
|
|
612
|
+
// The chain must survive a failed decision: log, then continue with
|
|
613
|
+
// the next queued submission.
|
|
614
|
+
ctx.logger.warn('dsh-tui: tui/input dispatch failed: %o', error);
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* The `tui/session-switch` decision event (pi's `session_before_switch`),
|
|
619
|
+
* fired before `/new` or `/resume` replaces the live session (rewind has
|
|
620
|
+
* its own prompt event). The first answering plugin may veto the switch;
|
|
621
|
+
* the reason is toasted here so the fallback string stays host-localized.
|
|
622
|
+
*/
|
|
623
|
+
const sessionSwitchVetoed = async (kind, targetSessionId) => {
|
|
624
|
+
// D-6 stale detection captures the AGENT REFERENCE (session ids are
|
|
625
|
+
// reusable — ABA): a slow decision must not let an older /resume roll
|
|
626
|
+
// over a newer session the user already switched to mid-await.
|
|
627
|
+
const originAgent = agent;
|
|
628
|
+
const decision = await withDecisionPending('tui/session-switch', dispatchTuiDecision(ctx, 'tui/session-switch', {
|
|
629
|
+
kind,
|
|
630
|
+
...(targetSessionId === undefined ? {} : { targetSessionId }),
|
|
631
|
+
sessionId: state.agentId,
|
|
632
|
+
cwd: state.cwd,
|
|
633
|
+
}, normalizeCancelDecision));
|
|
634
|
+
if (agent !== originAgent) {
|
|
635
|
+
// The world changed while the decision parked: drop the pending
|
|
636
|
+
// switch instead of replacing the user's newer session.
|
|
637
|
+
state.notify(t('ext-stale-dropped'), { color: 'warning', timeoutMs: 4000 });
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
if (decision !== undefined) {
|
|
641
|
+
state.notify(decision.reason ?? t('ext-action-cancelled'), { color: 'warning', timeoutMs: 4000 });
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
return false;
|
|
645
|
+
};
|
|
646
|
+
/** Fire-and-forget `tui/session-switched` (parallel): per-session plugin
|
|
647
|
+
* state rebinds here. Listener failures are logged, never propagated —
|
|
648
|
+
* the switch itself already succeeded. */
|
|
649
|
+
const notifySessionSwitched = (kind, sessionId, previousSessionId) => {
|
|
650
|
+
try {
|
|
651
|
+
void dispatchTuiNotification(ctx, 'tui/session-switched', { kind, sessionId, previousSessionId, cwd: state.cwd }).catch((error) => {
|
|
652
|
+
ctx.logger.warn('dsh-tui: tui/session-switched listener failed: %o', error);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
// A bare embedder's context may lack the event bus entirely; the
|
|
657
|
+
// switch itself already succeeded, so this stays a log line.
|
|
658
|
+
ctx.logger.warn('dsh-tui: tui/session-switched dispatch failed: %o', error);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
397
661
|
/** Monotonic token: only the latest `interruptAndDeliver` re-queues, so a
|
|
398
662
|
* second interrupt while the abort settles cannot double-deliver. */
|
|
399
663
|
let interruptSeq = 0;
|
|
@@ -508,6 +772,45 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
508
772
|
const executeRegistryCommand = async (name, rawInput) => {
|
|
509
773
|
if (!commandService)
|
|
510
774
|
return undefined;
|
|
775
|
+
// Resolve the exact definition that execute() will select for this agent.
|
|
776
|
+
// Same names may exist in distinct agent scopes, so a name-only lookup can
|
|
777
|
+
// apply another scope's owner policy.
|
|
778
|
+
const definition = commandService.find(agent, name);
|
|
779
|
+
const owner = commandOwner(ctx, definition);
|
|
780
|
+
// The root checkpoint covers host/direct registrations. A built-in name
|
|
781
|
+
// is not necessarily a namespaced contribution id, so use a deterministic
|
|
782
|
+
// host scope for that case; legacy unscoped denies still conservatively
|
|
783
|
+
// revoke every valid command scope.
|
|
784
|
+
const rootScope = owner?.commandId
|
|
785
|
+
?? (/^[a-z][a-z0-9]*(?:[.-][a-z0-9][a-z0-9-]*)+$/u.test(name)
|
|
786
|
+
? name
|
|
787
|
+
: `dsh-tui.${name.toLowerCase().replace(/[^a-z0-9-]+/gu, '-').replace(/^-+|-+$/gu, '') || 'command'}`);
|
|
788
|
+
if (!currentGrantStore().allows({ componentId: 'root' }, 'commands.invoke', rootScope)) {
|
|
789
|
+
ctx.logger.warn('dsh-tui: registry command invocation denied (commands.invoke revoked for "root" in the grants file)');
|
|
790
|
+
ctx.get('tuiEffectLedger')?.record({
|
|
791
|
+
operation: 'bind',
|
|
792
|
+
resource: { kind: 'permission', id: `root:commands.invoke:${rootScope}` },
|
|
793
|
+
result: 'failed',
|
|
794
|
+
errorCode: 'PERMISSION_NOT_GRANTED',
|
|
795
|
+
}, ctx);
|
|
796
|
+
return t('command-invoke-denied');
|
|
797
|
+
}
|
|
798
|
+
// Per-owner gate (C-041): a command REGISTERED BY A PLUGIN through the
|
|
799
|
+
// plugin-host row's mediated registerCommand (see command-attribution.js)
|
|
800
|
+
// is additionally gated on the OWNER's grant, so a denies entry for the
|
|
801
|
+
// plugin closes the host-mediated invocation of ITS commands.
|
|
802
|
+
// Unattributed host/direct registrations remain inside the documented
|
|
803
|
+
// trusted-in-process boundary and have no plugin grant to evaluate.
|
|
804
|
+
if (owner !== undefined && !currentGrantStore().allows({ componentId: owner.componentId, activationId: owner.activationId }, 'commands.invoke', owner.commandId)) {
|
|
805
|
+
ctx.logger.warn(`dsh-tui: registry command "/${name}" invocation denied — owner Component "${owner.componentId}" lost commands.invoke for "${owner.commandId}"`);
|
|
806
|
+
ctx.get('tuiEffectLedger')?.record({
|
|
807
|
+
operation: 'bind',
|
|
808
|
+
resource: { kind: 'permission', id: `${owner.componentId}:commands.invoke:${owner.commandId}` },
|
|
809
|
+
result: 'failed',
|
|
810
|
+
errorCode: 'PERMISSION_NOT_GRANTED',
|
|
811
|
+
}, ctx);
|
|
812
|
+
return t('command-invoke-denied-owner', { name, owner: owner.componentId });
|
|
813
|
+
}
|
|
511
814
|
try {
|
|
512
815
|
const execution = await commandService.execute(agent, `/${name}${rawInput}`, new AbortController().signal);
|
|
513
816
|
// `undefined` = not registered; a handler error surfaces as its
|
|
@@ -641,6 +944,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
641
944
|
modeIndex: 0,
|
|
642
945
|
workingActivity: undefined,
|
|
643
946
|
activityFrames: options.activityFrames,
|
|
947
|
+
diffLayout: options.diffLayout ?? 'auto',
|
|
644
948
|
activityEnabled: options.activity !== false,
|
|
645
949
|
contextBarEnabled: options.contextBar !== false,
|
|
646
950
|
agentPreset: options.agentPreset,
|
|
@@ -766,7 +1070,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
766
1070
|
// The current session is being used — move it to the MRU front
|
|
767
1071
|
// (/resume sorts by last-used).
|
|
768
1072
|
touchSession(state.agentId);
|
|
769
|
-
|
|
1073
|
+
void dispatchUserText(trimmed, 'followup');
|
|
770
1074
|
},
|
|
771
1075
|
/** Steer a message into the RUNNING turn (Codex/pi semantics): it is
|
|
772
1076
|
* injected at the next step boundary of the current turn and the agent
|
|
@@ -776,11 +1080,13 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
776
1080
|
if (!trimmed)
|
|
777
1081
|
return;
|
|
778
1082
|
touchSession(state.agentId);
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
|
|
1083
|
+
// Same tui/input decision pass as submit; the delivery re-validates
|
|
1084
|
+
// the live agent after the await. Official dsh-agent rc.6: steer() is
|
|
1085
|
+
// synchronous void — the message enters the next-step inbox; a
|
|
1086
|
+
// rejected step leaves it parked for the next wake, and the inbox
|
|
1087
|
+
// events retire the preview (claimed → turn boundary, discarded →
|
|
1088
|
+
// cancel).
|
|
1089
|
+
void dispatchUserText(trimmed, 'steer');
|
|
784
1090
|
},
|
|
785
1091
|
/** Pull a pending message back out of the inbox (Alt+Up): it returns to
|
|
786
1092
|
* the input for editing instead of being delivered. */
|
|
@@ -826,7 +1132,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
826
1132
|
return;
|
|
827
1133
|
for (const text of queued) {
|
|
828
1134
|
touchSession(state.agentId);
|
|
829
|
-
|
|
1135
|
+
// Same tui/input decision pass as a typed submit: Ctrl+Enter must
|
|
1136
|
+
// not bypass a plugin's cancel/transform policy, and re-queued
|
|
1137
|
+
// texts keep submission order through the one FIFO chain.
|
|
1138
|
+
dispatchUserText(text, 'followup');
|
|
830
1139
|
}
|
|
831
1140
|
};
|
|
832
1141
|
if (typeof whenIdle === 'function') {
|
|
@@ -839,7 +1148,43 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
839
1148
|
}
|
|
840
1149
|
return queued.length;
|
|
841
1150
|
},
|
|
842
|
-
|
|
1151
|
+
/**
|
|
1152
|
+
* The `tui/rewind-prompt` decision event (pi's `session_before_fork`):
|
|
1153
|
+
* fired when the rewind picker confirms a message, before any fork
|
|
1154
|
+
* work. The first answering plugin may cancel the rewind (the picker
|
|
1155
|
+
* stays open; the reason is toasted here so the UI string stays
|
|
1156
|
+
* host-localized when absent) or offer extra modes rendered in the
|
|
1157
|
+
* confirm pane. Returns 'cancel', the modes, or null for "no opinion".
|
|
1158
|
+
*/
|
|
1159
|
+
async promptRewind(row) {
|
|
1160
|
+
if (row.seq === undefined)
|
|
1161
|
+
return null;
|
|
1162
|
+
// D-6, same as the other decision points: a slow /new or /resume can
|
|
1163
|
+
// replace the agent while this decision parks. Without the identity
|
|
1164
|
+
// check the picker would go on to show the OLD session's row in the
|
|
1165
|
+
// confirm pane and rewindTo would cut the NEW session at the old
|
|
1166
|
+
// seq — a wrong rewind or a fork failure. Compare agent REFERENCES
|
|
1167
|
+
// (session ids are reusable — ABA) and stale-cancel.
|
|
1168
|
+
const originAgent = agent;
|
|
1169
|
+
const decision = await withDecisionPending('tui/rewind-prompt', dispatchTuiDecision(ctx, 'tui/rewind-prompt', {
|
|
1170
|
+
text: row.text,
|
|
1171
|
+
seq: row.seq,
|
|
1172
|
+
sessionId: state.agentId,
|
|
1173
|
+
cwd: state.cwd,
|
|
1174
|
+
}, normalizeRewindPromptDecision));
|
|
1175
|
+
if (agent !== originAgent) {
|
|
1176
|
+
state.notify(t('ext-stale-dropped'), { color: 'warning', timeoutMs: 4000 });
|
|
1177
|
+
return 'cancel';
|
|
1178
|
+
}
|
|
1179
|
+
if (decision === undefined)
|
|
1180
|
+
return null;
|
|
1181
|
+
if ('cancel' in decision) {
|
|
1182
|
+
state.notify(decision.reason ?? t('ext-action-cancelled'), { color: 'warning', timeoutMs: 4000 });
|
|
1183
|
+
return 'cancel';
|
|
1184
|
+
}
|
|
1185
|
+
return { modes: decision.modes };
|
|
1186
|
+
},
|
|
1187
|
+
async rewindTo(row, mode = null) {
|
|
843
1188
|
if (row.seq === undefined)
|
|
844
1189
|
return null;
|
|
845
1190
|
const sessions = ctx.get('sessions');
|
|
@@ -972,6 +1317,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
972
1317
|
renderEvent(event);
|
|
973
1318
|
// Rebind subscriptions to the new agent, then free the old one.
|
|
974
1319
|
const oldHandle = currentHandle;
|
|
1320
|
+
const sourceSessionId = String(agent.session.id);
|
|
975
1321
|
agent = handle.agent;
|
|
976
1322
|
currentHandle = handle;
|
|
977
1323
|
bindAgent();
|
|
@@ -982,6 +1328,43 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
982
1328
|
touchSession(childId);
|
|
983
1329
|
state.emit();
|
|
984
1330
|
void oldHandle?.dispose().catch(() => { });
|
|
1331
|
+
// Decision-event pair around the completed rewind: `tui/rewind-done`
|
|
1332
|
+
// (the first non-empty string is toasted as the post-rewind summary,
|
|
1333
|
+
// e.g. a plugin reporting restored files) and the generic
|
|
1334
|
+
// `tui/session-switched` notification. Listener failures are logged,
|
|
1335
|
+
// never surfaced — the rewind itself already succeeded.
|
|
1336
|
+
//
|
|
1337
|
+
// rewind-done is a post-hoc summary, NOT a gate, so it is dispatched
|
|
1338
|
+
// DECOUPLED from the return value: the picker is already closed and
|
|
1339
|
+
// PromptInput is live — awaiting a slow listener here would delay the
|
|
1340
|
+
// picked text's return to the draft, letting its late arrival
|
|
1341
|
+
// overwrite whatever the user typed meanwhile, and a listener that
|
|
1342
|
+
// never settles would park tui/session-switched forever. The summary
|
|
1343
|
+
// toasts whenever it lands.
|
|
1344
|
+
try {
|
|
1345
|
+
void dispatchTuiDecision(ctx, 'tui/rewind-done', {
|
|
1346
|
+
text: row.text,
|
|
1347
|
+
mode,
|
|
1348
|
+
boundarySeq: boundary,
|
|
1349
|
+
sourceSessionId,
|
|
1350
|
+
childSessionId: String(childId),
|
|
1351
|
+
sessionId: String(childId),
|
|
1352
|
+
cwd: state.cwd,
|
|
1353
|
+
}, normalizeRewindDoneSummary)
|
|
1354
|
+
.then(summary => {
|
|
1355
|
+
if (summary !== undefined)
|
|
1356
|
+
state.notify(summary, { timeoutMs: 6000 });
|
|
1357
|
+
})
|
|
1358
|
+
.catch((error) => {
|
|
1359
|
+
ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error);
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
// A bare embedder's context may lack the event bus entirely; the
|
|
1364
|
+
// rewind itself already succeeded, so this stays a log line.
|
|
1365
|
+
ctx.logger.warn('dsh-tui: tui/rewind-done dispatch failed: %o', error);
|
|
1366
|
+
}
|
|
1367
|
+
notifySessionSwitched('rewind', String(childId), sourceSessionId);
|
|
985
1368
|
return row.text;
|
|
986
1369
|
},
|
|
987
1370
|
async resumeTo(sessionId) {
|
|
@@ -997,6 +1380,11 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
997
1380
|
state.notify(t('resume-unavailable'), { color: 'error' });
|
|
998
1381
|
return false;
|
|
999
1382
|
}
|
|
1383
|
+
// Plugin veto point (tui/session-switch): before any read of the
|
|
1384
|
+
// target — a veto leaves the live session and its transcript
|
|
1385
|
+
// untouched.
|
|
1386
|
+
if (await sessionSwitchVetoed('resume', sessionId))
|
|
1387
|
+
return false;
|
|
1000
1388
|
let handle;
|
|
1001
1389
|
// Compat boundary: register vouched-for legacy event types (e.g.
|
|
1002
1390
|
// activity/status from pre-#143 logs) in every reachable dsh-session
|
|
@@ -1099,6 +1487,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1099
1487
|
settleStreaming();
|
|
1100
1488
|
// Rebind subscriptions to the resumed agent, then free the old one.
|
|
1101
1489
|
const oldHandle = currentHandle;
|
|
1490
|
+
const previousSessionId = String(agent.session.id);
|
|
1102
1491
|
agent = handle.agent;
|
|
1103
1492
|
currentHandle = handle;
|
|
1104
1493
|
bindAgent();
|
|
@@ -1112,6 +1501,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1112
1501
|
state.emit();
|
|
1113
1502
|
void oldHandle?.dispose().catch(() => { });
|
|
1114
1503
|
clearStagedImages();
|
|
1504
|
+
notifySessionSwitched('resume', sessionId, previousSessionId);
|
|
1115
1505
|
return true;
|
|
1116
1506
|
},
|
|
1117
1507
|
async newSession() {
|
|
@@ -1131,6 +1521,10 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1131
1521
|
});
|
|
1132
1522
|
return false;
|
|
1133
1523
|
}
|
|
1524
|
+
// Plugin veto point (tui/session-switch): no side effects have
|
|
1525
|
+
// happened yet — the session id below is not even allocated.
|
|
1526
|
+
if (await sessionSwitchVetoed('new'))
|
|
1527
|
+
return false;
|
|
1134
1528
|
const sessionId = SessionId(randomUUID());
|
|
1135
1529
|
let handle;
|
|
1136
1530
|
// A fresh session composes the caller's DEFAULT preset: the cordis.yml
|
|
@@ -1222,6 +1616,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1222
1616
|
tools: 0,
|
|
1223
1617
|
};
|
|
1224
1618
|
const oldHandle = currentHandle;
|
|
1619
|
+
const previousSessionId = String(agent.session.id);
|
|
1225
1620
|
agent = handle.agent;
|
|
1226
1621
|
currentHandle = handle;
|
|
1227
1622
|
bindAgent();
|
|
@@ -1233,6 +1628,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1233
1628
|
touchSession(handle.agent.id);
|
|
1234
1629
|
void oldHandle?.dispose().catch(() => { });
|
|
1235
1630
|
clearStagedImages();
|
|
1631
|
+
notifySessionSwitched('new', String(handle.agent.id), previousSessionId);
|
|
1236
1632
|
return true;
|
|
1237
1633
|
},
|
|
1238
1634
|
listWorkspaces() {
|
|
@@ -1444,13 +1840,30 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1444
1840
|
};
|
|
1445
1841
|
state.notifications.push(item);
|
|
1446
1842
|
state.emit();
|
|
1447
|
-
|
|
1843
|
+
const remove = () => {
|
|
1448
1844
|
const index = state.notifications.indexOf(item);
|
|
1449
1845
|
if (index >= 0) {
|
|
1450
1846
|
state.notifications.splice(index, 1);
|
|
1451
1847
|
state.emit();
|
|
1452
1848
|
}
|
|
1453
|
-
}
|
|
1849
|
+
};
|
|
1850
|
+
// timeoutMs 0 = sticky: no expiry timer, the dismiss handle is the
|
|
1851
|
+
// only way out (a decision-parked indicator must outlive the wait it
|
|
1852
|
+
// describes — auto-expiring it would hide a still-parked flow, D-8).
|
|
1853
|
+
const expire = item.timeoutMs > 0 ? setTimeout(remove, item.timeoutMs) : undefined;
|
|
1854
|
+
// Early-dismiss handle for flows that know their notice went stale
|
|
1855
|
+
// (e.g. a decision-parked indicator whose decision just landed).
|
|
1856
|
+
return () => {
|
|
1857
|
+
if (expire !== undefined)
|
|
1858
|
+
clearTimeout(expire);
|
|
1859
|
+
remove();
|
|
1860
|
+
};
|
|
1861
|
+
},
|
|
1862
|
+
setDiffLayout(layout) {
|
|
1863
|
+
if (layout === state.diffLayout)
|
|
1864
|
+
return;
|
|
1865
|
+
state.diffLayout = layout;
|
|
1866
|
+
state.emit();
|
|
1454
1867
|
},
|
|
1455
1868
|
setActivityFrames(name) {
|
|
1456
1869
|
if (!isPresetName(name)) {
|
|
@@ -1561,12 +1974,84 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1561
1974
|
return Promise.all(providers.map(provider => llm.listModels(provider.id).catch(() => [])))
|
|
1562
1975
|
.then(lists => lists.flat());
|
|
1563
1976
|
},
|
|
1977
|
+
async listSkills() {
|
|
1978
|
+
// snapshot() over list(): only a COMPLETE observation is authoritative
|
|
1979
|
+
// (same contract as the skill-command merge above) — a partial catalog
|
|
1980
|
+
// must surface as "failed", not as a misleading near-empty picker.
|
|
1981
|
+
const registry = skillRegistryFor(agent);
|
|
1982
|
+
if (registry === undefined)
|
|
1983
|
+
return [];
|
|
1984
|
+
try {
|
|
1985
|
+
const observation = await registry.snapshot(skillViewOptions(agent));
|
|
1986
|
+
if (!observation.complete)
|
|
1987
|
+
return undefined;
|
|
1988
|
+
return observation.skills.map(skill => ({
|
|
1989
|
+
name: skill.name,
|
|
1990
|
+
description: skill.description,
|
|
1991
|
+
userInvocable: isUserInvocable(skill),
|
|
1992
|
+
source: skill.source,
|
|
1993
|
+
}));
|
|
1994
|
+
}
|
|
1995
|
+
catch {
|
|
1996
|
+
return undefined;
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1564
1999
|
async describeCredential(ref) {
|
|
1565
2000
|
const credentials = ctx.get('credentials');
|
|
1566
2001
|
if (!credentials)
|
|
1567
2002
|
return undefined;
|
|
1568
2003
|
return credentials.describe(ref);
|
|
1569
2004
|
},
|
|
2005
|
+
settingsHost() {
|
|
2006
|
+
if (settingsHostResolved)
|
|
2007
|
+
return settingsHostCache;
|
|
2008
|
+
settingsHostResolved = true;
|
|
2009
|
+
// The `/settings` screen's runtime surface, over the same dsh-base
|
|
2010
|
+
// seams the `/provider` wizard uses: settings (namespace descriptors +
|
|
2011
|
+
// revision-fenced mutate) and credentials (secret writes). Structurally
|
|
2012
|
+
// typed like the other optional seams in this file.
|
|
2013
|
+
const settings = ctx.get('settings');
|
|
2014
|
+
const credentials = ctx.get('credentials');
|
|
2015
|
+
if (!settings)
|
|
2016
|
+
return undefined;
|
|
2017
|
+
settingsHostCache = {
|
|
2018
|
+
listNamespaces() {
|
|
2019
|
+
// redactSecrets: the screen never renders a secret literal — secret
|
|
2020
|
+
// fields are write-only controls over the credentials seam.
|
|
2021
|
+
return settings.describe({ redactSecrets: true }).map(descriptor => ({
|
|
2022
|
+
ns: descriptor.ns,
|
|
2023
|
+
revision: descriptor.revision,
|
|
2024
|
+
applies: descriptor.applies,
|
|
2025
|
+
value: descriptor.value,
|
|
2026
|
+
user: descriptor.user,
|
|
2027
|
+
}));
|
|
2028
|
+
},
|
|
2029
|
+
write(ns, ops, expectedRevision) {
|
|
2030
|
+
return settings.mutate(ns, ops, expectedRevision);
|
|
2031
|
+
},
|
|
2032
|
+
async credentialConfigured(ref) {
|
|
2033
|
+
// The environment shadows the store (providerSetup.envShadows), so
|
|
2034
|
+
// an env-provided key counts as configured.
|
|
2035
|
+
if (process.env[ref] !== undefined)
|
|
2036
|
+
return true;
|
|
2037
|
+
return credentials !== undefined && (await credentials.resolve(ref)) !== undefined;
|
|
2038
|
+
},
|
|
2039
|
+
async writeCredential(ref, value) {
|
|
2040
|
+
if (!credentials)
|
|
2041
|
+
throw new Error('credentials service unavailable');
|
|
2042
|
+
await credentials.set(ref, value);
|
|
2043
|
+
},
|
|
2044
|
+
};
|
|
2045
|
+
return settingsHostCache;
|
|
2046
|
+
},
|
|
2047
|
+
settingsSections() {
|
|
2048
|
+
const sections = ctx.get('tuiSettingsSections');
|
|
2049
|
+
return sections?.list() ?? [];
|
|
2050
|
+
},
|
|
2051
|
+
subscribeSettingsSections(listener) {
|
|
2052
|
+
const sections = ctx.get('tuiSettingsSections');
|
|
2053
|
+
return sections?.subscribe(listener) ?? (() => { });
|
|
2054
|
+
},
|
|
1570
2055
|
providerSetup() {
|
|
1571
2056
|
// The `/provider` wizard's runtime surface, over the dsh-base seams:
|
|
1572
2057
|
// settings (profile persistence), credentials (key storage) and the
|
|
@@ -1749,20 +2234,60 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1749
2234
|
state.notify(t('compact-while-working'), { color: 'warning' });
|
|
1750
2235
|
return;
|
|
1751
2236
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2237
|
+
// Plugin veto point (tui/compact): the first answering plugin may
|
|
2238
|
+
// cancel the compaction before anything runs.
|
|
2239
|
+
const originAgentId = state.agentId;
|
|
2240
|
+
// Compare the AGENT REFERENCE after the await, not the id — session
|
|
2241
|
+
// ids are reusable (A → /new → /resume A returns the same id on a new
|
|
2242
|
+
// agent), so an id comparison has an ABA hole that would hand the new
|
|
2243
|
+
// agent to the OLD scope's compaction service.
|
|
2244
|
+
const originAgent = agent;
|
|
2245
|
+
void (async () => {
|
|
2246
|
+
const decision = await withDecisionPending('tui/compact', dispatchTuiDecision(ctx, 'tui/compact', {
|
|
2247
|
+
sessionId: originAgentId,
|
|
2248
|
+
cwd: state.cwd,
|
|
2249
|
+
}, normalizeCancelDecision));
|
|
2250
|
+
if (decision !== undefined) {
|
|
2251
|
+
state.notify(decision.reason ?? t('ext-action-cancelled'), { color: 'warning', timeoutMs: 4000 });
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
// Stale-drop (same rule as tui/input): the await parked us while the
|
|
2255
|
+
// user switched sessions — `compactService` was resolved through the
|
|
2256
|
+
// OLD agent's scope chain, and the mutable `agent` now points at the
|
|
2257
|
+
// new session. Running now would hand the new agent to the old
|
|
2258
|
+
// service (or call into an unloaded one).
|
|
2259
|
+
if (agent !== originAgent) {
|
|
2260
|
+
state.notify(t('ext-compact-stale'), { color: 'warning', timeoutMs: 4000 });
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
if (state.working) {
|
|
2264
|
+
// The await above gave a queued turn time to start; compacting
|
|
2265
|
+
// mid-turn now would be the same race the check upfront avoided.
|
|
2266
|
+
state.notify(t('compact-while-working'), { color: 'warning' });
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const signal = new AbortController().signal;
|
|
2270
|
+
state.notify(t('compact-working'));
|
|
2271
|
+
void compactService
|
|
2272
|
+
.compactNow(agent, signal)
|
|
2273
|
+
.then((result) => {
|
|
2274
|
+
state.notify(result ? t('compact-done') : t('compact-nothing'));
|
|
2275
|
+
})
|
|
2276
|
+
.catch((error) => {
|
|
2277
|
+
state.notify(t('compact-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
2278
|
+
});
|
|
2279
|
+
})();
|
|
1762
2280
|
},
|
|
1763
2281
|
runExternalCommand(name, rawInput) {
|
|
1764
2282
|
return executeRegistryCommand(name, rawInput);
|
|
1765
2283
|
},
|
|
2284
|
+
pluginScene: sceneRuntime?.active,
|
|
2285
|
+
openPluginScene(id) {
|
|
2286
|
+
return sceneRuntime?.open(id) ?? false;
|
|
2287
|
+
},
|
|
2288
|
+
closePluginScene() {
|
|
2289
|
+
sceneRuntime?.close();
|
|
2290
|
+
},
|
|
1766
2291
|
pushLocal(title, lines) {
|
|
1767
2292
|
state.rows.push({ id: nextRowId++, kind: 'local', text: title });
|
|
1768
2293
|
for (const line of lines) {
|
|
@@ -1920,8 +2445,23 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1920
2445
|
if (existsSync(LEGACY_DATA_DIR)) {
|
|
1921
2446
|
lines.push(t('doctor-legacy-dir'));
|
|
1922
2447
|
}
|
|
2448
|
+
// Plugin-spec diagnostics (v0.15): the runtime generation and the
|
|
2449
|
+
// vendored registry self-check, both soft-probed (#183 discipline).
|
|
2450
|
+
const pluginHost = ctx.get('tuiPluginHost');
|
|
2451
|
+
lines.push(t('doctor-plugin-generation', { id: pluginHost?.generationId ?? t('doctor-plugin-host-missing') }));
|
|
2452
|
+
const violations = pluginHost?.selfCheck();
|
|
2453
|
+
lines.push(t('doctor-plugin-registry', {
|
|
2454
|
+
state: violations === undefined ? t('doctor-plugin-host-missing') : violations.length === 0 ? '✓' : `✗ ${violations.length}`,
|
|
2455
|
+
}));
|
|
1923
2456
|
return lines;
|
|
1924
2457
|
},
|
|
2458
|
+
pluginsInfo(args) {
|
|
2459
|
+
const host = ctx.get('tuiPluginHost');
|
|
2460
|
+
return pluginsInfoLines(args, {
|
|
2461
|
+
grants: host?.grants ?? currentGrantStore(),
|
|
2462
|
+
host: host?.describe(),
|
|
2463
|
+
});
|
|
2464
|
+
},
|
|
1925
2465
|
async listSubagents() {
|
|
1926
2466
|
const subagents = ctx.get('subagents');
|
|
1927
2467
|
if (!subagents)
|
|
@@ -1943,6 +2483,7 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
1943
2483
|
},
|
|
1944
2484
|
releaseContributions() {
|
|
1945
2485
|
releaseSkillCommands();
|
|
2486
|
+
unsubscribeScenes?.();
|
|
1946
2487
|
},
|
|
1947
2488
|
traceEvents() {
|
|
1948
2489
|
// Immutable per-append snapshot (dsh-session caches the frozen array);
|
|
@@ -2274,16 +2815,38 @@ export function createChannel(ctx, initialAgent, options) {
|
|
|
2274
2815
|
},
|
|
2275
2816
|
});
|
|
2276
2817
|
skillCommands.set(name, { dispose, description });
|
|
2818
|
+
ctx.get('tuiEffectLedger')?.record({ operation: 'create', resource: { kind: 'command', id: name }, result: 'applied' }, ctx);
|
|
2277
2819
|
}
|
|
2278
2820
|
catch (error) {
|
|
2821
|
+
// C-041: a duplicate registration arrives as a plain-message Error
|
|
2822
|
+
// from dsh-commands; map it onto the contract code before handling
|
|
2823
|
+
// (the refusal path itself is unchanged).
|
|
2824
|
+
const mapped = mapCommandError(error);
|
|
2279
2825
|
skillCommandsRefused.add(name);
|
|
2280
|
-
ctx.logger.warn(`skill commands: "${name}" not registrable: %o`,
|
|
2826
|
+
ctx.logger.warn(`skill commands: "${name}" not registrable%s: %o`, hasCommandErrorCode(mapped, 'DUPLICATE_CONTRIBUTION_ID') ? ' (DUPLICATE_CONTRIBUTION_ID)' : '', mapped);
|
|
2827
|
+
ctx.get('tuiEffectLedger')?.record({
|
|
2828
|
+
operation: 'create',
|
|
2829
|
+
resource: { kind: 'command', id: name },
|
|
2830
|
+
result: 'failed',
|
|
2831
|
+
errorCode: hasCommandErrorCode(mapped, 'DUPLICATE_CONTRIBUTION_ID') ? 'DUPLICATE_CONTRIBUTION_ID' : 'COMMAND_FAILED',
|
|
2832
|
+
}, ctx);
|
|
2281
2833
|
}
|
|
2282
2834
|
}
|
|
2283
2835
|
};
|
|
2284
2836
|
ctx.on('skills/change', () => {
|
|
2285
2837
|
void refreshSkillCommands();
|
|
2286
2838
|
});
|
|
2839
|
+
/**
|
|
2840
|
+
* Mirror the scene runtime's active scene into channel state, so screens
|
|
2841
|
+
* swap to it through the ordinary version-bump re-render instead of a
|
|
2842
|
+
* second subscription channel.
|
|
2843
|
+
*/
|
|
2844
|
+
const unsubscribeScenes = sceneRuntime?.subscribe(() => {
|
|
2845
|
+
if (state.pluginScene === sceneRuntime.active)
|
|
2846
|
+
return;
|
|
2847
|
+
state.pluginScene = sceneRuntime.active;
|
|
2848
|
+
state.emit();
|
|
2849
|
+
});
|
|
2287
2850
|
/** See {@link Channel.releaseContributions}. */
|
|
2288
2851
|
const releaseSkillCommands = () => {
|
|
2289
2852
|
if (skillCommandsRetry !== undefined)
|
|
@@ -2614,12 +3177,18 @@ ${output}
|
|
|
2614
3177
|
break;
|
|
2615
3178
|
}
|
|
2616
3179
|
case 'assistant/message': {
|
|
2617
|
-
const row = ensureStreaming(event.seq);
|
|
2618
|
-
row.time = event.time;
|
|
2619
3180
|
const text = textOf(event.data.message.content);
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3181
|
+
// Reasoning/tool-only steps emit no text: creating an assistant row
|
|
3182
|
+
// anyway leaves an empty `●` bullet in the transcript. A pre-existing
|
|
3183
|
+
// streaming row always has text (ensureStreaming is only reached on
|
|
3184
|
+
// non-empty text deltas), so only create one when text arrives.
|
|
3185
|
+
const row = streaming ?? (text ? ensureStreaming(event.seq) : undefined);
|
|
3186
|
+
if (row !== undefined) {
|
|
3187
|
+
row.time = event.time;
|
|
3188
|
+
if (text)
|
|
3189
|
+
row.text = text;
|
|
3190
|
+
row.streaming = false;
|
|
3191
|
+
}
|
|
2623
3192
|
streaming = undefined;
|
|
2624
3193
|
if (reasoning !== undefined) {
|
|
2625
3194
|
// Seal, don't fold: the per-step duration settles here, but the
|
|
@@ -2861,6 +3430,30 @@ ${output}
|
|
|
2861
3430
|
text: t('agent-preset-switched', { preset: data.agentPreset ?? 'unknown' }),
|
|
2862
3431
|
});
|
|
2863
3432
|
nextRowId += 1;
|
|
3433
|
+
break;
|
|
3434
|
+
}
|
|
3435
|
+
// Custom plugin events (tuiRenderers seam): a registered renderer
|
|
3436
|
+
// maps the payload to text rows — title as a local row, body as
|
|
3437
|
+
// preview-clipped local-output rows, same shape pushLocal uses.
|
|
3438
|
+
// Runs on the live stream AND on replay (resume/rewind), so the
|
|
3439
|
+
// projection must stay total; the runtime isolates renderer
|
|
3440
|
+
// crashes per type.
|
|
3441
|
+
if (rendererRuntime !== undefined) {
|
|
3442
|
+
const rendered = rendererRuntime.render(event.type, event.data);
|
|
3443
|
+
if (rendered !== undefined) {
|
|
3444
|
+
if (rendered.title !== undefined && rendered.title !== '') {
|
|
3445
|
+
state.rows.push({ id: nextRowId, kind: 'local', text: rendered.title });
|
|
3446
|
+
nextRowId += 1;
|
|
3447
|
+
}
|
|
3448
|
+
for (const line of rendered.lines) {
|
|
3449
|
+
state.rows.push({
|
|
3450
|
+
id: nextRowId,
|
|
3451
|
+
kind: 'local-output',
|
|
3452
|
+
text: preview(String(line), LOCAL_OUTPUT_LIMIT),
|
|
3453
|
+
});
|
|
3454
|
+
nextRowId += 1;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
2864
3457
|
}
|
|
2865
3458
|
break;
|
|
2866
3459
|
}
|
|
@@ -2998,6 +3591,10 @@ ${output}
|
|
|
2998
3591
|
ctx.on('session/event', (session, event) => {
|
|
2999
3592
|
if (session !== agent.session)
|
|
3000
3593
|
return;
|
|
3594
|
+
// Observation broker (C-042): maps user/message + assistant/message
|
|
3595
|
+
// into grant-gated envelopes; every other event type is a no-op, and
|
|
3596
|
+
// publish never throws into this arm.
|
|
3597
|
+
messageObserver?.publish(session, event);
|
|
3001
3598
|
activityTracker.onSessionEvent(event);
|
|
3002
3599
|
renderWorkingActivity();
|
|
3003
3600
|
// Mode-affecting atoms fold into the Shift+Tab mode indicator the
|