From 0fd39beedfa299c058ec794136e870692f4a085e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 6 Nov 2026 21:21:39 -0800 Subject: [PATCH] fix(runtime): hold an agy composer whose draft reads like an edit mode (#39296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A draft such as `dark mode: fix the toggle`, or agy's plan placeholder typed verbatim, matches the composer caret row's edit-mode alternative. agy hides `? for shortcuts` while the composer holds text, so composer_ready refused, but composer_ready_custom_status_line (#38930) took agy's own `[mode · ]model · effort` footer for a custom status row and answered idle. Orca would then paste into the operator's unsent text. Status rows may no longer be agy's built-in footer. The captured agy 1.2.7 screen of the plan placeholder typed as a draft comes from #23402. Split from #23402. Co-authored-by: seonghobae <8172694+seonghobae@users.noreply.github.com> --- ...tigravity-plan-hint-as-draft-127.meta.json | 9 +++++++ .../antigravity-plan-hint-as-draft-127.txt | 13 ++++++++++ .../antigravity-composer-draft.test.ts | 24 +++++++++++++++++++ .../agent-state-rules/antigravity.json | 11 +++++---- 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.meta.json create mode 100644 src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.txt create mode 100644 src/main/runtime/agent-state-rules/antigravity-composer-draft.test.ts diff --git a/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.meta.json b/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.meta.json new file mode 100644 index 00000000000..ad47541763a --- /dev/null +++ b/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.meta.json @@ -0,0 +1,9 @@ +{ + "capturedAt": "2026-09-19T07:34:50.080Z", + "platform": "darwin", + "command": ["agy", "--mode", "plan"], + "cols": 120, + "rows": 40, + "note": "Plan mode; exact placeholder wording typed as an unsent draft at 12 seconds", + "exitCode": 0 +} diff --git a/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.txt b/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.txt new file mode 100644 index 00000000000..3a80b194cb3 --- /dev/null +++ b/src/main/runtime/__fixtures__/antigravity-plan-hint-as-draft-127.txt @@ -0,0 +1,13 @@ +_Ga=q,f=32,s=1,v=1,i=31;AAAAAA==\[?2026$p[?2027$p[?5W[?2004h[>4;2m[>1u[?u[0 q  +▄▀▀▄ Antigravity CLI 1.2.7 +▀▀▀▀▀▀ Gemini API key +▀▀▀▀▀▀▀▀ Gemini 3.7 Flash (Low) + ▄▀▀ ▀▀▄ ~/orca/workspaces/orca/agy-gemini-issues + ▄▀▀ ▀▀▄ + +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +> Plan mode: research & plan only (shift+tab to cycle) +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +? for shortcutsplan · Gemini 3.7 Flash · low [?25h[?25lPlan + + p[?25h[?25lmode: re[?25h[?25lsearch[?25h[?25l& plan[?25h[?25l only (s[?25h[?25lhift+ta[?25h[?25lb to cy[?25h[?25lcle)[?25h \ No newline at end of file diff --git a/src/main/runtime/agent-state-rules/antigravity-composer-draft.test.ts b/src/main/runtime/agent-state-rules/antigravity-composer-draft.test.ts new file mode 100644 index 00000000000..5a179ae2d73 --- /dev/null +++ b/src/main/runtime/agent-state-rules/antigravity-composer-draft.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest' +import { finalReplayFrame } from '../agent-transcript-replay-test-harness' +import { evaluateAgentStateRules } from './agent-state-rules-engine' + +const verdict = (lines: readonly string[]) => + evaluateAgentStateRules('antigravity', { readScreenLines: () => lines })?.state + +describe('an Antigravity composer holding a mode-shaped draft', () => { + it('holds when agy 1.2.7 shows the plan placeholder typed as unsent text', async () => { + const { ruledScreenLines } = await finalReplayFrame( + 'antigravity-plan-hint-as-draft-127', + 120, + 40 + ) + expect(verdict(ruledScreenLines)).toBe('hold') + }) + + it('holds for a draft that only reads like an edit mode', () => { + const rule = '─'.repeat(40) + expect(verdict([rule, '> dark mode: fix the toggle', rule, 'Gemini 3.7 Flash · low'])).toBe( + 'hold' + ) + }) +}) diff --git a/src/main/runtime/agent-state-rules/antigravity.json b/src/main/runtime/agent-state-rules/antigravity.json index 15fcef598c4..39b055db75a 100644 --- a/src/main/runtime/agent-state-rules/antigravity.json +++ b/src/main/runtime/agent-state-rules/antigravity.json @@ -28,7 +28,7 @@ }, { "id": "composer_ready_custom_status_line", - "why": "A custom statusLine paints its own rows where the hint row was, so `esc to cancel` never shows. agy 1.5.2 then marks a turn only with a braille spinner row right above the composer. Status rows may hold no rule run, hint or busy hint, so hint-row layouts and garbled grids cannot match, and pickers drawn between the composer and the status rows break the run of rows.", + "why": "A custom statusLine replaces the hint row, so `esc to cancel` never shows; agy 1.5.2 then marks a turn only with a braille spinner row above the composer. Status rows may not hold a rule run, a hint, or agy's own `[mode · ]model · effort` footer, which drops its hint while the composer holds a draft. Pickers between the composer and status rows break the run. A custom row shaped like that footer holds; a mode-shaped draft above a custom statusLine still reads idle.", "priority": 400, "when": { "region": "screen", @@ -41,7 +41,8 @@ "optional": { "none": [ { "regex": "──" }, - { "regex": "esc to cancel|for shortcuts", "ignoreCase": true } + { "regex": "esc to cancel|for shortcuts", "ignoreCase": true }, + { "regex": "^(?:[a-z][a-z-]* · )?[^·]+ · (?:low|medium|high)$" } ] } }, @@ -49,14 +50,16 @@ "optional": { "none": [ { "regex": "──" }, - { "regex": "esc to cancel|for shortcuts", "ignoreCase": true } + { "regex": "esc to cancel|for shortcuts", "ignoreCase": true }, + { "regex": "^(?:[a-z][a-z-]* · )?[^·]+ · (?:low|medium|high)$" } ] } }, { "none": [ { "regex": "──" }, - { "regex": "esc to cancel|for shortcuts", "ignoreCase": true } + { "regex": "esc to cancel|for shortcuts", "ignoreCase": true }, + { "regex": "^(?:[a-z][a-z-]* · )?[^·]+ · (?:low|medium|high)$" } ] } ]