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
|
@@ -38,6 +38,7 @@ export class AppPopupActionController {
|
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
async submitSlashCommand(text) {
|
|
41
|
+
const scope = this.captureScope();
|
|
41
42
|
const parsed = this.menuItems.parseSlashInput(text);
|
|
42
43
|
if (!parsed)
|
|
43
44
|
return;
|
|
@@ -67,7 +68,7 @@ export class AppPopupActionController {
|
|
|
67
68
|
this.host.render();
|
|
68
69
|
try {
|
|
69
70
|
if (command.kind === "resource") {
|
|
70
|
-
await this.executeResourceSlashCommand(command, parsed.arguments);
|
|
71
|
+
await this.executeResourceSlashCommand(command, parsed.arguments, scope.session);
|
|
71
72
|
}
|
|
72
73
|
else {
|
|
73
74
|
if (!command.run)
|
|
@@ -76,11 +77,13 @@ export class AppPopupActionController {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
catch (error) {
|
|
80
|
+
if (!this.isScopeActive(scope))
|
|
81
|
+
return;
|
|
79
82
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
80
83
|
this.host.showToast(`/${command.name} failed`, "error");
|
|
81
84
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
82
85
|
}
|
|
83
|
-
if (this.
|
|
86
|
+
if (this.isScopeActive(scope))
|
|
84
87
|
this.host.render();
|
|
85
88
|
}
|
|
86
89
|
async submitSelectedSlashCommand() {
|
|
@@ -97,6 +100,7 @@ export class AppPopupActionController {
|
|
|
97
100
|
return true;
|
|
98
101
|
}
|
|
99
102
|
async submitSelectedModel() {
|
|
103
|
+
const scope = this.captureScope();
|
|
100
104
|
const selected = this.popupMenus.selectedModel();
|
|
101
105
|
if (!selected)
|
|
102
106
|
return false;
|
|
@@ -110,15 +114,18 @@ export class AppPopupActionController {
|
|
|
110
114
|
await this.commandController.runModelCommand(selected.value.model);
|
|
111
115
|
}
|
|
112
116
|
catch (error) {
|
|
117
|
+
if (!this.isScopeActive(scope))
|
|
118
|
+
return true;
|
|
113
119
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
114
120
|
this.host.showToast("/model failed", "error");
|
|
115
121
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
116
122
|
}
|
|
117
|
-
if (this.
|
|
123
|
+
if (this.isScopeActive(scope))
|
|
118
124
|
this.host.render();
|
|
119
125
|
return true;
|
|
120
126
|
}
|
|
121
127
|
async submitSelectedThinking() {
|
|
128
|
+
const scope = this.captureScope();
|
|
122
129
|
const selected = this.popupMenus.selectedThinking();
|
|
123
130
|
if (!selected)
|
|
124
131
|
return false;
|
|
@@ -132,15 +139,18 @@ export class AppPopupActionController {
|
|
|
132
139
|
await this.commandController.runThinkingCommand(selected.value.level);
|
|
133
140
|
}
|
|
134
141
|
catch (error) {
|
|
142
|
+
if (!this.isScopeActive(scope))
|
|
143
|
+
return true;
|
|
135
144
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
136
145
|
this.host.showToast("/thinking failed", "error");
|
|
137
146
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
138
147
|
}
|
|
139
|
-
if (this.
|
|
148
|
+
if (this.isScopeActive(scope))
|
|
140
149
|
this.host.render();
|
|
141
150
|
return true;
|
|
142
151
|
}
|
|
143
152
|
async submitSelectedUserMessageAction() {
|
|
153
|
+
const scope = this.captureScope();
|
|
144
154
|
const selected = this.popupMenus.selectedUserMessageAction();
|
|
145
155
|
if (!selected)
|
|
146
156
|
return false;
|
|
@@ -163,6 +173,8 @@ export class AppPopupActionController {
|
|
|
163
173
|
return true;
|
|
164
174
|
}
|
|
165
175
|
catch (error) {
|
|
176
|
+
if (!this.isScopeActive(scope))
|
|
177
|
+
return true;
|
|
166
178
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
167
179
|
this.host.showToast(`${selected.label} failed`, "error");
|
|
168
180
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
@@ -170,11 +182,15 @@ export class AppPopupActionController {
|
|
|
170
182
|
}
|
|
171
183
|
}
|
|
172
184
|
async submitSelectedUserMessageJump() {
|
|
185
|
+
const scope = this.captureScope();
|
|
173
186
|
const selected = this.popupMenus.selectedUserMessageJump();
|
|
174
187
|
if (!selected)
|
|
175
188
|
return false;
|
|
176
189
|
this.popupMenus.closeUserMessageJumpMenu();
|
|
177
|
-
|
|
190
|
+
const found = await this.host.scrollToUserMessageJumpTarget(selected);
|
|
191
|
+
if (!this.isScopeActive(scope))
|
|
192
|
+
return true;
|
|
193
|
+
if (!found) {
|
|
178
194
|
this.host.showToast("User message not found", "error");
|
|
179
195
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
180
196
|
return true;
|
|
@@ -184,6 +200,7 @@ export class AppPopupActionController {
|
|
|
184
200
|
return true;
|
|
185
201
|
}
|
|
186
202
|
async submitSelectedQueueMessageAction() {
|
|
203
|
+
const scope = this.captureScope();
|
|
187
204
|
const selected = this.popupMenus.selectedQueueMessageAction();
|
|
188
205
|
if (!selected)
|
|
189
206
|
return false;
|
|
@@ -202,6 +219,8 @@ export class AppPopupActionController {
|
|
|
202
219
|
return true;
|
|
203
220
|
}
|
|
204
221
|
catch (error) {
|
|
222
|
+
if (!this.isScopeActive(scope))
|
|
223
|
+
return true;
|
|
205
224
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
206
225
|
this.host.showToast(`${selected.label} failed`, "error");
|
|
207
226
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
@@ -229,7 +248,11 @@ export class AppPopupActionController {
|
|
|
229
248
|
this.host.render();
|
|
230
249
|
try {
|
|
231
250
|
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
251
|
+
if (!this.isRuntimeActive(runtime))
|
|
252
|
+
return true;
|
|
232
253
|
const result = await runtime.switchSession(session.path);
|
|
254
|
+
if (!this.isRuntimeActive(runtime))
|
|
255
|
+
return true;
|
|
233
256
|
if (result.cancelled) {
|
|
234
257
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Resume cancelled." });
|
|
235
258
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -240,21 +263,33 @@ export class AppPopupActionController {
|
|
|
240
263
|
this.host.afterSessionReplacement(`Resumed session "${name}"`);
|
|
241
264
|
}
|
|
242
265
|
catch (error) {
|
|
266
|
+
if (!this.isRuntimeActive(runtime))
|
|
267
|
+
return true;
|
|
243
268
|
this.host.addEntry({ id: createId("error"), kind: "error", text: `Resume failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
244
269
|
this.host.showToast("Failed to resume session", "error");
|
|
245
270
|
this.host.setSessionStatus(runtime.session);
|
|
246
271
|
}
|
|
247
|
-
if (this.
|
|
272
|
+
if (this.isRuntimeActive(runtime))
|
|
248
273
|
this.host.render();
|
|
249
274
|
return true;
|
|
250
275
|
}
|
|
251
276
|
formatSlashCommandLine(name, argumentsText) {
|
|
252
277
|
return `/${name}${argumentsText ? ` ${argumentsText}` : ""}`;
|
|
253
278
|
}
|
|
254
|
-
async executeResourceSlashCommand(command, argumentsText) {
|
|
279
|
+
async executeResourceSlashCommand(command, argumentsText, session) {
|
|
255
280
|
const promptText = this.formatSlashCommandLine(command.name, argumentsText);
|
|
256
281
|
this.host.setStatus(`running /${command.name}`);
|
|
257
|
-
await this.queuedMessages.submitUserMessage(this.queuedMessages.createSubmittedUserMessage(promptText, promptText, []));
|
|
282
|
+
await this.queuedMessages.submitUserMessage(this.queuedMessages.createSubmittedUserMessage(promptText, promptText, []), session);
|
|
283
|
+
}
|
|
284
|
+
captureScope() {
|
|
285
|
+
const runtime = this.host.runtime();
|
|
286
|
+
return { runtime, session: runtime?.session };
|
|
287
|
+
}
|
|
288
|
+
isScopeActive(scope) {
|
|
289
|
+
return this.host.isRunning() && this.host.runtime() === scope.runtime && scope.runtime?.session === scope.session;
|
|
290
|
+
}
|
|
291
|
+
isRuntimeActive(runtime) {
|
|
292
|
+
return this.host.isRunning() && this.host.runtime() === runtime;
|
|
258
293
|
}
|
|
259
294
|
getRuntime(commandName) {
|
|
260
295
|
const runtime = this.host.runtime();
|
|
@@ -19,13 +19,14 @@ export type AppQueuedMessageControllerHost = {
|
|
|
19
19
|
setInput(value: string): void;
|
|
20
20
|
insertInput(value: string): void;
|
|
21
21
|
attachImage(data: string, mimeType: string): void;
|
|
22
|
+
requeueAutoUserMessageForSession?(session: AgentSession, message: SubmittedUserMessage): void;
|
|
22
23
|
onDeferredUserMessagesChanged?(): void;
|
|
23
24
|
};
|
|
24
25
|
export declare class AppQueuedMessageController {
|
|
25
26
|
private readonly host;
|
|
26
27
|
readonly autoUserMessages: SubmittedUserMessage[];
|
|
27
28
|
readonly deferredUserMessages: SubmittedUserMessage[];
|
|
28
|
-
private
|
|
29
|
+
private readonly promptSubmissionsInFlight;
|
|
29
30
|
private immediateSendInProgress;
|
|
30
31
|
constructor(host: AppQueuedMessageControllerHost);
|
|
31
32
|
reset(): void;
|
|
@@ -34,11 +35,11 @@ export declare class AppQueuedMessageController {
|
|
|
34
35
|
captureDeferredUserMessages(): SubmittedUserMessage[];
|
|
35
36
|
restoreDeferredUserMessages(messages: readonly SubmittedUserMessage[]): void;
|
|
36
37
|
createSubmittedUserMessage(promptText: string, displayText: string, images: ImageContent[]): SubmittedUserMessage;
|
|
37
|
-
submitUserMessage(message: SubmittedUserMessage): Promise<void>;
|
|
38
|
+
submitUserMessage(message: SubmittedUserMessage, session?: AgentSession): Promise<void>;
|
|
38
39
|
sendUserMessageToSession(message: SubmittedUserMessage, options?: {
|
|
39
40
|
streamingBehavior?: "steer" | "followUp";
|
|
40
|
-
}): Promise<void>;
|
|
41
|
-
flushAutoUserMessages(): Promise<void>;
|
|
41
|
+
}, session?: AgentSession): Promise<void>;
|
|
42
|
+
flushAutoUserMessages(expectedSession?: AgentSession): Promise<void>;
|
|
42
43
|
queuedMessageCounts(): {
|
|
43
44
|
steering: number;
|
|
44
45
|
followUp: number;
|
|
@@ -5,7 +5,7 @@ export class AppQueuedMessageController {
|
|
|
5
5
|
host;
|
|
6
6
|
autoUserMessages = [];
|
|
7
7
|
deferredUserMessages = [];
|
|
8
|
-
|
|
8
|
+
promptSubmissionsInFlight = new Set();
|
|
9
9
|
immediateSendInProgress = false;
|
|
10
10
|
constructor(host) {
|
|
11
11
|
this.host = host;
|
|
@@ -13,7 +13,7 @@ export class AppQueuedMessageController {
|
|
|
13
13
|
reset() {
|
|
14
14
|
this.autoUserMessages.length = 0;
|
|
15
15
|
this.deferredUserMessages.length = 0;
|
|
16
|
-
this.
|
|
16
|
+
this.promptSubmissionsInFlight.clear();
|
|
17
17
|
}
|
|
18
18
|
captureAutoUserMessages() {
|
|
19
19
|
return this.autoUserMessages.map(cloneSubmittedUserMessage);
|
|
@@ -40,51 +40,52 @@ export class AppQueuedMessageController {
|
|
|
40
40
|
images,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
async submitUserMessage(message) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
await this.sendUserMessageToSession(message, { streamingBehavior: "steer" });
|
|
43
|
+
async submitUserMessage(message, session) {
|
|
44
|
+
const targetSession = session ?? this.host.requireRuntime().session;
|
|
45
|
+
if (targetSession.isStreaming) {
|
|
46
|
+
await this.sendUserMessageToSession(message, { streamingBehavior: "steer" }, targetSession);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
if (this.shouldQueueUserMessageAsSteering(
|
|
49
|
+
if (this.shouldQueueUserMessageAsSteering(targetSession)) {
|
|
50
50
|
await this.queueUserMessageAsSteering(message);
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
await this.sendUserMessageToSession(message);
|
|
53
|
+
await this.sendUserMessageToSession(message, {}, targetSession);
|
|
54
54
|
}
|
|
55
|
-
async sendUserMessageToSession(message, options = {}) {
|
|
56
|
-
const
|
|
57
|
-
const markInFlight = !
|
|
55
|
+
async sendUserMessageToSession(message, options = {}, session) {
|
|
56
|
+
const targetSession = session ?? this.host.requireRuntime().session;
|
|
57
|
+
const markInFlight = !targetSession.isStreaming;
|
|
58
58
|
if (markInFlight)
|
|
59
|
-
this.
|
|
59
|
+
this.promptSubmissionsInFlight.add(targetSession);
|
|
60
60
|
this.host.setSessionActivity("running");
|
|
61
61
|
try {
|
|
62
62
|
const opts = {};
|
|
63
|
-
if (
|
|
63
|
+
if (targetSession.isStreaming)
|
|
64
64
|
opts.streamingBehavior = options.streamingBehavior ?? "steer";
|
|
65
65
|
if (message.images.length > 0)
|
|
66
66
|
opts.images = message.images;
|
|
67
|
-
await
|
|
67
|
+
await targetSession.prompt(message.promptText, Object.keys(opts).length > 0 ? opts : undefined);
|
|
68
68
|
}
|
|
69
69
|
finally {
|
|
70
|
-
if (markInFlight
|
|
71
|
-
this.
|
|
70
|
+
if (markInFlight)
|
|
71
|
+
this.promptSubmissionsInFlight.delete(targetSession);
|
|
72
72
|
const runtime = this.host.runtime();
|
|
73
|
-
if (runtime)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
73
|
+
if (runtime?.session !== targetSession)
|
|
74
|
+
return;
|
|
75
|
+
this.host.setSessionStatus(targetSession);
|
|
76
|
+
this.host.setSessionActivity(targetSession.isStreaming || targetSession.isCompacting ? "running" : "idle");
|
|
78
77
|
if (this.totalQueuedMessageCount() > 0)
|
|
79
78
|
this.updateQueuedMessageStatus();
|
|
80
|
-
void this.flushAutoUserMessages();
|
|
79
|
+
void this.flushAutoUserMessages(targetSession);
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
|
-
async flushAutoUserMessages() {
|
|
82
|
+
async flushAutoUserMessages(expectedSession) {
|
|
84
83
|
if (this.immediateSendInProgress || this.autoUserMessages.length === 0)
|
|
85
84
|
return;
|
|
86
85
|
const session = this.host.runtime()?.session;
|
|
87
|
-
if (
|
|
86
|
+
if (expectedSession && session !== expectedSession)
|
|
87
|
+
return;
|
|
88
|
+
if (!session || session.isStreaming || session.isCompacting || this.promptSubmissionsInFlight.has(session))
|
|
88
89
|
return;
|
|
89
90
|
const message = this.autoUserMessages.shift();
|
|
90
91
|
if (!message)
|
|
@@ -94,9 +95,13 @@ export class AppQueuedMessageController {
|
|
|
94
95
|
if (this.host.isRunning())
|
|
95
96
|
this.host.render();
|
|
96
97
|
try {
|
|
97
|
-
await this.sendUserMessageToSession(message);
|
|
98
|
+
await this.sendUserMessageToSession(message, {}, session);
|
|
98
99
|
}
|
|
99
100
|
catch (error) {
|
|
101
|
+
if (this.host.runtime()?.session !== session) {
|
|
102
|
+
this.host.requeueAutoUserMessageForSession?.(session, message);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
100
105
|
this.autoUserMessages.unshift(message);
|
|
101
106
|
this.notifyAutoUserMessagesChanged();
|
|
102
107
|
this.updateQueuedMessageStatus();
|
|
@@ -201,7 +206,7 @@ export class AppQueuedMessageController {
|
|
|
201
206
|
const message = typeof taken.removed === "string"
|
|
202
207
|
? this.createSubmittedUserMessage(taken.removed, taken.removed, [])
|
|
203
208
|
: taken.removed;
|
|
204
|
-
await this.sendUserMessageToSession(message, { streamingBehavior: "steer" });
|
|
209
|
+
await this.sendUserMessageToSession(message, { streamingBehavior: "steer" }, session);
|
|
205
210
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
206
211
|
this.host.showToast("Queued message sent", "success");
|
|
207
212
|
}
|
|
@@ -237,7 +242,7 @@ export class AppQueuedMessageController {
|
|
|
237
242
|
return deferredQueuedMessageEntries(this.deferredUserMessages);
|
|
238
243
|
}
|
|
239
244
|
shouldQueueUserMessageAsSteering(session) {
|
|
240
|
-
return session.isCompacting || this.
|
|
245
|
+
return session.isCompacting || this.promptSubmissionsInFlight.has(session);
|
|
241
246
|
}
|
|
242
247
|
async queueUserMessageAsSteering(message) {
|
|
243
248
|
this.autoUserMessages.push(message);
|
|
@@ -9,12 +9,14 @@ export class AppRequestHistory {
|
|
|
9
9
|
entries = [];
|
|
10
10
|
cursor;
|
|
11
11
|
draft = "";
|
|
12
|
+
saveChain = Promise.resolve();
|
|
13
|
+
saveSequence = 0;
|
|
12
14
|
constructor(host) {
|
|
13
15
|
this.host = host;
|
|
14
16
|
}
|
|
15
17
|
async load() {
|
|
16
18
|
if (this.host.noSession)
|
|
17
|
-
return;
|
|
19
|
+
return Promise.resolve();
|
|
18
20
|
try {
|
|
19
21
|
const raw = await readFile(this.filePath(), "utf8");
|
|
20
22
|
const parsed = JSON.parse(raw);
|
|
@@ -93,21 +95,26 @@ export class AppRequestHistory {
|
|
|
93
95
|
this.replaceInput(entry);
|
|
94
96
|
return true;
|
|
95
97
|
}
|
|
96
|
-
|
|
98
|
+
save() {
|
|
97
99
|
if (this.host.noSession)
|
|
98
|
-
return;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
return Promise.resolve();
|
|
101
|
+
this.entries = this.limited(this.entries);
|
|
102
|
+
const payload = this.payload(this.entries);
|
|
103
|
+
const sequence = ++this.saveSequence;
|
|
104
|
+
const writeSnapshot = async () => {
|
|
105
|
+
try {
|
|
106
|
+
const filePath = this.filePath();
|
|
107
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
108
|
+
const tempPath = `${filePath}.${process.pid}.${sequence}.tmp`;
|
|
109
|
+
await writeFile(tempPath, payload, "utf8");
|
|
110
|
+
await rename(tempPath, filePath);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Request history is a convenience feature; never interrupt the UI on disk errors.
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
this.saveChain = this.saveChain.then(writeSnapshot, writeSnapshot);
|
|
117
|
+
return this.saveChain;
|
|
111
118
|
}
|
|
112
119
|
replaceInput(value) {
|
|
113
120
|
if (value !== this.host.getInput()) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createId } from "../id.js";
|
|
2
2
|
import { extractImageContents, renderContent, renderUserMessageContent, stringifyUnknown } from "../rendering/message-content.js";
|
|
3
3
|
import { customMessageEntry, extensionSessionEntry, loadSessionHistoryEntries, loadSessionHistoryEntriesAsync } from "./session-history.js";
|
|
4
|
-
import { sessionHistoryDisplayMessages, sessionHistoryDisplayMessagesFromEntries, sessionHistoryFullBranchEntries } from "./pix-system-message.js";
|
|
4
|
+
import { sessionHistoryDisplayMessages, sessionHistoryDisplayMessagesFromEntries, sessionHistoryFullBranchEntries, sessionHistoryOlderMessagesReader } from "./pix-system-message.js";
|
|
5
5
|
import { THINKING_TOOL_NAME } from "../constants.js";
|
|
6
6
|
import { isRecord } from "../guards.js";
|
|
7
7
|
/**
|
|
@@ -132,24 +132,44 @@ export class AppSessionEventController {
|
|
|
132
132
|
this.olderHistoryLoader = undefined;
|
|
133
133
|
this.historyEntries = [];
|
|
134
134
|
this.historyWindowStart = 0;
|
|
135
|
-
const
|
|
135
|
+
const lazyOlderHistory = options.lazyOlderHistory === true;
|
|
136
|
+
const historyEntries = lazyOlderHistory ? undefined : [];
|
|
137
|
+
const messages = lazyOlderHistory
|
|
138
|
+
? sessionHistoryDisplayMessages(runtime.session)
|
|
139
|
+
: sessionHistoryDisplayMessagesFromEntries(await sessionHistoryFullBranchEntries(runtime.session));
|
|
136
140
|
if (options.isCancelled())
|
|
137
141
|
return false;
|
|
138
|
-
const historyEntries = [];
|
|
139
142
|
const loaded = await loadSessionHistoryEntriesAsync({
|
|
140
|
-
messages
|
|
141
|
-
addEntry: (entry) =>
|
|
142
|
-
|
|
143
|
+
messages,
|
|
144
|
+
addEntry: (entry) => {
|
|
145
|
+
if (historyEntries)
|
|
146
|
+
historyEntries.push(entry);
|
|
147
|
+
else
|
|
148
|
+
this.addEntry(entry);
|
|
149
|
+
},
|
|
150
|
+
prependEntries: (entries) => {
|
|
151
|
+
if (historyEntries)
|
|
152
|
+
historyEntries.unshift(...entries);
|
|
153
|
+
else
|
|
154
|
+
this.prependEntries(entries);
|
|
155
|
+
},
|
|
143
156
|
setToolEntryId: (toolCallId, entryId) => this.toolEntryIdsByCallId.set(toolCallId, entryId),
|
|
144
157
|
toolDefaultExpanded: (toolName) => this.host.toolDefaultExpanded(toolName),
|
|
145
158
|
observeSubagentsToolResult: (toolName, details, options) => this.host.observeSubagentsToolResult(toolName, details, options),
|
|
146
159
|
observeTodoToolResult: (toolName, details, isError) => this.host.observeTodoToolResult(toolName, details, isError),
|
|
147
160
|
isCancelled: options.isCancelled,
|
|
148
|
-
render: () => { },
|
|
149
|
-
lazyOlderHistory
|
|
161
|
+
render: lazyOlderHistory ? options.render : () => { },
|
|
162
|
+
lazyOlderHistory,
|
|
163
|
+
olderMessagesReader: lazyOlderHistory ? sessionHistoryOlderMessagesReader(runtime.session) : undefined,
|
|
164
|
+
onOlderLoaderReady: (loader) => {
|
|
165
|
+
if (!options.isCancelled())
|
|
166
|
+
this.olderHistoryLoader = loader;
|
|
167
|
+
},
|
|
150
168
|
});
|
|
151
169
|
if (!loaded)
|
|
152
170
|
return false;
|
|
171
|
+
if (!historyEntries)
|
|
172
|
+
return !options.isCancelled();
|
|
153
173
|
this.historyEntries = historyEntries;
|
|
154
174
|
this.setHistoryWindowStart(this.maxHistoryWindowStart());
|
|
155
175
|
options.render();
|
|
@@ -112,11 +112,17 @@ async function externalOlderMessagesBatch(olderMessagesReader, chunkSize) {
|
|
|
112
112
|
}
|
|
113
113
|
function expandedTailStart(messages, initialStart) {
|
|
114
114
|
let start = initialStart;
|
|
115
|
+
const firstMessage = messages[start];
|
|
116
|
+
if (!isRecord(firstMessage) || (firstMessage.role !== "assistant" && firstMessage.role !== "toolResult"))
|
|
117
|
+
return start;
|
|
118
|
+
// A fixed-size tail can otherwise begin inside a long assistant/tool turn,
|
|
119
|
+
// hiding the user prompt that owns the first visible response. Keep that turn
|
|
120
|
+
// atomic so reaching the top never stops at an orphaned assistant block.
|
|
115
121
|
while (start > 0) {
|
|
122
|
+
start -= 1;
|
|
116
123
|
const message = messages[start];
|
|
117
|
-
if (
|
|
124
|
+
if (isRecord(message) && message.role === "user")
|
|
118
125
|
break;
|
|
119
|
-
start -= 1;
|
|
120
126
|
}
|
|
121
127
|
return start;
|
|
122
128
|
}
|
|
@@ -55,9 +55,15 @@ export type BindCurrentSessionOptions = {
|
|
|
55
55
|
export declare class AppSessionLifecycleController {
|
|
56
56
|
private readonly host;
|
|
57
57
|
private unsubscribe;
|
|
58
|
+
private ownershipRuntime;
|
|
59
|
+
private ownershipSession;
|
|
60
|
+
private ownershipGeneration;
|
|
61
|
+
private subscriptionGeneration;
|
|
62
|
+
private replacementHistoryGeneration;
|
|
58
63
|
private extensionBindPromise;
|
|
59
64
|
private extensionBindRuntime;
|
|
60
65
|
private extensionBindSession;
|
|
66
|
+
private extensionBindOwnershipGeneration;
|
|
61
67
|
constructor(host: AppSessionLifecycleHost);
|
|
62
68
|
start(): Promise<void>;
|
|
63
69
|
bindCurrentSession(options?: BindCurrentSessionOptions): Promise<void>;
|
|
@@ -70,6 +76,7 @@ export declare class AppSessionLifecycleController {
|
|
|
70
76
|
requireRuntime(): AgentSessionRuntime;
|
|
71
77
|
private bindSessionExtensions;
|
|
72
78
|
private collectAvailabilityIssues;
|
|
79
|
+
private advanceOwnership;
|
|
73
80
|
private isCurrentRuntimeSession;
|
|
74
81
|
private extensionUiScope;
|
|
75
82
|
}
|