@agi-fans/dsh-tui 0.16.0 → 0.17.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 +18 -1
- package/lib/chrome/tool-args.d.ts +9 -0
- package/lib/chrome/tool-args.js +20 -0
- package/lib/chrome/tool-args.js.map +1 -0
- package/lib/chrome/tool-renderers.js +4 -13
- package/lib/chrome/tool-renderers.js.map +1 -1
- package/lib/commands/session.js +2 -4
- package/lib/commands/session.js.map +1 -1
- package/lib/runtime/composer-images.d.ts +44 -0
- package/lib/runtime/composer-images.js +149 -0
- package/lib/runtime/composer-images.js.map +1 -0
- package/lib/runtime/herdr-agent.d.ts +95 -0
- package/lib/runtime/herdr-agent.js +258 -0
- package/lib/runtime/herdr-agent.js.map +1 -0
- package/lib/runtime/plain-tui.d.ts +34 -0
- package/lib/runtime/plain-tui.js +119 -0
- package/lib/runtime/plain-tui.js.map +1 -0
- package/lib/runtime/provider-local.d.ts +3 -0
- package/lib/runtime/provider-local.js +107 -268
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/tool-presentation.js +15 -0
- package/lib/runtime/tool-presentation.js.map +1 -1
- package/lib/session/content-text.d.ts +12 -0
- package/lib/session/content-text.js +13 -0
- package/lib/session/content-text.js.map +1 -0
- package/lib/session/history-store.js.map +1 -0
- package/lib/session/json-file.d.ts +5 -0
- package/lib/session/json-file.js +21 -0
- package/lib/session/json-file.js.map +1 -0
- package/lib/session/model-favorites.js +15 -25
- package/lib/session/model-favorites.js.map +1 -1
- package/lib/session/release-notes.js +1 -18
- package/lib/session/release-notes.js.map +1 -1
- package/lib/session/session-controller.js +64 -140
- package/lib/session/session-controller.js.map +1 -1
- package/lib/session/session-library.js +4 -14
- package/lib/session/session-library.js.map +1 -1
- package/lib/session/subagent-roster.js +13 -43
- package/lib/session/subagent-roster.js.map +1 -1
- package/lib/session/subagent-tracker.d.ts +58 -0
- package/lib/session/subagent-tracker.js +129 -0
- package/lib/session/subagent-tracker.js.map +1 -0
- package/lib/session/update-check.js +2 -15
- package/lib/session/update-check.js.map +1 -1
- package/lib/session/version-compare.d.ts +3 -0
- package/lib/session/version-compare.js +21 -0
- package/lib/session/version-compare.js.map +1 -0
- package/lib/views/event-views.d.ts +11 -270
- package/lib/views/event-views.js +12 -964
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/settings-list.js +12 -11
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/transcript-render.d.ts +179 -0
- package/lib/views/transcript-render.js +916 -0
- package/lib/views/transcript-render.js.map +1 -0
- package/lib/views/transcript-types.d.ts +104 -0
- package/lib/views/transcript-types.js +53 -0
- package/lib/views/transcript-types.js.map +1 -0
- package/package.json +2 -1
- package/lib/views/history-store.js.map +0 -1
- /package/lib/{views → session}/history-store.d.ts +0 -0
- /package/lib/{views → session}/history-store.js +0 -0
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
* @module @agi-fans/dsh-tui
|
|
12
12
|
*/
|
|
13
13
|
import { TerminalNotificationController, terminalNotificationSequence, terminalProgressSequence, } from "./terminal-notifications.js";
|
|
14
|
+
import { HerdrAgentReporter } from "./herdr-agent.js";
|
|
15
|
+
import { ComposerImages } from "./composer-images.js";
|
|
16
|
+
import { PlainTui } from "./plain-tui.js";
|
|
14
17
|
import { homedir } from 'node:os';
|
|
15
18
|
import { join, sep } from 'node:path';
|
|
16
|
-
import { createInterface } from 'node:readline';
|
|
17
19
|
import { StringDecoder } from 'node:string_decoder';
|
|
18
20
|
import { TUI_SERVICE, } from "../definition.js";
|
|
19
21
|
import { applySlashCompletion, formatHelpText, parseSlashInput, resolveSlashCommand, slashSuggestions, BUILTIN_SLASH_COMMANDS, } from "../views/autocomplete.js";
|
|
@@ -28,28 +30,42 @@ import { InputEditor, lineEnd, lineStart } from "../input/editor.js";
|
|
|
28
30
|
import { applySettingsEvent, createSettings, } from "../views/settings-list.js";
|
|
29
31
|
import { appendTrajectoryEvent, applyTrajectoryEvent, createTrajectory, trajectoryDetailMetrics, trajectoryListMetrics, } from "../views/trajectory.js";
|
|
30
32
|
import { applyAgentHubEvent, createAgentHub, updateAgentHub, } from "../views/agent-hub.js";
|
|
31
|
-
import { applyEvent, applyStreamChunk,
|
|
33
|
+
import { applyEvent, applyStreamChunk, blockSearchText, initialTranscript, replayEvents, renderView, TRANSCRIPT_FAST_SCROLL, } from "../views/event-views.js";
|
|
32
34
|
import { applyTranscriptSearchEvent, createTranscriptSearch, searchBlockIndexes, } from "../views/transcript-search.js";
|
|
33
35
|
import { nextRevealStep, revealStreamingAssistant, streamingAssistantKey, streamingAssistantUnits, } from "../views/streaming-reveal.js";
|
|
34
36
|
import { flushPending, parseKeys } from "../input/keys.js";
|
|
35
37
|
import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
|
|
36
|
-
import { colorDisabledByEnv,
|
|
38
|
+
import { colorDisabledByEnv, detectTrueColor, parseThemeName } from "../chrome/theme.js";
|
|
37
39
|
import { renderTool } from "../chrome/tool-renderers.js";
|
|
38
40
|
import { TUI_SETTINGS_NAMESPACE, TuiSettingsSchema } from "../session/tui-settings.js";
|
|
39
41
|
import { defaultStatusBarConfig, resolveStatusBarConfig } from "../chrome/status-config.js";
|
|
40
|
-
import { HistoryStore } from "../
|
|
42
|
+
import { HistoryStore } from "../session/history-store.js";
|
|
41
43
|
import { loadKeybindings } from "../input/keybindings-config.js";
|
|
42
44
|
import { editExternally } from "../input/external-editor.js";
|
|
43
45
|
import { movePromptSelection, filteredPromptOptions, selectedPromptAnswer, selectedFilteredPromptAnswer, togglePromptSelection, } from "../views/prompt-selector.js";
|
|
44
46
|
import { refreshProjectContext, resolveProjectContext } from "../session/project-context.js";
|
|
45
47
|
import { pickWelcomeTips } from "../chrome/welcome-tips.js";
|
|
46
48
|
import { formatEssentialHotkeysText, formatHotkeysText, hotkeyCount } from "../views/hotkeys.js";
|
|
47
|
-
import {
|
|
49
|
+
import { imagePathCandidates, stripComposerImageMarkers, readImageFile, readImageFromClipboard, readMacClipboardFiles, } from "../input/image-paste.js";
|
|
48
50
|
import { APP_NAME, APP_VERSION } from "../session/package-metadata.js";
|
|
49
51
|
const DOUBLE_CTRL_C_MS = 500;
|
|
50
52
|
const DOUBLE_ESCAPE_MS = 500;
|
|
51
53
|
const STREAMING_REVEAL_MS = 1000 / 30;
|
|
52
54
|
const TERMINAL_PROGRESS_KEEPALIVE_MS = 1_000;
|
|
55
|
+
/**
|
|
56
|
+
* Durable settlement events that arrive in bursts while a turn runs. Renders
|
|
57
|
+
* for these are coalesced through the stream-render timer; rarer control
|
|
58
|
+
* events (command lifecycle, inbox splices, titles, notices) render at once.
|
|
59
|
+
*/
|
|
60
|
+
const BURST_EVENT_TYPES = new Set([
|
|
61
|
+
'step/start',
|
|
62
|
+
'step/end',
|
|
63
|
+
'tool/call',
|
|
64
|
+
'tool/result',
|
|
65
|
+
'assistant/message',
|
|
66
|
+
'assistant/attempt',
|
|
67
|
+
'llm/retry',
|
|
68
|
+
]);
|
|
53
69
|
function shortenPath(cwd) {
|
|
54
70
|
const home = homedir();
|
|
55
71
|
if (cwd === home)
|
|
@@ -146,11 +162,11 @@ export class LocalTui {
|
|
|
146
162
|
#pasteBuf = '';
|
|
147
163
|
#pasteInFlight = 0;
|
|
148
164
|
#deferredPasteEvents = [];
|
|
149
|
-
#images =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
#
|
|
165
|
+
#images = new ComposerImages({
|
|
166
|
+
editor: this.#editor,
|
|
167
|
+
notice: text => { this.notice(text, { level: 'error' }); },
|
|
168
|
+
});
|
|
169
|
+
#plain;
|
|
154
170
|
#offData = null;
|
|
155
171
|
#offResize = null;
|
|
156
172
|
#pwd;
|
|
@@ -173,6 +189,12 @@ export class LocalTui {
|
|
|
173
189
|
#checkUpdates = true;
|
|
174
190
|
#startupChangelog = 'summary';
|
|
175
191
|
#notifications = new TerminalNotificationController();
|
|
192
|
+
/**
|
|
193
|
+
* Herdr lifecycle authority for this pane. The plugin entry injects the
|
|
194
|
+
* environment-aware reporter; a directly constructed instance is inert so
|
|
195
|
+
* tests running inside a Herdr pane cannot report against the live server.
|
|
196
|
+
*/
|
|
197
|
+
#herdr;
|
|
176
198
|
#notificationPolicy = 'off';
|
|
177
199
|
#notificationThreshold = '30s';
|
|
178
200
|
#statusBar = defaultStatusBarConfig();
|
|
@@ -220,7 +242,6 @@ export class LocalTui {
|
|
|
220
242
|
#searchFiles;
|
|
221
243
|
#searchFileMentions;
|
|
222
244
|
#searchSessions;
|
|
223
|
-
#validateImageDraft;
|
|
224
245
|
#autocompleteDebounceMs;
|
|
225
246
|
#persistPrefs = null;
|
|
226
247
|
#agentBehavior;
|
|
@@ -264,8 +285,15 @@ export class LocalTui {
|
|
|
264
285
|
this.#terminalProfile = paths.terminalProfile ?? detectTerminalProfile();
|
|
265
286
|
this.#resizeDebounceMs = Math.max(0, paths.resizeDebounceMs ?? 120);
|
|
266
287
|
this.#streamRenderMs = Math.max(0, paths.streamRenderMs ?? 8);
|
|
288
|
+
this.#herdr = paths.herdrReporter ?? new HerdrAgentReporter({ env: {} });
|
|
267
289
|
this.#syncTrueColor();
|
|
268
290
|
this.#tty = term.input.isTTY === true;
|
|
291
|
+
this.#plain = new PlainTui({
|
|
292
|
+
term,
|
|
293
|
+
blocks: () => this.#state.blocks,
|
|
294
|
+
promptRequest: () => this.#prompt?.request,
|
|
295
|
+
finishPrompt: answer => { this.#finishPrompt(answer); },
|
|
296
|
+
});
|
|
269
297
|
this.#pwd = shortenPath(project.root);
|
|
270
298
|
this.#branch = project.gitLabel;
|
|
271
299
|
this.#renderer = new MainScreenRenderer({ write: (chunk) => { this.#term.output.write(chunk); } }, {
|
|
@@ -301,6 +329,7 @@ export class LocalTui {
|
|
|
301
329
|
term.output.write('\x1b[?2004h');
|
|
302
330
|
}
|
|
303
331
|
this.#render();
|
|
332
|
+
this.#herdr.start();
|
|
304
333
|
}
|
|
305
334
|
/** Refresh Git workspace metadata after a turn; the footer shows realtime branch/dirty state. */
|
|
306
335
|
#refreshProjectContext() {
|
|
@@ -325,10 +354,18 @@ export class LocalTui {
|
|
|
325
354
|
this.#syncStreamingReveal(undefined);
|
|
326
355
|
this.#syncTick();
|
|
327
356
|
if (this.#tty) {
|
|
328
|
-
|
|
357
|
+
// Settlement events arrive in bursts (parallel tool calls, in-process
|
|
358
|
+
// subagent settlements); coalesce them like setSession. Control events
|
|
359
|
+
// (user echo, command lifecycle, inbox splices) stay immediate.
|
|
360
|
+
if (this.#streamRenderMs > 0 && this.#busy() && BURST_EVENT_TYPES.has(event.type)) {
|
|
361
|
+
this.#scheduleStreamRender();
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
this.#render();
|
|
365
|
+
}
|
|
329
366
|
}
|
|
330
367
|
else if (event.type === 'user/message' || event.type === 'assistant/message' || event.type === 'tool/result' || event.type === 'turn/end') {
|
|
331
|
-
this.#
|
|
368
|
+
this.#plain.print();
|
|
332
369
|
}
|
|
333
370
|
}
|
|
334
371
|
/**
|
|
@@ -353,6 +390,10 @@ export class LocalTui {
|
|
|
353
390
|
}
|
|
354
391
|
}
|
|
355
392
|
setStatus(status) {
|
|
393
|
+
// Only the displayed top-level agent drives the Herdr pane state; an
|
|
394
|
+
// inspected child must not, and closing the inspector re-syncs the root.
|
|
395
|
+
if (this.#inspected === undefined)
|
|
396
|
+
this.#herdr.setRunning(status === 'running');
|
|
356
397
|
if (this.#state.status === 'compacting')
|
|
357
398
|
return;
|
|
358
399
|
this.#state = { ...this.#state, status };
|
|
@@ -463,7 +504,7 @@ export class LocalTui {
|
|
|
463
504
|
this.#render();
|
|
464
505
|
}
|
|
465
506
|
setImageValidator(validate) {
|
|
466
|
-
this.#
|
|
507
|
+
this.#images.setValidator(validate);
|
|
467
508
|
}
|
|
468
509
|
toolCallContext(callId) {
|
|
469
510
|
const block = this.#state.blocks.findLast(candidate => candidate.kind === 'tool' && String(candidate.callId) === callId);
|
|
@@ -501,14 +542,14 @@ export class LocalTui {
|
|
|
501
542
|
if (this.#tty)
|
|
502
543
|
this.#render();
|
|
503
544
|
else
|
|
504
|
-
this.#
|
|
545
|
+
this.#plain.print();
|
|
505
546
|
}
|
|
506
547
|
commandOutput(command, text) {
|
|
507
548
|
this.#state = { ...this.#state, blocks: [...this.#state.blocks, { kind: 'commandOutput', command, text }] };
|
|
508
549
|
if (this.#tty)
|
|
509
550
|
this.#render();
|
|
510
551
|
else
|
|
511
|
-
this.#
|
|
552
|
+
this.#plain.print();
|
|
512
553
|
}
|
|
513
554
|
prompt(request) {
|
|
514
555
|
if (this.#prompt !== null)
|
|
@@ -516,6 +557,7 @@ export class LocalTui {
|
|
|
516
557
|
if (this.#disposed || request.signal?.aborted === true)
|
|
517
558
|
return Promise.resolve(null);
|
|
518
559
|
this.#emitNotification(this.#notifications.humanPrompt());
|
|
560
|
+
this.#herdr.prompt(request.title);
|
|
519
561
|
this.#editor.setText('');
|
|
520
562
|
this.#ac = null;
|
|
521
563
|
const displaced = this.#displaceSurface();
|
|
@@ -552,17 +594,18 @@ export class LocalTui {
|
|
|
552
594
|
this.#trajectory = null;
|
|
553
595
|
const state = replayEvents(events, presentations);
|
|
554
596
|
this.#state = { ...state, status, compactCommandId: undefined };
|
|
555
|
-
this.#
|
|
597
|
+
this.#plain.resetPrinted();
|
|
556
598
|
this.#followTail();
|
|
557
599
|
this.#deferInitialRender = false;
|
|
558
600
|
this.#renderer.startEpoch({ replay: status === 'idle' ? 'full' : 'pinned' });
|
|
559
601
|
if (this.#tty)
|
|
560
602
|
this.#render();
|
|
561
603
|
else
|
|
562
|
-
this.#
|
|
604
|
+
this.#plain.print();
|
|
563
605
|
}
|
|
564
606
|
setSession(info) {
|
|
565
607
|
this.#sessionId = info.id;
|
|
608
|
+
this.#herdr.setSession(info.id);
|
|
566
609
|
const title = info.title?.trim();
|
|
567
610
|
this.#sessionTitle = title === undefined || title === '' ? undefined : title;
|
|
568
611
|
this.#recentSessions = info.recent.map((session) => ({ ...session }));
|
|
@@ -604,25 +647,7 @@ export class LocalTui {
|
|
|
604
647
|
}
|
|
605
648
|
/** Apply prefs loaded from the settings document (does not persist). */
|
|
606
649
|
applyStoredPrefs(prefs) {
|
|
607
|
-
|
|
608
|
-
this.#themeName = prefs.theme;
|
|
609
|
-
this.#colors = prefs.colors;
|
|
610
|
-
this.#syncTrueColor();
|
|
611
|
-
this.#motion = prefs.motion ?? 'full';
|
|
612
|
-
this.#terminalProgress = prefs.terminalProgress ?? false;
|
|
613
|
-
this.#expandTools = prefs.expandTools;
|
|
614
|
-
this.#checkUpdates = prefs.checkUpdates ?? true;
|
|
615
|
-
this.#startupChangelog = prefs.startupChangelog ?? 'summary';
|
|
616
|
-
this.#notificationPolicy = prefs.notifications ?? 'off';
|
|
617
|
-
this.#notificationThreshold = prefs.notificationThreshold ?? '30s';
|
|
618
|
-
this.#configureNotifications();
|
|
619
|
-
this.#statusBar = resolveStatusBarConfig(prefs.statusBar, prefs.statusPreset);
|
|
620
|
-
this.#toolsExpanded = prefs.expandTools;
|
|
621
|
-
if (previousMotion !== this.#motion) {
|
|
622
|
-
this.#stopRevealTick();
|
|
623
|
-
this.#reveal = undefined;
|
|
624
|
-
}
|
|
625
|
-
this.#syncTick();
|
|
650
|
+
this.#applyPrefs(prefs, { persist: false, forceToolsSync: true });
|
|
626
651
|
if (this.#settings !== null)
|
|
627
652
|
this.#settings = { ...this.#settings, prefs };
|
|
628
653
|
if (this.#tty)
|
|
@@ -667,7 +692,7 @@ export class LocalTui {
|
|
|
667
692
|
return Promise.resolve(null);
|
|
668
693
|
}
|
|
669
694
|
if (!this.#tty)
|
|
670
|
-
return this.#
|
|
695
|
+
return this.#plain.readline(signal);
|
|
671
696
|
// Lines submitted while a turn was still running were queued instead of
|
|
672
697
|
// dropped; serve the oldest before waiting for fresh input.
|
|
673
698
|
const queued = this.#queuedSubmissions.shift();
|
|
@@ -695,16 +720,7 @@ export class LocalTui {
|
|
|
695
720
|
return (await this.readInput())?.text ?? null;
|
|
696
721
|
}
|
|
697
722
|
restoreInput(submission) {
|
|
698
|
-
|
|
699
|
-
const currentImages = this.#images;
|
|
700
|
-
let rebasedCurrent = currentText;
|
|
701
|
-
for (let index = currentImages.length - 1; index >= 0; index -= 1) {
|
|
702
|
-
const image = currentImages[index];
|
|
703
|
-
rebasedCurrent = rebasedCurrent.replaceAll(imageMarker(index, image), imageMarker(index + submission.images.length, image));
|
|
704
|
-
}
|
|
705
|
-
const separator = submission.text !== '' && rebasedCurrent !== '' ? '\n' : '';
|
|
706
|
-
this.#images = [...submission.images.map(image => ({ ...image })), ...currentImages];
|
|
707
|
-
this.#editor.setText(submission.text + separator + rebasedCurrent);
|
|
723
|
+
this.#images.restore(submission);
|
|
708
724
|
this.#refreshAutocomplete();
|
|
709
725
|
if (this.#tty)
|
|
710
726
|
this.#render();
|
|
@@ -714,14 +730,14 @@ export class LocalTui {
|
|
|
714
730
|
return;
|
|
715
731
|
this.#queueEditPending = false;
|
|
716
732
|
if (submission === null) {
|
|
717
|
-
if (this.#editor.text === '' && this.#images.
|
|
733
|
+
if (this.#editor.text === '' && this.#images.count === 0 && this.#queueEditNewer?.length === 0) {
|
|
718
734
|
this.#queueEditNewer = null;
|
|
719
735
|
}
|
|
720
736
|
return;
|
|
721
737
|
}
|
|
722
738
|
if (this.#queueEditNewer === null)
|
|
723
739
|
this.#queueEditNewer = [];
|
|
724
|
-
if (this.#editor.text !== '' || this.#images.
|
|
740
|
+
if (this.#editor.text !== '' || this.#images.count > 0) {
|
|
725
741
|
this.#queueEditNewer.unshift(this.#currentSubmission());
|
|
726
742
|
}
|
|
727
743
|
this.#replaceInput(submission);
|
|
@@ -729,12 +745,11 @@ export class LocalTui {
|
|
|
729
745
|
#currentSubmission() {
|
|
730
746
|
return {
|
|
731
747
|
text: this.#editor.text,
|
|
732
|
-
images: this.#images.
|
|
748
|
+
images: this.#images.copies(),
|
|
733
749
|
};
|
|
734
750
|
}
|
|
735
751
|
#replaceInput(submission) {
|
|
736
|
-
this.#images
|
|
737
|
-
this.#editor.setText(submission.text);
|
|
752
|
+
this.#images.replace(submission);
|
|
738
753
|
this.#historyIndex = 0;
|
|
739
754
|
this.#refreshAutocomplete();
|
|
740
755
|
if (this.#tty)
|
|
@@ -814,11 +829,12 @@ export class LocalTui {
|
|
|
814
829
|
this.#autocompleteAbort?.abort();
|
|
815
830
|
this.#autocompleteTimer = null;
|
|
816
831
|
this.#autocompleteAbort = null;
|
|
817
|
-
this.#
|
|
832
|
+
this.#plain.dispose();
|
|
818
833
|
this.#offAgentBehaviorWatch?.();
|
|
819
834
|
this.#offAgentBehaviorWatch = undefined;
|
|
820
835
|
this.#settlePending(null);
|
|
821
836
|
this.#finishPrompt(null);
|
|
837
|
+
this.#herdr.release();
|
|
822
838
|
}
|
|
823
839
|
/** Settle one pending read, detaching its abort listener. */
|
|
824
840
|
#settlePending(submission) {
|
|
@@ -833,96 +849,6 @@ export class LocalTui {
|
|
|
833
849
|
refresh() {
|
|
834
850
|
this.#render();
|
|
835
851
|
}
|
|
836
|
-
#readlinePlain(signal) {
|
|
837
|
-
return new Promise((resolve) => {
|
|
838
|
-
if (this.#lineReader === null) {
|
|
839
|
-
this.#lineReader = createInterface({ input: this.#term.input });
|
|
840
|
-
// Permanent listeners: once() handlers would auto-pause the input
|
|
841
|
-
// stream after one line and miss the EOF close.
|
|
842
|
-
this.#lineReader.on('line', (line) => { this.#plainResolve(line); });
|
|
843
|
-
this.#lineReader.on('close', () => {
|
|
844
|
-
this.#plainClosed = true;
|
|
845
|
-
this.#plainResolve(null);
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
const pending = { resolve, signal: signal ?? null };
|
|
849
|
-
this.#plainPending = pending;
|
|
850
|
-
if (signal !== undefined) {
|
|
851
|
-
const onAbort = () => {
|
|
852
|
-
if (this.#plainPending !== pending)
|
|
853
|
-
return;
|
|
854
|
-
this.#plainPending = null;
|
|
855
|
-
resolve(null);
|
|
856
|
-
};
|
|
857
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
858
|
-
pending.offAbort = () => { signal.removeEventListener('abort', onAbort); };
|
|
859
|
-
}
|
|
860
|
-
this.#pumpPlain();
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Queue one readline delivery, then drain. A single stream chunk can carry
|
|
865
|
-
* several lines; every line is buffered until the runner asks for the next
|
|
866
|
-
* read, and EOF waits for the queue before closing the reader.
|
|
867
|
-
*/
|
|
868
|
-
#plainResolve(line) {
|
|
869
|
-
if (this.#prompt !== null && line !== null) {
|
|
870
|
-
const value = line.trim();
|
|
871
|
-
if (value === '') {
|
|
872
|
-
this.#finishPrompt(null);
|
|
873
|
-
}
|
|
874
|
-
else if (this.#prompt.request.allowCustom === false) {
|
|
875
|
-
const options = this.#prompt.request.options ?? [];
|
|
876
|
-
const numeric = /^\d+$/u.test(value) ? Number(value) - 1 : -1;
|
|
877
|
-
const option = numeric >= 0
|
|
878
|
-
? options[numeric]
|
|
879
|
-
: options.find(item => item.label.toLowerCase() === value.toLowerCase());
|
|
880
|
-
this.#finishPrompt(option?.value ?? option?.label ?? null);
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
this.#finishPrompt(value);
|
|
884
|
-
}
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
if (this.#prompt !== null)
|
|
888
|
-
this.#finishPrompt(null);
|
|
889
|
-
if (line !== null)
|
|
890
|
-
this.#plainQueue.push(line);
|
|
891
|
-
this.#pumpPlain();
|
|
892
|
-
}
|
|
893
|
-
/** Resolve the pending plain read from the queue, or close it after EOF drained. */
|
|
894
|
-
#pumpPlain() {
|
|
895
|
-
const pending = this.#plainPending;
|
|
896
|
-
if (pending === null)
|
|
897
|
-
return;
|
|
898
|
-
const line = this.#plainQueue.shift();
|
|
899
|
-
if (line !== undefined) {
|
|
900
|
-
pending.offAbort?.();
|
|
901
|
-
this.#plainPending = null;
|
|
902
|
-
pending.resolve({ text: line, images: [] });
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
if (this.#plainClosed) {
|
|
906
|
-
pending.offAbort?.();
|
|
907
|
-
this.#plainPending = null;
|
|
908
|
-
pending.resolve(null);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
/** Print plain-mode blocks that settled since the last flush. */
|
|
912
|
-
#printPlain() {
|
|
913
|
-
const theme = createTheme(false, false);
|
|
914
|
-
const width = this.#term.width();
|
|
915
|
-
const fresh = this.#state.blocks.slice(this.#plainPrinted);
|
|
916
|
-
let out = '';
|
|
917
|
-
for (const block of fresh) {
|
|
918
|
-
// Pipe / CI output is not a viewport: print the full tool body.
|
|
919
|
-
for (const line of blockLines(block, theme, width, 0, true))
|
|
920
|
-
out += line + '\n';
|
|
921
|
-
}
|
|
922
|
-
this.#plainPrinted = this.#state.blocks.length;
|
|
923
|
-
if (out !== '')
|
|
924
|
-
this.#term.output.write(out);
|
|
925
|
-
}
|
|
926
852
|
#busy() {
|
|
927
853
|
if (this.#state.status !== 'idle')
|
|
928
854
|
return true;
|
|
@@ -1048,7 +974,7 @@ export class LocalTui {
|
|
|
1048
974
|
...(this.#reasoningEffort === undefined ? {} : { reasoningEffort: this.#reasoningEffort }),
|
|
1049
975
|
input: this.#editor.text,
|
|
1050
976
|
inputCursor: this.#editor.cursor,
|
|
1051
|
-
inputImages: this.#images.
|
|
977
|
+
inputImages: this.#images.count,
|
|
1052
978
|
queuedSubmissions: this.#queueEditNewer === null
|
|
1053
979
|
? this.#queuedSubmissions
|
|
1054
980
|
: [...this.#queuedSubmissions, ...this.#queueEditNewer],
|
|
@@ -1143,7 +1069,6 @@ export class LocalTui {
|
|
|
1143
1069
|
this.#scrollStart = this.#maxStart;
|
|
1144
1070
|
}
|
|
1145
1071
|
#utf8 = new StringDecoder('utf8');
|
|
1146
|
-
#plainQueue = [];
|
|
1147
1072
|
#onData(chunk) {
|
|
1148
1073
|
// Decode bytes across the whole stream so a multi-byte UTF-8 character
|
|
1149
1074
|
// split between data events is never corrupted into replacement chars.
|
|
@@ -1204,8 +1129,8 @@ export class LocalTui {
|
|
|
1204
1129
|
if (images.every((image) => image !== null)) {
|
|
1205
1130
|
let inserted = false;
|
|
1206
1131
|
for (const image of images) {
|
|
1207
|
-
if (await this.#
|
|
1208
|
-
this.#
|
|
1132
|
+
if (await this.#images.admit(image)) {
|
|
1133
|
+
this.#images.insert(image);
|
|
1209
1134
|
inserted = true;
|
|
1210
1135
|
}
|
|
1211
1136
|
}
|
|
@@ -1220,7 +1145,7 @@ export class LocalTui {
|
|
|
1220
1145
|
// image instead of leaking that stale path into the prompt.
|
|
1221
1146
|
const clipboardImage = await this.#readClipboardImage();
|
|
1222
1147
|
if (clipboardImage !== null) {
|
|
1223
|
-
this.#
|
|
1148
|
+
this.#images.insert(clipboardImage);
|
|
1224
1149
|
this.#refreshAutocomplete();
|
|
1225
1150
|
this.#render();
|
|
1226
1151
|
return;
|
|
@@ -1234,8 +1159,8 @@ export class LocalTui {
|
|
|
1234
1159
|
if (this.#search === null && this.#settings === null && this.#copySelector === null) {
|
|
1235
1160
|
const image = await this.#readClipboardImage();
|
|
1236
1161
|
if (image !== null) {
|
|
1237
|
-
if (await this.#
|
|
1238
|
-
this.#
|
|
1162
|
+
if (await this.#images.admit(image)) {
|
|
1163
|
+
this.#images.insert(image);
|
|
1239
1164
|
this.#refreshAutocomplete();
|
|
1240
1165
|
this.#render();
|
|
1241
1166
|
}
|
|
@@ -1249,8 +1174,8 @@ export class LocalTui {
|
|
|
1249
1174
|
if (images.length > 0) {
|
|
1250
1175
|
let inserted = false;
|
|
1251
1176
|
for (const candidate of images) {
|
|
1252
|
-
if (await this.#
|
|
1253
|
-
this.#
|
|
1177
|
+
if (await this.#images.admit(candidate)) {
|
|
1178
|
+
this.#images.insert(candidate);
|
|
1254
1179
|
inserted = true;
|
|
1255
1180
|
}
|
|
1256
1181
|
}
|
|
@@ -1266,98 +1191,6 @@ export class LocalTui {
|
|
|
1266
1191
|
if (text !== '')
|
|
1267
1192
|
await this.#acceptPastedText(text);
|
|
1268
1193
|
}
|
|
1269
|
-
/**
|
|
1270
|
-
* Run the Harness image-admission check for one paste candidate. A refusal
|
|
1271
|
-
* becomes an error notice and skips the draft, instead of failing the whole
|
|
1272
|
-
* submission after the user has typed a prompt around it.
|
|
1273
|
-
*/
|
|
1274
|
-
async #admitImage(image) {
|
|
1275
|
-
if (this.#validateImageDraft === undefined)
|
|
1276
|
-
return true;
|
|
1277
|
-
try {
|
|
1278
|
-
await this.#validateImageDraft(image);
|
|
1279
|
-
return true;
|
|
1280
|
-
}
|
|
1281
|
-
catch (error) {
|
|
1282
|
-
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
1283
|
-
return false;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
#insertImageDraft(input) {
|
|
1287
|
-
const size = input.width === undefined || input.height === undefined
|
|
1288
|
-
? probeImageDimensions(input.data, input.mediaType)
|
|
1289
|
-
: undefined;
|
|
1290
|
-
const image = {
|
|
1291
|
-
...input,
|
|
1292
|
-
...(input.width === undefined && size !== undefined ? { width: size.width } : {}),
|
|
1293
|
-
...(input.height === undefined && size !== undefined ? { height: size.height } : {}),
|
|
1294
|
-
};
|
|
1295
|
-
const marker = imageMarker(this.#images.length, image);
|
|
1296
|
-
const before = this.#editor.cursor > 0 && !/\s/u.test(this.#editor.text[this.#editor.cursor - 1] ?? '') ? ' ' : '';
|
|
1297
|
-
const after = this.#editor.cursor >= this.#editor.text.length || !/\s/u.test(this.#editor.text[this.#editor.cursor] ?? '')
|
|
1298
|
-
? ' '
|
|
1299
|
-
: '';
|
|
1300
|
-
this.#images.push(image);
|
|
1301
|
-
this.#editor.handle({ type: 'text', value: before + marker + after });
|
|
1302
|
-
}
|
|
1303
|
-
#removeImageAtCursor(key) {
|
|
1304
|
-
const cursor = this.#editor.cursor;
|
|
1305
|
-
for (let index = 0; index < this.#images.length; index += 1) {
|
|
1306
|
-
const image = this.#images[index];
|
|
1307
|
-
const marker = imageMarker(index, image);
|
|
1308
|
-
const start = this.#editor.text.indexOf(marker);
|
|
1309
|
-
if (start < 0)
|
|
1310
|
-
continue;
|
|
1311
|
-
let from = start;
|
|
1312
|
-
let to = start + marker.length;
|
|
1313
|
-
const touches = key === 'backspace'
|
|
1314
|
-
? cursor > start && cursor <= to
|
|
1315
|
-
: cursor >= start && cursor < to;
|
|
1316
|
-
if (!touches)
|
|
1317
|
-
continue;
|
|
1318
|
-
if (this.#editor.text[to] === ' ')
|
|
1319
|
-
to += 1;
|
|
1320
|
-
else if (from > 0 && this.#editor.text[from - 1] === ' ')
|
|
1321
|
-
from -= 1;
|
|
1322
|
-
const oldImages = this.#images;
|
|
1323
|
-
let text = this.#editor.text.slice(0, from) + this.#editor.text.slice(to);
|
|
1324
|
-
const nextImages = oldImages.filter((_, oldIndex) => oldIndex !== index);
|
|
1325
|
-
let nextIndex = 0;
|
|
1326
|
-
for (let oldIndex = 0; oldIndex < oldImages.length; oldIndex += 1) {
|
|
1327
|
-
if (oldIndex === index)
|
|
1328
|
-
continue;
|
|
1329
|
-
const remaining = oldImages[oldIndex];
|
|
1330
|
-
text = text.replaceAll(imageMarker(oldIndex, remaining), imageMarker(nextIndex, remaining));
|
|
1331
|
-
nextIndex += 1;
|
|
1332
|
-
}
|
|
1333
|
-
this.#images = nextImages;
|
|
1334
|
-
this.#editor.setText(text, Math.min(from, text.length));
|
|
1335
|
-
this.#refreshAutocomplete();
|
|
1336
|
-
this.#render();
|
|
1337
|
-
return true;
|
|
1338
|
-
}
|
|
1339
|
-
return false;
|
|
1340
|
-
}
|
|
1341
|
-
#reconcileImageDrafts() {
|
|
1342
|
-
if (this.#images.length === 0)
|
|
1343
|
-
return;
|
|
1344
|
-
const oldImages = this.#images;
|
|
1345
|
-
const retained = oldImages.filter((image, index) => this.#editor.text.includes(imageMarker(index, image)));
|
|
1346
|
-
if (retained.length === oldImages.length)
|
|
1347
|
-
return;
|
|
1348
|
-
let text = this.#editor.text;
|
|
1349
|
-
let nextIndex = 0;
|
|
1350
|
-
for (let oldIndex = 0; oldIndex < oldImages.length; oldIndex += 1) {
|
|
1351
|
-
const image = oldImages[oldIndex];
|
|
1352
|
-
const oldMarker = imageMarker(oldIndex, image);
|
|
1353
|
-
if (!text.includes(oldMarker))
|
|
1354
|
-
continue;
|
|
1355
|
-
text = text.replaceAll(oldMarker, imageMarker(nextIndex, image));
|
|
1356
|
-
nextIndex += 1;
|
|
1357
|
-
}
|
|
1358
|
-
this.#images = retained;
|
|
1359
|
-
this.#editor.setText(text, Math.min(this.#editor.cursor, text.length));
|
|
1360
|
-
}
|
|
1361
1194
|
#dispatch(event) {
|
|
1362
1195
|
if (this.#state.status === 'compacting') {
|
|
1363
1196
|
if (event.type === 'key' && event.id === 'pageUp') {
|
|
@@ -1483,7 +1316,7 @@ export class LocalTui {
|
|
|
1483
1316
|
}
|
|
1484
1317
|
else {
|
|
1485
1318
|
this.#editor.clear();
|
|
1486
|
-
this.#images
|
|
1319
|
+
this.#images.clear();
|
|
1487
1320
|
this.#historyIndex = 0;
|
|
1488
1321
|
this.#ac = null;
|
|
1489
1322
|
this.#render();
|
|
@@ -1511,9 +1344,9 @@ export class LocalTui {
|
|
|
1511
1344
|
return;
|
|
1512
1345
|
if (event.type === 'key' && event.id === 'escape') {
|
|
1513
1346
|
if (this.#inspected !== undefined) {
|
|
1514
|
-
if (this.#inspected.writable === true && (this.#editor.text !== '' || this.#images.
|
|
1347
|
+
if (this.#inspected.writable === true && (this.#editor.text !== '' || this.#images.count > 0)) {
|
|
1515
1348
|
this.#editor.clear();
|
|
1516
|
-
this.#images
|
|
1349
|
+
this.#images.clear();
|
|
1517
1350
|
this.#ac = null;
|
|
1518
1351
|
this.#render();
|
|
1519
1352
|
return;
|
|
@@ -1522,7 +1355,7 @@ export class LocalTui {
|
|
|
1522
1355
|
return;
|
|
1523
1356
|
}
|
|
1524
1357
|
if (this.#state.status !== 'idle' || this.#pending === null
|
|
1525
|
-
|| this.#editor.text !== '' || this.#images.
|
|
1358
|
+
|| this.#editor.text !== '' || this.#images.count > 0) {
|
|
1526
1359
|
this.#lastEscapeTime = 0;
|
|
1527
1360
|
}
|
|
1528
1361
|
else {
|
|
@@ -1539,8 +1372,11 @@ export class LocalTui {
|
|
|
1539
1372
|
}
|
|
1540
1373
|
}
|
|
1541
1374
|
if (event.type === 'key' && (event.id === 'backspace' || event.id === 'delete')
|
|
1542
|
-
&& this.#
|
|
1375
|
+
&& this.#images.removeAtCursor(event.id)) {
|
|
1376
|
+
this.#refreshAutocomplete();
|
|
1377
|
+
this.#render();
|
|
1543
1378
|
return;
|
|
1379
|
+
}
|
|
1544
1380
|
this.#applyCommand(this.#editor.handle(event));
|
|
1545
1381
|
}
|
|
1546
1382
|
#handlePrompt(event) {
|
|
@@ -1751,7 +1587,7 @@ export class LocalTui {
|
|
|
1751
1587
|
}
|
|
1752
1588
|
if (!hasAgents || event.type !== 'key' || event.id !== 'down')
|
|
1753
1589
|
return false;
|
|
1754
|
-
if (this.#editor.text !== '' || this.#images.
|
|
1590
|
+
if (this.#editor.text !== '' || this.#images.count > 0 || this.#historyIndex !== 0 || this.#ac !== null)
|
|
1755
1591
|
return false;
|
|
1756
1592
|
if (this.#queuedSubmissions.length > 0 || this.#queueEditNewer !== null || this.#queueEditPending)
|
|
1757
1593
|
return false;
|
|
@@ -1806,12 +1642,12 @@ export class LocalTui {
|
|
|
1806
1642
|
},
|
|
1807
1643
|
};
|
|
1808
1644
|
}
|
|
1809
|
-
#applyPrefs(prefs) {
|
|
1645
|
+
#applyPrefs(prefs, options) {
|
|
1646
|
+
const previousMotion = this.#motion;
|
|
1810
1647
|
const expandChanged = prefs.expandTools !== this.#expandTools;
|
|
1811
1648
|
this.#themeName = prefs.theme;
|
|
1812
1649
|
this.#colors = prefs.colors;
|
|
1813
1650
|
this.#syncTrueColor();
|
|
1814
|
-
const previousMotion = this.#motion;
|
|
1815
1651
|
this.#motion = prefs.motion ?? 'full';
|
|
1816
1652
|
this.#terminalProgress = prefs.terminalProgress ?? false;
|
|
1817
1653
|
this.#expandTools = prefs.expandTools;
|
|
@@ -1821,14 +1657,15 @@ export class LocalTui {
|
|
|
1821
1657
|
this.#notificationThreshold = prefs.notificationThreshold ?? '30s';
|
|
1822
1658
|
this.#configureNotifications();
|
|
1823
1659
|
this.#statusBar = resolveStatusBarConfig(prefs.statusBar, prefs.statusPreset);
|
|
1824
|
-
if (expandChanged)
|
|
1660
|
+
if (options.forceToolsSync || expandChanged)
|
|
1825
1661
|
this.#toolsExpanded = prefs.expandTools;
|
|
1826
1662
|
if (previousMotion !== this.#motion) {
|
|
1827
1663
|
this.#stopRevealTick();
|
|
1828
1664
|
this.#reveal = undefined;
|
|
1829
1665
|
}
|
|
1830
1666
|
this.#syncTick();
|
|
1831
|
-
|
|
1667
|
+
if (options.persist)
|
|
1668
|
+
this.#persistPrefs?.(prefs);
|
|
1832
1669
|
}
|
|
1833
1670
|
#applyStoredAgentBehavior(next) {
|
|
1834
1671
|
this.#agentBehavior = { language: next.language };
|
|
@@ -1878,7 +1715,7 @@ export class LocalTui {
|
|
|
1878
1715
|
return;
|
|
1879
1716
|
}
|
|
1880
1717
|
this.#settings = command.state;
|
|
1881
|
-
this.#applyPrefs(command.state.prefs);
|
|
1718
|
+
this.#applyPrefs(command.state.prefs, { persist: true, forceToolsSync: false });
|
|
1882
1719
|
this.#render();
|
|
1883
1720
|
return;
|
|
1884
1721
|
}
|
|
@@ -2090,7 +1927,7 @@ export class LocalTui {
|
|
|
2090
1927
|
}
|
|
2091
1928
|
#applyCommand(command) {
|
|
2092
1929
|
if (command.kind === 'changed') {
|
|
2093
|
-
this.#
|
|
1930
|
+
this.#images.reconcile();
|
|
2094
1931
|
if (command.edited === true)
|
|
2095
1932
|
this.#historyIndex = 0;
|
|
2096
1933
|
this.#refreshAutocomplete();
|
|
@@ -2125,7 +1962,7 @@ export class LocalTui {
|
|
|
2125
1962
|
}
|
|
2126
1963
|
if (command.kind === 'clear') {
|
|
2127
1964
|
this.#editor.clear();
|
|
2128
|
-
this.#images
|
|
1965
|
+
this.#images.clear();
|
|
2129
1966
|
this.#queueEditNewer = null;
|
|
2130
1967
|
this.#queueEditPending = false;
|
|
2131
1968
|
this.#historyIndex = 0;
|
|
@@ -2153,7 +1990,7 @@ export class LocalTui {
|
|
|
2153
1990
|
}
|
|
2154
1991
|
}
|
|
2155
1992
|
#historyPrev() {
|
|
2156
|
-
if (this.#images.
|
|
1993
|
+
if (this.#images.count > 0)
|
|
2157
1994
|
return;
|
|
2158
1995
|
if (this.#history.length === 0 || this.#historyIndex >= this.#history.length)
|
|
2159
1996
|
return;
|
|
@@ -2182,7 +2019,7 @@ export class LocalTui {
|
|
|
2182
2019
|
listener();
|
|
2183
2020
|
return true;
|
|
2184
2021
|
}
|
|
2185
|
-
if (this.#editor.text !== '' || this.#images.
|
|
2022
|
+
if (this.#editor.text !== '' || this.#images.count > 0 || this.#historyIndex !== 0)
|
|
2186
2023
|
return false;
|
|
2187
2024
|
const submission = this.#queuedSubmissions.pop();
|
|
2188
2025
|
if (submission !== undefined) {
|
|
@@ -2200,7 +2037,7 @@ export class LocalTui {
|
|
|
2200
2037
|
return true;
|
|
2201
2038
|
}
|
|
2202
2039
|
#historyNext() {
|
|
2203
|
-
if (this.#images.
|
|
2040
|
+
if (this.#images.count > 0)
|
|
2204
2041
|
return;
|
|
2205
2042
|
if (this.#historyIndex === 0)
|
|
2206
2043
|
return;
|
|
@@ -2228,7 +2065,7 @@ export class LocalTui {
|
|
|
2228
2065
|
this.#render();
|
|
2229
2066
|
return;
|
|
2230
2067
|
}
|
|
2231
|
-
const images = this.#images.
|
|
2068
|
+
const images = this.#images.copies();
|
|
2232
2069
|
const submittedText = images.length > 0 ? text.trim() : text;
|
|
2233
2070
|
const queueEditNewer = this.#queueEditNewer;
|
|
2234
2071
|
const historyText = stripComposerImageMarkers(submittedText, images)
|
|
@@ -2243,7 +2080,7 @@ export class LocalTui {
|
|
|
2243
2080
|
this.#queueEditNewer = null;
|
|
2244
2081
|
this.#queueEditPending = false;
|
|
2245
2082
|
this.#editor.setText('');
|
|
2246
|
-
this.#images
|
|
2083
|
+
this.#images.clear();
|
|
2247
2084
|
this.#ac = null;
|
|
2248
2085
|
this.#search = null;
|
|
2249
2086
|
this.#settings = null;
|
|
@@ -2279,7 +2116,7 @@ export class LocalTui {
|
|
|
2279
2116
|
this.#render();
|
|
2280
2117
|
}
|
|
2281
2118
|
#submitInspect(text) {
|
|
2282
|
-
const images = this.#images.
|
|
2119
|
+
const images = this.#images.copies();
|
|
2283
2120
|
const submittedText = images.length > 0 ? text.trim() : text;
|
|
2284
2121
|
const historyText = stripComposerImageMarkers(submittedText, images)
|
|
2285
2122
|
.replace(/[ \t]{2,}/gu, ' ')
|
|
@@ -2291,7 +2128,7 @@ export class LocalTui {
|
|
|
2291
2128
|
this.#historyIndex = 0;
|
|
2292
2129
|
this.#draft = '';
|
|
2293
2130
|
this.#editor.setText('');
|
|
2294
|
-
this.#images
|
|
2131
|
+
this.#images.clear();
|
|
2295
2132
|
this.#ac = null;
|
|
2296
2133
|
this.#search = null;
|
|
2297
2134
|
this.#followTail();
|
|
@@ -2410,6 +2247,7 @@ export class LocalTui {
|
|
|
2410
2247
|
if (pending === null)
|
|
2411
2248
|
return;
|
|
2412
2249
|
this.#prompt = null;
|
|
2250
|
+
this.#herdr.promptResolved();
|
|
2413
2251
|
pending.offAbort?.();
|
|
2414
2252
|
pending.resolve(answer);
|
|
2415
2253
|
this.#restoreDisplacedSurface();
|
|
@@ -2559,7 +2397,7 @@ export class LocalTui {
|
|
|
2559
2397
|
return true;
|
|
2560
2398
|
}
|
|
2561
2399
|
if (action === 'search-history') {
|
|
2562
|
-
if (this.#images.
|
|
2400
|
+
if (this.#images.count > 0)
|
|
2563
2401
|
return false;
|
|
2564
2402
|
this.#search = createHistorySearch(this.#history);
|
|
2565
2403
|
this.#ac = null;
|
|
@@ -2571,7 +2409,7 @@ export class LocalTui {
|
|
|
2571
2409
|
// transcript there; a non-empty draft keeps the editor's own binding.
|
|
2572
2410
|
if (this.#search !== null)
|
|
2573
2411
|
return false;
|
|
2574
|
-
if (this.#editor.text !== '' || this.#images.
|
|
2412
|
+
if (this.#editor.text !== '' || this.#images.count > 0)
|
|
2575
2413
|
return false;
|
|
2576
2414
|
this.#transcriptSearch = createTranscriptSearch();
|
|
2577
2415
|
this.#ac = null;
|
|
@@ -2615,7 +2453,7 @@ export class LocalTui {
|
|
|
2615
2453
|
this.#term.input.setRawMode?.(false);
|
|
2616
2454
|
const text = editExternally(this.#editor.text);
|
|
2617
2455
|
this.#editor.setText(text);
|
|
2618
|
-
this.#
|
|
2456
|
+
this.#images.reconcile();
|
|
2619
2457
|
}
|
|
2620
2458
|
catch (error) {
|
|
2621
2459
|
this.notice(error instanceof Error ? error.message : String(error), { level: 'error' });
|
|
@@ -2687,6 +2525,7 @@ export function apply(ctx, config) {
|
|
|
2687
2525
|
deferInitialRender: true,
|
|
2688
2526
|
terminalProfile,
|
|
2689
2527
|
alternateScreenOverlays: terminalProfile === 'direct',
|
|
2528
|
+
herdrReporter: new HerdrAgentReporter(),
|
|
2690
2529
|
historyPath: config.historyPath ?? join(dshHome, 'omdsh', 'history.jsonl'),
|
|
2691
2530
|
keybindingsPath: config.keybindingsPath ?? join(dshHome, 'omdsh', 'keybindings.json'),
|
|
2692
2531
|
});
|