Skip to content

Render all tool activity inline: edit ribbons, inline questions, bare terminal commands - #1731

Open
bra1nDump wants to merge 18 commits into
mainfrom
worktree/better-tool-rendering
Open

Render all tool activity inline: edit ribbons, inline questions, bare terminal commands#1731
bra1nDump wants to merge 18 commits into
mainfrom
worktree/better-tool-rendering

Conversation

@bra1nDump

@bra1nDump bra1nDump commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Context for reviewers new to this area

Happy is three cooperating projects:

  • This repo (slopus/happy) — the mobile/web app (packages/happy-app), plus the legacy CLI (packages/happy-cli) and shared wire types (packages/happy-wire). When docs say "mobile app", they mean happy-app.
  • slopus/happy-agent — the coding-agent backend ("Happy Agent" / Rig). It produces the sessions the app renders and syncs them through the server as encrypted messages.
  • Happy Desktop — the desktop sibling, not touched here.

Tool calls arrive in the app as { name, input, state, result } records. Rendering is keyed by tool name:

  • sources/components/tools/knownTools.tsx — titles, icons, zod input schemas per tool.
  • sources/components/tools/views/_all.tsx — registry mapping tool name → custom view component.
  • sources/utils/toolDisplay.ts — name sets that decide chrome: compact row vs card vs inline ribbon.
  • sources/sync/agentCommunications.ts — selectors for agent → user questions (request_user_input communications riding on agent state).

The direction of this PR: everything the agent shows or asks lives inline in the chat transcript. No full-screen modals, no hidden content, and nothing the agent is blocked on may ever be invisible.

What changed

1. Every edit tool renders as one expanded inline ribbon

Write, MultiEdit, NotebookEdit, and CodexDiff now join Edit and CodexPatch in the shared InlineFileEditBlock: file-path header, +/− stats, full highlighted diff, no generic card chrome, permission footer inside the ribbon. Diffs are expanded by default; an edit above 40 changed lines starts collapsed to a ~300 px preview with a "Show all N changed lines" control (fileEditMetrics.ts holds the threshold).

2. Agent questions are inline, always answerable, never dropped

  • The full-screen AgentQuestionModal is deleted. It had already become unreachable dead code once choice forms moved into the transcript; ~660 lines of parallel form logic went with it.
  • A pending form whose tool call the transcript renders is answered there (RequestUserInputView / AskUserQuestionViewInlineQuestionForm).
  • A pending form whose tool message never arrived (older CLI, trimmed history) renders the same inline form pinned above the composer (AgentQuestionBanner), keyed off a set of transcript question tool ids computed in SessionView.
  • De-trimming: selectPendingCommunications no longer drops empty forms (they surface as a dismissible notice), an option-less question always accepts written text even if the producer sent allowCustom: false (shouldOfferCustomAnswer), and a question tool call with unparseable input falls back to the ordinary card so its permission footer stays reachable (hasRenderableQuestionContent).

3. Terminal rows show the command

A compact terminal row used to read "Ran 1 command: git status" next to a terminal icon. It is now just git status. The action label remains only when no command can be extracted (e.g. write_stdin).

4. Gemini tool rendering removed

The Gemini harness is dead. Its tool views, knownTools entries (GeminiBash/Patch/Diff/Reasoning, lowercase edit, execute, shell, search, read), and the flavor special-case in ToolView are gone. Old sessions containing Gemini tools fall back to the generic tool card. Harness selection UI (AgentInput, settings) and the CLI's src/gemini/ runner are untouched — follow-up.

Tool rendering registry (state after this PR)

Group Tools Rendering
Questions AskUserQuestion, request_user_input Inline form in transcript; un-joined pending forms pinned above composer; unknown kinds → dismissible notice
Edits Edit, MultiEdit, Write, NotebookEdit, CodexPatch, CodexDiff Inline ribbon, expanded, collapse only above 40 changed lines
Terminal Bash, CodexBash, exec_command, run_terminal_command, write_stdin Compact row: terminal icon + bare command, tap for full output
Read/search/web Read, Grep, Glob, WebSearch, WebFetch, list_*, … Compact rows with detail
Tasks/orchestration Task, TodoWrite, spawn_agent, create_workspace, … Compact rows; Task groups nested tools
Still on compact fallback (future work) apply_patch, search_replace (GPT-harness raw shapes), update_plan Compact row; the diff/plan body is not yet parsed client-side

Compatibility

  • Rendering-only changes; no producer or wire changes in this PR beyond the already-optional tool-call-end.result/isError fields (additive, old apps strip them).
  • Readers stay liberal: CodexPatchView still accepts every historical changes shape; unknown tool names still get the name-agnostic compact row.
  • Released apps are unaffected until this ships; old CLIs against this app keep working (that is what the pinned-form fallback and shape-tolerant parsers are for).

How to test

  • pnpm vitest run sources in packages/happy-app — 1034 tests, all passing; new coverage in agentCommunications.spec.ts, toolDisplay.test.ts, fileEditMetrics.spec.ts.
  • Dev screens (Settings → Developer):
    • File Edit Preview (/dev/file-edits) — Claude Edit, Codex CodexPatch, Write, MultiEdit, and a 60-line edit demonstrating the collapse control.
    • Inline Questions (/dev/agent-question) — pending, answered, and superseded question states.
    • Tool Views Demo (/dev/tools2) — the broader registry.

Screenshots

Help welcome capturing these from the dev screens above (iOS + web is enough):

  • Small edit ribbon (Edit / CodexPatch side by side)
  • Large edit collapsed + expanded
  • Write and MultiEdit ribbons
  • Inline question pending / answered / superseded
  • Un-joined question form pinned above the composer
  • Terminal compact rows (bare command)

Related work

Producer-side normalization: slopus/happy-agent#12. It maps Codex apply_patch into the CodexPatch { changes, modify: { old_content, new_content } } shape this app's ribbon renders, so mobile never parses Codex patch grammar, and sends real tool results on tool-call-end — the optional fields this PR's wire schema accepts.

Follow-ups (not in this PR)

  • Parse apply_patch / search_replace raw shapes into the ribbon (or normalize producer-side like happy-agent does).
  • Render update_plan as a checklist like TodoWrite.
  • Remove Gemini from harness selection UI and the CLI runner.
  • Move CodexPatchView's shape tolerance and parseRawRequestUserInputQuestions into the sync layer.
  • Unused translation keys left behind by the modal (agentQuestion.title/submit/chooseMultiple/submitFailed/moreQuestions) — drop in a later sweep to avoid an 11-file churn here.

Gemini harness support is dead. Sessions recorded with Gemini tools fall
back to the generic tool card. Harness selection UI and the CLI runner
are untouched and tracked as follow-up.
A compact terminal row read 'Ran 1 command: git status' next to a
terminal icon that already says what it is. The row is now the command
itself; the action label remains only when no command can be extracted.
Write, MultiEdit, NotebookEdit, and CodexDiff join Edit and CodexPatch in
the shared file-edit ribbon: file path header, +/- stats, and the full
highlighted diff expanded by default with no card chrome. Edits above 40
changed lines start collapsed to a preview with a show-all control so one
large edit cannot swallow the transcript.
The full-screen question modal is gone. A pending form whose tool call
the transcript renders is answered there; one whose tool message never
arrived renders the same inline form pinned above the composer. Nothing
the agent is blocked on is dropped any more: empty forms and unknown
kinds surface as a dismissible notice, an option-less question always
accepts written text even when the producer refused it, and a question
tool call with unrenderable input falls back to the ordinary card so its
permission footer stays reachable.
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