feat(app-server): render live Spine agent trees - #3
Conversation
# Summary GitHub's latest-release endpoint can return compact, single-line JSON. The standalone installer treated release metadata as line-oriented text, so those responses could make asset lookup fail even though the requested assets were present. The regression was introduced by [#31056](openai/codex#31056). That change reused the `/releases/latest` metadata response for both version resolution and asset lookup, exposing the existing formatting-sensitive asset parser to compact responses from that endpoint. This change parses the release metadata once with a one-pass POSIX awk scanner. The scanner tracks JSON strings and nesting, extracts the root release tag plus direct asset name/digest pairs, and produces the same result regardless of whitespace or object field order. It uses POSIX `fold` to bound awk record sizes so compact responses stay fast across awk implementations. Fixes #31520. ## Changes - replace line-oriented release metadata matching with structure-aware parsing - reuse the parsed metadata for latest-version and asset-digest lookup - add regression coverage for compact JSON, reordered fields, nested decoys, and JSON-looking release text ## Design decisions - Keep the installer dependency-free by using standard POSIX tools already required by the shell installer. - Parse only the GitHub release fields the installer consumes, in one pass, instead of vendoring a general JSON library. - Preserve asset-object boundaries so nested or string-encoded `name` and `digest` fields cannot be mistaken for release assets. ## Testing - Tests: focused installer suite locally and on Linux. - Smoke tests: real pretty and compact GitHub release metadata, latest-release resolution, and checksum-asset selection. - Portability: macOS awk plus Linux gawk, mawk, and nawk. - Stress coverage: randomized formatting and field order, adversarial nested/string content, and a synthetic 2,000-asset compact response.
Installer needs to symlink code-mode-host next to codex on install.
…ernal process (#31899) ## Why Not every Codex distribution currently includes the `codex-code-mode-host` companion binary. Enabling the process-host feature should not make code mode unavailable on those surfaces while packaging support is being completed. ## What changed - Fall back to an in-process code-mode session only when spawning the companion binary returns `io::ErrorKind::NotFound`. - Keep permission, handshake, timeout, and other host failures visible instead of silently falling back. - Store the provider's owned-process/in-process choice as one enum-backed state so later sessions reuse the fallback decision. - Preserve the underlying spawn `io::Error` while retaining the host path in the displayed error. - Update provider, `CodeModeService`, and end-to-end coverage to verify successful fallback execution. ## Test plan - `just test -p codex-code-mode` - `just test -p codex-core missing_process_host`
## Summary
- Revert c27a909508e09105c80cc162e250e623a96a8f82 ("Update auto review
prompting") in full on the `release/0.144` branch.
- Restore the prior Guardian policy template, review request layout, and
tool specifications.
- Restore the corresponding Guardian tests and snapshots.
## Why
The auto-review prompting update needs to be rolled back from the 0.144
release line. This PR contains the direct, conflict-free Git cherry-pick
with no additional product changes.
## Validation
- `just fmt`
- `just test -p codex-core guardian::tests` (58 passed)
- `just test -p codex-core` (2,806 passed; 137 environment-sensitive
failures caused by sandbox restrictions on local ports and process
operations)
- `git show --check`
- Add an `auto_review.policy` field to model catalog messages. - Use the selected Guardian model's catalog policy for review-session instructions, while preserving the precedence of `guardian_policy_config` and falling back to the built-in policy when neither is present. - Preserve auto-review messages when model instruction overrides remove catalog instruction templates. - Cover configured-policy precedence, explicit empty catalog policies, catalog-message preservation, and propagation of the catalog policy into a prewarmed Guardian session. GitOrigin-RevId: 26b61ae2958ea8325a64834dcf91f47e140d74b3
## Summary Cherry-picks the seven commits from [openai/codex-internal#1942](openai/codex-internal#1942) onto `release/0.144`. This backport: 1. enables dangerous-command detection in danger-full-access mode 2. expands literal Bash parsing so additional forced `rm` forms are detected 3. returns a specific rejection reason to the model when a dangerous command is denied The commits applied without conflicts or release-only changes. `git range-diff` confirms every cherry-picked patch matches the source PR exactly. ## Validation - `just test -p codex-shell-command` (141 passed) - `just test -p codex-core exec_policy` (107 passed) - `just test -p codex-core` outside the sandbox (2,947 passed; 4 unrelated environment/setup failures) - 3 RMCP tests could not locate the test-only `test_stdio_server` binary - 1 user-shell environment test observed the tool runner's mandated `CODEX_SANDBOX_NETWORK_DISABLED=1` - `just fix -p codex-core` - `just fix -p codex-shell-command` - `just fmt` - `git diff --check`
There was a problem hiding this comment.
Pull request overview
This PR adds a live-only Spine tree card for Codex Desktop by introducing an internal, env-gated MCP “Spine UI” surface (__codex_internal_spine_tree_ui__) and wiring app-server thread/listener state so live Spine tree/spawn progress can be rendered per turn without persisting the UI carrier into rollout history. It also refines Core Spine spawn progress semantics (more truthful, ordered lifecycle reporting) and adds extensive unit/integration coverage.
Changes:
- Core: reworks
spine.spawnprogress/receipt handling to preserve initial/observed statuses, serialize progress emission, and improve terminal-status handling; adds new spawn-progress integration coverage. - App-server: adds Spine UI state tracking, listener lifecycle hooks, and an internal MCP server/tool/resource that serves a compiled-in
tree.htmlwidget and emits live-only item notifications. - Tests: adds dedicated app-server suite tests for live-only behavior and MCP surface isolation/collisions, plus expanded Core spawn tests.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| codex-rs/app-server/BUILD.bazel | Adds embedded tree.html as compile-time data for Bazel builds. |
| codex-rs/app-server/src/lib.rs | Registers the new spine_ui module. |
| codex-rs/app-server/src/message_processor.rs | Passes ThreadStateManager to MCP processor to enable Spine UI interception/state. |
| codex-rs/app-server/src/request_processors/mcp_processor.rs | Intercepts reserved internal MCP resource/tool and injects reserved server/status handling. |
| codex-rs/app-server/src/request_processors/thread_lifecycle.rs | Hooks Spine UI lifecycle into listener start/event/exit/failure and command handling. |
| codex-rs/app-server/src/spine_ui.rs | Defines Spine UI state model, revisioning, activation detection, and helpers. |
| codex-rs/app-server/src/spine_ui/listener.rs | Converts Core events + manager commands into Spine UI state updates and live notifications. |
| codex-rs/app-server/src/spine_ui/mcp.rs | Implements internal MCP server/tool/resource, gating, reserved-name checks, and activation parsing. |
| codex-rs/app-server/src/spine_ui/render.rs | Serializes Spine UI state into the structured widget payload schema. |
| codex-rs/app-server/src/spine_ui/tree.html | Embedded widget UI: renders live Spine task/agent tree and handles updates. |
| codex-rs/app-server/src/spine_ui_tests.rs | Unit tests for Spine UI activation, rendering, terminal behavior, dedupe, and invariants. |
| codex-rs/app-server/src/thread_state.rs | Adds Spine UI runtime fields/commands and clears transient UI state on listener changes. |
| codex-rs/app-server/src/thread_state_spine_ui.rs | Per-thread Spine UI runtime: active/pending/terminal state, mounting, revisions, completion. |
| codex-rs/app-server/src/thread_state_spine_ui_manager.rs | Cross-thread routing/forwarding, generation handling, and route cleanup logic. |
| codex-rs/app-server/src/thread_state_spine_ui_tests.rs | Large state-machine test suite covering routing, listener replacement, ABA, rollback/reconnect, etc. |
| codex-rs/app-server/tests/suite/v2/mod.rs | Registers new Spine UI integration test modules. |
| codex-rs/app-server/tests/suite/v2/spine_ui_live.rs | Integration coverage for live-only item notifications + cold-resume rebuild behavior. |
| codex-rs/app-server/tests/suite/v2/spine_ui_mcp.rs | Integration coverage for MCP surface enable/disable, resource/tool behavior, and name collisions. |
| codex-rs/core/src/spine/spawn.rs | Implements ordered spawn progress emitter + preserves initial/terminal statuses and receipt mapping. |
| codex-rs/core/src/spine/spawn_tests.rs | Updates unit tests for terminal status watching and aborted-status mapping. |
| codex-rs/core/tests/suite/spine_spawn.rs | Adds integration test for Running progress reporting without child Spine nodes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| progress_emitter.emit_initial().await; | ||
| for (ordinal, _, _, status) in &live { | ||
| progress_emitter | ||
| .emit_status(*ordinal, flat_tasks[*ordinal].0, status.clone()) | ||
| .await; | ||
| } |
| function validTreePayload(value) { | ||
| const pending = [{ value, isSubtree: false }]; | ||
| while (pending.length) { | ||
| const candidate = pending.pop(); | ||
| const current = candidate.value; | ||
| if (!current || typeof current !== "object" || current.schemaVersion !== 1) return false; | ||
| if (!Number.isSafeInteger(current.uiRevision) || current.uiRevision < 0) return false; | ||
| if (candidate.isSubtree && typeof current.threadId !== "string") return false; | ||
| if (!current.snapshot || typeof current.snapshot !== "object" || !Array.isArray(current.snapshot.nodes)) return false; | ||
| if (current.snapshot.nodes.some((node) => !node || typeof node !== "object" | ||
| || typeof node.nodeId !== "string" | ||
| || (node.summary != null && typeof node.summary !== "string") | ||
| || (node.spawnOutcome != null && !["completed", "errored", "aborted"].includes(node.spawnOutcome)))) return false; | ||
| const spawnCalls = current.spawnCalls || []; | ||
| const agentSubtrees = current.agentSubtrees || []; | ||
| if (!Array.isArray(spawnCalls) || !Array.isArray(agentSubtrees)) return false; | ||
| if (spawnCalls.some((call) => !call || typeof call !== "object" || !Array.isArray(call.tasks) | ||
| || call.tasks.some((task) => !task || typeof task !== "object" | ||
| || !["pending", "running", "interrupted", "completed", "error", "shutdown", "not_found"].includes(task.status)))) return false; | ||
| agentSubtrees.forEach((subtree) => pending.push({ value: subtree, isSubtree: true })); | ||
| } | ||
| return true; | ||
| } |
|
This is actually PR #3 (feat: render live Spine agent trees) — looks like it was filed as an issue. The implementation is complete with the |
6c92152 to
2cc9644
Compare
Summary
This change adds a live-only Spine tree card for Codex Desktop.
It uses the existing MCP App/widget surface and keeps the feature isolated behind
the process-level
CODEX_SPINE_APP_UIenvironment variable.Motivation
Spine events already describe the active reasoning tree, nested agent work, and
spawn progress, but Desktop did not have a live per-turn visual surface for that
state. The implementation provides a stable card identity and structured widget
content without persisting the internal UI carrier into rollout history.
What changed
Core Spine spawn lifecycle
PendingInit,Running, and terminalstates when the child has a real observable lifecycle.
Running, so pending and fast-terminal children remain truthful.InterruptedorShutdownstatus when the finalspawn receipt collapses both cases into
Aborted; only results without a morespecific observed status use
Interruptedas the conservative fallback.memory salvage.
failures, capacity rejection, nested spawn, receipt ordering, and teardown.
App-server live UI state
while preserving normal task nesting and settled-result reconciliation.
restored results, and ordinary tasks share one chronological tree order.
pending completion, and stale event invalidation.
subtree, rejects queued projections from the old listener, and accepts the new
listener's state.
using the authoritative thread generation, so a stale listener cannot remove
the incoming or outgoing routes needed by its replacement.
ThreadStateArc identity and listener generation during routecleanup, so an unloaded listener cannot delete routes created by a reloaded
instance that reused the same generation number.
current turn's first snapshot, preventing inherited history from appearing
inside a child agent row.
unexpectedly, without affecting normal listener replacement.
event stream fails, preserving terminal
interruptedorerrorstate in thecumulative tree so later live cards do not inherit
runningforever.Abortedresult asInterruptedwithoutoverwriting an already observed
Shutdownor any other terminal agent state.Internal MCP App surface
__codex_internal_spine_tree_ui__only whenCODEX_SPINE_APP_UI=1,true, oron.spine_treetool and the embeddedui://spine/tree.htmlresource.compile-time data.
spine-ui-{turn_id}carrier identity so Desktop can upsert liveupdates for one turn.
Completedand aborted or listener-failed turns asFailed, with a structured terminal outcome and reason.terminal carrier cannot leave its former active node displayed as
Current.Abortedresult without live status metadata asInterrupted, soevent ordering and process restart cannot manufacture a false
Stoppedstate.not for the reserved internal server/tool/resource.
Code Mode and exec integration
Code Mode can invoke Spine controls inside an outer
execcall. Those controlsarrive as a
CustomToolCallOutputcarrier rather than as a directFunctionCall. Previously, the app-server activation check saw only directfunction calls, so a nested
spine.closecould update Core state without openingthe live UI card for that turn.
The activation check now:
spine.open,spine.next,spine.close, andspine.spawnbehavior.
spine.code_mode.output.v1marker.nested_spine_callscontainsopen,next,close, orspawn.trim-only carriers, unmarked output, wrongschema versions, malformed JSON, and non-text bodies.
This parser is intentionally local to the app-server UI layer. Core remains the
source of truth for carrier validation and execution; the app-server check only
decides whether the live card should be mounted. Client-side injection of the
reserved carrier marker remains rejected.
File-by-file change map
The commit changes 21 files. The table below separates Base Codex integration
points from Spine-owned implementation and test files so future rebases can
review the real conflict surface directly.
codex-rs/app-server/BUILD.bazeltree.htmlas compile-time data.codex-rs/app-server/src/lib.rsspine_uimodule.codex-rs/app-server/src/message_processor.rsThreadStateManagerwith the MCP processor.codex-rs/app-server/src/request_processors/mcp_processor.rscodex-rs/app-server/src/request_processors/thread_lifecycle.rscodex-rs/app-server/src/spine_ui.rscodex-rs/app-server/src/spine_ui/listener.rscodex-rs/app-server/src/spine_ui/mcp.rscodex-rs/app-server/src/spine_ui/render.rscodex-rs/app-server/src/spine_ui/tree.htmlcodex-rs/app-server/src/spine_ui_tests.rscodex-rs/app-server/src/thread_state.rscodex-rs/app-server/src/thread_state_spine_ui.rscodex-rs/app-server/src/thread_state_spine_ui_manager.rscodex-rs/app-server/src/thread_state_spine_ui_tests.rscodex-rs/app-server/tests/suite/v2/mod.rscodex-rs/app-server/tests/suite/v2/spine_ui_live.rscodex-rs/app-server/tests/suite/v2/spine_ui_mcp.rscodex-rs/core/src/spine/spawn.rscodex-rs/core/src/spine/spawn_tests.rscodex-rs/core/tests/suite/spine_spawn.rsThe 10 modified Base files are the integration conflict surface; the 11 added
files are Spine-owned implementation or tests. No TUI or app-server protocol
crate files are changed.
Compatibility and scope
CODEX_SPINE_APP_UI=offor an unset variable leaves the existing behavior unchanged.carrier items.
Tests and checks
Passed on the current commit:
cargo +1.95.0 test -p codex-app-server --lib spine_ui: 56 passed.cargo +1.95.0 test -p codex-app-server --test all spine_ui: 5 passed.cargo +1.95.0 test -p codex-core spine::spawn: 20 passed.$env:RUST_MIN_STACK='16777216'; cargo +1.95.0 test -p codex-core --test all spine_spawn -- --nocapture --test-threads=1: 11 passed.cargo +1.95.0 check -p codex-app-server -p codex-core: passed.cargo +1.95.0 clippy -p codex-app-server --lib --no-deps: passed.just fmt: passed.git diff --check: passed.codex-spine-83d7c2e9.exe, an x86_64 debugbinary on the current commit
(
SHA-256: A57144E50D7D63ADC854F5A274AA04E8E05803F697C4AA073B1F06DE0A66A761).cargo +1.95.0 build --manifest-path codex-rs/Cargo.toml --release -p codex-cli:passed on the current commit and produced an arm64 release binary
(
SHA-256: 41460f804fa0e3ab8258ba4afa7ce3fc093054a80359c74e1f5386da6035e95b).bash -n; their binary-hash-isolatedCODEX_SQLITE_HOMEinitializes cleanly with all SQLite integrity checks passing.unmarked, wrong schema, malformed, and non-text cases.
its following snapshot, while a trim carrier only seeds cumulative state.
forwarding, failed listener terminalization, aborted-turn outcomes, and truthful
fast-terminal child status.
InterruptedandShutdownremain distinct,while an
Abortedresult without live status metadata falls back toInterruptedand cannot overwrite a known terminal state.settled agents replace their result rows in place without duplication or
chronological reordering.
progress arrives before the current turn's first tree snapshot.