pi-ui-extend 0.1.20 → 0.1.24
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 +1 -10
- package/bin/pix.mjs +11 -154
- package/dist/app/app.d.ts +4 -0
- package/dist/app/app.js +102 -17
- package/dist/app/cli/startup-info.d.ts +0 -1
- package/dist/app/cli/startup-info.js +0 -3
- package/dist/app/commands/command-session-actions.js +3 -0
- package/dist/app/extensions/extension-ui-controller.js +2 -2
- package/dist/app/input/voice-controller.d.ts +3 -2
- package/dist/app/input/voice-controller.js +9 -0
- package/dist/app/popup/popup-menu-controller.js +7 -1
- package/dist/app/rendering/conversation-entry-renderer.js +29 -10
- package/dist/app/rendering/conversation-tool-renderer.js +1 -1
- package/dist/app/rendering/conversation-viewport.d.ts +1 -5
- package/dist/app/rendering/conversation-viewport.js +9 -16
- package/dist/app/rendering/editor-layout-renderer.js +1 -1
- package/dist/app/rendering/render-text.d.ts +6 -0
- package/dist/app/rendering/render-text.js +9 -0
- package/dist/app/rendering/tab-line-renderer.js +1 -5
- package/dist/app/rendering/tool-block-renderer.d.ts +2 -0
- package/dist/app/rendering/tool-block-renderer.js +20 -2
- package/dist/app/runtime.d.ts +2 -0
- package/dist/app/runtime.js +27 -4
- package/dist/app/screen/mouse-controller.js +14 -6
- package/dist/app/screen/screen-styler.js +2 -2
- package/dist/app/session/session-event-controller.js +5 -4
- package/dist/app/session/session-lifecycle-controller.d.ts +2 -0
- package/dist/app/session/session-lifecycle-controller.js +43 -20
- package/dist/app/session/tabs-controller.d.ts +6 -2
- package/dist/app/session/tabs-controller.js +114 -30
- package/dist/app/types.d.ts +5 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +3 -0
- package/dist/app/workspace/workspace-actions-controller.js +71 -16
- package/dist/app/workspace/workspace-undo.js +41 -6
- package/dist/config.d.ts +1 -0
- package/dist/config.js +19 -7
- package/dist/markdown-format.d.ts +6 -0
- package/dist/markdown-format.js +11 -3
- package/dist/syntax-highlight.js +3 -1
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +53 -28
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +2 -1
- package/external/pi-tools-suite/src/telegram-mirror/README.md +81 -46
- package/external/pi-tools-suite/src/telegram-mirror/bot.ts +81 -10
- package/external/pi-tools-suite/src/telegram-mirror/events.ts +6 -38
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +246 -40
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +20 -0
- package/external/pi-tools-suite/src/telegram-mirror/multiplexer.ts +247 -17
- package/external/pi-tools-suite/src/telegram-mirror/renderer.ts +75 -78
- package/external/pi-tools-suite/src/todo/index.ts +7 -6
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +1 -1
- package/external/pi-tools-suite/src/web-search/index.ts +139 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -87,7 +87,6 @@ Useful flags:
|
|
|
87
87
|
- `--cwd <path>`: workspace used for Pi tools, settings, resources, and sessions.
|
|
88
88
|
- `--no-session`: run with an in-memory SDK session.
|
|
89
89
|
- `--model <provider/model[:thinking]>`: request a specific model, for example `anthropic/claude-sonnet-4-20250514:medium`.
|
|
90
|
-
- `--reload-on-build`: restart the running Pix process after a successful watcher build.
|
|
91
90
|
|
|
92
91
|
## Updating Pix
|
|
93
92
|
|
|
@@ -143,20 +142,12 @@ Run Pix against a workspace:
|
|
|
143
142
|
pix --cwd /path/to/workspace
|
|
144
143
|
```
|
|
145
144
|
|
|
146
|
-
During UI development, run the watcher in another terminal:
|
|
145
|
+
During UI development, run the watcher in another terminal, then restart Pix after rebuilds when needed:
|
|
147
146
|
|
|
148
147
|
```bash
|
|
149
148
|
npm run watch:pix
|
|
150
149
|
```
|
|
151
150
|
|
|
152
|
-
Each running instance can reload after successful builds:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
PIX_RELOAD_ON_BUILD=1 pix --cwd /path/to/workspace
|
|
156
|
-
# or
|
|
157
|
-
pix --reload-on-build --cwd /path/to/workspace
|
|
158
|
-
```
|
|
159
|
-
|
|
160
151
|
For a one-shot dev launch that rebuilds and refreshes the global link first:
|
|
161
152
|
|
|
162
153
|
```bash
|
package/bin/pix.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
4
3
|
import { delimiter, dirname, join } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
|
|
@@ -11,10 +10,7 @@ const packageRoot = dirname(dirname(launcherPath));
|
|
|
11
10
|
const mainPath = fileURLToPath(new URL("../dist/main.js", import.meta.url));
|
|
12
11
|
const updatePath = fileURLToPath(new URL("../dist/app/cli/update.js", import.meta.url));
|
|
13
12
|
const installPath = fileURLToPath(new URL("../dist/app/cli/install.js", import.meta.url));
|
|
14
|
-
const
|
|
15
|
-
const rawArgs = process.argv.slice(2);
|
|
16
|
-
const childArgs = [];
|
|
17
|
-
let reloadOnBuild = truthyEnv(process.env.PIX_RELOAD_ON_BUILD);
|
|
13
|
+
const cliArgs = process.argv.slice(2);
|
|
18
14
|
|
|
19
15
|
if (!isCurrentNodeSupported()) {
|
|
20
16
|
console.error(`[pix] Node ${minimumNodeVersionLabel}+ is required; current Node is ${process.versions.node}.`);
|
|
@@ -22,63 +18,31 @@ if (!isCurrentNodeSupported()) {
|
|
|
22
18
|
process.exit(1);
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
if (arg === "--reload-on-build") {
|
|
27
|
-
reloadOnBuild = true;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (arg === "--no-reload-on-build") {
|
|
31
|
-
reloadOnBuild = false;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
childArgs.push(arg);
|
|
35
|
-
}
|
|
21
|
+
applyPixRuntimeEnv();
|
|
36
22
|
|
|
37
|
-
if (
|
|
23
|
+
if (cliArgs[0] === "update") {
|
|
38
24
|
if (!existsSync(updatePath)) {
|
|
39
25
|
console.error("pix update is not built yet. Run `npm run build:pix` or update from a published package.");
|
|
40
26
|
process.exit(1);
|
|
41
27
|
}
|
|
42
28
|
const { runPixUpdateCli } = await import(new URL("../dist/app/cli/update.js", import.meta.url));
|
|
43
|
-
process.exit(await runPixUpdateCli(
|
|
29
|
+
process.exit(await runPixUpdateCli(cliArgs.slice(1)));
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
if (
|
|
32
|
+
if (cliArgs[0] === "install" || cliArgs[0] === "setup") {
|
|
47
33
|
if (!existsSync(installPath)) {
|
|
48
34
|
console.error("pix install is not built yet. Run `npm run build:pix` or update from a published package.");
|
|
49
35
|
process.exit(1);
|
|
50
36
|
}
|
|
51
37
|
const { runPixInstallCli } = await import(new URL("../dist/app/cli/install.js", import.meta.url));
|
|
52
|
-
process.exit(await runPixInstallCli(
|
|
38
|
+
process.exit(await runPixInstallCli(cliArgs.slice(1), { env: process.env }));
|
|
53
39
|
}
|
|
54
40
|
|
|
55
41
|
if (!existsSync(mainPath)) {
|
|
56
42
|
console.error("pix is not built yet. Run `npm run build:pix` or `npm run watch:pix`.");
|
|
57
43
|
process.exit(1);
|
|
58
44
|
}
|
|
59
|
-
|
|
60
|
-
let child = undefined;
|
|
61
|
-
let reloadTimer = undefined;
|
|
62
|
-
let distPollTimer = undefined;
|
|
63
|
-
let distSnapshot = snapshotDist();
|
|
64
|
-
let restarting = false;
|
|
65
|
-
let shuttingDown = false;
|
|
66
|
-
|
|
67
|
-
startChild();
|
|
68
|
-
if (reloadOnBuild) startDistPolling();
|
|
69
|
-
|
|
70
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
71
|
-
process.on(signal, () => {
|
|
72
|
-
shuttingDown = true;
|
|
73
|
-
stopDistPolling();
|
|
74
|
-
child?.kill(signal);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function truthyEnv(value) {
|
|
79
|
-
if (!value) return false;
|
|
80
|
-
return !["0", "false", "no", "off"].includes(value.toLowerCase());
|
|
81
|
-
}
|
|
45
|
+
await import(new URL("../dist/main.js", import.meta.url));
|
|
82
46
|
|
|
83
47
|
function isCurrentNodeSupported() {
|
|
84
48
|
const parts = process.versions.node.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -91,117 +55,10 @@ function isCurrentNodeSupported() {
|
|
|
91
55
|
return true;
|
|
92
56
|
}
|
|
93
57
|
|
|
94
|
-
function
|
|
95
|
-
child = spawn(process.execPath, [mainPath, ...childArgs], {
|
|
96
|
-
stdio: "inherit",
|
|
97
|
-
env: pixChildEnv(),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
child.on("error", (error) => {
|
|
101
|
-
console.error(error.message);
|
|
102
|
-
process.exitCode = 1;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
child.on("exit", (code, signal) => {
|
|
106
|
-
child = undefined;
|
|
107
|
-
if (restarting) return;
|
|
108
|
-
|
|
109
|
-
shuttingDown = true;
|
|
110
|
-
stopDistPolling();
|
|
111
|
-
if (signal) {
|
|
112
|
-
process.exitCode = signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
process.exitCode = code ?? 1;
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function pixChildEnv() {
|
|
120
|
-
const env = { ...process.env };
|
|
58
|
+
function applyPixRuntimeEnv() {
|
|
121
59
|
const bundledBinPath = join(packageRoot, "node_modules", ".bin");
|
|
122
60
|
if (existsSync(bundledBinPath)) {
|
|
123
|
-
env.PATH = [bundledBinPath, env.PATH ?? ""].filter(Boolean).join(delimiter);
|
|
124
|
-
env.PIX_BUNDLED_PI_BIN = bundledBinPath;
|
|
61
|
+
process.env.PATH = [bundledBinPath, process.env.PATH ?? ""].filter(Boolean).join(delimiter);
|
|
62
|
+
process.env.PIX_BUNDLED_PI_BIN = bundledBinPath;
|
|
125
63
|
}
|
|
126
|
-
return env;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function startDistPolling() {
|
|
130
|
-
const pollInterval = Number(process.env.PIX_RELOAD_POLL_MS ?? 1000);
|
|
131
|
-
distPollTimer = setInterval(() => {
|
|
132
|
-
const nextSnapshot = snapshotDist();
|
|
133
|
-
if (nextSnapshot === distSnapshot) return;
|
|
134
|
-
|
|
135
|
-
distSnapshot = nextSnapshot;
|
|
136
|
-
queueReload();
|
|
137
|
-
}, Number.isFinite(pollInterval) && pollInterval > 0 ? pollInterval : 1000);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function stopDistPolling() {
|
|
141
|
-
if (reloadTimer) clearTimeout(reloadTimer);
|
|
142
|
-
if (distPollTimer) clearInterval(distPollTimer);
|
|
143
|
-
reloadTimer = undefined;
|
|
144
|
-
distPollTimer = undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function queueReload() {
|
|
148
|
-
if (shuttingDown) return;
|
|
149
|
-
if (reloadTimer) clearTimeout(reloadTimer);
|
|
150
|
-
reloadTimer = setTimeout(() => {
|
|
151
|
-
reloadTimer = undefined;
|
|
152
|
-
restartChild();
|
|
153
|
-
}, 250);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function restartChild() {
|
|
157
|
-
if (shuttingDown) return;
|
|
158
|
-
if (!child) {
|
|
159
|
-
startChild();
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
restarting = true;
|
|
164
|
-
const currentChild = child;
|
|
165
|
-
currentChild.once("exit", () => {
|
|
166
|
-
restarting = false;
|
|
167
|
-
if (!shuttingDown) startChild();
|
|
168
|
-
});
|
|
169
|
-
console.error("[pix] dist changed; restarting renderer");
|
|
170
|
-
currentChild.kill("SIGTERM");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function snapshotDist() {
|
|
174
|
-
let newestMtime = 0;
|
|
175
|
-
let runtimeFileCount = 0;
|
|
176
|
-
const pendingDirs = [distPath];
|
|
177
|
-
|
|
178
|
-
while (pendingDirs.length > 0) {
|
|
179
|
-
const currentDir = pendingDirs.pop();
|
|
180
|
-
if (!currentDir) continue;
|
|
181
|
-
|
|
182
|
-
let entries;
|
|
183
|
-
try {
|
|
184
|
-
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
185
|
-
} catch {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
for (const entry of entries) {
|
|
190
|
-
const entryPath = join(currentDir, entry.name);
|
|
191
|
-
if (entry.isDirectory()) {
|
|
192
|
-
pendingDirs.push(entryPath);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (!entry.isFile() || !entry.name.endsWith(".js")) continue;
|
|
196
|
-
|
|
197
|
-
runtimeFileCount += 1;
|
|
198
|
-
try {
|
|
199
|
-
newestMtime = Math.max(newestMtime, statSync(entryPath).mtimeMs);
|
|
200
|
-
} catch {
|
|
201
|
-
// If tsc is replacing a file while we scan, the next poll will see the final state.
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return `${runtimeFileCount}:${newestMtime}`;
|
|
207
64
|
}
|
package/dist/app/app.d.ts
CHANGED
|
@@ -60,6 +60,9 @@ export declare class PiUiExtendApp {
|
|
|
60
60
|
private resumeLoading;
|
|
61
61
|
constructor(options: AppOptions);
|
|
62
62
|
private createRuntime;
|
|
63
|
+
private loadStartupConfig;
|
|
64
|
+
private applyPixConfig;
|
|
65
|
+
private updateOutputFilters;
|
|
63
66
|
start(): Promise<void>;
|
|
64
67
|
private checkPixUpdateOnStartup;
|
|
65
68
|
private bindCurrentSession;
|
|
@@ -82,6 +85,7 @@ export declare class PiUiExtendApp {
|
|
|
82
85
|
private openSearchResultInNewTab;
|
|
83
86
|
private scrollToUserMessageJumpTarget;
|
|
84
87
|
private findUserEntryBySessionEntryId;
|
|
88
|
+
private findUserEntryByJumpText;
|
|
85
89
|
private loadSessionHistoryAsync;
|
|
86
90
|
private handleSessionEvent;
|
|
87
91
|
private findEntry;
|
package/dist/app/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { THEMES } from "../theme.js";
|
|
2
2
|
import { InputEditor } from "../input-editor.js";
|
|
3
|
-
import { compileOutputFilterPatterns, loadPixConfig, resolveToolRule, } from "../config.js";
|
|
3
|
+
import { compileOutputFilterPatterns, defaultPixConfig, loadPixConfig, resolveToolRule, } from "../config.js";
|
|
4
4
|
import { AppCommandController } from "./commands/command-controller.js";
|
|
5
5
|
import { ConversationViewport } from "./rendering/conversation-viewport.js";
|
|
6
6
|
import { EditorLayoutRenderer } from "./rendering/editor-layout-renderer.js";
|
|
@@ -46,6 +46,9 @@ import { AppVoiceController } from "./input/voice-controller.js";
|
|
|
46
46
|
import { createIsolatedExtensionEventBus } from "./extensions/extension-event-bus.js";
|
|
47
47
|
import { setAppIconTheme } from "./icons.js";
|
|
48
48
|
const TERMINAL_BELL_ATTENTION_EVENT = "pix:terminal-bell:attention";
|
|
49
|
+
function normalizeJumpTargetText(text) {
|
|
50
|
+
return text.replace(/\s+/gu, " ").trim();
|
|
51
|
+
}
|
|
49
52
|
const SUBAGENTS_LIVE_STATE_EVENT = "pi-tools-suite:async-subagents:live-state";
|
|
50
53
|
const TODO_STATE_EVENT = "pi-tools-suite:todo:state";
|
|
51
54
|
const COALESCED_RENDER_DELAY_MS = 16;
|
|
@@ -68,7 +71,7 @@ export class PiUiExtendApp {
|
|
|
68
71
|
extensionUiController;
|
|
69
72
|
extensionActions;
|
|
70
73
|
pixConfig;
|
|
71
|
-
outputFilters;
|
|
74
|
+
outputFilters = [];
|
|
72
75
|
commandController;
|
|
73
76
|
inputActions;
|
|
74
77
|
inputController;
|
|
@@ -127,7 +130,7 @@ export class PiUiExtendApp {
|
|
|
127
130
|
constructor(options) {
|
|
128
131
|
this.options = options;
|
|
129
132
|
this.theme = THEMES[options.themeName];
|
|
130
|
-
this.pixConfig =
|
|
133
|
+
this.pixConfig = defaultPixConfig();
|
|
131
134
|
setAppIconTheme(this.pixConfig.iconTheme.name);
|
|
132
135
|
const app = this;
|
|
133
136
|
this.blinkController = new AppBlinkController({
|
|
@@ -160,7 +163,7 @@ export class PiUiExtendApp {
|
|
|
160
163
|
});
|
|
161
164
|
this.tabsController = new AppTabsController({
|
|
162
165
|
options: this.options,
|
|
163
|
-
maxProjectSessions: this.pixConfig.maxProjectSessions,
|
|
166
|
+
maxProjectSessions: () => this.pixConfig.maxProjectSessions,
|
|
164
167
|
blinkController: this.blinkController,
|
|
165
168
|
runtime: () => this.runtime,
|
|
166
169
|
createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options)),
|
|
@@ -398,18 +401,16 @@ export class PiUiExtendApp {
|
|
|
398
401
|
get subagentsWidgetState() { return app.subagentsWidgetController.widgetState; },
|
|
399
402
|
get voicePartialText() { return app.voicePartialText; },
|
|
400
403
|
get autocompleteSuggestion() { return app.autocompleteController.suggestionText(); },
|
|
401
|
-
get queuedMessageWidgetEntries() { return app.queuedMessages.
|
|
404
|
+
get queuedMessageWidgetEntries() { return app.queuedMessages.queuedEntries(); },
|
|
402
405
|
renderExtensionInputComponent: (width) => this.extensionUiController.renderActiveCustomUi(width),
|
|
403
406
|
extensionInputUsesEditor: () => this.extensionUiController.activeCustomUiUsesEditor(),
|
|
404
407
|
widgetTuiHandle: () => this.extensionUiController.widgetTuiHandle(),
|
|
405
408
|
createExtensionTheme: () => this.extensionUiController.createExtensionTheme(),
|
|
406
409
|
suppressExtensionWidget: (key) => this.extensionUiController.suppressWidget(key),
|
|
407
410
|
});
|
|
408
|
-
this.
|
|
411
|
+
this.updateOutputFilters();
|
|
409
412
|
this.conversationViewport = new ConversationViewport({
|
|
410
413
|
get entries() { return app.entries; },
|
|
411
|
-
get session() { return app.runtime?.session; },
|
|
412
|
-
get deferredUserMessages() { return app.queuedMessages.deferredUserMessages; },
|
|
413
414
|
get entryRenderVersions() { return app.sessionEvents.entryRenderVersions; },
|
|
414
415
|
get superCompactTools() { return app.superCompactTools; },
|
|
415
416
|
get allThinkingExpanded() { return app.allThinkingExpanded; },
|
|
@@ -700,6 +701,7 @@ export class PiUiExtendApp {
|
|
|
700
701
|
inputEditor: () => this.inputEditor,
|
|
701
702
|
enableTerminal: () => this.terminalController.enableTerminal(),
|
|
702
703
|
disposeRuntimeForQuit: (runtime) => this.terminalController.disposeRuntimeForQuit(runtime),
|
|
704
|
+
loadStartupConfig: () => this.loadStartupConfig(),
|
|
703
705
|
loadRequestHistory: () => this.requestHistory.load(),
|
|
704
706
|
startSubagentsPolling: () => this.subagentsWidgetController.startPolling(),
|
|
705
707
|
closeSdkMenuForBind: () => this.popupMenus.closeSdkMenu(undefined, { render: false, restoreStatus: false }),
|
|
@@ -750,8 +752,35 @@ export class PiUiExtendApp {
|
|
|
750
752
|
createRuntime(options) {
|
|
751
753
|
return createPixRuntime(options, {
|
|
752
754
|
eventBus: this.createExtensionEventBus(),
|
|
755
|
+
config: this.pixConfig,
|
|
753
756
|
});
|
|
754
757
|
}
|
|
758
|
+
async loadStartupConfig() {
|
|
759
|
+
await yieldToEventLoop();
|
|
760
|
+
this.applyPixConfig(loadPixConfig(this.options.cwd));
|
|
761
|
+
}
|
|
762
|
+
applyPixConfig(config) {
|
|
763
|
+
replaceRecord(this.pixConfig.toolRenderer, config.toolRenderer);
|
|
764
|
+
replaceRecord(this.pixConfig.outputFilters, config.outputFilters);
|
|
765
|
+
replaceRecord(this.pixConfig.promptEnhancer, config.promptEnhancer);
|
|
766
|
+
replaceRecord(this.pixConfig.autocomplete, config.autocomplete);
|
|
767
|
+
replaceRecord(this.pixConfig.modelColors, config.modelColors);
|
|
768
|
+
replaceRecord(this.pixConfig.iconTheme, config.iconTheme);
|
|
769
|
+
replaceRecord(this.pixConfig.dictation, config.dictation);
|
|
770
|
+
if (config.defaultModel === undefined)
|
|
771
|
+
delete this.pixConfig.defaultModel;
|
|
772
|
+
else
|
|
773
|
+
this.pixConfig.defaultModel = { ...config.defaultModel };
|
|
774
|
+
this.pixConfig.ignoreContextFiles = config.ignoreContextFiles;
|
|
775
|
+
this.pixConfig.maxProjectSessions = config.maxProjectSessions;
|
|
776
|
+
this.updateOutputFilters();
|
|
777
|
+
this.voiceController.updateDictationConfig(this.pixConfig.dictation);
|
|
778
|
+
setAppIconTheme(this.pixConfig.iconTheme.name);
|
|
779
|
+
this.conversationViewport.clear();
|
|
780
|
+
}
|
|
781
|
+
updateOutputFilters() {
|
|
782
|
+
this.outputFilters.splice(0, this.outputFilters.length, ...compileOutputFilterPatterns(this.pixConfig.outputFilters.patterns));
|
|
783
|
+
}
|
|
755
784
|
async start() {
|
|
756
785
|
await this.sessionLifecycle.start();
|
|
757
786
|
this.modelUsageController.startPolling();
|
|
@@ -888,20 +917,42 @@ export class PiUiExtendApp {
|
|
|
888
917
|
async scrollToUserMessageJumpTarget(target) {
|
|
889
918
|
if (target.entryId && this.scrollController.scrollToConversationEntry(target.entryId))
|
|
890
919
|
return true;
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
920
|
+
this.workspaceActions.syncUserSessionEntryMetadata();
|
|
921
|
+
if (target.sessionEntryId) {
|
|
922
|
+
let entry = this.findUserEntryBySessionEntryId(target.sessionEntryId);
|
|
923
|
+
while (!entry && this.sessionEvents.hasOlderSessionHistory() && !this.sessionEvents.isLoadingOlderSessionHistory()) {
|
|
924
|
+
const loaded = await this.sessionEvents.loadOlderSessionHistory({ render: false });
|
|
925
|
+
if (!loaded)
|
|
926
|
+
break;
|
|
927
|
+
entry = this.findUserEntryBySessionEntryId(target.sessionEntryId);
|
|
928
|
+
}
|
|
929
|
+
if (entry && this.scrollController.scrollToConversationEntry(entry.id))
|
|
930
|
+
return true;
|
|
899
931
|
}
|
|
900
|
-
|
|
932
|
+
const fallbackEntry = this.findUserEntryByJumpText(target);
|
|
933
|
+
return fallbackEntry ? this.scrollController.scrollToConversationEntry(fallbackEntry.id) : false;
|
|
901
934
|
}
|
|
902
935
|
findUserEntryBySessionEntryId(sessionEntryId) {
|
|
903
936
|
return this.entries.find((entry) => entry.kind === "user" && entry.sessionEntryId === sessionEntryId);
|
|
904
937
|
}
|
|
938
|
+
findUserEntryByJumpText(target) {
|
|
939
|
+
if (!target.text)
|
|
940
|
+
return undefined;
|
|
941
|
+
const userEntries = this.entries.filter((entry) => entry.kind === "user");
|
|
942
|
+
if (target.userIndex !== undefined && target.userCount !== undefined) {
|
|
943
|
+
const visibleIndex = target.userIndex - (target.userCount - userEntries.length);
|
|
944
|
+
const entry = userEntries[visibleIndex];
|
|
945
|
+
if (entry && normalizeJumpTargetText(entry.text) === normalizeJumpTargetText(target.text))
|
|
946
|
+
return entry;
|
|
947
|
+
}
|
|
948
|
+
const normalizedTargetText = normalizeJumpTargetText(target.text);
|
|
949
|
+
for (let index = userEntries.length - 1; index >= 0; index -= 1) {
|
|
950
|
+
const entry = userEntries[index];
|
|
951
|
+
if (entry && normalizeJumpTargetText(entry.text) === normalizedTargetText)
|
|
952
|
+
return entry;
|
|
953
|
+
}
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
905
956
|
async loadSessionHistoryAsync(options) {
|
|
906
957
|
return this.sessionEvents.loadSessionHistoryAsync(options);
|
|
907
958
|
}
|
|
@@ -1053,3 +1104,37 @@ function newTabRuntimeOptions(options) {
|
|
|
1053
1104
|
...(options.modelRef === undefined ? {} : { modelRef: options.modelRef }),
|
|
1054
1105
|
};
|
|
1055
1106
|
}
|
|
1107
|
+
async function yieldToEventLoop() {
|
|
1108
|
+
await new Promise((resolve) => { setTimeout(resolve, 0); });
|
|
1109
|
+
}
|
|
1110
|
+
function replaceRecord(target, source) {
|
|
1111
|
+
for (const key of Object.keys(target)) {
|
|
1112
|
+
if (!(key in source))
|
|
1113
|
+
delete target[key];
|
|
1114
|
+
}
|
|
1115
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1116
|
+
const existing = target[key];
|
|
1117
|
+
if (isMutableRecord(existing) && isMutableRecord(value)) {
|
|
1118
|
+
replaceRecord(existing, value);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
target[key] = cloneConfigValue(value);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function cloneConfigValue(value) {
|
|
1126
|
+
if (Array.isArray(value))
|
|
1127
|
+
return value.map(cloneConfigValue);
|
|
1128
|
+
if (isMutableRecord(value))
|
|
1129
|
+
return cloneConfigRecord(value);
|
|
1130
|
+
return value;
|
|
1131
|
+
}
|
|
1132
|
+
function cloneConfigRecord(value) {
|
|
1133
|
+
const cloned = {};
|
|
1134
|
+
for (const [key, nested] of Object.entries(value))
|
|
1135
|
+
cloned[key] = cloneConfigValue(nested);
|
|
1136
|
+
return cloned;
|
|
1137
|
+
}
|
|
1138
|
+
function isMutableRecord(value) {
|
|
1139
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1140
|
+
}
|
|
@@ -12,9 +12,6 @@ export function createStartupInfoMessage(runtime) {
|
|
|
12
12
|
...sections.flatMap(formatSection),
|
|
13
13
|
].join("\n").trimEnd();
|
|
14
14
|
}
|
|
15
|
-
export function isEmptyStartupSession(runtime) {
|
|
16
|
-
return Array.isArray(runtime.session.messages) && runtime.session.messages.length === 0;
|
|
17
|
-
}
|
|
18
15
|
function startupSections(runtime) {
|
|
19
16
|
const loader = runtime.session.resourceLoader;
|
|
20
17
|
const context = loader.getAgentsFiles();
|
|
@@ -9,6 +9,7 @@ import { runProcess } from "../process.js";
|
|
|
9
9
|
import { copyTextToClipboard } from "../screen/clipboard.js";
|
|
10
10
|
import { formatAccountUsageReport, queryAccountUsageReport } from "../model/model-usage-status.js";
|
|
11
11
|
import { checkPixUpdate, formatPixUpdateCheck, parsePixUpdateArgs, pixUpdateUsage } from "../cli/update.js";
|
|
12
|
+
import { createStartupInfoMessage } from "../cli/startup-info.js";
|
|
12
13
|
export class SessionCommandActions {
|
|
13
14
|
host;
|
|
14
15
|
constructor(host) {
|
|
@@ -114,6 +115,8 @@ export class SessionCommandActions {
|
|
|
114
115
|
return;
|
|
115
116
|
const stats = runtime.session.getSessionStats();
|
|
116
117
|
const lines = [
|
|
118
|
+
createStartupInfoMessage(runtime),
|
|
119
|
+
"",
|
|
117
120
|
"Session info",
|
|
118
121
|
...(runtime.session.sessionName ? [`name: ${runtime.session.sessionName}`] : []),
|
|
119
122
|
`file: ${stats.sessionFile ?? "in-memory"}`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANSI_RESET, colorize, THEMES } from "../../theme.js";
|
|
1
|
+
import { ANSI_RESET, ansiStylePrefix, colorize, THEMES } from "../../theme.js";
|
|
2
2
|
import { isToastKind } from "../../ui.js";
|
|
3
3
|
import { ellipsizeDisplay, sanitizeText } from "../rendering/render-text.js";
|
|
4
4
|
const CUSTOM_UI_WIDGET_KEY = "pix-custom-ui";
|
|
@@ -20,7 +20,7 @@ export class ExtensionUiController {
|
|
|
20
20
|
const colors = this.host.theme.colors;
|
|
21
21
|
const foreground = (color) => extensionForegroundColor(colors, String(color));
|
|
22
22
|
const background = (color) => extensionBackgroundColor(colors, String(color));
|
|
23
|
-
const prefix = (options) => (
|
|
23
|
+
const prefix = (options) => ansiStylePrefix(options);
|
|
24
24
|
return {
|
|
25
25
|
fg: (color, text) => colorize(text, { foreground: foreground(color) }),
|
|
26
26
|
bg: (color, text) => colorize(text, { background: background(color) }),
|
|
@@ -54,8 +54,8 @@ type VoiceControllerTestDeps = {
|
|
|
54
54
|
export declare function setVoiceControllerTestDeps(overrides?: Partial<VoiceControllerTestDeps>): void;
|
|
55
55
|
export declare class AppVoiceController {
|
|
56
56
|
private readonly host;
|
|
57
|
-
private
|
|
58
|
-
private
|
|
57
|
+
private modelDefinitions;
|
|
58
|
+
private languages;
|
|
59
59
|
private language;
|
|
60
60
|
private state;
|
|
61
61
|
private readonly modelCache;
|
|
@@ -69,6 +69,7 @@ export declare class AppVoiceController {
|
|
|
69
69
|
private partialTranscriptTimer;
|
|
70
70
|
private startGeneration;
|
|
71
71
|
constructor(host: AppVoiceControllerHost, dictationConfig: DictationConfig);
|
|
72
|
+
updateDictationConfig(dictationConfig: DictationConfig): void;
|
|
72
73
|
statusWidgetText(): string;
|
|
73
74
|
showLanguageSwitcher(): boolean;
|
|
74
75
|
statusWidgetActive(): boolean;
|
|
@@ -51,6 +51,15 @@ export class AppVoiceController {
|
|
|
51
51
|
this.languages = Object.keys(this.modelDefinitions);
|
|
52
52
|
this.language = this.initialLanguage(dictationConfig.language);
|
|
53
53
|
}
|
|
54
|
+
updateDictationConfig(dictationConfig) {
|
|
55
|
+
this.modelDefinitions = dictationConfig.languages;
|
|
56
|
+
this.languages = Object.keys(this.modelDefinitions);
|
|
57
|
+
this.language = this.initialLanguage(dictationConfig.language ?? this.language);
|
|
58
|
+
for (const language of this.modelCache.keys()) {
|
|
59
|
+
if (!this.modelDefinitions[language])
|
|
60
|
+
this.modelCache.delete(language);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
54
63
|
statusWidgetText() {
|
|
55
64
|
const languageLabel = this.showLanguageSwitcher() ? ` ${this.language.toUpperCase()}` : "";
|
|
56
65
|
switch (this.state) {
|
|
@@ -810,7 +810,13 @@ export function buildUserMessageJumpItems(entries) {
|
|
|
810
810
|
const preview = sanitizeText(entry.text).replace(/\s+/g, " ").trim();
|
|
811
811
|
const label = `${index + 1}. ${preview || "(empty message)"}`;
|
|
812
812
|
return {
|
|
813
|
-
value: {
|
|
813
|
+
value: {
|
|
814
|
+
...(entry.entryId === undefined ? {} : { entryId: entry.entryId }),
|
|
815
|
+
...(entry.sessionEntryId === undefined ? {} : { sessionEntryId: entry.sessionEntryId }),
|
|
816
|
+
text: entry.text,
|
|
817
|
+
userIndex: index,
|
|
818
|
+
userCount: userEntries.length,
|
|
819
|
+
},
|
|
814
820
|
label,
|
|
815
821
|
...(entry.entryId ? {} : { description: "load older history and jump" }),
|
|
816
822
|
aliases: [entry.sessionEntryId ?? "", entry.entryId ?? ""],
|
|
@@ -2,7 +2,7 @@ import { applyOutputFilters } from "../../config.js";
|
|
|
2
2
|
import { renderMarkdownTextLines } from "../../markdown-format.js";
|
|
3
3
|
import { attachImageClickTargets } from "../screen/image-click-targets.js";
|
|
4
4
|
import { APP_ICONS } from "../icons.js";
|
|
5
|
-
import { horizontalPaddingLayout, padHorizontalText,
|
|
5
|
+
import { horizontalPaddingLayout, padHorizontalText, wrapTextLines } from "./render-text.js";
|
|
6
6
|
import { renderConversationShellEntry } from "./conversation-shell-renderer.js";
|
|
7
7
|
import { renderConversationToolEntry, renderThinkingEntry } from "./conversation-tool-renderer.js";
|
|
8
8
|
export function renderConversationEntry(entry, width, options) {
|
|
@@ -10,6 +10,7 @@ export function renderConversationEntry(entry, width, options) {
|
|
|
10
10
|
const userLine = (text, entryId, syntaxHighlight, segments) => ({
|
|
11
11
|
text: padHorizontalText(text, width),
|
|
12
12
|
colorOverride: options.colors.userForeground,
|
|
13
|
+
backgroundOverride: options.colors.userMessageBackground,
|
|
13
14
|
...(segments && segments.length > 0 ? { segments: segments.map((segment) => ({ ...segment, start: segment.start + userContentLeft, end: segment.end + userContentLeft })) } : {}),
|
|
14
15
|
...(syntaxHighlight === undefined ? {} : { syntaxHighlight }),
|
|
15
16
|
...(entryId === undefined ? {} : { target: { kind: "user-message", id: entryId } }),
|
|
@@ -21,17 +22,25 @@ export function renderConversationEntry(entry, width, options) {
|
|
|
21
22
|
target: { kind: "queue-message", id: entryId },
|
|
22
23
|
});
|
|
23
24
|
const userMessageLines = (userEntry) => {
|
|
24
|
-
const lines = renderMarkdownTextLines(userEntry.text, userContentWidth, userContentLeft).map((line) =>
|
|
25
|
+
const lines = renderMarkdownTextLines(userEntry.text, userContentWidth, userContentLeft).map((line) => ({
|
|
26
|
+
...userLine(line.text, userEntry.id, line.syntaxHighlight, line.segments),
|
|
27
|
+
...(line.copyText === undefined ? {} : { copyText: line.copyText }),
|
|
28
|
+
...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}),
|
|
29
|
+
}));
|
|
25
30
|
return attachImageClickTargets(lines, userEntry.id, userEntry.images, { foreground: options.colors.info, underline: true });
|
|
26
31
|
};
|
|
27
32
|
const queuedMessageLines = (queuedEntry) => {
|
|
28
33
|
const icon = queuedEntry.queueSource === "deferred" ? APP_ICONS.pause : APP_ICONS.timerSand;
|
|
29
|
-
const contentLines =
|
|
30
|
-
return contentLines.map((
|
|
34
|
+
const contentLines = wrapTextLines(`${icon} ${queuedEntry.text}`, width);
|
|
35
|
+
return contentLines.map((line, index) => ({
|
|
36
|
+
...queuedLine(line.text, queuedEntry.id, index === 0 ? [{ start: 0, end: icon.length, foreground: options.colors.info }] : undefined),
|
|
37
|
+
copyText: line.copyText,
|
|
38
|
+
...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}),
|
|
39
|
+
}));
|
|
31
40
|
};
|
|
32
41
|
switch (entry.kind) {
|
|
33
42
|
case "system":
|
|
34
|
-
return
|
|
43
|
+
return wrapTextLines(`system: ${entry.text}`, width).map((line) => ({ text: line.text, copyText: line.copyText, ...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}), variant: "muted" }));
|
|
35
44
|
case "user":
|
|
36
45
|
return userMessageLines(entry);
|
|
37
46
|
case "queued":
|
|
@@ -41,21 +50,23 @@ export function renderConversationEntry(entry, width, options) {
|
|
|
41
50
|
case "custom":
|
|
42
51
|
return renderCustomEntry(entry, width);
|
|
43
52
|
case "session-aborted":
|
|
44
|
-
return
|
|
53
|
+
return wrapTextLines(entry.text, width).map((line) => ({ text: line.text, copyText: line.copyText, ...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}), variant: "error" }));
|
|
45
54
|
case "shell":
|
|
46
55
|
return renderConversationShellEntry(entry, width, options);
|
|
47
56
|
case "thinking":
|
|
48
57
|
return renderThinkingEntry(entry, width, options);
|
|
49
58
|
case "error":
|
|
50
|
-
return
|
|
59
|
+
return wrapTextLines(`error: ${entry.text}`, width).map((line) => ({ text: line.text, copyText: line.copyText, ...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}), variant: "error" }));
|
|
51
60
|
case "tool":
|
|
52
61
|
return renderConversationToolEntry(entry, width, options);
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
function renderCustomEntry(entry, width) {
|
|
56
65
|
const label = `[${entry.customType}]`;
|
|
57
|
-
return
|
|
58
|
-
text,
|
|
66
|
+
return wrapTextLines(`${label}\n${entry.text}`, width).map((line, index) => ({
|
|
67
|
+
text: line.text,
|
|
68
|
+
copyText: line.copyText,
|
|
69
|
+
...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}),
|
|
59
70
|
variant: index === 0 ? "accent" : "normal",
|
|
60
71
|
}));
|
|
61
72
|
}
|
|
@@ -69,10 +80,18 @@ function renderAssistantLines(text, width, options) {
|
|
|
69
80
|
return [];
|
|
70
81
|
const lines = [];
|
|
71
82
|
for (const line of contentLines) {
|
|
83
|
+
const headingSegment = line.heading
|
|
84
|
+
? { start: contentLeft, end: contentLeft + line.text.length, foreground: options.colors.assistantForeground, bold: true }
|
|
85
|
+
: undefined;
|
|
86
|
+
const existingSegments = line.segments?.map((segment) => ({ ...segment, start: segment.start + contentLeft, end: segment.end + contentLeft })) ?? [];
|
|
87
|
+
const allSegments = headingSegment ? [headingSegment, ...existingSegments] : existingSegments;
|
|
72
88
|
lines.push({
|
|
73
89
|
text: padHorizontalText(line.text, width),
|
|
90
|
+
...(line.copyText === undefined ? {} : { copyText: line.copyText }),
|
|
91
|
+
...(line.continuesOnNextLine ? { continuesOnNextLine: true } : {}),
|
|
74
92
|
colorOverride: options.colors.assistantForeground,
|
|
75
|
-
|
|
93
|
+
backgroundOverride: options.colors.assistantMessageBackground,
|
|
94
|
+
...(allSegments.length > 0 ? { segments: allSegments } : {}),
|
|
76
95
|
...(line.syntaxHighlight ? { syntaxHighlight: line.syntaxHighlight } : {}),
|
|
77
96
|
});
|
|
78
97
|
}
|
|
@@ -63,7 +63,7 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
63
63
|
expandedText: compactExpandedText || "(empty)",
|
|
64
64
|
bodyWrap: "word",
|
|
65
65
|
syntaxHighlight: compactExpandedText ? markdownSyntaxHighlightsForText(compactExpandedText) : undefined,
|
|
66
|
-
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded) });
|
|
66
|
+
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded), backgroundOverride: options.colors.thinkingMessageBackground, skipHeaderBackground: true });
|
|
67
67
|
}
|
|
68
68
|
function trimTrailingBlankLines(text) {
|
|
69
69
|
return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
|