Skip to content

feat: Grok Build first-class harness adapter - #123

Merged
AltanS merged 3 commits into
AltanS:mainfrom
kennymcavoy:feat/grok-first-class
Aug 24, 2026
Merged

feat: Grok Build first-class harness adapter#123
AltanS merged 3 commits into
AltanS:mainfrom
kennymcavoy:feat/grok-first-class

Conversation

@kennymcavoy

Copy link
Copy Markdown
Contributor

Summary

Grok Build panes become first-class like Claude: composer chrome stripped with an opaque status strip re-surfaced natively, probed dialogs lifted into tappable buttons, a journal adapter, and the Grok slash catalog.

  • Chrome (Tier 1)web/src/lib/harness/grok/: the rounded composer box is located by its bottom status border and stripped; the status run is opaque across models/efforts (never matches Grok 4.6 / (high)). composerReady gates the reply path; the startup screen's bare [stable] chip is matched literally as chrome so a fresh pane accepts its first message. Grok's full-width background paint on blank rows is dropped (no color inference) so the mirror doesn't stripe; glyph rows keep every background.
  • Permission cards (prompt-select, family permission) — classified, not layout-pinned: the footer must name the family and count its rows (1/N:select + Tab:next option + Ctrl+o:always-approve + Ctrl+c:cancel), the last row must be the reject, the row above it the one-shot Yes, and every earlier row must prove itself a persistent mode change — those never become buttons. Unclassified rows refuse the whole card. Both live layouts (3-option rm, 4-option file-edit) lift; digits probed per layout (PERMISSION_NOTES.md), including the one-shot-doesn't-persist negative control.
  • ask_user_question (family select) — radio cards lift with per-row digits (digit submits directly — probed). The z free-text row is modelled as feedback and locks the buttons whenever the keyboard sits on it; the inner hint's Enter:edit verb is the tell even when the row repaints idle (live-probed: a digit there types into the field). Esc-parked cards send Tab — the footer's own key — before the digit, because a bare digit is silently swallowed in scrollback (probed both ways). Checkbox cards stay raw (a digit submits rather than toggles). Multi-question wizards step through as consecutive cards.
  • Plan approval (menu) — footer-named keys only, no digits (ADR 0009); composer gated while the review is up.
  • Journalbridge/journal/grok.ts reads chat_history.jsonl under $GROK_HOME (override COLLIE_GROK_ROOT) through containedRealpath, refusing non-UUID refs.
  • Neutral-model additions (called out per your feat: add AGY (Antigravity) harness adapter, interactive prompts, and slash commands #99 note about separate arguments): PromptFeedback.purpose — Grok's z row is "free-text", so the renderer offers no feedback composer and submitPromptFeedback refuses it (the Claude plan-feedback choreography is the wrong recipe); and PromptOption.keyLabel — the badge shows the digit when a recovery key precedes it. Claude behavior unchanged, pinned by the existing claude suites.

The #99 bar

  • Exact agent string only: adapterFor stays Object.hasOwn over adapter.agent; grok-build catalog folding lives in canonicalAgent, never in the registry.
  • Dated fixture corpus (25 grok--*.txt), notes files with the verified recipes (PERMISSION/ASK/PLAN_NOTES.md), describeAdapterConformance running grok against the claude+omp cohorts and vice versa.
  • Live verification: run by me against real Grok Build 1.0.5 panes per the arrangement in feat: add AGY (Antigravity) harness adapter, interactive prompts, and slash commands #99 — every dialog exercised end-to-end through the bridge (buttons pressed via the guarded /keys path, files created/removed on disk as the negative controls), plus stale-signature replays refused with 409.
  • No digits synthesized from numbered rows: permission/ask digits are probed specific grammars; the plan menu is driven by the keys its footer names.

Honesty notes

  • The six Tier-1 composer files (grok--fresh-idlegrok--user-bubble) are sanitized structure fixtures (plain UTF-8, no ESC); the live 3-SGR-run border shape is pinned separately in grok/markers.test.ts from a dated probe. All dialog captures are byte-faithful format:ansi.
  • Deliberately not lifted (each needs its own capture + probe): checkbox asks (digit submits, not toggles), typing a custom z answer from the phone, and Grok's cancel-turn panel.
  • Observed variance: after a plan approval Grok sometimes gates the plan's own writes with a permission card and sometimes doesn't — both paths handled.

Suggested CHANGELOG entry

Grok Build gets a first-class harness adapter — strips the composer chrome, re-surfaces the status strip, lifts permission cards, ask_user_question radios/wizards and plan approval into native buttons, and reads the Grok session journal.

Verification

cd web && bun run typecheck clean; full web suite + conformance green; bun test bridge/ green; live pass against real panes as above. Versions untouched at 0.32.0 per the fork-PR rule.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PFH7jQVDVhPGC6FqLetNfd

@AltanS

AltanS commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Reviewed — same verdict as #124: strong shape, fail-closed classification, and the journal containment is actually stricter than our own adapters (re-running containedRealpath on the cached path is a nice touch). Version files correctly untouched. All gates pass here (root 687, web 2971, both typechecks clean).

Three changes requested before merge:

  1. Agent-string match. operator-scope.ts:39 folds grok*grok for the slash catalog, but harness/registry.ts and bridge/journal/registry.ts match "grok" exactly. If Herdr reports the agent as grok-build on any install, that pane gets the slash palette and nothing else — no chrome strip, no dialogs, no journal. Can you confirm the exact agent string Herdr reports for your Grok Build panes? If it can be grok-build (or varies), widen the harness + journal registries to the same fold the catalog uses.
  2. One byte-faithful composer capture. Six of the Tier-1 fixtures are sanitized (no ESC), as you disclosed. markers.test.ts pins the real 3-SGR border, which covers part of the gap, but the chrome strip is regression-guarded against partly synthetic input. One format:ansi capture of a real composer (idle + one with a draft) would close it.
  3. scripts/journal-probe.ts — the per-root rework is good but unrelated to the adapter; please split it into its own commit inside this PR so it can carry its own changelog line.

Smaller, take or leave: the free-text entry in prompt-select-block.tsx:63-73 carries five empty-string fields gated only by convention — a discriminated union would turn misuse into a type error.

On our side: Tier-2 means we live-verify the four dialog recipes against a real Grok Build pane before merge. Plan: merge #124 first, then this one after a rebase (six trivial overlaps).

Flattening every root into one newest-first cap let a populated healthy
root starve a broken sibling out of the list — the condition this
script exists to catch.
Grok Build panes get the Claude-level treatment: stripped composer
chrome with an opaque status strip, native buttons for the probed
dialogs, a journal adapter, and a slash-command palette.

Registered by exact agent string only (the AltanS#99 bar): adapterFor stays
Object.hasOwn over adapter.agent; grok-build catalog folding lives in
canonicalAgent. Every keystroke the adapter emits comes from a dated
live probe recorded in the notes files, against a byte-faithful fixture
corpus, with describeAdapterConformance running grok against the claude
and omp cohorts and vice versa.

Chrome (Tier 1): the rounded composer box is located by its bottom
status border and stripped; the status run is opaque across models and
efforts. composerReady gates the reply path. The startup screen's bare
[stable] telemetry chip under the box is chrome (matched literally —
any other bracket tag is torn transcript and refuses), so a fresh pane
accepts its first message. Grok paints background across fully blank
rows, which striped the mirror; empty rows lose their paint — no
colour inference — while glyph rows keep every background they drew.

Permission cards: classified, not layout-pinned. The footer must name
the family and count its rows (1/N:select), the last row must be the
reject, the row above it the one-shot Yes, and every earlier row must
prove itself a persistent mode change — those never become buttons.
Unrecognized control rows, or text below the options, refuse the whole
card. Both live layouts (3-option rm, 4-option edit) lift to Yes/No.

ask_user_question: radio cards lift with digit keys; the z free-text
row is modelled as feedback (purpose "free-text" — Collie never types
into it) and locks the buttons whenever the keyboard sits on it, which
the inner hint row's Enter:edit verb reveals even when the row repaints
idle (the hint is matched as a whole anchored row, never as a substring
of prose). Esc-parked cards send Tab (the footer's own key) before the
digit — a bare digit is swallowed in scrollback. Checkbox cards stay
raw: a digit submits rather than toggles. Multi-question wizards step
through as consecutive lifted cards.

Plan approval: a menu of footer-named keys only (ADR 0009 — no digits),
with the composer gated while the review is up.

Dialogs replace the screen from their first option row down, so the
question stays in the raw mirror above the buttons — the same contract
as Claude's prompt-select. The dialog signature spans the whole card;
the bridge race guard refuses a stale press (409) after the screen
moves.

Journal: bridge/journal/grok.ts reads chat_history.jsonl from
$GROK_HOME (override: COLLIE_GROK_ROOT) via containedRealpath, refusing
non-UUID refs.

Neutral-model changes riding along: PromptFeedback.purpose (free-text
rows render no feedback composer and submitPromptFeedback refuses
them), and PromptOption.keyLabel (the badge shows the digit when a
recovery key precedes it). /auto joins the dangerous two-tap set — it
changes approval behavior. Claude behavior unchanged, pinned by the
existing claude suites.

Versions untouched per the fork-PR rule; CHANGELOG line in the PR body.
Replace the sanitized no-ESC chrome fixtures with format:ansi dumps
from a sandbox pane, so locateComposer is guarded against the real
3-SGR status border. Darwin temp-dir token is length-preserved.

Herdr reports agent "grok" on every Grok Build pane on this host
(kind list + pane.agent + agent_session.agent, 2026-08-23).
@kennymcavoy

Copy link
Copy Markdown
Contributor Author

Review follow-up:

  1. Agent string. Confirmed live 2026-08-23: Herdr's kind list is grok, and every Grok Build pane on this host reports agent: "grok" / source: herdr:grok (pane.agent and agent_session.agent). No grok-build variant showed up. Registries stay exact; the grok-build catalog fold in canonicalAgent is unused here. Noted on harness/grok/index.ts.

  2. Byte-faithful composer. Replaced grok--fresh-idle.txt and grok--draft-single.txt with format:ansi captures from a sandbox pane (empty idle box, then a testing stuff draft). Darwin temp-dir token is length-preserved. The other four Tier-1 chrome files remain structure fixtures.

  3. journal-probe split. fix(journal): probe each root on its own is now its own commit ahead of the adapter.

@kennymcavoy
kennymcavoy force-pushed the feat/grok-first-class branch from 3a052a5 to 06c2175 Compare August 23, 2026 19:52
AltanS added a commit that referenced this pull request Aug 24, 2026
Grok Build first-class harness adapter
@AltanS
AltanS merged commit f1a7dbc into AltanS:main Aug 24, 2026
@AltanS

AltanS commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Merged — thanks. I did the post-#124 rebase on our side: union of the four both-add files, plus one semantic conflict git missed (main renamed the no-adapter test agent codexAgent → opencodeAgent; grokAgent now builds from that — f1a7dbc). Follow-up 7d8ffb4 makes codex and grok treat each other's corpora as foreign.

AltanS added a commit that referenced this pull request Aug 24, 2026
…estion

One comment-paragraph conflict in conformance.test.ts: the foreign-cohort
note now names all three sibling corpora (omp + codex + grok).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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