Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions web/src/components/agent-chat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,15 @@ describe("AgentChat — prompt-select race guard wiring (frozen {text, revision}
// The block grammars are provably scoped to the pane's own adapter (spec T8): an agent with no
// adapter gets the plain raw mirror — no prompt-select buttons, no chrome stripping, no re-surfaced
// status strip — because running Claude-tuned matchers on an unverified TUI could mis-lift or
// mis-strip its output. codex is such an agent; omp has an adapter but lifts no dialog kind at all.
// mis-strip its output. opencode is such an agent (codex graduated to its own adapter); omp has an
// adapter but lifts no dialog kind at all.
describe("AgentChat — block-grammar scoping (an agent with no adapter)", () => {
// A codex agent sharing the Claude fixture's ids, so only the agent kind differs from the default.
const codexAgent = { ...fixtureAgents[0]!, agent: "codex" };
// An opencode agent sharing the Claude fixture's ids, so only the agent kind differs from the default.
const opencodeAgent = { ...fixtureAgents[0]!, agent: "opencode" };

it("does NOT lift a codex tail menu into buttons — it stays raw mirror text", () => {
renderChat({ text: MENU_TEXT, agent: codexAgent });
// No native prompt buttons: the Claude prompt-select grammar never runs for codex
it("does NOT lift an adapterless agent's tail menu into buttons — it stays raw mirror text", () => {
renderChat({ text: MENU_TEXT, agent: opencodeAgent });
// No native prompt buttons: the Claude prompt-select grammar never runs without an adapter
expect(screen.queryByRole("button", { name: "Yes" })).not.toBeInTheDocument();
// …and the menu row shows verbatim in the raw mirror instead (drivable by the keys pad).
expect(screen.getByText(/1\. Yes/)).toBeInTheDocument();
Expand All @@ -392,8 +393,8 @@ describe("AgentChat — block-grammar scoping (an agent with no adapter)", () =>
expect(screen.queryByText(/❯/)).toBeNull(); // the input box was stripped off the mirror
});

it("leaves a codex input-box buffer fully raw — no status strip, box kept in the mirror", () => {
renderChat({ text: STATUS_TEXT, agent: codexAgent });
it("leaves an adapterless agent's input-box buffer fully raw — no status strip, box kept in the mirror", () => {
renderChat({ text: STATUS_TEXT, agent: opencodeAgent });
// The statusline is NOT hoisted into an app strip — it stays inside the raw <pre> mirror…
const status = screen.getByText(/\[Opus 4\.8\] ~\/webapp · main/);
expect(status.closest("pre")).not.toBeNull();
Expand Down
27 changes: 27 additions & 0 deletions web/src/fixtures/panes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,33 @@ scripts/capture-fixture.sh <paneId> <name> [lines] # paneIds: /api/snapshot
(`less -R <file>`) for private content before `git add` — prefer generating states in a sandbox
pane over capturing real work sessions.

## Codex corpus (captured 2026-08-22, Codex v0.149.0, sandbox panes)

Byte-faithful `format:ansi` captures with one sanitization pass, every substitution
LENGTH-PRESERVING so row padding stays byte-identical: the operator's username and hostname
(`collie-user`, `collies-macbook-pro-1` — painted in the shell prompt line and host config
path, which no sandbox can avoid), the Darwin per-user temp-dir token
(`sanitizedtempdirtoken000000000`), and the Codex session UUIDs from `codex resume` lines
(`00000000-0000-7000-8000-…`). Codex's chrome is boxless: a
`› ` prompt row (wrapping onto two-space-indented continuation rows) with a dot-separated status
row beneath (`<model> · <cwd> · Context N% left · weekly N% left`); every section of a screen is
separated by exactly one blank row. Approval dialogs need `-c approvals_reviewer=user` — with
`auto_review` (the capture host's default) eligible requests route through a reviewer subagent
and the observed commands were approved with no dialog painted.

| Fixture | State / what's in it | Herdr status |
|---|---|---|
| `codex--trust-prompt.txt` | First screen in an untrusted directory: trust paragraph, `› 1. Yes, continue / 2. No, quit`, `Press enter to continue`. Digit 2 live-probed: quit immediately | `blocked` |
| `codex--fresh-idle.txt` | Welcome banner box, tips, empty `› Ask Codex to do anything` composer, status row | `idle` |
| `codex--draft.txt` | One-line draft on the `› ` row | `idle` |
| `codex--draft-wrapped.txt` | Long draft word-wrapped onto a two-space-indented continuation row | `idle` |
| `codex--working.txt` | `• Working (3s • esc to interrupt)` above a still-visible composer (Codex queues mid-turn) | `working` |
| `codex--approval-exec.txt` | Exec approval: header, Environment/Reason, `$ command`, options `1. Yes, proceed (y)` / `2. …don't ask again… (p)` / `3. No… (esc)`, enter/esc footer. Digits 1 and 3 live-probed (1 ran the command, 3 rejected it — file verified absent); `y` probed too | `blocked` |
| `codex--ask-fruit.txt` | `request_user_input` card: `Question 1/1` header, options with descriptions plus the auto-added `None of the above`, notes footer. Digit live-probed: answers AND submits | `blocked` |
| `codex--ask-wizard-q1.txt` | Two-question set, `Question 1/2`; footer adds `←/→ to navigate questions`. Digit probed: answers and advances | `blocked` |
| `codex--ask-wizard-q2.txt` | Same set, `Question 2/2`; footer `enter to submit all`. Digit probed: submits the whole set | `blocked` |
| `codex--ask-notes-focused.txt` | Notes box open (`› Add notes`, footer `tab or esc to clear notes`): a digit would TYPE — the adapter refuses to raw | `blocked` |

## Corpus (captured 2026-07-04, Claude Code TUI as of that date)

| Fixture | State / what's in it | Herdr status |
Expand Down
Loading