pi-ui-extend 0.1.13 → 0.1.15
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 -1
- package/dist/app/app.d.ts +5 -0
- package/dist/app/app.js +82 -12
- package/dist/app/commands/command-controller.js +1 -0
- package/dist/app/commands/command-host.d.ts +3 -0
- package/dist/app/commands/command-model-actions.d.ts +2 -0
- package/dist/app/commands/command-model-actions.js +40 -4
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-registry.d.ts +1 -0
- package/dist/app/commands/command-registry.js +8 -0
- package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
- package/dist/app/extensions/extension-ui-controller.js +99 -61
- package/dist/app/input/input-action-controller.d.ts +1 -0
- package/dist/app/input/input-action-controller.js +8 -2
- package/dist/app/logger.d.ts +25 -0
- package/dist/app/logger.js +90 -0
- package/dist/app/model/model-usage-status.js +30 -15
- package/dist/app/popup/menu-items-controller.d.ts +2 -0
- package/dist/app/popup/menu-items-controller.js +45 -6
- package/dist/app/popup/popup-action-controller.d.ts +2 -1
- package/dist/app/popup/popup-action-controller.js +7 -4
- package/dist/app/popup/popup-menu-controller.d.ts +36 -23
- package/dist/app/popup/popup-menu-controller.js +68 -322
- package/dist/app/rendering/conversation-entry-renderer.js +3 -3
- package/dist/app/rendering/conversation-viewport.d.ts +10 -2
- package/dist/app/rendering/conversation-viewport.js +157 -16
- package/dist/app/rendering/editor-panels.js +4 -2
- package/dist/app/rendering/popup-menu-renderer.d.ts +50 -0
- package/dist/app/rendering/popup-menu-renderer.js +307 -0
- package/dist/app/rendering/render-controller.js +5 -13
- package/dist/app/rendering/status-line-renderer.d.ts +1 -1
- package/dist/app/rendering/status-line-renderer.js +27 -24
- package/dist/app/rendering/toast-controller.d.ts +11 -3
- package/dist/app/rendering/toast-controller.js +53 -12
- package/dist/app/runtime.d.ts +2 -1
- package/dist/app/runtime.js +20 -10
- package/dist/app/screen/mouse-controller.d.ts +2 -2
- package/dist/app/screen/mouse-controller.js +27 -48
- package/dist/app/screen/screen-styler.d.ts +1 -1
- package/dist/app/screen/screen-styler.js +9 -7
- package/dist/app/screen/scroll-controller.d.ts +11 -9
- package/dist/app/screen/scroll-controller.js +50 -45
- package/dist/app/session/lazy-session-manager.d.ts +11 -0
- package/dist/app/session/lazy-session-manager.js +539 -0
- package/dist/app/session/pix-system-message.d.ts +16 -0
- package/dist/app/session/pix-system-message.js +64 -0
- package/dist/app/session/session-event-controller.d.ts +11 -0
- package/dist/app/session/session-event-controller.js +58 -2
- package/dist/app/session/session-history.d.ts +18 -0
- package/dist/app/session/session-history.js +72 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
- package/dist/app/session/session-lifecycle-controller.js +7 -2
- package/dist/app/session/tabs-controller.d.ts +13 -1
- package/dist/app/session/tabs-controller.js +248 -27
- package/dist/app/todo/todo-model.d.ts +3 -1
- package/dist/app/todo/todo-model.js +14 -2
- package/dist/app/types.d.ts +5 -2
- package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
- package/dist/app/workspace/workspace-actions-controller.js +12 -0
- package/dist/config.d.ts +5 -1
- package/dist/config.js +73 -25
- package/dist/default-pix-config.js +2 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
- package/dist/schemas/pi-tools-suite-schema.js +1 -0
- package/dist/schemas/pix-schema.d.ts +2 -1
- package/dist/schemas/pix-schema.js +5 -4
- package/dist/terminal-width.d.ts +2 -0
- package/dist/terminal-width.js +64 -3
- package/external/pi-tools-suite/README.md +1 -0
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +12 -3
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +2 -4
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +2 -2
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +8 -2
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +102 -50
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +81 -2
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +29 -8
- package/external/pi-tools-suite/src/config.ts +8 -0
- package/external/pi-tools-suite/src/dcp/index.ts +16 -1
- package/external/pi-tools-suite/src/dcp/state.ts +35 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
- package/external/pi-tools-suite/src/todo/index.ts +181 -11
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +23 -10
- package/external/pi-tools-suite/src/todo/todo.ts +10 -5
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +33 -6
- package/external/pi-tools-suite/src/todo/tool/types.ts +9 -1
- package/external/pi-tools-suite/src/todo/view/format.ts +2 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +2 -1
- package/external/pi-tools-suite/src/usage/index.ts +5 -2
- package/external/pi-tools-suite/src/usage/lib/google.ts +6 -13
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +4 -0
- package/schemas/pix.json +6 -2
|
@@ -6,20 +6,15 @@ export class ExtensionUiController {
|
|
|
6
6
|
host;
|
|
7
7
|
extensionWidgets = new Map();
|
|
8
8
|
terminalInputHandlers = new Set();
|
|
9
|
-
|
|
10
|
-
aboveInputRenderer = {
|
|
11
|
-
set: (key, content) => {
|
|
12
|
-
this.setAboveInputWidget(key, content);
|
|
13
|
-
},
|
|
14
|
-
clear: (key) => {
|
|
15
|
-
this.clearAboveInputWidget(key);
|
|
16
|
-
},
|
|
17
|
-
};
|
|
9
|
+
activeCustomUis = new Map();
|
|
18
10
|
constructor(host) {
|
|
19
11
|
this.host = host;
|
|
20
12
|
}
|
|
21
13
|
get widgets() {
|
|
22
|
-
|
|
14
|
+
const activeScopeKey = this.activeScopeKey();
|
|
15
|
+
return new Map([...this.extensionWidgets.entries()]
|
|
16
|
+
.filter(([, widget]) => widget.scopeKey === activeScopeKey)
|
|
17
|
+
.map(([scopedKey, widget]) => [this.unscopedWidgetKey(scopedKey, widget.scopeKey), widget]));
|
|
23
18
|
}
|
|
24
19
|
createExtensionTheme() {
|
|
25
20
|
const colors = this.host.theme.colors;
|
|
@@ -47,44 +42,54 @@ export class ExtensionUiController {
|
|
|
47
42
|
};
|
|
48
43
|
}
|
|
49
44
|
setWidget(key, content, options) {
|
|
50
|
-
const
|
|
45
|
+
const scopeKey = this.normalizeScopeKey(options?.scopeKey);
|
|
46
|
+
const scopedKey = this.scopedWidgetKey(scopeKey, key);
|
|
47
|
+
const existing = this.extensionWidgets.get(scopedKey);
|
|
51
48
|
if (existing)
|
|
52
49
|
this.invalidateWidget(existing);
|
|
53
50
|
if (content === undefined) {
|
|
54
|
-
this.extensionWidgets.delete(
|
|
51
|
+
this.extensionWidgets.delete(scopedKey);
|
|
55
52
|
if (this.host.isRunning())
|
|
56
53
|
this.host.render();
|
|
57
54
|
return;
|
|
58
55
|
}
|
|
59
56
|
if (!Array.isArray(content) && typeof content !== "function")
|
|
60
57
|
return;
|
|
61
|
-
this.extensionWidgets.set(
|
|
58
|
+
this.extensionWidgets.set(scopedKey, {
|
|
62
59
|
key,
|
|
60
|
+
scopeKey,
|
|
63
61
|
placement: options?.placement === "belowEditor" ? "belowEditor" : "aboveEditor",
|
|
64
62
|
content: content,
|
|
65
63
|
});
|
|
66
64
|
if (this.host.isRunning())
|
|
67
65
|
this.host.render();
|
|
68
66
|
}
|
|
69
|
-
clearWidgets() {
|
|
70
|
-
this.
|
|
71
|
-
|
|
67
|
+
clearWidgets(scopeKey, options = {}) {
|
|
68
|
+
const normalizedScopeKey = this.normalizeScopeKey(scopeKey);
|
|
69
|
+
if (options.cancelCustomUi !== false)
|
|
70
|
+
this.cancelActiveCustomUi(normalizedScopeKey);
|
|
71
|
+
for (const [key, widget] of this.extensionWidgets.entries()) {
|
|
72
|
+
if (widget.scopeKey !== normalizedScopeKey)
|
|
73
|
+
continue;
|
|
72
74
|
this.invalidateWidget(widget);
|
|
73
|
-
|
|
75
|
+
this.extensionWidgets.delete(key);
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
suppressWidget(key) {
|
|
76
|
-
const
|
|
79
|
+
const scopedKey = this.scopedWidgetKey(this.activeScopeKey(), key);
|
|
80
|
+
const widget = this.extensionWidgets.get(scopedKey);
|
|
77
81
|
if (!widget)
|
|
78
82
|
return;
|
|
79
83
|
this.invalidateWidget(widget);
|
|
80
|
-
this.extensionWidgets.delete(
|
|
84
|
+
this.extensionWidgets.delete(scopedKey);
|
|
81
85
|
}
|
|
82
86
|
handleTerminalInput(data) {
|
|
83
|
-
|
|
87
|
+
const active = this.activeCustomUiForActiveScope();
|
|
88
|
+
if (active) {
|
|
84
89
|
if (data === "\u0003")
|
|
85
90
|
return { consume: false };
|
|
86
91
|
try {
|
|
87
|
-
const result =
|
|
92
|
+
const result = active.component.handleInput?.(data);
|
|
88
93
|
if (result && typeof result === "object") {
|
|
89
94
|
return {
|
|
90
95
|
consume: result.consume !== false,
|
|
@@ -98,7 +103,10 @@ export class ExtensionUiController {
|
|
|
98
103
|
return { consume: true };
|
|
99
104
|
}
|
|
100
105
|
let current = data;
|
|
101
|
-
|
|
106
|
+
const activeScopeKey = this.activeScopeKey();
|
|
107
|
+
for (const { scopeKey, handler } of [...this.terminalInputHandlers]) {
|
|
108
|
+
if (scopeKey !== activeScopeKey)
|
|
109
|
+
continue;
|
|
102
110
|
const result = handler(current);
|
|
103
111
|
if (result?.data !== undefined)
|
|
104
112
|
current = result.data;
|
|
@@ -108,7 +116,7 @@ export class ExtensionUiController {
|
|
|
108
116
|
return current === data ? { consume: false } : { consume: false, data: current };
|
|
109
117
|
}
|
|
110
118
|
renderActiveCustomUi(width) {
|
|
111
|
-
const active = this.
|
|
119
|
+
const active = this.activeCustomUiForActiveScope();
|
|
112
120
|
if (!active)
|
|
113
121
|
return undefined;
|
|
114
122
|
try {
|
|
@@ -119,7 +127,7 @@ export class ExtensionUiController {
|
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
activeCustomUiUsesEditor() {
|
|
122
|
-
const active = this.
|
|
130
|
+
const active = this.activeCustomUiForActiveScope();
|
|
123
131
|
if (!active)
|
|
124
132
|
return false;
|
|
125
133
|
try {
|
|
@@ -130,7 +138,7 @@ export class ExtensionUiController {
|
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
handleCustomUiMouse(event) {
|
|
133
|
-
const active = this.
|
|
141
|
+
const active = this.activeCustomUiForActiveScope();
|
|
134
142
|
if (!active)
|
|
135
143
|
return false;
|
|
136
144
|
try {
|
|
@@ -142,13 +150,14 @@ export class ExtensionUiController {
|
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
widgetTuiHandle() {
|
|
153
|
+
const activeScopeToastNotifier = this.host.toastNotifierForScope?.(this.activeScopeKey()) ?? this.host.toastNotifier;
|
|
145
154
|
return {
|
|
146
155
|
requestRender: () => {
|
|
147
156
|
if (this.host.isRunning())
|
|
148
157
|
this.host.render();
|
|
149
158
|
},
|
|
150
|
-
showToast:
|
|
151
|
-
toast:
|
|
159
|
+
showToast: activeScopeToastNotifier.show,
|
|
160
|
+
toast: activeScopeToastNotifier,
|
|
152
161
|
showMenu: this.host.menuController.show,
|
|
153
162
|
menu: this.host.menuController,
|
|
154
163
|
pix: {
|
|
@@ -157,9 +166,11 @@ export class ExtensionUiController {
|
|
|
157
166
|
},
|
|
158
167
|
};
|
|
159
168
|
}
|
|
160
|
-
createExtensionUIContext() {
|
|
169
|
+
createExtensionUIContext(scopeKey) {
|
|
170
|
+
const contextScopeKey = this.normalizeScopeKey(scopeKey);
|
|
171
|
+
const scopedToastNotifier = this.host.toastNotifierForScope?.(contextScopeKey) ?? this.host.toastNotifier;
|
|
161
172
|
const notify = (message, type) => {
|
|
162
|
-
this.host.showToast(message, isToastKind(type) ? type : "info");
|
|
173
|
+
this.host.showToast(message, isToastKind(type) ? type : "info", { scopeKey: contextScopeKey });
|
|
163
174
|
};
|
|
164
175
|
const extensionTheme = this.createExtensionTheme();
|
|
165
176
|
const renderIfRunning = () => {
|
|
@@ -169,17 +180,24 @@ export class ExtensionUiController {
|
|
|
169
180
|
return {
|
|
170
181
|
select: async (title, options, opts) => await this.selectDialog(title, options, opts),
|
|
171
182
|
confirm: async (title, message, opts) => await this.confirmDialog(title, message, opts),
|
|
172
|
-
input: async (title, placeholder, opts) => await this.inputDialog(title, placeholder, opts),
|
|
183
|
+
input: async (title, placeholder, opts) => await this.inputDialog(title, placeholder, opts, contextScopeKey),
|
|
173
184
|
notify,
|
|
174
|
-
toast:
|
|
175
|
-
aboveInput:
|
|
185
|
+
toast: scopedToastNotifier,
|
|
186
|
+
aboveInput: {
|
|
187
|
+
set: (key, content) => {
|
|
188
|
+
this.setAboveInputWidget(key, content, contextScopeKey);
|
|
189
|
+
},
|
|
190
|
+
clear: (key) => {
|
|
191
|
+
this.clearAboveInputWidget(key, contextScopeKey);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
176
194
|
renderAboveInput: (key, content) => {
|
|
177
|
-
this.setAboveInputWidget(key, content);
|
|
195
|
+
this.setAboveInputWidget(key, content, contextScopeKey);
|
|
178
196
|
},
|
|
179
197
|
showMenu: this.host.menuController.show,
|
|
180
198
|
menu: this.host.menuController,
|
|
181
199
|
onTerminalInput: (handler) => {
|
|
182
|
-
const terminalInputHandler = handler;
|
|
200
|
+
const terminalInputHandler = { scopeKey: contextScopeKey, handler: handler };
|
|
183
201
|
this.terminalInputHandlers.add(terminalInputHandler);
|
|
184
202
|
return () => {
|
|
185
203
|
this.terminalInputHandlers.delete(terminalInputHandler);
|
|
@@ -187,13 +205,13 @@ export class ExtensionUiController {
|
|
|
187
205
|
},
|
|
188
206
|
setStatus: (_key, text) => {
|
|
189
207
|
if (text)
|
|
190
|
-
this.host.showToast(text, "info");
|
|
208
|
+
this.host.showToast(text, "info", { scopeKey: contextScopeKey });
|
|
191
209
|
this.host.restoreSessionStatus();
|
|
192
210
|
renderIfRunning();
|
|
193
211
|
},
|
|
194
212
|
setWorkingMessage: (message) => {
|
|
195
213
|
if (message)
|
|
196
|
-
this.host.showToast(message, "info");
|
|
214
|
+
this.host.showToast(message, "info", { scopeKey: contextScopeKey });
|
|
197
215
|
this.host.restoreSessionStatus();
|
|
198
216
|
renderIfRunning();
|
|
199
217
|
},
|
|
@@ -201,7 +219,7 @@ export class ExtensionUiController {
|
|
|
201
219
|
setWorkingIndicator: () => undefined,
|
|
202
220
|
setHiddenThinkingLabel: () => undefined,
|
|
203
221
|
setWidget: ((key, content, options) => {
|
|
204
|
-
this.setWidget(key, content, options);
|
|
222
|
+
this.setWidget(key, content, { ...options, scopeKey: contextScopeKey });
|
|
205
223
|
}),
|
|
206
224
|
setFooter: () => undefined,
|
|
207
225
|
setHeader: () => undefined,
|
|
@@ -209,7 +227,7 @@ export class ExtensionUiController {
|
|
|
209
227
|
process.title = title;
|
|
210
228
|
renderIfRunning();
|
|
211
229
|
},
|
|
212
|
-
custom: (async (factory) => await this.showCustomUi(factory)),
|
|
230
|
+
custom: (async (factory) => await this.showCustomUi(factory, { scopeKey: contextScopeKey })),
|
|
213
231
|
pasteToEditor: (text) => {
|
|
214
232
|
this.host.setInput(text);
|
|
215
233
|
renderIfRunning();
|
|
@@ -219,7 +237,7 @@ export class ExtensionUiController {
|
|
|
219
237
|
renderIfRunning();
|
|
220
238
|
},
|
|
221
239
|
getEditorText: () => this.host.getInput(),
|
|
222
|
-
editor: async (title, prefill) => await this.editorDialog(title, prefill),
|
|
240
|
+
editor: async (title, prefill) => await this.editorDialog(title, prefill, contextScopeKey),
|
|
223
241
|
addAutocompleteProvider: () => undefined,
|
|
224
242
|
setEditorComponent: () => undefined,
|
|
225
243
|
getEditorComponent: () => undefined,
|
|
@@ -241,11 +259,11 @@ export class ExtensionUiController {
|
|
|
241
259
|
},
|
|
242
260
|
};
|
|
243
261
|
}
|
|
244
|
-
setAboveInputWidget(key, content) {
|
|
245
|
-
this.setWidget(key, content, { placement: "aboveEditor" });
|
|
262
|
+
setAboveInputWidget(key, content, scopeKey = this.activeScopeKey()) {
|
|
263
|
+
this.setWidget(key, content, { placement: "aboveEditor", scopeKey });
|
|
246
264
|
}
|
|
247
|
-
clearAboveInputWidget(key) {
|
|
248
|
-
this.setWidget(key, undefined, { placement: "aboveEditor" });
|
|
265
|
+
clearAboveInputWidget(key, scopeKey = this.activeScopeKey()) {
|
|
266
|
+
this.setWidget(key, undefined, { placement: "aboveEditor", scopeKey });
|
|
249
267
|
}
|
|
250
268
|
async selectDialog(title, options, opts) {
|
|
251
269
|
if (opts?.signal?.aborted)
|
|
@@ -265,7 +283,7 @@ export class ExtensionUiController {
|
|
|
265
283
|
});
|
|
266
284
|
return selected === true;
|
|
267
285
|
}
|
|
268
|
-
async inputDialog(title, placeholder, opts) {
|
|
286
|
+
async inputDialog(title, placeholder, opts, scopeKey = this.activeScopeKey()) {
|
|
269
287
|
if (opts?.signal?.aborted)
|
|
270
288
|
return undefined;
|
|
271
289
|
return await this.editorBackedDialog({
|
|
@@ -274,21 +292,21 @@ export class ExtensionUiController {
|
|
|
274
292
|
mode: "input",
|
|
275
293
|
...(placeholder === undefined ? {} : { placeholder }),
|
|
276
294
|
...(opts === undefined ? {} : { opts }),
|
|
277
|
-
});
|
|
295
|
+
}, scopeKey);
|
|
278
296
|
}
|
|
279
|
-
async editorDialog(title, prefill = "") {
|
|
297
|
+
async editorDialog(title, prefill = "", scopeKey = this.activeScopeKey()) {
|
|
280
298
|
return await this.editorBackedDialog({
|
|
281
299
|
title,
|
|
282
300
|
initialValue: prefill,
|
|
283
301
|
mode: "editor",
|
|
284
|
-
});
|
|
302
|
+
}, scopeKey);
|
|
285
303
|
}
|
|
286
|
-
async editorBackedDialog(options) {
|
|
304
|
+
async editorBackedDialog(options, scopeKey = this.activeScopeKey()) {
|
|
287
305
|
if (options.opts?.signal?.aborted)
|
|
288
306
|
return undefined;
|
|
289
307
|
if (!this.host.isRunning())
|
|
290
308
|
return undefined;
|
|
291
|
-
if (this.
|
|
309
|
+
if (this.activeCustomUis.has(scopeKey))
|
|
292
310
|
throw new Error("Another extension custom UI is already active.");
|
|
293
311
|
const savedInput = this.host.getInput();
|
|
294
312
|
this.host.setInput(options.initialValue);
|
|
@@ -315,9 +333,9 @@ export class ExtensionUiController {
|
|
|
315
333
|
return { consume: false, data };
|
|
316
334
|
},
|
|
317
335
|
};
|
|
318
|
-
}, { savedInput });
|
|
336
|
+
}, { savedInput, scopeKey });
|
|
319
337
|
return await this.withDialogAutoDismiss(promise, options.opts, () => {
|
|
320
|
-
this.cancelActiveCustomUi();
|
|
338
|
+
this.cancelActiveCustomUi(scopeKey);
|
|
321
339
|
});
|
|
322
340
|
}
|
|
323
341
|
renderEditorBackedDialog(options, width) {
|
|
@@ -356,7 +374,8 @@ export class ExtensionUiController {
|
|
|
356
374
|
async showCustomUi(factory, options = {}) {
|
|
357
375
|
if (!this.host.isRunning())
|
|
358
376
|
return undefined;
|
|
359
|
-
|
|
377
|
+
const scopeKey = this.normalizeScopeKey(options.scopeKey);
|
|
378
|
+
if (this.activeCustomUis.has(scopeKey))
|
|
360
379
|
throw new Error("Another extension custom UI is already active.");
|
|
361
380
|
const savedInput = options.savedInput ?? this.host.getInput();
|
|
362
381
|
return await new Promise((resolve, reject) => {
|
|
@@ -365,7 +384,7 @@ export class ExtensionUiController {
|
|
|
365
384
|
if (settled)
|
|
366
385
|
return;
|
|
367
386
|
settled = true;
|
|
368
|
-
this.finishActiveCustomUi(value, { resolve: true });
|
|
387
|
+
this.finishActiveCustomUi(scopeKey, value, { resolve: true });
|
|
369
388
|
resolve(value);
|
|
370
389
|
};
|
|
371
390
|
void (async () => {
|
|
@@ -375,8 +394,9 @@ export class ExtensionUiController {
|
|
|
375
394
|
component.dispose?.();
|
|
376
395
|
return;
|
|
377
396
|
}
|
|
378
|
-
this.
|
|
397
|
+
this.activeCustomUis.set(scopeKey, {
|
|
379
398
|
key: CUSTOM_UI_WIDGET_KEY,
|
|
399
|
+
scopeKey,
|
|
380
400
|
component,
|
|
381
401
|
savedInput,
|
|
382
402
|
resolve: (value) => {
|
|
@@ -391,7 +411,7 @@ export class ExtensionUiController {
|
|
|
391
411
|
settled = true;
|
|
392
412
|
reject(error);
|
|
393
413
|
},
|
|
394
|
-
};
|
|
414
|
+
});
|
|
395
415
|
if (this.host.isRunning())
|
|
396
416
|
this.host.render();
|
|
397
417
|
}
|
|
@@ -404,17 +424,20 @@ export class ExtensionUiController {
|
|
|
404
424
|
})();
|
|
405
425
|
});
|
|
406
426
|
}
|
|
407
|
-
cancelActiveCustomUi() {
|
|
408
|
-
this.finishActiveCustomUi(undefined, { resolve: true });
|
|
427
|
+
cancelActiveCustomUi(scopeKey = this.activeScopeKey()) {
|
|
428
|
+
this.finishActiveCustomUi(scopeKey, undefined, { resolve: true });
|
|
409
429
|
}
|
|
410
430
|
rejectActiveCustomUi(error) {
|
|
411
|
-
this.
|
|
431
|
+
const active = this.activeCustomUiForActiveScope();
|
|
432
|
+
if (!active)
|
|
433
|
+
return;
|
|
434
|
+
this.finishActiveCustomUi(active.scopeKey, error, { resolve: false });
|
|
412
435
|
}
|
|
413
|
-
finishActiveCustomUi(value, options) {
|
|
414
|
-
const active = this.
|
|
436
|
+
finishActiveCustomUi(scopeKey, value, options) {
|
|
437
|
+
const active = this.activeCustomUis.get(scopeKey);
|
|
415
438
|
if (!active)
|
|
416
439
|
return;
|
|
417
|
-
this.
|
|
440
|
+
this.activeCustomUis.delete(scopeKey);
|
|
418
441
|
if (this.host.getInput() !== active.savedInput)
|
|
419
442
|
this.host.setInput(active.savedInput);
|
|
420
443
|
try {
|
|
@@ -436,6 +459,21 @@ export class ExtensionUiController {
|
|
|
436
459
|
if (this.host.isRunning())
|
|
437
460
|
this.host.render();
|
|
438
461
|
}
|
|
462
|
+
activeCustomUiForActiveScope() {
|
|
463
|
+
return this.activeCustomUis.get(this.activeScopeKey());
|
|
464
|
+
}
|
|
465
|
+
activeScopeKey() {
|
|
466
|
+
return this.normalizeScopeKey(this.host.activeExtensionUiScope?.());
|
|
467
|
+
}
|
|
468
|
+
normalizeScopeKey(scopeKey) {
|
|
469
|
+
return scopeKey ?? "";
|
|
470
|
+
}
|
|
471
|
+
scopedWidgetKey(scopeKey, key) {
|
|
472
|
+
return `${scopeKey.length}:${scopeKey}:${key}`;
|
|
473
|
+
}
|
|
474
|
+
unscopedWidgetKey(scopedKey, scopeKey) {
|
|
475
|
+
return scopedKey.slice(`${scopeKey.length}:${scopeKey}:`.length);
|
|
476
|
+
}
|
|
439
477
|
invalidateWidget(widget) {
|
|
440
478
|
try {
|
|
441
479
|
widget.component?.dispose?.();
|
|
@@ -39,6 +39,7 @@ export declare class AppInputActionController {
|
|
|
39
39
|
queueInputFromEditor(): Promise<void>;
|
|
40
40
|
handleInterrupt(): Promise<void>;
|
|
41
41
|
handleEscape(): Promise<void>;
|
|
42
|
+
private closeActiveGlobalUi;
|
|
42
43
|
private abortStreamingSession;
|
|
43
44
|
private restoreSessionState;
|
|
44
45
|
private sessionActivity;
|
|
@@ -63,6 +63,8 @@ export class AppInputActionController {
|
|
|
63
63
|
await this.host.stop();
|
|
64
64
|
}
|
|
65
65
|
async handleEscape() {
|
|
66
|
+
if (this.closeActiveGlobalUi())
|
|
67
|
+
return;
|
|
66
68
|
const session = this.host.runtime()?.session;
|
|
67
69
|
if (session?.isCompacting) {
|
|
68
70
|
this.host.setStatus("aborting compaction");
|
|
@@ -76,8 +78,12 @@ export class AppInputActionController {
|
|
|
76
78
|
await this.abortStreamingSession(runtime, { stopIfAlreadyAborting: false });
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
}
|
|
82
|
+
closeActiveGlobalUi() {
|
|
83
|
+
if (!this.popupMenus.syncActivePopupMenu())
|
|
84
|
+
return false;
|
|
85
|
+
this.popupMenus.cancelActivePopupMenu();
|
|
86
|
+
return true;
|
|
81
87
|
}
|
|
82
88
|
async abortStreamingSession(runtime, options) {
|
|
83
89
|
const session = runtime.session;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const PIX_LOG_MAX_LINES = 1000;
|
|
2
|
+
export type PixLogLevel = "debug" | "info" | "warn" | "error";
|
|
3
|
+
export type PixLogDetails = Record<string, unknown>;
|
|
4
|
+
export type PixFileLoggerOptions = {
|
|
5
|
+
logPath?: string;
|
|
6
|
+
maxLines?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function getPixLogPath(homeDir?: string): string;
|
|
9
|
+
export declare class PixFileLogger {
|
|
10
|
+
readonly logPath: string;
|
|
11
|
+
private readonly maxLines;
|
|
12
|
+
private pending;
|
|
13
|
+
constructor(options?: PixFileLoggerOptions);
|
|
14
|
+
log(level: PixLogLevel, event: string, details?: PixLogDetails): Promise<void>;
|
|
15
|
+
debug(event: string, details?: PixLogDetails): Promise<void>;
|
|
16
|
+
info(event: string, details?: PixLogDetails): Promise<void>;
|
|
17
|
+
warn(event: string, details?: PixLogDetails): Promise<void>;
|
|
18
|
+
error(event: string, details?: PixLogDetails): Promise<void>;
|
|
19
|
+
flush(): Promise<void>;
|
|
20
|
+
private formatLine;
|
|
21
|
+
private writeLine;
|
|
22
|
+
}
|
|
23
|
+
export declare const pixLogger: PixFileLogger;
|
|
24
|
+
export declare function logPixEvent(level: PixLogLevel, event: string, details?: PixLogDetails): void;
|
|
25
|
+
export declare function trimLogFile(logPath: string, maxLines?: number): Promise<void>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export const PIX_LOG_MAX_LINES = 1000;
|
|
5
|
+
export function getPixLogPath(homeDir = homedir()) {
|
|
6
|
+
return join(homeDir, ".config", "pi", "pix.log");
|
|
7
|
+
}
|
|
8
|
+
export class PixFileLogger {
|
|
9
|
+
logPath;
|
|
10
|
+
maxLines;
|
|
11
|
+
pending = Promise.resolve();
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.logPath = options.logPath ?? getPixLogPath();
|
|
14
|
+
this.maxLines = Math.max(1, Math.floor(options.maxLines ?? PIX_LOG_MAX_LINES));
|
|
15
|
+
}
|
|
16
|
+
log(level, event, details = {}) {
|
|
17
|
+
const line = this.formatLine(level, event, details);
|
|
18
|
+
this.pending = this.pending
|
|
19
|
+
.then(() => this.writeLine(line))
|
|
20
|
+
.catch(() => undefined);
|
|
21
|
+
return this.pending;
|
|
22
|
+
}
|
|
23
|
+
debug(event, details) {
|
|
24
|
+
return this.log("debug", event, details);
|
|
25
|
+
}
|
|
26
|
+
info(event, details) {
|
|
27
|
+
return this.log("info", event, details);
|
|
28
|
+
}
|
|
29
|
+
warn(event, details) {
|
|
30
|
+
return this.log("warn", event, details);
|
|
31
|
+
}
|
|
32
|
+
error(event, details) {
|
|
33
|
+
return this.log("error", event, details);
|
|
34
|
+
}
|
|
35
|
+
async flush() {
|
|
36
|
+
await this.pending;
|
|
37
|
+
}
|
|
38
|
+
formatLine(level, event, details) {
|
|
39
|
+
return `${new Date().toISOString()} ${level.toUpperCase()} ${sanitizeToken(event)} ${safeJson(details)}\n`;
|
|
40
|
+
}
|
|
41
|
+
async writeLine(line) {
|
|
42
|
+
await mkdir(dirname(this.logPath), { recursive: true });
|
|
43
|
+
await appendFile(this.logPath, line, "utf8");
|
|
44
|
+
await trimLogFile(this.logPath, this.maxLines);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const pixLogger = new PixFileLogger();
|
|
48
|
+
export function logPixEvent(level, event, details) {
|
|
49
|
+
void pixLogger.log(level, event, details);
|
|
50
|
+
}
|
|
51
|
+
export async function trimLogFile(logPath, maxLines = PIX_LOG_MAX_LINES) {
|
|
52
|
+
const text = await readFile(logPath, "utf8").catch((error) => {
|
|
53
|
+
if (isNodeError(error) && error.code === "ENOENT")
|
|
54
|
+
return undefined;
|
|
55
|
+
throw error;
|
|
56
|
+
});
|
|
57
|
+
if (text === undefined)
|
|
58
|
+
return;
|
|
59
|
+
const lines = text.split(/\r?\n/u);
|
|
60
|
+
if (lines.at(-1) === "")
|
|
61
|
+
lines.pop();
|
|
62
|
+
if (lines.length <= maxLines)
|
|
63
|
+
return;
|
|
64
|
+
await writeFile(logPath, `${lines.slice(-maxLines).join("\n")}\n`, "utf8");
|
|
65
|
+
}
|
|
66
|
+
function sanitizeToken(value) {
|
|
67
|
+
return value
|
|
68
|
+
.replace(/[\t\r\n]+/gu, " ")
|
|
69
|
+
.replace(/\s+/gu, "_")
|
|
70
|
+
.replace(/[^\w:.-]+/gu, "_")
|
|
71
|
+
.replace(/^_+|_+$/gu, "")
|
|
72
|
+
.slice(0, 120) || "event";
|
|
73
|
+
}
|
|
74
|
+
function safeJson(value) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.stringify(value, (_key, item) => {
|
|
77
|
+
if (item instanceof Error)
|
|
78
|
+
return { name: item.name, message: item.message, stack: item.stack };
|
|
79
|
+
if (typeof item === "bigint")
|
|
80
|
+
return item.toString();
|
|
81
|
+
return item;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return JSON.stringify({ logSerializationError: error instanceof Error ? error.message : String(error) });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isNodeError(error) {
|
|
89
|
+
return error instanceof Error && "code" in error;
|
|
90
|
+
}
|
|
@@ -3,7 +3,6 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
6
|
import { formatCompactProgressBar } from "../../context-progress-bar.js";
|
|
8
7
|
const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
9
8
|
const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
@@ -12,7 +11,13 @@ const GOOGLE_QUOTA_API_URL = "https://cloudcode-pa.googleapis.com/v1internal:fet
|
|
|
12
11
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
13
12
|
const DAY_SECONDS = 86_400;
|
|
14
13
|
const HOUR_SECONDS = 3_600;
|
|
14
|
+
const PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
|
15
15
|
const DEFAULT_ANTIGRAVITY_PROJECT_ID = "rising-fact-p41fc";
|
|
16
|
+
function getPiAuthPath() {
|
|
17
|
+
return process.env.NODE_ENV === "test" && process.env.PI_TOOLS_SUITE_TEST_AUTH_PATH
|
|
18
|
+
? process.env.PI_TOOLS_SUITE_TEST_AUTH_PATH
|
|
19
|
+
: PI_AUTH_PATH;
|
|
20
|
+
}
|
|
16
21
|
const OPENAI_QUOTA_PROVIDERS = new Set(["openai", "openai-codex"]);
|
|
17
22
|
const ZHIPU_QUOTA_PROVIDERS = new Set(["zai", "zhipuai-coding-plan"]);
|
|
18
23
|
const ANTIGRAVITY_QUOTA_PROVIDERS = new Set(["antigravity", "google-antigravity"]);
|
|
@@ -245,7 +250,7 @@ async function readOpenCodeAuth() {
|
|
|
245
250
|
}
|
|
246
251
|
async function readPiAuth() {
|
|
247
252
|
try {
|
|
248
|
-
const content = await readFile(
|
|
253
|
+
const content = await readFile(getPiAuthPath(), "utf8");
|
|
249
254
|
return JSON.parse(content);
|
|
250
255
|
}
|
|
251
256
|
catch {
|
|
@@ -472,7 +477,7 @@ function readAllAntigravityQuotaAccounts() {
|
|
|
472
477
|
}
|
|
473
478
|
function readPiAuthSync() {
|
|
474
479
|
try {
|
|
475
|
-
return JSON.parse(readFileSync(
|
|
480
|
+
return JSON.parse(readFileSync(getPiAuthPath(), "utf8"));
|
|
476
481
|
}
|
|
477
482
|
catch {
|
|
478
483
|
return {};
|
|
@@ -821,19 +826,29 @@ function maskCredential(value) {
|
|
|
821
826
|
return visible ? "****" : "unknown";
|
|
822
827
|
return `${visible.slice(0, 4)}****${visible.slice(-4)}`;
|
|
823
828
|
}
|
|
824
|
-
function formatUsageWindow(
|
|
825
|
-
|
|
829
|
+
function formatUsageWindow(prefix, window, now) {
|
|
830
|
+
const resetLabel = prefix === "H" ? formatResetTime(window.resetAt, now) : formatGlobalResetLabel(window.resetAt, now);
|
|
831
|
+
return `${window.remainingPercent}% ${formatCompactProgressBar(window.remainingPercent)} ${resetLabel}`;
|
|
826
832
|
}
|
|
827
|
-
function
|
|
833
|
+
function formatGlobalResetLabel(resetAt, now) {
|
|
828
834
|
if (resetAt <= now)
|
|
829
835
|
return "reset";
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
836
|
+
return resetAt - now <= DAY_SECONDS * 1000 ? formatResetTime(resetAt, now) : formatResetDate(resetAt, now);
|
|
837
|
+
}
|
|
838
|
+
function formatResetTime(resetAt, now) {
|
|
839
|
+
if (resetAt <= now)
|
|
840
|
+
return "reset";
|
|
841
|
+
return new Date(resetAt).toLocaleTimeString("ru-RU", {
|
|
842
|
+
hour: "2-digit",
|
|
843
|
+
minute: "2-digit",
|
|
844
|
+
hourCycle: "h23",
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
function formatResetDate(resetAt, now) {
|
|
848
|
+
if (resetAt <= now)
|
|
849
|
+
return "reset";
|
|
850
|
+
return new Date(resetAt).toLocaleDateString("ru-RU", {
|
|
851
|
+
day: "2-digit",
|
|
852
|
+
month: "2-digit",
|
|
853
|
+
});
|
|
839
854
|
}
|
|
@@ -10,6 +10,7 @@ export type AppMenuItemsControllerHost = {
|
|
|
10
10
|
export declare class AppMenuItemsController {
|
|
11
11
|
private readonly host;
|
|
12
12
|
private resumeMenuLoaderCache;
|
|
13
|
+
private userMessageJumpItems;
|
|
13
14
|
constructor(host: AppMenuItemsControllerHost);
|
|
14
15
|
parseSlashInput(text: string): import("../types.js").ParsedSlashInput | undefined;
|
|
15
16
|
getResourceSlashCommands(): SlashCommand[];
|
|
@@ -21,6 +22,7 @@ export declare class AppMenuItemsController {
|
|
|
21
22
|
getThinkingMenuItems(query: string): PopupMenuItem<ThinkingMenuValue>[];
|
|
22
23
|
getUserMessageMenuItems(): PopupMenuItem<UserMessageMenuValue>[];
|
|
23
24
|
getUserMessageJumpMenuItems(query: string): PopupMenuItem<UserMessageJumpMenuValue>[];
|
|
25
|
+
refreshUserMessageJumpMenuItems(): Promise<void>;
|
|
24
26
|
getQueueMessageMenuItems(): PopupMenuItem<QueueMessageMenuValue>[];
|
|
25
27
|
getResumeMenuItems(query: string, limit?: number): PopupMenuItem<ResumeMenuValue>[];
|
|
26
28
|
private getResumeMenuItemsLoader;
|