# 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<string>? 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.
