Commit Graph

74 Commits

  • feat: add new config option: model_supports_reasoning_summaries (#1524)
    As noted in the updated docs, this makes it so that you can set:
    
    ```toml
    model_supports_reasoning_summaries = true
    ```
    
    as a way of overriding the existing heuristic for when to set the
    `reasoning` field on a sampling request:
    
    
    https://github.com/openai/codex/blob/341c091c5b09dc706ab5c7d629516e6ef5aaf902/codex-rs/core/src/client_common.rs#L152-L166
  • chore(rs): update dependencies (#1494)
    ### Chores
    - Update cargo dependencies
    - Remove unused cargo dependencies
    - Fix clippy warnings
    - Update Dockerfile (package.json requires node 22)
    - Let Dependabot update bun, cargo, devcontainers, docker,
    github-actions, npm (nix still not supported)
    
    ### TODO
    - Upgrade dependencies with breaking changes
    
    ```shell
    $ cargo update --verbose
       Unchanged crossterm v0.28.1 (available: v0.29.0)
       Unchanged schemars v0.8.22 (available: v1.0.4)
    ```
  • feat: add support for --sandbox flag (#1476)
    On a high-level, we try to design `config.toml` so that you don't have
    to "comment out a lot of stuff" when testing different options.
    
    Previously, defining a sandbox policy was somewhat at odds with this
    principle because you would define the policy as attributes of
    `[sandbox]` like so:
    
    ```toml
    [sandbox]
    mode = "workspace-write"
    writable_roots = [ "/tmp" ]
    ```
    
    but if you wanted to temporarily change to a read-only sandbox, you
    might feel compelled to modify your file to be:
    
    ```toml
    [sandbox]
    mode = "read-only"
    # mode = "workspace-write"
    # writable_roots = [ "/tmp" ]
    ```
    
    Technically, commenting out `writable_roots` would not be strictly
    necessary, as `mode = "read-only"` would ignore `writable_roots`, but
    it's still a reasonable thing to do to keep things tidy.
    
    Currently, the various values for `mode` do not support that many
    attributes, so this is not that hard to maintain, but one could imagine
    this becoming more complex in the future.
    
    In this PR, we change Codex CLI so that it no longer recognizes
    `[sandbox]`. Instead, it introduces a top-level option, `sandbox_mode`,
    and `[sandbox_workspace_write]` is used to further configure the sandbox
    when when `sandbox_mode = "workspace-write"` is used:
    
    ```toml
    sandbox_mode = "workspace-write"
    
    [sandbox_workspace_write]
    writable_roots = [ "/tmp" ]
    ```
    
    This feels a bit more future-proof in that it is less tedious to
    configure different sandboxes:
    
    ```toml
    sandbox_mode = "workspace-write"
    
    [sandbox_read_only]
    # read-only options here...
    
    [sandbox_workspace_write]
    writable_roots = [ "/tmp" ]
    
    [sandbox_danger_full_access]
    # danger-full-access options here...
    ```
    
    In this scheme, you never need to comment out the configuration for an
    individual sandbox type: you only need to redefine `sandbox_mode`.
    
    Relatedly, previous to this change, a user had to do `-c
    sandbox.mode=read-only` to change the mode on the command line. With
    this change, things are arguably a bit cleaner because the equivalent
    option is `-c sandbox_mode=read-only` (and now `-c
    sandbox_workspace_write=...` can be set separately).
    
    Though more importantly, we introduce the `-s/--sandbox` option to the
    CLI, which maps directly to `sandbox_mode` in `config.toml`, making
    config override behavior easier to reason about. Moreover, as you can
    see in the updates to the various Markdown files, it is much easier to
    explain how to configure sandboxing when things like `--sandbox
    read-only` can be used as an example.
    
    Relatedly, this cleanup also made it straightforward to add support for
    a `sandbox` option for Codex when used as an MCP server (see the changes
    to `mcp-server/src/codex_tool_config.rs`).
    
    Fixes https://github.com/openai/codex/issues/1248.
  • Fix Unicode handling in chat_composer "@" token detection (#1467)
    ## Issues Fixed
    
    - **Primary Issue (#1450)**: Unicode cursor positioning was incorrect
    due to mixing character positions with byte positions
    - **Additional Issue**: Full-width spaces (CJK whitespace like " ")
    weren't properly handled as token boundaries
    - ref:
    https://doc.rust-lang.org/std/primitive.char.html#method.is_whitespace
    
    ---------
    
    Co-authored-by: Michael Bolin <bolinfest@gmail.com>
  • feat: introduce --compute-indices flag to codex-file-search (#1419)
    This is a small quality-of-life feature, the addition of
    `--compute-indices` to the CLI, which, if enabled, will compute and set
    the `indices` field for each `FileMatch` returned by `run()`. Note we
    only bother to compute `indices` once we have the top N results because
    there could be a lot of intermediate "top N" results during the search
    that are ultimately discarded.
    
    When set, the indices are included in the JSON output when `--json` is
    specified and the matching indices are displayed in bold when `--json`
    is not specified.
  • feat: add support for @ to do file search (#1401)
    Introduces support for `@` to trigger a fuzzy-filename search in the
    composer. Under the hood, this leverages
    https://crates.io/crates/nucleo-matcher to do the fuzzy matching and
    https://crates.io/crates/ignore to build up the list of file candidates
    (so that it respects `.gitignore`).
    
    For simplicity (at least for now), we do not do any caching between
    searches like VS Code does for its file search:
    
    
    https://github.com/microsoft/vscode/blob/1d89ed699b2e924d418c856318a3e12bca67ff3a/src/vs/workbench/services/search/node/rawSearchService.ts#L212-L218
    
    Because we do not do any caching, I saw queries take up to three seconds
    on large repositories with hundreds of thousands of files. To that end,
    we do not perform searches synchronously on each keystroke, but instead
    dispatch an event to do the search on a background thread that
    asynchronously reports back to the UI when the results are available.
    This is largely handled by the `FileSearchManager` introduced in this
    PR, which also has logic for debouncing requests so there is at most one
    search in flight at a time.
    
    While we could potentially polish and tune this feature further, it may
    already be overengineered for how it will be used, in practice, so we
    can improve things going forward if it turns out that this is not "good
    enough" in the wild.
    
    Note this feature does not work like `@` in the TypeScript CLI, which
    was more like directory-based tab completion. In the Rust CLI, `@`
    triggers a full-repo fuzzy-filename search.
    
    Fixes https://github.com/openai/codex/issues/1261.
  • Handle Ctrl+C quit when idle (#1402)
    ## Summary
    - show `Ctrl+C to quit` hint when pressing Ctrl+C with no active task
    - exiting with Ctrl+C if the hint is already visible
    - clear the hint when tasks begin or other keys are pressed
    
    
    https://github.com/user-attachments/assets/931e2d7c-1c80-4b45-9908-d119f74df23c
    
    
    
    ------
    https://chatgpt.com/s/cd_685ec8875a308191beaa95886dc1379e
    
    Fixes #1245
  • feat: add support for /diff command (#1389)
    Adds support for a `/diff` command comparable to the one available in
    the TypeScript CLI.
    
    <img width="1103" alt="Screenshot 2025-06-26 at 12 31 33 PM"
    src="https://github.com/user-attachments/assets/5dc646ca-301f-41ff-92a7-595c68db64b6"
    />
    
    While here, changed the `SlashCommand` enum so the declared variant
    order is the order the commands appear in the popup menu. This way,
    `/toggle-mouse-mode` is listed last, as it is the least likely to be
    used.
    
    Fixes https://github.com/openai/codex/issues/1253.
  • feat: show number of tokens remaining in UI (#1388)
    When using the OpenAI Responses API, we now record the `usage` field for
    a `"response.completed"` event, which includes metrics about the number
    of tokens consumed. We also introduce `openai_model_info.rs`, which
    includes current data about the most common OpenAI models available via
    the API (specifically `context_window` and `max_output_tokens`). If
    Codex does not recognize the model, you can set `model_context_window`
    and `model_max_output_tokens` explicitly in `config.toml`.
    
    When then introduce a new event type to `protocol.rs`, `TokenCount`,
    which includes the `TokenUsage` for the most recent turn.
    
    Finally, we update the TUI to record the running sum of tokens used so
    the percentage of available context window remaining can be reported via
    the placeholder text for the composer:
    
    ![Screenshot 2025-06-25 at 11 20
    55 PM](https://github.com/user-attachments/assets/6fd6982f-7247-4f14-84b2-2e600cb1fd49)
    
    We could certainly get much fancier with this (such as reporting the
    estimated cost of the conversation), but for now, we are just trying to
    achieve feature parity with the TypeScript CLI.
    
    Though arguably this improves upon the TypeScript CLI, as the TypeScript
    CLI uses heuristics to estimate the number of tokens used rather than
    using the `usage` information directly:
    
    
    https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
    
    Fixes https://github.com/openai/codex/issues/1242
  • feat: add --dangerously-bypass-approvals-and-sandbox (#1384)
    This PR reworks `assess_command_safety()` so that the combination of
    `AskForApproval::Never` and `SandboxPolicy::DangerFullAccess` ensures
    that commands are run without _any_ sandbox and the user should never be
    prompted. In turn, it adds support for a new
    `--dangerously-bypass-approvals-and-sandbox` flag (that cannot be used
    with `--approval-policy` or `--full-auto`) that sets both of those
    options.
    
    Fixes https://github.com/openai/codex/issues/1254
  • chore: improve docstring for --full-auto (#1379)
    Reference `-c sandbox.mode=workspace-write` in the docstring and users
    can read the config docs for `sandbox` for more information.
  • fix: pretty-print the sandbox config in the TUI/exec modes (#1376)
    Now that https://github.com/openai/codex/pull/1373 simplified the
    sandbox config, we can print something much simpler in the TUI (and in
    `codex exec`) to summarize the sandbox config.
    
    Before:
    
    ![Screenshot 2025-06-24 at 5 45
    52 PM](https://github.com/user-attachments/assets/b7633efb-a619-43e1-9abe-7bb0be2d0ec0)
    
    With this change:
    
    ![Screenshot 2025-06-24 at 5 46
    44 PM](https://github.com/user-attachments/assets/8d099bdd-a429-4796-a08d-70931d984e4f)
    
    For reference, my `config.toml` contains:
    
    ```
    [sandbox]
    mode = "workspace-write"
    writable_roots = ["/tmp", "/Users/mbolin/.pyenv/shims"]
    ```
    
    Fixes https://github.com/openai/codex/issues/1248
  • feat: redesign sandbox config (#1373)
    This is a major redesign of how sandbox configuration works and aims to
    fix https://github.com/openai/codex/issues/1248. Specifically, it
    replaces `sandbox_permissions` in `config.toml` (and the
    `-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
    three variants:
    
    ```toml
    # Safest option: full disk is read-only, but writes and network access are disallowed.
    [sandbox]
    mode = "read-only"
    
    # The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
    # writable_roots can be used to specify additional writable folders.
    [sandbox]
    mode = "workspace-write"
    writable_roots = []  # Optional, defaults to the empty list.
    network_access = false  # Optional, defaults to false.
    
    # Disable sandboxing: use at your own risk!!!
    [sandbox]
    mode = "danger-full-access"
    ```
    
    This should make sandboxing easier to reason about. While we have
    dropped support for `-s`, the way it works now is:
    
    - no flags => `read-only`
    - `--full-auto` => `workspace-write`
    - currently, there is no way to specify `danger-full-access` via a CLI
    flag, but we will revisit that as part of
    https://github.com/openai/codex/issues/1254
    
    Outstanding issue:
    
    - As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
    still conflating sandbox preferences with approval preferences in that
    case, which needs to be cleaned up.
  • codex-rs: Rename /clear to /new, make it start an entirely new chat (#1264)
    I noticed that `/clear` wasn't fully clearing chat history; it would
    clear the chat history widgets _in the UI_, but the LLM still had access
    to information from previous messages.
    
    This PR renames `/clear` to `/new` for clarity as per Michael's
    suggestion, resetting `app_state` to a fresh `ChatWidget`.
  • feat: add support for login with ChatGPT (#1212)
    This does not implement the full Login with ChatGPT experience, but it
    should unblock people.
    
    **What works**
    
    * The `codex` multitool now has a `login` subcommand, so you can run
    `codex login`, which should write `CODEX_HOME/auth.json` if you complete
    the flow successfully. The TUI will now read the `OPENAI_API_KEY` from
    `auth.json`.
    * The TUI should refresh the token if it has expired and the necessary
    information is in `auth.json`.
    * There is a `LoginScreen` in the TUI that tells you to run `codex
    login` if both (1) your model provider expects to use `OPENAI_API_KEY`
    as its env var, and (2) `OPENAI_API_KEY` is not set.
    
    **What does not work**
    
    * The `LoginScreen` does not support the login flow from within the TUI.
    Instead, it tells you to quit, run `codex login`, and then run `codex`
    again.
    * `codex exec` does read from `auth.json` yet, nor does it direct the
    user to go through the login flow if `OPENAI_API_KEY` is not be found.
    * The `maybeRedeemCredits()` function from `get-api-key.tsx` has not
    been ported from TypeScript to `login_with_chatgpt.py` yet:
    
    
    https://github.com/openai/codex/blob/a67a67f3258fc21e147b6786a143fe3e15e6d5ba/codex-cli/src/utils/get-api-key.tsx#L84-L89
    
    **Implementation**
    
    Currently, the OAuth flow requires running a local webserver on
    `127.0.0.1:1455`. It seemed wasteful to incur the additional binary cost
    of a webserver dependency in the Rust CLI just to support login, so
    instead we implement this logic in Python, as Python has a `http.server`
    module as part of its standard library. Specifically, we bundle the
    contents of a single Python file as a string in the Rust CLI and then
    use it to spawn a subprocess as `python3 -c
    {{SOURCE_FOR_PYTHON_SERVER}}`.
    
    As such, the most significant files in this PR are:
    
    ```
    codex-rs/login/src/login_with_chatgpt.py
    codex-rs/login/src/lib.rs
    ```
    
    Now that the CLI may load `OPENAI_API_KEY` from the environment _or_
    `CODEX_HOME/auth.json`, we need a new abstraction for reading/writing
    this variable, so we introduce:
    
    ```
    codex-rs/core/src/openai_api_key.rs
    ```
    
    Note that `std::env::set_var()` is [rightfully] `unsafe` in Rust 2024,
    so we use a LazyLock<RwLock<Option<String>>> to store `OPENAI_API_KEY`
    so it is read in a thread-safe manner.
    
    Ultimately, it should be possible to go through the entire login flow
    from the TUI. This PR introduces a placeholder `LoginScreen` UI for that
    right now, though the new `codex login` subcommand introduced in this PR
    should be a viable workaround until the UI is ready.
    
    **Testing**
    
    Because the login flow is currently implemented in a standalone Python
    file, you can test it without building any Rust code as follows:
    
    ```
    rm -rf /tmp/codex_home && mkdir /tmp/codex_home
    CODEX_HOME=/tmp/codex_home python3 codex-rs/login/src/login_with_chatgpt.py
    ```
    
    For reference:
    
    * the original TypeScript implementation was introduced in
    https://github.com/openai/codex/pull/963
    * support for redeeming credits was later added in
    https://github.com/openai/codex/pull/974
  • codex-rs: make tool calls prettier (#1211)
    This PR overhauls how active tool calls and completed tool calls are
    displayed:
    
    1. More use of colour to indicate success/failure and distinguish
    between components like tool name+arguments
    2. Previously, the entire `CallToolResult` was serialized to JSON and
    pretty-printed. Now, we extract each individual `CallToolResultContent`
    and print those
    1. The previous solution was wasting space by unnecessarily showing
    details of the `CallToolResult` struct to users, without formatting the
    actual tool call results nicely
    2. We're now able to show users more information from tool results in
    less space, with nicer formatting when tools return JSON results
    
    ### Before:
    
    <img width="1251" alt="Screenshot 2025-06-03 at 11 24 26"
    src="https://github.com/user-attachments/assets/5a58f222-219c-4c53-ace7-d887194e30cf"
    />
    
    ### After:
    
    <img width="1265" alt="image"
    src="https://github.com/user-attachments/assets/99fe54d0-9ebe-406a-855b-7aa529b91274"
    />
    
    ## Future Work
    
    1. Integrate image tool result handling better. We should be able to
    display images even if they're not the first `CallToolResultContent`
    2. Users should have some way to view the full version of truncated tool
    results
    3. It would be nice to add some left padding for tool results, make it
    more clear that they are results. This is doable, just a little fiddly
    due to the way `first_visible_line` scrolling works
    4. There's almost certainly a better way to format JSON than "all on 1
    line with spaces to make Ratatui wrapping work". But I think that works
    OK for now.
  • chore: replace regex with regex-lite, where appropriate (#1200)
    As explained on https://crates.io/crates/regex-lite, `regex-lite` is a
    lighter alternative to `regex` and seems to be sufficient for our
    purposes.
  • feat: make reasoning effort/summaries configurable (#1199)
    Previous to this PR, we always set `reasoning` when making a request
    using the Responses API:
    
    
    https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-rs/core/src/client.rs#L108-L111
    
    Though if you tried to use the Rust CLI with `--model gpt-4.1`, this
    would fail with:
    
    ```shell
    "Unsupported parameter: 'reasoning.effort' is not supported with this model."
    ```
    
    We take a cue from the TypeScript CLI, which does a check on the model
    name:
    
    
    https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-cli/src/utils/agent/agent-loop.ts#L786-L789
    
    This PR does a similar check, though also adds support for the following
    config options:
    
    ```
    model_reasoning_effort = "low" | "medium" | "high" | "none"
    model_reasoning_summary = "auto" | "concise" | "detailed" | "none"
    ```
    
    This way, if you have a model whose name happens to start with `"o"` (or
    `"codex"`?), you can set these to `"none"` to explicitly disable
    reasoning, if necessary. (That said, it seems unlikely anyone would use
    the Responses API with non-OpenAI models, but we provide an escape
    hatch, anyway.)
    
    This PR also updates both the TUI and `codex exec` to show `reasoning
    effort` and `reasoning summaries` in the header.
  • feat: show the version when starting Codex (#1182)
    The TypeScript version of the CLI shows the version when it starts up,
    which is helpful when users share screenshots (and nice to know, as a
    user).
  • feat: add hide_agent_reasoning config option (#1181)
    This PR introduces a `hide_agent_reasoning` config option (that defaults
    to `false`) that users can enable to make the output less verbose by
    suppressing reasoning output.
    
    To test, verified that this includes agent reasoning in the output:
    
    ```
    echo hello | just exec
    ```
    
    whereas this does not:
    
    ```
    echo hello | just exec --config hide_agent_reasoning=false
    ```
  • fix: introduce ResponseInputItem::McpToolCallOutput variant (#1151)
    The output of an MCP server tool call can be one of several types, but
    to date, we treated all outputs as text by showing the serialized JSON
    as the "tool output" in Codex:
    
    
    https://github.com/openai/codex/blob/25a9949c49194d5a64de54a11bcc5b4724ac9bd5/codex-rs/mcp-types/src/lib.rs#L96-L101
    
    This PR adds support for the `ImageContent` variant so we can now
    display an image output from an MCP tool call.
    
    In making this change, we introduce a new
    `ResponseInputItem::McpToolCallOutput` variant so that we can work with
    the `mcp_types::CallToolResult` directly when the function call is made
    to an MCP server.
    
    Though arguably the more significant change is the introduction of
    `HistoryCell::CompletedMcpToolCallWithImageOutput`, which is a cell that
    uses `ratatui_image` to render an image into the terminal. To support
    this, we introduce `ImageRenderCache`, cache a
    `ratatui_image::picker::Picker`, and `ensure_image_cache()` to cache the
    appropriate scaled image data and dimensions based on the current
    terminal size.
    
    To test, I created a minimal `package.json`:
    
    ```json
    {
      "name": "kitty-mcp",
      "version": "1.0.0",
      "type": "module",
      "description": "MCP that returns image of kitty",
      "main": "index.js",
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.12.0"
      }
    }
    ```
    
    with the following `index.js` to define the MCP server:
    
    ```js
    #!/usr/bin/env node
    
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { readFile } from "node:fs/promises";
    import { join } from "node:path";
    
    const IMAGE_URI = "image://Ada.png";
    
    const server = new McpServer({
      name: "Demo",
      version: "1.0.0",
    });
    
    server.tool(
      "get-cat-image",
      "If you need a cat image, this tool will provide one.",
      async () => ({
        content: [
          { type: "image", data: await getAdaPngBase64(), mimeType: "image/png" },
        ],
      })
    );
    
    server.resource("Ada the Cat", IMAGE_URI, async (uri) => {
      const base64Image = await getAdaPngBase64();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "image/png",
            blob: base64Image,
          },
        ],
      };
    });
    
    async function getAdaPngBase64() {
      const __dirname = new URL(".", import.meta.url).pathname;
      // From https://github.com/benjajaja/ratatui-image/blob/9705ce2c59ec669abbce2924cbfd1f5ae22c9860/assets/Ada.png
      const filePath = join(__dirname, "Ada.png");
      const imageData = await readFile(filePath);
      const base64Image = imageData.toString("base64");
      return base64Image;
    }
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    ```
    
    With the local changes from this PR, I added the following to my
    `config.toml`:
    
    ```toml
    [mcp_servers.kitty]
    command = "node"
    args = ["/Users/mbolin/code/kitty-mcp/index.js"]
    ```
    
    Running the TUI from source:
    
    ```
    cargo run --bin codex -- --model o3 'I need a picture of a cat'
    ```
    
    I get:
    
    <img width="732" alt="image"
    src="https://github.com/user-attachments/assets/bf80b721-9ca0-4d81-aec7-77d6899e2869"
    />
    
    Now, that said, I have only tested in iTerm and there is definitely some
    funny business with getting an accurate character-to-pixel ratio
    (sometimes the `CompletedMcpToolCallWithImageOutput` thinks it needs 10
    rows to render instead of 4), so there is still work to be done here.
  • feat: introduce CellWidget trait (#1148)
    The motivation behind this PR is to make it so a `HistoryCell` is more
    like a `WidgetRef` that knows how to render itself into a `Rect` so that
    it can be backed by something other than a `Vec<Line>`. Because a
    `HistoryCell` is intended to appear in a scrollable list, we want to
    ensure the stack of cells can be scrolled one `Line` at a time even if
    the `HistoryCell` is not backed by a `Vec<Line>` itself.
    
    To this end, we introduce the `CellWidget` trait whose key method is:
    
    ```
    fn render_window(&self, first_visible_line: usize, area: Rect, buf: &mut Buffer);
    ```
    
    The `first_visible_line` param is what differs from
    `WidgetRef::render_ref()`, as a `CellWidget` needs to know the offset
    into its "full view" at which it should start rendering.
    
    The bookkeeping in `ConversationHistoryWidget` has been updated
    accordingly to ensure each `CellWidget` in the history is rendered
    appropriately.
  • feat: add support for -c/--config to override individual config items (#1137)
    This PR introduces support for `-c`/`--config` so users can override
    individual config values on the command line using `--config
    name=value`. Example:
    
    ```
    codex --config model=o4-mini
    ```
    
    Making it possible to set arbitrary config values on the command line
    results in a more flexible configuration scheme and makes it easier to
    provide single-line examples that can be copy-pasted from documentation.
    
    Effectively, it means there are four levels of configuration for some
    values:
    
    - Default value (e.g., `model` currently defaults to `o4-mini`)
    - Value in `config.toml` (e.g., user could override the default to be
    `model = "o3"` in their `config.toml`)
    - Specifying `-c` or `--config` to override `model` (e.g., user can
    include `-c model=o3` in their list of args to Codex)
    - If available, a config-specific flag can be used, which takes
    precedence over `-c` (e.g., user can specify `--model o3` in their list
    of args to Codex)
    
    Now that it is possible to specify anything that could be configured in
    `config.toml` on the command line using `-c`, we do not need to have a
    custom flag for every possible config option (which can clutter the
    output of `--help`). To that end, as part of this PR, we drop support
    for the `--disable-response-storage` flag, as users can now specify `-c
    disable_response_storage=true` to get the equivalent functionality.
    
    Under the hood, this works by loading the `config.toml` into a
    `toml::Value`. Then for each `key=value`, we create a small synthetic
    TOML file with `value` so that we can run the TOML parser to get the
    equivalent `toml::Value`. We then parse `key` to determine the point in
    the original `toml::Value` to do the insert/replace. Once all of the
    overrides from `-c` args have been applied, the `toml::Value` is
    deserialized into a `ConfigToml` and then the `ConfigOverrides` are
    applied, as before.
  • fix: TUI was not honoring --skip-git-repo-check correctly (#1105)
    I discovered that if I ran `codex <PROMPT>` in a cwd that was not a Git
    repo, Codex did not automatically run `<PROMPT>` after I accepted the
    Git warning. It appears that we were not managing the `AppState`
    transition correctly, so this fixes the bug and ensures the Codex
    session does not start until the user accepts the Git warning.
    
    In particular, we now create the `ChatWidget` lazily and store it in the
    `AppState::Chat` variant.
  • fix: overhaul how we spawn commands under seccomp/landlock on Linux (#1086)
    Historically, we spawned the Seatbelt and Landlock sandboxes in
    substantially different ways:
    
    For **Seatbelt**, we would run `/usr/bin/sandbox-exec` with our policy
    specified as an arg followed by the original command:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec.rs#L147-L219
    
    For **Landlock/Seccomp**, we would do
    `tokio::runtime::Builder::new_current_thread()`, _invoke
    Landlock/Seccomp APIs to modify the permissions of that new thread_, and
    then spawn the command:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec_linux.rs#L28-L49
    
    While it is neat that Landlock/Seccomp supports applying a policy to
    only one thread without having to apply it to the entire process, it
    requires us to maintain two different codepaths and is a bit harder to
    reason about. The tipping point was
    https://github.com/openai/codex/pull/1061, in which we had to start
    building up the `env` in an unexpected way for the existing
    Landlock/Seccomp approach to continue to work.
    
    This PR overhauls things so that we do similar things for Mac and Linux.
    It turned out that we were already building our own "helper binary"
    comparable to Mac's `sandbox-exec` as part of the `cli` crate:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/cli/Cargo.toml#L10-L12
    
    We originally created this to build a small binary to include with the
    Node.js version of the Codex CLI to provide support for Linux
    sandboxing.
    
    Though the sticky bit is that, at this point, we still want to deploy
    the Rust version of Codex as a single, standalone binary rather than a
    CLI and a supporting sandboxing binary. To satisfy this goal, we use
    "the arg0 trick," in which we:
    
    * use `std::env::current_exe()` to get the path to the CLI that is
    currently running
    * use the CLI as the `program` for the `Command`
    * set `"codex-linux-sandbox"` as arg0 for the `Command`
    
    A CLI that supports sandboxing should check arg0 at the start of the
    program. If it is `"codex-linux-sandbox"`, it must invoke
    `codex_linux_sandbox::run_main()`, which runs the CLI as if it were
    `codex-linux-sandbox`. When acting as `codex-linux-sandbox`, we make the
    appropriate Landlock/Seccomp API calls and then use `execvp(3)` to spawn
    the original command, so do _replace_ the process rather than spawn a
    subprocess. Incidentally, we do this before starting the Tokio runtime,
    so the process should only have one thread when `execvp(3)` is called.
    
    Because the `core` crate that needs to spawn the Linux sandboxing is not
    a CLI in its own right, this means that every CLI that includes `core`
    and relies on this behavior has to (1) implement it and (2) provide the
    path to the sandboxing executable. While the path is almost always
    `std::env::current_exe()`, we needed to make this configurable for
    integration tests, so `Config` now has a `codex_linux_sandbox_exe:
    Option<PathBuf>` property to facilitate threading this through,
    introduced in https://github.com/openai/codex/pull/1089.
    
    This common pattern is now captured in
    `codex_linux_sandbox::run_with_sandbox()` and all of the `main.rs`
    functions that should use it have been updated as part of this PR.
    
    The `codex-linux-sandbox` crate added to the Cargo workspace as part of
    this PR now has the bulk of the Landlock/Seccomp logic, which makes
    `core` a bit simpler. Indeed, `core/src/exec_linux.rs` and
    `core/src/landlock.rs` were removed/ported as part of this PR. I also
    moved the unit tests for this code into an integration test,
    `linux-sandbox/tests/landlock.rs`, in which I use
    `env!("CARGO_BIN_EXE_codex-linux-sandbox")` as the value for
    `codex_linux_sandbox_exe` since `std::env::current_exe()` is not
    appropriate in that case.
  • feat: add codex_linux_sandbox_exe: Option<PathBuf> field to Config (#1089)
    https://github.com/openai/codex/pull/1086 is a work-in-progress to make
    Linux sandboxing work more like Seatbelt where, for the command we want
    to sandbox, we build up the command and then hand it, and some sandbox
    configuration flags, to another command to set up the sandbox and then
    run it.
    
    In the case of Seatbelt, macOS provides this helper binary and provides
    it at `/usr/bin/sandbox-exec`. For Linux, we have to build our own and
    pass it through (which is what #1086 does), so this makes the new
    `codex_linux_sandbox_exe` available on `Config` so that it will later be
    available in `exec.rs` when we need it in #1086.
  • chore: move types out of config.rs into config_types.rs (#1054)
    `config.rs` is already quite long without these definitions. Since they
    have no real dependencies of their own, let's move them to their own
    file so `config.rs` can focus on the business logic of loading a config.
  • feat: experimental --output-last-message flag to exec subcommand (#1037)
    This introduces an experimental `--output-last-message` flag that can be
    used to identify a file where the final message from the agent will be
    written. Two use cases:
    
    - Ultimately, we will likely add a `--quiet` option to `exec`, but even
    if the user does not want any output written to the terminal, they
    probably want to know what the agent did. Writing the output to a file
    makes it possible to get that information in a clean way.
    - Relatedly, when using `exec` in CI, it is easier to review the
    transcript written "normally," (i.e., not as JSON or something with
    extra escapes), but getting programmatic access to the last message is
    likely helpful, so writing the last message to a file gets the best of
    both worlds.
    
    I am calling this "experimental" because it is possible that we are
    overfitting and will want a more general solution to this problem that
    would justify removing this flag.
  • fix: artifacts from previous frames were bleeding through in TUI (#989)
    Prior to this PR, I would frequently see glyphs from previous frames
    "bleed" through like this:
    
    
    ![image](https://github.com/user-attachments/assets/8784b3d7-f691-4df6-8666-34e2f134ee85)
    
    I think this was due to two issues (now addressed in this PR):
    
    * We were not making use of `ratatui::widgets::Clear` to clear out the
    buffer before drawing into it.
    * To calculate the `width` used with `wrapped_line_count_for_cell()`, we
    were not accounting for the scrollbar.
    * Now we calculate `effective_width` using
    `inner.width.saturating_sub(1)` where the `1` is for the scrollbar.
    * We compute `text_area` using `effective_with` and pass the `text_area`
    to `paragraph.render()`.
    * We eliminate the conditional `needs_scrollbar` check and always call
    `render(Scrollbar)`
    
    I suspect this bug was introduced in
    https://github.com/openai/codex/pull/937, though I did not try to
    verify: I'm just happy that it appears to be fixed!
  • fix: ensure the first user message always displays after the session info (#988)
    Previously, if the first user message was sent with the command
    invocation, e.g.:
    
    ```
    $ cargo run --bin codex 'hello'
    ```
    
    Then the user message was added as the first entry in the history and
    then `is_first_event` would be `false` here:
    
    
    https://github.com/openai/codex/blob/031df77dfb9248fe47b40ec52bf8b05052231722/codex-rs/tui/src/conversation_history_widget.rs#L178-L179
    
    which would prevent the "welcome" message with things like the the model
    version from displaying.
    
    The fix in this PR is twofold:
    
    * Reorganize the logic so the `ChatWidget` constructor stores
    `initial_user_message` rather than sending it right away. Now inside
    `handle_codex_event()`, it waits for the `SessionConfigured` event and
    sends the `initial_user_message`, if it exists.
    * In `conversation_history_widget.rs`, `add_session_info()` checks to
    see whether a `WelcomeMessage` exists in the history when determining
    the value of `has_welcome_message`. By construction, we expect that
    `WelcomeMessage` is always the first message (in which case the existing
    `let is_first_event = self.entries.is_empty();` logic would be sound),
    but we decide to be extra defensive in case an `EventMsg::Error` is
    processed before `EventMsg::SessionConfigured`.
  • feat: make it possible to toggle mouse mode in the Rust TUI (#971)
    I did a bit of research to understand why I could not use my mouse to
    drag to select text to copy to the clipboard in iTerm.
    
    Apparently https://github.com/openai/codex/pull/641 to enable mousewheel
    scrolling broke this functionality. It seems that, unless we put in a
    bit of effort, we can have drag-to-select or scrolling, but not both.
    Though if you know the trick to hold down `Option` will dragging with
    the mouse in iTerm, you can probably get by with this. (I did not know
    about this option prior to researching this issue.)
    
    Nevertheless, users may still prefer to disable mouse capture
    altogether, so this PR introduces:
    
    * the ability to set `tui.disable_mouse_capture = true` in `config.toml`
    to disable mouse capture
    * a new command, `/toggle-mouse-mode` to toggle mouse capture
  • fix: use text other than 'TODO' as test example (#969)
    I casually `rg TODO` to look for TODOs, so the use of TODO in a sample
    string in test output was throwing things off.
  • feat: add support for file_opener option in Rust, similiar to #911 (#957)
    This ports the enhancement introduced in
    https://github.com/openai/codex/pull/911 (and the fixes in
    https://github.com/openai/codex/pull/919) for the TypeScript CLI to the
    Rust one.
  • feat: record messages from user in ~/.codex/history.jsonl (#939)
    This is a large change to support a "history" feature like you would
    expect in a shell like Bash.
    
    History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
    is a JSONL file, it is straightforward to append new entries (as opposed
    to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
    valid JSON, each new entry entails rewriting the entire file). Because
    it is possible for there to be multiple instances of Codex CLI writing
    to `history.jsonl` at once, we use advisory file locking when working
    with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
    
    Because we believe history is a sufficiently useful feature, we enable
    it by default. Though to provide some safety, we set the file
    permissions of `history.jsonl` to be `o600` so that other users on the
    system cannot read the user's history. We do not yet support a default
    list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
    
    
    https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
    
    We are going to take a more conservative approach to this list in the
    Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
    sensitive information like API tokens, it would also exclude valuable
    information such as references to Git commits.
    
    As noted in the updated documentation, users can opt-out of history by
    adding the following to `config.toml`:
    
    ```toml
    [history]
    persistence = "none" 
    ```
    
    Because `history.jsonl` could, in theory, be quite large, we take a[n
    arguably overly pedantic] approach in reading history entries into
    memory. Specifically, we start by telling the client the current number
    of entries in the history file (`history_entry_count`) as well as the
    inode (`history_log_id`) of `history.jsonl` (see the new fields on
    `SessionConfiguredEvent`).
    
    The client is responsible for keeping new entries in memory to create a
    "local history," but if the user hits up enough times to go "past" the
    end of local history, then the client should use the new
    `GetHistoryEntryRequest` in the protocol to fetch older entries.
    Specifically, it should pass the `history_log_id` it was given
    originally and work backwards from `history_entry_count`. (It should
    really fetch history in batches rather than one-at-a-time, but that is
    something we can improve upon in subsequent PRs.)
    
    The motivation behind this crazy scheme is that it is designed to defend
    against:
    
    * The `history.jsonl` being truncated during the session such that the
    index into the history is no longer consistent with what had been read
    up to that point. We do not yet have logic to enforce a `max_bytes` for
    `history.jsonl`, but once we do, we will aspire to implement it in a way
    that should result in a new inode for the file on most systems.
    * New items from concurrent Codex CLI sessions amending to the history.
    Because, in absence of truncation, `history.jsonl` is an append-only
    log, so long as the client reads backwards from `history_entry_count`,
    it should always get a consistent view of history. (That said, it will
    not be able to read _new_ commands from concurrent sessions, but perhaps
    we will introduce a `/` command to reload latest history or something
    down the road.)
    
    Admittedly, my testing of this feature thus far has been fairly light. I
    expect we will find bugs and introduce enhancements/fixes going forward.
  • chore: introduce AppEventSender to help fix clippy warnings and update to Rust 1.87 (#948)
    Moving to Rust 1.87 introduced a clippy warning that
    `SendError<AppEvent>` was too large.
    
    In practice, the only thing we ever did when we got this error was log
    it (if the mspc channel is closed, then the app is likely shutting down
    or something, so there's not much to do...), so this finally motivated
    me to introduce `AppEventSender`, which wraps
    `std::sync::mpsc::Sender<AppEvent>` with a `send()` method that invokes
    `send()` on the underlying `Sender` and logs an `Err` if it gets one.
    
    This greatly simplifies the code, as many functions that previously
    returned `Result<(), SendError<AppEvent>>` now return `()`, so we don't
    have to propagate an `Err` all over the place that we don't really
    handle, anyway.
    
    This also makes it so we can upgrade to Rust 1.87 in CI.
  • fix: properly wrap lines in the Rust TUI (#937)
    As discussed on
    https://github.com/openai/codex/commit/699ec5a87f09796d17c0202cd92a1dd4d8b4f3f5#commitcomment-156776835,
    to properly support scrolling long content in Ratatui for a sequence of
    cells, we need to:
    
    * take the `Vec<Line>` for each cell
    * using the wrapping logic we want to use at render time, compute the
    _effective line count_ using `Paragraph::line_count()` (see
    `wrapped_line_count_for_cell()` in this PR)
    * sum up the effective line count to compute the height of the area
    being scrolled
    * given a `scroll_position: usize`, index into the list of "effective
    lines" and accumulate the appropriate `Vec<Line>` for the cells that
    should be displayed
    * take that `Vec<Line>` to create a `Paragraph` and use the same
    line-wrapping policy that was used in `wrapped_line_count_for_cell()`
    * display the resulting `Paragraph` and use the accounting to display a
    scrollbar with the appropriate thumb size and offset without having to
    render the `Vec<Line>` for the full history
    
    With this change, lines wrap as I expect and everything appears to
    redraw correctly as I resize my terminal!
  • feat: add mcp subcommand to CLI to run Codex as an MCP server (#934)
    Previously, running Codex as an MCP server required a standalone binary
    in our Cargo workspace, but this PR makes it available as a subcommand
    (`mcp`) of the main CLI.
    
    Ran this with:
    
    ```
    RUST_LOG=debug npx @modelcontextprotocol/inspector cargo run --bin codex -- mcp
    ```
    
    and verified it worked as expected in the inspector at
    `http://127.0.0.1:6274/`.
  • feat: add support for commands in the Rust TUI (#935)
    Introduces support for slash commands like in the TypeScript CLI. We do
    not support the full set of commands yet, but the core abstraction is
    there now.
    
    In particular, we have a `SlashCommand` enum and due to thoughtful use
    of the [strum](https://crates.io/crates/strum) crate, it requires
    minimal boilerplate to add a new command to the list.
    
    The key new piece of UI is `CommandPopup`, though the keyboard events
    are still handled by `ChatComposer`. The behavior is roughly as follows:
    
    * if the first character in the composer is `/`, the command popup is
    displayed (if you really want to send a message to Codex that starts
    with a `/`, simply put a space before the `/`)
    * while the popup is displayed, up/down can be used to change the
    selection of the popup
    * if there is a selection, hitting tab completes the command, but does
    not send it
    * if there is a selection, hitting enter sends the command
    * if the prefix of the composer matches a command, the command will be
    visible in the popup so the user can see the description (commands could
    take arguments, so additional text may appear after the command name
    itself)
    
    
    https://github.com/user-attachments/assets/39c3e6ee-eeb7-4ef7-a911-466d8184975f
    
    Incidentally, Codex wrote almost all the code for this PR!
  • chore: move each view used in BottomPane into its own file (#928)
    `BottomPane` was getting a bit unwieldy because it maintained a
    `PaneState` enum with three variants and many of its methods had `match`
    statements to handle each variant. To replace the enum, this PR:
    
    * Introduces a `trait BottomPaneView` that has two implementations:
    `StatusIndicatorView` and `ApprovalModalView`.
    * Migrates `PaneState::TextInput` into its own struct, `ChatComposer`,
    that does **not** implement `BottomPaneView`.
    * Updates `BottomPane` so it has `composer: ChatComposer` and
    `active_view: Option<Box<dyn BottomPaneView<'a> + 'a>>`. The idea is
    that `active_view` takes priority and is displayed when it is `Some`;
    otherwise, `ChatComposer` is displayed.
    * While methods of `BottomPane` often have to check whether
    `active_view` is present to decide which component to delegate to, the
    code is more straightforward than before and introducing new
    implementations of `BottomPaneView` should be less painful.
    
    Because we want to retain the `TextArea` owned by `ChatComposer` even
    when another view is displayed, to keep the ownership logic simple, it
    seemed best to keep `ChatComposer` distinct from `BottomPaneView`.
  • feat: Ctrl+J for newline in Rust TUI, default to one line of height (#926)
    While the `TextArea` used in the Rust TUI is "multiline," it is not like
    an HTML `<textarea>` in that it does not wrap, so there was not much
    benefit to setting `MIN_TEXTAREA_ROWS` to `3`, so this PR changes it to
    `1`. Though there are now three ways to "increase" the height due to
    actual linebreaks:
    
    * paste in multiline content (this worked before this PR)
    * pressing `Ctrl+J` will insert a newline
    * if you have your terminal emulator set such that it is possible to
    press something that `crossterm` interprets as "Enter plus some
    modifier," then now that will also work
    
    Now things look a bit more compact on startup:
    
    <img width="745" alt="image"
    src="https://github.com/user-attachments/assets/86e2857f-f31c-46f5-a80b-1ab2120b266e"
    />
  • fix: change EventMsg enum so every variant takes a single struct (#925)
    https://github.com/openai/codex/pull/922 did this for the
    `SessionConfigured` enum variant, and I think it is generally helpful to
    be able to work with the values as each enum variant as their own type,
    so this converts the remaining variants and updates all of the
    callsites.
    
    Added a simple unit test to verify that the JSON-serialized version of
    `Event` does not have any unexpected nesting.
  • fix: tighten up some logic around session timestamps and ids (#922)
    * update `SessionConfigured` event to include the UUID for the session
    * show the UUID in the Rust TUI
    * use local timestamps in log files instead of UTC
    * include timestamps in log file names for easier discovery
  • feat: introduce --profile for Rust CLI (#921)
    This introduces a much-needed "profile" concept where users can specify
    a collection of options under one name and then pass that via
    `--profile` to the CLI.
    
    This PR introduces the `ConfigProfile` struct and makes it a field of
    `CargoToml`. It further updates
    `Config::load_from_base_config_with_overrides()` to respect
    `ConfigProfile`, overriding default values where appropriate. A detailed
    unit test is added at the end of `config.rs` to verify this behavior.
    
    Details on how to use this feature have also been added to
    `codex-rs/README.md`.
  • fix: fix border style for BottomPane (#893)
    This PR fixes things so that:
    
    * when the `BottomPane` is in the `StatusIndicator` state, the border
    should be dim
    * when the `BottomPane` does not have input focus, the border should be
    dim
    
    To make it easier to enforce this invariant, this PR introduces
    `BottomPane::set_state()` that will:
    
    * update `self.state`
    * call `update_border_for_input_focus()`
    * request a repaint
    
    This should make it easier to enforce other updates for state changes
    going forward.