pi-ui-extend 0.1.33 → 0.1.35
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/README.md +20 -0
- package/dist/app/app.js +4 -2
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/icons.js +1 -1
- package/dist/app/input/input-controller.d.ts +4 -1
- package/dist/app/input/input-controller.js +95 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +20 -0
- package/dist/app/input/terminal-edit-shortcuts.js +50 -16
- package/dist/app/model/model-usage-status.d.ts +2 -1
- package/dist/app/model/model-usage-status.js +33 -25
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/rendering/status-line-renderer.d.ts +1 -0
- package/dist/app/rendering/status-line-renderer.js +15 -1
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +110 -7
- package/dist/app/session/tabs-controller.js +3 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +37 -56
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -160,6 +160,26 @@ Before committing code changes, run:
|
|
|
160
160
|
npm run check
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
### Keeping the bundled extension's SDK pin in sync
|
|
164
|
+
|
|
165
|
+
Pix bundles the `pi-tools-suite` extension (in `external/pi-tools-suite`), which
|
|
166
|
+
runs inside the Pi host process. Its `@earendil-works/*` peerDependencies must
|
|
167
|
+
match the host Pi SDK version exactly, or npm can resolve a stale copy in the
|
|
168
|
+
suite's own `node_modules` and cause a double-load (e.g. `0.75.4` in the suite
|
|
169
|
+
vs `0.79.4` in the host).
|
|
170
|
+
|
|
171
|
+
When you bump the Pi SDK in the root `package.json`, re-pin the suite and
|
|
172
|
+
reinstall it:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run sync:sdk-pin # rewrite suite peerDeps to host version
|
|
176
|
+
cd external/pi-tools-suite && npm install --ignore-scripts # update suite lockfile/node_modules
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`npm run check` runs `npm run sync:sdk-pin:check` first, so a stale pin fails
|
|
180
|
+
the check fast. Use `npm run sync:sdk-pin:check` on its own for a drift-only
|
|
181
|
+
report (non-zero exit on drift).
|
|
182
|
+
|
|
163
183
|
## Configuration
|
|
164
184
|
|
|
165
185
|
Useful environment variables:
|
package/dist/app/app.js
CHANGED
|
@@ -166,7 +166,7 @@ export class PiUiExtendApp {
|
|
|
166
166
|
maxProjectSessions: () => this.pixConfig.maxProjectSessions,
|
|
167
167
|
blinkController: this.blinkController,
|
|
168
168
|
runtime: () => this.runtime,
|
|
169
|
-
createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options)),
|
|
169
|
+
createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options), this.runtime === undefined ? {} : { reuseServicesFrom: this.runtime }),
|
|
170
170
|
createRuntimeForSession: (sessionPath) => this.createRuntime({
|
|
171
171
|
...this.options,
|
|
172
172
|
noSession: false,
|
|
@@ -334,6 +334,7 @@ export class PiUiExtendApp {
|
|
|
334
334
|
});
|
|
335
335
|
this.todoWidgetController = new AppTodoWidgetController({
|
|
336
336
|
sessionFile: () => this.runtime?.session.sessionFile,
|
|
337
|
+
sessionId: () => this.runtime?.session.sessionId,
|
|
337
338
|
isRunning: () => this.running,
|
|
338
339
|
render: () => this.scheduleRender(),
|
|
339
340
|
});
|
|
@@ -761,10 +762,11 @@ export class PiUiExtendApp {
|
|
|
761
762
|
});
|
|
762
763
|
this.slashCommands = this.commandController.slashCommands;
|
|
763
764
|
}
|
|
764
|
-
createRuntime(options) {
|
|
765
|
+
createRuntime(options, runtimeOptions = {}) {
|
|
765
766
|
return createPixRuntime(options, {
|
|
766
767
|
eventBus: this.createExtensionEventBus(),
|
|
767
768
|
config: this.pixConfig,
|
|
769
|
+
...runtimeOptions,
|
|
768
770
|
});
|
|
769
771
|
}
|
|
770
772
|
async loadStartupConfig() {
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export declare const REQUEST_HISTORY_VERSION = 1;
|
|
|
19
19
|
export declare const REQUEST_HISTORY_MAX_ENTRIES = 200;
|
|
20
20
|
export declare const REQUEST_HISTORY_MAX_BYTES: number;
|
|
21
21
|
export declare const REQUEST_HISTORY_MAX_ENTRY_BYTES: number;
|
|
22
|
-
export declare const ENABLE_TERMINAL_KEY_REPORTING = "\u001B[>7u\u001B[
|
|
22
|
+
export declare const ENABLE_TERMINAL_KEY_REPORTING = "\u001B[>7u\u001B[?u\u001B[c";
|
|
23
|
+
export declare const ENABLE_TERMINAL_MODIFY_OTHER_KEYS = "\u001B[>4;2m";
|
|
23
24
|
export declare const DISABLE_TERMINAL_KEY_REPORTING = "\u001B[<u\u001B[>4;0m";
|
|
24
25
|
export declare const ENABLE_BRACKETED_PASTE = "\u001B[?2004h";
|
|
25
26
|
export declare const DISABLE_BRACKETED_PASTE = "\u001B[?2004l";
|
package/dist/app/constants.js
CHANGED
|
@@ -52,7 +52,12 @@ export const REQUEST_HISTORY_VERSION = 1;
|
|
|
52
52
|
export const REQUEST_HISTORY_MAX_ENTRIES = 200;
|
|
53
53
|
export const REQUEST_HISTORY_MAX_BYTES = 128 * 1024;
|
|
54
54
|
export const REQUEST_HISTORY_MAX_ENTRY_BYTES = 16 * 1024;
|
|
55
|
-
|
|
55
|
+
// Match pi/@earendil-works/pi-tui keyboard setup: request Kitty keyboard
|
|
56
|
+
// protocol flags, query the terminal response, and use xterm modifyOtherKeys
|
|
57
|
+
// only as a response-driven fallback. Enabling both protocols blindly can make
|
|
58
|
+
// terminals disagree about modified Enter reporting.
|
|
59
|
+
export const ENABLE_TERMINAL_KEY_REPORTING = "\x1b[>7u\x1b[?u\x1b[c";
|
|
60
|
+
export const ENABLE_TERMINAL_MODIFY_OTHER_KEYS = "\x1b[>4;2m";
|
|
56
61
|
export const DISABLE_TERMINAL_KEY_REPORTING = "\x1b[<u\x1b[>4;0m";
|
|
57
62
|
export const ENABLE_BRACKETED_PASTE = "\x1b[?2004h";
|
|
58
63
|
export const DISABLE_BRACKETED_PASTE = "\x1b[?2004l";
|
package/dist/app/icons.js
CHANGED
|
@@ -35,7 +35,7 @@ export declare class AppInputController {
|
|
|
35
35
|
private readonly pasteHandler;
|
|
36
36
|
constructor(host: InputControllerHost);
|
|
37
37
|
handleChunk(chunk: Buffer): void;
|
|
38
|
-
private
|
|
38
|
+
private consumeSharedEditorInput;
|
|
39
39
|
private drainInputBuffer;
|
|
40
40
|
private consumeBracketedPastePayload;
|
|
41
41
|
private getEscapeSequences;
|
|
@@ -45,6 +45,9 @@ export declare class AppInputController {
|
|
|
45
45
|
private consumeCommandArrowPageSequence;
|
|
46
46
|
private consumeCommandArrowPageMatch;
|
|
47
47
|
private consumeTerminalEditShortcutSequence;
|
|
48
|
+
private consumeIgnoredModifiedKeySequence;
|
|
49
|
+
private consumeClipboardImagePasteSequence;
|
|
50
|
+
private consumeShiftEnterSequence;
|
|
48
51
|
private consumeTerminalInterruptSequence;
|
|
49
52
|
private handleArrowUp;
|
|
50
53
|
private handleArrowDown;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InputPasteHandler } from "./input-paste-handler.js";
|
|
2
2
|
import { hasTerminalCommandModifier, isNativeCommandPressed, isNativeShiftPressed } from "./native-modifiers.js";
|
|
3
|
-
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, terminalEditShortcutForControlChar } from "./terminal-edit-shortcuts.js";
|
|
3
|
+
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, parseTerminalModifiedKeySequence, terminalEditShortcutForControlChar, terminalKeyIsClipboardImagePaste, terminalKeyIsShiftEnter, terminalKeyShouldIgnore, } from "./terminal-edit-shortcuts.js";
|
|
4
|
+
const SHIFT_ENTER_ESCAPE_SEQUENCES = ["\x1b\r", "\x1b\n"];
|
|
4
5
|
export class AppInputController {
|
|
5
6
|
host;
|
|
6
7
|
inputBuffer = "";
|
|
@@ -16,7 +17,7 @@ export class AppInputController {
|
|
|
16
17
|
this.drainInputBuffer();
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
if (this.
|
|
20
|
+
if (this.consumeSharedEditorInput(data))
|
|
20
21
|
return;
|
|
21
22
|
const extensionInput = this.host.handleExtensionTerminalInput(data);
|
|
22
23
|
if (extensionInput.consume)
|
|
@@ -28,17 +29,41 @@ export class AppInputController {
|
|
|
28
29
|
this.inputBuffer += data;
|
|
29
30
|
this.drainInputBuffer();
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
consumeSharedEditorInput(data) {
|
|
32
33
|
if (this.host.extensionInputUsesEditor?.() !== true)
|
|
33
34
|
return false;
|
|
34
35
|
if (this.host.inputEditor.isInBracketedPaste)
|
|
35
36
|
return false;
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
if (data === "\n") {
|
|
38
|
+
this.insertInputNewline();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (data === "\r" && this.isShiftPressed()) {
|
|
42
|
+
this.insertInputNewline();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (SHIFT_ENTER_ESCAPE_SEQUENCES.includes(data)) {
|
|
46
|
+
this.insertInputNewline();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (data === "\x16") {
|
|
50
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const modifiedKey = parseTerminalModifiedKeySequence(data);
|
|
54
|
+
if (modifiedKey.kind !== "key")
|
|
39
55
|
return false;
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
if (terminalKeyShouldIgnore(modifiedKey.key))
|
|
57
|
+
return true;
|
|
58
|
+
if (terminalKeyIsShiftEnter(modifiedKey.key)) {
|
|
59
|
+
this.insertInputNewline();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (terminalKeyIsClipboardImagePaste(modifiedKey.key)) {
|
|
63
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
42
67
|
}
|
|
43
68
|
drainInputBuffer() {
|
|
44
69
|
while (this.inputBuffer.length > 0) {
|
|
@@ -72,11 +97,26 @@ export class AppInputController {
|
|
|
72
97
|
continue;
|
|
73
98
|
if (terminalInterruptSequence === "pending")
|
|
74
99
|
return;
|
|
100
|
+
const shiftEnterSequence = this.consumeShiftEnterSequence();
|
|
101
|
+
if (shiftEnterSequence === "consumed")
|
|
102
|
+
continue;
|
|
103
|
+
if (shiftEnterSequence === "pending")
|
|
104
|
+
return;
|
|
105
|
+
const clipboardImagePasteSequence = this.consumeClipboardImagePasteSequence();
|
|
106
|
+
if (clipboardImagePasteSequence === "consumed")
|
|
107
|
+
continue;
|
|
108
|
+
if (clipboardImagePasteSequence === "pending")
|
|
109
|
+
return;
|
|
75
110
|
const terminalEditShortcutSequence = this.consumeTerminalEditShortcutSequence();
|
|
76
111
|
if (terminalEditShortcutSequence === "consumed")
|
|
77
112
|
continue;
|
|
78
113
|
if (terminalEditShortcutSequence === "pending")
|
|
79
114
|
return;
|
|
115
|
+
const ignoredModifiedKeySequence = this.consumeIgnoredModifiedKeySequence();
|
|
116
|
+
if (ignoredModifiedKeySequence === "consumed")
|
|
117
|
+
continue;
|
|
118
|
+
if (ignoredModifiedKeySequence === "pending")
|
|
119
|
+
return;
|
|
80
120
|
if (this.consumeEscapeSequence())
|
|
81
121
|
continue;
|
|
82
122
|
if (this.isPendingEscapeSequence())
|
|
@@ -106,13 +146,9 @@ export class AppInputController {
|
|
|
106
146
|
}
|
|
107
147
|
getEscapeSequences() {
|
|
108
148
|
return [
|
|
109
|
-
[
|
|
110
|
-
["\x1b[13;2~", () => this.insertInputNewline()],
|
|
111
|
-
["\x1b[27;2;13~", () => this.insertInputNewline()],
|
|
149
|
+
...SHIFT_ENTER_ESCAPE_SEQUENCES.map((sequence) => [sequence, () => this.insertInputNewline()]),
|
|
112
150
|
["\x1b[13u", () => this.host.handleEnter()],
|
|
113
151
|
["\x1b[13;1u", () => this.host.handleEnter()],
|
|
114
|
-
["\x1b\r", () => this.insertInputNewline()],
|
|
115
|
-
["\x1b\n", () => this.insertInputNewline()],
|
|
116
152
|
["\x1b[5~", () => this.host.scrollByPage(-1)],
|
|
117
153
|
["\x1b[6~", () => this.host.scrollByPage(1)],
|
|
118
154
|
["\x1b[A", () => this.handleArrowUp()],
|
|
@@ -139,8 +175,6 @@ export class AppInputController {
|
|
|
139
175
|
["\x1b[201~", () => this.pasteHandler.endBracketedPaste()],
|
|
140
176
|
["\x1b[1;2H", () => { this.host.inputEditor.moveToLineStartExtend(); this.host.render(); }],
|
|
141
177
|
["\x1b[1;2F", () => { this.host.inputEditor.moveToLineEndExtend(); this.host.render(); }],
|
|
142
|
-
["\x1b[118;5u", () => { void this.pasteHandler.handleClipboardImagePaste(); }],
|
|
143
|
-
["\x1b[27;5;118~", () => { void this.pasteHandler.handleClipboardImagePaste(); }],
|
|
144
178
|
["\x1b[122;9u", () => this.undoInput()],
|
|
145
179
|
["\x1b[27;9;122~", () => this.undoInput()],
|
|
146
180
|
["\x1b[90;10u", () => this.redoInput()],
|
|
@@ -232,6 +266,43 @@ export class AppInputController {
|
|
|
232
266
|
}
|
|
233
267
|
return "consumed";
|
|
234
268
|
}
|
|
269
|
+
consumeIgnoredModifiedKeySequence() {
|
|
270
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
271
|
+
if (result.kind === "pending")
|
|
272
|
+
return "pending";
|
|
273
|
+
if (result.kind === "none")
|
|
274
|
+
return "none";
|
|
275
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
276
|
+
return "none";
|
|
277
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
278
|
+
return "consumed";
|
|
279
|
+
}
|
|
280
|
+
consumeClipboardImagePasteSequence() {
|
|
281
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
282
|
+
if (result.kind === "pending")
|
|
283
|
+
return "pending";
|
|
284
|
+
if (result.kind === "none")
|
|
285
|
+
return "none";
|
|
286
|
+
if (!terminalKeyIsClipboardImagePaste(result.key))
|
|
287
|
+
return "none";
|
|
288
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
289
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
290
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
291
|
+
return "consumed";
|
|
292
|
+
}
|
|
293
|
+
consumeShiftEnterSequence() {
|
|
294
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
295
|
+
if (result.kind === "pending")
|
|
296
|
+
return "pending";
|
|
297
|
+
if (result.kind === "none")
|
|
298
|
+
return "none";
|
|
299
|
+
if (!terminalKeyIsShiftEnter(result.key))
|
|
300
|
+
return "none";
|
|
301
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
302
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
303
|
+
this.insertInputNewline();
|
|
304
|
+
return "consumed";
|
|
305
|
+
}
|
|
235
306
|
consumeTerminalInterruptSequence() {
|
|
236
307
|
const result = parseTerminalInterruptSequence(this.inputBuffer);
|
|
237
308
|
if (result.kind === "pending")
|
|
@@ -401,7 +472,15 @@ export class AppInputController {
|
|
|
401
472
|
this.host.toggleVoiceRecording();
|
|
402
473
|
return;
|
|
403
474
|
}
|
|
404
|
-
if (char === "\
|
|
475
|
+
if (char === "\n") {
|
|
476
|
+
if (this.host.inputEditor.isInBracketedPaste) {
|
|
477
|
+
this.pasteHandler.appendBracketedPasteText("\n");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.insertInputNewline();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (char === "\r") {
|
|
405
484
|
if (this.host.inputEditor.isInBracketedPaste) {
|
|
406
485
|
this.pasteHandler.appendBracketedPasteText("\n");
|
|
407
486
|
return;
|
|
@@ -92,8 +92,10 @@ export class InputPasteHandler {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
handlePasteEnd(text) {
|
|
95
|
-
if (!text)
|
|
95
|
+
if (!text) {
|
|
96
|
+
void this.handleClipboardImagePaste();
|
|
96
97
|
return;
|
|
98
|
+
}
|
|
97
99
|
const filePath = this.plainPasteFilePath(text);
|
|
98
100
|
if (filePath) {
|
|
99
101
|
if (isImagePath(filePath) && Date.now() < this.suppressImagePathPasteUntil) {
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export type TerminalEditShortcut = "undo" | "redo";
|
|
2
|
+
export type ParsedTerminalModifiedKeyResult = {
|
|
3
|
+
readonly kind: "key";
|
|
4
|
+
readonly key: ParsedModifiedKey;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "pending";
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "none";
|
|
9
|
+
};
|
|
2
10
|
export type TerminalEditShortcutSequenceResult = {
|
|
3
11
|
readonly kind: "shortcut";
|
|
4
12
|
readonly shortcut: TerminalEditShortcut;
|
|
@@ -11,6 +19,14 @@ export type TerminalEditShortcutSequenceResult = {
|
|
|
11
19
|
} | {
|
|
12
20
|
readonly kind: "none";
|
|
13
21
|
};
|
|
22
|
+
interface ParsedModifiedKey {
|
|
23
|
+
readonly codepoint: number;
|
|
24
|
+
readonly baseLayoutKey: number | undefined;
|
|
25
|
+
readonly modifier: number;
|
|
26
|
+
readonly eventType: number | undefined;
|
|
27
|
+
readonly length: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function parseTerminalModifiedKeySequence(input: string): ParsedTerminalModifiedKeyResult;
|
|
14
30
|
export declare function parseTerminalEditShortcutSequence(input: string): TerminalEditShortcutSequenceResult;
|
|
15
31
|
export declare function parseTerminalInterruptSequence(input: string): {
|
|
16
32
|
readonly kind: "interrupt";
|
|
@@ -20,4 +36,8 @@ export declare function parseTerminalInterruptSequence(input: string): {
|
|
|
20
36
|
} | {
|
|
21
37
|
readonly kind: "none";
|
|
22
38
|
};
|
|
39
|
+
export declare function terminalKeyIsShiftEnter(key: ParsedModifiedKey): boolean;
|
|
40
|
+
export declare function terminalKeyIsClipboardImagePaste(key: ParsedModifiedKey): boolean;
|
|
41
|
+
export declare function terminalKeyShouldIgnore(key: ParsedModifiedKey): boolean;
|
|
23
42
|
export declare function terminalEditShortcutForControlChar(char: string, shiftPressed: boolean): TerminalEditShortcut | undefined;
|
|
43
|
+
export {};
|
|
@@ -3,34 +3,60 @@ const CONTROL_MODIFIER_FLAG = 4;
|
|
|
3
3
|
const COMMAND_MODIFIER_FLAG = 8;
|
|
4
4
|
const LOCK_MODIFIER_MASK = 64 + 128;
|
|
5
5
|
const KEY_CODE_C = 99;
|
|
6
|
+
const KEY_CODE_ENTER = 13;
|
|
7
|
+
const KEY_CODE_V = 118;
|
|
6
8
|
const KEY_CODE_Y = 121;
|
|
7
9
|
const KEY_CODE_Z = 122;
|
|
8
10
|
const CYRILLIC_SMALL_ES_CODE = 1089;
|
|
9
11
|
const CYRILLIC_CAPITAL_ES_CODE = 1057;
|
|
12
|
+
const CYRILLIC_SMALL_EM_CODE = 1084;
|
|
13
|
+
const CYRILLIC_CAPITAL_EM_CODE = 1052;
|
|
10
14
|
const KITTY_CSI_U_SEQUENCE = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u/;
|
|
11
15
|
const XTERM_MODIFY_OTHER_KEYS_SEQUENCE = /^\x1b\[27;(\d+);(\d+)~/;
|
|
12
|
-
export function
|
|
16
|
+
export function parseTerminalModifiedKeySequence(input) {
|
|
13
17
|
const kitty = parseKittyCsiUSequence(input);
|
|
14
18
|
if (kitty)
|
|
15
|
-
return
|
|
19
|
+
return { kind: "key", key: kitty };
|
|
16
20
|
const xterm = parseXtermModifyOtherKeysSequence(input);
|
|
17
21
|
if (xterm)
|
|
18
|
-
return
|
|
19
|
-
if (
|
|
22
|
+
return { kind: "key", key: xterm };
|
|
23
|
+
if (isPotentialModifiedKeyPrefix(input))
|
|
20
24
|
return { kind: "pending" };
|
|
21
25
|
return { kind: "none" };
|
|
22
26
|
}
|
|
27
|
+
export function parseTerminalEditShortcutSequence(input) {
|
|
28
|
+
const result = parseTerminalModifiedKeySequence(input);
|
|
29
|
+
if (result.kind === "pending")
|
|
30
|
+
return { kind: "pending" };
|
|
31
|
+
if (result.kind === "none")
|
|
32
|
+
return { kind: "none" };
|
|
33
|
+
return terminalEditShortcutResult(result.key);
|
|
34
|
+
}
|
|
23
35
|
export function parseTerminalInterruptSequence(input) {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
return { kind: "interrupt", length: kitty.length };
|
|
27
|
-
const xterm = parseXtermModifyOtherKeysSequence(input);
|
|
28
|
-
if (xterm && terminalKeyIsControlC(xterm))
|
|
29
|
-
return { kind: "interrupt", length: xterm.length };
|
|
30
|
-
if (isPotentialInterruptPrefix(input))
|
|
36
|
+
const result = parseTerminalModifiedKeySequence(input);
|
|
37
|
+
if (result.kind === "pending")
|
|
31
38
|
return { kind: "pending" };
|
|
39
|
+
if (result.kind === "key" && terminalKeyIsControlC(result.key))
|
|
40
|
+
return { kind: "interrupt", length: result.key.length };
|
|
41
|
+
if (result.kind === "key" || isPotentialInterruptPrefix(input))
|
|
42
|
+
return { kind: "none" };
|
|
32
43
|
return { kind: "none" };
|
|
33
44
|
}
|
|
45
|
+
export function terminalKeyIsShiftEnter(key) {
|
|
46
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
47
|
+
if ((effectiveModifier & SHIFT_MODIFIER_FLAG) === 0)
|
|
48
|
+
return false;
|
|
49
|
+
return terminalKeyMatchesCodepoint(key, KEY_CODE_ENTER);
|
|
50
|
+
}
|
|
51
|
+
export function terminalKeyIsClipboardImagePaste(key) {
|
|
52
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
53
|
+
if ((effectiveModifier & (CONTROL_MODIFIER_FLAG | COMMAND_MODIFIER_FLAG)) === 0)
|
|
54
|
+
return false;
|
|
55
|
+
return terminalKeyMatchesCodepoint(key, KEY_CODE_V, CYRILLIC_SMALL_EM_CODE, CYRILLIC_CAPITAL_EM_CODE);
|
|
56
|
+
}
|
|
57
|
+
export function terminalKeyShouldIgnore(key) {
|
|
58
|
+
return key.eventType === 3;
|
|
59
|
+
}
|
|
34
60
|
export function terminalEditShortcutForControlChar(char, shiftPressed) {
|
|
35
61
|
if (char === "\u001a")
|
|
36
62
|
return shiftPressed ? "redo" : "undo";
|
|
@@ -95,6 +121,17 @@ function interruptCodepointIsC(codepoint) {
|
|
|
95
121
|
const normalized = normalizeLetterCodepoint(codepoint);
|
|
96
122
|
return normalized === KEY_CODE_C || normalized === CYRILLIC_SMALL_ES_CODE || normalized === CYRILLIC_CAPITAL_ES_CODE;
|
|
97
123
|
}
|
|
124
|
+
function terminalKeyMatchesCodepoint(key, ...expectedCodepoints) {
|
|
125
|
+
return expectedCodepoints.some((codepoint) => keyMatchesCodepoint(key, codepoint));
|
|
126
|
+
}
|
|
127
|
+
function keyMatchesCodepoint(key, expectedCodepoint) {
|
|
128
|
+
const normalizedExpected = normalizeLetterCodepoint(expectedCodepoint);
|
|
129
|
+
const primary = normalizeLetterCodepoint(key.codepoint);
|
|
130
|
+
if (primary === normalizedExpected)
|
|
131
|
+
return true;
|
|
132
|
+
const baseLayout = key.baseLayoutKey;
|
|
133
|
+
return baseLayout !== undefined && normalizeLetterCodepoint(baseLayout) === normalizedExpected;
|
|
134
|
+
}
|
|
98
135
|
function terminalEditShortcutForKey(key, effectiveModifier) {
|
|
99
136
|
const codepoint = editShortcutCodepoint(key);
|
|
100
137
|
if (codepoint === KEY_CODE_Y)
|
|
@@ -114,16 +151,13 @@ function normalizeLetterCodepoint(codepoint) {
|
|
|
114
151
|
return codepoint + 32;
|
|
115
152
|
return codepoint;
|
|
116
153
|
}
|
|
117
|
-
function
|
|
154
|
+
function isPotentialModifiedKeyPrefix(input) {
|
|
118
155
|
if (!input.startsWith("\x1b["))
|
|
119
156
|
return false;
|
|
120
157
|
if (input.includes("u") || input.includes("~"))
|
|
121
158
|
return false;
|
|
122
159
|
const body = input.slice(2);
|
|
123
|
-
|
|
124
|
-
return false;
|
|
125
|
-
const possibleStarts = ["122", "121", "90", "27;"];
|
|
126
|
-
return possibleStarts.some((start) => start.startsWith(body) || body.startsWith(start));
|
|
160
|
+
return /^[\d:;]*$/.test(body);
|
|
127
161
|
}
|
|
128
162
|
function isPotentialInterruptPrefix(input) {
|
|
129
163
|
if (!input.startsWith("\x1b["))
|
|
@@ -7,13 +7,14 @@ export type ModelUsageDescriptor = BaseModelUsageDescriptor & ({
|
|
|
7
7
|
} | {
|
|
8
8
|
readonly kind: "google-antigravity";
|
|
9
9
|
readonly quotaModelKey: string;
|
|
10
|
-
readonly account
|
|
10
|
+
readonly account?: AntigravityQuotaAccount;
|
|
11
11
|
readonly accounts?: readonly AntigravityQuotaAccount[];
|
|
12
12
|
});
|
|
13
13
|
export type ModelUsageLimitWindow = {
|
|
14
14
|
readonly remainingPercent: number;
|
|
15
15
|
readonly resetAt: number;
|
|
16
16
|
readonly windowSeconds: number;
|
|
17
|
+
readonly hasKnownWindowDuration?: boolean;
|
|
17
18
|
};
|
|
18
19
|
export type ModelUsageStatus = {
|
|
19
20
|
readonly modelKey: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
2
|
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
4
|
import { join } from "node:path";
|
|
6
5
|
import { formatCompactProgressBar } from "../../context-progress-bar.js";
|
|
6
|
+
import { APP_ICONS } from "../icons.js";
|
|
7
7
|
const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
8
8
|
const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
9
9
|
const ZHIPU_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/limit";
|
|
@@ -35,16 +35,12 @@ export function modelUsageDescriptor(model) {
|
|
|
35
35
|
}
|
|
36
36
|
if (ANTIGRAVITY_QUOTA_PROVIDERS.has(provider)) {
|
|
37
37
|
const quotaModelKey = resolveAntigravityQuotaModelKey(model);
|
|
38
|
-
|
|
39
|
-
const account = readActiveAntigravityQuotaAccount(accounts);
|
|
40
|
-
if (!quotaModelKey || !account)
|
|
38
|
+
if (!quotaModelKey)
|
|
41
39
|
return undefined;
|
|
42
40
|
return {
|
|
43
41
|
kind: "google-antigravity",
|
|
44
|
-
modelKey: `${model.provider}/${model.id}
|
|
42
|
+
modelKey: `${model.provider}/${model.id}`,
|
|
45
43
|
quotaModelKey,
|
|
46
|
-
account,
|
|
47
|
-
accounts,
|
|
48
44
|
};
|
|
49
45
|
}
|
|
50
46
|
return undefined;
|
|
@@ -395,7 +391,7 @@ export function googleAntigravityUsageStatusFromResponse(data, descriptor, now =
|
|
|
395
391
|
modelKey: descriptor.modelKey,
|
|
396
392
|
provider: "google-antigravity",
|
|
397
393
|
updatedAt: now,
|
|
398
|
-
...(descriptor.account
|
|
394
|
+
...(descriptor.account?.email ? { accountEmail: descriptor.account.email } : {}),
|
|
399
395
|
...(weekly ? { weekly } : {}),
|
|
400
396
|
...(hourly ? { hourly } : {}),
|
|
401
397
|
};
|
|
@@ -413,7 +409,9 @@ function googleAntigravityWindowFromResponse(data, quotaModelKey, now) {
|
|
|
413
409
|
}
|
|
414
410
|
async function queryGoogleAntigravityModelUsage(descriptor) {
|
|
415
411
|
const now = Date.now();
|
|
416
|
-
const accounts =
|
|
412
|
+
const accounts = await readAllAntigravityQuotaAccounts();
|
|
413
|
+
if (accounts.length === 0)
|
|
414
|
+
return undefined;
|
|
417
415
|
const windows = (await Promise.all(accounts.map(async (account) => {
|
|
418
416
|
try {
|
|
419
417
|
const response = await fetchGoogleAntigravityQuotaForAccount(account, now);
|
|
@@ -451,7 +449,7 @@ const GOOGLE_ACCOUNT_QUOTA_WINDOWS = [
|
|
|
451
449
|
{ label: "G3 Pro", quotaModelKey: "gemini-3.1-pro-low" },
|
|
452
450
|
];
|
|
453
451
|
async function queryGoogleAntigravityAccountUsage(now) {
|
|
454
|
-
const accounts = readAllAntigravityQuotaAccounts();
|
|
452
|
+
const accounts = await readAllAntigravityQuotaAccounts();
|
|
455
453
|
const results = await Promise.all(accounts.map(async (account) => {
|
|
456
454
|
const accountLabel = account.email ?? maskCredential(account.refreshToken);
|
|
457
455
|
try {
|
|
@@ -474,12 +472,8 @@ async function queryGoogleAntigravityAccountUsage(now) {
|
|
|
474
472
|
}));
|
|
475
473
|
return results;
|
|
476
474
|
}
|
|
477
|
-
function
|
|
478
|
-
const credential =
|
|
479
|
-
return accounts[clampAccountIndex(credential?.activeIndex, accounts.length)];
|
|
480
|
-
}
|
|
481
|
-
function readAllAntigravityQuotaAccounts() {
|
|
482
|
-
const credential = readPiAuthSync().antigravity;
|
|
475
|
+
async function readAllAntigravityQuotaAccounts() {
|
|
476
|
+
const credential = (await readPiAuth()).antigravity;
|
|
483
477
|
if (!credential)
|
|
484
478
|
return [];
|
|
485
479
|
const credentialClient = getGoogleOAuthClientCredentials(credential);
|
|
@@ -504,14 +498,6 @@ function readAllAntigravityQuotaAccounts() {
|
|
|
504
498
|
}) : undefined;
|
|
505
499
|
return account ? [account] : [];
|
|
506
500
|
}
|
|
507
|
-
function readPiAuthSync() {
|
|
508
|
-
try {
|
|
509
|
-
return JSON.parse(readFileSync(getPiAuthPath(), "utf8"));
|
|
510
|
-
}
|
|
511
|
-
catch {
|
|
512
|
-
return {};
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
501
|
function getAccountRefreshToken(account) {
|
|
516
502
|
if (account.refreshToken)
|
|
517
503
|
return account.refreshToken;
|
|
@@ -844,6 +830,7 @@ function modelUsageWindow(window, now) {
|
|
|
844
830
|
remainingPercent: clampPercent(Math.round(100 - window.used_percent)),
|
|
845
831
|
resetAt: now + Math.max(0, Math.round(window.reset_after_seconds)) * 1000,
|
|
846
832
|
windowSeconds: Math.max(0, Math.round(window.limit_window_seconds)),
|
|
833
|
+
hasKnownWindowDuration: true,
|
|
847
834
|
};
|
|
848
835
|
}
|
|
849
836
|
function accountWindowFromRateLimit(window, now) {
|
|
@@ -930,5 +917,26 @@ function maskCredential(value) {
|
|
|
930
917
|
return `${visible.slice(0, 4)}****${visible.slice(-4)}`;
|
|
931
918
|
}
|
|
932
919
|
function formatUsageWindow(_prefix, window, now) {
|
|
933
|
-
|
|
920
|
+
const warning = modelUsageWindowWillExhaustBeforeReset(window, now) ? ` ${APP_ICONS.alert}` : "";
|
|
921
|
+
return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)}${warning} ${formatDurationShort(window.resetAt, now)}`;
|
|
922
|
+
}
|
|
923
|
+
function modelUsageWindowWillExhaustBeforeReset(window, now) {
|
|
924
|
+
if (!window.hasKnownWindowDuration)
|
|
925
|
+
return false;
|
|
926
|
+
if (window.windowSeconds <= DAY_SECONDS)
|
|
927
|
+
return false;
|
|
928
|
+
if (window.remainingPercent <= 0)
|
|
929
|
+
return false;
|
|
930
|
+
const timeUntilResetSeconds = Math.max(0, (window.resetAt - now) / 1000);
|
|
931
|
+
const elapsedSeconds = Math.max(0, window.windowSeconds - timeUntilResetSeconds);
|
|
932
|
+
if (elapsedSeconds <= 0)
|
|
933
|
+
return false;
|
|
934
|
+
const total = 100;
|
|
935
|
+
const used = total - window.remainingPercent;
|
|
936
|
+
if (used <= 0)
|
|
937
|
+
return false;
|
|
938
|
+
const remaining = total - used;
|
|
939
|
+
const averageRate = used / elapsedSeconds;
|
|
940
|
+
const projectedSecondsUntilExhaustion = remaining / averageRate;
|
|
941
|
+
return projectedSecondsUntilExhaustion < timeUntilResetSeconds;
|
|
934
942
|
}
|
|
@@ -14,6 +14,7 @@ export type ConversationEntryRenderOptions = {
|
|
|
14
14
|
availableThinkingLevels?: readonly string[];
|
|
15
15
|
superCompactTools?: boolean;
|
|
16
16
|
allThinkingExpanded?: boolean;
|
|
17
|
+
currentTimeMs?: number;
|
|
17
18
|
renderInlineUserMessageMenu: (entry: Extract<Entry, {
|
|
18
19
|
kind: "user";
|
|
19
20
|
}>, context: InlineUserMessageMenuContext) => RenderedLine[];
|
|
@@ -75,7 +75,7 @@ function renderAssistantLines(text, width, options) {
|
|
|
75
75
|
if (!displayText)
|
|
76
76
|
return [];
|
|
77
77
|
const { left: contentLeft, contentWidth } = horizontalPaddingLayout(width);
|
|
78
|
-
const contentLines = renderMarkdownTextLines(displayText, contentWidth, contentLeft);
|
|
78
|
+
const contentLines = renderMarkdownTextLines(displayText, contentWidth, contentLeft, { preserveWrappedWordSeparator: true });
|
|
79
79
|
if (contentLines.length === 0)
|
|
80
80
|
return [];
|
|
81
81
|
const lines = [];
|
|
@@ -8,6 +8,7 @@ export type ConversationToolRenderOptions = {
|
|
|
8
8
|
availableThinkingLevels?: readonly string[];
|
|
9
9
|
superCompactTools?: boolean;
|
|
10
10
|
allThinkingExpanded?: boolean;
|
|
11
|
+
currentTimeMs?: number;
|
|
11
12
|
};
|
|
12
13
|
export declare function renderConversationToolEntry(entry: Extract<Entry, {
|
|
13
14
|
kind: "tool";
|
|
@@ -56,9 +56,11 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
56
56
|
const headerColorOverride = entry.level
|
|
57
57
|
? thinkingLevelThemeColor(entry.level, options.colors, options.availableThinkingLevels)
|
|
58
58
|
: undefined;
|
|
59
|
+
const elapsed = thinkingElapsedText(entry, options.currentTimeMs ?? Date.now());
|
|
59
60
|
return renderToolBlock({
|
|
60
61
|
id: entry.id,
|
|
61
62
|
toolName: THINKING_TOOL_NAME,
|
|
63
|
+
...(elapsed === undefined ? {} : { headerArgs: elapsed }),
|
|
62
64
|
expanded,
|
|
63
65
|
status: entry.status,
|
|
64
66
|
isError: false,
|
|
@@ -74,6 +76,25 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
74
76
|
...(headerColorOverride === undefined ? {} : { headerColorOverride }),
|
|
75
77
|
});
|
|
76
78
|
}
|
|
79
|
+
function thinkingElapsedText(entry, currentTimeMs) {
|
|
80
|
+
if (entry.startedAt === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
const endTimeMs = entry.finishedAt ?? currentTimeMs;
|
|
83
|
+
const elapsedMs = Math.max(0, endTimeMs - entry.startedAt);
|
|
84
|
+
return formatThinkingElapsed(elapsedMs);
|
|
85
|
+
}
|
|
86
|
+
function formatThinkingElapsed(elapsedMs) {
|
|
87
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
88
|
+
const seconds = totalSeconds % 60;
|
|
89
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
90
|
+
if (totalMinutes < 1)
|
|
91
|
+
return `${seconds}s`;
|
|
92
|
+
const minutes = totalMinutes % 60;
|
|
93
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
94
|
+
if (hours < 1)
|
|
95
|
+
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
96
|
+
return `${hours}h ${minutes.toString().padStart(2, "0")}m`;
|
|
97
|
+
}
|
|
77
98
|
function trimTrailingBlankLines(text) {
|
|
78
99
|
return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
|
|
79
100
|
}
|