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
package/dist/app/app.js
CHANGED
|
@@ -221,6 +221,7 @@ export class PiUiExtendApp {
|
|
|
221
221
|
runtime: () => this.runtime,
|
|
222
222
|
inputEditor: () => this.inputEditor,
|
|
223
223
|
activeInputTabId: () => this.tabsController.activeInputTabId(),
|
|
224
|
+
isInputTabOwnedByRuntime: (tabId, runtime, session) => this.tabsController.inputTabOwnedByRuntime(tabId, runtime, session),
|
|
224
225
|
inputStateForTab: (tabId) => this.tabsController.inputStateForTab(tabId),
|
|
225
226
|
setInputStateForTab: (tabId, state) => this.tabsController.setInputStateForTab(tabId, state),
|
|
226
227
|
promptEnhancerConfig: () => this.pixConfig.promptEnhancer,
|
|
@@ -239,6 +240,7 @@ export class PiUiExtendApp {
|
|
|
239
240
|
render: () => this.render(),
|
|
240
241
|
});
|
|
241
242
|
this.voiceController = new AppVoiceController({
|
|
243
|
+
activeInputScope: () => this.tabsController.activeInputTabId(),
|
|
242
244
|
insertTranscript: (text) => this.insertVoiceTranscript(text),
|
|
243
245
|
setPartialTranscript: (text) => this.setVoicePartialTranscript(text),
|
|
244
246
|
addSystemMessage: (message) => this.addVoiceSystemMessage(message),
|
|
@@ -320,6 +322,7 @@ export class PiUiExtendApp {
|
|
|
320
322
|
this.extensionUiController = new ExtensionUiController({
|
|
321
323
|
theme: this.theme,
|
|
322
324
|
activeExtensionUiScope: () => this.activeExtensionUiScope(),
|
|
325
|
+
isExtensionUiScopeActive: (scopeKey) => !this.tabsController.isSwitching() && this.activeExtensionUiScope() === scopeKey,
|
|
323
326
|
isRunning: () => this.running,
|
|
324
327
|
render: () => this.render(),
|
|
325
328
|
showToast: (message, kind, options) => this.showToast(message, kind, options),
|
|
@@ -335,6 +338,7 @@ export class PiUiExtendApp {
|
|
|
335
338
|
});
|
|
336
339
|
this.extensionActions = new AppExtensionActionsController({
|
|
337
340
|
isRunning: () => this.running,
|
|
341
|
+
runtime: () => this.runtime,
|
|
338
342
|
getInput: () => this.input,
|
|
339
343
|
setInput: (value) => this.setInput(value),
|
|
340
344
|
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
@@ -430,6 +434,7 @@ export class PiUiExtendApp {
|
|
|
430
434
|
setInput: (value) => this.inputEditor.setText(value),
|
|
431
435
|
insertInput: (value) => this.inputEditor.insert(value),
|
|
432
436
|
attachImage: (data, mimeType) => this.inputEditor.attachImage(data, mimeType),
|
|
437
|
+
requeueAutoUserMessageForSession: (session, message) => this.tabsController.requeueAutoUserMessageForSession(session, message),
|
|
433
438
|
onDeferredUserMessagesChanged: () => this.tabsController.persistActiveDeferredUserMessages(),
|
|
434
439
|
});
|
|
435
440
|
this.editorLayoutRenderer = new EditorLayoutRenderer({
|
|
@@ -648,6 +653,7 @@ export class PiUiExtendApp {
|
|
|
648
653
|
this.shellController = new AppShellController({
|
|
649
654
|
cwd: this.options.cwd,
|
|
650
655
|
isRunning: () => this.running,
|
|
656
|
+
activeScopeKey: () => this.tabsController.activeInputTabId(),
|
|
651
657
|
addEntry: (entry) => this.addEntry(entry),
|
|
652
658
|
touchEntry: (entry) => this.touchEntry(entry),
|
|
653
659
|
setStatus: (status) => this.setStatus(status),
|
|
@@ -658,6 +664,7 @@ export class PiUiExtendApp {
|
|
|
658
664
|
});
|
|
659
665
|
this.inputActions = new AppInputActionController({
|
|
660
666
|
runtime: () => this.runtime,
|
|
667
|
+
inputScopeKey: () => this.tabsController.activeInputTabId(),
|
|
661
668
|
isRunning: () => this.running,
|
|
662
669
|
isSessionSwitching: () => this.tabsController.isSwitching(),
|
|
663
670
|
inputEditor: () => this.inputEditor,
|
|
@@ -688,6 +695,7 @@ export class PiUiExtendApp {
|
|
|
688
695
|
this.inputController = new AppInputController({
|
|
689
696
|
inputEditor: this.inputEditor,
|
|
690
697
|
cwd: this.options.cwd,
|
|
698
|
+
inputScopeKey: () => this.tabsController.activeInputTabId(),
|
|
691
699
|
handleExtensionTerminalInput: (data) => this.extensionUiController.handleTerminalInput(data),
|
|
692
700
|
extensionInputUsesEditor: () => this.extensionUiController.activeCustomUiUsesEditor(),
|
|
693
701
|
getInput: () => this.input,
|
|
@@ -718,6 +726,7 @@ export class PiUiExtendApp {
|
|
|
718
726
|
setRunning: (running) => {
|
|
719
727
|
this.running = running;
|
|
720
728
|
},
|
|
729
|
+
cancelPendingTabLifecycle: () => this.tabsController.cancelPendingLifecycleWork(),
|
|
721
730
|
runtime: () => this.runtime,
|
|
722
731
|
saveInputStateForQuit: () => this.tabsController.saveInputStateForQuit(),
|
|
723
732
|
disposeInactiveRuntimesForQuit: () => this.tabsController.disposeInactiveRuntimes((runtime) => this.terminalController.disposeRuntimeForQuit(runtime)),
|
|
@@ -874,11 +883,17 @@ export class PiUiExtendApp {
|
|
|
874
883
|
await this.sessionLifecycle.awaitCurrentSessionExtensions(runtime);
|
|
875
884
|
}
|
|
876
885
|
async activateRuntime(runtime, options) {
|
|
886
|
+
this.extensionUiController.cancelCustomUi(this.activeExtensionUiScope());
|
|
887
|
+
void this.voiceController.stopRecording();
|
|
877
888
|
this.runtime = runtime;
|
|
878
889
|
runtime.setRebindSession(async () => {
|
|
890
|
+
if (!this.running || this.runtime !== runtime)
|
|
891
|
+
return;
|
|
879
892
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
880
893
|
});
|
|
881
894
|
runtime.setBeforeSessionInvalidate(() => {
|
|
895
|
+
if (!this.running || this.runtime !== runtime)
|
|
896
|
+
return;
|
|
882
897
|
this.extensionUiController.clearWidgets(this.activeExtensionUiScope());
|
|
883
898
|
});
|
|
884
899
|
await this.bindCurrentSession(options);
|
|
@@ -929,7 +944,7 @@ export class PiUiExtendApp {
|
|
|
929
944
|
restoreTabInputState(state) {
|
|
930
945
|
this.requestHistory.resetNavigation();
|
|
931
946
|
this.popupMenus.resetInputMenuDismissals();
|
|
932
|
-
this.inputEditor.
|
|
947
|
+
this.inputEditor.restoreDraftState(state);
|
|
933
948
|
this.autocompleteController.dispose();
|
|
934
949
|
}
|
|
935
950
|
async clearPersistedInputDraft() {
|
|
@@ -1130,6 +1145,8 @@ export class PiUiExtendApp {
|
|
|
1130
1145
|
});
|
|
1131
1146
|
}
|
|
1132
1147
|
showToast(message, kind = "info", options) {
|
|
1148
|
+
if (!this.running)
|
|
1149
|
+
return;
|
|
1133
1150
|
this.toastController.showToast(message, kind, options);
|
|
1134
1151
|
}
|
|
1135
1152
|
toastNotifierForScope(scopeKey) {
|
|
@@ -1154,6 +1171,8 @@ export class PiUiExtendApp {
|
|
|
1154
1171
|
clearTimeout(this.scheduledRenderTimer);
|
|
1155
1172
|
this.scheduledRenderTimer = undefined;
|
|
1156
1173
|
}
|
|
1174
|
+
if (!this.running)
|
|
1175
|
+
return;
|
|
1157
1176
|
this.autocompleteController.observeInput();
|
|
1158
1177
|
this.syncScrollAfterInputEditorChange();
|
|
1159
1178
|
this.renderController.render();
|
|
@@ -1163,6 +1182,8 @@ export class PiUiExtendApp {
|
|
|
1163
1182
|
return;
|
|
1164
1183
|
this.scheduledRenderTimer = setTimeout(() => {
|
|
1165
1184
|
this.scheduledRenderTimer = undefined;
|
|
1185
|
+
if (!this.running)
|
|
1186
|
+
return;
|
|
1166
1187
|
this.syncScrollAfterInputEditorChange();
|
|
1167
1188
|
this.renderController.render();
|
|
1168
1189
|
}, 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
|
}
|