@agi-fans/dsh-tui 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/lib/CHANGELOG.md +199 -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 +97 -32
- package/lib/commands/export.js.map +1 -1
- package/lib/commands/files.js +2 -8
- 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 +197 -120
- 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 +111 -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 +112 -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 +1264 -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 +32 -13
- package/lib/session/session-controller.js +444 -98
- package/lib/session/session-controller.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 +10 -7
- package/lib/views/autocomplete.js +44 -37
- 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 +59 -5
- 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
|
*
|
|
@@ -437,6 +471,10 @@ export class LocalTui {
|
|
|
437
471
|
*/
|
|
438
472
|
#subagentSelection;
|
|
439
473
|
#inspected;
|
|
474
|
+
#promptActions = [];
|
|
475
|
+
#promptActionPress;
|
|
476
|
+
#promptChoices = [];
|
|
477
|
+
#promptChoicePress;
|
|
440
478
|
#promptDocument;
|
|
441
479
|
/**
|
|
442
480
|
* 24-bit switch for the current color preference, re-derived whenever that
|
|
@@ -444,6 +482,7 @@ export class LocalTui {
|
|
|
444
482
|
* hexadecimal palettes without a restart.
|
|
445
483
|
*/
|
|
446
484
|
#colorDepth = 'ansi16';
|
|
485
|
+
#compactLinks = supportsHyperlinks(process.env);
|
|
447
486
|
/** Hold the first frame until the capability probe settles; scrollback rows are immutable, so the first real paint must use the reported colors. */
|
|
448
487
|
#probeGate = false;
|
|
449
488
|
#copy;
|
|
@@ -468,6 +507,7 @@ export class LocalTui {
|
|
|
468
507
|
#prefsSaveTask;
|
|
469
508
|
#disposeTask;
|
|
470
509
|
#prefsVersion = 0;
|
|
510
|
+
#uiLanguage = 'auto';
|
|
471
511
|
#agentBehavior;
|
|
472
512
|
#updateAgentBehavior;
|
|
473
513
|
#offAgentBehaviorWatch;
|
|
@@ -527,6 +567,7 @@ export class LocalTui {
|
|
|
527
567
|
this.#syncColorDepth();
|
|
528
568
|
this.#tty = term.input.isTTY === true && term.output.isTTY === true;
|
|
529
569
|
this.#draftStore = this.#tty && paths.draftsPath !== undefined ? new ComposerDraftStore(paths.draftsPath) : undefined;
|
|
570
|
+
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
571
|
this.#plain = new PlainTui({
|
|
531
572
|
term,
|
|
532
573
|
blocks: () => this.#state.blocks,
|
|
@@ -554,7 +595,9 @@ export class LocalTui {
|
|
|
554
595
|
// A terminal resize changes the committed/live seam; re-anchor the
|
|
555
596
|
// live window so it stays anchored at the bottom.
|
|
556
597
|
this.#selection.clear();
|
|
598
|
+
this.#mouseActionPress = undefined;
|
|
557
599
|
this.#jumpPress = undefined;
|
|
600
|
+
this.#disclosurePress = undefined;
|
|
558
601
|
const repaint = () => {
|
|
559
602
|
this.#resizeTimer = null;
|
|
560
603
|
this.#renderer.resize(this.#term.width(), this.#term.height());
|
|
@@ -637,7 +680,13 @@ export class LocalTui {
|
|
|
637
680
|
this.#retryDeadline = Date.now() + event.data.delayMs;
|
|
638
681
|
if (event.type === 'llm/retry-started' || event.type === 'turn/end')
|
|
639
682
|
this.#retryDeadline = undefined;
|
|
683
|
+
const claimedHumanInput = event.type === 'agent/inbox/spliced' && event.data.outcome !== 'canceled'
|
|
684
|
+
&& event.data.inserted.length === 0 && (event.data.target === 'next-step' ? this.#state.nextStepInbox : this.#state.nextTurnInbox)
|
|
685
|
+
.slice(event.data.start, event.data.start + (event.data.removedCount ?? 0)).some(message => message.source.kind === 'user');
|
|
640
686
|
this.#state = applyEvent(this.#state, event, presentation, workspace);
|
|
687
|
+
if (claimedHumanInput && this.#tty && event.type === 'agent/inbox/spliced') {
|
|
688
|
+
this.#showTransientStatus(event.data.target === 'next-step' ? 'Steer taken for the next step' : 'Queue taken for the next turn');
|
|
689
|
+
}
|
|
641
690
|
const compacting = this.#state.status === 'compacting';
|
|
642
691
|
if (compacting && this.#compactionStartedAt === undefined)
|
|
643
692
|
this.#compactionStartedAt = Date.now();
|
|
@@ -749,12 +798,24 @@ export class LocalTui {
|
|
|
749
798
|
const statusChanged = previous.length !== incoming.length || incoming.some((agent, index) => {
|
|
750
799
|
const before = previous[index];
|
|
751
800
|
return before === undefined || before.id !== agent.id || before.label !== agent.label
|
|
752
|
-
|| before.phase !== agent.phase || before.depth !== agent.depth || before.mode !== agent.mode;
|
|
801
|
+
|| before.phase !== agent.phase || before.needsApproval !== agent.needsApproval || before.depth !== agent.depth || before.mode !== agent.mode;
|
|
753
802
|
});
|
|
803
|
+
const previousItems = rosterItems(this.#subagents);
|
|
804
|
+
const incomingItems = rosterItems(roster);
|
|
805
|
+
const workflowsChanged = (this.#subagents?.workflows?.length ?? 0) !== (roster?.workflows?.length ?? 0)
|
|
806
|
+
|| (roster?.workflows ?? []).some((run, index) => {
|
|
807
|
+
const before = this.#subagents?.workflows?.[index];
|
|
808
|
+
return before?.id !== run.id || before.name !== run.name || before.status !== run.status || before.phase !== run.phase || before.members !== run.members;
|
|
809
|
+
});
|
|
810
|
+
const selectedId = this.#subagentSelection === undefined ? undefined : previousItems[this.#subagentSelection]?.key;
|
|
754
811
|
this.#subagents = roster === undefined
|
|
755
812
|
? undefined
|
|
756
|
-
: { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })) };
|
|
757
|
-
if (
|
|
813
|
+
: { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })), ...(roster.workflows === undefined ? {} : { workflows: [...roster.workflows] }) };
|
|
814
|
+
if (selectedId !== undefined) {
|
|
815
|
+
const index = incomingItems.findIndex(item => item.key === selectedId);
|
|
816
|
+
this.#subagentSelection = index < 0 ? undefined : index;
|
|
817
|
+
}
|
|
818
|
+
if (incomingItems.length === 0)
|
|
758
819
|
this.#subagentSelection = undefined;
|
|
759
820
|
const inspected = this.#inspected;
|
|
760
821
|
if (inspected !== undefined) {
|
|
@@ -764,12 +825,13 @@ export class LocalTui {
|
|
|
764
825
|
id: current.id,
|
|
765
826
|
label: current.label,
|
|
766
827
|
phase: current.phase,
|
|
828
|
+
needsApproval: current.needsApproval === true,
|
|
767
829
|
...(current.mode === undefined ? {} : { mode: current.mode }),
|
|
768
830
|
writable: current.mode === 'continuable',
|
|
769
831
|
};
|
|
770
832
|
}
|
|
771
833
|
}
|
|
772
|
-
if (!statusChanged)
|
|
834
|
+
if (!statusChanged && !workflowsChanged)
|
|
773
835
|
return;
|
|
774
836
|
this.#syncTick();
|
|
775
837
|
if (this.#tty)
|
|
@@ -777,11 +839,16 @@ export class LocalTui {
|
|
|
777
839
|
}
|
|
778
840
|
setInspectedSubagent(inspected) {
|
|
779
841
|
if (this.#inspected?.id === inspected?.id && this.#inspected?.label === inspected?.label
|
|
780
|
-
&& this.#inspected?.phase === inspected?.phase && this.#inspected?.mode === inspected?.mode
|
|
842
|
+
&& this.#inspected?.phase === inspected?.phase && this.#inspected?.needsApproval === inspected?.needsApproval && this.#inspected?.mode === inspected?.mode
|
|
781
843
|
&& this.#inspected?.writable === inspected?.writable)
|
|
782
844
|
return;
|
|
845
|
+
if (this.#inspected?.id !== inspected?.id) {
|
|
846
|
+
this.#fileReviewAbort?.abort();
|
|
847
|
+
this.#closeFileBrowser();
|
|
848
|
+
}
|
|
783
849
|
this.#switchDraft(inspected?.id ?? this.#sessionId);
|
|
784
850
|
this.#inspected = inspected === undefined ? undefined : { ...inspected };
|
|
851
|
+
this.#drainPendingInput();
|
|
785
852
|
if (inspected !== undefined) {
|
|
786
853
|
this.#inspectEscapeFenceUntil = 0;
|
|
787
854
|
this.#subagentSelection = undefined;
|
|
@@ -794,20 +861,54 @@ export class LocalTui {
|
|
|
794
861
|
this.#tools = tools.map((tool) => ({ name: tool.name, description: tool.description }));
|
|
795
862
|
}
|
|
796
863
|
setCommands(commands) {
|
|
797
|
-
this.#runtimeCommands = commands.map(
|
|
798
|
-
name: command.name,
|
|
799
|
-
description: command.description,
|
|
800
|
-
...(command.inputHint === undefined ? {} : { inputHint: command.inputHint }),
|
|
801
|
-
}));
|
|
864
|
+
this.#runtimeCommands = commands.map(command => ({ ...command }));
|
|
802
865
|
this.#syncSettingsActions();
|
|
803
866
|
this.#refreshAutocomplete();
|
|
804
867
|
if (this.#tty)
|
|
805
868
|
this.#render();
|
|
806
869
|
}
|
|
870
|
+
async reviewFiles(request) {
|
|
871
|
+
if (!this.#tty || this.#disposed || request.signal.aborted)
|
|
872
|
+
return;
|
|
873
|
+
this.#flushPasteBurst(true);
|
|
874
|
+
this.#pasteBurst.clear();
|
|
875
|
+
this.#finishPrompt(null);
|
|
876
|
+
this.#closeFileBrowser();
|
|
877
|
+
this.#selection.clear();
|
|
878
|
+
this.#trajectory = null;
|
|
879
|
+
this.#settings = null;
|
|
880
|
+
this.#search = null;
|
|
881
|
+
this.#ac = null;
|
|
882
|
+
const browser = new FileBrowser(request, () => { if (!this.#disposed)
|
|
883
|
+
this.#render(); }, () => {
|
|
884
|
+
if (this.#fileBrowser === browser)
|
|
885
|
+
this.#fileBrowser = null;
|
|
886
|
+
if (this.#promptDisplaced?.kind === 'fileBrowser' && this.#promptDisplaced.browser === browser)
|
|
887
|
+
this.#promptDisplaced = null;
|
|
888
|
+
this.#selection.clear();
|
|
889
|
+
this.#trajectoryPress = undefined;
|
|
890
|
+
if (!this.#disposed)
|
|
891
|
+
this.#render();
|
|
892
|
+
});
|
|
893
|
+
this.#fileBrowser = browser;
|
|
894
|
+
browser.start();
|
|
895
|
+
await browser.done;
|
|
896
|
+
}
|
|
897
|
+
#closeFileBrowser() {
|
|
898
|
+
this.#fileBrowser?.close();
|
|
899
|
+
if (this.#promptDisplaced?.kind === 'fileBrowser')
|
|
900
|
+
this.#promptDisplaced.browser.close();
|
|
901
|
+
}
|
|
902
|
+
#applyFileBrowserInput(event) {
|
|
903
|
+
this.#fileBrowser?.input(event, createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks), this.#term.width(), this.#term.height());
|
|
904
|
+
}
|
|
807
905
|
openTrajectory(events) {
|
|
808
906
|
if (!this.#tty)
|
|
809
907
|
return false;
|
|
810
908
|
this.#finishPrompt(null);
|
|
909
|
+
this.#selection.clear();
|
|
910
|
+
this.#trajectoryPress = undefined;
|
|
911
|
+
this.#closeFileBrowser();
|
|
811
912
|
this.#trajectory = createTrajectory(events);
|
|
812
913
|
this.#settings = null;
|
|
813
914
|
this.#search = null;
|
|
@@ -848,6 +949,11 @@ export class LocalTui {
|
|
|
848
949
|
.join('\n');
|
|
849
950
|
}
|
|
850
951
|
notice(text, options = {}) {
|
|
952
|
+
if (options.transient) {
|
|
953
|
+
if (this.#tty && !this.#disposed)
|
|
954
|
+
this.#showTransientStatus(text);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
851
957
|
const merged = options.job === undefined ? undefined : mergeJobSettlement(this.#state, options.job);
|
|
852
958
|
if (merged !== undefined && this.#tty) {
|
|
853
959
|
const origin = merged.blocks.find((block, index) => block !== this.#state.blocks[index]);
|
|
@@ -946,7 +1052,6 @@ export class LocalTui {
|
|
|
946
1052
|
output: result.output === '' ? current.output : result.output,
|
|
947
1053
|
status: failed ? 'error' : 'ok',
|
|
948
1054
|
...(result.code === undefined ? {} : { code: result.code }),
|
|
949
|
-
...(request.share === true ? { shared: true } : {}),
|
|
950
1055
|
}));
|
|
951
1056
|
paint();
|
|
952
1057
|
return result;
|
|
@@ -1080,7 +1185,24 @@ export class LocalTui {
|
|
|
1080
1185
|
next[at] = update(next[at]);
|
|
1081
1186
|
this.#state = { ...this.#state, blocks: next };
|
|
1082
1187
|
}
|
|
1188
|
+
openContext(request) {
|
|
1189
|
+
let snapshot = request.read();
|
|
1190
|
+
return this.#promptWithDocument({
|
|
1191
|
+
localize: ['title', 'options'], title: 'Context', question: '',
|
|
1192
|
+
detail: contextOverviewDetails(snapshot), presentation: 'document', allowCustom: false, notify: false,
|
|
1193
|
+
options: [{ label: 'Details', value: 'details' }, { label: 'Copy session ID', value: 'copy' },
|
|
1194
|
+
...(request.canCompact ? [{ label: 'Compact', value: 'compact' }] : [])],
|
|
1195
|
+
refreshDocument: () => { snapshot = request.read(); return contextOverviewDetails(snapshot); },
|
|
1196
|
+
signal: request.signal, onSuperseded: request.onSuperseded,
|
|
1197
|
+
}, (theme, width) => renderContextOverview(snapshot, theme, width));
|
|
1198
|
+
}
|
|
1083
1199
|
prompt(request) {
|
|
1200
|
+
return this.#promptWithDocument(request);
|
|
1201
|
+
}
|
|
1202
|
+
#promptWithDocument(request, renderDocument) {
|
|
1203
|
+
const sourceRequest = request;
|
|
1204
|
+
const browserState = promptBrowserState(request);
|
|
1205
|
+
request = localizePrompt(request, resolveUiLanguage(this.#uiLanguage));
|
|
1084
1206
|
// Background approvals take the terminal from a user-opened file reader.
|
|
1085
1207
|
if (this.#fileReviewAbort !== undefined && request.signal !== this.#fileReviewAbort.signal)
|
|
1086
1208
|
this.#fileReviewAbort.abort();
|
|
@@ -1108,13 +1230,22 @@ export class LocalTui {
|
|
|
1108
1230
|
}
|
|
1109
1231
|
if (this.#tty && request.documentImage !== undefined && this.#imageProtocol !== undefined)
|
|
1110
1232
|
this.#term.output.write('\x1b[16t');
|
|
1233
|
+
this.#selection.clear();
|
|
1234
|
+
this.#promptActionPress = undefined;
|
|
1235
|
+
this.#promptChoicePress = undefined;
|
|
1236
|
+
this.#disclosurePress = undefined;
|
|
1237
|
+
this.#mouseActionPress = undefined;
|
|
1238
|
+
this.#jumpPress = undefined;
|
|
1239
|
+
this.#wheelGesture = IDLE_WHEEL;
|
|
1111
1240
|
const savedInput = this.#editor.snapshot();
|
|
1112
|
-
this.#editor.setText(request.
|
|
1241
|
+
this.#editor.setText(request.filterable === true ? browserState?.query ?? ''
|
|
1242
|
+
: request.allowCustom === false || (request.options?.length && request.initialInput === undefined) ? '' : request.initialInput ?? '');
|
|
1113
1243
|
this.#ac = null;
|
|
1114
1244
|
const displaced = this.#displaceSurface();
|
|
1115
1245
|
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,
|
|
1246
|
+
const selected = Math.max(0, filteredPromptOptions(request, this.#editor.text).findIndex(option => (option.value ?? option.label) === (browserState?.selected ?? request.initialValue)));
|
|
1247
|
+
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,
|
|
1248
|
+
...(browserState === undefined ? {} : { browserState }),
|
|
1118
1249
|
...(request.armMs !== undefined && request.armMs > 0 ? { armedUntil: Date.now() + request.armMs } : {}),
|
|
1119
1250
|
...(request.documentPosition === undefined ? {} : { documentAnchor: request.documentPosition, documentQuery: request.documentPosition.query,
|
|
1120
1251
|
documentWordWrap: request.documentPosition.wordWrap, documentColumn: request.documentPosition.column }),
|
|
@@ -1137,7 +1268,7 @@ export class LocalTui {
|
|
|
1137
1268
|
}
|
|
1138
1269
|
if (this.#tty && (request.refreshDocument !== undefined || request.refreshDocumentSource !== undefined || request.refreshOptions !== undefined)) {
|
|
1139
1270
|
const tick = setInterval(() => {
|
|
1140
|
-
if (this.#disposed || this.#prompt === null)
|
|
1271
|
+
if (this.#disposed || this.#prompt === null || this.#selection.active)
|
|
1141
1272
|
return;
|
|
1142
1273
|
try {
|
|
1143
1274
|
const detail = request.refreshDocument?.();
|
|
@@ -1147,16 +1278,17 @@ export class LocalTui {
|
|
|
1147
1278
|
if (source !== undefined && source !== this.#prompt.request.documentSource) {
|
|
1148
1279
|
const shift = (this.#prompt.request.documentSource?.firstLine ?? 1) - (source.firstLine ?? 1);
|
|
1149
1280
|
const anchor = this.#prompt.documentAnchor;
|
|
1150
|
-
this.#prompt = { ...this.#prompt, request: { ...this.#prompt.request, documentSource: source },
|
|
1281
|
+
this.#prompt = { ...this.#prompt, sourceRequest: { ...this.#prompt.sourceRequest, documentSource: source }, request: { ...this.#prompt.request, documentSource: source },
|
|
1151
1282
|
...(anchor === undefined ? {} : { documentAnchor: { ...anchor, row: Math.max(0, anchor.row + shift) } }) };
|
|
1152
1283
|
changed = true;
|
|
1153
1284
|
}
|
|
1154
|
-
if (options !== undefined && options !== this.#prompt.
|
|
1155
|
-
this.#prompt =
|
|
1285
|
+
if (options !== undefined && options !== this.#prompt.sourceRequest.options) {
|
|
1286
|
+
this.#prompt.sourceRequest = { ...this.#prompt.sourceRequest, options };
|
|
1287
|
+
this.#prompt = refreshPromptOptions(this.#prompt, localizePrompt(this.#prompt.sourceRequest, resolveUiLanguage(this.#uiLanguage)).options, this.#editor.text);
|
|
1156
1288
|
changed = true;
|
|
1157
1289
|
}
|
|
1158
1290
|
if (detail !== undefined && detail !== this.#prompt.request.detail) {
|
|
1159
|
-
this.#prompt = { ...this.#prompt, request: { ...this.#prompt.request, detail } };
|
|
1291
|
+
this.#prompt = { ...this.#prompt, sourceRequest: { ...this.#prompt.sourceRequest, detail }, request: { ...this.#prompt.request, detail } };
|
|
1160
1292
|
changed = true;
|
|
1161
1293
|
}
|
|
1162
1294
|
if (changed)
|
|
@@ -1193,8 +1325,11 @@ export class LocalTui {
|
|
|
1193
1325
|
});
|
|
1194
1326
|
}
|
|
1195
1327
|
replaceSession(events, presentations, status = 'idle', reason = 'open') {
|
|
1196
|
-
if (reason !== 'refresh')
|
|
1328
|
+
if (reason !== 'refresh') {
|
|
1197
1329
|
this.#fileReviewAbort?.abort();
|
|
1330
|
+
this.#closeFileBrowser();
|
|
1331
|
+
this.#clearTransientStatus();
|
|
1332
|
+
}
|
|
1198
1333
|
this.#trajectory = null;
|
|
1199
1334
|
const state = replayEvents(events, presentations);
|
|
1200
1335
|
// A notice does not turn a blank session into a document that /new needs
|
|
@@ -1212,10 +1347,13 @@ export class LocalTui {
|
|
|
1212
1347
|
this.#plain.print();
|
|
1213
1348
|
}
|
|
1214
1349
|
setSession(info) {
|
|
1215
|
-
if (info.id !== this.#sessionId)
|
|
1350
|
+
if (info.id !== this.#sessionId) {
|
|
1216
1351
|
this.#fileReviewAbort?.abort();
|
|
1352
|
+
this.#closeFileBrowser();
|
|
1353
|
+
}
|
|
1217
1354
|
this.#switchDraft(this.#inspected?.id ?? info.id);
|
|
1218
1355
|
this.#sessionId = info.id;
|
|
1356
|
+
this.#drainPendingInput();
|
|
1219
1357
|
this.#herdr.setSession(info.id);
|
|
1220
1358
|
const title = info.title?.trim();
|
|
1221
1359
|
this.#sessionTitle = title === undefined || title === '' ? undefined : title;
|
|
@@ -1287,6 +1425,7 @@ export class LocalTui {
|
|
|
1287
1425
|
/** Currently applied preferences; sibling rows read these instead of the config references. */
|
|
1288
1426
|
prefs() {
|
|
1289
1427
|
return {
|
|
1428
|
+
uiLanguage: this.#uiLanguage,
|
|
1290
1429
|
theme: this.#themeName,
|
|
1291
1430
|
colors: this.#colors,
|
|
1292
1431
|
motion: this.#motion,
|
|
@@ -1298,6 +1437,7 @@ export class LocalTui {
|
|
|
1298
1437
|
terminalState: this.#terminalState,
|
|
1299
1438
|
copyOnSelect: this.#copyOnSelect,
|
|
1300
1439
|
mouseInteraction: this.#mouseInteraction,
|
|
1440
|
+
busyEnter: this.#busyEnter,
|
|
1301
1441
|
pasteProtection: this.#pasteProtection,
|
|
1302
1442
|
checkUpdates: this.#checkUpdates,
|
|
1303
1443
|
startupChangelog: this.#startupChangelog,
|
|
@@ -1350,6 +1490,26 @@ export class LocalTui {
|
|
|
1350
1490
|
this.#render();
|
|
1351
1491
|
};
|
|
1352
1492
|
}
|
|
1493
|
+
#takeQueuedInput() {
|
|
1494
|
+
const scope = this.#sessionId ?? '__default__';
|
|
1495
|
+
const inputs = this.#pendingInputs.session(scope).queued;
|
|
1496
|
+
const index = inputs.findIndex(item => !this.#pendingInputs.receipt(item)?.recovered);
|
|
1497
|
+
const queued = index < 0 ? undefined : inputs.splice(index, 1)[0];
|
|
1498
|
+
if (queued !== undefined)
|
|
1499
|
+
this.#pendingInputs.take(queued, scope);
|
|
1500
|
+
return queued;
|
|
1501
|
+
}
|
|
1502
|
+
#drainPendingInput() {
|
|
1503
|
+
if (this.#pending === null || this.#disposed)
|
|
1504
|
+
return;
|
|
1505
|
+
const queued = this.#takeQueuedInput();
|
|
1506
|
+
if (queued === undefined)
|
|
1507
|
+
return;
|
|
1508
|
+
const pending = this.#pending;
|
|
1509
|
+
this.#pending = null;
|
|
1510
|
+
pending.offAbort?.();
|
|
1511
|
+
pending.resolve(queued);
|
|
1512
|
+
}
|
|
1353
1513
|
readInput(signal) {
|
|
1354
1514
|
if (this.#pending !== null)
|
|
1355
1515
|
return Promise.reject(new Error('omdsh-tui: input read already in flight'));
|
|
@@ -1365,7 +1525,7 @@ export class LocalTui {
|
|
|
1365
1525
|
return this.#plain.readline(signal);
|
|
1366
1526
|
// Lines submitted while a turn was still running were queued instead of
|
|
1367
1527
|
// dropped; serve the oldest before waiting for fresh input.
|
|
1368
|
-
const queued = this.#
|
|
1528
|
+
const queued = this.#takeQueuedInput();
|
|
1369
1529
|
if (queued !== undefined) {
|
|
1370
1530
|
this.#render();
|
|
1371
1531
|
return Promise.resolve(queued);
|
|
@@ -1387,20 +1547,83 @@ export class LocalTui {
|
|
|
1387
1547
|
}
|
|
1388
1548
|
/** Compatibility helper for provider-level text editing tests and embedders. */
|
|
1389
1549
|
async readline() {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1550
|
+
const submission = await this.readInput();
|
|
1551
|
+
if (submission !== null)
|
|
1552
|
+
this.#pendingInputs.finish(submission);
|
|
1553
|
+
return submission?.text ?? null;
|
|
1554
|
+
}
|
|
1555
|
+
restoreInput(submission, sessionId) {
|
|
1556
|
+
const origin = this.#pendingInputs.receipt(submission)?.sessionId;
|
|
1557
|
+
if (origin !== '__default__')
|
|
1558
|
+
sessionId ??= origin;
|
|
1559
|
+
if (sessionId !== undefined && sessionId !== this.#draftScope || this.#prompt !== null || this.#disposed) {
|
|
1560
|
+
const target = sessionId ?? this.#draftScope;
|
|
1561
|
+
const current = target === this.#draftScope;
|
|
1562
|
+
const editor = new InputEditor();
|
|
1563
|
+
const images = new ComposerImages({ editor, notice: text => this.notice(text) });
|
|
1564
|
+
const files = new ComposerFiles({ text: () => editor.text,
|
|
1565
|
+
append: marker => { editor.setCursor(editor.text.length); editor.handle({ type: 'text', value: (editor.text === '' ? '' : ' ') + marker }); } });
|
|
1566
|
+
let existing = current ? this.#captureDraft() : target === undefined ? undefined : this.#unsavedDrafts.get(target);
|
|
1567
|
+
try {
|
|
1568
|
+
if (target !== undefined)
|
|
1569
|
+
existing ??= this.#draftStore?.load(target);
|
|
1570
|
+
}
|
|
1571
|
+
catch (error) {
|
|
1572
|
+
this.#reportDraftError('restore', error);
|
|
1573
|
+
}
|
|
1574
|
+
if (existing !== undefined) {
|
|
1575
|
+
images.replace({ text: existing.editor.text, images: existing.images });
|
|
1576
|
+
editor.restore(existing.editor);
|
|
1577
|
+
files.restoreSnapshot(existing.files);
|
|
1578
|
+
}
|
|
1579
|
+
images.restore(submission, files.adopt(submission.files ?? []));
|
|
1580
|
+
const draft = { editor: editor.snapshot(), images: images.copies(), files: files.snapshot(editor.text), literal: existing?.literal === true || submission.literal === true };
|
|
1581
|
+
if (current && this.#prompt !== null) {
|
|
1582
|
+
const answer = this.#editor.snapshot();
|
|
1583
|
+
this.#restoreDraft(draft);
|
|
1584
|
+
this.#prompt.savedInput = draft.editor;
|
|
1585
|
+
this.#editor.restore(answer);
|
|
1586
|
+
this.#draftDirty = true;
|
|
1587
|
+
this.#flushDraft();
|
|
1588
|
+
if (this.#draftStore === undefined || !this.#unsavedDrafts.has(this.#pendingScope()))
|
|
1589
|
+
this.#pendingInputs.finish(submission);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
if (target === undefined)
|
|
1593
|
+
return;
|
|
1594
|
+
this.#unsavedDrafts.set(target, draft);
|
|
1595
|
+
try {
|
|
1596
|
+
if (this.#draftStore !== undefined) {
|
|
1597
|
+
this.#draftStore.save(target, draft);
|
|
1598
|
+
this.#unsavedDrafts.delete(target);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
catch (error) {
|
|
1602
|
+
this.#reportDraftError('save', error);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
this.#pendingInputs.finish(submission);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1393
1608
|
this.#literalInput ||= submission.literal === true;
|
|
1394
|
-
this.#images.restore(submission);
|
|
1395
|
-
|
|
1396
|
-
|
|
1609
|
+
this.#images.restore(submission, this.#files.adopt(submission.files ?? []));
|
|
1610
|
+
this.#draftDirty = true;
|
|
1611
|
+
this.#flushDraft();
|
|
1612
|
+
if (this.#draftStore === undefined || !this.#unsavedDrafts.has(this.#pendingScope()))
|
|
1613
|
+
this.#pendingInputs.finish(submission);
|
|
1397
1614
|
this.#refreshAutocomplete();
|
|
1398
1615
|
if (this.#tty)
|
|
1399
1616
|
this.#render();
|
|
1400
1617
|
}
|
|
1401
|
-
resolveQueueEdit(submission) {
|
|
1618
|
+
resolveQueueEdit(submission, request) {
|
|
1619
|
+
if (request !== undefined && (request !== this.#queueRecall?.request || request.signal.aborted)) {
|
|
1620
|
+
if (submission !== null)
|
|
1621
|
+
this.restoreInput(submission, request.sessionId);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1402
1624
|
if (!this.#queueEditPending)
|
|
1403
1625
|
return;
|
|
1626
|
+
this.#queueRecall = undefined;
|
|
1404
1627
|
this.#queueEditPending = false;
|
|
1405
1628
|
if (submission === null) {
|
|
1406
1629
|
if (this.#editor.text === '' && this.#images.count === 0 && this.#queueEditNewer?.length === 0) {
|
|
@@ -1410,25 +1633,21 @@ export class LocalTui {
|
|
|
1410
1633
|
}
|
|
1411
1634
|
if (this.#queueEditNewer === null)
|
|
1412
1635
|
this.#queueEditNewer = [];
|
|
1413
|
-
if (this.#
|
|
1636
|
+
if (this.#hasComposerContent()) {
|
|
1414
1637
|
this.#queueEditNewer.unshift(this.#currentSubmission());
|
|
1415
1638
|
}
|
|
1416
1639
|
this.#replaceInput(submission);
|
|
1417
1640
|
}
|
|
1418
1641
|
#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
|
-
};
|
|
1642
|
+
return this.#buildSubmission(this.#editor.expandedText).submission;
|
|
1426
1643
|
}
|
|
1427
1644
|
#replaceInput(submission) {
|
|
1428
1645
|
this.#literalInput = submission.literal === true;
|
|
1429
|
-
this.#
|
|
1430
|
-
this.#files.
|
|
1646
|
+
this.#files.clear();
|
|
1647
|
+
this.#images.replace(submission, this.#files.adopt(submission.files ?? []));
|
|
1431
1648
|
this.#historyIndex = 0;
|
|
1649
|
+
this.#draftDirty = true;
|
|
1650
|
+
this.#flushDraft();
|
|
1432
1651
|
this.#refreshAutocomplete();
|
|
1433
1652
|
if (this.#tty)
|
|
1434
1653
|
this.#render();
|
|
@@ -1438,30 +1657,39 @@ export class LocalTui {
|
|
|
1438
1657
|
return () => { this.#interrupts.delete(listener); };
|
|
1439
1658
|
}
|
|
1440
1659
|
pendingInputs = {
|
|
1660
|
+
receipt: submission => this.#pendingInputs.receipt(submission),
|
|
1661
|
+
prepare: (submission, messageId) => this.#pendingInputs.prepare(submission, messageId),
|
|
1662
|
+
acknowledge: submission => { this.#pendingInputs.finish(submission); if (this.#tty && !this.#disposed)
|
|
1663
|
+
this.#render(); },
|
|
1664
|
+
reconcile: (sessionId, accepted) => this.#pendingInputs.reconcile(sessionId, accepted),
|
|
1441
1665
|
list: () => {
|
|
1442
1666
|
if (this.#disposed)
|
|
1443
1667
|
return [];
|
|
1444
1668
|
if (this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1445
1669
|
throw new Error('Finish editing the queued follow-up with Enter before opening Queue.');
|
|
1446
1670
|
return this.#queuedSubmissions.map(submission => {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
id = `local-${++this.#queuedInputSerial}`;
|
|
1450
|
-
this.#queuedInputIds.set(submission, id);
|
|
1451
|
-
}
|
|
1452
|
-
return { id, submission };
|
|
1671
|
+
const receipt = this.#pendingInputs.identify(submission, this.#pendingScope());
|
|
1672
|
+
return { id: receipt.id, submission, ...(receipt.recovered ? { recovered: true } : {}) };
|
|
1453
1673
|
});
|
|
1454
1674
|
},
|
|
1455
1675
|
update: (id, change) => {
|
|
1456
1676
|
if (this.#disposed)
|
|
1457
1677
|
return false;
|
|
1458
|
-
const index = this.#queuedSubmissions.findIndex(item => this.#
|
|
1678
|
+
const index = this.#queuedSubmissions.findIndex(item => this.#pendingInputs.receipt(item)?.id === id);
|
|
1459
1679
|
if (index < 0)
|
|
1460
1680
|
return false;
|
|
1461
|
-
if (change.kind === '
|
|
1681
|
+
if (change.kind === 'release') {
|
|
1682
|
+
const submission = this.#queuedSubmissions[index];
|
|
1683
|
+
this.#queuedSubmissions.splice(index, 1);
|
|
1684
|
+
this.#dispatchInput(submission);
|
|
1685
|
+
}
|
|
1686
|
+
else if (change.kind === 'remove')
|
|
1462
1687
|
this.#queuedSubmissions.splice(index, 1);
|
|
1463
1688
|
else if (change.kind === 'replace') {
|
|
1464
|
-
this.#
|
|
1689
|
+
const old = this.#pendingInputs.receipt(this.#queuedSubmissions[index]);
|
|
1690
|
+
const receipt = this.#pendingInputs.identify(change.submission, this.#pendingScope());
|
|
1691
|
+
receipt.id = id;
|
|
1692
|
+
receipt.recovered = old.recovered ?? false;
|
|
1465
1693
|
this.#queuedSubmissions[index] = change.submission;
|
|
1466
1694
|
}
|
|
1467
1695
|
else {
|
|
@@ -1477,6 +1705,46 @@ export class LocalTui {
|
|
|
1477
1705
|
return true;
|
|
1478
1706
|
},
|
|
1479
1707
|
};
|
|
1708
|
+
setContinueHandler(handler) {
|
|
1709
|
+
this.#continueHandler = handler;
|
|
1710
|
+
return () => { if (this.#continueHandler === handler)
|
|
1711
|
+
this.#continueHandler = undefined; };
|
|
1712
|
+
}
|
|
1713
|
+
#canContinue() {
|
|
1714
|
+
if (this.#continueHandler === undefined || this.#continuing || this.#disposed || this.#sessionId === undefined
|
|
1715
|
+
|| this.#sessionControls?.continuation === undefined || this.#inspected !== undefined || this.#state.status !== 'idle'
|
|
1716
|
+
|| this.#prompt !== null || this.#hasComposerContent() || this.#pasteInFlight > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1717
|
+
return false;
|
|
1718
|
+
const pending = this.#pendingInputs.session(this.#sessionId);
|
|
1719
|
+
return pending.queued.length === 0 && pending.sending.size === 0
|
|
1720
|
+
&& this.#state.nextTurnInbox.length === 0 && !this.#state.nextStepInbox.some(message => message.source.kind === 'user');
|
|
1721
|
+
}
|
|
1722
|
+
setGoalHandler(handler) {
|
|
1723
|
+
this.#goalHandler = handler;
|
|
1724
|
+
this.#syncSettingsActions();
|
|
1725
|
+
return () => { if (this.#goalHandler === handler) {
|
|
1726
|
+
this.#goalAbort?.abort();
|
|
1727
|
+
this.#goalHandler = undefined;
|
|
1728
|
+
this.#syncSettingsActions();
|
|
1729
|
+
} };
|
|
1730
|
+
}
|
|
1731
|
+
#openGoal() {
|
|
1732
|
+
if (this.#inspected !== undefined)
|
|
1733
|
+
return;
|
|
1734
|
+
if (this.#goalHandler === undefined) {
|
|
1735
|
+
this.notice('Goal controls are unavailable in this profile.');
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
if (this.#goalAbort !== undefined)
|
|
1739
|
+
return;
|
|
1740
|
+
const abort = new AbortController();
|
|
1741
|
+
this.#goalAbort = abort;
|
|
1742
|
+
void this.#goalHandler(abort.signal).catch(error => {
|
|
1743
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
1744
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
1745
|
+
}).finally(() => { if (this.#goalAbort === abort)
|
|
1746
|
+
this.#goalAbort = undefined; });
|
|
1747
|
+
}
|
|
1480
1748
|
setQueueHandler(handler) {
|
|
1481
1749
|
this.#queueHandler = handler;
|
|
1482
1750
|
return () => { if (this.#queueHandler === handler) {
|
|
@@ -1496,6 +1764,10 @@ export class LocalTui {
|
|
|
1496
1764
|
this.#inspects.add(listener);
|
|
1497
1765
|
return () => { this.#inspects.delete(listener); };
|
|
1498
1766
|
}
|
|
1767
|
+
onInspectWorkflow(listener) {
|
|
1768
|
+
this.#workflowInspects.add(listener);
|
|
1769
|
+
return () => { this.#workflowInspects.delete(listener); };
|
|
1770
|
+
}
|
|
1499
1771
|
onInspectClose(listener) {
|
|
1500
1772
|
this.#inspectCloses.add(listener);
|
|
1501
1773
|
return () => { this.#inspectCloses.delete(listener); };
|
|
@@ -1504,11 +1776,6 @@ export class LocalTui {
|
|
|
1504
1776
|
this.#inspectSubmits.add(listener);
|
|
1505
1777
|
return () => { this.#inspectSubmits.delete(listener); };
|
|
1506
1778
|
}
|
|
1507
|
-
setSteerHandler(handler) {
|
|
1508
|
-
this.#steerHandler = handler;
|
|
1509
|
-
return () => { if (this.#steerHandler === handler)
|
|
1510
|
-
this.#steerHandler = undefined; };
|
|
1511
|
-
}
|
|
1512
1779
|
/**
|
|
1513
1780
|
* Last-resort restore for a process that exits without disposing: leave the
|
|
1514
1781
|
* alternate screen, give every mode back and show the cursor. Writes to a TTY
|
|
@@ -1529,10 +1796,11 @@ export class LocalTui {
|
|
|
1529
1796
|
return this.#disposeTask ?? this.#herdr.release();
|
|
1530
1797
|
this.#flushPasteBurst(true);
|
|
1531
1798
|
this.#flushDraft();
|
|
1799
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1532
1800
|
this.#disposed = true;
|
|
1533
|
-
this.#steerHandler = undefined;
|
|
1534
1801
|
this.#imagePreviewAbort?.abort();
|
|
1535
1802
|
this.#fileReviewAbort?.abort();
|
|
1803
|
+
this.#closeFileBrowser();
|
|
1536
1804
|
this.#asideAbort?.abort();
|
|
1537
1805
|
for (const directory of this.#imageTemporary) {
|
|
1538
1806
|
try {
|
|
@@ -1541,8 +1809,13 @@ export class LocalTui {
|
|
|
1541
1809
|
catch { /* OS may still hold the opened file. */ }
|
|
1542
1810
|
}
|
|
1543
1811
|
this.#imageTemporary.clear();
|
|
1812
|
+
this.#goalAbort?.abort();
|
|
1813
|
+
this.#goalAbort = undefined;
|
|
1814
|
+
this.#goalHandler = undefined;
|
|
1544
1815
|
this.#queueHotkeyAbort?.abort();
|
|
1545
1816
|
this.#queueHandler = undefined;
|
|
1817
|
+
this.#continueHandler = undefined;
|
|
1818
|
+
this.#cancelQueueRecall();
|
|
1546
1819
|
if (this.#notificationTimer !== undefined)
|
|
1547
1820
|
clearTimeout(this.#notificationTimer);
|
|
1548
1821
|
this.#notificationQueue.clear();
|
|
@@ -1590,7 +1863,7 @@ export class LocalTui {
|
|
|
1590
1863
|
this.#session.dispose();
|
|
1591
1864
|
this.#term.output.write('\x1b[?25h\r\n');
|
|
1592
1865
|
if (this.#resumeHintRequested && this.#sessionId !== undefined) {
|
|
1593
|
-
this.#term.output.write(`\r\
|
|
1866
|
+
this.#term.output.write(`\r\n${uiText(resolveUiLanguage(this.#uiLanguage), 'Resume this session with {command}', { command: `${APP_NAME} --resume ${this.#sessionId}` })}\r\n`);
|
|
1594
1867
|
}
|
|
1595
1868
|
// A tty stdin keeps the event loop alive after the tree disposes;
|
|
1596
1869
|
// release the descriptor so natural completion can exit the process.
|
|
@@ -1638,10 +1911,10 @@ export class LocalTui {
|
|
|
1638
1911
|
/** Countdown shown on the working row while a retry or a compaction is pending. */
|
|
1639
1912
|
#workingLabel() {
|
|
1640
1913
|
if (this.#retryDeadline !== undefined) {
|
|
1641
|
-
return
|
|
1914
|
+
return uiText(resolveUiLanguage(this.#uiLanguage), 'Retrying in {seconds}s', { seconds: Math.max(0, Math.ceil((this.#retryDeadline - Date.now()) / 1000)) });
|
|
1642
1915
|
}
|
|
1643
1916
|
if (this.#state.status === 'compacting' && this.#compactionStartedAt !== undefined) {
|
|
1644
|
-
return
|
|
1917
|
+
return uiText(resolveUiLanguage(this.#uiLanguage), 'Compacting {seconds}s', { seconds: Math.max(0, Math.floor((Date.now() - this.#compactionStartedAt) / 1000)) });
|
|
1645
1918
|
}
|
|
1646
1919
|
return undefined;
|
|
1647
1920
|
}
|
|
@@ -1762,7 +2035,13 @@ export class LocalTui {
|
|
|
1762
2035
|
clearTimeout(this.#draftTimer);
|
|
1763
2036
|
this.#draftTimer = undefined;
|
|
1764
2037
|
const id = this.#draftScope;
|
|
1765
|
-
if (id === undefined
|
|
2038
|
+
if (id === undefined)
|
|
2039
|
+
return;
|
|
2040
|
+
if (this.#draftStore === undefined) {
|
|
2041
|
+
this.#unsavedDrafts.set(id, this.#captureDraft());
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (!this.#draftDirty && !this.#unsavedDrafts.has(id))
|
|
1766
2045
|
return;
|
|
1767
2046
|
const draft = this.#captureDraft();
|
|
1768
2047
|
this.#draftDirty = false;
|
|
@@ -1784,7 +2063,7 @@ export class LocalTui {
|
|
|
1784
2063
|
}
|
|
1785
2064
|
#clearDraft() {
|
|
1786
2065
|
this.#historyDraft = undefined;
|
|
1787
|
-
if (this.#
|
|
2066
|
+
if (this.#draftScope === undefined)
|
|
1788
2067
|
return;
|
|
1789
2068
|
// A sent or explicitly discarded input must not return after a quick exit.
|
|
1790
2069
|
this.#draftFingerprint = this.#draftKey();
|
|
@@ -1792,12 +2071,20 @@ export class LocalTui {
|
|
|
1792
2071
|
this.#flushDraft();
|
|
1793
2072
|
}
|
|
1794
2073
|
#switchDraft(id) {
|
|
1795
|
-
if (
|
|
2074
|
+
if (id === this.#draftScope)
|
|
1796
2075
|
return;
|
|
2076
|
+
this.#goalAbort?.abort();
|
|
2077
|
+
this.#goalAbort = undefined;
|
|
2078
|
+
this.#queueHotkeyAbort?.abort();
|
|
2079
|
+
this.#queueHotkeyAbort = undefined;
|
|
2080
|
+
this.#cancelQueueRecall();
|
|
1797
2081
|
this.#flushPasteBurst(true);
|
|
1798
2082
|
this.#finishPrompt(null);
|
|
1799
2083
|
this.#flushDraft();
|
|
2084
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1800
2085
|
const initialInput = this.#draftScope === undefined && this.#editor.text !== '' ? this.#captureDraft() : undefined;
|
|
2086
|
+
if (this.#draftScope === undefined && id !== undefined)
|
|
2087
|
+
this.#pendingInputs.bindInitial(id);
|
|
1801
2088
|
this.#draftScope = id;
|
|
1802
2089
|
this.#historyIndex = 0;
|
|
1803
2090
|
this.#historyDraft = undefined;
|
|
@@ -1810,7 +2097,7 @@ export class LocalTui {
|
|
|
1810
2097
|
let restoreError;
|
|
1811
2098
|
if (id !== undefined) {
|
|
1812
2099
|
try {
|
|
1813
|
-
const draft = this.#unsavedDrafts.get(id) ?? this.#draftStore
|
|
2100
|
+
const draft = this.#unsavedDrafts.get(id) ?? this.#draftStore?.load(id);
|
|
1814
2101
|
if (draft !== undefined)
|
|
1815
2102
|
this.#restoreDraft(draft);
|
|
1816
2103
|
}
|
|
@@ -1826,6 +2113,7 @@ export class LocalTui {
|
|
|
1826
2113
|
this.#reportDraftError('restore', restoreError);
|
|
1827
2114
|
}
|
|
1828
2115
|
#render() {
|
|
2116
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
1829
2117
|
this.#watchDraft();
|
|
1830
2118
|
if (this.#streamRenderTimer !== null) {
|
|
1831
2119
|
clearTimeout(this.#streamRenderTimer);
|
|
@@ -1848,16 +2136,16 @@ export class LocalTui {
|
|
|
1848
2136
|
imageCellSize: this.#imageCellSize,
|
|
1849
2137
|
mathMode: this.#mathMode,
|
|
1850
2138
|
mermaidMode: this.#mermaidMode,
|
|
1851
|
-
hideThinking:
|
|
2139
|
+
hideThinking: true,
|
|
1852
2140
|
height: this.#term.height(),
|
|
1853
2141
|
model: this.#model,
|
|
1854
2142
|
...(this.#reasoningEffort === undefined ? {} : { reasoningEffort: this.#reasoningEffort }),
|
|
1855
2143
|
input: this.#editor.text,
|
|
1856
2144
|
inputCursor: this.#editor.cursor,
|
|
1857
2145
|
inputImages: this.#images.count,
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2146
|
+
canContinue: this.#canContinue(),
|
|
2147
|
+
queuedSubmissionScope: this.#pendingScope(),
|
|
2148
|
+
queuedSubmissions: [...this.#queuedSubmissions, ...(this.#queueEditNewer ?? [])].map(submission => this.#pendingInputs.receipt(submission)?.recovered ? { ...submission, recovered: true } : submission),
|
|
1861
2149
|
colors: this.#colors,
|
|
1862
2150
|
pwd: this.#pwd,
|
|
1863
2151
|
...(this.#branch !== undefined ? { branch: this.#branch } : {}),
|
|
@@ -1868,12 +2156,16 @@ export class LocalTui {
|
|
|
1868
2156
|
spinnerFrame: this.#spinner,
|
|
1869
2157
|
motion: this.#motion,
|
|
1870
2158
|
colorDepth: this.#colorDepth,
|
|
2159
|
+
compactLinks: this.#compactLinks,
|
|
2160
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
1871
2161
|
themeName: this.#themeName,
|
|
1872
2162
|
terminal: this.#terminalColors,
|
|
1873
2163
|
...(workingLabel === undefined ? {} : { workingLabel }),
|
|
1874
|
-
...(this.#transientStatus
|
|
2164
|
+
...(this.#transientStatus !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), this.#transientStatus) }
|
|
2165
|
+
: this.#selection.active ? { selectionActions: this.#canQuoteSelection() ? 'copy-quote' : 'copy' }
|
|
2166
|
+
: 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
2167
|
scrollStart: this.#follow ? Number.POSITIVE_INFINITY : this.#scrollStart,
|
|
1876
|
-
...(this.#focusBlock === undefined ? {} : { focusBlock: this.#focusBlock, focusBlockEdge: this.#focusBlockEdge ?? 'start' }),
|
|
2168
|
+
...(this.#focusBlock === undefined ? {} : { focusBlock: this.#focusBlock, focusBlockEdge: this.#focusBlockEdge ?? 'start', ...(this.#focusBlockOffset === undefined ? {} : { focusBlockOffset: this.#focusBlockOffset }) }),
|
|
1877
2169
|
...(this.#transcriptSearch === null ? {} : {
|
|
1878
2170
|
transcriptSearch: {
|
|
1879
2171
|
query: this.#transcriptSearch.query,
|
|
@@ -1884,9 +2176,14 @@ export class LocalTui {
|
|
|
1884
2176
|
toolsExpanded: this.#toolsExpanded,
|
|
1885
2177
|
expandedOutputs: this.#expandedOutputs,
|
|
1886
2178
|
expandedTools: this.#detailedToolIds(),
|
|
2179
|
+
rawTools: this.#detailedToolIds(false),
|
|
2180
|
+
expandedReasoning: this.#visibleReasoning(),
|
|
2181
|
+
...(this.#processSelection === undefined ? {} : { selectedProcess: this.#processSelection }),
|
|
1887
2182
|
toolDetailsKey: keysForAction(this.#keybindings, 'toggle-tool-details'),
|
|
1888
2183
|
turnDetailsKey: keysForAction(this.#keybindings, 'toggle-tools'),
|
|
1889
|
-
|
|
2184
|
+
busyEnter: this.#queueEditNewer !== null || this.#queueEditPending ? 'queue' : this.#busyEnter,
|
|
2185
|
+
steerKey: keysForAction(this.#keybindings, 'steer-turn'),
|
|
2186
|
+
queueKey: keysForAction(this.#keybindings, 'follow-up'),
|
|
1890
2187
|
openedGroups: this.#openedGroups,
|
|
1891
2188
|
commands: this.#commands(),
|
|
1892
2189
|
modelReady: this.#modelReady,
|
|
@@ -1897,6 +2194,7 @@ export class LocalTui {
|
|
|
1897
2194
|
...(this.#subagentSelection === undefined ? {} : { subagentSelection: this.#subagentSelection }),
|
|
1898
2195
|
...(this.#inspected === undefined ? {} : { inspected: this.#inspected }),
|
|
1899
2196
|
...(this.#trajectory === null ? {} : { trajectory: this.#trajectory }),
|
|
2197
|
+
...(this.#fileBrowser === null ? {} : { fileBrowser: this.#fileBrowser.state }),
|
|
1900
2198
|
...(this.#aside === undefined ? {} : { aside: this.#aside }),
|
|
1901
2199
|
statusBar: this.#statusBar,
|
|
1902
2200
|
...(this.#prompt === null ? {} : { promptSelector: this.#prompt }),
|
|
@@ -1909,6 +2207,9 @@ export class LocalTui {
|
|
|
1909
2207
|
: { lines: [] };
|
|
1910
2208
|
this.#focusBlock = undefined;
|
|
1911
2209
|
this.#focusBlockEdge = undefined;
|
|
2210
|
+
this.#focusBlockOffset = undefined;
|
|
2211
|
+
this.#promptActions = frame.promptActions ?? [];
|
|
2212
|
+
this.#promptChoices = frame.promptChoices ?? [];
|
|
1912
2213
|
this.#promptDocument = frame.promptDocument;
|
|
1913
2214
|
if (this.#prompt !== null && this.#prompt.documentScroll !== Number.POSITIVE_INFINITY && frame.promptDocument?.position !== undefined) {
|
|
1914
2215
|
this.#prompt = { ...this.#prompt, documentAnchor: frame.promptDocument.position };
|
|
@@ -1916,8 +2217,8 @@ export class LocalTui {
|
|
|
1916
2217
|
this.#syncScroll(frame.transcript);
|
|
1917
2218
|
this.#jumpToLatest = frame.jumpToLatest;
|
|
1918
2219
|
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));
|
|
2220
|
+
this.#setMouseTracking(this.#fileBrowser !== null || this.#trajectory !== null || this.#documentMouseEnabled() || this.#promptChoices.length > 0 || this.#promptActions.length > 0 || (frame.transcript !== undefined && frame.transientSurface !== 'overlay'
|
|
2221
|
+
&& (this.#inspecting || this.#maxStart > 0 || (frame.transcript.fileRegions?.length ?? 0) > 0 || (frame.transcript.disclosures?.length ?? 0) > 0)));
|
|
1921
2222
|
// A run opening or folding reshapes rows above the screen in place; the
|
|
1922
2223
|
// renderer's frozen boundary is a row index and has to be told, or a fold
|
|
1923
2224
|
// blanks the screen and a large one replays the transcript into history.
|
|
@@ -1996,14 +2297,38 @@ export class LocalTui {
|
|
|
1996
2297
|
this.#toolsExpanded = false;
|
|
1997
2298
|
this.#openedGroups.clear();
|
|
1998
2299
|
this.#detailedTurns.clear();
|
|
2300
|
+
this.#expandedOutputs.clear();
|
|
2301
|
+
this.#expandedToolRows.clear();
|
|
2302
|
+
this.#toolReadingPositions.clear();
|
|
2303
|
+
this.#expandedReasoning.clear();
|
|
2304
|
+
this.#closedReasoning.clear();
|
|
2305
|
+
this.#processSelection = undefined;
|
|
2306
|
+
}
|
|
2307
|
+
#documentMouseEnabled() {
|
|
2308
|
+
const prompt = this.#prompt;
|
|
2309
|
+
return prompt !== null && prompt.feedback !== true && this.#promptDocument !== undefined
|
|
2310
|
+
&& (prompt.request.presentation === 'document' || prompt.request.presentation === 'plan-review');
|
|
2311
|
+
}
|
|
2312
|
+
#scrollPromptDocument(row) {
|
|
2313
|
+
if (this.#prompt === null)
|
|
2314
|
+
return;
|
|
2315
|
+
this.#prompt = {
|
|
2316
|
+
...this.#prompt,
|
|
2317
|
+
documentAnchor: undefined,
|
|
2318
|
+
documentTarget: undefined,
|
|
2319
|
+
documentScroll: row === Number.POSITIVE_INFINITY ? row : Math.max(0, Math.min(row, this.#promptDocument?.maxStart ?? 0)),
|
|
2320
|
+
};
|
|
2321
|
+
this.#render();
|
|
1999
2322
|
}
|
|
2000
2323
|
/** Capture transcript navigation; overlays and disposal restore terminal ownership. */
|
|
2001
2324
|
#setMouseTracking(enabled) {
|
|
2002
2325
|
enabled = enabled && this.#mouseInteraction !== 'native'
|
|
2003
2326
|
&& (this.#mouseInteraction === 'tui' || this.#tmuxMouseEnabled !== false);
|
|
2004
2327
|
if (!enabled) {
|
|
2328
|
+
this.#mouseActionPress = undefined;
|
|
2005
2329
|
this.#selection.clear();
|
|
2006
2330
|
this.#jumpPress = undefined;
|
|
2331
|
+
this.#disclosurePress = undefined;
|
|
2007
2332
|
}
|
|
2008
2333
|
if (!this.#tty || enabled === this.#mouseTracking)
|
|
2009
2334
|
return;
|
|
@@ -2035,7 +2360,7 @@ export class LocalTui {
|
|
|
2035
2360
|
* or go back to the live tail once nothing is open any more.
|
|
2036
2361
|
*/
|
|
2037
2362
|
#inspect(target) {
|
|
2038
|
-
const open = this.#toolsExpanded || this.#openedGroups.size > 0;
|
|
2363
|
+
const open = this.#toolsExpanded || this.#openedGroups.size > 0 || this.#expandedOutputs.size > 0;
|
|
2039
2364
|
if (!open) {
|
|
2040
2365
|
this.#inspecting = false;
|
|
2041
2366
|
this.#followTail();
|
|
@@ -2079,9 +2404,9 @@ export class LocalTui {
|
|
|
2079
2404
|
}
|
|
2080
2405
|
}
|
|
2081
2406
|
#routeInputBatch(events) {
|
|
2082
|
-
if (this.#pasteProtection &&
|
|
2407
|
+
if (this.#pasteProtection && !this.#paste && this.#pasteInFlight === 0
|
|
2083
2408
|
&& this.#prompt === null && this.#settings === null
|
|
2084
|
-
&& this.#search === null && this.#trajectory === null && this.#transcriptSearch === null
|
|
2409
|
+
&& this.#search === null && this.#trajectory === null && this.#fileBrowser === null && this.#transcriptSearch === null
|
|
2085
2410
|
&& events.every(event => event.type === 'text' || (event.type === 'key' && ['enter', 'ctrl+j', 'tab'].includes(event.id)))) {
|
|
2086
2411
|
if (this.#pasteBurstTimer !== undefined)
|
|
2087
2412
|
clearTimeout(this.#pasteBurstTimer);
|
|
@@ -2104,8 +2429,8 @@ export class LocalTui {
|
|
|
2104
2429
|
this.#dispatch(event);
|
|
2105
2430
|
return;
|
|
2106
2431
|
}
|
|
2107
|
-
const composer = this.#
|
|
2108
|
-
&& this.#search === null && this.#trajectory === null && this.#transcriptSearch === null;
|
|
2432
|
+
const composer = this.#prompt === null && this.#settings === null
|
|
2433
|
+
&& this.#search === null && this.#trajectory === null && this.#fileBrowser === null && this.#transcriptSearch === null;
|
|
2109
2434
|
if (this.#paste || this.#pasteInFlight > 0 || !this.#pasteProtection || !composer
|
|
2110
2435
|
|| event.type === 'paste-start' || event.type === 'paste-end') {
|
|
2111
2436
|
this.#flushPasteBurst(true);
|
|
@@ -2156,6 +2481,10 @@ export class LocalTui {
|
|
|
2156
2481
|
});
|
|
2157
2482
|
}
|
|
2158
2483
|
async #acceptPastedText(text) {
|
|
2484
|
+
if (this.#fileBrowser !== null) {
|
|
2485
|
+
this.#applyFileBrowserInput({ type: 'text', value: text });
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2159
2488
|
if (this.#prompt !== null) {
|
|
2160
2489
|
const prompt = this.#prompt;
|
|
2161
2490
|
if (prompt.documentInput !== undefined) {
|
|
@@ -2254,14 +2583,198 @@ export class LocalTui {
|
|
|
2254
2583
|
if (this.#tty)
|
|
2255
2584
|
this.#render();
|
|
2256
2585
|
}
|
|
2586
|
+
/** Resolve only current painted rows, never reinterpret a frozen native-history snapshot. */
|
|
2587
|
+
#disclosureAt(row, column, action = false) {
|
|
2588
|
+
const frame = this.#fileActionFrame;
|
|
2589
|
+
const document = this.#renderer.documentRowAt(row);
|
|
2590
|
+
const source = frame?.documentRows;
|
|
2591
|
+
if (document === undefined || source == null || document < source.documentStart || document >= source.documentEnd)
|
|
2592
|
+
return;
|
|
2593
|
+
const target = frame?.transcript?.disclosures?.find(item => item.row === document && (action
|
|
2594
|
+
? item.actionColumn !== undefined && column >= item.actionColumn && column < item.actionColumn + (item.key.startsWith('output:') ? 6 : 5)
|
|
2595
|
+
: column >= item.column && column < item.column + item.width));
|
|
2596
|
+
const expected = frame?.lines[source.frameStart + document - source.documentStart];
|
|
2597
|
+
const actual = this.#renderer.viewport()[row];
|
|
2598
|
+
if (target === undefined || expected === undefined || actual === undefined
|
|
2599
|
+
|| stripAnsi(sanitizeDisplayLine(expected)).trimEnd() !== stripAnsi(actual).trimEnd())
|
|
2600
|
+
return;
|
|
2601
|
+
return target;
|
|
2602
|
+
}
|
|
2603
|
+
/** Preview text and spinner frames may change while a click stays on the same item. */
|
|
2604
|
+
#pressedDisclosureAt(row, column) {
|
|
2605
|
+
const pressed = this.#disclosurePress;
|
|
2606
|
+
if (pressed === undefined || pressed.row !== row)
|
|
2607
|
+
return;
|
|
2608
|
+
if (pressed.target.key.startsWith('output:') && Math.abs(pressed.column - column) > 1)
|
|
2609
|
+
return;
|
|
2610
|
+
const target = this.#disclosureAt(row, column);
|
|
2611
|
+
if (target?.key === pressed.target.key && target.row === pressed.target.row
|
|
2612
|
+
&& target.column === pressed.target.column && target.width === pressed.target.width)
|
|
2613
|
+
return target;
|
|
2614
|
+
}
|
|
2615
|
+
#activateDisclosure(target) {
|
|
2616
|
+
const output = this.#state.blocks[target.block];
|
|
2617
|
+
if (output?.kind === 'commandOutput' && target.key === `output:${target.block}`) {
|
|
2618
|
+
if (this.#expandedOutputs.has(output))
|
|
2619
|
+
this.#expandedOutputs.delete(output);
|
|
2620
|
+
else
|
|
2621
|
+
this.#expandedOutputs.add(output);
|
|
2622
|
+
this.#inspect(target.block);
|
|
2623
|
+
this.#focusBlock = target.block;
|
|
2624
|
+
this.#focusBlockEdge = 'start';
|
|
2625
|
+
this.#render();
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
const groups = turnGroups(this.#state);
|
|
2629
|
+
const group = groups.find(item => target.group === undefined
|
|
2630
|
+
? target.block >= item.start && target.block < item.until : item.key === target.group);
|
|
2631
|
+
if (group === undefined)
|
|
2632
|
+
return;
|
|
2633
|
+
const block = this.#state.blocks[target.block];
|
|
2634
|
+
if (target.group === undefined && target.key !== (block?.kind === 'tool' ? `call:${block.callId}`
|
|
2635
|
+
: block?.kind === 'assistant' ? `thought:${reasoningKey(block)}` : undefined))
|
|
2636
|
+
return;
|
|
2637
|
+
const scope = this.#detailScope(group);
|
|
2638
|
+
for (const member of groups)
|
|
2639
|
+
if (this.#detailScope(member) === scope)
|
|
2640
|
+
this.#openedGroups.add(member.key);
|
|
2641
|
+
this.#processSelection = this.#processRows().includes(target.block) ? target.block : this.#processRows()[0];
|
|
2642
|
+
this.#ac = null;
|
|
2643
|
+
this.#inspect(target.block);
|
|
2644
|
+
if (target.group === undefined)
|
|
2645
|
+
this.#toggleProcessItem(target.block, 'enter');
|
|
2646
|
+
else {
|
|
2647
|
+
this.#focusBlock = target.block;
|
|
2648
|
+
this.#focusBlockEdge = 'start';
|
|
2649
|
+
this.#render();
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
#mouseActionAt(row, column) {
|
|
2653
|
+
const frame = this.#fileActionFrame;
|
|
2654
|
+
const viewport = this.#renderer.viewport();
|
|
2655
|
+
for (const target of frame?.selectionActions ?? []) {
|
|
2656
|
+
const physical = viewport.length - (frame.lines.length - target.row);
|
|
2657
|
+
if (row === physical && column >= target.column && column < target.column + target.width
|
|
2658
|
+
&& this.#selection.active && this.#selection.valid(viewport)
|
|
2659
|
+
&& stripAnsi(viewport[row] ?? '').trimEnd() === stripAnsi(frame.lines[target.row] ?? '').trimEnd()) {
|
|
2660
|
+
return { ...target, row: physical, key: target.action };
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
const tool = this.#disclosureAt(row, column, true);
|
|
2664
|
+
if (tool?.actionColumn !== undefined)
|
|
2665
|
+
return { row, column: tool.actionColumn, width: tool.key.startsWith('output:') ? 6 : 5, key: tool.key, action: 'tool', block: tool.block };
|
|
2666
|
+
return undefined;
|
|
2667
|
+
}
|
|
2257
2668
|
#handleMouse(event) {
|
|
2258
2669
|
if (!this.#mouseTracking || this.#paste || this.#pasteInFlight > 0)
|
|
2259
2670
|
return;
|
|
2671
|
+
if (this.#trajectory !== null || this.#fileBrowser !== null) {
|
|
2672
|
+
this.#handleTrajectoryMouse(event);
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
const promptAction = this.#promptActions.find(action => event.row === action.row && event.column >= action.column && event.column < action.column + action.width);
|
|
2676
|
+
if (this.#promptActionPress !== undefined) {
|
|
2677
|
+
const pressed = this.#promptActionPress;
|
|
2678
|
+
if (event.action === 'release') {
|
|
2679
|
+
this.#promptActionPress = undefined;
|
|
2680
|
+
this.#promptChoicePress = undefined;
|
|
2681
|
+
if (event.button === 'left' && !event.shift && promptAction?.index === pressed.index && promptAction.key === pressed.key
|
|
2682
|
+
&& promptAction.row === pressed.row && promptAction.column === pressed.column && this.#prompt !== null) {
|
|
2683
|
+
if (pressed.key === undefined) {
|
|
2684
|
+
this.#prompt = { ...this.#prompt, selected: pressed.index, documentInput: undefined };
|
|
2685
|
+
this.#handlePlanReview({ type: 'key', id: 'enter' }, this.#prompt);
|
|
2686
|
+
}
|
|
2687
|
+
else if (pressed.selectedValue === (filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected]?.value
|
|
2688
|
+
?? filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected]?.label)) {
|
|
2689
|
+
this.#handlePrompt({ type: 'key', id: pressed.key });
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
else if (event.action === 'drag' && (promptAction?.index !== pressed.index || promptAction.key !== pressed.key
|
|
2694
|
+
|| promptAction.row !== pressed.row || promptAction.column !== pressed.column || event.shift)) {
|
|
2695
|
+
this.#promptActionPress = { ...pressed, index: -2 };
|
|
2696
|
+
}
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (event.button === 'left' && event.action === 'press' && !event.shift && promptAction !== undefined) {
|
|
2700
|
+
this.#selection.clear();
|
|
2701
|
+
const selected = this.#prompt === null ? undefined : filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed)[this.#prompt.selected];
|
|
2702
|
+
this.#promptActionPress = { ...promptAction, selectedValue: selected?.value ?? selected?.label };
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
if (this.#promptChoices.length > 0 && event.button === 'left') {
|
|
2706
|
+
const choice = this.#promptChoices.find(item => item.row === event.row && event.column >= item.column && event.column < item.column + item.width);
|
|
2707
|
+
if (event.action === 'press') {
|
|
2708
|
+
const prompt = this.#prompt;
|
|
2709
|
+
if (prompt?.request.wait !== undefined && prompt.waitHeld !== true) {
|
|
2710
|
+
prompt.request.wait.hold();
|
|
2711
|
+
prompt.offTick?.();
|
|
2712
|
+
this.#prompt = { ...prompt, waitHeld: true };
|
|
2713
|
+
}
|
|
2714
|
+
this.#promptChoicePress = choice === undefined ? undefined : { value: choice.value, row: choice.row, dragged: event.shift };
|
|
2715
|
+
this.#selection.begin(this.#renderer.viewport(), event, Date.now(), event.shift);
|
|
2716
|
+
}
|
|
2717
|
+
else if (event.action === 'drag') {
|
|
2718
|
+
if (this.#promptChoicePress !== undefined)
|
|
2719
|
+
this.#promptChoicePress.dragged = true;
|
|
2720
|
+
this.#selection.move(event);
|
|
2721
|
+
}
|
|
2722
|
+
else {
|
|
2723
|
+
const pressed = this.#promptChoicePress;
|
|
2724
|
+
this.#promptChoicePress = undefined;
|
|
2725
|
+
const text = this.#selection.release(event);
|
|
2726
|
+
if (text !== undefined) {
|
|
2727
|
+
if (this.#copyOnSelect)
|
|
2728
|
+
void this.#copySelection();
|
|
2729
|
+
}
|
|
2730
|
+
else if (pressed !== undefined && !pressed.dragged && !event.shift && choice?.value === pressed.value && choice.row === pressed.row && this.#prompt !== null) {
|
|
2731
|
+
this.#prompt = { ...this.#prompt, selected: choice.index, previewScroll: 0 };
|
|
2732
|
+
if (this.#prompt.request.multiSelect === true && filteredPromptOptions(this.#prompt.request, this.#editor.text)[choice.index]?.disabled !== true)
|
|
2733
|
+
this.#prompt = togglePromptSelection(this.#prompt, this.#editor.text);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
this.#render();
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
const pressedAction = this.#mouseActionPress;
|
|
2740
|
+
if (pressedAction !== undefined) {
|
|
2741
|
+
const target = this.#mouseActionAt(event.row, event.column);
|
|
2742
|
+
if (event.action === 'release') {
|
|
2743
|
+
this.#mouseActionPress = undefined;
|
|
2744
|
+
if (event.button === 'left' && !event.shift && target?.key === pressedAction.key && target.row === pressedAction.row && target.column === pressedAction.column) {
|
|
2745
|
+
if (target.action === 'copy')
|
|
2746
|
+
void this.#copySelection(true);
|
|
2747
|
+
else if (target.action === 'quote')
|
|
2748
|
+
this.#quoteSelection();
|
|
2749
|
+
else {
|
|
2750
|
+
const block = this.#state.blocks[target.block];
|
|
2751
|
+
if (block?.kind === 'tool')
|
|
2752
|
+
void this.#openToolActions(block);
|
|
2753
|
+
else if (block?.kind === 'commandOutput')
|
|
2754
|
+
void this.#readCommandOutput(block);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
else if (event.action === 'drag' && (target?.key !== pressedAction.key || event.shift)) {
|
|
2759
|
+
// Keep consuming the gesture, but never activate after leaving the button.
|
|
2760
|
+
this.#mouseActionPress = { ...pressedAction, key: '' };
|
|
2761
|
+
}
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
if (event.button === 'left' && event.action === 'press' && !event.shift) {
|
|
2765
|
+
const target = this.#mouseActionAt(event.row, event.column);
|
|
2766
|
+
if (target !== undefined) {
|
|
2767
|
+
this.#mouseActionPress = target;
|
|
2768
|
+
this.#disclosurePress = undefined;
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2260
2772
|
const inside = (target) => event.row === target.row && event.column >= target.column && event.column < target.column + target.width;
|
|
2261
2773
|
if (event.button === 'right') {
|
|
2774
|
+
this.#disclosurePress = undefined;
|
|
2262
2775
|
if (event.action === 'press') {
|
|
2263
2776
|
if (this.#selection.active)
|
|
2264
|
-
void this.#copySelection();
|
|
2777
|
+
void this.#copySelection(true);
|
|
2265
2778
|
else {
|
|
2266
2779
|
const document = this.#renderer.documentRowAt(event.row);
|
|
2267
2780
|
const frame = this.#fileActionFrame;
|
|
@@ -2283,6 +2796,8 @@ export class LocalTui {
|
|
|
2283
2796
|
return;
|
|
2284
2797
|
}
|
|
2285
2798
|
if (event.action === 'press') {
|
|
2799
|
+
const disclosure = event.shift ? undefined : this.#disclosureAt(event.row, event.column);
|
|
2800
|
+
this.#disclosurePress = disclosure === undefined ? undefined : { target: disclosure, row: event.row, column: event.column };
|
|
2286
2801
|
const target = this.#jumpToLatest;
|
|
2287
2802
|
this.#jumpPress = target !== undefined && inside(target) ? target : undefined;
|
|
2288
2803
|
if (this.#jumpPress !== undefined)
|
|
@@ -2301,15 +2816,93 @@ export class LocalTui {
|
|
|
2301
2816
|
this.#followTail();
|
|
2302
2817
|
}
|
|
2303
2818
|
}
|
|
2304
|
-
else if (event.action === 'drag')
|
|
2305
|
-
|
|
2819
|
+
else if (event.action === 'drag') {
|
|
2820
|
+
// Motion confined to the empty gutter is click jitter. Once it leaves,
|
|
2821
|
+
// selection owns the gesture even if the pointer returns to the arrow.
|
|
2822
|
+
if (event.shift || this.#pressedDisclosureAt(event.row, event.column) === undefined) {
|
|
2823
|
+
this.#disclosurePress = undefined;
|
|
2824
|
+
this.#selection.move(event);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2306
2827
|
else {
|
|
2828
|
+
const target = this.#pressedDisclosureAt(event.row, event.column);
|
|
2829
|
+
this.#disclosurePress = undefined;
|
|
2830
|
+
if (!event.shift && target !== undefined) {
|
|
2831
|
+
this.#selection.clear();
|
|
2832
|
+
this.#activateDisclosure(target);
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2307
2835
|
const text = this.#selection.release(event);
|
|
2308
2836
|
if (text !== undefined && this.#copyOnSelect)
|
|
2309
2837
|
void this.#copySelection();
|
|
2310
2838
|
}
|
|
2311
2839
|
this.#render();
|
|
2312
2840
|
}
|
|
2841
|
+
#handleTrajectoryMouse(event) {
|
|
2842
|
+
if (event.button === 'right') {
|
|
2843
|
+
if (event.action === 'press' && this.#selection.active)
|
|
2844
|
+
void this.#copySelection(true);
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
if (event.action === 'press') {
|
|
2848
|
+
this.#trajectoryPress = { row: event.row, column: event.column,
|
|
2849
|
+
text: stripAnsi(this.#renderer.viewport()[event.row] ?? ''), dragged: event.shift };
|
|
2850
|
+
this.#selection.begin(this.#renderer.viewport(), event, Date.now(), event.shift);
|
|
2851
|
+
}
|
|
2852
|
+
else if (event.action === 'drag') {
|
|
2853
|
+
if (this.#trajectoryPress !== undefined)
|
|
2854
|
+
this.#trajectoryPress.dragged = true;
|
|
2855
|
+
this.#selection.move(event);
|
|
2856
|
+
}
|
|
2857
|
+
else {
|
|
2858
|
+
const press = this.#trajectoryPress;
|
|
2859
|
+
this.#trajectoryPress = undefined;
|
|
2860
|
+
const text = this.#selection.release(event);
|
|
2861
|
+
if (text !== undefined) {
|
|
2862
|
+
if (this.#copyOnSelect)
|
|
2863
|
+
void this.#copySelection();
|
|
2864
|
+
}
|
|
2865
|
+
else if (press !== undefined && !press.dragged && !event.shift && press.row === event.row
|
|
2866
|
+
&& Math.abs(press.column - event.column) <= 1
|
|
2867
|
+
&& stripAnsi(this.#renderer.viewport()[event.row] ?? '') === press.text) {
|
|
2868
|
+
if (this.#fileBrowser !== null)
|
|
2869
|
+
this.#fileBrowser.click(event.row, event.column, this.#term.width(), this.#term.height());
|
|
2870
|
+
else if (this.#trajectory !== null)
|
|
2871
|
+
this.#trajectory = trajectoryClick(this.#trajectory, this.#term.width(), this.#term.height(), event.row, event.column);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
this.#render();
|
|
2875
|
+
}
|
|
2876
|
+
#applyTrajectoryInput(event) {
|
|
2877
|
+
const state = this.#trajectory;
|
|
2878
|
+
if (state === null)
|
|
2879
|
+
return;
|
|
2880
|
+
const { pageSize } = trajectoryListMetrics(state, this.#term.height(), this.#term.width());
|
|
2881
|
+
const { pageLines } = trajectoryDetailMetrics(state, this.#term.height());
|
|
2882
|
+
const theme = createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks);
|
|
2883
|
+
const maxDetailScroll = trajectoryDetailScrollLimit(state, theme, this.#term.width(), this.#term.height());
|
|
2884
|
+
const command = applyTrajectoryEvent(state, event, { pageSize, detailPageLines: pageLines, width: this.#term.width(), maxDetailScroll });
|
|
2885
|
+
if ('close' in command) {
|
|
2886
|
+
this.#trajectory = null;
|
|
2887
|
+
this.#trajectoryPress = undefined;
|
|
2888
|
+
}
|
|
2889
|
+
else if ('copy' in command) {
|
|
2890
|
+
void this.#deliverCopy(command.copy).then(delivery => {
|
|
2891
|
+
if (this.#trajectory === state) {
|
|
2892
|
+
this.#trajectory = { ...state, notice: delivery === 'requested' ? 'Sent to terminal clipboard' : `Copied ${command.label}` };
|
|
2893
|
+
this.#render();
|
|
2894
|
+
}
|
|
2895
|
+
}, () => {
|
|
2896
|
+
if (this.#trajectory === state) {
|
|
2897
|
+
this.#trajectory = { ...state, notice: 'Copy failed' };
|
|
2898
|
+
this.#render();
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
else
|
|
2903
|
+
this.#trajectory = command.state;
|
|
2904
|
+
this.#render();
|
|
2905
|
+
}
|
|
2313
2906
|
/** Keep older clipboard operations from finishing after a newer user copy. */
|
|
2314
2907
|
#deliverCopy(text) {
|
|
2315
2908
|
const task = this.#copyTask.then(() => {
|
|
@@ -2320,7 +2913,7 @@ export class LocalTui {
|
|
|
2320
2913
|
this.#copyTask = task.catch(() => { });
|
|
2321
2914
|
return task;
|
|
2322
2915
|
}
|
|
2323
|
-
async #copySelection() {
|
|
2916
|
+
async #copySelection(explicit = false) {
|
|
2324
2917
|
if (!this.#selection.valid(this.#renderer.viewport())) {
|
|
2325
2918
|
this.#selection.clear();
|
|
2326
2919
|
return;
|
|
@@ -2329,11 +2922,99 @@ export class LocalTui {
|
|
|
2329
2922
|
if (text === undefined)
|
|
2330
2923
|
return;
|
|
2331
2924
|
try {
|
|
2332
|
-
await this.#deliverCopy(text);
|
|
2925
|
+
const delivery = await this.#deliverCopy(text);
|
|
2926
|
+
if (explicit && !this.#disposed) {
|
|
2927
|
+
this.#showTransientStatus(delivery === 'requested' ? 'Selection sent to terminal clipboard' : 'Copied selection');
|
|
2928
|
+
this.#render();
|
|
2929
|
+
}
|
|
2333
2930
|
}
|
|
2334
2931
|
catch {
|
|
2335
|
-
if (!this.#disposed)
|
|
2336
|
-
this
|
|
2932
|
+
if (!this.#disposed) {
|
|
2933
|
+
this.#showTransientStatus('Copy failed');
|
|
2934
|
+
this.#render();
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
#canQuoteSelection() {
|
|
2939
|
+
return this.#prompt === null && this.#settings === null && this.#trajectory === null && this.#fileBrowser === null && this.#search === null
|
|
2940
|
+
&& this.#transcriptSearch === null && (this.#inspected === undefined || this.#inspected.writable === true);
|
|
2941
|
+
}
|
|
2942
|
+
#quoteSelection() {
|
|
2943
|
+
if (!this.#canQuoteSelection())
|
|
2944
|
+
return;
|
|
2945
|
+
const text = this.#selection.valid(this.#renderer.viewport()) ? this.#selection.text() : undefined;
|
|
2946
|
+
this.#selection.clear();
|
|
2947
|
+
if (text !== undefined) {
|
|
2948
|
+
this.#appendQuote(text.split('\n').map(line => '> ' + line).join('\n'));
|
|
2949
|
+
}
|
|
2950
|
+
this.#render();
|
|
2951
|
+
}
|
|
2952
|
+
#appendQuote(text) {
|
|
2953
|
+
this.#editor.setCursor(this.#editor.text.length);
|
|
2954
|
+
this.#editor.paste((this.#editor.text === '' ? '' : '\n\n') + text + '\n\n');
|
|
2955
|
+
this.#processSelection = undefined;
|
|
2956
|
+
this.#refreshAutocomplete();
|
|
2957
|
+
this.#showTransientStatus('Quoted in composer');
|
|
2958
|
+
this.#render();
|
|
2959
|
+
}
|
|
2960
|
+
/** Explicit process selection wins; scrolling through an open detail follows that tool. */
|
|
2961
|
+
#currentTool() {
|
|
2962
|
+
const detail = this.#fileActionFrame?.transcript?.disclosures?.findLast(target => target.detailEnd !== undefined && this.#scrollStart > target.row && this.#scrollStart < target.detailEnd);
|
|
2963
|
+
const selected = this.#processSelection;
|
|
2964
|
+
const row = selected === undefined ? undefined : this.#blockStarts[selected];
|
|
2965
|
+
const selectedVisible = row !== undefined && row >= this.#scrollStart && row < this.#scrollStart + this.#scrollBudget - 1;
|
|
2966
|
+
const index = selectedVisible ? selected : detail?.block ?? selected ?? this.#viewportBlock();
|
|
2967
|
+
const block = index === undefined ? undefined : this.#state.blocks[index];
|
|
2968
|
+
return block?.kind === 'tool' ? block : undefined;
|
|
2969
|
+
}
|
|
2970
|
+
async #openToolActions(block = this.#currentTool()) {
|
|
2971
|
+
if (this.#fileReviewAbort !== undefined || this.#search !== null || this.#prompt !== null || this.#settings !== null)
|
|
2972
|
+
return;
|
|
2973
|
+
if (block === undefined) {
|
|
2974
|
+
this.#showTransientStatus(uiText(resolveUiLanguage(this.#uiLanguage), 'No tool selected · use {key}', { key: keysForAction(this.#keybindings, 'toggle-tools') }), 4_000);
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
const command = toolCommand(block);
|
|
2978
|
+
const files = blockFileAction(block);
|
|
2979
|
+
const source = toolSource(block);
|
|
2980
|
+
const options = [
|
|
2981
|
+
...(command === undefined ? [] : [{ label: 'Copy command', value: 'command', description: command.split('\n')[0] ?? '' }]),
|
|
2982
|
+
...(block.output.trim() === '' ? [] : [{ label: 'Copy result', value: 'result' }]),
|
|
2983
|
+
...(files === undefined ? [] : [{ label: 'View file', value: 'files' }]),
|
|
2984
|
+
...(source === undefined ? [] : [{ label: 'Copy source', value: 'source-copy', description: source.label },
|
|
2985
|
+
...(this.#canQuoteSelection() ? [{ label: 'Quote source', value: 'source-quote', description: source.label }] : [])]),
|
|
2986
|
+
];
|
|
2987
|
+
if (options.length === 0) {
|
|
2988
|
+
this.#showTransientStatus('No content available for this tool.', 4_000);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const abort = new AbortController();
|
|
2992
|
+
this.#fileReviewAbort = abort;
|
|
2993
|
+
try {
|
|
2994
|
+
const answer = await this.prompt({ localize: ['title'], title: 'Tool actions', question: toolActionContext(block),
|
|
2995
|
+
options: options.map(option => ({ ...option, label: uiText(resolveUiLanguage(this.#uiLanguage), option.label) })), allowCustom: false, notify: false, signal: abort.signal, onSuperseded: () => { abort.abort(); } });
|
|
2996
|
+
if (abort.signal.aborted || this.#disposed)
|
|
2997
|
+
return;
|
|
2998
|
+
if (answer === 'command' && command !== undefined)
|
|
2999
|
+
await this.#copyPicked(command, 'command');
|
|
3000
|
+
else if (answer === 'result')
|
|
3001
|
+
await this.#copyPicked(block.output, 'tool result');
|
|
3002
|
+
else if (answer === 'source-copy' && source !== undefined)
|
|
3003
|
+
await this.#copyPicked(source.text, 'source');
|
|
3004
|
+
else if (answer === 'source-quote' && source !== undefined && this.#canQuoteSelection())
|
|
3005
|
+
this.#appendQuote(sourceQuote(source));
|
|
3006
|
+
else if (answer === 'files' && files !== undefined) {
|
|
3007
|
+
this.#fileReviewAbort = undefined;
|
|
3008
|
+
this.#openFileAction(files);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
catch (error) {
|
|
3012
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
3013
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
3014
|
+
}
|
|
3015
|
+
finally {
|
|
3016
|
+
if (this.#fileReviewAbort === abort)
|
|
3017
|
+
this.#fileReviewAbort = undefined;
|
|
2337
3018
|
}
|
|
2338
3019
|
}
|
|
2339
3020
|
#openFileAction(action, initial) {
|
|
@@ -2382,10 +3063,23 @@ export class LocalTui {
|
|
|
2382
3063
|
this.#handleMouse(event);
|
|
2383
3064
|
return;
|
|
2384
3065
|
}
|
|
3066
|
+
this.#promptActionPress = undefined;
|
|
3067
|
+
this.#promptChoicePress = undefined;
|
|
2385
3068
|
this.#jumpPress = undefined;
|
|
3069
|
+
this.#disclosurePress = undefined;
|
|
3070
|
+
this.#mouseActionPress = undefined;
|
|
3071
|
+
if (this.#keyTest !== undefined && this.#prompt?.request.signal === this.#keyTest.signal
|
|
3072
|
+
&& (event.type === 'text' || event.type === 'paste-start' || event.type === 'paste-end' || event.type === 'key' && !['escape', 'ctrl+c'].includes(event.id))) {
|
|
3073
|
+
this.#keyTest.observe(event);
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
2386
3076
|
if (this.#selection.active) {
|
|
3077
|
+
if (event.type === 'key' && this.#boundAction(event.id) === 'quote-selection') {
|
|
3078
|
+
this.#quoteSelection();
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
2387
3081
|
if (event.type === 'key' && (['super+c', 'enter'].includes(event.id) || this.#boundAction(event.id) === 'copy-prompt')) {
|
|
2388
|
-
void this.#copySelection();
|
|
3082
|
+
void this.#copySelection(true);
|
|
2389
3083
|
return;
|
|
2390
3084
|
}
|
|
2391
3085
|
this.#selection.clear();
|
|
@@ -2396,32 +3090,37 @@ export class LocalTui {
|
|
|
2396
3090
|
}
|
|
2397
3091
|
}
|
|
2398
3092
|
if (event.type === 'wheel') {
|
|
3093
|
+
if (this.#fileBrowser !== null) {
|
|
3094
|
+
this.#applyFileBrowserInput(event);
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
if (this.#trajectory !== null) {
|
|
3098
|
+
this.#applyTrajectoryInput(event);
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
2399
3101
|
this.#jumpPress = undefined;
|
|
2400
3102
|
if (this.#mouseTracking && !this.#paste && this.#pasteInFlight === 0) {
|
|
2401
3103
|
const step = wheelStep(this.#wheelGesture, Date.now(), event.direction === 'up' ? -1 : 1, !this.#wheelAccelerates);
|
|
2402
3104
|
this.#wheelGesture = step.gesture;
|
|
2403
|
-
if (step.lines !== 0)
|
|
2404
|
-
this.#
|
|
3105
|
+
if (step.lines !== 0) {
|
|
3106
|
+
if (this.#prompt !== null && this.#prompt.request.presentation !== 'document' && this.#prompt.request.presentation !== 'plan-review') {
|
|
3107
|
+
if (this.#prompt.request.wait !== undefined && this.#prompt.waitHeld !== true) {
|
|
3108
|
+
this.#prompt.request.wait.hold();
|
|
3109
|
+
this.#prompt.offTick?.();
|
|
3110
|
+
this.#prompt = { ...this.#prompt, waitHeld: true };
|
|
3111
|
+
}
|
|
3112
|
+
const count = filteredPromptOptions(this.#prompt.request, this.#editor.text, this.#prompt.collapsed).length;
|
|
3113
|
+
this.#prompt = movePromptSelection(this.#prompt, Math.max(0, Math.min(count - 1, this.#prompt.selected + step.lines)), count);
|
|
3114
|
+
this.#render();
|
|
3115
|
+
}
|
|
3116
|
+
else if (this.#documentMouseEnabled())
|
|
3117
|
+
this.#scrollPromptDocument(this.#promptDocument.start + step.lines);
|
|
3118
|
+
else
|
|
3119
|
+
this.#scrollBy(step.lines);
|
|
3120
|
+
}
|
|
2405
3121
|
}
|
|
2406
3122
|
return;
|
|
2407
3123
|
}
|
|
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
3124
|
// Clipboard image inspection is asynchronous. Preserve the exact key
|
|
2426
3125
|
// order so a fast Ctrl+V, Enter submits the finished image draft rather
|
|
2427
3126
|
// than an empty prompt.
|
|
@@ -2449,6 +3148,8 @@ export class LocalTui {
|
|
|
2449
3148
|
return;
|
|
2450
3149
|
}
|
|
2451
3150
|
if (event.type === 'paste-start') {
|
|
3151
|
+
if (this.#prompt === null && this.#settings === null && this.#trajectory === null && this.#fileBrowser === null)
|
|
3152
|
+
this.#processSelection = undefined;
|
|
2452
3153
|
this.#paste = true;
|
|
2453
3154
|
this.#pasteBuf = '';
|
|
2454
3155
|
return;
|
|
@@ -2466,15 +3167,12 @@ export class LocalTui {
|
|
|
2466
3167
|
}
|
|
2467
3168
|
if (this.#handlePrompt(event))
|
|
2468
3169
|
return;
|
|
3170
|
+
if (this.#fileBrowser !== null) {
|
|
3171
|
+
this.#applyFileBrowserInput(event);
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
2469
3174
|
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();
|
|
3175
|
+
this.#applyTrajectoryInput(event);
|
|
2478
3176
|
return;
|
|
2479
3177
|
}
|
|
2480
3178
|
if (this.#transcriptSearch !== null) {
|
|
@@ -2500,6 +3198,8 @@ export class LocalTui {
|
|
|
2500
3198
|
this.#applySettings(event);
|
|
2501
3199
|
return;
|
|
2502
3200
|
}
|
|
3201
|
+
if (this.#search === null && this.#handleProcessNavigation(event))
|
|
3202
|
+
return;
|
|
2503
3203
|
/**
|
|
2504
3204
|
* The side pane is a companion, not a mode: it answers where the reader is
|
|
2505
3205
|
* already working, so it must not take keys the composer is using. It reads
|
|
@@ -2588,10 +3288,7 @@ export class LocalTui {
|
|
|
2588
3288
|
return true;
|
|
2589
3289
|
}
|
|
2590
3290
|
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();
|
|
3291
|
+
// Harness retains accepted input; stopping a turn does not withdraw messages.
|
|
2595
3292
|
for (const listener of this.#interrupts)
|
|
2596
3293
|
listener();
|
|
2597
3294
|
return true;
|
|
@@ -2629,7 +3326,7 @@ export class LocalTui {
|
|
|
2629
3326
|
}
|
|
2630
3327
|
// A running Shell line owns Ctrl+C before the composer does: the reader is
|
|
2631
3328
|
// watching a command, and the key they press to stop it is the same one.
|
|
2632
|
-
if (this.#shellAbort !== undefined && !this.#hasComposerContent()) {
|
|
3329
|
+
if (this.#inspected === undefined && this.#shellAbort !== undefined && !this.#hasComposerContent()) {
|
|
2633
3330
|
this.#shellAbort();
|
|
2634
3331
|
return true;
|
|
2635
3332
|
}
|
|
@@ -2658,7 +3355,6 @@ export class LocalTui {
|
|
|
2658
3355
|
// A running turn keeps Ctrl+C as the familiar abort while the composer is
|
|
2659
3356
|
// empty; Escape already covers it, and refusing the reflex would be worse.
|
|
2660
3357
|
if (this.#state.status !== 'idle' && !this.#hasComposerContent()) {
|
|
2661
|
-
this.#restoreQueuedBeforeAbort();
|
|
2662
3358
|
for (const listener of this.#interrupts)
|
|
2663
3359
|
listener();
|
|
2664
3360
|
return true;
|
|
@@ -2666,20 +3362,6 @@ export class LocalTui {
|
|
|
2666
3362
|
this.#clearComposerWithUndo();
|
|
2667
3363
|
return true;
|
|
2668
3364
|
}
|
|
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
3365
|
#handlePrompt(event) {
|
|
2684
3366
|
let prompt = this.#prompt;
|
|
2685
3367
|
if (prompt === null)
|
|
@@ -2727,13 +3409,15 @@ export class LocalTui {
|
|
|
2727
3409
|
const selected = filteredPromptOptions(prompt.request, '', prompt.collapsed)[prompt.selected];
|
|
2728
3410
|
if (action !== undefined && (action.scope === 'list' || selected !== undefined)) {
|
|
2729
3411
|
if (!disarmed) {
|
|
2730
|
-
this.#finishPrompt(action.valuePrefix + (action.scope === 'list' ? '' : selected.value ?? selected.label));
|
|
3412
|
+
this.#finishPrompt(action.valuePrefix + (action.scope === 'list' ? '' : selected.value ?? selected.label), 'action');
|
|
2731
3413
|
this.#render();
|
|
2732
3414
|
}
|
|
2733
3415
|
return true;
|
|
2734
3416
|
}
|
|
2735
3417
|
}
|
|
2736
|
-
|
|
3418
|
+
const toggleAnswer = prompt.request.answerFormat === 'json' && event.type === 'key' && event.id === 'ctrl+g';
|
|
3419
|
+
const toggleSpace = event.type === 'text' && event.value === ' ' && (prompt.request.answerFormat !== 'json' || this.#editor.text === '');
|
|
3420
|
+
if (prompt.request.multiSelect === true && (toggleAnswer || toggleSpace)) {
|
|
2737
3421
|
this.#prompt = togglePromptSelection(prompt, this.#editor.text);
|
|
2738
3422
|
this.#render();
|
|
2739
3423
|
return true;
|
|
@@ -2755,14 +3439,11 @@ export class LocalTui {
|
|
|
2755
3439
|
const filtered = filteredPromptOptions(prompt.request, this.#editor.text, prompt.collapsed);
|
|
2756
3440
|
const count = filtered.length;
|
|
2757
3441
|
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')) {
|
|
3442
|
+
if (keyAction !== undefined && (keyAction.scope === 'list' || filtered[prompt.selected] !== undefined)) {
|
|
2761
3443
|
if (!disarmed) {
|
|
2762
3444
|
const selected = filtered[prompt.selected];
|
|
2763
3445
|
if (keyAction.scope === 'list' || selected !== undefined) {
|
|
2764
|
-
this.#
|
|
2765
|
-
this.#finishPrompt(keyAction.valuePrefix + (keyAction.scope === 'list' ? '' : selected.value ?? selected.label));
|
|
3446
|
+
this.#finishPrompt(keyAction.valuePrefix + (keyAction.scope === 'list' ? '' : selected.value ?? selected.label), 'action');
|
|
2766
3447
|
this.#render();
|
|
2767
3448
|
}
|
|
2768
3449
|
}
|
|
@@ -2821,7 +3502,7 @@ export class LocalTui {
|
|
|
2821
3502
|
// the other lists about what `tab` or `pageUp` means. `enter` stays below
|
|
2822
3503
|
// because what it submits depends on the editor and on multi-select.
|
|
2823
3504
|
const page = prompt.request.presentation === 'tree'
|
|
2824
|
-
? promptTreePageSize(prompt.request, this.#term.width(), this.#term.height()) : 10;
|
|
3505
|
+
? promptTreePageSize(prompt.request, this.#term.width(), this.#term.height(), resolveUiLanguage(this.#uiLanguage)) : 10;
|
|
2825
3506
|
const navigation = listNavigation(event);
|
|
2826
3507
|
if (navigation.kind === 'move' || navigation.kind === 'page') {
|
|
2827
3508
|
// The tree clamps: its rows are a structure, and wrapping past the last
|
|
@@ -2838,11 +3519,10 @@ export class LocalTui {
|
|
|
2838
3519
|
}
|
|
2839
3520
|
if (event.id === 'enter' && (this.#editor.text === '' || prompt.request.multiSelect === true)) {
|
|
2840
3521
|
const answer = selectedPromptAnswer(prompt);
|
|
2841
|
-
//
|
|
2842
|
-
|
|
2843
|
-
if (answer === null)
|
|
3522
|
+
// Supplementary text can answer a question even with no checked options.
|
|
3523
|
+
if (answer === null && !(prompt.request.answerFormat === 'json' && this.#editor.expandedText.trim()))
|
|
2844
3524
|
return true;
|
|
2845
|
-
this.#finishPrompt(answer);
|
|
3525
|
+
this.#finishPrompt(answer ?? '', 'selection');
|
|
2846
3526
|
this.#render();
|
|
2847
3527
|
return true;
|
|
2848
3528
|
}
|
|
@@ -2852,7 +3532,6 @@ export class LocalTui {
|
|
|
2852
3532
|
// closing on it would lose both the list and the filter.
|
|
2853
3533
|
if (answer === null)
|
|
2854
3534
|
return true;
|
|
2855
|
-
this.#editor.setText('');
|
|
2856
3535
|
this.#finishPrompt(answer);
|
|
2857
3536
|
this.#render();
|
|
2858
3537
|
return true;
|
|
@@ -2882,7 +3561,7 @@ export class LocalTui {
|
|
|
2882
3561
|
}
|
|
2883
3562
|
return true;
|
|
2884
3563
|
}
|
|
2885
|
-
if (!document && event.type === 'key' && (event.id === 'ctrl+x' || this.#boundAction(event.id) === 'external-editor')) {
|
|
3564
|
+
if (!document && prompt.request.allowCustom !== false && event.type === 'key' && (event.id === 'ctrl+x' || this.#boundAction(event.id) === 'external-editor')) {
|
|
2886
3565
|
this.#openPlanFeedbackEditor(prompt);
|
|
2887
3566
|
return true;
|
|
2888
3567
|
}
|
|
@@ -2945,13 +3624,7 @@ export class LocalTui {
|
|
|
2945
3624
|
else if (event.id === 'end')
|
|
2946
3625
|
documentScroll = prompt.request.documentTail === true ? Number.POSITIVE_INFINITY : scroll?.maxStart ?? prompt.documentScroll ?? 0;
|
|
2947
3626
|
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();
|
|
3627
|
+
this.#scrollPromptDocument(documentScroll);
|
|
2955
3628
|
return true;
|
|
2956
3629
|
}
|
|
2957
3630
|
if (event.id === 'tab' || event.id === 'right') {
|
|
@@ -2972,7 +3645,7 @@ export class LocalTui {
|
|
|
2972
3645
|
return true;
|
|
2973
3646
|
const approve = prompt.request.approveValue ?? prompt.request.options?.[0]?.value
|
|
2974
3647
|
?? prompt.request.options?.[0]?.label;
|
|
2975
|
-
if (document || answer === approve) {
|
|
3648
|
+
if (document || answer === approve || prompt.request.allowCustom === false) {
|
|
2976
3649
|
this.#finishPrompt(answer);
|
|
2977
3650
|
}
|
|
2978
3651
|
else {
|
|
@@ -2993,7 +3666,11 @@ export class LocalTui {
|
|
|
2993
3666
|
* something up in it.
|
|
2994
3667
|
*/
|
|
2995
3668
|
#handleRenderedDocument(event, prompt) {
|
|
2996
|
-
const rows = () =>
|
|
3669
|
+
const rows = () => {
|
|
3670
|
+
const theme = createTheme(this.#colors, this.#colorDepth, this.#themeName, this.#terminalColors, resolveUiLanguage(this.#uiLanguage), this.#compactLinks);
|
|
3671
|
+
const width = Math.max(1, this.#term.width() - 6);
|
|
3672
|
+
return prompt.renderDocument?.(theme, width) ?? documentRows(prompt.request, theme, width);
|
|
3673
|
+
};
|
|
2997
3674
|
// Always build on the newest state: typing updates the query while the find
|
|
2998
3675
|
// is open, and jumping from a stale copy would drop it again.
|
|
2999
3676
|
const jumpTo = (row) => {
|
|
@@ -3055,7 +3732,7 @@ export class LocalTui {
|
|
|
3055
3732
|
}
|
|
3056
3733
|
#handleDocumentReader(event, prompt) {
|
|
3057
3734
|
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), {
|
|
3735
|
+
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
3736
|
wordWrap: prompt.documentWordWrap ?? true, column: prompt.documentColumn ?? 0,
|
|
3060
3737
|
});
|
|
3061
3738
|
const start = this.#promptDocument?.start ?? 0;
|
|
@@ -3137,7 +3814,7 @@ export class LocalTui {
|
|
|
3137
3814
|
listener(id);
|
|
3138
3815
|
}
|
|
3139
3816
|
#handleSubagentLauncher(event) {
|
|
3140
|
-
const agents = this.#subagents
|
|
3817
|
+
const agents = rosterItems(this.#subagents);
|
|
3141
3818
|
if (this.#subagentSelection !== undefined) {
|
|
3142
3819
|
if (agents.length === 0) {
|
|
3143
3820
|
this.#subagentSelection = undefined;
|
|
@@ -3146,7 +3823,12 @@ export class LocalTui {
|
|
|
3146
3823
|
const at = Math.min(this.#subagentSelection, agents.length - 1);
|
|
3147
3824
|
if (event.type === 'key' && event.id === 'enter') {
|
|
3148
3825
|
this.#subagentSelection = undefined;
|
|
3149
|
-
|
|
3826
|
+
const item = agents[at];
|
|
3827
|
+
if (item.kind === 'agent')
|
|
3828
|
+
this.#openSelectedSubagent(item.agent);
|
|
3829
|
+
else
|
|
3830
|
+
for (const listener of this.#workflowInspects)
|
|
3831
|
+
listener(item.run.id);
|
|
3150
3832
|
return true;
|
|
3151
3833
|
}
|
|
3152
3834
|
if (event.type === 'key' && event.id === 'escape') {
|
|
@@ -3172,7 +3854,7 @@ export class LocalTui {
|
|
|
3172
3854
|
return false;
|
|
3173
3855
|
if (this.#queuedSubmissions.length > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
3174
3856
|
return false;
|
|
3175
|
-
this.#subagentSelection = Math.min(agents.length - 1, Math.max(0,
|
|
3857
|
+
this.#subagentSelection = Math.min(agents.length - 1, Math.max(0, rosterPreviewStart(this.#subagents)));
|
|
3176
3858
|
this.#render();
|
|
3177
3859
|
return true;
|
|
3178
3860
|
}
|
|
@@ -3216,11 +3898,15 @@ export class LocalTui {
|
|
|
3216
3898
|
const previous = options.persist ? this.prefs() : undefined;
|
|
3217
3899
|
const version = this.#prefsVersion;
|
|
3218
3900
|
const previousMotion = this.#motion;
|
|
3901
|
+
this.#uiLanguage = prefs.uiLanguage ?? 'auto';
|
|
3902
|
+
if (this.#prompt !== null)
|
|
3903
|
+
this.#prompt = { ...this.#prompt, request: localizePrompt(this.#prompt.sourceRequest, resolveUiLanguage(this.#uiLanguage)) };
|
|
3219
3904
|
this.#themeName = prefs.theme;
|
|
3220
3905
|
this.#colors = prefs.colors;
|
|
3221
3906
|
this.#syncColorDepth();
|
|
3222
3907
|
this.#motion = prefs.motion ?? 'full';
|
|
3223
3908
|
this.#flushPasteBurst(true);
|
|
3909
|
+
this.#busyEnter = prefs.busyEnter ?? 'queue';
|
|
3224
3910
|
this.#pasteProtection = prefs.pasteProtection !== false;
|
|
3225
3911
|
this.#externalEditor = prefs.editor ?? 'auto';
|
|
3226
3912
|
this.#terminalTitle = prefs.terminalTitle ?? 'status';
|
|
@@ -3229,7 +3915,7 @@ export class LocalTui {
|
|
|
3229
3915
|
this.#copyOnSelect = prefs.copyOnSelect ?? true;
|
|
3230
3916
|
this.#mathMode = prefs.mathMode ?? 'auto';
|
|
3231
3917
|
this.#mermaidMode = prefs.mermaidMode ?? 'auto';
|
|
3232
|
-
this.#hideThinking = prefs.hideThinking
|
|
3918
|
+
this.#hideThinking = prefs.hideThinking !== false;
|
|
3233
3919
|
this.#mouseInteraction = prefs.mouseInteraction ?? 'auto';
|
|
3234
3920
|
this.#checkUpdates = prefs.checkUpdates ?? true;
|
|
3235
3921
|
this.#startupChangelog = prefs.startupChangelog ?? 'summary';
|
|
@@ -3334,6 +4020,10 @@ export class LocalTui {
|
|
|
3334
4020
|
if (command.kind === 'open-command') {
|
|
3335
4021
|
if (this.#prefsSaveTask !== undefined)
|
|
3336
4022
|
return;
|
|
4023
|
+
if (command.name === 'manage-goal') {
|
|
4024
|
+
this.#openGoal();
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
3337
4027
|
if (command.name === 'keybindings') {
|
|
3338
4028
|
void this.#editKeybindings();
|
|
3339
4029
|
return;
|
|
@@ -3514,7 +4204,7 @@ export class LocalTui {
|
|
|
3514
4204
|
return;
|
|
3515
4205
|
}
|
|
3516
4206
|
const commands = this.#commands();
|
|
3517
|
-
const slashResult = slashSuggestions(this.#editor.text, this.#editor.cursor, commands);
|
|
4207
|
+
const slashResult = slashSuggestions(this.#editor.text, this.#editor.cursor, commands, resolveUiLanguage(this.#uiLanguage));
|
|
3518
4208
|
if (slashResult !== null) {
|
|
3519
4209
|
this.#setAutocomplete(slashResult);
|
|
3520
4210
|
return;
|
|
@@ -3653,11 +4343,6 @@ export class LocalTui {
|
|
|
3653
4343
|
return;
|
|
3654
4344
|
}
|
|
3655
4345
|
if (command.kind === 'submit') {
|
|
3656
|
-
if (this.#inspected !== undefined) {
|
|
3657
|
-
if (this.#inspected.writable === true)
|
|
3658
|
-
this.#submitInspect(command.text);
|
|
3659
|
-
return;
|
|
3660
|
-
}
|
|
3661
4346
|
this.#submit(command.text);
|
|
3662
4347
|
return;
|
|
3663
4348
|
}
|
|
@@ -3713,8 +4398,8 @@ export class LocalTui {
|
|
|
3713
4398
|
#toggleThinking() {
|
|
3714
4399
|
const next = !this.#hideThinking;
|
|
3715
4400
|
this.#hideThinking = next;
|
|
3716
|
-
|
|
3717
|
-
|
|
4401
|
+
this.#expandedReasoning.clear();
|
|
4402
|
+
this.#closedReasoning.clear();
|
|
3718
4403
|
const persist = this.#persistPrefs;
|
|
3719
4404
|
if (persist !== null)
|
|
3720
4405
|
void persist(this.prefs(), [{ op: 'set', path: ['hideThinking'], value: next }]);
|
|
@@ -3749,7 +4434,22 @@ export class LocalTui {
|
|
|
3749
4434
|
this.#refreshAutocomplete();
|
|
3750
4435
|
this.#render();
|
|
3751
4436
|
}
|
|
4437
|
+
#cancelQueueRecall() {
|
|
4438
|
+
this.#queueRecall?.abort.abort();
|
|
4439
|
+
this.#queueRecall = undefined;
|
|
4440
|
+
this.#queueEditPending = false;
|
|
4441
|
+
}
|
|
4442
|
+
#requestQueueRecall() {
|
|
4443
|
+
const abort = new AbortController();
|
|
4444
|
+
const request = { signal: abort.signal, ...(this.#draftScope === undefined ? {} : { sessionId: this.#draftScope }) };
|
|
4445
|
+
this.#queueRecall = { abort, request };
|
|
4446
|
+
this.#queueEditPending = true;
|
|
4447
|
+
for (const listener of this.#queueEdits)
|
|
4448
|
+
listener(request);
|
|
4449
|
+
}
|
|
3752
4450
|
#restoreLatestQueuedSubmission(allowNonEmpty = false) {
|
|
4451
|
+
if (this.#inspected !== undefined && !this.#inspected.writable)
|
|
4452
|
+
return false;
|
|
3753
4453
|
if (this.#queueEditPending)
|
|
3754
4454
|
return true;
|
|
3755
4455
|
if (this.#queueEditNewer !== null) {
|
|
@@ -3762,9 +4462,7 @@ export class LocalTui {
|
|
|
3762
4462
|
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
3763
4463
|
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
3764
4464
|
return true;
|
|
3765
|
-
this.#
|
|
3766
|
-
for (const listener of this.#queueEdits)
|
|
3767
|
-
listener();
|
|
4465
|
+
this.#requestQueueRecall();
|
|
3768
4466
|
return true;
|
|
3769
4467
|
}
|
|
3770
4468
|
if (this.#historyIndex !== 0)
|
|
@@ -3781,10 +4479,7 @@ export class LocalTui {
|
|
|
3781
4479
|
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
3782
4480
|
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
3783
4481
|
return false;
|
|
3784
|
-
this.#
|
|
3785
|
-
this.#queueEditPending = true;
|
|
3786
|
-
for (const listener of this.#queueEdits)
|
|
3787
|
-
listener();
|
|
4482
|
+
this.#requestQueueRecall();
|
|
3788
4483
|
return true;
|
|
3789
4484
|
}
|
|
3790
4485
|
#historyNext() {
|
|
@@ -3826,7 +4521,8 @@ export class LocalTui {
|
|
|
3826
4521
|
const images = this.#images.copies();
|
|
3827
4522
|
const files = this.#files.copies();
|
|
3828
4523
|
const typedText = this.#files.text(images.length > 0 ? text.trim() : text);
|
|
3829
|
-
const segments = images.length === 0 ? []
|
|
4524
|
+
const segments = images.length === 0 && files.length === 0 ? []
|
|
4525
|
+
: splitComposerAttachmentMarkers(text.trim(), images, this.#files.snapshot(text).entries.map(item => item.marker));
|
|
3830
4526
|
// Stripping a marker that sat on its own line leaves that line break behind.
|
|
3831
4527
|
const submittedText = images.length > 0 ? stripComposerImageMarkers(typedText, images).trim() : typedText;
|
|
3832
4528
|
const literal = text === this.#editor.expandedText && (this.#literalInput || this.#editor.startsWithPaste);
|
|
@@ -3842,19 +4538,48 @@ export class LocalTui {
|
|
|
3842
4538
|
historyText,
|
|
3843
4539
|
};
|
|
3844
4540
|
}
|
|
3845
|
-
#
|
|
4541
|
+
#dispatchInput(submission) {
|
|
4542
|
+
const receipt = this.#pendingInputs.identify(submission, this.#pendingScope());
|
|
4543
|
+
receipt.recovered = false;
|
|
4544
|
+
if (this.#inspected !== undefined) {
|
|
4545
|
+
this.#pendingInputs.take(submission, this.#pendingScope());
|
|
4546
|
+
for (const listener of this.#inspectSubmits)
|
|
4547
|
+
listener(submission);
|
|
4548
|
+
}
|
|
4549
|
+
else if (this.#pending !== null) {
|
|
4550
|
+
const pending = this.#pending;
|
|
4551
|
+
this.#pending = null;
|
|
4552
|
+
pending.offAbort?.();
|
|
4553
|
+
this.#pendingInputs.take(submission, this.#pendingScope());
|
|
4554
|
+
pending.resolve(submission);
|
|
4555
|
+
}
|
|
4556
|
+
else
|
|
4557
|
+
this.#queuedSubmissions.push(submission);
|
|
4558
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4559
|
+
}
|
|
4560
|
+
#submit(text, delivery) {
|
|
3846
4561
|
if (this.#prompt !== null) {
|
|
3847
4562
|
if (this.#prompt.request.presentation === 'progress')
|
|
3848
4563
|
return;
|
|
3849
|
-
this.#editor.setText('');
|
|
3850
4564
|
const answer = this.#prompt.request.preserveWhitespace === true ? text : text.trim();
|
|
3851
4565
|
this.#finishPrompt(answer.trim() === '' ? null : answer);
|
|
3852
4566
|
this.#render();
|
|
3853
4567
|
return;
|
|
3854
4568
|
}
|
|
4569
|
+
if (this.#inspected !== undefined) {
|
|
4570
|
+
this.#submitInspect(text, delivery);
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
3855
4573
|
const { submission, historyText } = this.#buildSubmission(text);
|
|
3856
|
-
const { images, files = [],
|
|
4574
|
+
const { images, files = [], text: submittedText } = submission;
|
|
3857
4575
|
const queueEditNewer = this.#queueEditNewer;
|
|
4576
|
+
// Commands keep their own routing; recalled follow-ups remain next-turn messages.
|
|
4577
|
+
if (delivery === 'steer')
|
|
4578
|
+
submission.literal = true;
|
|
4579
|
+
const command = !submission.literal && (submittedText.trimStart().startsWith('/') || (images.length === 0 && files.length === 0 && submittedText.startsWith('!')));
|
|
4580
|
+
if (!command && this.#state.status === 'running') {
|
|
4581
|
+
submission.delivery = queueEditNewer !== null || this.#queueEditPending ? 'queue' : delivery ?? this.#busyEnter;
|
|
4582
|
+
}
|
|
3858
4583
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
3859
4584
|
this.#history.push(historyText);
|
|
3860
4585
|
this.#historyStore?.add(historyText);
|
|
@@ -3863,11 +4588,10 @@ export class LocalTui {
|
|
|
3863
4588
|
this.#draft = '';
|
|
3864
4589
|
this.#literalInput = false;
|
|
3865
4590
|
this.#queueEditNewer = null;
|
|
3866
|
-
this.#
|
|
4591
|
+
this.#cancelQueueRecall();
|
|
3867
4592
|
this.#editor.setText('');
|
|
3868
4593
|
this.#images.clear();
|
|
3869
4594
|
this.#files.clear();
|
|
3870
|
-
this.#clearDraft();
|
|
3871
4595
|
this.#ac = null;
|
|
3872
4596
|
this.#search = null;
|
|
3873
4597
|
this.#settings = null;
|
|
@@ -3875,63 +4599,69 @@ export class LocalTui {
|
|
|
3875
4599
|
// Image placeholders are TUI-owned. Mixed image+slash drafts stay a
|
|
3876
4600
|
// submission so restore can keep the original markers; the runner strips
|
|
3877
4601
|
// them only to detect and execute Harness commands.
|
|
3878
|
-
const slash = !literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
4602
|
+
const slash = !submission.literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
3879
4603
|
if (slash !== null) {
|
|
3880
4604
|
if (queueEditNewer !== null)
|
|
3881
4605
|
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3882
4606
|
this.#runSlash(slash.name, slash.args);
|
|
4607
|
+
this.#clearDraft();
|
|
3883
4608
|
return;
|
|
3884
4609
|
}
|
|
3885
4610
|
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
|
-
}
|
|
4611
|
+
if (submittedText !== '' || images.length > 0 || files.length > 0 || this.#pending !== null)
|
|
4612
|
+
this.#dispatchInput(submission);
|
|
4613
|
+
if (queueEditNewer !== null)
|
|
4614
|
+
this.#queuedSubmissions.push(...queueEditNewer);
|
|
3901
4615
|
if (submittedText !== '' || images.length > 0 || files.length > 0) {
|
|
3902
4616
|
this.#popStashedDraft();
|
|
3903
4617
|
}
|
|
4618
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4619
|
+
this.#clearDraft();
|
|
3904
4620
|
this.#render();
|
|
3905
4621
|
}
|
|
3906
|
-
#submitInspect(text) {
|
|
4622
|
+
#submitInspect(text, delivery) {
|
|
4623
|
+
if (this.#inspected?.writable !== true)
|
|
4624
|
+
return;
|
|
3907
4625
|
const { submission, historyText } = this.#buildSubmission(text);
|
|
4626
|
+
const newer = this.#queueEditNewer;
|
|
4627
|
+
if (delivery === 'steer')
|
|
4628
|
+
submission.literal = true;
|
|
4629
|
+
submission.delivery = newer !== null || this.#queueEditPending ? 'queue' : this.#state.status === 'running' ? delivery ?? this.#busyEnter : 'queue';
|
|
3908
4630
|
const { images, files = [], literal = false, text: submittedText } = submission;
|
|
3909
4631
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
3910
4632
|
this.#history.push(historyText);
|
|
3911
4633
|
this.#historyStore?.add(historyText);
|
|
3912
4634
|
}
|
|
4635
|
+
this.#queueEditNewer = null;
|
|
4636
|
+
this.#cancelQueueRecall();
|
|
3913
4637
|
this.#historyIndex = 0;
|
|
3914
4638
|
this.#draft = '';
|
|
3915
4639
|
this.#literalInput = false;
|
|
3916
4640
|
this.#editor.setText('');
|
|
3917
4641
|
this.#images.clear();
|
|
3918
4642
|
this.#files.clear();
|
|
3919
|
-
this.#clearDraft();
|
|
3920
4643
|
this.#ac = null;
|
|
3921
4644
|
this.#search = null;
|
|
3922
4645
|
const slash = !literal && images.length === 0 && files.length === 0 ? parseSlashInput(submittedText) : null;
|
|
3923
4646
|
if (slash !== null) {
|
|
4647
|
+
this.#queueEditNewer = newer;
|
|
3924
4648
|
this.#runSlash(slash.name, slash.args);
|
|
3925
4649
|
return;
|
|
3926
4650
|
}
|
|
3927
4651
|
this.#followTail();
|
|
3928
|
-
if (submittedText === '' && images.length === 0 && files.length === 0) {
|
|
4652
|
+
if (submittedText === '' && images.length === 0 && files.length === 0 && !newer?.length) {
|
|
3929
4653
|
this.#render();
|
|
3930
4654
|
return;
|
|
3931
4655
|
}
|
|
3932
4656
|
const { literal: _literal, ...inspectSubmission } = submission;
|
|
3933
|
-
|
|
3934
|
-
|
|
4657
|
+
if (submittedText !== '' || images.length > 0 || files.length > 0) {
|
|
4658
|
+
this.#dispatchInput(inspectSubmission);
|
|
4659
|
+
}
|
|
4660
|
+
for (const draft of newer ?? []) {
|
|
4661
|
+
this.#dispatchInput({ ...draft, delivery: 'queue' });
|
|
4662
|
+
}
|
|
4663
|
+
this.#pendingInputs.flush(this.#pendingScope());
|
|
4664
|
+
this.#clearDraft();
|
|
3935
4665
|
this.#popStashedDraft();
|
|
3936
4666
|
this.#render();
|
|
3937
4667
|
}
|
|
@@ -3949,6 +4679,10 @@ export class LocalTui {
|
|
|
3949
4679
|
}
|
|
3950
4680
|
// Reading controls cover the current view; submitting a command that
|
|
3951
4681
|
// opens one must preserve the same return position as a keyboard action.
|
|
4682
|
+
if (command.name === 'queue' && this.#inspected !== undefined) {
|
|
4683
|
+
this.#runAction('manage-queue');
|
|
4684
|
+
return;
|
|
4685
|
+
}
|
|
3952
4686
|
if (command.name === 'settings') {
|
|
3953
4687
|
this.#runSettings(args);
|
|
3954
4688
|
return;
|
|
@@ -3966,18 +4700,17 @@ export class LocalTui {
|
|
|
3966
4700
|
return;
|
|
3967
4701
|
}
|
|
3968
4702
|
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
|
-
}
|
|
4703
|
+
this.#dispatchInput({ text: raw, images: [] });
|
|
3978
4704
|
this.#render();
|
|
3979
4705
|
return;
|
|
3980
4706
|
}
|
|
4707
|
+
if (command.name === 'help') {
|
|
4708
|
+
if (args !== '' && args !== 'full')
|
|
4709
|
+
this.notice('Usage: /help [full]');
|
|
4710
|
+
else
|
|
4711
|
+
this.#runHelp(args === 'full');
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
3981
4714
|
this.#followTail();
|
|
3982
4715
|
if (command.name === 'quit') {
|
|
3983
4716
|
this.#render();
|
|
@@ -3998,30 +4731,39 @@ export class LocalTui {
|
|
|
3998
4731
|
this.#runTools();
|
|
3999
4732
|
return;
|
|
4000
4733
|
}
|
|
4001
|
-
|
|
4002
|
-
this.#notice('Usage: /help [full]');
|
|
4003
|
-
this.#render();
|
|
4004
|
-
return;
|
|
4005
|
-
}
|
|
4006
|
-
this.#runHelp(args === 'full');
|
|
4734
|
+
this.#render();
|
|
4007
4735
|
}
|
|
4008
4736
|
#commands() {
|
|
4009
4737
|
const localNames = new Set(BUILTIN_SLASH_COMMANDS.flatMap((command) => [command.name, ...(command.aliases ?? [])]));
|
|
4010
4738
|
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
|
-
}));
|
|
4739
|
+
.filter((command) => !localNames.has(command.name));
|
|
4018
4740
|
return [...BUILTIN_SLASH_COMMANDS, ...runtime];
|
|
4019
4741
|
}
|
|
4020
|
-
#finishPrompt(answer) {
|
|
4742
|
+
#finishPrompt(answer, kind = 'text') {
|
|
4021
4743
|
const pending = this.#prompt;
|
|
4022
4744
|
if (pending === null)
|
|
4023
4745
|
return;
|
|
4746
|
+
const draft = structuredPromptAnswer(pending, this.#editor.expandedText);
|
|
4747
|
+
try {
|
|
4748
|
+
pending.request.onAnswerDraft?.(draft);
|
|
4749
|
+
}
|
|
4750
|
+
catch (error) {
|
|
4751
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
4752
|
+
}
|
|
4753
|
+
if (answer !== null && pending.request.answerFormat === 'json' && kind !== 'action' && this.#tty) {
|
|
4754
|
+
answer = JSON.stringify(kind === 'selection' ? draft : { selected: [], ...(answer === '' ? {} : { custom: answer }) });
|
|
4755
|
+
}
|
|
4756
|
+
if (pending.browserState !== undefined) {
|
|
4757
|
+
const selected = filteredPromptOptions(pending.request, this.#editor.text, pending.collapsed)[pending.selected];
|
|
4758
|
+
pending.browserState.query = this.#editor.text;
|
|
4759
|
+
if (selected !== undefined)
|
|
4760
|
+
pending.browserState.selected = selected.value ?? selected.label;
|
|
4761
|
+
}
|
|
4024
4762
|
this.#prompt = null;
|
|
4763
|
+
this.#promptActionPress = undefined;
|
|
4764
|
+
this.#promptChoicePress = undefined;
|
|
4765
|
+
this.#selection.clear();
|
|
4766
|
+
this.#wheelGesture = IDLE_WHEEL;
|
|
4025
4767
|
if (pending.request.notify !== false) {
|
|
4026
4768
|
this.#herdr.promptResolved();
|
|
4027
4769
|
this.#syncWindowTitle();
|
|
@@ -4054,11 +4796,14 @@ export class LocalTui {
|
|
|
4054
4796
|
}
|
|
4055
4797
|
/** Save and close the visible overlay so a prompt owns both input and screen. */
|
|
4056
4798
|
#displaceSurface() {
|
|
4057
|
-
const displaced = this.#
|
|
4058
|
-
? { kind: '
|
|
4059
|
-
: this.#
|
|
4060
|
-
? { kind: '
|
|
4061
|
-
: this.#
|
|
4799
|
+
const displaced = this.#fileBrowser !== null
|
|
4800
|
+
? { kind: 'fileBrowser', browser: this.#fileBrowser }
|
|
4801
|
+
: this.#trajectory !== null
|
|
4802
|
+
? { kind: 'trajectory', state: this.#trajectory }
|
|
4803
|
+
: this.#settings !== null
|
|
4804
|
+
? { kind: 'settings', state: this.#settings }
|
|
4805
|
+
: this.#search !== null ? { kind: 'search', state: this.#search } : null;
|
|
4806
|
+
this.#fileBrowser = null;
|
|
4062
4807
|
this.#trajectory = null;
|
|
4063
4808
|
this.#settings = null;
|
|
4064
4809
|
this.#search = null;
|
|
@@ -4070,7 +4815,9 @@ export class LocalTui {
|
|
|
4070
4815
|
this.#promptDisplaced = null;
|
|
4071
4816
|
if (displaced === null)
|
|
4072
4817
|
return;
|
|
4073
|
-
if (displaced.kind === '
|
|
4818
|
+
if (displaced.kind === 'fileBrowser')
|
|
4819
|
+
this.#fileBrowser = displaced.browser;
|
|
4820
|
+
else if (displaced.kind === 'trajectory')
|
|
4074
4821
|
this.#trajectory = displaced.state;
|
|
4075
4822
|
else if (displaced.kind === 'settings') {
|
|
4076
4823
|
this.#settings = displaced.state;
|
|
@@ -4116,7 +4863,7 @@ export class LocalTui {
|
|
|
4116
4863
|
* that did not answer to the shared list vocabulary, which is exactly the
|
|
4117
4864
|
* inconsistency the reader noticed.
|
|
4118
4865
|
*/
|
|
4119
|
-
const answer = await this.prompt({
|
|
4866
|
+
const answer = await this.prompt({ localize: true,
|
|
4120
4867
|
title: 'Copy',
|
|
4121
4868
|
question: items.length === 1 ? 'One thing to copy' : 'Choose what to copy',
|
|
4122
4869
|
filterable: true,
|
|
@@ -4152,13 +4899,17 @@ export class LocalTui {
|
|
|
4152
4899
|
}
|
|
4153
4900
|
#settingsActions() {
|
|
4154
4901
|
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',
|
|
4902
|
+
{ 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' },
|
|
4903
|
+
{ 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' },
|
|
4904
|
+
{ 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' },
|
|
4905
|
+
{ 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' },
|
|
4906
|
+
{ 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' },
|
|
4907
|
+
{ 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
4908
|
].filter(item => this.#runtimeCommands.some(command => command.name === item.action));
|
|
4159
4909
|
const customBindings = Object.entries(this.#keybindings).filter(([key, action]) => DEFAULT_KEYBINDINGS[key] !== action).length;
|
|
4160
4910
|
return [
|
|
4161
4911
|
...runtimeActions,
|
|
4912
|
+
...(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
4913
|
...(this.#keybindingsPath === undefined ? [] : [{
|
|
4163
4914
|
id: 'action:keybindings',
|
|
4164
4915
|
label: 'Keybindings',
|
|
@@ -4208,6 +4959,7 @@ export class LocalTui {
|
|
|
4208
4959
|
}
|
|
4209
4960
|
#configureNotifications() {
|
|
4210
4961
|
this.#notifications.configure({
|
|
4962
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
4211
4963
|
policy: this.#notificationPolicy,
|
|
4212
4964
|
thresholdMs: NOTIFICATION_THRESHOLD_MS,
|
|
4213
4965
|
});
|
|
@@ -4234,32 +4986,66 @@ export class LocalTui {
|
|
|
4234
4986
|
this.#term.output.write(sequence);
|
|
4235
4987
|
}, 25);
|
|
4236
4988
|
}
|
|
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
4989
|
#runHelp(full) {
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4990
|
+
if (!this.#tty) {
|
|
4991
|
+
this.commandOutput('help', ['Help', '', '## Essential Shortcuts', '', formatEssentialHotkeysText(this.#keybindings),
|
|
4992
|
+
'', formatHelpText(this.#commands(), resolveUiLanguage(this.#uiLanguage)),
|
|
4993
|
+
...(full ? ['', formatHotkeysText(this.#keybindings)] : ['', 'Use `/help full` to view every keyboard shortcut.'])].join('\n'));
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null)
|
|
4997
|
+
return;
|
|
4998
|
+
const abort = new AbortController();
|
|
4999
|
+
this.#fileReviewAbort = abort;
|
|
5000
|
+
const surface = {
|
|
5001
|
+
prompt: (request) => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5002
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5003
|
+
insertText: (text) => this.insertText(text),
|
|
5004
|
+
};
|
|
5005
|
+
void browseHelp(surface, this.#commands(), this.#keybindings, resolveUiLanguage(this.#uiLanguage), full, abort.signal)
|
|
5006
|
+
.catch(error => { if (!abort.signal.aborted && !this.#disposed)
|
|
5007
|
+
this.notice(String(error), { level: 'error' }); })
|
|
5008
|
+
.finally(() => { if (this.#fileReviewAbort === abort)
|
|
5009
|
+
this.#fileReviewAbort = undefined; });
|
|
4254
5010
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
5011
|
+
async #readCommandOutput(block) {
|
|
5012
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null || this.#disposed)
|
|
5013
|
+
return;
|
|
5014
|
+
const abort = new AbortController();
|
|
5015
|
+
this.#fileReviewAbort = abort;
|
|
5016
|
+
try {
|
|
5017
|
+
await readReference({
|
|
5018
|
+
prompt: request => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5019
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5020
|
+
}, `/${block.command}`, block.text, abort.signal);
|
|
5021
|
+
}
|
|
5022
|
+
catch (error) {
|
|
5023
|
+
if (!abort.signal.aborted && !this.#disposed)
|
|
5024
|
+
this.notice(String(error), { level: 'error' });
|
|
5025
|
+
}
|
|
5026
|
+
finally {
|
|
5027
|
+
if (this.#fileReviewAbort === abort)
|
|
5028
|
+
this.#fileReviewAbort = undefined;
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
/** Open the searchable catalog, retaining plain output for line mode. */
|
|
4259
5032
|
#runTools() {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
5033
|
+
if (!this.#tty) {
|
|
5034
|
+
this.commandOutput('tools', `Tools · ${this.#tools.length} active\n\n${formatToolsText(this.#tools)}`);
|
|
5035
|
+
return;
|
|
5036
|
+
}
|
|
5037
|
+
if (this.#fileReviewAbort !== undefined || this.#prompt !== null)
|
|
5038
|
+
return;
|
|
5039
|
+
const abort = new AbortController();
|
|
5040
|
+
this.#fileReviewAbort = abort;
|
|
5041
|
+
void browseTools({
|
|
5042
|
+
prompt: request => this.prompt({ ...request, signal: abort.signal, onSuperseded: () => { abort.abort(); } }),
|
|
5043
|
+
copyText: (text, label) => this.copyText(text, label),
|
|
5044
|
+
}, this.#tools, resolveUiLanguage(this.#uiLanguage), abort.signal)
|
|
5045
|
+
.catch(error => { if (!abort.signal.aborted && !this.#disposed)
|
|
5046
|
+
this.notice(String(error), { level: 'error' }); })
|
|
5047
|
+
.finally(() => { if (this.#fileReviewAbort === abort)
|
|
5048
|
+
this.#fileReviewAbort = undefined; });
|
|
4263
5049
|
}
|
|
4264
5050
|
/** Resolve a configured action for one key id, tolerating camelCase ids. */
|
|
4265
5051
|
#boundAction(id) {
|
|
@@ -4363,10 +5149,12 @@ export class LocalTui {
|
|
|
4363
5149
|
this.#render();
|
|
4364
5150
|
return true;
|
|
4365
5151
|
}
|
|
5152
|
+
if (action === 'manage-goal') {
|
|
5153
|
+
this.#openGoal();
|
|
5154
|
+
return true;
|
|
5155
|
+
}
|
|
4366
5156
|
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)
|
|
5157
|
+
if (this.#queueHandler === undefined)
|
|
4370
5158
|
this.notice('Queue management is unavailable in this profile.');
|
|
4371
5159
|
else if (this.#queueHotkeyAbort === undefined) {
|
|
4372
5160
|
const abort = new AbortController();
|
|
@@ -4382,33 +5170,30 @@ export class LocalTui {
|
|
|
4382
5170
|
if (action === 'steer-turn') {
|
|
4383
5171
|
if (this.#search !== null)
|
|
4384
5172
|
return false;
|
|
4385
|
-
if (this.#inspected !== undefined
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
this.notice('Use Enter to queue attachments; steering accepts text only.');
|
|
5173
|
+
if (this.#inspected !== undefined && !this.#inspected.writable)
|
|
5174
|
+
return true;
|
|
5175
|
+
if (this.#state.status !== 'running') {
|
|
5176
|
+
this.notice('Steering needs an active turn. Use Enter to send the next message.');
|
|
4390
5177
|
}
|
|
4391
5178
|
else if (this.#queueEditNewer !== null || this.#queueEditPending) {
|
|
4392
5179
|
this.notice('Finish editing the queued follow-up with Enter before steering.');
|
|
4393
5180
|
}
|
|
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
|
-
}
|
|
5181
|
+
else if (this.#editor.expandedText.trim() !== '' || this.#images.count > 0 || this.#files.copies().length > 0) {
|
|
5182
|
+
this.#submit(this.#editor.expandedText, 'steer');
|
|
4409
5183
|
}
|
|
4410
5184
|
return true;
|
|
4411
5185
|
}
|
|
5186
|
+
if (action === 'quote-selection')
|
|
5187
|
+
return true;
|
|
5188
|
+
if (action === 'tool-actions') {
|
|
5189
|
+
const anchor = this.#viewportBlock();
|
|
5190
|
+
const block = anchor === undefined ? undefined : this.#state.blocks[anchor];
|
|
5191
|
+
if (block?.kind === 'commandOutput')
|
|
5192
|
+
void this.#readCommandOutput(block);
|
|
5193
|
+
else
|
|
5194
|
+
void this.#openToolActions();
|
|
5195
|
+
return true;
|
|
5196
|
+
}
|
|
4412
5197
|
if (action === 'review-changes') {
|
|
4413
5198
|
// The keyboard twin of right-clicking a tool's path: open the files the
|
|
4414
5199
|
// tool under the viewport declared. It shares the mouse path's region
|
|
@@ -4424,7 +5209,7 @@ export class LocalTui {
|
|
|
4424
5209
|
const regions = this.#fileActionFrame?.transcript?.fileRegions ?? [];
|
|
4425
5210
|
const region = (top === undefined ? undefined : regions.find(region => top >= region.start && top < region.end))
|
|
4426
5211
|
?? (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)];
|
|
5212
|
+
const block = this.#currentTool() ?? this.#state.blocks[region?.block ?? (this.#viewportBlock() ?? this.#state.blocks.length - 1)];
|
|
4428
5213
|
const files = block?.kind === 'tool' ? blockFileAction(block) : undefined;
|
|
4429
5214
|
if (files === undefined) {
|
|
4430
5215
|
this.#showTransientStatus('No tool files here · use /diff', 4_000);
|
|
@@ -4490,7 +5275,16 @@ export class LocalTui {
|
|
|
4490
5275
|
return true;
|
|
4491
5276
|
}
|
|
4492
5277
|
if (action === 'retry') {
|
|
4493
|
-
this.#
|
|
5278
|
+
if (!this.#canContinue())
|
|
5279
|
+
return true;
|
|
5280
|
+
const request = { sessionId: this.#sessionId, failureSeq: this.#sessionControls.continuation };
|
|
5281
|
+
this.#continuing = true;
|
|
5282
|
+
void this.#continueHandler(request).catch(error => {
|
|
5283
|
+
if (!this.#disposed && this.#sessionId === request.sessionId)
|
|
5284
|
+
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
5285
|
+
}).finally(() => { this.#continuing = false; if (!this.#disposed)
|
|
5286
|
+
this.#render(); });
|
|
5287
|
+
this.#render();
|
|
4494
5288
|
return true;
|
|
4495
5289
|
}
|
|
4496
5290
|
if (action === 'cycle-model-forward') {
|
|
@@ -4514,12 +5308,11 @@ export class LocalTui {
|
|
|
4514
5308
|
this.#startAsyncPaste(this.#pasteClipboard());
|
|
4515
5309
|
return true;
|
|
4516
5310
|
}
|
|
4517
|
-
//
|
|
4518
|
-
// it belongs to is running; Enter is which-turn, this is after-this-turn.
|
|
5311
|
+
// Explicit queue delivery is independent of the busy-Enter preference.
|
|
4519
5312
|
if (action === 'follow-up') {
|
|
4520
|
-
if (this.#editor.expandedText.trim() === '')
|
|
5313
|
+
if (this.#editor.expandedText.trim() === '' && this.#images.count === 0 && this.#files.copies().length === 0)
|
|
4521
5314
|
return false;
|
|
4522
|
-
this.#submit(this.#editor.expandedText);
|
|
5315
|
+
this.#submit(this.#editor.expandedText, 'queue');
|
|
4523
5316
|
return true;
|
|
4524
5317
|
}
|
|
4525
5318
|
if (action !== 'external-editor')
|
|
@@ -4574,8 +5367,10 @@ export class LocalTui {
|
|
|
4574
5367
|
this.#images.clear();
|
|
4575
5368
|
this.#files.clear();
|
|
4576
5369
|
if (options.clearQueueEdit) {
|
|
5370
|
+
if (this.#queueEditNewer !== null)
|
|
5371
|
+
this.#queuedSubmissions.push(...this.#queueEditNewer);
|
|
4577
5372
|
this.#queueEditNewer = null;
|
|
4578
|
-
this.#
|
|
5373
|
+
this.#cancelQueueRecall();
|
|
4579
5374
|
}
|
|
4580
5375
|
if (options.closeSearch)
|
|
4581
5376
|
this.#search = null;
|
|
@@ -4633,11 +5428,39 @@ export class LocalTui {
|
|
|
4633
5428
|
this.#historyIndex = 0;
|
|
4634
5429
|
this.#refreshAutocomplete();
|
|
4635
5430
|
}
|
|
5431
|
+
async #testKeys() {
|
|
5432
|
+
const abort = new AbortController();
|
|
5433
|
+
// Reuse browsing cancellation: approvals and session switches own the terminal.
|
|
5434
|
+
this.#fileReviewAbort = abort;
|
|
5435
|
+
this.#keyTest = { signal: abort.signal, observe: event => {
|
|
5436
|
+
const pending = this.#prompt;
|
|
5437
|
+
const detail = keyTestDescription(event, this.#keybindings, resolveUiLanguage(this.#uiLanguage));
|
|
5438
|
+
if (pending?.request.signal !== abort.signal || detail === undefined)
|
|
5439
|
+
return;
|
|
5440
|
+
pending.sourceRequest = { ...pending.sourceRequest, detail };
|
|
5441
|
+
pending.request = localizePrompt(pending.sourceRequest, resolveUiLanguage(this.#uiLanguage));
|
|
5442
|
+
this.#render();
|
|
5443
|
+
} };
|
|
5444
|
+
try {
|
|
5445
|
+
await this.prompt({ localize: true, title: 'Key test', presentation: 'progress',
|
|
5446
|
+
question: 'Press a key. Esc or Ctrl+C returns without changing bindings.',
|
|
5447
|
+
detail: uiText(resolveUiLanguage(this.#uiLanguage), 'If nothing appears, the terminal or operating system may have intercepted the key.'),
|
|
5448
|
+
allowCustom: false, notify: false, signal: abort.signal, onSuperseded: () => { abort.abort(); } });
|
|
5449
|
+
}
|
|
5450
|
+
finally {
|
|
5451
|
+
if (this.#keyTest?.signal === abort.signal)
|
|
5452
|
+
this.#keyTest = undefined;
|
|
5453
|
+
if (this.#fileReviewAbort === abort)
|
|
5454
|
+
this.#fileReviewAbort = undefined;
|
|
5455
|
+
}
|
|
5456
|
+
return !abort.signal.aborted;
|
|
5457
|
+
}
|
|
4636
5458
|
async #editKeybindings() {
|
|
4637
5459
|
if (this.#keybindingsPath === undefined)
|
|
4638
5460
|
return;
|
|
4639
5461
|
while (!this.#disposed) {
|
|
4640
5462
|
const options = [
|
|
5463
|
+
{ label: 'Test keys', value: '__test_keys__', description: 'See what the terminal sends without running an action.' },
|
|
4641
5464
|
...TUI_ACTIONS.map(action => {
|
|
4642
5465
|
const keys = keysForAction(this.#keybindings, action);
|
|
4643
5466
|
const defaultKeys = keysForAction(DEFAULT_KEYBINDINGS, action);
|
|
@@ -4653,7 +5476,7 @@ export class LocalTui {
|
|
|
4653
5476
|
description: 'Restore default shortcuts in keybindings.json',
|
|
4654
5477
|
},
|
|
4655
5478
|
];
|
|
4656
|
-
const choice = await this.prompt({
|
|
5479
|
+
const choice = await this.prompt({ localize: true,
|
|
4657
5480
|
title: 'Keybindings',
|
|
4658
5481
|
question: 'Choose an action to remap, or Esc to return to /settings.',
|
|
4659
5482
|
options,
|
|
@@ -4663,6 +5486,11 @@ export class LocalTui {
|
|
|
4663
5486
|
});
|
|
4664
5487
|
if (choice === null || this.#disposed)
|
|
4665
5488
|
break;
|
|
5489
|
+
if (choice === '__test_keys__') {
|
|
5490
|
+
if (!await this.#testKeys())
|
|
5491
|
+
break;
|
|
5492
|
+
continue;
|
|
5493
|
+
}
|
|
4666
5494
|
if (choice === '__reset_all__') {
|
|
4667
5495
|
try {
|
|
4668
5496
|
saveKeybindings(this.#keybindingsPath, DEFAULT_KEYBINDINGS);
|
|
@@ -4681,7 +5509,7 @@ export class LocalTui {
|
|
|
4681
5509
|
continue;
|
|
4682
5510
|
const currentRawKeys = Object.entries(this.#keybindings).filter(([, bound]) => bound === action).map(([key]) => key);
|
|
4683
5511
|
const defaultKeys = keysForAction(DEFAULT_KEYBINDINGS, action);
|
|
4684
|
-
const entered = await this.prompt({
|
|
5512
|
+
const entered = await this.prompt({ localize: true,
|
|
4685
5513
|
title: `Remap ${TUI_ACTION_LABELS[action]}`,
|
|
4686
5514
|
question: `Enter key chord (e.g. ctrl+s, alt+k), "default" (${defaultKeys}), or "none" to unbind.`,
|
|
4687
5515
|
initialInput: currentRawKeys[0] ?? '',
|
|
@@ -4756,16 +5584,18 @@ export class LocalTui {
|
|
|
4756
5584
|
* above them.
|
|
4757
5585
|
*/
|
|
4758
5586
|
const anchored = anchor === undefined ? undefined : this.#state.blocks[anchor];
|
|
4759
|
-
if (anchor !== undefined && (anchored?.kind === 'commandOutput' || anchored?.kind === 'aside')) {
|
|
5587
|
+
if (anchor !== undefined && (anchored?.kind === 'commandOutput' || anchored?.kind === 'aside' || (anchored?.kind === 'notice' && anchored.detail !== undefined))) {
|
|
4760
5588
|
if (this.#expandedOutputs.has(anchored))
|
|
4761
5589
|
this.#expandedOutputs.delete(anchored);
|
|
4762
5590
|
else
|
|
4763
5591
|
this.#expandedOutputs.add(anchored);
|
|
4764
5592
|
this.#inspect(anchor);
|
|
5593
|
+
this.#focusBlock = anchor;
|
|
5594
|
+
this.#focusBlockEdge = 'start';
|
|
4765
5595
|
this.#render();
|
|
4766
5596
|
return;
|
|
4767
5597
|
}
|
|
4768
|
-
const groups = turnGroups(this.#state)
|
|
5598
|
+
const groups = turnGroups(this.#state);
|
|
4769
5599
|
// The answer, changed-file record, and following notices still address
|
|
4770
5600
|
// their turn when the reader is following the tail.
|
|
4771
5601
|
const reach = (group) => {
|
|
@@ -4788,18 +5618,132 @@ export class LocalTui {
|
|
|
4788
5618
|
return anchor >= first && anchor <= reach(item);
|
|
4789
5619
|
});
|
|
4790
5620
|
if (group !== undefined) {
|
|
4791
|
-
this.#
|
|
4792
|
-
|
|
5621
|
+
const scope = this.#detailScope(group);
|
|
5622
|
+
for (const member of groups)
|
|
5623
|
+
if (this.#detailScope(member) === scope)
|
|
5624
|
+
this.#openedGroups.add(member.key);
|
|
5625
|
+
const rows = this.#processRows();
|
|
5626
|
+
this.#processSelection = rows.includes(anchor) ? anchor : rows[0];
|
|
5627
|
+
this.#ac = null;
|
|
5628
|
+
this.#inspect(this.#processSelection ?? group.start);
|
|
5629
|
+
this.#focusBlock = this.#processSelection ?? group.start;
|
|
5630
|
+
this.#focusBlockEdge = 'start';
|
|
4793
5631
|
this.#render();
|
|
4794
5632
|
return;
|
|
4795
5633
|
}
|
|
4796
|
-
|
|
5634
|
+
}
|
|
5635
|
+
/** The process list follows durable block order and grows without moving selection. */
|
|
5636
|
+
#processRows() {
|
|
5637
|
+
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() !== ''
|
|
5638
|
+
? [group.start + offset] : []));
|
|
5639
|
+
}
|
|
5640
|
+
#handleProcessNavigation(event) {
|
|
5641
|
+
const selected = this.#processSelection;
|
|
5642
|
+
if (selected === undefined)
|
|
5643
|
+
return false;
|
|
5644
|
+
if (event.type === 'text' || event.type === 'paste-start') {
|
|
5645
|
+
this.#processSelection = undefined;
|
|
5646
|
+
return false;
|
|
5647
|
+
}
|
|
5648
|
+
if (event.type !== 'key')
|
|
5649
|
+
return false;
|
|
5650
|
+
if (event.id === 'escape' || event.id === 'end') {
|
|
5651
|
+
this.#followTail();
|
|
5652
|
+
this.#render();
|
|
5653
|
+
return true;
|
|
5654
|
+
}
|
|
5655
|
+
if (event.id === 'home') {
|
|
5656
|
+
const current = this.#fileActionFrame?.transcript?.disclosures?.findLast(target => target.detailEnd !== undefined && this.#scrollStart > target.row && this.#scrollStart < target.detailEnd);
|
|
5657
|
+
this.#processSelection = current?.block ?? selected;
|
|
5658
|
+
this.#focusBlock = this.#processSelection;
|
|
5659
|
+
this.#focusBlockEdge = 'start';
|
|
5660
|
+
this.#render();
|
|
5661
|
+
return true;
|
|
5662
|
+
}
|
|
5663
|
+
if (event.id === 'up' || event.id === 'down') {
|
|
5664
|
+
const rows = this.#processRows();
|
|
5665
|
+
const at = Math.max(0, rows.indexOf(selected));
|
|
5666
|
+
const next = rows[Math.max(0, Math.min(rows.length - 1, at + (event.id === 'up' ? -1 : 1)))];
|
|
5667
|
+
if (next !== undefined) {
|
|
5668
|
+
this.#processSelection = next;
|
|
5669
|
+
const row = this.#blockStarts[next];
|
|
5670
|
+
if (row === undefined || row < this.#scrollStart || row >= this.#scrollStart + this.#scrollBudget - 1) {
|
|
5671
|
+
this.#focusBlock = next;
|
|
5672
|
+
this.#focusBlockEdge = 'start';
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
this.#render();
|
|
5676
|
+
return true;
|
|
5677
|
+
}
|
|
5678
|
+
if (!['enter', 'left', 'right'].includes(event.id))
|
|
5679
|
+
return false;
|
|
5680
|
+
this.#toggleProcessItem(selected, event.id);
|
|
5681
|
+
return true;
|
|
5682
|
+
}
|
|
5683
|
+
#toggleProcessItem(selected, direction) {
|
|
5684
|
+
const block = this.#state.blocks[selected];
|
|
5685
|
+
if (block?.kind === 'tool') {
|
|
5686
|
+
const visible = this.#detailedToolIds();
|
|
5687
|
+
const wasOpen = visible.has(block.callId);
|
|
5688
|
+
const open = direction === 'right' || direction !== 'left' && !wasOpen;
|
|
5689
|
+
const raw = this.#detailedToolIds(false).has(block.callId);
|
|
5690
|
+
if (wasOpen && !open) {
|
|
5691
|
+
const target = this.#fileActionFrame?.transcript?.disclosures?.find(item => item.block === selected);
|
|
5692
|
+
const offset = target?.detailEnd !== undefined && this.#scrollStart >= target.row && this.#scrollStart < target.detailEnd
|
|
5693
|
+
? this.#scrollStart - target.row : 0;
|
|
5694
|
+
this.#toolReadingPositions.set(block.callId, { offset, width: this.#term.width(), raw });
|
|
5695
|
+
}
|
|
5696
|
+
if (open !== wasOpen) {
|
|
5697
|
+
const saved = open ? this.#toolReadingPositions.get(block.callId) : undefined;
|
|
5698
|
+
this.#focusBlock = selected;
|
|
5699
|
+
this.#focusBlockEdge = 'start';
|
|
5700
|
+
// Width changes and raw/readable switches change the row layout.
|
|
5701
|
+
this.#focusBlockOffset = saved?.width === this.#term.width() && saved.raw === raw ? saved.offset : 0;
|
|
5702
|
+
}
|
|
5703
|
+
// Once a reader edits one disclosure, preserve the other visible calls as
|
|
5704
|
+
// individual choices instead of letting the turn-wide override reopen it.
|
|
5705
|
+
this.#expandedToolRows = new Set(visible);
|
|
5706
|
+
this.#detailedTurns.clear();
|
|
5707
|
+
if (open)
|
|
5708
|
+
this.#expandedToolRows.add(block.callId);
|
|
5709
|
+
else
|
|
5710
|
+
this.#expandedToolRows.delete(block.callId);
|
|
5711
|
+
}
|
|
5712
|
+
else if (block?.kind === 'assistant') {
|
|
5713
|
+
const key = reasoningKey(block);
|
|
5714
|
+
const open = direction === 'right' || direction !== 'left' && !this.#visibleReasoning().has(key);
|
|
5715
|
+
if (open) {
|
|
5716
|
+
this.#expandedReasoning.add(key);
|
|
5717
|
+
this.#closedReasoning.delete(key);
|
|
5718
|
+
}
|
|
5719
|
+
else {
|
|
5720
|
+
this.#expandedReasoning.delete(key);
|
|
5721
|
+
this.#closedReasoning.add(key);
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
const row = this.#blockStarts[selected];
|
|
5725
|
+
if (row === undefined || row < this.#scrollStart || row > this.#scrollStart + Math.floor(this.#scrollBudget / 2)) {
|
|
5726
|
+
this.#focusBlock = selected;
|
|
5727
|
+
this.#focusBlockEdge = 'start';
|
|
5728
|
+
}
|
|
5729
|
+
this.#render();
|
|
5730
|
+
}
|
|
5731
|
+
#visibleReasoning() {
|
|
5732
|
+
const open = new Set(this.#expandedReasoning);
|
|
5733
|
+
if (!this.#hideThinking)
|
|
5734
|
+
for (const block of this.#state.blocks) {
|
|
5735
|
+
if (block.kind === 'assistant')
|
|
5736
|
+
open.add(reasoningKey(block));
|
|
5737
|
+
}
|
|
5738
|
+
for (const key of this.#closedReasoning)
|
|
5739
|
+
open.delete(key);
|
|
5740
|
+
return open;
|
|
4797
5741
|
}
|
|
4798
5742
|
#detailScope(group) {
|
|
4799
5743
|
return group.turn === undefined ? group.key : `turn:${group.turn}`;
|
|
4800
5744
|
}
|
|
4801
|
-
#detailedToolIds() {
|
|
4802
|
-
const calls = new Set();
|
|
5745
|
+
#detailedToolIds(includeReadable = true) {
|
|
5746
|
+
const calls = new Set(includeReadable ? this.#expandedToolRows : []);
|
|
4803
5747
|
if (this.#detailedTurns.size === 0)
|
|
4804
5748
|
return calls;
|
|
4805
5749
|
for (const group of turnGroups(this.#state)) {
|
|
@@ -4843,6 +5787,11 @@ export class LocalTui {
|
|
|
4843
5787
|
this.#openedGroups.add(member.key);
|
|
4844
5788
|
const tools = members.flatMap(member => this.#state.blocks.slice(member.start, member.until)
|
|
4845
5789
|
.flatMap((block, offset) => block.kind === 'tool' ? [member.start + offset] : []));
|
|
5790
|
+
for (const at of tools) {
|
|
5791
|
+
const block = this.#state.blocks[at];
|
|
5792
|
+
if (block?.kind === 'tool')
|
|
5793
|
+
this.#expandedToolRows.delete(block.callId);
|
|
5794
|
+
}
|
|
4846
5795
|
const target = tools.includes(anchor) ? anchor : tools[0];
|
|
4847
5796
|
this.#inspect(target);
|
|
4848
5797
|
this.#focusBlock = target;
|
|
@@ -4868,14 +5817,19 @@ export class LocalTui {
|
|
|
4868
5817
|
return [];
|
|
4869
5818
|
const options = {
|
|
4870
5819
|
width: this.#term.width(),
|
|
5820
|
+
language: resolveUiLanguage(this.#uiLanguage),
|
|
4871
5821
|
colors: this.#colors,
|
|
4872
5822
|
themeName: this.#themeName,
|
|
5823
|
+
terminal: this.#terminalColors,
|
|
5824
|
+
compactLinks: this.#compactLinks,
|
|
4873
5825
|
...(this.#colorDepth === undefined ? {} : { colorDepth: this.#colorDepth }),
|
|
4874
5826
|
mathMode: this.#mathMode,
|
|
5827
|
+
mermaidMode: this.#mermaidMode,
|
|
4875
5828
|
toolsExpanded: this.#toolsExpanded,
|
|
4876
5829
|
expandedOutputs: this.#expandedOutputs,
|
|
4877
|
-
hideThinking:
|
|
5830
|
+
hideThinking: true,
|
|
4878
5831
|
expandedTools: this.#detailedToolIds(),
|
|
5832
|
+
expandedReasoning: this.#visibleReasoning(),
|
|
4879
5833
|
...(this.#openedGroups === undefined ? {} : { openedGroups: this.#openedGroups }),
|
|
4880
5834
|
};
|
|
4881
5835
|
return searchMatches(renderedBlocks(this.#state, options), search.query);
|
|
@@ -4978,6 +5932,7 @@ export function apply(ctx, config) {
|
|
|
4978
5932
|
// settings service, which persists the edit into the active profile patch.
|
|
4979
5933
|
const storedPrefs = () => {
|
|
4980
5934
|
const prefs = {
|
|
5935
|
+
uiLanguage: config.uiLanguage.get(),
|
|
4981
5936
|
theme: config.theme.get(),
|
|
4982
5937
|
colors: resolveColors(config.colors.get(), term.output.isTTY === true),
|
|
4983
5938
|
motion: config.motion.get(),
|
|
@@ -4988,6 +5943,7 @@ export function apply(ctx, config) {
|
|
|
4988
5943
|
terminalState: config.terminalState.get(),
|
|
4989
5944
|
copyOnSelect: config.copyOnSelect.get(),
|
|
4990
5945
|
mouseInteraction: config.mouseInteraction.get(),
|
|
5946
|
+
busyEnter: config.busyEnter.get(),
|
|
4991
5947
|
pasteProtection: config.pasteProtection.get(),
|
|
4992
5948
|
checkUpdates: config.checkUpdates.get(),
|
|
4993
5949
|
startupChangelog: config.startupChangelog.get(),
|