diff --git a/bin/contributor-relay.sh b/bin/contributor-relay.sh index 300e5e09a..043460753 100755 --- a/bin/contributor-relay.sh +++ b/bin/contributor-relay.sh @@ -1368,6 +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 (BACKEND === 'agy' && /Terms of Service & Data Use/.test(recent) && /\[(?:Previous|Back)\]\s+\[Done\]/.test(recent)) return 'Down Right'; return null; } @@ -1449,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.