Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Deep design decisions live in [`ARCHITECTURE.md`](ARCHITECTURE.md) (sync-only, M

**Other crates:**

- **`src/agent/`** — agent profile registry; one `AgentProfile` impl per hosted agent (claude/codex/agy/zot). Adding an agent = one impl + one `REGISTRY` entry. Per-agent **P1-2 scrape-fallback** rules (for a state an agent's hooks can't report) live in `detect_rules.rs` (`Region`/`Matcher`/`DetectionRule`), returned by `AgentProfile::detection_rules()` — agy's approval prompt and Codex's complete command/file-edit dialogues. `codex_approval.rs` holds the verified Codex rule and its temporary precedence over non-blocked reports. `chrome.rs` and `chrome/codex.rs` verify native input/footer boundaries (`AgentProfile::output_len`), so `gf`/`J` read the agent's output; Codex also applies that boundary to live Quick Select. `codex_home.rs` resolves the home captured from each pane's launch environment, shared by rollout lookup and fork ancestry. `codex_records.rs` normalizes current and legacy rollout messages/tool records without OS or App dependencies; transcript rendering deduplicates identified records within each session. Its `metrics` child projects bounded telemetry through the same record identities. `codex_metrics/` folds pure usage/mode/tool snapshots and follows one exact rollout through `tail/`, the agent-neutral half: a bounded incremental reader of one session file (`Tail`, behind a `Follow` that names the file's containment, identity and line meaning) and the consumer-owned watch that wakes it (kqueue on macOS, `notify` elsewhere, no idle polling). The Codex HUD subscribes through `app::agent_hud`; reading starts only for a visible or explicit consumer. `claude_metrics/` is its Claude Code counterpart over one session transcript, with its own token type because Claude's `input` excludes the cache reads and writes Codex's includes: a pure projection (`records`) keeping only labels, counts and bounded ids, and a reducer that counts a message once however many content blocks repeat it, forgets the context's size at a compaction while keeping lifetime totals, and accumulates a plan from `TodoWrite`'s full-list reports or `TaskCreate`/`TaskUpdate`'s own `from`/`to` transitions, whichever a session uses. Its `stream`/`watch` follow one exact transcript through `tail/`; a transcript has no header, so its identity is its name, `<claude_home>/projects/…/<session>.jsonl`, with every record's `sessionId` checked too. The agent HUD subscribes through `app::agent_hud`, and only spyc's own MCP server keeps its name in a tool label; `examples/claude_metrics.rs` observes one named transcript. `claude_statusline.rs` is the bounded projection of Claude Code's statusline input (the context window and the plan's rate-limit windows) that `spyc --statusline` (`mcp::statusline`) forwards over the internal `spyc/statusline` method, never an MCP tool. `codex_recovery.rs` folds bounded question lifecycle metadata in the Model, requiring pane/session/turn/call correlation before completion can restore working. `status_hook.rs` bounds and sanitizes lifecycle metadata for diagnostics, excluding hook arguments and response content; of Claude's `Stop` lists of pending background work it keeps only their entry counts. `waiting.rs` is the pure precedence of a live `waiting` report over the hook `done`s that follow it, plus the turn a hook opens and ends, inside which an agent's own `waiting` is held until the turn ends.
- **`src/agent/`** — agent profile registry; one `AgentProfile` impl per hosted agent (claude/codex/agy/zot). Adding an agent = one impl + one `REGISTRY` entry. Per-agent **P1-2 scrape-fallback** rules (for a state an agent's hooks can't report) live in `detect_rules.rs` (`Region`/`Matcher`/`DetectionRule`), returned by `AgentProfile::detection_rules()` — agy's approval prompt and Codex's complete command/file-edit dialogues. `codex_approval.rs` holds verified visible waits and their temporary precedence over non-blocked reports; `codex_questions.rs` recognizes the default async-question editor and collapsed summary without storing question content. `chrome.rs` and `chrome/codex.rs` verify native input/footer boundaries (`AgentProfile::output_len`), so `gf`/`J` read the agent's output; Codex also applies that boundary to live Quick Select. `codex_home.rs` resolves the home captured from each pane's launch environment, shared by rollout lookup and fork ancestry. `codex_records.rs` normalizes current and legacy rollout messages/tool records without OS or App dependencies; transcript rendering deduplicates identified records within each session. Its `metrics` child projects bounded telemetry through the same record identities. `codex_metrics/` folds pure usage/mode/tool snapshots and follows one exact rollout through `tail/`, the agent-neutral half: a bounded incremental reader of one session file (`Tail`, behind a `Follow` that names the file's containment, identity and line meaning) and the consumer-owned watch that wakes it (kqueue on macOS, `notify` elsewhere, no idle polling). The Codex HUD subscribes through `app::agent_hud`; reading starts only for a visible or explicit consumer. `claude_metrics/` is its Claude Code counterpart over one session transcript, with its own token type because Claude's `input` excludes the cache reads and writes Codex's includes: a pure projection (`records`) keeping only labels, counts and bounded ids, and a reducer that counts a message once however many content blocks repeat it, forgets the context's size at a compaction while keeping lifetime totals, and accumulates a plan from `TodoWrite`'s full-list reports or `TaskCreate`/`TaskUpdate`'s own `from`/`to` transitions, whichever a session uses. Its `stream`/`watch` follow one exact transcript through `tail/`; a transcript has no header, so its identity is its name, `<claude_home>/projects/…/<session>.jsonl`, with every record's `sessionId` checked too. The agent HUD subscribes through `app::agent_hud`, and only spyc's own MCP server keeps its name in a tool label; `examples/claude_metrics.rs` observes one named transcript. `claude_statusline.rs` is the bounded projection of Claude Code's statusline input (the context window and the plan's rate-limit windows) that `spyc --statusline` (`mcp::statusline`) forwards over the internal `spyc/statusline` method, never an MCP tool. `codex_recovery.rs` folds bounded question lifecycle metadata in the Model, requiring pane/session/turn/call correlation before completion can restore working. `status_hook.rs` bounds and sanitizes lifecycle metadata for diagnostics, excluding hook arguments and response content; of Claude's `Stop` lists of pending background work it keeps only their entry counts. `waiting.rs` is the pure precedence of a live `waiting` report over the hook `done`s that follow it, plus the turn a hook opens and ends, inside which an agent's own `waiting` is held until the turn ends.
- **`src/archive/`** — archive browsing: a mount is an **index, not a directory**, so entering a large zip writes zero bytes. `mod` (format detect), `index` (the entry table + the name normalization that makes a zip-slip *name* structurally impossible), `scan` (oddities → `Capability`), `budget` (mount/confirm/refuse), `journal` (pending changes), `listing` (index + journal → an `fs::Listing`), `read` (index a seekable container, stream a compressed tar into staging, materialize one member — **extraction never traverses a symlink, and a link target is judged the same way**), `write` (the repack: temp file, verified by reading it back, then an atomic rename). Containment rules, the permissions split, and the streaming trade: ARCHITECTURE.md → "Archive mounts". Design history in [`docs/archive/ARCHIVE_BROWSING_PLAN.md`](docs/archive/ARCHIVE_BROWSING_PLAN.md).
- **`crates/spyc-vt-sys/`** — the FFI to libghostty-vt, at a spyc-owned pinned ghostty commit, with prebuilt static archives vendored per target. Everything C has one home here: the pin (`pin.rs`, plus the bump policy — a pin bump owes a full harness re-run), the bindgen-generated bindings (`bindings.rs`, checked in so neither bindgen nor libclang is a build dependency; ~371 compile-time layout assertions catch ABI drift), the row-budget→limits derivation (`scrollback.rs` — **rows are the UX contract, bytes are a safety valve**, and neither may be left at its default), and `build.rs`, which verifies each archive's SHA-256 before it links. Five targets, not four: CI's test host (`x86_64-unknown-linux-gnu`) is not a release artifact but still needs an archive. Rationale in the crate's README; posture in SECURITY.md and deny.toml.
- **`src/keymap/`** — `Action` enum (`action.rs` — the full vocabulary of behaviours, each tagged with its `tier()`), resolver, DSL parser, default bindings.
Expand Down
11 changes: 11 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ with the first pending repaint rather than moving with every output event;
continuous modal redraws therefore cannot postpone the scan indefinitely.
Other agents retain the trailing quiet-window debounce.

Codex's complete default async-question editor or collapsed positive question
summary supplies the same temporary attention, through `codex_questions`.
Its posting tool returns immediately, so neither that completion nor Enter alone
establishes an answer. The current native view is the signal: answering, skipping,
queuing, follow-up input or native resolution can clear it. A semantic block keeps
precedence, but the visible wait stays cached behind it because a later working or
done report need not repaint the child. Clearing the view retires only this fallback;
the latest live report resumes, and expiry still applies. Unknown/customized or
clipped footers produce no guess. No question content or answer ids are retained,
and no new hook, subscription or idle timer is introduced.

## Update model: Elm-architecture (MVU)

spyc follows the Elm/Model-View-Update pattern. The structural migration
Expand Down
6 changes: 5 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,11 @@ protocol, no image protocols, no OSC 52 clipboard.
Known command, file-edit, MCP-tool and network forms require their default
choices and complete footer at the viewport bottom; diagnostics identify
this `scrape-fallback`. Missing required text and other forms produce no guess. Codex's `Interrupt` hook
reports `idle` after cancellation. Its `request_user_input` hooks report
reports `idle` after cancellation. Complete default async-question editors
and collapsed question summaries also show `blocked` while the tab needs an
answer, even if Codex keeps working. Clearing the native view retires that
fallback and resumes the latest live report; clipped/customized views are
not guessed. Its `request_user_input` hooks report
`blocked` on start and recover `working` only for the same pending
pane/session/turn/call; other waits stay blocked. Project trust and `/hooks`
review are separate from spyc's consent; spyc never auto-approves hooks. **agy** (Antigravity)
Expand Down
31 changes: 22 additions & 9 deletions docs/AGENT_ORCHESTRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,28 @@ Real-CLI native question and quiet-after-answer checks passed through the
automated TUI harness with already trusted hooks. Fresh hook trust onboarding
remains a separate acceptance case; see `docs/HARNESS.md` for the driver.

`request_user_input_async` is a separate question path. Its immediate completion
acknowledges posting a question; it does not establish that the user answered.
These native question hooks do not instrument that path. The
[`0.160.1` async handler](https://github.com/openai/codex/blob/rust-v0.160.1/codex-rs/core/src/tools/handlers/request_user_input_async.rs)
posts an async message and immediately returns `accepted`; the
[TUI reply parser](https://github.com/openai/codex/blob/rust-v0.160.1/codex-rs/tui/src/async_question_reply.rs)
handles the answer as a later user message. A blocked latch tied to the tool's
completion would misrepresent an agent that continues working. Async-answer
lifecycle coverage remains open in A3.
`request_user_input_async` posts a question and returns immediately; its tool
completion does not establish that the user answered. The
[`0.162.1` async handler](https://github.com/openai/codex/blob/rust-v0.162.1/codex-rs/core/src/tools/handlers/request_user_input_async.rs)
and [question editor](https://github.com/openai/codex/blob/rust-v0.162.1/codex-rs/tui/src/bottom_pane/async_questions/state.rs)
keep posting, local answer delivery, skipping and native resolution separate.

spyc recognizes the default native question editor's complete submit/skip/navigation
footer, or its collapsed positive question count and answer hint, at the current
viewport bottom. The dot reads `blocked` with `awaiting async question answer`;
this means the tab needs you, even while Codex continues working. `:why-status`
and `:activity dump` label the source `scrape-fallback`. The latest semantic report
remains stored and resumes when the native view clears. An independent semantic
block keeps precedence and is not retired by a disappearing async view. Expired
reports are not revived, and another pane's view cannot supply attention.

This follows visible attention, not semantic proof of an answer. Native skipping,
queued answers, follow-up input, turn completion or auto-resolution can clear the
view. Quoted history above the composer, clipped views and customized shortcut
labels produce no guess. The existing dirty-viewport scan supplies the bounded
delay; no new hook, question-content store or idle polling is added. Exact-version
[fixture provenance](../tests/fixtures/codex-questions/README.md) and controlled
PTY tests are separate from native CLI acceptance.

### Duplicate Codex hook sources

Expand Down
53 changes: 44 additions & 9 deletions docs/HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,50 @@ Real-CLI native question and quiet-after-answer checks passed through the
automated TUI harness with already trusted hooks. Fresh hook trust onboarding
remains a separate acceptance case.

`request_user_input_async` is a separate question path. Its immediate completion
acknowledges posting a question; it does not establish that the user answered.
These native question hooks do not instrument that path. The
[`0.160.1` async handler](https://github.com/openai/codex/blob/rust-v0.160.1/codex-rs/core/src/tools/handlers/request_user_input_async.rs)
posts an async message and immediately returns `accepted`; the
[TUI reply parser](https://github.com/openai/codex/blob/rust-v0.160.1/codex-rs/tui/src/async_question_reply.rs)
handles the answer as a later user message. A blocked latch tied to the tool's
completion would misrepresent an agent that continues working. Async-answer
lifecycle coverage remains open in A3.
`request_user_input_async` posts a question and returns immediately; its tool
completion does not establish that the user answered. The
[`0.162.1` async handler](https://github.com/openai/codex/blob/rust-v0.162.1/codex-rs/core/src/tools/handlers/request_user_input_async.rs)
and [question editor](https://github.com/openai/codex/blob/rust-v0.162.1/codex-rs/tui/src/bottom_pane/async_questions/state.rs)
keep posting, local answer delivery, skipping and native resolution separate.

spyc recognizes the default native question editor's complete submit/skip/navigation
footer, or its collapsed positive question count and answer hint, at the current
viewport bottom. The dot reads `blocked` with `awaiting async question answer`;
this means the tab needs you, even while Codex continues working. `:why-status`
and `:activity dump` label the source `scrape-fallback`. The latest semantic report
remains stored and resumes when the native view clears. An independent semantic
block keeps precedence and is not retired by a disappearing async view. Expired
reports are not revived, and another pane's view cannot supply attention.

This follows visible attention, not semantic proof of an answer. Native skipping,
queued answers, follow-up input, turn completion or auto-resolution can clear the
view. Quoted history above the composer, clipped views and customized shortcut
labels produce no guess. The existing dirty-viewport scan supplies the bounded
delay; no new hook, question-content store or idle polling is added. Exact-version
[fixture provenance](../tests/fixtures/codex-questions/README.md) and controlled
PTY tests are separate from native CLI acceptance.

For an async-attention test, stay in Default mode and ask Codex to post two
`request_user_input_async` questions, then continue with a quiet `sleep 30`.
While the complete question view is visible, check the red dot and
`awaiting async question answer` in `:why-status`. Collapse and reopen the default
question editor; answer or skip one question and check the remaining one still
needs attention. After the last native view clears, the latest live semantic
status resumes. Repeat in two tabs: clearing one leaves the other waiting.
A customized/clipped editor or native auto-resolution is a separate case; no
semantic answer claim is made from a disappearing view.

The controlled terminal replay uses exact-version fixtures and pane-bound MCP
reports, not native Codex or hook trust:

```sh
python3 scripts/codex-async-question-smoke.py --binary /absolute/path/to/spyc --output /tmp/spyc-async-question-run
```

It checks expanded/collapsed views, a semantic report transition without a child
repaint, two-pane isolation, restoration of working/done and zero idle draws.
Every run uses a new named Ghostty session and private home/state, closes only
that session, and saves the binary hash, captures and result.

For a native-hook test, restart Codex after any restart-needed diagnostic and
review the new hooks in `/hooks`. Enter `/plan` as an actual CLI slash command
Expand Down
25 changes: 21 additions & 4 deletions docs/drafts/CODEX_SUPPORT_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Codex Support and Session HUD Plan

**Status:** Track A implementation in progress, delivered through local-build
acceptance before integration. The new HUD is proposed follow-up scope; its
design review and telemetry spike remain open. This document does not change
the accepted release gates.
**Status:** The core A1–A6 changes and B1–B3 HUD delivery have shipped. Deferred
native acceptance and remaining A3 onboarding/attention cases stay open.
Async-question visible attention is implemented with native acceptance pending;
semantic async-answer events and additional approval forms remain separate gaps.
Historical assessments below preserve their original evidence and CLI versions.

**Evidence date:** 2026-10-05. Assessment of the release-candidate code and current
`main`, against the locally installed Codex CLI 0.160.0. These are historical
Expand Down Expand Up @@ -636,6 +637,22 @@ Two tracks keep the RC manageable:
against the old implementation; final artifact acceptance is recorded with the
PR. Native hook execution remains separate from these file-edit tests.

### Async-question visible attention

The visible-attention follow-up recognizes Codex `0.162.1`'s complete default async-question
editor and collapsed summary through the existing bounded dirty-viewport scan.
An unanswered visible view requires attention while preserving the latest semantic
working/done report. Answering, skipping, queuing or native resolution retires
only the fallback when the view clears; independent semantic blocks and report
expiry remain intact. No new hook or content-bearing lifecycle store is added.

Exact-version upstream snapshots supply the fixtures. Pure scanner and real-PTY
App regressions reproduce missing attention and a semantic transition discarding
an unanswered view. Native CLI capture was attempted twice, but its filesystem
sandbox helper failed during startup before a question could be shown. Native
acceptance remains distinct from fixture/replay coverage. Fresh trust onboarding,
semantic async-answer events and other approval forms remain A3 follow-ups.

### Baseline

The shared foundation already works: MCP context/search/git tools are attributed
Expand Down
Loading
Loading