Commit Graph

123 Commits

  • Support skills shortDescription. (#8278)
    Allow SKILL.md to specify a more human-readable short description as
    skill metadata.
  • feat(app-server): add v2 deprecation notice (#8285)
    Add a v2 event for deprecation notices so we can get rid of
    `codex/event/deprecation_notice`.
  • Support SYSTEM skills. (#8220)
    1. Remove PUBLIC skills and introduce SYSTEM skills embedded in the
    binary and installed into $CODEX_HOME/skills/.system at startup.
    2. Skills are now always enabled (feature flag removed).
    3. Update skills/list to accept forceReload and plumb it through (not
    used by clients yet).
  • chore: cleanup Config instantiation codepaths (#8226)
    This PR does various types of cleanup before I can proceed with more
    ambitious changes to config loading.
    
    First, I noticed duplicated code across these two methods:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L314-L324
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L334-L344
    
    This has now been consolidated in
    `load_config_as_toml_with_cli_overrides()`.
    
    Further, I noticed that `Config::load_with_cli_overrides()` took two
    similar arguments:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L308-L311
    
    The difference between `cli_overrides` and `overrides` was not
    immediately obvious to me. At first glance, it appears that one should
    be able to be expressed in terms of the other, but it turns out that
    some fields of `ConfigOverrides` (such as `cwd` and
    `codex_linux_sandbox_exe`) are, by design, not configurable via a
    `.toml` file or a command-line `--config` flag.
    
    That said, I discovered that many callers of
    `Config::load_with_cli_overrides()` were passing
    `ConfigOverrides::default()` for `overrides`, so I created two separate
    methods:
    
    - `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
    TomlValue)>)`
    - `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
    Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`
    
    The latter has a long name, as it is _not_ what should be used in the
    common case, so the extra typing is designed to draw attention to this
    fact. I tried to update the existing callsites to use the shorter name,
    where possible.
    
    Further, in the cases where `ConfigOverrides` is used, usually only a
    limited subset of fields are actually set, so I updated the declarations
    to leverage `..Default::default()` where possible.
  • feat: make list_models non-blocking (#8198)
    ### Summary
    * Make `app_server.list_models` to be non-blocking and consumers (i.e.
    extension) can manage the flow themselves.
    * Force config to use remote models and therefore fetch codex-auto model
    list.
  • chore: update listMcpServerStatus to be non-blocking (#8151)
    ### Summary
    * Update `listMcpServerStatus` to be non-blocking by wrapping it with
    tokio:spawn.
  • [app-server] add new RawResponseItem v2 event (#8152)
    ``codex/event/raw_response_item` (v1) -> `rawResponseItem/completed`
    (v1).
    
    test client log:
    ````
    < {
    <   "method": "codex/event/raw_response_item",
    <   "params": {
    <     "conversationId": "019b29f7-b089-7140-a535-3fe681562c15",
    <     "id": "0",
    <     "msg": {
    <       "item": {
    <         "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
    <         "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
    <         "name": "shell_command",
    <         "type": "function_call"
    <       },
    <       "type": "raw_response_item"
    <     }
    <   }
    < }
    < {
    <   "method": "rawResponseItem/completed",
    <   "params": {
    <     "item": {
    <       "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
    <       "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
    <       "name": "shell_command",
    <       "type": "function_call"
    <     },
    <     "threadId": "019b29f7-b089-7140-a535-3fe681562c15",
    <     "turnId": "0"
    <   }
    < }
    ```
  • chore: update listMcpServers to listMcpServerStatus (#8114)
    ### Summary
    * rename app server `listMcpServers` to `listMcpServerStatuses`.
  • chore(app-server): remove stubbed thread/compact API (#8086)
    We want to rely on server-side auto-compaction instead of having the
    client trigger context compaction manually. This API was stubbed as a
    placeholder and never implemented.
  • better name for windows sandbox features (#8077)
    `--enable enable...` is a bad look
  • Reimplement skills loading using SkillsManager + skills/list op. (#7914)
    refactor the way we load and manage skills:
    1. Move skill discovery/caching into SkillsManager and reuse it across
    sessions.
    2. Add the skills/list API (Op::ListSkills/SkillsListResponse) to fetch
    skills for one or more cwds. Also update app-server for VSCE/App;
    3. Trigger skills/list during session startup so UIs preload skills and
    handle errors immediately.
  • feat: clean config loading and config api (#7924)
    Check the README of the `config_loader` for details
  • feat: use latest disk value for mcp servers status (#7907)
    ### Summary
    Instead of stale in memory config value for listing mcp server statuses,
    we pull the latest disk value.
  • [app-server] make app server not throw error when login id is not found (#7831)
    Our previous design of cancellation endpoint is not idempotent, which
    caused a bunch of flaky tests. Make app server just returned a not_found
    status instead of throwing an error if the login id is not found. Keep
    V1 endpoint behavior the same.
  • fix: thread/list returning fewer than the requested amount due to filtering CXA-293 (#7509)
    This caused some conversations to not appear when they otherwise should.
    
    Prior to this change, `thread/list`/`list_conversations_common` would:
    - Fetch N conversations from `RolloutRecorder::list_conversations`
    - Then it would filter those (like by the provided `model_providers`)
    - This would make it potentially return less than N items.
    
    With this change:
    - `list_conversations_common` now continues fetching more conversations
    from `RolloutRecorder::list_conversations` until it "fills up" the
    `requested_page_size`.
    - Ultimately this means that clients can rely on getting eg 20
    conversations if they request 20 conversations.
  • [app-server-protocol] Add types for config (#7658)
    Currently the config returned by `config/read` in untyped. Add types so
    it's easier for client to parse the config. Since currently configs are
    all defined in snake case we'll keep that instead of using camel case
    like the rest of V2.
    
    Sample output by testing using the app server test client:
    ```
    {
    <   "id": "f28449f4-b015-459b-b07b-eef06980165d",
    <   "result": {
    <     "config": {
    <       "approvalPolicy": null,
    <       "compactPrompt": null,
    <       "developerInstructions": null,
    <       "features": {
    <         "experimental_use_rmcp_client": true
    <       },
    <       "forcedChatgptWorkspaceId": null,
    <       "forcedLoginMethod": null,
    <       "instructions": null,
    <       "model": "gpt-5.1-codex-max",
    <       "modelAutoCompactTokenLimit": null,
    <       "modelContextWindow": null,
    <       "modelProvider": null,
    <       "modelReasoningEffort": null,
    <       "modelReasoningSummary": null,
    <       "modelVerbosity": null,
    <       "model_providers": {
    <         "local": {
    <           "base_url": "http://localhost:8061/api/codex",
    <           "env_http_headers": {
    <             "ChatGPT-Account-ID": "OPENAI_ACCOUNT_ID"
    <           },
    <           "env_key": "CHATGPT_TOKEN_STAGING",
    <           "name": "local",
    <           "wire_api": "responses"
    <         }
    <       },
    <       "model_reasoning_effort": "medium",
    <       "notice": {
    <         "hide_gpt-5.1-codex-max_migration_prompt": true,
    <         "hide_gpt5_1_migration_prompt": true
    <       },
    <       "profile": null,
    <       "profiles": {},
    <       "projects": {
    <         "/Users/celia/code": {
    <           "trust_level": "trusted"
    <         },
    <         "/Users/celia/code/codex": {
    <           "trust_level": "trusted"
    <         },
    <         "/Users/celia/code/openai": {
    <           "trust_level": "trusted"
    <         }
    <       },
    <       "reviewModel": null,
    <       "sandboxMode": null,
    <       "sandboxWorkspaceWrite": null,
    <       "tools": {
    <         "viewImage": null,
    <         "webSearch": null
    <       }
    <     },
    <     "origins": {
    <       "features.experimental_use_rmcp_client": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.base_url": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.env_http_headers.ChatGPT-Account-ID": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.env_key": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.name": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_providers.local.wire_api": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "model_reasoning_effort": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "notice.hide_gpt-5.1-codex-max_migration_prompt": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "notice.hide_gpt5_1_migration_prompt": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code/codex.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "projects./Users/celia/code/openai.trust_level": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       },
    <       "tools.web_search": {
    <         "name": "user",
    <         "source": "/Users/celia/.codex/config.toml",
    <         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
    <       }
    <     }
    <   }
    < }
    ```
  • make model optional in config (#7769)
    - Make Config.model optional and centralize default-selection logic in
    ModelsManager, including a default_model helper (with
    codex-auto-balanced when available) so sessions now carry an explicit
    chosen model separate from the base config.
    - Resolve `model` once in `core` and `tui` from config. Then store the
    state of it on other structs.
    - Move refreshing models to be before resolving the default model
  • [app-server] Make sure that config writes preserve comments & order or configs (#7789)
    Make sure that config writes preserve comments and order of configs by
    utilizing the ConfigEditsBuilder in core.
    
    Tested by running a real example and made sure that nothing in the
    config file changes other than the configs to edit.
  • Removed experimental "command risk assessment" feature (#7799)
    This experimental feature received lukewarm reception during internal
    testing. Removing from the code base.
  • refactoring with_escalated_permissions to use SandboxPermissions instead (#7750)
    helpful in the future if we want more granularity for requesting
    escalated permissions:
    e.g when running in readonly sandbox, model can request to escalate to a
    sandbox that allows writes
  • feat: support mcp in-session login (#7751)
    ### Summary
    * Added `mcpServer/oauthLogin` in app server for supporting in session
    MCP server login
    * Added `McpServerOauthLoginParams` and `McpServerOauthLoginResponse` to
    support above method with response returning the auth URL for consumer
    to open browser or display accordingly.
    * Added `McpServerOauthLoginCompletedNotification` which the app server
    would emit on MCP server login success or failure (i.e. timeout).
    * Refactored rmcp-client oath_login to have the ability on starting a
    auth server which the codex_message_processor uses for in-session auth.
  • updating app server types to support execpoilcy amendment (#7747)
    also includes minor refactor merging `ApprovalDecision` with
    `CommandExecutionRequestAcceptSettings`
  • fix: taking plan type from usage endpoint instead of thru auth token (#7610)
    pull plan type from the usage endpoint, persist it in session state /
    tui state, and propagate through rate limit snapshots
  • fix(app-server): add will_retry to ErrorNotification (#7611)
    VSCE renders `codex/event/stream_error` (automatically retried, e.g.
    `"Reconnecting... 1/n"`) and `codex/event/error` (terminal errors)
    differently, so add `will_retry` on ErrorNotification to indicate this.
  • fix(app-server): add duration_ms to McpToolCallItem (#7605)
    Seems like a nice field to have, and also VSCE does render this one.
  • Refactor execpolicy fallback evaluation (#7544)
    ## Refactor of the `execpolicy` crate
    
    To illustrate why we need this refactor, consider an agent attempting to
    run `apple | rm -rf ./`. Suppose `apple` is allowed by `execpolicy`.
    Before this PR, `execpolicy` would consider `apple` and `pear` and only
    render one rule match: `Allow`. We would skip any heuristics checks on
    `rm -rf ./` and immediately approve `apple | rm -rf ./` to run.
    
    To fix this, we now thread a `fallback` evaluation function into
    `execpolicy` that runs when no `execpolicy` rules match a given command.
    In our example, we would run `fallback` on `rm -rf ./` and prevent
    `apple | rm -rf ./` from being run without approval.
  • whitelist command prefix integration in core and tui (#7033)
    this PR enables TUI to approve commands and add their prefixes to an
    allowlist:
    <img width="708" height="605" alt="Screenshot 2025-11-21 at 4 18 07 PM"
    src="https://github.com/user-attachments/assets/56a19893-4553-4770-a881-becf79eeda32"
    />
    
    note: we only show the option to whitelist the command when 
    1) command is not multi-part (e.g `git add -A && git commit -m 'hello
    world'`)
    2) command is not already matched by an existing rule
  • [app-server] make file_path for config optional (#7560)
    When we are writing to config using `config/value/write` or
    `config/batchWrite`, it always require a `config/read` before it right
    now in order to get the correct file path to write to. make this
    optional so we read from the default user config file if this is not
    passed in.
  • [app-server] fix: add thread_id to turn/plan/updated (#7553)
    Realized we're missing this while migrating VSCE.
  • Migrate model preset (#7542)
    - Introduce `openai_models` in `/core`
    - Move `PRESETS` under it
    - Move `ModelPreset`, `ModelUpgrade`, `ReasoningEffortPreset`,
    `ReasoningEffortPreset`, and `ReasoningEffortPreset` to `protocol`
    - Introduce `Op::ListModels` and `EventMsg::AvailableModels`
    
    Next steps:
    - migrate `app-server` and `tui` to use the introduced Operation
  • chore: conversation_id -> thread_id in app-server feedback/upload (#7538)
    Use `thread_id: Option<String>` instead of `conversation_id:
    Option<ConversationId>` to be consistent with the rest of app-server v2
    APIs.
  • feat: support list mcp servers in app server (#7505)
    ### Summary
    Added `mcp/servers/list` which is equivalent to `/mcp` slash command in
    CLI for response. This will be used in VSCE MCP settings to show log in
    status, available tools etc.
  • fix: remove serde(flatten) annotation for TurnError (#7499)
    The problem with using `serde(flatten)` on Turn status is that it
    conditionally serializes the `error` field, which is not the pattern we
    want in API v2 where all fields on an object should always be returned.
    
    ```
    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
    #[serde(rename_all = "camelCase")]
    #[ts(export_to = "v2/")]
    pub struct Turn {
        pub id: String,
        /// Only populated on a `thread/resume` response.
        /// For all other responses and notifications returning a Turn,
        /// the items field will be an empty list.
        pub items: Vec<ThreadItem>,
        #[serde(flatten)]
        pub status: TurnStatus,
    }
    
    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
    #[serde(tag = "status", rename_all = "camelCase")]
    #[ts(tag = "status", export_to = "v2/")]
    pub enum TurnStatus {
        Completed,
        Interrupted,
        Failed { error: TurnError },
        InProgress,
    }
    ```
    
    serializes to:
    ```
    {
      "id": "turn-123",
      "items": [],
      "status": "completed"
    }
    
    {
      "id": "turn-123",
      "items": [],
      "status": "failed",
      "error": {
        "message": "Tool timeout",
        "codexErrorInfo": null
      }
    }
    ```
    
    Instead we want:
    ```
    {
      "id": "turn-123",
      "items": [],
      "status": "completed",
      "error": null
    }
    
    {
      "id": "turn-123",
      "items": [],
      "status": "failed",
      "error": {
        "message": "Tool timeout",
        "codexErrorInfo": null
      }
    }
    ```
  • fix: add ts number annotations for app-server v2 types (#7492)
    These will be more ergonomic to work with in Typescript.
  • [app-server] Add ImageView item (#7468)
    Add view_image tool call as image_view item.
    
    Before:
    ```
    < {
    <   "method": "codex/event/view_image_tool_call",
    <   "params": {
    <     "conversationId": "019adc2f-2922-7e43-ace9-64f394019616",
    <     "id": "0",
    <     "msg": {
    <       "call_id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
    <       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
    <       "type": "view_image_tool_call"
    <     }
    <   }
    < }
    ```
    
    After:
    ```
    < {
    <   "method": "item/started",
    <   "params": {
    <     "item": {
    <       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
    <       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
    <       "type": "imageView"
    <     },
    <     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
    <     "turnId": "0"
    <   }
    < }
    
    < {
    <   "method": "item/completed",
    <   "params": {
    <     "item": {
    <       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
    <       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
    <       "type": "imageView"
    <     },
    <     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
    <     "turnId": "0"
    <   }
    < }
    ```
  • [app-server] fix: ensure thread_id and turn_id are on all events (#7408)
    This is an improvement for client-side developer ergonomics by
    simplifying the state the client needs to keep track of.
  • [app-server] add turn/plan/updated event (#7329)
    transform `EventMsg::PlanDate` to v2 `turn/plan/updated` event. similar
    to `turn/diff/updated`.
  • [app-server] add thread/tokenUsage/updated v2 event (#7268)
    the TokenEvent event message becomes `thread/tokenUsage/updated` in v2.
    before & after:
    ```
    < {
    <   "method": "codex/event/token_count",
    <   "params": {
    <     "conversationId": "019ab891-4c55-7790-9670-6c3b48c33281",
    <     "id": "1",
    <     "msg": {
    <       "info": {
    <         "last_token_usage": {
    <           "cached_input_tokens": 3072,
    <           "input_tokens": 5152,
    <           "output_tokens": 16,
    <           "reasoning_output_tokens": 0,
    <           "total_tokens": 5168
    <         },
    <         "model_context_window": 258400,
    <         "total_token_usage": {
    <           "cached_input_tokens": 3072,
    <           "input_tokens": 5152,
    <           "output_tokens": 16,
    <           "reasoning_output_tokens": 0,
    <           "total_tokens": 5168
    <         }
    <       },
    <       "rate_limits": {
    <         "credits": null,
    <         "primary": null,
    <         "secondary": null
    <       },
    <       "type": "token_count"
    <     }
    <   }
    < }
    < {
    <   "method": "thread/tokenUsage/updated",
    <   "params": {
    <     "threadId": "019ab891-4c55-7790-9670-6c3b48c33281",
    <     "tokenUsage": {
    <       "last": {
    <         "cachedInputTokens": 3072,
    <         "inputTokens": 5152,
    <         "outputTokens": 16,
    <         "reasoningOutputTokens": 0,
    <         "totalTokens": 5168
    <       },
    <       "modelContextWindow": 258400,
    <       "total": {
    <         "cachedInputTokens": 3072,
    <         "inputTokens": 5152,
    <         "outputTokens": 16,
    <         "reasoningOutputTokens": 0,
    <         "totalTokens": 5168
    <       }
    <     },
    <     "turnId": "1"
    <   }
    < }
    ```
  • [app-server] feat: add turn/diff/updated event (#7279)
    This is the V2 version of `EventMsg::TurnDiff`.
    
    I decided to expose this as a `turn/*` notification as opposed to an
    Item to make it more explicit that the diff is accumulated throughout a
    turn (every `apply_patch` call updates the running diff). Also, I don't
    think it's worth persisting this diff as an Item because it can always
    be recomputed from the actual `FileChange` Items.
  • [app-server] feat: add thread_id and turn_id to item and error notifications (#7124)
    Add `thread_id` and `turn_id` to `item/started`, `item/completed`, and
    `error` notifications. Otherwise the client will have a hard time
    knowing which thread & turn (if multiple threads are running in
    parallel) a new item/error is for.
    
    Also add `thread_id` to `turn/started` and `turn/completed`.