# Plan — In-App Interactive Sessions Spec: `docs/superpowers/specs/2026-06-26-in-app-interactive-sessions-design.md` Implement on the shared main tree. Commit explicit paths per task (never `git add -A`). Build with `-c Release` (running Worker locks Debug). No real-Claude tests — fake the process stream. Sonnet subagents. Autonomous `TaskRunner`/`ClaudeProcess` path stays untouched. ## Task 1 — StreamingClaudeSession (worker, new file) - `Runner/StreamingClaudeSession.cs`: persistent `claude` process. Ctor takes resolved args, working dir, seeded first prompt, a line callback, `WorkerConfig`. Reuse the `ProcessStartInfo` shape + `MCP_TOOL_TIMEOUT="200000"` from `ClaudeProcess`. - Keeps stdin open; sends the first prompt as a user-message JSON line (escape via `JsonSerializer`). - stdout/stderr read tasks → line callback; parse `result` events to track `IsTurnInFlight`. - `SendUserMessageAsync(text, ct)` — enqueue/write a user-message JSON line; if `IsTurnInFlight`, also `InterruptAsync`. - `InterruptAsync(ct)` — write the control-protocol interrupt line; best-effort (swallow + log on failure → queue fallback applies). - `StopAsync` / `DisposeAsync` — close stdin, kill the tree, await exit. - Injectable stream seam so a fake can drive it without a real `claude` binary. - Test: `StreamingClaudeSessionTests` (fake stream) — first message emitted; `result` flips `IsTurnInFlight` off; a sent message produces a second turn; mid-turn send calls interrupt then delivers; interrupt throw → delivered at natural turn end; stop kills. ## Task 2 — LiveSessionRegistry (worker, new file) - `Runner/LiveSessionRegistry.cs`: singleton; `Register(taskId, StreamingClaudeSession)`, `bool TryGet(taskId, out session)`, `Unregister(taskId)`, `Task StopAsync(taskId)`. - Test: register→get; unregister; second register stops+replaces; missing get returns false. ## Task 3 — InteractiveSessionService (worker, new file) - `Planning/InteractiveSessionService.cs`: inject `IDbContextFactory`, `WorkerConfig`, `ClaudeArgsBuilder` (or build args inline), `HubBroadcaster`, `LiveSessionRegistry`. - `StartAsync(taskId, ct)`: resolve list working dir + seeded prompt (reuse the body of `PlanningSessionManager.OpenInteractiveAsync` + `BuildInteractivePrompt`); build interactive args (`--model PlanningAlias --permission-mode auto` + streaming flags); spawn the session with a callback that does `HubBroadcaster.TaskMessage(taskId, "[stdout] " + line)`; register; broadcast `InteractiveSessionStarted`. Reject if one is already live for the task. - `SendAsync(taskId, text, ct)` → registry `TryGet` → `SendUserMessageAsync`. - `StopAsync(taskId, ct)` → registry stop + `InteractiveSessionEnded`. - Move `OpenInteractiveAsync`/`BuildInteractivePrompt` out of `PlanningSessionManager` if it reads cleaner (or call into it). Remove the `InteractiveLaunchContext` terminal coupling. - Test: `InteractiveSessionServiceTests` (fake session factory + fake broadcaster) — start resolves dir, seeds prompt, registers, broadcasts started; missing working dir throws; send routes; stop broadcasts ended. ## Task 4 — Remove terminal interactive path (worker) - `Planning/Interfaces/ITerminalLauncher.cs` + `WindowsTerminalLauncher.cs`: delete `LaunchInteractiveAsync`; remove `InteractiveLaunchContext` from `PlanningSessionContext.cs`. Keep planning start/resume launches. - Fix any references; ensure the planning launcher tests still build. ## Task 5 — Hub + Broadcaster + DI (worker) - `Hub/WorkerHub.cs`: re-point `OpenInteractiveTerminalAsync` to `InteractiveSessionService.StartAsync` (drop `_launcher.LaunchInteractiveAsync`); add `Task SendInteractiveMessage(taskId, text)`, `Task StopInteractiveSession(taskId)` (+ optional `InterruptInteractiveSession`). - `Hub/HubBroadcaster.cs`: `InteractiveSessionStarted(taskId)`, `InteractiveSessionEnded(taskId)`. - `Program.cs`: register `LiveSessionRegistry` + `InteractiveSessionService` singletons. - Test: `WorkerHub` send routes to a fake service; start invokes the service. ## Task 6 — UI client + fakes (ui) - `Services/Interfaces/IWorkerClient.cs` + `WorkerClient.cs`: `SendInteractiveMessageAsync( taskId, text)`, `StopInteractiveSessionAsync(taskId)` (+ optional interrupt); events `Action? InteractiveSessionStartedEvent`, `InteractiveSessionEndedEvent` with `On<...>` handlers. `OpenInteractiveTerminalAsync` keeps name/signature. - Update hand-rolled `IWorkerClient` fakes in **both** Ui.Tests and Worker.Tests. ## Task 7 — StreamLineFormatter user bubble (ui) - Render `type:"user"` NDJSON events as `LogKind.User` (add the kind if missing). - Test: a `user` event yields a `LogKind.User` `LogLineViewModel` with the text. ## Task 8 — Shared composer state on the session VMs (ui, hot files) - Add to `TaskMonitorViewModel` and `DetailsIslandViewModel` (factor a shared helper — `InteractiveComposer` — to avoid duplication): `ComposerDraft`, `IsInteractiveLive` (toggled by `InteractiveSessionStarted/Ended` for the subscribed task), `SubmitComposerCommand` (CanExecute: non-empty draft && (`HasPendingQuestion` || `IsInteractiveLive`)). Route: pending question → existing `AnswerTaskQuestionAsync`; else → `SendInteractiveMessageAsync`. Clear draft on submit; clear `IsInteractiveLive` on ended. - `MissionControlViewModel`: `EnsureMonitor(taskId)` on `InteractiveSessionStarted`. - Test: composer enabled while interactive-live; submit routes (chat vs answer) + clears; ended clears live state. ## Task 9 — SessionTerminalView composer (ui) - `Views/Islands/SessionTerminalView.axaml(.cs)`: optional composer docked bottom (styled props `IsComposerVisible`, `ComposerText`, `SubmitCommand`, `ComposerPlaceholder`); TextBox (Enter submits) + Send button. Reuse existing tokens (no inline values). - Bind it in `MonitorPaneView.axaml` and `DetailsIslandView.axaml` to each VM's composer state. Fold the existing AskUser banner into the composer's "answering" state if it reads cleaner; otherwise leave the banner and add the composer below. ## Task 10 — Localization - `en.json` + `de.json`: `interactive.composer.placeholder`, `.send`, `.stop`, plus any "session ended" notice. Keep parity (Localization.Tests). ## Task 11 — Build + test + verify - Build App + Worker `-c Release`; run Worker.Tests, Ui.Tests, Localization.Tests. - Self-review diffs. **Manual smoke (real CLI) — flag to Mika:** (a) Run interactively opens an in-app chat (no terminal) and streams; (b) sending a message mid-turn interrupts + redirects; (c) stop kills the process; (d) session shows in both task detail and Mission Control. Do not push.