()
| 2370 | } |
| 2371 | |
| 2372 | private async detachRunningShellCommand(): Promise<void> { |
| 2373 | // Only one `!` command runs at a time (input is queued while busy). |
| 2374 | const next = this.shellOutputStreams.entries().next(); |
| 2375 | if (next.done) { |
| 2376 | this.showDetachHint('No shell command running.'); |
| 2377 | return; |
| 2378 | } |
| 2379 | const [commandId, stream] = next.value; |
| 2380 | if (stream.taskId === undefined) { |
| 2381 | this.showDetachHint('Command is still starting — try again.'); |
| 2382 | return; |
| 2383 | } |
| 2384 | const session = this.session; |
| 2385 | if (session === undefined) return; |
| 2386 | try { |
| 2387 | const info = await session.detachBackgroundTask(stream.taskId); |
| 2388 | if (info === undefined) { |
| 2389 | this.showDetachHint('Command already finished.'); |
| 2390 | return; |
| 2391 | } |
| 2392 | } catch (error) { |
| 2393 | this.showError(`Failed to move to background: ${formatErrorMessage(error)}`); |
| 2394 | return; |
| 2395 | } |
| 2396 | // Finalize the card as backgrounded and drop the stream so the eventual |
| 2397 | // runShellCommand resolution (which carries background metadata) is a no-op |
| 2398 | // instead of overwriting this view. |
| 2399 | stream.component.finishBackgrounded(); |
| 2400 | stream.entry.content = 'Moved to background.'; |
| 2401 | this.shellOutputStreams.delete(commandId); |
| 2402 | // The backgrounded command's notification turn (started by agent-core via |
| 2403 | // appendSystemReminderAndNotify) owns the streaming phase and drains the |
| 2404 | // queue when it completes, so we intentionally leave both untouched here. |
| 2405 | this.showDetachHint('Moved to background. /tasks to view.'); |
| 2406 | } |
| 2407 | |
| 2408 | async detachCurrentForegroundTask(): Promise<void> { |
| 2409 | // A running `!` shell command takes priority over agent foreground tasks. |
no test coverage detected