pi-ui-extend 0.1.70 → 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.
Files changed (49) hide show
  1. package/dist/app/app.js +22 -1
  2. package/dist/app/commands/command-host.d.ts +7 -0
  3. package/dist/app/commands/command-host.js +10 -1
  4. package/dist/app/commands/command-model-actions.d.ts +1 -1
  5. package/dist/app/commands/command-model-actions.js +36 -2
  6. package/dist/app/commands/command-navigation-actions.d.ts +1 -1
  7. package/dist/app/commands/command-navigation-actions.js +40 -6
  8. package/dist/app/commands/command-session-actions.d.ts +1 -1
  9. package/dist/app/commands/command-session-actions.js +56 -0
  10. package/dist/app/commands/shell-controller.d.ts +9 -2
  11. package/dist/app/commands/shell-controller.js +32 -21
  12. package/dist/app/extensions/extension-actions-controller.d.ts +3 -0
  13. package/dist/app/extensions/extension-actions-controller.js +25 -7
  14. package/dist/app/extensions/extension-ui-controller.d.ts +5 -1
  15. package/dist/app/extensions/extension-ui-controller.js +104 -64
  16. package/dist/app/input/input-action-controller.d.ts +4 -1
  17. package/dist/app/input/input-action-controller.js +68 -27
  18. package/dist/app/input/input-controller.d.ts +3 -0
  19. package/dist/app/input/input-controller.js +45 -1
  20. package/dist/app/input/input-paste-handler.d.ts +3 -0
  21. package/dist/app/input/input-paste-handler.js +21 -12
  22. package/dist/app/input/prompt-enhancer-controller.d.ts +1 -0
  23. package/dist/app/input/prompt-enhancer-controller.js +11 -5
  24. package/dist/app/input/voice-controller.d.ts +4 -0
  25. package/dist/app/input/voice-controller.js +76 -35
  26. package/dist/app/popup/popup-action-controller.d.ts +3 -0
  27. package/dist/app/popup/popup-action-controller.js +43 -8
  28. package/dist/app/session/queued-message-controller.d.ts +5 -4
  29. package/dist/app/session/queued-message-controller.js +32 -27
  30. package/dist/app/session/request-history.d.ts +2 -0
  31. package/dist/app/session/request-history.js +22 -15
  32. package/dist/app/session/session-event-controller.js +28 -8
  33. package/dist/app/session/session-history.js +8 -2
  34. package/dist/app/session/session-lifecycle-controller.d.ts +7 -0
  35. package/dist/app/session/session-lifecycle-controller.js +91 -19
  36. package/dist/app/session/tabs-controller.d.ts +26 -0
  37. package/dist/app/session/tabs-controller.js +469 -96
  38. package/dist/app/terminal/terminal-controller.d.ts +4 -0
  39. package/dist/app/terminal/terminal-controller.js +38 -11
  40. package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
  41. package/dist/app/workspace/workspace-actions-controller.js +37 -9
  42. package/dist/input-editor.d.ts +2 -0
  43. package/dist/input-editor.js +17 -0
  44. package/docs/concurrency.md +76 -0
  45. package/external/pi-tools-suite/package.json +3 -3
  46. package/external/pi-tools-suite/src/dcp/state-persistence.ts +29 -14
  47. package/external/pi-tools-suite/src/todo/index.ts +2 -2
  48. package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
  49. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { StringDecoder } from "node:string_decoder";
1
2
  import { InputPasteHandler } from "./input-paste-handler.js";
2
3
  import { hasTerminalCommandModifier, isNativeCommandPressed, isNativeShiftPressed } from "./native-modifiers.js";
3
4
  import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, parseTerminalModifiedKeySequence, terminalKeyArrowDirection, terminalEditShortcutForControlChar, terminalKeyIsClipboardImagePaste, terminalKeyIsEscape, terminalKeyIsShiftEnter, terminalKeyShouldIgnore, } from "./terminal-edit-shortcuts.js";
@@ -5,13 +6,16 @@ const SHIFT_ENTER_ESCAPE_SEQUENCES = ["\x1b\r", "\x1b\n"];
5
6
  export class AppInputController {
6
7
  host;
7
8
  inputBuffer = "";
9
+ inputDecoder = new StringDecoder("utf8");
8
10
  pasteHandler;
9
11
  constructor(host) {
10
12
  this.host = host;
11
13
  this.pasteHandler = new InputPasteHandler(host);
12
14
  }
13
15
  handleChunk(chunk) {
14
- let data = chunk.toString("utf8");
16
+ let data = this.inputDecoder.write(chunk);
17
+ if (!data)
18
+ return;
15
19
  const bufferedSharedEditorInput = this.consumeBufferedSharedEditorInput(data);
16
20
  if (bufferedSharedEditorInput.kind === "consumed" || bufferedSharedEditorInput.kind === "pending")
17
21
  return;
@@ -159,12 +163,52 @@ export class AppInputController {
159
163
  continue;
160
164
  if (this.isPendingEscapeSequence())
161
165
  return;
166
+ if (this.consumePrintableRun())
167
+ continue;
162
168
  const char = this.inputBuffer[0];
163
169
  this.inputBuffer = this.inputBuffer.slice(1);
164
170
  if (char)
165
171
  this.handleChar(char);
166
172
  }
167
173
  }
174
+ consumePrintableRun() {
175
+ let length = 0;
176
+ while (length < this.inputBuffer.length) {
177
+ const char = this.inputBuffer[length];
178
+ if (!char || char < " " || char === "\u007f")
179
+ break;
180
+ // Native undo/redo shortcuts must retain their individual key behavior.
181
+ const lower = char.toLowerCase();
182
+ if ((lower === "z" || lower === "y") && isNativeCommandPressed())
183
+ break;
184
+ length += 1;
185
+ }
186
+ if (length === 0)
187
+ return false;
188
+ const text = this.inputBuffer.slice(0, length);
189
+ this.inputBuffer = this.inputBuffer.slice(length);
190
+ let editorChanged = false;
191
+ let pendingText = "";
192
+ const flushPendingText = () => {
193
+ if (!pendingText)
194
+ return;
195
+ this.host.resetRequestHistoryNavigation();
196
+ this.host.inputEditor.insert(pendingText);
197
+ pendingText = "";
198
+ editorChanged = true;
199
+ };
200
+ for (const char of text) {
201
+ if (this.host.handleDirectPopupInput(char)) {
202
+ flushPendingText();
203
+ continue;
204
+ }
205
+ pendingText += char;
206
+ }
207
+ flushPendingText();
208
+ if (editorChanged)
209
+ this.host.render();
210
+ return true;
211
+ }
168
212
  consumeBracketedPastePayload() {
169
213
  if (!this.host.inputEditor.isInBracketedPaste)
170
214
  return false;
@@ -2,6 +2,7 @@ import { InputEditor } from "../../input-editor.js";
2
2
  export type InputPasteHost = {
3
3
  readonly inputEditor: InputEditor;
4
4
  readonly cwd: string;
5
+ inputScopeKey?(): string | undefined;
5
6
  resetRequestHistoryNavigation(): void;
6
7
  render(): void;
7
8
  };
@@ -10,6 +11,7 @@ export declare class InputPasteHandler {
10
11
  private pasteBufferParts;
11
12
  private readonly recentPasteFingerprints;
12
13
  private suppressImagePathPasteUntil;
14
+ private bracketedPasteScopeKey;
13
15
  constructor(host: InputPasteHost);
14
16
  handlePlainData(data: string): boolean;
15
17
  beginBracketedPaste(): void;
@@ -23,6 +25,7 @@ export declare class InputPasteHandler {
23
25
  private handlePastedFilePath;
24
26
  private schedulePastedText;
25
27
  private handleFilePaste;
28
+ private scopeIsActive;
26
29
  private filePathForInput;
27
30
  private displayPathForInput;
28
31
  private insertPastedPathText;
@@ -10,6 +10,7 @@ export class InputPasteHandler {
10
10
  pasteBufferParts = [];
11
11
  recentPasteFingerprints = new Map();
12
12
  suppressImagePathPasteUntil = 0;
13
+ bracketedPasteScopeKey;
13
14
  constructor(host) {
14
15
  this.host = host;
15
16
  }
@@ -25,6 +26,7 @@ export class InputPasteHandler {
25
26
  beginBracketedPaste() {
26
27
  this.host.inputEditor.beginBracketedPaste();
27
28
  this.pasteBufferParts = [];
29
+ this.bracketedPasteScopeKey = this.host.inputScopeKey?.();
28
30
  }
29
31
  appendBracketedPasteText(text) {
30
32
  if (text)
@@ -34,11 +36,16 @@ export class InputPasteHandler {
34
36
  this.host.inputEditor.endBracketedPaste();
35
37
  const text = this.pasteBufferParts.join("");
36
38
  this.pasteBufferParts = [];
39
+ const scopeKey = this.bracketedPasteScopeKey;
40
+ this.bracketedPasteScopeKey = undefined;
41
+ if (!this.scopeIsActive(scopeKey))
42
+ return;
37
43
  this.handlePasteEnd(text);
38
44
  }
39
45
  async handleClipboardImagePaste() {
46
+ const scopeKey = this.host.inputScopeKey?.();
40
47
  const image = await readClipboardImage();
41
- if (!image)
48
+ if (!image || !this.scopeIsActive(scopeKey))
42
49
  return;
43
50
  if (this.isDuplicatePaste(`image:${image.mimeType}`, image.data)) {
44
51
  this.host.render();
@@ -101,26 +108,28 @@ export class InputPasteHandler {
101
108
  this.host.render();
102
109
  return true;
103
110
  }
104
- void this.handleFilePaste(filePath);
111
+ void this.handleFilePaste(filePath, this.host.inputScopeKey?.());
105
112
  return true;
106
113
  }
107
114
  schedulePastedText(text) {
108
- const timer = setTimeout(() => {
109
- if (this.isDuplicatePaste("text", text)) {
110
- this.host.render();
111
- return;
112
- }
113
- this.host.resetRequestHistoryNavigation();
114
- this.host.inputEditor.attachPastedText(text);
115
+ if (this.isDuplicatePaste("text", text)) {
115
116
  this.host.render();
116
- }, 0);
117
- timer.unref?.();
117
+ return;
118
+ }
119
+ this.host.resetRequestHistoryNavigation();
120
+ this.host.inputEditor.attachPastedText(text);
121
+ this.host.render();
118
122
  }
119
- async handleFilePaste(filePath) {
123
+ async handleFilePaste(filePath, scopeKey = this.host.inputScopeKey?.()) {
120
124
  const inputPath = await this.filePathForInput(filePath);
125
+ if (!this.scopeIsActive(scopeKey))
126
+ return;
121
127
  this.insertPastedPathText(inputPath);
122
128
  this.host.render();
123
129
  }
130
+ scopeIsActive(scopeKey) {
131
+ return !this.host.inputScopeKey || this.host.inputScopeKey() === scopeKey;
132
+ }
124
133
  async filePathForInput(filePath) {
125
134
  const resolved = resolve(this.host.cwd, filePath);
126
135
  try {
@@ -8,6 +8,7 @@ export type AppPromptEnhancerControllerHost = {
8
8
  runtime(): AgentSessionRuntime | undefined;
9
9
  inputEditor(): InputEditor;
10
10
  activeInputTabId(): string | undefined;
11
+ isInputTabOwnedByRuntime(tabId: string | undefined, runtime: AgentSessionRuntime, session: AgentSessionRuntime["session"]): boolean;
11
12
  inputStateForTab(tabId: string | undefined): TabInputState | undefined;
12
13
  setInputStateForTab(tabId: string | undefined, state: TabInputState): void;
13
14
  promptEnhancerConfig(): PromptEnhancerConfig;
@@ -47,6 +47,7 @@ export class AppPromptEnhancerController {
47
47
  this.host.toast.error("Prompt enhancer unavailable: runtime is not initialized");
48
48
  return;
49
49
  }
50
+ const session = runtime.session;
50
51
  const target = this.currentTarget();
51
52
  if (!target) {
52
53
  this.host.toast.warning(`Type at least ${PROMPT_ENHANCER_MIN_TEXT_LENGTH} characters to enhance`);
@@ -58,21 +59,26 @@ export class AppPromptEnhancerController {
58
59
  this.host.render();
59
60
  try {
60
61
  const enhanced = await this.enhancePromptWithPi(runtime, target.text, this.host.promptEnhancerConfig());
62
+ if (!this.host.isInputTabOwnedByRuntime(target.tabId, runtime, session))
63
+ return;
61
64
  const currentInputState = this.host.inputStateForTab(target.tabId);
62
65
  if (!currentInputState || currentInputState.text !== target.originalEditorText) {
63
66
  this.host.toast.warning("Prompt was changed before enhancement completed; result was not applied");
64
67
  return;
65
68
  }
66
- this.applyEnhancedPrompt(target, enhanced);
69
+ this.applyEnhancedPrompt(target, enhanced, currentInputState);
67
70
  this.host.resetInputAfterProgrammaticEdit();
68
71
  this.host.toast.success(target.kind === "selection" ? "Selection enhanced" : "Prompt enhanced");
69
72
  }
70
73
  catch (error) {
74
+ if (!this.host.isInputTabOwnedByRuntime(target.tabId, runtime, session))
75
+ return;
71
76
  this.host.toast.error(`Prompt enhance failed: ${stringifyUnknown(error)}`);
72
77
  }
73
78
  finally {
74
79
  this.enhancing = false;
75
- this.restoreSessionState();
80
+ if (this.host.runtime() === runtime && runtime.session === session)
81
+ this.restoreSessionState(session);
76
82
  this.host.render();
77
83
  }
78
84
  }
@@ -106,15 +112,15 @@ export class AppPromptEnhancerController {
106
112
  end: originalEditorText.length,
107
113
  };
108
114
  }
109
- applyEnhancedPrompt(target, enhanced) {
115
+ applyEnhancedPrompt(target, enhanced, currentState) {
110
116
  const nextText = `${target.originalEditorText.slice(0, target.start)}${enhanced}${target.originalEditorText.slice(target.end)}`;
111
117
  this.host.setInputStateForTab(target.tabId, {
112
118
  text: nextText,
113
119
  cursor: target.start + enhanced.length,
120
+ ...(currentState.attachments ? { attachments: currentState.attachments } : {}),
114
121
  });
115
122
  }
116
- restoreSessionState() {
117
- const session = this.host.runtime()?.session;
123
+ restoreSessionState(session) {
118
124
  this.host.setSessionStatus(session);
119
125
  this.host.setSessionActivity(session?.isStreaming || session?.isCompacting ? "running" : "idle");
120
126
  }
@@ -3,6 +3,7 @@ import { savePixDictationLanguage, type DictationConfig, type DictationLanguageM
3
3
  export type VoiceLanguage = string;
4
4
  export type VoiceInputState = "idle" | "installing" | "downloading" | "loading" | "listening";
5
5
  export type AppVoiceControllerHost = {
6
+ activeInputScope(): string | undefined;
6
7
  insertTranscript(text: string): void;
7
8
  setPartialTranscript(text: string | undefined): void;
8
9
  addSystemMessage(message: string): void;
@@ -68,6 +69,7 @@ export declare class AppVoiceController {
68
69
  private partialTranscript;
69
70
  private partialTranscriptTimer;
70
71
  private startGeneration;
72
+ private recordingScope;
71
73
  constructor(host: AppVoiceControllerHost, dictationConfig: DictationConfig);
72
74
  updateDictationConfig(dictationConfig: DictationConfig): void;
73
75
  statusWidgetText(): string;
@@ -95,7 +97,9 @@ export declare class AppVoiceController {
95
97
  private clearPartialTranscript;
96
98
  private schedulePartialTranscriptEmit;
97
99
  private isCurrentStart;
100
+ private continueStart;
98
101
  private isCurrentAudioProcess;
102
+ private isScopeActive;
99
103
  }
100
104
  declare function ensureModel(language: VoiceLanguage, definition: VoiceModelDefinition): Promise<string>;
101
105
  declare function tryLoadVosk(): VoskLoadAttempt;
@@ -45,6 +45,7 @@ export class AppVoiceController {
45
45
  partialTranscript;
46
46
  partialTranscriptTimer;
47
47
  startGeneration = 0;
48
+ recordingScope;
48
49
  constructor(host, dictationConfig) {
49
50
  this.host = host;
50
51
  this.modelDefinitions = dictationConfig.languages;
@@ -97,9 +98,12 @@ export class AppVoiceController {
97
98
  async toggleLanguage() {
98
99
  if (!this.showLanguageSwitcher())
99
100
  return;
101
+ const scope = this.host.activeInputScope();
100
102
  const wasActive = this.state !== "idle";
101
103
  if (wasActive)
102
104
  await this.stopRecording();
105
+ if (!this.isScopeActive(scope))
106
+ return;
103
107
  this.language = this.nextLanguage();
104
108
  this.saveLanguageSelection(this.language);
105
109
  this.host.showToast(`Voice language: ${this.modelDefinition(this.language).label}`, "info");
@@ -109,21 +113,24 @@ export class AppVoiceController {
109
113
  }
110
114
  async stopRecording() {
111
115
  this.startGeneration += 1;
116
+ const scope = this.recordingScope;
112
117
  const audioProcess = this.audioProcess;
113
118
  const recognizer = this.recognizer;
114
119
  this.audioProcess = undefined;
115
120
  this.recognizer = undefined;
121
+ this.recordingScope = undefined;
116
122
  if (audioProcess && !audioProcess.killed)
117
123
  audioProcess.kill("SIGTERM");
118
124
  if (recognizer) {
119
- this.clearPartialTranscript();
120
- this.emitTranscript(recognizer.finalResult());
125
+ this.clearPartialTranscript(scope);
126
+ this.emitTranscript(recognizer.finalResult(), scope);
121
127
  recognizer.free?.();
122
128
  }
123
129
  if (this.state !== "idle") {
124
130
  this.clearProgressMessage();
125
131
  this.state = "idle";
126
- this.host.render();
132
+ if (this.isScopeActive(scope))
133
+ this.host.render();
127
134
  }
128
135
  }
129
136
  async dispose() {
@@ -134,25 +141,29 @@ export class AppVoiceController {
134
141
  }
135
142
  async startRecording() {
136
143
  const language = this.language;
144
+ const scope = this.host.activeInputScope();
137
145
  const generation = this.startGeneration + 1;
138
146
  this.startGeneration = generation;
147
+ this.recordingScope = scope;
139
148
  try {
140
149
  const initialVosk = voiceControllerDeps.tryLoadVosk();
141
150
  const vosk = initialVosk.ok
142
151
  ? initialVosk.module
143
- : await this.installAndLoadVosk(initialVosk.error, generation);
144
- if (!this.isCurrentStart(generation))
152
+ : await this.installAndLoadVosk(initialVosk.error, generation, scope);
153
+ if (!this.continueStart(generation, scope))
145
154
  return;
146
155
  vosk.setLogLevel?.(-1);
147
156
  this.state = "downloading";
148
157
  this.host.render();
149
158
  const modelPath = await voiceControllerDeps.ensureModel(language, this.modelDefinition(language));
150
- if (!this.isCurrentStart(generation))
159
+ if (!this.continueStart(generation, scope))
151
160
  return;
152
161
  this.state = "loading";
153
162
  this.host.render();
154
163
  const model = this.cachedModel(language, modelPath, vosk);
155
164
  const recorder = await voiceControllerDeps.selectRecorderCommand();
165
+ if (!this.continueStart(generation, scope))
166
+ return;
156
167
  const recognizer = new vosk.Recognizer({ model, sampleRate: SAMPLE_RATE });
157
168
  const audioProcess = voiceControllerDeps.spawn(recorder.command, recorder.args, { stdio: ["ignore", "pipe", "pipe"] });
158
169
  this.recognizer = recognizer;
@@ -160,10 +171,10 @@ export class AppVoiceController {
160
171
  this.state = "listening";
161
172
  this.host.render();
162
173
  this.host.showToast(`Voice input on (${this.modelDefinition(language).label}, ${recorder.description})`, "info");
163
- this.bindAudioProcess(audioProcess, recognizer, generation);
174
+ this.bindAudioProcess(audioProcess, recognizer, generation, scope);
164
175
  }
165
176
  catch (error) {
166
- if (!this.isCurrentStart(generation))
177
+ if (!this.continueStart(generation, scope))
167
178
  return;
168
179
  this.clearProgressMessage();
169
180
  this.cleanupRecognizer();
@@ -205,31 +216,31 @@ export class AppVoiceController {
205
216
  throw new Error(`dictation language is not configured: ${language}`);
206
217
  return definition;
207
218
  }
208
- async installAndLoadVosk(initialError, generation) {
219
+ async installAndLoadVosk(initialError, generation, scope) {
209
220
  this.state = "installing";
210
- this.setProgressMessage("Installing Vosk voice bindings...");
221
+ this.setProgressMessage("Installing Vosk voice bindings...", generation, scope);
211
222
  const vosk = await loadVoskWithAutoInstall(initialError, (message) => {
212
- if (this.isCurrentStart(generation))
213
- this.setProgressMessage(message);
223
+ if (this.isCurrentStart(generation, scope))
224
+ this.setProgressMessage(message, generation, scope);
214
225
  });
215
- if (this.isCurrentStart(generation))
226
+ if (this.isCurrentStart(generation, scope))
216
227
  this.addProgressSystemMessage("Vosk voice bindings are ready.");
217
- if (this.isCurrentStart(generation))
228
+ if (this.isCurrentStart(generation, scope))
218
229
  this.clearProgressMessage();
219
230
  return vosk;
220
231
  }
221
- bindAudioProcess(audioProcess, recognizer, generation) {
232
+ bindAudioProcess(audioProcess, recognizer, generation, scope = this.recordingScope) {
222
233
  let stderr = "";
223
234
  audioProcess.stdout.on("data", (chunk) => {
224
- if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation))
235
+ if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation, scope))
225
236
  return;
226
237
  try {
227
238
  if (recognizer.acceptWaveform(chunk)) {
228
- this.clearPartialTranscript();
229
- this.emitTranscript(recognizer.result());
239
+ this.clearPartialTranscript(scope);
240
+ this.emitTranscript(recognizer.result(), scope);
230
241
  }
231
242
  else {
232
- this.emitPartialTranscript(recognizer.partialResult?.());
243
+ this.emitPartialTranscript(recognizer.partialResult?.(), audioProcess, recognizer, generation, scope);
233
244
  }
234
245
  }
235
246
  catch (error) {
@@ -241,9 +252,9 @@ export class AppVoiceController {
241
252
  stderr = `${stderr}${chunk.toString("utf8")}`.slice(-600);
242
253
  });
243
254
  audioProcess.once("error", (error) => {
244
- if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation))
255
+ if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation, scope))
245
256
  return;
246
- this.clearPartialTranscript();
257
+ this.clearPartialTranscript(scope);
247
258
  this.cleanupRecognizer();
248
259
  this.audioProcess = undefined;
249
260
  this.state = "idle";
@@ -251,10 +262,10 @@ export class AppVoiceController {
251
262
  this.host.render();
252
263
  });
253
264
  audioProcess.once("close", (code, signal) => {
254
- if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation))
265
+ if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation, scope))
255
266
  return;
256
- this.clearPartialTranscript();
257
- this.emitTranscript(recognizer.finalResult());
267
+ this.clearPartialTranscript(scope);
268
+ this.emitTranscript(recognizer.finalResult(), scope);
258
269
  this.cleanupRecognizer();
259
270
  this.audioProcess = undefined;
260
271
  this.state = "idle";
@@ -269,11 +280,13 @@ export class AppVoiceController {
269
280
  this.recognizer?.free?.();
270
281
  this.recognizer = undefined;
271
282
  }
272
- setProgressMessage(message) {
283
+ setProgressMessage(message, generation = this.startGeneration, scope = this.recordingScope) {
273
284
  this.progressMessage = message;
274
285
  this.addProgressSystemMessage(message);
275
286
  if (!this.progressTimer) {
276
287
  this.progressTimer = setInterval(() => {
288
+ if (!this.isCurrentStart(generation, scope))
289
+ return;
277
290
  this.progressFrame += 1;
278
291
  this.host.render();
279
292
  }, 120);
@@ -296,20 +309,26 @@ export class AppVoiceController {
296
309
  this.lastSystemProgressMessage = text;
297
310
  this.host.addSystemMessage(text);
298
311
  }
299
- emitTranscript(result) {
312
+ emitTranscript(result, scope = this.recordingScope) {
313
+ if (!this.isScopeActive(scope))
314
+ return;
300
315
  const text = transcriptText(result);
301
316
  if (!text)
302
317
  return;
303
318
  this.host.insertTranscript(text);
304
319
  }
305
- emitPartialTranscript(result) {
320
+ emitPartialTranscript(result, audioProcess = this.audioProcess, recognizer = this.recognizer, generation = this.startGeneration, scope = this.recordingScope) {
321
+ if (audioProcess || recognizer) {
322
+ if (!audioProcess || !recognizer || !this.isCurrentAudioProcess(audioProcess, recognizer, generation, scope))
323
+ return;
324
+ }
306
325
  const text = partialTranscriptText(result);
307
326
  if (text === this.partialTranscript)
308
327
  return;
309
328
  this.partialTranscript = text;
310
- this.schedulePartialTranscriptEmit();
329
+ this.schedulePartialTranscriptEmit(audioProcess, recognizer, generation, scope);
311
330
  }
312
- clearPartialTranscript() {
331
+ clearPartialTranscript(scope = this.recordingScope) {
313
332
  if (!this.partialTranscript)
314
333
  return;
315
334
  this.partialTranscript = undefined;
@@ -317,22 +336,44 @@ export class AppVoiceController {
317
336
  clearTimeout(this.partialTranscriptTimer);
318
337
  this.partialTranscriptTimer = undefined;
319
338
  }
320
- this.host.setPartialTranscript(undefined);
339
+ if (this.isScopeActive(scope))
340
+ this.host.setPartialTranscript(undefined);
321
341
  }
322
- schedulePartialTranscriptEmit() {
342
+ schedulePartialTranscriptEmit(audioProcess, recognizer, generation, scope) {
323
343
  if (this.partialTranscriptTimer)
324
344
  return;
325
345
  this.partialTranscriptTimer = setTimeout(() => {
326
346
  this.partialTranscriptTimer = undefined;
347
+ if (audioProcess && recognizer) {
348
+ if (!this.isCurrentAudioProcess(audioProcess, recognizer, generation, scope))
349
+ return;
350
+ }
351
+ else if (!this.isScopeActive(scope)) {
352
+ return;
353
+ }
327
354
  this.host.setPartialTranscript(this.partialTranscript);
328
355
  }, VOICE_PARTIAL_TRANSCRIPT_THROTTLE_MS);
329
356
  this.partialTranscriptTimer.unref?.();
330
357
  }
331
- isCurrentStart(generation) {
332
- return this.startGeneration === generation;
358
+ isCurrentStart(generation, scope = this.recordingScope) {
359
+ return this.startGeneration === generation && this.recordingScope === scope && this.isScopeActive(scope);
360
+ }
361
+ continueStart(generation, scope) {
362
+ if (this.startGeneration !== generation || this.recordingScope !== scope)
363
+ return false;
364
+ if (this.isScopeActive(scope))
365
+ return true;
366
+ this.startGeneration += 1;
367
+ this.recordingScope = undefined;
368
+ this.clearProgressMessage();
369
+ this.state = "idle";
370
+ return false;
371
+ }
372
+ isCurrentAudioProcess(audioProcess, recognizer, generation, scope = this.recordingScope) {
373
+ return this.isCurrentStart(generation, scope) && this.audioProcess === audioProcess && this.recognizer === recognizer;
333
374
  }
334
- isCurrentAudioProcess(audioProcess, recognizer, generation) {
335
- return this.startGeneration === generation && this.audioProcess === audioProcess && this.recognizer === recognizer;
375
+ isScopeActive(scope) {
376
+ return this.host.activeInputScope() === scope;
336
377
  }
337
378
  }
338
379
  async function ensureModel(language, definition) {
@@ -39,6 +39,9 @@ export declare class AppPopupActionController {
39
39
  private submitSelectedResume;
40
40
  private formatSlashCommandLine;
41
41
  private executeResourceSlashCommand;
42
+ private captureScope;
43
+ private isScopeActive;
44
+ private isRuntimeActive;
42
45
  private getRuntime;
43
46
  private getIdleRuntime;
44
47
  }