@oh-my-pi/pi-tui 18.2.11 → 18.3.1
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 +44 -0
- package/THIRD-PARTY-NOTICES.txt +3 -3
- package/dist/types/apps/ps-data.d.ts +1 -1
- package/dist/types/apps/ps-top.d.ts +1 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +1 -1
- package/dist/types/chat/read-tool-group.d.ts +3 -3
- package/dist/types/chat/user-message.d.ts +4 -1
- package/dist/types/components/editor.d.ts +0 -2
- package/dist/types/components/input.d.ts +16 -0
- package/dist/types/overlays/annotation-overlay.d.ts +30 -0
- package/dist/types/overlays/annotation-types.d.ts +84 -0
- package/dist/types/overlays/btw-history-panel.d.ts +4 -1
- package/dist/types/overlays/copy-selector.d.ts +30 -2
- package/dist/types/overlays/copy-targets.d.ts +2 -0
- package/dist/types/overlays/model-hub.d.ts +2 -2
- package/dist/types/overlays/oauth-selector.d.ts +3 -6
- package/dist/types/overlays/settings-defs.d.ts +5 -0
- package/dist/types/overlays/usage-dashboard.d.ts +2 -0
- package/dist/types/prompt/composer.d.ts +12 -1
- package/dist/types/prompt/custom-editor.d.ts +5 -26
- package/dist/types/prompt/image-source.d.ts +9 -8
- package/dist/types/space-hold.d.ts +45 -0
- package/dist/types/status-line/context-usage.d.ts +3 -1
- package/dist/types/status-line/host.d.ts +2 -0
- package/dist/types/theme/mermaid-cache.d.ts +4 -6
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/cfg-render.d.ts +37 -0
- package/dist/types/tools/cfg-url.d.ts +16 -0
- package/dist/types/tools/daemon.d.ts +46 -0
- package/dist/types/tools/find.d.ts +2 -2
- package/dist/types/tools/irc.d.ts +30 -0
- package/dist/types/tools/line-ranges.d.ts +1 -1
- package/dist/types/tools/output-meta.d.ts +2 -0
- package/dist/types/tools/proc-render.d.ts +27 -0
- package/dist/types/tools/read.d.ts +18 -4
- package/dist/types/tools/result-card.d.ts +20 -0
- package/dist/types/tools/url-scheme-host.d.ts +29 -0
- package/dist/types/tools/wait.d.ts +131 -0
- package/dist/types/tools/write.d.ts +6 -0
- package/dist/types/tools/xd-url.d.ts +8 -0
- package/dist/types/tools/xdev.d.ts +9 -1
- package/dist/types/windows-altgr.d.ts +54 -0
- package/package.json +13 -10
- package/src/apps/debug/protocol-probe.ts +1 -1
- package/src/apps/ps-data.ts +1 -1
- package/src/apps/ps-top.ts +1 -1
- package/src/chat/chat-transcript-builder.ts +9 -5
- package/src/chat/read-tool-group.ts +8 -9
- package/src/chat/tool-execution.ts +15 -13
- package/src/chat/transcript-render-helpers.ts +3 -3
- package/src/chat/user-message.ts +16 -6
- package/src/components/editor.ts +2 -4
- package/src/components/input.ts +103 -30
- package/src/components/markdown.ts +19 -7
- package/src/desktop-notify.ts +1 -1
- package/src/overlays/annotation-overlay.ts +1144 -0
- package/src/overlays/annotation-types.ts +78 -0
- package/src/overlays/btw-history-panel.ts +4 -0
- package/src/overlays/copy-selector.ts +45 -19
- package/src/overlays/copy-targets.ts +3 -3
- package/src/overlays/model-hub.ts +5 -4
- package/src/overlays/oauth-selector.ts +8 -12
- package/src/overlays/plan-review-overlay.ts +213 -40
- package/src/overlays/settings-defs.ts +6 -0
- package/src/overlays/settings-selector.ts +4 -3
- package/src/overlays/usage-dashboard.ts +22 -1
- package/src/prompt/composer.ts +83 -56
- package/src/prompt/custom-editor.ts +15 -137
- package/src/prompt/image-source.ts +9 -8
- package/src/prompt/tips.txt +1 -0
- package/src/setup/scenes/sign-in.ts +1 -1
- package/src/space-hold.ts +143 -0
- package/src/status-line/component.ts +6 -0
- package/src/status-line/context-usage.ts +15 -3
- package/src/status-line/host.ts +2 -0
- package/src/status-line/segments.ts +4 -0
- package/src/terminal-capabilities.ts +2 -2
- package/src/terminal.ts +8 -1
- package/src/theme/mermaid-cache.ts +83 -25
- package/src/tools/bash.ts +14 -1
- package/src/tools/cfg-render.ts +117 -0
- package/src/tools/cfg-url.ts +30 -0
- package/src/tools/daemon.ts +51 -0
- package/src/tools/find.ts +7 -6
- package/src/tools/index.ts +2 -2
- package/src/tools/irc.ts +33 -0
- package/src/tools/line-ranges.ts +13 -3
- package/src/tools/output-meta.ts +2 -0
- package/src/tools/proc-render.ts +270 -0
- package/src/tools/read.ts +118 -56
- package/src/tools/result-card.ts +36 -0
- package/src/tools/url-scheme-host.ts +42 -0
- package/src/tools/wait.ts +595 -0
- package/src/tools/write.ts +120 -1
- package/src/tools/xd-url.ts +9 -0
- package/src/tools/xdev.ts +9 -1
- package/src/windows-altgr.ts +227 -0
- package/dist/types/chat/read-target.d.ts +0 -4
- package/dist/types/tools/hub.d.ts +0 -342
- package/src/chat/read-target.ts +0 -7
- package/src/tools/hub.ts +0 -1492
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import {
|
|
20
20
|
type Component,
|
|
21
21
|
Ellipsis,
|
|
22
|
-
|
|
22
|
+
Editor,
|
|
23
23
|
Markdown,
|
|
24
24
|
type MarkdownTheme,
|
|
25
25
|
matchesKey,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
} from "../index";
|
|
32
32
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
33
33
|
import { sanitizeStatusText } from "../chrome/shared";
|
|
34
|
-
import { getMarkdownTheme, theme } from "../theme/theme";
|
|
34
|
+
import { getEditorTheme, getMarkdownTheme, theme } from "../theme/theme";
|
|
35
35
|
import {
|
|
36
36
|
matchesAppExternalEditor,
|
|
37
37
|
matchesSelectCancel,
|
|
@@ -57,6 +57,8 @@ import { renderSegmentTrack } from "../chrome/segment-track";
|
|
|
57
57
|
const OVERLAY_TITLE = "Plan Review";
|
|
58
58
|
/** Minimum plan-body rows kept visible even on short terminals. */
|
|
59
59
|
const MIN_BODY_ROWS = 3;
|
|
60
|
+
/** Visible rows for the in-overlay annotation editor before it scrolls. */
|
|
61
|
+
const MAX_ANNOTATION_EDITOR_ROWS = 6;
|
|
60
62
|
/** Sidebar gates: enough headings, a wide terminal, and a usable body column. */
|
|
61
63
|
const SIDEBAR_MIN_HEADINGS = 2;
|
|
62
64
|
const SIDEBAR_MIN_TOTAL_WIDTH = 64;
|
|
@@ -93,6 +95,16 @@ interface BodyRowAnchor {
|
|
|
93
95
|
contextTruncated: boolean;
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
interface AnnotationSelection {
|
|
99
|
+
sectionIndex: number;
|
|
100
|
+
annotationIndex: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface AnnotationChooser {
|
|
104
|
+
entries: AnnotationSelection[];
|
|
105
|
+
selected: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
/** Serializable annotations retained by the plan-review owner between overlays. */
|
|
97
109
|
export interface PlanReviewAnnotationState {
|
|
98
110
|
annotations: Array<{
|
|
@@ -207,8 +219,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
207
219
|
/** Label of the committed choice, shown while the async approval settles. */
|
|
208
220
|
#committedLabel: string | undefined;
|
|
209
221
|
#annotating = false;
|
|
210
|
-
#
|
|
222
|
+
#editor: Editor;
|
|
211
223
|
#annotationTarget: BodyRowAnchor | { sectionIndex: number; row: null; context: null } | undefined;
|
|
224
|
+
#editingAnnotation: AnnotationSelection | undefined;
|
|
225
|
+
#annotationChooser: AnnotationChooser | undefined;
|
|
212
226
|
|
|
213
227
|
constructor(
|
|
214
228
|
planContent: string,
|
|
@@ -236,10 +250,11 @@ export class PlanReviewOverlay implements Component {
|
|
|
236
250
|
} else {
|
|
237
251
|
this.#sliderIndex = 0;
|
|
238
252
|
}
|
|
239
|
-
this.#
|
|
240
|
-
this.#
|
|
241
|
-
this.#
|
|
242
|
-
this.#
|
|
253
|
+
this.#editor = new Editor(getEditorTheme());
|
|
254
|
+
this.#editor.setBorderVisible(false);
|
|
255
|
+
this.#editor.setPromptGutter("> ");
|
|
256
|
+
this.#editor.setUseTerminalCursor(false);
|
|
257
|
+
this.#editor.onSubmit = value => this.#submitAnnotation(value);
|
|
243
258
|
this.#setSections(planContent);
|
|
244
259
|
this.#restoreAnnotationState(options.annotationState);
|
|
245
260
|
if (Array.isArray(options.annotationState?.annotations) && options.annotationState.annotations.length > 0) {
|
|
@@ -490,14 +505,22 @@ export class PlanReviewOverlay implements Component {
|
|
|
490
505
|
handleInput(keyData: string): void {
|
|
491
506
|
if (this.#committed) return;
|
|
492
507
|
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) return;
|
|
508
|
+
if (this.#annotationChooser) {
|
|
509
|
+
this.#handleAnnotationChooser(keyData);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
493
512
|
if (this.#annotating) {
|
|
494
513
|
if (this.callbacks.onAnnotationExternalEditor && matchesAppExternalEditor(keyData)) {
|
|
495
|
-
this.callbacks.onAnnotationExternalEditor(this.#
|
|
496
|
-
if (text !== null) this.#
|
|
514
|
+
this.callbacks.onAnnotationExternalEditor(this.#editor.getExpandedText(), text => {
|
|
515
|
+
if (text !== null && this.#annotating) this.#editor.setText(text);
|
|
497
516
|
});
|
|
498
517
|
return;
|
|
499
518
|
}
|
|
500
|
-
|
|
519
|
+
if (matchesSelectCancel(keyData)) {
|
|
520
|
+
this.#exitAnnotate();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
this.#editor.handleInput(keyData);
|
|
501
524
|
return;
|
|
502
525
|
}
|
|
503
526
|
if (matchesSelectCancel(keyData)) {
|
|
@@ -505,6 +528,14 @@ export class PlanReviewOverlay implements Component {
|
|
|
505
528
|
this.callbacks.onCancel();
|
|
506
529
|
return;
|
|
507
530
|
}
|
|
531
|
+
if (this.#focus !== "actions" && keyData === "u") {
|
|
532
|
+
this.#undoLast();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (this.#focus !== "actions" && keyData === "e") {
|
|
536
|
+
this.#editCurrentAnnotation();
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
508
539
|
if (this.callbacks.onExternalEditor && matchesAppExternalEditor(keyData)) {
|
|
509
540
|
this.callbacks.onExternalEditor();
|
|
510
541
|
return;
|
|
@@ -726,10 +757,6 @@ export class PlanReviewOverlay implements Component {
|
|
|
726
757
|
this.#startSectionAnnotate();
|
|
727
758
|
return;
|
|
728
759
|
}
|
|
729
|
-
if (data === "u") {
|
|
730
|
-
this.#undoLast();
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
760
|
}
|
|
734
761
|
|
|
735
762
|
#moveTocCursor(delta: number): void {
|
|
@@ -828,39 +855,145 @@ export class PlanReviewOverlay implements Component {
|
|
|
828
855
|
|
|
829
856
|
#startAnnotate(target: BodyRowAnchor | { sectionIndex: number; row: null; context: null }): void {
|
|
830
857
|
this.#annotationTarget = target;
|
|
858
|
+
this.#editingAnnotation = undefined;
|
|
859
|
+
this.#annotating = true;
|
|
860
|
+
this.#editor.setText("");
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
#startExistingAnnotation(selection: AnnotationSelection): void {
|
|
864
|
+
const section = this.#sections[selection.sectionIndex];
|
|
865
|
+
const annotation = section?.annotations[selection.annotationIndex];
|
|
866
|
+
if (!section || !annotation) return;
|
|
867
|
+
this.#annotationChooser = undefined;
|
|
868
|
+
this.#editingAnnotation = selection;
|
|
869
|
+
this.#annotationTarget =
|
|
870
|
+
annotation.target.kind === "section"
|
|
871
|
+
? { sectionIndex: selection.sectionIndex, row: null, context: null }
|
|
872
|
+
: {
|
|
873
|
+
sectionIndex: selection.sectionIndex,
|
|
874
|
+
row: annotation.target.row,
|
|
875
|
+
context: annotation.target.context,
|
|
876
|
+
contextTruncated: annotation.target.contextTruncated,
|
|
877
|
+
};
|
|
831
878
|
this.#annotating = true;
|
|
832
|
-
this.#
|
|
879
|
+
this.#editor.setText(annotation.note);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
#annotationMatchesBodyAnchor(annotation: OverlayAnnotation, anchor: BodyRowAnchor): boolean {
|
|
883
|
+
if (annotation.target.kind === "section") return true;
|
|
884
|
+
if (annotation.target.row === anchor.row && annotation.target.context === anchor.context) return true;
|
|
885
|
+
const seenRows = new Set<number>();
|
|
886
|
+
const sectionAnchors: LineAnchorContext[] = [];
|
|
887
|
+
for (const candidate of this.#bodyRowAnchors) {
|
|
888
|
+
if (candidate.sectionIndex !== anchor.sectionIndex || seenRows.has(candidate.row)) continue;
|
|
889
|
+
seenRows.add(candidate.row);
|
|
890
|
+
sectionAnchors.push({ text: candidate.context, truncated: candidate.contextTruncated });
|
|
891
|
+
}
|
|
892
|
+
const resolvedRow = this.#resolveLineRow(
|
|
893
|
+
annotation.target.row,
|
|
894
|
+
{ text: annotation.target.context, truncated: annotation.target.contextTruncated },
|
|
895
|
+
sectionAnchors,
|
|
896
|
+
);
|
|
897
|
+
return resolvedRow === anchor.row;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
#annotationCandidates(): AnnotationSelection[] {
|
|
901
|
+
if (this.#focus === "toc") {
|
|
902
|
+
const sectionIndex = this.#toc[this.#tocCursor];
|
|
903
|
+
const section = sectionIndex === undefined ? undefined : this.#sections[sectionIndex];
|
|
904
|
+
if (!section || section.annotations.length === 0) return [];
|
|
905
|
+
return section.annotations.map((_, annotationIndex) => ({ sectionIndex, annotationIndex }));
|
|
906
|
+
}
|
|
907
|
+
if (this.#focus !== "body") return [];
|
|
908
|
+
const maxRow = this.#bodyRowAnchors.length - 1;
|
|
909
|
+
if (maxRow < 0) return [];
|
|
910
|
+
const topRow = Math.max(0, Math.min(maxRow, Math.floor(this.#scrollView.getScrollOffset())));
|
|
911
|
+
const anchor = this.#bodyRowAnchors[topRow];
|
|
912
|
+
const section = anchor ? this.#sections[anchor.sectionIndex] : undefined;
|
|
913
|
+
if (!anchor || !section) return [];
|
|
914
|
+
const entries: AnnotationSelection[] = [];
|
|
915
|
+
for (const [annotationIndex, annotation] of section.annotations.entries()) {
|
|
916
|
+
if (this.#annotationMatchesBodyAnchor(annotation, anchor)) {
|
|
917
|
+
entries.push({ sectionIndex: anchor.sectionIndex, annotationIndex });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return entries;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
#editCurrentAnnotation(): void {
|
|
924
|
+
const entries = this.#annotationCandidates();
|
|
925
|
+
if (entries.length === 0) return;
|
|
926
|
+
if (entries.length === 1) {
|
|
927
|
+
this.#startExistingAnnotation(entries[0]!);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
this.#annotationChooser = { entries, selected: 0 };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
#handleAnnotationChooser(data: string): void {
|
|
934
|
+
const chooser = this.#annotationChooser;
|
|
935
|
+
if (!chooser) return;
|
|
936
|
+
if (matchesSelectCancel(data)) {
|
|
937
|
+
this.#annotationChooser = undefined;
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
941
|
+
chooser.selected = (chooser.selected - 1 + chooser.entries.length) % chooser.entries.length;
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
945
|
+
chooser.selected = (chooser.selected + 1) % chooser.entries.length;
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
|
949
|
+
const selection = chooser.entries[chooser.selected];
|
|
950
|
+
this.#annotationChooser = undefined;
|
|
951
|
+
if (selection) this.#startExistingAnnotation(selection);
|
|
952
|
+
}
|
|
833
953
|
}
|
|
834
954
|
|
|
835
955
|
#submitAnnotation(value: string): void {
|
|
836
|
-
this.#annotating = false;
|
|
837
956
|
const note = value.trim();
|
|
838
957
|
const target = this.#annotationTarget;
|
|
958
|
+
const editing = this.#editingAnnotation;
|
|
959
|
+
this.#annotating = false;
|
|
839
960
|
this.#annotationTarget = undefined;
|
|
840
|
-
|
|
841
|
-
|
|
961
|
+
this.#editingAnnotation = undefined;
|
|
962
|
+
this.#editor.setText("");
|
|
963
|
+
if (editing) {
|
|
964
|
+
const section = this.#sections[editing.sectionIndex];
|
|
965
|
+
const annotation = section?.annotations[editing.annotationIndex];
|
|
966
|
+
if (!section || !annotation) return;
|
|
842
967
|
this.#pushUndo();
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
target:
|
|
846
|
-
target.row === null
|
|
847
|
-
? { kind: "section" }
|
|
848
|
-
: {
|
|
849
|
-
kind: "line",
|
|
850
|
-
row: target.row,
|
|
851
|
-
context: target.context,
|
|
852
|
-
contextTruncated: target.contextTruncated,
|
|
853
|
-
},
|
|
854
|
-
});
|
|
968
|
+
if (note) annotation.note = note;
|
|
969
|
+
else section.annotations.splice(editing.annotationIndex, 1);
|
|
855
970
|
this.#recomputeFeedback();
|
|
971
|
+
return;
|
|
856
972
|
}
|
|
857
|
-
|
|
973
|
+
if (!note || !target) return;
|
|
974
|
+
const section = this.#sections[target.sectionIndex];
|
|
975
|
+
if (!section) return;
|
|
976
|
+
this.#pushUndo();
|
|
977
|
+
section.annotations.push({
|
|
978
|
+
note,
|
|
979
|
+
target:
|
|
980
|
+
target.row === null
|
|
981
|
+
? { kind: "section" }
|
|
982
|
+
: {
|
|
983
|
+
kind: "line",
|
|
984
|
+
row: target.row,
|
|
985
|
+
context: target.context,
|
|
986
|
+
contextTruncated: target.contextTruncated,
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
this.#recomputeFeedback();
|
|
858
990
|
}
|
|
859
991
|
|
|
860
992
|
#exitAnnotate(): void {
|
|
861
993
|
this.#annotating = false;
|
|
862
994
|
this.#annotationTarget = undefined;
|
|
863
|
-
this.#
|
|
995
|
+
this.#editingAnnotation = undefined;
|
|
996
|
+
this.#editor.setText("");
|
|
864
997
|
}
|
|
865
998
|
|
|
866
999
|
#recomputeFeedback(): void {
|
|
@@ -942,10 +1075,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
942
1075
|
if (this.#slider) parts.push("◂▸ model");
|
|
943
1076
|
break;
|
|
944
1077
|
case "toc":
|
|
945
|
-
parts.push("↑↓ section", "⏎ open", "a annotate", "d delete", "u undo");
|
|
1078
|
+
parts.push("↑↓ section", "⏎ open", "a annotate", "e edit", "d delete", "u undo");
|
|
946
1079
|
break;
|
|
947
1080
|
case "body":
|
|
948
|
-
parts.push("↑↓ scroll", "⇧ faster", "pgup/pgdn", "g/G ends", "a annotate");
|
|
1081
|
+
parts.push("↑↓ scroll", "⇧ faster", "pgup/pgdn", "g/G ends", "a annotate", "e edit", "u undo");
|
|
949
1082
|
break;
|
|
950
1083
|
}
|
|
951
1084
|
if (this.callbacks.onCopyPlan) parts.push("c copy");
|
|
@@ -1136,8 +1269,39 @@ export class PlanReviewOverlay implements Component {
|
|
|
1136
1269
|
if (glow) return theme.fg("accent", line);
|
|
1137
1270
|
return theme.fg("muted", line);
|
|
1138
1271
|
}
|
|
1272
|
+
#renderAnnotationChooserLines(innerWidth: number, maxRows = Number.POSITIVE_INFINITY): string[] {
|
|
1273
|
+
const chooser = this.#annotationChooser;
|
|
1274
|
+
if (!chooser || maxRows <= 0) return [];
|
|
1275
|
+
const optionLimit = Number.isFinite(maxRows) ? Math.max(0, Math.floor(maxRows) - 2) : chooser.entries.length;
|
|
1276
|
+
const start =
|
|
1277
|
+
optionLimit >= chooser.entries.length
|
|
1278
|
+
? 0
|
|
1279
|
+
: Math.max(
|
|
1280
|
+
0,
|
|
1281
|
+
Math.min(chooser.selected - Math.floor(optionLimit / 2), chooser.entries.length - optionLimit),
|
|
1282
|
+
);
|
|
1283
|
+
const lines = [theme.bold(theme.fg("accent", "Edit annotation"))];
|
|
1284
|
+
for (let windowIndex = 0; windowIndex < optionLimit; windowIndex++) {
|
|
1285
|
+
const optionIndex = start + windowIndex;
|
|
1286
|
+
const selection = chooser.entries[optionIndex];
|
|
1287
|
+
if (!selection) continue;
|
|
1288
|
+
const section = this.#sections[selection.sectionIndex];
|
|
1289
|
+
const annotation = section?.annotations[selection.annotationIndex];
|
|
1290
|
+
if (!section || !annotation) continue;
|
|
1291
|
+
const target = annotation.target.kind === "section" ? "section" : `line ${annotation.target.context}`;
|
|
1292
|
+
const note = sanitizeStatusText(annotation.note);
|
|
1293
|
+
const marker = optionIndex === chooser.selected ? theme.fg("accent", "› ") : " ";
|
|
1294
|
+
const label = `${marker}${sanitizeStatusText(section.title || "Plan preamble")} · ${target} · ${note}`;
|
|
1295
|
+
lines.push(truncateToWidth(label, innerWidth, Ellipsis.Unicode));
|
|
1296
|
+
}
|
|
1297
|
+
if (!Number.isFinite(maxRows) || maxRows >= 2) {
|
|
1298
|
+
lines.push(theme.fg("dim", "↑↓ choose · enter edit · esc cancel"));
|
|
1299
|
+
}
|
|
1300
|
+
return lines.slice(0, Math.max(0, Math.floor(maxRows)));
|
|
1301
|
+
}
|
|
1139
1302
|
|
|
1140
|
-
#renderFooterLines(innerWidth: number): string[] {
|
|
1303
|
+
#renderFooterLines(innerWidth: number, maxChooserRows = Number.POSITIVE_INFINITY): string[] {
|
|
1304
|
+
if (this.#annotationChooser) return this.#renderAnnotationChooserLines(innerWidth, maxChooserRows);
|
|
1141
1305
|
if (this.#annotating) {
|
|
1142
1306
|
const target = this.#annotationTarget;
|
|
1143
1307
|
const section = target ? this.#sections[target.sectionIndex] : undefined;
|
|
@@ -1151,9 +1315,12 @@ export class PlanReviewOverlay implements Component {
|
|
|
1151
1315
|
innerWidth,
|
|
1152
1316
|
Ellipsis.Unicode,
|
|
1153
1317
|
);
|
|
1154
|
-
const hintParts = ["enter save", "esc cancel"];
|
|
1318
|
+
const hintParts = ["enter save", "shift+enter newline", "esc cancel"];
|
|
1319
|
+
if (this.#editingAnnotation) hintParts.push("empty deletes");
|
|
1155
1320
|
if (this.#externalEditorLabel) hintParts.push(`${this.#externalEditorLabel} editor`);
|
|
1156
|
-
|
|
1321
|
+
this.#editor.setMaxHeight(Math.max(1, Math.min(MAX_ANNOTATION_EDITOR_ROWS, (process.stdout.rows || 40) - 12)));
|
|
1322
|
+
this.#editor.focused = true;
|
|
1323
|
+
return [caption, ...this.#editor.render(innerWidth), theme.fg("dim", hintParts.join(" · "))];
|
|
1157
1324
|
}
|
|
1158
1325
|
return [theme.fg("dim", this.#buildHelp())];
|
|
1159
1326
|
}
|
|
@@ -1171,14 +1338,20 @@ export class PlanReviewOverlay implements Component {
|
|
|
1171
1338
|
const submittingLabel = this.#committedLabel ? `${this.#committedLabel} — submitting…` : "Submitting…";
|
|
1172
1339
|
const optionLines = committed ? [theme.bold(theme.fg("accent", submittingLabel))] : this.#renderOptionLines();
|
|
1173
1340
|
const promptLines = this.#promptTitle ? [theme.bold(theme.fg("accent", this.#promptTitle))] : [];
|
|
1341
|
+
const baseChrome = 4 + promptLines.length + sliderLines.length + optionLines.length;
|
|
1342
|
+
const chooserFooterRows = this.#annotationChooser
|
|
1343
|
+
? Math.max(0, termHeight - baseChrome - MIN_BODY_ROWS)
|
|
1344
|
+
: Number.POSITIVE_INFINITY;
|
|
1174
1345
|
const footerLines = committed
|
|
1175
1346
|
? [theme.fg("dim", "Applying your selection — this can take a moment while context is prepared.")]
|
|
1176
|
-
: this.#renderFooterLines(innerWidth);
|
|
1347
|
+
: this.#renderFooterLines(innerWidth, chooserFooterRows);
|
|
1177
1348
|
|
|
1178
1349
|
// Chrome rows: top border, two dividers, bottom border, plus the
|
|
1179
1350
|
// prompt/slider/option/footer rows between them.
|
|
1180
|
-
const chrome =
|
|
1181
|
-
const regionRows =
|
|
1351
|
+
const chrome = baseChrome + footerLines.length;
|
|
1352
|
+
const regionRows = this.#annotationChooser
|
|
1353
|
+
? Math.max(0, termHeight - chrome)
|
|
1354
|
+
: Math.max(MIN_BODY_ROWS, termHeight - chrome);
|
|
1182
1355
|
|
|
1183
1356
|
const bodyLines = this.#buildBody(bodyContentWidth);
|
|
1184
1357
|
this.#layoutBody(bodyLines, regionRows);
|
|
@@ -75,6 +75,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
|
|
|
75
75
|
"Todos",
|
|
76
76
|
"Grep & Browser",
|
|
77
77
|
"Computer",
|
|
78
|
+
"IDA Pro",
|
|
78
79
|
"GitHub",
|
|
79
80
|
"Output Limits",
|
|
80
81
|
"Execution",
|
|
@@ -131,6 +132,11 @@ export interface SettingsHost {
|
|
|
131
132
|
entries: readonly SettingsDisplayEntry[];
|
|
132
133
|
get(path: string): unknown;
|
|
133
134
|
set(path: string, value: unknown): void;
|
|
135
|
+
/**
|
|
136
|
+
* Removes the value from the global config: a project or other layer, or an environment
|
|
137
|
+
* variable, that configures the setting still applies; otherwise the default does.
|
|
138
|
+
*/
|
|
139
|
+
unset(path: string): void;
|
|
134
140
|
normalizeProviderLimits(value: unknown): Record<string, number>;
|
|
135
141
|
validateProviderLimits(value: unknown): Record<string, number>;
|
|
136
142
|
}
|
|
@@ -1100,9 +1100,10 @@ export class SettingsSelectorComponent implements Component {
|
|
|
1100
1100
|
this.#formatTextInputEditValue(def.path, this.#context.settings.get(def.path)),
|
|
1101
1101
|
def.secret,
|
|
1102
1102
|
value => {
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1105
|
-
this.#
|
|
1103
|
+
// An empty field removes the persisted value, so the default (or an
|
|
1104
|
+
// environment fallback) applies again instead of a pinned "".
|
|
1105
|
+
if (value === "") this.#context.settings.unset(def.path);
|
|
1106
|
+
else this.#setSettingValue(def.path, value);
|
|
1106
1107
|
this.#callbacks.onChange(def.path, this.#context.settings.get(def.path));
|
|
1107
1108
|
wrappedDone(this.#formatTextInputValue(def, this.#context.settings.get(def.path)));
|
|
1108
1109
|
},
|
|
@@ -83,6 +83,8 @@ export interface ProviderCard {
|
|
|
83
83
|
soonestExpiryMs?: number;
|
|
84
84
|
unavailableReasons: string[];
|
|
85
85
|
};
|
|
86
|
+
/** Labels of accounts with verified Daybreak access. */
|
|
87
|
+
daybreakAccounts?: string[];
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
@@ -202,6 +204,17 @@ export function buildProviderCards(reports: UsageReport[], nowMs: number): Provi
|
|
|
202
204
|
unavailableReasons,
|
|
203
205
|
}
|
|
204
206
|
: undefined;
|
|
207
|
+
const daybreakAccounts = providerReports.flatMap((report, index) =>
|
|
208
|
+
report.metadata?.daybreak === true
|
|
209
|
+
? [
|
|
210
|
+
typeof report.metadata.email === "string" && report.metadata.email
|
|
211
|
+
? report.metadata.email
|
|
212
|
+
: typeof report.metadata.accountId === "string" && report.metadata.accountId
|
|
213
|
+
? report.metadata.accountId
|
|
214
|
+
: `account ${index + 1}`,
|
|
215
|
+
]
|
|
216
|
+
: [],
|
|
217
|
+
);
|
|
205
218
|
cards.push({
|
|
206
219
|
provider,
|
|
207
220
|
name: formatProviderName(provider),
|
|
@@ -209,8 +222,11 @@ export function buildProviderCards(reports: UsageReport[], nowMs: number): Provi
|
|
|
209
222
|
windows,
|
|
210
223
|
unlimited: windows.length === 0,
|
|
211
224
|
idle:
|
|
212
|
-
!resetCredits &&
|
|
225
|
+
!resetCredits &&
|
|
226
|
+
daybreakAccounts.length === 0 &&
|
|
227
|
+
windows.every(window => window.fraction !== undefined && window.fraction < IDLE_FRACTION),
|
|
213
228
|
resetCredits,
|
|
229
|
+
...(daybreakAccounts.length > 0 ? { daybreakAccounts } : {}),
|
|
214
230
|
});
|
|
215
231
|
}
|
|
216
232
|
|
|
@@ -453,6 +469,11 @@ export class UsageDashboardComponent implements Component {
|
|
|
453
469
|
const titlePad = Math.max(0, width - 2 - visibleWidth(title) - visibleWidth(accountsText));
|
|
454
470
|
lines.push(`${this.#statusIcon(cardStatus)} ${title}${" ".repeat(titlePad)}${accountsText}`);
|
|
455
471
|
|
|
472
|
+
for (const account of card.daybreakAccounts ?? []) {
|
|
473
|
+
const label = sanitizeText(account.replace(/[\r\n\t]+/g, " "));
|
|
474
|
+
lines.push(` ${theme.fg("success", truncateToWidth(`daybreak · ${label}`, width - 2))}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
456
477
|
if (card.resetCredits) {
|
|
457
478
|
const resets = card.resetCredits;
|
|
458
479
|
let resetText = `✦ ${resets.bankedCount} reset${resets.bankedCount === 1 ? "" : "s"}`;
|
package/src/prompt/composer.ts
CHANGED
|
@@ -91,6 +91,18 @@ export interface ComposerOptions {
|
|
|
91
91
|
readonly now?: () => number;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/** How {@link Composer.setRuntimeChildren} bills below-transcript roots against transcript retirement. */
|
|
95
|
+
export interface RuntimeChildrenOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Below-transcript roots whose growth is momentary (inline dialogs, a tall
|
|
98
|
+
* multi-line editor). They are billed at their smallest height so expansion
|
|
99
|
+
* clips the live tail instead of retiring rows a later shrink could not
|
|
100
|
+
* reclaim (#11007). Every other root is billed at its current height, so
|
|
101
|
+
* settled rows it displaces retire to native scrollback.
|
|
102
|
+
*/
|
|
103
|
+
readonly transient?: readonly Component[];
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
/** Controls the first terminal paint for a composer that does not already own the terminal. */
|
|
95
107
|
export interface ComposerStartOptions {
|
|
96
108
|
readonly clearScrollback?: boolean;
|
|
@@ -253,15 +265,18 @@ export class Composer implements TerminalFrameProvider {
|
|
|
253
265
|
#retiredHeaderStart = 0;
|
|
254
266
|
#resizeRetiredHeaderStart: number | undefined;
|
|
255
267
|
#lastNormalRows = 0;
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
268
|
+
// Roots from `RuntimeChildrenOptions.transient`, and the smallest height
|
|
269
|
+
// they have rendered at since mount. Retirement bills transient roots at
|
|
270
|
+
// this floor, never their peak, so a dialog or tall editor that later
|
|
271
|
+
// shrinks never leaves committed transcript rows the live viewport cannot
|
|
272
|
+
// reclaim (#11007). Persistent roots (loader, todo/subagent HUDs) bill at
|
|
273
|
+
// their current height: they stay up for a whole turn, and billing them at
|
|
274
|
+
// an idle floor hid the settled rows they displaced until the turn ended.
|
|
275
|
+
// The floor is terminal-height independent — editor and dialog heights do
|
|
276
|
+
// not scale with rows — so it is retained across resizes rather than
|
|
277
|
+
// rediscovered from whatever chrome is expanded when the height changes.
|
|
278
|
+
#transientChrome: ReadonlySet<Component> = new Set();
|
|
279
|
+
#transientChromeFloor: number | undefined;
|
|
265
280
|
#lastInterruptAt = 0;
|
|
266
281
|
#started = false;
|
|
267
282
|
#stopped = false;
|
|
@@ -346,60 +361,24 @@ export class Composer implements TerminalFrameProvider {
|
|
|
346
361
|
const afterRoots = roots.slice(transcriptIndex + 1);
|
|
347
362
|
const after: string[] = [];
|
|
348
363
|
const afterSpans: ViewportClickSpan[] = [];
|
|
364
|
+
let transientRows = 0;
|
|
349
365
|
for (const root of afterRoots) {
|
|
350
366
|
const start = after.length;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// `Container.render` is a pure concatenation, so child spans tile
|
|
354
|
-
// the root span exactly. Render those children once and share the
|
|
355
|
-
// rows for composition and measurement — a second render per frame
|
|
356
|
-
// would duplicate render-time side effects (image placement
|
|
357
|
-
// registration). Roots with a custom render keep the composed
|
|
358
|
-
// output as the source of truth and measure up to the last target.
|
|
359
|
-
const plainContainer = root instanceof Container && root.render === Container.prototype.render;
|
|
360
|
-
const targets = root instanceof Container ? root.children : [root];
|
|
361
|
-
const resolves = targets.map(rowTargetCandidates);
|
|
362
|
-
const lastTarget = resolves.findLastIndex(resolve => resolve !== undefined);
|
|
363
|
-
if (plainContainer) {
|
|
364
|
-
let offset = start;
|
|
365
|
-
for (let index = 0; index < targets.length; index++) {
|
|
366
|
-
const childLines = targets[index]!.render(width);
|
|
367
|
-
after.push(...childLines);
|
|
368
|
-
if (index > lastTarget) continue;
|
|
369
|
-
const resolve = resolves[index];
|
|
370
|
-
if (resolve !== undefined && childLines.length > 0) {
|
|
371
|
-
afterSpans.push({ start: offset, end: offset + childLines.length, candidates: resolve });
|
|
372
|
-
}
|
|
373
|
-
offset += childLines.length;
|
|
374
|
-
}
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
after.push(...root.render(width));
|
|
378
|
-
if (lastTarget === -1) continue;
|
|
379
|
-
let offset = start;
|
|
380
|
-
for (let index = 0; index <= lastTarget; index++) {
|
|
381
|
-
const childLines = targets[index] === root ? after.length - start : targets[index]!.render(width).length;
|
|
382
|
-
const resolve = resolves[index];
|
|
383
|
-
if (resolve !== undefined && childLines > 0) {
|
|
384
|
-
afterSpans.push({ start: offset, end: offset + childLines, candidates: resolve });
|
|
385
|
-
}
|
|
386
|
-
offset += childLines;
|
|
387
|
-
}
|
|
367
|
+
this.#renderBelowRoot(root, width, after, afterSpans);
|
|
368
|
+
if (this.#transientChrome.has(root)) transientRows += after.length - start;
|
|
388
369
|
}
|
|
389
370
|
// Offer history under capacity pressure only: blocks stay live (and keep
|
|
390
371
|
// reflowing to the current width) while the screen has room. A batch
|
|
391
372
|
// leaves the mutable viewport in the same frame it is appended, so its
|
|
392
373
|
// rows are never painted twice.
|
|
393
374
|
//
|
|
394
|
-
// Retirement
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
this.#
|
|
401
|
-
this.#retirementBelowFloor === undefined ? after.length : Math.min(this.#retirementBelowFloor, after.length);
|
|
402
|
-
const belowFloor = this.#retirementBelowFloor;
|
|
375
|
+
// Retirement bills transient roots at their floor, not their peak: a
|
|
376
|
+
// confirmation dialog or a tall multi-line editor clips the live tail for
|
|
377
|
+
// its lifetime, but must not permanently commit transcript rows to native
|
|
378
|
+
// history — otherwise a later shrink cannot refill the freed rows and the
|
|
379
|
+
// editor drifts up above a band of blank rows (#11007).
|
|
380
|
+
this.#transientChromeFloor = Math.min(this.#transientChromeFloor ?? transientRows, transientRows);
|
|
381
|
+
const belowFloor = after.length - transientRows + this.#transientChromeFloor;
|
|
403
382
|
const history = this.#offerHistory(transcript, width, rows, preRoots.length + belowFloor);
|
|
404
383
|
const headerVisible = !this.#headerRetired && this.#offeredHistory?.source !== "header";
|
|
405
384
|
const headerRows = headerVisible ? this.#header.render(width) : [];
|
|
@@ -443,6 +422,52 @@ export class Composer implements TerminalFrameProvider {
|
|
|
443
422
|
return { history, viewport: this.#paintHoverBand(mutable, spans) };
|
|
444
423
|
}
|
|
445
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Append one below-transcript root's rows to `after`, recording click spans
|
|
427
|
+
* for its row targets in `after` coordinates.
|
|
428
|
+
*
|
|
429
|
+
* Row targets usually nest one level down: chrome roots are plain
|
|
430
|
+
* containers (the HUD lives inside `subagentContainer`), and
|
|
431
|
+
* `Container.render` is a pure concatenation, so child spans tile the root
|
|
432
|
+
* span exactly. Render those children once and share the rows for
|
|
433
|
+
* composition and measurement — a second render per frame would duplicate
|
|
434
|
+
* render-time side effects (image placement registration). Roots with a
|
|
435
|
+
* custom render keep the composed output as the source of truth and measure
|
|
436
|
+
* up to the last target.
|
|
437
|
+
*/
|
|
438
|
+
#renderBelowRoot(root: Component, width: number, after: string[], spans: ViewportClickSpan[]): void {
|
|
439
|
+
const start = after.length;
|
|
440
|
+
const plainContainer = root instanceof Container && root.render === Container.prototype.render;
|
|
441
|
+
const targets = root instanceof Container ? root.children : [root];
|
|
442
|
+
const resolves = targets.map(rowTargetCandidates);
|
|
443
|
+
const lastTarget = resolves.findLastIndex(resolve => resolve !== undefined);
|
|
444
|
+
if (plainContainer) {
|
|
445
|
+
let offset = start;
|
|
446
|
+
for (let index = 0; index < targets.length; index++) {
|
|
447
|
+
const childLines = targets[index]!.render(width);
|
|
448
|
+
after.push(...childLines);
|
|
449
|
+
if (index > lastTarget) continue;
|
|
450
|
+
const resolve = resolves[index];
|
|
451
|
+
if (resolve !== undefined && childLines.length > 0) {
|
|
452
|
+
spans.push({ start: offset, end: offset + childLines.length, candidates: resolve });
|
|
453
|
+
}
|
|
454
|
+
offset += childLines.length;
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
after.push(...root.render(width));
|
|
459
|
+
if (lastTarget === -1) return;
|
|
460
|
+
let offset = start;
|
|
461
|
+
for (let index = 0; index <= lastTarget; index++) {
|
|
462
|
+
const childLines = targets[index] === root ? after.length - start : targets[index]!.render(width).length;
|
|
463
|
+
const resolve = resolves[index];
|
|
464
|
+
if (resolve !== undefined && childLines > 0) {
|
|
465
|
+
spans.push({ start: offset, end: offset + childLines, candidates: resolve });
|
|
466
|
+
}
|
|
467
|
+
offset += childLines;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
446
471
|
/**
|
|
447
472
|
* Band the hovered click target's current rows. Id-anchored (not
|
|
448
473
|
* line-anchored) so the band follows an agent whose rows shift while it
|
|
@@ -847,8 +872,10 @@ export class Composer implements TerminalFrameProvider {
|
|
|
847
872
|
}
|
|
848
873
|
|
|
849
874
|
/** Mount or replace session-aware root children while preserving the header and status hosts. */
|
|
850
|
-
setRuntimeChildren(children: readonly Component[]): void {
|
|
875
|
+
setRuntimeChildren(children: readonly Component[], options: RuntimeChildrenOptions = {}): void {
|
|
851
876
|
if (this.#stopped) return;
|
|
877
|
+
this.#transientChrome = new Set(options.transient);
|
|
878
|
+
this.#transientChromeFloor = undefined;
|
|
852
879
|
this.ui.removeChild(this.#statusHost);
|
|
853
880
|
if (this.#runtimeMounted) {
|
|
854
881
|
for (const child of this.#runtimeChildren) this.ui.removeChild(child);
|