pi-ui-extend 0.1.13 → 0.1.17
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 +7 -0
- package/dist/app/app.js +102 -17
- package/dist/app/commands/command-controller.js +2 -0
- package/dist/app/commands/command-host.d.ts +5 -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.d.ts +9 -0
- package/dist/app/commands/command-navigation-actions.js +62 -0
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +16 -0
- package/dist/app/constants.d.ts +0 -1
- package/dist/app/constants.js +0 -1
- package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
- package/dist/app/extensions/extension-ui-controller.js +99 -61
- package/dist/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- 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 +4 -0
- package/dist/app/popup/menu-items-controller.js +68 -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 +97 -326
- 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 +22 -9
- package/dist/app/rendering/popup-menu-renderer.d.ts +62 -0
- package/dist/app/rendering/popup-menu-renderer.js +405 -0
- package/dist/app/rendering/render-controller.js +30 -28
- package/dist/app/rendering/render-text.js +5 -2
- package/dist/app/rendering/status-line-renderer.d.ts +8 -1
- package/dist/app/rendering/status-line-renderer.js +217 -117
- package/dist/app/rendering/toast-controller.d.ts +12 -3
- package/dist/app/rendering/toast-controller.js +70 -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 +12 -9
- package/dist/app/screen/scroll-controller.js +56 -45
- package/dist/app/screen/status-controller.js +2 -1
- 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/request-history.d.ts +4 -0
- package/dist/app/session/request-history.js +11 -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/session-search.js +10 -0
- package/dist/app/session/tabs-controller.d.ts +17 -5
- package/dist/app/session/tabs-controller.js +308 -29
- package/dist/app/todo/todo-model.d.ts +4 -2
- package/dist/app/todo/todo-model.js +23 -13
- package/dist/app/types.d.ts +17 -6
- 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 +6 -1
- package/dist/config.js +82 -25
- package/dist/default-pix-config.js +4 -0
- package/dist/fuzzy.d.ts +2 -0
- package/dist/fuzzy.js +27 -7
- package/dist/input-editor.d.ts +9 -0
- package/dist/input-editor.js +52 -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 +3 -1
- package/dist/schemas/pix-schema.js +6 -4
- package/dist/terminal-width.d.ts +2 -0
- package/dist/terminal-width.js +64 -3
- package/dist/theme.js +6 -6
- package/dist/ui.d.ts +8 -0
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +52 -8
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +3 -41
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +11 -18
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +129 -61
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +82 -3
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +20 -7
- package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
- 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 +123 -14
- package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +26 -43
- package/external/pi-tools-suite/src/todo/todo.ts +12 -23
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +34 -16
- package/external/pi-tools-suite/src/todo/tool/types.ts +7 -28
- package/external/pi-tools-suite/src/todo/view/format.ts +2 -3
- package/external/pi-tools-suite/src/tool-descriptions.ts +6 -4
- package/external/pi-tools-suite/src/usage/index.ts +5 -2
- package/external/pi-tools-suite/src/usage/lib/google.ts +53 -40
- package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +4 -0
- package/schemas/pix.json +11 -2
|
@@ -32,6 +32,14 @@ export function createSlashCommands(actions, host) {
|
|
|
32
32
|
allowArguments: true,
|
|
33
33
|
run: (argumentsText) => actions.runAutocompleteSlashCommand(argumentsText),
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: "no-context-files",
|
|
37
|
+
description: "Set project AGENTS.md/CLAUDE.md loading off or on",
|
|
38
|
+
kind: "builtin",
|
|
39
|
+
keywords: ["context", "agents", "claude", "project", "config"],
|
|
40
|
+
allowArguments: true,
|
|
41
|
+
run: (argumentsText) => actions.runNoContextFilesSlashCommand(argumentsText),
|
|
42
|
+
},
|
|
35
43
|
{
|
|
36
44
|
name: "scoped-models",
|
|
37
45
|
description: "Show or set models used by the model selector/cycling",
|
|
@@ -177,6 +185,14 @@ export function createSlashCommands(actions, host) {
|
|
|
177
185
|
allowArguments: true,
|
|
178
186
|
run: (argumentsText) => actions.runJumpCommand(argumentsText),
|
|
179
187
|
},
|
|
188
|
+
{
|
|
189
|
+
name: "history",
|
|
190
|
+
description: "Search command history and restore a match",
|
|
191
|
+
kind: "builtin",
|
|
192
|
+
keywords: ["command", "request", "prompt", "find", "recent"],
|
|
193
|
+
allowArguments: true,
|
|
194
|
+
run: (argumentsText) => actions.runHistoryCommand(argumentsText),
|
|
195
|
+
},
|
|
180
196
|
{
|
|
181
197
|
name: "search",
|
|
182
198
|
description: "Search sessions and open a match in a new tab",
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -42,7 +42,6 @@ export declare const GIT_BRANCH_CACHE_MS = 30000;
|
|
|
42
42
|
export declare const TODO_TOOL_NAME = "todo";
|
|
43
43
|
export declare const TODO_ACTIONS: readonly ["create", "update", "batch_create", "batch_update", "list", "get", "delete", "clear", "export", "import"];
|
|
44
44
|
export declare const TODO_STATUSES: readonly ["pending", "in_progress", "deferred", "completed", "deleted"];
|
|
45
|
-
export declare const TODO_PRIORITIES: readonly ["low", "medium", "high", "urgent"];
|
|
46
45
|
export declare const SUBAGENT_STATUSES: readonly ["planned", "running", "retrying", "done", "failed", "stopped"];
|
|
47
46
|
export declare const SUBAGENT_ACTIVE_STATUSES: readonly ["planned", "running", "retrying"];
|
|
48
47
|
export declare const SUBAGENT_TERMINAL_STATUSES: readonly ["done", "failed", "stopped"];
|
package/dist/app/constants.js
CHANGED
|
@@ -90,7 +90,6 @@ export const TODO_ACTIONS = [
|
|
|
90
90
|
"import",
|
|
91
91
|
];
|
|
92
92
|
export const TODO_STATUSES = ["pending", "in_progress", "deferred", "completed", "deleted"];
|
|
93
|
-
export const TODO_PRIORITIES = ["low", "medium", "high", "urgent"];
|
|
94
93
|
export const SUBAGENT_STATUSES = ["planned", "running", "retrying", "done", "failed", "stopped"];
|
|
95
94
|
export const SUBAGENT_ACTIVE_STATUSES = ["planned", "running", "retrying"];
|
|
96
95
|
export const SUBAGENT_TERMINAL_STATUSES = ["done", "failed", "stopped"];
|
|
@@ -7,10 +7,14 @@ export type ExtensionTerminalInputResult = {
|
|
|
7
7
|
};
|
|
8
8
|
export type ExtensionUiControllerHost = {
|
|
9
9
|
readonly theme: Theme;
|
|
10
|
+
activeExtensionUiScope?(): string | undefined;
|
|
10
11
|
isRunning(): boolean;
|
|
11
12
|
render(): void;
|
|
12
|
-
showToast(message: string, kind?: ToastKind
|
|
13
|
+
showToast(message: string, kind?: ToastKind, options?: {
|
|
14
|
+
scopeKey?: string;
|
|
15
|
+
}): void;
|
|
13
16
|
readonly toastNotifier: ToastNotifier;
|
|
17
|
+
toastNotifierForScope?(scopeKey: string | undefined): ToastNotifier;
|
|
14
18
|
readonly menuController: PixMenuController;
|
|
15
19
|
setStatus(status: string): void;
|
|
16
20
|
restoreSessionStatus(): void;
|
|
@@ -23,22 +27,24 @@ export declare class ExtensionUiController {
|
|
|
23
27
|
private readonly host;
|
|
24
28
|
private readonly extensionWidgets;
|
|
25
29
|
private readonly terminalInputHandlers;
|
|
26
|
-
private
|
|
27
|
-
private readonly aboveInputRenderer;
|
|
30
|
+
private readonly activeCustomUis;
|
|
28
31
|
constructor(host: ExtensionUiControllerHost);
|
|
29
32
|
get widgets(): ReadonlyMap<string, ExtensionWidgetRegistration>;
|
|
30
33
|
createExtensionTheme(): ExtensionWidgetTheme;
|
|
31
34
|
setWidget(key: string, content: unknown, options?: {
|
|
32
35
|
placement?: WidgetPlacement;
|
|
36
|
+
scopeKey?: string;
|
|
37
|
+
}): void;
|
|
38
|
+
clearWidgets(scopeKey?: string, options?: {
|
|
39
|
+
cancelCustomUi?: boolean;
|
|
33
40
|
}): void;
|
|
34
|
-
clearWidgets(): void;
|
|
35
41
|
suppressWidget(key: string): void;
|
|
36
42
|
handleTerminalInput(data: string): ExtensionTerminalInputResult;
|
|
37
43
|
renderActiveCustomUi(width: number): string[] | undefined;
|
|
38
44
|
activeCustomUiUsesEditor(): boolean;
|
|
39
45
|
handleCustomUiMouse(event: ExtensionInputMouseEvent): boolean;
|
|
40
46
|
widgetTuiHandle(): WidgetTuiHandle;
|
|
41
|
-
createExtensionUIContext(): PixExtensionUIContext;
|
|
47
|
+
createExtensionUIContext(scopeKey?: string): PixExtensionUIContext;
|
|
42
48
|
private setAboveInputWidget;
|
|
43
49
|
private clearAboveInputWidget;
|
|
44
50
|
private selectDialog;
|
|
@@ -53,5 +59,10 @@ export declare class ExtensionUiController {
|
|
|
53
59
|
private cancelActiveCustomUi;
|
|
54
60
|
private rejectActiveCustomUi;
|
|
55
61
|
private finishActiveCustomUi;
|
|
62
|
+
private activeCustomUiForActiveScope;
|
|
63
|
+
private activeScopeKey;
|
|
64
|
+
private normalizeScopeKey;
|
|
65
|
+
private scopedWidgetKey;
|
|
66
|
+
private unscopedWidgetKey;
|
|
56
67
|
private invalidateWidget;
|
|
57
68
|
}
|
|
@@ -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?.();
|
package/dist/app/icons.d.ts
CHANGED
package/dist/app/icons.js
CHANGED
|
@@ -18,6 +18,7 @@ const NERD_FONT_ICONS = {
|
|
|
18
18
|
deleted: "\u{f0159}",
|
|
19
19
|
deferred: "\u{f0377}",
|
|
20
20
|
info: "\u{f02fc}",
|
|
21
|
+
lightbulb: "\u{f0335}",
|
|
21
22
|
microphone: "\u{f036c}",
|
|
22
23
|
plus: "\u{f0415}",
|
|
23
24
|
pause: "\u{f03e4}",
|
|
@@ -44,6 +45,7 @@ const FALLBACK_ICONS = {
|
|
|
44
45
|
deleted: "×",
|
|
45
46
|
deferred: "↷",
|
|
46
47
|
info: "i",
|
|
48
|
+
lightbulb: "💡",
|
|
47
49
|
microphone: "m",
|
|
48
50
|
plus: "+",
|
|
49
51
|
pause: "⏸",
|
|
@@ -19,6 +19,7 @@ export type AppInputActionControllerHost = {
|
|
|
19
19
|
addEntry(entry: Entry): void;
|
|
20
20
|
addSessionAbortedEntry(): void;
|
|
21
21
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
22
|
+
dismissActiveDialog?(): boolean;
|
|
22
23
|
stopVoiceInput(): Promise<void>;
|
|
23
24
|
isShellCommandRunning(): boolean;
|
|
24
25
|
runChatShellCommand(command: string): Promise<InteractiveShellCommandResult>;
|
|
@@ -39,6 +40,7 @@ export declare class AppInputActionController {
|
|
|
39
40
|
queueInputFromEditor(): Promise<void>;
|
|
40
41
|
handleInterrupt(): Promise<void>;
|
|
41
42
|
handleEscape(): Promise<void>;
|
|
43
|
+
private closeActiveGlobalUi;
|
|
42
44
|
private abortStreamingSession;
|
|
43
45
|
private restoreSessionState;
|
|
44
46
|
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,13 @@ export class AppInputActionController {
|
|
|
76
78
|
await this.abortStreamingSession(runtime, { stopIfAlreadyAborting: false });
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
}
|
|
82
|
+
closeActiveGlobalUi() {
|
|
83
|
+
if (this.popupMenus.syncActivePopupMenu()) {
|
|
80
84
|
this.popupMenus.cancelActivePopupMenu();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return this.host.dismissActiveDialog?.() ?? false;
|
|
81
88
|
}
|
|
82
89
|
async abortStreamingSession(runtime, options) {
|
|
83
90
|
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
|
+
}
|