Skip to content

feat: add AGY (Antigravity) harness adapter, interactive prompts, and slash commands - #99

Open
Kryvonis wants to merge 6 commits into
AltanS:mainfrom
Kryvonis:main
Open

feat: add AGY (Antigravity) harness adapter, interactive prompts, and slash commands#99
Kryvonis wants to merge 6 commits into
AltanS:mainfrom
Kryvonis:main

Conversation

@Kryvonis

@Kryvonis Kryvonis commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Adds support for AGY (Google Antigravity CLI / agy / antigravity) to Collie:

  1. Interactive Prompt Up-Leveling (web/src/lib/harness/agy/):

    • Lifts ask_question dialogs, numbered options (1. Option, 1) Option, [1] Option, ( ) 1.Option), and permission/confirmation prompts into native tappable buttons.
    • Handles options with or without explicit footer hint lines.
    • Guards against free-text sends while an interactive prompt is open (composerReady).
    • Re-derives signatures through the race guard before sending keystrokes ([digit, "Enter"] or [digit]).
  2. Slash Command Palette (web/src/lib/agent-commands.ts):

    • Adds the public AGY command catalog with prefix matching for agy, agy-cli, and antigravity:
      /plan, /grill-me, /learn, /compact, /clear, /model, /status, /subagents, /skills, /rules, /mcp, /hooks, /review, /diff, /doctor, /theme, /help, /exit.
  3. Brand Styling & Icon (web/src/components/agent-icon*):

    • Adds the official brand mark and tile background (#1A73E8) for agy and antigravity pane badges and switcher tiles.
  4. Registry & Case/Prefix Normalization (web/src/lib/harness/registry.ts):

    • Normalizes agent keys in adapterFor to support casing and common prefix variants (agy, agy-cli, antigravity).

Suggested CHANGELOG Entry

AGY (Antigravity CLI) gets a harness adapter — up-levels interactive question options (ask_question) and tool permission dialogs into tappable buttons, gates the composer against typing into active menus, adds the AGY slash command palette, and includes brand icons.

Verification

- **Harness Unit Tests:**
  `cd web && bunx vitest run src/lib/harness/agy/` (7 passed)
- **Commands & Icon Tests:**
  `cd web && bunx vitest run src/lib/agent-commands.test.ts src/components/agent-icon.test.tsx` (39 passed)
- **Full Frontend Suite:**
  `cd web && bun run test` (108 test files, 2,161 tests passing)
- **Bridge Tests:**
  `bun test bridge/` (559 tests passing)
- **Typecheck & Production Build:**
  `cd web && bun run typecheck && bun run build` (0 errors, clean build)
- **Live Local Test:**
  Verified prompt button tapping and submission against a live `agy` session on Herdr.

@AltanS

AltanS commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Thanks — the agy work is welcome. The command catalog and the icon are fine as they are.

The adapter emits prompt-select, whose keystroke recipe is already live, so this is Tier 2 in HARNESS_CONTRIBUTING.md and needs: a captured fixture corpus, a notes file with the verified recipe, a describeAdapterConformance call, and live verification. I can't do the last one — agy isn't installed here.

Two changes I'd want regardless:

  1. keys: [n, "Enter"] synthesises digits from numbered rows, and the detector matches any numbered list near the tail — it'll claim /model and /skills. ADR 0009 closes that off (in Claude's /model a digit also rewrites the user's default). Drive it from the footer, or record the live probe showing agy's digits are safe.

  2. Please drop the adapterFor prefix-matching — it widens claude/omp to any agent string with those prefixes. Separate PR, separate argument. Register agy by its exact agent strings.

Bring the corpus + notes + conformance and run the live check on your session, and I'll take it whole.

kennymcavoy added a commit to kennymcavoy/collie that referenced this pull request Aug 21, 2026
agy's first drop emitted prompt-select (Tier 2, live keystrokes) and
prefix-matched inside adapterFor. Stay Tier 1, register agent "grok"
exactly, and keep catalog folding in canonicalAgent. ADR 0009 still
blocks digit synthesis if a later dialog lift lands.
kennymcavoy added a commit to kennymcavoy/collie that referenced this pull request Aug 21, 2026
rstrip not peel (same helper as omp/claude). Bottom-border capture
uses [\s\S] so U+2028 cannot silently decline a frame. Cite AltanS#99 the
way the rest of the repo cites issues. Revert the 0.33.0 bump —
CLAUDE.md: a fork PR leaves version and CHANGELOG to the maintainer.
kennymcavoy added a commit to kennymcavoy/collie that referenced this pull request Aug 22, 2026
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. journal-probe verifies every populated root separately —
one healthy root never vouches for a broken sibling.

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFH7jQVDVhPGC6FqLetNfd
kennymcavoy added a commit to kennymcavoy/collie that referenced this pull request Aug 23, 2026
Codex panes get the Claude-level treatment: the boxless chrome stripped
and re-surfaced natively, plus native buttons for the three probed
dialogs.

Registered by exact agent string only (the AltanS#99 bar):
adapterFor stays Object.hasOwn over adapter.agent; variant spellings
stay raw. Every keystroke the adapter emits comes from a dated live
probe recorded in the notes files, against a byte-faithful fixture
corpus (identifying strings length-preservingly genericized — public
repo), with describeAdapterConformance running codex against the claude
and omp cohorts and vice versa.

Chrome (Tier 1): Codex paints a bare `› ` prompt row that word-wraps
onto indented continuation rows, a dot-separated status row beneath,
and exactly one blank row between every section. The composer is
located by the status row at the tail (the ` · Context N% left` token;
everything before it is opaque — models and paths change) with the
prompt row above it — a transcript echo of a submitted message paints
the same `› ` prefix and never has the status row beneath it. The
status row feeds the strip, the prompt row the draft (wrapped drafts
rejoin with single spaces, verified against the typed original) and
composerPrompt.

Trust prompt: the exact captured two-option layout lifts as family
trust. Digits confirm directly — 2 quit on the spot, 1 accepted through
the guarded send path (both probed).

Exec approval: classified, not layout-pinned — the first row must be
the one-shot Yes, the last the reject, and every row between must prove
itself persistent (`don't ask again …`); those never become buttons.
An unclassified row refuses the whole card. Digits confirm directly:
1 ran the approved command, 3 rejected it with the command verified
never run. The header, Reason and `$ command` rows stay in the raw
mirror above the buttons. Only the exec approval is captured; other
approval kinds fail closed until probed. Note the config dependency:
approvals_reviewer = "auto_review" (the host default here) approves
silently and the dialog never paints — probing used
`-c approvals_reviewer=user`.

request_user_input: the Question X/Y card lifts as family select, one
digit per row — a digit answers the current question, advances a
multi-question set, and submits on the last (all probed). The
notes-box state refuses to raw (footer `tab or esc to clear notes`, or
the `› Add notes` row): a digit there would type into the box. Esc
interrupts the whole conversation and is never emitted.

Live-verified end to end through the bridge on a real pane: trust
accepted, a reply typed-verified-submitted through the new draft
extraction, an approval Yes landing its command, and an ask answer
registering in the tool result — every press through the guarded /keys
endpoint with its region signature.

Versions untouched per the fork-PR rule; CHANGELOG line in the PR body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFH7jQVDVhPGC6FqLetNfd
kennymcavoy added a commit to kennymcavoy/collie that referenced this pull request Aug 23, 2026
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.
@AltanS

AltanS commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Both asks are addressed — adapterFor is exact-keyed again, and the detector is footer-gated so /model and /skills stay raw. Thanks. Three things before I take it.

1. The fixtures aren't captures. Every file in the corpus is a byte-faithful format:ansi capture with real ESC bytes; the 8 agy files have none, and their rule widths disagree between files (60 vs 113 columns).

claude--select-menu.txt  27 ESC     grok--ask-color.txt   54 ESC
codex--ask-fruit.txt     78 ESC     agy--select-menu.txt   0 ESC

This is the one that blocks the rest: the keystroke table in AGY_NOTES.md is the only evidence the recipe is right, and hand-typed screens can't corroborate it. Capture them with scripts/capture-fixture.sh <paneId> <name> off a sandbox agy pane. If you need to scrub a username or path, keep the substitution length-preserving so the row padding stays byte-identical — see the codex corpus note in the fixtures README.

2. chrome.ts never finds the input box. ? for shortcuts matches its PROMPT_REGEX (the \?\s* branch), so the walk stops on the status line. On your own fixtures:

agy--fresh-idle.txt  DRAFT = "for shortcuts    accept-edits · Gemini 3.7 Flash · low"
agy--done.txt        DRAFT = "for shortcuts    accept-edits · Gemini 3.7 Flash · low"
agy--fresh-idle.txt  blocks = [{raw, 9 lines}]      // the box is still in the mirror

Collie would paint that as the operator's draft. extractStatusLines returns [] unconditionally too, so the status row is never lifted out. Please cover both against a fixture — the unit tests use hand-built strings, which is why this got through.

3. Please restore the completeness gate in prompt-binding-contract.test.ts. Narrowing it to claude-- also switches it off for codex, grok and omp. With the original filter, five agy fixtures come back unpinned:

agy--permission-bash, agy--permission-edit, agy--plan-approval,
agy--select-menu, agy--trust-prompt

Claude's detectPromptSelect claims all five. Add their regions to prompt-binding-regions.json rather than shrinking the filter.

Two notes, not conditions. isAlienBuffer is carrying more weight than it looks: with that one function stubbed out, the agy grammar claims 12 foreign fixtures with digit keys. Nothing routes that way in production, so it isn't a live hazard — but it tells you the grammar is loose, and a real agy screen that prints Enter to select will be claimed the same way. A brand-name blocklist won't catch that one. Separately, the geminisomeunknown edit in agent-icon.test.tsx looks unrelated; gemini is still an unknown agent everywhere else.

Bring the real captures and the chrome fix and I'll take it.

bermudi pushed a commit to bermudi/collie that referenced this pull request Aug 24, 2026
Codex panes get the Claude-level treatment: the boxless chrome stripped
and re-surfaced natively, plus native buttons for the three probed
dialogs.

Registered by exact agent string only (the AltanS#99 bar):
adapterFor stays Object.hasOwn over adapter.agent; variant spellings
stay raw. Every keystroke the adapter emits comes from a dated live
probe recorded in the notes files, against a byte-faithful fixture
corpus (identifying strings length-preservingly genericized — public
repo), with describeAdapterConformance running codex against the claude
and omp cohorts and vice versa.

Chrome (Tier 1): Codex paints a bare `› ` prompt row that word-wraps
onto indented continuation rows, a dot-separated status row beneath,
and exactly one blank row between every section. The composer is
located by the status row at the tail (the ` · Context N% left` token;
everything before it is opaque — models and paths change) with the
prompt row above it — a transcript echo of a submitted message paints
the same `› ` prefix and never has the status row beneath it. The
status row feeds the strip, the prompt row the draft (wrapped drafts
rejoin with single spaces, verified against the typed original) and
composerPrompt.

Trust prompt: the exact captured two-option layout lifts as family
trust. Digits confirm directly — 2 quit on the spot, 1 accepted through
the guarded send path (both probed).

Exec approval: classified, not layout-pinned — the first row must be
the one-shot Yes, the last the reject, and every row between must prove
itself persistent (`don't ask again …`); those never become buttons.
An unclassified row refuses the whole card. Digits confirm directly:
1 ran the approved command, 3 rejected it with the command verified
never run. The header, Reason and `$ command` rows stay in the raw
mirror above the buttons. Only the exec approval is captured; other
approval kinds fail closed until probed. Note the config dependency:
approvals_reviewer = "auto_review" (the host default here) approves
silently and the dialog never paints — probing used
`-c approvals_reviewer=user`.

request_user_input: the Question X/Y card lifts as family select, one
digit per row — a digit answers the current question, advances a
multi-question set, and submits on the last (all probed). The
notes-box state refuses to raw (footer `tab or esc to clear notes`, or
the `› Add notes` row): a digit there would type into the box. Esc
interrupts the whole conversation and is never emitted.

Live-verified end to end through the bridge on a real pane: trust
accepted, a reply typed-verified-submitted through the new draft
extraction, an approval Yes landing its command, and an ask answer
registering in the tool result — every press through the guarded /keys
endpoint with its region signature.

Versions untouched per the fork-PR rule; CHANGELOG line in the PR body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFH7jQVDVhPGC6FqLetNfd
(cherry picked from commit e5fab3a)
bermudi pushed a commit to bermudi/collie that referenced this pull request Aug 24, 2026
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.
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