@robota-sdk/agent-ui-terminal 3.0.0-beta.100
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/CHANGELOG.md +1204 -0
- package/LICENSE +661 -0
- package/README.md +50 -0
- package/dist/node/index.d.ts +1224 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +33 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +88 -0
- package/src/App.tsx +157 -0
- package/src/AppPresentation.tsx +190 -0
- package/src/AppView.tsx +26 -0
- package/src/BackgroundTaskPanel.tsx +73 -0
- package/src/CjkTextInput.tsx +336 -0
- package/src/ConfirmPrompt.tsx +140 -0
- package/src/ContextWarningBanner.tsx +45 -0
- package/src/ExecutionWorkspaceDetailPane.tsx +85 -0
- package/src/ExecutionWorkspaceSwitcher.tsx +213 -0
- package/src/HistorySearchOverlay.tsx +150 -0
- package/src/InputArea.tsx +338 -0
- package/src/ListPicker.tsx +158 -0
- package/src/MenuSelect.tsx +159 -0
- package/src/MessageList.tsx +308 -0
- package/src/MultiSelectList.tsx +294 -0
- package/src/PendingActionPrompt.tsx +121 -0
- package/src/PermissionPrompt.tsx +236 -0
- package/src/PluginTUI.tsx +258 -0
- package/src/RoleLabel.tsx +72 -0
- package/src/SafeText.tsx +66 -0
- package/src/SessionEventNotices.tsx +27 -0
- package/src/SessionPicker.tsx +92 -0
- package/src/SessionStatusBar.tsx +80 -0
- package/src/SlashAutocomplete.tsx +154 -0
- package/src/StatusBar.tsx +308 -0
- package/src/StreamingIndicator.tsx +145 -0
- package/src/SupervisedSessionView.tsx +872 -0
- package/src/TextPrompt.tsx +112 -0
- package/src/ThemePicker.tsx +366 -0
- package/src/ToolCommandOutput.tsx +48 -0
- package/src/ToolDiffBlock.tsx +44 -0
- package/src/TransportTUI.tsx +166 -0
- package/src/TuiInteractionChannel.ts +530 -0
- package/src/UpdateNotice.tsx +18 -0
- package/src/UsageSummaryEntry.tsx +49 -0
- package/src/WaveText.tsx +70 -0
- package/src/__tests__/ListPicker.test.tsx +188 -0
- package/src/__tests__/MenuSelect.test.tsx +127 -0
- package/src/__tests__/PendingActionPrompt.test.tsx +193 -0
- package/src/__tests__/PluginTUI.test.tsx +167 -0
- package/src/__tests__/SlashAutocomplete.test.tsx +162 -0
- package/src/__tests__/SupervisedSessionView-render.test.tsx +52 -0
- package/src/__tests__/SupervisedSessionView.test.tsx +1112 -0
- package/src/__tests__/TextPrompt.test.tsx +112 -0
- package/src/__tests__/TransportTUI.test.tsx +124 -0
- package/src/__tests__/TuiInteractionChannel.askUser.test.ts +124 -0
- package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +294 -0
- package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +650 -0
- package/src/__tests__/UpdateNotice.test.tsx +15 -0
- package/src/__tests__/__fixtures__/korean-text.json +17 -0
- package/src/__tests__/abort-after-permission.test.tsx +160 -0
- package/src/__tests__/abort-streaming-e2e.test.tsx +183 -0
- package/src/__tests__/attached-app-session-switch.test.tsx +317 -0
- package/src/__tests__/attention-bell-and-marks.test.ts +144 -0
- package/src/__tests__/autocomplete-editing-keys.test.tsx +127 -0
- package/src/__tests__/background-task-panel-countdown.test.tsx +75 -0
- package/src/__tests__/background-task-panel.test.tsx +98 -0
- package/src/__tests__/background-task-row-format.test.ts +152 -0
- package/src/__tests__/channel-factory-integration.test.ts +150 -0
- package/src/__tests__/checkpoint-store-per-channel.test.ts +81 -0
- package/src/__tests__/cjk-defer-submit.test.tsx +68 -0
- package/src/__tests__/cjk-fallback-render.test.tsx +89 -0
- package/src/__tests__/cjk-text-input-flow.test.ts +187 -0
- package/src/__tests__/cjk-text-input.test.ts +196 -0
- package/src/__tests__/command-handoff-pty-e2e.test.ts +107 -0
- package/src/__tests__/command-output-summary.test.ts +95 -0
- package/src/__tests__/command-result-handler.test.ts +171 -0
- package/src/__tests__/compact-event-bridge.test.ts +20 -0
- package/src/__tests__/confirm-permission-flow.test.ts +149 -0
- package/src/__tests__/confirm-prompt.test.tsx +103 -0
- package/src/__tests__/contextual-keybindings-input.test.tsx +158 -0
- package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +47 -0
- package/src/__tests__/effort-reaches-the-session.test.ts +41 -0
- package/src/__tests__/execution-workspace-switcher.test.tsx +147 -0
- package/src/__tests__/execution-workspace-view-model.test.ts +94 -0
- package/src/__tests__/external-prompt-notice.test.tsx +100 -0
- package/src/__tests__/fixtures/command-handoff-driver.tsx +114 -0
- package/src/__tests__/fixtures/fake-editor.sh +7 -0
- package/src/__tests__/fixtures/terminal-handoff-driver.tsx +90 -0
- package/src/__tests__/fork-attach.test.tsx +235 -0
- package/src/__tests__/grant-history-reaches-the-session.test.ts +31 -0
- package/src/__tests__/helpers/terminal-profiles.ts +152 -0
- package/src/__tests__/helpers/vt-cursor-interpreter.ts +175 -0
- package/src/__tests__/history-clear-broadcast.test.ts +66 -0
- package/src/__tests__/history-search-overlay.test.tsx +329 -0
- package/src/__tests__/humanize-tool-name.test.ts +30 -0
- package/src/__tests__/input-area-bottom-border.test.tsx +45 -0
- package/src/__tests__/input-area-flow.test.ts +199 -0
- package/src/__tests__/input-area-focus-handoff.test.tsx +77 -0
- package/src/__tests__/interaction-channel-charter.test.ts +13 -0
- package/src/__tests__/key-hint-consistency.test.tsx +249 -0
- package/src/__tests__/key-hint-footer.test.tsx +69 -0
- package/src/__tests__/live-trace-reaches-session.test.ts +14 -0
- package/src/__tests__/message-list-rendering.test.tsx +474 -0
- package/src/__tests__/numbered-selection-chunk.test.tsx +41 -0
- package/src/__tests__/org-policy-reaches-the-channel.test.ts +77 -0
- package/src/__tests__/output-style-reaches-session.test.ts +35 -0
- package/src/__tests__/palette-consistency.test.ts +329 -0
- package/src/__tests__/permission-prompt-arming.test.tsx +153 -0
- package/src/__tests__/permission-prompt-availability.test.tsx +23 -0
- package/src/__tests__/permission-prompt-peer.test.tsx +34 -0
- package/src/__tests__/permission-prompt-requester.test.tsx +34 -0
- package/src/__tests__/product-display-name.test.tsx +79 -0
- package/src/__tests__/prompt-queue.test.tsx +260 -0
- package/src/__tests__/provider-definitions-reach-the-session.test.ts +75 -0
- package/src/__tests__/pty/ask-user-question.ptytest.ts +72 -0
- package/src/__tests__/pty/background-work-switcher.ptytest.ts +53 -0
- package/src/__tests__/pty/behavior-2437-git.ptytest.ts +350 -0
- package/src/__tests__/pty/fixtures/ask-user-question.jsonl +3 -0
- package/src/__tests__/pty/fixtures/replay-conversation.jsonl +4 -0
- package/src/__tests__/pty/fixtures/screen-005-ask-shapes.jsonl +5 -0
- package/src/__tests__/pty/fixtures/screen-005-permission.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-006-colors.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-2002-themes.jsonl +5 -0
- package/src/__tests__/pty/flag-tui.ptytest.ts +56 -0
- package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +320 -0
- package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +211 -0
- package/src/__tests__/pty/ime-cursor.ptytest.ts +187 -0
- package/src/__tests__/pty/isolated-home.ts +97 -0
- package/src/__tests__/pty/org-policy.ptytest.ts +71 -0
- package/src/__tests__/pty/provider-setup.ptytest.ts +61 -0
- package/src/__tests__/pty/pty-driver.ts +155 -0
- package/src/__tests__/pty/replay-conversation.ptytest.ts +68 -0
- package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +135 -0
- package/src/__tests__/pty/screen-006-no-color.ptytest.ts +140 -0
- package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +123 -0
- package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +124 -0
- package/src/__tests__/pty/screen-2002-themes.ptytest.ts +292 -0
- package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +228 -0
- package/src/__tests__/pty/screen-reader-mode.ptytest.ts +228 -0
- package/src/__tests__/pty/session-attach.ptytest.ts +182 -0
- package/src/__tests__/pty/settings-screen.ptytest.ts +49 -0
- package/src/__tests__/pty/spawn-pty.test.ts +120 -0
- package/src/__tests__/pty/spawn-pty.ts +229 -0
- package/src/__tests__/pty/terminal-handoff.ptytest.ts +79 -0
- package/src/__tests__/pty/trust-prompt.ptytest.ts +134 -0
- package/src/__tests__/pty/tui-pty.ptytest.ts +65 -0
- package/src/__tests__/pty-driver-dispose.test.ts +43 -0
- package/src/__tests__/real-cursor-positioning.test.tsx +264 -0
- package/src/__tests__/rename-broadcast-persistence.test.tsx +140 -0
- package/src/__tests__/render-channel-options.test.ts +201 -0
- package/src/__tests__/render-lifecycle.test.ts +36 -0
- package/src/__tests__/render-markdown.test.ts +164 -0
- package/src/__tests__/rendered-markdown-styling.test.tsx +98 -0
- package/src/__tests__/safe-text-boundary.test.tsx +75 -0
- package/src/__tests__/sandbox-reaches-the-session.test.ts +37 -0
- package/src/__tests__/screen-reader-input.test.tsx +106 -0
- package/src/__tests__/screen-reader-labels.test.ts +91 -0
- package/src/__tests__/screen-reader-menus.test.tsx +200 -0
- package/src/__tests__/screen-reader-pacing.test.ts +230 -0
- package/src/__tests__/screen-reader-prepark-echo.test.tsx +200 -0
- package/src/__tests__/screen-reader-render-options.test.ts +262 -0
- package/src/__tests__/screen-reader-stdout.test.ts +322 -0
- package/src/__tests__/screen-reader-turn-marks-port.test.tsx +85 -0
- package/src/__tests__/screen-reader-turn-signals.test.tsx +125 -0
- package/src/__tests__/sec-019-terminal-sanitizer.test.ts +276 -0
- package/src/__tests__/sec-019-tool-label-render.test.tsx +207 -0
- package/src/__tests__/selection-flow.test.ts +61 -0
- package/src/__tests__/session-event-notices.test.tsx +31 -0
- package/src/__tests__/session-init-poller.test.ts +102 -0
- package/src/__tests__/session-picker.test.tsx +93 -0
- package/src/__tests__/session-switch-channel.test.tsx +470 -0
- package/src/__tests__/shutdown-signal.test.ts +34 -0
- package/src/__tests__/status-activity.test.ts +71 -0
- package/src/__tests__/status-bar.test.tsx +290 -0
- package/src/__tests__/status-glyph.test.ts +30 -0
- package/src/__tests__/streaming-indicator.test.tsx +178 -0
- package/src/__tests__/terminal-capabilities.test.ts +153 -0
- package/src/__tests__/terminal-handoff-controller.test.ts +173 -0
- package/src/__tests__/terminal-handoff-pty-e2e.test.ts +77 -0
- package/src/__tests__/text-prompt-flow.test.ts +77 -0
- package/src/__tests__/theme-picker.test.tsx +345 -0
- package/src/__tests__/transport-boundary.test.ts +11 -0
- package/src/__tests__/tui-app-channel-port.test.ts +31 -0
- package/src/__tests__/tui-channel-init-failure.test.ts +70 -0
- package/src/__tests__/tui-channel-lifecycle-coordinator.test.ts +258 -0
- package/src/__tests__/tui-interaction-queues.test.ts +149 -0
- package/src/__tests__/tui-preset-capabilities.test.ts +33 -0
- package/src/__tests__/tui-state-manager.error-stall.test.ts +169 -0
- package/src/__tests__/tui-state-manager.test.ts +482 -0
- package/src/__tests__/ui-intent-rendering.test.tsx +291 -0
- package/src/__tests__/wave-text.test.tsx +133 -0
- package/src/__tests__/wire-tui-channel.test.ts +1457 -0
- package/src/__tests__/wire-tui-client-commands.test.ts +243 -0
- package/src/app-banner.tsx +42 -0
- package/src/app-static-items.ts +35 -0
- package/src/app-view-model.ts +140 -0
- package/src/attached-session-connection.ts +14 -0
- package/src/attention/__tests__/attention-coordinator.test.ts +166 -0
- package/src/attention/__tests__/attention-tracker.test.ts +103 -0
- package/src/attention/__tests__/countdown.test.ts +15 -0
- package/src/attention/__tests__/focus-input-filter.test.ts +93 -0
- package/src/attention/__tests__/interval-recap.test.ts +49 -0
- package/src/attention/attention-coordinator.ts +71 -0
- package/src/attention/attention-tracker.ts +124 -0
- package/src/attention/countdown.ts +24 -0
- package/src/attention/focus-input-filter.ts +131 -0
- package/src/attention/interval-recap.ts +148 -0
- package/src/attention/time-units.ts +6 -0
- package/src/attention-bell.ts +79 -0
- package/src/attributed-user-echo.ts +30 -0
- package/src/background-task-row-format.ts +99 -0
- package/src/command-interaction.ts +8 -0
- package/src/command-output-summary.ts +129 -0
- package/src/create-default-tui-cli-adapter.ts +45 -0
- package/src/execution-workspace-switcher-selection.ts +118 -0
- package/src/execution-workspace-view-model.ts +139 -0
- package/src/external-prompt-notice.tsx +38 -0
- package/src/flows/__tests__/background-focus-flow.test.ts +48 -0
- package/src/flows/__tests__/defer-submit.test.ts +88 -0
- package/src/flows/__tests__/real-cursor-flow.test.ts +139 -0
- package/src/flows/background-focus-flow.ts +44 -0
- package/src/flows/cjk-cursor-motion.ts +75 -0
- package/src/flows/cjk-text-deletion.ts +53 -0
- package/src/flows/cjk-text-input-flow.ts +248 -0
- package/src/flows/cjk-text-input-types.ts +24 -0
- package/src/flows/confirm-prompt-flow.ts +90 -0
- package/src/flows/defer-submit.ts +67 -0
- package/src/flows/fork-attach-flow.ts +62 -0
- package/src/flows/input-area-flow.ts +188 -0
- package/src/flows/permission-prompt-flow.ts +108 -0
- package/src/flows/real-cursor-flow.ts +83 -0
- package/src/flows/selection-flow.ts +205 -0
- package/src/flows/session-init-poller.ts +76 -0
- package/src/flows/text-prompt-flow.ts +93 -0
- package/src/history-search/__tests__/history-search-flow.test.ts +86 -0
- package/src/history-search/history-search-flow.ts +94 -0
- package/src/history-search/useHistorySearch.ts +336 -0
- package/src/hooks/__tests__/use-app-detach-key.test.tsx +72 -0
- package/src/hooks/__tests__/use-app-loop-escape.test.tsx +87 -0
- package/src/hooks/__tests__/use-plugin-callbacks.test.ts +18 -0
- package/src/hooks/__tests__/use-tui-channel-stability.test.tsx +100 -0
- package/src/hooks/__tests__/useAppThemeState.test.tsx +130 -0
- package/src/hooks/command-result-handler.ts +44 -0
- package/src/hooks/side-effects-types.ts +45 -0
- package/src/hooks/useAppController.ts +271 -0
- package/src/hooks/useAppInputBindings.ts +163 -0
- package/src/hooks/useAppInteractionState.ts +84 -0
- package/src/hooks/useAppLifecycleState.ts +81 -0
- package/src/hooks/useAppOverlays.ts +90 -0
- package/src/hooks/useAppScreenState.ts +81 -0
- package/src/hooks/useAppSubmissionState.ts +45 -0
- package/src/hooks/useAppThemeState.ts +157 -0
- package/src/hooks/useAppWorkspaceState.ts +149 -0
- package/src/hooks/useAppearanceSettings.ts +30 -0
- package/src/hooks/useAutocomplete.ts +101 -0
- package/src/hooks/useBackgroundPanel.ts +58 -0
- package/src/hooks/useCountdownTick.ts +24 -0
- package/src/hooks/useExecutionDetailPage.ts +69 -0
- package/src/hooks/useForkAttach.ts +60 -0
- package/src/hooks/useInputAreaHistorySearch.ts +72 -0
- package/src/hooks/useInputAreaKeys.ts +114 -0
- package/src/hooks/useNumberedSelection.ts +93 -0
- package/src/hooks/usePluginCallbacks.ts +38 -0
- package/src/hooks/usePluginScreenData.ts +85 -0
- package/src/hooks/useRealCursorPosition.ts +125 -0
- package/src/hooks/useRenderMarkdown.ts +38 -0
- package/src/hooks/useScreenReaderTurnSignals.ts +113 -0
- package/src/hooks/useSideEffects.ts +144 -0
- package/src/hooks/useStatusLineSettings.ts +50 -0
- package/src/hooks/useTerminalHandoffSuspension.ts +30 -0
- package/src/hooks/useTuiChannel.ts +141 -0
- package/src/humanize-tool-name.ts +51 -0
- package/src/index.ts +72 -0
- package/src/input-area-rules.tsx +71 -0
- package/src/json-value.ts +18 -0
- package/src/key-hint-footer.tsx +54 -0
- package/src/keybindings/__tests__/keybinding-registry.test.ts +228 -0
- package/src/keybindings/__tests__/node-keybindings-source.test.ts +118 -0
- package/src/keybindings/keybinding-catalogue.ts +171 -0
- package/src/keybindings/keybinding-registry.ts +303 -0
- package/src/keybindings/keybinding-syntax.ts +155 -0
- package/src/keybindings/keybindings-context.tsx +124 -0
- package/src/keybindings/node-keybindings-source.ts +189 -0
- package/src/numbered-list.tsx +113 -0
- package/src/plugin-tui-handlers.ts +163 -0
- package/src/product-display-name-context.tsx +30 -0
- package/src/render-markdown.ts +209 -0
- package/src/render.tsx +746 -0
- package/src/sanitize-terminal-text.ts +171 -0
- package/src/screen-reader-announcement.ts +52 -0
- package/src/screen-reader-context.tsx +32 -0
- package/src/screen-reader-labels.ts +64 -0
- package/src/screen-reader-pacing-context.tsx +47 -0
- package/src/screen-reader-pacing.ts +178 -0
- package/src/screen-reader-stdout.ts +328 -0
- package/src/short-session-id.ts +9 -0
- package/src/shutdown-signal.ts +34 -0
- package/src/slash-command-input.ts +11 -0
- package/src/status-activity.ts +70 -0
- package/src/status-glyph.ts +78 -0
- package/src/terminal-capabilities-context.tsx +27 -0
- package/src/terminal-capabilities.ts +77 -0
- package/src/terminal-focus-reporting.ts +50 -0
- package/src/terminal-handoff-controller.ts +98 -0
- package/src/terminal-marks.ts +63 -0
- package/src/theme/__tests__/built-in-themes.test.ts +117 -0
- package/src/theme/__tests__/color-vision.test.ts +82 -0
- package/src/theme/__tests__/theme-context.test.tsx +146 -0
- package/src/theme/__tests__/theme-document.test.ts +222 -0
- package/src/theme/__tests__/theme-registry.test.ts +138 -0
- package/src/theme/__tests__/theme-styles.test.ts +233 -0
- package/src/theme/built-in-themes.ts +342 -0
- package/src/theme/color-vision.ts +171 -0
- package/src/theme/index.ts +45 -0
- package/src/theme/theme-context.tsx +81 -0
- package/src/theme/theme-contracts.ts +139 -0
- package/src/theme/theme-document.ts +278 -0
- package/src/theme/theme-registry.ts +127 -0
- package/src/theme/theme-styles.ts +177 -0
- package/src/tool-summary-status.ts +59 -0
- package/src/tui-app-channel-port.ts +111 -0
- package/src/tui-channel-lifecycle-coordinator.ts +149 -0
- package/src/tui-channel-options.ts +184 -0
- package/src/tui-cli-adapter-context.tsx +13 -0
- package/src/tui-cli-adapter.ts +24 -0
- package/src/tui-interaction-queues.ts +171 -0
- package/src/tui-session-binding.ts +52 -0
- package/src/tui-session-event-projector.ts +149 -0
- package/src/tui-session-events.ts +89 -0
- package/src/tui-session-notice-store.ts +43 -0
- package/src/tui-session-options.ts +134 -0
- package/src/tui-state-manager.ts +347 -0
- package/src/types.ts +21 -0
- package/src/use-terminal-title.ts +29 -0
- package/src/utils/__tests__/edit-diff.test.ts +426 -0
- package/src/utils/__tests__/paste-detection.test.ts +116 -0
- package/src/utils/__tests__/paste-labels.test.ts +46 -0
- package/src/utils/__tests__/tool-call-extractor.test.ts +227 -0
- package/src/utils/__tests__/tool-diff-summary.test.ts +104 -0
- package/src/utils/edit-diff.ts +151 -0
- package/src/utils/input-top-border.ts +19 -0
- package/src/utils/paste-labels.ts +9 -0
- package/src/utils/tool-call-extractor.ts +92 -0
- package/src/utils/tool-diff-summary.ts +75 -0
- package/src/waiting-loop-stop-notice.ts +23 -0
- package/src/wire-history-sync.ts +159 -0
- package/src/wire-tui-channel.ts +963 -0
- package/src/wire-tui-client-commands.ts +142 -0
- package/src/wire-tui-projection.ts +136 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useApp, useInput } from 'ink';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useKeybindingActions } from '../keybindings/keybindings-context.js';
|
|
5
|
+
import { useScreenReader } from '../screen-reader-context.js';
|
|
6
|
+
import { handleInterrupt } from '../shutdown-signal.js';
|
|
7
|
+
import { useScreenReaderTurnSignals } from './useScreenReaderTurnSignals.js';
|
|
8
|
+
|
|
9
|
+
import type { IPendingPermissionRequest } from '../types.js';
|
|
10
|
+
import type { IActionRequest, TSessionEndReason } from '@robota-sdk/agent-core';
|
|
11
|
+
import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-execution';
|
|
12
|
+
import type { IToolState } from '@robota-sdk/agent-interface-session';
|
|
13
|
+
|
|
14
|
+
interface IOptions {
|
|
15
|
+
readonly isThinking: boolean;
|
|
16
|
+
readonly isShuttingDown: boolean;
|
|
17
|
+
/** An observer stops nothing: neither the drivers' turn nor their loop. */
|
|
18
|
+
readonly readOnly: boolean;
|
|
19
|
+
/** Attached to a session a host runs: Ctrl-] detaches, like Ctrl-C and `/exit` there. */
|
|
20
|
+
readonly attached: boolean;
|
|
21
|
+
readonly permissionRequest: IPendingPermissionRequest | null;
|
|
22
|
+
readonly pendingUserAction: IActionRequest | null;
|
|
23
|
+
readonly pluginVisible: boolean;
|
|
24
|
+
readonly transportVisible: boolean;
|
|
25
|
+
readonly sessionPickerVisible: boolean;
|
|
26
|
+
readonly workspaceSwitcherVisible: boolean;
|
|
27
|
+
/** SCREEN-1993: the history-search overlay owns Esc and the switcher key while open. */
|
|
28
|
+
readonly historySearchOpen: boolean;
|
|
29
|
+
readonly selectedEntry: IExecutionWorkspaceEntry | undefined;
|
|
30
|
+
readonly backgroundListFocused: boolean;
|
|
31
|
+
readonly mainThreadEntryId: string | undefined;
|
|
32
|
+
readonly activeTools: readonly IToolState[];
|
|
33
|
+
readonly abort: () => void;
|
|
34
|
+
readonly stopWaitingLoop: () => Promise<void>;
|
|
35
|
+
readonly toggleWorkspaceSwitcher: () => void;
|
|
36
|
+
readonly selectWorkspaceEntry: (entryId: string) => void;
|
|
37
|
+
readonly shutdown: (reason?: TSessionEndReason) => Promise<void>;
|
|
38
|
+
readonly recoveryError: string | undefined;
|
|
39
|
+
readonly recoveryPending: boolean;
|
|
40
|
+
readonly coordinationBlocked: boolean;
|
|
41
|
+
/** SCREEN-2002: the theme picker owns its keys while it is open, like every sibling overlay. */
|
|
42
|
+
readonly themePickerVisible: boolean;
|
|
43
|
+
readonly retryRecovery: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function overlaysBlockKeys(options: IOptions): boolean {
|
|
47
|
+
return Boolean(
|
|
48
|
+
options.permissionRequest ||
|
|
49
|
+
options.pendingUserAction ||
|
|
50
|
+
options.pluginVisible ||
|
|
51
|
+
options.transportVisible ||
|
|
52
|
+
options.sessionPickerVisible ||
|
|
53
|
+
options.workspaceSwitcherVisible ||
|
|
54
|
+
options.historySearchOpen ||
|
|
55
|
+
options.themePickerVisible ||
|
|
56
|
+
options.coordinationBlocked,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function useEscapeBindings(options: IOptions): void {
|
|
61
|
+
const context = options.isThinking ? 'thinking' : 'background-detail';
|
|
62
|
+
useKeybindingActions(context, (actions) => {
|
|
63
|
+
if (overlaysBlockKeys(options)) return;
|
|
64
|
+
if (actions.includes('abort') && options.isThinking) {
|
|
65
|
+
if (!options.readOnly) options.abort();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const selected = options.selectedEntry;
|
|
69
|
+
if (
|
|
70
|
+
actions.includes('return-to-main') &&
|
|
71
|
+
selected &&
|
|
72
|
+
selected.kind !== 'main_thread' &&
|
|
73
|
+
options.mainThreadEntryId
|
|
74
|
+
) {
|
|
75
|
+
options.selectWorkspaceEntry(options.mainThreadEntryId);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
actions.includes('return-to-main') &&
|
|
80
|
+
!options.backgroundListFocused &&
|
|
81
|
+
!options.readOnly
|
|
82
|
+
) {
|
|
83
|
+
void options.stopWaitingLoop();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function useWorkspaceSwitcherBinding(options: IOptions): void {
|
|
89
|
+
useKeybindingActions('app', (actions) => {
|
|
90
|
+
if (!actions.includes('open-workspace-switcher')) return;
|
|
91
|
+
if (
|
|
92
|
+
options.permissionRequest ||
|
|
93
|
+
options.pendingUserAction ||
|
|
94
|
+
options.pluginVisible ||
|
|
95
|
+
options.sessionPickerVisible ||
|
|
96
|
+
options.historySearchOpen ||
|
|
97
|
+
options.themePickerVisible ||
|
|
98
|
+
options.coordinationBlocked ||
|
|
99
|
+
options.isShuttingDown
|
|
100
|
+
)
|
|
101
|
+
return;
|
|
102
|
+
options.toggleWorkspaceSwitcher();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function useRecoveryBinding(options: IOptions): void {
|
|
107
|
+
useKeybindingActions('recovery', (actions) => {
|
|
108
|
+
if (
|
|
109
|
+
options.recoveryError !== undefined &&
|
|
110
|
+
!options.recoveryPending &&
|
|
111
|
+
actions.includes('retry')
|
|
112
|
+
) {
|
|
113
|
+
options.retryRecovery();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Ctrl-]: the raw byte, or `]` with ctrl where the terminal reports modifiers. */
|
|
119
|
+
const DETACH_BYTE = '\x1d';
|
|
120
|
+
|
|
121
|
+
function isDetachKey(input: string, ctrl: boolean): boolean {
|
|
122
|
+
return input === DETACH_BYTE || (ctrl && input === ']');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function useShutdownBindings(options: IOptions): void {
|
|
126
|
+
const { exit } = useApp();
|
|
127
|
+
const requestShutdown = (reason: TSessionEndReason): void => {
|
|
128
|
+
handleInterrupt({
|
|
129
|
+
isShuttingDown: options.isShuttingDown,
|
|
130
|
+
graceful: () => void options.shutdown(reason).finally(() => exit()),
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
useInput((input, key) => {
|
|
134
|
+
if (key.ctrl && input === 'c') requestShutdown('prompt_input_exit');
|
|
135
|
+
// Ctrl-] is the attach client's detach key. On a terminal running its own session there is
|
|
136
|
+
// nothing to detach from, and quitting that session is left to Ctrl-C and `/exit`.
|
|
137
|
+
else if (options.attached && isDetachKey(input, key.ctrl)) requestShutdown('prompt_input_exit');
|
|
138
|
+
});
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const onSignal = (): void => requestShutdown('other');
|
|
141
|
+
process.on('SIGINT', onSignal);
|
|
142
|
+
process.on('SIGTERM', onSignal);
|
|
143
|
+
return () => {
|
|
144
|
+
process.off('SIGINT', onSignal);
|
|
145
|
+
process.off('SIGTERM', onSignal);
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function useAppInputBindings(options: IOptions): boolean {
|
|
151
|
+
useEscapeBindings(options);
|
|
152
|
+
useRecoveryBinding(options);
|
|
153
|
+
useWorkspaceSwitcherBinding(options);
|
|
154
|
+
useShutdownBindings(options);
|
|
155
|
+
const screenReader = useScreenReader();
|
|
156
|
+
useScreenReaderTurnSignals({
|
|
157
|
+
enabled: screenReader,
|
|
158
|
+
isThinking: options.isThinking,
|
|
159
|
+
activeTools: options.activeTools,
|
|
160
|
+
awaitingAnswer: options.permissionRequest !== null || options.pendingUserAction !== null,
|
|
161
|
+
});
|
|
162
|
+
return screenReader;
|
|
163
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useAppInputBindings } from './useAppInputBindings.js';
|
|
2
|
+
import { useAppOverlays } from './useAppOverlays.js';
|
|
3
|
+
import { useAppSubmissionState } from './useAppSubmissionState.js';
|
|
4
|
+
|
|
5
|
+
import type { IAppOverlays } from './useAppOverlays.js';
|
|
6
|
+
import type { IAppScreenState } from './useAppScreenState.js';
|
|
7
|
+
import type { IAppSubmissionState } from './useAppSubmissionState.js';
|
|
8
|
+
import type { ITuiChannelState } from './useTuiChannel.js';
|
|
9
|
+
import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-command';
|
|
10
|
+
import type { IInteractiveSessionStore } from '@robota-sdk/agent-interface-session';
|
|
11
|
+
import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
|
|
12
|
+
|
|
13
|
+
interface IOptions {
|
|
14
|
+
readonly cwd: string;
|
|
15
|
+
readonly state: ITuiChannelState;
|
|
16
|
+
readonly shell: IAppScreenState;
|
|
17
|
+
readonly sessionStore: IInteractiveSessionStore | undefined;
|
|
18
|
+
readonly onSessionSwitch: (sessionId: string) => Promise<void>;
|
|
19
|
+
readonly pluginAdapter: ICommandPluginAdapter | undefined;
|
|
20
|
+
readonly transportRegistry: ITransportRegistryView | undefined;
|
|
21
|
+
readonly recoveryError: string | undefined;
|
|
22
|
+
readonly recoveryPending: boolean;
|
|
23
|
+
readonly coordinationBlocked: boolean;
|
|
24
|
+
readonly retryRecovery: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IAppInteractionState {
|
|
28
|
+
readonly submission: IAppSubmissionState;
|
|
29
|
+
readonly overlays: IAppOverlays;
|
|
30
|
+
readonly screenReader: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useAppInteractionState(options: IOptions): IAppInteractionState {
|
|
34
|
+
const { screens, workspace } = options.shell;
|
|
35
|
+
const submission = useAppSubmissionState({
|
|
36
|
+
selectedEntry: workspace.background.selectedEntry,
|
|
37
|
+
submit: screens.handleSubmit,
|
|
38
|
+
sendAgentJob: options.state.sendAgentJob,
|
|
39
|
+
isThinking: options.state.isThinking,
|
|
40
|
+
});
|
|
41
|
+
const screenReader = useAppInputBindings({
|
|
42
|
+
isThinking: options.state.isThinking,
|
|
43
|
+
isShuttingDown: options.state.isShuttingDown,
|
|
44
|
+
readOnly: options.state.readOnly,
|
|
45
|
+
attached: options.state.attached,
|
|
46
|
+
permissionRequest: options.state.permissionRequest,
|
|
47
|
+
pendingUserAction: options.state.pendingUserAction,
|
|
48
|
+
pluginVisible: screens.showPluginTUI,
|
|
49
|
+
transportVisible: screens.showTransportTUI,
|
|
50
|
+
sessionPickerVisible: screens.showSessionPicker,
|
|
51
|
+
workspaceSwitcherVisible: workspace.background.switcherVisible,
|
|
52
|
+
historySearchOpen: options.shell.historySearchOpen,
|
|
53
|
+
themePickerVisible: screens.showThemePicker,
|
|
54
|
+
selectedEntry: workspace.background.selectedEntry,
|
|
55
|
+
backgroundListFocused: workspace.isBackgroundListFocused,
|
|
56
|
+
mainThreadEntryId: workspace.mainThreadEntryId,
|
|
57
|
+
activeTools: options.state.activeTools,
|
|
58
|
+
abort: options.state.handleAbort,
|
|
59
|
+
stopWaitingLoop: options.state.handleStopWaitingLoop,
|
|
60
|
+
toggleWorkspaceSwitcher: workspace.toggleSwitcher,
|
|
61
|
+
selectWorkspaceEntry: options.state.selectExecutionWorkspaceEntry,
|
|
62
|
+
shutdown: options.state.handleShutdown,
|
|
63
|
+
recoveryError: options.recoveryError,
|
|
64
|
+
recoveryPending: options.recoveryPending,
|
|
65
|
+
coordinationBlocked: options.coordinationBlocked,
|
|
66
|
+
retryRecovery: options.retryRecovery,
|
|
67
|
+
});
|
|
68
|
+
const overlays = useAppOverlays({
|
|
69
|
+
cwd: options.cwd,
|
|
70
|
+
pluginAdapter: options.pluginAdapter,
|
|
71
|
+
pluginVisible: screens.showPluginTUI,
|
|
72
|
+
setPluginVisible: screens.setShowPluginTUI,
|
|
73
|
+
transportRegistry: options.transportRegistry,
|
|
74
|
+
transportVisible: screens.showTransportTUI,
|
|
75
|
+
setTransportVisible: screens.setShowTransportTUI,
|
|
76
|
+
sessionStore: options.sessionStore,
|
|
77
|
+
hostSessions: options.state.hostSessions,
|
|
78
|
+
sessionPickerVisible: screens.showSessionPicker,
|
|
79
|
+
setSessionPickerVisible: screens.setShowSessionPicker,
|
|
80
|
+
onSessionSwitch: options.onSessionSwitch,
|
|
81
|
+
addEntry: options.state.addEntry,
|
|
82
|
+
});
|
|
83
|
+
return { submission, overlays, screenReader };
|
|
84
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTerminalTitle } from '../use-terminal-title.js';
|
|
4
|
+
import { useTerminalHandoffSuspension } from './useTerminalHandoffSuspension.js';
|
|
5
|
+
|
|
6
|
+
import type { ITuiAppChannelPort } from '../tui-app-channel-port.js';
|
|
7
|
+
|
|
8
|
+
export interface IAppLifecycleState {
|
|
9
|
+
readonly sessionName: string | undefined;
|
|
10
|
+
readonly setSessionName: (name: string) => void;
|
|
11
|
+
readonly handoffSuspended: boolean;
|
|
12
|
+
readonly updateNotice: string | undefined;
|
|
13
|
+
readonly startError: string | undefined;
|
|
14
|
+
readonly startPending: boolean;
|
|
15
|
+
readonly retryStart: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function useChannelLifecycle(channel: ITuiAppChannelPort): {
|
|
19
|
+
startError: string | undefined;
|
|
20
|
+
startPending: boolean;
|
|
21
|
+
retryStart: () => void;
|
|
22
|
+
} {
|
|
23
|
+
const [startError, setStartError] = useState<string | undefined>();
|
|
24
|
+
const [startPending, setStartPending] = useState(true);
|
|
25
|
+
const retryStart = useCallback((): void => {
|
|
26
|
+
setStartPending(true);
|
|
27
|
+
void channel
|
|
28
|
+
.start()
|
|
29
|
+
.then(() => setStartError(undefined))
|
|
30
|
+
.catch((cause) => {
|
|
31
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
32
|
+
setStartError(`TUI start failed: ${message}`);
|
|
33
|
+
})
|
|
34
|
+
.finally(() => setStartPending(false));
|
|
35
|
+
}, [channel]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
retryStart();
|
|
38
|
+
}, [channel, retryStart]);
|
|
39
|
+
return { startError, startPending, retryStart };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function useStartupUpdateNotice(source?: Promise<string | undefined>): string | undefined {
|
|
43
|
+
const [notice, setNotice] = useState<string | undefined>();
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let mounted = true;
|
|
46
|
+
source
|
|
47
|
+
?.then((value) => {
|
|
48
|
+
if (mounted && value !== undefined) setNotice(value);
|
|
49
|
+
})
|
|
50
|
+
.catch(() => {
|
|
51
|
+
// Startup update checks are best-effort and must not disrupt the TUI.
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
mounted = false;
|
|
55
|
+
};
|
|
56
|
+
}, [source]);
|
|
57
|
+
return notice;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useAppLifecycleState(
|
|
61
|
+
channel: ITuiAppChannelPort,
|
|
62
|
+
startupUpdateNotice?: Promise<string | undefined>,
|
|
63
|
+
): IAppLifecycleState {
|
|
64
|
+
const channelLifecycle = useChannelLifecycle(channel);
|
|
65
|
+
const [sessionName, setSessionName] = useState<string | undefined>(channel.sessionName);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (channel.sessionName !== undefined && channel.sessionName !== sessionName) {
|
|
68
|
+
setSessionName(channel.sessionName);
|
|
69
|
+
}
|
|
70
|
+
}, [channel, channel.sessionName, sessionName]);
|
|
71
|
+
useTerminalTitle(sessionName);
|
|
72
|
+
const handoffSuspended = useTerminalHandoffSuspension(channel.terminalHandoffController);
|
|
73
|
+
const updateNotice = useStartupUpdateNotice(startupUpdateNotice);
|
|
74
|
+
return {
|
|
75
|
+
sessionName,
|
|
76
|
+
setSessionName,
|
|
77
|
+
handoffSuspended,
|
|
78
|
+
updateNotice,
|
|
79
|
+
...channelLifecycle,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createSystemMessage, messageToHistoryEntry } from '@robota-sdk/agent-core';
|
|
2
|
+
import { listResumableSessionSummaries } from '@robota-sdk/agent-framework';
|
|
3
|
+
import { useCallback, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
import { usePluginCallbacks } from './usePluginCallbacks.js';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
IAppPluginViewModel,
|
|
9
|
+
IAppSessionPickerViewModel,
|
|
10
|
+
IAppTransportViewModel,
|
|
11
|
+
} from '../app-view-model.js';
|
|
12
|
+
import type { IHistoryEntry } from '@robota-sdk/agent-core';
|
|
13
|
+
import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-command';
|
|
14
|
+
import type {
|
|
15
|
+
IInteractiveSessionStore,
|
|
16
|
+
ISessionListingEntry,
|
|
17
|
+
} from '@robota-sdk/agent-interface-session';
|
|
18
|
+
import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
|
|
19
|
+
|
|
20
|
+
interface IOptions {
|
|
21
|
+
readonly cwd: string;
|
|
22
|
+
readonly pluginAdapter: ICommandPluginAdapter | undefined;
|
|
23
|
+
readonly pluginVisible: boolean;
|
|
24
|
+
readonly setPluginVisible: (visible: boolean) => void;
|
|
25
|
+
readonly transportRegistry: ITransportRegistryView | undefined;
|
|
26
|
+
readonly transportVisible: boolean;
|
|
27
|
+
readonly setTransportVisible: (visible: boolean) => void;
|
|
28
|
+
readonly sessionStore: IInteractiveSessionStore | undefined;
|
|
29
|
+
/** The host's sessions, when the channel's host keeps them; they replace the local store's. */
|
|
30
|
+
readonly hostSessions: readonly ISessionListingEntry[] | undefined;
|
|
31
|
+
readonly sessionPickerVisible: boolean;
|
|
32
|
+
readonly setSessionPickerVisible: (visible: boolean) => void;
|
|
33
|
+
readonly onSessionSwitch: (sessionId: string) => Promise<void>;
|
|
34
|
+
readonly addEntry: (entry: IHistoryEntry) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const NO_SESSIONS_NOTICE = 'No saved sessions to resume in this workspace.';
|
|
38
|
+
|
|
39
|
+
export interface IAppOverlays {
|
|
40
|
+
readonly plugin: IAppPluginViewModel;
|
|
41
|
+
readonly transport: IAppTransportViewModel;
|
|
42
|
+
readonly sessionPicker: IAppSessionPickerViewModel;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useAppOverlays(options: IOptions): IAppOverlays {
|
|
46
|
+
const pluginCallbacks = usePluginCallbacks(options.pluginAdapter);
|
|
47
|
+
const addPluginMessage = useCallback(
|
|
48
|
+
(content: string) => options.addEntry(messageToHistoryEntry(createSystemMessage(content))),
|
|
49
|
+
[options.addEntry],
|
|
50
|
+
);
|
|
51
|
+
const selectSession = useCallback(
|
|
52
|
+
(sessionId: string) => {
|
|
53
|
+
options.setSessionPickerVisible(false);
|
|
54
|
+
void options.onSessionSwitch(sessionId);
|
|
55
|
+
},
|
|
56
|
+
[options.onSessionSwitch, options.setSessionPickerVisible],
|
|
57
|
+
);
|
|
58
|
+
const cancelSessionPicker = useCallback(() => {
|
|
59
|
+
options.setSessionPickerVisible(false);
|
|
60
|
+
options.addEntry(messageToHistoryEntry(createSystemMessage('Session resume cancelled.')));
|
|
61
|
+
}, [options.addEntry, options.setSessionPickerVisible]);
|
|
62
|
+
const sessions =
|
|
63
|
+
options.hostSessions ?? listResumableSessionSummaries(options.sessionStore, options.cwd);
|
|
64
|
+
// A picker with nothing to pick would only hold the prompt: say so and give the prompt back.
|
|
65
|
+
const nothingToResume = options.sessionPickerVisible && sessions.length === 0;
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!nothingToResume) return;
|
|
68
|
+
options.setSessionPickerVisible(false);
|
|
69
|
+
options.addEntry(messageToHistoryEntry(createSystemMessage(NO_SESSIONS_NOTICE)));
|
|
70
|
+
}, [nothingToResume, options.addEntry, options.setSessionPickerVisible]);
|
|
71
|
+
return {
|
|
72
|
+
plugin: {
|
|
73
|
+
visible: options.pluginVisible,
|
|
74
|
+
callbacks: pluginCallbacks,
|
|
75
|
+
close: () => options.setPluginVisible(false),
|
|
76
|
+
addMessage: addPluginMessage,
|
|
77
|
+
},
|
|
78
|
+
transport: {
|
|
79
|
+
visible: options.transportVisible,
|
|
80
|
+
registry: options.transportRegistry,
|
|
81
|
+
close: () => options.setTransportVisible(false),
|
|
82
|
+
},
|
|
83
|
+
sessionPicker: {
|
|
84
|
+
visible: options.sessionPickerVisible && !nothingToResume,
|
|
85
|
+
sessions,
|
|
86
|
+
select: selectSession,
|
|
87
|
+
cancel: cancelSessionPicker,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useAppearanceSettings } from './useAppearanceSettings.js';
|
|
4
|
+
import { useAppWorkspaceState } from './useAppWorkspaceState.js';
|
|
5
|
+
import { useSideEffects } from './useSideEffects.js';
|
|
6
|
+
import { useStatusLineSettings } from './useStatusLineSettings.js';
|
|
7
|
+
|
|
8
|
+
import type { IUseSideEffectsResult } from './side-effects-types.js';
|
|
9
|
+
import type { IAppWorkspaceState } from './useAppWorkspaceState.js';
|
|
10
|
+
import type { ITuiChannelState } from './useTuiChannel.js';
|
|
11
|
+
import type {
|
|
12
|
+
IAppearanceSettings,
|
|
13
|
+
IStatusLineCommandSettings,
|
|
14
|
+
} from '@robota-sdk/agent-interface-command';
|
|
15
|
+
import type { IInteractiveSessionStore } from '@robota-sdk/agent-interface-session';
|
|
16
|
+
|
|
17
|
+
interface IOptions {
|
|
18
|
+
readonly state: ITuiChannelState;
|
|
19
|
+
readonly setSessionName: (name: string) => void;
|
|
20
|
+
readonly sessionStore: IInteractiveSessionStore | undefined;
|
|
21
|
+
readonly onSessionSwitch: (sessionId: string) => Promise<void>;
|
|
22
|
+
readonly showSessionPickerOnStart: boolean | undefined;
|
|
23
|
+
readonly coordinationBlocked: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IAppScreenState {
|
|
27
|
+
readonly screens: IUseSideEffectsResult;
|
|
28
|
+
readonly workspace: IAppWorkspaceState;
|
|
29
|
+
readonly statusSettings: IStatusLineCommandSettings;
|
|
30
|
+
/** SCREEN-2002: the persisted appearance, re-read after every slash-command result. */
|
|
31
|
+
readonly appearance: IAppearanceSettings;
|
|
32
|
+
/** SCREEN-1993: the input area's history-search overlay is open and owns the keys. */
|
|
33
|
+
readonly historySearchOpen: boolean;
|
|
34
|
+
readonly setHistorySearchOpen: (open: boolean) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useAppScreenState(options: IOptions): IAppScreenState {
|
|
38
|
+
const [workspaceSwitcherVisible, setWorkspaceSwitcherVisible] = useState(false);
|
|
39
|
+
const [historySearchOpen, setHistorySearchOpen] = useState(false);
|
|
40
|
+
const [statusSettings, refreshStatusSettings] = useStatusLineSettings();
|
|
41
|
+
const [appearance, refreshAppearance] = useAppearanceSettings();
|
|
42
|
+
const screens = useSideEffects({
|
|
43
|
+
uiEventPort: options.state.uiEventPort,
|
|
44
|
+
baseHandleSubmit: options.state.handleSubmit,
|
|
45
|
+
setSessionName: options.setSessionName,
|
|
46
|
+
refreshStatusLineSettings: refreshStatusSettings,
|
|
47
|
+
refreshAppearanceSettings: refreshAppearance,
|
|
48
|
+
showSessionPickerOnStart: options.showSessionPickerOnStart,
|
|
49
|
+
openAgentSwitcher: () => setWorkspaceSwitcherVisible(true),
|
|
50
|
+
});
|
|
51
|
+
const navigationEnabled =
|
|
52
|
+
options.state.permissionRequest === null &&
|
|
53
|
+
options.state.pendingUserAction === null &&
|
|
54
|
+
!screens.showPluginTUI &&
|
|
55
|
+
!screens.showTransportTUI &&
|
|
56
|
+
!screens.showSessionPicker &&
|
|
57
|
+
!screens.showThemePicker &&
|
|
58
|
+
!workspaceSwitcherVisible &&
|
|
59
|
+
!historySearchOpen &&
|
|
60
|
+
!options.coordinationBlocked;
|
|
61
|
+
const workspace = useAppWorkspaceState({
|
|
62
|
+
snapshot: options.state.executionWorkspaceSnapshot,
|
|
63
|
+
selectedEntryId: options.state.selectedExecutionEntryId,
|
|
64
|
+
select: options.state.selectExecutionWorkspaceEntry,
|
|
65
|
+
read: options.state.readExecutionWorkspaceDetail,
|
|
66
|
+
sessionStore: options.sessionStore,
|
|
67
|
+
onSessionSwitch: options.onSessionSwitch,
|
|
68
|
+
addEntry: options.state.addEntry,
|
|
69
|
+
navigationEnabled,
|
|
70
|
+
switcherVisible: workspaceSwitcherVisible,
|
|
71
|
+
setSwitcherVisible: setWorkspaceSwitcherVisible,
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
screens,
|
|
75
|
+
workspace,
|
|
76
|
+
statusSettings,
|
|
77
|
+
appearance,
|
|
78
|
+
historySearchOpen,
|
|
79
|
+
setHistorySearchOpen,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-execution';
|
|
4
|
+
|
|
5
|
+
interface IOptions {
|
|
6
|
+
readonly selectedEntry: IExecutionWorkspaceEntry | undefined;
|
|
7
|
+
readonly submit: (input: string) => Promise<void>;
|
|
8
|
+
readonly sendAgentJob: (taskId: string, input: string) => Promise<void>;
|
|
9
|
+
readonly isThinking: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IAppSubmissionState {
|
|
13
|
+
readonly submit: (input: string) => Promise<void>;
|
|
14
|
+
readonly gitRefreshToken: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useAppSubmissionState(options: IOptions): IAppSubmissionState {
|
|
18
|
+
const [gitRefreshToken, setGitRefreshToken] = useState(0);
|
|
19
|
+
const routedSubmit = useCallback(
|
|
20
|
+
async (input: string): Promise<void> => {
|
|
21
|
+
const entry = options.selectedEntry;
|
|
22
|
+
if (entry && entry.kind !== 'main_thread' && entry.controls.includes('send')) {
|
|
23
|
+
await options.sendAgentJob(entry.sourceId, input);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await options.submit(input);
|
|
27
|
+
},
|
|
28
|
+
[options.selectedEntry, options.sendAgentJob, options.submit],
|
|
29
|
+
);
|
|
30
|
+
const submit = useCallback(
|
|
31
|
+
async (input: string): Promise<void> => {
|
|
32
|
+
setGitRefreshToken((token) => token + 1);
|
|
33
|
+
await routedSubmit(input);
|
|
34
|
+
},
|
|
35
|
+
[routedSubmit],
|
|
36
|
+
);
|
|
37
|
+
const wasThinkingRef = useRef(false);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (wasThinkingRef.current && !options.isThinking) {
|
|
40
|
+
setGitRefreshToken((token) => token + 1);
|
|
41
|
+
}
|
|
42
|
+
wasThinkingRef.current = options.isThinking;
|
|
43
|
+
}, [options.isThinking]);
|
|
44
|
+
return { submit, gitRefreshToken };
|
|
45
|
+
}
|