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.
This commit is contained in:
Devvyn committed 2026-10-18 00:45:52 +08:00
1 parent 4a47ead824
commit 0085a75c33
5 files changed
+40 -11

No files matched your search

+1 -1
View File
@@ -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.
+2 -1
View File
@@ -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 `> <text>` 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
+3 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+33 -7
View File
@@ -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