Skip to content

P0 parity block + agent capabilities: providers, MCP, subagents, input UX — review-hardened#6

Merged
bajajra merged 26 commits into
mainfrom
bun-migration
Jul 18, 2026
Merged

P0 parity block + agent capabilities: providers, MCP, subagents, input UX — review-hardened#6
bajajra merged 26 commits into
mainfrom
bun-migration

Conversation

@bajajra

@bajajra bajajra commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Everything since the cutover PR (#4): the full P0 parity block from docs/PARITY_BACKLOG.md, the top agent-capability features, a wave of UX polish — and two adversarial review rounds (10 verified findings each) with every finding fixed. 98 tests, 0 fail.

Agent capabilities

  • Provider matrix (§1) — provider-neutral ModelClient + createModelClient dispatch: OpenAI-protocol and Gemini streaming clients (Anthropic-shaped history translated per provider), round-robin wrapper, per-provider env keys/default bases, protocol-faithful mock servers + engine-level parity tests
  • MCP (§2) — official SDK, all 3 transports, Python-compatible mcp_servers.json, auto-start on session boot, tools merged into the engine belt, /mcp status/start/stop/restart, liveness wiring for restart-on-crash
  • Generic subagentsinvoke_subagent with isolated child context, parallel fan-out of batched calls, one delegation level; the Python specialized-agent roster is deliberately dropped (owner decision, recorded in the backlog)
  • Headless surfaces (§3) — groundwork: headless.ts / headless_run.ts + tests
  • Anti-stall + request cap — auto-continue when a turn ends with open plan items; the per-turn request cap (silent killer of long implementation turns at 25) raised to 500 (100 subagents), env-tunable, and loud + resumable when hit
  • Real-token compaction — trigger uses the API's actual input_tokens (default 200k, clamped to 80% of registry context_length), checked mid-turn too; estimate is cold-start fallback only

Input & UX (§4/§5)

  • Ref-backed useReadline: cursor editing (Ctrl+A/E/K/U/W, word jumps), multiline, Ctrl+R reverse search (Esc cancels), surrogate-pair-safe motion, paste/emoji wholesale insert — one buffer owns idle, steering, and question modes
  • @path + /cmd Tab completion with working candidate cycling; !cmd shell passthrough
  • Config commands: /set (+SETTING_DEFS table), /show, /cd, /reasoning, /verbosity; history surgery /pop /prune /truncate on genuine turn boundaries (tool-traffic-safe, /prune 0 refused)
  • Welcome banner with Misty pixel art (truecolor half-blocks), session name in the input-frame border, session /rename + auto-generated titles, resume replays the last 3 exchanges, arrow-key option menus for agent questions, input box visible while busy
  • Demon Slayer theme identity: mist = Mist Breathing (default), hinokami = Sun, moon = Moon (16 forms), each with its own spinner-verb vocabulary + verb color; context-aware spinner glyphs (scan/fillsweep/cascade/sparkle)

Review hardening

Two structured review rounds (multi-angle finders + verification) produced 10 confirmed findings each — all 20 fixed in-branch, including: round-robin factory reachability, Gemini strict-schema + functionResponse name mapping, o-series max_completion_tokens, mid-stream error surfacing, MCP runtime wiring (was test-only), a TDZ crash in @-completion, dead idle history, and history surgery that could orphan tool_use pairs (API 400s). Regression tests pin each class.

Verification

  • cd ts && bun test: 98 pass / 0 fail (507 assertions, 22 files)
  • Live tmux verification throughout: subagent fan-out, themes/banner, completion cycling, history recall, Ctrl+R/Esc, /prune 0 refusal, resume replay, real-token compaction line
  • Docs: full parity backlog with two priority rankings (parity + importance-to-agent)

🤖 Generated with Claude Code

bajajra and others added 26 commits July 17, 2026 13:29
The bordered prompt box no longer disappears during a running turn — it
stays below the status line (Claude-Code style) so steering is always
visible and inviting. Steer hint moved beneath the box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ask_user now accepts optional `options` (2-4 short choices). The TUI
renders them Claude-Code style: ↑/↓ + Enter or 1-9 to pick, a final
"✎ type my own answer…" row, and typing any character drops straight
into free-text (Esc returns to the options). Options flow through
question.asked → protocol → UI; mock model gains a MOCK_ASK=1 stage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A distinctive glyph per activity, paired with the existing verb pools —
the timeline is legible at a glance (design principle + frames from
Hux's lynx-agent-spinners, MIT):

  study   scan       reading head sweeping the line
  edit    fillsweep  cells filling up
  shell   cascade    output tumbling out
  general sparkle    the Mist signature (unchanged)

Frames normalized to a fixed 4-cell width so the status line never
shifts; each spinner animates at its native interval (60-150ms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- /rename <name> renames the current session; persisted in the store
  meta so /resume and /sessions show it (works even before first turn)
- New sessions auto-name themselves: after the first turn a tiny
  background model call turns the first question into a 3-6 word title
  (falls back to the raw prompt; never overwrites a user-chosen name)
- session.renamed event → transcript line (✎ named/renamed)
- SessionStore.rename rewrites the meta line in place; store test added
- Mock model answers title-generation calls with a canned name

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third theme, from the Demon Slayer VS Code spec: Tanjiro's checkered
green-black (borders/sage), Yoriichi's crimson + gold sun crest
(error/accent), flame-arc ramp (deep red → crimson → orange → gold →
yellow-gold) on warm parchment text — firelight, not a stock dark theme.

Themes can now carry their own spinner vocabulary (Theme.verbs): while
hinokami is active the status line announces the twelve Sun Breathing /
Hinokami Kagura forms (Dance, Raging Sun, Fire Wheel, Setting Sun
Transformation, …) instead of the standard verb pools; switching themes
restores them. Context glyphs (scan/fillsweep/cascade/sparkle) still
follow the work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flagship palette moves from generic pastels to the Mist Breathing spec:
fog lifting into clarity — ramp pale fog → deeper haze → distant fog →
mint hair tips → sudden clarity; brand #2E8C7C (sudden clarity), dim
#93A6A4 (distant fog), warning/path #C9A227 (the gold eye), error
#B5555A (muted, not alarming), border #45646E (calm slate).

Spinner announces the seven Mist Breathing forms while active (Low
Clouds Distant Haze … Obscuring Clouds), via the same Theme.verbs
override hinokami uses. Verb tests now pin standard pools under
cinnamon (the remaining no-override theme).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fourth theme, from the Moon VS Code spec: moonrise ramp (deep purple →
kimono pattern → living blade → flame markings → gold irises), brand
#9C2F52 (the living blade), error #E0435A (red sclera, unsubtle),
path/warning #D4AF5A (gold irises), moss-green success, moonlit
lavender text on the moonless-night palette.

Spinner announces the sixteen Moon Breathing forms while active (Dark
Moon Evening Palace … Moonbow, Half Moon) via Theme.verbs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interactive launches open with a bordered banner: ◆ Mist vX · centered
"Welcome back, <user>!" · Misty the ghost rendered as truecolor pixel
art (half-block ▀/▄ cells, two pixels per row — 20×16 grid from the
character sheet: blue-gray cloud body, mint crown arc, navy eyes,
highlight, trailing tail dots) · model/engine/session/cwd facts.
Right rail (≥92 cols): getting-started tips + most recent session with
a /resume pointer. Long cwds middle-truncate; the rail is fixed-width.

One-shot argv runs keep the compact one-line brand so scripted and
tmux output stays clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implementation detail, not user-facing info — the info line now reads
just model (+ session id when resuming).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inventory of the deprecated Python app vs the shipped TS app: what's
already at parity, P0-P2 workstreams with task checklists (provider
matrix, MCP, headless surfaces, input UX, config, subagents, safety,
plugins, DBOS, images, browser), and cross-cutting gotchas (sealed-
binary dynamic import, test asymmetry, Windows, packaging, config/
registry compat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second axis alongside parity priority: capability/autonomy impact.
Provider matrix and MCP top both lists; input UX drops to last; goal
loops, skills, and memory jump from P2 long-tail to agent top-8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per owner decision the Python specialized-agent roster (code-puppy,
planning-agent, qa-kitten, helios, agent-creator) is NOT ported.
Subagents are generic, Claude-Code-style:

- invoke_subagent engine tool: fresh child MistEngine per task; only
  the final report returns to the parent (context isolation)
- Several calls in one model response run CONCURRENTLY (fan-out), also
  concurrent with the batch's sequential tools
- One delegation level: children don't get invoke_subagent; child
  system prompt forbids user questions and mandates a final report
- Model override propagates to children; child diffs still render
- subagent.started/step/done/error envelopes → transcript blocks
  (◇ subagent “label” … ◇ label done + report preview) and live
  [label] step rows
- Mock model MOCK_SUB=1 stage (two-child fan-out + canned child
  reports); engine test asserts parallel starts, isolated reports in
  parent history, and the turn completing after

Also removes a dead stray 'case toolblock' JSX block that had slipped
into handleEnvelope (unreachable; Bun doesn't typecheck).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lors

- Narration blocks: pin the text column width — inside <Static> the
  flexGrow child next to the ● bullet could overflow the terminal and
  hard-wrap at column 0 (the 'need to replicate' artifact)
- Input box now carries the session name right-aligned in its top
  border, Claude-Code style (updates on /rename, auto-title, resume;
  clears on /new); same frame while busy
- Theme.verb: spinner-verb text gets a per-theme color — mist
  sudden-clarity green, hinokami flame orange, moon living-blade
  crimson, cinnamon pumpkin — stats stay dim

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…view

Provider matrix (P0 §1, written by the parallel Mist session):
ModelClient interface + createModelClient dispatch; OpenAI-protocol and
Gemini streaming clients with Anthropic-shaped-history translation;
RoundRobinClient; per-provider env keys and default bases; parity mock
servers (mock_openai, mock_gemini) + client unit tests. MCP manager
groundwork (mcp.ts, unwired). Also lands the engine anti-stall fix
(auto-continue when a turn ends with open plan items + prompt rule).

Review fixes applied (all 10 findings from the adversarial review):
1. round_robin reachable: configFromDef no longer demands an endpoint
   for distributor types (+ factory-path regression test)
2. Gemini functionDeclarations strictly name/description/parameters —
   spread-in input_schema was a 400 on every tool request
3. Gemini functionResponse.name now echoes the originating call via an
   id→name map (was hardcoded 'tool', breaking multi-turn tool use)
4. OpenAI reasoning models get max_completion_tokens (max_tokens is a
   400 on o-series/gpt-5); early o1 previews skip reasoning_effort
5. Mid-stream {"error":{…}} frames now throw in OpenAI+Gemini clients
   instead of ending as a silent blank turn (the 429 lesson)
6. MCP liveness wired (client.onclose/onerror → status) so
   restart-on-crash can actually fire and dead tools stop advertising
7. minimax gets MINIMAX_API_KEY (was falling through to Anthropic's);
   anthropic.ts docstring no longer claims the bare minimax type
8. Missing-key preflight restored for public endpoints (clear config
   error instead of a wire 401); keyless custom endpoints still allowed
9. round_robin cycle guard in the engine resolver
10. Engine-level parity test: full runTurn loop through OpenAIClient

Cleanup from the same review: dead DEFAULT_BASE table and dead
thinkingStart removed (openai.ts), MIST_DEBUG_STREAM capture added to
Gemini, env lookup hoisted out of stream hot loops, static rename
import + @modelcontextprotocol/sdk declared in @mist/core, honest
comments (partialArgs claim dropped, thoughtSignature typed).

41 tests green; binary rebuilds clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resuming (-c, -r, /resume picker) now paints the tail of the stored
conversation above the ↺ resumed line: '— earlier in this session —',
then the last 3 exchanges — user prompts and assistant text verbatim
(text-with-tools renders as ● narration), tool activity collapsed to
one dim '⋯ ran N tool calls' line per gap. Steer prefixes stripped;
auto-continue nudges and compaction summaries stay hidden. Both resume
paths share one historyTailItems helper (unit-tested; replaces the old
last-user+last-assistant-only replay that showed nothing when a turn
ended in tool calls).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of the P0 session's repeated mid-implementation stops,
confirmed from its session file: the last turn contained EXACTLY 25
assistant requests (MAX_REQUESTS_PER_TURN), ended on a tool_use whose
result was recorded, then the loop exhausted its budget and exited
silently — no final text, no event, and the anti-stall nudge never ran
because the cap exit bypasses that branch.

- Cap raised: default 100 (subagents 50), MIST_MAX_REQUESTS env
- Cap exit is now loud: '⏸ request cap hit (N model calls this turn)'
  step + a final message telling the user progress is saved and to
  send 'continue' to resume
- Regression test: capped run surfaces the pause and the resume path

Also snapshots the parallel P0 session's in-progress work at a green
checkpoint (65 tests): MCP wired into the engine tool belt (§2) and
headless-surface groundwork (headless.ts/headless_run.ts, §3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trigger previously used the chars/2.5 estimate at a 60k default —
compacting a 200k-window model 3x too eagerly, and only checking at
turn start. Now:

- Every API response's input_tokens (the actual context size) is
  tracked; the estimate is only the cold-start fallback
- Threshold: MIST_COMPACT_AT, default 200,000 real tokens, clamped to
  80% of the model's registry context_length when declared
- Mid-turn check after each tool round — a 500-request turn can outgrow
  the window between turn starts (one failed attempt stops retries)
- compact() resets the reading (history changed → size unknown until
  the next request) and reports the real before-count in the ⇣ line
- /status context figure now shows the real reading too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Input stack (§4) rebuilt on a single owner:
- useReadline state now lives in REFS with a render tick — input()
  mutates synchronously, so multi-key batches never hit stale closures;
  all readline tests pass (were 9/9 failing, omitted from the P0
  session's '0 fail' report)
- TDZ crash fixed: recordHistory declared before submit's dep array
  (typing @path completions crashed the app with a ReferenceError)
- rl.reset() TypeError on every submit → rl.handle.reset()
- readline now owns ALL modes: busy/steer and question free-text use
  the same buffer (full editing everywhere); the old input state,
  historyNav, and applyCompletion duplicates are deleted — idle ↑/↓
  history works again (was writing an invisible buffer)
- Emoji/CJK/paste chunks insert wholesale; cursor motion + backspace
  are surrogate-pair aware
- Tab cycling rebuilt: anchor+tail captured at first Tab, candidates
  cycle in place; /cmd completions drop the trailing space that
  emptied the token
- Ctrl+R search: Esc now cancels (was advertised, swallowed); Ctrl+T
  transpose dropped — the plan toggle owns the key (doc + test agree)

MCP (§2) actually wired at runtime:
- EngineSession attaches McpManager + auto-starts servers on construct
  (mcp_servers.json was silently ignored outside unit tests), exposes
  .mcp for the /mcp command (which read a nonexistent property), emits
  mcp.status (⚡ started / failures in transcript), stopAll on /quit

History surgery (§5) on genuine turn boundaries:
- turnStarts() skips tool_results/steers/auto-continue riding in
  user-role messages — /pop no longer orphans tool_use pairs (API 400),
  /prune keeps whole turns atomically, /truncate drops whole turns and
  never cuts inside one; /prune 0 refused (silently wiped everything)
- Regression tests for tool-traffic boundaries + orphan prevention

98 tests, 0 fail. Live-verified in tmux: @completion (no crash), Tab
cycle, ↑ history recall, Ctrl+R + Esc, /prune 0 refusal, submit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Misty the ghost retires; the welcome banner now perches the Owlgebra
owl — 22×18 pixel grid, same half-block truecolor renderer: blue tufts
and wings, light face/belly with dots, big amber eyes with white
glints, yellow beak + talons on a brown branch, sparkles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The owl hands the perch to a witchy crow, per the new character sheet:
side profile facing left on a 24×20 grid — charcoal witch hat with
green band, gold buckle and crooked tip, gray-blue head, green eye,
big orange beak, slate wing behind a lighter chest. Same half-block
truecolor renderer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The profile crow read as mush at half-block resolution. Redrawn with
the discipline that makes Clawd clean: front-facing, every row a
strict mirror (asserted at design time), 8 flat colors, big features
(3px eyes with pupils, center beak, buckled hat band), solid
silhouette with zero stray pixels. 20×18 grid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Birds retired. The character is now the brand itself: a small round
mist cloud with dot eyes, rosy cheeks, a tiny mouth, and cloud-puff
base. Clawd-grade minimalism — 3 colors, 16×12, strictly symmetric.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fresh-system experience was broken: the default model was minimax-m3
(dead quota) and any model name not in a registry file hard-failed —
a new user with just an API key couldn't start.

- scripts/install.sh: clone → ./scripts/install.sh gets a working
  `mist` — installs Bun if missing, builds ts/dist/mist, links onto
  PATH (~/.local/bin or /opt/homebrew/bin; MIST_BIN_DIR overrides),
  prints first-run model setup
- Zero-config models: default is now claude-opus-4-8; well-known names
  (claude-*, gpt-*, o*, gemini-*) resolve without a registry entry,
  keyed by the provider env var (ANTHROPIC/OPENAI/GEMINI_API_KEY);
  registry entries still win
- getConfiguredModelName now respects $HOME (was hardwired to real
  homedir — untestable)
- README: install-on-a-new-system section with the one-command path,
  first-run model options, and the custom-endpoint registry example
- Tests for the defaults + builtin resolution (env stashed/restored —
  sibling test files share the process env)

100 tests, 0 fail; script verified end-to-end on this machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tests relied on env leaked from agent.test.ts (MIST_MODEL,
MIST_MODELS_JSON, MOCK_FAST) and reused its mock port (9871) — passing
locally by file-order luck, failing on CI. Now self-sufficient: own
registry + dedicated ports (9931/9932), env stashed and restored,
MOCK_FAST set in-file. Verified passing in isolation AND in the full
suite (100 tests, 0 fail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bajajra
bajajra merged commit 4ac9179 into main Jul 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant