@oh-my-pi/pi-coding-agent 16.2.2 → 16.2.3
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/CHANGELOG.md +49 -0
- package/dist/cli.js +3624 -3568
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/config/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +30 -1
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +7 -2
- package/dist/types/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +242 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/collab/guest.ts +43 -7
- package/src/config/model-registry.ts +80 -18
- package/src/config/settings-schema.ts +77 -0
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/edit/hashline/diff.ts +57 -4
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +15 -6
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +144 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +23 -2
- package/src/modes/controllers/event-controller.ts +106 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +65 -5
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +7 -2
- package/src/modes/utils/ui-helpers.ts +41 -23
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +28 -24
- package/src/session/agent-session.ts +867 -428
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +113 -19
- package/src/session/session-persistence.ts +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +16 -3
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
package/src/debug/log-viewer.ts
CHANGED
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
extractPrintableText,
|
|
4
4
|
matchesKey,
|
|
5
5
|
padding,
|
|
6
|
+
parseSgrMouse,
|
|
6
7
|
replaceTabs,
|
|
7
8
|
truncateToWidth,
|
|
8
9
|
visibleWidth,
|
|
9
10
|
} from "@oh-my-pi/pi-tui";
|
|
10
11
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { bottomBorder, divider, row, topBorder } from "../modes/components/overlay-box";
|
|
11
13
|
import { theme } from "../modes/theme/theme";
|
|
12
14
|
import { copyToClipboard } from "../utils/clipboard";
|
|
13
15
|
import {
|
|
@@ -23,6 +25,8 @@ export const LOAD_OLDER_LABEL = "### MOVE UP TO LOAD MORE...";
|
|
|
23
25
|
|
|
24
26
|
const INITIAL_LOG_CHUNK = 50;
|
|
25
27
|
const LOAD_OLDER_CHUNK = 50;
|
|
28
|
+
const MIN_LOG_VIEWER_WIDTH = 48;
|
|
29
|
+
const LOG_VIEWER_CHROME_LINES = 8;
|
|
26
30
|
|
|
27
31
|
type LogEntry = {
|
|
28
32
|
rawLine: string;
|
|
@@ -184,6 +188,31 @@ export class DebugLogViewerModel {
|
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
moveCursorToRow(rowIndex: number, extendSelection: boolean): boolean {
|
|
192
|
+
const selectableIndex = this.#selectableRowIndices.indexOf(rowIndex);
|
|
193
|
+
if (selectableIndex < 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (extendSelection && this.#selectionAnchorSelectableIndex === undefined) {
|
|
198
|
+
const row = this.#getCursorRow();
|
|
199
|
+
if (row?.kind === "log") {
|
|
200
|
+
this.#selectionAnchorSelectableIndex = this.#cursorSelectableIndex;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.#cursorSelectableIndex = selectableIndex;
|
|
205
|
+
|
|
206
|
+
if (!extendSelection) {
|
|
207
|
+
this.#selectionAnchorSelectableIndex = undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (this.#getCursorRow()?.kind !== "log" && !extendSelection) {
|
|
211
|
+
this.#selectionAnchorSelectableIndex = undefined;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
187
216
|
getSelectedLogIndices(): number[] {
|
|
188
217
|
if (this.#selectableRowIndices.length === 0) {
|
|
189
218
|
return [];
|
|
@@ -484,6 +513,9 @@ export class DebugLogViewerComponent implements Component {
|
|
|
484
513
|
#scrollRowOffset = 0;
|
|
485
514
|
#statusMessage: string | undefined;
|
|
486
515
|
#loadingOlder = false;
|
|
516
|
+
#bodyRowStart = 0;
|
|
517
|
+
#bodyRowCount = 0;
|
|
518
|
+
#bodyLineToRowIndex: Array<number | undefined> = [];
|
|
487
519
|
|
|
488
520
|
constructor(options: DebugLogViewerComponentOptions) {
|
|
489
521
|
this.#logSource = options.logSource;
|
|
@@ -501,6 +533,10 @@ export class DebugLogViewerComponent implements Component {
|
|
|
501
533
|
}
|
|
502
534
|
|
|
503
535
|
handleInput(keyData: string): void {
|
|
536
|
+
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
504
540
|
if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
505
541
|
this.#onExit();
|
|
506
542
|
return;
|
|
@@ -598,59 +634,99 @@ export class DebugLogViewerComponent implements Component {
|
|
|
598
634
|
}
|
|
599
635
|
}
|
|
600
636
|
|
|
637
|
+
#handleMouse(keyData: string): boolean {
|
|
638
|
+
const event = parseSgrMouse(keyData);
|
|
639
|
+
if (!event) return false;
|
|
640
|
+
|
|
641
|
+
const overBody = event.row >= this.#bodyRowStart && event.row < this.#bodyRowStart + this.#bodyRowCount;
|
|
642
|
+
if (event.wheel !== null && overBody) {
|
|
643
|
+
this.#statusMessage = undefined;
|
|
644
|
+
const maxOffset = Math.max(0, this.#model.rows.length - this.#bodyHeight());
|
|
645
|
+
this.#scrollRowOffset = Math.max(0, Math.min(maxOffset, this.#scrollRowOffset + event.wheel * 3));
|
|
646
|
+
this.#onUpdate?.();
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (!event.leftClick || !overBody) return false;
|
|
651
|
+
const rowIndex = this.#bodyLineToRowIndex[event.row - this.#bodyRowStart];
|
|
652
|
+
if (rowIndex === undefined) return false;
|
|
653
|
+
|
|
654
|
+
const target = this.#model.rows[rowIndex];
|
|
655
|
+
if (!target || target.kind === "warning") return false;
|
|
656
|
+
this.#statusMessage = undefined;
|
|
657
|
+
this.#model.moveCursorToRow(rowIndex, false);
|
|
658
|
+
if (target.kind === "load-older") {
|
|
659
|
+
void this.#handleLoadOlder();
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (this.#model.isExpanded(target.logIndex)) {
|
|
664
|
+
this.#model.collapseSelected();
|
|
665
|
+
} else {
|
|
666
|
+
this.#model.expandSelected();
|
|
667
|
+
}
|
|
668
|
+
this.#ensureCursorVisible();
|
|
669
|
+
this.#onUpdate?.();
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
|
|
601
673
|
invalidate(): void {
|
|
602
674
|
// no cached child state
|
|
603
675
|
}
|
|
604
676
|
|
|
605
677
|
render(width: number): readonly string[] {
|
|
606
|
-
this.#lastRenderWidth = Math.max(
|
|
678
|
+
this.#lastRenderWidth = Math.max(MIN_LOG_VIEWER_WIDTH, width);
|
|
607
679
|
this.#ensureCursorVisible();
|
|
608
680
|
|
|
609
|
-
const
|
|
681
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
610
682
|
const bodyHeight = this.#bodyHeight();
|
|
611
683
|
|
|
612
|
-
const rows = this.#renderRows(
|
|
613
|
-
|
|
684
|
+
const rows = this.#renderRows(contentWidth);
|
|
685
|
+
this.#bodyRowStart = 4;
|
|
686
|
+
this.#bodyRowCount = bodyHeight;
|
|
687
|
+
const visibleBodyLines = this.#renderVisibleBodyLines(rows, bodyHeight);
|
|
614
688
|
|
|
615
689
|
return [
|
|
616
|
-
this.#
|
|
617
|
-
|
|
618
|
-
this.#
|
|
619
|
-
|
|
620
|
-
this.#frameSeparator(innerWidth),
|
|
690
|
+
topBorder(this.#lastRenderWidth, "Recent Logs"),
|
|
691
|
+
row(this.#summaryText(), this.#lastRenderWidth),
|
|
692
|
+
row(this.#filterText(), this.#lastRenderWidth),
|
|
693
|
+
divider(this.#lastRenderWidth),
|
|
621
694
|
...visibleBodyLines,
|
|
622
|
-
|
|
623
|
-
this.#
|
|
695
|
+
divider(this.#lastRenderWidth),
|
|
696
|
+
row(this.#statusText(), this.#lastRenderWidth),
|
|
697
|
+
row(theme.fg("dim", this.#controlsText()), this.#lastRenderWidth),
|
|
698
|
+
bottomBorder(this.#lastRenderWidth),
|
|
624
699
|
];
|
|
625
700
|
}
|
|
626
701
|
|
|
627
702
|
#summaryText(): string {
|
|
628
|
-
|
|
703
|
+
const selected = this.#model.getSelectedCount();
|
|
704
|
+
const expanded = this.#model.expandedCount;
|
|
705
|
+
return `${theme.fg("muted", "showing")} ${theme.fg("accent", `${this.#model.visibleLogCount}/${this.#model.logCount}`)} ${theme.fg("muted", "selected")} ${theme.fg(selected > 0 ? "accent" : "muted", String(selected))} ${theme.fg("muted", "expanded")} ${theme.fg(expanded > 0 ? "accent" : "muted", String(expanded))}`;
|
|
629
706
|
}
|
|
630
707
|
|
|
631
708
|
#controlsText(): string {
|
|
632
|
-
return "Esc
|
|
709
|
+
return "Esc close · Ctrl+C copy · ↑/↓/wheel move · click toggle · Shift+↑/↓ select · ←/→ collapse/expand · Ctrl+A all · Ctrl+O older · Ctrl+P pid";
|
|
633
710
|
}
|
|
634
711
|
|
|
635
712
|
#filterText(): string {
|
|
636
713
|
const sanitized = replaceTabs(sanitizeText(this.#model.filterQuery));
|
|
637
|
-
const query = sanitized.length === 0 ? "" : theme.fg("accent", sanitized);
|
|
714
|
+
const query = sanitized.length === 0 ? theme.fg("muted", "type to filter") : theme.fg("accent", sanitized);
|
|
638
715
|
const pidStatus = this.#model.isProcessFilterEnabled()
|
|
639
|
-
? theme.fg("success", "pid
|
|
640
|
-
: theme.fg("muted", "pid
|
|
641
|
-
|
|
716
|
+
? theme.fg("success", "pid on")
|
|
717
|
+
: theme.fg("muted", "pid off");
|
|
718
|
+
const loading = this.#loadingOlder ? ` ${theme.fg("warning", "loading older…")}` : "";
|
|
719
|
+
return `${theme.fg("muted", "filter")} ${query} ${pidStatus}${loading}`;
|
|
642
720
|
}
|
|
643
721
|
|
|
644
722
|
#statusText(): string {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
return base;
|
|
723
|
+
return this.#statusMessage
|
|
724
|
+
? theme.fg("success", this.#statusMessage)
|
|
725
|
+
: theme.fg("dim", "Enter loads older when highlighted; printable keys update filter");
|
|
650
726
|
}
|
|
651
727
|
|
|
652
728
|
#bodyHeight(): number {
|
|
653
|
-
return Math.max(3, this.#terminalRows -
|
|
729
|
+
return Math.max(3, (process.stdout.rows || this.#terminalRows || 24) - LOG_VIEWER_CHROME_LINES);
|
|
654
730
|
}
|
|
655
731
|
|
|
656
732
|
async #handleLoadOlder(additionalCount: number = LOAD_OLDER_CHUNK): Promise<void> {
|
|
@@ -780,26 +856,25 @@ export class DebugLogViewerComponent implements Component {
|
|
|
780
856
|
return rendered;
|
|
781
857
|
}
|
|
782
858
|
|
|
783
|
-
#renderVisibleBodyLines(
|
|
784
|
-
|
|
785
|
-
innerWidth: number,
|
|
786
|
-
bodyHeight: number,
|
|
787
|
-
): string[] {
|
|
859
|
+
#renderVisibleBodyLines(rows: Array<{ lines: string[]; rowIndex: number }>, bodyHeight: number): string[] {
|
|
860
|
+
this.#bodyLineToRowIndex = [];
|
|
788
861
|
const lines: string[] = [];
|
|
789
862
|
if (rows.length === 0) {
|
|
790
|
-
|
|
863
|
+
this.#bodyLineToRowIndex.push(undefined);
|
|
864
|
+
lines.push(row(theme.fg("muted", "no matches"), this.#lastRenderWidth));
|
|
791
865
|
}
|
|
792
866
|
for (let i = this.#scrollRowOffset; i < rows.length; i++) {
|
|
793
|
-
const
|
|
794
|
-
if (!
|
|
867
|
+
const renderedRow = rows[i];
|
|
868
|
+
if (!renderedRow) {
|
|
795
869
|
continue;
|
|
796
870
|
}
|
|
797
871
|
|
|
798
|
-
for (const line of
|
|
872
|
+
for (const line of renderedRow.lines) {
|
|
799
873
|
if (lines.length >= bodyHeight) {
|
|
800
874
|
break;
|
|
801
875
|
}
|
|
802
|
-
|
|
876
|
+
this.#bodyLineToRowIndex.push(renderedRow.rowIndex);
|
|
877
|
+
lines.push(row(line, this.#lastRenderWidth));
|
|
803
878
|
}
|
|
804
879
|
|
|
805
880
|
if (lines.length >= bodyHeight) {
|
|
@@ -808,7 +883,8 @@ export class DebugLogViewerComponent implements Component {
|
|
|
808
883
|
}
|
|
809
884
|
|
|
810
885
|
while (lines.length < bodyHeight) {
|
|
811
|
-
|
|
886
|
+
this.#bodyLineToRowIndex.push(undefined);
|
|
887
|
+
lines.push(row("", this.#lastRenderWidth));
|
|
812
888
|
}
|
|
813
889
|
|
|
814
890
|
return lines;
|
|
@@ -840,7 +916,7 @@ export class DebugLogViewerComponent implements Component {
|
|
|
840
916
|
return;
|
|
841
917
|
}
|
|
842
918
|
const bodyHeight = Math.max(1, this.#bodyHeight());
|
|
843
|
-
const innerWidth = Math.max(1, this.#lastRenderWidth -
|
|
919
|
+
const innerWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
844
920
|
|
|
845
921
|
// Scroll up: cursor is above viewport
|
|
846
922
|
if (cursorRowIndex < this.#scrollRowOffset) {
|
|
@@ -865,24 +941,6 @@ export class DebugLogViewerComponent implements Component {
|
|
|
865
941
|
}
|
|
866
942
|
}
|
|
867
943
|
|
|
868
|
-
#frameTop(innerWidth: number): string {
|
|
869
|
-
return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
#frameSeparator(innerWidth: number): string {
|
|
873
|
-
return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
#frameBottom(innerWidth: number): string {
|
|
877
|
-
return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
#frameLine(content: string, innerWidth: number): string {
|
|
881
|
-
const truncated = truncateToWidth(content, innerWidth);
|
|
882
|
-
const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
883
|
-
return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
944
|
#copySelected() {
|
|
887
945
|
const selectedPayload = buildLogCopyPayload(this.#model.getSelectedRawLines());
|
|
888
946
|
const selected = selectedPayload.length === 0 ? [] : selectedPayload.split("\n");
|
package/src/debug/raw-sse.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Component,
|
|
3
|
-
matchesKey,
|
|
4
|
-
padding,
|
|
5
|
-
replaceTabs,
|
|
6
|
-
ScrollView,
|
|
7
|
-
truncateToWidth,
|
|
8
|
-
visibleWidth,
|
|
9
|
-
} from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { type Component, matchesKey, parseSgrMouse, replaceTabs, ScrollView, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
10
2
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { bottomBorder, divider, row, topBorder } from "../modes/components/overlay-box";
|
|
11
4
|
import { theme } from "../modes/theme/theme";
|
|
12
5
|
import { copyToClipboard } from "../utils/clipboard";
|
|
13
6
|
import {
|
|
@@ -17,8 +10,8 @@ import {
|
|
|
17
10
|
rawSseRecordLines,
|
|
18
11
|
} from "./raw-sse-buffer";
|
|
19
12
|
|
|
20
|
-
const MIN_VIEWER_WIDTH =
|
|
21
|
-
const
|
|
13
|
+
const MIN_VIEWER_WIDTH = 40;
|
|
14
|
+
const VIEWER_CHROME_LINES = 6;
|
|
22
15
|
// `data:` lines below this width render fine on a single row; anything wider gets pretty-printed
|
|
23
16
|
// across multiple `data:` lines so streamed JSON blobs stop getting clipped by `truncateToWidth`.
|
|
24
17
|
const PRETTY_PRINT_DATA_THRESHOLD = 100;
|
|
@@ -79,6 +72,8 @@ export class RawSseViewerComponent implements Component {
|
|
|
79
72
|
#followTail = true;
|
|
80
73
|
#lastRenderWidth = MIN_VIEWER_WIDTH;
|
|
81
74
|
#statusMessage: string | undefined;
|
|
75
|
+
#bodyRowStart = 0;
|
|
76
|
+
#bodyRowCount = 0;
|
|
82
77
|
// Pretty-printed wire lines keyed by `record.sequence`. Pretty-printing is
|
|
83
78
|
// the JSON.parse + JSON.stringify per `data:` line, so we cache the result —
|
|
84
79
|
// the render path runs on every keypress and from `#maxScrollOffset()`.
|
|
@@ -98,9 +93,17 @@ export class RawSseViewerComponent implements Component {
|
|
|
98
93
|
});
|
|
99
94
|
}
|
|
100
95
|
|
|
96
|
+
dispose(): void {
|
|
97
|
+
this.#unsubscribe();
|
|
98
|
+
}
|
|
99
|
+
|
|
101
100
|
handleInput(keyData: string): void {
|
|
101
|
+
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
102
105
|
if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
103
|
-
this
|
|
106
|
+
this.dispose();
|
|
104
107
|
this.#onExit();
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
@@ -145,15 +148,44 @@ export class RawSseViewerComponent implements Component {
|
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
#handleMouse(keyData: string): boolean {
|
|
152
|
+
const event = parseSgrMouse(keyData);
|
|
153
|
+
if (!event) return false;
|
|
154
|
+
|
|
155
|
+
const overBody = event.row >= this.#bodyRowStart && event.row < this.#bodyRowStart + this.#bodyRowCount;
|
|
156
|
+
if (event.wheel !== null && overBody) {
|
|
157
|
+
this.#followTail = false;
|
|
158
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#maxScrollOffset(), this.#scrollOffset + event.wheel * 3));
|
|
159
|
+
this.#onUpdate?.();
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!event.leftClick) return false;
|
|
164
|
+
if (event.row === 1) {
|
|
165
|
+
this.#followTail = !this.#followTail;
|
|
166
|
+
this.#followIfNeeded();
|
|
167
|
+
this.#onUpdate?.();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (overBody) {
|
|
171
|
+
this.#followTail = false;
|
|
172
|
+
const clickedOffset = this.#scrollOffset + event.row - this.#bodyRowStart;
|
|
173
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#maxScrollOffset(), clickedOffset));
|
|
174
|
+
this.#onUpdate?.();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
148
180
|
invalidate(): void {}
|
|
149
181
|
|
|
150
182
|
render(width: number): readonly string[] {
|
|
151
183
|
this.#lastRenderWidth = Math.max(MIN_VIEWER_WIDTH, width);
|
|
152
184
|
this.#followIfNeeded();
|
|
153
185
|
|
|
154
|
-
const
|
|
186
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
155
187
|
const bodyHeight = this.#bodyHeight();
|
|
156
|
-
const rawLines = this.#renderRawLines(
|
|
188
|
+
const rawLines = this.#renderRawLines(contentWidth);
|
|
157
189
|
const sv = new ScrollView(rawLines.slice(this.#scrollOffset, this.#scrollOffset + bodyHeight), {
|
|
158
190
|
height: bodyHeight,
|
|
159
191
|
scrollbar: "auto",
|
|
@@ -161,15 +193,18 @@ export class RawSseViewerComponent implements Component {
|
|
|
161
193
|
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
162
194
|
});
|
|
163
195
|
sv.setScrollOffset(this.#scrollOffset);
|
|
164
|
-
const bodyRows = sv.render(
|
|
196
|
+
const bodyRows = sv.render(contentWidth);
|
|
197
|
+
this.#bodyRowStart = 3;
|
|
198
|
+
this.#bodyRowCount = bodyHeight;
|
|
165
199
|
|
|
166
200
|
return [
|
|
167
|
-
this.#
|
|
168
|
-
|
|
169
|
-
this.#
|
|
170
|
-
...bodyRows.map(line =>
|
|
171
|
-
|
|
172
|
-
this.#
|
|
201
|
+
topBorder(this.#lastRenderWidth, "Raw Provider Stream"),
|
|
202
|
+
row(this.#summaryText(), this.#lastRenderWidth),
|
|
203
|
+
divider(this.#lastRenderWidth),
|
|
204
|
+
...bodyRows.map(line => row(line, this.#lastRenderWidth)),
|
|
205
|
+
divider(this.#lastRenderWidth),
|
|
206
|
+
row(this.#statusText(), this.#lastRenderWidth),
|
|
207
|
+
bottomBorder(this.#lastRenderWidth),
|
|
173
208
|
];
|
|
174
209
|
}
|
|
175
210
|
|
|
@@ -221,22 +256,25 @@ export class RawSseViewerComponent implements Component {
|
|
|
221
256
|
if (key < firstSequence) this.#prettyLinesCache.delete(key);
|
|
222
257
|
}
|
|
223
258
|
}
|
|
224
|
-
|
|
225
259
|
#summaryText(): string {
|
|
226
260
|
const snapshot = this.#buffer.snapshot();
|
|
227
|
-
const last = snapshot.lastUpdatedAt
|
|
228
|
-
|
|
229
|
-
|
|
261
|
+
const last = snapshot.lastUpdatedAt
|
|
262
|
+
? `${theme.fg("muted", "last")} ${theme.fg("accent", formatRawSseIsoTime(snapshot.lastUpdatedAt))}`
|
|
263
|
+
: theme.fg("muted", "waiting for first frame");
|
|
264
|
+
const follow = this.#followTail ? theme.fg("success", "follow on") : theme.fg("warning", "follow off");
|
|
265
|
+
return `${theme.fg("muted", "events")} ${theme.fg("accent", String(snapshot.totalEvents))} ${theme.fg("muted", "records")} ${theme.fg("accent", String(snapshot.records.length))} ${last} ${follow}`;
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
#statusText(): string {
|
|
233
|
-
|
|
269
|
+
const help = "Esc close · Ctrl+C copy raw · End follow tail · wheel scroll · click summary toggles follow";
|
|
270
|
+
return this.#statusMessage
|
|
271
|
+
? `${theme.fg("success", this.#statusMessage)} ${theme.fg("dim", help)}`
|
|
272
|
+
: theme.fg("dim", help);
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
#bodyHeight(): number {
|
|
237
|
-
return Math.max(3, this.#terminalRows -
|
|
276
|
+
return Math.max(3, (process.stdout.rows || this.#terminalRows || 24) - VIEWER_CHROME_LINES);
|
|
238
277
|
}
|
|
239
|
-
|
|
240
278
|
#followIfNeeded(): void {
|
|
241
279
|
if (this.#followTail) this.#scrollToTail();
|
|
242
280
|
}
|
|
@@ -246,8 +284,8 @@ export class RawSseViewerComponent implements Component {
|
|
|
246
284
|
}
|
|
247
285
|
|
|
248
286
|
#maxScrollOffset(): number {
|
|
249
|
-
const
|
|
250
|
-
return Math.max(0, this.#renderRawLines(
|
|
287
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
288
|
+
return Math.max(0, this.#renderRawLines(contentWidth).length - this.#bodyHeight());
|
|
251
289
|
}
|
|
252
290
|
|
|
253
291
|
#copyAll(): void {
|
|
@@ -271,22 +309,4 @@ export class RawSseViewerComponent implements Component {
|
|
|
271
309
|
}
|
|
272
310
|
this.#onUpdate?.();
|
|
273
311
|
}
|
|
274
|
-
|
|
275
|
-
#frameTop(innerWidth: number): string {
|
|
276
|
-
return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
#frameSeparator(innerWidth: number): string {
|
|
280
|
-
return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
#frameBottom(innerWidth: number): string {
|
|
284
|
-
return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
#frameLine(content: string, innerWidth: number): string {
|
|
288
|
-
const truncated = truncateToWidth(content, innerWidth);
|
|
289
|
-
const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
290
|
-
return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
|
|
291
|
-
}
|
|
292
312
|
}
|
package/src/discovery/codex.ts
CHANGED
|
@@ -342,12 +342,20 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
342
342
|
const codexDir = getProjectCodexDir(ctx);
|
|
343
343
|
const projectHooksDir = path.join(codexDir, "hooks");
|
|
344
344
|
|
|
345
|
+
// OMP hooks must be named `pre-<tool>.<ts|js>` or `post-<tool>.<ts|js>`.
|
|
346
|
+
// Files without that prefix are not OMP hooks (e.g. the standalone Codex
|
|
347
|
+
// hook scripts users keep alongside) — silently dropping the prefix and
|
|
348
|
+
// defaulting to `pre:<basename>` caused those scripts to be imported as
|
|
349
|
+
// extension factories and any top-level `process.exit()` killed startup
|
|
350
|
+
// (#3680).
|
|
345
351
|
const transformHook =
|
|
346
|
-
(level: "user" | "project") =>
|
|
352
|
+
(level: "user" | "project") =>
|
|
353
|
+
(name: string, _content: string, path: string, source: SourceMeta): Hook | null => {
|
|
347
354
|
const baseName = name.replace(/\.(ts|js)$/, "");
|
|
348
355
|
const match = baseName.match(/^(pre|post)-(.+)$/);
|
|
349
|
-
|
|
350
|
-
const
|
|
356
|
+
if (!match) return null;
|
|
357
|
+
const hookType = match[1] as "pre" | "post";
|
|
358
|
+
const toolName = match[2];
|
|
351
359
|
return {
|
|
352
360
|
name,
|
|
353
361
|
path,
|
|
@@ -359,11 +367,11 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
359
367
|
};
|
|
360
368
|
|
|
361
369
|
const results = await Promise.all([
|
|
362
|
-
loadFilesFromDir(ctx, userHooksDir, PROVIDER_ID, "user", {
|
|
370
|
+
loadFilesFromDir<Hook>(ctx, userHooksDir, PROVIDER_ID, "user", {
|
|
363
371
|
extensions: ["ts", "js"],
|
|
364
372
|
transform: transformHook("user"),
|
|
365
373
|
}),
|
|
366
|
-
loadFilesFromDir(ctx, projectHooksDir, PROVIDER_ID, "project", {
|
|
374
|
+
loadFilesFromDir<Hook>(ctx, projectHooksDir, PROVIDER_ID, "project", {
|
|
367
375
|
extensions: ["ts", "js"],
|
|
368
376
|
transform: transformHook("project"),
|
|
369
377
|
}),
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* match is accepted even when the tag was minted by a source that did not keep
|
|
10
10
|
* history, and stale tags recover through the session snapshot store when possible.
|
|
11
11
|
*/
|
|
12
|
+
import * as path from "node:path";
|
|
12
13
|
import {
|
|
13
14
|
type ApplyResult,
|
|
14
15
|
applyEdits,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
} from "@oh-my-pi/hashline";
|
|
31
32
|
import { resolveToCwd } from "../../tools/path-utils";
|
|
32
33
|
import { generateDiffString } from "../diff";
|
|
34
|
+
import { canonicalSnapshotKey } from "../file-snapshot-store";
|
|
33
35
|
import { readEditFileText } from "../read-file";
|
|
34
36
|
import { nativeBlockResolver } from "./block-resolver";
|
|
35
37
|
|
|
@@ -90,6 +92,54 @@ async function readSectionTextCached(absolutePath: string, sectionPath: string):
|
|
|
90
92
|
return rawContent;
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a missing authored path to a file read this session by matching its
|
|
97
|
+
* basename and snapshot tag, mirroring {@link Patcher}'s apply-time recovery so
|
|
98
|
+
* a bare/wrong-directory `[basename#tag]` header previews against the same file
|
|
99
|
+
* the edit will land on. Returns `undefined` when no unique basename+tag match
|
|
100
|
+
* exists, leaving the caller to surface the original read error.
|
|
101
|
+
*/
|
|
102
|
+
function recoverSectionPathFromTag(
|
|
103
|
+
section: PatchSection,
|
|
104
|
+
authoredAbsolutePath: string,
|
|
105
|
+
snapshots: SnapshotStore,
|
|
106
|
+
): string | undefined {
|
|
107
|
+
if (section.fileHash === undefined) return undefined;
|
|
108
|
+
const authoredName = path.basename(section.path);
|
|
109
|
+
const authoredKey = canonicalSnapshotKey(authoredAbsolutePath);
|
|
110
|
+
const candidates = [
|
|
111
|
+
...new Set(
|
|
112
|
+
snapshots
|
|
113
|
+
.findByHash(section.fileHash)
|
|
114
|
+
.filter(snapshot => path.basename(snapshot.path) === authoredName)
|
|
115
|
+
.map(snapshot => snapshot.path),
|
|
116
|
+
),
|
|
117
|
+
].filter(candidate => candidate !== authoredKey);
|
|
118
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Read the section's target file for a preview, recovering a bare/mis-typed
|
|
123
|
+
* `[basename#tag]` path onto the file its tag uniquely names. Recovery fires
|
|
124
|
+
* only when the authored path is absent — matching {@link Patcher}'s apply-time
|
|
125
|
+
* order — so a permission/parse error on an existing file surfaces against the
|
|
126
|
+
* authored path instead of silently previewing a different tagged file. Returns
|
|
127
|
+
* the path actually read so callers key snapshot lookups off the same file.
|
|
128
|
+
*/
|
|
129
|
+
async function readSectionForPreview(
|
|
130
|
+
section: PatchSection,
|
|
131
|
+
authoredAbsolutePath: string,
|
|
132
|
+
snapshots: SnapshotStore,
|
|
133
|
+
streaming: boolean | undefined,
|
|
134
|
+
): Promise<{ absolutePath: string; rawContent: string }> {
|
|
135
|
+
const read = streaming ? readSectionTextCached : readSectionText;
|
|
136
|
+
const recovered = (await Bun.file(authoredAbsolutePath).exists())
|
|
137
|
+
? undefined
|
|
138
|
+
: recoverSectionPathFromTag(section, authoredAbsolutePath, snapshots);
|
|
139
|
+
const target = recovered ?? authoredAbsolutePath;
|
|
140
|
+
return { absolutePath: target, rawContent: await read(target, section.path) };
|
|
141
|
+
}
|
|
142
|
+
|
|
93
143
|
function hasAnchorScopedEdit(edits: readonly Edit[]): boolean {
|
|
94
144
|
return edits.some(edit => {
|
|
95
145
|
if (edit.kind === "delete") return true;
|
|
@@ -258,10 +308,13 @@ export async function computeHashlineSectionDiff(
|
|
|
258
308
|
options: HashlineDiffOptions = {},
|
|
259
309
|
): Promise<{ diff: string; firstChangedLine: number | undefined } | { error: string }> {
|
|
260
310
|
try {
|
|
261
|
-
const
|
|
262
|
-
const rawContent =
|
|
263
|
-
|
|
264
|
-
|
|
311
|
+
const authoredPath = resolveToCwd(section.path, cwd);
|
|
312
|
+
const { absolutePath, rawContent } = await readSectionForPreview(
|
|
313
|
+
section,
|
|
314
|
+
authoredPath,
|
|
315
|
+
snapshots,
|
|
316
|
+
options.streaming,
|
|
317
|
+
);
|
|
265
318
|
const { text: content } = stripBom(rawContent);
|
|
266
319
|
const normalized = normalizeToLF(content);
|
|
267
320
|
// Streaming favors a stable, monotonic preview over an exact unified
|
|
@@ -29,6 +29,7 @@ export class LocalModuleLoader {
|
|
|
29
29
|
#externalModules = new Map<string, Promise<vm.Module>>();
|
|
30
30
|
#requireCache = new Map<string, NodeJS.Require>();
|
|
31
31
|
#modulePaths = new WeakMap<vm.Module, string>();
|
|
32
|
+
#linkChain: Promise<void> = Promise.resolve();
|
|
32
33
|
|
|
33
34
|
constructor(sessionId: string) {
|
|
34
35
|
this.#context = vm.createContext(globalThis);
|
|
@@ -147,7 +148,17 @@ export class LocalModuleLoader {
|
|
|
147
148
|
async #linkAndEvaluate(entry: LocalModuleEntry, modulePath: string): Promise<void> {
|
|
148
149
|
const { module } = entry;
|
|
149
150
|
try {
|
|
150
|
-
|
|
151
|
+
// Serialize the link phase across every graph root. Bun's node:vm linker
|
|
152
|
+
// segfaults (getImportedModule on a null record) when two link passes
|
|
153
|
+
// instantiate overlapping module instances concurrently — e.g.
|
|
154
|
+
// Promise.all([import("./a"), import("./b")]) over a graph that shares
|
|
155
|
+
// dependencies. Holding the lock for the whole module.link() (including its
|
|
156
|
+
// async resolver callbacks) guarantees the linker is never re-entered
|
|
157
|
+
// mid-instantiation. The lock is released before evaluate(), so a dynamic
|
|
158
|
+
// import during evaluation can re-acquire it without deadlock.
|
|
159
|
+
await this.#serializeLink(async () => {
|
|
160
|
+
if (module.status === "unlinked") await module.link(this.#linkResolve);
|
|
161
|
+
});
|
|
151
162
|
if (module.status === "linked") await module.evaluate();
|
|
152
163
|
} catch (error) {
|
|
153
164
|
this.#invalidateFailedLoad(modulePath);
|
|
@@ -159,6 +170,17 @@ export class LocalModuleLoader {
|
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
|
|
173
|
+
// Promise-chain mutex serializing node:vm link passes (see #linkAndEvaluate).
|
|
174
|
+
// #linkChain is kept non-rejecting so a failed link never wedges the queue.
|
|
175
|
+
#serializeLink<T>(run: () => Promise<T>): Promise<T> {
|
|
176
|
+
const result = this.#linkChain.then(run);
|
|
177
|
+
this.#linkChain = result.then(
|
|
178
|
+
() => undefined,
|
|
179
|
+
() => undefined,
|
|
180
|
+
);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
162
184
|
// Shared static-link resolver for `module.link()`. node:vm passes the referencing
|
|
163
185
|
// module and reuses this one resolver for the entire graph, so the referrer path is
|
|
164
186
|
// recovered from `#modulePaths`. Local dependencies are constructed but NOT linked or
|
|
@@ -1236,7 +1236,7 @@
|
|
|
1236
1236
|
let html = `
|
|
1237
1237
|
<div class="header">
|
|
1238
1238
|
<h1>Session: ${escapeHtml(header?.id || 'unknown')}</h1>
|
|
1239
|
-
<div class="help-bar">
|
|
1239
|
+
<div class="help-bar">T toggle thinking · O toggle tools</div>
|
|
1240
1240
|
<div class="header-info">
|
|
1241
1241
|
<div class="info-item"><span class="info-label">Date:</span><span class="info-value">${header?.timestamp ? new Date(header.timestamp).toLocaleString() : 'unknown'}</span></div>
|
|
1242
1242
|
<div class="info-item"><span class="info-label">Models:</span><span class="info-value">${globalStats.models.join(', ') || 'unknown'}</span></div>
|
|
@@ -1580,13 +1580,19 @@
|
|
|
1580
1580
|
searchQuery = '';
|
|
1581
1581
|
navigateTo(leafId, 'bottom');
|
|
1582
1582
|
}
|
|
1583
|
-
if (e.
|
|
1583
|
+
if (e.key === 't' || e.key === 'T' || e.key === 'o' || e.key === 'O') {
|
|
1584
|
+
// Skip when typing in the sidebar search (or any other editable target)
|
|
1585
|
+
// so the chord can't fire on a user's letter input. Avoid Ctrl/Cmd-based
|
|
1586
|
+
// chords entirely — every major browser reserves Ctrl+T (new tab) and
|
|
1587
|
+
// Ctrl+O (open file), so the shortcut would never reach the page.
|
|
1588
|
+
const t = e.target;
|
|
1589
|
+
const editable =
|
|
1590
|
+
t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable);
|
|
1591
|
+
if (editable) return;
|
|
1592
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
1584
1593
|
e.preventDefault();
|
|
1585
|
-
toggleThinking();
|
|
1586
|
-
|
|
1587
|
-
if (e.ctrlKey && e.key === 'o') {
|
|
1588
|
-
e.preventDefault();
|
|
1589
|
-
toggleToolOutputs();
|
|
1594
|
+
if (e.key === 't' || e.key === 'T') toggleThinking();
|
|
1595
|
+
else toggleToolOutputs();
|
|
1590
1596
|
}
|
|
1591
1597
|
});
|
|
1592
1598
|
|