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
package/dist/app/app.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare class PiUiExtendApp {
|
|
|
62
62
|
private subagentsPanelExpanded;
|
|
63
63
|
private allThinkingExpanded;
|
|
64
64
|
private superCompactTools;
|
|
65
|
+
private internalClipboardText;
|
|
65
66
|
private voicePartialText;
|
|
66
67
|
private resumeSessions;
|
|
67
68
|
private resumeLoading;
|
|
@@ -86,6 +87,7 @@ export declare class PiUiExtendApp {
|
|
|
86
87
|
private restoreTabInputState;
|
|
87
88
|
private clearPersistedInputDraft;
|
|
88
89
|
private insertVoiceTranscript;
|
|
90
|
+
private pasteInternalClipboard;
|
|
89
91
|
private setVoicePartialTranscript;
|
|
90
92
|
private addVoiceSystemMessage;
|
|
91
93
|
private resetSessionView;
|
package/dist/app/app.js
CHANGED
|
@@ -132,6 +132,7 @@ export class PiUiExtendApp {
|
|
|
132
132
|
subagentsPanelExpanded = true;
|
|
133
133
|
allThinkingExpanded = false;
|
|
134
134
|
superCompactTools = false;
|
|
135
|
+
internalClipboardText;
|
|
135
136
|
voicePartialText;
|
|
136
137
|
resumeSessions = [];
|
|
137
138
|
resumeLoading = false;
|
|
@@ -221,6 +222,7 @@ export class PiUiExtendApp {
|
|
|
221
222
|
runtime: () => this.runtime,
|
|
222
223
|
inputEditor: () => this.inputEditor,
|
|
223
224
|
activeInputTabId: () => this.tabsController.activeInputTabId(),
|
|
225
|
+
isInputTabOwnedByRuntime: (tabId, runtime, session) => this.tabsController.inputTabOwnedByRuntime(tabId, runtime, session),
|
|
224
226
|
inputStateForTab: (tabId) => this.tabsController.inputStateForTab(tabId),
|
|
225
227
|
setInputStateForTab: (tabId, state) => this.tabsController.setInputStateForTab(tabId, state),
|
|
226
228
|
promptEnhancerConfig: () => this.pixConfig.promptEnhancer,
|
|
@@ -239,6 +241,7 @@ export class PiUiExtendApp {
|
|
|
239
241
|
render: () => this.render(),
|
|
240
242
|
});
|
|
241
243
|
this.voiceController = new AppVoiceController({
|
|
244
|
+
activeInputScope: () => this.tabsController.activeInputTabId(),
|
|
242
245
|
insertTranscript: (text) => this.insertVoiceTranscript(text),
|
|
243
246
|
setPartialTranscript: (text) => this.setVoicePartialTranscript(text),
|
|
244
247
|
addSystemMessage: (message) => this.addVoiceSystemMessage(message),
|
|
@@ -308,6 +311,7 @@ export class PiUiExtendApp {
|
|
|
308
311
|
voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
|
|
309
312
|
conversationQuickScrollDirections: () => this.conversationQuickScrollDirections(),
|
|
310
313
|
queueableInputActive: () => this.inputEditor.promptText.trimEnd().length > 0 || this.inputEditor.images.length > 0,
|
|
314
|
+
internalClipboardActive: () => Boolean(this.internalClipboardText),
|
|
311
315
|
userMessageJumpMenuActive: () => this.popupMenus.directMenu === "user-message-jump",
|
|
312
316
|
allThinkingExpandedActive: () => this.allThinkingExpanded,
|
|
313
317
|
superCompactToolsActive: () => this.superCompactTools,
|
|
@@ -320,6 +324,7 @@ export class PiUiExtendApp {
|
|
|
320
324
|
this.extensionUiController = new ExtensionUiController({
|
|
321
325
|
theme: this.theme,
|
|
322
326
|
activeExtensionUiScope: () => this.activeExtensionUiScope(),
|
|
327
|
+
isExtensionUiScopeActive: (scopeKey) => !this.tabsController.isSwitching() && this.activeExtensionUiScope() === scopeKey,
|
|
323
328
|
isRunning: () => this.running,
|
|
324
329
|
render: () => this.render(),
|
|
325
330
|
showToast: (message, kind, options) => this.showToast(message, kind, options),
|
|
@@ -335,6 +340,7 @@ export class PiUiExtendApp {
|
|
|
335
340
|
});
|
|
336
341
|
this.extensionActions = new AppExtensionActionsController({
|
|
337
342
|
isRunning: () => this.running,
|
|
343
|
+
runtime: () => this.runtime,
|
|
338
344
|
getInput: () => this.input,
|
|
339
345
|
setInput: (value) => this.setInput(value),
|
|
340
346
|
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
@@ -430,6 +436,7 @@ export class PiUiExtendApp {
|
|
|
430
436
|
setInput: (value) => this.inputEditor.setText(value),
|
|
431
437
|
insertInput: (value) => this.inputEditor.insert(value),
|
|
432
438
|
attachImage: (data, mimeType) => this.inputEditor.attachImage(data, mimeType),
|
|
439
|
+
requeueAutoUserMessageForSession: (session, message) => this.tabsController.requeueAutoUserMessageForSession(session, message),
|
|
433
440
|
onDeferredUserMessagesChanged: () => this.tabsController.persistActiveDeferredUserMessages(),
|
|
434
441
|
});
|
|
435
442
|
this.editorLayoutRenderer = new EditorLayoutRenderer({
|
|
@@ -608,6 +615,10 @@ export class PiUiExtendApp {
|
|
|
608
615
|
this.render();
|
|
609
616
|
});
|
|
610
617
|
},
|
|
618
|
+
pasteInternalClipboard: () => this.pasteInternalClipboard(),
|
|
619
|
+
setInternalClipboardText: (text) => {
|
|
620
|
+
this.internalClipboardText = text;
|
|
621
|
+
},
|
|
611
622
|
scrollConversationQuick: (direction) => this.scrollConversationQuick(direction),
|
|
612
623
|
toggleAllThinkingExpanded: () => {
|
|
613
624
|
this.allThinkingExpanded = !this.allThinkingExpanded;
|
|
@@ -648,6 +659,7 @@ export class PiUiExtendApp {
|
|
|
648
659
|
this.shellController = new AppShellController({
|
|
649
660
|
cwd: this.options.cwd,
|
|
650
661
|
isRunning: () => this.running,
|
|
662
|
+
activeScopeKey: () => this.tabsController.activeInputTabId(),
|
|
651
663
|
addEntry: (entry) => this.addEntry(entry),
|
|
652
664
|
touchEntry: (entry) => this.touchEntry(entry),
|
|
653
665
|
setStatus: (status) => this.setStatus(status),
|
|
@@ -658,6 +670,7 @@ export class PiUiExtendApp {
|
|
|
658
670
|
});
|
|
659
671
|
this.inputActions = new AppInputActionController({
|
|
660
672
|
runtime: () => this.runtime,
|
|
673
|
+
inputScopeKey: () => this.tabsController.activeInputTabId(),
|
|
661
674
|
isRunning: () => this.running,
|
|
662
675
|
isSessionSwitching: () => this.tabsController.isSwitching(),
|
|
663
676
|
inputEditor: () => this.inputEditor,
|
|
@@ -688,6 +701,7 @@ export class PiUiExtendApp {
|
|
|
688
701
|
this.inputController = new AppInputController({
|
|
689
702
|
inputEditor: this.inputEditor,
|
|
690
703
|
cwd: this.options.cwd,
|
|
704
|
+
inputScopeKey: () => this.tabsController.activeInputTabId(),
|
|
691
705
|
handleExtensionTerminalInput: (data) => this.extensionUiController.handleTerminalInput(data),
|
|
692
706
|
extensionInputUsesEditor: () => this.extensionUiController.activeCustomUiUsesEditor(),
|
|
693
707
|
getInput: () => this.input,
|
|
@@ -718,6 +732,7 @@ export class PiUiExtendApp {
|
|
|
718
732
|
setRunning: (running) => {
|
|
719
733
|
this.running = running;
|
|
720
734
|
},
|
|
735
|
+
cancelPendingTabLifecycle: () => this.tabsController.cancelPendingLifecycleWork(),
|
|
721
736
|
runtime: () => this.runtime,
|
|
722
737
|
saveInputStateForQuit: () => this.tabsController.saveInputStateForQuit(),
|
|
723
738
|
disposeInactiveRuntimesForQuit: () => this.tabsController.disposeInactiveRuntimes((runtime) => this.terminalController.disposeRuntimeForQuit(runtime)),
|
|
@@ -783,6 +798,7 @@ export class PiUiExtendApp {
|
|
|
783
798
|
this.mouseController.statusContextTarget = undefined;
|
|
784
799
|
this.mouseController.statusModelUsageTarget = undefined;
|
|
785
800
|
this.mouseController.statusDraftQueueTarget = undefined;
|
|
801
|
+
this.mouseController.statusInternalClipboardTarget = undefined;
|
|
786
802
|
this.mouseController.statusUserJumpTarget = undefined;
|
|
787
803
|
this.mouseController.statusThinkingExpandTarget = undefined;
|
|
788
804
|
this.mouseController.statusCompactToolsTarget = undefined;
|
|
@@ -874,11 +890,17 @@ export class PiUiExtendApp {
|
|
|
874
890
|
await this.sessionLifecycle.awaitCurrentSessionExtensions(runtime);
|
|
875
891
|
}
|
|
876
892
|
async activateRuntime(runtime, options) {
|
|
893
|
+
this.extensionUiController.cancelCustomUi(this.activeExtensionUiScope());
|
|
894
|
+
void this.voiceController.stopRecording();
|
|
877
895
|
this.runtime = runtime;
|
|
878
896
|
runtime.setRebindSession(async () => {
|
|
897
|
+
if (!this.running || this.runtime !== runtime)
|
|
898
|
+
return;
|
|
879
899
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
880
900
|
});
|
|
881
901
|
runtime.setBeforeSessionInvalidate(() => {
|
|
902
|
+
if (!this.running || this.runtime !== runtime)
|
|
903
|
+
return;
|
|
882
904
|
this.extensionUiController.clearWidgets(this.activeExtensionUiScope());
|
|
883
905
|
});
|
|
884
906
|
await this.bindCurrentSession(options);
|
|
@@ -929,7 +951,7 @@ export class PiUiExtendApp {
|
|
|
929
951
|
restoreTabInputState(state) {
|
|
930
952
|
this.requestHistory.resetNavigation();
|
|
931
953
|
this.popupMenus.resetInputMenuDismissals();
|
|
932
|
-
this.inputEditor.
|
|
954
|
+
this.inputEditor.restoreDraftState(state);
|
|
933
955
|
this.autocompleteController.dispose();
|
|
934
956
|
}
|
|
935
957
|
async clearPersistedInputDraft() {
|
|
@@ -951,6 +973,16 @@ export class PiUiExtendApp {
|
|
|
951
973
|
this.inputEditor.insert(`${prefix}${transcript}${suffix}`);
|
|
952
974
|
this.render();
|
|
953
975
|
}
|
|
976
|
+
pasteInternalClipboard() {
|
|
977
|
+
if (!this.internalClipboardText) {
|
|
978
|
+
this.showToast("Nothing has been copied inside Pix yet.", "info");
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
this.requestHistory.resetNavigation();
|
|
982
|
+
this.popupMenus.resetInputMenuDismissals();
|
|
983
|
+
this.inputEditor.attachPastedText(this.internalClipboardText);
|
|
984
|
+
this.render();
|
|
985
|
+
}
|
|
954
986
|
setVoicePartialTranscript(text) {
|
|
955
987
|
if (this.voicePartialText === text)
|
|
956
988
|
return;
|
|
@@ -1130,6 +1162,8 @@ export class PiUiExtendApp {
|
|
|
1130
1162
|
});
|
|
1131
1163
|
}
|
|
1132
1164
|
showToast(message, kind = "info", options) {
|
|
1165
|
+
if (!this.running)
|
|
1166
|
+
return;
|
|
1133
1167
|
this.toastController.showToast(message, kind, options);
|
|
1134
1168
|
}
|
|
1135
1169
|
toastNotifierForScope(scopeKey) {
|
|
@@ -1154,6 +1188,8 @@ export class PiUiExtendApp {
|
|
|
1154
1188
|
clearTimeout(this.scheduledRenderTimer);
|
|
1155
1189
|
this.scheduledRenderTimer = undefined;
|
|
1156
1190
|
}
|
|
1191
|
+
if (!this.running)
|
|
1192
|
+
return;
|
|
1157
1193
|
this.autocompleteController.observeInput();
|
|
1158
1194
|
this.syncScrollAfterInputEditorChange();
|
|
1159
1195
|
this.renderController.render();
|
|
@@ -1163,6 +1199,8 @@ export class PiUiExtendApp {
|
|
|
1163
1199
|
return;
|
|
1164
1200
|
this.scheduledRenderTimer = setTimeout(() => {
|
|
1165
1201
|
this.scheduledRenderTimer = undefined;
|
|
1202
|
+
if (!this.running)
|
|
1203
|
+
return;
|
|
1166
1204
|
this.syncScrollAfterInputEditorChange();
|
|
1167
1205
|
this.renderController.render();
|
|
1168
1206
|
}, COALESCED_RENDER_DELAY_MS);
|
|
@@ -52,3 +52,10 @@ export type CommandControllerHost = {
|
|
|
52
52
|
openNewTab(): Promise<void>;
|
|
53
53
|
openSearchResultInNewTab(result: SessionSearchResult): Promise<void>;
|
|
54
54
|
};
|
|
55
|
+
export type CommandScope = {
|
|
56
|
+
readonly runtime: AgentSessionRuntime | undefined;
|
|
57
|
+
readonly session: AgentSession | undefined;
|
|
58
|
+
};
|
|
59
|
+
export declare function captureCommandScope(host: CommandControllerHost): CommandScope;
|
|
60
|
+
export declare function isCommandRuntimeActive(host: CommandControllerHost, runtime: AgentSessionRuntime): boolean;
|
|
61
|
+
export declare function isCommandScopeActive(host: CommandControllerHost, scope: CommandScope): boolean;
|
|
@@ -1 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export function captureCommandScope(host) {
|
|
2
|
+
const runtime = host.runtime();
|
|
3
|
+
return { runtime, session: runtime?.session };
|
|
4
|
+
}
|
|
5
|
+
export function isCommandRuntimeActive(host, runtime) {
|
|
6
|
+
return host.isRunning() && host.runtime() === runtime;
|
|
7
|
+
}
|
|
8
|
+
export function isCommandScopeActive(host, scope) {
|
|
9
|
+
return host.isRunning() && host.runtime() === scope.runtime && scope.runtime?.session === scope.session;
|
|
10
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getIdleRuntime, getRuntime } from "./command-runtime.js";
|
|
2
|
+
import { captureCommandScope, isCommandScopeActive, } from "./command-host.js";
|
|
2
3
|
import { getProjectPixConfigPath, savePixAutocompleteModel, savePixDefaultModel, savePixDefaultThinking, saveProjectPixIgnoreContextFiles } from "../../config.js";
|
|
3
4
|
import { createId } from "../id.js";
|
|
4
5
|
import { isThinkingLevel, parseScopedModelRef } from "../model/model-ref.js";
|
|
@@ -41,6 +42,7 @@ export class ModelCommandActions {
|
|
|
41
42
|
this.host.setSessionStatus(runtime.session);
|
|
42
43
|
}
|
|
43
44
|
async runModelSlashCommand(argumentsText) {
|
|
45
|
+
const scope = captureCommandScope(this.host);
|
|
44
46
|
const modelRef = argumentsText.trim();
|
|
45
47
|
if (!modelRef) {
|
|
46
48
|
const selected = await this.host.showMenu(this.host.getModelMenuItems(""), {
|
|
@@ -48,6 +50,8 @@ export class ModelCommandActions {
|
|
|
48
50
|
placeholder: "Search models",
|
|
49
51
|
emptyText: "No matching models",
|
|
50
52
|
});
|
|
53
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
54
|
+
return;
|
|
51
55
|
if (!selected) {
|
|
52
56
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
53
57
|
this.host.render();
|
|
@@ -64,10 +68,14 @@ export class ModelCommandActions {
|
|
|
64
68
|
if (!parsed)
|
|
65
69
|
throw new Error("Model must use provider/model[:thinking] format");
|
|
66
70
|
await runtime.services.modelRuntime.reloadConfig();
|
|
71
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
72
|
+
return;
|
|
67
73
|
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
68
74
|
if (!model)
|
|
69
75
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
70
76
|
await this.runModelCommand(model);
|
|
77
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
78
|
+
return;
|
|
71
79
|
if (parsed.thinkingLevel !== undefined) {
|
|
72
80
|
runtime.session.setThinkingLevel(parsed.thinkingLevel);
|
|
73
81
|
this.addPersistentSystemEntry(runtime.session, `Selected thinking level ${runtime.session.thinkingLevel}`);
|
|
@@ -75,6 +83,7 @@ export class ModelCommandActions {
|
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
async runDefaultModelSlashCommand(argumentsText) {
|
|
86
|
+
const scope = captureCommandScope(this.host);
|
|
78
87
|
const modelRef = argumentsText.trim();
|
|
79
88
|
if (!modelRef) {
|
|
80
89
|
const selected = await this.host.showMenu(this.host.getModelMenuItems(""), {
|
|
@@ -82,6 +91,8 @@ export class ModelCommandActions {
|
|
|
82
91
|
placeholder: "Search models",
|
|
83
92
|
emptyText: "No matching models",
|
|
84
93
|
});
|
|
94
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
95
|
+
return;
|
|
85
96
|
if (!selected) {
|
|
86
97
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
87
98
|
this.host.render();
|
|
@@ -98,6 +109,8 @@ export class ModelCommandActions {
|
|
|
98
109
|
if (!parsed)
|
|
99
110
|
throw new Error("Model must use provider/model[:thinking] format");
|
|
100
111
|
await runtime.services.modelRuntime.reloadConfig();
|
|
112
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
113
|
+
return;
|
|
101
114
|
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
102
115
|
if (!model)
|
|
103
116
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
@@ -105,6 +118,7 @@ export class ModelCommandActions {
|
|
|
105
118
|
this.host.setSessionStatus(runtime.session);
|
|
106
119
|
}
|
|
107
120
|
async runAutocompleteSlashCommand(argumentsText) {
|
|
121
|
+
const scope = captureCommandScope(this.host);
|
|
108
122
|
const modelRef = argumentsText.trim();
|
|
109
123
|
if (!modelRef) {
|
|
110
124
|
const saved = savePixAutocompleteModel("");
|
|
@@ -119,6 +133,8 @@ export class ModelCommandActions {
|
|
|
119
133
|
if (!parsed)
|
|
120
134
|
throw new Error("Model must use provider/model[:thinking] format, or run /autocomplete with no arguments to disable");
|
|
121
135
|
await runtime.services.modelRuntime.reloadConfig();
|
|
136
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
137
|
+
return;
|
|
122
138
|
const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
123
139
|
if (!model)
|
|
124
140
|
throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
|
|
@@ -161,6 +177,7 @@ export class ModelCommandActions {
|
|
|
161
177
|
const runtime = getIdleRuntime(this.host, "scoped-models");
|
|
162
178
|
if (!runtime)
|
|
163
179
|
return;
|
|
180
|
+
const scope = captureCommandScope(this.host);
|
|
164
181
|
const value = argumentsText.trim();
|
|
165
182
|
if (!value) {
|
|
166
183
|
const enabledModels = runtime.services.settingsManager.getEnabledModels();
|
|
@@ -200,6 +217,8 @@ export class ModelCommandActions {
|
|
|
200
217
|
const scopedModels = [];
|
|
201
218
|
const invalidRefs = [];
|
|
202
219
|
await runtime.services.modelRuntime.reloadConfig();
|
|
220
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
221
|
+
return;
|
|
203
222
|
for (const ref of refs) {
|
|
204
223
|
const parsed = parseScopedModelRef(ref);
|
|
205
224
|
const model = parsed ? runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId) : undefined;
|
|
@@ -219,6 +238,7 @@ export class ModelCommandActions {
|
|
|
219
238
|
this.host.setSessionStatus(runtime.session);
|
|
220
239
|
}
|
|
221
240
|
async runThinkingSlashCommand(argumentsText) {
|
|
241
|
+
const scope = captureCommandScope(this.host);
|
|
222
242
|
const level = argumentsText.trim();
|
|
223
243
|
if (!level) {
|
|
224
244
|
const selected = await this.host.showMenu(this.host.getThinkingMenuItems(""), {
|
|
@@ -226,6 +246,8 @@ export class ModelCommandActions {
|
|
|
226
246
|
placeholder: "Search thinking levels",
|
|
227
247
|
emptyText: "No matching thinking levels",
|
|
228
248
|
});
|
|
249
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
250
|
+
return;
|
|
229
251
|
if (!selected) {
|
|
230
252
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
231
253
|
this.host.render();
|
|
@@ -240,6 +262,7 @@ export class ModelCommandActions {
|
|
|
240
262
|
await this.runThinkingCommand(level);
|
|
241
263
|
}
|
|
242
264
|
async runDefaultThinkingSlashCommand(argumentsText) {
|
|
265
|
+
const scope = captureCommandScope(this.host);
|
|
243
266
|
const level = argumentsText.trim();
|
|
244
267
|
if (!level) {
|
|
245
268
|
const selected = await this.host.showMenu(this.host.getThinkingMenuItems(""), {
|
|
@@ -247,6 +270,8 @@ export class ModelCommandActions {
|
|
|
247
270
|
placeholder: "Search thinking levels",
|
|
248
271
|
emptyText: "No matching thinking levels",
|
|
249
272
|
});
|
|
273
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
274
|
+
return;
|
|
250
275
|
if (!selected) {
|
|
251
276
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
252
277
|
this.host.render();
|
|
@@ -264,10 +289,13 @@ export class ModelCommandActions {
|
|
|
264
289
|
const runtime = getRuntime(this.host, "model");
|
|
265
290
|
if (!runtime)
|
|
266
291
|
return;
|
|
292
|
+
const scope = captureCommandScope(this.host);
|
|
267
293
|
const ref = this.host.modelRef(model);
|
|
268
294
|
this.host.setStatus(`selecting model ${ref}`);
|
|
269
295
|
this.host.render();
|
|
270
296
|
await runtime.session.setModel(model);
|
|
297
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
298
|
+
return;
|
|
271
299
|
this.host.addEntry({ id: createId("system"), kind: "system", text: `Selected model ${ref}` });
|
|
272
300
|
if (runtime.session.isStreaming) {
|
|
273
301
|
this.host.addEntry({
|
|
@@ -279,7 +307,9 @@ export class ModelCommandActions {
|
|
|
279
307
|
this.host.setSessionStatus(runtime.session);
|
|
280
308
|
return;
|
|
281
309
|
}
|
|
282
|
-
await this.reloadAfterModelChange(runtime.session, ref);
|
|
310
|
+
await this.reloadAfterModelChange(runtime.session, ref, scope);
|
|
311
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
312
|
+
return;
|
|
283
313
|
this.host.setSessionStatus(runtime.session);
|
|
284
314
|
}
|
|
285
315
|
async runThinkingCommand(level) {
|
|
@@ -296,11 +326,13 @@ export class ModelCommandActions {
|
|
|
296
326
|
appendPixSystemDisplayEntry(session, text);
|
|
297
327
|
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
298
328
|
}
|
|
299
|
-
async reloadAfterModelChange(session, ref) {
|
|
329
|
+
async reloadAfterModelChange(session, ref, scope) {
|
|
300
330
|
this.host.setStatus(`reloading resources for ${ref}`);
|
|
301
331
|
this.host.render();
|
|
302
332
|
try {
|
|
303
333
|
await session.reload();
|
|
334
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
335
|
+
return;
|
|
304
336
|
this.host.addEntry({
|
|
305
337
|
id: createId("system"),
|
|
306
338
|
kind: "system",
|
|
@@ -309,6 +341,8 @@ export class ModelCommandActions {
|
|
|
309
341
|
this.host.toast.success("Model changed and resources reloaded");
|
|
310
342
|
}
|
|
311
343
|
catch (error) {
|
|
344
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
345
|
+
return;
|
|
312
346
|
this.host.addEntry({
|
|
313
347
|
id: createId("error"),
|
|
314
348
|
kind: "error",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type
|
|
2
|
+
import { type CommandControllerHost } from "./command-host.js";
|
|
3
3
|
import { type ResumeSessionLoaderOptions } from "../session/resume-session-loader.js";
|
|
4
4
|
import type { PopupMenuPlacement } from "../types.js";
|
|
5
5
|
export declare function formatHistoryMenuLabel(text: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { captureCommandScope, isCommandRuntimeActive, isCommandScopeActive, } from "./command-host.js";
|
|
2
3
|
import { getIdleRuntime, getRuntime } from "./command-runtime.js";
|
|
3
4
|
import { createId } from "../id.js";
|
|
4
5
|
import { isRecord } from "../guards.js";
|
|
@@ -51,7 +52,11 @@ export class NavigationCommandActions {
|
|
|
51
52
|
this.host.setStatus("forking session");
|
|
52
53
|
this.host.render();
|
|
53
54
|
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
55
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
56
|
+
return;
|
|
54
57
|
const result = await runtime.fork(entryId);
|
|
58
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
59
|
+
return;
|
|
55
60
|
if (result.cancelled) {
|
|
56
61
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Fork cancelled." });
|
|
57
62
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -76,7 +81,11 @@ export class NavigationCommandActions {
|
|
|
76
81
|
this.host.setStatus("cloning session");
|
|
77
82
|
this.host.render();
|
|
78
83
|
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
84
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
85
|
+
return;
|
|
79
86
|
const result = await runtime.fork(leafId, { position: "at" });
|
|
87
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
88
|
+
return;
|
|
80
89
|
if (result.cancelled) {
|
|
81
90
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Clone cancelled." });
|
|
82
91
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -96,7 +105,10 @@ export class NavigationCommandActions {
|
|
|
96
105
|
}
|
|
97
106
|
this.host.setStatus("navigating tree");
|
|
98
107
|
this.host.render();
|
|
108
|
+
const scope = captureCommandScope(this.host);
|
|
99
109
|
const result = await runtime.session.navigateTree(targetId);
|
|
110
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
111
|
+
return;
|
|
100
112
|
if (result.aborted) {
|
|
101
113
|
this.host.toast.info("Tree navigation cancelled");
|
|
102
114
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -118,6 +130,7 @@ export class NavigationCommandActions {
|
|
|
118
130
|
const runtime = getRuntime(this.host, "jump");
|
|
119
131
|
if (!runtime)
|
|
120
132
|
return;
|
|
133
|
+
const scope = captureCommandScope(this.host);
|
|
121
134
|
this.host.openDirectPopupMenu("user-message-jump", { preserveStatus: true });
|
|
122
135
|
this.host.setDirectPopupMenuQuery(argumentsText.trim());
|
|
123
136
|
this.host.render();
|
|
@@ -125,13 +138,17 @@ export class NavigationCommandActions {
|
|
|
125
138
|
await this.host.refreshUserMessageJumpMenuItems();
|
|
126
139
|
}
|
|
127
140
|
catch (error) {
|
|
141
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
142
|
+
return;
|
|
128
143
|
this.host.toast.error(`Could not load jump messages: ${error instanceof Error ? error.message : String(error)}`);
|
|
129
144
|
}
|
|
130
145
|
finally {
|
|
131
|
-
this.host
|
|
146
|
+
if (isCommandScopeActive(this.host, scope))
|
|
147
|
+
this.host.render();
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
async runHistoryCommand(argumentsText) {
|
|
151
|
+
const scope = captureCommandScope(this.host);
|
|
135
152
|
const query = argumentsText.trim();
|
|
136
153
|
const matches = this.host.requestHistory().searchMatches(query, 100);
|
|
137
154
|
if (matches.length === 0) {
|
|
@@ -157,6 +174,8 @@ export class NavigationCommandActions {
|
|
|
157
174
|
minScorePerCharacter: 8,
|
|
158
175
|
preferKeyboardLayoutMatches: true,
|
|
159
176
|
});
|
|
177
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
178
|
+
return;
|
|
160
179
|
if (!selected) {
|
|
161
180
|
this.host.setSessionStatus(this.host.runtime()?.session);
|
|
162
181
|
return;
|
|
@@ -170,6 +189,7 @@ export class NavigationCommandActions {
|
|
|
170
189
|
const runtime = getIdleRuntime(this.host, "search");
|
|
171
190
|
if (!runtime)
|
|
172
191
|
return;
|
|
192
|
+
const scope = captureCommandScope(this.host);
|
|
173
193
|
const query = argumentsText.trim();
|
|
174
194
|
if (!query) {
|
|
175
195
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Usage: /search <text>" });
|
|
@@ -184,6 +204,8 @@ export class NavigationCommandActions {
|
|
|
184
204
|
const results = await searchSessions(query, {
|
|
185
205
|
cwd: this.host.options.cwd,
|
|
186
206
|
onProgress: (loaded, total) => {
|
|
207
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
208
|
+
return;
|
|
187
209
|
const progressText = total > 0 ? `searching sessions… ${loaded}/${total}` : "searching sessions…";
|
|
188
210
|
if (progressText === lastProgressText)
|
|
189
211
|
return;
|
|
@@ -192,6 +214,8 @@ export class NavigationCommandActions {
|
|
|
192
214
|
this.host.render();
|
|
193
215
|
},
|
|
194
216
|
});
|
|
217
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
218
|
+
return;
|
|
195
219
|
if (results.length === 0) {
|
|
196
220
|
this.host.addEntry({ id: createId("system"), kind: "system", text: `No sessions found for: ${query}` });
|
|
197
221
|
this.host.toast.info("No matching sessions");
|
|
@@ -205,6 +229,8 @@ export class NavigationCommandActions {
|
|
|
205
229
|
emptyText: "No matching search results",
|
|
206
230
|
searchable: true,
|
|
207
231
|
});
|
|
232
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
233
|
+
return;
|
|
208
234
|
if (!selected) {
|
|
209
235
|
this.host.setSessionStatus(runtime.session);
|
|
210
236
|
return;
|
|
@@ -212,6 +238,8 @@ export class NavigationCommandActions {
|
|
|
212
238
|
await this.host.openSearchResultInNewTab(selected);
|
|
213
239
|
}
|
|
214
240
|
catch (error) {
|
|
241
|
+
if (!isCommandScopeActive(this.host, scope))
|
|
242
|
+
return;
|
|
215
243
|
this.host.addEntry({ id: createId("error"), kind: "error", text: `Session search failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
216
244
|
this.host.toast.error("Session search failed");
|
|
217
245
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -231,7 +259,11 @@ export class NavigationCommandActions {
|
|
|
231
259
|
this.host.setStatus("switching session");
|
|
232
260
|
this.host.render();
|
|
233
261
|
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
262
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
263
|
+
return;
|
|
234
264
|
const result = await runtime.switchSession(resolvedSessionPath);
|
|
265
|
+
if (!isCommandRuntimeActive(this.host, runtime))
|
|
266
|
+
return;
|
|
235
267
|
if (result.cancelled) {
|
|
236
268
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Resume cancelled." });
|
|
237
269
|
this.host.setSessionStatus(runtime.session);
|
|
@@ -275,28 +307,30 @@ export class NavigationCommandActions {
|
|
|
275
307
|
}
|
|
276
308
|
this.host.render();
|
|
277
309
|
const loadId = ++this.resumeLoadId;
|
|
278
|
-
void this.loadResumeSessionsInBackground({ loadId, preserveStatus,
|
|
310
|
+
void this.loadResumeSessionsInBackground({ loadId, preserveStatus, scope: captureCommandScope(this.host) });
|
|
279
311
|
}
|
|
280
312
|
async loadResumeSessionsInBackground(options) {
|
|
281
313
|
try {
|
|
282
314
|
await nextTick();
|
|
315
|
+
if (!isCommandScopeActive(this.host, options.scope))
|
|
316
|
+
return;
|
|
283
317
|
await this.resumeSessionLoader({
|
|
284
318
|
cwd: this.host.options.cwd,
|
|
285
319
|
onChunk: (sessions, progress) => {
|
|
286
|
-
if (options.loadId !== this.resumeLoadId)
|
|
320
|
+
if (options.loadId !== this.resumeLoadId || !isCommandScopeActive(this.host, options.scope))
|
|
287
321
|
return;
|
|
288
322
|
this.host.setResumeSessions([...sessions]);
|
|
289
323
|
if (progress.done) {
|
|
290
324
|
this.host.setResumeLoading(false);
|
|
291
325
|
if (!options.preserveStatus)
|
|
292
|
-
this.host.setSessionStatus(options.session);
|
|
326
|
+
this.host.setSessionStatus(options.scope.session);
|
|
293
327
|
}
|
|
294
328
|
this.host.render();
|
|
295
329
|
},
|
|
296
330
|
});
|
|
297
331
|
}
|
|
298
332
|
catch (error) {
|
|
299
|
-
if (options.loadId !== this.resumeLoadId)
|
|
333
|
+
if (options.loadId !== this.resumeLoadId || !isCommandScopeActive(this.host, options.scope))
|
|
300
334
|
return;
|
|
301
335
|
this.host.setResumeLoading(false);
|
|
302
336
|
this.host.setDirectPopupMenu(undefined);
|
|
@@ -307,7 +341,7 @@ export class NavigationCommandActions {
|
|
|
307
341
|
if (!options.preserveStatus)
|
|
308
342
|
this.host.toast.error("Failed to load sessions");
|
|
309
343
|
if (!options.preserveStatus)
|
|
310
|
-
this.host.setSessionStatus(options.session);
|
|
344
|
+
this.host.setSessionStatus(options.scope.session);
|
|
311
345
|
this.host.render();
|
|
312
346
|
}
|
|
313
347
|
}
|