pi-ui-extend 0.1.18 → 0.1.19
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/dist/app/app.js +8 -6
- package/dist/app/constants.d.ts +1 -0
- package/dist/app/constants.js +1 -0
- package/dist/app/input/voice-controller.js +16 -12
- package/dist/app/popup/popup-menu-controller.d.ts +1 -5
- package/dist/app/popup/popup-menu-controller.js +7 -8
- package/dist/app/process.js +7 -0
- package/dist/app/rendering/conversation-entry-renderer.js +17 -16
- package/dist/app/rendering/conversation-viewport.js +4 -35
- package/dist/app/rendering/editor-layout-renderer.d.ts +5 -1
- package/dist/app/rendering/editor-layout-renderer.js +25 -16
- package/dist/app/rendering/popup-menu-renderer.d.ts +1 -5
- package/dist/app/rendering/popup-menu-renderer.js +24 -34
- package/dist/app/rendering/render-controller.d.ts +2 -0
- package/dist/app/rendering/render-controller.js +26 -25
- package/dist/app/rendering/render-text.js +2 -2
- package/dist/app/rendering/status-line-renderer.js +1 -1
- package/dist/app/rendering/tab-line-renderer.js +3 -3
- package/dist/app/runtime.js +29 -3
- package/dist/app/screen/file-link-opener.d.ts +2 -0
- package/dist/app/screen/file-link-opener.js +84 -17
- package/dist/app/screen/mouse-controller.d.ts +0 -2
- package/dist/app/screen/mouse-controller.js +6 -12
- package/dist/app/screen/screen-styler.js +1 -1
- package/dist/app/session/lazy-session-manager.d.ts +1 -1
- package/dist/app/session/lazy-session-manager.js +64 -52
- package/dist/app/session/queued-message-controller.d.ts +6 -0
- package/dist/app/session/queued-message-controller.js +9 -1
- package/dist/app/session/queued-message-entries.d.ts +8 -0
- package/dist/app/session/queued-message-entries.js +41 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +9 -1
- package/dist/app/session/session-lifecycle-controller.js +45 -11
- package/dist/app/session/tabs-controller.d.ts +11 -1
- package/dist/app/session/tabs-controller.js +197 -30
- package/dist/app/terminal/terminal-controller.d.ts +2 -0
- package/dist/app/terminal/terminal-controller.js +7 -5
- package/dist/schemas/pi-tools-suite-schema.d.ts +3 -0
- package/dist/schemas/pi-tools-suite-schema.js +3 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +8 -2
- package/extensions/session-title/config.ts +3 -3
- package/extensions/session-title/index.ts +60 -5
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +1 -0
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +0 -3
- package/external/pi-tools-suite/src/async-subagents/core/notifications.ts +64 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +54 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -4
- package/external/pi-tools-suite/src/config.ts +13 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -4
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +5 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +580 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -5
- package/external/pi-tools-suite/src/lsp/_shared/config.ts +2 -0
- package/external/pi-tools-suite/src/lsp/_shared/types.ts +2 -0
- package/external/pi-tools-suite/src/lsp/manager.ts +15 -9
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +1 -0
- package/external/pi-tools-suite/src/todo/index.ts +81 -4
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +5 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +3 -14
- package/schemas/pi-tools-suite.json +19 -0
- package/apps/desktop-tauri/README.md +0 -103
- package/apps/desktop-tauri/bin/pix-desktop.mjs +0 -89
|
@@ -5,6 +5,9 @@ import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-
|
|
|
5
5
|
export class AppSessionLifecycleController {
|
|
6
6
|
host;
|
|
7
7
|
unsubscribe;
|
|
8
|
+
extensionBindPromise;
|
|
9
|
+
extensionBindRuntime;
|
|
10
|
+
extensionBindSession;
|
|
8
11
|
constructor(host) {
|
|
9
12
|
this.host = host;
|
|
10
13
|
}
|
|
@@ -25,9 +28,9 @@ export class AppSessionLifecycleController {
|
|
|
25
28
|
}
|
|
26
29
|
this.host.setRuntime(runtime);
|
|
27
30
|
runtime.setRebindSession(async () => {
|
|
28
|
-
await this.bindCurrentSession();
|
|
31
|
+
await this.bindCurrentSession({ awaitExtensions: false });
|
|
29
32
|
});
|
|
30
|
-
await this.bindCurrentSession();
|
|
33
|
+
await this.bindCurrentSession({ awaitExtensions: false });
|
|
31
34
|
if (isEmptyStartupSession(runtime)) {
|
|
32
35
|
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(runtime) });
|
|
33
36
|
}
|
|
@@ -64,21 +67,28 @@ export class AppSessionLifecycleController {
|
|
|
64
67
|
this.host.render();
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
|
-
async bindCurrentSession() {
|
|
70
|
+
async bindCurrentSession(options = {}) {
|
|
68
71
|
const runtime = this.requireRuntime();
|
|
72
|
+
const session = runtime.session;
|
|
69
73
|
this.unsubscribe?.();
|
|
70
|
-
this.unsubscribe =
|
|
74
|
+
this.unsubscribe = session.subscribe((event) => {
|
|
71
75
|
this.host.handleSessionEvent(event);
|
|
72
76
|
});
|
|
73
77
|
this.host.closeSdkMenuForBind();
|
|
74
|
-
const extensionUiScope = this.extensionUiScope(
|
|
78
|
+
const extensionUiScope = this.extensionUiScope(session);
|
|
75
79
|
this.host.clearExtensionWidgets(extensionUiScope, { cancelCustomUi: false });
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const bindPromise = this.bindSessionExtensions(runtime, session, extensionUiScope);
|
|
81
|
+
if (options.awaitExtensions === false) {
|
|
82
|
+
void bindPromise.catch((error) => {
|
|
83
|
+
if (!this.isCurrentRuntimeSession(runtime, session))
|
|
84
|
+
return;
|
|
85
|
+
this.host.addEntry({ id: createId("error"), kind: "error", text: `Extension bind failed: ${stringifyUnknown(error)}` });
|
|
86
|
+
this.host.showToast("Extension initialization failed", "error");
|
|
87
|
+
this.host.render();
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await bindPromise;
|
|
82
92
|
}
|
|
83
93
|
unsubscribeSession() {
|
|
84
94
|
this.unsubscribe?.();
|
|
@@ -123,6 +133,30 @@ export class AppSessionLifecycleController {
|
|
|
123
133
|
throw new Error("Runtime is not initialized");
|
|
124
134
|
return runtime;
|
|
125
135
|
}
|
|
136
|
+
bindSessionExtensions(runtime, session, scopeKey) {
|
|
137
|
+
if (this.extensionBindPromise && this.extensionBindRuntime === runtime && this.extensionBindSession === session) {
|
|
138
|
+
return this.extensionBindPromise;
|
|
139
|
+
}
|
|
140
|
+
const promise = session.bindExtensions({
|
|
141
|
+
uiContext: this.host.createExtensionUIContext(scopeKey),
|
|
142
|
+
commandContextActions: this.host.createExtensionCommandContextActions(runtime),
|
|
143
|
+
shutdownHandler: this.host.extensionShutdownHandler(),
|
|
144
|
+
onError: (error) => this.host.handleExtensionError(error),
|
|
145
|
+
}).finally(() => {
|
|
146
|
+
if (this.extensionBindPromise !== promise)
|
|
147
|
+
return;
|
|
148
|
+
this.extensionBindPromise = undefined;
|
|
149
|
+
this.extensionBindRuntime = undefined;
|
|
150
|
+
this.extensionBindSession = undefined;
|
|
151
|
+
});
|
|
152
|
+
this.extensionBindPromise = promise;
|
|
153
|
+
this.extensionBindRuntime = runtime;
|
|
154
|
+
this.extensionBindSession = session;
|
|
155
|
+
return promise;
|
|
156
|
+
}
|
|
157
|
+
isCurrentRuntimeSession(runtime, session) {
|
|
158
|
+
return this.host.isRunning() && this.host.runtime() === runtime && runtime.session === session;
|
|
159
|
+
}
|
|
126
160
|
extensionUiScope(session) {
|
|
127
161
|
return session.sessionFile ?? session.sessionId;
|
|
128
162
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { BindCurrentSessionOptions } from "./session-lifecycle-controller.js";
|
|
2
3
|
import type { InputEditorDraftState } from "../../input-editor.js";
|
|
3
4
|
import type { AppBlinkController } from "../screen/blink-controller.js";
|
|
4
5
|
import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
|
|
@@ -10,7 +11,7 @@ export type AppTabsControllerHost = {
|
|
|
10
11
|
runtime(): AgentSessionRuntime | undefined;
|
|
11
12
|
createRuntimeForNewSession(): Promise<AgentSessionRuntime>;
|
|
12
13
|
createRuntimeForSession(sessionPath: string): Promise<AgentSessionRuntime>;
|
|
13
|
-
activateRuntime(runtime: AgentSessionRuntime): Promise<void>;
|
|
14
|
+
activateRuntime(runtime: AgentSessionRuntime, options?: BindCurrentSessionOptions): Promise<void>;
|
|
14
15
|
disposeRuntime(runtime: AgentSessionRuntime): Promise<void>;
|
|
15
16
|
isRunning(): boolean;
|
|
16
17
|
setStatus(status: string): void;
|
|
@@ -37,7 +38,9 @@ export declare class AppTabsController {
|
|
|
37
38
|
private readonly host;
|
|
38
39
|
private readonly tabItems;
|
|
39
40
|
private readonly runtimesByTabId;
|
|
41
|
+
private readonly runtimeLoadsByTabId;
|
|
40
42
|
private readonly runtimeSubscriptionsByTabId;
|
|
43
|
+
private readonly runtimeRefreshTimersByTabId;
|
|
41
44
|
private readonly inputStatesByTabId;
|
|
42
45
|
private readonly deferredUserMessagesByTabId;
|
|
43
46
|
private activeTabId;
|
|
@@ -46,6 +49,8 @@ export declare class AppTabsController {
|
|
|
46
49
|
private restored;
|
|
47
50
|
private retentionCleanupRunning;
|
|
48
51
|
private retentionCleanupScheduled;
|
|
52
|
+
private prewarmScheduled;
|
|
53
|
+
private prewarmRunning;
|
|
49
54
|
constructor(host: AppTabsControllerHost);
|
|
50
55
|
tabs(): readonly SessionTab[];
|
|
51
56
|
isSwitching(): boolean;
|
|
@@ -81,6 +86,9 @@ export declare class AppTabsController {
|
|
|
81
86
|
private clearRuntimeSubscriptions;
|
|
82
87
|
private observeRuntimeForTab;
|
|
83
88
|
private shouldSyncTabFromRuntimeEvent;
|
|
89
|
+
private shouldScheduleDelayedSyncForRuntimeEvent;
|
|
90
|
+
private scheduleDelayedRuntimeSync;
|
|
91
|
+
private clearRuntimeRefreshTimers;
|
|
84
92
|
private syncTabFromObservedRuntime;
|
|
85
93
|
private storeActiveInputState;
|
|
86
94
|
private storeActiveDeferredUserMessages;
|
|
@@ -115,6 +123,8 @@ export declare class AppTabsController {
|
|
|
115
123
|
private filePath;
|
|
116
124
|
private sessionDir;
|
|
117
125
|
private scheduleProjectSessionRetention;
|
|
126
|
+
private scheduleTabPrewarm;
|
|
127
|
+
private prewarmTabs;
|
|
118
128
|
private cleanupOldProjectSessions;
|
|
119
129
|
private preservedSessionPaths;
|
|
120
130
|
private maxProjectSessions;
|
|
@@ -9,13 +9,16 @@ import { createStartupInfoMessage, isEmptyStartupSession } from "../cli/startup-
|
|
|
9
9
|
import { tabPanelRows } from "../rendering/tab-line-renderer.js";
|
|
10
10
|
const TAB_STATE_VERSION = 3;
|
|
11
11
|
const MAX_RESTORED_TABS = 8;
|
|
12
|
+
const BACKGROUND_PREWARM_TAB_LIMIT = 2;
|
|
12
13
|
const TAB_ATTENTION_BLINK_KEY = "tab-attention";
|
|
13
14
|
const LOADING_TAB_TITLE_PATTERN = /^loading(?:…|\.\.\.)?$/iu;
|
|
14
15
|
export class AppTabsController {
|
|
15
16
|
host;
|
|
16
17
|
tabItems = [];
|
|
17
18
|
runtimesByTabId = new Map();
|
|
19
|
+
runtimeLoadsByTabId = new Map();
|
|
18
20
|
runtimeSubscriptionsByTabId = new Map();
|
|
21
|
+
runtimeRefreshTimersByTabId = new Map();
|
|
19
22
|
inputStatesByTabId = new Map();
|
|
20
23
|
deferredUserMessagesByTabId = new Map();
|
|
21
24
|
activeTabId;
|
|
@@ -24,6 +27,8 @@ export class AppTabsController {
|
|
|
24
27
|
restored = false;
|
|
25
28
|
retentionCleanupRunning = false;
|
|
26
29
|
retentionCleanupScheduled = false;
|
|
30
|
+
prewarmScheduled = false;
|
|
31
|
+
prewarmRunning = false;
|
|
27
32
|
constructor(host) {
|
|
28
33
|
this.host = host;
|
|
29
34
|
}
|
|
@@ -192,6 +197,7 @@ export class AppTabsController {
|
|
|
192
197
|
this.settleStartupTabPlaceholders();
|
|
193
198
|
await this.saveTabs();
|
|
194
199
|
this.scheduleProjectSessionRetention();
|
|
200
|
+
this.scheduleTabPrewarm();
|
|
195
201
|
return;
|
|
196
202
|
}
|
|
197
203
|
let restoredRuntime = runtime;
|
|
@@ -200,7 +206,7 @@ export class AppTabsController {
|
|
|
200
206
|
this.host.render();
|
|
201
207
|
try {
|
|
202
208
|
restoredRuntime = await this.host.createRuntimeForSession(desiredPath);
|
|
203
|
-
await this.host.activateRuntime(restoredRuntime);
|
|
209
|
+
await this.host.activateRuntime(restoredRuntime, { awaitExtensions: false });
|
|
204
210
|
}
|
|
205
211
|
catch {
|
|
206
212
|
this.host.showToast("Could not restore the previous active tab", "warning");
|
|
@@ -224,6 +230,7 @@ export class AppTabsController {
|
|
|
224
230
|
this.restoreInputState(this.activeTabId);
|
|
225
231
|
await this.saveTabs();
|
|
226
232
|
this.scheduleProjectSessionRetention();
|
|
233
|
+
this.scheduleTabPrewarm();
|
|
227
234
|
}
|
|
228
235
|
async openNewTab() {
|
|
229
236
|
if (this.pendingActiveTabId) {
|
|
@@ -239,31 +246,72 @@ export class AppTabsController {
|
|
|
239
246
|
this.syncActiveTabFromRuntime();
|
|
240
247
|
this.storeActiveInputState();
|
|
241
248
|
this.storeActiveDeferredUserMessages();
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
const previousTabId = this.activeTabId;
|
|
250
|
+
const previousRuntime = runtime;
|
|
251
|
+
const tab = {
|
|
252
|
+
id: createId("tab"),
|
|
253
|
+
title: "new",
|
|
254
|
+
titlePlaceholder: "new",
|
|
255
|
+
status: "active",
|
|
256
|
+
activity: "thinking",
|
|
257
|
+
};
|
|
258
|
+
this.tabItems.push(tab);
|
|
249
259
|
this.activeTabId = tab.id;
|
|
250
260
|
this.pendingActiveTabId = tab.id;
|
|
251
261
|
this.clearTabAttention(tab);
|
|
252
|
-
this.updateTabFromSession(tab, newRuntime.session);
|
|
253
|
-
this.setRuntimeForTab(tab.id, newRuntime);
|
|
254
262
|
this.restoreInputState(tab.id);
|
|
255
263
|
this.host.closeMenusForTabSwitch?.();
|
|
264
|
+
this.host.resetSessionView();
|
|
265
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
266
|
+
this.host.setSessionActivity("thinking");
|
|
267
|
+
this.host.setStatus("starting new tab");
|
|
268
|
+
this.host.render();
|
|
269
|
+
let newRuntime;
|
|
256
270
|
try {
|
|
257
|
-
await this.host.
|
|
271
|
+
newRuntime = await this.host.createRuntimeForNewSession();
|
|
258
272
|
}
|
|
259
|
-
|
|
273
|
+
catch (error) {
|
|
260
274
|
if (this.pendingActiveTabId === tab.id)
|
|
261
275
|
this.pendingActiveTabId = undefined;
|
|
276
|
+
this.removeTab(tab.id);
|
|
277
|
+
this.activeTabId = previousTabId;
|
|
278
|
+
if (previousTabId)
|
|
279
|
+
this.restoreInputState(previousTabId);
|
|
280
|
+
this.host.closeMenusForTabSwitch?.();
|
|
281
|
+
this.host.resetSessionView();
|
|
282
|
+
if (previousTabId)
|
|
283
|
+
this.restoreDeferredUserMessages(previousTabId);
|
|
284
|
+
this.host.loadSessionHistory();
|
|
285
|
+
this.host.setSessionStatus(previousRuntime.session);
|
|
286
|
+
this.host.setSessionActivity(this.sessionActivity(previousRuntime.session));
|
|
287
|
+
this.host.render();
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
const existingTab = this.findTabForSession(newRuntime.session);
|
|
291
|
+
const targetTab = existingTab && existingTab.id !== tab.id ? existingTab : tab;
|
|
292
|
+
if (targetTab !== tab)
|
|
293
|
+
this.removeTab(tab.id);
|
|
294
|
+
this.activeTabId = targetTab.id;
|
|
295
|
+
this.pendingActiveTabId = targetTab.id;
|
|
296
|
+
this.clearTabAttention(targetTab);
|
|
297
|
+
this.updateTabFromSession(targetTab, newRuntime.session);
|
|
298
|
+
this.setRuntimeForTab(targetTab.id, newRuntime);
|
|
299
|
+
this.restoreInputState(targetTab.id);
|
|
300
|
+
this.host.resetSessionView();
|
|
301
|
+
this.restoreDeferredUserMessages(targetTab.id);
|
|
302
|
+
this.host.setSessionActivity("thinking");
|
|
303
|
+
this.host.render();
|
|
304
|
+
try {
|
|
305
|
+
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
if (this.pendingActiveTabId === targetTab.id)
|
|
309
|
+
this.pendingActiveTabId = undefined;
|
|
262
310
|
}
|
|
263
311
|
void this.saveTabs();
|
|
264
312
|
this.scheduleProjectSessionRetention();
|
|
265
313
|
this.host.resetSessionView();
|
|
266
|
-
this.restoreDeferredUserMessages(
|
|
314
|
+
this.restoreDeferredUserMessages(targetTab.id);
|
|
267
315
|
if (isEmptyStartupSession(newRuntime)) {
|
|
268
316
|
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(newRuntime) });
|
|
269
317
|
}
|
|
@@ -306,27 +354,50 @@ export class AppTabsController {
|
|
|
306
354
|
const previousRuntime = runtime;
|
|
307
355
|
this.host.setStatus("opening session tab");
|
|
308
356
|
this.host.render();
|
|
357
|
+
const tab = {
|
|
358
|
+
id: createId("tab"),
|
|
359
|
+
title: basename(resolvedSessionPath, extname(resolvedSessionPath)) || "loading",
|
|
360
|
+
titlePlaceholder: "loading",
|
|
361
|
+
status: "active",
|
|
362
|
+
activity: "thinking",
|
|
363
|
+
sessionPath: resolvedSessionPath,
|
|
364
|
+
};
|
|
365
|
+
this.tabItems.push(tab);
|
|
366
|
+
this.activeTabId = tab.id;
|
|
367
|
+
this.pendingActiveTabId = tab.id;
|
|
368
|
+
this.clearTabAttention(tab);
|
|
369
|
+
this.restoreInputState(tab.id);
|
|
370
|
+
this.host.closeMenusForTabSwitch?.();
|
|
371
|
+
this.host.resetSessionView();
|
|
372
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
373
|
+
this.host.setSessionActivity("thinking");
|
|
374
|
+
this.host.render();
|
|
309
375
|
let newRuntime;
|
|
310
376
|
try {
|
|
311
377
|
newRuntime = await this.host.createRuntimeForSession(resolvedSessionPath);
|
|
312
378
|
}
|
|
313
379
|
catch {
|
|
380
|
+
this.pendingActiveTabId = undefined;
|
|
381
|
+
this.removeTab(tab.id);
|
|
382
|
+
this.activeTabId = previousTabId;
|
|
383
|
+
if (previousTabId)
|
|
384
|
+
this.restoreInputState(previousTabId);
|
|
385
|
+
this.host.closeMenusForTabSwitch?.();
|
|
386
|
+
this.host.resetSessionView();
|
|
387
|
+
if (previousTabId)
|
|
388
|
+
this.restoreDeferredUserMessages(previousTabId);
|
|
389
|
+
this.host.loadSessionHistory();
|
|
314
390
|
this.host.showToast("Could not open session tab", "warning");
|
|
315
391
|
this.host.setSessionStatus(previousRuntime.session);
|
|
392
|
+
this.host.setSessionActivity(this.sessionActivity(previousRuntime.session));
|
|
316
393
|
this.host.render();
|
|
317
394
|
return false;
|
|
318
395
|
}
|
|
319
|
-
const tab = this.tabFromSession(newRuntime.session, { titlePlaceholder: "loading" });
|
|
320
|
-
this.tabItems.push(tab);
|
|
321
|
-
this.activeTabId = tab.id;
|
|
322
|
-
this.pendingActiveTabId = tab.id;
|
|
323
|
-
this.clearTabAttention(tab);
|
|
324
396
|
this.updateTabFromSession(tab, newRuntime.session);
|
|
325
397
|
this.setRuntimeForTab(tab.id, newRuntime);
|
|
326
|
-
this.
|
|
327
|
-
this.host.closeMenusForTabSwitch?.();
|
|
398
|
+
this.host.render();
|
|
328
399
|
try {
|
|
329
|
-
await this.host.activateRuntime(newRuntime);
|
|
400
|
+
await this.host.activateRuntime(newRuntime, { awaitExtensions: false });
|
|
330
401
|
}
|
|
331
402
|
catch {
|
|
332
403
|
this.pendingActiveTabId = undefined;
|
|
@@ -337,7 +408,7 @@ export class AppTabsController {
|
|
|
337
408
|
this.host.closeMenusForTabSwitch?.();
|
|
338
409
|
if (this.host.runtime() !== previousRuntime) {
|
|
339
410
|
try {
|
|
340
|
-
await this.host.activateRuntime(previousRuntime);
|
|
411
|
+
await this.host.activateRuntime(previousRuntime, { awaitExtensions: false });
|
|
341
412
|
}
|
|
342
413
|
catch {
|
|
343
414
|
// Keep the best available runtime below and surface the switch failure.
|
|
@@ -362,6 +433,7 @@ export class AppTabsController {
|
|
|
362
433
|
this.setRuntimeForTab(tab.id, newRuntime);
|
|
363
434
|
this.restoreInputState(tab.id);
|
|
364
435
|
void this.saveTabs();
|
|
436
|
+
this.scheduleTabPrewarm();
|
|
365
437
|
await this.loadActiveSessionHistory(newRuntime);
|
|
366
438
|
return true;
|
|
367
439
|
}
|
|
@@ -435,8 +507,12 @@ export class AppTabsController {
|
|
|
435
507
|
this.inputStatesByTabId.set(tab.id, this.inputStateFromText(result.selectedText));
|
|
436
508
|
this.restoreInputState(tab.id);
|
|
437
509
|
this.host.closeMenusForTabSwitch?.();
|
|
510
|
+
this.host.resetSessionView();
|
|
511
|
+
this.restoreDeferredUserMessages(tab.id);
|
|
512
|
+
this.host.setSessionActivity("thinking");
|
|
513
|
+
this.host.render();
|
|
438
514
|
try {
|
|
439
|
-
await this.host.activateRuntime(forkRuntime);
|
|
515
|
+
await this.host.activateRuntime(forkRuntime, { awaitExtensions: false });
|
|
440
516
|
}
|
|
441
517
|
catch {
|
|
442
518
|
this.pendingActiveTabId = undefined;
|
|
@@ -447,7 +523,7 @@ export class AppTabsController {
|
|
|
447
523
|
this.host.closeMenusForTabSwitch?.();
|
|
448
524
|
if (this.host.runtime() !== previousRuntime) {
|
|
449
525
|
try {
|
|
450
|
-
await this.host.activateRuntime(previousRuntime);
|
|
526
|
+
await this.host.activateRuntime(previousRuntime, { awaitExtensions: false });
|
|
451
527
|
}
|
|
452
528
|
catch {
|
|
453
529
|
// Keep the best available runtime below and surface the switch failure.
|
|
@@ -472,6 +548,7 @@ export class AppTabsController {
|
|
|
472
548
|
this.restoreInputState(tab.id);
|
|
473
549
|
void this.saveTabs();
|
|
474
550
|
this.scheduleProjectSessionRetention();
|
|
551
|
+
this.scheduleTabPrewarm();
|
|
475
552
|
await this.loadActiveSessionHistory(forkRuntime);
|
|
476
553
|
this.host.addEntry({ id: createId("system"), kind: "system", text: `Forked from entry ${entryId} in a new tab.` });
|
|
477
554
|
this.host.setSessionStatus(forkRuntime.session);
|
|
@@ -520,7 +597,7 @@ export class AppTabsController {
|
|
|
520
597
|
targetRuntime = await this.runtimeForTab(target);
|
|
521
598
|
if (!targetRuntime)
|
|
522
599
|
throw new Error("Could not load tab runtime");
|
|
523
|
-
await this.host.activateRuntime(targetRuntime);
|
|
600
|
+
await this.host.activateRuntime(targetRuntime, { awaitExtensions: false });
|
|
524
601
|
}
|
|
525
602
|
catch {
|
|
526
603
|
this.pendingActiveTabId = undefined;
|
|
@@ -534,7 +611,7 @@ export class AppTabsController {
|
|
|
534
611
|
this.host.closeMenusForTabSwitch?.();
|
|
535
612
|
if (this.host.runtime() !== previousRuntime) {
|
|
536
613
|
try {
|
|
537
|
-
await this.host.activateRuntime(previousRuntime);
|
|
614
|
+
await this.host.activateRuntime(previousRuntime, { awaitExtensions: false });
|
|
538
615
|
}
|
|
539
616
|
catch {
|
|
540
617
|
// Keep the best available runtime below and surface the switch failure.
|
|
@@ -558,6 +635,7 @@ export class AppTabsController {
|
|
|
558
635
|
this.setRuntimeForTab(target.id, targetRuntime);
|
|
559
636
|
this.restoreInputState(target.id);
|
|
560
637
|
void this.saveTabs();
|
|
638
|
+
this.scheduleTabPrewarm();
|
|
561
639
|
await this.loadActiveSessionHistory(targetRuntime);
|
|
562
640
|
}
|
|
563
641
|
async closeTab(tabId) {
|
|
@@ -605,7 +683,7 @@ export class AppTabsController {
|
|
|
605
683
|
const nextRuntime = await this.runtimeForTab(nextTab);
|
|
606
684
|
if (!nextRuntime)
|
|
607
685
|
return;
|
|
608
|
-
await this.host.activateRuntime(nextRuntime);
|
|
686
|
+
await this.host.activateRuntime(nextRuntime, { awaitExtensions: false });
|
|
609
687
|
this.tabItems.splice(index, 1);
|
|
610
688
|
this.deleteRuntimeForTab(tabId);
|
|
611
689
|
this.inputStatesByTabId.delete(tabId);
|
|
@@ -619,6 +697,7 @@ export class AppTabsController {
|
|
|
619
697
|
this.host.closeMenusForTabSwitch?.();
|
|
620
698
|
void this.host.disposeRuntime(runtime);
|
|
621
699
|
void this.saveTabs();
|
|
700
|
+
this.scheduleTabPrewarm();
|
|
622
701
|
await this.loadActiveSessionHistory(nextRuntime);
|
|
623
702
|
}
|
|
624
703
|
async replaceLastTabWithNewSession(tabId) {
|
|
@@ -739,11 +818,16 @@ export class AppTabsController {
|
|
|
739
818
|
}
|
|
740
819
|
deleteRuntimeForTab(tabId) {
|
|
741
820
|
this.runtimesByTabId.delete(tabId);
|
|
821
|
+
this.runtimeLoadsByTabId.delete(tabId);
|
|
822
|
+
this.clearRuntimeRefreshTimers(tabId);
|
|
742
823
|
const subscription = this.runtimeSubscriptionsByTabId.get(tabId);
|
|
743
824
|
subscription?.unsubscribe();
|
|
744
825
|
this.runtimeSubscriptionsByTabId.delete(tabId);
|
|
745
826
|
}
|
|
746
827
|
clearRuntimeSubscriptions() {
|
|
828
|
+
for (const tabId of this.runtimeRefreshTimersByTabId.keys()) {
|
|
829
|
+
this.clearRuntimeRefreshTimers(tabId);
|
|
830
|
+
}
|
|
747
831
|
for (const subscription of this.runtimeSubscriptionsByTabId.values()) {
|
|
748
832
|
subscription.unsubscribe();
|
|
749
833
|
}
|
|
@@ -755,6 +839,9 @@ export class AppTabsController {
|
|
|
755
839
|
return;
|
|
756
840
|
existing?.unsubscribe();
|
|
757
841
|
const unsubscribe = runtime.session.subscribe((event) => {
|
|
842
|
+
if (this.shouldScheduleDelayedSyncForRuntimeEvent(event)) {
|
|
843
|
+
this.scheduleDelayedRuntimeSync(tabId, runtime);
|
|
844
|
+
}
|
|
758
845
|
if (!this.shouldSyncTabFromRuntimeEvent(event))
|
|
759
846
|
return;
|
|
760
847
|
this.syncTabFromObservedRuntime(tabId, runtime);
|
|
@@ -768,6 +855,35 @@ export class AppTabsController {
|
|
|
768
855
|
|| event.type === "compaction_start"
|
|
769
856
|
|| event.type === "compaction_end";
|
|
770
857
|
}
|
|
858
|
+
shouldScheduleDelayedSyncForRuntimeEvent(event) {
|
|
859
|
+
return event.type === "agent_end"
|
|
860
|
+
|| event.type === "turn_end"
|
|
861
|
+
|| event.type === "compaction_end";
|
|
862
|
+
}
|
|
863
|
+
scheduleDelayedRuntimeSync(tabId, runtime) {
|
|
864
|
+
this.clearRuntimeRefreshTimers(tabId);
|
|
865
|
+
for (const delayMs of [0, 100, 500, 1500, 3000]) {
|
|
866
|
+
const timer = setTimeout(() => {
|
|
867
|
+
this.runtimeRefreshTimersByTabId.get(tabId)?.delete(timer);
|
|
868
|
+
this.syncTabFromObservedRuntime(tabId, runtime);
|
|
869
|
+
}, delayMs);
|
|
870
|
+
timer.unref?.();
|
|
871
|
+
let timers = this.runtimeRefreshTimersByTabId.get(tabId);
|
|
872
|
+
if (!timers) {
|
|
873
|
+
timers = new Set();
|
|
874
|
+
this.runtimeRefreshTimersByTabId.set(tabId, timers);
|
|
875
|
+
}
|
|
876
|
+
timers.add(timer);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
clearRuntimeRefreshTimers(tabId) {
|
|
880
|
+
const timers = this.runtimeRefreshTimersByTabId.get(tabId);
|
|
881
|
+
if (!timers)
|
|
882
|
+
return;
|
|
883
|
+
for (const timer of timers)
|
|
884
|
+
clearTimeout(timer);
|
|
885
|
+
this.runtimeRefreshTimersByTabId.delete(tabId);
|
|
886
|
+
}
|
|
771
887
|
syncTabFromObservedRuntime(tabId, runtime) {
|
|
772
888
|
const tab = this.tabItems.find((item) => item.id === tabId);
|
|
773
889
|
if (!tab) {
|
|
@@ -827,13 +943,32 @@ export class AppTabsController {
|
|
|
827
943
|
const existing = this.runtimesByTabId.get(tab.id);
|
|
828
944
|
if (existing)
|
|
829
945
|
return existing;
|
|
946
|
+
const loading = this.runtimeLoadsByTabId.get(tab.id);
|
|
947
|
+
if (loading)
|
|
948
|
+
return await loading;
|
|
830
949
|
if (!tab.sessionPath) {
|
|
831
950
|
this.host.showToast("Tab has no persisted session path", "warning");
|
|
832
951
|
return undefined;
|
|
833
952
|
}
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
953
|
+
const expectedPath = resolve(tab.sessionPath);
|
|
954
|
+
const pending = (async () => {
|
|
955
|
+
const runtime = await this.host.createRuntimeForSession(expectedPath);
|
|
956
|
+
const liveTab = this.tabItems.find((item) => item.id === tab.id);
|
|
957
|
+
if (!liveTab || !liveTab.sessionPath || resolve(liveTab.sessionPath) !== expectedPath) {
|
|
958
|
+
void this.host.disposeRuntime(runtime);
|
|
959
|
+
return undefined;
|
|
960
|
+
}
|
|
961
|
+
this.setRuntimeForTab(tab.id, runtime);
|
|
962
|
+
return runtime;
|
|
963
|
+
})();
|
|
964
|
+
this.runtimeLoadsByTabId.set(tab.id, pending);
|
|
965
|
+
try {
|
|
966
|
+
return await pending;
|
|
967
|
+
}
|
|
968
|
+
finally {
|
|
969
|
+
if (this.runtimeLoadsByTabId.get(tab.id) === pending)
|
|
970
|
+
this.runtimeLoadsByTabId.delete(tab.id);
|
|
971
|
+
}
|
|
837
972
|
}
|
|
838
973
|
findTabForSession(session, options = {}) {
|
|
839
974
|
const sessionPath = this.sessionPath(session);
|
|
@@ -1146,6 +1281,38 @@ export class AppTabsController {
|
|
|
1146
1281
|
void this.cleanupOldProjectSessions();
|
|
1147
1282
|
}, 0);
|
|
1148
1283
|
}
|
|
1284
|
+
scheduleTabPrewarm() {
|
|
1285
|
+
if (this.host.options.noSession || this.prewarmScheduled || this.prewarmRunning)
|
|
1286
|
+
return;
|
|
1287
|
+
this.prewarmScheduled = true;
|
|
1288
|
+
setTimeout(() => {
|
|
1289
|
+
this.prewarmScheduled = false;
|
|
1290
|
+
void this.prewarmTabs();
|
|
1291
|
+
}, 0);
|
|
1292
|
+
}
|
|
1293
|
+
async prewarmTabs() {
|
|
1294
|
+
if (this.prewarmRunning || this.pendingActiveTabId || !this.host.isRunning())
|
|
1295
|
+
return;
|
|
1296
|
+
this.prewarmRunning = true;
|
|
1297
|
+
try {
|
|
1298
|
+
let warmed = 0;
|
|
1299
|
+
for (const tab of this.tabItems) {
|
|
1300
|
+
if (warmed >= BACKGROUND_PREWARM_TAB_LIMIT)
|
|
1301
|
+
break;
|
|
1302
|
+
if (tab.id === this.activeTabId || !tab.sessionPath)
|
|
1303
|
+
continue;
|
|
1304
|
+
if (this.runtimesByTabId.has(tab.id) || this.runtimeLoadsByTabId.has(tab.id))
|
|
1305
|
+
continue;
|
|
1306
|
+
const runtime = await this.runtimeForTab(tab).catch(() => undefined);
|
|
1307
|
+
if (!runtime)
|
|
1308
|
+
continue;
|
|
1309
|
+
warmed += 1;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
finally {
|
|
1313
|
+
this.prewarmRunning = false;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1149
1316
|
async cleanupOldProjectSessions() {
|
|
1150
1317
|
if (this.retentionCleanupRunning)
|
|
1151
1318
|
return;
|
|
@@ -24,6 +24,8 @@ export declare class AppTerminalController {
|
|
|
24
24
|
private terminalEnabled;
|
|
25
25
|
private interactiveSuspended;
|
|
26
26
|
private stopPromise;
|
|
27
|
+
private readonly enterInteractiveSequence;
|
|
28
|
+
private readonly exitInteractiveSequence;
|
|
27
29
|
constructor(host: AppTerminalControllerHost);
|
|
28
30
|
isSuspended(): boolean;
|
|
29
31
|
enableTerminal(): void;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ANSI_RESET } from "../../theme.js";
|
|
2
|
-
import { DISABLE_BRACKETED_PASTE, DISABLE_TERMINAL_KEY_REPORTING, DISABLE_TERMINAL_WRAP, CLEAR_TERMINAL, ENABLE_BRACKETED_PASTE, ENABLE_TERMINAL_KEY_REPORTING, ENABLE_TERMINAL_WRAP, HIDE_CURSOR, RUNTIME_DISPOSE_GRACE_MS, SHOW_CURSOR, } from "../constants.js";
|
|
2
|
+
import { DISABLE_BRACKETED_PASTE, DISABLE_TERMINAL_KEY_REPORTING, DISABLE_TERMINAL_WRAP, CLEAR_TERMINAL, ENABLE_BRACKETED_PASTE, ENABLE_TERMINAL_KEY_REPORTING, ENABLE_TERMINAL_WRAP, HIDE_CURSOR, RESET_TERMINAL_VIEWPORT_STATE, RUNTIME_DISPOSE_GRACE_MS, SHOW_CURSOR, } from "../constants.js";
|
|
3
3
|
export class AppTerminalController {
|
|
4
4
|
host;
|
|
5
5
|
terminalEnabled = false;
|
|
6
6
|
interactiveSuspended = false;
|
|
7
7
|
stopPromise;
|
|
8
|
+
enterInteractiveSequence = `${ANSI_RESET}${RESET_TERMINAL_VIEWPORT_STATE}${CLEAR_TERMINAL}\x1b[?1049h${RESET_TERMINAL_VIEWPORT_STATE}${CLEAR_TERMINAL}${ENABLE_TERMINAL_KEY_REPORTING}${ENABLE_BRACKETED_PASTE}${DISABLE_TERMINAL_WRAP}\x1b[?1002h\x1b[?1006h${HIDE_CURSOR}`;
|
|
9
|
+
exitInteractiveSequence = `${ANSI_RESET}${RESET_TERMINAL_VIEWPORT_STATE}${DISABLE_TERMINAL_KEY_REPORTING}${DISABLE_BRACKETED_PASTE}${ENABLE_TERMINAL_WRAP}\x1b[?1006l\x1b[?1002l\x1b[?1049l${SHOW_CURSOR}`;
|
|
8
10
|
constructor(host) {
|
|
9
11
|
this.host = host;
|
|
10
12
|
}
|
|
@@ -19,7 +21,7 @@ export class AppTerminalController {
|
|
|
19
21
|
process.stdin.resume();
|
|
20
22
|
process.stdin.on("data", this.onInputData);
|
|
21
23
|
process.stdout.on("resize", this.onResize);
|
|
22
|
-
process.stdout.write(
|
|
24
|
+
process.stdout.write(this.enterInteractiveSequence);
|
|
23
25
|
process.on("exit", this.restoreTerminal);
|
|
24
26
|
}
|
|
25
27
|
async stop() {
|
|
@@ -66,7 +68,7 @@ export class AppTerminalController {
|
|
|
66
68
|
return;
|
|
67
69
|
this.terminalEnabled = false;
|
|
68
70
|
this.interactiveSuspended = false;
|
|
69
|
-
process.stdout.write(
|
|
71
|
+
process.stdout.write(this.exitInteractiveSequence);
|
|
70
72
|
if (process.stdin.isTTY)
|
|
71
73
|
process.stdin.setRawMode(false);
|
|
72
74
|
};
|
|
@@ -77,7 +79,7 @@ export class AppTerminalController {
|
|
|
77
79
|
process.stdin.off("data", this.onInputData);
|
|
78
80
|
process.stdin.pause();
|
|
79
81
|
process.stdout.off("resize", this.onResize);
|
|
80
|
-
process.stdout.write(
|
|
82
|
+
process.stdout.write(this.exitInteractiveSequence);
|
|
81
83
|
if (process.stdin.isTTY)
|
|
82
84
|
process.stdin.setRawMode(false);
|
|
83
85
|
}
|
|
@@ -91,7 +93,7 @@ export class AppTerminalController {
|
|
|
91
93
|
process.stdin.on("data", this.onInputData);
|
|
92
94
|
process.stdout.on("resize", this.onResize);
|
|
93
95
|
this.host.resetRenderOutputBuffer();
|
|
94
|
-
process.stdout.write(
|
|
96
|
+
process.stdout.write(this.enterInteractiveSequence);
|
|
95
97
|
this.host.render();
|
|
96
98
|
}
|
|
97
99
|
async stopInternal() {
|
|
@@ -10,6 +10,7 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
10
10
|
enabled: Type.TOptional<Type.TBoolean>;
|
|
11
11
|
disabledModules: Type.TOptional<Type.TArray<Type.TString>>;
|
|
12
12
|
todoThinking: Type.TOptional<Type.TBoolean>;
|
|
13
|
+
lookupModel: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
|
|
13
14
|
terminalBell: Type.TOptional<Type.TObject<{
|
|
14
15
|
sound: Type.TOptional<Type.TBoolean>;
|
|
15
16
|
}>>;
|
|
@@ -170,6 +171,8 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
170
171
|
languageIdByExtension: Type.TOptional<Type.TRecord<"^.*$", Type.TString>>;
|
|
171
172
|
startupTimeoutMs: Type.TOptional<Type.TNumber>;
|
|
172
173
|
diagnosticsWaitMs: Type.TOptional<Type.TNumber>;
|
|
174
|
+
pullDiagnostics: Type.TOptional<Type.TBoolean>;
|
|
175
|
+
waitForPublishDiagnostics: Type.TOptional<Type.TBoolean>;
|
|
173
176
|
initializationOptions: Type.TOptional<Type.TUnknown>;
|
|
174
177
|
settings: Type.TOptional<Type.TUnknown>;
|
|
175
178
|
}>>>;
|
|
@@ -190,6 +190,8 @@ const LspServerConfig = Type.Object({
|
|
|
190
190
|
languageIdByExtension: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "File extension → language ID mapping, e.g. {'.ts': 'typescript'}." })),
|
|
191
191
|
startupTimeoutMs: Type.Optional(Type.Number({ description: "Server startup timeout in ms.", minimum: 1000 })),
|
|
192
192
|
diagnosticsWaitMs: Type.Optional(Type.Number({ description: "Wait time for diagnostics after file change.", minimum: 0 })),
|
|
193
|
+
pullDiagnostics: Type.Optional(Type.Boolean({ description: "When false, skip textDocument/diagnostic pull requests and rely on published diagnostics. Useful for servers where pull diagnostics is slow or incomplete." })),
|
|
194
|
+
waitForPublishDiagnostics: Type.Optional(Type.Boolean({ description: "When false, do not wait for a fresh textDocument/publishDiagnostics notification after file change. diagnosticsWaitMs still bounds the wait when enabled." })),
|
|
193
195
|
initializationOptions: Type.Optional(Type.Unknown({ description: "LSP initialization options passed to the server." })),
|
|
194
196
|
settings: Type.Optional(Type.Unknown({ description: "LSP workspace/settings passed to the server." })),
|
|
195
197
|
}, { description: "LSP server configuration." });
|
|
@@ -204,6 +206,7 @@ export const PiToolsSuiteConfigSchema = Type.Object({
|
|
|
204
206
|
enabled: Type.Optional(Type.Boolean({ description: "Enable or disable the entire pi-tools-suite extension." })),
|
|
205
207
|
disabledModules: Type.Optional(Type.Array(Type.String(), { description: "List of disabled module names (e.g. ['lsp', 'prompt-commands'])." })),
|
|
206
208
|
todoThinking: Type.Optional(Type.Boolean({ description: "Enable per-todo thinking levels and automatic thinking switch/restore when tasks become in-progress/completed." })),
|
|
209
|
+
lookupModel: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Vision-capable provider/model used by GLM's lookup tool; unset or null disables lookup." })),
|
|
207
210
|
terminalBell: Type.Optional(TerminalBellConfig),
|
|
208
211
|
dcp: Type.Optional(DcpConfig),
|
|
209
212
|
asyncSubagents: Type.Optional(AsyncSubagentsConfig),
|
package/dist/theme.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type Theme = {
|
|
|
13
13
|
inputForeground: string;
|
|
14
14
|
inputBackground: string;
|
|
15
15
|
inputBorder: string;
|
|
16
|
+
inputBorderWidgetBackground: string;
|
|
17
|
+
tabBorder: string;
|
|
18
|
+
assistantMessageBackground: string;
|
|
16
19
|
userMessageBackground: string;
|
|
17
20
|
inputCursorBackground: string;
|
|
18
21
|
popupForeground: string;
|