Skip to content

Make every agent herdr detects visible: Grok and Antigravity chats, terminal rows for the rest, Codex fixes - #172

Merged
cobanov merged 17 commits into
mainfrom
hc-45-agent-support
Oct 20, 2026
Merged

cobanov merged 17 commits into
mainfrom
hc-45-agent-support

Conversation

@woosal1337

@woosal1337 woosal1337 commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Rebased onto main at 2ce4a9a (#178), with the changes asked for in review. Commit ids below are the rebased ones.

HerdrChat read the history of four agents and treated the other fourteen that herdr detects as plain terminal rows (iOS) or nothing at all (Android, and the Agents view). This branch does three things:

  1. One description per agent. src/lib/herdr/agents.ts holds each agent's name, history reader, herdr integration, default titles and the text for a missing session. The supported list, names, titles, integration banner and install button all read it, and TranscriptStore.transcriptPath picks the lookup by reader. Adding an agent used to touch 15–20 files.
  2. Grok and Antigravity as first-class chats, next to Claude Code and Codex.
    • Grok: its history, model, tool runs, titles, approval prompts, slash commands and push previews, and starting it from New Chat.
    • Antigravity (agy): its history, model and tool runs, and starting it from New Chat.
  3. An agent the app cannot read is a terminal row everywhere. On Android, which has no terminal view, and in the Agents view, such an agent was missing. It now shows there as a terminal row too.

It also fixes a misrouted send, follows Codex rewinds, and shows Codex calls that were silently dropped.

#18

#18 asks for one agent at a time, on request, and no terminal view. Grok is the requested agent here (used daily, 69 local sessions). The screen chat for agents with no reader was dropped in review: such an agent stays a terminal row, since the Terminal tab already covers it.

Changes, one commit each

Agents

  • fb6655d A chat's message goes to the agent whose transcript it shows. In a workspace with Claude and an agent the app cannot read, focus alone picked the target, so the message went to the other agent and the bubble said "Failed to send". sendTarget picks a conversational agent first.
  • a1b67ef The agent table, and Grok's reader. Grok keeps each session in ${GROK_HOME:-~/.grok}/sessions/<cwd>/<id>/updates.jsonl, a log of ACP session/updates; the store finds the folder by the exact id herdr reports, never by recency. Demo workspace w8 and regression/grok.
  • 9002aac An agent with no reader is a terminal row on Android and in the Agents view, where it was missing. Demo workspace w9 (opencode) has one, and regression/list-modes finds its row in the Agents view.
  • da18f19 CLAUDE.md records the rule: a new agent is a table entry, a parser and a Demo pane, never another === 'codex' branch in a feature. An agent with no reader stays a terminal row.

Grok

  • 8e12808 Approval prompts: Grok's ┃ 2 (○) Yes, proceed rows parse, and the digit alone answers (Grok's own keyboard docs). Which agents answer on the digit is digitAnswers in the table; Codex and unknown agents keep digit then Enter.
  • 10a2bdc Titles: <session> - grok is read as the session, and a bare grok names no chat.
  • decfbd9 Slash commands: Grok's ~60 commands from its bundled docs, sent like Codex's. The palette now follows sendTarget, so it offers the menu of the agent a message would go to.
  • 6b6a6cd Push previews for Grok, Pi and OMP; the watcher also honours CLAUDE_CONFIG_DIR, CODEX_HOME and archived_sessions. WATCHER_VERSION is 7. What reaches the relay is unchanged.

Antigravity (against agy 1.3.6 on this Mac)

  • 600d7d9 Antigravity chats are read from agy's own conversation log.
    • Where the log is. herdr's antigravity-cli hook reports the conversation id, which is the name of the folder under ~/.gemini/antigravity-cli/brain. The log is .system_generated/logs/transcript.jsonl in that folder.
    • How a step is read. Each line is one step: the prompt (the text inside <USER_REQUEST>), the model's turn (thinking, reply, tool calls), or a tool's output.
    • Pairing a call with its output. An output belongs to the call in the step just before it. Paired by order alone, an interrupted call in a real conversation took the next output and shifted every one after it.
    • The header. It shows the model a prompt switched to and the context of each turn.
    • Demo. The Demo adds wA ("assets"), and regression/agy opens it, checks the header and the folded run, and sends a message.
  • 1e272c0 The search finds Antigravity by the name on its row. On a real host, a search for "Antigravity" found nothing: the search matched herdr's id for the agent, agy. It now matches the id and the name, and regression/agy finds its row that way.

UI flows

  • 5a4cf24 Four flows that came with the tabs (Files, Terminal and Computers tabs; every computer is its own host (builds 87 to 90) #178), composer-keys, terminal, space-layout and list-modes, failed on the longer Demo list in dark and light: a row scrolled near the end sat under the floating tab bar, and a tap there scrolled the list to the top. They now open their rows through the search field (_open-chat.yaml), as history and multi-agent do.
  • a16d4ec regression/history and regression/multi-agent find their rows with the search field, not by scrolling (HC-115). With the longer Demo list, a row moved between groups during the scroll, and the tap landed on the search field.

Codex (against the source at rust-v0.163.0)

  • 187a16c A rewind (0.157+) goes on in rollout-<ts>-<id>_<rollout id>.jsonl. The lookup finds every file of the id and, when there are several, asks Codex's state database (threads.rollout_path) which is current. Never by recency; no answer opens nothing.
  • 56b31e1 The earlier turns of a rewound or forked rollout live in the file its session_meta.history_base names. Paging continues there from end_byte_offset.
  • 23a736f local_shell_call, web_search_call, tool_search_call/_output and image_generation_call show in the tool run. Every phase of a reply shows; the pre-0.157 channel: 'analysis' stays hidden.
  • ac78f62 A compressed rollout (.jsonl.zst) is reported as unreadable, not as missing.
  • 4121c80 The guide says to trust the Herdr hook in /hooks. Codex runs no untrusted hook, so herdr never gets the session id. This is the likely reason Codex chats show "Can't identify this chat".

Test plan

  • npx tsc --noEmit: no errors
  • npx expo lint: no errors
  • npx jest: 1,979 passed, 14 skipped
  • python3 -m unittest discover -s scripts -p 'test_*.py': passes
  • The Grok reader against 8 real local sessions: 31 prompts, 308 tool calls, every finished result paired with its call
  • regression/grok passed once in Dark on an iOS 27 simulator (development build)
  • The agy reader against the one real local conversation (agy 1.3.6): 20 steps, 13 tool calls, every output with its own call, and the interrupted call left open
  • regression/agy on the iPhone 17 Pro simulator (iOS 27), Dark and Light
  • A real agy pane after herdr integration install antigravity-cli and an agy restart: herdr reports the session, and the chat shows its history
  • npm run e2e in Dark and Light on the iPhone 17 Pro simulator (iOS 27), Release build of the rebased stack (Make every agent herdr detects visible: Grok and Antigravity chats, terminal rows for the rest, Codex fixes #172 to Close the remaining product gaps: one socket bridge, Antigravity dialogs, encrypted keys, accessibility #177): 52 of 52, the four flows 5a4cf24 fixes rerun on their own after it. Screenshots opened in both modes.
  • A real Grok pane on a host (this Mac, through a private sshd): the history matches Grok's own screen, a new chat from New chat, a message and its reply, a command run streamed in. Not done: a picture, because the iOS 27 simulator could not add photos.
  • A real Codex host: hook trusted, a rewound session

The Codex changes follow upstream source and are not tested against real rewound files, because Codex is not installed here. The Grok approval fixture is built from the rows herdr's grok.toml quotes, not from a live prompt.

Not in this PR

@cobanov

cobanov commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks, we'd like to take everything except the screen chat. Could you drop b69a624 (ScreenView, the w9 demo pane and regression/screen-agent)? Agents without a reader should stay terminal rows, since the Terminal tab already covers them. If they're missing on Android or in the Agents view, showing them there as terminal rows would be the preferred fix.

The thread picked its send target by focus alone: the focused pane with any
agent. In a workspace holding Claude and an agent the app cannot read
(opencode, say), focusing the other pane on the host sent the phone's message
there, while the thread kept showing Claude's transcript, so the echo was
never confirmed and the bubble said "Failed to send". Both the polled target
and the one re-read at send time now prefer a conversational agent, focused
first, and fall back to any agent only when the chat has none.

(cherry picked from commit 4663cef)
Grok was a terminal row with no history: the app read Claude, Codex, Pi and
OMP, and which agents those were was retyped in a dozen files, so adding one
meant touching fifteen to twenty.

`src/lib/herdr/agents.ts` is now the one place an agent is described: its
name, the reader for its history, its herdr integration, its default
terminal titles and what to say when its session cannot be found. The
supported list, agent names, Pi-family checks, default titles, the
integration banner, the install button and the "unsupported" copy all read
it. `TranscriptStore.transcriptPath` and `forgetTranscript` pick the lookup
by the agent's reader, so the thread and the chat list's previews no longer
branch on the agent themselves. Claude's session signature stays bare and
Codex's stays `codex:<id>`, so no cached chat is re-keyed.

Grok's integration reports the session id, which names the session's folder
under `${GROK_HOME:-~/.grok}/sessions/<encoded cwd>/`. The store finds that
exact folder (never the newest one for a cwd) and reads its `updates.jsonl`,
a log of ACP `session/update` notifications. `acp.ts` maps them: prompts
(not the ones Grok writes for itself), replies, thoughts, tool calls and
their final update as the result, and the prompt's model id for the header.
Measured against Grok 1.0.52 on eight real sessions: 308 calls, every
finished result paired. Grok's tool names fold like the others ("ran 1
command"). New Chat can start Grok through `herdr agent start --kind grok`.

The "can't identify this chat" screen offered Claude's integration for any
agent it did not list; it now offers the agent's own, or none.

The Demo gains a Grok workspace (w8, "infra"), answered by the same lookup
and format, with `regression/grok` covering its row, header model, folded
run and a confirmed reply.

(cherry picked from commit 8e1b1fd)
The rule the last two commits rest on, written where the next change will
read it: a new agent is a table entry, a parser and a Demo pane, never a
new branch on its kind in a feature, and an agent with no reader is still a
chat that shows its screen.

(cherry picked from commit 7115da7)
Since Codex 0.157 (openai/codex #38127, paginated local history by default
from #47900) a rewind writes the thread on into a new rollout,
`rollout-<ts>-<id>_<rollout id>.jsonl`, and leaves the old file. The lookup
searched only `rollout-*-<id>.jsonl`, so after a rewind the chat kept showing
the old file, reverted turns included, and no new turn ever arrived.

Every file of the id is found now. One is opened as before. More than one is
settled by Codex's own pointer, `threads.rollout_path` in its state database,
never by recency; without an answer (no `sqlite3` on the host, a schema this
was not written against) nothing is opened, as before.

Written from Codex's source at 0.163, not checked against a real rewound
session: Codex is not installed here. A rewound file's earlier history lives
in its `history_base` prefix file, which is not followed yet, so such a chat
shows the turns since the rewind.

(cherry picked from commit 3f914eb)
Codex records four calls the Responses API runs on its own, and the reader
dropped all of them: a model's local shell (`local_shell_call`, an argv whose
output arrives as a `function_call_output`), web search, tool search with its
own output item, and image generation. A turn that searched the web or ran a
command through the local shell showed nothing of it. Each now folds into the
tool run like any other call: "ran 1 command", "searched 1 time".

Codex 0.157 replaced the `channel` field on a reply with `phase`
(commentary, partial answer, final answer). The TUI shows every phase, so the
thread does too; `channel: 'analysis'` stays hidden, since rollouts written
before 0.157 still carry it. A test pins each phase.

Shapes from codex-rs/protocol/src/models.rs at rust-v0.163.0, and
rollout/src/policy.rs for which items are persisted.

(cherry picked from commit b7d8da1)
A rewound or forked Codex rollout holds only its own turns. Its first line,
`session_meta`, names the file the earlier ones are in: `history_base`, a
rollout id (the thread id, or the `_<rollout id>` of a rewind) and the byte
offset where the inherited part ends. The thread stopped at the top of the
new file, so after a rewind the conversation seemed to start at the rewind.

`TranscriptStore.codexHistoryBase` reads that pointer and finds the prefix
file by its exact rollout id in the live and archived sessions; anything but
exactly one match is no answer, never a guess. When paging reaches the top
of a Codex file, the thread continues in the prefix from that offset, and
only a file with no base is the start. The search shared with the session
lookup is one helper now.

Shape from codex-rs/protocol/src/protocol.rs (`HistoryPosition`) at
rust-v0.163.0. Not run against a real rewound session: Codex is not
installed here.

(cherry picked from commit dff450f)
Codex can compress a rollout untouched for 7 days into `.jsonl.zst`
(rollout/src/compression.rs). The feature is off by default at 0.163, but
once on, the lookup found no `.jsonl` and the chat said the session's
transcript was not on the host, sending the reader to look for a file that
is there. The lookup now sees the compressed file in the same search and
says HerdrChat cannot read a compressed Codex history yet. A plain file
beside it is still opened as before.

(cherry picked from commit bdff6a1)
…pects

A Grok approval reached the phone as bare chips. Grok draws each choice as
`┃  2 (○) Yes, proceed` (herdr's grok.toml, from Grok 1.0.34 pane reads),
and the parser only knew `2.` and `2)`, so it found no menu. Those rows now
parse, the `┃` gutter already gone with the other borders; the letter rows
(`z (○) Type your answer here`) take typing, not a key, and stay out.

Grok picks an option on its digit alone (`1`–`9`, Grok's
03-keyboard-shortcuts.md), as Claude does. Every agent but Claude was sent
the digit and then Enter, which for Grok would land the Enter in whatever
had the keyboard after the card closed. Which agents answer on the digit is
now `digitAnswers` in the agent table; an agent not described there keeps
the digit and Enter.

The fixture is built from the rows herdr quotes, not captured from a live
Grok prompt: the Grok here runs with approvals off.

(cherry picked from commit 2ea08cd)
Grok titles its terminal `grok`, then `<session> - grok` once the session
has a name, with a braille spinner and activity text ahead of it during a
turn and "⚠ Action Required" during a prompt (herdr's grok.toml, from Grok
1.0.34). Read as is, a Grok chat was titled "grok", or "⠧ Reading files -
Deploy check flake - grok" while it worked.

The session name is now the segment before ` - grok`, and a bare `grok` is
a default title that names no chat, so the row falls through to the
workspace as Claude Code's "Claude Code" does. Pi's default title is still
not listed: neither herdr's pi.toml nor anything here shows one.

(cherry picked from commit f872a6c)
The host watcher quoted the agent's last words for Claude and Codex only, so
a push from a Grok, Pi or OMP chat said just "finished its task". It also
read `~/.claude` and `~/.codex/sessions` directly, ignoring the
`CLAUDE_CONFIG_DIR` and `CODEX_HOME` the app honours, and never looked in
`archived_sessions`.

- Claude and Codex are read from their configured folders, archived Codex
  rollouts included. A rewound Codex thread goes on in
  `rollout-<ts>-<id>_<rollout id>.jsonl`; every such file is that one
  thread's, so the one written last is quoted.
- Grok's `updates.jsonl` is found by its exact session id: the reply is the
  last message chunk, and a waiting Grok names the command it waits on.
- Pi and OMP report their journal's path, read only when it is absolute,
  ends in `.jsonl` and holds no `..`.
- A Grok terminal title is read as the app reads it (`<session> - grok`), and
  `grok` joins the default titles that name no chat.

What reaches the relay is unchanged: the same fields, with
`NOTIFY_PREVIEW=0` still keeping the agent's words off it. WATCHER_VERSION
is 7, so the app offers hosts the update.

(cherry picked from commit 039bac5)
Typing `/` in a Grok chat showed nothing, while Grok's own terminal lists
about sixty commands. Grok gets its menu as Codex does: a fixed list, from
the 04-slash-commands.md bundled with Grok 1.0.52, each name, its arguments,
and the first sentence under it. Aliases are left to the fuzzy match, and
enabled skills, which Grok lists too, are not read.

A picked Grok command is sent the way a Codex one is: typed and run, with no
prompt wait and no second Enter, and no wait for a receipt, since Grok
writes nothing to its log for a command. Grok's `/home`, which leaves the
session, joins the commands that are filled rather than sent from a tap.

The palette now follows `sendTarget`, so it offers the menu of the agent a
message would go to; with an agent the app cannot read focused beside
Claude, it showed no commands while messages went to Claude.

(cherry picked from commit bbe3e1f)
Codex runs a hook only after a person trusts it (openai/codex #20321), and
the Herdr hook is what reports a Codex session's id. Untrusted, it never
runs, herdr has no id, and the chat says "Can't identify this chat" with
nothing in the guide that names the cause. The Codex section now does: trust
the hook in `/hooks`, then exit and resume.

(cherry picked from commit 5dbd795)
agy is the agent most used on the host, and the phone listed none of its
chats. herdr detects it (`agent: "agy"`), but the app read no Antigravity
history, so an agy pane was at best a screen. The format was held back as
undocumented (HC-63); it is read here from agy 1.3.6 itself.

- The table names it: `agy`, "Antigravity", integration `antigravity-cli`.
  herdr's integration hook reports the conversation id and, when agy gives
  it, the transcript's path (read from the hook herdr installs).
- The id is the name of its folder under ~/.gemini/antigravity-cli/brain,
  and the log is `.system_generated/logs/transcript.jsonl`. The `_full`
  copy beside it differs only in keeping tool output whole. A reported path
  is taken as reported, with the same checks as OMP's.
- `agy.ts` reads a step a line: the prompt (the text inside
  <USER_REQUEST>, so a sent message's echo is confirmed), the model's turn
  (thinking, reply, tool calls with their JSON-encoded arguments decoded),
  and a tool's output. An output is the step after its call's, which names
  the call: paired by order alone, an interrupted call in a real
  conversation took the next output and shifted every one after it.
- The header has the model a prompt switched to ("Gemini 3.8 Flash (High)")
  and the context of each turn.
- run_command, view_file, search_web and edit_file fold as commands, reads,
  searches and edits.
- New chats can start Antigravity (`agent start --kind agy`).
- A chat waiting for its session names Antigravity, not `antigravity-cli`:
  the placeholder is given the integration's name, which for every agent
  before this one was the agent's own.

The Demo gains an Antigravity workspace (wA, "assets"), and
regression/agy opens it, checks the model and the folded run, and sends a
message. Tests read a transcript in agy's shape, the interrupted call, and
the Demo's chat end to end.

(cherry picked from commit 07783b8)
regression/history and regression/multi-agent failed on the simulator once
the Demo list grew past a screen (HC-115). A row scrolled into the middle
moved before the tap, as agents finished and rows changed group, and the tap
landed on the search field above it. Typing the workspace's name into the
search narrows the list to the row, which then cannot move away.

(cherry picked from commit 76fc037)
On a real host, a search for "Antigravity" found nothing, though the row
says Antigravity and the empty list says to try an agent's name: the search
matched herdr's id for the agent, `agy`. Every other agent's name holds its
id, so only this one was lost. The search now matches both, and
regression/agy finds its row that way.

(cherry picked from commit 76bf5c4)
… Agents

An agent with no history reader (opencode, Copilot, Letta and the rest herdr
detects) is a terminal row under its workspace in Spaces, which opens its
pane's terminal. Android draws no terminal rows, since a shell's would only
open the screen saying the terminal is iPhone and iPad only, and the Agents
view lists agents with a reader. So such an agent was in no list at all
there.

On Android it now keeps its terminal row (a shell still has none), and the
Agents view lists it as a terminal row of its own, under no card, in the
group its status puts it in, found by its agent, folder or workspace. The
Demo gains such an agent again (w9, "docs", opencode), as a terminal row, and
regression/list-modes checks its row in Agents.
…asses

The Demo's Grok, opencode and Antigravity workspaces make its list longer,
and four of the flows that came with the tabs (#178) failed on it in dark
and light: composer-keys, terminal, space-layout and list-modes. Each
scrolled to a row and tapped or checked it where the shorter list had put
it. A row scrolled to near the end sits under the floating tab bar, and a
tap there lands on the Chats tab, which scrolls the list back to the top;
a scroll UP to a row that is now below fails outright.

composer-keys and terminal open w2 through `_open-chat.yaml`, the search
field, as history and multi-agent already do (HC-115). space-layout finds
w7 by searching "site", which leaves its card and both rows in view.
list-modes scrolls UP to w6's working agent, which sits above w2's Idle.
All four pass in dark and light on the stack's Release build.
@woosal1337 woosal1337 changed the title Make every agent herdr detects a chat: Grok history, screen chat for the rest, Codex fixes Make every agent herdr detects visible: Grok and Antigravity chats, terminal rows for the rest, Codex fixes Oct 16, 2026
@woosal1337

Copy link
Copy Markdown
Collaborator Author

Done in the rebase onto main (2ce4a9a): b69a624 is dropped, so there is no ScreenView, no Demo screen chat and no regression/screen-agent. An agent without a reader stays a terminal row, and 9002aac shows it as one on Android and in the Agents view, where it was missing. The full list of changes across the stack is in #177 (comment).

woosal1337 added a commit that referenced this pull request Oct 16, 2026
The Demo's docs space holds only OpenCode, and its card said "no agent · 1
tab · 1 pane" above a row reading "OpenCode · demo/docs". The line counted
the agents the app reads and nothing else; one it cannot read is a terminal
row (#172), and still an agent (HC-135).

`spaceStats` counts both, and what each is doing from its pane's status, so
a waiting OpenCode reads "1 agent needs you". The panes fallback counts each
pane once. rowText.test.ts covers alone, waiting and beside a Claude, and
fails without the change; regression/space-layout finds the docs space by
search and reads "1 agent" on it, in dark and light.
@cobanov
cobanov marked this pull request as ready for review October 20, 2026 08:03
@cobanov
cobanov merged commit 089d3d4 into main Oct 20, 2026
2 checks passed
cobanov added a commit that referenced this pull request Oct 20, 2026
Both sides bumped the watcher from 6 to 7 on their own, so it is 8 here:
a host running build 91's watcher gets the Grok, Pi and OMP previews.
The grok and agy flows join the new e2e flow list and the thread flows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cobanov pushed a commit that referenced this pull request Oct 26, 2026
The Demo's docs space holds only OpenCode, and its card said "no agent · 1
tab · 1 pane" above a row reading "OpenCode · demo/docs". The line counted
the agents the app reads and nothing else; one it cannot read is a terminal
row (#172), and still an agent (HC-135).

`spaceStats` counts both, and what each is doing from its pane's status, so
a waiting OpenCode reads "1 agent needs you". The panes fallback counts each
pane once. rowText.test.ts covers alone, waiting and beside a Claude, and
fails without the change; regression/space-layout finds the docs space by
search and reads "1 agent" on it, in dark and light.
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.

2 participants