# Architectural Specification: pi-subagents UI/UX Overhaul + Live Session TUI

## Scope

- **Phase A**: Agent management UI (model picker, tool picker, create wizard, list/detail/edit)
- **Phase B**: Live agent session TUI with input harness
- **Phase C**: Deep integration with main session pipeline

---

## Architectural Overview

```
┌─────────────────────────────────────────────────────┐
│                   Main Session TUI                     │
│  ┌─────────────────────────────────────────────────┐  │
│  │  Editor / Conversation / Tool Output             │  │
│  │  [User: "explain this"]                          │  │
│  │  [Assistant: runs Agent tool → spawns subagent]  │  │
│  └─────────────────────────────────────────────────┘  │
│  ┌─────────────────────────────────────────────────┐  │
│  │  Agent Widget (status bar: running agents)        │  │
│  │  ╰─ Explore (searching...) ╴ code-reviewer (idle) │  │
│  └─────────────────────────────────────────────────┘  │
│                                                       │
│  ┌─── /agents overlay ─────────────────────────────┐  │
│  │  Agent management (list / create / edit / detail)│  │
│  └─────────────────────────────────────────────────┘  │
│                                                       │
│  ┌─── live session overlay ─────────────────────────┐  │
│  │  ╭─ Exploring codebase ───────────────────────╮  │  │
│  │  │  searching for patterns...                  │  │  │
│  │  │  [Tool: grep in src/components/]            │  │  │
│  │  │  found 3 matches                            │  │  │
│  │  │  reading agents/types.ts...                 │  │  │
│  │  │                                              │  │  │
│  │  │  ═══════════════════════════════════════     │  │  │
│  │  │  > inject a command or question...          │  │  │
│  │  ╰──────────────────────────────────────────╯  │  │
│  └─────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
```

### Rendering Layers

| Layer | Technology | Purpose |
|-------|-----------|---------|
| **Widget** | `ctx.ui.setWidget()` | Persistent status bar showing running agents |
| **Management UI** | `ctx.ui.*` (select/input/editor/confirm) | Agent creation, listing, editing, settings |
| **Live Session** | `pi-tui Component` via `ctx.ui.custom()` | Full-screen streaming agent session |
| **Agent Widget** | `pi-tui Component` via `ctx.ui.setWidget()` | Live agent widget with streaming status |

---

## Phase A: Agent Management UI (ctx.ui.*)

Uses the familiar `ctx.ui.*` primitives. Same as previous plan — see `PLAN-UI-UX-OVERHAUL.md`.

**Files:**
- `src/ui/agent-menu.ts` — Hub
- `src/ui/agent-list.ts` — Grouped agent list
- `src/ui/agent-detail.ts` — Detail card + actions
- `src/ui/agent-editor.ts` — Inline editing
- `src/ui/create-wizard.ts` — Multi-step creation
- `src/ui/model-picker.ts` — Models.json browser
- `src/ui/tool-picker.ts` — Categorized tool selector

**Design constraint:** All `ctx.ui.*` dialogs are **non-overlapping** with the live session TUI. They're quick config interactions. The live session TUI is invoked separately when the user wants to watch/interact with a running agent.

---

## Phase B: Live Agent Session TUI

### When it activates

1. **Manual invocation**: User selects "Watch" on a running agent in the management list
2. **Auto-attach**: The Agent tool could return with a flag saying "session available — enter live view?"
3. **Persistent widget**: Even when not in live view, a mini status widget shows running agent activity

### Core Component: LiveSessionView

Implements `Component` from `@mariozechner/pi-tui`:

```typescript
class LiveSessionView implements Component {
  // State
  private tokens: string[] = []
  private toolCalls: ToolCallEvent[] = []
  private status: AgentStatus = 'running'
  private turnCount = 1
  private toolUses = 0
  private elapsed = 0
  private scrollOffset = 0
  private autoScroll = true
  
  // Input harness state
  private inputBuffer = ''
  private inputMode: 'view' | 'input' = 'view'
  
  // Render lifecycle
  render(width: number): string[] { ... }
  handleInput(data: string): void { ... }
  invalidate(): void { ... }
  dispose(): void { ... }
}
```

### Rendering Layout

```
┌────────────────────────────────────────────────────────────────┐
│ ◉ code-reviewer · reviewing src/agent-manager.ts               │
│ 42s · 3 tools · 2 turns · 12.4k tokens                        │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│ [User]: Review the agent manager code for issues               │
│                                                                │
│ [Assistant]: Let me analyze the agent manager...               │
│  Looking at the architecture, I notice...                     │
│  The error handling could be improved...                       │
│                                                                │
│  ╭─ Tool: grep (searching) ─────────────────────────────╮     │
│  │  searching for "catch" in src/agent-manager.ts...     │     │
│  │  Found 7 occurrences                                  │     │
│  ╰──────────────────────────────────────────────────────╯     │
│                                                                │
│  reading src/agent-manager.ts...                              │
│                                                                │
│ ════════════════════════════════════════════════════════════  │
│ > inject a message or Ctrl+C to interrupt, Esc to close       │
└────────────────────────────────────────────────────────────────┘
```

### Sub-components

#### SessionStatusBar
```
◉ code-reviewer · reviewing src/agent-manager.ts
42s · 3 tools · 2 turns · 12.4k tokens
```
- Animated spinner for running state
- Agent type + description
- Elapsed time (ticking)
- Tool use count
- Turn count / max turns
- Token usage
- Color-coded status icon

#### SessionStreamPane
- Token-by-token streaming from agent output
- Word-wrapped to viewport width
- Auto-scrolling (follows new content)
- Scrollback support (↑/↓/PgUp/PgDn/Home/End)
- User messages shown with `[User]` prefix in accent color
- Assistant responses in normal text
- Thinking indicator (animated dots) when agent is processing

#### ToolCallCard
```
╭─ Tool: grep (searching) ─────────────────────────────╮
│  searching for "catch" in src/agent-manager.ts...     │
│  Found 7 occurrences                                  │
╰──────────────────────────────────────────────────────╯
```
- Displayed as embedded box during tool execution
- Shows tool name, status, and output
- Animated for active tools, dimmed for completed

#### ThinkingIndicator
```
▍ thinking...
```
- Shown when agent is processing but no output yet
- Animated dots

#### InputHarness
```
════════════════════════════════════════════════════════
> _ (cursor blinks here)
Ctrl+C to interrupt · Enter to send · ↑↓ for history
```
- Bottom-pinned input line
- Text input with send on Enter
- Interrupt on Ctrl+C
- History of previously injected inputs
- Visual indicator when agent is processing (show spinner)

### Data Flow

```
AgentRunner (agent-runner.ts)
  │
  │  AgentSession events
  │  session.subscribe(callback)
  │  ──────────────────────────────────────────
  │  Token streaming via message.content updates
  │  Tool call events via tool_use blocks
  │  Status changes via session lifecycle
  │  ──────────────────────────────────────────
  ▼
LiveSessionView
  │
  │  Subscribes to session events on mount
  │  Updates local state arrays
  │  Calls requestRender() on each event
  │
  │  handleInput:
  │    - Enter → session.inject(inputBuffer)
  │    - Ctrl+C → session.interrupt() / abort()
  │    - Escape → close view (done())
  │    - ↑/↓ → scroll stream pane
  │
  ▼
  render(width) → string[]
  │
  ▼
TUI.differential render → terminal output
```

### Input Harness Integration

The harness bridges user intent to the running agent:

```typescript
// When user presses Enter in input mode:
session.inject(inputBuffer)
// This calls the session's appendEntry or similar
// Agent receives it as next input in its turn loop

// When user presses Ctrl+C:
session.interrupt()  // graceful stop
// OR
abortController.abort()  // hard stop
```

**What the harness can inject:**
- Follow-up questions / clarifications
- Additional context from the main session
- Commands to steer the agent's direction
- Corrections or hints

**How it integrates with the main session:**
- When the live session closes, the agent's final output is captured
- This output is returned to the parent Agent tool caller
- The main session can then consume the output as a normal tool result
- The harness enables an intermediate interactive debugging/steering mode

---

## Phase C: Deep Integration with Main Session

### Event System for Agent Sessions

For the live session TUI to work with streaming data, the agent runner needs to emit granular events. Currently `AgentSession.subscribe()` fires on any change. We need:

```typescript
interface AgentSessionEvents {
  'token': (text: string) => void
  'tool_start': (toolName: string, args: any) => void
  'tool_end': (toolName: string, result: any) => void
  'turn_end': (turnNumber: number) => void
  'status': (status: AgentStatus) => void
  'usage': (usage: { input: number; output: number }) => void
  'error': (error: Error) => void
  'message': (msg: SessionMessage) => void  // raw message event
}
```

These events can be derived from the existing `AgentSession` message subscription by parsing message deltas. No changes to the agent runner's core needed if we can extract them from the existing message stream.

### AgentSession Enhancements

The `AgentSession` from `@mariozechner/pi-coding-agent` may need wrapper methods:

- `session.inject(text: string)`: Append a user message and trigger agent response
- `session.interrupt()`: Send a steering message or abort gracefully
- `session.onToken(callback)`: Convenience wrapper around message subscription

If the session API doesn't expose inject/interrupt natively, we can implement them via the existing `sendMessage` / `abort` primitives.

### Persistent Agent Widget

The widget (shown above the editor via `setWidget()`) already exists in `agent-widget.ts`. Enhance it to:

- Show real-time activity descriptions for running agents
- Animated spinners that update on each render cycle
- Quick-action keybindings: press key to focus a running agent's live view
- Collapsible tree for multiple running agents

---

## Dependency Strategy

### pi-tui (already a peer dependency)
The `@mariozechner/pi-tui` package already provides:
- `Component` interface
- `TUI`, `Container`
- `Input`, `Editor`, `SelectList`, `Box`, `Text` components
- Overlay support
- Differential rendering

**No new UI dependencies needed.** The pi-tui Component model is sufficient for live streaming.

### React / Ink
**Not required.** While Ink provides a declarative JSX model, pi-tui's imperative Component model is a better fit because:
- It's already a dependency
- Its `requestRender()` / differential rendering matches our streaming use case
- Components compose via `Container` with focus management
- Overlays are built-in (`TUI.showOverlay()`)
- Input handling is built-in (`handleInput` on Component, `addInputListener` on TUI)
- No JSX transpilation step needed

The trade-off is ergonomics: Component state updates require explicit `requestRender()` calls rather than automatic React reconciliation. But for a focused session viewer with well-defined state transitions, this is manageable.

---

## File Map (Complete)

### Existing files (keep/modify)

| File | Status | Notes |
|------|--------|-------|
| `src/index.ts` | MODIFY | Replace inline menu with agent-menu.ts delegates |
| `src/agent-runner.ts` | MODIFY | Add event emission for granular session events |
| `src/ui/agent-widget.ts` | MODIFY | Enhance for live status + keyboard shortcuts |
| `src/ui/conversation-viewer.ts` | KEEP | Post-hoc session viewer still useful |
| `src/ui/schedule-menu.ts` | KEEP | No changes |
| `src/model-resolver.ts` | MODIFY | Add `scanModelsConfig()`, `getModelPickerOptions()` |

### New files (Phase A — Management UI)

| File | Purpose |
|------|---------|
| `src/ui/agent-menu.ts` | Top-level /agents command hub |
| `src/ui/agent-list.ts` | Rich agent list with source grouping |
| `src/ui/agent-detail.ts` | Agent detail card + actions |
| `src/ui/agent-editor.ts` | Inline editing (tools, model, config) |
| `src/ui/create-wizard.ts` | Multi-step creation wizard |
| `src/ui/model-picker.ts` | Model browser scanning models.json |
| `src/ui/tool-picker.ts` | Categorized tool selection |

### New files (Phase B — Live Session TUI)

| File | Purpose |
|------|---------|
| `src/ui/live-session.ts` | Live agent session TUI component |
| `src/ui/session-stream-pane.ts` | Streaming token renderer |
| `src/ui/session-tool-calls.ts` | Tool call visualization |
| `src/ui/session-input-harness.ts` | Input injection interface |
| `src/ui/session-status-bar.ts` | Status bar component |

### New files (Phase C — Integration)

| File | Purpose |
|------|---------|
| `src/session-events.ts` | Agent session event types |
| `src/session-injector.ts` | Input injection bridge |

---

## Implementation Order

```
Phase 1: model-resolver.ts → model-picker.ts → tool-picker.ts
Phase 2: agent-list.ts → agent-detail.ts → agent-editor.ts → create-wizard.ts → agent-menu.ts
Phase 3: session-events.ts → session-stream-pane.ts → session-tool-calls.ts
Phase 4: session-input-harness.ts → session-status-bar.ts → live-session.ts → agent-runner events
Phase 5: Integrate into index.ts, widget enhancements, main-session bridge
```

Phases 1-2 are the management UI (ctx.ui.* based).
Phases 3-4 are the live session TUI (pi-tui Component based).
Phase 5 ties everything together.

All phases build on the same architectural foundation: agent runner → events → TUI renders.

---

## Key Design Decisions

### 1. No React/Ink
pi-tui's `Component` + `TUI` + differential rendering is sufficient. Adding React/Ink would add ~15MB of deps, a JSX build step, and contend with pi-tui's existing rendering pipeline. The imperative Component model is a better fit for a focused session viewer.

### 2. Events over polling
The live session TUI subscribes to agent session events rather than polling. This gives token-by-token responsiveness and minimizes overhead. The existing `session.subscribe()` pattern from conversation-viewer.ts proves this works.

### 3. ctx.ui.* for config, pi-tui for live
Configuration dialogs are quick, sequential interactions — ctx.ui.* is perfect for these. Live sessions need concurrent rendering with partial updates — pi-tui Components handle this.

### 4. Input harness as a Component
The input harness is a pi-tui `Component` with `Focusable` interface, placed at the bottom of the live session layout. It uses pi-tui's built-in `Input` component under the hood. When focused, the user can type and send messages to the running agent.

### 5. Streaming via message subscriptions
Rather than modifying the agent runner to emit fine-grained events, we derive token streams from the existing `AgentSession.message` subscription. A `StreamParser` utility watches message deltas and emits the higher-level events (token, tool_start, tool_end, turn_end).

---

## Risks and Mitigations

| Risk | Mitigation |
|------|-----------|
| `AgentSession.subscribe()` fires too coarsely for token-level streaming | Parse message deltas in the subscription callback to extract new tokens |
| `session.inject()` not exposed by pi-coding-agent | Implement via sendMessage / appendEntry; may need upstream contribution |
| pi-tui Components don't compose well for complex layouts | Use `Box` for layout; `Container` for child management; test with prototype |
| models.json has 30+ models, select becomes unwieldy | Group by provider; show context window; use pi-tui `SelectList` with filtering |
| `ctx.ui.custom()` overlay steals keyboard from agent widget | Implement focused component switching; Esc returns to main view |
| Agent widget and live session both try to render simultaneously | Widget remains simple (status + counts); live session is an overlay that suppresses widget |

---

## Appendix: pi-tui Component Lifecycle

```typescript
interface Component {
  render(width: number): string[];
  handleInput?(data: string): void;
  wantsKeyRelease?: boolean;
  invalidate(): void;
}

interface Focusable {
  focused: boolean;
}

// TUI manages focus and input routing:
// 1. Input received → TUI.handleInput(data)
// 2. TUI routes to focused overlay → overlay.handleInput(data)
// 3. Or TUI routes to focused component → component.handleInput(data)
// 4. If unhandled → addInputListener handlers

// Components request re-render:
const tui: TUI;
tui.requestRender(); // schedules async diff + repaint

// Overlays:
const handle = tui.showOverlay(component, options);
handle.hide(); // dismiss overlay
tui.hideOverlay(); // dismiss topmost
```
