From fabcab108f9db383e3cd6889f254c8d0d66dcd7b Mon Sep 17 00:00:00 2001 From: Can Celik Date: Mon, 5 Oct 2026 18:56:03 +0300 Subject: [PATCH] fix: detect agy dialogs and mid-turn work, treat background work as idle for agy and grok (#4959) refs #3871 refs #3530 refs #4735 refs #3419 --- distribution/agent-detection/antigravity.toml | 71 +++++++++++++++---- distribution/agent-detection/grok.toml | 17 ++--- scripts/agent_detection_manifest_check.py | 14 ++-- .../test_agent_detection_manifest_check.py | 41 +++++++---- src/detect/manifests/antigravity.toml | 71 +++++++++++++++---- src/detect/manifests/grok.toml | 26 ++----- 6 files changed, 157 insertions(+), 83 deletions(-) diff --git a/distribution/agent-detection/antigravity.toml b/distribution/agent-detection/antigravity.toml index 8b9bcfcf4..5daa8dec7 100644 --- a/distribution/agent-detection/antigravity.toml +++ b/distribution/agent-detection/antigravity.toml @@ -1,13 +1,60 @@ id = "agy" -version = "2026.06.24.1" +version = "2026.10.05.1" min_engine_version = 1 -updated_at = "2026-06-24T00:00:00Z" +updated_at = "2026-10-05T00:00:00Z" aliases = ["antigravity", "antigravity-cli"] +# Evidence: Antigravity CLI 1.2.17 live pane reads, plus reporter captures +# from 1.1.28 through 1.2.16. +# +# Dialogs replace the prompt box and end with a key-hint row directly above +# the "esc to cancel" footer: +# permission: "↑/↓ Navigate · tab Amend · ctrl+g edit/expand command" +# "↑/↓ Navigate · tab Amend · f full diff" +# question: "↑/↓ Navigate · enter Select · esc Skip" +# trust: "↑/↓ Navigate · enter Confirm" +# slash menu: "↑/↓ Navigate · enter Select · tab Complete" (idle) +# Some captures hold only the two footer rows, so dialog rules must not +# require the question text. +# +# Working turns draw a braille spinner followed by either an -ing word or a +# thought summary above the prompt box, and the footer reads "esc to cancel" +# directly under the prompt box's bottom border. The idle footer reads +# "? for shortcuts". +# +# Background tasks and subagents show "· N task(s)" in the footer and a task +# panel under the prompt box. Once the agent is back at its prompt they do +# not count as working. + [[rules]] id = "permission_prompt" state = "blocked" priority = 300 +region = "bottom_non_empty_lines(2)" +visible_blocker = true +contains = ["navigate", "tab amend"] + +[[rules]] +id = "question_prompt" +state = "blocked" +priority = 290 +region = "bottom_non_empty_lines(2)" +visible_blocker = true +contains = ["navigate", "esc skip"] + +[[rules]] +id = "trust_prompt" +state = "blocked" +priority = 280 +region = "bottom_non_empty_lines(8)" +visible_blocker = true +contains = ["do you trust the contents of this project?", "yes, i trust this folder", "enter confirm"] + +# Pre-1.1.28 permission dialog. +[[rules]] +id = "legacy_permission_prompt" +state = "blocked" +priority = 270 region = "whole_recent" visible_blocker = true contains = ["requesting permission for:"] @@ -16,18 +63,18 @@ any = [ { contains = ["tab amend", "edit command"] }, ] +[[rules]] +id = "esc_cancel_footer_working" +state = "working" +priority = 110 +region = "bottom_non_empty_lines(2)" +visible_working = true +regex = ['(?m)^\s*─{3,}\s*esc to cancel\b'] + [[rules]] id = "spinner_working" state = "working" priority = 100 -region = "whole_recent" +region = "bottom_non_empty_lines(12)" visible_working = true -line_regex = ['^\s*[\u2800-\u28FF]+\s+\p{Alphabetic}+\w*ing\b'] - -[[rules]] -id = "background_tasks_working" -state = "working" -priority = 90 -region = "bottom_non_empty_lines(5)" -visible_working = true -line_regex = ['(?i)·\s*[1-9][0-9]*\s+task'] +line_regex = ['^\s*[\u2801-\u28FF]+\s+\S'] diff --git a/distribution/agent-detection/grok.toml b/distribution/agent-detection/grok.toml index 4abcefd6f..bc37bc4be 100644 --- a/distribution/agent-detection/grok.toml +++ b/distribution/agent-detection/grok.toml @@ -1,7 +1,7 @@ id = "grok" -version = "2026.09.18.1" -min_engine_version = 2 -updated_at = "2026-09-18T00:00:00Z" +version = "2026.10.05.1" +min_engine_version = 3 +updated_at = "2026-10-05T00:00:00Z" aliases = ["grok-build"] # Evidence: Grok Build 0.2.101 source and 1.0.34 live pane reads. @@ -74,15 +74,8 @@ any = [ { contains = ["←/→:scope"] }, ] -# Grok 1.0.34 moves background counts above the composer and clears OSC -# progress between turns even while these commands are still running. -[[rules]] -id = "background_status_working" -state = "working" -priority = 1165 -region = "bottom_non_empty_lines(12)" -visible_working = true -line_regex = ['^\s*[○◎◉]\s+[1-9][0-9]*\s+(?:commands?|monitors?|loops?|subagents?)(?:\s+·\s+[1-9][0-9]*\s+(?:commands?|monitors?|loops?|subagents?))*\s+still running(?:\s+·\s+send a message to interrupt)?\s*$'] +# Background commands, monitors, and subagents left running after Grok returns +# to its prompt do not count as working, matching Claude and agy (#1217, #4735). [[rules]] id = "osc_progress_working" diff --git a/scripts/agent_detection_manifest_check.py b/scripts/agent_detection_manifest_check.py index bfc2c312a..026a1e4b2 100644 --- a/scripts/agent_detection_manifest_check.py +++ b/scripts/agent_detection_manifest_check.py @@ -53,16 +53,10 @@ MAX_MATCHERS_PER_GATE = 32 MAX_TOTAL_MATCHERS = 1024 MAX_MATCHER_CHARS = 512 -# Keep engine-2 clients on the OSC-capable manifest until an engine-3 release -# can consume top_non_empty_lines. Remove this entry when the distribution -# publishes the bundled Grok manifest. -STAGED_PUBLISHED_MANIFESTS = { - "grok": ( - "2026.09.18.2", - "2026.09.18.1", - "0f31b111144900b02f303577d27587f72d58d8c505185a682bd7887f822316ee", - ), -} +# Keep older-engine clients on a published manifest until a release with the +# bundled manifest's engine ships. Maps agent id to (bundled version, published +# version, published sha256). Remove an entry once the bundled manifest ships. +STAGED_PUBLISHED_MANIFESTS: dict[str, tuple[str, str, str]] = {} UNPUBLISHED_BUNDLED_MANIFESTS: dict[str, tuple[str, str]] = {} diff --git a/scripts/test_agent_detection_manifest_check.py b/scripts/test_agent_detection_manifest_check.py index fed0e06f7..ae01a4ada 100644 --- a/scripts/test_agent_detection_manifest_check.py +++ b/scripts/test_agent_detection_manifest_check.py @@ -29,18 +29,27 @@ path = "{path}" ''' -def staged_grok_dirs(root: Path) -> tuple[Path, Path]: +STAGED_BUNDLED_MANIFEST = manifest("codex", "2026.06.10.2").replace( + "min_engine_version = 1", "min_engine_version = 2" +) +STAGED_PUBLISHED_MANIFEST = manifest("codex", "2026.06.10.1") +STAGED_TEST_EXCEPTION = { + "codex": ( + "2026.06.10.2", + "2026.06.10.1", + hashlib.sha256(STAGED_PUBLISHED_MANIFEST.encode()).hexdigest(), + ), +} + + +def staged_manifest_dirs(root: Path) -> tuple[Path, Path]: bundled = root / "bundled" published = root / "published" bundled.mkdir() published.mkdir() - (bundled / "grok.toml").write_bytes( - (check.DEFAULT_BUNDLED_DIR / "grok.toml").read_bytes() - ) - (published / "grok.toml").write_bytes( - (check.DEFAULT_PUBLISHED_DIR / "grok.toml").read_bytes() - ) - (published / "index.toml").write_text(catalog("grok", "grok.toml")) + (bundled / "codex.toml").write_text(STAGED_BUNDLED_MANIFEST, encoding="utf-8", newline="\n") + (published / "codex.toml").write_text(STAGED_PUBLISHED_MANIFEST, encoding="utf-8", newline="\n") + (published / "index.toml").write_text(catalog()) return bundled, published @@ -94,22 +103,24 @@ class AgentDetectionManifestCheckTests(unittest.TestCase): with self.assertRaisesRegex(check.CheckError, "lower than bundled"): check.validate_catalog(website, bundled_manifests, engine_version=1) + @patch.dict(check.STAGED_PUBLISHED_MANIFESTS, STAGED_TEST_EXCEPTION, clear=True) def test_allows_explicitly_staged_published_manifest(self): with tempfile.TemporaryDirectory() as tmp: - bundled, website = staged_grok_dirs(Path(tmp)) + bundled, website = staged_manifest_dirs(Path(tmp)) - bundled_manifests = check.load_manifest_dir(bundled, engine_version=3) - check.validate_catalog(website, bundled_manifests, engine_version=3) + bundled_manifests = check.load_manifest_dir(bundled, engine_version=2) + check.validate_catalog(website, bundled_manifests, engine_version=2) + @patch.dict(check.STAGED_PUBLISHED_MANIFESTS, STAGED_TEST_EXCEPTION, clear=True) def test_rejects_mutated_staged_published_manifest(self): with tempfile.TemporaryDirectory() as tmp: - bundled, website = staged_grok_dirs(Path(tmp)) - with (website / "grok.toml").open("a") as manifest_file: + bundled, website = staged_manifest_dirs(Path(tmp)) + with (website / "codex.toml").open("a") as manifest_file: manifest_file.write("\n# unexpected mutation\n") - bundled_manifests = check.load_manifest_dir(bundled, engine_version=3) + bundled_manifests = check.load_manifest_dir(bundled, engine_version=2) with self.assertRaisesRegex(check.CheckError, "lower than bundled"): - check.validate_catalog(website, bundled_manifests, engine_version=3) + check.validate_catalog(website, bundled_manifests, engine_version=2) def test_rejects_unlisted_published_manifest_lag_for_new_engine(self): with tempfile.TemporaryDirectory() as tmp: diff --git a/src/detect/manifests/antigravity.toml b/src/detect/manifests/antigravity.toml index 8b9bcfcf4..5daa8dec7 100644 --- a/src/detect/manifests/antigravity.toml +++ b/src/detect/manifests/antigravity.toml @@ -1,13 +1,60 @@ id = "agy" -version = "2026.06.24.1" +version = "2026.10.05.1" min_engine_version = 1 -updated_at = "2026-06-24T00:00:00Z" +updated_at = "2026-10-05T00:00:00Z" aliases = ["antigravity", "antigravity-cli"] +# Evidence: Antigravity CLI 1.2.17 live pane reads, plus reporter captures +# from 1.1.28 through 1.2.16. +# +# Dialogs replace the prompt box and end with a key-hint row directly above +# the "esc to cancel" footer: +# permission: "↑/↓ Navigate · tab Amend · ctrl+g edit/expand command" +# "↑/↓ Navigate · tab Amend · f full diff" +# question: "↑/↓ Navigate · enter Select · esc Skip" +# trust: "↑/↓ Navigate · enter Confirm" +# slash menu: "↑/↓ Navigate · enter Select · tab Complete" (idle) +# Some captures hold only the two footer rows, so dialog rules must not +# require the question text. +# +# Working turns draw a braille spinner followed by either an -ing word or a +# thought summary above the prompt box, and the footer reads "esc to cancel" +# directly under the prompt box's bottom border. The idle footer reads +# "? for shortcuts". +# +# Background tasks and subagents show "· N task(s)" in the footer and a task +# panel under the prompt box. Once the agent is back at its prompt they do +# not count as working. + [[rules]] id = "permission_prompt" state = "blocked" priority = 300 +region = "bottom_non_empty_lines(2)" +visible_blocker = true +contains = ["navigate", "tab amend"] + +[[rules]] +id = "question_prompt" +state = "blocked" +priority = 290 +region = "bottom_non_empty_lines(2)" +visible_blocker = true +contains = ["navigate", "esc skip"] + +[[rules]] +id = "trust_prompt" +state = "blocked" +priority = 280 +region = "bottom_non_empty_lines(8)" +visible_blocker = true +contains = ["do you trust the contents of this project?", "yes, i trust this folder", "enter confirm"] + +# Pre-1.1.28 permission dialog. +[[rules]] +id = "legacy_permission_prompt" +state = "blocked" +priority = 270 region = "whole_recent" visible_blocker = true contains = ["requesting permission for:"] @@ -16,18 +63,18 @@ any = [ { contains = ["tab amend", "edit command"] }, ] +[[rules]] +id = "esc_cancel_footer_working" +state = "working" +priority = 110 +region = "bottom_non_empty_lines(2)" +visible_working = true +regex = ['(?m)^\s*─{3,}\s*esc to cancel\b'] + [[rules]] id = "spinner_working" state = "working" priority = 100 -region = "whole_recent" +region = "bottom_non_empty_lines(12)" visible_working = true -line_regex = ['^\s*[\u2800-\u28FF]+\s+\p{Alphabetic}+\w*ing\b'] - -[[rules]] -id = "background_tasks_working" -state = "working" -priority = 90 -region = "bottom_non_empty_lines(5)" -visible_working = true -line_regex = ['(?i)·\s*[1-9][0-9]*\s+task'] +line_regex = ['^\s*[\u2801-\u28FF]+\s+\S'] diff --git a/src/detect/manifests/grok.toml b/src/detect/manifests/grok.toml index 67ba4ebcc..bc37bc4be 100644 --- a/src/detect/manifests/grok.toml +++ b/src/detect/manifests/grok.toml @@ -1,7 +1,7 @@ id = "grok" -version = "2026.09.18.2" +version = "2026.10.05.1" min_engine_version = 3 -updated_at = "2026-09-18T00:00:00Z" +updated_at = "2026-10-05T00:00:00Z" aliases = ["grok-build"] # Evidence: Grok Build 0.2.101 source and 1.0.34 live pane reads. @@ -74,26 +74,8 @@ any = [ { contains = ["←/→:scope"] }, ] -# Grok clears its OSC busy signals while background work runs. The first -# non-empty row is pinned application chrome, where this animated chip shows -# the number of running background tasks and disappears when the count is zero. -[[rules]] -id = "background_work_chip_working" -state = "working" -priority = 1170 -region = "top_non_empty_lines(1)" -visible_working = true -line_regex = ['[⋅:⸬⁙.·]\s+[1-9][0-9]*\s+│'] - -# Grok 1.0.34 moves background counts above the composer and clears OSC -# progress between turns even while these commands are still running. -[[rules]] -id = "background_status_working" -state = "working" -priority = 1165 -region = "bottom_non_empty_lines(12)" -visible_working = true -line_regex = ['^\s*[○◎◉]\s+[1-9][0-9]*\s+(?:commands?|monitors?|loops?|subagents?)(?:\s+·\s+[1-9][0-9]*\s+(?:commands?|monitors?|loops?|subagents?))*\s+still running(?:\s+·\s+send a message to interrupt)?\s*$'] +# Background commands, monitors, and subagents left running after Grok returns +# to its prompt do not count as working, matching Claude and agy (#1217, #4735). [[rules]] id = "osc_progress_working"