@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
|
@@ -5,6 +5,7 @@ import { BRACKETED_PASTE_END, BRACKETED_PASTE_START } from "../stdin-buffer";
|
|
|
5
5
|
import { Editor, type EditorTextDecorationContext, type EditorTheme } from "../components/editor";
|
|
6
6
|
import { addKeyAliases, canonicalKeyId, getKeybindings } from "../keybindings";
|
|
7
7
|
import { type KeyId, parseKey, parseKittySequence } from "../keys";
|
|
8
|
+
import { SpaceHoldGesture } from "../space-hold";
|
|
8
9
|
import { TUI } from "../tui";
|
|
9
10
|
import type { AppKeybinding } from "../app-keybindings";
|
|
10
11
|
import { allowsModelMentions, allowsSkillTokens, SKILL_TOKEN_RE } from "./skill-tokens";
|
|
@@ -117,34 +118,6 @@ const ABSOLUTE_PATH_PREFIX_REGEX = new RegExp(`^${ABSOLUTE_PATH_PREFIX_SOURCE}`)
|
|
|
117
118
|
*/
|
|
118
119
|
const INTERIOR_PATH_ANCHOR_REGEX = new RegExp(String.raw`(?<!\\)\s(?:${ABSOLUTE_PATH_PREFIX_SOURCE}|\.\.?[\\/])`);
|
|
119
120
|
|
|
120
|
-
/** Max gap (ms) between two spaces for the later one to count as OS key auto-repeat rather than a
|
|
121
|
-
* deliberate press. OS auto-repeat is fast; a deliberate tap (even a fast one) is slower. */
|
|
122
|
-
export const SPACE_REPEAT_MAX_GAP_MS = 120;
|
|
123
|
-
/** Two consecutive inter-space gaps are "mechanical" (machine-driven auto-repeat) when both are
|
|
124
|
-
* within {@link SPACE_REPEAT_MAX_GAP_MS} and differ by no more than this — an absolute jitter floor
|
|
125
|
-
* or, for slower repeat rates, {@link SPACE_REPEAT_JITTER_RATIO} of the smaller gap. OS key-repeat
|
|
126
|
-
* is metronomic; a human smashing the bar is fast but irregular, so its deltas never stay this
|
|
127
|
-
* steady. */
|
|
128
|
-
export const SPACE_REPEAT_JITTER_MS = 18;
|
|
129
|
-
export const SPACE_REPEAT_JITTER_RATIO = 0.35;
|
|
130
|
-
/** Consecutive mechanical (fast + steady) deltas that confirm the space bar is held and start
|
|
131
|
-
* recording. Needs a sustained metronomic cadence, so jittery smashing and deliberate taps never
|
|
132
|
-
* reach it. */
|
|
133
|
-
export const SPACE_HOLD_MECHANICAL_RUN = 2;
|
|
134
|
-
/** Idle gap (ms) after the last repeated space that counts as the space bar being released, ending
|
|
135
|
-
* the push-to-talk recording. Must comfortably exceed the OS key-repeat interval. */
|
|
136
|
-
export const SPACE_HOLD_RELEASE_MS = 250;
|
|
137
|
-
|
|
138
|
-
/** Whether two consecutive inter-space gaps look machine-driven: both within the auto-repeat band
|
|
139
|
-
* and steady enough (small absolute or proportional difference). OS key-repeat is metronomic, so
|
|
140
|
-
* its successive deltas match closely; human smashing is fast but irregular and deliberate taps are
|
|
141
|
-
* too slow, so neither passes. */
|
|
142
|
-
function gapsAreMechanical(gap: number, prevGap: number): boolean {
|
|
143
|
-
if (gap > SPACE_REPEAT_MAX_GAP_MS || prevGap > SPACE_REPEAT_MAX_GAP_MS) return false;
|
|
144
|
-
const tolerance = Math.max(SPACE_REPEAT_JITTER_MS, Math.min(gap, prevGap) * SPACE_REPEAT_JITTER_RATIO);
|
|
145
|
-
return Math.abs(gap - prevGap) <= tolerance;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
121
|
function isPastedPathSeparator(char: string | undefined): boolean {
|
|
149
122
|
return char === undefined || char === " " || char === "\t" || char === "\r" || char === "\n";
|
|
150
123
|
}
|
|
@@ -902,15 +875,13 @@ export class CustomEditor extends Editor {
|
|
|
902
875
|
/** Called when left-arrow is pressed while the editor is empty (cursor necessarily at start). */
|
|
903
876
|
onLeftAtStart?: () => void;
|
|
904
877
|
|
|
905
|
-
/**
|
|
906
|
-
*
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
* normally; wired to `stt.enabled` so disabling STT restores plain space behavior. */
|
|
913
|
-
sttHoldEnabled?: () => boolean;
|
|
878
|
+
/** Space-bar push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so it
|
|
879
|
+
* stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion) and away from an
|
|
880
|
+
* open autocomplete menu. */
|
|
881
|
+
readonly spaceHold = new SpaceHoldGesture(
|
|
882
|
+
count => this.deleteBeforeCursor(count),
|
|
883
|
+
() => this.vimMode === "insert" && !this.isShowingAutocomplete(),
|
|
884
|
+
);
|
|
914
885
|
|
|
915
886
|
/** Custom key handlers from extensions and non-built-in app actions. */
|
|
916
887
|
#customKeyHandlers = new Map<KeyId, () => void>();
|
|
@@ -928,18 +899,6 @@ export class CustomEditor extends Editor {
|
|
|
928
899
|
/** Input chunks deferred behind an in-flight paste, drained in FIFO order once the paste
|
|
929
900
|
* count returns to zero. */
|
|
930
901
|
#pendingInput: string[] = [];
|
|
931
|
-
/** Spaces actually inserted in the current run; tracked back out when a hold is recognized. */
|
|
932
|
-
#spaceRunInserted = 0;
|
|
933
|
-
/** Consecutive "mechanical" deltas (fast + steady); a sustained run of these confirms a held bar. */
|
|
934
|
-
#mechanicalRun = 0;
|
|
935
|
-
/** Inter-space gap (ms) of the previous space pair, compared against the next to judge steadiness. */
|
|
936
|
-
#prevSpaceGap: number | undefined;
|
|
937
|
-
/** Monotonic timestamp (ms) of the last space, to measure the gap to the next one. */
|
|
938
|
-
#lastSpaceAt = Number.NEGATIVE_INFINITY;
|
|
939
|
-
/** True while a recognized space-hold push-to-talk recording is in progress. */
|
|
940
|
-
#spaceHoldActive = false;
|
|
941
|
-
/** Idle timer that fires `onSpaceHoldEnd` once repeated spaces stop arriving. */
|
|
942
|
-
#spaceHoldTimer: NodeJS.Timeout | undefined;
|
|
943
902
|
#actionKeys = new Map<ConfigurableEditorAction, KeyId[]>(
|
|
944
903
|
Object.entries(DEFAULT_ACTION_KEYS).map(([action, keys]) => [action as ConfigurableEditorAction, [...keys]]),
|
|
945
904
|
);
|
|
@@ -1004,93 +963,6 @@ export class CustomEditor extends Editor {
|
|
|
1004
963
|
this.#rebuildCustomMatchKeys();
|
|
1005
964
|
}
|
|
1006
965
|
|
|
1007
|
-
#spaceHoldGestureEnabled(): boolean {
|
|
1008
|
-
// Push-to-talk is a text-composition gesture, so it stays out of Vim's Normal/Visual modes
|
|
1009
|
-
// where the space bar is the `l` motion.
|
|
1010
|
-
if (this.vimMode !== "insert") return false;
|
|
1011
|
-
return this.onSpaceHoldStart !== undefined && (this.sttHoldEnabled?.() ?? false) && !this.isShowingAutocomplete();
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/** Drive the space-hold push-to-talk state machine. Returns true when the gesture consumed the
|
|
1015
|
-
* input so it must not reach normal editing. A held space bar emits OS auto-repeat: a *steady*
|
|
1016
|
-
* stream of spaces at a fixed fast interval. We watch the inter-space deltas and only recognize a
|
|
1017
|
-
* hold once {@link SPACE_HOLD_MECHANICAL_RUN} consecutive deltas are "mechanical" — both
|
|
1018
|
-
* auto-repeat-fast and near-identical (see {@link gapsAreMechanical}). Smashing the bar is fast
|
|
1019
|
-
* but jittery and deliberate taps are too slow, so neither escalates and both keep typing real
|
|
1020
|
-
* spaces; the few spaces typed before a real hold is recognized are tracked back out. */
|
|
1021
|
-
#handleSpaceHold(data: string, canonical: string | undefined): boolean {
|
|
1022
|
-
const isSpace = canonical === "space";
|
|
1023
|
-
if (this.#spaceHoldActive) {
|
|
1024
|
-
if (isSpace) {
|
|
1025
|
-
// Auto-repeat while held: swallow it and keep the release timer alive.
|
|
1026
|
-
this.#armSpaceHoldReleaseTimer();
|
|
1027
|
-
return true;
|
|
1028
|
-
}
|
|
1029
|
-
// Any non-space means the bar was released — stop recording, then let the key through.
|
|
1030
|
-
this.#endSpaceHold();
|
|
1031
|
-
return false;
|
|
1032
|
-
}
|
|
1033
|
-
if (!isSpace) {
|
|
1034
|
-
this.#resetSpaceRun();
|
|
1035
|
-
return false;
|
|
1036
|
-
}
|
|
1037
|
-
if (!this.#spaceHoldGestureEnabled()) return false;
|
|
1038
|
-
const now = performance.now();
|
|
1039
|
-
const gap = now - this.#lastSpaceAt;
|
|
1040
|
-
const prevGap = this.#prevSpaceGap;
|
|
1041
|
-
this.#lastSpaceAt = now;
|
|
1042
|
-
this.#prevSpaceGap = gap;
|
|
1043
|
-
if (prevGap === undefined || !gapsAreMechanical(gap, prevGap)) {
|
|
1044
|
-
// First space, a deliberate tap, or jittery smashing: not a steady machine cadence yet, so
|
|
1045
|
-
// type a real space and reset the mechanical run.
|
|
1046
|
-
this.#mechanicalRun = 0;
|
|
1047
|
-
this.#forwardInput(data);
|
|
1048
|
-
this.#spaceRunInserted++;
|
|
1049
|
-
return true;
|
|
1050
|
-
}
|
|
1051
|
-
// Steady fast repeat: swallow it. Once the cadence has held for SPACE_HOLD_MECHANICAL_RUN
|
|
1052
|
-
// deltas it's a held bar — track back the few pre-burst spaces already typed and start.
|
|
1053
|
-
if (++this.#mechanicalRun >= SPACE_HOLD_MECHANICAL_RUN) {
|
|
1054
|
-
this.deleteBeforeCursor(this.#spaceRunInserted);
|
|
1055
|
-
this.#resetSpaceRun();
|
|
1056
|
-
this.#beginSpaceHold();
|
|
1057
|
-
}
|
|
1058
|
-
return true;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
#resetSpaceRun(): void {
|
|
1062
|
-
this.#spaceRunInserted = 0;
|
|
1063
|
-
this.#mechanicalRun = 0;
|
|
1064
|
-
this.#prevSpaceGap = undefined;
|
|
1065
|
-
this.#lastSpaceAt = Number.NEGATIVE_INFINITY;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
#beginSpaceHold(): void {
|
|
1069
|
-
this.#spaceHoldActive = true;
|
|
1070
|
-
this.#armSpaceHoldReleaseTimer();
|
|
1071
|
-
this.onSpaceHoldStart?.();
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
#armSpaceHoldReleaseTimer(): void {
|
|
1075
|
-
if (this.#spaceHoldTimer) clearTimeout(this.#spaceHoldTimer);
|
|
1076
|
-
this.#spaceHoldTimer = setTimeout(() => {
|
|
1077
|
-
this.#spaceHoldTimer = undefined;
|
|
1078
|
-
this.#endSpaceHold();
|
|
1079
|
-
}, SPACE_HOLD_RELEASE_MS);
|
|
1080
|
-
this.#spaceHoldTimer.unref?.();
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
#endSpaceHold(): void {
|
|
1084
|
-
if (!this.#spaceHoldActive) return;
|
|
1085
|
-
this.#spaceHoldActive = false;
|
|
1086
|
-
this.#resetSpaceRun();
|
|
1087
|
-
if (this.#spaceHoldTimer) {
|
|
1088
|
-
clearTimeout(this.#spaceHoldTimer);
|
|
1089
|
-
this.#spaceHoldTimer = undefined;
|
|
1090
|
-
}
|
|
1091
|
-
this.onSpaceHoldEnd?.();
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
966
|
/** Decrement {@link #pasteInFlight} once an async paste settles and, when the count returns
|
|
1095
967
|
* to zero, drain {@link #pendingInput} through `handleInput` so requeueing still works if a
|
|
1096
968
|
* drained chunk triggers another async paste. Bound member so it can be passed straight to
|
|
@@ -1188,7 +1060,13 @@ export class CustomEditor extends Editor {
|
|
|
1188
1060
|
}
|
|
1189
1061
|
|
|
1190
1062
|
// Space-hold push-to-talk: a sustained space bar starts/stops STT instead of typing spaces.
|
|
1191
|
-
|
|
1063
|
+
switch (this.spaceHold.process(canonical === "space")) {
|
|
1064
|
+
case "type":
|
|
1065
|
+
this.#forwardInput(data);
|
|
1066
|
+
return;
|
|
1067
|
+
case "swallow":
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1192
1070
|
|
|
1193
1071
|
// One union probe decides whether any per-action interception below can
|
|
1194
1072
|
// match — plain typing then skips the ~20 per-action set lookups per key.
|
|
@@ -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];
|
package/src/prompt/tips.txt
CHANGED
|
@@ -25,4 +25,5 @@ No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm
|
|
|
25
25
|
Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice
|
|
26
26
|
Try starting your prompt with a ->, and writing a list (1. Do X, 2. Do Y)
|
|
27
27
|
Press shift+tab to cycle through reasoning effort levels
|
|
28
|
+
omp can reconfigure and extend itself — ask any questions you have!
|
|
28
29
|
Lint/type errors piling up? `omp cleanse` (or /cleanse right here) hunts project diagnostics and fixes them with parallel subagents — esc cancels
|
|
@@ -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 => {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** Max gap (ms) between two spaces for the later one to count as OS key auto-repeat rather than a
|
|
2
|
+
* deliberate press. OS auto-repeat is fast; a deliberate tap (even a fast one) is slower. */
|
|
3
|
+
export const SPACE_REPEAT_MAX_GAP_MS = 120;
|
|
4
|
+
/** Two consecutive inter-space gaps are "mechanical" (machine-driven auto-repeat) when both are
|
|
5
|
+
* within {@link SPACE_REPEAT_MAX_GAP_MS} and differ by no more than this — an absolute jitter floor
|
|
6
|
+
* or, for slower repeat rates, {@link SPACE_REPEAT_JITTER_RATIO} of the smaller gap. OS key-repeat
|
|
7
|
+
* is metronomic; a human smashing the bar is fast but irregular, so its deltas never stay this
|
|
8
|
+
* steady. */
|
|
9
|
+
export const SPACE_REPEAT_JITTER_MS = 18;
|
|
10
|
+
export const SPACE_REPEAT_JITTER_RATIO = 0.35;
|
|
11
|
+
/** Consecutive mechanical (fast + steady) deltas that confirm the space bar is held and start
|
|
12
|
+
* recording. Needs a sustained metronomic cadence, so jittery smashing and deliberate taps never
|
|
13
|
+
* reach it. */
|
|
14
|
+
export const SPACE_HOLD_MECHANICAL_RUN = 2;
|
|
15
|
+
/** Idle gap (ms) after the last repeated space that counts as the space bar being released, ending
|
|
16
|
+
* the push-to-talk recording. Must comfortably exceed the OS key-repeat interval. */
|
|
17
|
+
export const SPACE_HOLD_RELEASE_MS = 250;
|
|
18
|
+
|
|
19
|
+
/** Whether two consecutive inter-space gaps look machine-driven: both within the auto-repeat band
|
|
20
|
+
* and steady enough (small absolute or proportional difference). OS key-repeat is metronomic, so
|
|
21
|
+
* its successive deltas match closely; human smashing is fast but irregular and deliberate taps are
|
|
22
|
+
* too slow, so neither passes. */
|
|
23
|
+
function gapsAreMechanical(gap: number, prevGap: number): boolean {
|
|
24
|
+
if (gap > SPACE_REPEAT_MAX_GAP_MS || prevGap > SPACE_REPEAT_MAX_GAP_MS) return false;
|
|
25
|
+
const tolerance = Math.max(SPACE_REPEAT_JITTER_MS, Math.min(gap, prevGap) * SPACE_REPEAT_JITTER_RATIO);
|
|
26
|
+
return Math.abs(gap - prevGap) <= tolerance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What a recognized space-bar hold drives — the push-to-talk start and stop. */
|
|
30
|
+
export interface SpaceHoldHandler {
|
|
31
|
+
/** Gate for the gesture. Returns false to keep the space bar inserting spaces normally, so
|
|
32
|
+
* disabling push-to-talk restores plain space behavior. */
|
|
33
|
+
enabled(): boolean;
|
|
34
|
+
/** A sustained hold was recognized. The optimistically-typed spaces have already been deleted. */
|
|
35
|
+
onStart(): void;
|
|
36
|
+
/** The held space bar was released (an idle gap with no further repeated spaces, or any other key). */
|
|
37
|
+
onEnd(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** What the host must do with a keypress after {@link SpaceHoldGesture.process} has seen it:
|
|
41
|
+
* `"pass"` — handle it normally; `"type"` — insert the space now (it is tracked back out if the run
|
|
42
|
+
* turns into a hold); `"swallow"` — drop it, the gesture consumed it. */
|
|
43
|
+
export type SpaceHoldStep = "pass" | "type" | "swallow";
|
|
44
|
+
|
|
45
|
+
/** Space-hold push-to-talk state machine. A held space bar emits OS auto-repeat: a *steady* stream
|
|
46
|
+
* of spaces at a fixed fast interval. We watch the inter-space deltas and only recognize a hold once
|
|
47
|
+
* {@link SPACE_HOLD_MECHANICAL_RUN} consecutive deltas are "mechanical" — both auto-repeat-fast and
|
|
48
|
+
* near-identical (see {@link gapsAreMechanical}). Smashing the bar is fast but jittery and deliberate
|
|
49
|
+
* taps are too slow, so neither escalates and both keep typing real spaces; the few spaces typed
|
|
50
|
+
* before a real hold is recognized are tracked back out of the host input. */
|
|
51
|
+
export class SpaceHoldGesture {
|
|
52
|
+
/** Unset (the default) keeps the space bar typing spaces. */
|
|
53
|
+
handler: SpaceHoldHandler | undefined;
|
|
54
|
+
/** Deletes the given number of characters before the host input's cursor. */
|
|
55
|
+
readonly #deleteTyped: (count: number) => void;
|
|
56
|
+
/** Host input state that rules the gesture out, e.g. an editor mode where space is not text. */
|
|
57
|
+
readonly #allowed: () => boolean;
|
|
58
|
+
/** Spaces the host typed in the current run; tracked back out when a hold is recognized. */
|
|
59
|
+
#typed = 0;
|
|
60
|
+
/** Consecutive "mechanical" deltas (fast + steady); a sustained run of these confirms a held bar. */
|
|
61
|
+
#mechanicalRun = 0;
|
|
62
|
+
/** Inter-space gap (ms) of the previous space pair, compared against the next to judge steadiness. */
|
|
63
|
+
#prevGap: number | undefined;
|
|
64
|
+
/** Monotonic timestamp (ms) of the last space, to measure the gap to the next one. */
|
|
65
|
+
#lastSpaceAt = Number.NEGATIVE_INFINITY;
|
|
66
|
+
/** True while a recognized space-hold push-to-talk recording is in progress. */
|
|
67
|
+
#active = false;
|
|
68
|
+
/** Idle timer that ends the hold once repeated spaces stop arriving. */
|
|
69
|
+
#releaseTimer: NodeJS.Timeout | undefined;
|
|
70
|
+
|
|
71
|
+
constructor(deleteTyped: (count: number) => void, allowed: () => boolean = () => true) {
|
|
72
|
+
this.#deleteTyped = deleteTyped;
|
|
73
|
+
this.#allowed = allowed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Feed one keypress, `isSpace` when it is a plain space bar press. */
|
|
77
|
+
process(isSpace: boolean): SpaceHoldStep {
|
|
78
|
+
if (this.#active) {
|
|
79
|
+
if (isSpace) {
|
|
80
|
+
// Auto-repeat while held: swallow it and keep the release timer alive.
|
|
81
|
+
this.#armReleaseTimer();
|
|
82
|
+
return "swallow";
|
|
83
|
+
}
|
|
84
|
+
// Any non-space means the bar was released — stop recording, then let the key through.
|
|
85
|
+
this.#end();
|
|
86
|
+
return "pass";
|
|
87
|
+
}
|
|
88
|
+
if (!isSpace) {
|
|
89
|
+
this.#resetRun();
|
|
90
|
+
return "pass";
|
|
91
|
+
}
|
|
92
|
+
if (!(this.handler?.enabled() && this.#allowed())) return "pass";
|
|
93
|
+
const now = performance.now();
|
|
94
|
+
const gap = now - this.#lastSpaceAt;
|
|
95
|
+
const prevGap = this.#prevGap;
|
|
96
|
+
this.#lastSpaceAt = now;
|
|
97
|
+
this.#prevGap = gap;
|
|
98
|
+
if (prevGap === undefined || !gapsAreMechanical(gap, prevGap)) {
|
|
99
|
+
// First space, a deliberate tap, or jittery smashing: not a steady machine cadence yet, so
|
|
100
|
+
// type a real space and reset the mechanical run.
|
|
101
|
+
this.#mechanicalRun = 0;
|
|
102
|
+
this.#typed++;
|
|
103
|
+
return "type";
|
|
104
|
+
}
|
|
105
|
+
// Steady fast repeat: swallow it. Once the cadence has held for SPACE_HOLD_MECHANICAL_RUN
|
|
106
|
+
// deltas it's a held bar — track back the few pre-burst spaces already typed and start.
|
|
107
|
+
if (++this.#mechanicalRun >= SPACE_HOLD_MECHANICAL_RUN) {
|
|
108
|
+
this.#deleteTyped(this.#typed);
|
|
109
|
+
this.#resetRun();
|
|
110
|
+
this.#active = true;
|
|
111
|
+
this.#armReleaseTimer();
|
|
112
|
+
this.handler?.onStart();
|
|
113
|
+
}
|
|
114
|
+
return "swallow";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#resetRun(): void {
|
|
118
|
+
this.#typed = 0;
|
|
119
|
+
this.#mechanicalRun = 0;
|
|
120
|
+
this.#prevGap = undefined;
|
|
121
|
+
this.#lastSpaceAt = Number.NEGATIVE_INFINITY;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#armReleaseTimer(): void {
|
|
125
|
+
if (this.#releaseTimer) clearTimeout(this.#releaseTimer);
|
|
126
|
+
this.#releaseTimer = setTimeout(() => {
|
|
127
|
+
this.#releaseTimer = undefined;
|
|
128
|
+
this.#end();
|
|
129
|
+
}, SPACE_HOLD_RELEASE_MS);
|
|
130
|
+
this.#releaseTimer.unref?.();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#end(): void {
|
|
134
|
+
if (!this.#active) return;
|
|
135
|
+
this.#active = false;
|
|
136
|
+
this.#resetRun();
|
|
137
|
+
if (this.#releaseTimer) {
|
|
138
|
+
clearTimeout(this.#releaseTimer);
|
|
139
|
+
this.#releaseTimer = undefined;
|
|
140
|
+
}
|
|
141
|
+
this.handler?.onEnd();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -301,6 +301,7 @@ interface StatusLineExternalInputs {
|
|
|
301
301
|
isStreaming: boolean | undefined;
|
|
302
302
|
isAutoThinking: boolean | undefined;
|
|
303
303
|
isFastModeActive: boolean;
|
|
304
|
+
anthropicSlowModeLabel: string | undefined;
|
|
304
305
|
compactionSpeculation: unknown;
|
|
305
306
|
}
|
|
306
307
|
|
|
@@ -2369,6 +2370,10 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2369
2370
|
isAutoThinking: this.session.isAutoThinking,
|
|
2370
2371
|
isFastModeActive:
|
|
2371
2372
|
typeof this.session.isFastModeActive === "function" ? this.session.isFastModeActive() : false,
|
|
2373
|
+
anthropicSlowModeLabel:
|
|
2374
|
+
typeof this.session.getAnthropicSlowModeLabel === "function"
|
|
2375
|
+
? this.session.getAnthropicSlowModeLabel()
|
|
2376
|
+
: undefined,
|
|
2372
2377
|
compactionSpeculation: this.session.compactionSpeculation,
|
|
2373
2378
|
};
|
|
2374
2379
|
}
|
|
@@ -2423,6 +2428,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2423
2428
|
left.isStreaming === right.isStreaming &&
|
|
2424
2429
|
left.isAutoThinking === right.isAutoThinking &&
|
|
2425
2430
|
left.isFastModeActive === right.isFastModeActive &&
|
|
2431
|
+
left.anthropicSlowModeLabel === right.anthropicSlowModeLabel &&
|
|
2426
2432
|
left.compactionSpeculation === right.compactionSpeculation
|
|
2427
2433
|
);
|
|
2428
2434
|
}
|
|
@@ -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 ?? ""));
|
package/src/status-line/host.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface StatusLineSession {
|
|
|
53
53
|
getContextUsage(): { tokens: number; contextWindow: number; percent: number | null } | undefined;
|
|
54
54
|
autoResolvedThinkingLevel(): string | undefined;
|
|
55
55
|
isFastModeActive(): boolean;
|
|
56
|
+
/** Anthropic subscription slow-mode label (`low priority until 14:30 · 62% left`) while active. */
|
|
57
|
+
getAnthropicSlowModeLabel?(): string | undefined;
|
|
56
58
|
getPrewalkState?(): unknown;
|
|
57
59
|
getAsyncJobSnapshot(): { running: readonly { type: string; agentId?: string }[] } | null | undefined;
|
|
58
60
|
getGoalModeState(): { goal?: { status: string; tokensUsed: number; tokenBudget?: number } } | undefined;
|
|
@@ -293,6 +293,10 @@ const modelSegment: StatusLineSegment = {
|
|
|
293
293
|
content += accentFg(ctx, "statusLineModel", tail);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
// Anthropic slow mode: a warning-colored badge so a throttled lane is never
|
|
297
|
+
// mistaken for normal service.
|
|
298
|
+
const slowModeLabel = ctx.session.getAnthropicSlowModeLabel?.();
|
|
299
|
+
if (slowModeLabel) content += theme.fg("warning", `${theme.sep.dot}${slowModeLabel}`);
|
|
296
300
|
return { content, visible: true };
|
|
297
301
|
},
|
|
298
302
|
};
|
|
@@ -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
|
|