diff --git a/docs-ai/063-agent-workflows/000-plan.md b/docs-ai/063-agent-workflows/000-plan.md index bc699940..4e8b581b 100644 --- a/docs-ai/063-agent-workflows/000-plan.md +++ b/docs-ai/063-agent-workflows/000-plan.md @@ -2,7 +2,7 @@ | | | | --- | --- | -| **Status** | In progress — R1 foundations C0/A1/A1b/A2 and 064-S1 merged; 064-S2 is next | +| **Status** | In progress — R1 foundations C0/A1/A1b/A2 and 064-S1/S2 merged; 064-S3 wave 1 is next | | **Anchor date** | 2026-08-21 | | **Primary PRs** | R1 foundations: #709 (C0), #710 (A1), #713 (A1b), #714 (A2); B1–D3 TBD | | **Related** | [047 cross-agent-handoff](../047-cross-agent-handoff/000-plan.md), [049 agents-toolbar-entry](../049-agents-toolbar-entry/000-plan.md), [053 agent-profiles](../053-agent-profiles/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), [061 native-toolbar-controls](../061-native-toolbar-controls/toolbar-controls.md), [064 agent-completion-signals](../064-agent-completion-signals/000-plan.md) (signal bus, `agents signal` / `agents wait`), [#699 `prowl create pane`](https://github.com/onevcat/Prowl/issues/699), [PR #651 (direction reference, not merged)](https://github.com/onevcat/Prowl/pull/651), [DSL spec (living)](dsl-spec.md), [release plan (living)](release-plan.md), `docs/components/handoff.md`, `docs/components/agent-profiles.md`, `docs/components/cli.md` | @@ -593,6 +593,10 @@ attaches hooks through A2's launch boundary. ## Amendments +- Updated 2026-08-23: 064-S2 merged in #718 after full validation and authenticated + Claude/Codex dispatch E2E. The next R1 orchestration critical-path slice is 064-S3 wave 1; + 065-S0/K1 remains independent parallel work — see [release-plan.md](release-plan.md) and + [064.005](../064-agent-completion-signals/005-s2-action.md). - Updated 2026-08-23: 064-S1 merged in #715 and the owner locked S2's paired dispatch and evidence-wait contract, leaving S2 as the next R1 critical-path PR — see [064.003](../064-agent-completion-signals/003-s2-dispatch-wait-design.md). diff --git a/docs-ai/063-agent-workflows/release-plan.md b/docs-ai/063-agent-workflows/release-plan.md index 32beeb06..9a41d9c1 100644 --- a/docs-ai/063-agent-workflows/release-plan.md +++ b/docs-ai/063-agent-workflows/release-plan.md @@ -33,14 +33,13 @@ user-facing surface may merge before "their" release and stay dormant. Three rel | A1b | Merged | #713 | | A2 | Merged | #714 | | S1 | Merged | #715: bus, multicast observer, `agents signal` | -| S2 | Draft PR #718 | Paired dispatch receipt, strict ID wait, generic evidence wait; [action record](../064-agent-completion-signals/005-s2-action.md) | -| S3 wave 1 | Planned | Follows S2: tier-A launch hooks consume S2 wait/channel infrastructure | +| S2 | Merged | #718: paired dispatch receipt, strict ID wait, generic evidence wait; [action record](../064-agent-completion-signals/005-s2-action.md) | +| S3 wave 1 | Planned, next | Tier-A launch hooks consume S2 wait/channel infrastructure | | 065-S0/K1 | Planned, parallel | Skill-target spike + bundled-skill registry | | 065-K2/K3 | Planned | Follow S0/K1 inside R1 | -A2 completes 063's R1 implementation work, S1 is on `main`, and S2 is in draft review. -The next orchestration critical-path slice after merge is S3 wave 1; 065-S0/K1 may proceed -independently in parallel. +A2 completes 063's R1 implementation work, and S1/S2 are on `main`. The next orchestration +critical-path slice is S3 wave 1; 065-S0/K1 may proceed independently in parallel. ### R1 — CLI orchestration primitives + completion signals @@ -108,6 +107,9 @@ R3+: V2 / S5 rest; delete HANDOFF_RETIRED stubs ## Change log +- 2026-08-23 — S2 merged in #718 after full gates, authenticated Claude/Codex dispatch E2E, + and two adversarial review rounds. The next R1 orchestration critical-path slice is S3 + wave 1; 065-S0/K1 remains independent parallel work. - 2026-08-23 — S2 review corrected the explicit critical path to A2 + S1 → S2 → S3 wave 1; S3 consumes the wait/channel/self-check infrastructure delivered by S2 rather than branching directly from its two transitive prerequisites. diff --git a/docs-ai/064-agent-completion-signals/000-plan.md b/docs-ai/064-agent-completion-signals/000-plan.md index 53b5df36..91c85823 100644 --- a/docs-ai/064-agent-completion-signals/000-plan.md +++ b/docs-ai/064-agent-completion-signals/000-plan.md @@ -2,9 +2,9 @@ | | | | --- | --- | -| **Status** | In progress — S1 merged in #715; S2 implemented in draft PR #718 | +| **Status** | In progress — S1 merged in #715; S2 merged in #718; S3 wave 1 is next | | **Anchor date** | 2026-08-22 | -| **Primary PRs** | #715 (S1); #718 (S2, draft) | +| **Primary PRs** | #715 (S1); #718 (S2); S3 wave 1 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` | ## Background @@ -242,6 +242,9 @@ opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusi ## Amendments +- Updated 2026-08-23 after S2 merged in #718: the paired dispatch and agent-wait slice is on + `main`; S3 wave 1 is now the next R1 orchestration critical-path slice. The independent + 065-S0/K1 bundled-skills work may continue in parallel. - Updated 2026-08-23 during S2 review: corrected explicit slice dependencies to 063-A2 + S1 → S2 → S3 wave 1. S3 consumes S2's wait/channel/self-check infrastructure; A2 and S1 are transitive rather than parallel alternatives. diff --git a/docs-ai/064-agent-completion-signals/005-s2-action.md b/docs-ai/064-agent-completion-signals/005-s2-action.md index 25998bee..e32091a1 100644 --- a/docs-ai/064-agent-completion-signals/005-s2-action.md +++ b/docs-ai/064-agent-completion-signals/005-s2-action.md @@ -2,7 +2,7 @@ ## Status -Implemented and fully validated on `feat/agent-dispatch-wait-s2`; draft PR [#718](https://github.com/onevcat/Prowl/pull/718). +Complete and merged in PR [#718](https://github.com/onevcat/Prowl/pull/718). ## Delivered behavior -- 2.51.2 From 5d21aae018dd94c5d4c4d7759cf91f719a3defe9 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 23 Aug 2026 22:45:46 +0900 Subject: [PATCH 2/6] docs(ai): remove completion hook wave two --- docs-ai/063-agent-workflows/000-plan.md | 9 +++++++-- docs-ai/063-agent-workflows/release-plan.md | 10 +++++++--- .../064-agent-completion-signals/000-plan.md | 20 ++++++++++++------- .../research-agent-completion-signals.md | 15 ++++++++------ 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/docs-ai/063-agent-workflows/000-plan.md b/docs-ai/063-agent-workflows/000-plan.md index 4e8b581b..7f5236a1 100644 --- a/docs-ai/063-agent-workflows/000-plan.md +++ b/docs-ai/063-agent-workflows/000-plan.md @@ -511,8 +511,10 @@ attaches hooks through A2's launch boundary. - **PR order / releases** (revised 2026-08-22): three releases — R1 = C0, A1, A2, 064-S1/S2/S3-wave-1, 065-S0/K1/K2/K3 (CLI orchestration + signals + skill distribution); R2 = B1, B2, B3, C1, C2, D1, D2 - (Agent Workflows); R3 = D3, 064-S3-wave-2/S4, first V2 items (handoff migration). The - single source for order and release assignment is [release-plan.md](release-plan.md); + (Agent Workflows); R3 = D3, 064-S4, first V2 items (handoff migration). S3 has no wave 2: + runtimes that require global-config, dedicated-home, or project-file writes do not receive + Prowl-managed hooks. The single source for order and release assignment is + [release-plan.md](release-plan.md); the slice tables in 063/064 define contents only. The new Adversarial Review flow validates the engine before the shipped handoff is migrated; the `ObservedAgentState` observer moved from B3 to 064-S1 so R1 can ship `agents wait`. @@ -593,6 +595,9 @@ attaches hooks through A2's launch boundary. ## Amendments +- Updated 2026-08-23: removed 064-S3 wave 2 from R3. Prowl does not install hooks for + runtimes that require writes to global configuration, dedicated homes, or project files; + those runtimes continue to use cooperative, transcript/process, or heuristic evidence. - Updated 2026-08-23: 064-S2 merged in #718 after full validation and authenticated Claude/Codex dispatch E2E. The next R1 orchestration critical-path slice is 064-S3 wave 1; 065-S0/K1 remains independent parallel work — see [release-plan.md](release-plan.md) and diff --git a/docs-ai/063-agent-workflows/release-plan.md b/docs-ai/063-agent-workflows/release-plan.md index 9a41d9c1..8e6f25ab 100644 --- a/docs-ai/063-agent-workflows/release-plan.md +++ b/docs-ai/063-agent-workflows/release-plan.md @@ -82,10 +82,11 @@ R2b = C2–D2 (GUI entry, Settings, skills, E2E). Default is one R2. Docs: `work | Order | Slice | Entry | Depends | Outcome | | --- | --- | --- | --- | --- | | 1 | **D3** `prowl.handoff` + `prowl.handoff-checkpoint` built-ins, `HANDOFF_RETIRED` stubs, removal of `HandoffHudFeature` / `HandoffCommandHandler` / `HandoffRequestRegistry`, `docs/components/handoff.md` rewrite | 063 | D2 | handoff is a workflow | -| 1 | **S3 wave 2** tier-B runtimes via dedicated-home profiles (Gemini, Qwen, Grok, Cline, Kimi) | 064 | S3 wave 1, 053 homes | more runtimes exact | | 1 | **S4** transcript file-watch + OSC producers | 064 | S1 | layer-2 signals without hooks | -The `HANDOFF_RETIRED` stubs are deleted one release after R3. +There is no S3 wave 2. Runtimes that require writes to a global config, dedicated home, or +project file do not receive Prowl-managed hooks. The `HANDOFF_RETIRED` stubs are deleted one +release after R3. ### R3+ — V2 @@ -101,12 +102,15 @@ R1: C0 A1 ──► A1b S1 ──┘ 065-S0/K1 ──► 065-K2 ──► 065-K3 R2: B1 ──► B2 ──► B3 (◄ A2, S1) ──► C1 ──► C2 ──► D1 (◄ 065-K1) ──► D2 (◄ S3w1) -R3: D3 (◄ D2) S3w2 (◄ S3w1) S4 (◄ S1) +R3: D3 (◄ D2) S4 (◄ S1) R3+: V2 / S5 rest; delete HANDOFF_RETIRED stubs ``` ## Change log +- 2026-08-23 — S3 wave 2 was removed. Prowl ships launch-scoped hooks only for tier-A + runtimes that need no global-config, dedicated-home, or project-file writes; Gemini, + Qwen, Grok, Cline, Kimi, Cursor, and Amp remain on non-hook evidence layers. - 2026-08-23 — S2 merged in #718 after full gates, authenticated Claude/Codex dispatch E2E, and two adversarial review rounds. The next R1 orchestration critical-path slice is S3 wave 1; 065-S0/K1 remains independent parallel work. diff --git a/docs-ai/064-agent-completion-signals/000-plan.md b/docs-ai/064-agent-completion-signals/000-plan.md index 91c85823..8b6fe802 100644 --- a/docs-ai/064-agent-completion-signals/000-plan.md +++ b/docs-ai/064-agent-completion-signals/000-plan.md @@ -171,14 +171,15 @@ permission or question dialog), tells the agent to use `--include-screen` and This section defines **what** each slice contains; **when** it ships, and how it interleaves with 063's slices, is owned by the shared living [release-plan.md](../063-agent-workflows/release-plan.md) (R1: S1, S2, S3 wave 1 with -063's C0/A1/A2; R2: the S5 watchdog part; R3: S3 wave 2 and S4). +063's C0/A1/A2; R2: the S5 watchdog part; R3: S4). S3 ends after wave 1: runtimes that +require global-config, dedicated-home, or project-file writes do not receive Prowl-managed +hooks. | Slice | Depends | Contents / expectation | | --- | --- | --- | | **S1** | — | Signal bus state + the `ObservedAgentState` multicast observer (snapshot / changed / removed / surfaceClosed / `.signal`; first specified in 063, delivered here so it ships first) + `prowl agents signal` for `turn-ended`, `needs-input`, session, and progress events (CLI four layers, bounded detail). Layer 0 works for every runtime immediately; 063-B3 later consumes the same observer. | | **S2** | 063-A2, S1 | One atomic paired-dispatch path: every CLI `create tab|pane --profile --prompt` appends the completion protocol and returns `dispatch_id`; cooperative `dispatch-complete --outcome succeeded|failed --summary`; 256-entry non-destructive in-memory receipts; ID-only strict `prowl agents wait --dispatch`; generic `wait --until` with automatic overflow resnapshot and honest heuristic fallback; `agents` current evidence field; `--include-screen`; skill rubric. Route B becomes usable without polling or stale completion. | -| **S3 wave 1** | S2, research matrix | Launch-scoped hook injection (adapter `signalHooks`, self-check) for tier A of the research matrix (flag/env per launch, live-verified): Claude Code `--settings`, Codex `-c notify=[…]` (native `agent-turn-complete` maps to `turn-ended`; hook trust bypass is never passed), Copilot `--plugin-dir`, Droid `--settings`, Qoder `--settings`, Pi `-e`, OMP `--hook`, OpenCode `OPENCODE_CONFIG_CONTENT`. `agents wait` becomes deterministic for Prowl-launched agents on these runtimes. | -| **S3 wave 2** | S3 wave 1, 053 dedicated homes | Tier B (`configDirOnly`: Gemini, Qwen, Grok, Cline, Kimi) for dedicated-home profiles only; tier C (Cursor, Amp: project files) is not attached. | +| **S3 wave 1** | S2, research matrix | Launch-scoped hook injection (adapter `signalHooks`, self-check) for tier A of the research matrix (flag/env per launch, live-verified): Claude Code `--settings`, Codex `-c notify=[…]` (native `agent-turn-complete` maps to `turn-ended`; hook trust bypass is never passed), Copilot `--plugin-dir`, Droid `--settings`, Qoder `--settings`, Pi `-e`, OMP `--hook`, OpenCode `OPENCODE_CONFIG_CONTENT`. `agents wait` becomes deterministic for Prowl-launched agents on these runtimes. This is the complete S3 hook scope. | | **S4** | S1 | Transcript file-watch and OSC producers — layer 2 without hooks. | | **S5** | 063 C1 (part), S3/S4 + 063 V2 (rest) | 063's watchdog consumes exact signals (nudge on `turn-ended` without `done`, immediate attention on `needs-input`) — ships with 063-D2; later: 063 V2 observe mode (`expect.status` + `agents read` / hook `last_assistant_message`) and `on_attention: ask `. Recorded in 063 amendments. | @@ -214,8 +215,9 @@ installed locally; live hook runs for claude, codex, copilot, kimi, droid, pi, o 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). + (tier A above) and form the complete S3 scope. Gemini, Qwen, Grok, Cline, and Kimi require + a Prowl-owned home; Cursor Agent and Amp require project files. Prowl does not attach hooks + for either group. - 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 the native `agent-turn-complete` event (mapped to `turn-ended`) through ungated `notify`; its permission prompts stay @@ -223,8 +225,8 @@ opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusi - 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. +- Kimi's `--config-file` replaces the whole config; per-launch hooks there would require + Prowl to re-supply the user's provider config, so Kimi does not receive managed hooks. - 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 @@ -242,6 +244,10 @@ opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusi ## Amendments +- Updated 2026-08-23: removed S3 wave 2. Managed hooks are limited to tier-A runtimes that + accept per-launch flag/environment injection without configuration writes. Gemini, Qwen, + Grok, Cline, Kimi, Cursor, and Amp remain on cooperative, transcript/process, OSC, or + heuristic evidence; dedicated homes and project files are not hook-installation surfaces. - Updated 2026-08-23 after S2 merged in #718: the paired dispatch and agent-wait slice is on `main`; S3 wave 1 is now the next R1 orchestration critical-path slice. The independent 065-S0/K1 bundled-skills work may continue in parallel. 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 index ab6e9f88..83540b4d 100644 --- a/docs-ai/064-agent-completion-signals/research-agent-completion-signals.md +++ b/docs-ai/064-agent-completion-signals/research-agent-completion-signals.md @@ -123,13 +123,16 @@ marker: Gemini, Cursor, Droid, Qwen 0.21.3, Cline, Pi/OMP (infer from assistant | 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 | +| **B — requires 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) | unsupported for managed hooks; Prowl does not write hook configuration into dedicated homes | +| **C — project files only** | Cursor Agent (`/.cursor/hooks.json`), Amp (`.amp/plugins/`) | unsupported for managed hooks; Prowl does not write into the user's project | -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. +S3 has no second wave. Only tier A receives Prowl-managed hooks; tiers B and C remain on +cooperative, transcript/process, OSC, or heuristic evidence. + +Within supported tier A, managed blocked/permission coverage is available for Claude, +Copilot, Droid, Qoder, and OpenCode; OMP requires an approval handler. Codex's permission +hooks are trust-gated and therefore omitted, while Pi has no permission system. Native hook +capabilities in unsupported tiers remain research facts only and are not Prowl-managed. 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. -- 2.51.2 From e6aa4a2e504e4e407e937f99f57ac7e5f8cd2f95 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 23 Aug 2026 23:02:51 +0900 Subject: [PATCH 3/6] docs(ai): plan S3 hook delivery --- docs-ai/063-agent-workflows/release-plan.md | 16 +- .../064-agent-completion-signals/000-plan.md | 10 +- .../006-s3-wave1-plan.md | 379 ++++++++++++++++++ 3 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md diff --git a/docs-ai/063-agent-workflows/release-plan.md b/docs-ai/063-agent-workflows/release-plan.md index 8e6f25ab..25f18825 100644 --- a/docs-ai/063-agent-workflows/release-plan.md +++ b/docs-ai/063-agent-workflows/release-plan.md @@ -34,13 +34,27 @@ user-facing surface may merge before "their" release and stay dormant. Three rel | A2 | Merged | #714 | | S1 | Merged | #715: bus, multicast observer, `agents signal` | | S2 | Merged | #718: paired dispatch receipt, strict ID wait, generic evidence wait; [action record](../064-agent-completion-signals/005-s2-action.md) | -| S3 wave 1 | Planned, next | Tier-A launch hooks consume S2 wait/channel infrastructure | +| S3 wave 1 | Planning, next | Three merge-safe PRs (S3a–S3c); tier-A launch hooks consume S2 wait/channel infrastructure | | 065-S0/K1 | Planned, parallel | Skill-target spike + bundled-skill registry | | 065-K2/K3 | Planned | Follow S0/K1 inside R1 | A2 completes 063's R1 implementation work, and S1/S2 are on `main`. The next orchestration critical-path slice is S3 wave 1; 065-S0/K1 may proceed independently in parallel. +#### S3 wave 1 PR breakdown + +S3 wave 1 remains one R1 release slice but lands as three sequential, independently +reviewable PRs. The slice is complete only after S3c: + +| PR | Runtime scope | Foundation / closure scope | Depends | +| --- | --- | --- | --- | +| **S3a** | Claude Code, Codex | Trusted launch-channel registration, native-hook ingress, payload normalization, self-check/channel lifecycle, bundled hook-resource boundary | S2 | +| **S3b** | Copilot, Droid, Qoder | Plugin/settings adapters and fixtures on the S3a foundation | S3a | +| **S3c** | Pi, OMP, OpenCode | Extension/plugin adapters, Active Agents exact-channel badge, complete docs and tier-A live verification | S3b | + +The detailed implementation and verification plan starts in +[064.006](../064-agent-completion-signals/006-s3-wave1-plan.md). + ### R1 — CLI orchestration primitives + completion signals | Order | Slice | Entry | Depends | Outcome | diff --git a/docs-ai/064-agent-completion-signals/000-plan.md b/docs-ai/064-agent-completion-signals/000-plan.md index 8b6fe802..4cded7f9 100644 --- a/docs-ai/064-agent-completion-signals/000-plan.md +++ b/docs-ai/064-agent-completion-signals/000-plan.md @@ -2,9 +2,9 @@ | | | | --- | --- | -| **Status** | In progress — S1 merged in #715; S2 merged in #718; S3 wave 1 is next | +| **Status** | In progress — S1 merged in #715; S2 merged in #718; S3 wave 1 planning is active | | **Anchor date** | 2026-08-22 | -| **Primary PRs** | #715 (S1); #718 (S2); S3 wave 1 TBD | +| **Primary PRs** | #715 (S1); #718 (S2); S3a–S3c 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` | ## Background @@ -179,7 +179,7 @@ hooks. | --- | --- | --- | | **S1** | — | Signal bus state + the `ObservedAgentState` multicast observer (snapshot / changed / removed / surfaceClosed / `.signal`; first specified in 063, delivered here so it ships first) + `prowl agents signal` for `turn-ended`, `needs-input`, session, and progress events (CLI four layers, bounded detail). Layer 0 works for every runtime immediately; 063-B3 later consumes the same observer. | | **S2** | 063-A2, S1 | One atomic paired-dispatch path: every CLI `create tab|pane --profile --prompt` appends the completion protocol and returns `dispatch_id`; cooperative `dispatch-complete --outcome succeeded|failed --summary`; 256-entry non-destructive in-memory receipts; ID-only strict `prowl agents wait --dispatch`; generic `wait --until` with automatic overflow resnapshot and honest heuristic fallback; `agents` current evidence field; `--include-screen`; skill rubric. Route B becomes usable without polling or stale completion. | -| **S3 wave 1** | S2, research matrix | Launch-scoped hook injection (adapter `signalHooks`, self-check) for tier A of the research matrix (flag/env per launch, live-verified): Claude Code `--settings`, Codex `-c notify=[…]` (native `agent-turn-complete` maps to `turn-ended`; hook trust bypass is never passed), Copilot `--plugin-dir`, Droid `--settings`, Qoder `--settings`, Pi `-e`, OMP `--hook`, OpenCode `OPENCODE_CONFIG_CONTENT`. `agents wait` becomes deterministic for Prowl-launched agents on these runtimes. This is the complete S3 hook scope. | +| **S3 wave 1** | S2, research matrix | Launch-scoped hook injection (adapter `signalHooks`, self-check) for tier A of the research matrix (flag/env per launch, live-verified): Claude Code `--settings`, Codex `-c notify=[…]` (native `agent-turn-complete` maps to `turn-ended`; hook trust bypass is never passed), Copilot `--plugin-dir`, Droid `--settings`, Qoder `--settings`, Pi `-e`, OMP `--hook`, OpenCode `OPENCODE_CONFIG_CONTENT`. `agents wait` becomes deterministic for Prowl-launched agents on these runtimes. This is the complete S3 hook scope, delivered as S3a–S3c in [006-s3-wave1-plan.md](006-s3-wave1-plan.md). | | **S4** | S1 | Transcript file-watch and OSC producers — layer 2 without hooks. | | **S5** | 063 C1 (part), S3/S4 + 063 V2 (rest) | 063's watchdog consumes exact signals (nudge on `turn-ended` without `done`, immediate attention on `needs-input`) — ships with 063-D2; later: 063 V2 observe mode (`expect.status` + `agents read` / hook `last_assistant_message`) and `on_attention: ask `. Recorded in 063 amendments. | @@ -244,6 +244,10 @@ opencode; partial for qodercli/qwen/amp; docs/bundle for the rest). Key conclusi ## Amendments +- Updated 2026-08-23: split S3 wave 1 into three merge-safe PRs: S3a foundation plus + Claude/Codex, S3b Copilot/Droid/Qoder, and S3c Pi/OMP/OpenCode plus UI/docs/full closure. + Detailed S3a research, implementation phases, and validation live in + [006-s3-wave1-plan.md](006-s3-wave1-plan.md). - Updated 2026-08-23: removed S3 wave 2. Managed hooks are limited to tier-A runtimes that accept per-launch flag/environment injection without configuration writes. Gemini, Qwen, Grok, Cline, Kimi, Cursor, and Amp remain on cooperative, transcript/process, OSC, or diff --git a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md new file mode 100644 index 00000000..0aafba3d --- /dev/null +++ b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md @@ -0,0 +1,379 @@ +# 064.006 — S3 Wave 1 PR Plan + +## Status + +Planning and owner alignment. No implementation has started. + +- Branch: `feat/agent-signal-hooks-s3a` +- Prerequisites: 063-A2, 064-S1, and 064-S2 are merged. +- Runtime baseline rechecked 2026-08-23: Claude Code 2.1.241; Codex CLI 0.149.0. +- S3 has no wave 2. Managed hooks are limited to runtimes that accept process-scoped + flag/environment injection without global-config, dedicated-home, or project-file writes. + +## S3 wave 1 PR breakdown + +S3 wave 1 remains one R1 release slice, delivered as three sequential merge-safe PRs. The +slice is not complete until S3c passes its complete tier-A gate. + +| PR | Runtime scope | Foundation / closure scope | Depends | +| --- | --- | --- | --- | +| **S3a** | Claude Code, Codex | Trusted launch-channel registration, native-hook ingress and payload normalization, self-check/channel lifecycle, launch-epoch integration, bundled CLI/resource locator | S2 | +| **S3b** | Copilot, Droid, Qoder | Plugin/settings adapters and fixtures on the S3a foundation | S3a | +| **S3c** | Pi, OMP, OpenCode | Extension/plugin adapters, Active Agents exact-channel badge, complete docs and tier-A live verification | S3b | + +Every PR keeps `main` shippable. Partial runtime support may exist on `main` between these PRs, +but release documentation must not call S3 wave 1 complete before S3c. + +## S3a objective + +Make every Prowl Agent Profile launch of Claude Code or Codex automatically report supported +native runtime events into the existing S1/S2 signal bus, with honest `verified_live` +coverage and no global configuration writes. + +S3a normalizes only runtime facts: + +- Claude `SessionStart` -> `session-start`; +- Claude `Stop` and `StopFailure` -> `turn-ended`; +- Claude `PermissionRequest` and supported elicitation events -> `needs-input`; +- Claude `SessionEnd` -> `session-end`; +- Codex `agent-turn-complete` notify -> `turn-ended`. + +A hook `turn-ended` never completes a dispatch or workflow step. S2 receipt priority and the +300 ms terminal-evidence coalescing window remain authoritative: a matching +`dispatch-complete` receipt wins; a turn edge without a receipt is `DISPATCH_INCOMPLETE`. + +### Non-goals + +- No Copilot, Droid, Qoder, Pi, OMP, or OpenCode adapters (S3b/S3c). +- No Gemini, Qwen, Grok, Cline, Kimi, Cursor, or Amp managed hooks. +- No Active Agents exact badge (S3c). +- No transcript/OSC producers, workflow watchdog changes, result capture, or full + `last_assistant_message` storage. +- No public configured/degraded channel state, token persistence, cryptographic trust model, + global config editing, or project-file writes. +- No redesign of `agents wait`, dispatch receipts, or the signal bus. + +## Research findings and existing seams + +### Reusable foundations + +The repository already has the required downstream behavior: + +- `AgentSignal.Source.hook` in + `supacode/Domain/AgentDetection/AgentSignal.swift`; +- process generation, session freshness, evidence epochs, and per-source channels in + `supacode/Features/Terminal/BusinessLogic/AgentObservationStore.swift`; +- exact caller attribution through socket peer PID ancestry in + `supacode/CLIService/CLICommandContext.swift`; +- strict dispatch/evidence handling in + `supacode/Features/Terminal/BusinessLogic/AgentDispatchStore.swift`; +- `verified_live` wait gating in `supacode/CLIService/AgentWaitCommandHandler.swift`; +- child-only launch carriers and the shared A2 launch boundary in + `supacode/Domain/AgentProfile/AgentProfileLaunchPlan.swift` and + `supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift`; +- an app-bundled CLI at `Resources/prowl-cli/prowl` in Debug and Release builds. + +S3a adds producers and registration to these boundaries; it does not create a second signal +or launch path. + +### Blocking implementation constraints + +1. **One Profile launch epoch.** `bindAgentDispatch` currently calls + `beginDispatchEpoch` after the Profile surface has launched. S3 registration must begin at + the actual Profile launch boundary, so dispatch binding must adopt that epoch rather than + minting another one and clearing early hook evidence. +2. **Early native events.** Claude `SessionStart` can reach Prowl before periodic detection has + resolved the agent PID. A valid registered token and exact caller pane must be accepted as + pending evidence, then bound when the first launch generation appears within the existing + acquisition window. It must not be silently downgraded or lost. +3. **Prompt/config carriers.** `AgentProfileLaunchPlan.terminalInput` currently replaces only + the final prompt argv value. Claude settings JSON is a non-final argument and can exceed the + canonical PTY limit. Shell rendering must support typed argv-index -> environment-carrier + replacement without putting hook JSON or tokens into initial input/history. +4. **Every A2 path.** Registration belongs in `WorktreeTerminalManager` so typed CLI launches + and menu/palette compatibility launches share preparation, exact returned surface identity, + rollback, and cleanup. Manually typing `claude` or `codex` into a shell is not covered. +5. **Fail-open observation.** A native hook must emit no stdout, make no runtime decision, use + a bounded socket attempt, and exit successfully even when Prowl rejects or cannot receive + the event. Hook failure may remove an optimization; it must never block or alter the agent. +6. **Codex internal work.** Codex can emit notify events for internal memories work. The + decoder/registration must reject a cwd outside the effective launch directory, including + `~/.codex/memories`. + +## Proposed S3a design + +### 1. Adapter-owned capability and deterministic base plan + +Add a small `AgentSignalHookCapability`/launch-template model exposed by runtime adapters. +Claude and Codex render their own native flags before a positional prompt. The base +`AgentProfileLaunchPlan` remains deterministic and preview-safe: it contains a hook template, +covered events, absolute bundled CLI path, socket transport setup, and carrier descriptors, +but no random execution token. + +Production planner callers pass an injected resource descriptor instead of having the pure +planner read the filesystem. Settings preview uses the same descriptor and redacted carrier +rendering. + +Runtime-specific rendering: + +- Claude: exactly one effective `--settings ` source with command hooks. Existing normal + user/project/local settings continue to merge additively. If a Profile already supplies an + explicit `--settings`, merge it only when it is safely readable/parseable; otherwise retain + the user's launch and omit managed hooks with honest degradation. +- Codex: one structured TOML `-c notify=[...]` override containing the bundled CLI absolute + path and hidden native-hook arguments. Never pass `--dangerously-bypass-hook-trust`. + +### 2. Execution-scoped token and child-only transport + +Immediately before surface creation, `WorktreeTerminalManager` generates an opaque UUID +channel token and patches only the execution copy of the launch plan: + +- the surface receives opaque carrier values; +- `env(1)` copies the token and exact `PROWL_CLI_SOCKET` path into the launched agent process; +- `env -u` removes the carriers from the child environment; +- the pane shell never receives the public hook token variable, so a later manually launched + runtime does not inherit the channel. + +After exact tab/surface creation succeeds, the manager begins one Profile launch epoch and +registers `{token, surface, runtime, launch cwd, covered events, epoch}`. Surface creation +failure creates no registration. Target-resolution rollback, process replacement, and surface +close revoke it. + +The token is a correctness capability, not a hostile-process secret. It never appears in CLI +arguments, prompt text, output payloads, logs, or persisted Profile/run state. + +### 3. Hidden native-hook bridge over the existing signal command + +Add a hidden `prowl agents _hook ` leaf. It is bundled-internal, not a +user command or targetable API. + +- Claude mode reads bounded stdin JSON. +- Codex mode reads the final bounded argv JSON payload. +- Pure decoders validate the native event, extract a bounded session/thread id and small + event-specific detail, ignore unknown future fields, and never copy the full last assistant + message. +- The bridge reads the token from its inherited environment and sends an internal hook context + through the existing `agents.signal` envelope/socket route. +- Public `prowl agents signal` cannot provide hook context and remains + `source=cooperative_cli`. +- The app validates token, exact caller pane, configured runtime/native event, launch cwd, and + current/pending process generation before constructing `.hook(...)`. + +The hook bridge uses the app-bundled CLI path, not `PATH` or `/usr/local/bin/prowl`, suppresses +all output, does not auto-launch Prowl, and has bounded connect/read/write behavior. Delivery +failure exits zero so observation cannot interfere with the runtime. + +### 4. Channel verification and lifecycle + +Keep registration state inside `AgentObservationStore`; do not add a parallel store. +Internally a registration may be pending, verified, or degraded, but the existing public +channel model remains: + +- unverified/degraded: no `verified_live` channel is exposed, so `auto` wait may use honest + heuristic fallback; +- verified: channel state is `verified_live` with the adapter-declared covered events; +- process replacement/surface close: registration and verified coverage are removed; +- a same-process Claude session replacement rotates signal freshness while retaining the + launch channel, provided a valid new `SessionStart` establishes the new session; +- cooperative signals update only their own channel and never erase hook liveness. + +Claude's first valid native event, normally `SessionStart`, verifies its configured hook set. +A 60-second active/non-blocked grace may record an internal degraded diagnostic; blocked +workspace trust pauses the diagnostic grace, and any later valid event recovers. This timer +never changes wait behavior by itself. + +Codex has no startup notify. Transport/resource preflight is diagnostic only; the first valid +`agent-turn-complete` is the only end-to-end verification and covers only `turn-ended`. + +## TDD implementation order for S3a + +Logic-layer work follows strict RED -> GREEN; bridge/runtime behavior adds isolated live +verification where unit tests cannot prove third-party behavior. + +### Phase 0 — freeze fixtures and transport policy + +- Capture current Claude/Codex versions and help in the work note. +- Use scratch homes/directories only; never edit live user/global config. +- Add representative official native payload fixtures, including optional/unknown fields, + malformed data, oversized strings, Codex memories cwd, and paths with spaces/non-ASCII. +- Resolve the owner decision under **Open owner decisions** before production rendering. + +### Phase 1 — pure models, renderers, and decoders + +RED/GREEN coverage: + +- Claude settings JSON generation and native event mapping; +- existing settings collision parsing (`--settings value` / `--settings=value`); +- Codex TOML argv rendering and exact top-level `notify` collision detection; +- no hook trust bypass; +- Claude stdin and Codex final-argv decoding; +- unknown native events and malformed/oversized payloads fail closed; +- session/thread extraction, cwd validation, and last-message exclusion; +- interactive/prompt/headless argument order keeps the positional prompt final. + +Primary files/tests: + +- `supacode/Domain/AgentRuntime/AgentRuntimeAdapter.swift`; +- new focused shared hook model/decoder files; +- `supacodeTests/AgentRuntimeAdapterTests.swift`; +- new `AgentNativeHookPayloadTests`. + +### Phase 2 — deterministic plan and generalized carriers + +RED/GREEN coverage: + +- typed arbitrary-argument carrier rendering in `AgentInvocation`/`AgentProfileLaunchPlan`; +- hook JSON/token/socket values absent from terminal initial input and preview; +- carrier cleanup works unchanged under zsh, bash, and fish; +- long prompt + long hook JSON remains below canonical PTY limits; +- Profile environment overrides cannot replace reserved Prowl hook variables; +- unavailable bundled CLI degrades without mutating the user's launch. + +Primary files/tests: + +- `supacode/Domain/AgentProfile/AgentProfileLaunchPlan.swift`; +- `supacode/Support/SupacodePaths.swift`; +- `supacodeTests/AgentProfileTests.swift`; +- `supacodeTests/AppFeatureAgentProfileTests.swift`. + +### Phase 3 — launch epoch, registration, and rollback + +RED/GREEN coverage: + +- one launch epoch shared by hook registration and dispatch binding; +- valid early `SessionStart` queues before detector generation and binds afterward; +- first timely process generation attaches; late/replacement generation revokes; +- wrong token, pane, runtime, native event, cwd, session, or generation cannot verify; +- target-resolution rollback, split/tab failure, process replacement, and surface close clean + registrations and pending events; +- typed CLI and menu/palette compatibility launches both register exact resulting surfaces; +- unprompted Profile launch gets hooks; manual shell launch does not. + +Primary files/tests: + +- `supacode/Features/Terminal/BusinessLogic/AgentObservationStore.swift`; +- `supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift`; +- `supacode/App/supacodeApp.swift`; +- `supacodeTests/AgentEvidenceEpochTests.swift`; +- `supacodeTests/AgentObservationTests.swift`; +- `supacodeTests/WorktreeTerminalStateAgentProfileTests.swift`; +- `supacodeTests/CLILifecycleCommandHandlerTests.swift`. + +### Phase 4 — hidden CLI ingress and schema contract + +RED/GREEN coverage: + +- hidden parser is absent from normal help/completion; +- public `agents signal` wire/receipt behavior remains cooperative and unchanged; +- native hook input round-trips over a real framed Unix socket with kernel peer PID ancestry; +- stale/missing token and outside-pane callers fail closed in the app while the hook process + itself stays silent and exits zero; +- bounded payload and socket deadlines; listener loss and malformed responses do not affect + the runtime; +- hook response sources validate as `hook_claude` / `hook_codex` without exposing a token. + +Primary files/tests: + +- `ProwlCLI/Commands/AgentsSignalCommand.swift` plus a hidden hook command; +- `supacode/CLIService/Shared/InputModels.swift`; +- `supacode/CLIService/AgentSignalCommandHandler.swift`; +- `ProwlCLIContracts/Resources/cli-output-schema.json`; +- `ProwlCLITests/AgentsCommandParsingTests.swift`; +- `ProwlCLITests/ProwlCLIIntegrationTests.swift`; +- `supacodeTests/CLIAgentSignalCommandHandlerTests.swift`; +- `supacodeTests/CLISocketServerTests.swift`. + +### Phase 5 — current docs and live acceptance + +Update: + +- `docs-ai/013-prowl-cli/contracts/agents-signal.md`; +- `docs/components/agent-detection.md`; +- `docs/components/cli.md`; +- `docs/components/agent-profiles.md` as needed; +- `skills/prowl-cli/SKILL.md` only where orchestration guidance changes; +- 064 plan/research/action records with verified runtime versions and behavior. + +Live isolated Debug gates use a custom socket and freshly embedded CLI: + +Claude: + +1. Scratch existing user/project hook plus Prowl CLI settings both fire exactly once; live + settings files remain unchanged. +2. `SessionStart` produces `verified_live` coverage. +3. `Stop` resolves generic idle wait with `source=hook_claude`. +4. A harmless permission request produces `needs-input` before user response. +5. Dispatch completion receipt still wins over the adjacent Stop hook. +6. Fresh workspace trust never creates false coverage before acceptance; a later valid event + recovers. + +Codex: + +1. `agent-turn-complete` resolves with `source=hook_codex` and no trust-bypass flag. +2. Internal memories cwd is ignored. +3. The exact owner-approved notify replacement/collision policy is observed. +4. A manually launched Codex remains honestly heuristic/cooperative. + +Both: + +- bundled absolute CLI works with `/usr/local/bin/prowl` unavailable; +- custom socket/token survives the native hook environment; +- invalid token, listener loss, and helper failure never alter runtime behavior; +- created test resources and isolated app/socket state are removed afterward. + +Repository gates: + +```bash +make build-cli +make test-cli-smoke +make test-cli-integration +make check +make test +make build-app +``` + +## Acceptance invariants + +- Only an app-issued, live launch registration plus exact caller-pane ancestry can produce + `source=hook` / `verified_live`; `--origin` and `PROWL_PANE_ID` never can. +- Native hook signals are observation evidence only; dispatch/workflow completion remains a + separate explicit protocol. +- One Profile launch owns one evidence epoch shared by hook registration and optional dispatch. +- No hook configuration is written to user global settings, dedicated homes, or repositories. +- Hook failure is fail-open for the runtime and fail-closed for trust/verification. +- Unverified coverage never suppresses heuristic `auto` fallback. +- The base Profile and preview remain deterministic; execution tokens are memory-only and + surface-scoped. +- No token, user environment value, full assistant result, credential, or provider config is + logged or persisted by Prowl. + +## Open owner decisions + +### 1. Codex `notify` ownership — blocking + +Codex exposes one legacy `notify: array` command. A CLI `-c notify=[...]` override +replaces the effective user notifier for that Prowl-launched process; it cannot append another +notifier. Native lifecycle hooks would require `--dangerously-bypass-hook-trust`, which Prowl +will not pass, and Codex exposes no reliable side-effect-free command for resolving and +chaining the complete effective notifier. + +Recommended policy: + +- permit Prowl's process-scoped notify override for ordinary Prowl Agent Profile launches and + document that the user's global Codex notifier does not run in that launched session; +- if the Profile's own `extraArguments` explicitly set top-level `notify`, preserve that + explicit Profile intent, omit Prowl's managed Codex hook, and expose no `verified_live` + coverage; +- never inspect/rewrite the user's global Codex config and never pass hook trust bypass. + +This decision must be resolved through `/grill-me` before S3a implementation. + +## Residual risks + +- Claude workspace trust can indefinitely delay every hook; fallback must remain honest. +- Codex legacy `notify` may be deprecated in favor of native hooks; keep its renderer/decoder + isolated behind the adapter capability. +- Third-party payloads and flag semantics can drift. Fixture tests pin supported shapes, while + live gates record the exact shipping versions. +- Early-event buffering and dispatch epoch adoption touch S2 correctness boundaries; focused + lifecycle tests and full dispatch regression gates are mandatory before merge. -- 2.51.2 From 03d1ad235e7f9eba12484fd4a5182e0d200138b3 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 23 Aug 2026 23:55:41 +0900 Subject: [PATCH 4/6] docs(ai): preserve Codex notifier in S3 --- .../006-s3-wave1-plan.md | 215 ++++++++++++++---- 1 file changed, 174 insertions(+), 41 deletions(-) diff --git a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md index 0aafba3d..56fc7a21 100644 --- a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md +++ b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md @@ -26,9 +26,11 @@ but release documentation must not call S3 wave 1 complete before S3c. ## S3a objective -Make every Prowl Agent Profile launch of Claude Code or Codex automatically report supported +Make Prowl Agent Profile launches of Claude Code or Codex automatically report supported native runtime events into the existing S1/S2 signal bus, with honest `verified_live` -coverage and no global configuration writes. +coverage, no global configuration writes, and no loss of an existing user notifier. When +Prowl cannot prepare a managed hook without preserving user behavior, the launch proceeds +without that hook and exposes no exact coverage. S3a normalizes only runtime facts: @@ -99,6 +101,15 @@ or launch path. 6. **Codex internal work.** Codex can emit notify events for internal memories work. The decoder/registration must reject a cwd outside the effective launch directory, including `~/.codex/memories`. +7. **Codex notifier preservation.** Codex accepts one effective `notify: array` + command. Prowl may replace it process-locally only after resolving the complete effective + user notifier and registering a transparent forward target. If resolution fails, Prowl + preserves the user's launch and omits its managed Codex hook instead of swallowing a + notifier it cannot reproduce. +8. **Asynchronous launch preparation.** Codex's own `app-server config/read` is the source of + truth for merged config parsing and must run with a bounded timeout off the main actor. + Every A2 launcher therefore needs one shared asynchronous preparation boundary before + exact surface creation, while preview remains pure and deterministic. ## Proposed S3a design @@ -120,10 +131,53 @@ Runtime-specific rendering: user/project/local settings continue to merge additively. If a Profile already supplies an explicit `--settings`, merge it only when it is safely readable/parseable; otherwise retain the user's launch and omit managed hooks with honest degradation. -- Codex: one structured TOML `-c notify=[...]` override containing the bundled CLI absolute - path and hidden native-hook arguments. Never pass `--dangerously-bypass-hook-trust`. - -### 2. Execution-scoped token and child-only transport +- Codex: when notifier preparation succeeds, one structured TOML `-c notify=[...]` override + containing the bundled CLI absolute path and hidden native-hook arguments. Never pass + `--dangerously-bypass-hook-trust`; never inject the override until any displaced notifier + has an exact forwarding registration. + +### 2. Codex effective-notifier resolver and transparent dispatcher + +Before a Codex surface is created, a bounded `CodexEffectiveNotifyResolver` resolves the +notifier that the user's unmodified launch would execute: + +1. query Codex 0.149's official `app-server config/read` protocol with the effective + `CODEX_HOME`, launch cwd, and relevant `-c/--config` overrides; +2. structurally recognize `-p/--profile`, whose profile file has higher precedence than the + base config; because `codex app-server` rejects `--profile`, let Codex parse that file in an + isolated temporary parser home and use only a profile-owned `notify` value, otherwise fall + back to the merged base result; +3. let the final top-level CLI `-c notify=...` override win, preserving the exact decoded argv; +4. reject empty, malformed, oversized, or recursively Prowl-managed forwarding targets. + +The resolver never edits user, dedicated-home, or project config. Temporary parser state is +owner-only, contains no generated agent account, and is removed immediately. It does not log +or persist returned config or notifier argv. A timeout, unsupported Codex protocol, malformed +response, or unreadable selected profile preserves the original launch, omits the Prowl +`notify` override, and exposes no `verified_live` Codex channel. The launch still succeeds but +returns one non-blocking degradation warning. + +After resolving the argv, Prowl atomically creates one session-scoped forwarding record under +a random directory in its private runtime area. The directory is `0700`, the record is `0600`, +and only an opaque locator—not its contents—crosses the child-only launch carrier. The record +contains no hook token or provider config and is never placed in user config, a dedicated +agent home, the worktree, terminal input, preview, logs, or durable Prowl state. + +The hidden bridge reads and validates that record, makes the bounded Prowl signal attempt, and +then `exec`s the original notifier directly—no shell—with the inherited cwd/environment and +the original Codex JSON payload appended unchanged. Before `exec`, it removes Prowl's internal +hook token and forwarding locator from the environment. Prowl transport success or failure +cannot suppress forwarding, and the original notifier retains the process/exit semantics Codex +would have observed. Prowl evidence and user forwarding are independent and each occurs at +most once per native notification. + +Surface creation failure removes the prepared record. Surface close, process replacement, and +rollback revoke and delete it; app startup and an age-bounded orphan sweep clean records left +by crashes without touching registered live records. Record creation, permission enforcement, +validation, or cleanup setup failure is a pre-launch degradation: preserve the original Codex +launch and do not inject Prowl's `notify` override. + +### 3. Execution-scoped token and child-only transport Immediately before surface creation, `WorktreeTerminalManager` generates an opaque UUID channel token and patches only the execution copy of the launch plan: @@ -135,14 +189,16 @@ channel token and patches only the execution copy of the launch plan: runtime does not inherit the channel. After exact tab/surface creation succeeds, the manager begins one Profile launch epoch and -registers `{token, surface, runtime, launch cwd, covered events, epoch}`. Surface creation -failure creates no registration. Target-resolution rollback, process replacement, and surface -close revoke it. +registers `{token, surface, runtime, launch cwd, covered events, epoch, optional forward record}`. +Surface creation failure creates no registration. Target-resolution rollback, process +replacement, and surface close revoke it and remove any forwarding record. The token is a correctness capability, not a hostile-process secret. It never appears in CLI -arguments, prompt text, output payloads, logs, or persisted Profile/run state. +arguments, prompt text, output payloads, logs, or persisted Profile/run state. A displaced user +notifier may contain credentials, so its argv is sensitive: the only at-rest copy Prowl creates +is the owner-only ephemeral forwarding record, and neither its contents nor path is public API. -### 3. Hidden native-hook bridge over the existing signal command +### 4. Hidden native-hook bridge over the existing signal command Add a hidden `prowl agents _hook ` leaf. It is bundled-internal, not a user command or targetable API. @@ -158,12 +214,14 @@ user command or targetable API. `source=cooperative_cli`. - The app validates token, exact caller pane, configured runtime/native event, launch cwd, and current/pending process generation before constructing `.hook(...)`. +- Codex forwarding is driven by the private launch record, not by a successful app response; + public `agents signal` never receives forwarding instructions or record contents. The hook bridge uses the app-bundled CLI path, not `PATH` or `/usr/local/bin/prowl`, suppresses all output, does not auto-launch Prowl, and has bounded connect/read/write behavior. Delivery failure exits zero so observation cannot interfere with the runtime. -### 4. Channel verification and lifecycle +### 5. Channel verification and lifecycle Keep registration state inside `AgentObservationStore`; do not add a parallel store. Internally a registration may be pending, verified, or degraded, but the existing public @@ -196,7 +254,7 @@ verification where unit tests cannot prove third-party behavior. - Use scratch homes/directories only; never edit live user/global config. - Add representative official native payload fixtures, including optional/unknown fields, malformed data, oversized strings, Codex memories cwd, and paths with spaces/non-ASCII. -- Resolve the owner decision under **Open owner decisions** before production rendering. +- Record the resolved Codex notifier-preservation decision before production rendering. ### Phase 1 — pure models, renderers, and decoders @@ -204,8 +262,9 @@ RED/GREEN coverage: - Claude settings JSON generation and native event mapping; - existing settings collision parsing (`--settings value` / `--settings=value`); -- Codex TOML argv rendering and exact top-level `notify` collision detection; -- no hook trust bypass; +- Codex TOML argv rendering and structural `-p/--profile` plus top-level + `-c/--config notify` recognition; +- no hook trust bypass or recursive Prowl forwarding target; - Claude stdin and Codex final-argv decoding; - unknown native events and malformed/oversized payloads fail closed; - session/thread extraction, cwd validation, and last-message exclusion; @@ -218,7 +277,28 @@ Primary files/tests: - `supacodeTests/AgentRuntimeAdapterTests.swift`; - new `AgentNativeHookPayloadTests`. -### Phase 2 — deterministic plan and generalized carriers +### Phase 2 — Codex effective-notifier resolution + +RED/GREEN coverage: + +- bounded app-server JSONL initialization and `config/read` request/response handling; +- effective base/system notifier resolution under the launch `CODEX_HOME` and cwd; +- selected profile notifier wins base, absent profile notifier falls back to base; +- final top-level CLI `-c notify` wins profile, while unrelated config overrides do not; +- exact argv preservation for spaces, empty arguments, Unicode, quotes, and secret-like values; +- unsupported protocol, timeout, malformed config/response, unreadable profile, empty argv, and + recursive Prowl target all produce a no-injection degradation rather than user-notifier loss; +- temporary parser homes are owner-only and removed on success, failure, and cancellation; +- no effective config or notifier argv reaches logs, durable settings/state, previews, or + terminal carriers; only the private ephemeral forwarding record may hold resolved argv. + +Primary files/tests: + +- new focused `CodexEffectiveNotifyResolver` and app-server protocol models; +- `supacodeTests/CodexEffectiveNotifyResolverTests.swift`; +- scratch-home live contract test against the pinned Codex CLI. + +### Phase 3 — deterministic plan and generalized carriers RED/GREEN coverage: @@ -236,7 +316,7 @@ Primary files/tests: - `supacodeTests/AgentProfileTests.swift`; - `supacodeTests/AppFeatureAgentProfileTests.swift`. -### Phase 3 — launch epoch, registration, and rollback +### Phase 4 — launch epoch, registration, and rollback RED/GREEN coverage: @@ -247,7 +327,16 @@ RED/GREEN coverage: - target-resolution rollback, split/tab failure, process replacement, and surface close clean registrations and pending events; - typed CLI and menu/palette compatibility launches both register exact resulting surfaces; -- unprompted Profile launch gets hooks; manual shell launch does not. +- unprompted Profile launch gets hooks; manual shell launch does not; +- Codex forwarding records use random paths, `0700`/`0600` permissions, atomic creation, and + exact cleanup on failure, rollback, process replacement, surface close, and orphan sweep; +- record contents never enter the child environment; only an opaque locator does; +- GUI and CLI Profile launchers both await the same bounded preparation before creating a + surface, without blocking the main actor; +- a degraded GUI launch emits exactly one non-blocking warning toast, while a degraded CLI + launch succeeds with one additive structured warning and human-readable warning output; +- degradation never becomes a persistent public channel state and never changes dispatch + receipt behavior. Primary files/tests: @@ -259,7 +348,7 @@ Primary files/tests: - `supacodeTests/WorktreeTerminalStateAgentProfileTests.swift`; - `supacodeTests/CLILifecycleCommandHandlerTests.swift`. -### Phase 4 — hidden CLI ingress and schema contract +### Phase 5 — hidden CLI ingress, forwarding, and schema contract RED/GREEN coverage: @@ -268,6 +357,11 @@ RED/GREEN coverage: - native hook input round-trips over a real framed Unix socket with kernel peer PID ancestry; - stale/missing token and outside-pane callers fail closed in the app while the hook process itself stays silent and exits zero; +- Codex forwarding uses exact argv boundaries and the unchanged native payload, never a shell; +- the bridge scrubs Prowl's token/locator and `exec`s the original notifier after the bounded + signal attempt, whether that attempt succeeds or fails; +- user notifier failure cannot suppress a recorded Prowl signal, and Prowl transport failure + cannot suppress, recursively invoke, or duplicate the user notifier; - bounded payload and socket deadlines; listener loss and malformed responses do not affect the runtime; - hook response sources validate as `hook_claude` / `hook_codex` without exposing a token. @@ -283,7 +377,7 @@ Primary files/tests: - `supacodeTests/CLIAgentSignalCommandHandlerTests.swift`; - `supacodeTests/CLISocketServerTests.swift`. -### Phase 5 — current docs and live acceptance +### Phase 6 — current docs and live acceptance Update: @@ -311,8 +405,14 @@ Codex: 1. `agent-turn-complete` resolves with `source=hook_codex` and no trust-bypass flag. 2. Internal memories cwd is ignored. -3. The exact owner-approved notify replacement/collision policy is observed. -4. A manually launched Codex remains honestly heuristic/cooperative. +3. Existing base, selected-profile, and explicit CLI-override notifiers each receive the exact + original payload once while Prowl also records the hook once. +4. User notifier exit failure cannot alter Codex or Prowl evidence; resolver failure preserves + the original notifier and omits exact Prowl coverage. +5. Notifier argv containing secret-like values appears only in the private `0600` session + record—not logs, preview, terminal input/environment, durable registration, or public CLI + output—and the record is deleted with its launch. +6. A manually launched Codex remains honestly heuristic/cooperative. Both: @@ -344,35 +444,68 @@ make build-app - Unverified coverage never suppresses heuristic `auto` fallback. - The base Profile and preview remain deterministic; execution tokens are memory-only and surface-scoped. -- No token, user environment value, full assistant result, credential, or provider config is - logged or persisted by Prowl. +- No token, user environment value, full assistant result, credential, notifier argv, or + provider config is logged or durably persisted by Prowl. The sole notifier-argv exception is + the owner-only ephemeral forwarding record required for transport-independent chaining. +- A process-scoped Codex `notify` override is legal only while the displaced effective user + notifier has a validated private forwarding record; uncertain resolution or record setup + preserves the user's launch and forfeits the managed hook. + +## Resolved owner decisions + +### 1. Codex `notify` ownership — resolved 2026-08-23 + +Codex exposes one legacy `notify: array` command rather than an additive notifier list. +The owner accepted transparent dispatcher semantics: + +- resolve the notifier that the unmodified Prowl Agent Profile launch would execute; +- inject Prowl's process-scoped notifier only after preparing exact, once-per-event forwarding + of the unchanged Codex payload; +- if resolution is uncertain, preserve the user's launch, omit Prowl's managed Codex hook, and + expose no `verified_live` coverage; +- never rewrite user config, place notifier argv in a terminal carrier, durably persist/log + returned config, or pass hook trust bypass. + +### 2. Codex launch-degradation disclosure — resolved 2026-08-23 -## Open owner decisions +The owner accepted a successful launch plus one explicit warning: -### 1. Codex `notify` ownership — blocking +- GUI launch shows one non-blocking warning toast; +- CLI launch remains successful and adds an optional structured warning to + `prowl.cli.create.v1`, rendered once in human-readable output; +- no persistent degraded state or repeated toast is added before S3c's exact-channel badge; +- S2 dispatch receipts and runtime behavior remain unchanged. -Codex exposes one legacy `notify: array` command. A CLI `-c notify=[...]` override -replaces the effective user notifier for that Prowl-launched process; it cannot append another -notifier. Native lifecycle hooks would require `--dangerously-bypass-hook-trust`, which Prowl -will not pass, and Codex exposes no reliable side-effect-free command for resolving and -chaining the complete effective notifier. +The supported worst case is bounded launch-preflight latency followed by the whole session +falling back to pre-S3 heuristic/cooperative observation. Generic wait may become less precise +or time out, but the user notifier, Codex process, config, and S2 receipt path remain intact; +degradation must never create false exact evidence. -Recommended policy: +### 3. Codex forwarding durability — resolved 2026-08-23 -- permit Prowl's process-scoped notify override for ordinary Prowl Agent Profile launches and - document that the user's global Codex notifier does not run in that launched session; -- if the Profile's own `extraArguments` explicitly set top-level `notify`, preserve that - explicit Profile intent, omit Prowl's managed Codex hook, and expose no `verified_live` - coverage; -- never inspect/rewrite the user's global Codex config and never pass hook trust bypass. +The owner accepted the recommended private session-record tradeoff so user forwarding does not +depend on a live Prowl socket response: -This decision must be resolved through `/grill-me` before S3a implementation. +- random Prowl runtime directory `0700`, forwarding record `0600`; +- opaque child-only locator; notifier argv never enters command text, shell history, environment, + logs, or durable settings/state; +- bridge attempts Prowl delivery, scrubs internal variables, then `exec`s the original notifier + regardless of Prowl transport outcome; +- exact lifecycle cleanup plus startup/age-bounded orphan cleanup; +- any inability to prepare this boundary degrades before override injection. ## Residual risks - Claude workspace trust can indefinitely delay every hook; fallback must remain honest. -- Codex legacy `notify` may be deprecated in favor of native hooks; keep its renderer/decoder - isolated behind the adapter capability. +- Codex legacy `notify` may be deprecated in favor of native hooks; keep its resolver, + renderer, decoder, and dispatcher isolated behind the adapter capability. +- `app-server config/read` adds bounded launch-preparation latency and may drift independently + of `codex` runtime flags; pin fixtures, time out safely, and preserve the user launch on any + mismatch. Local scratch measurements were 24 ms warm median and 210 ms cold, so use a 1-second + hard timeout rather than an unbounded launch stall. +- Forwarding records temporarily hold sensitive argv. Enforce owner-only creation, refuse + symlinks/non-regular files or permission drift, never print contents, and test every cleanup + edge plus stale-orphan collection. - Third-party payloads and flag semantics can drift. Fixture tests pin supported shapes, while live gates record the exact shipping versions. - Early-event buffering and dispatch epoch adoption touch S2 correctness boundaries; focused -- 2.51.2 From ccc5d724c4d137cc393c54726b42eb00265f1c29 Mon Sep 17 00:00:00 2001 From: onevcat Date: Mon, 24 Aug 2026 00:16:20 +0900 Subject: [PATCH 5/6] docs(ai): harden S3 hook plan --- .../006-s3-wave1-plan.md | 139 ++++++++++++------ 1 file changed, 98 insertions(+), 41 deletions(-) diff --git a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md index 56fc7a21..6809c458 100644 --- a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md +++ b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md @@ -81,13 +81,14 @@ or launch path. ### Blocking implementation constraints 1. **One Profile launch epoch.** `bindAgentDispatch` currently calls - `beginDispatchEpoch` after the Profile surface has launched. S3 registration must begin at - the actual Profile launch boundary, so dispatch binding must adopt that epoch rather than - minting another one and clearing early hook evidence. -2. **Early native events.** Claude `SessionStart` can reach Prowl before periodic detection has - resolved the agent PID. A valid registered token and exact caller pane must be accepted as - pending evidence, then bound when the first launch generation appears within the existing - acquisition window. It must not be silently downgraded or lost. + `beginDispatchEpoch` after the Profile surface has launched. S3 registration must begin + after the exact surface identity exists but before its initial input can execute, so dispatch + binding adopts that epoch rather than minting another one and clearing early hook evidence. +2. **Early native events.** Claude `SessionStart` can reach Prowl before surface creation + returns and before periodic detection has resolved the agent PID. The launch token must + already be registered to the exact caller pane before initial input delivery. Its event is + accepted as pending process evidence, then bound when the first launch generation appears + within the existing acquisition window; it must not be downgraded or lost. 3. **Prompt/config carriers.** `AgentProfileLaunchPlan.terminalInput` currently replaces only the final prompt argv value. Claude settings JSON is a non-final argument and can exceed the canonical PTY limit. Shell rendering must support typed argv-index -> environment-carrier @@ -108,8 +109,17 @@ or launch path. notifier it cannot reproduce. 8. **Asynchronous launch preparation.** Codex's own `app-server config/read` is the source of truth for merged config parsing and must run with a bounded timeout off the main actor. - Every A2 launcher therefore needs one shared asynchronous preparation boundary before - exact surface creation, while preview remains pure and deterministic. + Every A2 launcher therefore needs one shared asynchronous preparation boundary before any + dispatch issuance or launch mutation, while preview remains pure and deterministic. After + a dispatch slot is issued, no new suspension point may occur before synchronous + launch/bind/rollback; peer disconnect during preflight has no dispatch state to leak. +9. **Forwarding-record retirement.** Revoking hook trust and deleting the only forward argv + cannot be one operation: a Codex notifier helper may already be spawned but not yet have + opened its record. The bridge must open/validate/read before transport, while close, + replacement, and rollback retire the record through a lock-aware grace rather than unlinking + it immediately. +10. **No notifier is success.** A valid effective Codex configuration with no `notify` is the + common direct-injection path, not an empty/malformed-forward-target degradation. ## Proposed S3a design @@ -139,7 +149,13 @@ Runtime-specific rendering: ### 2. Codex effective-notifier resolver and transparent dispatcher Before a Codex surface is created, a bounded `CodexEffectiveNotifyResolver` resolves the -notifier that the user's unmodified launch would execute: +notifier that the user's unmodified launch would execute. Its typed result is exactly one of: + +- `.absent`: effective config has no `notify`; inject Prowl directly with no forwarding record; +- `.present(nonEmptyArgv)`: prepare the private record, then inject Prowl's dispatcher; +- `.degraded(reason)`: preserve the unmodified launch, inject nothing, and surface one warning. + +Resolution precedence: 1. query Codex 0.149's official `app-server config/read` protocol with the effective `CODEX_HOME`, launch cwd, and relevant `-c/--config` overrides; @@ -148,34 +164,44 @@ notifier that the user's unmodified launch would execute: isolated temporary parser home and use only a profile-owned `notify` value, otherwise fall back to the merged base result; 3. let the final top-level CLI `-c notify=...` override win, preserving the exact decoded argv; -4. reject empty, malformed, oversized, or recursively Prowl-managed forwarding targets. +4. treat an explicitly configured empty, malformed, oversized, or recursively Prowl-managed + forward target as `.degraded`, without conflating it with `.absent`. The resolver never edits user, dedicated-home, or project config. Temporary parser state is owner-only, contains no generated agent account, and is removed immediately. It does not log or persist returned config or notifier argv. A timeout, unsupported Codex protocol, malformed -response, or unreadable selected profile preserves the original launch, omits the Prowl -`notify` override, and exposes no `verified_live` Codex channel. The launch still succeeds but -returns one non-blocking degradation warning. +response, or unreadable selected profile produces `.degraded`: preserve the original launch, +omit the Prowl `notify` override, and expose no `verified_live` Codex channel. The launch still +succeeds but returns one non-blocking degradation warning. + +GUI and CLI launchers await this preflight before issuing an optional dispatch slot. Cancellation +at this suspension point removes scratch/parser/forwarding artifacts but has no launch, epoch, +or dispatch state to roll back. Once a dispatch is issued, plan rendering, surface creation, +pre-input registration, dispatch binding, and rollback remain one synchronous transaction. -After resolving the argv, Prowl atomically creates one session-scoped forwarding record under +For `.present`, Prowl atomically creates one session-scoped forwarding record under a random directory in its private runtime area. The directory is `0700`, the record is `0600`, and only an opaque locator—not its contents—crosses the child-only launch carrier. The record contains no hook token or provider config and is never placed in user config, a dedicated agent home, the worktree, terminal input, preview, logs, or durable Prowl state. -The hidden bridge reads and validates that record, makes the bounded Prowl signal attempt, and -then `exec`s the original notifier directly—no shell—with the inherited cwd/environment and -the original Codex JSON payload appended unchanged. Before `exec`, it removes Prowl's internal -hook token and forwarding locator from the environment. Prowl transport success or failure -cannot suppress forwarding, and the original notifier retains the process/exit semantics Codex -would have observed. Prowl evidence and user forwarding are independent and each occurs at -most once per native notification. - -Surface creation failure removes the prepared record. Surface close, process replacement, and -rollback revoke and delete it; app startup and an age-bounded orphan sweep clean records left -by crashes without touching registered live records. Record creation, permission enforcement, -validation, or cleanup setup failure is a pre-launch degradation: preserve the original Codex -launch and do not inject Prowl's `notify` override. +The hidden bridge first opens the record with no-follow semantics, validates owner/type/mode, +acquires a shared lease, and reads the bounded argv before attempting socket transport. It then +makes the bounded Prowl signal attempt and `exec`s the original notifier directly—no shell—with +the inherited cwd/environment and original Codex JSON payload appended unchanged. Before +`exec`, it removes Prowl's internal hook token and forwarding locator from the environment. +Prowl transport success or failure cannot suppress forwarding, and the original notifier +retains the process/exit semantics Codex would have observed. Prowl evidence and user +forwarding are independent and each occurs at most once per native notification. + +Surface creation failure removes a record that was never exposed to a child. Surface close, +process replacement, and rollback revoke hook trust immediately but move an exposed record to +a retired set for a bounded spawn grace. Cleanup takes an exclusive lease after that grace and +defers a locked record, so an already-started bridge cannot lose its sole argv copy between +spawn and read. App startup and an age-bounded orphan sweep clean crash leftovers without +touching registered live or leased records. Record creation, permission enforcement, +validation, retirement, or cleanup setup failure is a pre-launch degradation: preserve the +original Codex launch and do not inject Prowl's `notify` override. ### 3. Execution-scoped token and child-only transport @@ -188,10 +214,14 @@ channel token and patches only the execution copy of the launch plan: - the pane shell never receives the public hook token variable, so a later manually launched runtime does not inherit the channel. -After exact tab/surface creation succeeds, the manager begins one Profile launch epoch and -registers `{token, surface, runtime, launch cwd, covered events, epoch, optional forward record}`. -Surface creation failure creates no registration. Target-resolution rollback, process -replacement, and surface close revoke it and remove any forwarding record. +Surface creation becomes two-phase at the manager/state boundary: create and install the exact +surface identity without arming initial input; begin one Profile launch epoch and register +`{token, surface, runtime, launch cwd, covered events, epoch, optional forward record}`; then arm +initial input. A lower-level pre-input callback is also acceptable if it proves the same order. +Creation or pre-input registration failure rolls back the surface and registration before any +agent command executes. Dispatch binding adopts this epoch. Target-resolution rollback, +process replacement, and surface close revoke trust immediately and retire any exposed +forwarding record through the lease protocol above. The token is a correctness capability, not a hostile-process secret. It never appears in CLI arguments, prompt text, output payloads, logs, or persisted Profile/run state. A displaced user @@ -251,9 +281,13 @@ verification where unit tests cannot prove third-party behavior. ### Phase 0 — freeze fixtures and transport policy - Capture current Claude/Codex versions and help in the work note. +- Capture the exact Codex 0.149 app-server initialize/`config/read` JSONL transcript and a + scratch-home precedence matrix for absent/base/profile/final-CLI-override `notify`; retain + sanitized fixtures, not returned user/provider config. - Use scratch homes/directories only; never edit live user/global config. - Add representative official native payload fixtures, including optional/unknown fields, malformed data, oversized strings, Codex memories cwd, and paths with spaces/non-ASCII. +- Reconfirm Claude 2.1.241 payload/trust behavior in an isolated scratch workspace. - Record the resolved Codex notifier-preservation decision before production rendering. ### Phase 1 — pure models, renderers, and decoders @@ -282,6 +316,8 @@ Primary files/tests: RED/GREEN coverage: - bounded app-server JSONL initialization and `config/read` request/response handling; +- clean scratch home resolves `.absent`, injects Prowl directly, creates no forward record, and + emits no degradation warning; - effective base/system notifier resolution under the launch `CODEX_HOME` and cwd; - selected profile notifier wins base, absent profile notifier falls back to base; - final top-level CLI `-c notify` wins profile, while unrelated config overrides do not; @@ -289,6 +325,8 @@ RED/GREEN coverage: - unsupported protocol, timeout, malformed config/response, unreadable profile, empty argv, and recursive Prowl target all produce a no-injection degradation rather than user-notifier loss; - temporary parser homes are owner-only and removed on success, failure, and cancellation; +- cancellation/peer disconnect during preflight leaves no dispatch slot, surface, registration, + epoch, or forwarding artifact; - no effective config or notifier argv reaches logs, durable settings/state, previews, or terminal carriers; only the private ephemeral forwarding record may hold resolved argv. @@ -320,6 +358,8 @@ Primary files/tests: RED/GREEN coverage: +- two-phase surface creation installs exact identity and registration before initial input; +- a synchronously delivered `SessionStart` during launch cannot beat token registration; - one launch epoch shared by hook registration and dispatch binding; - valid early `SessionStart` queues before detector generation and binds afterward; - first timely process generation attaches; late/replacement generation revokes; @@ -329,12 +369,21 @@ RED/GREEN coverage: - typed CLI and menu/palette compatibility launches both register exact resulting surfaces; - unprompted Profile launch gets hooks; manual shell launch does not; - Codex forwarding records use random paths, `0700`/`0600` permissions, atomic creation, and - exact cleanup on failure, rollback, process replacement, surface close, and orphan sweep; + lease-aware retirement/cleanup on failure, rollback, process replacement, surface close, and + orphan sweep; +- deterministic close/replacement-versus-bridge-open tests prove an already-spawned bridge can + read before exclusive cleanup, while trust revocation remains immediate; - record contents never enter the child environment; only an opaque locator does; -- GUI and CLI Profile launchers both await the same bounded preparation before creating a - surface, without blocking the main actor; -- a degraded GUI launch emits exactly one non-blocking warning toast, while a degraded CLI - launch succeeds with one additive structured warning and human-readable warning output; +- GUI and CLI Profile launchers await the same bounded preparation before dispatch issuance or + surface creation, without blocking the main actor; +- cancellation at every suspension boundary cleans preparation, while the post-dispatch + launch/bind/rollback transaction contains no suspension point; +- a degraded GUI launch emits exactly one non-blocking warning toast; +- a degraded CLI launch succeeds with `warnings: [LifecycleCommandWarning]`, omitted when + empty; the first stable warning is `{code: "managed_hook_degraded", runtime, message}`; +- `prowl.cli.create.v1` closed-schema fixtures accept the additive warning array, existing + decoders tolerate it, JSON mode keeps it in stdout, and text mode renders it exactly once to + stderr; - degradation never becomes a persistent public channel state and never changes dispatch receipt behavior. @@ -342,6 +391,9 @@ Primary files/tests: - `supacode/Features/Terminal/BusinessLogic/AgentObservationStore.swift`; - `supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift`; +- `supacode/CLIService/Shared/LifecycleCommandPayload.swift`; +- `ProwlCLI/Output/OutputRenderer.swift`; +- `ProwlCLIContracts/Resources/cli-output-schema.json`; - `supacode/App/supacodeApp.swift`; - `supacodeTests/AgentEvidenceEpochTests.swift`; - `supacodeTests/AgentObservationTests.swift`; @@ -403,7 +455,8 @@ Claude: Codex: -1. `agent-turn-complete` resolves with `source=hook_codex` and no trust-bypass flag. +1. A clean scratch home with no notifier injects Prowl directly, creates no forwarding record, + and `agent-turn-complete` resolves with `source=hook_codex` and no trust-bypass flag. 2. Internal memories cwd is ignored. 3. Existing base, selected-profile, and explicit CLI-override notifiers each receive the exact original payload once while Prowl also records the hook once. @@ -471,8 +524,9 @@ The owner accepted transparent dispatcher semantics: The owner accepted a successful launch plus one explicit warning: - GUI launch shows one non-blocking warning toast; -- CLI launch remains successful and adds an optional structured warning to - `prowl.cli.create.v1`, rendered once in human-readable output; +- CLI launch remains successful and adds optional + `warnings: [{code: "managed_hook_degraded", runtime, message}]` to + `prowl.cli.create.v1`; JSON retains it in stdout and text renders it once to stderr; - no persistent degraded state or repeated toast is added before S3c's exact-channel badge; - S2 dispatch receipts and runtime behavior remain unchanged. @@ -491,7 +545,10 @@ depend on a live Prowl socket response: logs, or durable settings/state; - bridge attempts Prowl delivery, scrubs internal variables, then `exec`s the original notifier regardless of Prowl transport outcome; -- exact lifecycle cleanup plus startup/age-bounded orphan cleanup; +- immediate trust revocation plus shared-read/exclusive-cleanup leasing, a bounded retirement + grace, and startup/age-bounded orphan cleanup; +- the bridge opens, leases, and reads before transport, so transport rejection and concurrent + lifecycle cleanup cannot suppress forwarding; - any inability to prepare this boundary degrades before override injection. ## Residual risks -- 2.51.2 From 8724f85cc579ce78315281aa7cd250e1b2c2c18d Mon Sep 17 00:00:00 2001 From: onevcat Date: Mon, 24 Aug 2026 00:33:54 +0900 Subject: [PATCH 6/6] docs(ai): close S3 rereview gaps --- .../006-s3-wave1-plan.md | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md index 6809c458..9d366e00 100644 --- a/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md +++ b/docs-ai/064-agent-completion-signals/006-s3-wave1-plan.md @@ -120,6 +120,11 @@ or launch path. it immediately. 10. **No notifier is success.** A valid effective Codex configuration with no `notify` is the common direct-injection path, not an empty/malformed-forward-target degradation. +11. **Stable launch context across preflight.** Codex preserves user `-C/--cd` arguments, and + menu split/tab inheritance currently depends on live focus/cwd at creation time. Async + preflight cannot resolve config or register launch-cwd trust against one context and then + let the synchronous launch silently choose another. Capture a typed target/base/effective + cwd before preflight and revalidate it before dispatch issuance. ## Proposed S3a design @@ -138,9 +143,13 @@ rendering. Runtime-specific rendering: - Claude: exactly one effective `--settings ` source with command hooks. Existing normal - user/project/local settings continue to merge additively. If a Profile already supplies an - explicit `--settings`, merge it only when it is safely readable/parseable; otherwise retain - the user's launch and omit managed hooks with honest degradation. + user/project/local settings continue to merge additively. If a Profile already supplies one + or more explicit `--settings value` / `--settings=value` arguments, resolve the final effective + source using the pinned CLI precedence, bounded-read a file source or decode an inline object, + preserve every unknown/unrelated field and existing hook handler, append only Prowl's missing + event handlers, and render the merged object through a carrier. Duplicate Prowl handlers are + not appended. If the effective source is unreadable, malformed, non-object, oversized, or + changes during preparation, retain the user's argv unchanged, omit managed hooks, and warn. - Codex: when notifier preparation succeeds, one structured TOML `-c notify=[...]` override containing the bundled CLI absolute path and hidden native-hook arguments. Never pass `--dangerously-bypass-hook-trust`; never inject the override until any displaced notifier @@ -148,8 +157,16 @@ Runtime-specific rendering: ### 2. Codex effective-notifier resolver and transparent dispatcher -Before a Codex surface is created, a bounded `CodexEffectiveNotifyResolver` resolves the -notifier that the user's unmodified launch would execute. Its typed result is exactly one of: +Before a Codex surface is created, capture a typed `CodexLaunchContext`: exact target/anchor, +inherited base cwd, effective cwd after the final supported `-C/--cd` form, effective +`CODEX_HOME`, and ordered config/profile overrides. Menu/palette launches must not re-resolve a +different focused pane after the await. Revalidate target existence and cwd immediately before +dispatch issuance; a mismatch retries preparation against the new explicit context or preserves +the unmodified launch with one degradation warning. Registration binds the exact surface and +its effective post-`--cd` launch cwd. + +A bounded `CodexEffectiveNotifyResolver` resolves the notifier that this frozen unmodified +launch would execute. Its typed result is exactly one of: - `.absent`: effective config has no `notify`; inject Prowl directly with no forwarding record; - `.present(nonEmptyArgv)`: prepare the private record, then inject Prowl's dispatcher; @@ -157,8 +174,8 @@ notifier that the user's unmodified launch would execute. Its typed result is ex Resolution precedence: -1. query Codex 0.149's official `app-server config/read` protocol with the effective - `CODEX_HOME`, launch cwd, and relevant `-c/--config` overrides; +1. query Codex 0.149's official `app-server config/read` protocol with the frozen effective + `CODEX_HOME`, effective post-`--cd` cwd, and relevant `-c/--config` overrides; 2. structurally recognize `-p/--profile`, whose profile file has higher precedence than the base config; because `codex app-server` rejects `--profile`, let Codex parse that file in an isolated temporary parser home and use only a profile-owned `notify` value, otherwise fall @@ -282,8 +299,11 @@ verification where unit tests cannot prove third-party behavior. - Capture current Claude/Codex versions and help in the work note. - Capture the exact Codex 0.149 app-server initialize/`config/read` JSONL transcript and a - scratch-home precedence matrix for absent/base/profile/final-CLI-override `notify`; retain - sanitized fixtures, not returned user/provider config. + scratch-home precedence matrix for absent/base/profile/final-CLI-override `notify`, including + proof that project-layer `notify` is ignored; retain sanitized fixtures, not returned + user/provider config. +- Freeze supported Codex `-C/--cd` token forms, last-wins behavior, relative-path base, and + config-read cwd against 0.149 help/live probes. - Use scratch homes/directories only; never edit live user/global config. - Add representative official native payload fixtures, including optional/unknown fields, malformed data, oversized strings, Codex memories cwd, and paths with spaces/non-ASCII. @@ -295,7 +315,12 @@ verification where unit tests cannot prove third-party behavior. RED/GREEN coverage: - Claude settings JSON generation and native event mapping; -- existing settings collision parsing (`--settings value` / `--settings=value`); +- final-effective Claude settings collision parsing across repeated `--settings value` / + `--settings=value`, inline objects, and file sources; +- merge preserves unknown/unrelated fields and every existing hook handler, appends Prowl + handlers exactly once, and keeps existing normal user/project/local settings additive; +- bounded read, malformed/unreadable/non-object/changed settings preserve original argv exactly, + inject no hook, and produce one degradation warning; - Codex TOML argv rendering and structural `-p/--profile` plus top-level `-c/--config notify` recognition; - no hook trust bypass or recursive Prowl forwarding target; @@ -325,6 +350,10 @@ RED/GREEN coverage: - unsupported protocol, timeout, malformed config/response, unreadable profile, empty argv, and recursive Prowl target all produce a no-injection degradation rather than user-notifier loss; - temporary parser homes are owner-only and removed on success, failure, and cancellation; +- typed launch context freezes target/anchor, inherited base cwd, effective post-`--cd` cwd, + `CODEX_HOME`, and ordered overrides before preflight; +- target/focus/PWD mutation while preflight is suspended cannot launch with a stale resolver + result: revalidate before issuance, then retry or degrade without injection; - cancellation/peer disconnect during preflight leaves no dispatch slot, surface, registration, epoch, or forwarding artifact; - no effective config or notifier argv reaches logs, durable settings/state, previews, or @@ -334,7 +363,8 @@ Primary files/tests: - new focused `CodexEffectiveNotifyResolver` and app-server protocol models; - `supacodeTests/CodexEffectiveNotifyResolverTests.swift`; -- scratch-home live contract test against the pinned Codex CLI. +- scratch-home live contract tests against the pinned Codex CLI, including project-level + `notify` exclusion and supported `-C/--cd` forms. ### Phase 3 — deterministic plan and generalized carriers @@ -359,6 +389,8 @@ Primary files/tests: RED/GREEN coverage: - two-phase surface creation installs exact identity and registration before initial input; +- explicit typed CLI targets and menu/palette compatibility targets keep their frozen anchor/cwd + through preflight; anchor removal and cwd drift follow tested retry/degradation semantics; - a synchronously delivered `SessionStart` during launch cannot beat token registration; - one launch epoch shared by hook registration and dispatch binding; - valid early `SessionStart` queues before detector generation and binds afterward; @@ -433,6 +465,8 @@ Primary files/tests: Update: +- `docs-ai/013-prowl-cli/contracts/create.md` for warning shape, omission, and output-channel + behavior; - `docs-ai/013-prowl-cli/contracts/agents-signal.md`; - `docs/components/agent-detection.md`; - `docs/components/cli.md`; @@ -446,11 +480,14 @@ Claude: 1. Scratch existing user/project hook plus Prowl CLI settings both fire exactly once; live settings files remain unchanged. -2. `SessionStart` produces `verified_live` coverage. -3. `Stop` resolves generic idle wait with `source=hook_claude`. -4. A harmless permission request produces `needs-input` before user response. -5. Dispatch completion receipt still wins over the adjacent Stop hook. -6. Fresh workspace trust never creates false coverage before acceptance; a later valid event +2. Inline and file-backed explicit Profile `--settings` each preserve unknown fields and + existing hook arrays while the existing hook and Prowl handler fire exactly once; a malformed + explicit source launches unchanged with one warning and no exact coverage. +3. `SessionStart` produces `verified_live` coverage. +4. `Stop` resolves generic idle wait with `source=hook_claude`. +5. A harmless permission request produces `needs-input` before user response. +6. Dispatch completion receipt still wins over the adjacent Stop hook. +7. Fresh workspace trust never creates false coverage before acceptance; a later valid event recovers. Codex: