@agi-fans/dsh-tui 0.22.0 → 0.23.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/LICENSE +26 -0
- package/lib/CHANGELOG.md +219 -1
- package/lib/chrome/box.d.ts +1 -18
- package/lib/chrome/box.js +2 -45
- package/lib/chrome/box.js.map +1 -1
- package/lib/chrome/code-highlight.js +4 -4
- package/lib/chrome/code-highlight.js.map +1 -1
- package/lib/chrome/command-output.d.ts +0 -1
- package/lib/chrome/command-output.js +11 -5
- package/lib/chrome/command-output.js.map +1 -1
- package/lib/chrome/goal-bar.js +1 -1
- package/lib/chrome/goal-bar.js.map +1 -1
- package/lib/chrome/main-screen-renderer.js +4 -3
- package/lib/chrome/main-screen-renderer.js.map +1 -1
- package/lib/chrome/markdown.d.ts +3 -3
- package/lib/chrome/markdown.js +85 -36
- package/lib/chrome/markdown.js.map +1 -1
- package/lib/chrome/md-style.d.ts +5 -2
- package/lib/chrome/md-style.js +13 -4
- package/lib/chrome/md-style.js.map +1 -1
- package/lib/chrome/renderer.d.ts +37 -1
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/status-line.d.ts +3 -3
- package/lib/chrome/status-line.js +5 -5
- package/lib/chrome/status-line.js.map +1 -1
- package/lib/chrome/theme.d.ts +8 -3
- package/lib/chrome/theme.js +51 -153
- package/lib/chrome/theme.js.map +1 -1
- package/lib/commands/auth-account.d.ts +9 -0
- package/lib/commands/auth-account.js +202 -0
- package/lib/commands/auth-account.js.map +1 -0
- package/lib/commands/auth.js +238 -414
- package/lib/commands/auth.js.map +1 -1
- package/lib/commands/diff.js +22 -16
- package/lib/commands/diff.js.map +1 -1
- package/lib/commands/export.d.ts +0 -1
- package/lib/commands/export.js +99 -32
- package/lib/commands/export.js.map +1 -1
- package/lib/commands/files.js +11 -9
- package/lib/commands/files.js.map +1 -1
- package/lib/commands/integrations.d.ts +12 -1
- package/lib/commands/integrations.js +120 -23
- package/lib/commands/integrations.js.map +1 -1
- package/lib/commands/jobs-browser.d.ts +3 -0
- package/lib/commands/jobs-browser.js +152 -0
- package/lib/commands/jobs-browser.js.map +1 -0
- package/lib/commands/jobs.d.ts +3 -14
- package/lib/commands/jobs.js +26 -18
- package/lib/commands/jobs.js.map +1 -1
- package/lib/commands/model.d.ts +3 -0
- package/lib/commands/model.js +212 -128
- package/lib/commands/model.js.map +1 -1
- package/lib/commands/open-external.d.ts +2 -0
- package/lib/commands/open-external.js +19 -0
- package/lib/commands/open-external.js.map +1 -0
- package/lib/commands/permission.js +65 -54
- package/lib/commands/permission.js.map +1 -1
- package/lib/commands/plugins.js +381 -139
- package/lib/commands/plugins.js.map +1 -1
- package/lib/commands/schedule-catalog.d.ts +12 -0
- package/lib/commands/schedule-catalog.js +41 -0
- package/lib/commands/schedule-catalog.js.map +1 -0
- package/lib/commands/schedule-history.d.ts +4 -0
- package/lib/commands/schedule-history.js +62 -0
- package/lib/commands/schedule-history.js.map +1 -0
- package/lib/commands/schedule.d.ts +12 -15
- package/lib/commands/schedule.js +220 -187
- package/lib/commands/schedule.js.map +1 -1
- package/lib/commands/session-configuration.d.ts +3 -0
- package/lib/commands/session-configuration.js +62 -28
- package/lib/commands/session-configuration.js.map +1 -1
- package/lib/commands/session.js +116 -66
- package/lib/commands/session.js.map +1 -1
- package/lib/commands/terminal.js +14 -8
- package/lib/commands/terminal.js.map +1 -1
- package/lib/definition.d.ts +115 -15
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/input/actions.d.ts +1 -1
- package/lib/input/actions.js +5 -2
- package/lib/input/actions.js.map +1 -1
- package/lib/input/composer-files.d.ts +2 -0
- package/lib/input/composer-files.js +13 -3
- package/lib/input/composer-files.js.map +1 -1
- package/lib/input/image-paste.d.ts +5 -12
- package/lib/input/image-paste.js +32 -28
- package/lib/input/image-paste.js.map +1 -1
- package/lib/input/key-test.d.ts +5 -0
- package/lib/input/key-test.js +19 -0
- package/lib/input/key-test.js.map +1 -0
- package/lib/input/keys.d.ts +2 -0
- package/lib/input/keys.js +4 -1
- package/lib/input/keys.js.map +1 -1
- package/lib/locale/index.d.ts +6 -0
- package/lib/locale/index.js +32 -0
- package/lib/locale/index.js.map +1 -0
- package/lib/locale/prompt.d.ts +4 -0
- package/lib/locale/prompt.js +24 -0
- package/lib/locale/prompt.js.map +1 -0
- package/lib/locale/zh.d.ts +1114 -0
- package/lib/locale/zh.js +1115 -0
- package/lib/locale/zh.js.map +1 -0
- package/lib/prompt-intents.d.ts +4 -0
- package/lib/prompt-intents.js +4 -0
- package/lib/prompt-intents.js.map +1 -1
- package/lib/runtime/account-settings.d.ts +18 -0
- package/lib/runtime/account-settings.js +241 -0
- package/lib/runtime/account-settings.js.map +1 -0
- package/lib/runtime/agent-profile.d.ts +3 -3
- package/lib/runtime/agent-profile.js +4 -4
- package/lib/runtime/agent-profile.js.map +1 -1
- package/lib/runtime/btw-policy.js +2 -1
- package/lib/runtime/btw-policy.js.map +1 -1
- package/lib/runtime/btw.js +1 -2
- package/lib/runtime/btw.js.map +1 -1
- package/lib/runtime/composer-images.d.ts +2 -2
- package/lib/runtime/composer-images.js +4 -4
- package/lib/runtime/composer-images.js.map +1 -1
- package/lib/runtime/composer-shell.js +27 -1
- package/lib/runtime/composer-shell.js.map +1 -1
- package/lib/runtime/file-browser.d.ts +19 -0
- package/lib/runtime/file-browser.js +291 -0
- package/lib/runtime/file-browser.js.map +1 -0
- package/lib/runtime/file-review.d.ts +6 -9
- package/lib/runtime/file-review.js +91 -7
- package/lib/runtime/file-review.js.map +1 -1
- package/lib/runtime/help-reader.d.ts +12 -0
- package/lib/runtime/help-reader.js +50 -0
- package/lib/runtime/help-reader.js.map +1 -0
- package/lib/runtime/human-interaction.js +1 -1
- package/lib/runtime/human-interaction.js.map +1 -1
- package/lib/runtime/model-catalog-editor.d.ts +12 -0
- package/lib/runtime/model-catalog-editor.js +215 -0
- package/lib/runtime/model-catalog-editor.js.map +1 -0
- package/lib/runtime/model-settings.d.ts +11 -0
- package/lib/runtime/model-settings.js +404 -0
- package/lib/runtime/model-settings.js.map +1 -0
- package/lib/runtime/plugin-settings.d.ts +4 -1
- package/lib/runtime/plugin-settings.js +164 -88
- package/lib/runtime/plugin-settings.js.map +1 -1
- package/lib/runtime/prompt-browser.d.ts +9 -0
- package/lib/runtime/prompt-browser.js +19 -0
- package/lib/runtime/prompt-browser.js.map +1 -0
- package/lib/runtime/provider-local.d.ts +27 -27
- package/lib/runtime/provider-local.js +1288 -308
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/reference-reader.d.ts +15 -0
- package/lib/runtime/reference-reader.js +78 -0
- package/lib/runtime/reference-reader.js.map +1 -0
- package/lib/runtime/runner.js +38 -29
- package/lib/runtime/runner.js.map +1 -1
- package/lib/runtime/session-runtime.js +1 -1
- package/lib/runtime/session-runtime.js.map +1 -1
- package/lib/runtime/settings-draft.d.ts +21 -0
- package/lib/runtime/settings-draft.js +166 -0
- package/lib/runtime/settings-draft.js.map +1 -0
- package/lib/runtime/settings-preferences.js +1 -1
- package/lib/runtime/settings-preferences.js.map +1 -1
- package/lib/runtime/startup-notices.d.ts +0 -1
- package/lib/runtime/startup-notices.js +35 -6
- package/lib/runtime/startup-notices.js.map +1 -1
- package/lib/runtime/subagent-model-selection.js +2 -1
- package/lib/runtime/subagent-model-selection.js.map +1 -1
- package/lib/runtime/subagent-settings.d.ts +14 -0
- package/lib/runtime/subagent-settings.js +288 -0
- package/lib/runtime/subagent-settings.js.map +1 -0
- package/lib/runtime/terminal-notifications.d.ts +3 -0
- package/lib/runtime/terminal-notifications.js +13 -8
- package/lib/runtime/terminal-notifications.js.map +1 -1
- package/lib/runtime/terminal-probe.d.ts +2 -0
- package/lib/runtime/terminal-probe.js +9 -0
- package/lib/runtime/terminal-probe.js.map +1 -1
- package/lib/runtime/tool-presentation.js.map +1 -1
- package/lib/runtime/tool-reader.d.ts +5 -0
- package/lib/runtime/tool-reader.js +27 -0
- package/lib/runtime/tool-reader.js.map +1 -0
- package/lib/runtime/web-settings.d.ts +14 -0
- package/lib/runtime/web-settings.js +339 -0
- package/lib/runtime/web-settings.js.map +1 -0
- package/lib/session/command-attachments.d.ts +17 -0
- package/lib/session/command-attachments.js +50 -0
- package/lib/session/command-attachments.js.map +1 -0
- package/lib/session/context-diagnostics.d.ts +16 -0
- package/lib/session/context-diagnostics.js +15 -0
- package/lib/session/context-diagnostics.js.map +1 -1
- package/lib/session/external-agents.d.ts +3 -1
- package/lib/session/external-agents.js +35 -10
- package/lib/session/external-agents.js.map +1 -1
- package/lib/session/generation-continuation.d.ts +11 -0
- package/lib/session/generation-continuation.js +34 -0
- package/lib/session/generation-continuation.js.map +1 -0
- package/lib/session/goal-controls.d.ts +5 -0
- package/lib/session/goal-controls.js +81 -0
- package/lib/session/goal-controls.js.map +1 -0
- package/lib/session/interaction-adapter.d.ts +7 -3
- package/lib/session/interaction-adapter.js +121 -49
- package/lib/session/interaction-adapter.js.map +1 -1
- package/lib/session/message-queue.d.ts +2 -0
- package/lib/session/message-queue.js +102 -17
- package/lib/session/message-queue.js.map +1 -1
- package/lib/session/pending-inputs.d.ts +30 -0
- package/lib/session/pending-inputs.js +183 -0
- package/lib/session/pending-inputs.js.map +1 -0
- package/lib/session/session-configuration.js +1 -1
- package/lib/session/session-configuration.js.map +1 -1
- package/lib/session/session-controller.d.ts +33 -20
- package/lib/session/session-controller.js +500 -129
- package/lib/session/session-controller.js.map +1 -1
- package/lib/session/session-library.d.ts +3 -1
- package/lib/session/session-library.js +15 -0
- package/lib/session/session-library.js.map +1 -1
- package/lib/session/subagent-roster.d.ts +1 -0
- package/lib/session/subagent-roster.js +16 -5
- package/lib/session/subagent-roster.js.map +1 -1
- package/lib/session/subagent-tracker.d.ts +2 -0
- package/lib/session/subagent-tracker.js +18 -0
- package/lib/session/subagent-tracker.js.map +1 -1
- package/lib/session/tui-settings.d.ts +9 -5
- package/lib/session/tui-settings.js +3 -1
- package/lib/session/tui-settings.js.map +1 -1
- package/lib/session/workflow-projection.d.ts +2 -1
- package/lib/session/workflow-projection.js +34 -4
- package/lib/session/workflow-projection.js.map +1 -1
- package/lib/views/autocomplete.d.ts +12 -12
- package/lib/views/autocomplete.js +54 -45
- package/lib/views/autocomplete.js.map +1 -1
- package/lib/views/context-overview.d.ts +4 -0
- package/lib/views/context-overview.js +43 -0
- package/lib/views/context-overview.js.map +1 -0
- package/lib/views/copy-targets.d.ts +6 -2
- package/lib/views/copy-targets.js +19 -14
- package/lib/views/copy-targets.js.map +1 -1
- package/lib/views/document-reader.d.ts +10 -1
- package/lib/views/document-reader.js +63 -6
- package/lib/views/document-reader.js.map +1 -1
- package/lib/views/event-views.js +29 -11
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/file-actions.js +21 -0
- package/lib/views/file-actions.js.map +1 -1
- package/lib/views/file-browser.d.ts +98 -0
- package/lib/views/file-browser.js +120 -0
- package/lib/views/file-browser.js.map +1 -0
- package/lib/views/history-search.js +4 -4
- package/lib/views/history-search.js.map +1 -1
- package/lib/views/hotkey-format.d.ts +3 -2
- package/lib/views/hotkey-format.js +4 -3
- package/lib/views/hotkey-format.js.map +1 -1
- package/lib/views/hotkeys.js +10 -12
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/list-view.js +16 -11
- package/lib/views/list-view.js.map +1 -1
- package/lib/views/prompt-selector.d.ts +23 -1
- package/lib/views/prompt-selector.js +123 -60
- package/lib/views/prompt-selector.js.map +1 -1
- package/lib/views/settings-list.d.ts +6 -1
- package/lib/views/settings-list.js +69 -46
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/tool-actions.d.ts +16 -0
- package/lib/views/tool-actions.js +29 -0
- package/lib/views/tool-actions.js.map +1 -0
- package/lib/views/trajectory-timeline.d.ts +5 -6
- package/lib/views/trajectory-timeline.js +25 -15
- package/lib/views/trajectory-timeline.js.map +1 -1
- package/lib/views/trajectory.d.ts +23 -2
- package/lib/views/trajectory.js +404 -186
- package/lib/views/trajectory.js.map +1 -1
- package/lib/views/transcript-render.d.ts +39 -15
- package/lib/views/transcript-render.js +406 -208
- package/lib/views/transcript-render.js.map +1 -1
- package/lib/views/transcript-types.d.ts +3 -2
- package/lib/views/transcript-types.js.map +1 -1
- package/lib/views/workflow.d.ts +16 -0
- package/lib/views/workflow.js +42 -0
- package/lib/views/workflow.js.map +1 -0
- package/package.json +61 -58
- package/lib/chrome/welcome-tips.d.ts +0 -23
- package/lib/chrome/welcome-tips.js +0 -24
- package/lib/chrome/welcome-tips.js.map +0 -1
- package/lib/session/steering.d.ts +0 -3
- package/lib/session/steering.js +0 -9
- package/lib/session/steering.js.map +0 -1
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { renderContextOverview } from "../views/context-overview.js";
|
|
2
|
+
import { contextOverviewDetails } from "../session/context-diagnostics.js";
|
|
3
|
+
import { browseTools } from "./tool-reader.js";
|
|
4
|
+
import { promptBrowserState } from "./prompt-browser.js";
|
|
5
|
+
import { browseHelp } from "./help-reader.js";
|
|
6
|
+
import { readReference } from "./reference-reader.js";
|
|
7
|
+
import { FileBrowser } from "./file-browser.js";
|
|
8
|
+
import { reasoningKey } from "../views/transcript-types.js";
|
|
1
9
|
/**
|
|
2
10
|
* TUI capability seam — local terminal provider.
|
|
3
11
|
|
|
@@ -10,6 +18,10 @@
|
|
|
10
18
|
* printing of settled blocks.
|
|
11
19
|
* @module @agi-fans/dsh-tui
|
|
12
20
|
*/
|
|
21
|
+
import { rosterItems } from "../views/workflow.js";
|
|
22
|
+
import { localizePrompt } from "../locale/prompt.js";
|
|
23
|
+
import { resolveUiLanguage, uiText } from "../locale/index.js";
|
|
24
|
+
import { formatAgentPreset } from "../session/session-configuration.js";
|
|
13
25
|
import { TerminalNotificationController, TerminalNotificationQueue, terminalNotificationSequence, } from "./terminal-notifications.js";
|
|
14
26
|
import { PROGRAM_STATUS_QUERY, programStatusSequence, terminalTitle } from "./terminal-state.js";
|
|
15
27
|
import { TerminalSession } from "./terminal-session.js";
|
|
@@ -20,6 +32,7 @@ import { imageProtocol } from "../chrome/terminal-images.js";
|
|
|
20
32
|
import { previewImages } from "./image-preview.js";
|
|
21
33
|
import { rmSync } from 'node:fs';
|
|
22
34
|
import { ComposerImages } from "./composer-images.js";
|
|
35
|
+
import { PendingInputs } from "../session/pending-inputs.js";
|
|
23
36
|
import { ComposerDraftStore } from "../session/composer-drafts.js";
|
|
24
37
|
import { mergeJobSettlement } from "../session/job-notice.js";
|
|
25
38
|
import { PlainTui } from "./plain-tui.js";
|
|
@@ -39,14 +52,16 @@ import { matchesAtShortcutQuery, nextSelectableAutocompleteIndex, searchAtSugges
|
|
|
39
52
|
import { readTmuxMouseEnabled } from "../input/mouse-policy.js";
|
|
40
53
|
import { TextSelection } from "../input/text-selection.js";
|
|
41
54
|
import { copyToClipboard, readFromClipboard } from "../input/clipboard.js";
|
|
42
|
-
import {
|
|
55
|
+
import { toolActionContext, toolSource, sourceQuote } from "../views/tool-actions.js";
|
|
56
|
+
import { keyTestDescription } from "../input/key-test.js";
|
|
57
|
+
import { buildCopyTargets, toolCommand } from "../views/copy-targets.js";
|
|
43
58
|
import { applyHistorySearchEvent, createHistorySearch, } from "../views/history-search.js";
|
|
44
59
|
import { InputEditor, lineEnd, lineStart } from "../input/editor.js";
|
|
45
60
|
import { applySettingsEvent, createSettings, refreshSettings, tuiSettingItems, } from "../views/settings-list.js";
|
|
46
61
|
import { editSettingsEntry, pluginSettingsEntries, resetSettingsControl, setSettingsEntry, showSettingsError } from "./plugin-settings.js";
|
|
47
62
|
import { preferenceChanges } from "./settings-preferences.js";
|
|
48
|
-
import { appendTrajectoryEvent, applyTrajectoryEvent, createTrajectory, trajectoryDetailMetrics, trajectoryListMetrics, } from "../views/trajectory.js";
|
|
49
|
-
import { applyEvent, applyStreamChunk, renderedBlocks, formatAgentElapsed,
|
|
63
|
+
import { appendTrajectoryEvent, applyTrajectoryEvent, createTrajectory, trajectoryDetailMetrics, trajectoryDetailScrollLimit, trajectoryClick, trajectoryListMetrics, } from "../views/trajectory.js";
|
|
64
|
+
import { applyEvent, applyStreamChunk, renderedBlocks, formatAgentElapsed, rosterPreviewStart, initialTranscript, replayEvents, renderView, turnGroups, transcriptSeam, TRANSCRIPT_FAST_SCROLL, withWorkspaceSummary, } from "../views/event-views.js";
|
|
50
65
|
import { applyTranscriptSearchEvent, createTranscriptSearch, searchMatches, } from "../views/transcript-search.js";
|
|
51
66
|
import { nextRevealStep, revealStreamingAssistant, streamingAssistantKey, streamingAssistantUnits, } from "../views/streaming-reveal.js";
|
|
52
67
|
import { flushPending, parseKeys } from "../input/keys.js";
|
|
@@ -55,6 +70,7 @@ import { blockFileAction } from "../views/file-actions.js";
|
|
|
55
70
|
import { fileDocument, reviewFiles } from "./file-review.js";
|
|
56
71
|
import { stripAnsi } from "../chrome/width.js";
|
|
57
72
|
import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
|
|
73
|
+
import { supportsHyperlinks } from "./terminal-probe.js";
|
|
58
74
|
import { colorDisabledByEnv, createTheme, detectColorDepth } from "../chrome/theme.js";
|
|
59
75
|
import { formatToolsText } from "../chrome/tools-list.js";
|
|
60
76
|
import { renderTool } from "../chrome/tool-renderers.js";
|
|
@@ -66,12 +82,12 @@ import { DEFAULT_KEYBINDINGS, loadKeybindings, saveKeybindings, TUI_ACTION_LABEL
|
|
|
66
82
|
import { discoverEditors } from "../input/editor-discovery.js";
|
|
67
83
|
import { editExternally, editFileExternally } from "../input/external-editor.js";
|
|
68
84
|
import z from '@deepseek-ai/schemastery';
|
|
69
|
-
import { movePromptSelection, movePromptTree, searchPromptSelection, filteredPromptOptions, refreshPromptOptions, promptTreePageSize, selectedPromptAnswer, selectedFilteredPromptAnswer, togglePromptSelection, documentRows, findInRows, } from "../views/prompt-selector.js";
|
|
85
|
+
import { movePromptSelection, movePromptTree, searchPromptSelection, filteredPromptOptions, refreshPromptOptions, promptTreePageSize, selectedPromptAnswer, structuredPromptAnswer, selectedFilteredPromptAnswer, togglePromptSelection, documentRows, findInRows, } from "../views/prompt-selector.js";
|
|
70
86
|
import { edgeIndex, listNavigation, navigateIndex } from "../views/list-navigation.js";
|
|
71
87
|
import { refreshProjectContext, resolveGitStatusFiles, resolveProjectContext } from "../session/project-context.js";
|
|
72
88
|
import { modelRouteReady } from "../session/model-readiness.js";
|
|
73
|
-
import { formatEssentialHotkeysText, formatHotkeysText,
|
|
74
|
-
import { imagePathCandidates,
|
|
89
|
+
import { formatEssentialHotkeysText, formatHotkeysText, keysForAction } from "../views/hotkeys.js";
|
|
90
|
+
import { imagePathCandidates, splitComposerAttachmentMarkers, stripComposerImageMarkers, readImageFile, readImageFromClipboard, readMacClipboardFiles, } from "../input/image-paste.js";
|
|
75
91
|
import { APP_DISPLAY_NAME, APP_NAME, APP_VERSION } from "../session/package-metadata.js";
|
|
76
92
|
const DOUBLE_CTRL_C_MS = 500;
|
|
77
93
|
/**
|
|
@@ -188,6 +204,8 @@ export class LocalTui {
|
|
|
188
204
|
#imageCellSize = { width: 9, height: 18 };
|
|
189
205
|
#imagePreviewAbort;
|
|
190
206
|
#fileReviewAbort;
|
|
207
|
+
#keyTest;
|
|
208
|
+
#mouseActionPress;
|
|
191
209
|
#fileActionFrame;
|
|
192
210
|
#imageTemporary = new Set();
|
|
193
211
|
#terminalProfile;
|
|
@@ -255,12 +273,18 @@ export class LocalTui {
|
|
|
255
273
|
#features;
|
|
256
274
|
/** Profile patch the Tools feature controls write; absent hides those controls. */
|
|
257
275
|
#featurePatch;
|
|
276
|
+
#fileBrowser = null;
|
|
258
277
|
#trajectory = null;
|
|
278
|
+
#trajectoryPress;
|
|
259
279
|
#pending = null;
|
|
260
|
-
#
|
|
280
|
+
#pendingInputs;
|
|
281
|
+
#pendingScope() { return this.#draftScope ?? '__default__'; }
|
|
282
|
+
get #queuedSubmissions() { return this.#pendingInputs.session(this.#pendingScope()).queued; }
|
|
261
283
|
/** Newer queue entries temporarily detached while Up browses backward. */
|
|
262
|
-
#queueEditNewer
|
|
284
|
+
get #queueEditNewer() { return this.#pendingInputs.session(this.#pendingScope()).newer; }
|
|
285
|
+
set #queueEditNewer(value) { this.#pendingInputs.session(this.#pendingScope()).newer = value; }
|
|
263
286
|
#queueEditPending = false;
|
|
287
|
+
#queueRecall;
|
|
264
288
|
#quitRequested = false;
|
|
265
289
|
#resumeHintRequested = false;
|
|
266
290
|
#lastSigintTime = 0;
|
|
@@ -268,13 +292,15 @@ export class LocalTui {
|
|
|
268
292
|
#inspectEscapeFenceUntil = 0;
|
|
269
293
|
#interrupts = new Set();
|
|
270
294
|
#queueEdits = new Set();
|
|
295
|
+
#continueHandler;
|
|
296
|
+
#continuing = false;
|
|
297
|
+
#goalHandler;
|
|
298
|
+
#goalAbort;
|
|
271
299
|
#queueHandler;
|
|
272
300
|
#queueHotkeyAbort;
|
|
273
|
-
#queuedInputIds = new WeakMap();
|
|
274
|
-
#queuedInputSerial = 0;
|
|
275
|
-
#steerHandler;
|
|
276
301
|
#rewinds = new Set();
|
|
277
302
|
#inspects = new Set();
|
|
303
|
+
#workflowInspects = new Set();
|
|
278
304
|
#inspectCloses = new Set();
|
|
279
305
|
#inspectSubmits = new Set();
|
|
280
306
|
#disposed = false;
|
|
@@ -285,6 +311,7 @@ export class LocalTui {
|
|
|
285
311
|
#autocompleteRequestId = 0;
|
|
286
312
|
#pasteBurst = new PasteBurst();
|
|
287
313
|
#pasteBurstTimer;
|
|
314
|
+
#busyEnter = 'queue';
|
|
288
315
|
#pasteProtection = true;
|
|
289
316
|
#paste = false;
|
|
290
317
|
#pasteBuf = '';
|
|
@@ -316,6 +343,9 @@ export class LocalTui {
|
|
|
316
343
|
#follow = true;
|
|
317
344
|
#focusBlock;
|
|
318
345
|
#focusBlockEdge;
|
|
346
|
+
#focusBlockOffset;
|
|
347
|
+
/** Reading positions last only for this inspection and this detail layout. */
|
|
348
|
+
#toolReadingPositions = new Map();
|
|
319
349
|
#motion = 'full';
|
|
320
350
|
#externalEditor = 'auto';
|
|
321
351
|
#terminalTitle = 'status';
|
|
@@ -383,9 +413,10 @@ export class LocalTui {
|
|
|
383
413
|
#mathMode = 'auto';
|
|
384
414
|
#mermaidMode = 'auto';
|
|
385
415
|
/** Transcript collapses every thought to one line; Ctrl+T flips it. */
|
|
386
|
-
#hideThinking =
|
|
416
|
+
#hideThinking = true;
|
|
387
417
|
#mouseInteraction = 'auto';
|
|
388
418
|
#tmuxMouseEnabled;
|
|
419
|
+
#disclosurePress;
|
|
389
420
|
#jumpPress;
|
|
390
421
|
#jumpToLatest;
|
|
391
422
|
#openedGroups = new Set();
|
|
@@ -395,6 +426,9 @@ export class LocalTui {
|
|
|
395
426
|
#asideAbort;
|
|
396
427
|
/** Thoughts a reader opened individually, addressed by reasoning identity. */
|
|
397
428
|
#expandedReasoning = new Set();
|
|
429
|
+
#expandedToolRows = new Set();
|
|
430
|
+
#processSelection;
|
|
431
|
+
#closedReasoning = new Set();
|
|
398
432
|
/**
|
|
399
433
|
* The last action's confirmation, drawn on the composer's bottom border.
|
|
400
434
|
*
|
|
@@ -405,6 +439,8 @@ export class LocalTui {
|
|
|
405
439
|
*/
|
|
406
440
|
#transientStatus;
|
|
407
441
|
#transientTimer = null;
|
|
442
|
+
/** Idle submissions also pass through the inbox, but are not queue gestures. */
|
|
443
|
+
#directInputIds = new Set();
|
|
408
444
|
/** Aborts the live Shell card, so Ctrl+C interrupts the command it belongs to. */
|
|
409
445
|
#shellAbort;
|
|
410
446
|
/** Turn scopes whose tools stay detailed as new calls arrive during inspection. */
|
|
@@ -437,6 +473,10 @@ export class LocalTui {
|
|
|
437
473
|
*/
|
|
438
474
|
#subagentSelection;
|
|
439
475
|
#inspected;
|
|
476
|
+
#promptActions = [];
|
|
477
|
+
#promptActionPress;
|
|
478
|
+
#promptChoices = [];
|
|
479
|
+
#promptChoicePress;
|
|
440
480
|
#promptDocument;
|
|
441
481
|
/**
|
|
442
482
|
* 24-bit switch for the current color preference, re-derived whenever that
|
|
@@ -444,6 +484,7 @@ export class LocalTui {
|
|
|
444
484
|
* hexadecimal palettes without a restart.
|
|
445
485
|
*/
|
|
446
486
|
#colorDepth = 'ansi16';
|
|
487
|
+
#compactLinks = supportsHyperlinks(process.env);
|
|
447
488
|
/** Hold the first frame until the capability probe settles; scrollback rows are immutable, so the first real paint must use the reported colors. */
|
|
448
489
|
#probeGate = false;
|
|
449
490
|
#copy;
|
|
@@ -468,6 +509,7 @@ export class LocalTui {
|
|
|
468
509
|
#prefsSaveTask;
|
|
469
510
|
#disposeTask;
|
|
470
511
|
#prefsVersion = 0;
|
|
512
|
+
#uiLanguage = 'auto';
|
|
471
513
|
#agentBehavior;
|
|
472
514
|
#updateAgentBehavior;
|
|
473
515
|
#offAgentBehaviorWatch;
|
|
@@ -527,6 +569,7 @@ export class LocalTui {
|
|
|
527
569
|
this.#syncColorDepth();
|
|
528
570
|
this.#tty = term.input.isTTY === true && term.output.isTTY === true;
|
|
529
571
|
this.#draftStore = this.#tty && paths.draftsPath !== undefined ? new ComposerDraftStore(paths.draftsPath) : undefined;
|
|
572
|
+
this.#pendingInputs = new PendingInputs(this.#tty && paths.draftsPath !== undefined ? join(paths.draftsPath, 'pending') : undefined, (action, error) => this.notice(`Could not ${action} pending input: ${error instanceof Error ? error.message : String(error)}`, { level: 'error' }));
|
|
530
573
|
this.#plain = new PlainTui({
|
|
531
574
|
term,
|
|
532
575
|
blocks: () => this.#state.blocks,
|
|
@@ -554,7 +597,9 @@ export class LocalTui {
|
|
|
554
597
|
// A terminal resize changes the committed/live seam; re-anchor the
|
|
555
598
|
// live window so it stays anchored at the bottom.
|
|
556
599
|
this.#selection.clear();
|
|
600
|
+
this.#mouseActionPress = undefined;
|
|
557
601
|
this.#jumpPress = undefined;
|
|
602
|
+
this.#disclosurePress = undefined;
|
|
558
603
|
const repaint = () => {
|
|
559
604
|
this.#resizeTimer = null;
|
|
560
605
|
this.#renderer.resize(this.#term.width(), this.#term.height());
|
|
@@ -637,7 +682,27 @@ export class LocalTui {
|
|
|
637
682
|
this.#retryDeadline = Date.now() + event.data.delayMs;
|
|
638
683
|
if (event.type === 'llm/retry-started' || event.type === 'turn/end')
|
|
639
684
|
this.#retryDeadline = undefined;
|
|
685
|
+
let claimedHumanInput = false;
|
|
686
|
+
if (event.type === 'agent/inbox/spliced') {
|
|
687
|
+
const inbox = event.data.target === 'next-step' ? this.#state.nextStepInbox : this.#state.nextTurnInbox;
|
|
688
|
+
const removed = inbox.slice(event.data.start, event.data.start + (event.data.removedCount ?? 0));
|
|
689
|
+
claimedHumanInput = event.data.outcome !== 'canceled' && event.data.inserted.length === 0
|
|
690
|
+
&& removed.some(message => message.source.kind === 'user' && !this.#directInputIds.has(message.id));
|
|
691
|
+
for (const message of removed)
|
|
692
|
+
this.#directInputIds.delete(message.id);
|
|
693
|
+
if (event.data.target === 'next-turn' && this.#state.status === 'idle'
|
|
694
|
+
&& !inbox.some(message => message.source.kind === 'user')) {
|
|
695
|
+
const first = event.data.inserted.find(message => message.source.kind === 'user');
|
|
696
|
+
if (first !== undefined)
|
|
697
|
+
this.#directInputIds.add(first.id);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else if (event.type === 'session/end-seed')
|
|
701
|
+
this.#directInputIds.clear();
|
|
640
702
|
this.#state = applyEvent(this.#state, event, presentation, workspace);
|
|
703
|
+
if (claimedHumanInput && this.#tty && event.type === 'agent/inbox/spliced') {
|
|
704
|
+
this.#showTransientStatus(event.data.target === 'next-step' ? 'Steer taken for the next step' : 'Queue taken for the next turn');
|
|
705
|
+
}
|
|
641
706
|
const compacting = this.#state.status === 'compacting';
|
|
642
707
|
if (compacting && this.#compactionStartedAt === undefined)
|
|
643
708
|
this.#compactionStartedAt = Date.now();
|
|
@@ -749,12 +814,24 @@ export class LocalTui {
|
|
|
749
814
|
const statusChanged = previous.length !== incoming.length || incoming.some((agent, index) => {
|
|
750
815
|
const before = previous[index];
|
|
751
816
|
return before === undefined || before.id !== agent.id || before.label !== agent.label
|
|
752
|
-
|| before.phase !== agent.phase || before.depth !== agent.depth || before.mode !== agent.mode;
|
|
817
|
+
|| before.phase !== agent.phase || before.needsApproval !== agent.needsApproval || before.depth !== agent.depth || before.mode !== agent.mode;
|
|
753
818
|
});
|
|
819
|
+
const previousItems = rosterItems(this.#subagents);
|
|
820
|
+
const incomingItems = rosterItems(roster);
|
|
821
|
+
const workflowsChanged = (this.#subagents?.workflows?.length ?? 0) !== (roster?.workflows?.length ?? 0)
|
|
822
|
+
|| (roster?.workflows ?? []).some((run, index) => {
|
|
823
|
+
const before = this.#subagents?.workflows?.[index];
|
|
824
|
+
return before?.id !== run.id || before.name !== run.name || before.status !== run.status || before.phase !== run.phase || before.members !== run.members;
|
|
825
|
+
});
|
|
826
|
+
const selectedId = this.#subagentSelection === undefined ? undefined : previousItems[this.#subagentSelection]?.key;
|
|
754
827
|
this.#subagents = roster === undefined
|
|
755
828
|
? undefined
|
|
756
|
-
: { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })) };
|
|
757
|
-
if (
|
|
829
|
+
: { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })), ...(roster.workflows === undefined ? {} : { workflows: [...roster.workflows] }) };
|
|
830
|
+
if (selectedId !== undefined) {
|
|
831
|
+
const index = incomingItems.findIndex(item => item.key === selectedId);
|
|
832
|
+
this.#subagentSelection = index < 0 ? undefined : index;
|
|
833
|
+
}
|
|
834
|
+
if (incomingItems.length === 0)
|
|
758
835
|
this.#subagentSelection = undefined;
|
|
759
836
|
const inspected = this.#inspected;
|
|
760
837
|
if (inspected !== undefined) {
|
|
@@ -764,12 +841,13 @@ export class LocalTui {
|
|
|
764
841
|
id: current.id,
|
|
765
842
|
label: current.label,
|
|
766
843
|
phase: current.phase,
|
|
844
|
+
needsApproval: current.needsApproval === true,
|
|
767
845
|
...(current.mode === undefined ? {} : { mode: current.mode }),
|
|
768
846
|
writable: current.mode === 'continuable',
|
|
769
847
|
};
|
|
770
848
|
}
|
|
771
849
|
}
|
|
772
|
-
if (!statusChanged)
|
|
850
|
+
if (!statusChanged && !workflowsChanged)
|
|
773
851
|
return;
|
|
774
852
|
this.#syncTick();
|
|
775
853
|
if (this.#tty)
|
|
@@ -777,11 +855,16 @@ export class LocalTui {
|
|
|
777
855
|
}
|
|
778
856
|
setInspectedSubagent(inspected) {
|
|
779
857
|
if (this.#inspected?.id === inspected?.id && this.#inspected?.label === inspected?.label
|
|
780
|
-
&& this.#inspected?.phase === inspected?.phase && this.#inspected?.mode === inspected?.mode
|
|
858
|
+
&& this.#inspected?.phase === inspected?.phase && this.#inspected?.needsApproval === inspected?.needsApproval && this.#inspected?.mode === inspected?.mode
|
|
781
859
|
&& this.#inspected?.writable === inspected?.writable)
|
|
782
860
|
return;
|
|
861
|
+
if (this.#inspected?.id !== inspected?.id) {
|
|
862
|
+
this.#fileReviewAbort?.abort();
|
|
863
|
+
this.#closeFileBrowser();
|
|
864
|
+
}
|
|
783
865
|
this.#switchDraft(inspected?.id ?? this.#sessionId);
|
|
784
866
|
this.#inspected = inspected === undefined ? undefined : { ...inspected };
|
|
867
|
+
this.#drainPendingInput();
|
|
785
868
|
if (inspected !== undefined) {
|
|
786
869
|
this.#inspectEscapeFenceUntil = 0;
|
|
787
870
|
this.#subagentSelection = undefined;
|
|
@@ -794,20 +877,54 @@ export class LocalTui {
|
|
|
794
877
|
this.#tools = tools.map((tool) => ({ name: tool.name, description: tool.description }));
|
|
795
878
|
}
|
|
796
879
|
setCommands(commands) {
|
|
797
|
-
this.#runtimeCommands = commands.map(
|
|
798
|
-
name: command.name,
|
|
799
|
-
description: command.description,
|
|
800
|
-
...(command.inputHint === undefined ? {} : { inputHint: command.inputHint }),
|
|
801
|
-
}));
|
|
880
|
+
this.#runtimeCommands = commands.map(command => ({ ...command }));
|
|
802
881
|
this.#syncSettingsActions();
|
|
803
882
|
this.#refreshAutocomplete();
|
|
804
883
|
if (this.#tty)
|
|
805
884
|
this.#render();
|
|
806
885
|
}
|
|
886
|
+
async reviewFiles(request) {
|
|
887
|
+
if (!this.#tty || this.#disposed || request.signal.aborted)
|
|
888
|
+
return;
|
|
889
|
+
this.#flushPasteBurst(true);
|
|
890
|
+
this.#pasteBurst.clear();
|
|
891
|
+
this.#finishPrompt(null);
|
|
892
|
+
this.#closeFileBrowser();
|
|
893
|
+
this.#selection.clear();
|
|
894
|
+
this.#trajectory = null;
|
|
895
|
+
this.#settings = null;
|
|
896
|
+
this.#search = null;
|
|
897
|
+
this.#ac = null;
|
|
898
|
+
const browser = new FileBrowser(request, () => { if (!this.#disposed)
|
|
899
|
+
this.#render(); }, () => {
|
|
900
|
+
if (this.#fileBrowser === browser)
|
|
901
|
+
this.#fileBrowser = null;
|
|
902
|
+
if (this.#promptDisplaced?.kind === 'fileBrowser' && this.#promptDisplaced.browser === browser)
|
|
903
|
+
this.#promptDisplaced = null;
|
|
904
|
+
this.#selection.clear();
|
|
905
|
+
this.#trajectoryPress = undefined;
|
|
906
|
+
if (!this.#disposed)
|
|
907
|
+
this.#render();
|
|
908
|
+
});
|
|
909
|
+
this.#fileBrowser = browser;
|
|
910
|
+
browser.start();
|
|
911
|
+
await browser.done;
|
|
912
|
+
}
|
|
913
|
+
#closeFileBrowser() {
|
|
914
|
+
this.#fileBrowser?.close();
|
|
915
|
+
if (this.#promptDisplaced?.kind === 'fileBrowser')
|
|
916
|
+
this.#promptDisplaced.browser.close();
|
|
917
|
+
}
|
|
918
|
+
#applyFileBrowserInput(event) {
|
|
919
|
+
this.#fileBrowser?.input(event, createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks), this.#term.width(), this.#term.height());
|
|
920
|
+
}
|
|
807
921
|
openTrajectory(events) {
|
|
808
922
|
if (!this.#tty)
|
|
809
923
|
return false;
|
|
810
924
|
this.#finishPrompt(null);
|
|
925
|
+
this.#selection.clear();
|
|
926
|
+
this.#trajectoryPress = undefined;
|
|
927
|
+
this.#closeFileBrowser();
|
|
811
928
|
this.#trajectory = createTrajectory(events);
|
|
812
929
|
this.#settings = null;
|
|
813
930
|
this.#search = null;
|
|
@@ -848,6 +965,11 @@ export class LocalTui {
|
|
|
848
965
|
.join('\n');
|
|
849
966
|
}
|
|
850
967
|
notice(text, options = {}) {
|
|
968
|
+
if (options.transient) {
|
|
969
|
+
if (this.#tty && !this.#disposed)
|
|
970
|
+
this.#showTransientStatus(text);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
851
973
|
const merged = options.job === undefined ? undefined : mergeJobSettlement(this.#state, options.job);
|
|
852
974
|
if (merged !== undefined && this.#tty) {
|
|
853
975
|
const origin = merged.blocks.find((block, index) => block !== this.#state.blocks[index]);
|
|
@@ -946,7 +1068,6 @@ export class LocalTui {
|
|
|
946
1068
|
output: result.output === '' ? current.output : result.output,
|
|
947
1069
|
status: failed ? 'error' : 'ok',
|
|
948
1070
|
...(result.code === undefined ? {} : { code: result.code }),
|
|
949
|
-
...(request.share === true ? { shared: true } : {}),
|
|
950
1071
|
}));
|
|
951
1072
|
paint();
|
|
952
1073
|
return result;
|
|
@@ -1080,7 +1201,24 @@ export class LocalTui {
|
|
|
1080
1201
|
next[at] = update(next[at]);
|
|
1081
1202
|
this.#state = { ...this.#state, blocks: next };
|
|
1082
1203
|
}
|
|
1204
|
+
openContext(request) {
|
|
1205
|
+
let snapshot = request.read();
|
|
1206
|
+
return this.#promptWithDocument({
|
|
1207
|
+
localize: ['title', 'options'], title: 'Context', question: '',
|
|
1208
|
+
detail: contextOverviewDetails(snapshot), presentation: 'document', allowCustom: false, notify: false,
|
|
1209
|
+
options: [{ label: 'Details', value: 'details' }, { label: 'Copy session ID', value: 'copy' },
|
|
1210
|
+
...(request.canCompact ? [{ label: 'Compact', value: 'compact' }] : [])],
|
|
1211
|
+
refreshDocument: () => { snapshot = request.read(); return contextOverviewDetails(snapshot); },
|
|
1212
|
+
signal: request.signal, onSuperseded: request.onSuperseded,
|
|
1213
|
+
}, (theme, width) => renderContextOverview(snapshot, theme, width));
|
|
1214
|
+
}
|
|
1083
1215
|
prompt(request) {
|
|
1216
|
+
return this.#promptWithDocument(request);
|
|
1217
|
+
}
|
|
1218
|
+
#promptWithDocument(request, renderDocument) {
|
|
1219
|
+
const sourceRequest = request;
|
|
1220
|
+
const browserState = promptBrowserState(request);
|
|
1221
|
+
request = localizePrompt(request, resolveUiLanguage(this.#uiLanguage));
|
|
1084
1222
|
// Background approvals take the terminal from a user-opened file reader.
|
|
1085
1223
|
if (this.#fileReviewAbort !== undefined && request.signal !== this.#fileReviewAbort.signal)
|
|
1086
1224
|
this.#fileReviewAbort.abort();
|
|
@@ -1108,13 +1246,22 @@ export class LocalTui {
|
|
|
1108
1246
|
}
|
|
1109
1247
|
if (this.#tty && request.documentImage !== undefined && this.#imageProtocol !== undefined)
|
|
1110
1248
|
this.#term.output.write('\x1b[16t');
|
|
1249
|
+
this.#selection.clear();
|
|
1250
|
+
this.#promptActionPress = undefined;
|
|
1251
|
+
this.#promptChoicePress = undefined;
|
|
1252
|
+
this.#disclosurePress = undefined;
|
|
1253
|
+
this.#mouseActionPress = undefined;
|
|
1254
|
+
this.#jumpPress = undefined;
|
|
1255
|
+
this.#wheelGesture = IDLE_WHEEL;
|
|
1111
1256
|
const savedInput = this.#editor.snapshot();
|
|
1112
|
-
this.#editor.setText(request.
|
|
1257
|
+
this.#editor.setText(request.filterable === true ? browserState?.query ?? ''
|
|
1258
|
+
: request.allowCustom === false || (request.options?.length && request.initialInput === undefined) ? '' : request.initialInput ?? '');
|
|
1113
1259
|
this.#ac = null;
|
|
1114
1260
|
const displaced = this.#displaceSurface();
|
|
1115
1261
|
return new Promise((resolve) => {
|
|
1116
|
-
const selected = Math.max(0, filteredPromptOptions(request,
|
|
1117
|
-
const pending = { request, selected, checked: new Set((request.options ?? []).flatMap((option, index) => request.initialValues?.includes(option.value ?? option.label) ? [index] : [])), resolve, savedInput,
|
|
1262
|
+
const selected = Math.max(0, filteredPromptOptions(request, this.#editor.text).findIndex(option => (option.value ?? option.label) === (browserState?.selected ?? request.initialValue)));
|
|
1263
|
+
const pending = { sourceRequest, request, ...(renderDocument === undefined ? {} : { renderDocument }), selected, checked: new Set((request.options ?? []).flatMap((option, index) => request.initialValues?.includes(option.value ?? option.label) ? [index] : [])), resolve, savedInput,
|
|
1264
|
+
...(browserState === undefined ? {} : { browserState }),
|
|
1118
1265
|
...(request.armMs !== undefined && request.armMs > 0 ? { armedUntil: Date.now() + request.armMs } : {}),
|
|
1119
1266
|
...(request.documentPosition === undefined ? {} : { documentAnchor: request.documentPosition, documentQuery: request.documentPosition.query,
|
|
1120
1267
|
documentWordWrap: request.documentPosition.wordWrap, documentColumn: request.documentPosition.column }),
|
|
@@ -1137,7 +1284,7 @@ export class LocalTui {
|
|
|
1137
1284
|
}
|
|
1138
1285
|
if (this.#tty && (request.refreshDocument !== undefined || request.refreshDocumentSource !== undefined || request.refreshOptions !== undefined)) {
|
|
1139
1286
|
const tick = setInterval(() => {
|
|
1140
|
-
if (this.#disposed || this.#prompt === null)
|
|
1287
|
+
if (this.#disposed || this.#prompt === null || this.#selection.active)
|
|
1141
1288
|
return;
|
|
1142
1289
|
try {
|
|
1143
1290
|
const detail = request.refreshDocument?.();
|
|
@@ -1147,16 +1294,17 @@ export class LocalTui {
|
|
|
1147
1294
|
if (source !== undefined && source !== this.#prompt.request.documentSource) {
|
|
1148
1295
|
const shift = (this.#prompt.request.documentSource?.firstLine ?? 1) - (source.firstLine ?? 1);
|
|
1149
1296
|
const anchor = this.#prompt.documentAnchor;
|
|
1150
|
-
this.#prompt = { ...this.#prompt, request: { ...this.#prompt.request, documentSource: source },
|
|
1297
|
+
this.#prompt = { ...this.#prompt, sourceRequest: { ...this.#prompt.sourceRequest, documentSource: source }, request: { ...this.#prompt.request, documentSource: source },
|
|
1151
1298
|
...(anchor === undefined ? {} : { documentAnchor: { ...anchor, row: Math.max(0, anchor.row + shift) } }) };
|
|
1152
1299
|
changed = true;
|
|
1153
1300
|
}
|
|
1154
|
-
if (options !== undefined && options !== this.#prompt.
|
|
1155
|
-
this.#prompt =
|
|
1301
|
+
if (options !== undefined && options !== this.#prompt.sourceRequest.options) {
|
|
1302
|
+
this.#prompt.sourceRequest = { ...this.#prompt.sourceRequest, options };
|
|
1303
|
+
this.#prompt = refreshPromptOptions(this.#prompt, localizePrompt(this.#prompt.sourceRequest, resolveUiLanguage(this.#uiLanguage)).options, this.#editor.text);
|
|
1156
1304
|
changed = true;
|
|
1157
1305
|
}
|
|
1158
1306
|
if (detail !== undefined && detail !== this.#prompt.request.detail) {
|
|
1159
|
-
this.#prompt = { ...this.#prompt, request: { ...this.#prompt.request, detail } };
|
|
1307
|
+
this.#prompt = { ...this.#prompt, sourceRequest: { ...this.#prompt.sourceRequest, detail }, request: { ...this.#prompt.request, detail } };
|
|
1160
1308
|
changed = true;
|
|
1161
1309
|
}
|
|
1162
1310
|
if (changed)
|
|
@@ -1193,10 +1341,21 @@ export class LocalTui {
|
|
|
1193
1341
|
});
|
|
1194
1342
|
}
|
|
1195
1343
|
replaceSession(events, presentations, status = 'idle', reason = 'open') {
|
|
1196
|
-
if (reason !== 'refresh')
|
|
1344
|
+
if (reason !== 'refresh') {
|
|
1197
1345
|
this.#fileReviewAbort?.abort();
|
|
1346
|
+
this.#closeFileBrowser();
|
|
1347
|
+
this.#clearTransientStatus();
|
|
1348
|
+
}
|
|
1198
1349
|
this.#trajectory = null;
|
|
1199
1350
|
const state = replayEvents(events, presentations);
|
|
1351
|
+
if (reason === 'refresh') {
|
|
1352
|
+
const pendingIds = new Set(state.nextTurnInbox.map(message => message.id));
|
|
1353
|
+
for (const id of this.#directInputIds)
|
|
1354
|
+
if (!pendingIds.has(id))
|
|
1355
|
+
this.#directInputIds.delete(id);
|
|
1356
|
+
}
|
|
1357
|
+
else
|
|
1358
|
+
this.#directInputIds.clear();
|
|
1200
1359
|
// A notice does not turn a blank session into a document that /new needs
|
|
1201
1360
|
// to separate. Refreshes are the same document.
|
|
1202
1361
|
const boundary = reason === 'open' || (reason === 'new' && this.#state.blocks.some(block => block.kind !== 'notice'));
|
|
@@ -1212,10 +1371,13 @@ export class LocalTui {
|
|
|
1212
1371
|
this.#plain.print();
|
|
1213
1372
|
}
|
|
1214
1373
|
setSession(info) {
|
|
1215
|
-
if (info.id !== this.#sessionId)
|
|
1374
|
+
if (info.id !== this.#sessionId) {
|
|
1216
1375
|
this.#fileReviewAbort?.abort();
|
|
1376
|
+
this.#closeFileBrowser();
|
|
1377
|
+
}
|
|
1217
1378
|
this.#switchDraft(this.#inspected?.id ?? info.id);
|
|
1218
1379
|
this.#sessionId = info.id;
|
|
1380
|
+
this.#drainPendingInput();
|
|
1219
1381
|
this.#herdr.setSession(info.id);
|
|
1220
1382
|
const title = info.title?.trim();
|
|
1221
1383
|
this.#sessionTitle = title === undefined || title === '' ? undefined : title;
|
|
@@ -1287,6 +1449,7 @@ export class LocalTui {
|
|
|
1287
1449
|
/** Currently applied preferences; sibling rows read these instead of the config references. */
|
|
1288
1450
|
prefs() {
|
|
1289
1451
|
return {
|
|
1452
|
+
uiLanguage: this.#uiLanguage,
|
|
1290
1453
|
theme: this.#themeName,
|
|
1291
1454
|
colors: this.#colors,
|
|
1292
1455
|
motion: this.#motion,
|
|
@@ -1298,6 +1461,7 @@ export class LocalTui {
|
|
|
1298
1461
|
terminalState: this.#terminalState,
|
|
1299
1462
|
copyOnSelect: this.#copyOnSelect,
|
|
1300
1463
|
mouseInteraction: this.#mouseInteraction,
|
|
1464
|
+
busyEnter: this.#busyEnter,
|
|
1301
1465
|
pasteProtection: this.#pasteProtection,
|
|
1302
1466
|
checkUpdates: this.#checkUpdates,
|
|
1303
1467
|
startupChangelog: this.#startupChangelog,
|
|
@@ -1350,6 +1514,26 @@ export class LocalTui {
|
|
|
1350
1514
|
this.#render();
|
|
1351
1515
|
};
|
|
1352
1516
|
}
|
|
1517
|
+
#takeQueuedInput() {
|
|
1518
|
+
const scope = this.#sessionId ?? '__default__';
|
|
1519
|
+
const inputs = this.#pendingInputs.session(scope).queued;
|
|
1520
|
+
const index = inputs.findIndex(item => !this.#pendingInputs.receipt(item)?.recovered);
|
|
1521
|
+
const queued = index < 0 ? undefined : inputs.splice(index, 1)[0];
|
|
1522
|
+
if (queued !== undefined)
|
|
1523
|
+
this.#pendingInputs.take(queued, scope);
|
|
1524
|
+
return queued;
|
|
1525
|
+
}
|
|
1526
|
+
#drainPendingInput() {
|
|
1527
|
+
if (this.#pending === null || this.#disposed)
|
|
1528
|
+
return;
|
|
1529
|
+
const queued = this.#takeQueuedInput();
|
|
1530
|
+
if (queued === undefined)
|
|
1531
|
+
return;
|
|
1532
|
+
const pending = this.#pending;
|
|
1533
|
+
this.#pending = null;
|
|
1534
|
+
pending.offAbort?.();
|
|
1535
|
+
pending.resolve(queued);
|
|
1536
|
+
}
|
|
1353
1537
|
readInput(signal) {
|
|
1354
1538
|
if (this.#pending !== null)
|
|
1355
1539
|
return Promise.reject(new Error('omdsh-tui: input read already in flight'));
|
|
@@ -1365,7 +1549,7 @@ export class LocalTui {
|
|
|
1365
1549
|
return this.#plain.readline(signal);
|
|
1366
1550
|
// Lines submitted while a turn was still running were queued instead of
|
|
1367
1551
|
// dropped; serve the oldest before waiting for fresh input.
|
|
1368
|
-
const queued = this.#
|
|
1552
|
+
const queued = this.#takeQueuedInput();
|
|
1369
1553
|
if (queued !== undefined) {
|
|
1370
1554
|
this.#render();
|
|
1371
1555
|
return Promise.resolve(queued);
|
|
@@ -1387,20 +1571,83 @@ export class LocalTui {
|
|
|
1387
1571
|
}
|
|
1388
1572
|
/** Compatibility helper for provider-level text editing tests and embedders. */
|
|
1389
1573
|
async readline() {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1574
|
+
const submission = await this.readInput();
|
|
1575
|
+
if (submission !== null)
|
|
1576
|
+
this.#pendingInputs.finish(submission);
|
|
1577
|
+
return submission?.text ?? null;
|
|
1578
|
+
}
|
|
1579
|
+
restoreInput(submission, sessionId) {
|
|
1580
|
+
const origin = this.#pendingInputs.receipt(submission)?.sessionId;
|
|
1581
|
+
if (origin !== '__default__')
|
|
1582
|
+
sessionId ??= origin;
|
|
1583
|
+
if (sessionId !== undefined && sessionId !== this.#draftScope || this.#prompt !== null || this.#disposed) {
|
|
1584
|
+
const target = sessionId ?? this.#draftScope;
|
|
1585
|
+
const current = target === this.#draftScope;
|
|
1586
|
+
const editor = new InputEditor();
|
|
1587
|
+
const images = new ComposerImages({ editor, notice: text => this.notice(text) });
|
|
1588
|
+
const files = new ComposerFiles({ text: () => editor.text,
|
|
1589
|
+
append: marker => { editor.setCursor(editor.text.length); editor.handle({ type: 'text', value: (editor.text === '' ? '' : ' ') + marker }); } });
|
|
1590
|
+
let existing = current ? this.#captureDraft() : target === undefined ? undefined : this.#unsavedDrafts.get(target);
|
|
1591
|
+
try {
|
|
1592
|
+
if (target !== undefined)
|
|
1593
|
+
existing ??= this.#draftStore?.load(target);
|
|
1594
|
+
}
|
|
1595
|
+
catch (error) {
|
|
1596
|
+
this.#reportDraftError('restore', error);
|
|
1597
|
+
}
|
|
1598
|
+
if (existing !== undefined) {
|
|
1599
|
+
images.replace({ text: existing.editor.text, images: existing.images });
|
|
1600
|
+
editor.restore(existing.editor);
|
|
1601
|
+
files.restoreSnapshot(existing.files);
|
|
1602
|
+
}
|
|
1603
|
+
images.restore(submission, files.adopt(submission.files ?? []));
|
|
1604
|
+
const draft = { editor: editor.snapshot(), images: images.copies(), files: files.snapshot(editor.text), literal: existing?.literal === true || submission.literal === true };
|
|
1605
|
+
if (current && this.#prompt !== null) {
|
|
1606
|
+
const answer = this.#editor.snapshot();
|
|
1607
|
+
this.#restoreDraft(draft);
|
|
1608
|
+
this.#prompt.savedInput = draft.editor;
|
|
1609
|
+
this.#editor.restore(answer);
|
|
1610
|
+
this.#draftDirty = true;
|
|
1611
|
+
this.#flushDraft();
|
|
1612
|
+
if (this.#draftStore === undefined || !this.#unsavedDrafts.has(this.#pendingScope()))
|
|
1613
|
+
this.#pendingInputs.finish(submission);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
if (target === undefined)
|
|
1617
|
+
return;
|
|
1618
|
+
this.#unsavedDrafts.set(target, draft);
|
|
1619
|
+
try {
|
|
1620
|
+
if (this.#draftStore !== undefined) {
|
|
1621
|
+
this.#draftStore.save(target, draft);
|
|
1622
|
+
this.#unsavedDrafts.delete(target);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
this.#reportDraftError('save', error);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
this.#pendingInputs.finish(submission);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1393
1632
|
this.#literalInput ||= submission.literal === true;
|
|
1394
|
-
this.#images.restore(submission);
|
|
1395
|
-
|
|
1396
|
-
|
|
1633
|
+
this.#images.restore(submission, this.#files.adopt(submission.files ?? []));
|
|
1634
|
+
this.#draftDirty = true;
|
|
1635
|
+
this.#flushDraft();
|
|
1636
|
+
if (this.#draftStore === undefined || !this.#unsavedDrafts.has(this.#pendingScope()))
|
|
1637
|
+
this.#pendingInputs.finish(submission);
|
|
1397
1638
|
this.#refreshAutocomplete();
|
|
1398
1639
|
if (this.#tty)
|
|
1399
1640
|
this.#render();
|
|
1400
1641
|
}
|
|
1401
|
-
resolveQueueEdit(submission) {
|
|
1642
|
+
resolveQueueEdit(submission, request) {
|
|
1643
|
+
if (request !== undefined && (request !== this.#queueRecall?.request || request.signal.aborted)) {
|
|
1644
|
+
if (submission !== null)
|
|
1645
|
+
this.restoreInput(submission, request.sessionId);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1402
1648
|
if (!this.#queueEditPending)
|
|
1403
1649
|
return;
|
|
1650
|
+
this.#queueRecall = undefined;
|
|
1404
1651
|
this.#queueEditPending = false;
|
|
1405
1652
|
if (submission === null) {
|
|
1406
1653
|
if (this.#editor.text === '' && this.#images.count === 0 && this.#queueEditNewer?.length === 0) {
|
|
@@ -1410,25 +1657,21 @@ export class LocalTui {
|
|
|
1410
1657
|
}
|
|
1411
1658
|
if (this.#queueEditNewer === null)
|
|
1412
1659
|
this.#queueEditNewer = [];
|
|
1413
|
-
if (this.#
|
|
1660
|
+
if (this.#hasComposerContent()) {
|
|
1414
1661
|
this.#queueEditNewer.unshift(this.#currentSubmission());
|
|
1415
1662
|
}
|
|
1416
1663
|
this.#replaceInput(submission);
|
|
1417
1664
|
}
|
|
1418
1665
|
#currentSubmission() {
|
|
1419
|
-
|
|
1420
|
-
return {
|
|
1421
|
-
text: this.#files.text(this.#editor.expandedText),
|
|
1422
|
-
...(this.#literalInput || this.#editor.startsWithPaste ? { literal: true } : {}),
|
|
1423
|
-
images: this.#images.copies(),
|
|
1424
|
-
...(files.length === 0 ? {} : { files }),
|
|
1425
|
-
};
|
|
1666
|
+
return this.#buildSubmission(this.#editor.expandedText).submission;
|
|
1426
1667
|
}
|
|
1427
1668
|
#replaceInput(submission) {
|
|
1428
1669
|
this.#literalInput = submission.literal === true;
|
|
1429
|
-
this.#
|
|
1430
|
-
this.#files.
|
|
1670
|
+
this.#files.clear();
|
|
1671
|
+
this.#images.replace(submission, this.#files.adopt(submission.files ?? []));
|
|
1431
1672
|
this.#historyIndex = 0;
|
|
1673
|
+
this.#draftDirty = true;
|
|
1674
|
+
this.#flushDraft();
|
|
1432
1675
|
this.#refreshAutocomplete();
|
|
1433
1676
|
if (this.#tty)
|
|
1434
1677
|
this.#render();
|
|
@@ -1438,30 +1681,39 @@ export class LocalTui {
|
|
|
1438
1681
|
return () => { this.#interrupts.delete(listener); };
|
|
1439
1682
|
}
|
|
1440
1683
|
pendingInputs = {
|
|
1684
|
+
receipt: submission => this.#pendingInputs.receipt(submission),
|
|
1685
|
+
prepare: (submission, messageId) => this.#pendingInputs.prepare(submission, messageId),
|
|
1686
|
+
acknowledge: submission => { this.#pendingInputs.finish(submission); if (this.#tty && !this.#disposed)
|
|
1687
|
+
this.#render(); },
|
|
1688
|
+
reconcile: (sessionId, accepted) => this.#pendingInputs.reconcile(sessionId, accepted),
|
|
1441
1689
|
list: () => {
|
|
1442
1690
|
if (this.#disposed)
|
|
1443
1691
|
return [];
|
|
1444
1692
|
if (this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1445
1693
|
throw new Error('Finish editing the queued follow-up with Enter before opening Queue.');
|
|
1446
1694
|
return this.#queuedSubmissions.map(submission => {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
id = `local-${++this.#queuedInputSerial}`;
|
|
1450
|
-
this.#queuedInputIds.set(submission, id);
|
|
1451
|
-
}
|
|
1452
|
-
return { id, submission };
|
|
1695
|
+
const receipt = this.#pendingInputs.identify(submission, this.#pendingScope());
|
|
1696
|
+
return { id: receipt.id, submission, ...(receipt.recovered ? { recovered: true } : {}) };
|
|
1453
1697
|
});
|
|
1454
1698
|
},
|
|
1455
1699
|
update: (id, change) => {
|
|
1456
1700
|
if (this.#disposed)
|
|
1457
1701
|
return false;
|
|
1458
|
-
const index = this.#queuedSubmissions.findIndex(item => this.#
|
|
1702
|
+
const index = this.#queuedSubmissions.findIndex(item => this.#pendingInputs.receipt(item)?.id === id);
|
|
1459
1703
|
if (index < 0)
|
|
1460
1704
|
return false;
|
|
1461
|
-
if (change.kind === '
|
|
1705
|
+
if (change.kind === 'release') {
|
|
1706
|
+
const submission = this.#queuedSubmissions[index];
|
|
1707
|
+
this.#queuedSubmissions.splice(index, 1);
|
|
1708
|
+
this.#dispatchInput(submission);
|
|
1709
|
+
}
|
|
1710
|
+
else if (change.kind === 'remove')
|
|
1462
1711
|
this.#queuedSubmissions.splice(index, 1);
|
|
1463
1712
|
else if (change.kind === 'replace') {
|
|
1464
|
-
this.#
|
|
1713
|
+
const old = this.#pendingInputs.receipt(this.#queuedSubmissions[index]);
|
|
1714
|
+
const receipt = this.#pendingInputs.identify(change.submission, this.#pendingScope());
|
|
1715
|
+
receipt.id = id;
|
|
1716
|
+
receipt.recovered = old.recovered ?? false;
|
|
1465
1717
|
this.#queuedSubmissions[index] = change.submission;
|
|
1466
1718
|
}
|
|
1467
1719
|
else {
|
|
@@ -1477,6 +1729,46 @@ export class LocalTui {
|
|
|
1477
1729
|
return true;
|
|
1478
1730
|
},
|
|
1479
1731
|
};
|
|
1732
|
+
setContinueHandler(handler) {
|
|
1733
|
+
this.#continueHandler = handler;
|
|
1734
|
+
return () => { if (this.#continueHandler === handler)
|
|
1735
|
+
this.#continueHandler = undefined; };
|
|
1736
|
+
}
|
|
1737
|
+
#canContinue() {
|
|
1738
|
+
if (this.#continueHandler === undefined || this.#continuing || this.#disposed || this.#sessionId === undefined
|
|
1739
|
+
|| this.#sessionControls?.continuation === undefined || this.#inspected !== undefined || this.#state.status !== 'idle'
|
|
1740
|
+
|| this.#prompt !== null || this.#hasComposerContent() || this.#pasteInFlight > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1741
|
+
return false;
|
|
1742
|
+
const pending = this.#pendingInputs.session(this.#sessionId);
|
|
1743
|
+
return pending.queued.length === 0 && pending.sending.size === 0
|
|
1744
|
+
&& this.#state.nextTurnInbox.length === 0 && !this.#state.nextStepInbox.some(message => message.source.kind === 'user');
|
|
1745
|
+
}
|
|
1746
|
+
setGoalHandler(handler) {
|
|
1747
|
+
this.#goalHandler = handler;
|
|
1748
|
+
this.#syncSettingsActions();
|
|
1749
|
+
return () => { if (this.#goalHandler === handler) {
|
|
1750
|
+
this.#goalAbort?.abort();
|
|
1751
|
+
this.#goalHandler = undefined;
|
|
1752
|
+
this.#syncSettingsActions();
|
|
1753
|
+
} };
|
|
1754
|
+
}
|
|
1755
|
+
#openGoal() {
|
|
1756
|
+
if (this.#inspected !== undefined)
|
|
1757
|
+
return;
|
|
1758
|
+
if (this.#goalHandler === undefined) {
|
|
1759
|
+
this.notice('Goal controls are unavailable in this profile.');
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if (this.#goalAbort !== undefined)
|
|
1763
|
+
return;
|
|
1764
|
+
const abort = new AbortController();
|
|
1765
|
+
this.#goalAbort = abort;
|
|
1766
|
+
void this.#goalHandler(abort.signal).catch(error => {
|
|
1767
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
1768
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
1769
|
+
}).finally(() => { if (this.#goalAbort === abort)
|
|
1770
|
+
this.#goalAbort = undefined; });
|
|
1771
|
+
}
|
|
1480
1772
|
setQueueHandler(handler) {
|
|
1481
1773
|
this.#queueHandler = handler;
|
|
1482
1774
|
return () => { if (this.#queueHandler === handler) {
|
|
@@ -1496,6 +1788,10 @@ export class LocalTui {
|
|
|
1496
1788
|
this.#inspects.add(listener);
|
|
1497
1789
|
return () => { this.#inspects.delete(listener); };
|
|
1498
1790
|
}
|
|
1791
|
+
onInspectWorkflow(listener) {
|
|
1792
|
+
this.#workflowInspects.add(listener);
|
|
1793
|
+
return () => { this.#workflowInspects.delete(listener); };
|
|
1794
|
+
}
|
|
1499
1795
|
onInspectClose(listener) {
|
|
1500
1796
|
this.#inspectCloses.add(listener);
|
|
1501
1797
|
return () => { this.#inspectCloses.delete(listener); };
|
|
@@ -1504,11 +1800,6 @@ export class LocalTui {
|
|
|
1504
1800
|
this.#inspectSubmits.add(listener);
|
|
1505
1801
|
return () => { this.#inspectSubmits.delete(listener); };
|
|
1506
1802
|
}
|
|
1507
|
-
setSteerHandler(handler) {
|
|
1508
|
-
this.#steerHandler = handler;
|
|
1509
|
-
return () => { if (this.#steerHandler === handler)
|
|
1510
|
-
this.#steerHandler = undefined; };
|
|
1511
|
-
}
|
|
1512
1803
|
/**
|
|
1513
1804
|
* Last-resort restore for a process that exits without disposing: leave the
|
|
1514
1805
|
* alternate screen, give every mode back and show the cursor. Writes to a TTY
|
|
@@ -1529,10 +1820,11 @@ export class LocalTui {
|
|
|
1529
1820
|
return this.#disposeTask ?? this.#herdr.release();
|
|
1530
1821
|
this.#flushPasteBurst(true);
|
|
1531
1822
|
this.#flushDraft();
|
|
1823
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1532
1824
|
this.#disposed = true;
|
|
1533
|
-
this.#steerHandler = undefined;
|
|
1534
1825
|
this.#imagePreviewAbort?.abort();
|
|
1535
1826
|
this.#fileReviewAbort?.abort();
|
|
1827
|
+
this.#closeFileBrowser();
|
|
1536
1828
|
this.#asideAbort?.abort();
|
|
1537
1829
|
for (const directory of this.#imageTemporary) {
|
|
1538
1830
|
try {
|
|
@@ -1541,8 +1833,13 @@ export class LocalTui {
|
|
|
1541
1833
|
catch { /* OS may still hold the opened file. */ }
|
|
1542
1834
|
}
|
|
1543
1835
|
this.#imageTemporary.clear();
|
|
1836
|
+
this.#goalAbort?.abort();
|
|
1837
|
+
this.#goalAbort = undefined;
|
|
1838
|
+
this.#goalHandler = undefined;
|
|
1544
1839
|
this.#queueHotkeyAbort?.abort();
|
|
1545
1840
|
this.#queueHandler = undefined;
|
|
1841
|
+
this.#continueHandler = undefined;
|
|
1842
|
+
this.#cancelQueueRecall();
|
|
1546
1843
|
if (this.#notificationTimer !== undefined)
|
|
1547
1844
|
clearTimeout(this.#notificationTimer);
|
|
1548
1845
|
this.#notificationQueue.clear();
|
|
@@ -1590,7 +1887,7 @@ export class LocalTui {
|
|
|
1590
1887
|
this.#session.dispose();
|
|
1591
1888
|
this.#term.output.write('\x1b[?25h\r\n');
|
|
1592
1889
|
if (this.#resumeHintRequested && this.#sessionId !== undefined) {
|
|
1593
|
-
this.#term.output.write(`\r\
|
|
1890
|
+
this.#term.output.write(`\r\n${uiText(resolveUiLanguage(this.#uiLanguage), 'Resume this session with {command}', { command: `${APP_NAME} --resume ${this.#sessionId}` })}\r\n`);
|
|
1594
1891
|
}
|
|
1595
1892
|
// A tty stdin keeps the event loop alive after the tree disposes;
|
|
1596
1893
|
// release the descriptor so natural completion can exit the process.
|
|
@@ -1638,10 +1935,10 @@ export class LocalTui {
|
|
|
1638
1935
|
/** Countdown shown on the working row while a retry or a compaction is pending. */
|
|
1639
1936
|
#workingLabel() {
|
|
1640
1937
|
if (this.#retryDeadline !== undefined) {
|
|
1641
|
-
return
|
|
1938
|
+
return uiText(resolveUiLanguage(this.#uiLanguage), 'Retrying in {seconds}s', { seconds: Math.max(0, Math.ceil((this.#retryDeadline - Date.now()) / 1000)) });
|
|
1642
1939
|
}
|
|
1643
1940
|
if (this.#state.status === 'compacting' && this.#compactionStartedAt !== undefined) {
|
|
1644
|
-
return
|
|
1941
|
+
return uiText(resolveUiLanguage(this.#uiLanguage), 'Compacting {seconds}s', { seconds: Math.max(0, Math.floor((Date.now() - this.#compactionStartedAt) / 1000)) });
|
|
1645
1942
|
}
|
|
1646
1943
|
return undefined;
|
|
1647
1944
|
}
|
|
@@ -1762,7 +2059,13 @@ export class LocalTui {
|
|
|
1762
2059
|
clearTimeout(this.#draftTimer);
|
|
1763
2060
|
this.#draftTimer = undefined;
|
|
1764
2061
|
const id = this.#draftScope;
|
|
1765
|
-
if (id === undefined
|
|
2062
|
+
if (id === undefined)
|
|
2063
|
+
return;
|
|
2064
|
+
if (this.#draftStore === undefined) {
|
|
2065
|
+
this.#unsavedDrafts.set(id, this.#captureDraft());
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (!this.#draftDirty && !this.#unsavedDrafts.has(id))
|
|
1766
2069
|
return;
|
|
1767
2070
|
const draft = this.#captureDraft();
|
|
1768
2071
|
this.#draftDirty = false;
|
|
@@ -1784,7 +2087,7 @@ export class LocalTui {
|
|
|
1784
2087
|
}
|
|
1785
2088
|
#clearDraft() {
|
|
1786
2089
|
this.#historyDraft = undefined;
|
|
1787
|
-
if (this.#
|
|
2090
|
+
if (this.#draftScope === undefined)
|
|
1788
2091
|
return;
|
|
1789
2092
|
// A sent or explicitly discarded input must not return after a quick exit.
|
|
1790
2093
|
this.#draftFingerprint = this.#draftKey();
|
|
@@ -1792,12 +2095,20 @@ export class LocalTui {
|
|
|
1792
2095
|
this.#flushDraft();
|
|
1793
2096
|
}
|
|
1794
2097
|
#switchDraft(id) {
|
|
1795
|
-
if (
|
|
2098
|
+
if (id === this.#draftScope)
|
|
1796
2099
|
return;
|
|
2100
|
+
this.#goalAbort?.abort();
|
|
2101
|
+
this.#goalAbort = undefined;
|
|
2102
|
+
this.#queueHotkeyAbort?.abort();
|
|
2103
|
+
this.#queueHotkeyAbort = undefined;
|
|
2104
|
+
this.#cancelQueueRecall();
|
|
1797
2105
|
this.#flushPasteBurst(true);
|
|
1798
2106
|
this.#finishPrompt(null);
|
|
1799
2107
|
this.#flushDraft();
|
|
2108
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1800
2109
|
const initialInput = this.#draftScope === undefined && this.#editor.text !== '' ? this.#captureDraft() : undefined;
|
|
2110
|
+
if (this.#draftScope === undefined && id !== undefined)
|
|
2111
|
+
this.#pendingInputs.bindInitial(id);
|
|
1801
2112
|
this.#draftScope = id;
|
|
1802
2113
|
this.#historyIndex = 0;
|
|
1803
2114
|
this.#historyDraft = undefined;
|
|
@@ -1810,7 +2121,7 @@ export class LocalTui {
|
|
|
1810
2121
|
let restoreError;
|
|
1811
2122
|
if (id !== undefined) {
|
|
1812
2123
|
try {
|
|
1813
|
-
const draft = this.#unsavedDrafts.get(id) ?? this.#draftStore
|
|
2124
|
+
const draft = this.#unsavedDrafts.get(id) ?? this.#draftStore?.load(id);
|
|
1814
2125
|
if (draft !== undefined)
|
|
1815
2126
|
this.#restoreDraft(draft);
|
|
1816
2127
|
}
|
|
@@ -1826,6 +2137,7 @@ export class LocalTui {
|
|
|
1826
2137
|
this.#reportDraftError('restore', restoreError);
|
|
1827
2138
|
}
|
|
1828
2139
|
#render() {
|
|
2140
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1829
2141
|
this.#watchDraft();
|
|
1830
2142
|
if (this.#streamRenderTimer !== null) {
|
|
1831
2143
|
clearTimeout(this.#streamRenderTimer);
|
|
@@ -1848,16 +2160,16 @@ export class LocalTui {
|
|
|
1848
2160
|
imageCellSize: this.#imageCellSize,
|
|
1849
2161
|
mathMode: this.#mathMode,
|
|
1850
2162
|
mermaidMode: this.#mermaidMode,
|
|
1851
|
-
hideThinking:
|
|
2163
|
+
hideThinking: true,
|
|
1852
2164
|
height: this.#term.height(),
|
|
1853
2165
|
model: this.#model,
|
|
1854
2166
|
...(this.#reasoningEffort === undefined ? {} : { reasoningEffort: this.#reasoningEffort }),
|
|
1855
2167
|
input: this.#editor.text,
|
|
1856
2168
|
inputCursor: this.#editor.cursor,
|
|
1857
2169
|
inputImages: this.#images.count,
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2170
|
+
canContinue: this.#canContinue(),
|
|
2171
|
+
queuedSubmissionScope: this.#pendingScope(),
|
|
2172
|
+
queuedSubmissions: [...this.#queuedSubmissions, ...(this.#queueEditNewer ?? [])].map(submission => this.#pendingInputs.receipt(submission)?.recovered ? { ...submission, recovered: true } : submission),
|
|
1861
2173
|
colors: this.#colors,
|
|
1862
2174
|
pwd: this.#pwd,
|
|
1863
2175
|
...(this.#branch !== undefined ? { branch: this.#branch } : {}),
|
|
@@ -1868,12 +2180,16 @@ export class LocalTui {
|
|
|
1868
2180
|
spinnerFrame: this.#spinner,
|
|
1869
2181
|
motion: this.#motion,
|
|
1870
2182
|
colorDepth: this.#colorDepth,
|
|
2183
|
+
compactLinks: this.#compactLinks,
|
|
2184
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
1871
2185
|
themeName: this.#themeName,
|
|
1872
2186
|
terminal: this.#terminalColors,
|
|
1873
2187
|
...(workingLabel === undefined ? {} : { workingLabel }),
|
|
1874
|
-
...(this.#transientStatus
|
|
2188
|
+
...(this.#transientStatus !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), this.#transientStatus) }
|
|
2189
|
+
: this.#selection.active ? { selectionActions: this.#canQuoteSelection() ? 'copy-quote' : 'copy' }
|
|
2190
|
+
: this.#processSelection !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), 'Process · ↑↓ select · Enter toggle · {key} actions · Home title · Esc return', { key: keysForAction(this.#keybindings, 'tool-actions') }) } : {}),
|
|
1875
2191
|
scrollStart: this.#follow ? Number.POSITIVE_INFINITY : this.#scrollStart,
|
|
1876
|
-
...(this.#focusBlock === undefined ? {} : { focusBlock: this.#focusBlock, focusBlockEdge: this.#focusBlockEdge ?? 'start' }),
|
|
2192
|
+
...(this.#focusBlock === undefined ? {} : { focusBlock: this.#focusBlock, focusBlockEdge: this.#focusBlockEdge ?? 'start', ...(this.#focusBlockOffset === undefined ? {} : { focusBlockOffset: this.#focusBlockOffset }) }),
|
|
1877
2193
|
...(this.#transcriptSearch === null ? {} : {
|
|
1878
2194
|
transcriptSearch: {
|
|
1879
2195
|
query: this.#transcriptSearch.query,
|
|
@@ -1884,9 +2200,14 @@ export class LocalTui {
|
|
|
1884
2200
|
toolsExpanded: this.#toolsExpanded,
|
|
1885
2201
|
expandedOutputs: this.#expandedOutputs,
|
|
1886
2202
|
expandedTools: this.#detailedToolIds(),
|
|
2203
|
+
rawTools: this.#detailedToolIds(false),
|
|
2204
|
+
expandedReasoning: this.#visibleReasoning(),
|
|
2205
|
+
...(this.#processSelection === undefined ? {} : { selectedProcess: this.#processSelection }),
|
|
1887
2206
|
toolDetailsKey: keysForAction(this.#keybindings, 'toggle-tool-details'),
|
|
1888
2207
|
turnDetailsKey: keysForAction(this.#keybindings, 'toggle-tools'),
|
|
1889
|
-
|
|
2208
|
+
busyEnter: this.#queueEditNewer !== null || this.#queueEditPending ? 'queue' : this.#busyEnter,
|
|
2209
|
+
steerKey: keysForAction(this.#keybindings, 'steer-turn'),
|
|
2210
|
+
queueKey: keysForAction(this.#keybindings, 'follow-up'),
|
|
1890
2211
|
openedGroups: this.#openedGroups,
|
|
1891
2212
|
commands: this.#commands(),
|
|
1892
2213
|
modelReady: this.#modelReady,
|
|
@@ -1897,6 +2218,7 @@ export class LocalTui {
|
|
|
1897
2218
|
...(this.#subagentSelection === undefined ? {} : { subagentSelection: this.#subagentSelection }),
|
|
1898
2219
|
...(this.#inspected === undefined ? {} : { inspected: this.#inspected }),
|
|
1899
2220
|
...(this.#trajectory === null ? {} : { trajectory: this.#trajectory }),
|
|
2221
|
+
...(this.#fileBrowser === null ? {} : { fileBrowser: this.#fileBrowser.state }),
|
|
1900
2222
|
...(this.#aside === undefined ? {} : { aside: this.#aside }),
|
|
1901
2223
|
statusBar: this.#statusBar,
|
|
1902
2224
|
...(this.#prompt === null ? {} : { promptSelector: this.#prompt }),
|
|
@@ -1909,6 +2231,9 @@ export class LocalTui {
|
|
|
1909
2231
|
: { lines: [] };
|
|
1910
2232
|
this.#focusBlock = undefined;
|
|
1911
2233
|
this.#focusBlockEdge = undefined;
|
|
2234
|
+
this.#focusBlockOffset = undefined;
|
|
2235
|
+
this.#promptActions = frame.promptActions ?? [];
|
|
2236
|
+
this.#promptChoices = frame.promptChoices ?? [];
|
|
1912
2237
|
this.#promptDocument = frame.promptDocument;
|
|
1913
2238
|
if (this.#prompt !== null && this.#prompt.documentScroll !== Number.POSITIVE_INFINITY && frame.promptDocument?.position !== undefined) {
|
|
1914
2239
|
this.#prompt = { ...this.#prompt, documentAnchor: frame.promptDocument.position };
|
|
@@ -1916,8 +2241,8 @@ export class LocalTui {
|
|
|
1916
2241
|
this.#syncScroll(frame.transcript);
|
|
1917
2242
|
this.#jumpToLatest = frame.jumpToLatest;
|
|
1918
2243
|
this.#fileActionFrame = frame.transcript === undefined || frame.transientSurface === 'overlay' ? undefined : frame;
|
|
1919
|
-
this.#setMouseTracking(frame.transcript !== undefined && frame.transientSurface !== 'overlay'
|
|
1920
|
-
&& (this.#inspecting || this.#maxStart > 0 || (frame.transcript.fileRegions?.length ?? 0) > 0));
|
|
2244
|
+
this.#setMouseTracking(this.#fileBrowser !== null || this.#trajectory !== null || this.#documentMouseEnabled() || this.#promptChoices.length > 0 || this.#promptActions.length > 0 || (frame.transcript !== undefined && frame.transientSurface !== 'overlay'
|
|
2245
|
+
&& (this.#inspecting || this.#maxStart > 0 || (frame.transcript.fileRegions?.length ?? 0) > 0 || (frame.transcript.disclosures?.length ?? 0) > 0)));
|
|
1921
2246
|
// A run opening or folding reshapes rows above the screen in place; the
|
|
1922
2247
|
// renderer's frozen boundary is a row index and has to be told, or a fold
|
|
1923
2248
|
// blanks the screen and a large one replays the transcript into history.
|
|
@@ -1996,14 +2321,38 @@ export class LocalTui {
|
|
|
1996
2321
|
this.#toolsExpanded = false;
|
|
1997
2322
|
this.#openedGroups.clear();
|
|
1998
2323
|
this.#detailedTurns.clear();
|
|
2324
|
+
this.#expandedOutputs.clear();
|
|
2325
|
+
this.#expandedToolRows.clear();
|
|
2326
|
+
this.#toolReadingPositions.clear();
|
|
2327
|
+
this.#expandedReasoning.clear();
|
|
2328
|
+
this.#closedReasoning.clear();
|
|
2329
|
+
this.#processSelection = undefined;
|
|
2330
|
+
}
|
|
2331
|
+
#documentMouseEnabled() {
|
|
2332
|
+
const prompt = this.#prompt;
|
|
2333
|
+
return prompt !== null && prompt.feedback !== true && this.#promptDocument !== undefined
|
|
2334
|
+
&& (prompt.request.presentation === 'document' || prompt.request.presentation === 'plan-review');
|
|
2335
|
+
}
|
|
2336
|
+
#scrollPromptDocument(row) {
|
|
2337
|
+
if (this.#prompt === null)
|
|
2338
|
+
return;
|
|
2339
|
+
this.#prompt = {
|
|
2340
|
+
...this.#prompt,
|
|
2341
|
+
documentAnchor: undefined,
|
|
2342
|
+
documentTarget: undefined,
|
|
2343
|
+
documentScroll: row === Number.POSITIVE_INFINITY ? row : Math.max(0, Math.min(row, this.#promptDocument?.maxStart ?? 0)),
|
|
2344
|
+
};
|
|
2345
|
+
this.#render();
|
|
1999
2346
|
}
|
|
2000
2347
|
/** Capture transcript navigation; overlays and disposal restore terminal ownership. */
|
|
2001
2348
|
#setMouseTracking(enabled) {
|
|
2002
2349
|
enabled = enabled && this.#mouseInteraction !== 'native'
|
|
2003
2350
|
&& (this.#mouseInteraction === 'tui' || this.#tmuxMouseEnabled !== false);
|
|
2004
2351
|
if (!enabled) {
|
|
2352
|
+
this.#mouseActionPress = undefined;
|
|
2005
2353
|
this.#selection.clear();
|
|
2006
2354
|
this.#jumpPress = undefined;
|
|
2355
|
+
this.#disclosurePress = undefined;
|
|
2007
2356
|
}
|
|
2008
2357
|
if (!this.#tty || enabled === this.#mouseTracking)
|
|
2009
2358
|
return;
|
|
@@ -2035,7 +2384,7 @@ export class LocalTui {
|
|
|
2035
2384
|
* or go back to the live tail once nothing is open any more.
|
|
2036
2385
|
*/
|
|
2037
2386
|
#inspect(target) {
|
|
2038
|
-
const open = this.#toolsExpanded || this.#openedGroups.size > 0;
|
|
2387
|
+
const open = this.#toolsExpanded || this.#openedGroups.size > 0 || this.#expandedOutputs.size > 0;
|
|
2039
2388
|
if (!open) {
|
|
2040
2389
|
this.#inspecting = false;
|
|
2041
2390
|
this.#followTail();
|
|
@@ -2079,9 +2428,9 @@ export class LocalTui {
|
|
|
2079
2428
|
}
|
|
2080
2429
|
}
|
|
2081
2430
|
#routeInputBatch(events) {
|
|
2082
|
-
if (this.#pasteProtection &&
|
|
2431
|
+
if (this.#pasteProtection && !this.#paste && this.#pasteInFlight === 0
|
|
2083
2432
|
&& this.#prompt === null && this.#settings === null
|
|
2084
|
-
&& this.#search === null && this.#trajectory === null && this.#transcriptSearch === null
|
|
2433
|
+
&& this.#search === null && this.#trajectory === null && this.#fileBrowser === null && this.#transcriptSearch === null
|
|
2085
2434
|
&& events.every(event => event.type === 'text' || (event.type === 'key' && ['enter', 'ctrl+j', 'tab'].includes(event.id)))) {
|
|
2086
2435
|
if (this.#pasteBurstTimer !== undefined)
|
|
2087
2436
|
clearTimeout(this.#pasteBurstTimer);
|
|
@@ -2104,8 +2453,8 @@ export class LocalTui {
|
|
|
2104
2453
|
this.#dispatch(event);
|
|
2105
2454
|
return;
|
|
2106
2455
|
}
|
|
2107
|
-
const composer = this.#
|
|
2108
|
-
&& this.#search === null && this.#trajectory === null && this.#transcriptSearch === null;
|
|
2456
|
+
const composer = this.#prompt === null && this.#settings === null
|
|
2457
|
+
&& this.#search === null && this.#trajectory === null && this.#fileBrowser === null && this.#transcriptSearch === null;
|
|
2109
2458
|
if (this.#paste || this.#pasteInFlight > 0 || !this.#pasteProtection || !composer
|
|
2110
2459
|
|| event.type === 'paste-start' || event.type === 'paste-end') {
|
|
2111
2460
|
this.#flushPasteBurst(true);
|
|
@@ -2156,6 +2505,10 @@ export class LocalTui {
|
|
|
2156
2505
|
});
|
|
2157
2506
|
}
|
|
2158
2507
|
async #acceptPastedText(text) {
|
|
2508
|
+
if (this.#fileBrowser !== null) {
|
|
2509
|
+
this.#applyFileBrowserInput({ type: 'text', value: text });
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2159
2512
|
if (this.#prompt !== null) {
|
|
2160
2513
|
const prompt = this.#prompt;
|
|
2161
2514
|
if (prompt.documentInput !== undefined) {
|
|
@@ -2254,14 +2607,198 @@ export class LocalTui {
|
|
|
2254
2607
|
if (this.#tty)
|
|
2255
2608
|
this.#render();
|
|
2256
2609
|
}
|
|
2610
|
+
/** Resolve only current painted rows, never reinterpret a frozen native-history snapshot. */
|
|
2611
|
+
#disclosureAt(row, column, action = false) {
|
|
2612
|
+
const frame = this.#fileActionFrame;
|
|
2613
|
+
const document = this.#renderer.documentRowAt(row);
|
|
2614
|
+
const source = frame?.documentRows;
|
|
2615
|
+
if (document === undefined || source == null || document < source.documentStart || document >= source.documentEnd)
|
|
2616
|
+
return;
|
|
2617
|
+
const target = frame?.transcript?.disclosures?.find(item => item.row === document && (action
|
|
2618
|
+
? item.actionColumn !== undefined && column >= item.actionColumn && column < item.actionColumn + (item.key.startsWith('output:') ? 6 : 5)
|
|
2619
|
+
: column >= item.column && column < item.column + item.width));
|
|
2620
|
+
const expected = frame?.lines[source.frameStart + document - source.documentStart];
|
|
2621
|
+
const actual = this.#renderer.viewport()[row];
|
|
2622
|
+
if (target === undefined || expected === undefined || actual === undefined
|
|
2623
|
+
|| stripAnsi(sanitizeDisplayLine(expected)).trimEnd() !== stripAnsi(actual).trimEnd())
|
|
2624
|
+
return;
|
|
2625
|
+
return target;
|
|
2626
|
+
}
|
|
2627
|
+
/** Preview text and spinner frames may change while a click stays on the same item. */
|
|
2628
|
+
#pressedDisclosureAt(row, column) {
|
|
2629
|
+
const pressed = this.#disclosurePress;
|
|
2630
|
+
if (pressed === undefined || pressed.row !== row)
|
|
2631
|
+
return;
|
|
2632
|
+
if (pressed.target.key.startsWith('output:') && Math.abs(pressed.column - column) > 1)
|
|
2633
|
+
return;
|
|
2634
|
+
const target = this.#disclosureAt(row, column);
|
|
2635
|
+
if (target?.key === pressed.target.key && target.row === pressed.target.row
|
|
2636
|
+
&& target.column === pressed.target.column && target.width === pressed.target.width)
|
|
2637
|
+
return target;
|
|
2638
|
+
}
|
|
2639
|
+
#activateDisclosure(target) {
|
|
2640
|
+
const output = this.#state.blocks[target.block];
|
|
2641
|
+
if (output?.kind === 'commandOutput' && target.key === `output:${target.block}`) {
|
|
2642
|
+
if (this.#expandedOutputs.has(output))
|
|
2643
|
+
this.#expandedOutputs.delete(output);
|
|
2644
|
+
else
|
|
2645
|
+
this.#expandedOutputs.add(output);
|
|
2646
|
+
this.#inspect(target.block);
|
|
2647
|
+
this.#focusBlock = target.block;
|
|
2648
|
+
this.#focusBlockEdge = 'start';
|
|
2649
|
+
this.#render();
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
const groups = turnGroups(this.#state);
|
|
2653
|
+
const group = groups.find(item => target.group === undefined
|
|
2654
|
+
? target.block >= item.start && target.block < item.until : item.key === target.group);
|
|
2655
|
+
if (group === undefined)
|
|
2656
|
+
return;
|
|
2657
|
+
const block = this.#state.blocks[target.block];
|
|
2658
|
+
if (target.group === undefined && target.key !== (block?.kind === 'tool' ? `call:${block.callId}`
|
|
2659
|
+
: block?.kind === 'assistant' ? `thought:${reasoningKey(block)}` : undefined))
|
|
2660
|
+
return;
|
|
2661
|
+
const scope = this.#detailScope(group);
|
|
2662
|
+
for (const member of groups)
|
|
2663
|
+
if (this.#detailScope(member) === scope)
|
|
2664
|
+
this.#openedGroups.add(member.key);
|
|
2665
|
+
this.#processSelection = this.#processRows().includes(target.block) ? target.block : this.#processRows()[0];
|
|
2666
|
+
this.#ac = null;
|
|
2667
|
+
this.#inspect(target.block);
|
|
2668
|
+
if (target.group === undefined)
|
|
2669
|
+
this.#toggleProcessItem(target.block, 'enter');
|
|
2670
|
+
else {
|
|
2671
|
+
this.#focusBlock = target.block;
|
|
2672
|
+
this.#focusBlockEdge = 'start';
|
|
2673
|
+
this.#render();
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
#mouseActionAt(row, column) {
|
|
2677
|
+
const frame = this.#fileActionFrame;
|
|
2678
|
+
const viewport = this.#renderer.viewport();
|
|
2679
|
+
for (const target of frame?.selectionActions ?? []) {
|
|
2680
|
+
const physical = viewport.length - (frame.lines.length - target.row);
|
|
2681
|
+
if (row === physical && column >= target.column && column < target.column + target.width
|
|
2682
|
+
&& this.#selection.active && this.#selection.valid(viewport)
|
|
2683
|
+
&& stripAnsi(viewport[row] ?? '').trimEnd() === stripAnsi(frame.lines[target.row] ?? '').trimEnd()) {
|
|
2684
|
+
return { ...target, row: physical, key: target.action };
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const tool = this.#disclosureAt(row, column, true);
|
|
2688
|
+
if (tool?.actionColumn !== undefined)
|
|
2689
|
+
return { row, column: tool.actionColumn, width: tool.key.startsWith('output:') ? 6 : 5, key: tool.key, action: 'tool', block: tool.block };
|
|
2690
|
+
return undefined;
|
|
2691
|
+
}
|
|
2257
2692
|
#handleMouse(event) {
|
|
2258
2693
|
if (!this.#mouseTracking || this.#paste || this.#pasteInFlight > 0)
|
|
2259
2694
|
return;
|
|
2695
|
+
if (this.#trajectory !== null || this.#fileBrowser !== null) {
|
|
2696
|
+
this.#handleTrajectoryMouse(event);
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
const promptAction = this.#promptActions.find(action => event.row === action.row && event.column >= action.column && event.column < action.column + action.width);
|
|
2700
|
+
if (this.#promptActionPress !== undefined) {
|
|
2701
|
+
const pressed = this.#promptActionPress;
|
|
2702
|
+
if (event.action === 'release') {
|
|
2703
|
+
this.#promptActionPress = undefined;
|
|
2704
|
+
this.#promptChoicePress = undefined;
|
|
2705
|
+
if (event.button === 'left' && !event.shift && promptAction?.index === pressed.index && promptAction.key === pressed.key
|
|
2706
|
+
&& promptAction.row === pressed.row && promptAction.column === pressed.column && this.#prompt !== null) {
|
|
2707
|
+
if (pressed.key === undefined) {
|
|
2708
|
+
this.#prompt = { ...this.#prompt, selected: pressed.index, documentInput: undefined };
|
|
2709
|
+
this.#handlePlanReview({ type: 'key', id: 'enter' }, this.#prompt);
|
|
2710
|
+
}
|
|
2711
|
+
else if (pressed.selectedValue === (filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected]?.value
|
|
2712
|
+
?? filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected]?.label)) {
|
|
2713
|
+
this.#handlePrompt({ type: 'key', id: pressed.key });
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
else if (event.action === 'drag' && (promptAction?.index !== pressed.index || promptAction.key !== pressed.key
|
|
2718
|
+
|| promptAction.row !== pressed.row || promptAction.column !== pressed.column || event.shift)) {
|
|
2719
|
+
this.#promptActionPress = { ...pressed, index: -2 };
|
|
2720
|
+
}
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
if (event.button === 'left' && event.action === 'press' && !event.shift && promptAction !== undefined) {
|
|
2724
|
+
this.#selection.clear();
|
|
2725
|
+
const selected = this.#prompt === null ? undefined : filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected];
|
|
2726
|
+
this.#promptActionPress = { ...promptAction, selectedValue: selected?.value ?? selected?.label };
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
if (this.#promptChoices.length > 0 && event.button === 'left') {
|
|
2730
|
+
const choice = this.#promptChoices.find(item => item.row === event.row && event.column >= item.column && event.column < item.column + item.width);
|
|
2731
|
+
if (event.action === 'press') {
|
|
2732
|
+
const prompt = this.#prompt;
|
|
2733
|
+
if (prompt?.request.wait !== undefined && prompt.waitHeld !== true) {
|
|
2734
|
+
prompt.request.wait.hold();
|
|
2735
|
+
prompt.offTick?.();
|
|
2736
|
+
this.#prompt = { ...prompt, waitHeld: true };
|
|
2737
|
+
}
|
|
2738
|
+
this.#promptChoicePress = choice === undefined ? undefined : { value: choice.value, row: choice.row, dragged: event.shift };
|
|
2739
|
+
this.#selection.begin(this.#renderer.viewport(), event, Date.now(), event.shift);
|
|
2740
|
+
}
|
|
2741
|
+
else if (event.action === 'drag') {
|
|
2742
|
+
if (this.#promptChoicePress !== undefined)
|
|
2743
|
+
this.#promptChoicePress.dragged = true;
|
|
2744
|
+
this.#selection.move(event);
|
|
2745
|
+
}
|
|
2746
|
+
else {
|
|
2747
|
+
const pressed = this.#promptChoicePress;
|
|
2748
|
+
this.#promptChoicePress = undefined;
|
|
2749
|
+
const text = this.#selection.release(event);
|
|
2750
|
+
if (text !== undefined) {
|
|
2751
|
+
if (this.#copyOnSelect)
|
|
2752
|
+
void this.#copySelection();
|
|
2753
|
+
}
|
|
2754
|
+
else if (pressed !== undefined && !pressed.dragged && !event.shift && choice?.value === pressed.value && choice.row === pressed.row && this.#prompt !== null) {
|
|
2755
|
+
this.#prompt = { ...this.#prompt, selected: choice.index, previewScroll: 0 };
|
|
2756
|
+
if (this.#prompt.request.multiSelect === true && filteredPromptOptions(this.#prompt.request, this.#editor.text)[choice.index]?.disabled !== true)
|
|
2757
|
+
this.#prompt = togglePromptSelection(this.#prompt, this.#editor.text);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
this.#render();
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
const pressedAction = this.#mouseActionPress;
|
|
2764
|
+
if (pressedAction !== undefined) {
|
|
2765
|
+
const target = this.#mouseActionAt(event.row, event.column);
|
|
2766
|
+
if (event.action === 'release') {
|
|
2767
|
+
this.#mouseActionPress = undefined;
|
|
2768
|
+
if (event.button === 'left' && !event.shift && target?.key === pressedAction.key && target.row === pressedAction.row && target.column === pressedAction.column) {
|
|
2769
|
+
if (target.action === 'copy')
|
|
2770
|
+
void this.#copySelection(true);
|
|
2771
|
+
else if (target.action === 'quote')
|
|
2772
|
+
this.#quoteSelection();
|
|
2773
|
+
else {
|
|
2774
|
+
const block = this.#state.blocks[target.block];
|
|
2775
|
+
if (block?.kind === 'tool')
|
|
2776
|
+
void this.#openToolActions(block);
|
|
2777
|
+
else if (block?.kind === 'commandOutput')
|
|
2778
|
+
void this.#readCommandOutput(block);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
else if (event.action === 'drag' && (target?.key !== pressedAction.key || event.shift)) {
|
|
2783
|
+
// Keep consuming the gesture, but never activate after leaving the button.
|
|
2784
|
+
this.#mouseActionPress = { ...pressedAction, key: '' };
|
|
2785
|
+
}
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
if (event.button === 'left' && event.action === 'press' && !event.shift) {
|
|
2789
|
+
const target = this.#mouseActionAt(event.row, event.column);
|
|
2790
|
+
if (target !== undefined) {
|
|
2791
|
+
this.#mouseActionPress = target;
|
|
2792
|
+
this.#disclosurePress = undefined;
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2260
2796
|
const inside = (target) => event.row === target.row && event.column >= target.column && event.column < target.column + target.width;
|
|
2261
2797
|
if (event.button === 'right') {
|
|
2798
|
+
this.#disclosurePress = undefined;
|
|
2262
2799
|
if (event.action === 'press') {
|
|
2263
2800
|
if (this.#selection.active)
|
|
2264
|
-
void this.#copySelection();
|
|
2801
|
+
void this.#copySelection(true);
|
|
2265
2802
|
else {
|
|
2266
2803
|
const document = this.#renderer.documentRowAt(event.row);
|
|
2267
2804
|
const frame = this.#fileActionFrame;
|
|
@@ -2283,6 +2820,8 @@ export class LocalTui {
|
|
|
2283
2820
|
return;
|
|
2284
2821
|
}
|
|
2285
2822
|
if (event.action === 'press') {
|
|
2823
|
+
const disclosure = event.shift ? undefined : this.#disclosureAt(event.row, event.column);
|
|
2824
|
+
this.#disclosurePress = disclosure === undefined ? undefined : { target: disclosure, row: event.row, column: event.column };
|
|
2286
2825
|
const target = this.#jumpToLatest;
|
|
2287
2826
|
this.#jumpPress = target !== undefined && inside(target) ? target : undefined;
|
|
2288
2827
|
if (this.#jumpPress !== undefined)
|
|
@@ -2301,15 +2840,93 @@ export class LocalTui {
|
|
|
2301
2840
|
this.#followTail();
|
|
2302
2841
|
}
|
|
2303
2842
|
}
|
|
2304
|
-
else if (event.action === 'drag')
|
|
2305
|
-
|
|
2843
|
+
else if (event.action === 'drag') {
|
|
2844
|
+
// Motion confined to the empty gutter is click jitter. Once it leaves,
|
|
2845
|
+
// selection owns the gesture even if the pointer returns to the arrow.
|
|
2846
|
+
if (event.shift || this.#pressedDisclosureAt(event.row, event.column) === undefined) {
|
|
2847
|
+
this.#disclosurePress = undefined;
|
|
2848
|
+
this.#selection.move(event);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2306
2851
|
else {
|
|
2852
|
+
const target = this.#pressedDisclosureAt(event.row, event.column);
|
|
2853
|
+
this.#disclosurePress = undefined;
|
|
2854
|
+
if (!event.shift && target !== undefined) {
|
|
2855
|
+
this.#selection.clear();
|
|
2856
|
+
this.#activateDisclosure(target);
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2307
2859
|
const text = this.#selection.release(event);
|
|
2308
2860
|
if (text !== undefined && this.#copyOnSelect)
|
|
2309
2861
|
void this.#copySelection();
|
|
2310
2862
|
}
|
|
2311
2863
|
this.#render();
|
|
2312
2864
|
}
|
|
2865
|
+
#handleTrajectoryMouse(event) {
|
|
2866
|
+
if (event.button === 'right') {
|
|
2867
|
+
if (event.action === 'press' && this.#selection.active)
|
|
2868
|
+
void this.#copySelection(true);
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
if (event.action === 'press') {
|
|
2872
|
+
this.#trajectoryPress = { row: event.row, column: event.column,
|
|
2873
|
+
text: stripAnsi(this.#renderer.viewport()[event.row] ?? ''), dragged: event.shift };
|
|
2874
|
+
this.#selection.begin(this.#renderer.viewport(), event, Date.now(), event.shift);
|
|
2875
|
+
}
|
|
2876
|
+
else if (event.action === 'drag') {
|
|
2877
|
+
if (this.#trajectoryPress !== undefined)
|
|
2878
|
+
this.#trajectoryPress.dragged = true;
|
|
2879
|
+
this.#selection.move(event);
|
|
2880
|
+
}
|
|
2881
|
+
else {
|
|
2882
|
+
const press = this.#trajectoryPress;
|
|
2883
|
+
this.#trajectoryPress = undefined;
|
|
2884
|
+
const text = this.#selection.release(event);
|
|
2885
|
+
if (text !== undefined) {
|
|
2886
|
+
if (this.#copyOnSelect)
|
|
2887
|
+
void this.#copySelection();
|
|
2888
|
+
}
|
|
2889
|
+
else if (press !== undefined && !press.dragged && !event.shift && press.row === event.row
|
|
2890
|
+
&& Math.abs(press.column - event.column) <= 1
|
|
2891
|
+
&& stripAnsi(this.#renderer.viewport()[event.row] ?? '') === press.text) {
|
|
2892
|
+
if (this.#fileBrowser !== null)
|
|
2893
|
+
this.#fileBrowser.click(event.row, event.column, this.#term.width(), this.#term.height());
|
|
2894
|
+
else if (this.#trajectory !== null)
|
|
2895
|
+
this.#trajectory = trajectoryClick(this.#trajectory, this.#term.width(), this.#term.height(), event.row, event.column);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
this.#render();
|
|
2899
|
+
}
|
|
2900
|
+
#applyTrajectoryInput(event) {
|
|
2901
|
+
const state = this.#trajectory;
|
|
2902
|
+
if (state === null)
|
|
2903
|
+
return;
|
|
2904
|
+
const { pageSize } = trajectoryListMetrics(state, this.#term.height(), this.#term.width());
|
|
2905
|
+
const { pageLines } = trajectoryDetailMetrics(state, this.#term.height());
|
|
2906
|
+
const theme = createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks);
|
|
2907
|
+
const maxDetailScroll = trajectoryDetailScrollLimit(state, theme, this.#term.width(), this.#term.height());
|
|
2908
|
+
const command = applyTrajectoryEvent(state, event, { pageSize, detailPageLines: pageLines, width: this.#term.width(), maxDetailScroll });
|
|
2909
|
+
if ('close' in command) {
|
|
2910
|
+
this.#trajectory = null;
|
|
2911
|
+
this.#trajectoryPress = undefined;
|
|
2912
|
+
}
|
|
2913
|
+
else if ('copy' in command) {
|
|
2914
|
+
void this.#deliverCopy(command.copy).then(delivery => {
|
|
2915
|
+
if (this.#trajectory === state) {
|
|
2916
|
+
this.#trajectory = { ...state, notice: delivery === 'requested' ? 'Sent to terminal clipboard' : `Copied ${command.label}` };
|
|
2917
|
+
this.#render();
|
|
2918
|
+
}
|
|
2919
|
+
}, () => {
|
|
2920
|
+
if (this.#trajectory === state) {
|
|
2921
|
+
this.#trajectory = { ...state, notice: 'Copy failed' };
|
|
2922
|
+
this.#render();
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
else
|
|
2927
|
+
this.#trajectory = command.state;
|
|
2928
|
+
this.#render();
|
|
2929
|
+
}
|
|
2313
2930
|
/** Keep older clipboard operations from finishing after a newer user copy. */
|
|
2314
2931
|
#deliverCopy(text) {
|
|
2315
2932
|
const task = this.#copyTask.then(() => {
|
|
@@ -2320,7 +2937,7 @@ export class LocalTui {
|
|
|
2320
2937
|
this.#copyTask = task.catch(() => { });
|
|
2321
2938
|
return task;
|
|
2322
2939
|
}
|
|
2323
|
-
async #copySelection() {
|
|
2940
|
+
async #copySelection(explicit = false) {
|
|
2324
2941
|
if (!this.#selection.valid(this.#renderer.viewport())) {
|
|
2325
2942
|
this.#selection.clear();
|
|
2326
2943
|
return;
|
|
@@ -2329,11 +2946,99 @@ export class LocalTui {
|
|
|
2329
2946
|
if (text === undefined)
|
|
2330
2947
|
return;
|
|
2331
2948
|
try {
|
|
2332
|
-
await this.#deliverCopy(text);
|
|
2949
|
+
const delivery = await this.#deliverCopy(text);
|
|
2950
|
+
if (explicit && !this.#disposed) {
|
|
2951
|
+
this.#showTransientStatus(delivery === 'requested' ? 'Selection sent to terminal clipboard' : 'Copied selection');
|
|
2952
|
+
this.#render();
|
|
2953
|
+
}
|
|
2333
2954
|
}
|
|
2334
2955
|
catch {
|
|
2335
|
-
if (!this.#disposed)
|
|
2336
|
-
this
|
|
2956
|
+
if (!this.#disposed) {
|
|
2957
|
+
this.#showTransientStatus('Copy failed');
|
|
2958
|
+
this.#render();
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
#canQuoteSelection() {
|
|
2963
|
+
return this.#prompt === null && this.#settings === null && this.#trajectory === null && this.#fileBrowser === null && this.#search === null
|
|
2964
|
+
&& this.#transcriptSearch === null && (this.#inspected === undefined || this.#inspected.writable === true);
|
|
2965
|
+
}
|
|
2966
|
+
#quoteSelection() {
|
|
2967
|
+
if (!this.#canQuoteSelection())
|
|
2968
|
+
return;
|
|
2969
|
+
const text = this.#selection.valid(this.#renderer.viewport()) ? this.#selection.text() : undefined;
|
|
2970
|
+
this.#selection.clear();
|
|
2971
|
+
if (text !== undefined) {
|
|
2972
|
+
this.#appendQuote(text.split('\n').map(line => '> ' + line).join('\n'));
|
|
2973
|
+
}
|
|
2974
|
+
this.#render();
|
|
2975
|
+
}
|
|
2976
|
+
#appendQuote(text) {
|
|
2977
|
+
this.#editor.setCursor(this.#editor.text.length);
|
|
2978
|
+
this.#editor.paste((this.#editor.text === '' ? '' : '\n\n') + text + '\n\n');
|
|
2979
|
+
this.#processSelection = undefined;
|
|
2980
|
+
this.#refreshAutocomplete();
|
|
2981
|
+
this.#showTransientStatus('Quoted in composer');
|
|
2982
|
+
this.#render();
|
|
2983
|
+
}
|
|
2984
|
+
/** Explicit process selection wins; scrolling through an open detail follows that tool. */
|
|
2985
|
+
#currentTool() {
|
|
2986
|
+
const detail = this.#fileActionFrame?.transcript?.disclosures?.findLast(target => target.detailEnd !== undefined && this.#scrollStart > target.row && this.#scrollStart < target.detailEnd);
|
|
2987
|
+
const selected = this.#processSelection;
|
|
2988
|
+
const row = selected === undefined ? undefined : this.#blockStarts[selected];
|
|
2989
|
+
const selectedVisible = row !== undefined && row >= this.#scrollStart && row < this.#scrollStart + this.#scrollBudget - 1;
|
|
2990
|
+
const index = selectedVisible ? selected : detail?.block ?? selected ?? this.#viewportBlock();
|
|
2991
|
+
const block = index === undefined ? undefined : this.#state.blocks[index];
|
|
2992
|
+
return block?.kind === 'tool' ? block : undefined;
|
|
2993
|
+
}
|
|
2994
|
+
async #openToolActions(block = this.#currentTool()) {
|
|
2995
|
+
if (this.#fileReviewAbort !== undefined || this.#search !== null || this.#prompt !== null || this.#settings !== null)
|
|
2996
|
+
return;
|
|
2997
|
+
if (block === undefined) {
|
|
2998
|
+
this.#showTransientStatus(uiText(resolveUiLanguage(this.#uiLanguage), 'No tool selected · use {key}', { key: keysForAction(this.#keybindings, 'toggle-tools') }), 4_000);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const command = toolCommand(block);
|
|
3002
|
+
const files = blockFileAction(block);
|
|
3003
|
+
const source = toolSource(block);
|
|
3004
|
+
const options = [
|
|
3005
|
+
...(command === undefined ? [] : [{ label: 'Copy command', value: 'command', description: command.split('\n')[0] ?? '' }]),
|
|
3006
|
+
...(block.output.trim() === '' ? [] : [{ label: 'Copy result', value: 'result' }]),
|
|
3007
|
+
...(files === undefined ? [] : [{ label: 'View file', value: 'files' }]),
|
|
3008
|
+
...(source === undefined ? [] : [{ label: 'Copy source', value: 'source-copy', description: source.label },
|
|
3009
|
+
...(this.#canQuoteSelection() ? [{ label: 'Quote source', value: 'source-quote', description: source.label }] : [])]),
|
|
3010
|
+
];
|
|
3011
|
+
if (options.length === 0) {
|
|
3012
|
+
this.#showTransientStatus('No content available for this tool.', 4_000);
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
const abort = new AbortController();
|
|
3016
|
+
this.#fileReviewAbort = abort;
|
|
3017
|
+
try {
|
|
3018
|
+
const answer = await this.prompt({ localize: ['title'], title: 'Tool actions', question: toolActionContext(block),
|
|
3019
|
+
options: options.map(option => ({ ...option, label: uiText(resolveUiLanguage(this.#uiLanguage), option.label) })), allowCustom: false, notify: false, signal: abort.signal, onSuperseded: () => { abort.abort(); } });
|
|
3020
|
+
if (abort.signal.aborted || this.#disposed)
|
|
3021
|
+
return;
|
|
3022
|
+
if (answer === 'command' && command !== undefined)
|
|
3023
|
+
await this.#copyPicked(command, 'command');
|
|
3024
|
+
else if (answer === 'result')
|
|
3025
|
+
await this.#copyPicked(block.output, 'tool result');
|
|
3026
|
+
else if (answer === 'source-copy' && source !== undefined)
|
|
3027
|
+
await this.#copyPicked(source.text, 'source');
|
|
3028
|
+
else if (answer === 'source-quote' && source !== undefined && this.#canQuoteSelection())
|
|
3029
|
+
this.#appendQuote(sourceQuote(source));
|
|
3030
|
+
else if (answer === 'files' && files !== undefined) {
|
|
3031
|
+
this.#fileReviewAbort = undefined;
|
|
3032
|
+
this.#openFileAction(files);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
catch (error) {
|
|
3036
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
3037
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
3038
|
+
}
|
|
3039
|
+
finally {
|
|
3040
|
+
if (this.#fileReviewAbort === abort)
|
|
3041
|
+
this.#fileReviewAbort = undefined;
|
|
2337
3042
|
}
|
|
2338
3043
|
}
|
|
2339
3044
|
#openFileAction(action, initial) {
|
|
@@ -2382,10 +3087,23 @@ export class LocalTui {
|
|
|
2382
3087
|
this.#handleMouse(event);
|
|
2383
3088
|
return;
|
|
2384
3089
|
}
|
|
3090
|
+
this.#promptActionPress = undefined;
|
|
3091
|
+
this.#promptChoicePress = undefined;
|
|
2385
3092
|
this.#jumpPress = undefined;
|
|
3093
|
+
this.#disclosurePress = undefined;
|
|
3094
|
+
this.#mouseActionPress = undefined;
|
|
3095
|
+
if (this.#keyTest !== undefined && this.#prompt?.request.signal === this.#keyTest.signal
|
|
3096
|
+
&& (event.type === 'text' || event.type === 'paste-start' || event.type === 'paste-end' || event.type === 'key' && !['escape', 'ctrl+c'].includes(event.id))) {
|
|
3097
|
+
this.#keyTest.observe(event);
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
2386
3100
|
if (this.#selection.active) {
|
|
3101
|
+
if (event.type === 'key' && this.#boundAction(event.id) === 'quote-selection') {
|
|
3102
|
+
this.#quoteSelection();
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
2387
3105
|
if (event.type === 'key' && (['super+c', 'enter'].includes(event.id) || this.#boundAction(event.id) === 'copy-prompt')) {
|
|
2388
|
-
void this.#copySelection();
|
|
3106
|
+
void this.#copySelection(true);
|
|
2389
3107
|
return;
|
|
2390
3108
|
}
|
|
2391
3109
|
this.#selection.clear();
|
|
@@ -2396,32 +3114,37 @@ export class LocalTui {
|
|
|
2396
3114
|
}
|
|
2397
3115
|
}
|
|
2398
3116
|
if (event.type === 'wheel') {
|
|
3117
|
+
if (this.#fileBrowser !== null) {
|
|
3118
|
+
this.#applyFileBrowserInput(event);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
if (this.#trajectory !== null) {
|
|
3122
|
+
this.#applyTrajectoryInput(event);
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
2399
3125
|
this.#jumpPress = undefined;
|
|
2400
3126
|
if (this.#mouseTracking && !this.#paste && this.#pasteInFlight === 0) {
|
|
2401
3127
|
const step = wheelStep(this.#wheelGesture, Date.now(), event.direction === 'up' ? -1 : 1, !this.#wheelAccelerates);
|
|
2402
3128
|
this.#wheelGesture = step.gesture;
|
|
2403
|
-
if (step.lines !== 0)
|
|
2404
|
-
this.#
|
|
3129
|
+
if (step.lines !== 0) {
|
|
3130
|
+
if (this.#prompt !== null && this.#prompt.request.presentation !== 'document' && this.#prompt.request.presentation !== 'plan-review') {
|
|
3131
|
+
if (this.#prompt.request.wait !== undefined && this.#prompt.waitHeld !== true) {
|
|
3132
|
+
this.#prompt.request.wait.hold();
|
|
3133
|
+
this.#prompt.offTick?.();
|
|
3134
|
+
this.#prompt = { ...this.#prompt, waitHeld: true };
|
|
3135
|
+
}
|
|
3136
|
+
const count = filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed).length;
|
|
3137
|
+
this.#prompt = movePromptSelection(this.#prompt, Math.max(0, Math.min(count - 1, this.#prompt.selected + step.lines)), count);
|
|
3138
|
+
this.#render();
|
|
3139
|
+
}
|
|
3140
|
+
else if (this.#documentMouseEnabled())
|
|
3141
|
+
this.#scrollPromptDocument(this.#promptDocument.start + step.lines);
|
|
3142
|
+
else
|
|
3143
|
+
this.#scrollBy(step.lines);
|
|
3144
|
+
}
|
|
2405
3145
|
}
|
|
2406
3146
|
return;
|
|
2407
3147
|
}
|
|
2408
|
-
if (this.#state.status === 'compacting') {
|
|
2409
|
-
if (event.type === 'key' && event.id === 'end' && this.#jumpToLatest !== undefined) {
|
|
2410
|
-
this.#followTail();
|
|
2411
|
-
this.#render();
|
|
2412
|
-
return;
|
|
2413
|
-
}
|
|
2414
|
-
if (event.type === 'key' && event.id === 'pageUp') {
|
|
2415
|
-
this.#scrollBy(-this.#pageSize());
|
|
2416
|
-
return;
|
|
2417
|
-
}
|
|
2418
|
-
if (event.type === 'key' && event.id === 'pageDown') {
|
|
2419
|
-
this.#scrollBy(this.#pageSize());
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2422
|
-
if (event.type !== 'key' || event.id !== 'ctrl+c')
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
3148
|
// Clipboard image inspection is asynchronous. Preserve the exact key
|
|
2426
3149
|
// order so a fast Ctrl+V, Enter submits the finished image draft rather
|
|
2427
3150
|
// than an empty prompt.
|
|
@@ -2449,6 +3172,8 @@ export class LocalTui {
|
|
|
2449
3172
|
return;
|
|
2450
3173
|
}
|
|
2451
3174
|
if (event.type === 'paste-start') {
|
|
3175
|
+
if (this.#prompt === null && this.#settings === null && this.#trajectory === null && this.#fileBrowser === null)
|
|
3176
|
+
this.#processSelection = undefined;
|
|
2452
3177
|
this.#paste = true;
|
|
2453
3178
|
this.#pasteBuf = '';
|
|
2454
3179
|
return;
|
|
@@ -2466,15 +3191,12 @@ export class LocalTui {
|
|
|
2466
3191
|
}
|
|
2467
3192
|
if (this.#handlePrompt(event))
|
|
2468
3193
|
return;
|
|
3194
|
+
if (this.#fileBrowser !== null) {
|
|
3195
|
+
this.#applyFileBrowserInput(event);
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
2469
3198
|
if (this.#trajectory !== null) {
|
|
2470
|
-
|
|
2471
|
-
const { pageLines } = trajectoryDetailMetrics(this.#trajectory, this.#term.height());
|
|
2472
|
-
const command = applyTrajectoryEvent(this.#trajectory, event, { pageSize, detailPageLines: pageLines });
|
|
2473
|
-
if ('close' in command)
|
|
2474
|
-
this.#trajectory = null;
|
|
2475
|
-
else
|
|
2476
|
-
this.#trajectory = command.state;
|
|
2477
|
-
this.#render();
|
|
3199
|
+
this.#applyTrajectoryInput(event);
|
|
2478
3200
|
return;
|
|
2479
3201
|
}
|
|
2480
3202
|
if (this.#transcriptSearch !== null) {
|
|
@@ -2500,6 +3222,8 @@ export class LocalTui {
|
|
|
2500
3222
|
this.#applySettings(event);
|
|
2501
3223
|
return;
|
|
2502
3224
|
}
|
|
3225
|
+
if (this.#search === null && this.#handleProcessNavigation(event))
|
|
3226
|
+
return;
|
|
2503
3227
|
/**
|
|
2504
3228
|
* The side pane is a companion, not a mode: it answers where the reader is
|
|
2505
3229
|
* already working, so it must not take keys the composer is using. It reads
|
|
@@ -2588,10 +3312,7 @@ export class LocalTui {
|
|
|
2588
3312
|
return true;
|
|
2589
3313
|
}
|
|
2590
3314
|
if (this.#state.status !== 'idle') {
|
|
2591
|
-
//
|
|
2592
|
-
// hands those drafts back first, so nothing typed during the turn is lost
|
|
2593
|
-
// to the abort that ends it.
|
|
2594
|
-
this.#restoreQueuedBeforeAbort();
|
|
3315
|
+
// Harness retains accepted input; stopping a turn does not withdraw messages.
|
|
2595
3316
|
for (const listener of this.#interrupts)
|
|
2596
3317
|
listener();
|
|
2597
3318
|
return true;
|
|
@@ -2629,7 +3350,7 @@ export class LocalTui {
|
|
|
2629
3350
|
}
|
|
2630
3351
|
// A running Shell line owns Ctrl+C before the composer does: the reader is
|
|
2631
3352
|
// watching a command, and the key they press to stop it is the same one.
|
|
2632
|
-
if (this.#shellAbort !== undefined && !this.#hasComposerContent()) {
|
|
3353
|
+
if (this.#inspected === undefined && this.#shellAbort !== undefined && !this.#hasComposerContent()) {
|
|
2633
3354
|
this.#shellAbort();
|
|
2634
3355
|
return true;
|
|
2635
3356
|
}
|
|
@@ -2658,7 +3379,6 @@ export class LocalTui {
|
|
|
2658
3379
|
// A running turn keeps Ctrl+C as the familiar abort while the composer is
|
|
2659
3380
|
// empty; Escape already covers it, and refusing the reflex would be worse.
|
|
2660
3381
|
if (this.#state.status !== 'idle' && !this.#hasComposerContent()) {
|
|
2661
|
-
this.#restoreQueuedBeforeAbort();
|
|
2662
3382
|
for (const listener of this.#interrupts)
|
|
2663
3383
|
listener();
|
|
2664
3384
|
return true;
|
|
@@ -2666,20 +3386,6 @@ export class LocalTui {
|
|
|
2666
3386
|
this.#clearComposerWithUndo();
|
|
2667
3387
|
return true;
|
|
2668
3388
|
}
|
|
2669
|
-
/**
|
|
2670
|
-
* Hand durable follow-ups back to the composer before a turn is abandoned.
|
|
2671
|
-
*
|
|
2672
|
-
* The queued messages would otherwise be delivered to a turn that will never
|
|
2673
|
-
* produce the step they were aimed at, which silently loses them.
|
|
2674
|
-
*/
|
|
2675
|
-
#restoreQueuedBeforeAbort() {
|
|
2676
|
-
if (!this.#state.nextTurnInbox.some(message => message.source.kind === 'user'))
|
|
2677
|
-
return;
|
|
2678
|
-
if (this.#hasComposerContent())
|
|
2679
|
-
return;
|
|
2680
|
-
for (const listener of this.#queueEdits)
|
|
2681
|
-
listener();
|
|
2682
|
-
}
|
|
2683
3389
|
#handlePrompt(event) {
|
|
2684
3390
|
let prompt = this.#prompt;
|
|
2685
3391
|
if (prompt === null)
|
|
@@ -2727,13 +3433,15 @@ export class LocalTui {
|
|
|
2727
3433
|
const selected = filteredPromptOptions(prompt.request, '', prompt.collapsed)[prompt.selected];
|
|
2728
3434
|
if (action !== undefined && (action.scope === 'list' || selected !== undefined)) {
|
|
2729
3435
|
if (!disarmed) {
|
|
2730
|
-
this.#finishPrompt(action.valuePrefix + (action.scope === 'list' ? '' : selected.value ?? selected.label));
|
|
3436
|
+
this.#finishPrompt(action.valuePrefix + (action.scope === 'list' ? '' : selected.value ?? selected.label), 'action');
|
|
2731
3437
|
this.#render();
|
|
2732
3438
|
}
|
|
2733
3439
|
return true;
|
|
2734
3440
|
}
|
|
2735
3441
|
}
|
|
2736
|
-
|
|
3442
|
+
const toggleAnswer = prompt.request.answerFormat === 'json' && event.type === 'key' && event.id === 'ctrl+g';
|
|
3443
|
+
const toggleSpace = event.type === 'text' && event.value === ' ' && (prompt.request.answerFormat !== 'json' || this.#editor.text === '');
|
|
3444
|
+
if (prompt.request.multiSelect === true && (toggleAnswer || toggleSpace)) {
|
|
2737
3445
|
this.#prompt = togglePromptSelection(prompt, this.#editor.text);
|
|
2738
3446
|
this.#render();
|
|
2739
3447
|
return true;
|
|
@@ -2755,14 +3463,11 @@ export class LocalTui {
|
|
|
2755
3463
|
const filtered = filteredPromptOptions(prompt.request, this.#editor.text, prompt.collapsed);
|
|
2756
3464
|
const count = filtered.length;
|
|
2757
3465
|
const keyAction = prompt.request.actions?.find(item => item.key.toLowerCase() === event.id.toLowerCase());
|
|
2758
|
-
if (keyAction !== undefined && (keyAction.scope === 'list'
|
|
2759
|
-
|| prompt.request.presentation === 'tree'
|
|
2760
|
-
|| prompt.request.presentation === 'card')) {
|
|
3466
|
+
if (keyAction !== undefined && (keyAction.scope === 'list' || filtered[prompt.selected] !== undefined)) {
|
|
2761
3467
|
if (!disarmed) {
|
|
2762
3468
|
const selected = filtered[prompt.selected];
|
|
2763
3469
|
if (keyAction.scope === 'list' || selected !== undefined) {
|
|
2764
|
-
this.#
|
|
2765
|
-
this.#finishPrompt(keyAction.valuePrefix + (keyAction.scope === 'list' ? '' : selected.value ?? selected.label));
|
|
3470
|
+
this.#finishPrompt(keyAction.valuePrefix + (keyAction.scope === 'list' ? '' : selected.value ?? selected.label), 'action');
|
|
2766
3471
|
this.#render();
|
|
2767
3472
|
}
|
|
2768
3473
|
}
|
|
@@ -2821,7 +3526,7 @@ export class LocalTui {
|
|
|
2821
3526
|
// the other lists about what `tab` or `pageUp` means. `enter` stays below
|
|
2822
3527
|
// because what it submits depends on the editor and on multi-select.
|
|
2823
3528
|
const page = prompt.request.presentation === 'tree'
|
|
2824
|
-
? promptTreePageSize(prompt.request, this.#term.width(), this.#term.height()) : 10;
|
|
3529
|
+
? promptTreePageSize(prompt.request, this.#term.width(), this.#term.height(), resolveUiLanguage(this.#uiLanguage)) : 10;
|
|
2825
3530
|
const navigation = listNavigation(event);
|
|
2826
3531
|
if (navigation.kind === 'move' || navigation.kind === 'page') {
|
|
2827
3532
|
// The tree clamps: its rows are a structure, and wrapping past the last
|
|
@@ -2838,11 +3543,10 @@ export class LocalTui {
|
|
|
2838
3543
|
}
|
|
2839
3544
|
if (event.id === 'enter' && (this.#editor.text === '' || prompt.request.multiSelect === true)) {
|
|
2840
3545
|
const answer = selectedPromptAnswer(prompt);
|
|
2841
|
-
//
|
|
2842
|
-
|
|
2843
|
-
if (answer === null)
|
|
3546
|
+
// Supplementary text can answer a question even with no checked options.
|
|
3547
|
+
if (answer === null && !(prompt.request.answerFormat === 'json' && this.#editor.expandedText.trim()))
|
|
2844
3548
|
return true;
|
|
2845
|
-
this.#finishPrompt(answer);
|
|
3549
|
+
this.#finishPrompt(answer ?? '', 'selection');
|
|
2846
3550
|
this.#render();
|
|
2847
3551
|
return true;
|
|
2848
3552
|
}
|
|
@@ -2852,7 +3556,6 @@ export class LocalTui {
|
|
|
2852
3556
|
// closing on it would lose both the list and the filter.
|
|
2853
3557
|
if (answer === null)
|
|
2854
3558
|
return true;
|
|
2855
|
-
this.#editor.setText('');
|
|
2856
3559
|
this.#finishPrompt(answer);
|
|
2857
3560
|
this.#render();
|
|
2858
3561
|
return true;
|
|
@@ -2882,7 +3585,7 @@ export class LocalTui {
|
|
|
2882
3585
|
}
|
|
2883
3586
|
return true;
|
|
2884
3587
|
}
|
|
2885
|
-
if (!document && event.type === 'key' && (event.id === 'ctrl+x' || this.#boundAction(event.id) === 'external-editor')) {
|
|
3588
|
+
if (!document && prompt.request.allowCustom !== false && event.type === 'key' && (event.id === 'ctrl+x' || this.#boundAction(event.id) === 'external-editor')) {
|
|
2886
3589
|
this.#openPlanFeedbackEditor(prompt);
|
|
2887
3590
|
return true;
|
|
2888
3591
|
}
|
|
@@ -2945,13 +3648,7 @@ export class LocalTui {
|
|
|
2945
3648
|
else if (event.id === 'end')
|
|
2946
3649
|
documentScroll = prompt.request.documentTail === true ? Number.POSITIVE_INFINITY : scroll?.maxStart ?? prompt.documentScroll ?? 0;
|
|
2947
3650
|
if (documentScroll !== undefined) {
|
|
2948
|
-
this.#
|
|
2949
|
-
...prompt,
|
|
2950
|
-
documentAnchor: undefined,
|
|
2951
|
-
documentTarget: undefined,
|
|
2952
|
-
documentScroll: documentScroll === Number.POSITIVE_INFINITY ? documentScroll : Math.max(0, Math.min(documentScroll, scroll?.maxStart ?? Number.POSITIVE_INFINITY)),
|
|
2953
|
-
};
|
|
2954
|
-
this.#render();
|
|
3651
|
+
this.#scrollPromptDocument(documentScroll);
|
|
2955
3652
|
return true;
|
|
2956
3653
|
}
|
|
2957
3654
|
if (event.id === 'tab' || event.id === 'right') {
|
|
@@ -2972,7 +3669,7 @@ export class LocalTui {
|
|
|
2972
3669
|
return true;
|
|
2973
3670
|
const approve = prompt.request.approveValue ?? prompt.request.options?.[0]?.value
|
|
2974
3671
|
?? prompt.request.options?.[0]?.label;
|
|
2975
|
-
if (document || answer === approve) {
|
|
3672
|
+
if (document || answer === approve || prompt.request.allowCustom === false) {
|
|
2976
3673
|
this.#finishPrompt(answer);
|
|
2977
3674
|
}
|
|
2978
3675
|
else {
|
|
@@ -2993,7 +3690,11 @@ export class LocalTui {
|
|
|
2993
3690
|
* something up in it.
|
|
2994
3691
|
*/
|
|
2995
3692
|
#handleRenderedDocument(event, prompt) {
|
|
2996
|
-
const rows = () =>
|
|
3693
|
+
const rows = () => {
|
|
3694
|
+
const theme = createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks);
|
|
3695
|
+
const width = Math.max(1, this.#term.width() - 6);
|
|
3696
|
+
return prompt.renderDocument?.(theme, width) ?? documentRows(prompt.request, theme, width);
|
|
3697
|
+
};
|
|
2997
3698
|
// Always build on the newest state: typing updates the query while the find
|
|
2998
3699
|
// is open, and jumping from a stale copy would drop it again.
|
|
2999
3700
|
const jumpTo = (row) => {
|
|
@@ -3055,7 +3756,7 @@ export class LocalTui {
|
|
|
3055
3756
|
}
|
|
3056
3757
|
#handleDocumentReader(event, prompt) {
|
|
3057
3758
|
const source = prompt.request.documentSource;
|
|
3058
|
-
const layout = documentLayout(source, createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors), Math.max(1, this.#term.width() - 6), {
|
|
3759
|
+
const layout = documentLayout(source, createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks), Math.max(1, this.#term.width() - 6), {
|
|
3059
3760
|
wordWrap: prompt.documentWordWrap ?? true, column: prompt.documentColumn ?? 0,
|
|
3060
3761
|
});
|
|
3061
3762
|
const start = this.#promptDocument?.start ?? 0;
|
|
@@ -3137,7 +3838,7 @@ export class LocalTui {
|
|
|
3137
3838
|
listener(id);
|
|
3138
3839
|
}
|
|
3139
3840
|
#handleSubagentLauncher(event) {
|
|
3140
|
-
const agents = this.#subagents
|
|
3841
|
+
const agents = rosterItems(this.#subagents);
|
|
3141
3842
|
if (this.#subagentSelection !== undefined) {
|
|
3142
3843
|
if (agents.length === 0) {
|
|
3143
3844
|
this.#subagentSelection = undefined;
|
|
@@ -3146,7 +3847,12 @@ export class LocalTui {
|
|
|
3146
3847
|
const at = Math.min(this.#subagentSelection, agents.length - 1);
|
|
3147
3848
|
if (event.type === 'key' && event.id === 'enter') {
|
|
3148
3849
|
this.#subagentSelection = undefined;
|
|
3149
|
-
|
|
3850
|
+
const item = agents[at];
|
|
3851
|
+
if (item.kind === 'agent')
|
|
3852
|
+
this.#openSelectedSubagent(item.agent);
|
|
3853
|
+
else
|
|
3854
|
+
for (const listener of this.#workflowInspects)
|
|
3855
|
+
listener(item.run.id);
|
|
3150
3856
|
return true;
|
|
3151
3857
|
}
|
|
3152
3858
|
if (event.type === 'key' && event.id === 'escape') {
|
|
@@ -3172,7 +3878,7 @@ export class LocalTui {
|
|
|
3172
3878
|
return false;
|
|
3173
3879
|
if (this.#queuedSubmissions.length > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
3174
3880
|
return false;
|
|
3175
|
-
this.#subagentSelection = Math.min(agents.length - 1, Math.max(0,
|
|
3881
|
+
this.#subagentSelection = Math.min(agents.length - 1, Math.max(0, rosterPreviewStart(this.#subagents)));
|
|
3176
3882
|
this.#render();
|
|
3177
3883
|
return true;
|
|
3178
3884
|
}
|
|
@@ -3216,11 +3922,15 @@ export class LocalTui {
|
|
|
3216
3922
|
const previous = options.persist ? this.prefs() : undefined;
|
|
3217
3923
|
const version = this.#prefsVersion;
|
|
3218
3924
|
const previousMotion = this.#motion;
|
|
3925
|
+
this.#uiLanguage = prefs.uiLanguage ?? 'auto';
|
|
3926
|
+
if (this.#prompt !== null)
|
|
3927
|
+
this.#prompt = { ...this.#prompt, request: localizePrompt(this.#prompt.sourceRequest, resolveUiLanguage(this.#uiLanguage)) };
|
|
3219
3928
|
this.#themeName = prefs.theme;
|
|
3220
3929
|
this.#colors = prefs.colors;
|
|
3221
3930
|
this.#syncColorDepth();
|
|
3222
3931
|
this.#motion = prefs.motion ?? 'full';
|
|
3223
3932
|
this.#flushPasteBurst(true);
|
|
3933
|
+
this.#busyEnter = prefs.busyEnter ?? 'queue';
|
|
3224
3934
|
this.#pasteProtection = prefs.pasteProtection !== false;
|
|
3225
3935
|
this.#externalEditor = prefs.editor ?? 'auto';
|
|
3226
3936
|
this.#terminalTitle = prefs.terminalTitle ?? 'status';
|
|
@@ -3229,7 +3939,7 @@ export class LocalTui {
|
|
|
3229
3939
|
this.#copyOnSelect = prefs.copyOnSelect ?? true;
|
|
3230
3940
|
this.#mathMode = prefs.mathMode ?? 'auto';
|
|
3231
3941
|
this.#mermaidMode = prefs.mermaidMode ?? 'auto';
|
|
3232
|
-
this.#hideThinking = prefs.hideThinking
|
|
3942
|
+
this.#hideThinking = prefs.hideThinking !== false;
|
|
3233
3943
|
this.#mouseInteraction = prefs.mouseInteraction ?? 'auto';
|
|
3234
3944
|
this.#checkUpdates = prefs.checkUpdates ?? true;
|
|
3235
3945
|
this.#startupChangelog = prefs.startupChangelog ?? 'summary';
|
|
@@ -3334,6 +4044,10 @@ export class LocalTui {
|
|
|
3334
4044
|
if (command.kind === 'open-command') {
|
|
3335
4045
|
if (this.#prefsSaveTask !== undefined)
|
|
3336
4046
|
return;
|
|
4047
|
+
if (command.name === 'manage-goal') {
|
|
4048
|
+
this.#openGoal();
|
|
4049
|
+
return;
|
|
4050
|
+
}
|
|
3337
4051
|
if (command.name === 'keybindings') {
|
|
3338
4052
|
void this.#editKeybindings();
|
|
3339
4053
|
return;
|
|
@@ -3514,7 +4228,7 @@ export class LocalTui {
|
|
|
3514
4228
|
return;
|
|
3515
4229
|
}
|
|
3516
4230
|
const commands = this.#commands();
|
|
3517
|
-
const slashResult = slashSuggestions(this.#editor.text, this.#editor.cursor, commands);
|
|
4231
|
+
const slashResult = slashSuggestions(this.#editor.text, this.#editor.cursor, commands, resolveUiLanguage(this.#uiLanguage));
|
|
3518
4232
|
if (slashResult !== null) {
|
|
3519
4233
|
this.#setAutocomplete(slashResult);
|
|
3520
4234
|
return;
|
|
@@ -3653,11 +4367,6 @@ export class LocalTui {
|
|
|
3653
4367
|
return;
|
|
3654
4368
|
}
|
|
3655
4369
|
if (command.kind === 'submit') {
|
|
3656
|
-
if (this.#inspected !== undefined) {
|
|
3657
|
-
if (this.#inspected.writable === true)
|
|
3658
|
-
this.#submitInspect(command.text);
|
|
3659
|
-
return;
|
|
3660
|
-
}
|
|
3661
4370
|
this.#submit(command.text);
|
|
3662
4371
|
return;
|
|
3663
4372
|
}
|
|
@@ -3713,8 +4422,8 @@ export class LocalTui {
|
|
|
3713
4422
|
#toggleThinking() {
|
|
3714
4423
|
const next = !this.#hideThinking;
|
|
3715
4424
|
this.#hideThinking = next;
|
|
3716
|
-
|
|
3717
|
-
|
|
4425
|
+
this.#expandedReasoning.clear();
|
|
4426
|
+
this.#closedReasoning.clear();
|
|
3718
4427
|
const persist = this.#persistPrefs;
|
|
3719
4428
|
if (persist !== null)
|
|
3720
4429
|
void persist(this.prefs(), [{ op: 'set', path: ['hideThinking'], value: next }]);
|
|
@@ -3749,7 +4458,22 @@ export class LocalTui {
|
|
|
3749
4458
|
this.#refreshAutocomplete();
|
|
3750
4459
|
this.#render();
|
|
3751
4460
|
}
|
|
4461
|
+
#cancelQueueRecall() {
|
|
4462
|
+
this.#queueRecall?.abort.abort();
|
|
4463
|
+
this.#queueRecall = undefined;
|
|
4464
|
+
this.#queueEditPending = false;
|
|
4465
|
+
}
|
|
4466
|
+
#requestQueueRecall() {
|
|
4467
|
+
const abort = new AbortController();
|
|
4468
|
+
const request = { signal: abort.signal, ...(this.#draftScope === undefined ? {} : { sessionId: this.#draftScope }) };
|
|
4469
|
+
this.#queueRecall = { abort, request };
|
|
4470
|
+
this.#queueEditPending = true;
|
|
4471
|
+
for (const listener of this.#queueEdits)
|
|
4472
|
+
listener(request);
|
|
4473
|
+
}
|
|
3752
4474
|
#restoreLatestQueuedSubmission(allowNonEmpty = false) {
|
|
4475
|
+
if (this.#inspected !== undefined && !this.#inspected.writable)
|
|
4476
|
+
return false;
|
|
3753
4477
|
if (this.#queueEditPending)
|
|
3754
4478
|
return true;
|
|
3755
4479
|
if (this.#queueEditNewer !== null) {
|
|
@@ -3762,9 +4486,7 @@ export class LocalTui {
|
|
|
3762
4486
|
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
3763
4487
|
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
3764
4488
|
return true;
|
|
3765
|
-
this.#
|
|
3766
|
-
for (const listener of this.#queueEdits)
|
|
3767
|
-
listener();
|
|
4489
|
+
this.#requestQueueRecall();
|
|
3768
4490
|
return true;
|
|
3769
4491
|
}
|
|
3770
4492
|
if (this.#historyIndex !== 0)
|
|
@@ -3781,10 +4503,7 @@ export class LocalTui {
|
|
|
3781
4503
|
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
3782
4504
|
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
3783
4505
|
return false;
|
|
3784
|
-
this.#
|
|
3785
|
-
this.#queueEditPending = true;
|
|
3786
|
-
for (const listener of this.#queueEdits)
|
|
3787
|
-
listener();
|
|
4506
|
+
this.#requestQueueRecall();
|
|
3788
4507
|
return true;
|
|
3789
4508
|
}
|
|
3790
4509
|
#historyNext() {
|
|
@@ -3826,7 +4545,8 @@ export class LocalTui {
|
|
|
3826
4545
|
const images = this.#images.copies();
|
|
3827
4546
|
const files = this.#files.copies();
|
|
3828
4547
|
const typedText = this.#files.text(images.length > 0 ? text.trim() : text);
|
|
3829
|
-
const segments = images.length === 0 ? []
|
|
4548
|
+
const segments = images.length === 0 && files.length === 0 ? []
|
|
4549
|
+
: splitComposerAttachmentMarkers(text.trim(), images, this.#files.snapshot(text).entries.map(item => item.marker));
|
|
3830
4550
|
// Stripping a marker that sat on its own line leaves that line break behind.
|
|
3831
4551
|
const submittedText = images.length > 0 ? stripComposerImageMarkers(typedText, images).trim() : typedText;
|
|
3832
4552
|
const literal = text === this.#editor.expandedText && (this.#literalInput || this.#editor.startsWithPaste);
|
|
@@ -3842,19 +4562,48 @@ export class LocalTui {
|
|
|
3842
4562
|
historyText,
|
|
3843
4563
|
};
|
|
3844
4564
|
}
|
|
3845
|
-
#
|
|
4565
|
+
#dispatchInput(submission) {
|
|
4566
|
+
const receipt = this.#pendingInputs.identify(submission, this.#pendingScope());
|
|
4567
|
+
receipt.recovered = false;
|
|
4568
|
+
if (this.#inspected !== undefined) {
|
|
4569
|
+
this.#pendingInputs.take(submission, this.#pendingScope());
|
|
4570
|
+
for (const listener of this.#inspectSubmits)
|
|
4571
|
+
listener(submission);
|
|
4572
|
+
}
|
|
4573
|
+
else if (this.#pending !== null) {
|
|
4574
|
+
const pending = this.#pending;
|
|
4575
|
+
this.#pending = null;
|
|
4576
|
+
pending.offAbort?.();
|
|
4577
|
+
this.#pendingInputs.take(submission, this.#pendingScope());
|
|
4578
|
+
pending.resolve(submission);
|
|
4579
|
+
}
|
|
4580
|
+
else
|
|
4581
|
+
this.#queuedSubmissions.push(submission);
|
|
4582
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4583
|
+
}
|
|
4584
|
+
#submit(text, delivery) {
|
|
3846
4585
|
if (this.#prompt !== null) {
|
|
3847
4586
|
if (this.#prompt.request.presentation === 'progress')
|
|
3848
4587
|
return;
|
|
3849
|
-
this.#editor.setText('');
|
|
3850
4588
|
const answer = this.#prompt.request.preserveWhitespace === true ? text : text.trim();
|
|
3851
4589
|
this.#finishPrompt(answer.trim() === '' ? null : answer);
|
|
3852
4590
|
this.#render();
|
|
3853
4591
|
return;
|
|
3854
4592
|
}
|
|
4593
|
+
if (this.#inspected !== undefined) {
|
|
4594
|
+
this.#submitInspect(text, delivery);
|
|
4595
|
+
return;
|
|
4596
|
+
}
|
|
3855
4597
|
const { submission, historyText } = this.#buildSubmission(text);
|
|
3856
|
-
const { images, files = [],
|
|
4598
|
+
const { images, files = [], text: submittedText } = submission;
|
|
3857
4599
|
const queueEditNewer = this.#queueEditNewer;
|
|
4600
|
+
// Commands keep their own routing; recalled follow-ups remain next-turn messages.
|
|
4601
|
+
if (delivery === 'steer')
|
|
4602
|
+
submission.literal = true;
|
|
4603
|
+
const command = !submission.literal && (submittedText.trimStart().startsWith('/') || (images.length === 0 && files.length === 0 && submittedText.startsWith('!')));
|
|
4604
|
+
if (!command && this.#state.status === 'running') {
|
|
4605
|
+
submission.delivery = queueEditNewer !== null || this.#queueEditPending ? 'queue' : delivery ?? this.#busyEnter;
|
|
4606
|
+
}
|
|
3858
4607
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
3859
4608
|
this.#history.push(historyText);
|
|
3860
4609
|
this.#historyStore?.add(historyText);
|
|
@@ -3863,11 +4612,10 @@ export class LocalTui {
|
|
|
3863
4612
|
this.#draft = '';
|
|
3864
4613
|
this.#literalInput = false;
|
|
3865
4614
|
this.#queueEditNewer = null;
|
|
3866
|
-
this.#
|
|
4615
|
+
this.#cancelQueueRecall();
|
|
3867
4616
|
this.#editor.setText('');
|
|
3868
4617
|
this.#images.clear();
|
|
3869
4618
|
this.#files.clear();
|
|
3870
|
-
this.#clearDraft();
|
|
3871
4619
|
this.#ac = null;
|
|
3872
4620
|
this.#search = null;
|
|
3873
4621
|
this.#settings = null;
|
|
@@ -3875,63 +4623,69 @@ export class LocalTui {
|
|
|
3875
4623
|
// Image placeholders are TUI-owned. Mixed image+slash drafts stay a
|
|
3876
4624
|
// submission so restore can keep the original markers; the runner strips
|
|
3877
4625
|
// them only to detect and execute Harness commands.
|
|
3878
|
-
const slash = !literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
4626
|
+
const slash = !submission.literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
3879
4627
|
if (slash !== null) {
|
|
3880
4628
|
if (queueEditNewer !== null)
|
|
3881
4629
|
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3882
4630
|
this.#runSlash(slash.name, slash.args);
|
|
4631
|
+
this.#clearDraft();
|
|
3883
4632
|
return;
|
|
3884
4633
|
}
|
|
3885
4634
|
this.#followTail();
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
this.#
|
|
3890
|
-
pending.resolve(submission);
|
|
3891
|
-
if (queueEditNewer !== null)
|
|
3892
|
-
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3893
|
-
}
|
|
3894
|
-
else if (submittedText !== '' || images.length > 0 || files.length > 0 || queueEditNewer !== null) {
|
|
3895
|
-
if (submittedText !== '' || images.length > 0 || files.length > 0) {
|
|
3896
|
-
this.#queuedSubmissions.push(submission);
|
|
3897
|
-
}
|
|
3898
|
-
if (queueEditNewer !== null)
|
|
3899
|
-
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3900
|
-
}
|
|
4635
|
+
if (submittedText !== '' || images.length > 0 || files.length > 0 || this.#pending !== null)
|
|
4636
|
+
this.#dispatchInput(submission);
|
|
4637
|
+
if (queueEditNewer !== null)
|
|
4638
|
+
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3901
4639
|
if (submittedText !== '' || images.length > 0 || files.length > 0) {
|
|
3902
4640
|
this.#popStashedDraft();
|
|
3903
4641
|
}
|
|
4642
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4643
|
+
this.#clearDraft();
|
|
3904
4644
|
this.#render();
|
|
3905
4645
|
}
|
|
3906
|
-
#submitInspect(text) {
|
|
4646
|
+
#submitInspect(text, delivery) {
|
|
4647
|
+
if (this.#inspected?.writable !== true)
|
|
4648
|
+
return;
|
|
3907
4649
|
const { submission, historyText } = this.#buildSubmission(text);
|
|
4650
|
+
const newer = this.#queueEditNewer;
|
|
4651
|
+
if (delivery === 'steer')
|
|
4652
|
+
submission.literal = true;
|
|
4653
|
+
submission.delivery = newer !== null || this.#queueEditPending ? 'queue' : this.#state.status === 'running' ? delivery ?? this.#busyEnter : 'queue';
|
|
3908
4654
|
const { images, files = [], literal = false, text: submittedText } = submission;
|
|
3909
4655
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
3910
4656
|
this.#history.push(historyText);
|
|
3911
4657
|
this.#historyStore?.add(historyText);
|
|
3912
4658
|
}
|
|
4659
|
+
this.#queueEditNewer = null;
|
|
4660
|
+
this.#cancelQueueRecall();
|
|
3913
4661
|
this.#historyIndex = 0;
|
|
3914
4662
|
this.#draft = '';
|
|
3915
4663
|
this.#literalInput = false;
|
|
3916
4664
|
this.#editor.setText('');
|
|
3917
4665
|
this.#images.clear();
|
|
3918
4666
|
this.#files.clear();
|
|
3919
|
-
this.#clearDraft();
|
|
3920
4667
|
this.#ac = null;
|
|
3921
4668
|
this.#search = null;
|
|
3922
4669
|
const slash = !literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
3923
4670
|
if (slash !== null) {
|
|
4671
|
+
this.#queueEditNewer = newer;
|
|
3924
4672
|
this.#runSlash(slash.name, slash.args);
|
|
3925
4673
|
return;
|
|
3926
4674
|
}
|
|
3927
4675
|
this.#followTail();
|
|
3928
|
-
if (submittedText === '' && images.length === 0 && files.length === 0) {
|
|
4676
|
+
if (submittedText === '' && images.length === 0 && files.length === 0 && !newer?.length) {
|
|
3929
4677
|
this.#render();
|
|
3930
4678
|
return;
|
|
3931
4679
|
}
|
|
3932
4680
|
const { literal: _literal, ...inspectSubmission } = submission;
|
|
3933
|
-
|
|
3934
|
-
|
|
4681
|
+
if (submittedText !== '' || images.length > 0 || files.length > 0) {
|
|
4682
|
+
this.#dispatchInput(inspectSubmission);
|
|
4683
|
+
}
|
|
4684
|
+
for (const draft of newer ?? []) {
|
|
4685
|
+
this.#dispatchInput({ ...draft, delivery: 'queue' });
|
|
4686
|
+
}
|
|
4687
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4688
|
+
this.#clearDraft();
|
|
3935
4689
|
this.#popStashedDraft();
|
|
3936
4690
|
this.#render();
|
|
3937
4691
|
}
|
|
@@ -3949,6 +4703,10 @@ export class LocalTui {
|
|
|
3949
4703
|
}
|
|
3950
4704
|
// Reading controls cover the current view; submitting a command that
|
|
3951
4705
|
// opens one must preserve the same return position as a keyboard action.
|
|
4706
|
+
if (command.name === 'queue' && this.#inspected !== undefined) {
|
|
4707
|
+
this.#runAction('manage-queue');
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
3952
4710
|
if (command.name === 'settings') {
|
|
3953
4711
|
this.#runSettings(args);
|
|
3954
4712
|
return;
|
|
@@ -3966,18 +4724,17 @@ export class LocalTui {
|
|
|
3966
4724
|
return;
|
|
3967
4725
|
}
|
|
3968
4726
|
const raw = '/' + name + (args === '' ? '' : ' ' + args);
|
|
3969
|
-
|
|
3970
|
-
if (pending !== null) {
|
|
3971
|
-
pending.offAbort?.();
|
|
3972
|
-
this.#pending = null;
|
|
3973
|
-
pending.resolve({ text: raw, images: [] });
|
|
3974
|
-
}
|
|
3975
|
-
else {
|
|
3976
|
-
this.#queuedSubmissions.push({ text: raw, images: [] });
|
|
3977
|
-
}
|
|
4727
|
+
this.#dispatchInput({ text: raw, images: [] });
|
|
3978
4728
|
this.#render();
|
|
3979
4729
|
return;
|
|
3980
4730
|
}
|
|
4731
|
+
if (command.name === 'help') {
|
|
4732
|
+
if (args !== '' && args !== 'full')
|
|
4733
|
+
this.notice('Usage: /help [full]');
|
|
4734
|
+
else
|
|
4735
|
+
this.#runHelp(args === 'full');
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
3981
4738
|
this.#followTail();
|
|
3982
4739
|
if (command.name === 'quit') {
|
|
3983
4740
|
this.#render();
|
|
@@ -3998,30 +4755,39 @@ export class LocalTui {
|
|
|
3998
4755
|
this.#runTools();
|
|
3999
4756
|
return;
|
|
4000
4757
|
}
|
|
4001
|
-
|
|
4002
|
-
this.#notice('Usage: /help [full]');
|
|
4003
|
-
this.#render();
|
|
4004
|
-
return;
|
|
4005
|
-
}
|
|
4006
|
-
this.#runHelp(args === 'full');
|
|
4758
|
+
this.#render();
|
|
4007
4759
|
}
|
|
4008
4760
|
#commands() {
|
|
4009
4761
|
const localNames = new Set(BUILTIN_SLASH_COMMANDS.flatMap((command) => [command.name, ...(command.aliases ?? [])]));
|
|
4010
4762
|
const runtime = this.#runtimeCommands
|
|
4011
|
-
.filter((command) => !localNames.has(command.name))
|
|
4012
|
-
.map((command) => ({
|
|
4013
|
-
name: command.name,
|
|
4014
|
-
description: command.description,
|
|
4015
|
-
...(command.inputHint === undefined ? {} : { inputHint: command.inputHint }),
|
|
4016
|
-
...(command.complete === undefined ? {} : { complete: command.complete }),
|
|
4017
|
-
}));
|
|
4763
|
+
.filter((command) => !localNames.has(command.name));
|
|
4018
4764
|
return [...BUILTIN_SLASH_COMMANDS, ...runtime];
|
|
4019
4765
|
}
|
|
4020
|
-
#finishPrompt(answer) {
|
|
4766
|
+
#finishPrompt(answer, kind = 'text') {
|
|
4021
4767
|
const pending = this.#prompt;
|
|
4022
4768
|
if (pending === null)
|
|
4023
4769
|
return;
|
|
4770
|
+
const draft = structuredPromptAnswer(pending, this.#editor.expandedText);
|
|
4771
|
+
try {
|
|
4772
|
+
pending.request.onAnswerDraft?.(draft);
|
|
4773
|
+
}
|
|
4774
|
+
catch (error) {
|
|
4775
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
4776
|
+
}
|
|
4777
|
+
if (answer !== null && pending.request.answerFormat === 'json' && kind !== 'action' && this.#tty) {
|
|
4778
|
+
answer = JSON.stringify(kind === 'selection' ? draft : { selected: [], ...(answer === '' ? {} : { custom: answer }) });
|
|
4779
|
+
}
|
|
4780
|
+
if (pending.browserState !== undefined) {
|
|
4781
|
+
const selected = filteredPromptOptions(pending.request, this.#editor.text, pending.collapsed)[pending.selected];
|
|
4782
|
+
pending.browserState.query = this.#editor.text;
|
|
4783
|
+
if (selected !== undefined)
|
|
4784
|
+
pending.browserState.selected = selected.value ?? selected.label;
|
|
4785
|
+
}
|
|
4024
4786
|
this.#prompt = null;
|
|
4787
|
+
this.#promptActionPress = undefined;
|
|
4788
|
+
this.#promptChoicePress = undefined;
|
|
4789
|
+
this.#selection.clear();
|
|
4790
|
+
this.#wheelGesture = IDLE_WHEEL;
|
|
4025
4791
|
if (pending.request.notify !== false) {
|
|
4026
4792
|
this.#herdr.promptResolved();
|
|
4027
4793
|
this.#syncWindowTitle();
|
|
@@ -4054,11 +4820,14 @@ export class LocalTui {
|
|
|
4054
4820
|
}
|
|
4055
4821
|
/** Save and close the visible overlay so a prompt owns both input and screen. */
|
|
4056
4822
|
#displaceSurface() {
|
|
4057
|
-
const displaced = this.#
|
|
4058
|
-
? { kind: '
|
|
4059
|
-
: this.#
|
|
4060
|
-
? { kind: '
|
|
4061
|
-
: this.#
|
|
4823
|
+
const displaced = this.#fileBrowser !== null
|
|
4824
|
+
? { kind: 'fileBrowser', browser: this.#fileBrowser }
|
|
4825
|
+
: this.#trajectory !== null
|
|
4826
|
+
? { kind: 'trajectory', state: this.#trajectory }
|
|
4827
|
+
: this.#settings !== null
|
|
4828
|
+
? { kind: 'settings', state: this.#settings }
|
|
4829
|
+
: this.#search !== null ? { kind: 'search', state: this.#search } : null;
|
|
4830
|
+
this.#fileBrowser = null;
|
|
4062
4831
|
this.#trajectory = null;
|
|
4063
4832
|
this.#settings = null;
|
|
4064
4833
|
this.#search = null;
|
|
@@ -4070,7 +4839,9 @@ export class LocalTui {
|
|
|
4070
4839
|
this.#promptDisplaced = null;
|
|
4071
4840
|
if (displaced === null)
|
|
4072
4841
|
return;
|
|
4073
|
-
if (displaced.kind === '
|
|
4842
|
+
if (displaced.kind === 'fileBrowser')
|
|
4843
|
+
this.#fileBrowser = displaced.browser;
|
|
4844
|
+
else if (displaced.kind === 'trajectory')
|
|
4074
4845
|
this.#trajectory = displaced.state;
|
|
4075
4846
|
else if (displaced.kind === 'settings') {
|
|
4076
4847
|
this.#settings = displaced.state;
|
|
@@ -4116,7 +4887,7 @@ export class LocalTui {
|
|
|
4116
4887
|
* that did not answer to the shared list vocabulary, which is exactly the
|
|
4117
4888
|
* inconsistency the reader noticed.
|
|
4118
4889
|
*/
|
|
4119
|
-
const answer = await this.prompt({
|
|
4890
|
+
const answer = await this.prompt({ localize: true,
|
|
4120
4891
|
title: 'Copy',
|
|
4121
4892
|
question: items.length === 1 ? 'One thing to copy' : 'Choose what to copy',
|
|
4122
4893
|
filterable: true,
|
|
@@ -4152,13 +4923,17 @@ export class LocalTui {
|
|
|
4152
4923
|
}
|
|
4153
4924
|
#settingsActions() {
|
|
4154
4925
|
const runtimeActions = [
|
|
4155
|
-
{ id: 'command:model', label: 'Model and reasoning', description: 'Open /model; changes the current selection and saves the default route.', value: [this.#model, this.#reasoningEffort].filter(Boolean).join(' · '), values: [], action: 'model' },
|
|
4156
|
-
{ id: 'command:permission',
|
|
4157
|
-
{ id: 'command:agent',
|
|
4926
|
+
{ id: 'command:model', behavior: 'Session + Profile · Choose scope in Model', group: 'Models', label: 'Model and reasoning', description: 'Open /model; changes the current selection and saves the default route.', value: [this.#model, this.#reasoningEffort].filter(Boolean).join(' · '), values: [], action: 'model' },
|
|
4927
|
+
{ id: 'command:permission', behavior: 'Current session · Applies now', group: 'General', label: 'Current session access', description: 'Changes only this session. Default access controls future sessions.', value: this.#sessionControls?.permission ?? 'Choose', values: [], action: 'permission' },
|
|
4928
|
+
{ id: 'command:agent', behavior: 'Current session · Before the first prompt', group: 'Agent presets', label: 'Current session preset', description: 'Choose before the first prompt; does not change the new-session default.', value: this.#sessionControls?.agentPreset === undefined ? 'Choose' : formatAgentPreset(this.#sessionControls.agentPreset), values: [], action: 'agent' },
|
|
4929
|
+
{ id: 'command:login', behavior: 'Provider credentials', group: 'Models', label: 'Provider credentials', description: 'Sign in or configure a provider. Does not change the selected model.', value: 'Configure', values: [], action: 'login' },
|
|
4930
|
+
{ id: 'command:logout', behavior: 'Provider credentials', group: 'Models', label: 'Sign out', description: 'Remove saved provider credentials; environment credentials remain available.', value: 'Choose provider', values: [], action: 'logout' },
|
|
4931
|
+
{ id: 'command:plugins', group: 'Plugins', label: 'Manage plugins', description: 'Inspect installed entries, enable bundles, discover plugins or start Creator.', value: 'Open', values: [], action: 'plugins' },
|
|
4158
4932
|
].filter(item => this.#runtimeCommands.some(command => command.name === item.action));
|
|
4159
4933
|
const customBindings = Object.entries(this.#keybindings).filter(([key, action]) => DEFAULT_KEYBINDINGS[key] !== action).length;
|
|
4160
4934
|
return [
|
|
4161
4935
|
...runtimeActions,
|
|
4936
|
+
...(this.#goalHandler === undefined ? [] : [{ id: 'action:goal', behavior: 'Current session · Applies now', group: 'General', label: 'Current goal', description: 'Edit, pause, resume or clear this session’s goal.', value: 'Open', values: [], action: 'manage-goal' }]),
|
|
4162
4937
|
...(this.#keybindingsPath === undefined ? [] : [{
|
|
4163
4938
|
id: 'action:keybindings',
|
|
4164
4939
|
label: 'Keybindings',
|
|
@@ -4208,6 +4983,7 @@ export class LocalTui {
|
|
|
4208
4983
|
}
|
|
4209
4984
|
#configureNotifications() {
|
|
4210
4985
|
this.#notifications.configure({
|
|
4986
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
4211
4987
|
policy: this.#notificationPolicy,
|
|
4212
4988
|
thresholdMs: NOTIFICATION_THRESHOLD_MS,
|
|
4213
4989
|
});
|
|
@@ -4234,32 +5010,66 @@ export class LocalTui {
|
|
|
4234
5010
|
this.#term.output.write(sequence);
|
|
4235
5011
|
}, 25);
|
|
4236
5012
|
}
|
|
4237
|
-
/**
|
|
4238
|
-
* Help is reference, not conversation: 60 rows of it pushed into immutable
|
|
4239
|
-
* scrollback and vanished on resume, so the reader paid for it twice. It opens
|
|
4240
|
-
* in the reading surface instead, where it can be searched and left with Esc.
|
|
4241
|
-
*/
|
|
4242
5013
|
#runHelp(full) {
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
5014
|
+
if (!this.#tty) {
|
|
5015
|
+
this.commandOutput('help', ['Help', '', '## Essential Shortcuts', '', formatEssentialHotkeysText(this.#keybindings),
|
|
5016
|
+
'', formatHelpText(this.#commands(), resolveUiLanguage(this.#uiLanguage)),
|
|
5017
|
+
...(full ? ['', formatHotkeysText(this.#keybindings)] : ['', 'Use `/help full` to view every keyboard shortcut.'])].join('\n'));
|
|
5018
|
+
return;
|
|
5019
|
+
}
|
|
5020
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null)
|
|
5021
|
+
return;
|
|
5022
|
+
const abort = new AbortController();
|
|
5023
|
+
this.#fileReviewAbort = abort;
|
|
5024
|
+
const surface = {
|
|
5025
|
+
prompt: (request) => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5026
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5027
|
+
insertText: (text) => this.insertText(text),
|
|
5028
|
+
};
|
|
5029
|
+
void browseHelp(surface, this.#commands(), this.#keybindings, resolveUiLanguage(this.#uiLanguage), full, abort.signal)
|
|
5030
|
+
.catch(error => { if (!abort.signal.aborted && !this.#disposed)
|
|
5031
|
+
this.notice(String(error), { level: 'error' }); })
|
|
5032
|
+
.finally(() => { if (this.#fileReviewAbort === abort)
|
|
5033
|
+
this.#fileReviewAbort = undefined; });
|
|
4254
5034
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
5035
|
+
async #readCommandOutput(block) {
|
|
5036
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null || this.#disposed)
|
|
5037
|
+
return;
|
|
5038
|
+
const abort = new AbortController();
|
|
5039
|
+
this.#fileReviewAbort = abort;
|
|
5040
|
+
try {
|
|
5041
|
+
await readReference({
|
|
5042
|
+
prompt: request => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5043
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5044
|
+
}, `/${block.command}`, block.text, abort.signal);
|
|
5045
|
+
}
|
|
5046
|
+
catch (error) {
|
|
5047
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
5048
|
+
this.notice(String(error), { level: 'error' });
|
|
5049
|
+
}
|
|
5050
|
+
finally {
|
|
5051
|
+
if (this.#fileReviewAbort === abort)
|
|
5052
|
+
this.#fileReviewAbort = undefined;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
/** Open the searchable catalog, retaining plain output for line mode. */
|
|
4259
5056
|
#runTools() {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
5057
|
+
if (!this.#tty) {
|
|
5058
|
+
this.commandOutput('tools', `Tools · ${this.#tools.length} active\n\n${formatToolsText(this.#tools)}`);
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null)
|
|
5062
|
+
return;
|
|
5063
|
+
const abort = new AbortController();
|
|
5064
|
+
this.#fileReviewAbort = abort;
|
|
5065
|
+
void browseTools({
|
|
5066
|
+
prompt: request => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5067
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5068
|
+
}, this.#tools, resolveUiLanguage(this.#uiLanguage), abort.signal)
|
|
5069
|
+
.catch(error => { if (!abort.signal.aborted && !this.#disposed)
|
|
5070
|
+
this.notice(String(error), { level: 'error' }); })
|
|
5071
|
+
.finally(() => { if (this.#fileReviewAbort === abort)
|
|
5072
|
+
this.#fileReviewAbort = undefined; });
|
|
4263
5073
|
}
|
|
4264
5074
|
/** Resolve a configured action for one key id, tolerating camelCase ids. */
|
|
4265
5075
|
#boundAction(id) {
|
|
@@ -4363,10 +5173,12 @@ export class LocalTui {
|
|
|
4363
5173
|
this.#render();
|
|
4364
5174
|
return true;
|
|
4365
5175
|
}
|
|
5176
|
+
if (action === 'manage-goal') {
|
|
5177
|
+
this.#openGoal();
|
|
5178
|
+
return true;
|
|
5179
|
+
}
|
|
4366
5180
|
if (action === 'manage-queue') {
|
|
4367
|
-
if (this.#
|
|
4368
|
-
this.notice('Return to the parent session to manage queued messages.');
|
|
4369
|
-
else if (this.#queueHandler === undefined)
|
|
5181
|
+
if (this.#queueHandler === undefined)
|
|
4370
5182
|
this.notice('Queue management is unavailable in this profile.');
|
|
4371
5183
|
else if (this.#queueHotkeyAbort === undefined) {
|
|
4372
5184
|
const abort = new AbortController();
|
|
@@ -4382,33 +5194,30 @@ export class LocalTui {
|
|
|
4382
5194
|
if (action === 'steer-turn') {
|
|
4383
5195
|
if (this.#search !== null)
|
|
4384
5196
|
return false;
|
|
4385
|
-
if (this.#inspected !== undefined
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
this.notice('Use Enter to queue attachments; steering accepts text only.');
|
|
5197
|
+
if (this.#inspected !== undefined && !this.#inspected.writable)
|
|
5198
|
+
return true;
|
|
5199
|
+
if (this.#state.status !== 'running') {
|
|
5200
|
+
this.notice('Steering needs an active turn. Use Enter to send the next message.');
|
|
4390
5201
|
}
|
|
4391
5202
|
else if (this.#queueEditNewer !== null || this.#queueEditPending) {
|
|
4392
5203
|
this.notice('Finish editing the queued follow-up with Enter before steering.');
|
|
4393
5204
|
}
|
|
4394
|
-
else if (this.#editor.expandedText.trim() !== '') {
|
|
4395
|
-
|
|
4396
|
-
if (this.#steerHandler === undefined)
|
|
4397
|
-
throw new Error('Steering is unavailable in this profile.');
|
|
4398
|
-
this.#steerHandler(this.#editor.expandedText);
|
|
4399
|
-
this.#editor.setText('');
|
|
4400
|
-
this.#literalInput = false;
|
|
4401
|
-
this.#historyIndex = 0;
|
|
4402
|
-
this.#clearDraft();
|
|
4403
|
-
this.#ac = null;
|
|
4404
|
-
this.#render();
|
|
4405
|
-
}
|
|
4406
|
-
catch (error) {
|
|
4407
|
-
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
4408
|
-
}
|
|
5205
|
+
else if (this.#editor.expandedText.trim() !== '' || this.#images.count > 0 || this.#files.copies().length > 0) {
|
|
5206
|
+
this.#submit(this.#editor.expandedText, 'steer');
|
|
4409
5207
|
}
|
|
4410
5208
|
return true;
|
|
4411
5209
|
}
|
|
5210
|
+
if (action === 'quote-selection')
|
|
5211
|
+
return true;
|
|
5212
|
+
if (action === 'tool-actions') {
|
|
5213
|
+
const anchor = this.#viewportBlock();
|
|
5214
|
+
const block = anchor === undefined ? undefined : this.#state.blocks[anchor];
|
|
5215
|
+
if (block?.kind === 'commandOutput')
|
|
5216
|
+
void this.#readCommandOutput(block);
|
|
5217
|
+
else
|
|
5218
|
+
void this.#openToolActions();
|
|
5219
|
+
return true;
|
|
5220
|
+
}
|
|
4412
5221
|
if (action === 'review-changes') {
|
|
4413
5222
|
// The keyboard twin of right-clicking a tool's path: open the files the
|
|
4414
5223
|
// tool under the viewport declared. It shares the mouse path's region
|
|
@@ -4424,7 +5233,7 @@ export class LocalTui {
|
|
|
4424
5233
|
const regions = this.#fileActionFrame?.transcript?.fileRegions ?? [];
|
|
4425
5234
|
const region = (top === undefined ? undefined : regions.find(region => top >= region.start && top < region.end))
|
|
4426
5235
|
?? (this.#inspecting ? regions.find(region => this.#state.blocks[region.block]?.kind === 'tool' && rows.some(row => row >= region.start && row < region.end)) : undefined);
|
|
4427
|
-
const block = this.#state.blocks[region?.block ?? (this.#viewportBlock() ?? this.#state.blocks.length - 1)];
|
|
5236
|
+
const block = this.#currentTool() ?? this.#state.blocks[region?.block ?? (this.#viewportBlock() ?? this.#state.blocks.length - 1)];
|
|
4428
5237
|
const files = block?.kind === 'tool' ? blockFileAction(block) : undefined;
|
|
4429
5238
|
if (files === undefined) {
|
|
4430
5239
|
this.#showTransientStatus('No tool files here · use /diff', 4_000);
|
|
@@ -4490,7 +5299,16 @@ export class LocalTui {
|
|
|
4490
5299
|
return true;
|
|
4491
5300
|
}
|
|
4492
5301
|
if (action === 'retry') {
|
|
4493
|
-
this.#
|
|
5302
|
+
if (!this.#canContinue())
|
|
5303
|
+
return true;
|
|
5304
|
+
const request = { sessionId: this.#sessionId, failureSeq: this.#sessionControls.continuation };
|
|
5305
|
+
this.#continuing = true;
|
|
5306
|
+
void this.#continueHandler(request).catch(error => {
|
|
5307
|
+
if (!this.#disposed && this.#sessionId === request.sessionId)
|
|
5308
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
5309
|
+
}).finally(() => { this.#continuing = false; if (!this.#disposed)
|
|
5310
|
+
this.#render(); });
|
|
5311
|
+
this.#render();
|
|
4494
5312
|
return true;
|
|
4495
5313
|
}
|
|
4496
5314
|
if (action === 'cycle-model-forward') {
|
|
@@ -4514,12 +5332,11 @@ export class LocalTui {
|
|
|
4514
5332
|
this.#startAsyncPaste(this.#pasteClipboard());
|
|
4515
5333
|
return true;
|
|
4516
5334
|
}
|
|
4517
|
-
//
|
|
4518
|
-
// it belongs to is running; Enter is which-turn, this is after-this-turn.
|
|
5335
|
+
// Explicit queue delivery is independent of the busy-Enter preference.
|
|
4519
5336
|
if (action === 'follow-up') {
|
|
4520
|
-
if (this.#editor.expandedText.trim() === '')
|
|
5337
|
+
if (this.#editor.expandedText.trim() === '' && this.#images.count === 0 && this.#files.copies().length === 0)
|
|
4521
5338
|
return false;
|
|
4522
|
-
this.#submit(this.#editor.expandedText);
|
|
5339
|
+
this.#submit(this.#editor.expandedText, 'queue');
|
|
4523
5340
|
return true;
|
|
4524
5341
|
}
|
|
4525
5342
|
if (action !== 'external-editor')
|
|
@@ -4574,8 +5391,10 @@ export class LocalTui {
|
|
|
4574
5391
|
this.#images.clear();
|
|
4575
5392
|
this.#files.clear();
|
|
4576
5393
|
if (options.clearQueueEdit) {
|
|
5394
|
+
if (this.#queueEditNewer !== null)
|
|
5395
|
+
this.#queuedSubmissions.push(...this.#queueEditNewer);
|
|
4577
5396
|
this.#queueEditNewer = null;
|
|
4578
|
-
this.#
|
|
5397
|
+
this.#cancelQueueRecall();
|
|
4579
5398
|
}
|
|
4580
5399
|
if (options.closeSearch)
|
|
4581
5400
|
this.#search = null;
|
|
@@ -4633,11 +5452,39 @@ export class LocalTui {
|
|
|
4633
5452
|
this.#historyIndex = 0;
|
|
4634
5453
|
this.#refreshAutocomplete();
|
|
4635
5454
|
}
|
|
5455
|
+
async #testKeys() {
|
|
5456
|
+
const abort = new AbortController();
|
|
5457
|
+
// Reuse browsing cancellation: approvals and session switches own the terminal.
|
|
5458
|
+
this.#fileReviewAbort = abort;
|
|
5459
|
+
this.#keyTest = { signal: abort.signal, observe: event => {
|
|
5460
|
+
const pending = this.#prompt;
|
|
5461
|
+
const detail = keyTestDescription(event, this.#keybindings, resolveUiLanguage(this.#uiLanguage));
|
|
5462
|
+
if (pending?.request.signal !== abort.signal || detail === undefined)
|
|
5463
|
+
return;
|
|
5464
|
+
pending.sourceRequest = { ...pending.sourceRequest, detail };
|
|
5465
|
+
pending.request = localizePrompt(pending.sourceRequest, resolveUiLanguage(this.#uiLanguage));
|
|
5466
|
+
this.#render();
|
|
5467
|
+
} };
|
|
5468
|
+
try {
|
|
5469
|
+
await this.prompt({ localize: true, title: 'Key test', presentation: 'progress',
|
|
5470
|
+
question: 'Press a key. Esc or Ctrl+C returns without changing bindings.',
|
|
5471
|
+
detail: uiText(resolveUiLanguage(this.#uiLanguage), 'If nothing appears, the terminal or operating system may have intercepted the key.'),
|
|
5472
|
+
allowCustom: false, notify: false, signal: abort.signal, onSuperseded: () => { abort.abort(); } });
|
|
5473
|
+
}
|
|
5474
|
+
finally {
|
|
5475
|
+
if (this.#keyTest?.signal === abort.signal)
|
|
5476
|
+
this.#keyTest = undefined;
|
|
5477
|
+
if (this.#fileReviewAbort === abort)
|
|
5478
|
+
this.#fileReviewAbort = undefined;
|
|
5479
|
+
}
|
|
5480
|
+
return !abort.signal.aborted;
|
|
5481
|
+
}
|
|
4636
5482
|
async #editKeybindings() {
|
|
4637
5483
|
if (this.#keybindingsPath === undefined)
|
|
4638
5484
|
return;
|
|
4639
5485
|
while (!this.#disposed) {
|
|
4640
5486
|
const options = [
|
|
5487
|
+
{ label: 'Test keys', value: '__test_keys__', description: 'See what the terminal sends without running an action.' },
|
|
4641
5488
|
...TUI_ACTIONS.map(action => {
|
|
4642
5489
|
const keys = keysForAction(this.#keybindings, action);
|
|
4643
5490
|
const defaultKeys = keysForAction(DEFAULT_KEYBINDINGS, action);
|
|
@@ -4653,7 +5500,7 @@ export class LocalTui {
|
|
|
4653
5500
|
description: 'Restore default shortcuts in keybindings.json',
|
|
4654
5501
|
},
|
|
4655
5502
|
];
|
|
4656
|
-
const choice = await this.prompt({
|
|
5503
|
+
const choice = await this.prompt({ localize: true,
|
|
4657
5504
|
title: 'Keybindings',
|
|
4658
5505
|
question: 'Choose an action to remap, or Esc to return to /settings.',
|
|
4659
5506
|
options,
|
|
@@ -4663,6 +5510,11 @@ export class LocalTui {
|
|
|
4663
5510
|
});
|
|
4664
5511
|
if (choice === null || this.#disposed)
|
|
4665
5512
|
break;
|
|
5513
|
+
if (choice === '__test_keys__') {
|
|
5514
|
+
if (!await this.#testKeys())
|
|
5515
|
+
break;
|
|
5516
|
+
continue;
|
|
5517
|
+
}
|
|
4666
5518
|
if (choice === '__reset_all__') {
|
|
4667
5519
|
try {
|
|
4668
5520
|
saveKeybindings(this.#keybindingsPath, DEFAULT_KEYBINDINGS);
|
|
@@ -4681,7 +5533,7 @@ export class LocalTui {
|
|
|
4681
5533
|
continue;
|
|
4682
5534
|
const currentRawKeys = Object.entries(this.#keybindings).filter(([, bound]) => bound === action).map(([key]) => key);
|
|
4683
5535
|
const defaultKeys = keysForAction(DEFAULT_KEYBINDINGS, action);
|
|
4684
|
-
const entered = await this.prompt({
|
|
5536
|
+
const entered = await this.prompt({ localize: true,
|
|
4685
5537
|
title: `Remap ${TUI_ACTION_LABELS[action]}`,
|
|
4686
5538
|
question: `Enter key chord (e.g. ctrl+s, alt+k), "default" (${defaultKeys}), or "none" to unbind.`,
|
|
4687
5539
|
initialInput: currentRawKeys[0] ?? '',
|
|
@@ -4756,16 +5608,18 @@ export class LocalTui {
|
|
|
4756
5608
|
* above them.
|
|
4757
5609
|
*/
|
|
4758
5610
|
const anchored = anchor === undefined ? undefined : this.#state.blocks[anchor];
|
|
4759
|
-
if (anchor !== undefined && (anchored?.kind === 'commandOutput' || anchored?.kind === 'aside')) {
|
|
5611
|
+
if (anchor !== undefined && (anchored?.kind === 'commandOutput' || anchored?.kind === 'aside' || (anchored?.kind === 'notice' && anchored.detail !== undefined))) {
|
|
4760
5612
|
if (this.#expandedOutputs.has(anchored))
|
|
4761
5613
|
this.#expandedOutputs.delete(anchored);
|
|
4762
5614
|
else
|
|
4763
5615
|
this.#expandedOutputs.add(anchored);
|
|
4764
5616
|
this.#inspect(anchor);
|
|
5617
|
+
this.#focusBlock = anchor;
|
|
5618
|
+
this.#focusBlockEdge = 'start';
|
|
4765
5619
|
this.#render();
|
|
4766
5620
|
return;
|
|
4767
5621
|
}
|
|
4768
|
-
const groups = turnGroups(this.#state)
|
|
5622
|
+
const groups = turnGroups(this.#state);
|
|
4769
5623
|
// The answer, changed-file record, and following notices still address
|
|
4770
5624
|
// their turn when the reader is following the tail.
|
|
4771
5625
|
const reach = (group) => {
|
|
@@ -4788,18 +5642,132 @@ export class LocalTui {
|
|
|
4788
5642
|
return anchor >= first && anchor <= reach(item);
|
|
4789
5643
|
});
|
|
4790
5644
|
if (group !== undefined) {
|
|
4791
|
-
this.#
|
|
4792
|
-
|
|
5645
|
+
const scope = this.#detailScope(group);
|
|
5646
|
+
for (const member of groups)
|
|
5647
|
+
if (this.#detailScope(member) === scope)
|
|
5648
|
+
this.#openedGroups.add(member.key);
|
|
5649
|
+
const rows = this.#processRows();
|
|
5650
|
+
this.#processSelection = rows.includes(anchor) ? anchor : rows[0];
|
|
5651
|
+
this.#ac = null;
|
|
5652
|
+
this.#inspect(this.#processSelection ?? group.start);
|
|
5653
|
+
this.#focusBlock = this.#processSelection ?? group.start;
|
|
5654
|
+
this.#focusBlockEdge = 'start';
|
|
4793
5655
|
this.#render();
|
|
4794
5656
|
return;
|
|
4795
5657
|
}
|
|
4796
|
-
|
|
5658
|
+
}
|
|
5659
|
+
/** The process list follows durable block order and grows without moving selection. */
|
|
5660
|
+
#processRows() {
|
|
5661
|
+
return turnGroups(this.#state).filter(group => this.#openedGroups.has(group.key)).flatMap(group => this.#state.blocks.slice(group.start, group.until).flatMap((block, offset) => block.kind === 'tool' || block.kind === 'assistant' && block.reasoning.trim() !== ''
|
|
5662
|
+
? [group.start + offset] : []));
|
|
5663
|
+
}
|
|
5664
|
+
#handleProcessNavigation(event) {
|
|
5665
|
+
const selected = this.#processSelection;
|
|
5666
|
+
if (selected === undefined)
|
|
5667
|
+
return false;
|
|
5668
|
+
if (event.type === 'text' || event.type === 'paste-start') {
|
|
5669
|
+
this.#processSelection = undefined;
|
|
5670
|
+
return false;
|
|
5671
|
+
}
|
|
5672
|
+
if (event.type !== 'key')
|
|
5673
|
+
return false;
|
|
5674
|
+
if (event.id === 'escape' || event.id === 'end') {
|
|
5675
|
+
this.#followTail();
|
|
5676
|
+
this.#render();
|
|
5677
|
+
return true;
|
|
5678
|
+
}
|
|
5679
|
+
if (event.id === 'home') {
|
|
5680
|
+
const current = this.#fileActionFrame?.transcript?.disclosures?.findLast(target => target.detailEnd !== undefined && this.#scrollStart > target.row && this.#scrollStart < target.detailEnd);
|
|
5681
|
+
this.#processSelection = current?.block ?? selected;
|
|
5682
|
+
this.#focusBlock = this.#processSelection;
|
|
5683
|
+
this.#focusBlockEdge = 'start';
|
|
5684
|
+
this.#render();
|
|
5685
|
+
return true;
|
|
5686
|
+
}
|
|
5687
|
+
if (event.id === 'up' || event.id === 'down') {
|
|
5688
|
+
const rows = this.#processRows();
|
|
5689
|
+
const at = Math.max(0, rows.indexOf(selected));
|
|
5690
|
+
const next = rows[Math.max(0, Math.min(rows.length - 1, at + (event.id === 'up' ? -1 : 1)))];
|
|
5691
|
+
if (next !== undefined) {
|
|
5692
|
+
this.#processSelection = next;
|
|
5693
|
+
const row = this.#blockStarts[next];
|
|
5694
|
+
if (row === undefined || row < this.#scrollStart || row >= this.#scrollStart + this.#scrollBudget - 1) {
|
|
5695
|
+
this.#focusBlock = next;
|
|
5696
|
+
this.#focusBlockEdge = 'start';
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
this.#render();
|
|
5700
|
+
return true;
|
|
5701
|
+
}
|
|
5702
|
+
if (!['enter', 'left', 'right'].includes(event.id))
|
|
5703
|
+
return false;
|
|
5704
|
+
this.#toggleProcessItem(selected, event.id);
|
|
5705
|
+
return true;
|
|
5706
|
+
}
|
|
5707
|
+
#toggleProcessItem(selected, direction) {
|
|
5708
|
+
const block = this.#state.blocks[selected];
|
|
5709
|
+
if (block?.kind === 'tool') {
|
|
5710
|
+
const visible = this.#detailedToolIds();
|
|
5711
|
+
const wasOpen = visible.has(block.callId);
|
|
5712
|
+
const open = direction === 'right' || direction !== 'left' && !wasOpen;
|
|
5713
|
+
const raw = this.#detailedToolIds(false).has(block.callId);
|
|
5714
|
+
if (wasOpen && !open) {
|
|
5715
|
+
const target = this.#fileActionFrame?.transcript?.disclosures?.find(item => item.block === selected);
|
|
5716
|
+
const offset = target?.detailEnd !== undefined && this.#scrollStart >= target.row && this.#scrollStart < target.detailEnd
|
|
5717
|
+
? this.#scrollStart - target.row : 0;
|
|
5718
|
+
this.#toolReadingPositions.set(block.callId, { offset, width: this.#term.width(), raw });
|
|
5719
|
+
}
|
|
5720
|
+
if (open !== wasOpen) {
|
|
5721
|
+
const saved = open ? this.#toolReadingPositions.get(block.callId) : undefined;
|
|
5722
|
+
this.#focusBlock = selected;
|
|
5723
|
+
this.#focusBlockEdge = 'start';
|
|
5724
|
+
// Width changes and raw/readable switches change the row layout.
|
|
5725
|
+
this.#focusBlockOffset = saved?.width === this.#term.width() && saved.raw === raw ? saved.offset : 0;
|
|
5726
|
+
}
|
|
5727
|
+
// Once a reader edits one disclosure, preserve the other visible calls as
|
|
5728
|
+
// individual choices instead of letting the turn-wide override reopen it.
|
|
5729
|
+
this.#expandedToolRows = new Set(visible);
|
|
5730
|
+
this.#detailedTurns.clear();
|
|
5731
|
+
if (open)
|
|
5732
|
+
this.#expandedToolRows.add(block.callId);
|
|
5733
|
+
else
|
|
5734
|
+
this.#expandedToolRows.delete(block.callId);
|
|
5735
|
+
}
|
|
5736
|
+
else if (block?.kind === 'assistant') {
|
|
5737
|
+
const key = reasoningKey(block);
|
|
5738
|
+
const open = direction === 'right' || direction !== 'left' && !this.#visibleReasoning().has(key);
|
|
5739
|
+
if (open) {
|
|
5740
|
+
this.#expandedReasoning.add(key);
|
|
5741
|
+
this.#closedReasoning.delete(key);
|
|
5742
|
+
}
|
|
5743
|
+
else {
|
|
5744
|
+
this.#expandedReasoning.delete(key);
|
|
5745
|
+
this.#closedReasoning.add(key);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
const row = this.#blockStarts[selected];
|
|
5749
|
+
if (row === undefined || row < this.#scrollStart || row > this.#scrollStart + Math.floor(this.#scrollBudget / 2)) {
|
|
5750
|
+
this.#focusBlock = selected;
|
|
5751
|
+
this.#focusBlockEdge = 'start';
|
|
5752
|
+
}
|
|
5753
|
+
this.#render();
|
|
5754
|
+
}
|
|
5755
|
+
#visibleReasoning() {
|
|
5756
|
+
const open = new Set(this.#expandedReasoning);
|
|
5757
|
+
if (!this.#hideThinking)
|
|
5758
|
+
for (const block of this.#state.blocks) {
|
|
5759
|
+
if (block.kind === 'assistant')
|
|
5760
|
+
open.add(reasoningKey(block));
|
|
5761
|
+
}
|
|
5762
|
+
for (const key of this.#closedReasoning)
|
|
5763
|
+
open.delete(key);
|
|
5764
|
+
return open;
|
|
4797
5765
|
}
|
|
4798
5766
|
#detailScope(group) {
|
|
4799
5767
|
return group.turn === undefined ? group.key : `turn:${group.turn}`;
|
|
4800
5768
|
}
|
|
4801
|
-
#detailedToolIds() {
|
|
4802
|
-
const calls = new Set();
|
|
5769
|
+
#detailedToolIds(includeReadable = true) {
|
|
5770
|
+
const calls = new Set(includeReadable ? this.#expandedToolRows : []);
|
|
4803
5771
|
if (this.#detailedTurns.size === 0)
|
|
4804
5772
|
return calls;
|
|
4805
5773
|
for (const group of turnGroups(this.#state)) {
|
|
@@ -4843,6 +5811,11 @@ export class LocalTui {
|
|
|
4843
5811
|
this.#openedGroups.add(member.key);
|
|
4844
5812
|
const tools = members.flatMap(member => this.#state.blocks.slice(member.start, member.until)
|
|
4845
5813
|
.flatMap((block, offset) => block.kind === 'tool' ? [member.start + offset] : []));
|
|
5814
|
+
for (const at of tools) {
|
|
5815
|
+
const block = this.#state.blocks[at];
|
|
5816
|
+
if (block?.kind === 'tool')
|
|
5817
|
+
this.#expandedToolRows.delete(block.callId);
|
|
5818
|
+
}
|
|
4846
5819
|
const target = tools.includes(anchor) ? anchor : tools[0];
|
|
4847
5820
|
this.#inspect(target);
|
|
4848
5821
|
this.#focusBlock = target;
|
|
@@ -4868,14 +5841,19 @@ export class LocalTui {
|
|
|
4868
5841
|
return [];
|
|
4869
5842
|
const options = {
|
|
4870
5843
|
width: this.#term.width(),
|
|
5844
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
4871
5845
|
colors: this.#colors,
|
|
4872
5846
|
themeName: this.#themeName,
|
|
5847
|
+
terminal: this.#terminalColors,
|
|
5848
|
+
compactLinks: this.#compactLinks,
|
|
4873
5849
|
...(this.#colorDepth === undefined ? {} : { colorDepth: this.#colorDepth }),
|
|
4874
5850
|
mathMode: this.#mathMode,
|
|
5851
|
+
mermaidMode: this.#mermaidMode,
|
|
4875
5852
|
toolsExpanded: this.#toolsExpanded,
|
|
4876
5853
|
expandedOutputs: this.#expandedOutputs,
|
|
4877
|
-
hideThinking:
|
|
5854
|
+
hideThinking: true,
|
|
4878
5855
|
expandedTools: this.#detailedToolIds(),
|
|
5856
|
+
expandedReasoning: this.#visibleReasoning(),
|
|
4879
5857
|
...(this.#openedGroups === undefined ? {} : { openedGroups: this.#openedGroups }),
|
|
4880
5858
|
};
|
|
4881
5859
|
return searchMatches(renderedBlocks(this.#state, options), search.query);
|
|
@@ -4978,6 +5956,7 @@ export function apply(ctx, config) {
|
|
|
4978
5956
|
// settings service, which persists the edit into the active profile patch.
|
|
4979
5957
|
const storedPrefs = () => {
|
|
4980
5958
|
const prefs = {
|
|
5959
|
+
uiLanguage: config.uiLanguage.get(),
|
|
4981
5960
|
theme: config.theme.get(),
|
|
4982
5961
|
colors: resolveColors(config.colors.get(), term.output.isTTY === true),
|
|
4983
5962
|
motion: config.motion.get(),
|
|
@@ -4988,6 +5967,7 @@ export function apply(ctx, config) {
|
|
|
4988
5967
|
terminalState: config.terminalState.get(),
|
|
4989
5968
|
copyOnSelect: config.copyOnSelect.get(),
|
|
4990
5969
|
mouseInteraction: config.mouseInteraction.get(),
|
|
5970
|
+
busyEnter: config.busyEnter.get(),
|
|
4991
5971
|
pasteProtection: config.pasteProtection.get(),
|
|
4992
5972
|
checkUpdates: config.checkUpdates.get(),
|
|
4993
5973
|
startupChangelog: config.startupChangelog.get(),
|