# Command Center

The Command Center (CC) is the dashboard's conversational chat panel. It opens from the **CC** button in the top-right header and lets you drive the engine — dispatching work items, saving notes, approving plans, and asking questions about minions state — through a persistent Claude/Copilot session with full repo awareness.

CC is intentionally a thin wrapper around the runtime CLI: state changes happen via `Bash`-tool `curl` calls to the dashboard's own REST API, not via parsed delimiter blocks. The end-to-end flow is `dashboard/js/command-center.js` `_ccDoSend()` → `POST /api/command-center` (or `/api/command-center/stream`) in `dashboard.js` (`handleCommandCenter`) → `engine/llm.js` `callLLM({ direct: true })` → claude/copilot CLI session persisted in `engine/cc-sessions.json`. Per-turn API mutations are correlated via the `X-CC-Turn-Id` header and surfaced as standalone `role='action'` chips rendered outside the assistant bubble (`_ccActionResultLine` + `addMsg('action', ...)`).

For canonical detail (system prompt, session lifecycle, turn-ID surfacing pipeline, doc-chat integration, and CC API contract), read [`CLAUDE.md`](../CLAUDE.md) — see the **CC API Contract** and **Sessions** sections — and the source in [`dashboard/js/command-center.js`](../dashboard/js/command-center.js), [`dashboard.js`](../dashboard.js) (`handleCommandCenter`), and [`prompts/cc-system.md`](../prompts/cc-system.md).

## Error surfacing contract (W-mpmwxni2000c25c7-d)

Both CC and Doc-Chat emit a typed error envelope so the dashboard can render a red `.cc-error` bubble (role=alert) with a Retry button and stop the spinner immediately. Errors arrive in two shapes:

1. **SSE mid-stream** (most common). `writeCcEvent` / `writeDocEvent` write the wire as `event: error\ndata: {…envelope…}\n\n` so consumers using `addEventListener('error', …)` see them as named events. The JSON payload still carries `type: 'error'` for clients that only read the `data:` line, so both parser strategies keep working.
2. **Non-2xx POST response** (pre-stream — `readBody` guard, prototype-pollution rejection, etc.). The body is the same envelope as JSON; the dashboard's `if (!res.ok)` branch parses it and stashes it on the thrown Error as `_ccErrorEnvelope`, then renders the same red bubble.

Canonical envelope (`_buildCcErrorEnvelope` in `dashboard.js`):

```json
{ "type": "error",
  "message": "Human-readable cause + remediation hint",
  "code": "model-unavailable | auth-failure | context-limit | budget-exceeded | crash | cc-turn-timeout | worker-spawn-failed | acp-handshake-failed | worker-died",
  "retryable": false,
  "availableModels": ["gpt-4o", "gpt-5.4", "..."],
  "runtime": "copilot"
}
```

`code` is clamped to the allowlist (`CC_ERROR_CODES` constant); unknown codes collapse to `crash`. `retryable: true` is informational — there is **no auto-retry**; the dashboard always offers a manual Retry button instead. Auto-retrying these errors is a footgun because most are operator-fix categories (auth, budget, missing model) where re-spawning makes no progress.

**Watchdog (`engine.ccTurnTimeoutMs`, default 5 min, clamped 10s–1h).** Per-turn **no-progress** cap. Each turn arms a `setTimeout` that fires `event: error` with `code: 'cc-turn-timeout'`, aborts the in-flight LLM call, and ends the stream when no liveness signal arrives within the window. The timer **resets on every progress event** — token chunk, tool-call notification, tool-update — so a turn that's actively streaming or running long tools (e.g. `Bash` running `npm test`, `WebFetch`/`WebSearch` against slow targets, agent sub-dispatches) survives indefinitely up to the outer 1h `CC_CALL_TIMEOUT_MS` hard ceiling. Wired via `liveState._bumpTimer` (CC stream path) and the factory shape of `_raceCcDocChatTimeout` (doc-chat stream path); the non-streaming doc-chat path has no progress hooks and retains wall-clock semantics. Surfaced in Settings → CC overrides.

**No auto-retry policy.** The backend never re-spawns the LLM after an error envelope. The client never silently resends the user's turn. Retry is a single-click manual action — guards against silent budget burn on `budget-exceeded`, infinite loops on `auth-failure`, and accidental re-charges on `context-limit`. The 429 + reconnect paths (rate-limited fetch retry, SSE reconnect-after-disconnect) remain — those are transport-level, not error-envelope-level.

## Per-turn surfacing pipeline

CC handler generates `ccTurnId = 'cct-' + shared.uid()` per request; injected into sysprompt AND prompt body via `_ccTurnHeaderPart(turnId)` (load-bearing: on resumed sessions `engine/llm.js` skips re-sending the sysprompt, so without body injection CC keeps the stale turn ID). Handler reads via `_readCcTurnIdHeader(req)` and calls `_recordCcTurnCreation(turnId, ...)` on success. End-of-turn: `_buildSyntheticActionResultsForTurn` produces synthetic `{action, result}` pairs (`_serverExecuted: true`). Client renders as standalone `role='action'` messages outside the assistant bubble. TTL: 5 min. Endpoints wired: `/api/work-items`, `/api/notes`, `/api/plan`, `/api/knowledge`, `/api/watches`.
