pi-ui-extend 0.1.69 → 0.1.71
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 +22 -1
- package/dist/app/commands/command-host.d.ts +7 -0
- package/dist/app/commands/command-host.js +10 -1
- package/dist/app/commands/command-model-actions.d.ts +1 -1
- package/dist/app/commands/command-model-actions.js +36 -2
- package/dist/app/commands/command-navigation-actions.d.ts +1 -1
- package/dist/app/commands/command-navigation-actions.js +40 -6
- package/dist/app/commands/command-session-actions.d.ts +1 -1
- package/dist/app/commands/command-session-actions.js +56 -0
- package/dist/app/commands/shell-controller.d.ts +9 -2
- package/dist/app/commands/shell-controller.js +32 -21
- package/dist/app/extensions/extension-actions-controller.d.ts +3 -0
- package/dist/app/extensions/extension-actions-controller.js +25 -7
- package/dist/app/extensions/extension-ui-controller.d.ts +5 -1
- package/dist/app/extensions/extension-ui-controller.js +104 -64
- package/dist/app/input/input-action-controller.d.ts +4 -1
- package/dist/app/input/input-action-controller.js +68 -27
- package/dist/app/input/input-controller.d.ts +3 -0
- package/dist/app/input/input-controller.js +45 -1
- package/dist/app/input/input-paste-handler.d.ts +3 -0
- package/dist/app/input/input-paste-handler.js +21 -12
- package/dist/app/input/prompt-enhancer-controller.d.ts +1 -0
- package/dist/app/input/prompt-enhancer-controller.js +11 -5
- package/dist/app/input/voice-controller.d.ts +4 -0
- package/dist/app/input/voice-controller.js +76 -35
- package/dist/app/popup/popup-action-controller.d.ts +3 -0
- package/dist/app/popup/popup-action-controller.js +43 -8
- package/dist/app/session/queued-message-controller.d.ts +5 -4
- package/dist/app/session/queued-message-controller.js +32 -27
- package/dist/app/session/request-history.d.ts +2 -0
- package/dist/app/session/request-history.js +22 -15
- package/dist/app/session/session-event-controller.js +28 -8
- package/dist/app/session/session-history.js +8 -2
- package/dist/app/session/session-lifecycle-controller.d.ts +7 -0
- package/dist/app/session/session-lifecycle-controller.js +91 -19
- package/dist/app/session/tabs-controller.d.ts +26 -0
- package/dist/app/session/tabs-controller.js +469 -96
- package/dist/app/terminal/terminal-controller.d.ts +4 -0
- package/dist/app/terminal/terminal-controller.js +38 -11
- package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
- package/dist/app/workspace/workspace-actions-controller.js +37 -9
- package/dist/input-editor.d.ts +2 -0
- package/dist/input-editor.js +17 -0
- package/docs/concurrency.md +76 -0
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +29 -14
- package/external/pi-tools-suite/src/todo/index.ts +2 -2
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +6 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +4 -4
|
@@ -4,9 +4,15 @@ import { collectStartupAvailabilityIssues } from "../cli/startup-checks.js";
|
|
|
4
4
|
export class AppSessionLifecycleController {
|
|
5
5
|
host;
|
|
6
6
|
unsubscribe;
|
|
7
|
+
ownershipRuntime;
|
|
8
|
+
ownershipSession;
|
|
9
|
+
ownershipGeneration = 0;
|
|
10
|
+
subscriptionGeneration = 0;
|
|
11
|
+
replacementHistoryGeneration = 0;
|
|
7
12
|
extensionBindPromise;
|
|
8
13
|
extensionBindRuntime;
|
|
9
14
|
extensionBindSession;
|
|
15
|
+
extensionBindOwnershipGeneration;
|
|
10
16
|
constructor(host) {
|
|
11
17
|
this.host = host;
|
|
12
18
|
}
|
|
@@ -33,6 +39,8 @@ export class AppSessionLifecycleController {
|
|
|
33
39
|
}
|
|
34
40
|
this.host.setRuntime(runtime);
|
|
35
41
|
runtime.setRebindSession(async () => {
|
|
42
|
+
if (!this.host.isRunning() || this.host.runtime() !== runtime)
|
|
43
|
+
return;
|
|
36
44
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
37
45
|
});
|
|
38
46
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
@@ -65,17 +73,27 @@ export class AppSessionLifecycleController {
|
|
|
65
73
|
async bindCurrentSession(options = {}) {
|
|
66
74
|
const runtime = this.requireRuntime();
|
|
67
75
|
const session = runtime.session;
|
|
76
|
+
const ownershipGeneration = this.advanceOwnership(runtime, session);
|
|
77
|
+
const subscriptionGeneration = ++this.subscriptionGeneration;
|
|
78
|
+
this.replacementHistoryGeneration += 1;
|
|
68
79
|
this.unsubscribe?.();
|
|
69
80
|
this.unsubscribe = session.subscribe((event) => {
|
|
81
|
+
if (!this.isCurrentRuntimeSession(runtime, session, ownershipGeneration)
|
|
82
|
+
|| subscriptionGeneration !== this.subscriptionGeneration)
|
|
83
|
+
return;
|
|
70
84
|
this.host.handleSessionEvent(event);
|
|
71
85
|
});
|
|
72
86
|
this.host.closeSdkMenuForBind();
|
|
73
87
|
const extensionUiScope = this.extensionUiScope(session);
|
|
74
88
|
this.host.clearExtensionWidgets(extensionUiScope, { cancelCustomUi: false });
|
|
75
|
-
const bindPromise = this.bindSessionExtensions(runtime, session, extensionUiScope
|
|
89
|
+
const bindPromise = this.bindSessionExtensions(runtime, session, extensionUiScope, {
|
|
90
|
+
deferStart: options.awaitExtensions === false,
|
|
91
|
+
ownershipGeneration,
|
|
92
|
+
});
|
|
76
93
|
if (options.awaitExtensions === false) {
|
|
77
94
|
void bindPromise.catch((error) => {
|
|
78
|
-
if (!this.isCurrentRuntimeSession(runtime, session)
|
|
95
|
+
if (!this.isCurrentRuntimeSession(runtime, session, ownershipGeneration)
|
|
96
|
+
|| subscriptionGeneration !== this.subscriptionGeneration)
|
|
79
97
|
return;
|
|
80
98
|
this.host.addEntry({ id: createId("error"), kind: "error", text: `Extension bind failed: ${stringifyUnknown(error)}` });
|
|
81
99
|
this.host.showToast("Extension initialization failed", "error");
|
|
@@ -90,28 +108,53 @@ export class AppSessionLifecycleController {
|
|
|
90
108
|
return;
|
|
91
109
|
if (this.extensionBindRuntime !== runtime || this.extensionBindSession !== runtime.session)
|
|
92
110
|
return;
|
|
111
|
+
if (this.extensionBindOwnershipGeneration !== this.ownershipGeneration)
|
|
112
|
+
return;
|
|
93
113
|
await this.extensionBindPromise;
|
|
94
114
|
}
|
|
95
115
|
unsubscribeSession() {
|
|
96
116
|
this.unsubscribe?.();
|
|
117
|
+
this.unsubscribe = undefined;
|
|
118
|
+
this.ownershipRuntime = undefined;
|
|
119
|
+
this.ownershipSession = undefined;
|
|
120
|
+
this.ownershipGeneration += 1;
|
|
121
|
+
this.subscriptionGeneration += 1;
|
|
122
|
+
this.replacementHistoryGeneration += 1;
|
|
97
123
|
}
|
|
98
124
|
afterSessionReplacement(message) {
|
|
125
|
+
const runtime = this.host.runtime();
|
|
126
|
+
if (!runtime) {
|
|
127
|
+
this.resetSessionView();
|
|
128
|
+
this.host.render();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const session = runtime.session;
|
|
132
|
+
const ownershipGeneration = this.advanceOwnership(runtime, session);
|
|
133
|
+
const historyGeneration = ++this.replacementHistoryGeneration;
|
|
99
134
|
this.resetSessionView();
|
|
100
|
-
void this.loadReplacementHistory(message);
|
|
135
|
+
void this.loadReplacementHistory(runtime, session, ownershipGeneration, historyGeneration, message);
|
|
101
136
|
this.host.render();
|
|
102
137
|
}
|
|
103
|
-
async loadReplacementHistory(message) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
138
|
+
async loadReplacementHistory(runtime, session, ownershipGeneration, historyGeneration, message) {
|
|
139
|
+
const isCancelled = () => historyGeneration !== this.replacementHistoryGeneration
|
|
140
|
+
|| !this.isCurrentRuntimeSession(runtime, session, ownershipGeneration);
|
|
141
|
+
const completed = await this.host.loadSessionHistoryEntriesAsync({
|
|
142
|
+
isCancelled,
|
|
143
|
+
render: () => {
|
|
144
|
+
if (!isCancelled())
|
|
145
|
+
this.host.render();
|
|
146
|
+
},
|
|
107
147
|
lazyOlderHistory: true,
|
|
108
148
|
});
|
|
149
|
+
if (!completed || isCancelled())
|
|
150
|
+
return;
|
|
109
151
|
this.host.syncUserSessionEntryMetadata();
|
|
152
|
+
if (isCancelled())
|
|
153
|
+
return;
|
|
110
154
|
if (message)
|
|
111
155
|
this.host.addEntry({ id: createId("system"), kind: "system", text: message });
|
|
112
|
-
const session = this.host.runtime()?.session;
|
|
113
156
|
this.host.setSessionStatus(session);
|
|
114
|
-
this.host.setSessionActivity(session
|
|
157
|
+
this.host.setSessionActivity(session.isStreaming ? "running" : "idle");
|
|
115
158
|
this.host.render();
|
|
116
159
|
}
|
|
117
160
|
resetSessionView() {
|
|
@@ -135,25 +178,41 @@ export class AppSessionLifecycleController {
|
|
|
135
178
|
throw new Error("Runtime is not initialized");
|
|
136
179
|
return runtime;
|
|
137
180
|
}
|
|
138
|
-
bindSessionExtensions(runtime, session, scopeKey) {
|
|
139
|
-
if (this.extensionBindPromise
|
|
181
|
+
bindSessionExtensions(runtime, session, scopeKey, options) {
|
|
182
|
+
if (this.extensionBindPromise
|
|
183
|
+
&& this.extensionBindRuntime === runtime
|
|
184
|
+
&& this.extensionBindSession === session
|
|
185
|
+
&& this.extensionBindOwnershipGeneration === options.ownershipGeneration) {
|
|
140
186
|
return this.extensionBindPromise;
|
|
141
187
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
const startBind = () => {
|
|
189
|
+
if (!this.isCurrentRuntimeSession(runtime, session, options.ownershipGeneration))
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
return session.bindExtensions({
|
|
192
|
+
uiContext: this.host.createExtensionUIContext(scopeKey),
|
|
193
|
+
commandContextActions: this.host.createExtensionCommandContextActions(runtime),
|
|
194
|
+
shutdownHandler: this.host.extensionShutdownHandler(),
|
|
195
|
+
onError: (error) => {
|
|
196
|
+
if (this.isCurrentRuntimeSession(runtime, session, options.ownershipGeneration))
|
|
197
|
+
this.host.handleExtensionError(error);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
const bindPromise = options.deferStart
|
|
202
|
+
? new Promise((resolve) => setTimeout(resolve, 0)).then(startBind)
|
|
203
|
+
: startBind();
|
|
204
|
+
const promise = bindPromise.finally(() => {
|
|
148
205
|
if (this.extensionBindPromise !== promise)
|
|
149
206
|
return;
|
|
150
207
|
this.extensionBindPromise = undefined;
|
|
151
208
|
this.extensionBindRuntime = undefined;
|
|
152
209
|
this.extensionBindSession = undefined;
|
|
210
|
+
this.extensionBindOwnershipGeneration = undefined;
|
|
153
211
|
});
|
|
154
212
|
this.extensionBindPromise = promise;
|
|
155
213
|
this.extensionBindRuntime = runtime;
|
|
156
214
|
this.extensionBindSession = session;
|
|
215
|
+
this.extensionBindOwnershipGeneration = options.ownershipGeneration;
|
|
157
216
|
return promise;
|
|
158
217
|
}
|
|
159
218
|
async collectAvailabilityIssues(runtime) {
|
|
@@ -184,8 +243,21 @@ export class AppSessionLifecycleController {
|
|
|
184
243
|
this.host.render();
|
|
185
244
|
}
|
|
186
245
|
}
|
|
187
|
-
|
|
188
|
-
|
|
246
|
+
advanceOwnership(runtime, session) {
|
|
247
|
+
if (this.ownershipRuntime !== runtime || this.ownershipSession !== session) {
|
|
248
|
+
this.ownershipRuntime = runtime;
|
|
249
|
+
this.ownershipSession = session;
|
|
250
|
+
this.ownershipGeneration += 1;
|
|
251
|
+
}
|
|
252
|
+
return this.ownershipGeneration;
|
|
253
|
+
}
|
|
254
|
+
isCurrentRuntimeSession(runtime, session, ownershipGeneration) {
|
|
255
|
+
return this.host.isRunning()
|
|
256
|
+
&& this.host.runtime() === runtime
|
|
257
|
+
&& runtime.session === session
|
|
258
|
+
&& this.ownershipRuntime === runtime
|
|
259
|
+
&& this.ownershipSession === session
|
|
260
|
+
&& this.ownershipGeneration === ownershipGeneration;
|
|
189
261
|
}
|
|
190
262
|
extensionUiScope(session) {
|
|
191
263
|
return session.sessionFile ?? session.sessionId;
|
|
@@ -61,15 +61,27 @@ export declare class AppTabsController {
|
|
|
61
61
|
private readonly sessionViewsByTabId;
|
|
62
62
|
private readonly scrollStatesByTabId;
|
|
63
63
|
private readonly tabIdsNeedingHistoryReload;
|
|
64
|
+
private readonly historyInvalidationGenerationByTabId;
|
|
65
|
+
private readonly runtimeOwnershipGenerationByTabId;
|
|
66
|
+
private readonly orphanRuntimeDisposals;
|
|
67
|
+
private readonly lifecycleMutationQueue;
|
|
64
68
|
private activeTabId;
|
|
65
69
|
private pendingActiveTabId;
|
|
66
70
|
private historyLoadGeneration;
|
|
71
|
+
private historyInvalidationGeneration;
|
|
72
|
+
private runtimeOwnershipGeneration;
|
|
73
|
+
private lifecycleGeneration;
|
|
74
|
+
private lifecycleMutationRunning;
|
|
75
|
+
private saveTabsWriteTail;
|
|
67
76
|
private restored;
|
|
68
77
|
private retentionCleanupRunning;
|
|
69
78
|
private retentionCleanupScheduled;
|
|
70
79
|
private prewarmScheduled;
|
|
71
80
|
private prewarmRunning;
|
|
72
81
|
constructor(host: AppTabsControllerHost);
|
|
82
|
+
private runLifecycleMutation;
|
|
83
|
+
private isLifecycleOwner;
|
|
84
|
+
cancelPendingLifecycleWork(): void;
|
|
73
85
|
tabs(): readonly SessionTab[];
|
|
74
86
|
isSwitching(): boolean;
|
|
75
87
|
markTerminalBellAttention(sessionPath: string | undefined): void;
|
|
@@ -80,16 +92,24 @@ export declare class AppTabsController {
|
|
|
80
92
|
disposeInactiveRuntimes(disposeRuntime?: (runtime: AgentSessionRuntime) => Promise<void>): Promise<void>;
|
|
81
93
|
saveInputStateForQuit(): Promise<void>;
|
|
82
94
|
persistActiveDeferredUserMessages(): void;
|
|
95
|
+
requeueAutoUserMessageForSession(session: AgentSession, message: SubmittedUserMessage): void;
|
|
96
|
+
inputTabOwnedByRuntime(tabId: string | undefined, runtime: AgentSessionRuntime, session: AgentSession): boolean;
|
|
83
97
|
syncActiveTabFromRuntime(options?: {
|
|
84
98
|
save?: boolean;
|
|
85
99
|
force?: boolean;
|
|
86
100
|
}): void;
|
|
87
101
|
restoreAfterStartup(): Promise<void>;
|
|
102
|
+
private restoreAfterStartupMutation;
|
|
88
103
|
openNewTab(): Promise<void>;
|
|
104
|
+
private openNewTabMutation;
|
|
89
105
|
openSessionInNewTab(sessionPath: string): Promise<boolean>;
|
|
106
|
+
private openSessionInNewTabMutation;
|
|
90
107
|
forkSessionEntryInNewTab(entryId: string): Promise<boolean>;
|
|
108
|
+
private forkSessionEntryInNewTabMutation;
|
|
91
109
|
switchToTab(tabId: string): Promise<void>;
|
|
110
|
+
private switchToTabMutation;
|
|
92
111
|
closeTab(tabId: string): Promise<void>;
|
|
112
|
+
private closeTabMutation;
|
|
93
113
|
private replaceLastTabWithNewSession;
|
|
94
114
|
private loadActiveSessionHistory;
|
|
95
115
|
private cancelHistoryLoad;
|
|
@@ -103,8 +123,13 @@ export declare class AppTabsController {
|
|
|
103
123
|
private restoreStoredScrollState;
|
|
104
124
|
private setRuntimeForTab;
|
|
105
125
|
private deleteRuntimeForTab;
|
|
126
|
+
private releaseRuntimeOwnership;
|
|
106
127
|
private clearRuntimeSubscriptions;
|
|
107
128
|
private observeRuntimeForTab;
|
|
129
|
+
private isRuntimeOwnedByTab;
|
|
130
|
+
private queueOrphanRuntimeDisposal;
|
|
131
|
+
private disposeRuntimeIfOrphan;
|
|
132
|
+
private adoptCurrentRuntimeAfterFailedRollback;
|
|
108
133
|
private shouldSyncTabFromRuntimeEvent;
|
|
109
134
|
private shouldScheduleDelayedSyncForRuntimeEvent;
|
|
110
135
|
private shouldInvalidateCachedViewForRuntimeEvent;
|
|
@@ -151,6 +176,7 @@ export declare class AppTabsController {
|
|
|
151
176
|
private parsePersistedScrollState;
|
|
152
177
|
private parsePersistedSubmittedUserMessages;
|
|
153
178
|
private saveTabs;
|
|
179
|
+
private writeTabsSnapshot;
|
|
154
180
|
private filePath;
|
|
155
181
|
private sessionDir;
|
|
156
182
|
private scheduleProjectSessionRetention;
|