aboutsummaryrefslogtreecommitdiff
path: root/crates/detect-core/src/manifests
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-19 16:02:57 +0300
committerIgor Soarez <igor@soarez.org>2026-10-19 16:02:57 +0300
commitab33633ceb9e4bea3e292b2371241797d1587d02 (patch)
tree7b03faf43e5714782e43d582a00626a6f0d852f1 /crates/detect-core/src/manifests
initial dingger: tmux agent watcher with herdr's detection engine
extract detect-core from herdr's src/detect (engine + 22 manifests, tests unchanged) and add the dingger binary: tmux capture-pane watcher, process-tree agent identification, herdr's notification policy, embedded notification sounds, @agent_state write-back, and catalog-based manifest refresh. viewer screens are non-observations per the engine contract; reviewed by a three-model panel (opus 5.5, gpt 6.1 sol, kimi k3) with all confirmed findings applied as deletions.
Diffstat (limited to 'crates/detect-core/src/manifests')
-rw-r--r--crates/detect-core/src/manifests/amp.toml65
-rw-r--r--crates/detect-core/src/manifests/antigravity.toml80
-rw-r--r--crates/detect-core/src/manifests/claude.toml230
-rw-r--r--crates/detect-core/src/manifests/cline.toml63
-rw-r--r--crates/detect-core/src/manifests/codex.toml129
-rw-r--r--crates/detect-core/src/manifests/cursor.toml57
-rw-r--r--crates/detect-core/src/manifests/devin.toml86
-rw-r--r--crates/detect-core/src/manifests/droid.toml48
-rw-r--r--crates/detect-core/src/manifests/gemini.toml25
-rw-r--r--crates/detect-core/src/manifests/github-copilot.toml45
-rw-r--r--crates/detect-core/src/manifests/grok.toml161
-rw-r--r--crates/detect-core/src/manifests/hermes.toml102
-rw-r--r--crates/detect-core/src/manifests/kilo.toml24
-rw-r--r--crates/detect-core/src/manifests/kimi.toml77
-rw-r--r--crates/detect-core/src/manifests/kiro.toml97
-rw-r--r--crates/detect-core/src/manifests/letta.toml95
-rw-r--r--crates/detect-core/src/manifests/maki.toml68
-rw-r--r--crates/detect-core/src/manifests/muse.toml113
-rw-r--r--crates/detect-core/src/manifests/opencode.toml37
-rw-r--r--crates/detect-core/src/manifests/pi.toml27
-rw-r--r--crates/detect-core/src/manifests/qodercli.toml38
-rw-r--r--crates/detect-core/src/manifests/qwen.toml119
22 files changed, 1786 insertions, 0 deletions
diff --git a/crates/detect-core/src/manifests/amp.toml b/crates/detect-core/src/manifests/amp.toml
new file mode 100644
index 0000000..1fc5dfa
--- /dev/null
+++ b/crates/detect-core/src/manifests/amp.toml
@@ -0,0 +1,65 @@
+id = "amp"
+version = "2026.07.09.1"
+min_engine_version = 2
+updated_at = "2026-07-09T00:00:00Z"
+aliases = ["amp-local"]
+
+[[rules]]
+id = "osc_title_plugin_confirmation_blocked"
+state = "blocked"
+priority = 1100
+region = "osc_title"
+visible_blocker = true
+contains = ["Plugin confirmation needed"]
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1050
+region = "osc_title"
+visible_working = true
+regex = ['^[\x{2800}-\x{28FF}] ']
+
+[[rules]]
+id = "approval_footer"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["waiting for approval"] },
+ { contains = ["invoke tool"] },
+ { contains = ["run this command?"] },
+ { contains = ["allow editing file:"] },
+ { contains = ["allow creating file:"] },
+ { contains = ["confirm tool call"] },
+ { contains = ["approve"], any = [{ contains = ["allow all for this session"] }, { contains = ["allow all for every session"] }, { contains = ["allow file for every session"] }, { contains = ["deny with feedback"] }] },
+]
+
+[[rules]]
+id = "status_footer_working"
+state = "working"
+priority = 200
+region = "bottom_non_empty_lines(5)"
+visible_working = true
+line_regex = ['(?i)^\s*╰\s+\S+\s+(thinking|streaming|running tools|waiting)\s+─']
+
+[[rules]]
+id = "esc_cancel_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+contains = ["esc to cancel"]
+
+[[rules]]
+id = "osc_title_idle"
+state = "idle"
+priority = 50
+region = "osc_title"
+visible_idle = true
+contains = [" - amp - "]
+not = [
+ { regex = ['^[\x{2800}-\x{28FF}] '] },
+ { contains = ["Plugin confirmation needed"] },
+]
diff --git a/crates/detect-core/src/manifests/antigravity.toml b/crates/detect-core/src/manifests/antigravity.toml
new file mode 100644
index 0000000..5daa8de
--- /dev/null
+++ b/crates/detect-core/src/manifests/antigravity.toml
@@ -0,0 +1,80 @@
+id = "agy"
+version = "2026.10.05.1"
+min_engine_version = 1
+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:"]
+any = [
+ { contains = ["do you want to proceed?"] },
+ { 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 = "bottom_non_empty_lines(12)"
+visible_working = true
+line_regex = ['^\s*[\u2801-\u28FF]+\s+\S']
diff --git a/crates/detect-core/src/manifests/claude.toml b/crates/detect-core/src/manifests/claude.toml
new file mode 100644
index 0000000..0c7bdcf
--- /dev/null
+++ b/crates/detect-core/src/manifests/claude.toml
@@ -0,0 +1,230 @@
+id = "claude"
+version = "2026.09.11.1"
+min_engine_version = 2
+updated_at = "2026-09-11T00:00:00Z"
+aliases = ["claude-code"]
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1100
+region = "osc_title"
+visible_working = true
+# Braille covers <= 2.1.227; half-circles are the 2.1.228 busy spinner.
+regex = ['^[\x{2800}-\x{28FF}\x{25D0}-\x{25D3}] ']
+
+[[rules]]
+id = "live_turn_working"
+state = "working"
+priority = 970
+region = "bottom_non_empty_lines(12)"
+visible_working = true
+any = [
+ { line_regex = ['^\s*[⏸⏵].*esc to interrupt(?:\s|·|$)'] },
+ { line_regex = ['^\s*[\x{002A}\x{00B7}\x{2722}\x{2733}\x{2736}\x{273B}\x{273D}]\s+\S.*…(?:\s+\(\d+[smh](?:\s|·)|\s*$)'] },
+]
+
+[[rules]]
+id = "background_agents_working"
+state = "working"
+priority = 965
+region = "last_non_empty_above_prompt_box"
+visible_working = true
+line_regex = ['^\s*[\x{002A}\x{00B7}\x{2722}\x{2736}\x{273B}\x{273D}]\s+Waiting for [1-9]\d* background agents? to finish\s*$']
+
+[[rules]]
+id = "background_mcp_task_working"
+state = "working"
+priority = 965
+region = "bottom_non_empty_lines(12)"
+visible_working = true
+# Claude renders activity summaries at column zero; wrapped continuations are indented.
+# Keeping that shape prevents user prompt text from impersonating this signal.
+regex = ['(?m)^[\x{002A}\x{00B7}\x{2722}\x{2736}\x{273B}\x{273D}][ \t]+\S[^\n]*?(?:\n[ \t]+[^\n]*?){0,3}·(?:[ \t]+|\n[ \t]*)[1-9]\d*(?:[ \t]+|\n[ \t]*)MCP(?:[ \t]+|\n[ \t]*)tasks?(?:[ \t]+|\n[ \t]*)still(?:[ \t]+|\n[ \t]*)running[ \t]*$']
+not = [
+ { contains = ["do you want to proceed?"] },
+ { contains = ["esc to cancel"] },
+ { contains = ["waiting for permission"] },
+ { contains = ["do you want to allow this connection?"] },
+ { contains = ["tab to amend"] },
+ { contains = ["ctrl+e to explain"] },
+]
+
+[[rules]]
+id = "btw_overlay_working"
+state = "working"
+priority = 975
+region = "bottom_non_empty_lines(5)"
+visible_working = true
+line_regex = [
+ '^\s*/btw(?:\s|$)',
+ '(?i)esc to close\s*$',
+]
+
+[[rules]]
+id = "transcript_viewer"
+state = "unknown"
+priority = 1000
+region = "bottom_non_empty_lines(3)"
+skip_state_update = true
+contains = ["showing detailed transcript"]
+any = [
+ { contains = ["ctrl+o", "to toggle"] },
+ { contains = ["ctrl+e", "show all"] },
+ { contains = ["ctrl+e", "collapse"] },
+ { contains = ["↑↓ scroll"] },
+ { contains = ["? for shortcuts"] },
+]
+
+[[rules]]
+id = "live_blocked_form"
+state = "blocked"
+priority = 980
+region = "after_last_horizontal_rule"
+visible_blocker = true
+contains = ["esc to cancel"]
+any = [
+ { contains = ["enter to confirm"] },
+ { contains = ["enter to select"], any = [
+ { contains = ["tab/arrow keys to navigate"] },
+ { contains = ["arrow keys to navigate"] },
+ { contains = ["arrows to navigate"] },
+ { contains = ["↑/↓ to navigate"] },
+ { contains = ["↑↓ to navigate"] },
+ ] },
+]
+
+[[rules]]
+id = "dynamic_workflow_prompt"
+state = "blocked"
+priority = 980
+region = "whole_recent"
+visible_blocker = true
+contains = ["run a dynamic workflow?", "esc to cancel"]
+
+[[rules]]
+id = "mcp_elicitation_prompt"
+state = "blocked"
+priority = 980
+region = "whole_recent"
+visible_blocker = true
+# MCP elicitation dialogs (elicitation/create) show Accept/Decline controls
+# with an "Esc to cancel" footer but no Enter hint, so live_blocked_form
+# cannot see them (issue #3283). Gate on the invariant header line, the
+# Accept/Decline control line, and the cancel footer.
+contains = ["esc to cancel"]
+line_regex = ['(?i)^\s*MCP server ["\x{201C}].+["\x{201D}] requests your input\s*$']
+all = [
+ { any = [
+ { line_regex = ['^\s*\x{276F}?\s*Accept\b'] },
+ { line_regex = ['^\s*\x{276F}?\s*Decline\b'] },
+ ] },
+]
+
+[[rules]]
+id = "live_prompt_box"
+state = "idle"
+priority = 950
+region = "prompt_box_body"
+visible_idle = true
+line_regex = ['^\s*❯']
+not = [
+ { contains = ["enter to select"] },
+ { contains = ["esc to cancel"] },
+ { contains = ["tab/arrow keys"] },
+ { contains = ["arrow keys to navigate"] },
+ { contains = ["↑/↓ to navigate"] },
+]
+
+[[rules]]
+id = "model_picker_menu"
+state = "unknown"
+priority = 900
+region = "whole_recent"
+skip_state_update = true
+contains = ["select model", "enter to set as default", "esc to cancel"]
+not = [
+ { contains = ["do you want to proceed?"] },
+ { contains = ["enter to select"] },
+]
+
+[[rules]]
+id = "bash_permission_prompt"
+state = "blocked"
+priority = 850
+region = "whole_recent"
+visible_blocker = true
+contains = ["do you want to proceed?"]
+any = [
+ { contains = ["bash command"] },
+ { contains = ["bash("] },
+ { contains = ["contains expansion"] },
+ { contains = ["tab to amend"] },
+ { contains = ["ctrl+e to explain"] },
+]
+# Claude marks the selected option with "❯", so every option branch has to allow
+# that prefix. Numbered branches that omit it only match options the cursor has
+# moved away from, which left the resting layout below unmatched here (#2650):
+# ❯ 1. Yes / 2. Yes, and don't ask again for: <cmd> / 3. No
+# Cover both the two-option and "don't ask again" three-option shapes so this
+# rule, not the narrower generic_permission_prompt, claims Bash approvals.
+all = [
+ { any = [
+ { line_regex = ['(?i)^\s*❯?\s*yes\b'] },
+ { line_regex = ['(?i)^\s*❯?\s*1\.\s*yes\b'] },
+ { line_regex = ['(?i)^\s*❯?\s*2\.\s*yes\b'] },
+ { line_regex = ['(?i)^\s*❯?\s*2\.\s*no\b'] },
+ { line_regex = ['(?i)^\s*❯?\s*3\.\s*no\b'] },
+ ] },
+]
+
+[[rules]]
+id = "generic_permission_prompt"
+state = "blocked"
+priority = 840
+region = "after_last_horizontal_rule"
+visible_blocker = true
+contains = ["do you want to proceed?", "esc to cancel"]
+all = [
+ { any = [
+ { line_regex = ['(?i)^\s*❯?\s*1\.\s*yes\b'] },
+ { line_regex = ['(?i)^\s*2\.\s*yes\b'] },
+ { line_regex = ['(?i)^\s*2\.\s*no\b'] },
+ { line_regex = ['(?i)^\s*3\.\s*no\b'] },
+ ] },
+]
+
+[[rules]]
+id = "legacy_no_prompt_blocker"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+any = [
+ { contains = ["do you want to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
+ { contains = ["would you like to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
+ { contains = ["waiting for permission"] },
+ { contains = ["do you want to allow this connection?"] },
+ { contains = ["tab to amend"] },
+ { contains = ["ctrl+e to explain"] },
+ { contains = ["do you want to proceed?", "esc to cancel"] },
+ { contains = ["review your answers"] },
+ { contains = ["skip interview and plan immediately"] },
+]
+not = [
+ { regex = ['(?m)^\s*❯\s*$'] },
+]
+
+[[rules]]
+id = "osc_title_idle"
+state = "idle"
+priority = 250
+region = "osc_title"
+visible_idle = true
+regex = ['^\x{2733} ']
+
+[[rules]]
+id = "osc_progress_idle"
+state = "idle"
+priority = 250
+region = "osc_progress"
+regex = ['^4;0']
diff --git a/crates/detect-core/src/manifests/cline.toml b/crates/detect-core/src/manifests/cline.toml
new file mode 100644
index 0000000..2d31408
--- /dev/null
+++ b/crates/detect-core/src/manifests/cline.toml
@@ -0,0 +1,63 @@
+id = "cline"
+version = "2026.09.11.1"
+min_engine_version = 1
+updated_at = "2026-09-11T00:00:00Z"
+
+[[rules]]
+id = "tool_permission"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["let cline use this tool"] },
+ { contains = ["[act mode]", "execute command?", "yes"] },
+ { contains = ["[act mode]", "use this tool?", "yes"] },
+ { contains = ["[plan mode]", "execute command?", "yes"] },
+ { contains = ["[plan mode]", "use this tool?", "yes"] },
+]
+
+[[rules]]
+id = "inline_tool_permission"
+state = "blocked"
+priority = 300
+region = "bottom_non_empty_lines(16)"
+visible_blocker = true
+contains = ["Cline needs permission", "Approve tool call?", "[y] Approve", "[n] Deny"]
+
+[[rules]]
+id = "inline_question"
+state = "blocked"
+priority = 300
+region = "bottom_non_empty_lines(24)"
+visible_blocker = true
+line_regex = ['^\s*Cline is asking a question\s*$', '^\s*>\s+\S']
+contains = ["(Tab)", "Shift+Tab"]
+
+[[rules]]
+id = "active_turn"
+state = "working"
+priority = 200
+region = "bottom_non_empty_lines(20)"
+visible_working = true
+any = [
+ { line_regex = ['^\s*[\x{2801}-\x{28FF}]\s+\S'] },
+ { contains = ["Thinking... (esc to cancel)"] },
+]
+
+[[rules]]
+id = "composer_idle"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(12)"
+visible_idle = true
+line_regex = ['^\s*❯(?:\s.*)?$']
+contains = ["─", "(Tab)", "Shift+Tab"]
+
+[[rules]]
+id = "default_cline_working"
+state = "working"
+priority = -10
+region = "whole_recent"
+visible_working = true
+regex = ['(?s).+']
diff --git a/crates/detect-core/src/manifests/codex.toml b/crates/detect-core/src/manifests/codex.toml
new file mode 100644
index 0000000..d35820c
--- /dev/null
+++ b/crates/detect-core/src/manifests/codex.toml
@@ -0,0 +1,129 @@
+id = "codex"
+version = "2026.10.11.1"
+min_engine_version = 3
+updated_at = "2026-10-11T00:00:00Z"
+
+[[rules]]
+id = "osc_title_blocked"
+state = "blocked"
+priority = 1100
+region = "osc_title"
+visible_blocker = true
+contains = ["Action Required"]
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1050
+region = "osc_title"
+visible_working = true
+regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)']
+
+[[rules]]
+id = "transcript_viewer"
+state = "unknown"
+priority = 1000
+region = "after_last_prompt_marker"
+skip_state_update = true
+contains = ["↑/↓ to scroll", "pgup/pgdn to", "home/end to jump", "q to quit"]
+any = [
+ { contains = ["esc to edit prev"] },
+ { contains = ["esc/← to edit prev"] },
+]
+
+[[rules]]
+id = "trust_directory"
+state = "blocked"
+priority = 950
+region = "top_non_empty_lines(20)"
+visible_blocker = true
+all = [
+ { any = [
+ { regex = ['\A> You are in [^\r\n]+(?:\r?\n|$)'] },
+ { contains = ["Folder access"] },
+ ] },
+ { any = [
+ { regex = ['(?s)Do\s+you\s+trust\s+the\s+contents\s+of\s+this\s+directory\?'] },
+ { all = [
+ { contains = ["Trust this folder?", "Codex can read, edit, and run files here"] },
+ { any = [
+ { contains = ["Trust and continue"] },
+ { contains = ["enter continue"] },
+ ] },
+ ] },
+ ] },
+]
+
+[[rules]]
+id = "startup_update"
+state = "blocked"
+priority = 950
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+# Keep the legacy dialog; the shared picker (0.159+) has a different heading
+# and footer. Choices can scroll out of a short viewport, but both gates must
+# remain visible. The passive update history notice has neither picker footer.
+any = [
+ { all = [
+ { contains = ["Update available!", "Update now"] },
+ { regex = ['Skip\s+until\s+next\s+version', 'Press enter to continue\s*\z'] },
+ ] },
+ { all = [
+ { regex = ['(?m)^\s*Update\s+available\s+·'] },
+ { regex = ['enter\s+continue\s+·\s+esc\s+skip\s*\z'] },
+ ] },
+]
+
+[[rules]]
+id = "live_strong_blocker"
+state = "blocked"
+priority = 900
+region = "after_last_prompt_marker"
+visible_blocker = true
+any = [
+ { contains = ["press enter to confirm or esc to cancel"] },
+ { contains = ["enter to submit answer"] },
+ { contains = ["enter to submit all"] },
+ { contains = ["allow command?"] },
+ { contains = ["All Results", "Filesystem Only", "Plugins"] },
+]
+
+[[rules]]
+id = "weak_blocker"
+state = "blocked"
+priority = 600
+region = "whole_recent_without_current_prompt_marker"
+# Sparkles can replace the space after ›. A later response marker makes that prompt stale.
+not = [{ regex = ['(?m)^›[⠁⠂⠄⠈⠐⠠⡀⢀][^\n]*(?:\n(?:[^•■✗✓\n][^\n]*)?)*\z'] }]
+any = [
+ { contains = ["[y/n]"] },
+ { contains = ["yes (y)"] },
+ { contains = ["do you want to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
+ { contains = ["would you like to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
+]
+
+[[rules]]
+id = "screen_working_fallback"
+state = "working"
+priority = 500
+region = "before_current_prompt_marker"
+visible_working = true
+# Support animated and reduced-motion status, including dynamic activity labels.
+# The interrupt hint can be remapped, unbound, or hidden, and queued inputs can
+# sit below the status. Require the live timer suffix with no later response.
+any = [{ contains = [" to interrupt)"] }, { contains = ["s)"] }]
+regex = ['(?m)^(?:[•◦][ \t]+)?[^\s›•◦■✗✓─][^\r\n]* \((?:[0-9]+[hm] )*[0-9]+s(?: • [^\r\n]+? to interrupt)?\)(?: · [^\r\n]*)?(?:\r?\n(?:[^•◦›■✗✓─\r\n][^\r\n]*|•[ \t]+(?:Queued\s+follow-up\s+inputs|Messages\s+to\s+be\s+submitted\s+after\s+next\s+tool\s+call(?:\s+\(press\s+[^\r\n]+?\s+to\s+interrupt\s+and\s+send\s+immediately\))?|Messages\s+to\s+be\s+submitted\s+at\s+end\s+of\s+turn)|›[⠁⠂⠄⠈⠐⠠⡀⢀][^\r\n]*)?)*\s*\z']
+# A failed reconnect keeps its final elapsed timer but is no longer working.
+not = [{ line_regex = ['^(?:[•◦][ \t]+)?Reconnect failed — check the endpoint, then relaunch \([0-9hms ]+\)$'] }]
+
+[[rules]]
+id = "osc_title_idle"
+state = "idle"
+priority = 100
+region = "osc_title"
+visible_idle = true
+regex = ['\S']
+not = [
+ { regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)'] },
+ { contains = ["Action Required"] },
+]
diff --git a/crates/detect-core/src/manifests/cursor.toml b/crates/detect-core/src/manifests/cursor.toml
new file mode 100644
index 0000000..ee03e6d
--- /dev/null
+++ b/crates/detect-core/src/manifests/cursor.toml
@@ -0,0 +1,57 @@
+id = "cursor"
+version = "2026.08.03.1"
+min_engine_version = 1
+updated_at = "2026-08-03T01:08:04Z"
+aliases = ["cursor-agent"]
+
+[[rules]]
+id = "write_file_approval"
+state = "blocked"
+priority = 320
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = ["write to this file?", "proceed (y)"]
+any = [
+ { contains = ["reject & propose changes"] },
+ { contains = ["esc or n or p"] },
+ { contains = ["add write("] },
+]
+
+[[rules]]
+id = "approval_prompt"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["waiting for approval", "run this command?"], any = [{ contains = ["run (once) (y)"] }, { contains = ["skip (esc or n)"] }] },
+ { contains = ["(y) (enter)"] },
+ { line_regex = ['(?i)^\s*allow .*\(y\)'] },
+ { contains = ["keep (n)"] },
+ { contains = ["skip (esc or n)"] },
+ { line_regex = ['(?i)^\s*(?:→\s*)?run .*\(y\)'] },
+]
+
+[[rules]]
+id = "stop_hint_working"
+state = "working"
+priority = 100
+region = "bottom_non_empty_lines(6)"
+visible_working = true
+contains = ["ctrl+c to stop"]
+
+[[rules]]
+id = "background_task_status_working"
+state = "working"
+priority = 95
+region = "bottom_non_empty_lines(5)"
+visible_working = true
+line_regex = ['(?i)\b[1-9][0-9]*\s+background\s+tasks?\b']
+
+[[rules]]
+id = "spinner_working"
+state = "working"
+priority = 90
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+line_regex = ['^\s*(⬡|⬢|[\u2800-\u28FF]+)\s+\p{Alphabetic}+\w*ing\b']
diff --git a/crates/detect-core/src/manifests/devin.toml b/crates/detect-core/src/manifests/devin.toml
new file mode 100644
index 0000000..c9564f7
--- /dev/null
+++ b/crates/detect-core/src/manifests/devin.toml
@@ -0,0 +1,86 @@
+id = "devin"
+version = "2026.06.15.1"
+min_engine_version = 1
+updated_at = "2026-06-15T00:00:00Z"
+aliases = ["devin-cli", "devin cli"]
+
+[[rules]]
+id = "workspace_trust_prompt"
+state = "blocked"
+priority = 300
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = [
+ "do you trust the authors of this directory?",
+ "with untrusted content.",
+ "yes, trust ",
+]
+
+[[rules]]
+id = "permission_prompt"
+state = "blocked"
+priority = 290
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = ["approve once", "select", "confirm", "esc cancel"]
+
+[[rules]]
+id = "running_tools_footer"
+state = "working"
+priority = 200
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+contains = ["running tools", "esc to interrupt"]
+not = [
+ { contains = ["approve once", "esc cancel"] },
+]
+
+[[rules]]
+id = "guide_while_working"
+state = "working"
+priority = 190
+region = "bottom_non_empty_lines(6)"
+visible_working = true
+contains = ["guide devin while it works"]
+not = [
+ { contains = ["approve once", "esc cancel"] },
+]
+
+[[rules]]
+id = "tool_reading_timeout"
+state = "working"
+priority = 180
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+contains = ["reading shell ", "timeout:"]
+not = [
+ { contains = ["approve once", "esc cancel"] },
+]
+
+[[rules]]
+id = "welcome_prompt_footer"
+state = "idle"
+priority = 120
+region = "bottom_non_empty_lines(8)"
+visible_idle = true
+contains = ["ask devin to build", "features, fix bugs", "your code"]
+line_regex = ['^\s*❭ Ask Devin to build']
+not = [
+ { contains = ["approve once", "esc cancel"] },
+ { contains = ["running tools", "esc to interrupt"] },
+ { contains = ["guide devin while it works"] },
+]
+
+[[rules]]
+id = "live_prompt_footer"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(6)"
+visible_idle = true
+contains = ["context:"]
+line_regex = ['^\s*❭']
+not = [
+ { contains = ["approve once", "esc cancel"] },
+ { contains = ["running tools", "esc to interrupt"] },
+ { contains = ["guide devin while it works"] },
+]
diff --git a/crates/detect-core/src/manifests/droid.toml b/crates/detect-core/src/manifests/droid.toml
new file mode 100644
index 0000000..c41d71b
--- /dev/null
+++ b/crates/detect-core/src/manifests/droid.toml
@@ -0,0 +1,48 @@
+id = "droid"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+
+[[rules]]
+id = "execute_selection_blocker"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+contains = ["enter to select", "esc to cancel"]
+any = [
+ { contains = ["↑↓ to navigate"] },
+ { contains = ["use ↑↓ to navigate"] },
+]
+all = [
+ { any = [{ contains = ["> yes, allow"] }, { contains = ["> no, cancel"] }] },
+]
+
+[[rules]]
+id = "selection_menu_blocker"
+state = "blocked"
+priority = 290
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = ["enter select", "esc cancel"]
+any = [
+ { contains = ["↑/↓ navigate"] },
+ { contains = ["↑↓ navigate"] },
+]
+
+[[rules]]
+id = "spinner_stop_working"
+state = "working"
+priority = 110
+region = "whole_recent"
+visible_working = true
+contains = ["esc to stop"]
+line_regex = ['^\s*[\u2800-\u28FF]']
+
+[[rules]]
+id = "stop_hint_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+contains = ["esc to stop"]
diff --git a/crates/detect-core/src/manifests/gemini.toml b/crates/detect-core/src/manifests/gemini.toml
new file mode 100644
index 0000000..9d7a28e
--- /dev/null
+++ b/crates/detect-core/src/manifests/gemini.toml
@@ -0,0 +1,25 @@
+id = "gemini"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+
+[[rules]]
+id = "apply_or_allow_change"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["│ Apply this change"] },
+ { contains = ["│ Allow execution"] },
+ { all = [{ contains = ["yes"] }, { any = [{ contains = ["waiting for user confirmation"] }, { contains = ["│ Do you want to proceed"] }, { contains = ["do you want to proceed?"] }] }] },
+ { line_regex = ['(?i)^\s*❯.*(yes|allow)'] },
+]
+
+[[rules]]
+id = "esc_cancel_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+contains = ["esc to cancel"]
diff --git a/crates/detect-core/src/manifests/github-copilot.toml b/crates/detect-core/src/manifests/github-copilot.toml
new file mode 100644
index 0000000..57fafdd
--- /dev/null
+++ b/crates/detect-core/src/manifests/github-copilot.toml
@@ -0,0 +1,45 @@
+id = "copilot"
+version = "2026.08.29.1"
+min_engine_version = 1
+updated_at = "2026-08-29T00:00:00Z"
+aliases = ["github-copilot", "ghcs"]
+
+[[rules]]
+id = "selection_blocker"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+all = [
+ { any = [
+ { contains = ["esc to cancel"] },
+ { contains = ["esc cancel"] },
+ ] },
+ { any = [
+ { contains = ["enter to select"] },
+ { contains = ["enter to confirm"] },
+ { contains = ["enter to submit"] },
+ { contains = ["enter accept"] },
+ ] },
+]
+
+[[rules]]
+id = "background_agents_working"
+state = "working"
+priority = 110
+region = "bottom_non_empty_lines(6)"
+visible_working = true
+line_regex = ['^\s*◎\s+Waiting for background agents(?:\s|·|$)']
+
+[[rules]]
+id = "working_cancel_hint"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+any = [
+ { contains = ["esc to cancel"] },
+ { contains = ["esc cancel"] },
+ { contains = ["esc again to cancel"] },
+ { contains = ["esc interrupt"] }
+]
diff --git a/crates/detect-core/src/manifests/grok.toml b/crates/detect-core/src/manifests/grok.toml
new file mode 100644
index 0000000..bc37bc4
--- /dev/null
+++ b/crates/detect-core/src/manifests/grok.toml
@@ -0,0 +1,161 @@
+id = "grok"
+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.
+#
+# Grok emits OSC 0 titles by default. Idle is "grok" or
+# "<session> - grok". During a turn, the configured title gains a braille
+# spinner and activity text. Permission prompts add "⚠ Action Required";
+# that prefix blinks while the terminal is unfocused, so visible blocker
+# rules outrank the title spinner rule.
+#
+# Grok also emits OSC 9;4 progress on supported terminals. Herdr retains the
+# payload after "9;": "4;1;-1" while busy and "4;0;0" when idle.
+#
+# Working turns render one live status line directly above the prompt box:
+# "⠧ Waiting on subagent… 2.8s 13s ⇣29.7k [stop]"
+# "⠴ Explore /tmp/… + 1 more… 5.6s 19s ⇣29.7k [stop]"
+# with a braille spinner and a trailing [stop] chip, plus an Esc:cancel
+# footer hint. Permission prompts and ask-user-question dialogs replace the
+# spinner with "◆" and draw a "┃"-guttered option list:
+# "┃ 2 (○) Yes, proceed"
+# "┃ z (○) Type your answer here"
+# with footer hints "1/3:select │ Ctrl+o:yolo │ Ctrl+c:cancel" (permission)
+# or "Esc:unselect │ Tab:scrollback │ Shift+x:dismiss" (question dialog).
+# Idle footers end with "Ctrl+.:shortcuts" and never contain "Esc:cancel".
+# The startup splash draws its logo with braille characters, so working
+# rules must anchor on the [stop] chip, not on a bare spinner glyph.
+
+[[rules]]
+id = "osc_title_blocked"
+state = "blocked"
+priority = 1300
+region = "osc_title"
+visible_blocker = true
+contains = ["Action Required"]
+
+[[rules]]
+id = "option_dialog_blocked"
+state = "blocked"
+priority = 1200
+region = "whole_recent"
+visible_blocker = true
+line_regex = ['^\s*┃\s+[0-9a-z]+\s+\([●○]\)\s']
+
+[[rules]]
+id = "permission_hints_blocked"
+state = "blocked"
+priority = 1190
+region = "bottom_non_empty_lines(2)"
+visible_blocker = true
+contains = [":select", "ctrl+o:yolo", "ctrl+c:cancel"]
+
+[[rules]]
+id = "question_dialog_hints_blocked"
+state = "blocked"
+priority = 1185
+region = "bottom_non_empty_lines(2)"
+visible_blocker = true
+contains = ["tab:scrollback", "shift+x:dismiss"]
+
+# Pre-0.2.x permission UI kept for older Grok Build releases.
+[[rules]]
+id = "permission_scope_selector"
+state = "blocked"
+priority = 1180
+region = "whole_recent"
+visible_blocker = true
+contains = ["yes, proceed", "no, reject"]
+any = [
+ { contains = ["use ← → to choose permission whitelist scope"] },
+ { contains = ["←/→:scope"] },
+]
+
+# 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"
+state = "working"
+priority = 1150
+region = "osc_progress"
+visible_working = true
+regex = ['^4;1;-1$']
+
+[[rules]]
+id = "osc_title_idle"
+state = "idle"
+priority = 1100
+region = "osc_title"
+visible_idle = true
+regex = ['(?:^| - )grok$']
+not = [
+ { regex = ['[\x{2800}-\x{28FF}]'] },
+]
+
+# Title items are configurable; omitting "grok" does not imply activity.
+# Require a spinner, with OSC progress covering titles that omit it.
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1000
+region = "osc_title"
+visible_working = true
+regex = ['(?:^|\s)[\x{2801}-\x{28FF}](?:\s|$)']
+
+[[rules]]
+id = "osc_progress_idle"
+state = "idle"
+priority = 950
+region = "osc_progress"
+visible_idle = true
+regex = ['^4;0;0$']
+
+# Visible activity outranks idle OSC signals: disabling title updates can
+# leave the startup "grok" title unchanged throughout a turn.
+[[rules]]
+id = "spinner_status_working"
+state = "working"
+priority = 1160
+region = "whole_recent"
+visible_working = true
+line_regex = ['^\s*[\x{2801}-\x{28FF}]\s.*\[stop\]\s*$']
+
+[[rules]]
+id = "esc_cancel_hints_working"
+state = "working"
+priority = 1155
+region = "bottom_non_empty_lines(2)"
+visible_working = true
+contains = ["ctrl+.:shortcuts"]
+any = [
+ { contains = ["esc:cancel"] },
+ { contains = ["ctrl+c:cancel"] },
+]
+
+# Pre-0.2.x working chrome kept for older Grok Build releases.
+[[rules]]
+id = "waiting_tool_working"
+state = "working"
+priority = 1140
+region = "whole_recent"
+visible_working = true
+any = [
+ { all = [{ contains = ["ctrl+c:cancel", "ctrl+enter:interject"] }, { contains = ["waiting"] }] },
+ { line_regex = ['^\s*[\x{2801}-\x{28FF}]\s+(Run|Read|Search|List)\b'] },
+]
+
+[[rules]]
+id = "prompt_hints_idle"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(2)"
+visible_idle = true
+contains = ["ctrl+.:shortcuts"]
+not = [
+ { contains = ["esc:cancel"] },
+ { contains = ["ctrl+c:cancel"] },
+]
diff --git a/crates/detect-core/src/manifests/hermes.toml b/crates/detect-core/src/manifests/hermes.toml
new file mode 100644
index 0000000..1754218
--- /dev/null
+++ b/crates/detect-core/src/manifests/hermes.toml
@@ -0,0 +1,102 @@
+id = "hermes"
+version = "2026.07.24.1"
+min_engine_version = 2
+updated_at = "2026-07-24T19:12:54Z"
+aliases = ["hermes-agent"]
+
+[[rules]]
+id = "osc_title_blocked"
+state = "blocked"
+priority = 1100
+region = "osc_title"
+visible_blocker = true
+regex = ['^⚠[\u{fe0e}\u{fe0f}]?(?:\s|$)']
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1050
+region = "osc_title"
+visible_working = true
+regex = ['^⏳[\u{fe0e}\u{fe0f}]?(?:\s|$)']
+
+[[rules]]
+id = "dangerous_command_approval"
+state = "blocked"
+priority = 900
+region = "bottom_non_empty_lines(14)"
+visible_blocker = true
+any = [
+ { contains = ["dangerous"] },
+ { contains = ["approval"] },
+ { contains = ["allow once", "deny"] },
+ { line_regex = ['(?i)^\s*[▸>]?\s*1\.\s*allow'] },
+]
+all = [
+ { any = [{ contains = ["enter confirm"] }, { contains = ["enter to confirm"] }, { contains = ["↑/↓ to select"] }, { contains = ["show full command"] }] },
+]
+
+[[rules]]
+id = "clarification_prompt"
+state = "blocked"
+priority = 900
+region = "bottom_non_empty_lines(14)"
+visible_blocker = true
+any = [
+ { contains = ["hermes needs your"] },
+ { line_regex = ['^\s*ask\s+\S'] },
+ { contains = ["type your answer"] },
+]
+all = [
+ { any = [{ contains = ["enter confirm"] }, { contains = ["enter to confirm"] }, { contains = ["enter send"] }, { contains = ["press enter"] }, { contains = ["↑/↓ select"] }, { contains = ["↑/↓ to select"] }, { contains = ["other (type"] }] },
+]
+
+[[rules]]
+id = "credential_prompt"
+state = "blocked"
+priority = 900
+region = "bottom_non_empty_lines(14)"
+visible_blocker = true
+any = [
+ { contains = ["sudo password"] },
+ { contains = ["skill setup"] },
+ { contains = ["🔑", "for "] },
+]
+
+[[rules]]
+id = "confirmation_prompt"
+state = "blocked"
+priority = 900
+region = "bottom_non_empty_lines(14)"
+visible_blocker = true
+all = [
+ { any = [{ contains = ["approve once", "cancel"] }, { contains = ["start a new session", "keep going"] }] },
+ { any = [{ contains = ["enter to confirm"] }, { contains = ["enter confirm"] }, { contains = ["type 1/2/3"] }, { contains = ["y/n quick"] }] },
+]
+
+[[rules]]
+id = "interrupt_status_working"
+state = "working"
+priority = 950
+region = "bottom_non_empty_lines(5)"
+visible_working = true
+any = [
+ { contains = ["msg=interrupt"] },
+ { contains = ["ctrl+c to interrupt"] },
+]
+
+[[rules]]
+id = "classic_cancel_working"
+state = "working"
+priority = 500
+region = "bottom_non_empty_lines(5)"
+visible_working = true
+contains = ["ctrl+c cancel"]
+
+[[rules]]
+id = "osc_title_idle"
+state = "idle"
+priority = 100
+region = "osc_title"
+visible_idle = true
+regex = ['^✓[\u{fe0e}\u{fe0f}]?(?:\s|$)']
diff --git a/crates/detect-core/src/manifests/kilo.toml b/crates/detect-core/src/manifests/kilo.toml
new file mode 100644
index 0000000..4ef004e
--- /dev/null
+++ b/crates/detect-core/src/manifests/kilo.toml
@@ -0,0 +1,24 @@
+id = "kilo"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+aliases = ["kilo-code", "kilo code", "herdr:kilo"]
+
+[[rules]]
+id = "opencode_permission"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["△ Permission required"] },
+ { contains = ["esc dismiss"], any = [{ contains = ["enter confirm"] }, { contains = ["enter submit"] }, { contains = ["enter toggle"] }], all = [{ any = [{ contains = ["↑↓ select"] }, { contains = ["⇆ tab"] }] }] },
+]
+
+[[rules]]
+id = "esc_interrupt_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+contains = ["esc interrupt"]
diff --git a/crates/detect-core/src/manifests/kimi.toml b/crates/detect-core/src/manifests/kimi.toml
new file mode 100644
index 0000000..b4d0100
--- /dev/null
+++ b/crates/detect-core/src/manifests/kimi.toml
@@ -0,0 +1,77 @@
+id = "kimi"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+aliases = ["kimi-code", "kimi code"]
+
+[[rules]]
+id = "current_approval_panel"
+state = "blocked"
+priority = 400
+region = "whole_recent"
+visible_blocker = true
+contains = ["↵ confirm"]
+any = [
+ { contains = ["run this command?"] },
+ { contains = ["write this file?"] },
+ { contains = ["apply these edits?"] },
+ { contains = ["stop this task?"] },
+ { contains = ["ready to build with this plan?"] },
+ { line_regex = ['(?i)^\s*▶?\s*approve .*\?$'] },
+]
+all = [
+ { contains = [" choose"] },
+ { any = [{ contains = ["approve"] }, { contains = ["reject"] }, { contains = ["revise"] }] },
+]
+
+[[rules]]
+id = "question_panel"
+state = "blocked"
+priority = 390
+region = "whole_recent"
+visible_blocker = true
+contains = ["↑↓ select", "esc cancel"]
+line_regex = ['^\s*question\s*$', '^\s*\? ']
+any = [
+ { contains = ["↵ choose"] },
+ { contains = ["↵ toggle"] },
+ { contains = ["↵ save"] },
+]
+
+[[rules]]
+id = "legacy_approval_panel"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+contains = ["requesting approval", "reject"]
+any = [
+ { contains = ["approve once"] },
+ { contains = ["approve for this session"] },
+]
+all = [
+ { any = [{ contains = ["1/2/3/4 choose"] }, { contains = ["↵ confirm"] }] },
+]
+
+[[rules]]
+id = "background_agent_status_working"
+state = "working"
+priority = 120
+region = "bottom_non_empty_lines(3)"
+visible_working = true
+line_regex = ['(?i)\bkimi[-\w.]*\s+thinking\b.*\[[1-9][0-9]*\s+agents?\s+running\]']
+
+[[rules]]
+id = "moon_spinner_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+line_regex = ['^\s*(🌕|🌖|🌗|🌘|🌑|🌒|🌓|🌔)\s*$']
+
+[[rules]]
+id = "braille_spinner_working"
+state = "working"
+priority = 90
+region = "whole_recent"
+visible_working = true
+line_regex = ['(?i)^\s*[\u2800-\u28FF]+\s*(thinking\.\.\.|working\.\.\.|using )']
diff --git a/crates/detect-core/src/manifests/kiro.toml b/crates/detect-core/src/manifests/kiro.toml
new file mode 100644
index 0000000..9d50f93
--- /dev/null
+++ b/crates/detect-core/src/manifests/kiro.toml
@@ -0,0 +1,97 @@
+id = "kiro"
+version = "2026.09.19.1"
+min_engine_version = 2
+updated_at = "2026-09-19T00:00:00Z"
+aliases = ["kiro-cli"]
+
+[[rules]]
+id = "live_prompt_idle"
+state = "idle"
+priority = 1100
+region = "bottom_non_empty_lines(4)"
+visible_idle = true
+line_regex = ['(?i)^\s*[>›]\s*ask a question or describe a task(?:\s+(?:enter|↵))?\s*$']
+
+[[rules]]
+id = "tool_approval"
+state = "blocked"
+priority = 1050
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+any = [
+ { all = [
+ { regex = ['(?is)(?:^|\n)\s*esc\s+to\s+close\b(?:[^\n]*\bto\s+navigate\b[^\n]*|.*\bto\s+navigate\b.*\bto\s+select\b.*\btab\s+to\s+edit|[^\n]*\benter\s+to\s+see\s+more\s+options)\s*\z'] },
+ { any = [
+ { line_regex = [
+ '(?i)^\s*[>❯]\s*(?:allow|always allow|deny|always deny)\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?allow\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?always allow\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?deny\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?always deny\s*$',
+ ] },
+ { line_regex = [
+ '(?i)^\s*[>❯]\s*(?:yes, single permission|trust, always allow in this session|no \(tab to edit\))\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?yes, single permission\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?trust, always allow in this session\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?no \(tab to edit\)\s*$',
+ ] },
+ { line_regex = [
+ '(?i)^\s*[>❯]\s*(?:trust\b.*|entire tool)\s*$',
+ '(?i)^\s*(?:[>❯]\s*)?(?:trust )?entire tool(?:\s+\([^\n]*\))?(?:\s+(?:session|workspace|always))?\s*$',
+ ] },
+ ] },
+ ] },
+]
+
+[[rules]]
+id = "tool_approval_edit"
+state = "blocked"
+priority = 1045
+region = "whole_recent"
+visible_blocker = true
+regex = ['(?im)(?:^|\n)[ \t]*[-─]+[ \t]*\n[^\n]*requires\s+approval\s*[·.]\s*modify\s+request[ \t]*\n(?:[ \t]*\n|[^\n]*[^\s─-][^\n]*\n)*?[ \t]*[>›][ \t]*[^\n]*\n(?:[ \t]*\n|[^\n]*[^\s─-][^\n]*\n)*[ \t]*[-─]+[ \t]*\n[ \t]*esc[ \t]+to[ \t]+close[ \t]*\n?\z']
+
+[[rules]]
+id = "crew_approval"
+state = "blocked"
+priority = 1040
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = [
+ "tool approval",
+ "approve all pending",
+ "configure individually (agent monitor)",
+ "exit (cancel subagents)",
+]
+
+[[rules]]
+id = "question_panel"
+state = "blocked"
+priority = 1030
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+contains = ["to navigate", "to submit", "esc to cancel"]
+
+[[rules]]
+id = "live_working_footer"
+state = "working"
+priority = 950
+region = "bottom_non_empty_lines(4)"
+visible_working = true
+contains = ["kiro is working", "type to steer", "ctrl+s to queue"]
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 900
+region = "osc_title"
+visible_working = true
+regex = ['(?i)^[◐◓◑◒/|\\-]\s+kiro:']
+
+[[rules]]
+id = "osc_progress_working"
+state = "working"
+priority = 890
+region = "osc_progress"
+visible_working = true
+regex = ['^4;3;?$']
diff --git a/crates/detect-core/src/manifests/letta.toml b/crates/detect-core/src/manifests/letta.toml
new file mode 100644
index 0000000..e27e2aa
--- /dev/null
+++ b/crates/detect-core/src/manifests/letta.toml
@@ -0,0 +1,95 @@
+id = "letta"
+version = "2026.08.24.1"
+min_engine_version = 3
+updated_at = "2026-08-24T00:00:00Z"
+aliases = ["letta-code", "letta code"]
+
+# Letta keeps completed tool rows and spinners in the transcript. A standalone
+# screen spinner is not working evidence. Prefer its live OSC title/progress
+# signals, with visible status chrome and running tool rows as fallbacks.
+[[rules]]
+id = "osc_progress_blocked"
+state = "blocked"
+priority = 1400
+region = "osc_progress"
+visible_blocker = true
+regex = ['^4;3(?:;|$)']
+
+[[rules]]
+id = "osc_title_blocked"
+state = "blocked"
+priority = 1300
+region = "osc_title"
+visible_blocker = true
+regex = ['^\[ [!.] \] Action Required(?: \| |$)']
+
+[[rules]]
+id = "command_approval"
+state = "blocked"
+priority = 1200
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+contains = ["Run this command?", "Enter to select · Esc to cancel"]
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 900
+region = "osc_title"
+visible_working = true
+regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)']
+
+[[rules]]
+id = "active_status"
+state = "working"
+priority = 850
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+line_regex = ['^\s*(?:\S+\s+)+is(?: \S+)*… \((?:esc to interrupt(?: · .*)?|interrupting)\)\s*$']
+
+[[rules]]
+id = "running_tool"
+state = "working"
+priority = 800
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+line_regex = ['^\s*(?:└\s*)?Running\.\.\.\s*(?:\(.*\))?$']
+
+[[rules]]
+id = "profile_selector"
+state = "unknown"
+priority = 700
+region = "bottom_non_empty_lines(12)"
+contains = ["Create a new agent (--new)", "Enter select · Esc exit"]
+
+[[rules]]
+id = "composer_input"
+state = "unknown"
+priority = 150
+region = "bottom_non_empty_lines(8)"
+line_regex = ['^\s*›\s+\S.*$']
+not = [
+ { line_regex = ['^\s*›\s+Try\s+"'] },
+]
+
+[[rules]]
+id = "composer_idle"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(8)"
+visible_idle = true
+any = [
+ { line_regex = ['^\s*›\s*$'] },
+ { line_regex = ['^\s*›\s+Try\s+"'] },
+]
+not = [
+ { line_regex = ['^\s*(?:\S+\s+)+is(?: \S+)*… \((?:esc to interrupt(?: · .*)?|interrupting)\)\s*$'] },
+ { line_regex = ['^\s*(?:└\s*)?Running\.\.\.\s*(?:\(.*\))?$'] },
+]
+
+[[rules]]
+id = "no_live_state_evidence"
+state = "unknown"
+priority = 0
+region = "whole_recent"
+regex = ['(?s)^.*$']
diff --git a/crates/detect-core/src/manifests/maki.toml b/crates/detect-core/src/manifests/maki.toml
new file mode 100644
index 0000000..5c58404
--- /dev/null
+++ b/crates/detect-core/src/manifests/maki.toml
@@ -0,0 +1,68 @@
+id = "maki"
+version = "2026.07.09.2"
+min_engine_version = 1
+updated_at = "2026-07-09T00:00:00Z"
+
+# Maki renders a persistent one-line status bar on the bottom row. It starts
+# with the mode label "[BUILD]", "[PLAN]", or "[BASH]" when idle and gets a
+# leading braille spinner cell while the agent is streaming. Permission
+# requests and the plan-complete form replace the input box above the status
+# bar. Maki does not set OSC title or OSC 9;4 progress.
+
+[[rules]]
+id = "permission_prompt"
+state = "blocked"
+priority = 980
+region = "whole_recent"
+visible_blocker = true
+contains = ["permission required"]
+any = [
+ { contains = ["y allow", "n deny"] },
+ { contains = ["confirm allow"] },
+ { contains = ["confirm deny"] },
+ { contains = ["enter deny", "esc cancel"] },
+]
+
+[[rules]]
+id = "plan_complete_form"
+state = "blocked"
+priority = 970
+region = "whole_recent"
+visible_blocker = true
+contains = ["plan complete", "enter confirm"]
+any = [
+ { contains = ["space toggle parallel"] },
+ { contains = ["edit plan"] },
+]
+
+[[rules]]
+id = "status_bar_spinner_working"
+state = "working"
+priority = 900
+region = "bottom_non_empty_lines(1)"
+visible_working = true
+line_regex = ['^( [\x{2800}-\x{28FF}]){1,2} \[(BUILD|PLAN|BASH)\]']
+
+[[rules]]
+id = "status_bar_idle"
+state = "idle"
+priority = 850
+region = "bottom_non_empty_lines(1)"
+visible_idle = true
+line_regex = ['^ \[(BUILD|PLAN|BASH)\]']
+
+# On narrow panes the right side of the status bar overwrites the mode label,
+# so fall back to the prompt chevron above the input box border. The not-gates
+# keep this from matching the streaming placeholder or a status bar that still
+# shows the spinner.
+[[rules]]
+id = "prompt_box_idle"
+state = "idle"
+priority = 840
+region = "bottom_non_empty_lines(3)"
+visible_idle = true
+line_regex = ['^❯ ']
+not = [
+ { contains = ["queue another prompt"] },
+ { line_regex = ['^( [\x{2800}-\x{28FF}]){1,2} '] },
+]
diff --git a/crates/detect-core/src/manifests/muse.toml b/crates/detect-core/src/manifests/muse.toml
new file mode 100644
index 0000000..5081842
--- /dev/null
+++ b/crates/detect-core/src/manifests/muse.toml
@@ -0,0 +1,113 @@
+id = "muse"
+version = "2026.08.26.1"
+min_engine_version = 2
+updated_at = "2026-08-26T00:00:00Z"
+aliases = ["muse-code", "muse-cli"]
+
+# Evidence: live bottom-buffer captures from Muse Code 0.2.1 in Herdr, using a local
+# deterministic Responses provider so each UI state could be held and inspected.
+#
+# Idle has a `⟩` prompt and `model · effort · cwd` footer. Active turns show
+# `◆ Working (... · esc to interrupt)` or another activity label with the same interrupt hint.
+#
+# Structured questions show two co-occurring footer controls:
+# Enter to select · ↑/↓ to move · Tab for an optional note · Esc to interrupt
+# Multi-select uses `Enter to toggle` instead. The paired controls distinguish a real picker
+# from ordinary transcript text that happens to mention one action.
+#
+# First launch in an untrusted directory shows `Do you trust this workspace?` together with
+# `Trust and continue`. This is a real blocker. User-opened `/theme` and `/skills` menus are
+# not blockers; their paired footer controls identify overlays whose prior state must be kept.
+#
+# Muse 0.2.1 command approval shows `Allow this stage once` together with
+# `Always allow in this workspace`. Muse 0.1 used `Allow once` with
+# `Allow for this session`. Network approval shows `Yes, proceed` together with
+# `Yes, don't ask again this session`. Each approval rule requires a pair because Muse can
+# emit any one of these phrases as ordinary assistant text after a completed turn.
+
+[[rules]]
+id = "workspace_trust_blocked"
+state = "blocked"
+priority = 970
+region = "bottom_non_empty_lines(12)"
+visible_blocker = true
+contains = ["Do you trust this workspace?"]
+any = [
+ { contains = ["Trust and continue"] },
+ { contains = ["Use Up/Down"] },
+]
+
+[[rules]]
+id = "pick_request_blocked"
+state = "blocked"
+priority = 950
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+any = [
+ { contains = ["Enter to select", "Tab for an optional note"] },
+ { contains = ["Enter to toggle", "Esc to interrupt"] },
+]
+
+[[rules]]
+id = "menu_overlay"
+state = "unknown"
+priority = 940
+region = "bottom_non_empty_lines(8)"
+skip_state_update = true
+any = [
+ { contains = ["enter confirm", "esc go back"] },
+ { contains = ["enter save", "esc go back"] },
+ { contains = ["space toggle", "esc close", "type filter"] },
+]
+
+[[rules]]
+id = "working_esc_interrupt"
+state = "working"
+priority = 900
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+contains = ["esc to interrupt"]
+not = [
+ { contains = ["Enter to select", "Tab for an optional note"] },
+ { contains = ["Enter to toggle", "Esc to interrupt"] },
+]
+
+[[rules]]
+id = "blocked_approval"
+state = "blocked"
+priority = 850
+region = "bottom_non_empty_lines(8)"
+visible_blocker = true
+any = [
+ { contains = ["Allow this stage once", "Always allow in this workspace"] },
+ { contains = ["Allow once", "Allow for this session"] },
+ { contains = ["Yes, proceed", "Yes, don't ask again this session"] },
+]
+
+[[rules]]
+id = "idle_prompt"
+state = "idle"
+priority = 700
+region = "bottom_non_empty_lines(5)"
+visible_idle = true
+any = [
+ { line_regex = ['^\s*⟩\s*$'] },
+ { line_regex = ['^\s*⟩\s+\S'] },
+]
+not = [
+ { contains = ["esc to interrupt"] },
+ { contains = ["Enter to select", "Tab for an optional note"] },
+ { contains = ["Enter to toggle", "Esc to interrupt"] },
+ { contains = ["enter confirm", "esc go back"] },
+ { contains = ["enter save", "esc go back"] },
+ { contains = ["space toggle", "esc close", "type filter"] },
+]
+
+[[rules]]
+id = "idle_status_fallback"
+state = "idle"
+priority = 500
+region = "bottom_non_empty_lines(3)"
+visible_idle = true
+line_regex = ['^\s*\S+ · (none|minimal|low|medium|high|xhigh|ultra) · ']
+not = [{ contains = ["esc to interrupt"] }]
diff --git a/crates/detect-core/src/manifests/opencode.toml b/crates/detect-core/src/manifests/opencode.toml
new file mode 100644
index 0000000..5245238
--- /dev/null
+++ b/crates/detect-core/src/manifests/opencode.toml
@@ -0,0 +1,37 @@
+id = "opencode"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+aliases = ["open-code", "herdr:opencode"]
+
+[[rules]]
+id = "permission_required"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["△ Permission required"] },
+ { contains = ["esc dismiss"], any = [{ contains = ["enter confirm"] }, { contains = ["enter submit"] }, { contains = ["enter toggle"] }], all = [{ any = [{ contains = ["↑↓ select"] }, { contains = ["⇆ tab"] }] }] },
+]
+
+[[rules]]
+id = "interrupt_hint_working"
+state = "working"
+priority = 110
+region = "whole_recent"
+visible_working = true
+any = [
+ { contains = ["esc to interrupt"] },
+ { contains = ["ctrl+c to interrupt"] },
+ { contains = ["press esc to interrupt"] },
+ { line_regex = ['(?i).*opencode.*esc (again to )?interrupt'] },
+]
+
+[[rules]]
+id = "progress_bar_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+regex = ['(■|⬝){4,}']
diff --git a/crates/detect-core/src/manifests/pi.toml b/crates/detect-core/src/manifests/pi.toml
new file mode 100644
index 0000000..5ae51a8
--- /dev/null
+++ b/crates/detect-core/src/manifests/pi.toml
@@ -0,0 +1,27 @@
+id = "pi"
+version = "2026.10.01.1"
+min_engine_version = 1
+updated_at = "2026-10-01T00:00:00Z"
+aliases = ["herdr:pi"]
+
+[[rules]]
+id = "working_literal"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+any = [
+ { contains = ["Working..."] },
+ { line_regex = ['^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] Working$'] },
+]
+
+[[rules]]
+id = "working_border"
+state = "working"
+priority = 100
+region = "bottom_non_empty_lines(12)"
+visible_working = true
+any = [
+ { line_regex = ['^── [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] Working ─+$'] },
+ { line_regex = ['^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] Working$'] },
+]
diff --git a/crates/detect-core/src/manifests/qodercli.toml b/crates/detect-core/src/manifests/qodercli.toml
new file mode 100644
index 0000000..51ca805
--- /dev/null
+++ b/crates/detect-core/src/manifests/qodercli.toml
@@ -0,0 +1,38 @@
+id = "qodercli"
+version = "2026.06.10.1"
+min_engine_version = 1
+updated_at = "2026-06-10T00:00:00Z"
+aliases = ["qoderclicn", "qoder", "qodercn"]
+
+[[rules]]
+id = "confirmation_or_input_blocker"
+state = "blocked"
+priority = 300
+region = "whole_recent"
+visible_blocker = true
+any = [
+ { contains = ["waiting for user confirmation"], any = [{ contains = ["yes"] }, { contains = ["no"] }, { contains = ["allow"] }, { contains = ["reject"] }] },
+ { contains = ["awaiting approval"], any = [{ contains = ["allow"] }, { contains = ["reject"] }] },
+ { contains = ["permission required"] },
+ { contains = ["allow once or always?"] },
+ { contains = ["asking user"] },
+ { contains = ["enter your response"] },
+ { contains = ["review your answers:"] },
+ { contains = ["shell awaiting input"] },
+]
+
+[[rules]]
+id = "cancel_hint_working"
+state = "working"
+priority = 100
+region = "whole_recent"
+visible_working = true
+contains = ["(esc to cancel,"]
+
+[[rules]]
+id = "spinner_working"
+state = "working"
+priority = 90
+region = "whole_recent"
+visible_working = true
+line_regex = ['^\s*[\u2800-\u28FF]\s+.*\p{Alphabetic}']
diff --git a/crates/detect-core/src/manifests/qwen.toml b/crates/detect-core/src/manifests/qwen.toml
new file mode 100644
index 0000000..bbed237
--- /dev/null
+++ b/crates/detect-core/src/manifests/qwen.toml
@@ -0,0 +1,119 @@
+id = "qwen"
+version = "2026.08.14.1"
+min_engine_version = 2
+updated_at = "2026-08-14T00:00:00Z"
+aliases = ["qwen-code", "qwen code"]
+
+# Qwen Code keeps its composer visible while responding, so the prompt box is
+# not idle evidence by itself. Its status-prefixed terminal titles are the
+# primary locale-independent signals when ui.showStatusInTitle is enabled.
+# Exact screen fallbacks cover built-in locales and narrow terminals. OSC 9;4
+# is supplemental because Qwen emits it only while a tool is executing and
+# only in terminals that advertise progress support.
+
+[[rules]]
+id = "osc_title_blocked"
+state = "blocked"
+priority = 1200
+region = "osc_title"
+visible_blocker = true
+regex = ['^\x{2733}\x{FE0E}? ']
+
+[[rules]]
+id = "osc_title_working"
+state = "working"
+priority = 1100
+region = "osc_title"
+visible_working = true
+regex = ['^\x{25D0}\x{FE0E}? ']
+
+[[rules]]
+id = "waiting_for_confirmation"
+state = "blocked"
+priority = 1000
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+line_regex = ['^\s*⠏\s+.*\.\.\.\s*$']
+any = [
+ { contains = ["Waiting for user confirmation..."] },
+ { contains = ["等待用户确认..."] },
+ { contains = ["等待用戶確認..."] },
+ { contains = ["Warten auf Benutzerbestätigung..."] },
+ { contains = ["En attente de la confirmation de l'utilisateur..."] },
+ { contains = ["ユーザーの確認を待っています..."] },
+ { contains = ["Aguardando confirmação do usuário..."] },
+ { contains = ["Ожидание подтверждения от пользователя..."] },
+ { contains = ["Esperant la confirmació de l'usuari..."] },
+]
+
+[[rules]]
+id = "tool_confirmation"
+state = "blocked"
+priority = 990
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+contains = ["yes, allow once"]
+any = [
+ { contains = ["apply this change?"] },
+ { contains = ["allow execution of:"] },
+ { contains = ["allow execution of mcp tool"] },
+ { contains = ["do you want to proceed?"] },
+ { contains = ["shell command execution"] },
+]
+
+[[rules]]
+id = "question_dialog"
+state = "blocked"
+priority = 980
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+line_regex = [
+ '^\s*[❯›]\s*(?:\[(?: |✓)\]\s*)?\d+\.\s+',
+ '^\s*↑/↓\s*:.*(?:Enter|Return)\s*:',
+]
+
+[[rules]]
+id = "folder_trust_dialog"
+state = "blocked"
+priority = 970
+region = "bottom_non_empty_lines(20)"
+visible_blocker = true
+contains = ["do you trust this folder?", "trust folder (", "don't trust (esc)"]
+
+[[rules]]
+id = "cancel_hint_working"
+state = "working"
+priority = 900
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+line_regex = ['^\s*(?:[⠁-⣿]|\.{1,2})\s+.*\(\d+(?:m(?:\s+\d+s)?|s).*\s·\sesc to cancel\)\s*$']
+
+[[rules]]
+id = "narrow_cancel_hint_working"
+state = "working"
+priority = 890
+region = "bottom_non_empty_lines(8)"
+visible_working = true
+line_regex = ['^\s*\(\d+(?:m(?:\s+\d+s)?|s)\s·\sesc to cancel\)\s*$']
+
+[[rules]]
+id = "osc_tool_progress_working"
+state = "working"
+priority = 850
+region = "osc_progress"
+visible_working = true
+regex = ['^4;3(?:;|$)']
+
+[[rules]]
+id = "composer_idle"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(30)"
+visible_idle = true
+line_regex = ['^\s*>\s*(?:type\s*)?.*$']
+any = [
+ { contains = ["type your message"] },
+ { contains = ["your message or @path/to/file"] },
+ { contains = ["@path/to/file"] },
+ { contains = ["type", "mes", "sage", "@pat", "h/to", "/fil"] },
+]