pi-ui-extend 0.1.70 → 0.1.72
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.d.ts +2 -0
- package/dist/app/app.js +39 -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/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- 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/rendering/render-controller.js +1 -0
- package/dist/app/rendering/status-line-renderer.d.ts +3 -1
- package/dist/app/rendering/status-line-renderer.js +11 -0
- package/dist/app/screen/mouse-controller.d.ts +5 -1
- package/dist/app/screen/mouse-controller.js +14 -2
- 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/types.d.ts +10 -0
- 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/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();
|
|
@@ -268,6 +268,7 @@ export class AppRenderController {
|
|
|
268
268
|
this.deps.mouseController.statusContextTarget = this.deps.statusLineRenderer.contextTarget(statusLayout.text, statusRow, statusLayout);
|
|
269
269
|
this.deps.mouseController.statusModelUsageTarget = this.deps.statusLineRenderer.modelUsageTarget(statusLayout.text, statusRow, statusLayout);
|
|
270
270
|
this.deps.mouseController.statusDraftQueueTarget = this.deps.statusLineRenderer.draftQueueTarget?.(statusLayout, statusRow);
|
|
271
|
+
this.deps.mouseController.statusInternalClipboardTarget = this.deps.statusLineRenderer.internalClipboardTarget?.(statusLayout, statusRow);
|
|
271
272
|
this.deps.mouseController.statusUserJumpTarget = this.deps.statusLineRenderer.userJumpTarget?.(statusLayout, statusRow);
|
|
272
273
|
this.deps.mouseController.statusThinkingExpandTarget = this.deps.statusLineRenderer.thinkingExpandTarget?.(statusLayout, statusRow);
|
|
273
274
|
this.deps.mouseController.statusCompactToolsTarget = this.deps.statusLineRenderer.compactToolsTarget?.(statusLayout, statusRow);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { Theme } from "../../theme.js";
|
|
3
|
-
import type { SessionActivity, StatusCompactToolsTarget, StatusContextTarget, StatusDraftQueueTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
|
|
3
|
+
import type { SessionActivity, StatusCompactToolsTarget, StatusContextTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
|
|
4
4
|
import type { ScreenStyler } from "../screen/screen-styler.js";
|
|
5
5
|
import { type ModelColorsConfig } from "../../config.js";
|
|
6
6
|
export type StatusLineRendererHost = {
|
|
@@ -31,6 +31,7 @@ export type StatusLineRendererHost = {
|
|
|
31
31
|
down: boolean;
|
|
32
32
|
};
|
|
33
33
|
queueableInputActive?(): boolean;
|
|
34
|
+
internalClipboardActive?(): boolean;
|
|
34
35
|
userMessageJumpMenuActive?(): boolean;
|
|
35
36
|
allThinkingExpandedActive?(): boolean;
|
|
36
37
|
superCompactToolsActive?(): boolean;
|
|
@@ -52,6 +53,7 @@ export declare class StatusLineRenderer {
|
|
|
52
53
|
voiceLanguageTarget(layout: StatusLineLayout, row: number): StatusVoiceLanguageTarget | undefined;
|
|
53
54
|
userJumpTarget(layout: StatusLineLayout, row: number): StatusUserJumpTarget | undefined;
|
|
54
55
|
draftQueueTarget(layout: StatusLineLayout, row: number): StatusDraftQueueTarget | undefined;
|
|
56
|
+
internalClipboardTarget(layout: StatusLineLayout, row: number): StatusInternalClipboardTarget | undefined;
|
|
55
57
|
thinkingExpandTarget(layout: StatusLineLayout, row: number): StatusThinkingExpandTarget | undefined;
|
|
56
58
|
compactToolsTarget(layout: StatusLineLayout, row: number): StatusCompactToolsTarget | undefined;
|
|
57
59
|
quickScrollUpTarget(layout: StatusLineLayout, row: number): StatusQuickScrollTarget | undefined;
|
|
@@ -65,6 +65,9 @@ export class StatusLineRenderer {
|
|
|
65
65
|
appendWidget(draftQueueButton ? this.iconButtonText(draftQueueButton) : "", (column, text) => {
|
|
66
66
|
layout.draftQueueWidget = this.widgetLayout(column, text);
|
|
67
67
|
});
|
|
68
|
+
appendWidget(this.iconButtonText(APP_ICONS.clipboardPaste), (column, text) => {
|
|
69
|
+
layout.internalClipboardWidget = this.widgetLayout(column, text);
|
|
70
|
+
});
|
|
68
71
|
const promptEnhancerWidgetText = this.host.promptEnhancerStatusWidgetText();
|
|
69
72
|
appendWidget(promptEnhancerWidgetText ? this.iconButtonText(promptEnhancerWidgetText) : "", (column, text) => {
|
|
70
73
|
layout.promptEnhancerWidget = this.widgetLayout(column, text);
|
|
@@ -141,6 +144,7 @@ export class StatusLineRenderer {
|
|
|
141
144
|
});
|
|
142
145
|
};
|
|
143
146
|
pushWidgetSegment(layout.draftQueueWidget, colors.info);
|
|
147
|
+
pushWidgetSegment(layout.internalClipboardWidget, this.host.internalClipboardActive?.() ? colors.info : colors.muted);
|
|
144
148
|
pushWidgetSegment(layout.promptEnhancerWidget, this.host.promptEnhancerStatusWidgetActive()
|
|
145
149
|
? colors.warning
|
|
146
150
|
: this.host.promptEnhancerStatusWidgetEnabled()
|
|
@@ -251,6 +255,12 @@ export class StatusLineRenderer {
|
|
|
251
255
|
return undefined;
|
|
252
256
|
return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
|
|
253
257
|
}
|
|
258
|
+
internalClipboardTarget(layout, row) {
|
|
259
|
+
const widget = layout.internalClipboardWidget;
|
|
260
|
+
if (!widget)
|
|
261
|
+
return undefined;
|
|
262
|
+
return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
|
|
263
|
+
}
|
|
254
264
|
thinkingExpandTarget(layout, row) {
|
|
255
265
|
const widget = layout.thinkingExpandWidget;
|
|
256
266
|
if (!widget)
|
|
@@ -339,6 +349,7 @@ export class StatusLineRenderer {
|
|
|
339
349
|
const colors = this.host.theme.colors;
|
|
340
350
|
const widgets = [
|
|
341
351
|
{ widget: layout.draftQueueWidget, foreground: colors.info },
|
|
352
|
+
{ widget: layout.internalClipboardWidget, foreground: this.host.internalClipboardActive?.() ? colors.info : colors.muted },
|
|
342
353
|
{ widget: layout.promptEnhancerWidget, foreground: this.host.promptEnhancerStatusWidgetActive()
|
|
343
354
|
? colors.warning
|
|
344
355
|
: this.host.promptEnhancerStatusWidgetEnabled()
|
|
@@ -6,7 +6,7 @@ import type { ToastEntry, ToastVariant } from "../../ui.js";
|
|
|
6
6
|
import type { AppPopupActionController } from "../popup/popup-action-controller.js";
|
|
7
7
|
import type { AppPopupMenuController } from "../popup/popup-menu-controller.js";
|
|
8
8
|
import type { AppScrollController } from "./scroll-controller.js";
|
|
9
|
-
import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusContextTarget, StatusCompactToolsTarget, StatusDraftQueueTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
|
|
9
|
+
import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusContextTarget, StatusCompactToolsTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
|
|
10
10
|
import { type RenderedLink } from "./file-links.js";
|
|
11
11
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
type ClickFlash = {
|
|
@@ -56,6 +56,8 @@ export type AppMouseControllerHost = {
|
|
|
56
56
|
refreshModelUsageStatus(): void | Promise<void>;
|
|
57
57
|
refreshUserMessageJumpMenuItems?(): Promise<void>;
|
|
58
58
|
queueInputFromStatus?(): void | Promise<void>;
|
|
59
|
+
pasteInternalClipboard?(): void;
|
|
60
|
+
setInternalClipboardText?(text: string): void;
|
|
59
61
|
scrollConversationQuick(direction: "up" | "down"): void | Promise<void>;
|
|
60
62
|
toggleAllThinkingExpanded?(): void;
|
|
61
63
|
toggleSuperCompactTools?(): void;
|
|
@@ -102,6 +104,7 @@ export declare class AppMouseController {
|
|
|
102
104
|
statusModelUsageTarget: StatusModelUsageTarget | undefined;
|
|
103
105
|
statusUserJumpTarget: StatusUserJumpTarget | undefined;
|
|
104
106
|
statusDraftQueueTarget: StatusDraftQueueTarget | undefined;
|
|
107
|
+
statusInternalClipboardTarget: StatusInternalClipboardTarget | undefined;
|
|
105
108
|
statusThinkingExpandTarget: StatusThinkingExpandTarget | undefined;
|
|
106
109
|
statusCompactToolsTarget: StatusCompactToolsTarget | undefined;
|
|
107
110
|
statusQuickScrollUpTarget: StatusQuickScrollTarget | undefined;
|
|
@@ -161,6 +164,7 @@ export declare class AppMouseController {
|
|
|
161
164
|
private handleInputBorderStatusClick;
|
|
162
165
|
private openStatusUserJumpMenu;
|
|
163
166
|
private handleStatusDraftQueueClick;
|
|
167
|
+
private handleStatusInternalClipboardClick;
|
|
164
168
|
private handleStatusThinkingExpandClick;
|
|
165
169
|
private handleStatusCompactToolsClick;
|
|
166
170
|
private handleStatusTerminalBellSoundClick;
|
|
@@ -26,6 +26,7 @@ export class AppMouseController {
|
|
|
26
26
|
statusModelUsageTarget;
|
|
27
27
|
statusUserJumpTarget;
|
|
28
28
|
statusDraftQueueTarget;
|
|
29
|
+
statusInternalClipboardTarget;
|
|
29
30
|
statusThinkingExpandTarget;
|
|
30
31
|
statusCompactToolsTarget;
|
|
31
32
|
statusQuickScrollUpTarget;
|
|
@@ -317,6 +318,7 @@ export class AppMouseController {
|
|
|
317
318
|
this.statusContextTarget,
|
|
318
319
|
this.statusModelUsageTarget,
|
|
319
320
|
this.statusDraftQueueTarget,
|
|
321
|
+
this.statusInternalClipboardTarget,
|
|
320
322
|
this.statusUserJumpTarget,
|
|
321
323
|
this.statusThinkingExpandTarget,
|
|
322
324
|
this.statusCompactToolsTarget,
|
|
@@ -496,6 +498,7 @@ export class AppMouseController {
|
|
|
496
498
|
}
|
|
497
499
|
handleInputBorderStatusClick(event) {
|
|
498
500
|
return this.handleStatusDraftQueueClick(event)
|
|
501
|
+
|| this.handleStatusInternalClipboardClick(event)
|
|
499
502
|
|| this.handleStatusUserJumpClick(event)
|
|
500
503
|
|| this.handleStatusThinkingExpandClick(event)
|
|
501
504
|
|| this.handleStatusCompactToolsClick(event)
|
|
@@ -525,6 +528,12 @@ export class AppMouseController {
|
|
|
525
528
|
void this.host.queueInputFromStatus?.();
|
|
526
529
|
return true;
|
|
527
530
|
}
|
|
531
|
+
handleStatusInternalClipboardClick(event) {
|
|
532
|
+
if (!this.statusTargetContains(this.statusInternalClipboardTarget, event))
|
|
533
|
+
return false;
|
|
534
|
+
this.host.pasteInternalClipboard?.();
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
528
537
|
handleStatusThinkingExpandClick(event) {
|
|
529
538
|
if (!this.statusTargetContains(this.statusThinkingExpandTarget, event))
|
|
530
539
|
return false;
|
|
@@ -682,9 +691,12 @@ export class AppMouseController {
|
|
|
682
691
|
return false;
|
|
683
692
|
}
|
|
684
693
|
const selectedText = this.getSelectedText(selection);
|
|
685
|
-
|
|
686
|
-
|
|
694
|
+
if (selectedText.trim().length === 0) {
|
|
695
|
+
this.host.render();
|
|
687
696
|
return true;
|
|
697
|
+
}
|
|
698
|
+
this.host.setInternalClipboardText?.(selectedText);
|
|
699
|
+
this.host.render();
|
|
688
700
|
try {
|
|
689
701
|
this.copyTextToClipboard(selectedText);
|
|
690
702
|
this.host.showToast("Copied to clipboard", "success");
|
|
@@ -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
|
}
|