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