From 1cf84aa9b0ea6704c9c9badcb4dddb18d21b61d8 Mon Sep 17 00:00:00 2001 From: James Reilly Date: Wed, 2 Sep 2026 19:01:59 +0530 Subject: [PATCH 1/2] contributor-relay: recognise and dismiss the agy onboarding wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `agy` is a first-class backend throughout this file (launch flags, --effort handling, capability reporting) but getCLIState() has no branch for it, so an Antigravity pane always fell through to 'unknown'. The relay then waited out CLI_READY_TIMEOUT_MS and handed the task back — with the CLI sitting on a dismissible dialog the whole time. Antigravity gates first run behind a three-step wizard: colour scheme -> Terms of Service -> folder trust. This is not a once-per-image cost: agents that share a $HOME symlink one ~/.gemini, and whenever one writes antigravity-cli/cache/onboarding.json mode 600 every OTHER agent gets EACCES and re-enters the wizard from the start. Two changes: - getCLIState() gains an `agy` branch mapping the wizard to 'onboarding', the "not signed in / Select login method" screen to 'needs-login', and the banner/prompt to 'ready'. - blockingPromptKey() learns the Terms of Service step. It is the one page a bare Enter cannot leave: focus sits on the consent CHECKBOX, where Enter toggles rather than advances ("enter Toggle"), so the relay would dismiss in a loop until timeout. Down moves to the button row and Right selects [Done]; the caller appends Enter. The colour-scheme and folder-trust steps DO advance on a bare Enter and deliberately return null. Verified against real captured panes from a live fleet: all five agy states classify correctly, and codex's existing prompts are unaffected. Signed-off-by: James Reilly --- bin/contributor-relay.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bin/contributor-relay.sh b/bin/contributor-relay.sh index 300e5e09a..42e159b2b 100755 --- a/bin/contributor-relay.sh +++ b/bin/contributor-relay.sh @@ -1368,6 +1368,13 @@ function blockingPromptKey(text) { // persists, so this prompt stops coming back on every restart the way a // plain "Skip" would. if (/Update available!/.test(text) && /Skip until next version/.test(text)) return '3'; + // agy: "Terms of Service & Data Use" ends on a [Previous] [Done] button row + // with focus on the CHECKBOX above it, where Enter toggles consent instead of + // advancing ("enter Toggle"). A bare Enter therefore never leaves this page. + // Down moves to the button row, Right selects [Done]; the caller appends + // Enter. The other two steps (theme picker, folder trust) do advance on a + // bare Enter and deliberately fall through to null. + if (/Terms of Service & Data Use/.test(text) && /\[Done\]/.test(text)) return 'Down Right'; return null; } @@ -1410,6 +1417,16 @@ function getCLIState() { if (/copilot login|gh auth login/.test(text)) return 'needs-login'; if (/Confirm folder trust|trust the files|Do you trust/.test(text)) return 'onboarding'; if (/\/ commands.*help/.test(text)) return 'ready'; + } else if (BACKEND === 'agy') { + // Antigravity gates first run behind a THREE-step wizard, and every + // agent that shares a $HOME re-enters it whenever another agent writes + // antigravity-cli/cache/onboarding.json mode 600 (they symlink to one + // shared ~/.gemini, so the next agent gets EACCES and starts over). + // Without this branch the pane fell through to 'unknown', the relay + // waited out CLI_READY_TIMEOUT_MS and handed the task back. + if (/not signed in|Select login method/i.test(text)) return 'needs-login'; + if (/Choose your color scheme|Terms of Service & Data Use|Do you trust the contents|I trust this folder|Welcome to (the )?Antigravity/i.test(text)) return 'onboarding'; + if (/^>\s*$|❯|Antigravity CLI/m.test(text)) return 'ready'; } else if (BACKEND === 'gemini') { if (/not authenticated|login required/i.test(text)) return 'needs-login'; if (/>\s*$|❯/.test(text)) return 'ready'; From c87a062e08ee06707ec521ea29c29ad3d81e3efb Mon Sep 17 00:00:00 2001 From: Andy Anderson Date: Wed, 2 Sep 2026 22:07:32 -0400 Subject: [PATCH 2/2] fix agy onboarding relay detection Adopt hanthor's Antigravity onboarding support and fold it into the existing agy readiness branch with tail-scoped gates and regression coverage.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Andy Anderson --- bin/contributor-relay.sh | 26 +++--- bin/contributor-relay.test.js | 82 +++++++++++++++++++ .../fixed-5661-agy-onboarding-wizard.md | 1 + 3 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 changelog.d/fixed-5661-agy-onboarding-wizard.md diff --git a/bin/contributor-relay.sh b/bin/contributor-relay.sh index 42e159b2b..043460753 100755 --- a/bin/contributor-relay.sh +++ b/bin/contributor-relay.sh @@ -1368,13 +1368,14 @@ function blockingPromptKey(text) { // persists, so this prompt stops coming back on every restart the way a // plain "Skip" would. if (/Update available!/.test(text) && /Skip until next version/.test(text)) return '3'; + const recent = paneTail(text, 15); // agy: "Terms of Service & Data Use" ends on a [Previous] [Done] button row // with focus on the CHECKBOX above it, where Enter toggles consent instead of // advancing ("enter Toggle"). A bare Enter therefore never leaves this page. // Down moves to the button row, Right selects [Done]; the caller appends // Enter. The other two steps (theme picker, folder trust) do advance on a // bare Enter and deliberately fall through to null. - if (/Terms of Service & Data Use/.test(text) && /\[Done\]/.test(text)) return 'Down Right'; + if (BACKEND === 'agy' && /Terms of Service & Data Use/.test(recent) && /\[(?:Previous|Back)\]\s+\[Done\]/.test(recent)) return 'Down Right'; return null; } @@ -1417,16 +1418,6 @@ function getCLIState() { if (/copilot login|gh auth login/.test(text)) return 'needs-login'; if (/Confirm folder trust|trust the files|Do you trust/.test(text)) return 'onboarding'; if (/\/ commands.*help/.test(text)) return 'ready'; - } else if (BACKEND === 'agy') { - // Antigravity gates first run behind a THREE-step wizard, and every - // agent that shares a $HOME re-enters it whenever another agent writes - // antigravity-cli/cache/onboarding.json mode 600 (they symlink to one - // shared ~/.gemini, so the next agent gets EACCES and starts over). - // Without this branch the pane fell through to 'unknown', the relay - // waited out CLI_READY_TIMEOUT_MS and handed the task back. - if (/not signed in|Select login method/i.test(text)) return 'needs-login'; - if (/Choose your color scheme|Terms of Service & Data Use|Do you trust the contents|I trust this folder|Welcome to (the )?Antigravity/i.test(text)) return 'onboarding'; - if (/^>\s*$|❯|Antigravity CLI/m.test(text)) return 'ready'; } else if (BACKEND === 'gemini') { if (/not authenticated|login required/i.test(text)) return 'needs-login'; if (/>\s*$|❯/.test(text)) return 'ready'; @@ -1466,9 +1457,18 @@ function getCLIState() { } else if (BACKEND === 'pi') { if (/pi v\d|0\.0%|auto\)|\d+\.\d+%/.test(text)) return 'ready'; } else if (BACKEND === 'agy') { + // Antigravity gates first run behind login plus a three-step wizard, and + // every agent that shares a $HOME can re-enter it whenever another agent + // writes antigravity-cli/cache/onboarding.json mode 600. Check the + // visible tail only: old task output may quote the wizard text, and a + // stale quote must not make a live prompt look blocked. + const recent = paneTail(text, 15); + if (/not signed in|Select login method/i.test(recent)) return 'needs-login'; + if (/Choose your color scheme|Terms of Service & Data Use|Do you trust the contents|I trust this (?:folder|directory)|Welcome to (?:the )?Antigravity/i.test(recent)) return 'onboarding'; // agy shows "? for shortcuts" at the bottom when its interactive prompt - // is ready. The generic />\s*$/ fires too early (during the splash). - if (/\? for shortcuts/.test(text)) return 'ready'; + // is ready. The generic />\s*$/ fires too early during splash, and the + // wizard's selection cursor is also ❯. + if (/\? for shortcuts/.test(recent)) return 'ready'; } else { if (/>\s*$|❯|\$\s*$/.test(text)) return 'ready'; } diff --git a/bin/contributor-relay.test.js b/bin/contributor-relay.test.js index fbcd224e8..7bedb9de3 100644 --- a/bin/contributor-relay.test.js +++ b/bin/contributor-relay.test.js @@ -2590,6 +2590,33 @@ const CODEX_UPDATE_PANE = [ ' Press enter to continue', ].join('\n'); +const AGY_READY_PANE = [ + 'Antigravity CLI', + '', + '> ', + '? for shortcuts', +].join('\n'); + +const AGY_TOS_PANE = [ + 'Welcome to Antigravity', + 'Terms of Service & Data Use', + '', + '[ ] I agree to the Terms of Service', + '', + '[Previous] [Done]', +].join('\n'); + +const AGY_TRUST_PANE = [ + 'Do you trust the contents of this directory?', + '', + '[I trust this directory]', +].join('\n'); + +const AGY_LOGIN_PANE = [ + 'You are not signed in', + 'Select login method', +].join('\n'); + const CODEX_COMPLETED_NO_WORK_PANE = [ '• Running GH_TOKEN=... gh issue view 4065 --repo kubestellar/hive', '', @@ -2631,6 +2658,61 @@ test('codex numbered startup menus get explicit safe selections', () => { } finally { teardown(relay); } }); +test('agy startup gates are classified before readiness, using only the visible tail', () => { + const cases = [ + [AGY_LOGIN_PANE, 'needs-login'], + [AGY_TOS_PANE, 'onboarding'], + [AGY_TRUST_PANE, 'onboarding'], + [AGY_READY_PANE, 'ready'], + ]; + for (const [pane, want] of cases) { + const relay = loadRelay({ backend: 'agy', cliStates: [pane] }); + try { + assert.strictEqual(relay.getCLIState(), want); + } finally { teardown(relay); } + } +}); + +test('agy ready gate does not fire on splash or wizard cursor', () => { + for (const pane of [ + 'Antigravity CLI\nloading workspace...\n', + 'Choose your color scheme\n❯ Dark\n Light\n', + ]) { + const relay = loadRelay({ backend: 'agy', cliStates: [pane] }); + try { + assert.notStrictEqual(relay.getCLIState(), 'ready', + 'splash text and wizard cursors must not be treated as an idle agy prompt'); + } finally { teardown(relay); } + } +}); + +test('agy onboarding prose in old scrollback does not override a ready tail', () => { + const pane = [ + 'Earlier task output quoted Terms of Service & Data Use and [Done].', + ...Array.from({ length: 20 }, (_, i) => `ordinary output line ${i}`), + AGY_READY_PANE, + ].join('\n'); + const relay = loadRelay({ backend: 'agy', cliStates: [pane] }); + try { + assert.strictEqual(relay.getCLIState(), 'ready'); + assert.strictEqual(relay.blockingPromptKey(pane), null); + } finally { teardown(relay); } +}); + +test('agy ToS wizard selects Done, but non-agy or prose matches do not', () => { + const agy = loadRelay({ backend: 'agy' }); + try { + assert.strictEqual(agy.blockingPromptKey(AGY_TOS_PANE), 'Down Right'); + assert.strictEqual(agy.blockingPromptKey('Terms of Service & Data Use\n[Done] appears in a task summary'), null); + assert.strictEqual(agy.blockingPromptKey('Terms of Service & Data Use mentioned without the button row'), null); + } finally { teardown(agy); } + + const codex = loadRelay({ backend: 'codex' }); + try { + assert.strictEqual(codex.blockingPromptKey(AGY_TOS_PANE), null); + } finally { teardown(codex); } +}); + test('codex no-work verdict is COMPLETE despite stale activity in scrollback', () => { const relay = loadRelay({ backend: 'codex' }); try { diff --git a/changelog.d/fixed-5661-agy-onboarding-wizard.md b/changelog.d/fixed-5661-agy-onboarding-wizard.md new file mode 100644 index 000000000..d4e56676b --- /dev/null +++ b/changelog.d/fixed-5661-agy-onboarding-wizard.md @@ -0,0 +1 @@ +- The contributor relay can now recognize and safely dismiss Antigravity (`agy`) first-run login/onboarding panes without mistaking splash text or wizard cursors for a ready prompt ([#5661](https://github.com/kubestellar/hive/pull/5661)). The ready gate remains the documented `? for shortcuts` footer, onboarding checks are scoped to the visible tail, and the Terms/Data Use page selects the `[Done]` button row rather than toggling the checkbox forever.