@oh-my-pi/pi-tui 18.2.11 → 18.3.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/CHANGELOG.md +20 -0
- package/THIRD-PARTY-NOTICES.txt +1 -1
- 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/overlays/annotation-overlay.d.ts +30 -0
- package/dist/types/overlays/annotation-types.d.ts +84 -0
- 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/usage-dashboard.d.ts +2 -0
- package/dist/types/prompt/image-source.d.ts +9 -8
- package/dist/types/status-line/context-usage.d.ts +3 -1
- package/dist/types/theme/mermaid-cache.d.ts +4 -6
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/daemon.d.ts +46 -0
- package/dist/types/tools/irc.d.ts +30 -0
- package/dist/types/tools/line-ranges.d.ts +1 -1
- package/dist/types/tools/proc-render.d.ts +34 -0
- package/dist/types/tools/read.d.ts +4 -1
- package/dist/types/tools/wait.d.ts +131 -0
- package/dist/types/tools/write.d.ts +4 -0
- package/dist/types/tools/xd-url.d.ts +8 -0
- 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 +4 -4
- package/src/chat/tool-execution.ts +8 -8
- package/src/chat/transcript-render-helpers.ts +3 -3
- package/src/components/markdown.ts +1 -1
- package/src/desktop-notify.ts +1 -1
- package/src/overlays/annotation-overlay.ts +1145 -0
- package/src/overlays/annotation-types.ts +78 -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/usage-dashboard.ts +22 -1
- package/src/prompt/image-source.ts +9 -8
- package/src/setup/scenes/sign-in.ts +1 -1
- package/src/status-line/context-usage.ts +15 -3
- 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/daemon.ts +51 -0
- 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/proc-render.ts +294 -0
- package/src/tools/read.ts +22 -1
- package/src/tools/wait.ts +595 -0
- package/src/tools/write.ts +76 -0
- package/src/tools/xd-url.ts +9 -0
- package/src/windows-altgr.ts +227 -0
- package/dist/types/tools/hub.d.ts +0 -342
- 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);
|
|
@@ -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"}`;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
* materialization prefers it over a blob copy).
|
|
2
|
+
* File source carried on image attachments backed by a file on disk:
|
|
3
|
+
* path-pasted/drag-and-dropped images, clipboard images the coding agent commits
|
|
4
|
+
* to the session's `local://` root, and generated video contact-sheet previews.
|
|
5
|
+
* Symbol metadata stays out of serialized/model-bound image data while traveling
|
|
6
|
+
* with the draft object, until AgentSession creates the hidden companion message
|
|
7
|
+
* that tells the model the path (and link materialization prefers it over a blob copy).
|
|
8
8
|
*/
|
|
9
9
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
@@ -12,8 +12,9 @@ import { isRecord } from "@oh-my-pi/pi-utils";
|
|
|
12
12
|
/** How an image attachment's backing file entered the session. */
|
|
13
13
|
export type ImageAttachmentSourceKind = "image" | "video";
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/** Local file backing an image attachment. */
|
|
16
16
|
export interface ImageAttachmentSource {
|
|
17
|
+
/** Absolute filesystem path, or a session-relative `local://` URL for images committed to the session. */
|
|
17
18
|
readonly path: string;
|
|
18
19
|
readonly kind: ImageAttachmentSourceKind;
|
|
19
20
|
}
|
|
@@ -43,7 +44,7 @@ function isImageAttachmentSource(value: unknown): value is ImageAttachmentSource
|
|
|
43
44
|
return isRecord(value) && typeof value.path === "string" && (value.kind === "image" || value.kind === "video");
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
/** Return the
|
|
47
|
+
/** Return the local file backing an image attachment, or undefined for payloads with no file on disk. */
|
|
47
48
|
export function imageAttachmentSource(image: ImageContent): ImageAttachmentSource | undefined {
|
|
48
49
|
if (!(kImageAttachmentSource in image)) return undefined;
|
|
49
50
|
const source = image[kImageAttachmentSource];
|
|
@@ -230,7 +230,7 @@ export class SignInTab implements SetupTab {
|
|
|
230
230
|
this.#host.restoreFocus();
|
|
231
231
|
this.#host.requestRender();
|
|
232
232
|
try {
|
|
233
|
-
await this.#authStorage.login(providerId as OAuthProvider, {
|
|
233
|
+
await this.#authStorage.oauth.login(providerId as OAuthProvider, {
|
|
234
234
|
signal: this.#loginAbort.signal,
|
|
235
235
|
onBrowserSession: (request, signal) => this.#host.ctx.captureBrowserSession(request, signal),
|
|
236
236
|
onAuth: info => {
|
|
@@ -2,6 +2,7 @@ import type { AgentMessage, Tokenizer } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
3
|
import { effectiveReserveTokens, resolveThresholdTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
4
4
|
import type { Tool as AiTool, Model } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { renderToolExamples } from "@oh-my-pi/pi-ai/dialect";
|
|
5
6
|
import { toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
6
7
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import type { Theme } from "../theme";
|
|
@@ -12,7 +13,7 @@ interface ContextSkill {
|
|
|
12
13
|
readonly hide?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
type ContextTool = Pick<AiTool, "name" | "description" | "parameters">;
|
|
16
|
+
type ContextTool = Pick<AiTool, "name" | "description" | "parameters" | "examples">;
|
|
16
17
|
|
|
17
18
|
/** Savings computed by the host's inline-image planner, not by the renderer. */
|
|
18
19
|
export interface ContextSavingsEstimate {
|
|
@@ -140,6 +141,8 @@ export interface NonMessageTokenSource {
|
|
|
140
141
|
};
|
|
141
142
|
};
|
|
142
143
|
readonly skills?: readonly ContextSkill[];
|
|
144
|
+
/** Provider-facing, session-frozen descriptions when available. */
|
|
145
|
+
readonly renderedSkills?: readonly ContextSkill[];
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
/** Shared empty system-prompt part list, avoiding an allocation per render. */
|
|
@@ -244,9 +247,13 @@ export function estimateToolSchemaTokens(tools: ToolSchemaSource, tokenizer: Tok
|
|
|
244
247
|
name,
|
|
245
248
|
description,
|
|
246
249
|
parameters: parameters as AiTool["parameters"],
|
|
250
|
+
examples: tool.examples,
|
|
247
251
|
};
|
|
248
252
|
const wireJson = JSON.stringify(toolWireSchema(wireTool) ?? {});
|
|
249
253
|
if (typeof wireJson === "string") fragments.push(wireJson);
|
|
254
|
+
// The agent loop appends rendered examples to the wire description.
|
|
255
|
+
const examplesBlock = renderToolExamples(wireTool);
|
|
256
|
+
if (examplesBlock) fragments.push(examplesBlock);
|
|
250
257
|
} catch {
|
|
251
258
|
// Schema may contain functions or cycles; ignore.
|
|
252
259
|
}
|
|
@@ -313,7 +320,7 @@ function nonMessageTokenCacheEntry(
|
|
|
313
320
|
const systemPromptRef = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
314
321
|
const toolsRef = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
315
322
|
const toolsRevision = getToolSchemaMetadataRevision(toolsRef);
|
|
316
|
-
const skillsRef = session.skills ?? EMPTY_SKILLS;
|
|
323
|
+
const skillsRef = session.renderedSkills ?? session.skills ?? EMPTY_SKILLS;
|
|
317
324
|
let entry = cachedSession[NON_MESSAGE_TOKEN_CACHE];
|
|
318
325
|
if (
|
|
319
326
|
entry &&
|
|
@@ -378,7 +385,12 @@ export function computeNonMessageBreakdown(
|
|
|
378
385
|
if (entry.breakdown && entry.skillful === skillful) return entry.breakdown;
|
|
379
386
|
const tools = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
380
387
|
const skillsTokens =
|
|
381
|
-
skillful === false
|
|
388
|
+
skillful === false
|
|
389
|
+
? 0
|
|
390
|
+
: estimateSkillsTokens(
|
|
391
|
+
renderedSkills(session.renderedSkills ?? session.skills ?? EMPTY_SKILLS, tools),
|
|
392
|
+
tokenizer,
|
|
393
|
+
);
|
|
382
394
|
const toolsTokens = estimateToolSchemaTokens(tools, tokenizer, sourceRevision);
|
|
383
395
|
const systemPromptParts = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
384
396
|
const systemContextTokens = tokenizer.countTokens(Array.from(systemPromptParts.slice(1), part => part ?? ""));
|
|
@@ -42,7 +42,7 @@ export type TerminalId =
|
|
|
42
42
|
| "base"
|
|
43
43
|
| "trueColor";
|
|
44
44
|
|
|
45
|
-
const CMUX_NOTIFICATION_TITLE = "
|
|
45
|
+
const CMUX_NOTIFICATION_TITLE = "omp";
|
|
46
46
|
const CMUX_SURFACE_ID_PATTERN = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu;
|
|
47
47
|
|
|
48
48
|
/** Title and body for an out-of-band multiplexer notification (cmux, Herdr). */
|
|
@@ -1433,7 +1433,7 @@ function notificationToLine(n: TerminalNotification): string {
|
|
|
1433
1433
|
// C0/C1 control characters that are unsafe inside an OSC payload (must base64).
|
|
1434
1434
|
const OSC99_UNSAFE = /[\x00-\x1f\x7f\x80-\x9f]/u;
|
|
1435
1435
|
const OSC99_MAX_PAYLOAD_BYTES = 2048;
|
|
1436
|
-
const OSC99_APP_NAME = "
|
|
1436
|
+
const OSC99_APP_NAME = "omp";
|
|
1437
1437
|
let nextOsc99NotificationId = 1;
|
|
1438
1438
|
|
|
1439
1439
|
function base64Utf8(value: string): string {
|
package/src/terminal.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./terminal-capabilities";
|
|
26
26
|
import { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
|
|
27
27
|
import { setHangulCompatibilityJamoWidth } from "./utils";
|
|
28
|
+
import { translateWindowsAltGrSequence } from "./windows-altgr";
|
|
28
29
|
|
|
29
30
|
const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
|
|
30
31
|
const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
|
|
@@ -1463,7 +1464,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
1463
1464
|
return;
|
|
1464
1465
|
}
|
|
1465
1466
|
if (this.#inputHandler) {
|
|
1466
|
-
|
|
1467
|
+
// Windows console hosts drop AltGr text under kitty (AltGr+F → `CSI 102;3u`);
|
|
1468
|
+
// recover it from the active layout before any keybinding sees an Alt chord.
|
|
1469
|
+
const altGrText =
|
|
1470
|
+
this.#kittyProtocolActive && this.#conpty && process.platform === "win32"
|
|
1471
|
+
? translateWindowsAltGrSequence(sequence)
|
|
1472
|
+
: undefined;
|
|
1473
|
+
this.#inputHandler(altGrText ?? sequence);
|
|
1467
1474
|
}
|
|
1468
1475
|
});
|
|
1469
1476
|
|