diff --git a/docs-ai/064-agent-completion-signals/000-plan.md b/docs-ai/064-agent-completion-signals/000-plan.md index 3dac1138..3b14bce0 100644 --- a/docs-ai/064-agent-completion-signals/000-plan.md +++ b/docs-ai/064-agent-completion-signals/000-plan.md @@ -2,7 +2,7 @@ | | | | --- | --- | -| **Status** | Planned (per-runtime matrix pending research) | +| **Status** | Planned (research matrix recorded 2026-08-22) | | **Anchor date** | 2026-08-22 | | **Primary PRs** | TBD | | **Related** | [063 agent-workflows](../063-agent-workflows/000-plan.md) (consumer; defines the `ObservedAgentState` observer this entry feeds), [030 agent-status-detection](../030-agent-status-detection/000-plan.md), [045 native-agent-session-detection](../045-native-agent-session-detection/000-plan.md), [055 agent-profile-runtimes](../055-agent-profile-runtimes/000-plan.md), [059 agent-transcript-snapshots](../059-agent-transcript-snapshots/000-plan.md), [060 cli-targeting-and-contract-governance](../060-prowl-cli-targeting-and-contract-governance/000-plan.md), [#473](https://github.com/onevcat/Prowl/issues/473), [#676](https://github.com/onevcat/Prowl/issues/676), `docs/components/agent-detection.md`, `docs/components/cli.md` | @@ -149,7 +149,7 @@ permission or question dialog), tells the agent to use `--include-screen` and | --- | --- | --- | --- | | 1 | **S1** Signal bus state + `.signal` observer case + `prowl agents signal` (CLI four layers) | 063 B3's observer | Layer 0 works for every runtime immediately | | 2 | **S2** `prowl agents wait` + `agents` `signals` field + `--include-screen` + skill rubric | S1 | Route B usable; heuristic fallback honest | -| 3 | **S3** Launch-scoped hook injection per runtime (adapter `signalHooks`, self-check) | 063 A2, research matrix | Start with Claude Code and Codex; add runtimes as verified | +| 3 | **S3** Launch-scoped hook injection per runtime (adapter `signalHooks`, self-check) | 063 A2, research matrix | Wave 1 = tier A of the research matrix (flag/env per launch, live-verified): Claude Code `--settings`, Codex `-c notify=[…]` (turn-complete only; hook trust bypass is never passed), Copilot `--plugin-dir`, Droid `--settings`, Qoder `--settings`, Pi `-e`, OMP `--hook`, OpenCode `OPENCODE_CONFIG_CONTENT`. Wave 2 = tier B (`configDirOnly`: Gemini, Qwen, Grok, Cline, Kimi) for dedicated-home profiles only. Tier C (Cursor, Amp: project files) is not attached. | | 4 | **S4** Transcript file-watch and OSC producers | S1 | Layer 2 without hooks | | 5 | **S5** 063 consumption: watchdog uses exact signals; V2 observe mode / `on_attention: ask` | 063 C1+, S3 | Recorded in 063 amendments | @@ -177,13 +177,39 @@ arrive, `wait` resolves with `source=hook`, and a manually launched agent resolv detection/adapters/CLI rather than the runner, and need their own per-runtime maintenance; 063 consumes them through one observer type. +## Research outcome (2026-08-22) + +The per-runtime matrix lives in +[research-agent-completion-signals.md](research-agent-completion-signals.md) (all 15 CLIs +installed locally; live hook runs for claude, codex, copilot, kimi, droid, pi, omp, +opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusions: + +- Eight runtimes accept a Prowl hook **per launch without touching user config** + (tier A above); five more only through a Prowl-owned home (tier B, i.e. dedicated-home + profiles); Cursor Agent and Amp only via project files (not attached). +- Codex's hook system is trust-gated per command hash; per-launch `-c hooks.*` needs + `--dangerously-bypass-hook-trust`, which Prowl will **not** pass. Codex gets + `turn-complete` through the ungated `notify` config; its permission prompts stay + heuristic/transcript-based. +- Claude Code holds all hooks in interactive sessions until the workspace-trust dialog is + accepted — the self-check grace must tolerate that, and a trust prompt is itself a + `blocked` state worth surfacing. +- Kimi's `--config-file` replaces the whole config; per-launch hooks there mean Prowl + re-supplying the user's provider config — deferred to tier B. +- Several payloads carry `last_assistant_message` (Claude, Codex, Qoder, Qwen, Grok, + Gemini) — a cheap result channel for 063's V2 observe mode on those runtimes. +- Terminal escapes (OSC 9/99/777/BEL, 9;4) are focus-/threshold-gated everywhere and + therefore only a layer-2 hint, never a completion proof. + ## Open questions -- Per-runtime hook/notify/event support, per-launch enablement syntax, and payload shapes - — being researched; results land in `research-agent-completion-signals.md`. - Whether hook subprocesses can always reach Prowl's socket from sandboxed runtimes - (Codex sandbox); `PROWL_CLI_SOCKET` and the bundled binary path must be passed through. -- Exact `stable-for` and self-check grace defaults. + (Codex sandbox, OpenCode/Pi/OMP plugin runtimes); `PROWL_CLI_SOCKET` and the bundled + binary path must be passed through and verified per runtime in S3. +- Exact `stable-for` and self-check grace defaults (Claude's trust-dialog hold suggests a + generous, state-aware grace rather than a fixed few seconds). +- Re-verification cadence: the matrix is versioned per row; S3 adapters need fixture tests + that fail loudly when a CLI's hook syntax changes. ## Amendments diff --git a/docs-ai/064-agent-completion-signals/research-agent-completion-signals.md b/docs-ai/064-agent-completion-signals/research-agent-completion-signals.md new file mode 100644 index 00000000..ab6e9f88 --- /dev/null +++ b/docs-ai/064-agent-completion-signals/research-agent-completion-signals.md @@ -0,0 +1,135 @@ +# Agent completion signals — per-runtime research (living) + +> Living document for [064](000-plan.md): which deterministic "turn complete / needs +> input / session start-end" channels each recognized agent CLI offers, how they can be +> enabled per launch, what the payload carries, and what is not achievable. Update rows in +> place when a CLI changes; record the version and verification method per row. + +**Baseline (2026-08-22, this Mac):** claude 2.1.239 · codex 0.147.0 · gemini 0.46.0 · +cursor-agent 2026.05.09 · cline 3.0.48→3.0.56 · opencode 1.18.11 · copilot 1.0.77 · +kimi 1.41.0 · droid 0.186.0 · amp 0.0.1783746383 · qodercli 1.0.48 · qwen 0.21.3 · +grok 0.2.118 · pi 0.84.2 · omp 17.2.7. + +**Method:** `--help`; string search over binaries/bundles; read-only inspection of each +tool's session directory; official docs; and, where auth allowed, a live non-interactive +run with a capture hook (a script appending argv + stdin JSON to a scratch log) to prove +per-launch enablement and real payloads. Live hook runs succeeded for claude, codex, +copilot, kimi, droid, pi, omp, opencode; partially for qodercli (SessionStart/End), qwen +(SessionStart), amp (session.start/agent.start); blocked by login for gemini, +cursor-agent, cline; not attempted for grok (no per-launch channel). No user config files +were modified; all runs used scratch directories and per-launch flags/env. + +Confidence: **V** verified locally (live run or binary/source) · **D** official docs · +**C** community · **?** unknown. + +## 1. Summary matrix + +| Runtime | Turn-complete signal | Blocked / permission signal | Per-launch enablement (exact) | Payload: session id / last message | OSC self-report | Transcript marker | Conf. | +| --- | --- | --- | --- | --- | --- | --- | --- | +| Claude Code | hooks `Stop` (`StopFailure` on API error); `SessionStart` / `SessionEnd` | hooks `PermissionRequest` (immediate); `Notification` `notification_type` = `permission_prompt` (~6 s after prompt), `idle_prompt` (~60 s), `elicitation_dialog`; `Elicitation` | `claude --settings ''` or `--settings file.json` (hooks MERGE with user hooks) — live-verified; `--session-id `; `CLAUDE_CONFIG_DIR` = full relocation | stdin JSON: `session_id`, `cwd`, `transcript_path`, `permission_mode`, `last_assistant_message` (Stop) | `preferredNotifChannel` auto/iterm2(OSC 9)/kitty/ghostty/terminal_bell — same idle/permission gating; OSC 9;4 via `terminalProgressBarEnabled` | `~/.claude/projects//.jsonl`: assistant `stop_reason:"end_turn"` then `system/turn_duration` | V | +| Codex | `notify=[…]` → `agent-turn-complete`; hooks `Stop`; `SessionStart` / `SessionEnd` | hooks `PermissionRequest`; nothing for idle | `-c 'notify=["/abs/cmd"]'` (no trust gate) — live-verified; `-c 'hooks.Stop=[{hooks=[{type="command",command="/abs/cmd"}]}]'` fires ONLY with `--dangerously-bypass-hook-trust` (else silently skipped) — both live-verified; `CODEX_HOME` = full relocation | notify: JSON as last argv: `thread-id`, `turn-id`, `cwd`, `last-assistant-message`; hooks stdin: `session_id`, `turn_id`, `transcript_path`, `cwd`, `last_assistant_message` | `tui.notifications` (+ `tui.notification_method` osc9/bel, `tui.notification_condition` unfocused default / always); no 9;4 | `~/.codex/sessions/Y/M/D/rollout-*.jsonl`: `event_msg` `task_complete` / `turn_aborted` | V | +| Gemini CLI | hooks `AfterAgent` (once per turn); `SessionStart` / `SessionEnd` | hooks `Notification` `notification_type:"ToolPermission"` only; no idle/ask hook | no `--settings` flag; project `.gemini/settings.json`; `GEMINI_CLI_SYSTEM_SETTINGS_PATH=/file.json` (system layer, merges) — untested; `GEMINI_CLI_HOME` = full relocation | stdin JSON: `session_id`, `transcript_path`, `cwd`, `prompt_response` (AfterAgent) | off by default: `general.enableNotifications` + `notificationMethod` auto/osc9/osc777/bell; no 9;4 | `~/.gemini/tmp//chats/session-*.jsonl` (no explicit marker) | D + bundle | +| Cursor Agent | hooks `stop` (`status` completed/aborted/error), `afterAgentResponse` (`text`); `sessionStart` / `sessionEnd` | none (Claude-compat map sets `PermissionRequest→null`, `Notification→null`) | no flag/env (`CURSOR_CONFIG_DIR` does not move hooks.json); project `/.cursor/hooks.json` or `.claude/settings(.local).json`; `--plugin-dir` hooks not executed in this build | stdin JSON: `conversation_id`/`session_id`, `transcript_path`, `workspace_roots`, `text` | `cli-config.json` `notifications:true`: focus-gated → OSC 9 (iTerm2), OSC 777 (Ghostty/Warp), OSC 99 (Kitty), BEL (Terminal.app); no 9;4 | `~/.cursor/projects//agent-transcripts//.jsonl` (no marker) | V (bundle) | +| Cline | file hooks `TaskComplete` (=`agent_end`), `TaskError`, `TaskCancel`; `TaskStart` / `SessionShutdown` | none | dirs `~/.cline/hooks`, `/.cline/hooks`, `/.clinerules/hooks`; `--hooks-dir ` only sets `CLINE_HOOKS_DIR`, no reader found → unverified; `CLINE_DIR` = full relocation | stdin JSON: `taskId`, `hookName`, `workspaceRoots`, `turn.outputText`; no transcript path | none | `~/.cline/data/tasks//ui_messages.json`: `say:completion_result` → `ask:completion_result`; any `type:"ask"` = waiting | V (source) | +| OpenCode | plugin `event`: `session.idle`, `session.status {type:"idle"}`; `session.created` / `session.deleted`; SSE `GET /event` | `permission.asked` (until `permission.replied`), `question.asked`; sync `permission.ask` hook | `OPENCODE_CONFIG_CONTENT='{"plugin":["file:///abs/probe.ts"]}'` — live-verified; project `.opencode/plugins/*.ts` — live-verified; `OPENCODE_CONFIG=/file.json`; `--pure` disables | in-process `{type, properties:{sessionID,…}}`; `message.updated` `finish:"stop"` | only `tui.attention` (default disabled): OSC 99/777 when blurred | `~/.local/share/opencode/opencode.db` message `time.completed` / `finish:"stop"` | V | +| Copilot CLI | hooks `agentStop` (`stopReason:"end_turn"`); `sessionStart` / `sessionEnd` | hooks `notification` `notification_type` `permission_prompt`, `elicitation_dialog`; `permissionRequest` | `--plugin-dir ` (`plugin.json` + `hooks.json`) — live-verified; `COPILOT_PLUGIN_DIR_ONLY=1`; repo `.github/hooks/*.json` (trust-gated); `COPILOT_HOME` = full relocation | stdin JSON: `sessionId`, `cwd`, `transcriptPath`, `stopReason`; no last message | `terminalProgress` OSC 9;4 (default on); `beep` BEL (off); `notifications` toast (off) | `~/.copilot/session-state//events.jsonl`: `assistant.turn_end`, `session.shutdown` | V | +| Kimi CLI | `[[hooks]]` `Stop` (`StopFailure`); `SessionStart` / `SessionEnd` | none in hooks; `wire.jsonl` `ApprovalRequest` + BEL | `--config-file FILE` / `--config ''` REPLACE the whole config (must include providers) — live-verified with a full copy; `KIMI_SHARE_DIR` = full relocation | stdin JSON: `hook_event_name`, `session_id`, `cwd`, `stop_hook_active`; no transcript / last message | BEL on approval/question only | `~/.kimi/sessions///wire.jsonl`: `TurnEnd`; `ApprovalRequest` = blocked | V | +| Factory Droid | hooks `Stop`; `SessionStart` / `SessionEnd` | hooks `Notification` `permission_prompt`, `idle_prompt`, `elicitation_dialog` | `--settings /abs/file.json` (merged for this process only), also on `droid exec` — live-verified | stdin JSON: `session_id`, `transcript_path`, `cwd`, `permission_mode`; no last message | OSC 9;4 only when `TERM_PROGRAM` contains ghostty; `completionSound` / `awaitingInputSound` (`bell` = BEL) | `~/.factory/sessions//.jsonl` (no explicit marker) | V | +| Amp | plugin `agent.end` (`status` done/error/cancelled, `messages[]`); `session.start` | no event; in-process `ctx.thread.state` idle/running/awaiting-approval/error | project `.amp/plugins/*.ts` or `~/.config/amp/plugins/` only (no flag/env) — project load live-verified; `--settings-file` REPLACES user settings | in-process: `thread.id`, `messages`; no cwd field | `amp.notifications.enabled` (default on): local sounds; BEL only over SSH/`AMP_FORCE_BEL`; OSC 777 when unfocused | server-side threads; local files are stubs → unreliable | V + D | +| Qoder CLI | hooks `Stop` (`last_assistant_message`), `StopFailure`; `SessionStart` / `SessionEnd` | hooks `PermissionRequest`, `PermissionDenied`, `Notification` `permission_prompt` (not focus-gated), `idle_prompt` (focus-gated), `elicitation_dialog`, `Elicitation` | `--settings ''` or `--settings file.json` (highest priority, not trust-gated) — live-verified (both forms); `--config-dir` / `QODER_CONFIG_DIR`; `--setting-sources` may drop flag hooks | stdin JSON: `session_id`, `transcript_path`, `cwd`, `permission_mode`, `agent_id` | `general.enableNotifications` (default false): OSC 9 else BEL, unfocused only; no 9;4 | `~/.qoder/projects//.jsonl`: assistant `stop_reason:"end_turn"` | V | +| Qwen Code | hooks `Stop` (`last_assistant_message`), `StopFailure`; `SessionStart` / `SessionEnd` | hooks `PermissionRequest`, `PermissionDenied`, `Notification` `permission_prompt`, `idle_prompt` (not focus-gated) | no flag; project `.qwen/settings.json` — live-verified; `QWEN_CODE_SYSTEM_SETTINGS_PATH` exists but did NOT fire hooks in test; `QWEN_HOME` = full relocation | stdin JSON: `session_id`, `transcript_path`, `cwd`, `timestamp`, `permission_mode`, `model` | `general.terminalBell` (default true): OSC 9/99/777/BEL, unfocused only; completion only after ≥20 s turns | `~/.qwen/projects//chats/.jsonl` + `.runtime.json` (pid); no turn marker in 0.21.3 | V | +| Grok Build | hooks `Stop` (`reason:"end_turn"`, `lastAssistantMessage`); `[[ui.notifications.hooks]]` on `turn_complete`; `SessionStart` / `SessionEnd` | hooks `Notification` (`permission_prompt`, `idle_prompt`), `PermissionDenied`; `[[ui.notifications.hooks]]` `approval_required` | NO flag/env on the TUI (`GROK_HOME` relocates incl. auth); `~/.grok/hooks/*.json`, trusted `/.grok/hooks/*.json`, Claude/Cursor-compat files | stdin JSON camelCase: `sessionId`, `cwd`, `lastAssistantMessage`; env `GROK_SESSION_ID`, `GROK_EVENT`, `GROK_MESSAGE` | default ON but focus-gated: OSC 777/9/99/BEL; OSC 9;4 `progress_bar=true` | `~/.grok/sessions///updates.jsonl` `turn_completed`; `events.jsonl` `permission_requested`, `turn_ended` | D + binary | +| Pi | extension `agent_end` → `agent_settled` (idle); `session_start` / `session_shutdown` | none (no permission system) | `pi -e /abs/ext.ts` — live-verified; `PI_CODING_AGENT_DIR` = full relocation | in-process: `ctx.sessionManager.getSessionFile()`, `ctx.cwd`, `agent_end.messages` | none by default; OSC 9;4 if `terminal.showTerminalProgress`; OSC 133 | `~/.pi/agent/sessions/----/_.jsonl`: assistant `stopReason:"stop"` | V | +| Oh My Pi | extension `session_stop` → `agent_end`; `session_start` / `session_shutdown` | `tool_approval_requested` / `_resolved` (only with an approval handler; default `approvalMode: yolo`); `ask` tool → built-in notification only | `omp --hook /abs/ext.ts` (or `-e`) — live-verified; `--config overlay.yml` (repeatable); `--profile` isolates | in-process: session file, `ctx.cwd`, `agent_end.messages` | default ON: OSC 9/99/BEL; `PI_NOTIFICATIONS=off`; OSC 9;4 if `terminal.showProgress` | `~/.omp/agent/sessions//_.jsonl`: assistant `stopReason:"stop"`; `custom/session_exit` | V | + +## 2. Per-runtime notes (abridged; sources) + +- **Claude Code** — https://code.claude.com/docs/en/hooks , https://code.claude.com/docs/en/terminal-config. + `Stop` fires when the main agent finishes (not on interrupt). Live: `claude -p … --settings + '{"hooks":{"Stop":[{"hooks":[{"type":"command","command":"/abs/capture.sh"}]}],…}}'` + fired SessionStart → Stop → SessionEnd; payload includes `transcript_path`, + `last_assistant_message`, `permission_mode`. Env: `CLAUDE_PROJECT_DIR`, + `CLAUDE_CODE_SESSION_ID`, `CLAUDE_PID`. Caveat (docs): interactive sessions hold all hooks + until the workspace-trust dialog is accepted. +- **Codex** — https://learn.chatgpt.com/docs/hooks.md , + https://learn.chatgpt.com/docs/config-file/config-advanced.md#notifications. Live: + `codex exec -c 'notify=["/abs/capture.sh","tag"]'` fired `agent-turn-complete` (payload as + last argv incl. `last-assistant-message`); `-c hooks.*` fired only with + `--dangerously-bypass-hook-trust`. An internal "memories" sub-session (cwd + `~/.codex/memories`, `transcript_path:null`) also fires SessionStart/End — filter on cwd. +- **Gemini CLI** — https://geminicli.com/docs/hooks/reference/ , + https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/notifications.md. Hooks + merge across user/project/system layers; project hooks are fingerprinted/trust-warned. +- **Cursor Agent** — https://cursor.com/docs/agent/hooks. `stop` only fires when a + user/project `hooks.json` defines it; plugin hooks run only in builds ≥ 2026-08-11. +- **Cline** — https://docs.cline.bot/customization/hooks.md. The Claude-style hook list in + the binary is a bundled Claude settings schema, not Cline hooks. +- **OpenCode** — https://opencode.ai/docs/plugins , https://opencode.ai/docs/server. Live: + `session.status {busy}` → … → `session.status {idle}` + `session.idle` (fires even after a + model error). `opencode serve` exposes SSE `GET /event`. +- **Copilot CLI** — https://docs.github.com/en/copilot/reference/hooks-reference. Live: + `copilot -p … --plugin-dir ` (`plugin.json` + `hooks.json`) fired + sessionStart / agentStop / sessionEnd; `--session-id` makes the transcript path + deterministic. +- **Kimi CLI** — https://moonshotai.github.io/kimi-cli/en/customization/hooks.html. + `--config-file` replaces the whole config (no merge): a Prowl-generated copy must + re-supply the user's providers. +- **Factory Droid** — https://docs.factory.ai/reference/hooks-reference. Live: + `droid exec --settings /abs/settings.json` fired SessionStart/Stop/SessionEnd (Stop even + though the exec failed). +- **Amp** — https://ampcode.com/manual/plugin-api. Plugins only from project + `.amp/plugins/` or `~/.config/amp/plugins/`; `ctx.thread.state` exposes + `awaiting-approval` in-process. +- **Qoder CLI** — https://docs.qoder.com/cli/hooks. Live: `--settings` inline JSON and + file both fired SessionStart/End (quota error before Stop); flag hooks are not trust-gated. +- **Qwen Code** — https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/. Live: + project `.qwen/settings.json` fired SessionStart; `QWEN_CODE_SYSTEM_SETTINGS_PATH` did not. +- **Grok Build** — https://docs.x.ai/build/features/hooks , + https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/10-hooks.md. + No per-launch flag on the TUI; `--plugin-dir` only on `grok agent` (ACP). +- **Pi** — https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md. + Live: `pi -e /abs/probe.ts -p …` → session_start → agent_start → turn_start → turn_end → + agent_end → agent_settled → session_shutdown. +- **Oh My Pi** — https://github.com/can1357/oh-my-pi/blob/master/docs/extensions.md. Live: + `omp --hook /abs/probe.ts -p …` → session_start → … → session_stop → agent_end → + session_shutdown. Session dirs use hashed names in 17.2.5–17.2.8. + +## 3. What is not achievable (as of the baseline) + +**No per-launch injection without touching global config or a file inside the user's +project:** Grok Build (TUI), Cursor Agent, Amp, Cline (`--hooks-dir` unverified), Gemini +and Qwen (project settings only; system-settings env untested / did not fire), Kimi (only +by replacing the whole config), Codex hooks beyond `notify` (trust bypass flag required). + +**No native "blocked / waiting for input" channel:** Cursor Agent, Cline, Kimi (hooks), +Amp (event), Pi (no permission system), Codex idle between turns, Gemini beyond +`ToolPermission`, OMP `ask`. + +**Last assistant message in the payload** only for Claude Code, Codex, Grok, Gemini +(`prompt_response`), Cursor (`afterAgentResponse.text`), Cline (`turn.outputText`), +Amp/Pi/OMP/OpenCode (in-process messages), Qoder/Qwen (docs). Not in Copilot `agentStop`, +Droid `Stop`, Kimi `Stop`. + +**Terminal escapes are never a deterministic channel:** every runtime gates its OSC +9/99/777/BEL on focus, idle thresholds, or opt-in settings; OSC 9;4 progress (where +emitted) separates working from not-working but not blocked from idle. + +**No usable local transcript with a turn marker:** Amp (server-side). Weak/no explicit +marker: Gemini, Cursor, Droid, Qwen 0.21.3, Cline, Pi/OMP (infer from assistant +`stopReason`). + +## 4. Implications for Prowl (feeds 064 §Design / S3) + +| Tier | Runtimes | Channel Prowl can attach at launch | +| --- | --- | --- | +| **A — flag/env per launch, no user config touched** | Claude Code (`--settings`), Codex (`-c notify=[…]`, turn-complete only), Copilot CLI (`--plugin-dir`), Factory Droid (`--settings`), Qoder CLI (`--settings`), Pi (`-e`), Oh My Pi (`--hook`), OpenCode (`OPENCODE_CONFIG_CONTENT`) | `signalHooks = .launchFlag` — first S3 wave | +| **B — only via a Prowl-owned home** | Gemini (`GEMINI_CLI_HOME`), Qwen (`QWEN_HOME`), Grok (`GROK_HOME`), Cline (`CLINE_DIR`), Kimi (`KIMI_SHARE_DIR`; or full-config replacement), plus Claude/Codex/Copilot for completeness | `signalHooks = .configDirOnly` — available only for profiles that bind a dedicated home (053); Prowl writes the hook file into the provisioned home | +| **C — project files only** | Cursor Agent (`/.cursor/hooks.json`), Amp (`.amp/plugins/`) | not attached (Prowl does not write into the user's project); layers 2–3 only | + +Blocked/permission coverage via hooks: Claude, Codex (trust-gated), Gemini (tool +permission), Copilot, Droid, Qoder, Qwen, Grok, OpenCode (plugin), OMP (only with an +approval handler). Runtimes where blocked detection stays heuristic/transcript-only: Cursor, +Cline, Kimi (transcript `ApprovalRequest`), Amp, Pi. + +Payloads with `last_assistant_message` (Claude, Codex, Qoder, Qwen, Grok, Gemini) let 063's +V2 observe mode capture a result without transcript parsing for those runtimes.