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
This commit is contained in:
Can Celik
2026-10-05 18:56:03 +03:00
committed by GitHub
parent b06480673b
commit fabcab108f
6 changed files with 157 additions and 83 deletions
+59 -12
View File
@@ -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']
+5 -12
View File
@@ -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"
+4 -10
View File
@@ -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]] = {}
+26 -15
View File
@@ -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:
+59 -12
View File
@@ -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']
+4 -22
View File
@@ -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"