@agi-fans/dsh-tui 0.7.0 → 0.9.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/lib/CHANGELOG.md +44 -1
- package/lib/chrome/box.d.ts +0 -7
- package/lib/chrome/box.js +3 -22
- package/lib/chrome/box.js.map +1 -1
- package/lib/chrome/code-highlight.d.ts +45 -0
- package/lib/chrome/code-highlight.js +155 -0
- package/lib/chrome/code-highlight.js.map +1 -0
- package/lib/chrome/diff-render.d.ts +3 -1
- package/lib/chrome/diff-render.js +73 -9
- package/lib/chrome/diff-render.js.map +1 -1
- package/lib/chrome/main-screen-renderer.d.ts +55 -0
- package/lib/chrome/main-screen-renderer.js +343 -0
- package/lib/chrome/main-screen-renderer.js.map +1 -0
- package/lib/chrome/markdown.d.ts +3 -6
- package/lib/chrome/markdown.js +5 -59
- package/lib/chrome/markdown.js.map +1 -1
- package/lib/chrome/md-style.d.ts +31 -0
- package/lib/chrome/md-style.js +58 -0
- package/lib/chrome/md-style.js.map +1 -0
- package/lib/chrome/renderer.d.ts +8 -31
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/welcome-tips.js +2 -2
- package/lib/chrome/welcome-tips.js.map +1 -1
- package/lib/chrome/width.d.ts +15 -0
- package/lib/chrome/width.js +83 -0
- package/lib/chrome/width.js.map +1 -1
- package/lib/commands/auth.d.ts +2 -1
- package/lib/commands/auth.js +318 -38
- package/lib/commands/auth.js.map +1 -1
- package/lib/commands/permission.js +1 -6
- package/lib/commands/permission.js.map +1 -1
- package/lib/definition.d.ts +1 -1
- package/lib/input/keys.d.ts +1 -28
- package/lib/input/keys.js +15 -31
- package/lib/input/keys.js.map +1 -1
- package/lib/runtime/provider-local.d.ts +7 -5
- package/lib/runtime/provider-local.js +134 -208
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/session/authorization-interaction.d.ts +12 -0
- package/lib/session/authorization-interaction.js +70 -0
- package/lib/session/authorization-interaction.js.map +1 -0
- package/lib/session/session-controller.d.ts +8 -4
- package/lib/session/session-controller.js +13 -12
- package/lib/session/session-controller.js.map +1 -1
- package/lib/views/autocomplete.d.ts +0 -2
- package/lib/views/autocomplete.js +0 -8
- package/lib/views/autocomplete.js.map +1 -1
- package/lib/views/copy-selector.d.ts +0 -4
- package/lib/views/copy-selector.js +0 -17
- package/lib/views/copy-selector.js.map +1 -1
- package/lib/views/event-views.d.ts +3 -16
- package/lib/views/event-views.js +127 -93
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/history-search.d.ts +0 -5
- package/lib/views/history-search.js +0 -16
- package/lib/views/history-search.js.map +1 -1
- package/lib/views/hotkeys.js +0 -1
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/prompt-selector.d.ts +0 -5
- package/lib/views/prompt-selector.js +0 -13
- package/lib/views/prompt-selector.js.map +1 -1
- package/lib/views/settings-list.d.ts +0 -5
- package/lib/views/settings-list.js +0 -23
- package/lib/views/settings-list.js.map +1 -1
- package/package.json +29 -27
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
* Owns the tty: raw-mode key input (editing, history, slash/tab
|
|
5
5
|
* autocomplete, /settings overlay, /copy picker, Ctrl-R history search, PgUp/PgDn
|
|
6
|
-
* and
|
|
6
|
+
* and Shift+Up/Down transcript scroll, Ctrl-O tool
|
|
7
7
|
* expand, bracketed paste, double-Escape conversation rewind, double Ctrl-C exit, Ctrl-D quit),
|
|
8
8
|
* SIGWINCH reflow, and the differential renderer. In non-tty mode
|
|
9
9
|
* (pipes, CI) it degrades to line-based input with plain append-only
|
|
@@ -14,20 +14,19 @@ import { homedir } from 'node:os';
|
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { createInterface } from 'node:readline';
|
|
16
16
|
import { TUI_SERVICE, } from "../definition.js";
|
|
17
|
-
import { applySlashCompletion, formatHelpText,
|
|
17
|
+
import { applySlashCompletion, formatHelpText, parseSlashInput, resolveSlashCommand, slashSuggestions, BUILTIN_SLASH_COMMANDS, } from "../views/autocomplete.js";
|
|
18
18
|
import { applyPathCompletion, defaultPathSource, editorLineAt, findPathToken, parsePathPrefix, pathSuggestions, searchPathSuggestions, } from "../views/path-complete.js";
|
|
19
19
|
import { activeAtToken } from '@deepseek-ai/dsh-file-reference/grammar';
|
|
20
20
|
import { nextSelectableAutocompleteIndex, searchAtSuggestions, } from "../views/at-complete.js";
|
|
21
21
|
import { copyToClipboard, readFromClipboard } from "../input/clipboard.js";
|
|
22
|
-
import { applyCopySelectorEvent, createCopySelector,
|
|
22
|
+
import { applyCopySelectorEvent, createCopySelector, } from "../views/copy-selector.js";
|
|
23
23
|
import { buildCopyTargets, extractCopyTarget, parseCopyKind } from "../views/copy-targets.js";
|
|
24
|
-
import { applyHistorySearchEvent, createHistorySearch,
|
|
24
|
+
import { applyHistorySearchEvent, createHistorySearch, } from "../views/history-search.js";
|
|
25
25
|
import { InputEditor, lineEnd, lineStart } from "../input/editor.js";
|
|
26
|
-
import { applySettingsEvent, createSettings,
|
|
27
|
-
import { applyEvent, blockLines,
|
|
28
|
-
import { flushPending,
|
|
29
|
-
import {
|
|
30
|
-
import { hitTestEditor } from "../chrome/box.js";
|
|
26
|
+
import { applySettingsEvent, createSettings, } from "../views/settings-list.js";
|
|
27
|
+
import { applyEvent, blockLines, initialTranscript, replayEvents, renderView, TRANSCRIPT_FAST_SCROLL, } from "../views/event-views.js";
|
|
28
|
+
import { flushPending, parseKeys } from "../input/keys.js";
|
|
29
|
+
import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
|
|
31
30
|
import { createTheme, detectTrueColor, parseThemeName } from "../chrome/theme.js";
|
|
32
31
|
import { TUI_SETTINGS_NAMESPACE, TuiSettingsSchema } from "../session/tui-settings.js";
|
|
33
32
|
import { defaultStatusBarConfig, resolveStatusBarConfig } from "../chrome/status-config.js";
|
|
@@ -55,7 +54,19 @@ export const name = 'omdsh-tui';
|
|
|
55
54
|
/**
|
|
56
55
|
* Local terminal presentation service.
|
|
57
56
|
*/
|
|
57
|
+
function detectTerminalProfile() {
|
|
58
|
+
if (process.env.TMUX !== undefined || process.env.STY !== undefined || process.env.ZELLIJ !== undefined) {
|
|
59
|
+
return 'multiplexer';
|
|
60
|
+
}
|
|
61
|
+
return process.platform === 'win32' ? 'conpty' : 'direct';
|
|
62
|
+
}
|
|
58
63
|
export class LocalTui {
|
|
64
|
+
#deferInitialRender = false;
|
|
65
|
+
#terminalProfile;
|
|
66
|
+
#resizeDebounceMs;
|
|
67
|
+
#resizeTimer = null;
|
|
68
|
+
#streamRenderMs;
|
|
69
|
+
#streamRenderTimer = null;
|
|
59
70
|
#term;
|
|
60
71
|
#model;
|
|
61
72
|
#reasoningEffort;
|
|
@@ -93,7 +104,6 @@ export class LocalTui {
|
|
|
93
104
|
#autocompleteTimer = null;
|
|
94
105
|
#autocompleteAbort = null;
|
|
95
106
|
#autocompleteRequestId = 0;
|
|
96
|
-
#scrollRender = null;
|
|
97
107
|
#paste = false;
|
|
98
108
|
#pasteBuf = '';
|
|
99
109
|
#pasteInFlight = 0;
|
|
@@ -131,10 +141,9 @@ export class LocalTui {
|
|
|
131
141
|
#sessionControls;
|
|
132
142
|
#loopStatus;
|
|
133
143
|
#subagents;
|
|
144
|
+
#subagentLauncherFocused = false;
|
|
134
145
|
#inspected;
|
|
135
|
-
#
|
|
136
|
-
#chromeHit = null;
|
|
137
|
-
#overlayHit = null;
|
|
146
|
+
#promptDocument;
|
|
138
147
|
#trueColor;
|
|
139
148
|
#copy;
|
|
140
149
|
#readClipboard;
|
|
@@ -185,30 +194,45 @@ export class LocalTui {
|
|
|
185
194
|
this.#searchSessions = paths.searchSessions;
|
|
186
195
|
this.#autocompleteDebounceMs = Math.max(0, paths.autocompleteDebounceMs ?? 100);
|
|
187
196
|
this.#welcomeTips = pickWelcomeTips();
|
|
197
|
+
this.#deferInitialRender = paths.deferInitialRender === true;
|
|
198
|
+
this.#terminalProfile = paths.terminalProfile ?? detectTerminalProfile();
|
|
199
|
+
this.#resizeDebounceMs = Math.max(0, paths.resizeDebounceMs ?? 120);
|
|
200
|
+
this.#streamRenderMs = Math.max(0, paths.streamRenderMs ?? 8);
|
|
188
201
|
this.#trueColor = colors && detectTrueColor();
|
|
189
202
|
this.#tty = term.input.isTTY === true;
|
|
190
203
|
this.#pwd = shortenPath(project.root);
|
|
191
204
|
this.#branch = project.gitLabel;
|
|
192
|
-
this.#renderer = new
|
|
205
|
+
this.#renderer = new MainScreenRenderer({ write: (chunk) => { this.#term.output.write(chunk); } }, {
|
|
206
|
+
width: this.#term.width(),
|
|
207
|
+
height: this.#term.height(),
|
|
208
|
+
synchronized: this.#tty,
|
|
209
|
+
clearScrollback: this.#terminalProfile === 'direct',
|
|
210
|
+
alternateScreenOverlays: paths.alternateScreenOverlays === true,
|
|
211
|
+
});
|
|
193
212
|
if (this.#tty) {
|
|
213
|
+
this.#renderer.startEpoch();
|
|
194
214
|
term.input.setRawMode?.(true);
|
|
195
215
|
const listener = (chunk) => { this.#onData(chunk); };
|
|
196
216
|
term.input.on('data', listener);
|
|
197
217
|
this.#offData = () => { term.input.off('data', listener); };
|
|
198
218
|
this.#offResize = term.onResize?.(() => {
|
|
199
|
-
// A terminal resize
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
219
|
+
// A terminal resize changes the committed/live seam; re-anchor the
|
|
220
|
+
// live window so it stays anchored at the bottom.
|
|
221
|
+
const repaint = () => {
|
|
222
|
+
this.#resizeTimer = null;
|
|
223
|
+
this.#renderer.resize(this.#term.width(), this.#term.height());
|
|
224
|
+
this.#render();
|
|
225
|
+
};
|
|
226
|
+
if (this.#terminalProfile === 'multiplexer') {
|
|
227
|
+
if (this.#resizeTimer !== null)
|
|
228
|
+
clearTimeout(this.#resizeTimer);
|
|
229
|
+
this.#resizeTimer = setTimeout(repaint, this.#resizeDebounceMs);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
repaint();
|
|
233
|
+
}
|
|
204
234
|
}) ?? null;
|
|
205
|
-
|
|
206
|
-
// already scrolled the cursor off row 0; the renderer's screen-relative
|
|
207
|
-
// frames require a clean origin. Clear the screen and home the cursor
|
|
208
|
-
// before the first frame. Enable bracketed paste and SGR mouse
|
|
209
|
-
// tracking (wheel drives the virtual transcript; native scrollback
|
|
210
|
-
// is already unusable under full-screen diffs).
|
|
211
|
-
term.output.write('\x1b[2J\x1b[H\x1b[?2004h' + MOUSE_TRACKING_ON);
|
|
235
|
+
term.output.write('\x1b[?2004h');
|
|
212
236
|
}
|
|
213
237
|
this.#render();
|
|
214
238
|
}
|
|
@@ -216,7 +240,12 @@ export class LocalTui {
|
|
|
216
240
|
this.#state = applyEvent(this.#state, event, presentation);
|
|
217
241
|
this.#syncTick();
|
|
218
242
|
if (this.#tty) {
|
|
219
|
-
this.#
|
|
243
|
+
if (event.type === 'assistant/chunk' && this.#streamRenderMs > 0) {
|
|
244
|
+
this.#scheduleStreamRender();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.#render();
|
|
248
|
+
}
|
|
220
249
|
}
|
|
221
250
|
else if (event.type === 'user/message' || event.type === 'assistant/message' || event.type === 'tool/result' || event.type === 'turn/end') {
|
|
222
251
|
this.#printPlain();
|
|
@@ -246,6 +275,8 @@ export class LocalTui {
|
|
|
246
275
|
this.#subagents = roster === undefined
|
|
247
276
|
? undefined
|
|
248
277
|
: { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })) };
|
|
278
|
+
if ((this.#subagents?.agents.length ?? 0) === 0)
|
|
279
|
+
this.#subagentLauncherFocused = false;
|
|
249
280
|
const inspected = this.#inspected;
|
|
250
281
|
if (inspected !== undefined) {
|
|
251
282
|
const current = this.#subagents?.agents.find(agent => agent.id === inspected.id);
|
|
@@ -265,6 +296,8 @@ export class LocalTui {
|
|
|
265
296
|
}
|
|
266
297
|
setInspectedSubagent(inspected) {
|
|
267
298
|
this.#inspected = inspected === undefined ? undefined : { ...inspected };
|
|
299
|
+
if (inspected !== undefined)
|
|
300
|
+
this.#subagentLauncherFocused = false;
|
|
268
301
|
this.#syncTick();
|
|
269
302
|
if (this.#tty)
|
|
270
303
|
this.#render();
|
|
@@ -352,11 +385,13 @@ export class LocalTui {
|
|
|
352
385
|
}
|
|
353
386
|
});
|
|
354
387
|
}
|
|
355
|
-
replaceSession(events, presentations) {
|
|
388
|
+
replaceSession(events, presentations, status = 'idle') {
|
|
356
389
|
const state = replayEvents(events, presentations);
|
|
357
|
-
this.#state = { ...state, status
|
|
390
|
+
this.#state = { ...state, status, compactCommandId: undefined };
|
|
358
391
|
this.#plainPrinted = 0;
|
|
359
392
|
this.#followTail();
|
|
393
|
+
this.#deferInitialRender = false;
|
|
394
|
+
this.#renderer.startEpoch({ replay: status === 'idle' ? 'full' : 'pinned' });
|
|
360
395
|
if (this.#tty)
|
|
361
396
|
this.#render();
|
|
362
397
|
else
|
|
@@ -495,6 +530,14 @@ export class LocalTui {
|
|
|
495
530
|
if (this.#disposed)
|
|
496
531
|
return;
|
|
497
532
|
this.#disposed = true;
|
|
533
|
+
if (this.#resizeTimer !== null) {
|
|
534
|
+
clearTimeout(this.#resizeTimer);
|
|
535
|
+
this.#resizeTimer = null;
|
|
536
|
+
}
|
|
537
|
+
if (this.#streamRenderTimer !== null) {
|
|
538
|
+
clearTimeout(this.#streamRenderTimer);
|
|
539
|
+
this.#streamRenderTimer = null;
|
|
540
|
+
}
|
|
498
541
|
if (this.#tick !== null) {
|
|
499
542
|
clearInterval(this.#tick);
|
|
500
543
|
this.#tick = null;
|
|
@@ -503,19 +546,15 @@ export class LocalTui {
|
|
|
503
546
|
clearInterval(this.#loopTick);
|
|
504
547
|
this.#loopTick = null;
|
|
505
548
|
}
|
|
506
|
-
if (this.#scrollRender !== null) {
|
|
507
|
-
clearImmediate(this.#scrollRender);
|
|
508
|
-
this.#scrollRender = null;
|
|
509
|
-
}
|
|
510
549
|
if (this.#tty) {
|
|
511
550
|
this.#offData?.();
|
|
512
551
|
this.#offResize?.();
|
|
513
552
|
this.#term.input.setRawMode?.(false);
|
|
514
553
|
// Leave the cursor on a fresh line below the last frame so the shell
|
|
515
|
-
// prompt does not overwrite the transcript. Disable
|
|
516
|
-
// and
|
|
554
|
+
// prompt does not overwrite the transcript. Disable bracketed paste
|
|
555
|
+
// and restore the cursor.
|
|
517
556
|
this.#renderer.finish();
|
|
518
|
-
this.#term.output.write(
|
|
557
|
+
this.#term.output.write('\x1b[?2004l\x1b[?25h\r\n');
|
|
519
558
|
if (this.#resumeHintRequested && this.#sessionId !== undefined) {
|
|
520
559
|
this.#term.output.write(`\r\nResume this session with ${APP_NAME} --resume ${this.#sessionId}\r\n`);
|
|
521
560
|
}
|
|
@@ -635,10 +674,12 @@ export class LocalTui {
|
|
|
635
674
|
}
|
|
636
675
|
}
|
|
637
676
|
#render() {
|
|
638
|
-
if (this.#
|
|
639
|
-
|
|
640
|
-
this.#
|
|
677
|
+
if (this.#streamRenderTimer !== null) {
|
|
678
|
+
clearTimeout(this.#streamRenderTimer);
|
|
679
|
+
this.#streamRenderTimer = null;
|
|
641
680
|
}
|
|
681
|
+
if (this.#deferInitialRender)
|
|
682
|
+
return;
|
|
642
683
|
const width = this.#term.width();
|
|
643
684
|
const frame = this.#tty
|
|
644
685
|
? renderView(this.#state, {
|
|
@@ -671,6 +712,7 @@ export class LocalTui {
|
|
|
671
712
|
...(this.#sessionControls === undefined ? {} : { sessionControls: this.#sessionControls }),
|
|
672
713
|
...(this.#loopStatus === undefined ? {} : { loopStatus: this.#loopStatus }),
|
|
673
714
|
...(this.#subagents === undefined ? {} : { subagents: this.#subagents }),
|
|
715
|
+
subagentLauncherFocused: this.#subagentLauncherFocused,
|
|
674
716
|
...(this.#inspected === undefined ? {} : { inspected: this.#inspected }),
|
|
675
717
|
statusBar: this.#statusBar,
|
|
676
718
|
...(this.#prompt === null ? {} : { promptSelector: this.#prompt }),
|
|
@@ -684,12 +726,19 @@ export class LocalTui {
|
|
|
684
726
|
})
|
|
685
727
|
: { lines: [] };
|
|
686
728
|
this.#focusBlock = undefined;
|
|
687
|
-
this.#
|
|
688
|
-
this.#overlayHit = frame.overlay ?? null;
|
|
689
|
-
this.#chromeHit = frame.chrome ?? null;
|
|
729
|
+
this.#promptDocument = frame.promptDocument;
|
|
690
730
|
this.#syncScroll(frame.transcript);
|
|
691
731
|
this.#renderer.render(frame);
|
|
692
732
|
}
|
|
733
|
+
#scheduleStreamRender() {
|
|
734
|
+
if (this.#streamRenderTimer !== null)
|
|
735
|
+
return;
|
|
736
|
+
this.#streamRenderTimer = setTimeout(() => {
|
|
737
|
+
this.#streamRenderTimer = null;
|
|
738
|
+
if (!this.#disposed)
|
|
739
|
+
this.#render();
|
|
740
|
+
}, this.#streamRenderMs);
|
|
741
|
+
}
|
|
693
742
|
#syncScroll(scroll) {
|
|
694
743
|
if (scroll === undefined) {
|
|
695
744
|
this.#scrollStart = 0;
|
|
@@ -709,24 +758,14 @@ export class LocalTui {
|
|
|
709
758
|
#pageSize() {
|
|
710
759
|
return Math.max(1, this.#scrollBudget > 2 ? this.#scrollBudget - 2 : 1);
|
|
711
760
|
}
|
|
712
|
-
#scrollBy(delta
|
|
761
|
+
#scrollBy(delta) {
|
|
713
762
|
if (delta === 0 && this.#maxStart === 0)
|
|
714
763
|
return;
|
|
715
764
|
this.#follow = false;
|
|
716
765
|
this.#scrollStart += delta;
|
|
717
766
|
if (this.#scrollStart <= 0)
|
|
718
767
|
this.#scrollStart = 0;
|
|
719
|
-
|
|
720
|
-
this.#render();
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
if (this.#scrollRender !== null)
|
|
724
|
-
return;
|
|
725
|
-
this.#scrollRender = setImmediate(() => {
|
|
726
|
-
this.#scrollRender = null;
|
|
727
|
-
if (!this.#disposed)
|
|
728
|
-
this.#render();
|
|
729
|
-
});
|
|
768
|
+
this.#render();
|
|
730
769
|
}
|
|
731
770
|
#followTail() {
|
|
732
771
|
this.#follow = true;
|
|
@@ -945,10 +984,6 @@ export class LocalTui {
|
|
|
945
984
|
}
|
|
946
985
|
#dispatch(event) {
|
|
947
986
|
if (this.#state.status === 'compacting') {
|
|
948
|
-
if (event.type === 'mouse' && event.wheel !== null) {
|
|
949
|
-
this.#scrollBy(event.wheel < 0 ? -TRANSCRIPT_WHEEL_SCROLL : TRANSCRIPT_WHEEL_SCROLL, true);
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
987
|
if (event.type === 'key' && event.id === 'pageUp') {
|
|
953
988
|
this.#scrollBy(-this.#pageSize());
|
|
954
989
|
return;
|
|
@@ -987,11 +1022,6 @@ export class LocalTui {
|
|
|
987
1022
|
this.#pasteBuf = '';
|
|
988
1023
|
return;
|
|
989
1024
|
}
|
|
990
|
-
if (event.type === 'mouse') {
|
|
991
|
-
this.#lastEscapeTime = 0;
|
|
992
|
-
this.#handleMouse(event);
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
1025
|
if (event.type !== 'key' || event.id !== 'escape')
|
|
996
1026
|
this.#lastEscapeTime = 0;
|
|
997
1027
|
if (this.#handlePrompt(event))
|
|
@@ -1074,6 +1104,8 @@ export class LocalTui {
|
|
|
1074
1104
|
this.#lastEscapeTime = 0;
|
|
1075
1105
|
return;
|
|
1076
1106
|
}
|
|
1107
|
+
if (this.#handleSubagentLauncher(event))
|
|
1108
|
+
return;
|
|
1077
1109
|
if (event.type === 'key' && event.id === 'escape') {
|
|
1078
1110
|
if (this.#inspected !== undefined) {
|
|
1079
1111
|
if (this.#inspected.writable === true && (this.#editor.text !== '' || this.#images.length > 0)) {
|
|
@@ -1267,7 +1299,7 @@ export class LocalTui {
|
|
|
1267
1299
|
this.#render();
|
|
1268
1300
|
return true;
|
|
1269
1301
|
}
|
|
1270
|
-
const scroll = this.#
|
|
1302
|
+
const scroll = this.#promptDocument;
|
|
1271
1303
|
let documentScroll;
|
|
1272
1304
|
if (event.id === 'up')
|
|
1273
1305
|
documentScroll = (scroll?.start ?? prompt.documentScroll ?? 0) - 1;
|
|
@@ -1317,73 +1349,42 @@ export class LocalTui {
|
|
|
1317
1349
|
}
|
|
1318
1350
|
return true;
|
|
1319
1351
|
}
|
|
1320
|
-
#
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
return;
|
|
1324
|
-
if (this.#clickOverlay(event.row))
|
|
1325
|
-
return;
|
|
1326
|
-
this.#clickEditor(event.row, event.col);
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
if (event.wheel === null)
|
|
1330
|
-
return;
|
|
1331
|
-
const dir = event.wheel;
|
|
1332
|
-
if (this.#prompt !== null) {
|
|
1333
|
-
this.#handlePrompt({ type: 'key', id: dir < 0 ? 'up' : 'down' });
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
if (this.#settings !== null) {
|
|
1337
|
-
this.#applySettings({ type: 'key', id: dir < 0 ? 'up' : 'down' });
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
if (this.#copySelector !== null) {
|
|
1341
|
-
this.#applyCopySelector({ type: 'key', id: dir < 0 ? 'up' : 'down' });
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
if (this.#search !== null) {
|
|
1345
|
-
this.#applySearch({ type: 'key', id: dir < 0 ? 'up' : 'down' });
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
if (this.#ac !== null) {
|
|
1349
|
-
this.#moveAutocomplete(dir);
|
|
1350
|
-
this.#render();
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
this.#scrollBy(dir < 0 ? -TRANSCRIPT_WHEEL_SCROLL : TRANSCRIPT_WHEEL_SCROLL, true);
|
|
1352
|
+
#openInspect(id) {
|
|
1353
|
+
for (const listener of this.#inspects)
|
|
1354
|
+
listener(id);
|
|
1354
1355
|
}
|
|
1355
|
-
#
|
|
1356
|
-
const
|
|
1357
|
-
if (
|
|
1356
|
+
#handleSubagentLauncher(event) {
|
|
1357
|
+
const hasAgents = (this.#subagents?.agents.length ?? 0) > 0;
|
|
1358
|
+
if (this.#subagentLauncherFocused) {
|
|
1359
|
+
if (!hasAgents) {
|
|
1360
|
+
this.#subagentLauncherFocused = false;
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
if (event.type === 'key' && event.id === 'enter') {
|
|
1364
|
+
this.#subagentLauncherFocused = false;
|
|
1365
|
+
void this.#pickSubagent();
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
if (event.type === 'key' && (event.id === 'escape' || event.id === 'up')) {
|
|
1369
|
+
this.#subagentLauncherFocused = false;
|
|
1370
|
+
this.#render();
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
if (event.type === 'key' && event.id === 'down')
|
|
1374
|
+
return true;
|
|
1375
|
+
this.#subagentLauncherFocused = false;
|
|
1358
1376
|
return false;
|
|
1359
|
-
const inspect = hit.inspect;
|
|
1360
|
-
if (inspect !== undefined && row >= inspect.start && row < inspect.start + inspect.rows) {
|
|
1361
|
-
this.#closeInspect();
|
|
1362
|
-
return true;
|
|
1363
1377
|
}
|
|
1364
|
-
|
|
1365
|
-
if (roster === undefined)
|
|
1378
|
+
if (!hasAgents || event.type !== 'key' || event.id !== 'down')
|
|
1366
1379
|
return false;
|
|
1367
|
-
|
|
1368
|
-
if (localRow < 0 || localRow >= roster.rows)
|
|
1380
|
+
if (this.#editor.text !== '' || this.#images.length > 0 || this.#historyIndex !== 0 || this.#ac !== null)
|
|
1369
1381
|
return false;
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const id = hitTestSubagentRoster(roster.items, localRow);
|
|
1375
|
-
if (id === undefined)
|
|
1376
|
-
return true;
|
|
1377
|
-
if (this.#inspected?.id === id)
|
|
1378
|
-
this.#closeInspect();
|
|
1379
|
-
else
|
|
1380
|
-
this.#openInspect(id);
|
|
1382
|
+
if (this.#queuedSubmissions.length > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1383
|
+
return false;
|
|
1384
|
+
this.#subagentLauncherFocused = true;
|
|
1385
|
+
this.#render();
|
|
1381
1386
|
return true;
|
|
1382
1387
|
}
|
|
1383
|
-
#openInspect(id) {
|
|
1384
|
-
for (const listener of this.#inspects)
|
|
1385
|
-
listener(id);
|
|
1386
|
-
}
|
|
1387
1388
|
#closeInspect() {
|
|
1388
1389
|
if (this.#inspected === undefined)
|
|
1389
1390
|
return;
|
|
@@ -1391,6 +1392,7 @@ export class LocalTui {
|
|
|
1391
1392
|
listener();
|
|
1392
1393
|
}
|
|
1393
1394
|
async #pickSubagent() {
|
|
1395
|
+
this.#subagentLauncherFocused = false;
|
|
1394
1396
|
const agents = this.#subagents?.agents ?? [];
|
|
1395
1397
|
if (agents.length === 0) {
|
|
1396
1398
|
this.notice('No subagents are available in this session.');
|
|
@@ -1398,7 +1400,7 @@ export class LocalTui {
|
|
|
1398
1400
|
}
|
|
1399
1401
|
const initialValue = this.#inspected?.id ?? agents[0]?.id;
|
|
1400
1402
|
const answer = await this.prompt({
|
|
1401
|
-
title: '
|
|
1403
|
+
title: 'Agent Hub',
|
|
1402
1404
|
question: 'Open a subagent transcript',
|
|
1403
1405
|
options: agents.map(agent => ({
|
|
1404
1406
|
label: agent.label,
|
|
@@ -1407,93 +1409,14 @@ export class LocalTui {
|
|
|
1407
1409
|
})),
|
|
1408
1410
|
...(initialValue === undefined ? {} : { initialValue }),
|
|
1409
1411
|
allowCustom: false,
|
|
1410
|
-
|
|
1412
|
+
presentation: 'fullscreen-list',
|
|
1413
|
+
filterable: true,
|
|
1411
1414
|
submitLabel: 'open',
|
|
1412
1415
|
});
|
|
1413
1416
|
if (answer === null || answer === '')
|
|
1414
1417
|
return;
|
|
1415
1418
|
this.#openInspect(answer);
|
|
1416
1419
|
}
|
|
1417
|
-
#clickOverlay(row) {
|
|
1418
|
-
const hit = this.#overlayHit;
|
|
1419
|
-
if (hit === null)
|
|
1420
|
-
return false;
|
|
1421
|
-
const localRow = row - hit.start;
|
|
1422
|
-
if (hit.kind === 'settings' && this.#settings !== null) {
|
|
1423
|
-
const index = hitTestSettings(hit.itemRows ?? tuiSettingItems(this.#settings.prefs).length, localRow);
|
|
1424
|
-
if (index === undefined)
|
|
1425
|
-
return true;
|
|
1426
|
-
if (index === this.#settings.selected) {
|
|
1427
|
-
this.#applySettings({ type: 'key', id: 'enter' });
|
|
1428
|
-
}
|
|
1429
|
-
else {
|
|
1430
|
-
this.#settings = selectSetting(this.#settings, index);
|
|
1431
|
-
this.#render();
|
|
1432
|
-
}
|
|
1433
|
-
return true;
|
|
1434
|
-
}
|
|
1435
|
-
if (hit.kind === 'copy' && this.#copySelector !== null) {
|
|
1436
|
-
const index = hitTestCopySelector(this.#copySelector.items.length, this.#copySelector.selected, localRow);
|
|
1437
|
-
if (index === undefined)
|
|
1438
|
-
return true;
|
|
1439
|
-
if (index === this.#copySelector.selected) {
|
|
1440
|
-
this.#applyCopySelector({ type: 'key', id: 'enter' });
|
|
1441
|
-
}
|
|
1442
|
-
else {
|
|
1443
|
-
this.#copySelector = selectCopyTarget(this.#copySelector, index);
|
|
1444
|
-
this.#render();
|
|
1445
|
-
}
|
|
1446
|
-
return true;
|
|
1447
|
-
}
|
|
1448
|
-
if (hit.kind === 'autocomplete' && this.#ac !== null) {
|
|
1449
|
-
const index = hitTestAutocomplete(this.#ac.items.length, this.#ac.selected, localRow);
|
|
1450
|
-
if (index === undefined || this.#ac.items[index]?.kind === 'heading')
|
|
1451
|
-
return true;
|
|
1452
|
-
this.#ac = { ...this.#ac, selected: index };
|
|
1453
|
-
this.#applySelectedCompletion();
|
|
1454
|
-
this.#render();
|
|
1455
|
-
return true;
|
|
1456
|
-
}
|
|
1457
|
-
if (hit.kind === 'search' && this.#search !== null) {
|
|
1458
|
-
const index = hitTestHistorySearch(this.#search.results.length, this.#search.selected, localRow, hit.resultsRow ?? 0);
|
|
1459
|
-
if (index === undefined)
|
|
1460
|
-
return true;
|
|
1461
|
-
this.#search = { ...this.#search, selected: index };
|
|
1462
|
-
this.#applySearch({ type: 'key', id: 'enter' });
|
|
1463
|
-
return true;
|
|
1464
|
-
}
|
|
1465
|
-
if (hit.kind === 'prompt' && this.#prompt !== null) {
|
|
1466
|
-
const index = hit.itemRows?.[localRow];
|
|
1467
|
-
if (index === undefined)
|
|
1468
|
-
return true;
|
|
1469
|
-
if (index === this.#prompt.selected) {
|
|
1470
|
-
this.#finishPrompt(selectedFilteredPromptAnswer(this.#prompt, this.#editor.text));
|
|
1471
|
-
this.#editor.setText('');
|
|
1472
|
-
}
|
|
1473
|
-
else {
|
|
1474
|
-
this.#prompt = { ...this.#prompt, selected: index };
|
|
1475
|
-
}
|
|
1476
|
-
this.#render();
|
|
1477
|
-
return true;
|
|
1478
|
-
}
|
|
1479
|
-
return false;
|
|
1480
|
-
}
|
|
1481
|
-
#clickEditor(row, col) {
|
|
1482
|
-
if (this.#settings !== null || this.#copySelector !== null || this.#search !== null)
|
|
1483
|
-
return;
|
|
1484
|
-
const hit = this.#editorHit;
|
|
1485
|
-
if (hit === null)
|
|
1486
|
-
return;
|
|
1487
|
-
const localRow = row - hit.start;
|
|
1488
|
-
if (localRow < 0 || localRow >= hit.rows)
|
|
1489
|
-
return;
|
|
1490
|
-
const index = hitTestEditor(this.#editor.text, this.#term.width(), localRow, col);
|
|
1491
|
-
if (index === undefined)
|
|
1492
|
-
return;
|
|
1493
|
-
this.#editor.setCursor(index);
|
|
1494
|
-
this.#refreshAutocomplete();
|
|
1495
|
-
this.#render();
|
|
1496
|
-
}
|
|
1497
1420
|
#prefs() {
|
|
1498
1421
|
return {
|
|
1499
1422
|
theme: this.#themeName,
|
|
@@ -1806,7 +1729,6 @@ export class LocalTui {
|
|
|
1806
1729
|
}
|
|
1807
1730
|
if (command.kind === 'resetDisplay') {
|
|
1808
1731
|
this.#renderer.reset();
|
|
1809
|
-
this.#term.output.write('\x1b[2J\x1b[H');
|
|
1810
1732
|
this.#render();
|
|
1811
1733
|
}
|
|
1812
1734
|
}
|
|
@@ -1981,6 +1903,7 @@ export class LocalTui {
|
|
|
1981
1903
|
if (command.name === 'clear') {
|
|
1982
1904
|
this.#state = initialTranscript();
|
|
1983
1905
|
this.#followTail();
|
|
1906
|
+
this.#renderer.startEpoch();
|
|
1984
1907
|
this.#render();
|
|
1985
1908
|
return;
|
|
1986
1909
|
}
|
|
@@ -2155,7 +2078,6 @@ export class LocalTui {
|
|
|
2155
2078
|
finally {
|
|
2156
2079
|
this.#term.input.setRawMode?.(true);
|
|
2157
2080
|
this.#renderer.reset();
|
|
2158
|
-
this.#term.output.write('\x1b[2J\x1b[H');
|
|
2159
2081
|
this.#refreshAutocomplete();
|
|
2160
2082
|
this.#render();
|
|
2161
2083
|
}
|
|
@@ -2168,6 +2090,7 @@ export class LocalTui {
|
|
|
2168
2090
|
*/
|
|
2169
2091
|
export function apply(ctx, config) {
|
|
2170
2092
|
const dshHome = process.env.OMDSH_HOME ?? process.env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
2093
|
+
const terminalProfile = detectTerminalProfile();
|
|
2171
2094
|
const term = {
|
|
2172
2095
|
output: process.stdout,
|
|
2173
2096
|
input: process.stdin,
|
|
@@ -2179,6 +2102,9 @@ export function apply(ctx, config) {
|
|
|
2179
2102
|
},
|
|
2180
2103
|
};
|
|
2181
2104
|
const tui = new LocalTui(term, config.model, config.colors ?? term.output.isTTY === true, parseThemeName(config.theme), copyToClipboard, {
|
|
2105
|
+
deferInitialRender: true,
|
|
2106
|
+
terminalProfile,
|
|
2107
|
+
alternateScreenOverlays: terminalProfile === 'direct',
|
|
2182
2108
|
historyPath: config.historyPath ?? join(dshHome, 'omdsh', 'history.jsonl'),
|
|
2183
2109
|
keybindingsPath: config.keybindingsPath ?? join(dshHome, 'omdsh', 'keybindings.json'),
|
|
2184
2110
|
});
|