@agi-fans/dsh-tui 0.24.1 → 0.26.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 +42 -2
- package/lib/chrome/box.d.ts +7 -0
- package/lib/chrome/box.js +1 -0
- package/lib/chrome/box.js.map +1 -1
- package/lib/chrome/main-screen-renderer.d.ts +4 -2
- package/lib/chrome/main-screen-renderer.js +11 -5
- package/lib/chrome/main-screen-renderer.js.map +1 -1
- package/lib/chrome/renderer.d.ts +12 -0
- package/lib/chrome/renderer.js +1 -1
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/status-line.js +3 -2
- package/lib/chrome/status-line.js.map +1 -1
- package/lib/chrome/width.d.ts +2 -0
- package/lib/chrome/width.js +10 -0
- package/lib/chrome/width.js.map +1 -1
- package/lib/commands/session.js +23 -12
- package/lib/commands/session.js.map +1 -1
- package/lib/definition.d.ts +13 -2
- package/lib/input/actions.d.ts +1 -1
- package/lib/input/actions.js +16 -5
- package/lib/input/actions.js.map +1 -1
- package/lib/input/clipboard.d.ts +1 -1
- package/lib/input/clipboard.js +10 -4
- package/lib/input/clipboard.js.map +1 -1
- package/lib/input/editor.js +4 -0
- package/lib/input/editor.js.map +1 -1
- package/lib/input/image-paste.d.ts +1 -2
- package/lib/input/image-paste.js +2 -7
- package/lib/input/image-paste.js.map +1 -1
- package/lib/input/key-platform.d.ts +2 -0
- package/lib/input/key-platform.js +8 -0
- package/lib/input/key-platform.js.map +1 -0
- package/lib/input/keybindings-config.d.ts +2 -1
- package/lib/input/keybindings-config.js +14 -9
- package/lib/input/keybindings-config.js.map +1 -1
- package/lib/input/keys.d.ts +2 -0
- package/lib/input/keys.js +10 -0
- package/lib/input/keys.js.map +1 -1
- package/lib/locale/zh.d.ts +4 -3
- package/lib/locale/zh.js +4 -3
- package/lib/locale/zh.js.map +1 -1
- package/lib/runtime/help-reader.d.ts +1 -1
- package/lib/runtime/help-reader.js +2 -2
- package/lib/runtime/help-reader.js.map +1 -1
- package/lib/runtime/herdr-agent.d.ts +2 -2
- package/lib/runtime/herdr-agent.js +2 -2
- package/lib/runtime/herdr-agent.js.map +1 -1
- package/lib/runtime/provider-local.d.ts +4 -1
- package/lib/runtime/provider-local.js +327 -71
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/terminal-notifications.d.ts +3 -1
- package/lib/runtime/terminal-notifications.js +7 -2
- package/lib/runtime/terminal-notifications.js.map +1 -1
- package/lib/runtime/terminal-session.d.ts +1 -0
- package/lib/runtime/terminal-session.js +1 -0
- package/lib/runtime/terminal-session.js.map +1 -1
- package/lib/runtime/terminal-state.d.ts +10 -1
- package/lib/runtime/terminal-state.js +39 -7
- package/lib/runtime/terminal-state.js.map +1 -1
- package/lib/session/authorization-interaction.js +3 -3
- package/lib/session/authorization-interaction.js.map +1 -1
- package/lib/session/content-text.d.ts +5 -0
- package/lib/session/content-text.js +7 -0
- package/lib/session/content-text.js.map +1 -1
- package/lib/session/interaction-adapter.js +1 -0
- package/lib/session/interaction-adapter.js.map +1 -1
- package/lib/session/session-controller.js +40 -6
- package/lib/session/session-controller.js.map +1 -1
- package/lib/views/file-browser.d.ts +3 -0
- package/lib/views/file-browser.js +1 -0
- package/lib/views/file-browser.js.map +1 -1
- package/lib/views/hotkeys.d.ts +4 -4
- package/lib/views/hotkeys.js +24 -14
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/prompt-selector.d.ts +1 -0
- package/lib/views/prompt-selector.js +6 -3
- package/lib/views/prompt-selector.js.map +1 -1
- package/lib/views/settings-list.js +3 -3
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/trajectory.js +5 -5
- package/lib/views/trajectory.js.map +1 -1
- package/lib/views/transcript-render.d.ts +1 -0
- package/lib/views/transcript-render.js +48 -29
- package/lib/views/transcript-render.js.map +1 -1
- package/lib/views/transcript-types.d.ts +1 -4
- package/lib/views/transcript-types.js +4 -7
- package/lib/views/transcript-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ import { localizePrompt } from "../locale/prompt.js";
|
|
|
23
23
|
import { resolveUiLanguage, uiText } from "../locale/index.js";
|
|
24
24
|
import { formatAgentPreset } from "../session/session-configuration.js";
|
|
25
25
|
import { TerminalNotificationController, TerminalNotificationQueue, terminalNotificationSequence, } from "./terminal-notifications.js";
|
|
26
|
-
import { PROGRAM_STATUS_QUERY, programStatusSequence, terminalTitle } from "./terminal-state.js";
|
|
26
|
+
import { PROGRAM_STATUS_QUERY, programStatusNeedsAttention, programStatusSequence, terminalProgressSequence, terminalTitle, workingDirectorySequence, } from "./terminal-state.js";
|
|
27
27
|
import { TerminalSession } from "./terminal-session.js";
|
|
28
28
|
import { BRACKETED_PASTE, FOCUS_REPORTING, MOUSE_TRACKING, } from "./terminal-modes.js";
|
|
29
29
|
import { IDLE_WHEEL, terminalAcceleratesWheel, wheelStep } from "../input/wheel-scroll.js";
|
|
@@ -64,11 +64,11 @@ import { appendTrajectoryEvent, applyTrajectoryEvent, createTrajectory, trajecto
|
|
|
64
64
|
import { applyEvent, applyStreamChunk, renderedBlocks, formatAgentElapsed, rosterPreviewStart, initialTranscript, replayEvents, renderView, turnGroups, transcriptSeam, TRANSCRIPT_FAST_SCROLL, withWorkspaceSummary, } from "../views/event-views.js";
|
|
65
65
|
import { applyTranscriptSearchEvent, createTranscriptSearch, searchMatches, } from "../views/transcript-search.js";
|
|
66
66
|
import { nextRevealStep, revealStreamingAssistant, streamingAssistantKey, streamingAssistantUnits, } from "../views/streaming-reveal.js";
|
|
67
|
-
import { flushPending, parseKeys } from "../input/keys.js";
|
|
67
|
+
import { flushPending, normalizeKeyId, parseKeys } from "../input/keys.js";
|
|
68
68
|
import { sanitizeDisplayLine } from "../chrome/renderer.js";
|
|
69
69
|
import { blockFileAction } from "../views/file-actions.js";
|
|
70
70
|
import { fileDocument, reviewFiles } from "./file-review.js";
|
|
71
|
-
import { stripAnsi } from "../chrome/width.js";
|
|
71
|
+
import { indexAtCell, stripAnsi } from "../chrome/width.js";
|
|
72
72
|
import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
|
|
73
73
|
import { supportsHyperlinks } from "./terminal-probe.js";
|
|
74
74
|
import { colorDisabledByEnv, createTheme, detectColorDepth } from "../chrome/theme.js";
|
|
@@ -207,6 +207,8 @@ export class LocalTui {
|
|
|
207
207
|
#keyTest;
|
|
208
208
|
#mouseActionPress;
|
|
209
209
|
#fileActionFrame;
|
|
210
|
+
#inputFrame;
|
|
211
|
+
#inputPress;
|
|
210
212
|
#imageTemporary = new Set();
|
|
211
213
|
#terminalProfile;
|
|
212
214
|
#resizeDebounceMs;
|
|
@@ -227,7 +229,7 @@ export class LocalTui {
|
|
|
227
229
|
#files = new ComposerFiles({ text: () => this.#editor.text,
|
|
228
230
|
append: marker => { this.#editor.setCursor(this.#editor.text.length); this.#editor.handle({ type: 'text', value: (this.#editor.text === '' ? '' : ' ') + marker }); } });
|
|
229
231
|
insertText(text) {
|
|
230
|
-
if (text === '')
|
|
232
|
+
if (text === '' || this.#inspected?.writable === false)
|
|
231
233
|
return;
|
|
232
234
|
const before = this.#editor.text.slice(0, this.#editor.cursor);
|
|
233
235
|
const after = this.#editor.text.slice(this.#editor.cursor);
|
|
@@ -289,6 +291,7 @@ export class LocalTui {
|
|
|
289
291
|
#resumeHintRequested = false;
|
|
290
292
|
#lastSigintTime = 0;
|
|
291
293
|
#lastEscapeTime = 0;
|
|
294
|
+
#receivedModifiedEnter = new Set();
|
|
292
295
|
#inspectEscapeFenceUntil = 0;
|
|
293
296
|
#interrupts = new Set();
|
|
294
297
|
#queueEdits = new Set();
|
|
@@ -378,6 +381,10 @@ export class LocalTui {
|
|
|
378
381
|
#compactionStartedAt;
|
|
379
382
|
#readImageInput;
|
|
380
383
|
#writtenProgramState;
|
|
384
|
+
#writtenDirectory;
|
|
385
|
+
#launchDirectory = process.cwd();
|
|
386
|
+
#writtenProgress;
|
|
387
|
+
#progressTimer;
|
|
381
388
|
#checkUpdates = true;
|
|
382
389
|
#startupChangelog = 'summary';
|
|
383
390
|
#notifications = new TerminalNotificationController();
|
|
@@ -432,6 +439,8 @@ export class LocalTui {
|
|
|
432
439
|
#expandedReasoning = new Set();
|
|
433
440
|
#expandedToolRows = new Set();
|
|
434
441
|
#processSelection;
|
|
442
|
+
#promptAnchor;
|
|
443
|
+
#transcriptScopeChanged = false;
|
|
435
444
|
#closedReasoning = new Set();
|
|
436
445
|
/**
|
|
437
446
|
* The last action's confirmation, drawn on the composer's bottom border.
|
|
@@ -501,6 +510,7 @@ export class LocalTui {
|
|
|
501
510
|
#keybindingsPath;
|
|
502
511
|
#keybindings;
|
|
503
512
|
#cwd;
|
|
513
|
+
#rootDirectory;
|
|
504
514
|
#projectRoot;
|
|
505
515
|
#home;
|
|
506
516
|
#listDir;
|
|
@@ -555,6 +565,7 @@ export class LocalTui {
|
|
|
555
565
|
this.#cwd = paths.cwd ?? fallback.cwd;
|
|
556
566
|
this.#readImagePath = paths.readImagePath ?? (path => readImageFile(path, this.#cwd));
|
|
557
567
|
const project = resolveProjectContext(this.#cwd);
|
|
568
|
+
this.#rootDirectory = { cwd: this.#cwd, branch: project.gitLabel };
|
|
558
569
|
this.#projectRoot = paths.projectRoot ?? project.root;
|
|
559
570
|
this.#home = paths.home ?? fallback.home;
|
|
560
571
|
this.#listDir = paths.listDir ?? fallback.listDir;
|
|
@@ -602,6 +613,7 @@ export class LocalTui {
|
|
|
602
613
|
// live window so it stays anchored at the bottom.
|
|
603
614
|
this.#selection.clear();
|
|
604
615
|
this.#mouseActionPress = undefined;
|
|
616
|
+
this.#inputPress = undefined;
|
|
605
617
|
this.#workspacePress = undefined;
|
|
606
618
|
this.#jumpPress = undefined;
|
|
607
619
|
this.#disclosurePress = undefined;
|
|
@@ -623,14 +635,7 @@ export class LocalTui {
|
|
|
623
635
|
const resume = () => {
|
|
624
636
|
if (this.#disposed)
|
|
625
637
|
return;
|
|
626
|
-
this.#
|
|
627
|
-
this.#writtenWindowTitle = undefined;
|
|
628
|
-
this.#writtenProgramState = undefined;
|
|
629
|
-
this.#programQuerySent = false;
|
|
630
|
-
this.#programSupported = false;
|
|
631
|
-
this.#syncWindowTitle();
|
|
632
|
-
this.#session.takeBack();
|
|
633
|
-
this.#renderer.reset();
|
|
638
|
+
this.#takeBackTerminal();
|
|
634
639
|
this.#render();
|
|
635
640
|
};
|
|
636
641
|
process.on('SIGCONT', resume);
|
|
@@ -652,23 +657,38 @@ export class LocalTui {
|
|
|
652
657
|
this.#syncWindowTitle();
|
|
653
658
|
}
|
|
654
659
|
/** Follow the viewed Session without replacing its transcript or native history. */
|
|
655
|
-
setWorkingDirectory(cwd) {
|
|
660
|
+
setWorkingDirectory(cwd, options = {}) {
|
|
661
|
+
const root = options.root ?? this.#inspected === undefined;
|
|
662
|
+
const project = resolveProjectContext(cwd);
|
|
663
|
+
if (root) {
|
|
664
|
+
this.#rootDirectory = { cwd, branch: project.gitLabel };
|
|
665
|
+
this.#syncWindowTitle();
|
|
666
|
+
if (this.#inspected !== undefined)
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
656
669
|
if (cwd === this.#cwd && cwd === this.#projectRoot)
|
|
657
670
|
return;
|
|
658
|
-
this.#cwd
|
|
671
|
+
this.#setViewedDirectory(cwd);
|
|
672
|
+
this.#syncWindowTitle();
|
|
673
|
+
if (this.#tty)
|
|
674
|
+
this.#render();
|
|
675
|
+
}
|
|
676
|
+
#setViewedDirectory(cwd) {
|
|
659
677
|
const project = resolveProjectContext(cwd);
|
|
678
|
+
this.#cwd = cwd;
|
|
660
679
|
this.#projectRoot = cwd;
|
|
661
680
|
this.#pwd = shortenPath(cwd);
|
|
662
681
|
this.#branch = project.gitLabel;
|
|
663
682
|
this.#refreshAutocomplete();
|
|
664
|
-
this.#syncWindowTitle();
|
|
665
|
-
if (this.#tty)
|
|
666
|
-
this.#render();
|
|
667
683
|
}
|
|
668
684
|
/** Refresh Git metadata without changing the Session's effective directory. */
|
|
669
685
|
#refreshProjectContext() {
|
|
670
686
|
const branch = refreshProjectContext(this.#cwd).gitLabel;
|
|
671
|
-
|
|
687
|
+
const rootBranch = this.#rootDirectory.cwd === this.#cwd ? branch : refreshProjectContext(this.#rootDirectory.cwd).gitLabel;
|
|
688
|
+
const rootChanged = rootBranch !== this.#rootDirectory.branch;
|
|
689
|
+
if (rootChanged)
|
|
690
|
+
this.#rootDirectory = { ...this.#rootDirectory, branch: rootBranch };
|
|
691
|
+
if (branch === this.#branch && !rootChanged)
|
|
672
692
|
return;
|
|
673
693
|
this.#branch = branch;
|
|
674
694
|
this.#syncWindowTitle();
|
|
@@ -705,6 +725,8 @@ export class LocalTui {
|
|
|
705
725
|
else if (event.type === 'session/end-seed')
|
|
706
726
|
this.#directInputIds.clear();
|
|
707
727
|
this.#state = applyEvent(this.#state, event, presentation, workspace);
|
|
728
|
+
if (event.type === 'turn/start' && this.#herdr.programStatus.state !== 'blocked')
|
|
729
|
+
this.#notificationQueue.clear();
|
|
708
730
|
if (claimedHumanInput && this.#tty && event.type === 'agent/inbox/spliced') {
|
|
709
731
|
this.#showTransientStatus(event.data.target === 'next-step' ? 'Steer taken for the next step' : 'Queue taken for the next turn');
|
|
710
732
|
}
|
|
@@ -843,6 +865,7 @@ export class LocalTui {
|
|
|
843
865
|
const current = this.#subagents?.agents.find(agent => agent.id === inspected.id);
|
|
844
866
|
if (current !== undefined) {
|
|
845
867
|
this.#inspected = {
|
|
868
|
+
...inspected,
|
|
846
869
|
id: current.id,
|
|
847
870
|
label: current.label,
|
|
848
871
|
phase: current.phase,
|
|
@@ -861,15 +884,25 @@ export class LocalTui {
|
|
|
861
884
|
setInspectedSubagent(inspected) {
|
|
862
885
|
if (this.#inspected?.id === inspected?.id && this.#inspected?.label === inspected?.label
|
|
863
886
|
&& this.#inspected?.phase === inspected?.phase && this.#inspected?.needsApproval === inspected?.needsApproval && this.#inspected?.mode === inspected?.mode
|
|
864
|
-
&& this.#inspected?.writable === inspected?.writable
|
|
887
|
+
&& this.#inspected?.writable === inspected?.writable && this.#inspected?.model === inspected?.model
|
|
888
|
+
&& this.#inspected?.reasoningEffort === inspected?.reasoningEffort && this.#inspected?.stats === inspected?.stats
|
|
889
|
+
&& this.#inspected?.controls === inspected?.controls)
|
|
865
890
|
return;
|
|
866
891
|
if (this.#inspected?.id !== inspected?.id) {
|
|
892
|
+
this.#transcriptScopeChanged = true;
|
|
867
893
|
this.#workspacePress = undefined;
|
|
868
894
|
this.#fileReviewAbort?.abort();
|
|
869
895
|
this.#closeFileBrowser();
|
|
896
|
+
this.#transcriptSearch = null;
|
|
897
|
+
this.#followTail();
|
|
870
898
|
}
|
|
871
|
-
this.#
|
|
899
|
+
const leaving = this.#inspected !== undefined && inspected === undefined;
|
|
872
900
|
this.#inspected = inspected === undefined ? undefined : { ...inspected };
|
|
901
|
+
if (leaving) {
|
|
902
|
+
this.#setViewedDirectory(this.#rootDirectory.cwd);
|
|
903
|
+
this.#syncWindowTitle();
|
|
904
|
+
}
|
|
905
|
+
this.#switchDraft(inspected?.id ?? this.#sessionId);
|
|
873
906
|
this.#drainPendingInput();
|
|
874
907
|
if (inspected !== undefined) {
|
|
875
908
|
this.#inspectEscapeFenceUntil = 0;
|
|
@@ -903,7 +936,7 @@ export class LocalTui {
|
|
|
903
936
|
this.#settings = null;
|
|
904
937
|
this.#search = null;
|
|
905
938
|
this.#ac = null;
|
|
906
|
-
const browser = new FileBrowser(request, () => { if (!this.#disposed)
|
|
939
|
+
const browser = new FileBrowser({ ...request, canQuote: request.canQuote && (this.#inspected === undefined || this.#inspected.writable) }, () => { if (!this.#disposed)
|
|
907
940
|
this.#render(); }, () => {
|
|
908
941
|
if (this.#fileBrowser === browser)
|
|
909
942
|
this.#fileBrowser = null;
|
|
@@ -1251,7 +1284,7 @@ export class LocalTui {
|
|
|
1251
1284
|
this.#pasteBurst.clear();
|
|
1252
1285
|
if (request.notify !== false) {
|
|
1253
1286
|
this.#emitNotification(this.#notifications.humanPrompt());
|
|
1254
|
-
this.#herdr.prompt(request.title);
|
|
1287
|
+
this.#herdr.prompt(request.title, request.attentionKind);
|
|
1255
1288
|
this.#syncWindowTitle();
|
|
1256
1289
|
}
|
|
1257
1290
|
if (this.#tty && request.documentImage !== undefined && this.#imageProtocol !== undefined)
|
|
@@ -1371,10 +1404,23 @@ export class LocalTui {
|
|
|
1371
1404
|
// A notice does not turn a blank session into a document that /new needs
|
|
1372
1405
|
// to separate. Refreshes are the same document.
|
|
1373
1406
|
const boundary = reason === 'open' || (reason === 'new' && this.#state.blocks.some(block => block.kind !== 'notice'));
|
|
1374
|
-
const
|
|
1407
|
+
const refreshing = reason === 'refresh' && !this.#transcriptScopeChanged;
|
|
1408
|
+
this.#transcriptScopeChanged = false;
|
|
1409
|
+
const seam = refreshing && this.#state.blocks[0]?.kind === 'notice' && this.#state.blocks[0].boundary === true
|
|
1410
|
+
? this.#state.blocks[0] : undefined;
|
|
1411
|
+
const blocks = boundary ? [transcriptSeam('session-opened'), ...state.blocks] : seam === undefined ? state.blocks : [seam, ...state.blocks];
|
|
1375
1412
|
this.#state = { ...state, status, compactCommandId: undefined, blocks };
|
|
1376
1413
|
this.#plain.resetPrinted();
|
|
1377
|
-
this.#
|
|
1414
|
+
this.#promptAnchor = undefined;
|
|
1415
|
+
if (!refreshing) {
|
|
1416
|
+
this.#transcriptSearch = null;
|
|
1417
|
+
this.#followTail();
|
|
1418
|
+
}
|
|
1419
|
+
else if (this.#transcriptSearch !== null) {
|
|
1420
|
+
const match = this.#searchOccurrences()[this.#transcriptSearch.focus];
|
|
1421
|
+
if (match !== undefined)
|
|
1422
|
+
this.#focusSearchMatch(match);
|
|
1423
|
+
}
|
|
1378
1424
|
this.#deferInitialRender = false;
|
|
1379
1425
|
this.#renderer.startEpoch({ replay: reason === 'refresh' ? 'viewport' : status === 'idle' ? 'full' : 'pinned' });
|
|
1380
1426
|
if (this.#tty)
|
|
@@ -1384,9 +1430,12 @@ export class LocalTui {
|
|
|
1384
1430
|
}
|
|
1385
1431
|
setSession(info) {
|
|
1386
1432
|
if (info.id !== this.#sessionId) {
|
|
1433
|
+
this.#transcriptScopeChanged = true;
|
|
1387
1434
|
this.#workspacePress = undefined;
|
|
1388
1435
|
this.#fileReviewAbort?.abort();
|
|
1389
1436
|
this.#closeFileBrowser();
|
|
1437
|
+
this.#notificationQueue.clear();
|
|
1438
|
+
this.#notifications.reset();
|
|
1390
1439
|
}
|
|
1391
1440
|
this.#switchDraft(this.#inspected?.id ?? info.id);
|
|
1392
1441
|
this.#sessionId = info.id;
|
|
@@ -1413,11 +1462,13 @@ export class LocalTui {
|
|
|
1413
1462
|
this.#render();
|
|
1414
1463
|
}
|
|
1415
1464
|
/** Only the foreground terminal owner emits title and program-state sequences. */
|
|
1416
|
-
#syncWindowTitle() {
|
|
1417
|
-
if (!this.#tty || this.#disposed)
|
|
1465
|
+
#syncWindowTitle(notify = false) {
|
|
1466
|
+
if (!this.#tty || this.#disposed || this.#session.handedOff)
|
|
1418
1467
|
return;
|
|
1419
1468
|
const status = this.#herdr.programStatus;
|
|
1420
|
-
|
|
1469
|
+
this.#writeWorkingDirectory(this.#cwd);
|
|
1470
|
+
this.#syncProgress(status.state);
|
|
1471
|
+
const title = terminalTitle(this.#terminalTitle, status, this.#sessionTitle, this.#rootDirectory.cwd, this.#rootDirectory.branch);
|
|
1421
1472
|
if (title !== this.#writtenWindowTitle && !(this.#terminalTitle === 'off' && this.#writtenWindowTitle === undefined)) {
|
|
1422
1473
|
this.#writtenWindowTitle = title;
|
|
1423
1474
|
this.#term.output.write(`\x1b]2;${title}\x07`);
|
|
@@ -1426,20 +1477,78 @@ export class LocalTui {
|
|
|
1426
1477
|
this.#programQuerySent = true;
|
|
1427
1478
|
this.#term.output.write(PROGRAM_STATUS_QUERY);
|
|
1428
1479
|
}
|
|
1429
|
-
|
|
1430
|
-
if (!enabled) {
|
|
1480
|
+
if (!this.#reportsProgramState) {
|
|
1431
1481
|
if (this.#writtenProgramState !== undefined)
|
|
1432
1482
|
this.#term.output.write(programStatusSequence('clear'));
|
|
1433
1483
|
this.#writtenProgramState = undefined;
|
|
1434
1484
|
return;
|
|
1435
1485
|
}
|
|
1436
1486
|
// Only the session name is exported as metadata; prompt bodies and errors stay in the TUI.
|
|
1437
|
-
const sequence = programStatusSequence({ state: status.state,
|
|
1487
|
+
const sequence = programStatusSequence({ state: status.state,
|
|
1488
|
+
...(status.kind === undefined ? {} : { kind: status.kind }),
|
|
1489
|
+
...(this.#sessionTitle === undefined ? {} : { message: this.#sessionTitle }),
|
|
1490
|
+
});
|
|
1491
|
+
// Supporting terminals can turn these states into desktop notifications.
|
|
1492
|
+
// New attention reports use the same coalescing and focus gate as OSC 9.
|
|
1493
|
+
if (programStatusNeedsAttention(status.state)
|
|
1494
|
+
&& (this.#notificationPolicy === 'off' || !notify && sequence !== this.#writtenProgramState)) {
|
|
1495
|
+
if (this.#writtenProgramState !== undefined)
|
|
1496
|
+
this.#term.output.write(programStatusSequence('clear'));
|
|
1497
|
+
this.#writtenProgramState = undefined;
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1438
1500
|
if (sequence === this.#writtenProgramState)
|
|
1439
1501
|
return;
|
|
1440
1502
|
this.#writtenProgramState = sequence;
|
|
1441
1503
|
this.#term.output.write(sequence);
|
|
1442
1504
|
}
|
|
1505
|
+
get #reportsProgramState() {
|
|
1506
|
+
return this.#terminalState === 'on' || this.#terminalState === 'auto' && this.#programSupported;
|
|
1507
|
+
}
|
|
1508
|
+
#writeWorkingDirectory(cwd) {
|
|
1509
|
+
const sequence = workingDirectorySequence(cwd);
|
|
1510
|
+
if (sequence === this.#writtenDirectory)
|
|
1511
|
+
return;
|
|
1512
|
+
this.#term.output.write(sequence);
|
|
1513
|
+
this.#writtenDirectory = sequence;
|
|
1514
|
+
}
|
|
1515
|
+
#syncProgress(state) {
|
|
1516
|
+
const sequence = this.#terminalState === 'off' ? terminalProgressSequence('clear') : terminalProgressSequence(state);
|
|
1517
|
+
const active = sequence !== terminalProgressSequence('clear');
|
|
1518
|
+
if (sequence !== this.#writtenProgress && (active || this.#writtenProgress !== undefined))
|
|
1519
|
+
this.#term.output.write(sequence);
|
|
1520
|
+
this.#writtenProgress = active ? sequence : undefined;
|
|
1521
|
+
if (this.#progressTimer !== undefined && (state !== 'working' || this.#terminalState === 'off')) {
|
|
1522
|
+
clearInterval(this.#progressTimer);
|
|
1523
|
+
this.#progressTimer = undefined;
|
|
1524
|
+
}
|
|
1525
|
+
// Some terminals expire indeterminate progress without a heartbeat.
|
|
1526
|
+
if (state === 'working' && this.#terminalState !== 'off' && this.#progressTimer === undefined) {
|
|
1527
|
+
this.#progressTimer = setInterval(() => {
|
|
1528
|
+
if (!this.#disposed && !this.#session.handedOff && this.#writtenProgress !== undefined)
|
|
1529
|
+
this.#term.output.write(this.#writtenProgress);
|
|
1530
|
+
}, 1000);
|
|
1531
|
+
this.#progressTimer.unref?.();
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
/** Give shell/editor ownership back without retaining omdsh metadata. */
|
|
1535
|
+
#releaseTerminalMetadata() {
|
|
1536
|
+
if (this.#progressTimer !== undefined)
|
|
1537
|
+
clearInterval(this.#progressTimer);
|
|
1538
|
+
this.#progressTimer = undefined;
|
|
1539
|
+
if (this.#writtenProgress !== undefined)
|
|
1540
|
+
this.#term.output.write(terminalProgressSequence('clear'));
|
|
1541
|
+
this.#writtenProgress = undefined;
|
|
1542
|
+
if (this.#writtenProgramState !== undefined)
|
|
1543
|
+
this.#term.output.write(programStatusSequence('clear'));
|
|
1544
|
+
this.#writtenProgramState = undefined;
|
|
1545
|
+
if (this.#writtenWindowTitle !== undefined && this.#writtenWindowTitle !== '')
|
|
1546
|
+
this.#term.output.write('\x1b]2;\x07');
|
|
1547
|
+
this.#writtenWindowTitle = undefined;
|
|
1548
|
+
if (this.#writtenDirectory !== undefined)
|
|
1549
|
+
this.#writeWorkingDirectory(this.#launchDirectory);
|
|
1550
|
+
this.#writtenDirectory = undefined;
|
|
1551
|
+
}
|
|
1443
1552
|
/**
|
|
1444
1553
|
* Re-derive the color depth from the active color preference. Every path
|
|
1445
1554
|
* that changes `#colors` calls this so `/settings` takes effect immediately.
|
|
@@ -1835,7 +1944,9 @@ export class LocalTui {
|
|
|
1835
1944
|
this.#renderer.finish();
|
|
1836
1945
|
}
|
|
1837
1946
|
catch { /* best effort while exiting */ }
|
|
1947
|
+
this.#releaseTerminalMetadata();
|
|
1838
1948
|
this.#session.release();
|
|
1949
|
+
this.#term.input.setRawMode?.(false);
|
|
1839
1950
|
this.#term.output.write('\x1b[?25h');
|
|
1840
1951
|
}
|
|
1841
1952
|
dispose() {
|
|
@@ -1898,14 +2009,7 @@ export class LocalTui {
|
|
|
1898
2009
|
// prompt does not overwrite the transcript. Disable bracketed paste
|
|
1899
2010
|
// and restore the cursor.
|
|
1900
2011
|
this.#renderer.finish();
|
|
1901
|
-
|
|
1902
|
-
if (this.#writtenWindowTitle !== undefined && this.#writtenWindowTitle !== '') {
|
|
1903
|
-
this.#term.output.write('\x1b]2;\x07');
|
|
1904
|
-
this.#writtenWindowTitle = undefined;
|
|
1905
|
-
}
|
|
1906
|
-
if (this.#writtenProgramState !== undefined)
|
|
1907
|
-
this.#term.output.write(programStatusSequence('clear'));
|
|
1908
|
-
this.#writtenProgramState = undefined;
|
|
2012
|
+
this.#releaseTerminalMetadata();
|
|
1909
2013
|
this.#offExit?.();
|
|
1910
2014
|
this.#offExit = undefined;
|
|
1911
2015
|
this.#session.dispose();
|
|
@@ -1949,6 +2053,19 @@ export class LocalTui {
|
|
|
1949
2053
|
refresh() {
|
|
1950
2054
|
this.#render();
|
|
1951
2055
|
}
|
|
2056
|
+
searchTranscript(query) {
|
|
2057
|
+
if (!this.#tty || this.#disposed || query.trim() === '')
|
|
2058
|
+
return;
|
|
2059
|
+
// Commit the resumed document before moving its live viewport into history.
|
|
2060
|
+
this.#render();
|
|
2061
|
+
this.#transcriptSearch = { query, editing: false, focus: 0 };
|
|
2062
|
+
this.#promptAnchor = undefined;
|
|
2063
|
+
this.#ac = null;
|
|
2064
|
+
const match = this.#searchOccurrences()[0];
|
|
2065
|
+
if (match !== undefined)
|
|
2066
|
+
this.#focusSearchMatch(match);
|
|
2067
|
+
this.#render();
|
|
2068
|
+
}
|
|
1952
2069
|
#busy() {
|
|
1953
2070
|
if (this.#state.status !== 'idle')
|
|
1954
2071
|
return true;
|
|
@@ -2174,6 +2291,13 @@ export class LocalTui {
|
|
|
2174
2291
|
if (this.#probeGate)
|
|
2175
2292
|
return;
|
|
2176
2293
|
const width = this.#term.width();
|
|
2294
|
+
const alternateSendKey = keysForAction(Object.fromEntries(Object.entries(this.#keybindings).filter(([key]) => {
|
|
2295
|
+
if (key === 'super+enter')
|
|
2296
|
+
return this.#receivedModifiedEnter.has(key);
|
|
2297
|
+
if (key === 'ctrl+enter')
|
|
2298
|
+
return this.#session.keyboardProtocol === 'kitty' || this.#receivedModifiedEnter.has(key);
|
|
2299
|
+
return true;
|
|
2300
|
+
})), 'alternate-send');
|
|
2177
2301
|
const renderState = this.#reveal === undefined
|
|
2178
2302
|
? this.#state
|
|
2179
2303
|
: revealStreamingAssistant(this.#state, this.#reveal.revealed);
|
|
@@ -2211,9 +2335,9 @@ export class LocalTui {
|
|
|
2211
2335
|
themeName: this.#themeName,
|
|
2212
2336
|
terminal: this.#terminalColors,
|
|
2213
2337
|
...(workingLabel === undefined ? {} : { workingLabel }),
|
|
2338
|
+
...(this.#selection.active ? { selectionActions: this.#canQuoteSelection() ? 'copy-quote' : 'copy' } : {}),
|
|
2214
2339
|
...(this.#transientStatus !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), this.#transientStatus) }
|
|
2215
|
-
: this.#selection.active ? {
|
|
2216
|
-
: this.#processSelection !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), 'Process · ↑↓ select · Enter toggle · {key} actions · Home title · Esc return', { key: keysForAction(this.#keybindings, 'tool-actions') }) } : {}),
|
|
2340
|
+
: !this.#selection.active && this.#processSelection !== undefined ? { transientStatus: uiText(resolveUiLanguage(this.#uiLanguage), 'Process · ↑↓ select · Enter toggle · {key} actions · Home title · Esc return', { key: keysForAction(this.#keybindings, 'tool-actions') }) } : {}),
|
|
2217
2341
|
scrollStart: this.#follow ? Number.POSITIVE_INFINITY : this.#scrollStart,
|
|
2218
2342
|
...(this.#focusBlock === undefined ? {} : { focusBlock: this.#focusBlock, focusBlockEdge: this.#focusBlockEdge ?? 'start', ...(this.#focusBlockOffset === undefined ? {} : { focusBlockOffset: this.#focusBlockOffset }) }),
|
|
2219
2343
|
...(this.#transcriptSearch === null ? {} : {
|
|
@@ -2232,6 +2356,7 @@ export class LocalTui {
|
|
|
2232
2356
|
toolDetailsKey: keysForAction(this.#keybindings, 'toggle-tool-details'),
|
|
2233
2357
|
turnDetailsKey: keysForAction(this.#keybindings, 'toggle-tools'),
|
|
2234
2358
|
busyEnter: this.#queueEditNewer !== null || this.#queueEditPending ? 'queue' : this.#busyEnter,
|
|
2359
|
+
...(this.#queueEditNewer !== null || this.#queueEditPending || alternateSendKey === 'Disabled' ? {} : { alternateSendKey }),
|
|
2235
2360
|
steerKey: keysForAction(this.#keybindings, 'steer-turn'),
|
|
2236
2361
|
queueKey: keysForAction(this.#keybindings, 'follow-up'),
|
|
2237
2362
|
openedGroups: this.#openedGroups,
|
|
@@ -2267,7 +2392,8 @@ export class LocalTui {
|
|
|
2267
2392
|
this.#syncScroll(frame.transcript);
|
|
2268
2393
|
this.#jumpToLatest = frame.jumpToLatest;
|
|
2269
2394
|
this.#fileActionFrame = frame.transcript === undefined || frame.transientSurface === 'overlay' ? undefined : frame;
|
|
2270
|
-
this.#
|
|
2395
|
+
this.#inputFrame = frame.inputRows === undefined ? undefined : frame;
|
|
2396
|
+
this.#setMouseTracking(this.#inputFrame !== undefined || this.#fileBrowser !== null || this.#trajectory !== null || this.#documentMouseEnabled() || this.#promptChoices.length > 0 || this.#promptActions.length > 0 || (frame.transcript !== undefined && frame.transientSurface !== 'overlay'
|
|
2271
2397
|
&& (this.#inspecting || this.#maxStart > 0 || (frame.transcript.fileRegions?.length ?? 0) > 0 || (frame.transcript.disclosures?.length ?? 0) > 0)));
|
|
2272
2398
|
// A run opening or folding reshapes rows above the screen in place; the
|
|
2273
2399
|
// renderer's frozen boundary is a row index and has to be told, or a fold
|
|
@@ -2313,6 +2439,7 @@ export class LocalTui {
|
|
|
2313
2439
|
}
|
|
2314
2440
|
#scrollBy(delta) {
|
|
2315
2441
|
this.#workspacePress = undefined;
|
|
2442
|
+
this.#promptAnchor = undefined;
|
|
2316
2443
|
if (this.#follow && (delta > 0 || this.#maxStart === 0))
|
|
2317
2444
|
return;
|
|
2318
2445
|
if (delta === 0 && this.#maxStart === 0)
|
|
@@ -2336,6 +2463,7 @@ export class LocalTui {
|
|
|
2336
2463
|
this.#render();
|
|
2337
2464
|
}
|
|
2338
2465
|
#followTail() {
|
|
2466
|
+
this.#promptAnchor = undefined;
|
|
2339
2467
|
this.#follow = true;
|
|
2340
2468
|
this.#scrollStart = this.#maxStart;
|
|
2341
2469
|
this.#endInspection();
|
|
@@ -2390,20 +2518,31 @@ export class LocalTui {
|
|
|
2390
2518
|
/** Give every terminal mode back before the terminal is lent out (Ctrl+Z, external editor). */
|
|
2391
2519
|
#handOffTerminal() {
|
|
2392
2520
|
this.#setMouseTracking(false);
|
|
2521
|
+
this.#releaseTerminalMetadata();
|
|
2393
2522
|
this.#session.handOff();
|
|
2523
|
+
this.#term.input.setRawMode?.(false);
|
|
2524
|
+
}
|
|
2525
|
+
#takeBackTerminal() {
|
|
2526
|
+
this.#session.takeBack();
|
|
2527
|
+
this.#term.input.setRawMode?.(true);
|
|
2528
|
+
this.#terminalFocused = undefined;
|
|
2529
|
+
this.#writtenWindowTitle = undefined;
|
|
2530
|
+
this.#writtenProgramState = undefined;
|
|
2531
|
+
this.#writtenDirectory = undefined;
|
|
2532
|
+
this.#programQuerySent = false;
|
|
2533
|
+
this.#programSupported = false;
|
|
2534
|
+
this.#syncWindowTitle();
|
|
2535
|
+
this.#renderer.reset();
|
|
2394
2536
|
}
|
|
2395
2537
|
/** Run `run` with the terminal handed off; take it back and repaint afterwards. */
|
|
2396
2538
|
#handTerminal(run) {
|
|
2397
2539
|
this.#handOffTerminal();
|
|
2398
|
-
this.#term.input.setRawMode?.(false);
|
|
2399
2540
|
try {
|
|
2400
2541
|
return run();
|
|
2401
2542
|
}
|
|
2402
2543
|
finally {
|
|
2403
2544
|
if (!this.#disposed) {
|
|
2404
|
-
this.#
|
|
2405
|
-
this.#session.takeBack();
|
|
2406
|
-
this.#renderer.reset();
|
|
2545
|
+
this.#takeBackTerminal();
|
|
2407
2546
|
}
|
|
2408
2547
|
}
|
|
2409
2548
|
}
|
|
@@ -2412,6 +2551,7 @@ export class LocalTui {
|
|
|
2412
2551
|
* or go back to the live tail once nothing is open any more.
|
|
2413
2552
|
*/
|
|
2414
2553
|
#inspect(target) {
|
|
2554
|
+
this.#promptAnchor = undefined;
|
|
2415
2555
|
const open = this.#toolsExpanded || this.#openedGroups.size > 0 || this.#expandedOutputs.size > 0;
|
|
2416
2556
|
if (!open) {
|
|
2417
2557
|
this.#inspecting = false;
|
|
@@ -2747,6 +2887,40 @@ export class LocalTui {
|
|
|
2747
2887
|
this.#handleTrajectoryMouse(event);
|
|
2748
2888
|
return;
|
|
2749
2889
|
}
|
|
2890
|
+
const frameRow = this.#renderer.frameRowAt(event.row);
|
|
2891
|
+
const inputRow = this.#inputFrame?.inputRows?.find(row => row.row === frameRow);
|
|
2892
|
+
const completionRow = this.#inputFrame?.autocompleteRows?.find(row => row.row === frameRow);
|
|
2893
|
+
if (event.button === 'left') {
|
|
2894
|
+
const pressed = this.#inputPress;
|
|
2895
|
+
if (event.action === 'press' && !event.shift && (inputRow !== undefined || completionRow !== undefined)) {
|
|
2896
|
+
const completion = completionRow === undefined ? undefined : this.#ac?.items[completionRow.index];
|
|
2897
|
+
this.#inputPress = { row: event.row, column: event.column, text: this.#editor.text,
|
|
2898
|
+
...(inputRow === undefined ? {} : { cursor: inputRow.start + indexAtCell(inputRow.text, Math.max(0, event.column - inputRow.column)) }),
|
|
2899
|
+
...(completion === undefined ? {} : { completion }) };
|
|
2900
|
+
}
|
|
2901
|
+
else if (event.action === 'drag')
|
|
2902
|
+
this.#inputPress = undefined;
|
|
2903
|
+
else if (event.action === 'release') {
|
|
2904
|
+
this.#inputPress = undefined;
|
|
2905
|
+
if (pressed !== undefined && !event.shift && pressed.row === event.row && pressed.column === event.column && pressed.text === this.#editor.text) {
|
|
2906
|
+
this.#selection.clear();
|
|
2907
|
+
this.#processSelection = undefined;
|
|
2908
|
+
this.#subagentSelection = undefined;
|
|
2909
|
+
this.#transcriptSearch = null;
|
|
2910
|
+
if (pressed.completion !== undefined && this.#ac !== null && completionRow !== undefined && this.#ac.items[completionRow.index] === pressed.completion) {
|
|
2911
|
+
this.#ac.selected = completionRow.index;
|
|
2912
|
+
this.#applySelectedCompletion();
|
|
2913
|
+
}
|
|
2914
|
+
else if (pressed.cursor !== undefined && inputRow !== undefined
|
|
2915
|
+
&& pressed.cursor === inputRow.start + indexAtCell(inputRow.text, Math.max(0, event.column - inputRow.column))) {
|
|
2916
|
+
this.#editor.setCursor(pressed.cursor);
|
|
2917
|
+
this.#refreshAutocomplete();
|
|
2918
|
+
}
|
|
2919
|
+
this.#render();
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2750
2924
|
const promptAction = this.#promptActions.find(action => event.row === action.row && event.column >= action.column && event.column < action.column + action.width);
|
|
2751
2925
|
if (this.#promptActionPress !== undefined) {
|
|
2752
2926
|
const pressed = this.#promptActionPress;
|
|
@@ -2819,7 +2993,7 @@ export class LocalTui {
|
|
|
2819
2993
|
this.#workspacePress = undefined;
|
|
2820
2994
|
if (event.button === 'left' && !event.shift && target?.key === pressedAction.key && target.row === pressedAction.row && target.column === pressedAction.column) {
|
|
2821
2995
|
if (target.action === 'copy')
|
|
2822
|
-
void this.#copySelection(
|
|
2996
|
+
void this.#copySelection();
|
|
2823
2997
|
else if (target.action === 'quote')
|
|
2824
2998
|
this.#quoteSelection();
|
|
2825
2999
|
else {
|
|
@@ -2851,7 +3025,7 @@ export class LocalTui {
|
|
|
2851
3025
|
this.#disclosurePress = undefined;
|
|
2852
3026
|
if (event.action === 'press') {
|
|
2853
3027
|
if (this.#selection.active)
|
|
2854
|
-
void this.#copySelection(
|
|
3028
|
+
void this.#copySelection();
|
|
2855
3029
|
else {
|
|
2856
3030
|
const region = this.#fileRegionAt(event.row);
|
|
2857
3031
|
if (region !== undefined) {
|
|
@@ -2926,7 +3100,7 @@ export class LocalTui {
|
|
|
2926
3100
|
#handleTrajectoryMouse(event) {
|
|
2927
3101
|
if (event.button === 'right') {
|
|
2928
3102
|
if (event.action === 'press' && this.#selection.active)
|
|
2929
|
-
void this.#copySelection(
|
|
3103
|
+
void this.#copySelection();
|
|
2930
3104
|
return;
|
|
2931
3105
|
}
|
|
2932
3106
|
if (event.action === 'press') {
|
|
@@ -2998,7 +3172,7 @@ export class LocalTui {
|
|
|
2998
3172
|
this.#copyTask = task.catch(() => { });
|
|
2999
3173
|
return task;
|
|
3000
3174
|
}
|
|
3001
|
-
async #copySelection(
|
|
3175
|
+
async #copySelection() {
|
|
3002
3176
|
if (!this.#selection.valid(this.#renderer.viewport())) {
|
|
3003
3177
|
this.#selection.clear();
|
|
3004
3178
|
return;
|
|
@@ -3008,28 +3182,30 @@ export class LocalTui {
|
|
|
3008
3182
|
return;
|
|
3009
3183
|
try {
|
|
3010
3184
|
const delivery = await this.#deliverCopy(text);
|
|
3011
|
-
if (
|
|
3185
|
+
if (!this.#disposed && this.#selection.text() === text) {
|
|
3012
3186
|
this.#showTransientStatus(delivery === 'requested' ? 'Selection sent to terminal clipboard' : 'Copied selection');
|
|
3013
|
-
this.#render();
|
|
3014
3187
|
}
|
|
3015
3188
|
}
|
|
3016
3189
|
catch {
|
|
3017
|
-
if (!this.#disposed)
|
|
3190
|
+
if (!this.#disposed && this.#selection.text() === text)
|
|
3018
3191
|
this.#showTransientStatus('Copy failed');
|
|
3019
|
-
this.#render();
|
|
3020
|
-
}
|
|
3021
3192
|
}
|
|
3022
3193
|
}
|
|
3023
3194
|
#canQuoteSelection() {
|
|
3024
|
-
return this.#prompt === null && this.#settings === null && this.#trajectory === null && this.#fileBrowser === null && this.#search === null
|
|
3195
|
+
return this.#prompt === null && this.#settings === null && this.#trajectory === null && (this.#fileBrowser === null || this.#fileBrowser.state.canQuote && !this.#fileBrowserOwnsInput()) && this.#search === null
|
|
3025
3196
|
&& this.#transcriptSearch === null && (this.#inspected === undefined || this.#inspected.writable === true);
|
|
3026
3197
|
}
|
|
3198
|
+
#fileBrowserOwnsInput() {
|
|
3199
|
+
const state = this.#fileBrowser?.state;
|
|
3200
|
+
return state !== undefined && (state.editing !== undefined || state.help || state.menu !== undefined);
|
|
3201
|
+
}
|
|
3027
3202
|
#quoteSelection() {
|
|
3028
3203
|
if (!this.#canQuoteSelection())
|
|
3029
3204
|
return;
|
|
3030
3205
|
const text = this.#selection.valid(this.#renderer.viewport()) ? this.#selection.text() : undefined;
|
|
3031
3206
|
this.#selection.clear();
|
|
3032
3207
|
if (text !== undefined) {
|
|
3208
|
+
this.#closeFileBrowser();
|
|
3033
3209
|
this.#appendQuote(text.split('\n').map(line => '> ' + line).join('\n'));
|
|
3034
3210
|
}
|
|
3035
3211
|
this.#render();
|
|
@@ -3152,6 +3328,9 @@ export class LocalTui {
|
|
|
3152
3328
|
}
|
|
3153
3329
|
if (this.#session.handle(event))
|
|
3154
3330
|
return;
|
|
3331
|
+
if (!this.#paste && event.type === 'key' && (event.id === 'ctrl+enter' || event.id === 'super+enter')) {
|
|
3332
|
+
this.#receivedModifiedEnter.add(event.id);
|
|
3333
|
+
}
|
|
3155
3334
|
if (event.type === 'focus') {
|
|
3156
3335
|
this.#terminalFocused = event.focused;
|
|
3157
3336
|
if (event.focused && this.#notificationPolicy === 'unfocused')
|
|
@@ -3168,18 +3347,23 @@ export class LocalTui {
|
|
|
3168
3347
|
this.#disclosurePress = undefined;
|
|
3169
3348
|
this.#mouseActionPress = undefined;
|
|
3170
3349
|
this.#workspacePress = undefined;
|
|
3350
|
+
this.#inputPress = undefined;
|
|
3171
3351
|
if (this.#keyTest !== undefined && this.#prompt?.request.signal === this.#keyTest.signal
|
|
3172
3352
|
&& (event.type === 'text' || event.type === 'paste-start' || event.type === 'paste-end' || event.type === 'key' && !['escape', 'ctrl+c'].includes(event.id))) {
|
|
3173
3353
|
this.#keyTest.observe(event);
|
|
3174
3354
|
return;
|
|
3175
3355
|
}
|
|
3176
3356
|
if (this.#selection.active) {
|
|
3177
|
-
if (event.type === 'key' && this.#boundAction(event.id) === 'quote-selection') {
|
|
3357
|
+
if (event.type === 'key' && this.#boundAction(event.id) === 'quote-selection' && this.#canQuoteSelection()) {
|
|
3178
3358
|
this.#quoteSelection();
|
|
3179
3359
|
return;
|
|
3180
3360
|
}
|
|
3181
|
-
if (event.type === '
|
|
3182
|
-
|
|
3361
|
+
if (this.#fileBrowser !== null && event.type === 'text' && event.value.toLowerCase() === 'q' && this.#canQuoteSelection()) {
|
|
3362
|
+
this.#quoteSelection();
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
if (event.type === 'key' && (event.id === 'super+c' || event.id === 'enter' && !this.#fileBrowserOwnsInput() || this.#boundAction(event.id) === 'copy-prompt')) {
|
|
3366
|
+
void this.#copySelection();
|
|
3183
3367
|
return;
|
|
3184
3368
|
}
|
|
3185
3369
|
this.#selection.clear();
|
|
@@ -3198,6 +3382,11 @@ export class LocalTui {
|
|
|
3198
3382
|
this.#applyTrajectoryInput(event);
|
|
3199
3383
|
return;
|
|
3200
3384
|
}
|
|
3385
|
+
if (this.#ac !== null && this.#inputFrame?.autocompleteRows?.some(row => row.row === this.#renderer.frameRowAt(event.row ?? -1))) {
|
|
3386
|
+
this.#ac.selected = nextSelectableAutocompleteIndex(this.#ac.items, this.#ac.selected + (event.direction === 'up' ? -1 : 1), event.direction === 'up' ? -1 : 1);
|
|
3387
|
+
this.#render();
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3201
3390
|
this.#jumpPress = undefined;
|
|
3202
3391
|
if (this.#mouseTracking && !this.#paste && this.#pasteInFlight === 0) {
|
|
3203
3392
|
const step = wheelStep(this.#wheelGesture, Date.now(), event.direction === 'up' ? -1 : 1, !this.#wheelAccelerates);
|
|
@@ -3344,7 +3533,7 @@ export class LocalTui {
|
|
|
3344
3533
|
this.#render();
|
|
3345
3534
|
return;
|
|
3346
3535
|
}
|
|
3347
|
-
if (event.type === 'key' && (event.id === 'ctrl+-' || event.id === 'ctrl+_')
|
|
3536
|
+
if (event.type === 'key' && (event.id === 'ctrl+-' || event.id === 'ctrl+_' || event.id === 'super+z')
|
|
3348
3537
|
&& this.#clearedDraft !== undefined && this.#clearedDraft.revision === this.#editor.revision
|
|
3349
3538
|
&& !this.#hasComposerContent()) {
|
|
3350
3539
|
const cleared = this.#clearedDraft.draft;
|
|
@@ -4014,7 +4203,6 @@ export class LocalTui {
|
|
|
4014
4203
|
this.#externalEditor = prefs.editor ?? 'auto';
|
|
4015
4204
|
this.#terminalTitle = prefs.terminalTitle ?? 'status';
|
|
4016
4205
|
this.#terminalState = prefs.terminalState ?? 'auto';
|
|
4017
|
-
this.#syncWindowTitle();
|
|
4018
4206
|
this.#copyOnSelect = prefs.copyOnSelect ?? true;
|
|
4019
4207
|
this.#mathMode = prefs.mathMode ?? 'auto';
|
|
4020
4208
|
this.#mermaidMode = prefs.mermaidMode ?? 'auto';
|
|
@@ -4027,6 +4215,7 @@ export class LocalTui {
|
|
|
4027
4215
|
if (this.#notificationPolicy === 'off')
|
|
4028
4216
|
this.#notificationQueue.clear();
|
|
4029
4217
|
this.#configureNotifications();
|
|
4218
|
+
this.#syncWindowTitle();
|
|
4030
4219
|
this.#statusBar = resolveStatusBarConfig(prefs.statusBar, prefs.statusPreset);
|
|
4031
4220
|
if (options.forceToolsSync)
|
|
4032
4221
|
this.#toolsExpanded = false;
|
|
@@ -4522,7 +4711,10 @@ export class LocalTui {
|
|
|
4522
4711
|
try {
|
|
4523
4712
|
process.kill(process.pid, 'SIGTSTP');
|
|
4524
4713
|
}
|
|
4525
|
-
catch {
|
|
4714
|
+
catch {
|
|
4715
|
+
this.#takeBackTerminal();
|
|
4716
|
+
this.#render();
|
|
4717
|
+
}
|
|
4526
4718
|
}
|
|
4527
4719
|
#historyPrev() {
|
|
4528
4720
|
if (this.#images.count > 0)
|
|
@@ -4670,6 +4862,11 @@ export class LocalTui {
|
|
|
4670
4862
|
this.#queuedSubmissions.push(submission);
|
|
4671
4863
|
this.#pendingInputs.flush(this.#pendingScope());
|
|
4672
4864
|
}
|
|
4865
|
+
#resolveDelivery(delivery) {
|
|
4866
|
+
if (delivery === 'alternate')
|
|
4867
|
+
return this.#busyEnter === 'queue' ? 'steer' : 'queue';
|
|
4868
|
+
return delivery ?? this.#busyEnter;
|
|
4869
|
+
}
|
|
4673
4870
|
#submit(text, delivery) {
|
|
4674
4871
|
if (this.#prompt !== null) {
|
|
4675
4872
|
if (this.#prompt.request.presentation === 'progress')
|
|
@@ -4691,7 +4888,7 @@ export class LocalTui {
|
|
|
4691
4888
|
submission.literal = true;
|
|
4692
4889
|
const command = !submission.literal && (submittedText.trimStart().startsWith('/') || (images.length === 0 && files.length === 0 && submittedText.startsWith('!')));
|
|
4693
4890
|
if (!command && this.#state.status === 'running') {
|
|
4694
|
-
submission.delivery = queueEditNewer !== null || this.#queueEditPending ? 'queue' :
|
|
4891
|
+
submission.delivery = queueEditNewer !== null || this.#queueEditPending ? 'queue' : this.#resolveDelivery(delivery);
|
|
4695
4892
|
}
|
|
4696
4893
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
4697
4894
|
this.#history.push(historyText);
|
|
@@ -4739,7 +4936,7 @@ export class LocalTui {
|
|
|
4739
4936
|
const newer = this.#queueEditNewer;
|
|
4740
4937
|
if (delivery === 'steer')
|
|
4741
4938
|
submission.literal = true;
|
|
4742
|
-
submission.delivery = newer !== null || this.#queueEditPending
|
|
4939
|
+
submission.delivery = newer !== null || this.#queueEditPending || this.#state.status !== 'running' ? 'queue' : this.#resolveDelivery(delivery);
|
|
4743
4940
|
const { images, files = [], literal = false, text: submittedText } = submission;
|
|
4744
4941
|
if (historyText !== '' && this.#history[this.#history.length - 1] !== historyText) {
|
|
4745
4942
|
this.#history.push(historyText);
|
|
@@ -4880,6 +5077,8 @@ export class LocalTui {
|
|
|
4880
5077
|
this.#wheelGesture = IDLE_WHEEL;
|
|
4881
5078
|
if (pending.request.notify !== false) {
|
|
4882
5079
|
this.#herdr.promptResolved();
|
|
5080
|
+
if (this.#herdr.programStatus.state !== 'blocked')
|
|
5081
|
+
this.#notificationQueue.clear('blocked');
|
|
4883
5082
|
this.#syncWindowTitle();
|
|
4884
5083
|
}
|
|
4885
5084
|
pending.offAbort?.();
|
|
@@ -5092,20 +5291,29 @@ export class LocalTui {
|
|
|
5092
5291
|
const pending = this.#notificationQueue.take();
|
|
5093
5292
|
if (pending === undefined || this.#disposed || this.#notificationPolicy === 'off')
|
|
5094
5293
|
return;
|
|
5294
|
+
if (this.#session.handedOff)
|
|
5295
|
+
return;
|
|
5095
5296
|
// Re-read focus at delivery time: the reader may have come back inside the
|
|
5096
5297
|
// coalescing window, and a turn finishing while they watch is not news.
|
|
5097
5298
|
if (this.#notificationPolicy === 'unfocused' && this.#terminalFocused === true)
|
|
5098
5299
|
return;
|
|
5300
|
+
if (pending.programState !== undefined && pending.programState !== this.#herdr.programStatus.state)
|
|
5301
|
+
return;
|
|
5302
|
+
if (this.#reportsProgramState && programStatusNeedsAttention(this.#herdr.programStatus.state)) {
|
|
5303
|
+
this.#syncWindowTitle(true);
|
|
5304
|
+
return;
|
|
5305
|
+
}
|
|
5099
5306
|
const sequence = terminalNotificationSequence(pending);
|
|
5100
5307
|
if (sequence !== '')
|
|
5101
5308
|
this.#term.output.write(sequence);
|
|
5102
5309
|
}, 25);
|
|
5103
5310
|
}
|
|
5104
5311
|
#runHelp(full) {
|
|
5312
|
+
const enhancedKeys = this.#session.keyboardProtocol === 'kitty';
|
|
5105
5313
|
if (!this.#tty) {
|
|
5106
|
-
this.commandOutput('help', ['Help', '', '## Essential Shortcuts', '', formatEssentialHotkeysText(this.#keybindings),
|
|
5314
|
+
this.commandOutput('help', ['Help', '', '## Essential Shortcuts', '', formatEssentialHotkeysText(this.#keybindings, enhancedKeys),
|
|
5107
5315
|
'', formatHelpText(this.#commands(), resolveUiLanguage(this.#uiLanguage)),
|
|
5108
|
-
...(full ? ['', formatHotkeysText(this.#keybindings)] : ['', 'Use `/help full` to view every keyboard shortcut.'])].join('\n'));
|
|
5316
|
+
...(full ? ['', formatHotkeysText(this.#keybindings, enhancedKeys)] : ['', 'Use `/help full` to view every keyboard shortcut.'])].join('\n'));
|
|
5109
5317
|
return;
|
|
5110
5318
|
}
|
|
5111
5319
|
if (this.#fileReviewAbort !== undefined || this.#prompt !== null)
|
|
@@ -5117,7 +5325,7 @@ export class LocalTui {
|
|
|
5117
5325
|
copyText: (text, label) => this.copyText(text, label),
|
|
5118
5326
|
insertText: (text) => this.insertText(text),
|
|
5119
5327
|
};
|
|
5120
|
-
void browseHelp(surface, this.#commands(), this.#keybindings, resolveUiLanguage(this.#uiLanguage), full, abort.signal)
|
|
5328
|
+
void browseHelp(surface, this.#commands(), this.#keybindings, resolveUiLanguage(this.#uiLanguage), full, abort.signal, enhancedKeys)
|
|
5121
5329
|
.catch(error => { if (!abort.signal.aborted && !this.#disposed)
|
|
5122
5330
|
this.notice(String(error), { level: 'error' }); })
|
|
5123
5331
|
.finally(() => { if (this.#fileReviewAbort === abort)
|
|
@@ -5173,6 +5381,19 @@ export class LocalTui {
|
|
|
5173
5381
|
// closes. Claiming it here would skip that layering.
|
|
5174
5382
|
if (action === 'interrupt')
|
|
5175
5383
|
return false;
|
|
5384
|
+
if (action === 'alternate-send') {
|
|
5385
|
+
if (this.#settings !== null || this.#search !== null)
|
|
5386
|
+
return false;
|
|
5387
|
+
if (this.#inspected !== undefined && !this.#inspected.writable)
|
|
5388
|
+
return true;
|
|
5389
|
+
if (this.#handleAutocomplete({ type: 'key', id: 'enter' }))
|
|
5390
|
+
return true;
|
|
5391
|
+
if (this.#hasComposerContent())
|
|
5392
|
+
this.#submit(this.#editor.expandedText, 'alternate');
|
|
5393
|
+
else
|
|
5394
|
+
this.#render();
|
|
5395
|
+
return true;
|
|
5396
|
+
}
|
|
5176
5397
|
if (action === 'clear-composer')
|
|
5177
5398
|
return this.#runClearComposer();
|
|
5178
5399
|
if (action === 'quit') {
|
|
@@ -5339,6 +5560,33 @@ export class LocalTui {
|
|
|
5339
5560
|
this.#scrollBy(-this.#pageSize());
|
|
5340
5561
|
return true;
|
|
5341
5562
|
}
|
|
5563
|
+
if (action === 'previous-turn' || action === 'next-turn') {
|
|
5564
|
+
if (this.#search !== null)
|
|
5565
|
+
return false;
|
|
5566
|
+
const prompts = this.#state.blocks.flatMap((block, index) => block.kind === 'user' ? [index] : []);
|
|
5567
|
+
const anchor = this.#promptAnchor === undefined ? -1 : this.#state.blocks.indexOf(this.#promptAnchor);
|
|
5568
|
+
const top = this.#follow
|
|
5569
|
+
? this.#renderer.viewport().map((_, row) => this.#renderer.documentRowAt(row)).find(row => row !== undefined) ?? this.#scrollStart
|
|
5570
|
+
: this.#scrollStart;
|
|
5571
|
+
const target = action === 'previous-turn'
|
|
5572
|
+
? prompts.findLast(index => anchor >= 0 ? index < anchor : (this.#blockStarts[index] ?? 0) < top)
|
|
5573
|
+
: prompts.find(index => anchor >= 0 ? index > anchor : (this.#blockStarts[index] ?? 0) > top);
|
|
5574
|
+
this.#processSelection = undefined;
|
|
5575
|
+
this.#ac = null;
|
|
5576
|
+
if (target === undefined) {
|
|
5577
|
+
if (action === 'next-turn')
|
|
5578
|
+
this.#followTail();
|
|
5579
|
+
}
|
|
5580
|
+
else {
|
|
5581
|
+
this.#promptAnchor = this.#state.blocks[target];
|
|
5582
|
+
this.#inspecting = true;
|
|
5583
|
+
this.#follow = false;
|
|
5584
|
+
this.#focusBlock = target;
|
|
5585
|
+
this.#focusBlockEdge = 'start';
|
|
5586
|
+
}
|
|
5587
|
+
this.#render();
|
|
5588
|
+
return true;
|
|
5589
|
+
}
|
|
5342
5590
|
if (action === 'scroll-page-down') {
|
|
5343
5591
|
if (this.#search !== null)
|
|
5344
5592
|
return false;
|
|
@@ -5634,7 +5882,7 @@ export class LocalTui {
|
|
|
5634
5882
|
});
|
|
5635
5883
|
if (entered === null || this.#disposed)
|
|
5636
5884
|
continue;
|
|
5637
|
-
const normalized = entered
|
|
5885
|
+
const normalized = normalizeKeyId(entered);
|
|
5638
5886
|
if (normalized === '')
|
|
5639
5887
|
continue;
|
|
5640
5888
|
const next = { ...this.#keybindings };
|
|
@@ -5769,6 +6017,7 @@ export class LocalTui {
|
|
|
5769
6017
|
return true;
|
|
5770
6018
|
}
|
|
5771
6019
|
if (event.id === 'home') {
|
|
6020
|
+
this.#promptAnchor = undefined;
|
|
5772
6021
|
const current = this.#fileActionFrame?.transcript?.disclosures?.findLast(target => target.detailEnd !== undefined && this.#scrollStart > target.row && this.#scrollStart < target.detailEnd);
|
|
5773
6022
|
this.#processSelection = current?.block ?? selected;
|
|
5774
6023
|
this.#focusBlock = this.#processSelection;
|
|
@@ -5777,6 +6026,7 @@ export class LocalTui {
|
|
|
5777
6026
|
return true;
|
|
5778
6027
|
}
|
|
5779
6028
|
if (event.id === 'up' || event.id === 'down') {
|
|
6029
|
+
this.#promptAnchor = undefined;
|
|
5780
6030
|
const rows = this.#processRows();
|
|
5781
6031
|
const at = Math.max(0, rows.indexOf(selected));
|
|
5782
6032
|
const next = rows[Math.max(0, Math.min(rows.length - 1, at + (event.id === 'up' ? -1 : 1)))];
|
|
@@ -5963,12 +6213,18 @@ export class LocalTui {
|
|
|
5963
6213
|
return;
|
|
5964
6214
|
}
|
|
5965
6215
|
this.#transcriptSearch = command.state;
|
|
5966
|
-
if (command.kind === 'focus')
|
|
5967
|
-
this.#
|
|
5968
|
-
this.#focusBlockEdge = 'start';
|
|
5969
|
-
}
|
|
6216
|
+
if (command.kind === 'focus')
|
|
6217
|
+
this.#focusSearchMatch(command.match);
|
|
5970
6218
|
this.#render();
|
|
5971
6219
|
}
|
|
6220
|
+
#focusSearchMatch(match) {
|
|
6221
|
+
this.#promptAnchor = undefined;
|
|
6222
|
+
this.#processSelection = undefined;
|
|
6223
|
+
this.#inspecting = true;
|
|
6224
|
+
this.#follow = false;
|
|
6225
|
+
this.#focusBlock = match.block;
|
|
6226
|
+
this.#focusBlockEdge = 'start';
|
|
6227
|
+
}
|
|
5972
6228
|
}
|
|
5973
6229
|
/**
|
|
5974
6230
|
* Mount the local terminal provider as the tui service.
|