Commit Graph
8463 Commits
Author SHA1 Message Date
Michael Bolin b73c4b50a2 fix: make realtime conversation flake test order-insensitive (#12475)
## Why

`codex-core::all` has a flaky test,
`suite::realtime_conversation::conversation_start_audio_text_close_round_trip`,
that assumes a fixed ordering between `conversation.item.create` and
`response.input_audio.delta` requests.

That ordering is not guaranteed: realtime text and audio input are
forwarded through separate queues and a background task, so either
request can be observed first while still being correct behavior.

## What Changed

- Updated the assertion in
`codex-rs/core/tests/suite/realtime_conversation.rs` to compare the two
observed request types order-independently.
- Kept the existing checks that `session.create` is sent first and that
exactly two follow-up requests are recorded.

## Verification

- Re-ran `cargo test -p codex-core --test all
conversation_start_audio_text_close_round_trip` 10 times locally.
2026-02-21 17:06:35 -08:00
Ahmed Ibrahim 5e505ff877 Revert "Route inbound realtime text into turn start or steer" (#12479)
Reverts openai/codex#12469
2026-02-21 15:46:03 -08:00
Ahmed Ibrahim 031d701705 Route inbound realtime text into turn start or steer (#12469)
- Route inbound realtime websocket text into normal user input handling
so it steers an active turn or starts a new one
2026-02-21 15:45:27 -08:00
Felipe Coury 2ba2c57af4 fix(tui): preserve URL clickability across all TUI views (#12067)
## Problem

Long URLs containing `/` and `-` characters are split across multiple
terminal lines by `textwrap`'s default hyphenation rules. This breaks
terminal link detection: emulators can no longer identify the URL as
clickable, and copy-paste yields a truncated fragment. The issue affects
every view that renders user or agent text — exec output, history cells,
markdown, the app-link setup screen, and the VT100 scrollback path.

A secondary bug compounds the first: `desired_height()` calculations
count logical lines rather than viewport rows. When a URL overflows its
line and wraps visually, the height budget is too small, causing content
to clip or leave gaps.

Here is how the complete URL is interpreted by the terminal before
(first line only) and after (complete URL):

| Before | After |
|---|---|
| <img width="777" height="1002" alt="Screenshot 2026-02-17 at 7 59 11
PM"
src="https://github.com/user-attachments/assets/193a89a0-7e56-49c5-8b76-53499a76e7e3"
/> | <img width="777" height="1002" alt="Screenshot 2026-02-17 at 7 58
40 PM"
src="https://github.com/user-attachments/assets/0b9b4c14-aafb-439f-9ffe-f6bba556f95e"
/> |

## Mental model

The TUI now treats URL-like tokens as atomic units that must never be
split by the wrapping engine. Every call site that previously used
`word_wrap_*` has been migrated to `adaptive_wrap_*`, which inspects
each line for URL-like tokens and switches wrapping strategy
accordingly:

- **Non-URL lines** follow the existing `textwrap` path unchanged (word
boundaries, optional indentation, hyphenation).
- **URL-only lines** (with at most decorative markers like `│`, `-`,
`1.`) are emitted unwrapped so terminal link detection works; ratatui's
`Wrap { trim: false }` handles the final character wrap at render time.
- **Mixed lines** (URL + substantive non-URL prose) flow through
`adaptive_wrap_line` so prose wraps naturally at word boundaries while
URL tokens remain unsplit.

Height measurement everywhere now delegates to
`Paragraph::line_count(width)`, which accounts for the visual row cost
of overflowed lines. This single source of truth replaces ad-hoc line
counting in individual cells.

For terminal scrollback (the VT100 path that prints history when the TUI
exits), URL-only lines are emitted unwrapped so the terminal's own link
detector can find them. Mixed URL+prose lines use adaptive wrapping so
surrounding text wraps naturally. Continuation rows are pre-cleared to
avoid stale content artifacts.

## Non-goals

- Full RFC 3986 URL parsing. The detector is a conservative heuristic
that covers `scheme://host`, bare domains (`example.com/path`),
`localhost:port`, and IPv4 hosts. IPv6 (`[::1]:8080`) and exotic schemes
are intentionally excluded from v1.
- Changing wrapping behavior for non-URL content.
- Reflowing or reformatting existing terminal scrollback on resize.

## Tradeoffs

| Decision | Upside | Downside |
|----------|--------|----------|
| Heuristic URL detection vs. full parser | Fast, zero-alloc on the hot
path; conservative enough to reject file paths like `src/main.rs` |
False negatives on obscure URL formats (they get split as before) |
| Adaptive (three-path) wrapping | Non-URL lines are untouched — no
behavior change, no perf cost; mixed lines wrap prose naturally while
preserving URLs | Three wrapping strategies to reason about when
debugging layout |
| Row-based truncation with line-unit ellipsis | Accurate viewport
budget; stable "N lines omitted" count across terminal widths |
`truncate_lines_middle` is more complex (must compute per-line row cost)
|
| Unwrapped URL-only lines in scrollback | Terminal emulators detect
clickable links; copy-paste gets the full URL | TUI and scrollback
formatting diverge for URL-only lines |
| Default `desired_height` via `Paragraph::line_count` | DRY — most
cells inherit correct measurement | Cells with custom layout must
remember to override |

## Architecture

```mermaid
flowchart TD
    A["adaptive_wrap_*()"] --> B{"line_contains_url_like?"}
    B -- No URL tokens --> C["word_wrap_line<br/>(textwrap default)"]
    B -- Has URL tokens --> D{"mixed URL + prose?"}
    D -- "URL-only<br/>(+ decorative markers)" --> E["emit unwrapped<br/>(terminal char-wraps)"]
    D -- "Mixed<br/>(URL + substantive text)" --> F["adaptive_wrap_line<br/>(AsciiSpace + custom WordSplitter)"]
    C --> G["Paragraph::line_count(w)<br/>(single height truth)"]
    E --> G
    F --> G
```

**Changed files:**

| File | Role |
|------|------|
| `wrapping.rs` | URL detection heuristics, mixed-line detection,
`adaptive_wrap_*` functions, custom `WordSplitter` |
| `exec_cell/render.rs` | Row-aware `truncate_lines_middle`, adaptive
wrapping for command/output display |
| `history_cell.rs` | Migrate all cell types to `adaptive_wrap_*`;
default `desired_height` via `Paragraph::line_count` |
| `insert_history.rs` | Three-path scrollback wrapping (unwrapped
URL-only, adaptive mixed, word-wrapped text); continuation row clearing
|
| `app_link_view.rs` | Adaptive wrapping for setup URL; `desired_height`
via `Paragraph::line_count` |
| `markdown_render.rs` | Adaptive wrapping in `finish_paragraph` |
| `model_migration.rs` | Viewport-aware wrapping for narrow-pane
markdown |
| `pager_overlay.rs` | `Wrap { trim: false }` for transcript and
streaming chunks |
| `queued_user_messages.rs` | Migrate to `adaptive_wrap_lines` |
| `status/card.rs` | Migrate to `adaptive_wrap_lines` |

## Observability

- **Ellipsis message** in truncated exec output reports omitted count in
logical lines (stable across resize) rather than viewport rows
(fluctuates).
- URL detection is deterministic and stateless — no hidden caching or
memoization to go stale.
- Height mismatch bugs surface immediately as visual clipping or gaps;
the `Paragraph::line_count` path is the same code ratatui uses at render
time, so measurement and rendering cannot diverge.

## Tests

26 new unit tests across 7 files, covering:

- **URL integrity**: assert a URL-like token appears on exactly one
rendered line (not split across two).
- **Height accuracy**: compare `desired_height()` against
`Paragraph::line_count()` for URL-containing content.
- **Row-aware truncation**: verify ellipsis counts logical lines and
output fits within the row budget.
- **Scrollback rendering**: VT100 backend tests confirm prefix and URL
land on the same row; continuation rows are cleared; mixed URL+prose
lines wrap prose while preserving URL tokens.
- **Mixed URL+prose detection**: `line_has_mixed_url_and_non_url_tokens`
correctly distinguishes lines with substantive non-URL text from lines
with only decorative markers alongside a URL.
- **Heuristic correctness**: positive matches (`https://...`,
`example.com/path`, `localhost:3000/api`, `192.168.1.1:8080/health`) and
negative matches (`src/main.rs`, `foo/bar`, `hello-world`).

## Risks and open items

1. **URL-like tokens in code output** (e.g. `example.com/api` inside a
JSON blob) will trigger URL-preserving wrap on that line. This is
acceptable — the worst case is a slightly wider line, not broken output.
2. **Very long non-URL tokens on a URL line** can only break at
character boundaries (the custom splitter emits all char indices for
non-URL words). On extremely narrow terminals this could overflow, but
narrow terminals already degrade gracefully.
3. **No IPv6 support** — `[::1]:8080/path` will be treated as a non-URL
and may get split. Can be added later without API changes.

Fixes #5457
2026-02-21 15:31:41 -08:00
Michael Bolin 66d5d34e6e core: preserve constrained approval/sandbox policies in TurnContext (#12473) 2026-02-21 14:40:24 -08:00
Michael Bolin f33ac830aa fix: make skills loader tests hermetic with ~/.agents skills (#12474) 2026-02-21 14:40:13 -08:00
Eric Traut 3586fcb802 Improve token usage estimate for images (#12419)
Fixes #11845.

Adjust context/token estimation for inline image `data:*;base64,...`
URLs so we
do not count the raw base64 payload as model-visible text.

What changed:
- keep the existing JSON-length estimator as the baseline
- detect only inline base64 `data:` image URLs in message and
function-call
  output content items
- subtract only the base64 payload bytes (preserving data URL prefix +
JSON
  overhead)
- add a fixed per-image estimate of 340 bytes (~85 tokens at the repo’s
  4-bytes/token heuristic)

This avoids large overestimates from MCP image tool outputs while
leaving normal
image URLs (`https://`, `file://`, non-base64 `data:` URLs) unchanged.

Tests:
- message image data URL estimate regression
- function-call output image data URL estimate regression
- non-base64 image URLs unchanged
- non-base64 `data:` URLs unchanged
- `data:application/octet-stream;base64,...` adjusted
- multiple inline images apply multiple fixed costs
- text-only items unchanged
2026-02-21 14:25:36 -08:00
Michael Bolin a3c8b57def merge commit for archive created by Sapling 2026-02-21 13:36:48 -08:00
Michael Bolin 965ad4f99b ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 13:36:03 -08:00
Michael Bolin cbf06f162c Merge 1c4d59cc76 into sapling-pr-archive-bolinfest 2026-02-21 13:25:10 -08:00
Michael Bolin 1c4d59cc76 fix: sort value to address flakiness in conversation_start_audio_text_close_round_trip 2026-02-21 13:25:00 -08:00
Michael Bolin e831e5a871 Merge 70e598d749 into sapling-pr-archive-bolinfest 2026-02-21 13:15:33 -08:00
Michael Bolin 70e598d749 fix: tests should not read my personal ~/.agents folder 2026-02-21 13:15:25 -08:00
Michael Bolin a59d4812bc merge commit for archive created by Sapling 2026-02-21 13:08:37 -08:00
Michael Bolin fbf85d76b2 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 13:08:19 -08:00
Michael Bolin dd9eec1cfb Merge 9902581c6b into sapling-pr-archive-bolinfest 2026-02-21 13:06:06 -08:00
Michael Bolin 9902581c6b fix: update approval_policy and sandbox_policy in TurnContext to be Constrained 2026-02-21 13:05:44 -08:00
Michael Bolin 65667aa104 merge commit for archive created by Sapling 2026-02-21 12:47:46 -08:00
Michael Bolin f69e44be48 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 12:47:34 -08:00
Michael Bolin d36680b87d merge commit for archive created by Sapling 2026-02-21 12:21:50 -08:00
Michael Bolin f8c1b11c04 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 12:21:36 -08:00
pakrym-oaiandCodex b17148f13a Prefer v2 websockets if available (#12428)
And also cleanup settings flow to avoid reading many separate flags.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-02-21 20:08:04 +00:00
Eric Traut a6b2bacb5b Prevent replayed runtime events from forcing active status (#12420)
Fixes #11852

Resume replay was applying transient runtime events (`TurnStarted`,
`StreamError`) as if they were live, which could leave the TUI stuck in
a stale `Working` / `Reconnecting...` state after resuming an
interrupted reconnect.

This change makes replay transcript-oriented for these events by:
- skipping retry-status restoration for replayed non-stream events
- ignoring replayed `TurnStarted` for task-running state
- ignoring replayed `StreamError` for reconnect/status UI

Also adds TUI regression tests and snapshot coverage for the interrupted
reconnect replay case.
2026-02-21 11:55:03 -08:00
sayan-oai 5a635f3427 profile-level model_catalog_json overrie (#12410)
enable `model-catalog_json` config value on `ConfigProfile` as well
2026-02-21 19:39:02 +00:00
viyatb-oaiandCodex b3202cbd58 feat(linux-sandbox): implement proxy-only egress via TCP-UDS-TCP bridge (#11293)
## Summary
- Implement Linux proxy-only routing in `codex-rs/linux-sandbox` with a
two-stage bridge: host namespace `loopback TCP proxy endpoint -> UDS`,
then bwrap netns `loopback TCP listener -> host UDS`.
- Add hidden `--proxy-route-spec` plumbing for outer-to-inner stage
handoff.
- Fail closed in proxy mode when no valid loopback proxy endpoints can
be routed.
- Introduce explicit network seccomp modes: `Restricted` (legacy
restricted networking) and `ProxyRouted` (allow INET/INET6 for routed
proxy access, deny `AF_UNIX` and `socketpair`).
- Enforce that proxy bridge/routing is bwrap-only by validating
`--apply-seccomp-then-exec` requires `--use-bwrap-sandbox`.
- Keep landlock-only flows unchanged (no proxy bridge behavior outside
bwrap).

---------

Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-02-21 18:16:34 +00:00
Michael Bolin 7c3edfc747 merge commit for archive created by Sapling 2026-02-21 00:52:27 -08:00
Michael Bolin 1487fa3127 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 00:52:18 -08:00
pakrym-oai e7b6f38b58 Delete AggregatedStream (#12441)
Used only in test
2026-02-21 08:50:27 +00:00
Michael Bolin 2e7614b126 merge commit for archive created by Sapling 2026-02-21 00:46:38 -08:00
Michael Bolin c8a64c0c19 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 00:46:29 -08:00
Michael Bolin f5d7a74568 chore: delete empty codex-rs/code file (#12440)
This file was added in https://github.com/openai/codex/pull/4195, but I
think it may have been a mistake?
2026-02-21 08:44:55 +00:00
Michael Bolin a5024f7c43 merge commit for archive created by Sapling 2026-02-21 00:43:26 -08:00
Michael Bolin 4e1831a04e ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 00:43:19 -08:00
Michael Bolin f5f4f75dd6 merge commit for archive created by Sapling 2026-02-21 00:34:44 -08:00
Michael Bolin 64fd23bab4 ci: shard linux gnu nextest tests into 2 partitions 2026-02-21 00:34:31 -08:00
Michael Bolin 85ce91a5b3 refactor(core): move embedded system skills into codex-skills crate (#12435)
## Why

`codex-core` was carrying the embedded system-skill sample assets (and a
`build.rs` that walks those files to register rerun triggers). Those
assets change infrequently, but any change under `codex-core` still ties
them to `codex-core`'s build/cache lifecycle.

This change moves the embedded system-skills packaging into a dedicated
`codex-skills` crate so it can be cached independently. That reduces
unnecessary invalidation/rebuild pressure on `codex-core` when the
skills bundle is the only thing that changes.

## What Changed

- Added a new `codex-rs/skills` crate (`codex-skills`) with:
  - `Cargo.toml`
  - `BUILD.bazel`
  - `build.rs` to track skill asset file changes for Cargo rebuilds
- `src/lib.rs` containing the embedded system-skills install/cache logic
previously in `codex-core`
- Moved the embedded sample skill assets from
`codex-rs/core/src/skills/assets/samples` to
`codex-rs/skills/src/assets/samples`.
- Updated `codex-rs/core/Cargo.toml` to depend on `codex-skills` and
removed `codex-core`'s direct `include_dir` dependency.
- Removed `codex-core`'s `build.rs`.
- Replaced `codex-rs/core/src/skills/system.rs` implementation with a
thin re-export wrapper to keep existing `codex-core` call sites
unchanged.
- Updated workspace manifests/lockfile (`codex-rs/Cargo.toml`,
`codex-rs/Cargo.lock`) for the new crate.
2026-02-21 08:34:08 +00:00
Michael Bolin 7252d5f741 Merge de99ae9070 into sapling-pr-archive-bolinfest 2026-02-21 00:28:12 -08:00
Michael Bolin de99ae9070 chore: delete empty codex-rs/code file 2026-02-21 00:28:06 -08:00
Michael Bolin 2fe4be1aa9 fix: codex-arg0 no longer depends on codex-core (#12434)
## Why

`codex-rs/arg0` only needed two things from `codex-core`:

- the `find_codex_home()` wrapper
- the special argv flag used for the internal `apply_patch`
self-invocation path

That made `codex-arg0` depend on `codex-core` for a very small surface
area. This change removes that dependency edge and moves the shared
`apply_patch` invocation flag to a more natural boundary
(`codex-apply-patch`) while keeping the contract explicitly documented.

## What Changed

- Moved the internal `apply_patch` argv[1] flag constant out of
`codex-core` and into `codex-apply-patch`.
- Renamed the constant to `CODEX_CORE_APPLY_PATCH_ARG1` and documented
that it is part of the Codex core process-invocation contract (even
though it now lives in `codex-apply-patch`).
- Updated `arg0`, the core apply-patch runtime, and the `codex-exec`
apply-patch test to import the constant from `codex-apply-patch`.
- Updated `codex-rs/arg0` to call
`codex_utils_home_dir::find_codex_home()` directly instead of
`codex_core::config::find_codex_home()`.
- Removed the `codex-core` dependency from `codex-rs/arg0` and added the
needed direct dependency on `codex-utils-home-dir`.
- Added `codex-apply-patch` as a dev-dependency for `codex-rs/exec`
tests (the apply-patch test now imports the moved constant directly).

## Verification

- `cargo test -p codex-apply-patch`
- `cargo test -p codex-arg0`
- `cargo test -p codex-core --lib apply_patch`
- `cargo test -p codex-exec
test_standalone_exec_cli_can_use_apply_patch`
- `cargo shear`
2026-02-21 00:20:42 -08:00
Michael Bolin 8976a498bc merge commit for archive created by Sapling 2026-02-21 00:19:36 -08:00
Michael Bolin f714e73a58 ci: sample cargo timing artifacts by default 2026-02-21 00:19:08 -08:00
Michael Bolin 90f1e35bc7 ci: limit clippy --tests to one representative target 2026-02-21 00:18:26 -08:00
Michael Bolin 6575795777 ci: skip windows arm64 tests on PRs in rust-ci 2026-02-21 00:18:26 -08:00
Michael Bolin bc2bffdc23 Merge 01f4b4a146 into sapling-pr-archive-bolinfest 2026-02-21 00:14:01 -08:00
Michael Bolin 01f4b4a146 ci: sample cargo timing artifacts by default 2026-02-21 00:13:45 -08:00
Michael Bolin e1b4f8aa83 ci: limit clippy --tests to one representative target 2026-02-21 00:13:09 -08:00
Michael Bolin 110f83b61e ci: skip windows arm64 tests on PRs in rust-ci 2026-02-21 00:12:49 -08:00
Michael Bolin 06092faed2 Merge eddcfc1866 into sapling-pr-archive-bolinfest 2026-02-21 00:08:18 -08:00
Michael Bolin eddcfc1866 fix: introduce codex-skills crate 2026-02-21 00:08:02 -08:00
Michael Bolin c59eccc018 Merge 86d1ad97cc into sapling-pr-archive-bolinfest 2026-02-20 23:51:13 -08:00