From 0085a75c338af235a585a800f6da45b683024a47 Mon Sep 17 00:00:00 2001 From: Devvyn <22340871+opticon454@users.noreply.github.com> Date: Sat, 17 Oct 2026 05:02:59 +0800 Subject: [PATCH] fix(antigravity): a pending shell approval reads as working, plus a reattach test Review round: the footer hint is drawn only at rest, so nothing arms the idle check while the Run this command list is up. The stock.ts comment, wiki, changeset and test now say a pending approval shows as working until answered, and the test pins busy during the confirmation. Adds the antigravity copy of the reattached-pane settle test, and updates the promptGlyph comment and the fixture header. --- .changeset/antigravity-working-indicator.md | 2 +- docs/wiki/Agent-CLIs.md | 3 +- src/config/cli-registry/stock.ts | 4 ++- src/session.ts | 2 +- test/session-activity.test.ts | 40 +++++++++++++++++---- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/.changeset/antigravity-working-indicator.md b/.changeset/antigravity-working-indicator.md index fe69e3d7..bd43a7ee 100644 --- a/.changeset/antigravity-working-indicator.md +++ b/.changeset/antigravity-working-indicator.md @@ -2,4 +2,4 @@ 'aicodeman': patch --- -An Antigravity (`agy`) tab now shows that it is working. Its spinner animates a braille set Codeman did not recognise and the entry declared no work detection, so a running turn never marked the tab working and the activity indicator stayed off. The entry now declares what a turn draws (`⣯ Generating...`, `Working...`, `Loading...`) and its footer hint (`? for shortcuts`) as the idle mark, so a finished turn, or a shell call waiting for approval, reads as idle again. +An Antigravity (`agy`) tab now shows that it is working. Its spinner animates a braille set Codeman did not recognise and the entry declared no work detection, so a running turn never marked the tab working and the activity indicator stayed off. The entry now declares what a turn draws (`⣯ Generating...`, `Working...`, `Loading...`) and its footer hint (`? for shortcuts`) as the idle mark, so a finished turn reads as idle again. A shell call waiting for approval keeps showing as working until you answer it. diff --git a/docs/wiki/Agent-CLIs.md b/docs/wiki/Agent-CLIs.md index d1d1f581..3799fac5 100644 --- a/docs/wiki/Agent-CLIs.md +++ b/docs/wiki/Agent-CLIs.md @@ -192,7 +192,8 @@ to it as well. - **At rest the footer hint is the idle mark.** The composer's `>` is no good for that, since the submitted prompt is echoed as `> ` as well. `? for shortcuts` is drawn only at rest, and the turn's last repaint carries it. A shell call that waits for approval replaces - the composer, so the tab reads as idle (waiting on you) until you answer. + the composer and the footer hint is not drawn there, so the tab keeps reading as working + until you answer. ### Pi diff --git a/src/config/cli-registry/stock.ts b/src/config/cli-registry/stock.ts index 00c602ac..47a42e02 100644 --- a/src/config/cli-registry/stock.ts +++ b/src/config/cli-registry/stock.ts @@ -1002,7 +1002,9 @@ const ANTIGRAVITY: CliEntry = { // the composer, and the footer's left end reads `esc to cancel`; at rest the footer // reads `? for shortcuts` and nothing else on screen matches. A shell call replaces the // composer with a `Run this command?` list, the spinner stops, and the pane goes - // silent, so it reads as idle (waiting on the user), like gemini. + // silent. Nothing arms the idle check while the list is up (the footer hint is drawn only + // at rest), so a pending approval reads as working until it is answered. That differs from + // gemini, whose `▀` bar comes back on every spinner repaint. // ⚠️ The frames are the 8-dot braille set (`⣯ ⡿ ⣻ ⣾ ⣷ ⣟ ⢿ ⣽`), none of which is in // SPINNER_PATTERN's Claude set, and this entry used to declare nothing, so no frame // ever marked an antigravity tab working and it never showed the activity spinner. diff --git a/src/session.ts b/src/session.ts index c9d494cb..ba25e400 100644 --- a/src/session.ts +++ b/src/session.ts @@ -4780,7 +4780,7 @@ export class Session extends EventEmitter { : this._mux.capturePaneText?.(this._muxSession.muxName), sendEnter: () => this._mux?.sendInput(this.id, '\r'), // ⚠ NO fallback glyph here, unlike the screen-reading probe elsewhere in this file. - // Only claude, codex, pi, opencode, omp and gemini declare a promptGlyph; the other modes would fall back + // Only claude, codex, pi, opencode, omp, gemini and antigravity declare a promptGlyph; the other modes would fall back // to claude's `❯`, which is ALSO starship's default shell prompt (and pure's, and // spaceship's, and p10k lean's). On a shell session the line `❯ npm run build` sits // on screen for as long as the command runs, promptStillInComposer() reads that as diff --git a/test/session-activity.test.ts b/test/session-activity.test.ts index 3635860a..c4148d9a 100644 --- a/test/session-activity.test.ts +++ b/test/session-activity.test.ts @@ -1,6 +1,6 @@ /** * Working/idle detection for an interactive agent pane, Claude's, Codex's, pi's, opencode's, omp's - * and Gemini CLI's. + * Gemini CLI's and Antigravity's. * * The bug this pins: Claude redraws the composer (`❯`) about once a second all * the way through a turn, so the old "saw a ❯, wait 2s, call it idle" rule @@ -15,8 +15,9 @@ * * The status-line fixtures below are verbatim captures from live panes * (`tmux -L codeman capture-pane -p`) on Claude Code 2.1.220, Codex CLI 0.152.1, pi 1.1.0, - * opencode 1.3.0, omp 18.8.6 / 18.0.11 and Gemini CLI 0.63.0 (its turns driven by a local - * stand-in for the Gemini API, since the CLI's look does not depend on the backend). + * opencode 1.3.0, omp 18.8.6 / 18.0.11, Gemini CLI 0.63.0 (its turns driven by a local + * stand-in for the Gemini API, since the CLI's look does not depend on the backend) and + * Antigravity `agy` 1.3.3. */ import { describe, expect, it, vi, afterEach } from 'vitest'; import { Session } from '../src/session.js'; @@ -1070,7 +1071,7 @@ describe('antigravity interactive activity', () => { expect(session.status).toBe('idle'); }); - it('reads a pending shell confirmation as idle, and ends the resumed turn too', () => { + it('keeps a pending shell confirmation working until it is answered, and ends the resumed turn too', () => { vi.useFakeTimers(); let screen = AGY_WORKING; const session = withFakePane(() => screen, 'antigravity'); @@ -1084,9 +1085,12 @@ describe('antigravity interactive activity', () => { screen = AGY_CONFIRM; feed(session, 'Run this command?\r\n'); - vi.advanceTimersByTime(10_000); - // The footer hint is not drawn under the confirmation, so nothing armed the check: - // the pane stays working until the user answers and the turn ends below. + vi.advanceTimersByTime(60_000); + // The footer hint is not drawn under the confirmation, so nothing arms the idle check: + // the pane stays working (unlike gemini, whose bar returns on every repaint) until the + // user answers and the turn ends below. + expect(events).toEqual(['working']); + expect(session.status).toBe('busy'); screen = AGY_WORKING; spin(session, 3000); screen = AGY_AT_REST; @@ -1099,6 +1103,28 @@ describe('antigravity interactive activity', () => { }); }); +describe('antigravity reattach', () => { + afterEach(() => { + vi.useRealTimers(); + }); + + // Declaring workDetect turns off the 3 s settle for a restored pane, so a reattached agy pane + // depends on the footer hint arriving in tmux's attach repaint to leave `busy`. + it('settles a reattached antigravity pane that is at rest', () => { + vi.useFakeTimers(); + const session = withFakePane(AGY_AT_REST, 'antigravity'); + (session as unknown as { _status: string })._status = 'busy'; + const events: string[] = []; + session.on('idle', () => events.push('idle')); + + feed(session, AGY_TURN_END); + vi.advanceTimersByTime(20_000); + + expect(events).toEqual(['idle']); + expect(session.status).toBe('idle'); + }); +}); + describe('wire activity stamp across recovery', () => { // The stamp both home screens sort the quiet group on. Recovery restores the // previous run's value, and the settle window keeps the boot attach repaint