pi-ui-extend 0.1.8 → 0.1.11
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 +57 -2
- package/bin/pix.mjs +4 -4
- package/dist/app/app.d.ts +4 -0
- package/dist/app/app.js +112 -45
- package/dist/app/{cli.d.ts → cli/cli.d.ts} +1 -1
- package/dist/app/{cli.js → cli/cli.js} +1 -1
- package/dist/app/{install.d.ts → cli/install.d.ts} +2 -0
- package/dist/app/{install.js → cli/install.js} +18 -3
- package/dist/app/{command-controller.d.ts → commands/command-controller.d.ts} +1 -1
- package/dist/app/{command-controller.js → commands/command-controller.js} +4 -0
- package/dist/app/{command-host.d.ts → commands/command-host.d.ts} +7 -3
- package/dist/app/{command-model-actions.d.ts → commands/command-model-actions.d.ts} +6 -1
- package/dist/app/{command-model-actions.js → commands/command-model-actions.js} +106 -2
- package/dist/app/{command-navigation-actions.d.ts → commands/command-navigation-actions.d.ts} +7 -2
- package/dist/app/{command-navigation-actions.js → commands/command-navigation-actions.js} +42 -19
- package/dist/app/{command-registry.d.ts → commands/command-registry.d.ts} +5 -1
- package/dist/app/{command-registry.js → commands/command-registry.js} +32 -0
- package/dist/app/{command-runtime.js → commands/command-runtime.js} +1 -1
- package/dist/app/{command-session-actions.d.ts → commands/command-session-actions.d.ts} +1 -0
- package/dist/app/{command-session-actions.js → commands/command-session-actions.js} +18 -8
- package/dist/app/{shell-controller.d.ts → commands/shell-controller.d.ts} +2 -1
- package/dist/app/{shell-controller.js → commands/shell-controller.js} +2 -2
- package/dist/app/{slash-commands.d.ts → commands/slash-commands.d.ts} +2 -2
- package/dist/app/{slash-commands.js → commands/slash-commands.js} +1 -1
- package/dist/app/constants.d.ts +1 -1
- package/dist/app/constants.js +1 -1
- package/dist/app/{extension-actions-controller.d.ts → extensions/extension-actions-controller.d.ts} +1 -1
- package/dist/app/{extension-actions-controller.js → extensions/extension-actions-controller.js} +1 -1
- package/dist/app/{extension-ui-controller.d.ts → extensions/extension-ui-controller.d.ts} +3 -3
- package/dist/app/{extension-ui-controller.js → extensions/extension-ui-controller.js} +3 -3
- package/dist/app/icons.js +1 -1
- package/dist/app/input/autocomplete-controller.d.ts +52 -0
- package/dist/app/input/autocomplete-controller.js +352 -0
- package/dist/app/{input-action-controller.d.ts → input/input-action-controller.d.ts} +8 -7
- package/dist/app/{input-action-controller.js → input/input-action-controller.js} +24 -3
- package/dist/app/{input-controller.d.ts → input/input-controller.d.ts} +4 -3
- package/dist/app/{input-controller.js → input/input-controller.js} +3 -1
- package/dist/app/{input-paste-handler.d.ts → input/input-paste-handler.d.ts} +2 -1
- package/dist/app/{input-paste-handler.js → input/input-paste-handler.js} +25 -21
- package/dist/app/{native-modifiers.js → input/native-modifiers.js} +2 -2
- package/dist/app/{prompt-enhancer-controller.d.ts → input/prompt-enhancer-controller.d.ts} +5 -5
- package/dist/app/{prompt-enhancer-controller.js → input/prompt-enhancer-controller.js} +3 -3
- package/dist/app/{voice-controller.d.ts → input/voice-controller.d.ts} +3 -1
- package/dist/app/{voice-controller.js → input/voice-controller.js} +29 -17
- package/dist/app/{model-ref.d.ts → model/model-ref.d.ts} +1 -1
- package/dist/app/{model-ref.js → model/model-ref.js} +1 -1
- package/dist/app/{model-usage-controller.js → model/model-usage-controller.js} +1 -1
- package/dist/app/{model-usage-status.d.ts → model/model-usage-status.d.ts} +10 -1
- package/dist/app/{model-usage-status.js → model/model-usage-status.js} +125 -35
- package/dist/app/{menu-items-controller.d.ts → popup/menu-items-controller.d.ts} +4 -4
- package/dist/app/{menu-items-controller.js → popup/menu-items-controller.js} +5 -5
- package/dist/app/{popup-action-controller.d.ts → popup/popup-action-controller.d.ts} +4 -4
- package/dist/app/{popup-action-controller.js → popup/popup-action-controller.js} +3 -3
- package/dist/app/{popup-menu-controller.d.ts → popup/popup-menu-controller.d.ts} +4 -4
- package/dist/app/{popup-menu-controller.js → popup/popup-menu-controller.js} +7 -7
- package/dist/app/process.d.ts +17 -0
- package/dist/app/process.js +68 -0
- package/dist/app/{conversation-entry-renderer.d.ts → rendering/conversation-entry-renderer.d.ts} +3 -3
- package/dist/app/{conversation-entry-renderer.js → rendering/conversation-entry-renderer.js} +20 -9
- package/dist/app/{conversation-shell-renderer.d.ts → rendering/conversation-shell-renderer.d.ts} +1 -1
- package/dist/app/{conversation-shell-renderer.js → rendering/conversation-shell-renderer.js} +1 -1
- package/dist/app/{conversation-tool-renderer.d.ts → rendering/conversation-tool-renderer.d.ts} +3 -3
- package/dist/app/{conversation-tool-renderer.js → rendering/conversation-tool-renderer.js} +10 -9
- package/dist/app/{conversation-viewport.d.ts → rendering/conversation-viewport.d.ts} +3 -3
- package/dist/app/{dcp-stats.js → rendering/dcp-stats.js} +1 -1
- package/dist/app/{editor-layout-renderer.d.ts → rendering/editor-layout-renderer.d.ts} +4 -3
- package/dist/app/{editor-layout-renderer.js → rendering/editor-layout-renderer.js} +13 -3
- package/dist/app/{editor-panels.d.ts → rendering/editor-panels.d.ts} +2 -2
- package/dist/app/{editor-panels.js → rendering/editor-panels.js} +4 -4
- package/dist/app/{message-content.d.ts → rendering/message-content.d.ts} +1 -1
- package/dist/app/{message-content.js → rendering/message-content.js} +66 -8
- package/dist/app/{render-controller.d.ts → rendering/render-controller.d.ts} +6 -6
- package/dist/app/{render-controller.js → rendering/render-controller.js} +11 -6
- package/dist/app/{render-text.d.ts → rendering/render-text.d.ts} +5 -2
- package/dist/app/{render-text.js → rendering/render-text.js} +53 -5
- package/dist/app/{status-line-renderer.d.ts → rendering/status-line-renderer.d.ts} +8 -4
- package/dist/app/{status-line-renderer.js → rendering/status-line-renderer.js} +73 -29
- package/dist/app/{tab-line-renderer.d.ts → rendering/tab-line-renderer.d.ts} +3 -3
- package/dist/app/{tab-line-renderer.js → rendering/tab-line-renderer.js} +2 -2
- package/dist/app/{toast-controller.d.ts → rendering/toast-controller.d.ts} +1 -1
- package/dist/app/{toast-controller.js → rendering/toast-controller.js} +2 -2
- package/dist/app/{toast-renderer.d.ts → rendering/toast-renderer.d.ts} +3 -3
- package/dist/app/{toast-renderer.js → rendering/toast-renderer.js} +3 -3
- package/dist/app/{tool-block-renderer.d.ts → rendering/tool-block-renderer.d.ts} +5 -5
- package/dist/app/{tool-block-renderer.js → rendering/tool-block-renderer.js} +15 -33
- package/dist/app/runtime.d.ts +6 -1
- package/dist/app/runtime.js +35 -2
- package/dist/app/{blink-controller.js → screen/blink-controller.js} +1 -1
- package/dist/app/{clipboard.d.ts → screen/clipboard.d.ts} +2 -2
- package/dist/app/{clipboard.js → screen/clipboard.js} +13 -18
- package/dist/app/{image-click-targets.d.ts → screen/image-click-targets.d.ts} +2 -2
- package/dist/app/{image-opener.d.ts → screen/image-opener.d.ts} +1 -1
- package/dist/app/{mouse-controller.d.ts → screen/mouse-controller.d.ts} +17 -10
- package/dist/app/{mouse-controller.js → screen/mouse-controller.js} +72 -29
- package/dist/app/{screen-selection.d.ts → screen/screen-selection.d.ts} +1 -1
- package/dist/app/{screen-styler.d.ts → screen/screen-styler.d.ts} +6 -3
- package/dist/app/{screen-styler.js → screen/screen-styler.js} +7 -6
- package/dist/app/{scroll-controller.d.ts → screen/scroll-controller.d.ts} +3 -3
- package/dist/app/{scroll-controller.js → screen/scroll-controller.js} +1 -1
- package/dist/app/{status-controller.d.ts → screen/status-controller.d.ts} +5 -2
- package/dist/app/{status-controller.js → screen/status-controller.js} +24 -9
- package/dist/app/{queued-message-controller.d.ts → session/queued-message-controller.d.ts} +9 -3
- package/dist/app/{queued-message-controller.js → session/queued-message-controller.js} +34 -23
- package/dist/app/{request-history.js → session/request-history.js} +2 -2
- package/dist/app/session/resume-session-loader.d.ts +15 -0
- package/dist/app/session/resume-session-loader.js +204 -0
- package/dist/app/{session-event-controller.d.ts → session/session-event-controller.d.ts} +8 -4
- package/dist/app/{session-event-controller.js → session/session-event-controller.js} +75 -8
- package/dist/app/{session-history.d.ts → session/session-history.d.ts} +1 -1
- package/dist/app/{session-history.js → session/session-history.js} +7 -6
- package/dist/app/{session-lifecycle-controller.d.ts → session/session-lifecycle-controller.d.ts} +7 -2
- package/dist/app/{session-lifecycle-controller.js → session/session-lifecycle-controller.js} +13 -5
- package/dist/app/{session-search.d.ts → session/session-search.d.ts} +1 -1
- package/dist/app/{session-search.js → session/session-search.js} +3 -3
- package/dist/app/{tabs-controller.d.ts → session/tabs-controller.d.ts} +11 -2
- package/dist/app/{tabs-controller.js → session/tabs-controller.js} +105 -9
- package/dist/app/{subagents-files.d.ts → subagents/subagents-files.d.ts} +1 -1
- package/dist/app/{subagents-files.js → subagents/subagents-files.js} +1 -1
- package/dist/app/{subagents-model.d.ts → subagents/subagents-model.d.ts} +1 -1
- package/dist/app/{subagents-model.js → subagents/subagents-model.js} +4 -4
- package/dist/app/{subagents-widget-controller.d.ts → subagents/subagents-widget-controller.d.ts} +1 -1
- package/dist/app/{subagents-widget-controller.js → subagents/subagents-widget-controller.js} +2 -2
- package/dist/app/{nerd-font-controller.js → terminal/nerd-font-controller.js} +16 -17
- package/dist/app/{terminal-bell-sound-controller.js → terminal/terminal-bell-sound-controller.js} +1 -1
- package/dist/app/{terminal-controller.d.ts → terminal/terminal-controller.d.ts} +1 -0
- package/dist/app/{terminal-controller.js → terminal/terminal-controller.js} +3 -2
- package/dist/app/{todo-model.d.ts → todo/todo-model.d.ts} +1 -1
- package/dist/app/{todo-model.js → todo/todo-model.js} +3 -3
- package/dist/app/{todo-widget-controller.d.ts → todo/todo-widget-controller.d.ts} +1 -1
- package/dist/app/{todo-widget-controller.js → todo/todo-widget-controller.js} +2 -2
- package/dist/app/types.d.ts +16 -2
- package/dist/app/{workspace-actions-controller.d.ts → workspace/workspace-actions-controller.d.ts} +2 -2
- package/dist/app/{workspace-actions-controller.js → workspace/workspace-actions-controller.js} +6 -6
- package/dist/app/{workspace-undo.d.ts → workspace/workspace-undo.d.ts} +1 -1
- package/dist/app/{workspace-undo.js → workspace/workspace-undo.js} +22 -20
- package/dist/config.d.ts +27 -0
- package/dist/config.js +174 -1
- package/dist/default-pix-config.js +38 -353
- package/dist/input-editor.d.ts +7 -1
- package/dist/input-editor.js +47 -6
- package/dist/main.js +2 -2
- package/dist/markdown-format.d.ts +1 -0
- package/dist/markdown-format.js +26 -1
- package/external/pi-tools-suite/README.md +78 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +4 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -1
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +5 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +314 -193
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
- package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
- package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
- package/external/pi-tools-suite/src/opencode-import/commands.ts +86 -0
- package/external/pi-tools-suite/src/opencode-import/importer.ts +208 -0
- package/external/pi-tools-suite/src/opencode-import/index.ts +25 -0
- package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +1 -1
- /package/dist/app/{startup-checks.d.ts → cli/startup-checks.d.ts} +0 -0
- /package/dist/app/{startup-checks.js → cli/startup-checks.js} +0 -0
- /package/dist/app/{startup-info.d.ts → cli/startup-info.d.ts} +0 -0
- /package/dist/app/{startup-info.js → cli/startup-info.js} +0 -0
- /package/dist/app/{update.d.ts → cli/update.d.ts} +0 -0
- /package/dist/app/{update.js → cli/update.js} +0 -0
- /package/dist/app/{command-host.js → commands/command-host.js} +0 -0
- /package/dist/app/{command-runtime.d.ts → commands/command-runtime.d.ts} +0 -0
- /package/dist/app/{shell-command.d.ts → commands/shell-command.d.ts} +0 -0
- /package/dist/app/{shell-command.js → commands/shell-command.js} +0 -0
- /package/dist/app/{extension-event-bus.d.ts → extensions/extension-event-bus.d.ts} +0 -0
- /package/dist/app/{extension-event-bus.js → extensions/extension-event-bus.js} +0 -0
- /package/dist/app/{native-modifiers.d.ts → input/native-modifiers.d.ts} +0 -0
- /package/dist/app/{terminal-edit-shortcuts.d.ts → input/terminal-edit-shortcuts.d.ts} +0 -0
- /package/dist/app/{terminal-edit-shortcuts.js → input/terminal-edit-shortcuts.js} +0 -0
- /package/dist/app/{model-usage-controller.d.ts → model/model-usage-controller.d.ts} +0 -0
- /package/dist/app/{conversation-viewport.js → rendering/conversation-viewport.js} +0 -0
- /package/dist/app/{dcp-stats.d.ts → rendering/dcp-stats.d.ts} +0 -0
- /package/dist/app/{blink-controller.d.ts → screen/blink-controller.d.ts} +0 -0
- /package/dist/app/{file-link-opener.d.ts → screen/file-link-opener.d.ts} +0 -0
- /package/dist/app/{file-link-opener.js → screen/file-link-opener.js} +0 -0
- /package/dist/app/{file-links.d.ts → screen/file-links.d.ts} +0 -0
- /package/dist/app/{file-links.js → screen/file-links.js} +0 -0
- /package/dist/app/{image-click-targets.js → screen/image-click-targets.js} +0 -0
- /package/dist/app/{image-opener.js → screen/image-opener.js} +0 -0
- /package/dist/app/{screen-selection.js → screen/screen-selection.js} +0 -0
- /package/dist/app/{request-history.d.ts → session/request-history.d.ts} +0 -0
- /package/dist/app/{nerd-font-controller.d.ts → terminal/nerd-font-controller.d.ts} +0 -0
- /package/dist/app/{terminal-bell-sound-controller.d.ts → terminal/terminal-bell-sound-controller.d.ts} +0 -0
- /package/dist/app/{terminal-output-buffer.d.ts → terminal/terminal-output-buffer.d.ts} +0 -0
- /package/dist/app/{terminal-output-buffer.js → terminal/terminal-output-buffer.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { createWriteStream } from "node:fs";
|
|
3
3
|
import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import http from "node:http";
|
|
@@ -6,14 +6,16 @@ import https from "node:https";
|
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { savePixDictationLanguage } from "
|
|
10
|
-
import { APP_ICONS } from "
|
|
9
|
+
import { savePixDictationLanguage } from "../../config.js";
|
|
10
|
+
import { APP_ICONS } from "../icons.js";
|
|
11
|
+
import { commandExists } from "../process.js";
|
|
11
12
|
const SAMPLE_RATE = 16_000;
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const projectRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
14
15
|
const modelsRoot = join(projectRoot, "models", "vosk");
|
|
15
16
|
const VOSK_PACKAGE_SPEC = "vosk@0.3.39";
|
|
16
17
|
const VOICE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
18
|
+
const VOICE_PARTIAL_TRANSCRIPT_THROTTLE_MS = 100;
|
|
17
19
|
let voskInstallPromise;
|
|
18
20
|
export class AppVoiceController {
|
|
19
21
|
host;
|
|
@@ -29,6 +31,7 @@ export class AppVoiceController {
|
|
|
29
31
|
progressTimer;
|
|
30
32
|
lastSystemProgressMessage;
|
|
31
33
|
partialTranscript;
|
|
34
|
+
partialTranscriptTimer;
|
|
32
35
|
startGeneration = 0;
|
|
33
36
|
constructor(host, dictationConfig) {
|
|
34
37
|
this.host = host;
|
|
@@ -128,7 +131,7 @@ export class AppVoiceController {
|
|
|
128
131
|
this.state = "loading";
|
|
129
132
|
this.host.render();
|
|
130
133
|
const model = this.cachedModel(language, modelPath, vosk);
|
|
131
|
-
const recorder = selectRecorderCommand();
|
|
134
|
+
const recorder = await selectRecorderCommand();
|
|
132
135
|
const recognizer = new vosk.Recognizer({ model, sampleRate: SAMPLE_RATE });
|
|
133
136
|
const audioProcess = spawn(recorder.command, recorder.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
134
137
|
this.recognizer = recognizer;
|
|
@@ -283,14 +286,27 @@ export class AppVoiceController {
|
|
|
283
286
|
if (text === this.partialTranscript)
|
|
284
287
|
return;
|
|
285
288
|
this.partialTranscript = text;
|
|
286
|
-
this.
|
|
289
|
+
this.schedulePartialTranscriptEmit();
|
|
287
290
|
}
|
|
288
291
|
clearPartialTranscript() {
|
|
289
292
|
if (!this.partialTranscript)
|
|
290
293
|
return;
|
|
291
294
|
this.partialTranscript = undefined;
|
|
295
|
+
if (this.partialTranscriptTimer) {
|
|
296
|
+
clearTimeout(this.partialTranscriptTimer);
|
|
297
|
+
this.partialTranscriptTimer = undefined;
|
|
298
|
+
}
|
|
292
299
|
this.host.setPartialTranscript(undefined);
|
|
293
300
|
}
|
|
301
|
+
schedulePartialTranscriptEmit() {
|
|
302
|
+
if (this.partialTranscriptTimer)
|
|
303
|
+
return;
|
|
304
|
+
this.partialTranscriptTimer = setTimeout(() => {
|
|
305
|
+
this.partialTranscriptTimer = undefined;
|
|
306
|
+
this.host.setPartialTranscript(this.partialTranscript);
|
|
307
|
+
}, VOICE_PARTIAL_TRANSCRIPT_THROTTLE_MS);
|
|
308
|
+
this.partialTranscriptTimer.unref?.();
|
|
309
|
+
}
|
|
294
310
|
isCurrentStart(generation) {
|
|
295
311
|
return this.startGeneration === generation;
|
|
296
312
|
}
|
|
@@ -367,11 +383,11 @@ async function downloadFile(url, destination, redirects = 3) {
|
|
|
367
383
|
});
|
|
368
384
|
}
|
|
369
385
|
async function extractZip(zipPath, destination) {
|
|
370
|
-
if (commandExists("unzip")) {
|
|
386
|
+
if (await commandExists("unzip")) {
|
|
371
387
|
await runCommand("unzip", ["-q", zipPath, "-d", destination]);
|
|
372
388
|
return;
|
|
373
389
|
}
|
|
374
|
-
if (process.platform === "darwin" && commandExists("ditto")) {
|
|
390
|
+
if (process.platform === "darwin" && await commandExists("ditto")) {
|
|
375
391
|
await runCommand("ditto", ["-x", "-k", zipPath, destination]);
|
|
376
392
|
return;
|
|
377
393
|
}
|
|
@@ -535,7 +551,7 @@ function isVoskModule(value) {
|
|
|
535
551
|
const record = value;
|
|
536
552
|
return typeof record.Model === "function" && typeof record.Recognizer === "function";
|
|
537
553
|
}
|
|
538
|
-
function selectRecorderCommand() {
|
|
554
|
+
async function selectRecorderCommand() {
|
|
539
555
|
const commands = [
|
|
540
556
|
{
|
|
541
557
|
command: "rec",
|
|
@@ -566,15 +582,11 @@ function selectRecorderCommand() {
|
|
|
566
582
|
description: "arecord",
|
|
567
583
|
});
|
|
568
584
|
}
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
function commandExists(command) {
|
|
575
|
-
if (process.platform === "win32")
|
|
576
|
-
return spawnSync("where", [command], { stdio: "ignore" }).status === 0;
|
|
577
|
-
return spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" }).status === 0;
|
|
585
|
+
for (const candidate of commands) {
|
|
586
|
+
if (await commandExists(candidate.command))
|
|
587
|
+
return candidate;
|
|
588
|
+
}
|
|
589
|
+
throw new Error("audio recorder not found: install SoX (`rec`/`sox`), ffmpeg, or arecord");
|
|
578
590
|
}
|
|
579
591
|
function transcriptText(result) {
|
|
580
592
|
const parsed = typeof result === "string" ? parseResultString(result) : result;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MODEL_USAGE_POLL_INTERVAL_MS, MODEL_USAGE_STATUS_TICK_MS } from "
|
|
1
|
+
import { MODEL_USAGE_POLL_INTERVAL_MS, MODEL_USAGE_STATUS_TICK_MS } from "../constants.js";
|
|
2
2
|
import { formatModelUsageStatusLabel, modelUsageDescriptor, queryModelUsageStatus, } from "./model-usage-status.js";
|
|
3
3
|
export class AppModelUsageController {
|
|
4
4
|
host;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionModel } from "
|
|
1
|
+
import type { SessionModel } from "../types.js";
|
|
2
2
|
type BaseModelUsageDescriptor = {
|
|
3
3
|
readonly modelKey: string;
|
|
4
4
|
};
|
|
@@ -79,9 +79,18 @@ export type OpenAIUsageResponse = {
|
|
|
79
79
|
rate_limit: OpenAIRateLimit | null;
|
|
80
80
|
additional_rate_limits?: OpenAIAdditionalRateLimit[];
|
|
81
81
|
};
|
|
82
|
+
type AntigravityCachedQuotaBucket = {
|
|
83
|
+
remainingFraction?: number;
|
|
84
|
+
resetTime?: string;
|
|
85
|
+
modelCount?: number;
|
|
86
|
+
};
|
|
87
|
+
type AntigravityCachedQuota = Record<string, AntigravityCachedQuotaBucket | undefined>;
|
|
82
88
|
type AntigravityQuotaAccount = {
|
|
83
89
|
readonly email?: string;
|
|
84
90
|
readonly refreshToken: string;
|
|
91
|
+
readonly accessToken?: string;
|
|
92
|
+
readonly cachedQuota?: AntigravityCachedQuota;
|
|
93
|
+
readonly cachedQuotaUpdatedAt?: number;
|
|
85
94
|
readonly projectId: string;
|
|
86
95
|
readonly accountIndex?: number;
|
|
87
96
|
readonly accountCount?: number;
|
|
@@ -4,18 +4,15 @@ import { readFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { formatCompactProgressBar } from "
|
|
7
|
+
import { formatCompactProgressBar } from "../../context-progress-bar.js";
|
|
8
8
|
const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
9
9
|
const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
10
10
|
const ZHIPU_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/limit";
|
|
11
11
|
const GOOGLE_QUOTA_API_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
|
|
12
|
-
const GOOGLE_TOKEN_REFRESH_URL = "https://oauth2.googleapis.com/token";
|
|
13
12
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
14
13
|
const DAY_SECONDS = 86_400;
|
|
15
14
|
const HOUR_SECONDS = 3_600;
|
|
16
15
|
const DEFAULT_ANTIGRAVITY_PROJECT_ID = "rising-fact-p41fc";
|
|
17
|
-
const GOOGLE_CLIENT_ID = process.env.PIX_ANTIGRAVITY_GOOGLE_CLIENT_ID ?? process.env.ANTIGRAVITY_GOOGLE_CLIENT_ID ?? "";
|
|
18
|
-
const GOOGLE_CLIENT_SECRET = process.env.PIX_ANTIGRAVITY_GOOGLE_CLIENT_SECRET ?? process.env.ANTIGRAVITY_GOOGLE_CLIENT_SECRET ?? "";
|
|
19
16
|
const OPENAI_QUOTA_PROVIDERS = new Set(["openai", "openai-codex"]);
|
|
20
17
|
const ZHIPU_QUOTA_PROVIDERS = new Set(["zai", "zhipuai-coding-plan"]);
|
|
21
18
|
const ANTIGRAVITY_QUOTA_PROVIDERS = new Set(["antigravity", "google-antigravity"]);
|
|
@@ -401,8 +398,13 @@ export function googleAntigravityUsageStatusFromResponse(data, descriptor, now =
|
|
|
401
398
|
};
|
|
402
399
|
}
|
|
403
400
|
async function queryGoogleAntigravityModelUsage(descriptor) {
|
|
404
|
-
const
|
|
405
|
-
const
|
|
401
|
+
const now = Date.now();
|
|
402
|
+
const cachedResponse = googleQuotaResponseFromCachedQuota(descriptor.account.cachedQuota, descriptor.account.cachedQuotaUpdatedAt, now);
|
|
403
|
+
if (cachedResponse)
|
|
404
|
+
return googleAntigravityUsageStatusFromResponse(cachedResponse, descriptor, now);
|
|
405
|
+
if (!descriptor.account.accessToken)
|
|
406
|
+
return undefined;
|
|
407
|
+
const response = await fetchGoogleAntigravityQuota(descriptor.account.accessToken, descriptor.account.projectId);
|
|
406
408
|
return googleAntigravityUsageStatusFromResponse(response, descriptor);
|
|
407
409
|
}
|
|
408
410
|
const GOOGLE_ACCOUNT_QUOTA_WINDOWS = [
|
|
@@ -417,10 +419,12 @@ async function queryGoogleAntigravityAccountUsage(now) {
|
|
|
417
419
|
const results = await Promise.all(accounts.map(async (account) => {
|
|
418
420
|
const accountLabel = account.email ?? maskCredential(account.refreshToken);
|
|
419
421
|
try {
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
const response = account.cachedQuota ? googleQuotaResponseFromCachedQuota(account.cachedQuota, account.cachedQuotaUpdatedAt, now) : undefined;
|
|
423
|
+
const windows = response ? googleAccountWindowsFromResponse(response, now) : [];
|
|
424
|
+
if (windows.length === 0 && account.accessToken) {
|
|
425
|
+
const liveResponse = await fetchGoogleAntigravityQuota(account.accessToken, account.projectId);
|
|
426
|
+
windows.push(...googleAccountWindowsFromResponse(liveResponse, now));
|
|
427
|
+
}
|
|
424
428
|
return {
|
|
425
429
|
account: accountLabel,
|
|
426
430
|
windows,
|
|
@@ -449,15 +453,20 @@ function readAllAntigravityQuotaAccounts() {
|
|
|
449
453
|
return [];
|
|
450
454
|
const accounts = storedAntigravityAccounts(credential);
|
|
451
455
|
if (accounts.length > 0) {
|
|
456
|
+
const activeIndex = clampAccountIndex(credential.activeIndex, accounts.length);
|
|
457
|
+
const activeAccess = antigravityAccessFromCredential(credential);
|
|
452
458
|
return accounts.map((account, accountIndex) => antigravityQuotaAccount(account, {
|
|
453
459
|
...(credential.email ? { fallbackEmail: credential.email } : {}),
|
|
460
|
+
...(accountIndex === activeIndex && activeAccess ? { accessToken: activeAccess.accessToken } : {}),
|
|
454
461
|
accountIndex,
|
|
455
462
|
accountCount: accounts.length,
|
|
456
463
|
})).filter((account) => account !== undefined);
|
|
457
464
|
}
|
|
458
465
|
const fallbackAccount = antigravityAccountFromCredential(credential);
|
|
466
|
+
const fallbackAccess = antigravityAccessFromCredential(credential);
|
|
459
467
|
const account = fallbackAccount ? antigravityQuotaAccount(fallbackAccount, {
|
|
460
468
|
...(credential.email ? { fallbackEmail: credential.email } : {}),
|
|
469
|
+
...(fallbackAccess ? { accessToken: fallbackAccess.accessToken } : {}),
|
|
461
470
|
}) : undefined;
|
|
462
471
|
return account ? [account] : [];
|
|
463
472
|
}
|
|
@@ -480,12 +489,15 @@ function antigravityAccountFromCredential(credential) {
|
|
|
480
489
|
const refresh = splitAntigravityRefresh(credential.refresh);
|
|
481
490
|
if (!refresh.refreshToken)
|
|
482
491
|
return undefined;
|
|
492
|
+
const activeStoredAccount = credential.accounts?.[clampAccountIndex(credential.activeIndex, credential.accounts.length)];
|
|
483
493
|
return {
|
|
484
494
|
refreshToken: refresh.refreshToken,
|
|
485
495
|
projectId: refresh.projectId || refresh.managedProjectId || DEFAULT_ANTIGRAVITY_PROJECT_ID,
|
|
486
496
|
enabled: true,
|
|
487
497
|
...(credential.email ? { email: credential.email } : {}),
|
|
488
498
|
...(refresh.managedProjectId ? { managedProjectId: refresh.managedProjectId } : {}),
|
|
499
|
+
...(activeStoredAccount?.cachedQuota ? { cachedQuota: activeStoredAccount.cachedQuota } : {}),
|
|
500
|
+
...(typeof activeStoredAccount?.cachedQuotaUpdatedAt === "number" ? { cachedQuotaUpdatedAt: activeStoredAccount.cachedQuotaUpdatedAt } : {}),
|
|
489
501
|
};
|
|
490
502
|
}
|
|
491
503
|
function antigravityQuotaAccount(account, options = {}) {
|
|
@@ -498,11 +510,65 @@ function antigravityQuotaAccount(account, options = {}) {
|
|
|
498
510
|
refreshToken,
|
|
499
511
|
projectId,
|
|
500
512
|
cacheKey: email ? email.toLowerCase() : shortHash(refreshToken),
|
|
513
|
+
...(options.accessToken ? { accessToken: options.accessToken } : {}),
|
|
514
|
+
...(account.cachedQuota ? { cachedQuota: account.cachedQuota } : {}),
|
|
515
|
+
...(typeof account.cachedQuotaUpdatedAt === "number" ? { cachedQuotaUpdatedAt: account.cachedQuotaUpdatedAt } : {}),
|
|
501
516
|
...(email ? { email } : {}),
|
|
502
517
|
...(typeof options.accountIndex === "number" ? { accountIndex: options.accountIndex } : {}),
|
|
503
518
|
...(typeof options.accountCount === "number" ? { accountCount: options.accountCount } : {}),
|
|
504
519
|
};
|
|
505
520
|
}
|
|
521
|
+
function googleQuotaResponseFromCachedQuota(cachedQuota, cachedQuotaUpdatedAt, now = Date.now()) {
|
|
522
|
+
if (!cachedQuota)
|
|
523
|
+
return undefined;
|
|
524
|
+
const models = {};
|
|
525
|
+
addCachedQuotaModels(models, cachedQuota.claude, ["claude-opus-4-6-thinking", "claude-sonnet-4-6"], cachedQuotaUpdatedAt, now);
|
|
526
|
+
addCachedQuotaModels(models, cachedQuota["gemini-flash"], ["gemini-2.5-flash", "gemini-3-flash"], cachedQuotaUpdatedAt, now);
|
|
527
|
+
addCachedQuotaModels(models, cachedQuota["gemini-pro"], ["gemini-3.1-pro-low"], cachedQuotaUpdatedAt, now);
|
|
528
|
+
return Object.keys(models).length > 0 ? { models } : undefined;
|
|
529
|
+
}
|
|
530
|
+
function addCachedQuotaModels(models, quota, quotaModelKeys, cachedQuotaUpdatedAt, now) {
|
|
531
|
+
if (!quota || !Number.isFinite(quota.remainingFraction))
|
|
532
|
+
return;
|
|
533
|
+
const remainingFraction = quota.remainingFraction;
|
|
534
|
+
const resetTime = cachedQuotaResetTimeForDisplay(quota.resetTime, cachedQuotaUpdatedAt, now);
|
|
535
|
+
for (const quotaModelKey of quotaModelKeys) {
|
|
536
|
+
models[quotaModelKey] = {
|
|
537
|
+
quotaInfo: {
|
|
538
|
+
remainingFraction,
|
|
539
|
+
...(resetTime ? { resetTime } : {}),
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function cachedQuotaResetTimeForDisplay(resetTime, cachedQuotaUpdatedAt, now) {
|
|
545
|
+
if (!resetTime)
|
|
546
|
+
return undefined;
|
|
547
|
+
const resetAt = Date.parse(resetTime);
|
|
548
|
+
if (!Number.isFinite(resetAt) || resetAt > now)
|
|
549
|
+
return resetTime;
|
|
550
|
+
const cachedAt = normalizeTimestampMillis(cachedQuotaUpdatedAt);
|
|
551
|
+
if (!Number.isFinite(cachedAt) || resetAt <= cachedAt)
|
|
552
|
+
return resetTime;
|
|
553
|
+
return new Date(now + (resetAt - cachedAt)).toISOString();
|
|
554
|
+
}
|
|
555
|
+
function normalizeTimestampMillis(value) {
|
|
556
|
+
if (!Number.isFinite(value))
|
|
557
|
+
return Number.NaN;
|
|
558
|
+
const timestamp = value;
|
|
559
|
+
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp;
|
|
560
|
+
}
|
|
561
|
+
function antigravityAccessFromCredential(credential) {
|
|
562
|
+
if (credential.type !== "oauth" || !credential.access || isExpired(credential))
|
|
563
|
+
return undefined;
|
|
564
|
+
const [accessToken = "", projectId = ""] = credential.access.split("|");
|
|
565
|
+
if (!accessToken)
|
|
566
|
+
return undefined;
|
|
567
|
+
return {
|
|
568
|
+
accessToken,
|
|
569
|
+
...(projectId ? { projectId } : {}),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
506
572
|
function splitAntigravityRefresh(refresh) {
|
|
507
573
|
const [refreshToken = "", projectId = "", managedProjectId = ""] = refresh.split("|");
|
|
508
574
|
return {
|
|
@@ -519,26 +585,6 @@ function clampAccountIndex(index, accountCount) {
|
|
|
519
585
|
function shortHash(value) {
|
|
520
586
|
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
521
587
|
}
|
|
522
|
-
async function refreshGoogleAccessToken(refreshToken) {
|
|
523
|
-
if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
|
|
524
|
-
throw new Error("Antigravity Google OAuth credentials are not configured; set PIX_ANTIGRAVITY_GOOGLE_CLIENT_ID and PIX_ANTIGRAVITY_GOOGLE_CLIENT_SECRET.");
|
|
525
|
-
}
|
|
526
|
-
const response = await fetchWithTimeout(GOOGLE_TOKEN_REFRESH_URL, {
|
|
527
|
-
method: "POST",
|
|
528
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
529
|
-
body: new URLSearchParams({
|
|
530
|
-
client_id: GOOGLE_CLIENT_ID,
|
|
531
|
-
client_secret: GOOGLE_CLIENT_SECRET,
|
|
532
|
-
refresh_token: refreshToken,
|
|
533
|
-
grant_type: "refresh_token",
|
|
534
|
-
}),
|
|
535
|
-
});
|
|
536
|
-
if (!response.ok) {
|
|
537
|
-
const errorText = await response.text();
|
|
538
|
-
throw new Error(`Google token refresh failed (${response.status}): ${errorText}`);
|
|
539
|
-
}
|
|
540
|
-
return response.json();
|
|
541
|
-
}
|
|
542
588
|
async function fetchGoogleAntigravityQuota(accessToken, projectId) {
|
|
543
589
|
const response = await fetchWithTimeout(GOOGLE_QUOTA_API_URL, {
|
|
544
590
|
method: "POST",
|
|
@@ -633,18 +679,51 @@ function selectOpenAIRateLimitForModel(data, modelKey) {
|
|
|
633
679
|
return false;
|
|
634
680
|
return openAIModelMatchesAdditionalLimit(modelKey, limit);
|
|
635
681
|
});
|
|
682
|
+
// Prefer exact named per-model buckets when the API exposes them, but keep the
|
|
683
|
+
// top-level bucket as a fallback. Some Codex responses currently expose a
|
|
684
|
+
// usable selected-model/account bucket only at the top level while also
|
|
685
|
+
// listing unrelated named additional buckets; hiding the fallback makes the
|
|
686
|
+
// status bar disappear completely for those models.
|
|
636
687
|
return additionalLimit?.rate_limit ?? data.rate_limit;
|
|
637
688
|
}
|
|
638
689
|
function openAIModelMatchesAdditionalLimit(modelKey, limit) {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
690
|
+
const modelId = modelKey.split("/").at(-1) ?? modelKey;
|
|
691
|
+
return openAIModelIdMatchesLimitCandidate(modelId, limit.limit_name)
|
|
692
|
+
|| (limit.metered_feature ? openAIModelIdMatchesLimitCandidate(modelId, limit.metered_feature) : false);
|
|
693
|
+
}
|
|
694
|
+
function openAIModelIdMatchesLimitCandidate(modelId, candidate) {
|
|
695
|
+
const modelTokens = openAILimitTokens(modelId);
|
|
696
|
+
const candidateTokens = openAILimitTokens(candidate);
|
|
697
|
+
if (modelTokens.length === 0 || candidateTokens.length === 0)
|
|
698
|
+
return false;
|
|
699
|
+
if (containsTokenSequence(candidateTokens, modelTokens))
|
|
700
|
+
return true;
|
|
701
|
+
// Support compact names such as o4mini while avoiding prefix matches such as
|
|
702
|
+
// gpt-5 accidentally matching gpt-5.5.
|
|
703
|
+
return normalizeOpenAILimitName(candidate) === normalizeOpenAILimitName(modelId);
|
|
644
704
|
}
|
|
645
705
|
function normalizeOpenAILimitName(value) {
|
|
646
706
|
return value.toLowerCase().replace(/[^a-z0-9]+/gu, "");
|
|
647
707
|
}
|
|
708
|
+
function openAILimitTokens(value) {
|
|
709
|
+
return value.toLowerCase().split(/[^a-z0-9]+/u).filter((token) => token.length > 0);
|
|
710
|
+
}
|
|
711
|
+
function containsTokenSequence(tokens, sequence) {
|
|
712
|
+
if (sequence.length > tokens.length)
|
|
713
|
+
return false;
|
|
714
|
+
for (let start = 0; start <= tokens.length - sequence.length; start += 1) {
|
|
715
|
+
let matches = true;
|
|
716
|
+
for (let offset = 0; offset < sequence.length; offset += 1) {
|
|
717
|
+
if (tokens[start + offset] !== sequence[offset]) {
|
|
718
|
+
matches = false;
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (matches)
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
648
727
|
function selectWeeklyWindow(windows) {
|
|
649
728
|
return windows
|
|
650
729
|
.filter((window) => window.limit_window_seconds >= 6 * DAY_SECONDS)
|
|
@@ -683,6 +762,11 @@ function googleAccountWindowFromResponse(data, label, quotaModelKey, now) {
|
|
|
683
762
|
windowSeconds: Math.max(0, Math.round((resetAt - now) / 1000)),
|
|
684
763
|
};
|
|
685
764
|
}
|
|
765
|
+
function googleAccountWindowsFromResponse(data, now) {
|
|
766
|
+
return GOOGLE_ACCOUNT_QUOTA_WINDOWS
|
|
767
|
+
.map((window) => googleAccountWindowFromResponse(data, window.label, window.quotaModelKey, now))
|
|
768
|
+
.filter((window) => window !== undefined);
|
|
769
|
+
}
|
|
686
770
|
function clampPercent(percent) {
|
|
687
771
|
return Math.max(0, Math.min(100, percent));
|
|
688
772
|
}
|
|
@@ -706,6 +790,8 @@ function formatQuotaBar(percent, width) {
|
|
|
706
790
|
return `${"█".repeat(filled)}${"░".repeat(width - filled)}`;
|
|
707
791
|
}
|
|
708
792
|
function formatDurationLong(resetAt, now) {
|
|
793
|
+
if (resetAt <= now)
|
|
794
|
+
return "reset";
|
|
709
795
|
const totalMinutes = Math.max(0, Math.ceil((resetAt - now) / 60_000));
|
|
710
796
|
const days = Math.floor(totalMinutes / 1440);
|
|
711
797
|
const hours = Math.floor((totalMinutes % 1440) / 60);
|
|
@@ -717,6 +803,8 @@ function formatDurationLong(resetAt, now) {
|
|
|
717
803
|
return `${minutes}m`;
|
|
718
804
|
}
|
|
719
805
|
function formatDurationShort(resetAt, now) {
|
|
806
|
+
if (resetAt <= now)
|
|
807
|
+
return "reset";
|
|
720
808
|
const totalMinutes = Math.max(0, Math.ceil((resetAt - now) / 60_000));
|
|
721
809
|
const days = Math.floor(totalMinutes / 1440);
|
|
722
810
|
const hours = Math.floor((totalMinutes % 1440) / 60);
|
|
@@ -737,6 +825,8 @@ function formatUsageWindow(_prefix, window, now) {
|
|
|
737
825
|
return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)} ${formatResetCountdown(window.resetAt, now)}`;
|
|
738
826
|
}
|
|
739
827
|
function formatResetCountdown(resetAt, now) {
|
|
828
|
+
if (resetAt <= now)
|
|
829
|
+
return "reset";
|
|
740
830
|
const totalMinutes = Math.max(0, Math.ceil((resetAt - now) / 60_000));
|
|
741
831
|
const days = Math.floor(totalMinutes / 1440);
|
|
742
832
|
const hours = Math.floor((totalMinutes % 1440) / 60);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentSessionRuntime, SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { PopupMenuItem } from "
|
|
3
|
-
import type { Entry, ModelMenuValue, QueueMessageMenuValue, ResumeMenuValue, ScopedSessionModel, SessionModel, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "
|
|
2
|
+
import type { PopupMenuItem } from "../../ui.js";
|
|
3
|
+
import type { Entry, ModelMenuValue, QueueMessageMenuValue, ResumeMenuValue, ScopedSessionModel, SessionModel, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
|
|
4
4
|
export type AppMenuItemsControllerHost = {
|
|
5
5
|
runtime(): AgentSessionRuntime | undefined;
|
|
6
6
|
getBuiltinSlashCommands(): readonly SlashCommand[];
|
|
@@ -11,9 +11,9 @@ export declare class AppMenuItemsController {
|
|
|
11
11
|
private readonly host;
|
|
12
12
|
private resumeMenuLoaderCache;
|
|
13
13
|
constructor(host: AppMenuItemsControllerHost);
|
|
14
|
-
parseSlashInput(text: string): import("
|
|
14
|
+
parseSlashInput(text: string): import("../types.js").ParsedSlashInput | undefined;
|
|
15
15
|
getResourceSlashCommands(): SlashCommand[];
|
|
16
|
-
getSlashCommandMatches(query: string, limit?: number): import("
|
|
16
|
+
getSlashCommandMatches(query: string, limit?: number): import("../../fuzzy.js").FuzzyMatch<SlashCommand>[];
|
|
17
17
|
getSlashCommandMenuItems(query: string): PopupMenuItem<SlashCommand>[];
|
|
18
18
|
modelRef(model: SessionModel): string;
|
|
19
19
|
getFavoriteScopedModels(): ScopedSessionModel[];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { fuzzySearch } from "
|
|
3
|
-
import { PI_FAVORITE_MODEL_REFS, THINKING_LEVELS } from "
|
|
4
|
-
import { APP_ICONS } from "
|
|
5
|
-
import { parseScopedModelRef } from "
|
|
2
|
+
import { fuzzySearch } from "../../fuzzy.js";
|
|
3
|
+
import { PI_FAVORITE_MODEL_REFS, THINKING_LEVELS } from "../constants.js";
|
|
4
|
+
import { APP_ICONS } from "../icons.js";
|
|
5
|
+
import { parseScopedModelRef } from "../model/model-ref.js";
|
|
6
6
|
import { buildUserMessageJumpItems, createSessionInfoMenuItemsLoader } from "./popup-menu-controller.js";
|
|
7
|
-
import { getResourceSlashCommands, getSlashCommandMatches, parseSlashInput } from "
|
|
7
|
+
import { getResourceSlashCommands, getSlashCommandMatches, parseSlashInput } from "../commands/slash-commands.js";
|
|
8
8
|
export class AppMenuItemsController {
|
|
9
9
|
host;
|
|
10
10
|
resumeMenuLoaderCache;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AgentSession, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AppCommandController } from "
|
|
2
|
+
import type { AppCommandController } from "../commands/command-controller.js";
|
|
3
3
|
import type { AppMenuItemsController } from "./menu-items-controller.js";
|
|
4
4
|
import type { AppPopupMenuController } from "./popup-menu-controller.js";
|
|
5
|
-
import type { AppQueuedMessageController } from "
|
|
6
|
-
import type { Entry, SlashCommand } from "
|
|
7
|
-
import type { AppWorkspaceActionsController } from "
|
|
5
|
+
import type { AppQueuedMessageController } from "../session/queued-message-controller.js";
|
|
6
|
+
import type { Entry, SlashCommand } from "../types.js";
|
|
7
|
+
import type { AppWorkspaceActionsController } from "../workspace/workspace-actions-controller.js";
|
|
8
8
|
export type AppPopupActionControllerHost = {
|
|
9
9
|
runtime(): AgentSessionRuntime | undefined;
|
|
10
10
|
getBuiltinSlashCommands(): readonly SlashCommand[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createId } from "
|
|
2
|
-
import { stringifyUnknown } from "
|
|
1
|
+
import { createId } from "../id.js";
|
|
2
|
+
import { stringifyUnknown } from "../rendering/message-content.js";
|
|
3
3
|
export class AppPopupActionController {
|
|
4
4
|
host;
|
|
5
5
|
popupMenus;
|
|
@@ -148,7 +148,7 @@ export class AppPopupActionController {
|
|
|
148
148
|
this.host.render();
|
|
149
149
|
try {
|
|
150
150
|
if (selected.value === "copy") {
|
|
151
|
-
this.workspaceActions.copyUserMessage(selected.entryId);
|
|
151
|
+
await this.workspaceActions.copyUserMessage(selected.entryId);
|
|
152
152
|
return true;
|
|
153
153
|
}
|
|
154
154
|
if (selected.value === "fork") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Theme } from "
|
|
2
|
-
import { PopupMenu, type PopupMenuItem } from "
|
|
3
|
-
import type { ScreenStyler } from "
|
|
4
|
-
import type { ActivePopupMenu, Entry, ModelMenuValue, ParsedSlashInput, PixMenuController, PixMenuItem, PixMenuOptions, PixMenuSelectOptions, PopupMenuPlacement, QueueMessageMenuValue, RenderedLine, ResumeMenuValue, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "
|
|
1
|
+
import { type Theme } from "../../theme.js";
|
|
2
|
+
import { PopupMenu, type PopupMenuItem } from "../../ui.js";
|
|
3
|
+
import type { ScreenStyler } from "../screen/screen-styler.js";
|
|
4
|
+
import type { ActivePopupMenu, Entry, ModelMenuValue, ParsedSlashInput, PixMenuController, PixMenuItem, PixMenuOptions, PixMenuSelectOptions, PopupMenuPlacement, QueueMessageMenuValue, RenderedLine, ResumeMenuValue, SlashCommand, ThinkingMenuValue, UserMessageJumpMenuValue, UserMessageMenuValue } from "../types.js";
|
|
5
5
|
import type { AgentSession, SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
type SlashCommandMenuValue = SlashCommand;
|
|
7
7
|
type ModelPopupMenuValue = ModelMenuValue;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { fuzzySearch } from "
|
|
3
|
-
import { colorLine } from "
|
|
4
|
-
import { PopupMenu } from "
|
|
5
|
-
import { padOrTrimPlain, ellipsizeDisplay, sanitizeText } from "
|
|
6
|
-
import { stringDisplayWidth } from "
|
|
7
|
-
import { RESUME_MENU_INITIAL_SESSION_ROWS, RESUME_MENU_LOAD_BATCH_ROWS, RESUME_MENU_LOAD_THRESHOLD_ROWS, RESUME_MENU_MAX_ROWS, SLASH_COMMAND_DESCRIPTION_COLUMN, SLASH_COMMAND_MENU_MAX_ROWS, THINKING_MENU_MAX_ROWS, } from "
|
|
8
|
-
import { APP_ICONS } from "
|
|
2
|
+
import { fuzzySearch } from "../../fuzzy.js";
|
|
3
|
+
import { colorLine } from "../../theme.js";
|
|
4
|
+
import { PopupMenu } from "../../ui.js";
|
|
5
|
+
import { padOrTrimPlain, ellipsizeDisplay, sanitizeText } from "../rendering/render-text.js";
|
|
6
|
+
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
7
|
+
import { RESUME_MENU_INITIAL_SESSION_ROWS, RESUME_MENU_LOAD_BATCH_ROWS, RESUME_MENU_LOAD_THRESHOLD_ROWS, RESUME_MENU_MAX_ROWS, SLASH_COMMAND_DESCRIPTION_COLUMN, SLASH_COMMAND_MENU_MAX_ROWS, THINKING_MENU_MAX_ROWS, } from "../constants.js";
|
|
8
|
+
import { APP_ICONS } from "../icons.js";
|
|
9
9
|
const POPUP_MENU_ESCAPE_BUTTON = "Esc";
|
|
10
10
|
export class AppPopupMenuController {
|
|
11
11
|
host;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type AsyncProcessResult = {
|
|
2
|
+
status: number | null;
|
|
3
|
+
signal: NodeJS.Signals | null;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
error?: Error;
|
|
7
|
+
timedOut?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type RunProcessOptions = {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
input?: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
maxBufferBytes?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function runProcess(command: string, args?: readonly string[], options?: RunProcessOptions): Promise<AsyncProcessResult>;
|
|
17
|
+
export declare function commandExists(command: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const DEFAULT_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
3
|
+
export async function runProcess(command, args = [], options = {}) {
|
|
4
|
+
const maxBufferBytes = Math.max(1, options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES);
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
let stdout = "";
|
|
7
|
+
let stderr = "";
|
|
8
|
+
let error;
|
|
9
|
+
let timedOut = false;
|
|
10
|
+
const child = spawn(command, [...args], {
|
|
11
|
+
cwd: options.cwd,
|
|
12
|
+
env: options.env,
|
|
13
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
14
|
+
});
|
|
15
|
+
const append = (current, chunk) => {
|
|
16
|
+
const next = `${current}${chunk.toString("utf8")}`;
|
|
17
|
+
return next.length > maxBufferBytes ? next.slice(-maxBufferBytes) : next;
|
|
18
|
+
};
|
|
19
|
+
const timer = options.timeoutMs === undefined
|
|
20
|
+
? undefined
|
|
21
|
+
: setTimeout(() => {
|
|
22
|
+
timedOut = true;
|
|
23
|
+
child.kill("SIGTERM");
|
|
24
|
+
}, options.timeoutMs);
|
|
25
|
+
timer?.unref?.();
|
|
26
|
+
child.stdout.on("data", (chunk) => {
|
|
27
|
+
stdout = append(stdout, chunk);
|
|
28
|
+
});
|
|
29
|
+
child.stderr.on("data", (chunk) => {
|
|
30
|
+
stderr = append(stderr, chunk);
|
|
31
|
+
});
|
|
32
|
+
child.once("error", (err) => {
|
|
33
|
+
error = err;
|
|
34
|
+
});
|
|
35
|
+
child.once("close", (status, signal) => {
|
|
36
|
+
if (timer)
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
resolve({
|
|
39
|
+
status,
|
|
40
|
+
signal,
|
|
41
|
+
stdout,
|
|
42
|
+
stderr,
|
|
43
|
+
...(error === undefined ? {} : { error }),
|
|
44
|
+
...(timedOut ? { timedOut } : {}),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
if (options.input === undefined)
|
|
48
|
+
child.stdin.end();
|
|
49
|
+
else
|
|
50
|
+
child.stdin.end(options.input);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export async function commandExists(command, env = process.env) {
|
|
54
|
+
if (process.platform === "win32") {
|
|
55
|
+
const names = [command, command.replace(/\.exe$/iu, ".cmd"), command.replace(/\.exe$/iu, ".bat")];
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
const result = await runProcess("where", [name], { env, maxBufferBytes: 256 });
|
|
58
|
+
if (result.status === 0)
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const result = await runProcess("sh", ["-lc", `command -v ${shellQuote(command)}`], { env, maxBufferBytes: 256 });
|
|
64
|
+
return result.status === 0;
|
|
65
|
+
}
|
|
66
|
+
function shellQuote(value) {
|
|
67
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
68
|
+
}
|
package/dist/app/{conversation-entry-renderer.d.ts → rendering/conversation-entry-renderer.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type PixConfig } from "
|
|
2
|
-
import type { Theme } from "
|
|
3
|
-
import type { Entry, RenderedLine } from "
|
|
1
|
+
import { type PixConfig } from "../../config.js";
|
|
2
|
+
import type { Theme } from "../../theme.js";
|
|
3
|
+
import type { Entry, RenderedLine } from "../types.js";
|
|
4
4
|
export type InlineUserMessageMenuContext = {
|
|
5
5
|
userContentWidth: number;
|
|
6
6
|
userContentLeft: number;
|