diff --git a/.codespellrc b/.codespellrc index 1d7bbffdf..92cf5f7dc 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,6 +1,6 @@ [codespell] # Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,**/node_modules/**,**/dist/**,**/target/**,sdk/native/dist-examples_ignored/**,sdk/native/dist-examples/**,sdk/native/package-lock.json,**/.venv/**,**/.codex-ci/**,**/.fastembed_cache/** +skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,.codespellignore,*test.ts,*.jsonl,frame*.txt,**/node_modules/**,**/dist/**,**/target/**,sdk/native/dist-examples_ignored/**,sdk/native/dist-examples/**,sdk/native/package-lock.json,**/.cache/**,**/.venv/**,**/.codex-ci/**,**/.fastembed_cache/** check-hidden = true ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b -ignore-words-list = ratatui,ser,iTerm,trys,vEw,ANE +ignore-words-list = ratatui,ser,iTerm,trys,vEw,ANE,psuedo diff --git a/.github/actions/macos-code-sign/action.yml b/.github/actions/macos-code-sign/action.yml new file mode 100644 index 000000000..5c11ac772 --- /dev/null +++ b/.github/actions/macos-code-sign/action.yml @@ -0,0 +1,212 @@ +name: macos-code-sign +description: Configure, sign, notarize, and clean up macOS code signing artifacts. +inputs: + target: + description: Rust compilation target triple (e.g. aarch64-apple-darwin). + required: true + apple-certificate: + description: Base64-encoded Apple signing certificate (P12). + required: true + apple-certificate-password: + description: Password for the signing certificate. + required: true + apple-notarization-key-p8: + description: Base64-encoded Apple notarization key (P8). + required: true + apple-notarization-key-id: + description: Apple notarization key ID. + required: true + apple-notarization-issuer-id: + description: Apple notarization issuer ID. + required: true +runs: + using: composite + steps: + - name: Configure Apple code signing + shell: bash + env: + KEYCHAIN_PASSWORD: actions + APPLE_CERTIFICATE: ${{ inputs.apple-certificate }} + APPLE_CERTIFICATE_PASSWORD: ${{ inputs.apple-certificate-password }} + run: | + set -euo pipefail + + if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then + echo "APPLE_CERTIFICATE is required for macOS signing" + exit 1 + fi + + if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then + echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing" + exit 1 + fi + + cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12" + echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path" + + keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + security set-keychain-settings -lut 21600 "$keychain_path" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + + keychain_args=() + cleanup_keychain() { + if ((${#keychain_args[@]} > 0)); then + security list-keychains -s "${keychain_args[@]}" || true + security default-keychain -s "${keychain_args[0]}" || true + else + security list-keychains -s || true + fi + if [[ -f "$keychain_path" ]]; then + security delete-keychain "$keychain_path" || true + fi + } + + while IFS= read -r keychain; do + [[ -n "$keychain" ]] && keychain_args+=("$keychain") + done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') + + if ((${#keychain_args[@]} > 0)); then + security list-keychains -s "$keychain_path" "${keychain_args[@]}" + else + security list-keychains -s "$keychain_path" + fi + + security default-keychain -s "$keychain_path" + security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null + + codesign_hashes=() + while IFS= read -r hash; do + [[ -n "$hash" ]] && codesign_hashes+=("$hash") + done < <(security find-identity -v -p codesigning "$keychain_path" \ + | sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \ + | sort -u) + + if ((${#codesign_hashes[@]} == 0)); then + echo "No signing identities found in $keychain_path" + cleanup_keychain + rm -f "$cert_path" + exit 1 + fi + + if ((${#codesign_hashes[@]} > 1)); then + echo "Multiple signing identities found in $keychain_path:" + printf ' %s\n' "${codesign_hashes[@]}" + cleanup_keychain + rm -f "$cert_path" + exit 1 + fi + + APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}" + + rm -f "$cert_path" + + echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV" + echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV" + echo "::add-mask::$APPLE_CODESIGN_IDENTITY" + + - name: Sign macOS binaries + shell: bash + run: | + set -euo pipefail + + if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then + echo "APPLE_CODESIGN_IDENTITY is required for macOS signing" + exit 1 + fi + + keychain_args=() + if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then + keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}") + fi + + for binary in codex codex-responses-api-proxy; do + path="codex-rs/target/${{ inputs.target }}/release/${binary}" + codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path" + done + + - name: Notarize macOS binaries + shell: bash + env: + APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} + APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }} + APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }} + run: | + set -euo pipefail + + for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do + if [[ -z "${!var:-}" ]]; then + echo "$var is required for notarization" + exit 1 + fi + done + + notary_key_path="${RUNNER_TEMP}/notarytool.key.p8" + echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path" + cleanup_notary() { + rm -f "$notary_key_path" + } + trap cleanup_notary EXIT + + notarize_binary() { + local binary="$1" + local source_path="codex-rs/target/${{ inputs.target }}/release/${binary}" + local archive_path="${RUNNER_TEMP}/${binary}.zip" + + if [[ ! -f "$source_path" ]]; then + echo "Binary $source_path not found" + exit 1 + fi + + rm -f "$archive_path" + ditto -c -k --keepParent "$source_path" "$archive_path" + + submission_json=$(xcrun notarytool submit "$archive_path" \ + --key "$notary_key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ + --output-format json \ + --wait) + + status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') + submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""') + + if [[ -z "$submission_id" ]]; then + echo "Failed to retrieve submission ID for $binary" + exit 1 + fi + + echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}" + + if [[ "$status" != "Accepted" ]]; then + echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})" + exit 1 + fi + } + + notarize_binary "codex" + notarize_binary "codex-responses-api-proxy" + + - name: Remove signing keychain + if: ${{ always() }} + shell: bash + env: + APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }} + run: | + set -euo pipefail + if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then + keychain_args=() + while IFS= read -r keychain; do + [[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue + [[ -n "$keychain" ]] && keychain_args+=("$keychain") + done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') + if ((${#keychain_args[@]} > 0)); then + security list-keychains -s "${keychain_args[@]}" + security default-keychain -s "${keychain_args[0]}" + fi + + if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then + security delete-keychain "$APPLE_CODESIGN_KEYCHAIN" + fi + fi diff --git a/.github/actions/windows-code-sign/action.yml b/.github/actions/windows-code-sign/action.yml index 2be64efc9..f6cf73791 100644 --- a/.github/actions/windows-code-sign/action.yml +++ b/.github/actions/windows-code-sign/action.yml @@ -53,3 +53,5 @@ runs: files: | ${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex.exe ${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-responses-api-proxy.exe + ${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-windows-sandbox-setup.exe + ${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-command-runner.exe diff --git a/.github/dotslash-config.json b/.github/dotslash-config.json index 5e28cdf20..00e9032cf 100644 --- a/.github/dotslash-config.json +++ b/.github/dotslash-config.json @@ -55,6 +55,30 @@ "path": "codex-responses-api-proxy.exe" } } + }, + "codex-command-runner": { + "platforms": { + "windows-x86_64": { + "regex": "^codex-command-runner-x86_64-pc-windows-msvc\\.exe\\.zst$", + "path": "codex-command-runner.exe" + }, + "windows-aarch64": { + "regex": "^codex-command-runner-aarch64-pc-windows-msvc\\.exe\\.zst$", + "path": "codex-command-runner.exe" + } + } + }, + "codex-windows-sandbox-setup": { + "platforms": { + "windows-x86_64": { + "regex": "^codex-windows-sandbox-setup-x86_64-pc-windows-msvc\\.exe\\.zst$", + "path": "codex-windows-sandbox-setup.exe" + }, + "windows-aarch64": { + "regex": "^codex-windows-sandbox-setup-aarch64-pc-windows-msvc\\.exe\\.zst$", + "path": "codex-windows-sandbox-setup.exe" + } + } } } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6251f1506..7f9fd8cd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: - name: Upload staged npm package artifact if: github.repository == 'openai/codex-native' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: codex-npm-staging path: ${{ steps.stage_npm_package.outputs.pack_output }} diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 364425c2a..828a5b738 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -176,7 +176,7 @@ jobs: # avoid caching the large target dir on the gnu-dev job. - name: Restore cargo home cache id: cache_cargo_home_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: | ~/.cargo/bin/ @@ -217,7 +217,7 @@ jobs: - name: Restore sccache cache (fallback) if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} id: cache_sccache_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: ${{ github.workspace }}/.sccache/ key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} @@ -236,7 +236,7 @@ jobs: - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} name: Restore APT cache (musl) id: cache_apt_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: | /var/cache/apt @@ -290,7 +290,7 @@ jobs: - name: Save cargo home cache if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' continue-on-error: true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: | ~/.cargo/bin/ @@ -302,7 +302,7 @@ jobs: - name: Save sccache cache (fallback) if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' continue-on-error: true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ${{ github.workspace }}/.sccache/ key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} @@ -327,7 +327,7 @@ jobs: - name: Save APT cache (musl) if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true' continue-on-error: true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: | /var/cache/apt @@ -395,28 +395,6 @@ jobs: /opt/ghc sudo apt-get remove -y docker.io docker-compose podman buildah - # Ensure brew includes this fix so that brew's shellenv.sh loads - # cleanly in the Codex sandbox (it is frequently eval'd via .zprofile - # for Brew users, including the macOS runners on GitHub): - # - # https://github.com/Homebrew/brew/pull/21157 - # - # Once brew 5.0.5 is released and is the default on macOS runners, this - # step can be removed. - - name: Upgrade brew - if: ${{ startsWith(matrix.runner, 'macos') }} - shell: bash - run: | - set -euo pipefail - brew --version - git -C "$(brew --repo)" fetch origin - git -C "$(brew --repo)" checkout main - git -C "$(brew --repo)" reset --hard origin/main - export HOMEBREW_UPDATE_TO_TAG=0 - brew update - brew upgrade - brew --version - # Some integration tests rely on DotSlash being installed. # See https://github.com/openai/codex/pull/7617. - name: Install DotSlash @@ -447,7 +425,7 @@ jobs: - name: Restore cargo home cache id: cache_cargo_home_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: | ~/.cargo/bin/ @@ -487,7 +465,7 @@ jobs: - name: Restore sccache cache (fallback) if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} id: cache_sccache_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: ${{ github.workspace }}/.sccache/ key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} @@ -510,7 +488,7 @@ jobs: - name: Save cargo home cache if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' continue-on-error: true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: | ~/.cargo/bin/ @@ -522,7 +500,7 @@ jobs: - name: Save sccache cache (fallback) if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' continue-on-error: true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ${{ github.workspace }}/.sccache/ key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} diff --git a/.github/workflows/rust-release-prepare.yml b/.github/workflows/rust-release-prepare.yml new file mode 100644 index 000000000..94e0f59c8 --- /dev/null +++ b/.github/workflows/rust-release-prepare.yml @@ -0,0 +1,52 @@ +name: rust-release-prepare +on: + workflow_dispatch: + schedule: + - cron: "0 */4 * * *" + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + + - name: Update models.json + env: + OPENAI_API_KEY: ${{ secrets.CODEX_OPENAI_API_KEY }} + run: | + set -euo pipefail + + client_version="99.99.99" + terminal_info="github-actions" + user_agent="codex_cli_rs/99.99.99 (Linux $(uname -r); $(uname -m)) ${terminal_info}" + base_url="${OPENAI_BASE_URL:-https://chatgpt.com/backend-api/codex}" + + headers=( + -H "Authorization: Bearer ${OPENAI_API_KEY}" + -H "User-Agent: ${user_agent}" + ) + + url="${base_url%/}/models?client_version=${client_version}" + curl --http1.1 --fail --show-error --location "${headers[@]}" "${url}" | jq '.' > codex-rs/core/models.json + + - name: Open pull request (if changed) + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "Update models.json" + title: "Update models.json" + body: "Automated update of models.json." + branch: "bot/update-models-json" + reviewers: "pakrym-oai,aibrahim-oai" + delete-branch: true + path: codex-rs diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 141b2bd6e..65ecd0a5d 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -84,7 +84,7 @@ jobs: with: targets: ${{ matrix.target }} - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ~/.cargo/bin/ @@ -101,7 +101,13 @@ jobs: sudo apt-get install -y musl-tools pkg-config - name: Cargo build - run: cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy + shell: bash + run: | + if [[ "${{ contains(matrix.target, 'windows') }}" == 'true' ]]; then + cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy --bin codex-windows-sandbox-setup --bin codex-command-runner + else + cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy + fi - if: ${{ contains(matrix.target, 'linux') }} name: Cosign Linux artifacts @@ -123,173 +129,15 @@ jobs: certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} - if: ${{ matrix.runner == 'macos-15-xlarge' }} - name: Configure Apple code signing - shell: bash - env: - KEYCHAIN_PASSWORD: actions - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_P12 }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - run: | - set -euo pipefail - - if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then - echo "APPLE_CERTIFICATE is required for macOS signing" - exit 1 - fi - - if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then - echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing" - exit 1 - fi - - cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12" - echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path" - - keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db" - security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" - security set-keychain-settings -lut 21600 "$keychain_path" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" - - keychain_args=() - cleanup_keychain() { - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "${keychain_args[@]}" || true - security default-keychain -s "${keychain_args[0]}" || true - else - security list-keychains -s || true - fi - if [[ -f "$keychain_path" ]]; then - security delete-keychain "$keychain_path" || true - fi - } - - while IFS= read -r keychain; do - [[ -n "$keychain" ]] && keychain_args+=("$keychain") - done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') - - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "$keychain_path" "${keychain_args[@]}" - else - security list-keychains -s "$keychain_path" - fi - - security default-keychain -s "$keychain_path" - security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security - security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null - - codesign_hashes=() - while IFS= read -r hash; do - [[ -n "$hash" ]] && codesign_hashes+=("$hash") - done < <(security find-identity -v -p codesigning "$keychain_path" \ - | sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \ - | sort -u) - - if ((${#codesign_hashes[@]} == 0)); then - echo "No signing identities found in $keychain_path" - cleanup_keychain - rm -f "$cert_path" - exit 1 - fi - - if ((${#codesign_hashes[@]} > 1)); then - echo "Multiple signing identities found in $keychain_path:" - printf ' %s\n' "${codesign_hashes[@]}" - cleanup_keychain - rm -f "$cert_path" - exit 1 - fi - - APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}" - - rm -f "$cert_path" - - echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV" - echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV" - echo "::add-mask::$APPLE_CODESIGN_IDENTITY" - - - if: ${{ matrix.runner == 'macos-15-xlarge' }} - name: Sign macOS binaries - shell: bash - run: | - set -euo pipefail - - if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then - echo "APPLE_CODESIGN_IDENTITY is required for macOS signing" - exit 1 - fi - - keychain_args=() - if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then - keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}") - fi - - for binary in codex codex-responses-api-proxy; do - path="target/${{ matrix.target }}/release/${binary}" - codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path" - done - - - if: ${{ matrix.runner == 'macos-15-xlarge' }} - name: Notarize macOS binaries - shell: bash - env: - APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} - APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} - APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} - run: | - set -euo pipefail - - for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do - if [[ -z "${!var:-}" ]]; then - echo "$var is required for notarization" - exit 1 - fi - done - - notary_key_path="${RUNNER_TEMP}/notarytool.key.p8" - echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path" - cleanup_notary() { - rm -f "$notary_key_path" - } - trap cleanup_notary EXIT - - notarize_binary() { - local binary="$1" - local source_path="target/${{ matrix.target }}/release/${binary}" - local archive_path="${RUNNER_TEMP}/${binary}.zip" - - if [[ ! -f "$source_path" ]]; then - echo "Binary $source_path not found" - exit 1 - fi - - rm -f "$archive_path" - ditto -c -k --keepParent "$source_path" "$archive_path" - - submission_json=$(xcrun notarytool submit "$archive_path" \ - --key "$notary_key_path" \ - --key-id "$APPLE_NOTARIZATION_KEY_ID" \ - --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ - --output-format json \ - --wait) - - status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') - submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""') - - if [[ -z "$submission_id" ]]; then - echo "Failed to retrieve submission ID for $binary" - exit 1 - fi - - echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}" - - if [[ "$status" != "Accepted" ]]; then - echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})" - exit 1 - fi - } - - notarize_binary "codex" - notarize_binary "codex-responses-api-proxy" + name: MacOS code signing + uses: ./.github/actions/macos-code-sign + with: + target: ${{ matrix.target }} + apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} + apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} + apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} + apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} - name: Stage artifacts shell: bash @@ -300,6 +148,8 @@ jobs: if [[ "${{ matrix.runner }}" == windows* ]]; then cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe" cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe" + cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe" + cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe" else cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}" cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}" @@ -372,30 +222,7 @@ jobs: zstd "${zstd_args[@]}" "$dest/$base" done - - name: Remove signing keychain - if: ${{ always() && matrix.runner == 'macos-15-xlarge' }} - shell: bash - env: - APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }} - run: | - set -euo pipefail - if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then - keychain_args=() - while IFS= read -r keychain; do - [[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue - [[ -n "$keychain" ]] && keychain_args+=("$keychain") - done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "${keychain_args[@]}" - security default-keychain -s "${keychain_args[0]}" - fi - - if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then - security delete-keychain "$APPLE_CODESIGN_KEYCHAIN" - fi - fi - - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: ${{ matrix.target }} # Upload the per-binary .zst files as well as the new .tar.gz @@ -431,7 +258,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: dist diff --git a/.github/workflows/shell-tool-mcp-ci.yml b/.github/workflows/shell-tool-mcp-ci.yml index 5e0945964..739cc6e40 100644 --- a/.github/workflows/shell-tool-mcp-ci.yml +++ b/.github/workflows/shell-tool-mcp-ci.yml @@ -30,7 +30,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: "pnpm" diff --git a/.github/workflows/shell-tool-mcp.yml b/.github/workflows/shell-tool-mcp.yml index fd016cfaa..7ca49388f 100644 --- a/.github/workflows/shell-tool-mcp.yml +++ b/.github/workflows/shell-tool-mcp.yml @@ -113,7 +113,7 @@ jobs: cp "target/${{ matrix.target }}/release/codex-exec-mcp-server" "$dest/" cp "target/${{ matrix.target }}/release/codex-execve-wrapper" "$dest/" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: shell-tool-mcp-rust-${{ matrix.target }} path: artifacts/** @@ -211,7 +211,7 @@ jobs: mkdir -p "$dest" cp bash "$dest/bash" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }} path: artifacts/** @@ -253,7 +253,7 @@ jobs: mkdir -p "$dest" cp bash "$dest/bash" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }} path: artifacts/** @@ -280,7 +280,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} @@ -291,7 +291,7 @@ jobs: run: pnpm --filter @openai/codex-shell-tool-mcp run build - name: Download build artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: artifacts @@ -352,7 +352,7 @@ jobs: filename=$(PACK_INFO="$pack_info" node -e 'const data = JSON.parse(process.env.PACK_INFO); console.log(data[0].filename);') mv "dist/npm/${filename}" "dist/npm/codex-shell-tool-mcp-npm-${PACKAGE_VERSION}.tgz" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: codex-shell-tool-mcp-npm path: dist/npm/codex-shell-tool-mcp-npm-${{ env.PACKAGE_VERSION }}.tgz @@ -376,7 +376,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org @@ -386,7 +386,7 @@ jobs: run: npm install -g npm@latest - name: Download npm tarball - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: codex-shell-tool-mcp-npm path: dist/npm diff --git a/AGENTS.md b/AGENTS.md index 938b153fe..01195c9dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,6 @@ In the codex-rs folder where the rust code lives: - Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if - Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args - Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls -- Do not use unsigned integer even if the number cannot be negative. - When writing tests, prefer comparing the equality of entire objects over fields one by one. - When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable. @@ -95,6 +94,7 @@ The `sdk/native` package provides Node.js bindings to the Rust engine via N‑AP - Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already. - Prefer deep equals comparisons whenever possible. Perform `assert_eq!()` on entire objects, rather than individual fields. +- Avoid mutating process environment in tests; prefer passing environment-derived flags or dependencies from above. ### Integration tests (core) diff --git a/codex-cli/bin/codex.js b/codex-cli/bin/codex.js index 138796e5d..6ec8069bd 100644 --- a/codex-cli/bin/codex.js +++ b/codex-cli/bin/codex.js @@ -95,6 +95,14 @@ function detectPackageManager() { return "bun"; } + + if ( + __dirname.includes(".bun/install/global") || + __dirname.includes(".bun\\install\\global") + ) { + return "bun"; + } + return userAgent ? "npm" : null; } diff --git a/codex-cli/scripts/build_npm_package.py b/codex-cli/scripts/build_npm_package.py index ef96bef2e..bf0eb5f46 100755 --- a/codex-cli/scripts/build_npm_package.py +++ b/codex-cli/scripts/build_npm_package.py @@ -20,9 +20,14 @@ "codex-responses-api-proxy": ["codex-responses-api-proxy"], "codex-sdk": ["codex"], } +WINDOWS_ONLY_COMPONENTS: dict[str, list[str]] = { + "codex": ["codex-windows-sandbox-setup", "codex-command-runner"], +} COMPONENT_DEST_DIR: dict[str, str] = { "codex": "codex", "codex-responses-api-proxy": "codex-responses-api-proxy", + "codex-windows-sandbox-setup": "codex", + "codex-command-runner": "codex", "rg": "path", } @@ -103,7 +108,7 @@ def main() -> int: "pointing to a directory containing pre-installed binaries." ) - copy_native_binaries(vendor_src, staging_dir, native_components) + copy_native_binaries(vendor_src, staging_dir, package, native_components) if release_version: staging_dir_str = str(staging_dir) @@ -232,7 +237,12 @@ def stage_codex_sdk_sources(staging_dir: Path) -> None: shutil.copy2(license_src, staging_dir / "LICENSE") -def copy_native_binaries(vendor_src: Path, staging_dir: Path, components: list[str]) -> None: +def copy_native_binaries( + vendor_src: Path, + staging_dir: Path, + package: str, + components: list[str], +) -> None: vendor_src = vendor_src.resolve() if not vendor_src.exists(): raise RuntimeError(f"Vendor source directory not found: {vendor_src}") @@ -250,6 +260,9 @@ def copy_native_binaries(vendor_src: Path, staging_dir: Path, components: list[s if not target_dir.is_dir(): continue + if "windows" in target_dir.name: + components_set.update(WINDOWS_ONLY_COMPONENTS.get(package, [])) + dest_target_dir = vendor_dest / target_dir.name dest_target_dir.mkdir(parents=True, exist_ok=True) diff --git a/codex-cli/scripts/install_native_deps.py b/codex-cli/scripts/install_native_deps.py index 8d3909c9e..f2c3987b2 100755 --- a/codex-cli/scripts/install_native_deps.py +++ b/codex-cli/scripts/install_native_deps.py @@ -36,8 +36,11 @@ class BinaryComponent: artifact_prefix: str # matches the artifact filename prefix (e.g. codex-.zst) dest_dir: str # directory under vendor// where the binary is installed binary_basename: str # executable name inside dest_dir (before optional .exe) + targets: tuple[str, ...] | None = None # limit installation to specific targets +WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target) + BINARY_COMPONENTS = { "codex": BinaryComponent( artifact_prefix="codex", @@ -49,6 +52,18 @@ class BinaryComponent: dest_dir="codex-responses-api-proxy", binary_basename="codex-responses-api-proxy", ), + "codex-windows-sandbox-setup": BinaryComponent( + artifact_prefix="codex-windows-sandbox-setup", + dest_dir="codex", + binary_basename="codex-windows-sandbox-setup", + targets=WINDOWS_TARGETS, + ), + "codex-command-runner": BinaryComponent( + artifact_prefix="codex-command-runner", + dest_dir="codex", + binary_basename="codex-command-runner", + targets=WINDOWS_TARGETS, + ), } RG_TARGET_PLATFORM_PAIRS: list[tuple[str, str]] = [ @@ -79,7 +94,8 @@ def parse_args() -> argparse.Namespace: choices=tuple(list(BINARY_COMPONENTS) + ["rg"]), help=( "Limit installation to the specified components." - " May be repeated. Defaults to 'codex' and 'rg'." + " May be repeated. Defaults to codex, codex-windows-sandbox-setup," + " codex-command-runner, and rg." ), ) parser.add_argument( @@ -101,7 +117,12 @@ def main() -> int: vendor_dir = codex_cli_root / VENDOR_DIR_NAME vendor_dir.mkdir(parents=True, exist_ok=True) - components = args.components or ["codex", "rg"] + components = args.components or [ + "codex", + "codex-windows-sandbox-setup", + "codex-command-runner", + "rg", + ] workflow_url = (args.workflow_url or DEFAULT_WORKFLOW_URL).strip() if not workflow_url: @@ -116,8 +137,7 @@ def main() -> int: install_binary_components( artifacts_dir, vendor_dir, - BINARY_TARGETS, - [name for name in components if name in BINARY_COMPONENTS], + [BINARY_COMPONENTS[name] for name in components if name in BINARY_COMPONENTS], ) if "rg" in components: @@ -206,23 +226,19 @@ def _download_artifacts(workflow_id: str, dest_dir: Path) -> None: def install_binary_components( artifacts_dir: Path, vendor_dir: Path, - targets: Iterable[str], - component_names: Sequence[str], + selected_components: Sequence[BinaryComponent], ) -> None: - selected_components = [BINARY_COMPONENTS[name] for name in component_names if name in BINARY_COMPONENTS] if not selected_components: return - targets = list(targets) - if not targets: - return - for component in selected_components: + component_targets = list(component.targets or BINARY_TARGETS) + print( f"Installing {component.binary_basename} binaries for targets: " - + ", ".join(targets) + + ", ".join(component_targets) ) - max_workers = min(len(targets), max(1, (os.cpu_count() or 1))) + max_workers = min(len(component_targets), max(1, (os.cpu_count() or 1))) with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = { executor.submit( @@ -232,7 +248,7 @@ def install_binary_components( target, component, ): target - for target in targets + for target in component_targets } for future in as_completed(futures): installed_path = future.result() diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index d6d73bf05..d64c52557 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -12,11 +12,159 @@ dependencies = [ "regex", ] +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "bitflags 2.10.0", + "bytes", + "bytestring", + "derive_more 2.0.1", + "encoding_rs", + "foldhash 0.1.5", + "futures-core", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "mime", + "percent-encoding", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "bytes", + "bytestring", + "cfg-if", + "derive_more 2.0.1", + "encoding_rs", + "foldhash 0.1.5", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.1", + "time", + "tracing", + "url", +] + [[package]] name = "addr2line" -version = "0.25.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -52,9 +200,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -80,7 +228,7 @@ checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -113,7 +261,7 @@ version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" dependencies = [ - "nom 7.1.3", + "nom", "ratatui", "simdutf8", "smallvec", @@ -122,9 +270,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -137,9 +285,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" @@ -152,22 +300,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.11" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -250,12 +398,13 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.1.1" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" dependencies = [ "anstyle", "bstr", + "doc-comment", "libc", "predicates", "predicates-core", @@ -331,9 +480,9 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.2", + "rustix 1.0.8", "slab", - "windows-sys 0.61.2", + "windows-sys 0.61.1", ] [[package]] @@ -362,7 +511,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.2", + "rustix 1.0.8", ] [[package]] @@ -373,7 +522,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -388,10 +537,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.2", + "rustix 1.0.8", "signal-hook-registry", "slab", - "windows-sys 0.61.2", + "windows-sys 0.61.1", ] [[package]] @@ -413,7 +562,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -430,7 +579,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -447,14 +596,14 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.7" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "bytes", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -465,7 +614,8 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "serde_core", + "rustversion", + "serde", "sync_wrapper", "tokio", "tower", @@ -475,17 +625,18 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "http-body-util", "mime", "pin-project-lite", + "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -493,9 +644,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.76" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -503,7 +654,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.1", + "windows-targets 0.52.6", ] [[package]] @@ -512,6 +663,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + [[package]] name = "beef" version = "0.5.2" @@ -563,15 +720,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - [[package]] name = "blocking" version = "1.6.2" @@ -587,9 +735,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", @@ -604,9 +752,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -622,9 +770,18 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] [[package]] name = "cassowary" @@ -652,11 +809,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ - "find-msvc-tools", "shlex", ] @@ -668,9 +824,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -706,7 +862,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link 0.2.0", ] [[package]] @@ -727,9 +883,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", "clap_derive", @@ -737,9 +893,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -750,30 +906,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.61" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" +checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clipboard-win" @@ -811,7 +967,7 @@ dependencies = [ "codex-protocol", "eventsource-stream", "futures", - "http", + "http 1.3.1", "pretty_assertions", "regex-lite", "reqwest", @@ -847,18 +1003,15 @@ dependencies = [ "codex-utils-json-to-toml", "core_test_support", "mcp-types", - "opentelemetry-appender-tracing", "os_info", "pretty_assertions", "serde", "serde_json", "serial_test", - "sha2", "shlex", "tempfile", "tokio", - "toml 0.9.8", - "toml_edit", + "toml 0.9.5", "tracing", "tracing-subscriber", "uuid", @@ -872,6 +1025,7 @@ dependencies = [ "anyhow", "clap", "codex-protocol", + "codex-utils-absolute-path", "mcp-types", "pretty_assertions", "schemars 0.8.22", @@ -1010,7 +1164,7 @@ dependencies = [ "supports-color 3.0.2", "tempfile", "tokio", - "toml 0.9.8", + "toml 0.9.5", "tracing", ] @@ -1022,7 +1176,9 @@ dependencies = [ "bytes", "eventsource-stream", "futures", - "http", + "http 1.3.1", + "opentelemetry", + "opentelemetry_sdk", "rand 0.9.2", "reqwest", "serde", @@ -1030,6 +1186,8 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", + "tracing-opentelemetry", + "tracing-subscriber", ] [[package]] @@ -1086,7 +1244,7 @@ dependencies = [ "codex-ollama", "codex-protocol", "serde", - "toml 0.9.8", + "toml 0.9.5", ] [[package]] @@ -1130,9 +1288,9 @@ dependencies = [ "escargot", "eventsource-stream", "futures", - "http", + "http 1.3.1", "image", - "indexmap 2.12.1", + "indexmap 2.12.0", "keyring", "landlock", "libc", @@ -1166,9 +1324,10 @@ dependencies = [ "tokio", "tokio-test", "tokio-util", - "toml 0.9.8", + "toml 0.9.5", "toml_edit", "tracing", + "tracing-subscriber", "tracing-test", "tree-sitter", "tree-sitter-bash", @@ -1191,10 +1350,10 @@ dependencies = [ "codex-common", "codex-core", "codex-protocol", + "codex-utils-absolute-path", "core_test_support", "libc", "mcp-types", - "opentelemetry-appender-tracing", "owo-colors", "predicates", "pretty_assertions", @@ -1237,7 +1396,6 @@ dependencies = [ "tokio-util", "tracing", "tracing-subscriber", - "which", ] [[package]] @@ -1263,7 +1421,7 @@ dependencies = [ "allocative", "anyhow", "clap", - "derive_more 2.1.0", + "derive_more 2.0.1", "env_logger", "log", "multimap", @@ -1330,6 +1488,7 @@ version = "0.0.0" dependencies = [ "clap", "codex-core", + "codex-utils-absolute-path", "landlock", "libc", "seccompiler", @@ -1422,12 +1581,14 @@ name = "codex-otel" version = "0.0.0" dependencies = [ "chrono", + "codex-api", "codex-app-server-protocol", "codex-protocol", "codex-utils-absolute-path", "eventsource-stream", - "http", + "http 1.3.1", "opentelemetry", + "opentelemetry-appender-tracing", "opentelemetry-otlp", "opentelemetry-semantic-conventions", "opentelemetry_sdk", @@ -1438,6 +1599,8 @@ dependencies = [ "tokio", "tonic", "tracing", + "tracing-opentelemetry", + "tracing-subscriber", ] [[package]] @@ -1454,6 +1617,7 @@ version = "0.0.0" dependencies = [ "anyhow", "codex-git", + "codex-utils-absolute-path", "codex-utils-image", "icu_decimal", "icu_locale_core", @@ -1538,7 +1702,6 @@ dependencies = [ "anyhow", "arboard", "assert_matches", - "async-stream", "base64", "chrono", "clap", @@ -1552,10 +1715,11 @@ dependencies = [ "codex-file-search", "codex-login", "codex-protocol", + "codex-utils-absolute-path", "codex-windows-sandbox", "color-eyre", "crossterm", - "derive_more 2.1.0", + "derive_more 2.0.1", "diffy", "dirs", "dunce", @@ -1565,7 +1729,6 @@ dependencies = [ "lazy_static", "libc", "mcp-types", - "opentelemetry-appender-tracing", "pathdiff", "pretty_assertions", "pulldown-cmark", @@ -1586,7 +1749,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "toml 0.9.8", + "toml 0.9.5", "tracing", "tracing-appender", "tracing-subscriber", @@ -1621,10 +1784,11 @@ dependencies = [ "codex-login", "codex-protocol", "codex-tui", + "codex-utils-absolute-path", "codex-windows-sandbox", "color-eyre", "crossterm", - "derive_more 2.1.0", + "derive_more 2.0.1", "diffy", "dirs", "dunce", @@ -1634,7 +1798,6 @@ dependencies = [ "lazy_static", "libc", "mcp-types", - "opentelemetry-appender-tracing", "pathdiff", "pretty_assertions", "pulldown-cmark", @@ -1655,7 +1818,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "toml 0.9.8", + "toml 0.9.5", "tracing", "tracing-appender", "tracing-subscriber", @@ -1673,16 +1836,18 @@ name = "codex-utils-absolute-path" version = "0.0.0" dependencies = [ "path-absolutize", + "schemars 0.8.22", "serde", "serde_json", "tempfile", + "ts-rs", ] [[package]] name = "codex-utils-cache" version = "0.0.0" dependencies = [ - "lru", + "lru 0.16.2", "sha1", "tokio", ] @@ -1705,7 +1870,7 @@ version = "0.0.0" dependencies = [ "pretty_assertions", "serde_json", - "toml 0.9.8", + "toml 0.9.5", ] [[package]] @@ -1745,6 +1910,7 @@ dependencies = [ "base64", "chrono", "codex-protocol", + "codex-utils-absolute-path", "dirs-next", "dunce", "rand 0.8.5", @@ -1845,9 +2011,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.10.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" dependencies = [ "unicode-segmentation", ] @@ -1887,8 +2053,11 @@ dependencies = [ "base64", "codex-core", "codex-protocol", + "codex-utils-absolute-path", "notify", + "pretty_assertions", "regex-lite", + "reqwest", "serde_json", "shlex", "tempfile", @@ -1982,9 +2151,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -2047,7 +2216,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2061,7 +2230,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2072,7 +2241,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2083,14 +2252,14 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "dbus" -version = "0.9.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" +checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9" dependencies = [ "libc", "libdbus-sys", @@ -2154,11 +2323,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", "serde_core", @@ -2186,11 +2365,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl 2.1.0", + "derive_more-impl 2.0.1", ] [[package]] @@ -2202,21 +2381,20 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "unicode-xid", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "convert_case 0.10.0", + "convert_case 0.7.1", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.111", + "syn 2.0.104", "unicode-xid", ] @@ -2279,8 +2457,8 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", + "redox_users 0.5.0", + "windows-sys 0.61.1", ] [[package]] @@ -2322,9 +2500,15 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.7" @@ -2339,9 +2523,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtor" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" dependencies = [ "dtor-proc-macro", ] @@ -2375,14 +2559,14 @@ checksum = "83e195b4945e88836d826124af44fdcb262ec01ef94d44f14f4fb5103f19892a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "dyn-clone" -version = "1.0.20" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "either" @@ -2416,9 +2600,9 @@ dependencies = [ [[package]] name = "endi" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "endian-type" @@ -2444,7 +2628,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2455,9 +2639,9 @@ checksum = "dbfd0e7fc632dec5e6c9396a27bc9f9975b4e039720e1fd3e34021d3ce28c415" [[package]] name = "env_filter" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -2493,12 +2677,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2520,9 +2704,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -2546,7 +2730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" dependencies = [ "futures-core", - "nom 7.1.3", + "nom", "pin-project-lite", ] @@ -2595,7 +2779,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2605,7 +2789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.2", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -2629,12 +2813,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - [[package]] name = "findshlibs" version = "0.10.2" @@ -2649,9 +2827,9 @@ dependencies = [ [[package]] name = "fixed_decimal" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35eabf480f94d69182677e37571d3be065822acfafd12f2f085db44fbbcc8e57" +checksum = "35943d22b2f19c0cb198ecf915910a8158e94541c89dcc63300d7799d46c2c5e" dependencies = [ "displaydoc", "smallvec", @@ -2664,17 +2842,11 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2701,6 +2873,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2718,9 +2896,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -2803,7 +2981,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -2857,19 +3035,19 @@ dependencies = [ [[package]] name = "gethostname" -version = "1.1.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "rustix 1.1.2", - "windows-link 0.2.1", + "libc", + "windows-targets 0.48.5", ] [[package]] name = "getopts" -version = "0.2.24" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" dependencies = [ "unicode-width 0.2.1", ] @@ -2883,56 +3061,56 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasip2", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.32.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" -version = "0.4.18" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", - "regex-syntax 0.8.8", + "regex-syntax 0.8.5", ] [[package]] name = "h2" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http", - "indexmap 2.12.1", + "http 1.3.1", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -2941,13 +3119,12 @@ dependencies = [ [[package]] name = "half" -version = "2.7.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", - "zerocopy", ] [[package]] @@ -2968,20 +3145,25 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -3021,31 +3203,43 @@ dependencies = [ [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "hostname" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows-link 0.2.1", + "windows-link 0.1.3", ] [[package]] name = "http" -version = "1.4.0" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", "itoa", ] @@ -3056,7 +3250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -3067,7 +3261,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -3086,16 +3280,16 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http", + "http 1.3.1", "http-body", "httparse", "httpdate", @@ -3113,7 +3307,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.3.1", "hyper", "hyper-util", "rustls", @@ -3156,23 +3350,23 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3182,9 +3376,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3192,7 +3386,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -3295,9 +3489,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -3309,9 +3503,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -3338,9 +3532,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.1.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", @@ -3359,9 +3553,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", @@ -3386,14 +3580,20 @@ dependencies = [ "png", "tiff", "zune-core 0.5.0", - "zune-jpeg 0.5.6", + "zune-jpeg 0.5.5", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indenter" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" @@ -3408,24 +3608,21 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.16.0", "serde", "serde_core", ] [[package]] name = "indoc" -version = "2.0.7" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "inotify" @@ -3470,26 +3667,37 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling 0.20.11", "indoc", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "inventory" -version = "0.3.21" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" dependencies = [ "rustversion", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -3498,9 +3706,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", @@ -3508,13 +3716,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.17" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3525,9 +3733,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -3564,26 +3772,26 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde_core", + "serde", ] [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -3610,9 +3818,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -3669,7 +3877,7 @@ dependencies = [ "is-terminal", "itertools 0.10.5", "lalrpop-util", - "petgraph 0.6.5", + "petgraph", "regex", "regex-syntax 0.6.29", "string_cache", @@ -3689,15 +3897,21 @@ dependencies = [ [[package]] name = "landlock" -version = "0.4.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" +checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" dependencies = [ "enumflags2", "libc", "thiserror 2.0.17", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -3706,24 +3920,24 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libdbus-sys" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f" dependencies = [ "pkg-config", ] [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags 2.10.0", "libc", @@ -3747,30 +3961,37 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "logos" @@ -3801,7 +4022,16 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.15.4", +] + +[[package]] +name = "lru" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" +dependencies = [ + "hashbrown 0.16.0", ] [[package]] @@ -3875,9 +4105,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -3931,21 +4161,21 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.61.2", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "moxcms" -version = "0.7.10" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" dependencies = [ "num-traits", "pxfm", @@ -4045,15 +4275,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -4086,11 +4307,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.50.3" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4210,7 +4431,7 @@ dependencies = [ "base64", "chrono", "getrandom 0.2.16", - "http", + "http 1.3.1", "rand 0.8.5", "reqwest", "serde", @@ -4223,18 +4444,18 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.10.0", "objc2", @@ -4242,32 +4463,11 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - [[package]] name = "objc2-core-foundation" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.10.0", "dispatch2", @@ -4276,9 +4476,9 @@ dependencies = [ [[package]] name = "objc2-core-graphics" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ "bitflags 2.10.0", "dispatch2", @@ -4287,38 +4487,6 @@ dependencies = [ "objc2-io-surface", ] -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - [[package]] name = "objc2-encode" version = "4.1.0" @@ -4327,76 +4495,31 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.10.0", - "block2", - "libc", "objc2", "objc2-core-foundation", ] [[package]] name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags 2.10.0", - "block2", "objc2", - "objc2-cloud-kit", - "objc2-core-data", "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", ] [[package]] name = "object" -version = "0.37.3" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -4409,15 +4532,15 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -4436,7 +4559,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -4447,18 +4570,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.1+3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -4501,7 +4624,7 @@ checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" dependencies = [ "async-trait", "bytes", - "http", + "http 1.3.1", "opentelemetry", "reqwest", ] @@ -4512,7 +4635,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" dependencies = [ - "http", + "http 1.3.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -4583,35 +4706,31 @@ dependencies = [ [[package]] name = "os_info" -version = "3.13.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c39b5918402d564846d5aba164c09a66cc88d232179dfd3e3c619a25a268392" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" dependencies = [ - "android_system_properties", "log", - "nix 0.30.1", - "objc2", - "objc2-foundation", - "objc2-ui-kit", + "plist", "serde", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] name = "os_pipe" -version = "1.2.3" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" dependencies = [ "supports-color 2.1.0", "supports-color 3.0.2", @@ -4625,9 +4744,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -4635,15 +4754,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.12" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-targets 0.52.6", ] [[package]] @@ -4683,30 +4802,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] [[package]] -name = "petgraph" -version = "0.6.5" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset 0.4.2", - "indexmap 2.12.1", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.8.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset 0.5.7", - "hashbrown 0.15.5", - "indexmap 2.12.1", + "fixedbitset", + "indexmap 2.12.0", ] [[package]] @@ -4735,7 +4852,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -4767,6 +4884,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64", + "indexmap 2.12.0", + "quick-xml 0.38.0", + "serde", + "time", +] + [[package]] name = "png" version = "0.18.0" @@ -4790,8 +4920,8 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.2", - "windows-sys 0.61.2", + "rustix 1.0.8", + "windows-sys 0.61.1", ] [[package]] @@ -4913,9 +5043,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -4927,7 +5057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" dependencies = [ "futures", - "indexmap 2.12.1", + "indexmap 2.12.0", "nix 0.30.1", "tokio", "tracing", @@ -4954,7 +5084,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -4978,9 +5108,9 @@ checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" [[package]] name = "pxfm" -version = "0.1.27" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" dependencies = [ "num-traits", ] @@ -5000,6 +5130,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.9" @@ -5013,7 +5152,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.5.10", "thiserror 2.0.17", "tokio", "tracing", @@ -5027,7 +5166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.3.3", "lru-slab", "rand 0.9.2", "ring", @@ -5050,16 +5189,16 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -5136,7 +5275,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", ] [[package]] @@ -5151,7 +5290,7 @@ dependencies = [ "indoc", "instability", "itertools 0.13.0", - "lru", + "lru 0.12.5", "paste", "strum 0.26.3", "unicode-segmentation", @@ -5170,9 +5309,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.18" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags 2.10.0", ] @@ -5190,9 +5329,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", @@ -5201,22 +5340,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -5228,7 +5367,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.8", + "regex-syntax 0.8.5", ] [[package]] @@ -5239,14 +5378,14 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.8", + "regex-syntax 0.8.5", ] [[package]] name = "regex-lite" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" [[package]] name = "regex-syntax" @@ -5256,15 +5395,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -5273,7 +5412,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -5334,7 +5473,7 @@ dependencies = [ "bytes", "chrono", "futures", - "http", + "http 1.3.1", "http-body", "http-body-util", "oauth2", @@ -5344,7 +5483,7 @@ dependencies = [ "rand 0.9.2", "reqwest", "rmcp-macros", - "schemars 1.1.0", + "schemars 1.0.4", "serde", "serde_json", "sse-stream", @@ -5368,14 +5507,14 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -5407,22 +5546,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "log", "once_cell", @@ -5435,9 +5574,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -5447,9 +5586,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", "zeroize", @@ -5457,9 +5596,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -5468,9 +5607,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rustyline" @@ -5524,7 +5663,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.61.1", ] [[package]] @@ -5595,14 +5734,14 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "chrono", "dyn-clone", "ref-cast", - "schemars_derive 1.1.0", + "schemars_derive 1.0.4", "serde", "serde_json", ] @@ -5616,19 +5755,19 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "schemars_derive" -version = "1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -5715,13 +5854,14 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5484316556650182f03b43d4c746ce0e3e48074a21e2f51244b648b6542e1066" +checksum = "d9794f69ad475e76c057e326175d3088509649e3aed98473106b9fe94ba59424" dependencies = [ "httpdate", "native-tls", "reqwest", + "sentry-actix", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -5732,23 +5872,35 @@ dependencies = [ "ureq", ] +[[package]] +name = "sentry-actix" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0fee202934063ace4f1d1d063113b8982293762628e563a2d2fba08fb20b110" +dependencies = [ + "actix-http", + "actix-web", + "bytes", + "futures-util", + "sentry-core", +] + [[package]] name = "sentry-backtrace" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40aa225bb41e2ec9d7c90886834367f560efc1af028f1c5478a6cce6a59c463a" +checksum = "e81137ad53b8592bd0935459ad74c0376053c40084aa170451e74eeea8dbc6c3" dependencies = [ "backtrace", - "once_cell", "regex", "sentry-core", ] [[package]] name = "sentry-contexts" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a8dd746da3d16cb8c39751619cefd4fcdbd6df9610f3310fd646b55f6e39910" +checksum = "cfb403c66cc2651a01b9bacda2e7c22cd51f7e8f56f206aa4310147eb3259282" dependencies = [ "hostname", "libc", @@ -5760,33 +5912,32 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161283cfe8e99c8f6f236a402b9ccf726b201f365988b5bb637ebca0abbd4a30" +checksum = "cfc409727ae90765ca8ea76fe6c949d6f159a11d02e130b357fa652ee9efcada" dependencies = [ - "once_cell", - "rand 0.8.5", + "rand 0.9.2", "sentry-types", "serde", "serde_json", + "url", ] [[package]] name = "sentry-debug-images" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc6b25e945fcaa5e97c43faee0267eebda9f18d4b09a251775d8fef1086238a" +checksum = "06a2778a222fd90ebb01027c341a72f8e24b0c604c6126504a4fe34e5500e646" dependencies = [ "findshlibs", - "once_cell", "sentry-core", ] [[package]] name = "sentry-panic" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc74f229c7186dd971a9491ffcbe7883544aa064d1589bd30b83fb856cd22d63" +checksum = "3df79f4e1e72b2a8b75a0ebf49e78709ceb9b3f0b451f13adc92a0361b0aaabe" dependencies = [ "sentry-backtrace", "sentry-core", @@ -5794,10 +5945,11 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c5faf2103cd01eeda779ea439b68c4ee15adcdb16600836e97feafab362ec" +checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" dependencies = [ + "bitflags 2.10.0", "sentry-backtrace", "sentry-core", "tracing-core", @@ -5806,16 +5958,16 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.34.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d68cdf6bc41b8ff3ae2a9c4671e97426dcdd154cc1d4b6b72813f285d6b163f" +checksum = "c7b9b4e4c03a4d3643c18c78b8aa91d2cbee5da047d2fa0ca4bb29bc67e6c55c" dependencies = [ "debugid", "hex", - "rand 0.8.5", + "rand 0.9.2", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.17", "time", "url", "uuid", @@ -5848,7 +6000,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -5859,7 +6011,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -5868,7 +6020,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.12.0", "itoa", "memchr", "ryu", @@ -5895,16 +6047,16 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ - "serde_core", + "serde", ] [[package]] @@ -5929,9 +6081,9 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.12.0", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.0.4", "serde_core", "serde_json", "serde_with_macros", @@ -5947,7 +6099,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -5956,7 +6108,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.12.0", "itoa", "ryu", "serde", @@ -5965,9 +6117,9 @@ dependencies = [ [[package]] name = "serial2" -version = "0.2.33" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc76fa68e25e771492ca1e3c53d447ef0be3093e05cd3b47f4b712ba10c6f3c" +checksum = "26e1e5956803a69ddd72ce2de337b577898801528749565def03515f82bad5bb" dependencies = [ "cfg-if", "libc", @@ -5996,7 +6148,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6048,9 +6200,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" @@ -6070,9 +6222,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio", @@ -6081,18 +6233,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" @@ -6165,9 +6317,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "starlark" @@ -6218,7 +6370,7 @@ dependencies = [ "dupe", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6320,7 +6472,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6332,7 +6484,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6373,9 +6525,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -6399,7 +6551,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6439,10 +6591,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.3.3", "once_cell", - "rustix 1.1.2", - "windows-sys 0.61.2", + "rustix 1.0.8", + "windows-sys 0.61.1", ] [[package]] @@ -6467,12 +6619,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.1.2", - "windows-sys 0.60.2", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] @@ -6499,7 +6651,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6510,15 +6662,15 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "test-case-core", ] [[package]] name = "test-log" -version = "0.2.19" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" +checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" dependencies = [ "env_logger", "test-log-macros", @@ -6527,13 +6679,13 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.19" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" +checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6582,7 +6734,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6593,7 +6745,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6616,7 +6768,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg 0.4.21", + "zune-jpeg 0.4.19", ] [[package]] @@ -6675,12 +6827,11 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -6701,30 +6852,33 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ + "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2 0.6.1", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6739,9 +6893,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", @@ -6756,6 +6910,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -6773,9 +6928,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -6797,12 +6952,12 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ - "indexmap 2.12.1", - "serde_core", + "indexmap 2.12.0", + "serde", "serde_spanned", "toml_datetime", "toml_parser", @@ -6821,11 +6976,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.12.0", "toml_datetime", "toml_parser", "toml_writer", @@ -6858,7 +7013,7 @@ dependencies = [ "base64", "bytes", "h2", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -6886,7 +7041,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.1", + "indexmap 2.12.0", "pin-project-lite", "slab", "sync_wrapper", @@ -6899,14 +7054,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http", + "http 1.3.1", "http-body", "iri-string", "pin-project-lite", @@ -6941,12 +7096,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 1.0.69", "time", "tracing-subscriber", ] @@ -6959,7 +7114,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -6993,11 +7148,29 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcf5959f39507d0d04d6413119c04f33b623f4f951ebcbdddddfad2d0623a9c" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", @@ -7029,7 +7202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -7040,7 +7213,7 @@ checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" dependencies = [ "cc", "regex", - "regex-syntax 0.8.8", + "regex-syntax 0.8.5", "serde_json", "streaming-iterator", "tree-sitter-language", @@ -7048,9 +7221,9 @@ dependencies = [ [[package]] name = "tree-sitter-bash" -version = "0.25.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062" +checksum = "871b0606e667e98a1237ebdc1b0d7056e0aebfdc3141d12b399865d4cb6ed8a6" dependencies = [ "cc", "tree-sitter-language", @@ -7070,19 +7243,20 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae62f7eae5eb549c71b76658648b72cc6111f2d87d24a1e31fa907f4943e3ce" +checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" [[package]] name = "tree_magic_mini" -version = "3.2.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +checksum = "f943391d896cdfe8eec03a04d7110332d445be7df856db382dd96a730667562c" dependencies = [ "memchr", - "nom 8.0.0", - "petgraph 0.8.3", + "nom", + "once_cell", + "petgraph", ] [[package]] @@ -7093,9 +7267,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" -version = "11.1.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424" +checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" dependencies = [ "serde_json", "thiserror 2.0.17", @@ -7105,21 +7279,21 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "11.1.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" +checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "termcolor", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "uds_windows" @@ -7149,9 +7323,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -7208,22 +7382,38 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.1" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" dependencies = [ "base64", + "der", "log", "native-tls", - "once_cell", - "url", + "percent-encoding", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http 1.3.1", + "httparse", + "log", ] [[package]] name = "url" -version = "2.5.7" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -7237,6 +7427,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -7251,13 +7447,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", "js-sys", - "serde_core", + "serde", "sha1_smol", "wasm-bindgen", ] @@ -7336,32 +7532,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "wasi" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "wit-bindgen", + "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -7372,9 +7581,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7382,22 +7591,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] @@ -7423,7 +7632,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.2", + "rustix 1.0.8", "smallvec", "wayland-sys", ] @@ -7435,7 +7644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.10.0", - "rustix 1.1.2", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -7472,7 +7681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.37.5", "quote", ] @@ -7487,9 +7696,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -7522,19 +7731,28 @@ dependencies = [ ] [[package]] -name = "webpki-roots" +name = "webpki-root-certs" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] [[package]] name = "weezl" -version = "0.1.12" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "which" @@ -7572,11 +7790,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.11" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7636,26 +7854,13 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement 0.60.0", + "windows-interface 0.59.1", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" @@ -7675,18 +7880,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "windows-implement" -version = "0.60.2" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -7697,18 +7902,18 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "windows-interface" -version = "0.59.3" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -7719,9 +7924,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-numerics" @@ -7735,13 +7940,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.6.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -7762,15 +7967,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-strings" version = "0.1.0" @@ -7790,15 +7986,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -7832,16 +8019,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.5", + "windows-targets 0.53.2", ] [[package]] name = "windows-sys" -version = "0.61.2" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ - "windows-link 0.2.1", + "windows-link 0.2.0", ] [[package]] @@ -7859,6 +8046,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -7877,19 +8079,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.5" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -7907,6 +8108,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -7915,9 +8122,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" @@ -7925,6 +8132,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -7933,9 +8146,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" @@ -7943,6 +8156,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -7951,9 +8170,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" @@ -7963,9 +8182,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" @@ -7973,6 +8192,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -7981,9 +8206,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" @@ -7991,6 +8216,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -7999,9 +8230,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" @@ -8009,6 +8240,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -8017,9 +8254,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" @@ -8027,6 +8264,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -8035,15 +8278,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -8082,7 +8325,7 @@ dependencies = [ "base64", "deadpool", "futures", - "http", + "http 1.3.1", "http-body-util", "hyper", "hyper-util", @@ -8096,10 +8339,13 @@ dependencies = [ ] [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.10.0", +] [[package]] name = "wl-clipboard-rs" @@ -8128,20 +8374,20 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x11rb" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "gethostname", - "rustix 1.1.2", + "rustix 0.38.44", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xdg-home" @@ -8161,10 +8407,11 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -8172,13 +8419,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "synstructure", ] @@ -8229,7 +8476,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "zvariant_utils", ] @@ -8246,22 +8493,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -8281,7 +8528,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "synstructure", ] @@ -8302,14 +8549,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", @@ -8330,13 +8577,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] [[package]] @@ -8353,18 +8600,18 @@ checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" [[package]] name = "zune-jpeg" -version = "0.4.21" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core 0.4.12", ] [[package]] name = "zune-jpeg" -version = "0.5.6" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8" +checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" dependencies = [ "zune-core 0.5.0", ] @@ -8391,7 +8638,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", "zvariant_utils", ] @@ -8403,5 +8650,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.104", ] diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index e50ca35e0..c88c2217d 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -149,7 +149,7 @@ landlock = "0.4.1" lazy_static = "1" libc = "0.2.177" log = "0.4" -lru = "0.12.5" +lru = "0.16.2" maplit = "1.0.2" mime_guess = "2.0.5" multimap = "0.10.0" @@ -162,6 +162,7 @@ opentelemetry-appender-tracing = "0.30.0" opentelemetry-otlp = "0.30.0" opentelemetry-semantic-conventions = "0.31.0" opentelemetry_sdk = "0.30.0" +tracing-opentelemetry = "0.31.0" os_info = "3.12.0" owo-colors = "4.2.0" path-absolutize = "3.1.1" @@ -179,7 +180,7 @@ reqwest = "0.12" rmcp = { version = "0.10.0", default-features = false } schemars = "0.8.22" seccompiler = "0.5.0" -sentry = "0.34.0" +sentry = "0.46.0" serde = "1" serde_json = "1" serde_with = "3.16" @@ -189,7 +190,7 @@ sha1 = "0.10.6" sha2 = "0.10" shlex = "1.3.0" similar = "2.7.0" -socket2 = "0.6.0" +socket2 = "0.6.1" starlark = "0.13.0" strum = "0.27.2" strum_macros = "0.27.2" diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index c1a251c65..1c21bd6ea 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -15,6 +15,7 @@ workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } codex-protocol = { workspace = true } +codex-utils-absolute-path = { workspace = true } mcp-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 4de66dfb3..a60c1be62 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -31,6 +31,7 @@ use std::process::Command; use ts_rs::TS; const HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n"; +const IGNORED_DEFINITIONS: &[&str] = &["Option<()>"]; #[derive(Clone)] pub struct GeneratedSchema { @@ -184,7 +185,6 @@ fn build_schema_bundle(schemas: Vec) -> Result { "ServerNotification", "ServerRequest", ]; - const IGNORED_DEFINITIONS: &[&str] = &["Option<()>"]; let namespaced_types = collect_namespaced_types(&schemas); let mut definitions = Map::new(); @@ -304,8 +304,11 @@ where out_dir.join(format!("{file_stem}.json")) }; - write_pretty_json(out_path, &schema_value) - .with_context(|| format!("Failed to write JSON schema for {file_stem}"))?; + if !IGNORED_DEFINITIONS.contains(&logical_name) { + write_pretty_json(out_path, &schema_value) + .with_context(|| format!("Failed to write JSON schema for {file_stem}"))?; + } + let namespace = match raw_namespace { Some("v1") | None => None, Some(ns) => Some(ns.to_string()), diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 116a3c62d..bd7fd8e28 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -117,9 +117,9 @@ client_request_definitions! { params: v2::ThreadListParams, response: v2::ThreadListResponse, }, - ThreadCompact => "thread/compact" { - params: v2::ThreadCompactParams, - response: v2::ThreadCompactResponse, + SkillsList => "skills/list" { + params: v2::SkillsListParams, + response: v2::SkillsListResponse, }, TurnStart => "turn/start" { params: v2::TurnStartParams, @@ -144,9 +144,9 @@ client_request_definitions! { response: v2::McpServerOauthLoginResponse, }, - McpServersList => "mcpServers/list" { - params: v2::ListMcpServersParams, - response: v2::ListMcpServersResponse, + McpServerStatusList => "mcpServerStatus/list" { + params: v2::ListMcpServerStatusParams, + response: v2::ListMcpServerStatusResponse, }, LoginAccount => "account/login/start" { @@ -525,6 +525,8 @@ server_notification_definitions! { TurnPlanUpdated => "turn/plan/updated" (v2::TurnPlanUpdatedNotification), ItemStarted => "item/started" (v2::ItemStartedNotification), ItemCompleted => "item/completed" (v2::ItemCompletedNotification), + /// This event is internal-only. Used by Codex Cloud. + RawResponseItemCompleted => "rawResponseItem/completed" (v2::RawResponseItemCompletedNotification), AgentMessageDelta => "item/agentMessage/delta" (v2::AgentMessageDeltaNotification), CommandExecutionOutputDelta => "item/commandExecution/outputDelta" (v2::CommandExecutionOutputDeltaNotification), TerminalInteraction => "item/commandExecution/terminalInteraction" (v2::TerminalInteractionNotification), diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 853cb03b4..df39f8809 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -16,6 +16,7 @@ use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::TurnAbortReason; +use codex_utils_absolute_path::AbsolutePathBuf; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -359,7 +360,7 @@ pub struct Tools { #[serde(rename_all = "camelCase")] pub struct SandboxSettings { #[serde(default)] - pub writable_roots: Vec, + pub writable_roots: Vec, pub network_access: Option, pub exclude_tmpdir_env_var: Option, pub exclude_slash_tmp: Option, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 3429a4fc1..2acea04e9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -21,9 +21,13 @@ use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow; use codex_protocol::protocol::SessionSource as CoreSessionSource; +use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo; +use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata; +use codex_protocol::protocol::SkillScope as CoreSkillScope; use codex_protocol::protocol::TokenUsage as CoreTokenUsage; use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo; use codex_protocol::user_input::UserInput as CoreUserInput; +use codex_utils_absolute_path::AbsolutePathBuf; use mcp_types::ContentBlock as McpContentBlock; use mcp_types::Resource as McpResource; use mcp_types::ResourceTemplate as McpResourceTemplate; @@ -420,7 +424,7 @@ pub enum SandboxPolicy { #[ts(rename_all = "camelCase")] WorkspaceWrite { #[serde(default)] - writable_roots: Vec, + writable_roots: Vec, #[serde(default)] network_access: bool, #[serde(default)] @@ -757,7 +761,7 @@ pub struct ModelListResponse { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct ListMcpServersParams { +pub struct ListMcpServerStatusParams { /// Opaque pagination cursor returned by a previous call. pub cursor: Option, /// Optional page size; defaults to a server-defined value. @@ -767,7 +771,7 @@ pub struct ListMcpServersParams { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct McpServer { +pub struct McpServerStatus { pub name: String, pub tools: std::collections::HashMap, pub resources: Vec, @@ -778,8 +782,8 @@ pub struct McpServer { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct ListMcpServersResponse { - pub data: Vec, +pub struct ListMcpServerStatusResponse { + pub data: Vec, /// Opaque cursor to pass to the next call to continue after the last item. /// If None, there are no more items to return. pub next_cursor: Option, @@ -856,6 +860,12 @@ pub struct ThreadStartParams { pub config: Option>, pub base_instructions: Option, pub developer_instructions: Option, + /// If true, opt into emitting raw response items on the event stream. + /// + /// This is for internal use only (e.g. Codex Cloud). + /// (TODO): Figure out a better way to categorize internal / experimental events & protocols. + #[serde(default)] + pub experimental_raw_events: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] @@ -957,14 +967,83 @@ pub struct ThreadListResponse { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct ThreadCompactParams { - pub thread_id: String, +pub struct SkillsListParams { + /// When empty, defaults to the current session working directory. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub cwds: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct ThreadCompactResponse {} +pub struct SkillsListResponse { + pub data: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case")] +#[ts(export_to = "v2/")] +pub enum SkillScope { + User, + Repo, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillMetadata { + pub name: String, + pub description: String, + pub path: PathBuf, + pub scope: SkillScope, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillErrorInfo { + pub path: PathBuf, + pub message: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsListEntry { + pub cwd: PathBuf, + pub skills: Vec, + pub errors: Vec, +} + +impl From for SkillMetadata { + fn from(value: CoreSkillMetadata) -> Self { + Self { + name: value.name, + description: value.description, + path: value.path, + scope: value.scope.into(), + } + } +} + +impl From for SkillScope { + fn from(value: CoreSkillScope) -> Self { + match value { + CoreSkillScope::User => Self::User, + CoreSkillScope::Repo => Self::Repo, + } + } +} + +impl From for SkillErrorInfo { + fn from(value: CoreSkillErrorInfo) -> Self { + Self { + path: value.path, + message: value.message, + } + } +} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] @@ -1508,6 +1587,15 @@ pub struct ItemCompletedNotification { pub turn_id: String, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct RawResponseItemCompletedNotification { + pub thread_id: String, + pub turn_id: String, + pub item: ResponseItem, +} + // Item-specific progress notifications #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 948facdea..3695ffa1f 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -31,11 +31,9 @@ codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -sha2 = { workspace = true } mcp-types = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } -toml_edit = { workspace = true } tokio = { workspace = true, features = [ "io-std", "macros", @@ -45,7 +43,6 @@ tokio = { workspace = true, features = [ ] } tracing = { workspace = true, features = ["log"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -opentelemetry-appender-tracing = { workspace = true } uuid = { workspace = true, features = ["serde", "v7"] } [dev-dependencies] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index e9dee7127..460418c81 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -3,6 +3,7 @@ `codex app-server` is the interface Codex uses to power rich interfaces such as the [Codex VS Code extension](https://marketplace.visualstudio.com/items?itemName=openai.chatgpt). ## Table of Contents + - [Protocol](#protocol) - [Message Schema](#message-schema) - [Core Primitives](#core-primitives) @@ -28,6 +29,7 @@ codex app-server generate-json-schema --out DIR ## Core Primitives The API exposes three top level primitives representing an interaction between a user and Codex: + - **Thread**: A conversation between a user and the Codex agent. Each thread contains multiple turns. - **Turn**: One turn of the conversation, typically starting with a user message and finishing with an agent message. Each turn contains multiple items. - **Item**: Represents user inputs and agent outputs as part of the turn, persisted and used as the context for future conversations. Example items include user message, agent reasoning, agent message, shell command, file edit, etc. @@ -49,13 +51,23 @@ Clients must send a single `initialize` request before invoking any other method Applications building on top of `codex app-server` should identify themselves via the `clientInfo` parameter. Example (from OpenAI's official VSCode extension): + ```json -{ "method": "initialize", "id": 0, "params": { - "clientInfo": { "name": "codex-vscode", "title": "Codex VS Code Extension", "version": "0.1.0" } -} } +{ + "method": "initialize", + "id": 0, + "params": { + "clientInfo": { + "name": "codex-vscode", + "title": "Codex VS Code Extension", + "version": "0.1.0" + } + } +} ``` ## API Overview + - `thread/start` — create a new thread; emits `thread/started` and auto-subscribes you to turn/item events for that thread. - `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. - `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders` filtering. @@ -65,8 +77,9 @@ Example (from OpenAI's official VSCode extension): - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). - `model/list` — list available models (with reasoning effort options). +- `skills/list` — list skills for one or more `cwd` values. - `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes. -- `mcpServers/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination. +- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination. - `feedback/upload` — submit a feedback report (classification + optional reason/logs and conversation_id); returns the tracking thread id. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). - `config/read` — fetch the effective config on disk after resolving config layering. @@ -107,6 +120,7 @@ To continue a stored session, call `thread/resume` with the `thread.id` you prev ### Example: List threads (with pagination & filters) `thread/list` lets you render a history UI. Pass any combination of: + - `cursor` — opaque string from a prior response; omit for the first page. - `limit` — server defaults to a reasonable page size if unset. - `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers. @@ -227,22 +241,32 @@ Codex streams the usual `turn/started` notification followed by an `item/started with an `enteredReviewMode` item so clients can show progress: ```json -{ "method": "item/started", "params": { "item": { - "type": "enteredReviewMode", - "id": "turn_900", - "review": "current changes" -} } } +{ + "method": "item/started", + "params": { + "item": { + "type": "enteredReviewMode", + "id": "turn_900", + "review": "current changes" + } + } +} ``` When the reviewer finishes, the server emits `item/started` and `item/completed` containing an `exitedReviewMode` item with the final review text: ```json -{ "method": "item/completed", "params": { "item": { - "type": "exitedReviewMode", - "id": "turn_900", - "review": "Looks solid overall...\n\n- Prefer Stylize helpers — app.rs:10-20\n ..." -} } } +{ + "method": "item/completed", + "params": { + "item": { + "type": "exitedReviewMode", + "id": "turn_900", + "review": "Looks solid overall...\n\n- Prefer Stylize helpers — app.rs:10-20\n ..." + } + } +} ``` The `review` string is plain text that already bundles the overall explanation plus a bullet list for each structured finding (matching `ThreadItem::ExitedReviewMode` in the generated schema). Use this notification to render the reviewer output in your client. @@ -262,6 +286,7 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin ``` Notes: + - Empty `command` arrays are rejected. - `sandboxPolicy` accepts the same shape used by `turn/start` (e.g., `dangerFullAccess`, `readOnly`, `workspaceWrite` with flags). - When omitted, `timeoutMs` falls back to the server default. @@ -284,6 +309,7 @@ Today both notifications carry an empty `items` array even when item events were #### Items `ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items: + - `userMessage` — `{id, content}` where `content` is a list of user inputs (`text`, `image`, or `localImage`). - `agentMessage` — `{id, text}` containing the accumulated agent reply. - `reasoning` — `{id, summary, content}` where `summary` holds streamed reasoning summaries (applicable for most OpenAI models) and `content` holds raw reasoning blocks (applicable for e.g. open source models). @@ -297,37 +323,48 @@ Today both notifications carry an empty `items` array even when item events were - `compacted` - `{threadId, turnId}` when codex compacts the conversation history. This can happen automatically. All items emit two shared lifecycle events: + - `item/started` — emits the full `item` when a new unit of work begins so the UI can render it immediately; the `item.id` in this payload matches the `itemId` used by deltas. - `item/completed` — sends the final `item` once that work finishes (e.g., after a tool call or message completes); treat this as the authoritative state. There are additional item-specific events: + #### agentMessage + - `item/agentMessage/delta` — appends streamed text for the agent message; concatenate `delta` values for the same `itemId` in order to reconstruct the full reply. + #### reasoning + - `item/reasoning/summaryTextDelta` — streams readable reasoning summaries; `summaryIndex` increments when a new summary section opens. - `item/reasoning/summaryPartAdded` — marks the boundary between reasoning summary sections for an `itemId`; subsequent `summaryTextDelta` entries share the same `summaryIndex`. - `item/reasoning/textDelta` — streams raw reasoning text (only applicable for e.g. open source models); use `contentIndex` to group deltas that belong together before showing them in the UI. + #### commandExecution + - `item/commandExecution/outputDelta` — streams stdout/stderr for the command; append deltas in order to render live output alongside `aggregatedOutput` in the final item. -Final `commandExecution` items include parsed `commandActions`, `status`, `exitCode`, and `durationMs` so the UI can summarize what ran and whether it succeeded. + Final `commandExecution` items include parsed `commandActions`, `status`, `exitCode`, and `durationMs` so the UI can summarize what ran and whether it succeeded. + #### fileChange + - `item/fileChange/outputDelta` - contains the tool call response of the underlying `apply_patch` tool call. ### Errors + `error` event is emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorInfo? } }` payload as `turn.status: "failed"` and may precede that terminal notification. - `codexErrorInfo` maps to the `CodexErrorInfo` enum. Common values: - - `ContextWindowExceeded` - - `UsageLimitExceeded` - - `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx - - `ResponseStreamConnectionFailed { httpStatusCode? }`: failure to connect to the response SSE stream - - `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion - - `ResponseTooManyFailedAttempts { httpStatusCode? }` - - `BadRequest` - - `Unauthorized` - - `SandboxError` - - `InternalServerError` - - `Other`: all unclassified errors +`codexErrorInfo` maps to the `CodexErrorInfo` enum. Common values: + +- `ContextWindowExceeded` +- `UsageLimitExceeded` +- `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx +- `ResponseStreamConnectionFailed { httpStatusCode? }`: failure to connect to the response SSE stream +- `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion +- `ResponseTooManyFailedAttempts { httpStatusCode? }` +- `BadRequest` +- `Unauthorized` +- `SandboxError` +- `InternalServerError` +- `Other`: all unclassified errors When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant. @@ -341,6 +378,7 @@ Certain actions (shell commands or modifying files) may require explicit user ap ### Command execution approvals Order of messages: + 1. `item/started` — shows the pending `commandExecution` item with `command`, `cwd`, and other fields so you can render the proposed action. 2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `reason` or `risk`, plus `parsedCmd` for friendly display. 3. Client response — `{ "decision": "accept", "acceptSettings": { "forSession": false } }` or `{ "decision": "decline" }`. @@ -349,6 +387,7 @@ Order of messages: ### File change approvals Order of messages: + 1. `item/started` — emits a `fileChange` item with `changes` (diff chunk summaries) and `status: "inProgress"`. Show the proposed edits and paths to the user. 2. `item/fileChange/requestApproval` (request) — includes `itemId`, `threadId`, `turnId`, and an optional `reason`. 3. Client response — `{ "decision": "accept" }` or `{ "decision": "decline" }`. @@ -361,6 +400,7 @@ UI guidance for IDEs: surface an approval dialog as soon as the request arrives. The JSON-RPC auth/account surface exposes request/response methods plus server-initiated notifications (no `id`). Use these to determine auth state, start or cancel logins, logout, and inspect ChatGPT rate limits. ### API Overview + - `account/read` — fetch current account info; optionally refresh tokens. - `account/login/start` — begin login (`apiKey` or `chatgpt`). - `account/login/completed` (notify) — emitted when a login attempt finishes (success or error). @@ -374,11 +414,13 @@ The JSON-RPC auth/account surface exposes request/response methods plus server-i ### 1) Check auth state Request: + ```json { "method": "account/read", "id": 1, "params": { "refreshToken": false } } ``` Response examples: + ```json { "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models) { "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models) @@ -387,6 +429,7 @@ Response examples: ``` Field notes: + - `refreshToken` (bool): set `true` to force a token refresh. - `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials. @@ -394,7 +437,11 @@ Field notes: 1. Send: ```json - { "method": "account/login/start", "id": 2, "params": { "type": "apiKey", "apiKey": "sk-…" } } + { + "method": "account/login/start", + "id": 2, + "params": { "type": "apiKey", "apiKey": "sk-…" } + } ``` 2. Expect: ```json @@ -444,6 +491,7 @@ Field notes: ``` Field notes: + - `usedPercent` is current usage within the OpenAI quota window. - `windowDurationMins` is the quota window length. - `resetsAt` is a Unix timestamp (seconds) for the next reset. diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index b0161cd9f..dec9d8c08 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -31,6 +31,7 @@ use codex_app_server_protocol::McpToolCallResult; use codex_app_server_protocol::McpToolCallStatus; use codex_app_server_protocol::PatchApplyStatus; use codex_app_server_protocol::PatchChangeKind as V2PatchChangeKind; +use codex_app_server_protocol::RawResponseItemCompletedNotification; use codex_app_server_protocol::ReasoningSummaryPartAddedNotification; use codex_app_server_protocol::ReasoningSummaryTextDeltaNotification; use codex_app_server_protocol::ReasoningTextDeltaNotification; @@ -451,6 +452,16 @@ pub(crate) async fn apply_bespoke_event_handling( .send_server_notification(ServerNotification::ItemCompleted(completed)) .await; } + EventMsg::RawResponseItem(raw_response_item_event) => { + maybe_emit_raw_response_item_completed( + api_version, + conversation_id, + &event_turn_id, + raw_response_item_event.item, + outgoing.as_ref(), + ) + .await; + } EventMsg::PatchApplyBegin(patch_begin_event) => { // Until we migrate the core to be aware of a first class FileChangeItem // and emit the corresponding EventMsg, we repurpose the call_id as the item_id. @@ -820,6 +831,27 @@ async fn complete_command_execution_item( .await; } +async fn maybe_emit_raw_response_item_completed( + api_version: ApiVersion, + conversation_id: ConversationId, + turn_id: &str, + item: codex_protocol::models::ResponseItem, + outgoing: &OutgoingMessageSender, +) { + let ApiVersion::V2 = api_version else { + return; + }; + + let notification = RawResponseItemCompletedNotification { + thread_id: conversation_id.to_string(), + turn_id: turn_id.to_string(), + item, + }; + outgoing + .send_server_notification(ServerNotification::RawResponseItemCompleted(notification)) + .await; +} + async fn find_and_remove_turn_summary( conversation_id: ConversationId, turn_summary_store: &TurnSummaryStore, diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 9f4227b2d..0bcac39a5 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -46,8 +46,8 @@ use codex_app_server_protocol::InterruptConversationParams; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::ListConversationsParams; use codex_app_server_protocol::ListConversationsResponse; -use codex_app_server_protocol::ListMcpServersParams; -use codex_app_server_protocol::ListMcpServersResponse; +use codex_app_server_protocol::ListMcpServerStatusParams; +use codex_app_server_protocol::ListMcpServerStatusResponse; use codex_app_server_protocol::LoginAccountParams; use codex_app_server_protocol::LoginApiKeyParams; use codex_app_server_protocol::LoginApiKeyResponse; @@ -55,10 +55,10 @@ use codex_app_server_protocol::LoginChatGptCompleteNotification; use codex_app_server_protocol::LoginChatGptResponse; use codex_app_server_protocol::LogoutAccountResponse; use codex_app_server_protocol::LogoutChatGptResponse; -use codex_app_server_protocol::McpServer; use codex_app_server_protocol::McpServerOauthLoginCompletedNotification; use codex_app_server_protocol::McpServerOauthLoginParams; use codex_app_server_protocol::McpServerOauthLoginResponse; +use codex_app_server_protocol::McpServerStatus; use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; use codex_app_server_protocol::NewConversationParams; @@ -81,6 +81,8 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::SessionConfiguredNotification; use codex_app_server_protocol::SetDefaultModelParams; use codex_app_server_protocol::SetDefaultModelResponse; +use codex_app_server_protocol::SkillsListParams; +use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; @@ -117,10 +119,9 @@ use codex_core::auth::CLIENT_ID; use codex_core::auth::login_with_api_key; use codex_core::config::Config; use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; +use codex_core::config::ConfigService; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::types::McpServerTransportConfig; -use codex_core::config_loader::load_config_as_toml; use codex_core::default_client::get_codex_user_agent; use codex_core::exec::ExecParams; use codex_core::exec_env::create_env; @@ -367,12 +368,8 @@ impl CodexMessageProcessor { ClientRequest::ThreadList { request_id, params } => { self.thread_list(request_id, params).await; } - ClientRequest::ThreadCompact { - request_id, - params: _, - } => { - self.send_unimplemented_error(request_id, "thread/compact") - .await; + ClientRequest::SkillsList { request_id, params } => { + self.skills_list(request_id, params).await; } ClientRequest::TurnStart { request_id, params } => { self.turn_start(request_id, params).await; @@ -401,8 +398,8 @@ impl CodexMessageProcessor { ClientRequest::McpServerOauthLogin { request_id, params } => { self.mcp_server_oauth_login(request_id, params).await; } - ClientRequest::McpServersList { request_id, params } => { - self.list_mcp_servers(request_id, params).await; + ClientRequest::McpServerStatusList { request_id, params } => { + self.list_mcp_server_status(request_id, params).await; } ClientRequest::LoginAccount { request_id, params } => { self.login_v2(request_id, params).await; @@ -511,15 +508,6 @@ impl CodexMessageProcessor { } } - async fn send_unimplemented_error(&self, request_id: RequestId, method: &str) { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("{method} is not implemented yet"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - async fn login_v2(&mut self, request_id: RequestId, params: LoginAccountParams) { match params { LoginAccountParams::ApiKey { api_key } => { @@ -1108,25 +1096,13 @@ impl CodexMessageProcessor { } async fn get_user_saved_config(&self, request_id: RequestId) { - let toml_value = match load_config_as_toml(&self.config.codex_home).await { - Ok(val) => val, - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("failed to load config.toml: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - let cfg: ConfigToml = match toml_value.try_into() { - Ok(cfg) => cfg, + let service = ConfigService::new(self.config.codex_home.clone(), Vec::new()); + let user_saved_config: UserSavedConfig = match service.load_user_saved_config().await { + Ok(config) => config, Err(err) => { let error = JSONRPCErrorError { code: INTERNAL_ERROR_CODE, - message: format!("failed to parse config.toml: {err}"), + message: err.to_string(), data: None, }; self.outgoing.send_error(request_id, error).await; @@ -1134,8 +1110,6 @@ impl CodexMessageProcessor { } }; - let user_saved_config: UserSavedConfig = cfg.into(); - let response = GetUserSavedConfigResponse { config: user_saved_config, }; @@ -1280,7 +1254,7 @@ impl CodexMessageProcessor { let mut cli_overrides = cli_overrides.unwrap_or_default(); if cfg!(windows) && self.config.features.enabled(Feature::WindowsSandbox) { cli_overrides.insert( - "features.enable_experimental_windows_sandbox".to_string(), + "features.experimental_windows_sandbox".to_string(), serde_json::json!(true), ); } @@ -1399,9 +1373,13 @@ impl CodexMessageProcessor { }; // Auto-attach a conversation listener when starting a thread. - // Use the same behavior as the v1 API with experimental_raw_events=false. + // Use the same behavior as the v1 API, with opt-in support for raw item events. if let Err(err) = self - .attach_conversation_listener(conversation_id, false, ApiVersion::V2) + .attach_conversation_listener( + conversation_id, + params.experimental_raw_events, + ApiVersion::V2, + ) .await { tracing::warn!( @@ -2078,7 +2056,11 @@ impl CodexMessageProcessor { } } - async fn list_mcp_servers(&self, request_id: RequestId, params: ListMcpServersParams) { + async fn list_mcp_server_status( + &self, + request_id: RequestId, + params: ListMcpServerStatusParams, + ) { let config = match self.load_latest_config().await { Ok(config) => config, Err(error) => { @@ -2133,9 +2115,9 @@ impl CodexMessageProcessor { let end = start.saturating_add(effective_limit).min(total); - let data: Vec = server_names[start..end] + let data: Vec = server_names[start..end] .iter() - .map(|name| McpServer { + .map(|name| McpServerStatus { name: name.clone(), tools: tools_by_server.get(name).cloned().unwrap_or_default(), resources: snapshot.resources.get(name).cloned().unwrap_or_default(), @@ -2159,7 +2141,7 @@ impl CodexMessageProcessor { None }; - let response = ListMcpServersResponse { data, next_cursor }; + let response = ListMcpServerStatusResponse { data, next_cursor }; self.outgoing.send_response(request_id, response).await; } @@ -2197,7 +2179,7 @@ impl CodexMessageProcessor { let mut cli_overrides = cli_overrides.unwrap_or_default(); if cfg!(windows) && self.config.features.enabled(Feature::WindowsSandbox) { cli_overrides.insert( - "features.enable_experimental_windows_sandbox".to_string(), + "features.experimental_windows_sandbox".to_string(), serde_json::json!(true), ); } @@ -2630,6 +2612,42 @@ impl CodexMessageProcessor { .await; } + async fn skills_list(&self, request_id: RequestId, params: SkillsListParams) { + let SkillsListParams { cwds } = params; + let cwds = if cwds.is_empty() { + vec![self.config.cwd.clone()] + } else { + cwds + }; + + let data = if self.config.features.enabled(Feature::Skills) { + let skills_manager = self.conversation_manager.skills_manager(); + cwds.into_iter() + .map(|cwd| { + let outcome = skills_manager.skills_for_cwd(&cwd); + let errors = errors_to_info(&outcome.errors); + let skills = skills_to_info(&outcome.skills); + codex_app_server_protocol::SkillsListEntry { + cwd, + skills, + errors, + } + }) + .collect() + } else { + cwds.into_iter() + .map(|cwd| codex_app_server_protocol::SkillsListEntry { + cwd, + skills: Vec::new(), + errors: Vec::new(), + }) + .collect() + }; + self.outgoing + .send_response(request_id, SkillsListResponse { data }) + .await; + } + async fn interrupt_conversation( &mut self, request_id: RequestId, @@ -3275,6 +3293,32 @@ impl CodexMessageProcessor { } } +fn skills_to_info( + skills: &[codex_core::skills::SkillMetadata], +) -> Vec { + skills + .iter() + .map(|skill| codex_app_server_protocol::SkillMetadata { + name: skill.name.clone(), + description: skill.description.clone(), + path: skill.path.clone(), + scope: skill.scope.into(), + }) + .collect() +} + +fn errors_to_info( + errors: &[codex_core::skills::SkillError], +) -> Vec { + errors + .iter() + .map(|err| codex_app_server_protocol::SkillErrorInfo { + path: err.path.clone(), + message: err.message.clone(), + }) + .collect() +} + async fn derive_config_from_params( overrides: ConfigOverrides, cli_overrides: Option>, diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index c1eaf62d2..98e0f108e 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -1,66 +1,27 @@ use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; -use codex_app_server_protocol::Config; use codex_app_server_protocol::ConfigBatchWriteParams; -use codex_app_server_protocol::ConfigLayer; -use codex_app_server_protocol::ConfigLayerMetadata; -use codex_app_server_protocol::ConfigLayerName; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ConfigWriteErrorCode; use codex_app_server_protocol::ConfigWriteResponse; use codex_app_server_protocol::JSONRPCErrorError; -use codex_app_server_protocol::MergeStrategy; -use codex_app_server_protocol::OverriddenMetadata; -use codex_app_server_protocol::WriteStatus; -use codex_core::config::ConfigToml; -use codex_core::config::edit::ConfigEdit; -use codex_core::config::edit::ConfigEditsBuilder; -use codex_core::config_loader::LoadedConfigLayers; -use codex_core::config_loader::LoaderOverrides; -use codex_core::config_loader::load_config_layers_with_overrides; -use codex_core::config_loader::merge_toml_values; -use serde_json::Value as JsonValue; +use codex_core::config::ConfigService; +use codex_core::config::ConfigServiceError; use serde_json::json; -use sha2::Digest; -use sha2::Sha256; -use std::collections::HashMap; -use std::path::Path; use std::path::PathBuf; use toml::Value as TomlValue; -use toml_edit::Item as TomlItem; - -const SESSION_FLAGS_SOURCE: &str = "--config"; -const MDM_SOURCE: &str = "com.openai.codex/config_toml_base64"; -const CONFIG_FILE_NAME: &str = "config.toml"; #[derive(Clone)] pub(crate) struct ConfigApi { - codex_home: PathBuf, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, + service: ConfigService, } impl ConfigApi { pub(crate) fn new(codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>) -> Self { Self { - codex_home, - cli_overrides, - loader_overrides: LoaderOverrides::default(), - } - } - - #[cfg(test)] - fn with_overrides( - codex_home: PathBuf, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, - ) -> Self { - Self { - codex_home, - cli_overrides, - loader_overrides, + service: ConfigService::new(codex_home, cli_overrides), } } @@ -68,696 +29,32 @@ impl ConfigApi { &self, params: ConfigReadParams, ) -> Result { - let layers = self - .load_layers_state() - .await - .map_err(|err| internal_error("failed to read configuration layers", err))?; - - let effective = layers.effective_config(); - validate_config(&effective).map_err(|err| internal_error("invalid configuration", err))?; - - let config: Config = serde_json::from_value(to_json_value(&effective)) - .map_err(|err| internal_error("failed to deserialize configuration", err))?; - let response = ConfigReadResponse { - config, - origins: layers.origins(), - layers: params.include_layers.then(|| layers.layers_high_to_low()), - }; - - Ok(response) + self.service.read(params).await.map_err(map_error) } pub(crate) async fn write_value( &self, params: ConfigValueWriteParams, ) -> Result { - let edits = vec![(params.key_path, params.value, params.merge_strategy)]; - self.apply_edits(params.file_path, params.expected_version, edits) - .await + self.service.write_value(params).await.map_err(map_error) } pub(crate) async fn batch_write( &self, params: ConfigBatchWriteParams, ) -> Result { - let edits = params - .edits - .into_iter() - .map(|edit| (edit.key_path, edit.value, edit.merge_strategy)) - .collect(); - - self.apply_edits(params.file_path, params.expected_version, edits) - .await - } - - async fn apply_edits( - &self, - file_path: Option, - expected_version: Option, - edits: Vec<(String, JsonValue, MergeStrategy)>, - ) -> Result { - let allowed_path = self.codex_home.join(CONFIG_FILE_NAME); - let provided_path = file_path - .as_ref() - .map(PathBuf::from) - .unwrap_or_else(|| allowed_path.clone()); - - if !paths_match(&allowed_path, &provided_path) { - return Err(config_write_error( - ConfigWriteErrorCode::ConfigLayerReadonly, - "Only writes to the user config are allowed", - )); - } - - let layers = self - .load_layers_state() - .await - .map_err(|err| internal_error("failed to load configuration", err))?; - - if let Some(expected) = expected_version.as_deref() - && expected != layers.user.version - { - return Err(config_write_error( - ConfigWriteErrorCode::ConfigVersionConflict, - "Configuration was modified since last read. Fetch latest version and retry.", - )); - } - - let mut user_config = layers.user.config.clone(); - let mut parsed_segments = Vec::new(); - let mut config_edits = Vec::new(); - - for (key_path, value, strategy) in edits.into_iter() { - let segments = parse_key_path(&key_path).map_err(|message| { - config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) - })?; - let original_value = value_at_path(&user_config, &segments).cloned(); - let parsed_value = parse_value(value).map_err(|message| { - config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) - })?; - - apply_merge(&mut user_config, &segments, parsed_value.as_ref(), strategy).map_err( - |err| match err { - MergeError::PathNotFound => config_write_error( - ConfigWriteErrorCode::ConfigPathNotFound, - "Path not found", - ), - MergeError::Validation(message) => { - config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) - } - }, - )?; - - let updated_value = value_at_path(&user_config, &segments).cloned(); - if original_value != updated_value { - let edit = match updated_value { - Some(value) => ConfigEdit::SetPath { - segments: segments.clone(), - value: toml_value_to_item(&value) - .map_err(|err| internal_error("failed to build config edits", err))?, - }, - None => ConfigEdit::ClearPath { - segments: segments.clone(), - }, - }; - config_edits.push(edit); - } - - parsed_segments.push(segments); - } - - validate_config(&user_config).map_err(|err| { - config_write_error( - ConfigWriteErrorCode::ConfigValidationError, - format!("Invalid configuration: {err}"), - ) - })?; - - let updated_layers = layers.with_user_config(user_config.clone()); - let effective = updated_layers.effective_config(); - validate_config(&effective).map_err(|err| { - config_write_error( - ConfigWriteErrorCode::ConfigValidationError, - format!("Invalid configuration: {err}"), - ) - })?; - - if !config_edits.is_empty() { - ConfigEditsBuilder::new(&self.codex_home) - .with_edits(config_edits) - .apply() - .await - .map_err(|err| internal_error("failed to persist config.toml", err))?; - } - - let overridden = first_overridden_edit(&updated_layers, &effective, &parsed_segments); - let status = overridden - .as_ref() - .map(|_| WriteStatus::OkOverridden) - .unwrap_or(WriteStatus::Ok); - - let file_path = provided_path - .canonicalize() - .unwrap_or(provided_path.clone()) - .display() - .to_string(); - - Ok(ConfigWriteResponse { - status, - version: updated_layers.user.version.clone(), - file_path, - overridden_metadata: overridden, - }) - } - - async fn load_layers_state(&self) -> std::io::Result { - let LoadedConfigLayers { - base, - managed_config, - managed_preferences, - } = load_config_layers_with_overrides(&self.codex_home, self.loader_overrides.clone()) - .await?; - - let user = LayerState::new( - ConfigLayerName::User, - self.codex_home.join(CONFIG_FILE_NAME), - base, - ); - - let session_flags = LayerState::new( - ConfigLayerName::SessionFlags, - PathBuf::from(SESSION_FLAGS_SOURCE), - { - let mut root = TomlValue::Table(toml::map::Map::new()); - for (path, value) in self.cli_overrides.iter() { - apply_override(&mut root, path, value.clone()); - } - root - }, - ); - - let system = managed_config.map(|cfg| { - LayerState::new( - ConfigLayerName::System, - system_config_path(&self.codex_home), - cfg, - ) - }); - - let mdm = managed_preferences - .map(|cfg| LayerState::new(ConfigLayerName::Mdm, PathBuf::from(MDM_SOURCE), cfg)); - - Ok(LayersState { - user, - session_flags, - system, - mdm, - }) - } -} - -fn parse_value(value: JsonValue) -> Result, String> { - if value.is_null() { - return Ok(None); - } - - serde_json::from_value::(value) - .map(Some) - .map_err(|err| format!("invalid value: {err}")) -} - -fn parse_key_path(path: &str) -> Result, String> { - if path.trim().is_empty() { - return Err("keyPath must not be empty".to_string()); - } - Ok(path - .split('.') - .map(std::string::ToString::to_string) - .collect()) -} - -fn apply_override(target: &mut TomlValue, path: &str, value: TomlValue) { - use toml::value::Table; - - let segments: Vec<&str> = path.split('.').collect(); - let mut current = target; - - for (idx, segment) in segments.iter().enumerate() { - let is_last = idx == segments.len() - 1; - - if is_last { - match current { - TomlValue::Table(table) => { - table.insert(segment.to_string(), value); - } - _ => { - let mut table = Table::new(); - table.insert(segment.to_string(), value); - *current = TomlValue::Table(table); - } - } - return; - } - - match current { - TomlValue::Table(table) => { - current = table - .entry((*segment).to_string()) - .or_insert_with(|| TomlValue::Table(Table::new())); - } - _ => { - *current = TomlValue::Table(Table::new()); - if let TomlValue::Table(tbl) = current { - current = tbl - .entry((*segment).to_string()) - .or_insert_with(|| TomlValue::Table(Table::new())); - } - } - } - } -} - -#[derive(Debug)] -enum MergeError { - PathNotFound, - Validation(String), -} - -fn apply_merge( - root: &mut TomlValue, - segments: &[String], - value: Option<&TomlValue>, - strategy: MergeStrategy, -) -> Result { - let Some(value) = value else { - return clear_path(root, segments); - }; - - let Some((last, parents)) = segments.split_last() else { - return Err(MergeError::Validation( - "keyPath must not be empty".to_string(), - )); - }; - - let mut current = root; - - for segment in parents { - match current { - TomlValue::Table(table) => { - current = table - .entry(segment.clone()) - .or_insert_with(|| TomlValue::Table(toml::map::Map::new())); - } - _ => { - *current = TomlValue::Table(toml::map::Map::new()); - if let TomlValue::Table(table) = current { - current = table - .entry(segment.clone()) - .or_insert_with(|| TomlValue::Table(toml::map::Map::new())); - } - } - } - } - - let table = current.as_table_mut().ok_or_else(|| { - MergeError::Validation("cannot set value on non-table parent".to_string()) - })?; - - if matches!(strategy, MergeStrategy::Upsert) - && let Some(existing) = table.get_mut(last) - && matches!(existing, TomlValue::Table(_)) - && matches!(value, TomlValue::Table(_)) - { - merge_toml_values(existing, value); - return Ok(true); - } - - let changed = table - .get(last) - .map(|existing| Some(existing) != Some(value)) - .unwrap_or(true); - table.insert(last.clone(), value.clone()); - Ok(changed) -} - -fn clear_path(root: &mut TomlValue, segments: &[String]) -> Result { - let Some((last, parents)) = segments.split_last() else { - return Err(MergeError::Validation( - "keyPath must not be empty".to_string(), - )); - }; - - let mut current = root; - for segment in parents { - match current { - TomlValue::Table(table) => { - current = table.get_mut(segment).ok_or(MergeError::PathNotFound)?; - } - _ => return Err(MergeError::PathNotFound), - } - } - - let Some(parent) = current.as_table_mut() else { - return Err(MergeError::PathNotFound); - }; - - Ok(parent.remove(last).is_some()) -} - -fn toml_value_to_item(value: &TomlValue) -> anyhow::Result { - match value { - TomlValue::Table(table) => { - let mut table_item = toml_edit::Table::new(); - table_item.set_implicit(false); - for (key, val) in table { - table_item.insert(key, toml_value_to_item(val)?); - } - Ok(TomlItem::Table(table_item)) - } - other => Ok(TomlItem::Value(toml_value_to_value(other)?)), - } -} - -fn toml_value_to_value(value: &TomlValue) -> anyhow::Result { - match value { - TomlValue::String(val) => Ok(toml_edit::Value::from(val.clone())), - TomlValue::Integer(val) => Ok(toml_edit::Value::from(*val)), - TomlValue::Float(val) => Ok(toml_edit::Value::from(*val)), - TomlValue::Boolean(val) => Ok(toml_edit::Value::from(*val)), - TomlValue::Datetime(val) => Ok(toml_edit::Value::from(*val)), - TomlValue::Array(items) => { - let mut array = toml_edit::Array::new(); - for item in items { - array.push(toml_value_to_value(item)?); - } - Ok(toml_edit::Value::Array(array)) - } - TomlValue::Table(table) => { - let mut inline = toml_edit::InlineTable::new(); - for (key, val) in table { - inline.insert(key, toml_value_to_value(val)?); - } - Ok(toml_edit::Value::InlineTable(inline)) - } - } -} - -#[derive(Clone)] -struct LayerState { - name: ConfigLayerName, - source: PathBuf, - config: TomlValue, - version: String, -} - -impl LayerState { - fn new(name: ConfigLayerName, source: PathBuf, config: TomlValue) -> Self { - let version = version_for_toml(&config); - Self { - name, - source, - config, - version, - } - } - - fn metadata(&self) -> ConfigLayerMetadata { - ConfigLayerMetadata { - name: self.name.clone(), - source: self.source.display().to_string(), - version: self.version.clone(), - } - } - - fn as_layer(&self) -> ConfigLayer { - ConfigLayer { - name: self.name.clone(), - source: self.source.display().to_string(), - version: self.version.clone(), - config: to_json_value(&self.config), - } - } -} - -#[derive(Clone)] -struct LayersState { - user: LayerState, - session_flags: LayerState, - system: Option, - mdm: Option, -} - -impl LayersState { - fn with_user_config(self, user_config: TomlValue) -> Self { - Self { - user: LayerState::new(self.user.name, self.user.source, user_config), - session_flags: self.session_flags, - system: self.system, - mdm: self.mdm, - } - } - - fn effective_config(&self) -> TomlValue { - let mut merged = self.user.config.clone(); - merge_toml_values(&mut merged, &self.session_flags.config); - if let Some(system) = &self.system { - merge_toml_values(&mut merged, &system.config); - } - if let Some(mdm) = &self.mdm { - merge_toml_values(&mut merged, &mdm.config); - } - merged - } - - fn origins(&self) -> HashMap { - let mut origins = HashMap::new(); - let mut path = Vec::new(); - - record_origins( - &self.user.config, - &self.user.metadata(), - &mut path, - &mut origins, - ); - record_origins( - &self.session_flags.config, - &self.session_flags.metadata(), - &mut path, - &mut origins, - ); - if let Some(system) = &self.system { - record_origins(&system.config, &system.metadata(), &mut path, &mut origins); - } - if let Some(mdm) = &self.mdm { - record_origins(&mdm.config, &mdm.metadata(), &mut path, &mut origins); - } - - origins - } - - fn layers_high_to_low(&self) -> Vec { - let mut layers = Vec::new(); - if let Some(mdm) = &self.mdm { - layers.push(mdm.as_layer()); - } - if let Some(system) = &self.system { - layers.push(system.as_layer()); - } - layers.push(self.session_flags.as_layer()); - layers.push(self.user.as_layer()); - layers + self.service.batch_write(params).await.map_err(map_error) } } -fn record_origins( - value: &TomlValue, - meta: &ConfigLayerMetadata, - path: &mut Vec, - origins: &mut HashMap, -) { - match value { - TomlValue::Table(table) => { - for (key, val) in table { - path.push(key.clone()); - record_origins(val, meta, path, origins); - path.pop(); - } - } - TomlValue::Array(items) => { - for (idx, item) in items.iter().enumerate() { - path.push(idx.to_string()); - record_origins(item, meta, path, origins); - path.pop(); - } - } - _ => { - if !path.is_empty() { - origins.insert(path.join("."), meta.clone()); - } - } +fn map_error(err: ConfigServiceError) -> JSONRPCErrorError { + if let Some(code) = err.write_error_code() { + return config_write_error(code, err.to_string()); } -} - -fn to_json_value(value: &TomlValue) -> JsonValue { - serde_json::to_value(value).unwrap_or(JsonValue::Null) -} - -fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error> { - let _: ConfigToml = value.clone().try_into()?; - Ok(()) -} - -fn version_for_toml(value: &TomlValue) -> String { - let json = to_json_value(value); - let canonical = canonical_json(&json); - let serialized = serde_json::to_vec(&canonical).unwrap_or_default(); - let mut hasher = Sha256::new(); - hasher.update(serialized); - let hash = hasher.finalize(); - let hex = hash - .iter() - .map(|byte| format!("{byte:02x}")) - .collect::(); - format!("sha256:{hex}") -} -fn canonical_json(value: &JsonValue) -> JsonValue { - match value { - JsonValue::Object(map) => { - let mut sorted = serde_json::Map::new(); - let mut keys = map.keys().cloned().collect::>(); - keys.sort(); - for key in keys { - if let Some(val) = map.get(&key) { - sorted.insert(key, canonical_json(val)); - } - } - JsonValue::Object(sorted) - } - JsonValue::Array(items) => JsonValue::Array(items.iter().map(canonical_json).collect()), - other => other.clone(), - } -} - -fn paths_match(expected: &Path, provided: &Path) -> bool { - if let (Ok(expanded_expected), Ok(expanded_provided)) = - (expected.canonicalize(), provided.canonicalize()) - { - return expanded_expected == expanded_provided; - } - - expected == provided -} - -fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a TomlValue> { - let mut current = root; - for segment in segments { - match current { - TomlValue::Table(table) => { - current = table.get(segment)?; - } - TomlValue::Array(items) => { - let idx: usize = segment.parse().ok()?; - current = items.get(idx)?; - } - _ => return None, - } - } - Some(current) -} - -fn override_message(layer: &ConfigLayerName) -> String { - match layer { - ConfigLayerName::Mdm => "Overridden by managed policy (mdm)".to_string(), - ConfigLayerName::System => "Overridden by managed config (system)".to_string(), - ConfigLayerName::SessionFlags => "Overridden by session flags".to_string(), - ConfigLayerName::User => "Overridden by user config".to_string(), - } -} - -fn compute_override_metadata( - layers: &LayersState, - effective: &TomlValue, - segments: &[String], -) -> Option { - let user_value = value_at_path(&layers.user.config, segments); - let effective_value = value_at_path(effective, segments); - - if user_value.is_some() && user_value == effective_value { - return None; - } - - if user_value.is_none() && effective_value.is_none() { - return None; - } - - let effective_layer = find_effective_layer(layers, segments); - let overriding_layer = effective_layer.unwrap_or_else(|| layers.user.metadata()); - let message = override_message(&overriding_layer.name); - - Some(OverriddenMetadata { - message, - overriding_layer, - effective_value: effective_value - .map(to_json_value) - .unwrap_or(JsonValue::Null), - }) -} - -fn first_overridden_edit( - layers: &LayersState, - effective: &TomlValue, - edits: &[Vec], -) -> Option { - for segments in edits { - if let Some(meta) = compute_override_metadata(layers, effective, segments) { - return Some(meta); - } - } - None -} - -fn find_effective_layer(layers: &LayersState, segments: &[String]) -> Option { - let check = - |state: &LayerState| value_at_path(&state.config, segments).map(|_| state.metadata()); - - if let Some(mdm) = &layers.mdm - && let Some(meta) = check(mdm) - { - return Some(meta); - } - if let Some(system) = &layers.system - && let Some(meta) = check(system) - { - return Some(meta); - } - if let Some(meta) = check(&layers.session_flags) { - return Some(meta); - } - check(&layers.user) -} - -fn system_config_path(codex_home: &Path) -> PathBuf { - if let Ok(path) = std::env::var("CODEX_MANAGED_CONFIG_PATH") { - return PathBuf::from(path); - } - - #[cfg(unix)] - { - let _ = codex_home; - PathBuf::from("/etc/codex/managed_config.toml") - } - - #[cfg(not(unix))] - { - codex_home.join("managed_config.toml") - } -} - -fn internal_error(context: &str, err: E) -> JSONRPCErrorError { JSONRPCErrorError { code: INTERNAL_ERROR_CODE, - message: format!("{context}: {err}"), + message: err.to_string(), data: None, } } @@ -771,374 +68,3 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into) -> })), } } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - use codex_app_server_protocol::AskForApproval; - use pretty_assertions::assert_eq; - use tempfile::tempdir; - - #[test] - fn toml_value_to_item_handles_nested_config_tables() { - let config = r#" -[mcp_servers.docs] -command = "docs-server" - -[mcp_servers.docs.http_headers] -X-Doc = "42" -"#; - - let value: TomlValue = toml::from_str(config).expect("parse config example"); - let item = toml_value_to_item(&value).expect("convert to toml_edit item"); - - let root = item.as_table().expect("root table"); - assert!(!root.is_implicit(), "root table should be explicit"); - - let mcp_servers = root - .get("mcp_servers") - .and_then(TomlItem::as_table) - .expect("mcp_servers table"); - assert!( - !mcp_servers.is_implicit(), - "mcp_servers table should be explicit" - ); - - let docs = mcp_servers - .get("docs") - .and_then(TomlItem::as_table) - .expect("docs table"); - assert_eq!( - docs.get("command") - .and_then(TomlItem::as_value) - .and_then(toml_edit::Value::as_str), - Some("docs-server") - ); - - let http_headers = docs - .get("http_headers") - .and_then(TomlItem::as_table) - .expect("http_headers table"); - assert_eq!( - http_headers - .get("X-Doc") - .and_then(TomlItem::as_value) - .and_then(toml_edit::Value::as_str), - Some("42") - ); - } - - #[tokio::test] - async fn write_value_preserves_comments_and_order() -> Result<()> { - let tmp = tempdir().expect("tempdir"); - let original = r#"# Codex user configuration -model = "gpt-5" -approval_policy = "on-request" - -[notice] -# Preserve this comment -hide_full_access_warning = true - -[features] -unified_exec = true -"#; - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), original)?; - - let api = ConfigApi::new(tmp.path().to_path_buf(), vec![]); - api.write_value(ConfigValueWriteParams { - file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), - key_path: "features.remote_compaction".to_string(), - value: json!(true), - merge_strategy: MergeStrategy::Replace, - expected_version: None, - }) - .await - .expect("write succeeds"); - - let updated = - std::fs::read_to_string(tmp.path().join(CONFIG_FILE_NAME)).expect("read config"); - let expected = r#"# Codex user configuration -model = "gpt-5" -approval_policy = "on-request" - -[notice] -# Preserve this comment -hide_full_access_warning = true - -[features] -unified_exec = true -remote_compaction = true -"#; - assert_eq!(updated, expected); - Ok(()) - } - - #[tokio::test] - async fn read_includes_origins_and_layers() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); - - let managed_path = tmp.path().join("managed_config.toml"); - std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); - - let api = ConfigApi::with_overrides( - tmp.path().to_path_buf(), - vec![], - LoaderOverrides { - managed_config_path: Some(managed_path), - #[cfg(target_os = "macos")] - managed_preferences_base64: None, - }, - ); - - let response = api - .read(ConfigReadParams { - include_layers: true, - }) - .await - .expect("response"); - - assert_eq!(response.config.approval_policy, Some(AskForApproval::Never)); - - assert_eq!( - response - .origins - .get("approval_policy") - .expect("origin") - .name, - ConfigLayerName::System - ); - let layers = response.layers.expect("layers present"); - assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); - assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); - assert_eq!(layers.last().unwrap().name, ConfigLayerName::User); - } - - #[tokio::test] - async fn write_value_reports_override() { - let tmp = tempdir().expect("tempdir"); - std::fs::write( - tmp.path().join(CONFIG_FILE_NAME), - "approval_policy = \"on-request\"", - ) - .unwrap(); - - let managed_path = tmp.path().join("managed_config.toml"); - std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); - - let api = ConfigApi::with_overrides( - tmp.path().to_path_buf(), - vec![], - LoaderOverrides { - managed_config_path: Some(managed_path), - #[cfg(target_os = "macos")] - managed_preferences_base64: None, - }, - ); - - let result = api - .write_value(ConfigValueWriteParams { - file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), - key_path: "approval_policy".to_string(), - value: json!("never"), - merge_strategy: MergeStrategy::Replace, - expected_version: None, - }) - .await - .expect("result"); - - let read_after = api - .read(ConfigReadParams { - include_layers: true, - }) - .await - .expect("read"); - assert_eq!( - read_after.config.approval_policy, - Some(AskForApproval::Never) - ); - assert_eq!( - read_after - .origins - .get("approval_policy") - .expect("origin") - .name, - ConfigLayerName::System - ); - assert_eq!(result.status, WriteStatus::Ok); - assert!(result.overridden_metadata.is_none()); - } - - #[tokio::test] - async fn version_conflict_rejected() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); - - let api = ConfigApi::new(tmp.path().to_path_buf(), vec![]); - let error = api - .write_value(ConfigValueWriteParams { - file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), - key_path: "model".to_string(), - value: json!("gpt-5"), - merge_strategy: MergeStrategy::Replace, - expected_version: Some("sha256:bogus".to_string()), - }) - .await - .expect_err("should fail"); - - assert_eq!(error.code, INVALID_REQUEST_ERROR_CODE); - assert_eq!( - error - .data - .as_ref() - .and_then(|d| d.get("config_write_error_code")) - .and_then(serde_json::Value::as_str), - Some("configVersionConflict") - ); - } - - #[tokio::test] - async fn write_value_defaults_to_user_config_path() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "").unwrap(); - - let api = ConfigApi::new(tmp.path().to_path_buf(), vec![]); - api.write_value(ConfigValueWriteParams { - file_path: None, - key_path: "model".to_string(), - value: json!("gpt-new"), - merge_strategy: MergeStrategy::Replace, - expected_version: None, - }) - .await - .expect("write succeeds"); - - let contents = - std::fs::read_to_string(tmp.path().join(CONFIG_FILE_NAME)).expect("read config"); - assert!( - contents.contains("model = \"gpt-new\""), - "config.toml should be updated even when file_path is omitted" - ); - } - - #[tokio::test] - async fn invalid_user_value_rejected_even_if_overridden_by_managed() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); - - let managed_path = tmp.path().join("managed_config.toml"); - std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); - - let api = ConfigApi::with_overrides( - tmp.path().to_path_buf(), - vec![], - LoaderOverrides { - managed_config_path: Some(managed_path), - #[cfg(target_os = "macos")] - managed_preferences_base64: None, - }, - ); - - let error = api - .write_value(ConfigValueWriteParams { - file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), - key_path: "approval_policy".to_string(), - value: json!("bogus"), - merge_strategy: MergeStrategy::Replace, - expected_version: None, - }) - .await - .expect_err("should fail validation"); - - assert_eq!(error.code, INVALID_REQUEST_ERROR_CODE); - assert_eq!( - error - .data - .as_ref() - .and_then(|d| d.get("config_write_error_code")) - .and_then(serde_json::Value::as_str), - Some("configValidationError") - ); - - let contents = - std::fs::read_to_string(tmp.path().join(CONFIG_FILE_NAME)).expect("read config"); - assert_eq!(contents.trim(), "model = \"user\""); - } - - #[tokio::test] - async fn read_reports_managed_overrides_user_and_session_flags() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); - - let managed_path = tmp.path().join("managed_config.toml"); - std::fs::write(&managed_path, "model = \"system\"").unwrap(); - - let cli_overrides = vec![( - "model".to_string(), - TomlValue::String("session".to_string()), - )]; - - let api = ConfigApi::with_overrides( - tmp.path().to_path_buf(), - cli_overrides, - LoaderOverrides { - managed_config_path: Some(managed_path), - #[cfg(target_os = "macos")] - managed_preferences_base64: None, - }, - ); - - let response = api - .read(ConfigReadParams { - include_layers: true, - }) - .await - .expect("response"); - - assert_eq!(response.config.model.as_deref(), Some("system")); - assert_eq!( - response.origins.get("model").expect("origin").name, - ConfigLayerName::System - ); - let layers = response.layers.expect("layers"); - assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); - assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); - assert_eq!(layers.get(2).unwrap().name, ConfigLayerName::User); - } - - #[tokio::test] - async fn write_value_reports_managed_override() { - let tmp = tempdir().expect("tempdir"); - std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "").unwrap(); - - let managed_path = tmp.path().join("managed_config.toml"); - std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); - - let api = ConfigApi::with_overrides( - tmp.path().to_path_buf(), - vec![], - LoaderOverrides { - managed_config_path: Some(managed_path), - #[cfg(target_os = "macos")] - managed_preferences_base64: None, - }, - ); - - let result = api - .write_value(ConfigValueWriteParams { - file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), - key_path: "approval_policy".to_string(), - value: json!("on-request"), - merge_strategy: MergeStrategy::Replace, - expected_version: None, - }) - .await - .expect("result"); - - assert_eq!(result.status, WriteStatus::OkOverridden); - let overridden = result.overridden_metadata.expect("overridden metadata"); - assert_eq!(overridden.overriding_layer.name, ConfigLayerName::System); - assert_eq!(overridden.effective_value, json!("never")); - } -} diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 2aee5bc93..66d137fba 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -3,7 +3,6 @@ use codex_common::CliConfigOverrides; use codex_core::config::Config; use codex_core::config::ConfigOverrides; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use std::io::ErrorKind; use std::io::Result as IoResult; use std::path::PathBuf; @@ -103,6 +102,7 @@ pub async fn run_main( // control the log level with `RUST_LOG`. let stderr_fmt = tracing_subscriber::fmt::layer() .with_writer(std::io::stderr) + .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) .with_filter(EnvFilter::from_default_env()); let feedback_layer = tracing_subscriber::fmt::layer() @@ -111,14 +111,15 @@ pub async fn run_main( .with_target(false) .with_filter(Targets::new().with_default(Level::TRACE)); + let otel_logger_layer = otel.as_ref().and_then(|o| o.logger_layer()); + + let otel_tracing_layer = otel.as_ref().and_then(|o| o.tracing_layer()); + let _ = tracing_subscriber::registry() .with(stderr_fmt) .with(feedback_layer) - .with(otel.as_ref().map(|provider| { - OpenTelemetryTracingBridge::new(&provider.logger).with_filter( - tracing_subscriber::filter::filter_fn(codex_core::otel_init::codex_export_filter), - ) - })) + .with(otel_logger_layer) + .with(otel_tracing_layer) .try_init(); // Task: process incoming messages. diff --git a/codex-rs/app-server/tests/common/lib.rs b/codex-rs/app-server/tests/common/lib.rs index a095a713a..f3950595b 100644 --- a/codex-rs/app-server/tests/common/lib.rs +++ b/codex-rs/app-server/tests/common/lib.rs @@ -14,6 +14,9 @@ pub use core_test_support::format_with_current_shell; pub use core_test_support::format_with_current_shell_display; pub use core_test_support::format_with_current_shell_display_non_login; pub use core_test_support::format_with_current_shell_non_login; +pub use core_test_support::test_path_buf_with_windows; +pub use core_test_support::test_tmp_path; +pub use core_test_support::test_tmp_path_buf; pub use mcp_process::McpProcess; pub use mock_model_server::create_mock_chat_completions_server; pub use mock_model_server::create_mock_chat_completions_server_unchecked; diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index e41719899..be94dd822 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -410,7 +410,7 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() -> Result<( cwd: first_cwd.clone(), approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::WorkspaceWrite { - writable_roots: vec![first_cwd.clone()], + writable_roots: vec![first_cwd.try_into()?], network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, diff --git a/codex-rs/app-server/tests/suite/config.rs b/codex-rs/app-server/tests/suite/config.rs index 88e74a6fb..84b268a3c 100644 --- a/codex-rs/app-server/tests/suite/config.rs +++ b/codex-rs/app-server/tests/suite/config.rs @@ -1,5 +1,6 @@ use anyhow::Result; use app_test_support::McpProcess; +use app_test_support::test_tmp_path; use app_test_support::to_response; use codex_app_server_protocol::GetUserSavedConfigResponse; use codex_app_server_protocol::JSONRPCResponse; @@ -23,10 +24,12 @@ use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { + let writable_root = test_tmp_path(); let config_toml = codex_home.join("config.toml"); std::fs::write( config_toml, - r#" + format!( + r#" model = "gpt-5.1-codex-max" approval_policy = "on-request" sandbox_mode = "workspace-write" @@ -38,7 +41,7 @@ forced_chatgpt_workspace_id = "12345678-0000-0000-0000-000000000000" forced_login_method = "chatgpt" [sandbox_workspace_write] -writable_roots = ["/tmp"] +writable_roots = [{}] network_access = true exclude_tmpdir_env_var = true exclude_slash_tmp = true @@ -56,6 +59,8 @@ model_verbosity = "medium" model_provider = "openai" chatgpt_base_url = "https://api.chatgpt.com" "#, + serde_json::json!(writable_root) + ), ) } @@ -75,12 +80,13 @@ async fn get_config_toml_parses_all_fields() -> Result<()> { .await??; let config: GetUserSavedConfigResponse = to_response(resp)?; + let writable_root = test_tmp_path(); let expected = GetUserSavedConfigResponse { config: UserSavedConfig { approval_policy: Some(AskForApproval::OnRequest), sandbox_mode: Some(SandboxMode::WorkspaceWrite), sandbox_settings: Some(SandboxSettings { - writable_roots: vec!["/tmp".into()], + writable_roots: vec![writable_root], network_access: Some(true), exclude_tmpdir_env_var: Some(true), exclude_slash_tmp: Some(true), diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index b6615ef66..199d2f88e 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -1,5 +1,7 @@ use anyhow::Result; use app_test_support::McpProcess; +use app_test_support::test_path_buf_with_windows; +use app_test_support::test_tmp_path_buf; use app_test_support::to_response; use codex_app_server_protocol::AskForApproval; use codex_app_server_protocol::ConfigBatchWriteParams; @@ -18,7 +20,6 @@ use codex_app_server_protocol::ToolsV2; use codex_app_server_protocol::WriteStatus; use pretty_assertions::assert_eq; use serde_json::json; -use std::path::PathBuf; use tempfile::TempDir; use tokio::time::timeout; @@ -135,29 +136,37 @@ view_image = false #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn config_read_includes_system_layer_and_overrides() -> Result<()> { let codex_home = TempDir::new()?; + let user_dir = test_path_buf_with_windows("/user", Some(r"C:\Users\user")); + let system_dir = test_path_buf_with_windows("/system", Some(r"C:\System")); write_config( &codex_home, - r#" + &format!( + r#" model = "gpt-user" approval_policy = "on-request" sandbox_mode = "workspace-write" [sandbox_workspace_write] -writable_roots = ["/user"] +writable_roots = [{}] network_access = true "#, + serde_json::json!(user_dir) + ), )?; let managed_path = codex_home.path().join("managed_config.toml"); std::fs::write( &managed_path, - r#" + format!( + r#" model = "gpt-system" approval_policy = "never" [sandbox_workspace_write] -writable_roots = ["/system"] +writable_roots = [{}] "#, + serde_json::json!(system_dir.clone()) + ), )?; let managed_path_str = managed_path.display().to_string(); @@ -207,7 +216,7 @@ writable_roots = ["/system"] .sandbox_workspace_write .as_ref() .expect("sandbox workspace write"); - assert_eq!(sandbox.writable_roots, vec![PathBuf::from("/system")]); + assert_eq!(sandbox.writable_roots, vec![system_dir]); assert_eq!( origins .get("sandbox_workspace_write.writable_roots.0") @@ -350,6 +359,7 @@ async fn config_batch_write_applies_multiple_edits() -> Result<()> { let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + let writable_root = test_tmp_path_buf(); let batch_id = mcp .send_config_batch_write_request(ConfigBatchWriteParams { file_path: Some(codex_home.path().join("config.toml").display().to_string()), @@ -362,7 +372,7 @@ async fn config_batch_write_applies_multiple_edits() -> Result<()> { ConfigEdit { key_path: "sandbox_workspace_write".to_string(), value: json!({ - "writable_roots": ["/tmp"], + "writable_roots": [writable_root.clone()], "network_access": false }), merge_strategy: MergeStrategy::Replace, @@ -404,7 +414,7 @@ async fn config_batch_write_applies_multiple_edits() -> Result<()> { .sandbox_workspace_write .as_ref() .expect("sandbox workspace write"); - assert_eq!(sandbox.writable_roots, vec![PathBuf::from("/tmp")]); + assert_eq!(sandbox.writable_roots, vec![writable_root]); assert!(!sandbox.network_access); Ok(()) diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index afc22c707..1948487d1 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -532,7 +532,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { cwd: Some(first_cwd.clone()), approval_policy: Some(codex_app_server_protocol::AskForApproval::Never), sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite { - writable_roots: vec![first_cwd.clone()], + writable_roots: vec![first_cwd.try_into()?], network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, diff --git a/codex-rs/apply-patch/src/invocation.rs b/codex-rs/apply-patch/src/invocation.rs new file mode 100644 index 000000000..7623aef80 --- /dev/null +++ b/codex-rs/apply-patch/src/invocation.rs @@ -0,0 +1,813 @@ +use std::collections::HashMap; +use std::path::Path; +use std::sync::LazyLock; + +use tree_sitter::Parser; +use tree_sitter::Query; +use tree_sitter::QueryCursor; +use tree_sitter::StreamingIterator; +use tree_sitter_bash::LANGUAGE as BASH; + +use crate::ApplyPatchAction; +use crate::ApplyPatchArgs; +use crate::ApplyPatchError; +use crate::ApplyPatchFileChange; +use crate::ApplyPatchFileUpdate; +use crate::IoError; +use crate::MaybeApplyPatchVerified; +use crate::parser::Hunk; +use crate::parser::ParseError; +use crate::parser::parse_patch; +use crate::unified_diff_from_chunks; +use std::str::Utf8Error; +use tree_sitter::LanguageError; + +const APPLY_PATCH_COMMANDS: [&str; 2] = ["apply_patch", "applypatch"]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ApplyPatchShell { + Unix, + PowerShell, + Cmd, +} + +#[derive(Debug, PartialEq)] +pub enum MaybeApplyPatch { + Body(ApplyPatchArgs), + ShellParseError(ExtractHeredocError), + PatchParseError(ParseError), + NotApplyPatch, +} + +#[derive(Debug, PartialEq)] +pub enum ExtractHeredocError { + CommandDidNotStartWithApplyPatch, + FailedToLoadBashGrammar(LanguageError), + HeredocNotUtf8(Utf8Error), + FailedToParsePatchIntoAst, + FailedToFindHeredocBody, +} + +fn classify_shell_name(shell: &str) -> Option { + std::path::Path::new(shell) + .file_stem() + .and_then(|name| name.to_str()) + .map(str::to_ascii_lowercase) +} + +fn classify_shell(shell: &str, flag: &str) -> Option { + classify_shell_name(shell).and_then(|name| match name.as_str() { + "bash" | "zsh" | "sh" if matches!(flag, "-lc" | "-c") => Some(ApplyPatchShell::Unix), + "pwsh" | "powershell" if flag.eq_ignore_ascii_case("-command") => { + Some(ApplyPatchShell::PowerShell) + } + "cmd" if flag.eq_ignore_ascii_case("/c") => Some(ApplyPatchShell::Cmd), + _ => None, + }) +} + +fn can_skip_flag(shell: &str, flag: &str) -> bool { + classify_shell_name(shell).is_some_and(|name| { + matches!(name.as_str(), "pwsh" | "powershell") && flag.eq_ignore_ascii_case("-noprofile") + }) +} + +fn parse_shell_script(argv: &[String]) -> Option<(ApplyPatchShell, &str)> { + match argv { + [shell, flag, script] => classify_shell(shell, flag).map(|shell_type| { + let script = script.as_str(); + (shell_type, script) + }), + [shell, skip_flag, flag, script] if can_skip_flag(shell, skip_flag) => { + classify_shell(shell, flag).map(|shell_type| { + let script = script.as_str(); + (shell_type, script) + }) + } + _ => None, + } +} + +fn extract_apply_patch_from_shell( + shell: ApplyPatchShell, + script: &str, +) -> std::result::Result<(String, Option), ExtractHeredocError> { + match shell { + ApplyPatchShell::Unix | ApplyPatchShell::PowerShell | ApplyPatchShell::Cmd => { + extract_apply_patch_from_bash(script) + } + } +} + +// TODO: make private once we remove tests in lib.rs +pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch { + match argv { + // Direct invocation: apply_patch + [cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => match parse_patch(body) { + Ok(source) => MaybeApplyPatch::Body(source), + Err(e) => MaybeApplyPatch::PatchParseError(e), + }, + // Shell heredoc form: (optional `cd &&`) apply_patch <<'EOF' ... + _ => match parse_shell_script(argv) { + Some((shell, script)) => match extract_apply_patch_from_shell(shell, script) { + Ok((body, workdir)) => match parse_patch(&body) { + Ok(mut source) => { + source.workdir = workdir; + MaybeApplyPatch::Body(source) + } + Err(e) => MaybeApplyPatch::PatchParseError(e), + }, + Err(ExtractHeredocError::CommandDidNotStartWithApplyPatch) => { + MaybeApplyPatch::NotApplyPatch + } + Err(e) => MaybeApplyPatch::ShellParseError(e), + }, + None => MaybeApplyPatch::NotApplyPatch, + }, + } +} + +/// cwd must be an absolute path so that we can resolve relative paths in the +/// patch. +pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApplyPatchVerified { + // Detect a raw patch body passed directly as the command or as the body of a shell + // script. In these cases, report an explicit error rather than applying the patch. + if let [body] = argv + && parse_patch(body).is_ok() + { + return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation); + } + if let Some((_, script)) = parse_shell_script(argv) + && parse_patch(script).is_ok() + { + return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation); + } + + match maybe_parse_apply_patch(argv) { + MaybeApplyPatch::Body(ApplyPatchArgs { + patch, + hunks, + workdir, + }) => { + let effective_cwd = workdir + .as_ref() + .map(|dir| { + let path = Path::new(dir); + if path.is_absolute() { + path.to_path_buf() + } else { + cwd.join(path) + } + }) + .unwrap_or_else(|| cwd.to_path_buf()); + let mut changes = HashMap::new(); + for hunk in hunks { + let path = hunk.resolve_path(&effective_cwd); + match hunk { + Hunk::AddFile { contents, .. } => { + changes.insert(path, ApplyPatchFileChange::Add { content: contents }); + } + Hunk::DeleteFile { .. } => { + let content = match std::fs::read_to_string(&path) { + Ok(content) => content, + Err(e) => { + return MaybeApplyPatchVerified::CorrectnessError( + ApplyPatchError::IoError(IoError { + context: format!("Failed to read {}", path.display()), + source: e, + }), + ); + } + }; + changes.insert(path, ApplyPatchFileChange::Delete { content }); + } + Hunk::UpdateFile { + move_path, chunks, .. + } => { + let ApplyPatchFileUpdate { + unified_diff, + content: contents, + } = match unified_diff_from_chunks(&path, &chunks) { + Ok(diff) => diff, + Err(e) => { + return MaybeApplyPatchVerified::CorrectnessError(e); + } + }; + changes.insert( + path, + ApplyPatchFileChange::Update { + unified_diff, + move_path: move_path.map(|p| effective_cwd.join(p)), + new_content: contents, + }, + ); + } + } + } + MaybeApplyPatchVerified::Body(ApplyPatchAction { + changes, + patch, + cwd: effective_cwd, + }) + } + MaybeApplyPatch::ShellParseError(e) => MaybeApplyPatchVerified::ShellParseError(e), + MaybeApplyPatch::PatchParseError(e) => MaybeApplyPatchVerified::CorrectnessError(e.into()), + MaybeApplyPatch::NotApplyPatch => MaybeApplyPatchVerified::NotApplyPatch, + } +} + +/// Extract the heredoc body (and optional `cd` workdir) from a `bash -lc` script +/// that invokes the apply_patch tool using a heredoc. +/// +/// Supported top‑level forms (must be the only top‑level statement): +/// - `apply_patch <<'EOF'\n...\nEOF` +/// - `cd && apply_patch <<'EOF'\n...\nEOF` +/// +/// Notes about matching: +/// - Parsed with Tree‑sitter Bash and a strict query that uses anchors so the +/// heredoc‑redirected statement is the only top‑level statement. +/// - The connector between `cd` and `apply_patch` must be `&&` (not `|` or `||`). +/// - Exactly one positional `word` argument is allowed for `cd` (no flags, no quoted +/// strings, no second argument). +/// - The apply command is validated in‑query via `#any-of?` to allow `apply_patch` +/// or `applypatch`. +/// - Preceding or trailing commands (e.g., `echo ...;` or `... && echo done`) do not match. +/// +/// Returns `(heredoc_body, Some(path))` when the `cd` variant matches, or +/// `(heredoc_body, None)` for the direct form. Errors are returned if the script +/// cannot be parsed or does not match the allowed patterns. +fn extract_apply_patch_from_bash( + src: &str, +) -> std::result::Result<(String, Option), ExtractHeredocError> { + // This function uses a Tree-sitter query to recognize one of two + // whole-script forms, each expressed as a single top-level statement: + // + // 1. apply_patch <<'EOF'\n...\nEOF + // 2. cd && apply_patch <<'EOF'\n...\nEOF + // + // Key ideas when reading the query: + // - dots (`.`) between named nodes enforces adjacency among named children and + // anchor to the start/end of the expression. + // - we match a single redirected_statement directly under program with leading + // and trailing anchors (`.`). This ensures it is the only top-level statement + // (so prefixes like `echo ...;` or suffixes like `... && echo done` do not match). + // + // Overall, we want to be conservative and only match the intended forms, as other + // forms are likely to be model errors, or incorrectly interpreted by later code. + // + // If you're editing this query, it's helpful to start by creating a debugging binary + // which will let you see the AST of an arbitrary bash script passed in, and optionally + // also run an arbitrary query against the AST. This is useful for understanding + // how tree-sitter parses the script and whether the query syntax is correct. Be sure + // to test both positive and negative cases. + static APPLY_PATCH_QUERY: LazyLock = LazyLock::new(|| { + let language = BASH.into(); + #[expect(clippy::expect_used)] + Query::new( + &language, + r#" + ( + program + . (redirected_statement + body: (command + name: (command_name (word) @apply_name) .) + (#any-of? @apply_name "apply_patch" "applypatch") + redirect: (heredoc_redirect + . (heredoc_start) + . (heredoc_body) @heredoc + . (heredoc_end) + .)) + .) + + ( + program + . (redirected_statement + body: (list + . (command + name: (command_name (word) @cd_name) . + argument: [ + (word) @cd_path + (string (string_content) @cd_path) + (raw_string) @cd_raw_string + ] .) + "&&" + . (command + name: (command_name (word) @apply_name)) + .) + (#eq? @cd_name "cd") + (#any-of? @apply_name "apply_patch" "applypatch") + redirect: (heredoc_redirect + . (heredoc_start) + . (heredoc_body) @heredoc + . (heredoc_end) + .)) + .) + "#, + ) + .expect("valid bash query") + }); + + let lang = BASH.into(); + let mut parser = Parser::new(); + parser + .set_language(&lang) + .map_err(ExtractHeredocError::FailedToLoadBashGrammar)?; + let tree = parser + .parse(src, None) + .ok_or(ExtractHeredocError::FailedToParsePatchIntoAst)?; + + let bytes = src.as_bytes(); + let root = tree.root_node(); + + let mut cursor = QueryCursor::new(); + let mut matches = cursor.matches(&APPLY_PATCH_QUERY, root, bytes); + while let Some(m) = matches.next() { + let mut heredoc_text: Option = None; + let mut cd_path: Option = None; + + for capture in m.captures.iter() { + let name = APPLY_PATCH_QUERY.capture_names()[capture.index as usize]; + match name { + "heredoc" => { + let text = capture + .node + .utf8_text(bytes) + .map_err(ExtractHeredocError::HeredocNotUtf8)? + .trim_end_matches('\n') + .to_string(); + heredoc_text = Some(text); + } + "cd_path" => { + let text = capture + .node + .utf8_text(bytes) + .map_err(ExtractHeredocError::HeredocNotUtf8)? + .to_string(); + cd_path = Some(text); + } + "cd_raw_string" => { + let raw = capture + .node + .utf8_text(bytes) + .map_err(ExtractHeredocError::HeredocNotUtf8)?; + let trimmed = raw + .strip_prefix('\'') + .and_then(|s| s.strip_suffix('\'')) + .unwrap_or(raw); + cd_path = Some(trimmed.to_string()); + } + _ => {} + } + } + + if let Some(heredoc) = heredoc_text { + return Ok((heredoc, cd_path)); + } + } + + Err(ExtractHeredocError::CommandDidNotStartWithApplyPatch) +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions::assert_eq; + use std::fs; + use std::path::PathBuf; + use std::string::ToString; + use tempfile::tempdir; + + /// Helper to construct a patch with the given body. + fn wrap_patch(body: &str) -> String { + format!("*** Begin Patch\n{body}\n*** End Patch") + } + + fn strs_to_strings(strs: &[&str]) -> Vec { + strs.iter().map(ToString::to_string).collect() + } + + // Test helpers to reduce repetition when building bash -lc heredoc scripts + fn args_bash(script: &str) -> Vec { + strs_to_strings(&["bash", "-lc", script]) + } + + fn args_powershell(script: &str) -> Vec { + strs_to_strings(&["powershell.exe", "-Command", script]) + } + + fn args_powershell_no_profile(script: &str) -> Vec { + strs_to_strings(&["powershell.exe", "-NoProfile", "-Command", script]) + } + + fn args_pwsh(script: &str) -> Vec { + strs_to_strings(&["pwsh", "-NoProfile", "-Command", script]) + } + + fn args_cmd(script: &str) -> Vec { + strs_to_strings(&["cmd.exe", "/c", script]) + } + + fn heredoc_script(prefix: &str) -> String { + format!( + "{prefix}apply_patch <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH" + ) + } + + fn heredoc_script_ps(prefix: &str, suffix: &str) -> String { + format!( + "{prefix}apply_patch <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH{suffix}" + ) + } + + fn expected_single_add() -> Vec { + vec![Hunk::AddFile { + path: PathBuf::from("foo"), + contents: "hi\n".to_string(), + }] + } + + fn assert_match_args(args: Vec, expected_workdir: Option<&str>) { + match maybe_parse_apply_patch(&args) { + MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => { + assert_eq!(workdir.as_deref(), expected_workdir); + assert_eq!(hunks, expected_single_add()); + } + result => panic!("expected MaybeApplyPatch::Body got {result:?}"), + } + } + + fn assert_match(script: &str, expected_workdir: Option<&str>) { + let args = args_bash(script); + assert_match_args(args, expected_workdir); + } + + fn assert_not_match(script: &str) { + let args = args_bash(script); + assert_matches!( + maybe_parse_apply_patch(&args), + MaybeApplyPatch::NotApplyPatch + ); + } + + #[test] + fn test_implicit_patch_single_arg_is_error() { + let patch = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch".to_string(); + let args = vec![patch]; + let dir = tempdir().unwrap(); + assert_matches!( + maybe_parse_apply_patch_verified(&args, dir.path()), + MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) + ); + } + + #[test] + fn test_implicit_patch_bash_script_is_error() { + let script = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch"; + let args = args_bash(script); + let dir = tempdir().unwrap(); + assert_matches!( + maybe_parse_apply_patch_verified(&args, dir.path()), + MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) + ); + } + + #[test] + fn test_literal() { + let args = strs_to_strings(&[ + "apply_patch", + r#"*** Begin Patch +*** Add File: foo ++hi +*** End Patch +"#, + ]); + + match maybe_parse_apply_patch(&args) { + MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => { + assert_eq!( + hunks, + vec![Hunk::AddFile { + path: PathBuf::from("foo"), + contents: "hi\n".to_string() + }] + ); + } + result => panic!("expected MaybeApplyPatch::Body got {result:?}"), + } + } + + #[test] + fn test_literal_applypatch() { + let args = strs_to_strings(&[ + "applypatch", + r#"*** Begin Patch +*** Add File: foo ++hi +*** End Patch +"#, + ]); + + match maybe_parse_apply_patch(&args) { + MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => { + assert_eq!( + hunks, + vec![Hunk::AddFile { + path: PathBuf::from("foo"), + contents: "hi\n".to_string() + }] + ); + } + result => panic!("expected MaybeApplyPatch::Body got {result:?}"), + } + } + + #[test] + fn test_heredoc() { + assert_match(&heredoc_script(""), None); + } + + #[test] + fn test_heredoc_non_login_shell() { + let script = heredoc_script(""); + let args = strs_to_strings(&["bash", "-c", &script]); + assert_match_args(args, None); + } + + #[test] + fn test_heredoc_applypatch() { + let args = strs_to_strings(&[ + "bash", + "-lc", + r#"applypatch <<'PATCH' +*** Begin Patch +*** Add File: foo ++hi +*** End Patch +PATCH"#, + ]); + + match maybe_parse_apply_patch(&args) { + MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => { + assert_eq!(workdir, None); + assert_eq!( + hunks, + vec![Hunk::AddFile { + path: PathBuf::from("foo"), + contents: "hi\n".to_string() + }] + ); + } + result => panic!("expected MaybeApplyPatch::Body got {result:?}"), + } + } + + #[test] + fn test_powershell_heredoc() { + let script = heredoc_script(""); + assert_match_args(args_powershell(&script), None); + } + #[test] + fn test_powershell_heredoc_no_profile() { + let script = heredoc_script(""); + assert_match_args(args_powershell_no_profile(&script), None); + } + #[test] + fn test_pwsh_heredoc() { + let script = heredoc_script(""); + assert_match_args(args_pwsh(&script), None); + } + + #[test] + fn test_cmd_heredoc_with_cd() { + let script = heredoc_script("cd foo && "); + assert_match_args(args_cmd(&script), Some("foo")); + } + + #[test] + fn test_heredoc_with_leading_cd() { + assert_match(&heredoc_script("cd foo && "), Some("foo")); + } + + #[test] + fn test_cd_with_semicolon_is_ignored() { + assert_not_match(&heredoc_script("cd foo; ")); + } + + #[test] + fn test_cd_or_apply_patch_is_ignored() { + assert_not_match(&heredoc_script("cd bar || ")); + } + + #[test] + fn test_cd_pipe_apply_patch_is_ignored() { + assert_not_match(&heredoc_script("cd bar | ")); + } + + #[test] + fn test_cd_single_quoted_path_with_spaces() { + assert_match(&heredoc_script("cd 'foo bar' && "), Some("foo bar")); + } + + #[test] + fn test_cd_double_quoted_path_with_spaces() { + assert_match(&heredoc_script("cd \"foo bar\" && "), Some("foo bar")); + } + + #[test] + fn test_echo_and_apply_patch_is_ignored() { + assert_not_match(&heredoc_script("echo foo && ")); + } + + #[test] + fn test_apply_patch_with_arg_is_ignored() { + let script = "apply_patch foo <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH"; + assert_not_match(script); + } + + #[test] + fn test_double_cd_then_apply_patch_is_ignored() { + assert_not_match(&heredoc_script("cd foo && cd bar && ")); + } + + #[test] + fn test_cd_two_args_is_ignored() { + assert_not_match(&heredoc_script("cd foo bar && ")); + } + + #[test] + fn test_cd_then_apply_patch_then_extra_is_ignored() { + let script = heredoc_script_ps("cd bar && ", " && echo done"); + assert_not_match(&script); + } + + #[test] + fn test_echo_then_cd_and_apply_patch_is_ignored() { + // Ensure preceding commands before the `cd && apply_patch <<...` sequence do not match. + assert_not_match(&heredoc_script("echo foo; cd bar && ")); + } + + #[test] + fn test_unified_diff_last_line_replacement() { + // Replace the very last line of the file. + let dir = tempdir().unwrap(); + let path = dir.path().join("last.txt"); + fs::write(&path, "foo\nbar\nbaz\n").unwrap(); + + let patch = wrap_patch(&format!( + r#"*** Update File: {} +@@ + foo + bar +-baz ++BAZ +"#, + path.display() + )); + + let patch = parse_patch(&patch).unwrap(); + let chunks = match patch.hunks.as_slice() { + [Hunk::UpdateFile { chunks, .. }] => chunks, + _ => panic!("Expected a single UpdateFile hunk"), + }; + + let diff = unified_diff_from_chunks(&path, chunks).unwrap(); + let expected_diff = r#"@@ -2,2 +2,2 @@ + bar +-baz ++BAZ +"#; + let expected = ApplyPatchFileUpdate { + unified_diff: expected_diff.to_string(), + content: "foo\nbar\nBAZ\n".to_string(), + }; + assert_eq!(expected, diff); + } + + #[test] + fn test_unified_diff_insert_at_eof() { + // Insert a new line at end‑of‑file. + let dir = tempdir().unwrap(); + let path = dir.path().join("insert.txt"); + fs::write(&path, "foo\nbar\nbaz\n").unwrap(); + + let patch = wrap_patch(&format!( + r#"*** Update File: {} +@@ ++quux +*** End of File +"#, + path.display() + )); + + let patch = parse_patch(&patch).unwrap(); + let chunks = match patch.hunks.as_slice() { + [Hunk::UpdateFile { chunks, .. }] => chunks, + _ => panic!("Expected a single UpdateFile hunk"), + }; + + let diff = unified_diff_from_chunks(&path, chunks).unwrap(); + let expected_diff = r#"@@ -3 +3,2 @@ + baz ++quux +"#; + let expected = ApplyPatchFileUpdate { + unified_diff: expected_diff.to_string(), + content: "foo\nbar\nbaz\nquux\n".to_string(), + }; + assert_eq!(expected, diff); + } + + #[test] + fn test_apply_patch_should_resolve_absolute_paths_in_cwd() { + let session_dir = tempdir().unwrap(); + let relative_path = "source.txt"; + + // Note that we need this file to exist for the patch to be "verified" + // and parsed correctly. + let session_file_path = session_dir.path().join(relative_path); + fs::write(&session_file_path, "session directory content\n").unwrap(); + + let argv = vec![ + "apply_patch".to_string(), + r#"*** Begin Patch +*** Update File: source.txt +@@ +-session directory content ++updated session directory content +*** End Patch"# + .to_string(), + ]; + + let result = maybe_parse_apply_patch_verified(&argv, session_dir.path()); + + // Verify the patch contents - as otherwise we may have pulled contents + // from the wrong file (as we're using relative paths) + assert_eq!( + result, + MaybeApplyPatchVerified::Body(ApplyPatchAction { + changes: HashMap::from([( + session_dir.path().join(relative_path), + ApplyPatchFileChange::Update { + unified_diff: r#"@@ -1 +1 @@ +-session directory content ++updated session directory content +"# + .to_string(), + move_path: None, + new_content: "updated session directory content\n".to_string(), + }, + )]), + patch: argv[1].clone(), + cwd: session_dir.path().to_path_buf(), + }) + ); + } + + #[test] + fn test_apply_patch_resolves_move_path_with_effective_cwd() { + let session_dir = tempdir().unwrap(); + let worktree_rel = "alt"; + let worktree_dir = session_dir.path().join(worktree_rel); + fs::create_dir_all(&worktree_dir).unwrap(); + + let source_name = "old.txt"; + let dest_name = "renamed.txt"; + let source_path = worktree_dir.join(source_name); + fs::write(&source_path, "before\n").unwrap(); + + let patch = wrap_patch(&format!( + r#"*** Update File: {source_name} +*** Move to: {dest_name} +@@ +-before ++after"# + )); + + let shell_script = format!("cd {worktree_rel} && apply_patch <<'PATCH'\n{patch}\nPATCH"); + let argv = vec!["bash".into(), "-lc".into(), shell_script]; + + let result = maybe_parse_apply_patch_verified(&argv, session_dir.path()); + let action = match result { + MaybeApplyPatchVerified::Body(action) => action, + other => panic!("expected verified body, got {other:?}"), + }; + + assert_eq!(action.cwd, worktree_dir); + + let change = action + .changes() + .get(&worktree_dir.join(source_name)) + .expect("source file change present"); + + match change { + ApplyPatchFileChange::Update { move_path, .. } => { + assert_eq!( + move_path.as_deref(), + Some(worktree_dir.join(dest_name).as_path()) + ); + } + other => panic!("expected update change, got {other:?}"), + } + } +} diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index 645f39684..f58055f45 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -1,3 +1,4 @@ +mod invocation; mod parser; mod seek_sequence; mod standalone_executable; @@ -5,8 +6,6 @@ mod standalone_executable; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -use std::str::Utf8Error; -use std::sync::LazyLock; use anyhow::Context; use anyhow::Result; @@ -17,27 +16,15 @@ use parser::UpdateFileChunk; pub use parser::parse_patch; use similar::TextDiff; use thiserror::Error; -use tree_sitter::LanguageError; -use tree_sitter::Parser; -use tree_sitter::Query; -use tree_sitter::QueryCursor; -use tree_sitter::StreamingIterator; -use tree_sitter_bash::LANGUAGE as BASH; +pub use invocation::maybe_parse_apply_patch_verified; pub use standalone_executable::main; +use crate::invocation::ExtractHeredocError; + /// Detailed instructions for gpt-4.1 on how to use the `apply_patch` tool. pub const APPLY_PATCH_TOOL_INSTRUCTIONS: &str = include_str!("../apply_patch_tool_instructions.md"); -const APPLY_PATCH_COMMANDS: [&str; 2] = ["apply_patch", "applypatch"]; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ApplyPatchShell { - Unix, - PowerShell, - Cmd, -} - #[derive(Debug, Error, PartialEq)] pub enum ApplyPatchError { #[error(transparent)] @@ -86,14 +73,6 @@ impl PartialEq for IoError { } } -#[derive(Debug, PartialEq)] -pub enum MaybeApplyPatch { - Body(ApplyPatchArgs), - ShellParseError(ExtractHeredocError), - PatchParseError(ParseError), - NotApplyPatch, -} - /// Both the raw PATCH argument to `apply_patch` as well as the PATCH argument /// parsed into hunks. #[derive(Debug, PartialEq)] @@ -103,84 +82,6 @@ pub struct ApplyPatchArgs { pub workdir: Option, } -fn classify_shell_name(shell: &str) -> Option { - std::path::Path::new(shell) - .file_stem() - .and_then(|name| name.to_str()) - .map(str::to_ascii_lowercase) -} - -fn classify_shell(shell: &str, flag: &str) -> Option { - classify_shell_name(shell).and_then(|name| match name.as_str() { - "bash" | "zsh" | "sh" if matches!(flag, "-lc" | "-c") => Some(ApplyPatchShell::Unix), - "pwsh" | "powershell" if flag.eq_ignore_ascii_case("-command") => { - Some(ApplyPatchShell::PowerShell) - } - "cmd" if flag.eq_ignore_ascii_case("/c") => Some(ApplyPatchShell::Cmd), - _ => None, - }) -} - -fn can_skip_flag(shell: &str, flag: &str) -> bool { - classify_shell_name(shell).is_some_and(|name| { - matches!(name.as_str(), "pwsh" | "powershell") && flag.eq_ignore_ascii_case("-noprofile") - }) -} - -fn parse_shell_script(argv: &[String]) -> Option<(ApplyPatchShell, &str)> { - match argv { - [shell, flag, script] => classify_shell(shell, flag).map(|shell_type| { - let script = script.as_str(); - (shell_type, script) - }), - [shell, skip_flag, flag, script] if can_skip_flag(shell, skip_flag) => { - classify_shell(shell, flag).map(|shell_type| { - let script = script.as_str(); - (shell_type, script) - }) - } - _ => None, - } -} - -fn extract_apply_patch_from_shell( - shell: ApplyPatchShell, - script: &str, -) -> std::result::Result<(String, Option), ExtractHeredocError> { - match shell { - ApplyPatchShell::Unix | ApplyPatchShell::PowerShell | ApplyPatchShell::Cmd => { - extract_apply_patch_from_bash(script) - } - } -} - -pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch { - match argv { - // Direct invocation: apply_patch - [cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => match parse_patch(body) { - Ok(source) => MaybeApplyPatch::Body(source), - Err(e) => MaybeApplyPatch::PatchParseError(e), - }, - // Shell heredoc form: (optional `cd &&`) apply_patch <<'EOF' ... - _ => match parse_shell_script(argv) { - Some((shell, script)) => match extract_apply_patch_from_shell(shell, script) { - Ok((body, workdir)) => match parse_patch(&body) { - Ok(mut source) => { - source.workdir = workdir; - MaybeApplyPatch::Body(source) - } - Err(e) => MaybeApplyPatch::PatchParseError(e), - }, - Err(ExtractHeredocError::CommandDidNotStartWithApplyPatch) => { - MaybeApplyPatch::NotApplyPatch - } - Err(e) => MaybeApplyPatch::ShellParseError(e), - }, - None => MaybeApplyPatch::NotApplyPatch, - }, - } -} - #[derive(Debug, PartialEq)] pub enum ApplyPatchFileChange { Add { @@ -269,256 +170,6 @@ impl ApplyPatchAction { } } -/// cwd must be an absolute path so that we can resolve relative paths in the -/// patch. -pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApplyPatchVerified { - // Detect a raw patch body passed directly as the command or as the body of a shell - // script. In these cases, report an explicit error rather than applying the patch. - if let [body] = argv - && parse_patch(body).is_ok() - { - return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation); - } - if let Some((_, script)) = parse_shell_script(argv) - && parse_patch(script).is_ok() - { - return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation); - } - - match maybe_parse_apply_patch(argv) { - MaybeApplyPatch::Body(ApplyPatchArgs { - patch, - hunks, - workdir, - }) => { - let effective_cwd = workdir - .as_ref() - .map(|dir| { - let path = Path::new(dir); - if path.is_absolute() { - path.to_path_buf() - } else { - cwd.join(path) - } - }) - .unwrap_or_else(|| cwd.to_path_buf()); - let mut changes = HashMap::new(); - for hunk in hunks { - let path = hunk.resolve_path(&effective_cwd); - match hunk { - Hunk::AddFile { contents, .. } => { - changes.insert(path, ApplyPatchFileChange::Add { content: contents }); - } - Hunk::DeleteFile { .. } => { - let content = match std::fs::read_to_string(&path) { - Ok(content) => content, - Err(e) => { - return MaybeApplyPatchVerified::CorrectnessError( - ApplyPatchError::IoError(IoError { - context: format!("Failed to read {}", path.display()), - source: e, - }), - ); - } - }; - changes.insert(path, ApplyPatchFileChange::Delete { content }); - } - Hunk::UpdateFile { - move_path, chunks, .. - } => { - let ApplyPatchFileUpdate { - unified_diff, - content: contents, - } = match unified_diff_from_chunks(&path, &chunks) { - Ok(diff) => diff, - Err(e) => { - return MaybeApplyPatchVerified::CorrectnessError(e); - } - }; - changes.insert( - path, - ApplyPatchFileChange::Update { - unified_diff, - move_path: move_path.map(|p| effective_cwd.join(p)), - new_content: contents, - }, - ); - } - } - } - MaybeApplyPatchVerified::Body(ApplyPatchAction { - changes, - patch, - cwd: effective_cwd, - }) - } - MaybeApplyPatch::ShellParseError(e) => MaybeApplyPatchVerified::ShellParseError(e), - MaybeApplyPatch::PatchParseError(e) => MaybeApplyPatchVerified::CorrectnessError(e.into()), - MaybeApplyPatch::NotApplyPatch => MaybeApplyPatchVerified::NotApplyPatch, - } -} - -/// Extract the heredoc body (and optional `cd` workdir) from a `bash -lc` script -/// that invokes the apply_patch tool using a heredoc. -/// -/// Supported top‑level forms (must be the only top‑level statement): -/// - `apply_patch <<'EOF'\n...\nEOF` -/// - `cd && apply_patch <<'EOF'\n...\nEOF` -/// -/// Notes about matching: -/// - Parsed with Tree‑sitter Bash and a strict query that uses anchors so the -/// heredoc‑redirected statement is the only top‑level statement. -/// - The connector between `cd` and `apply_patch` must be `&&` (not `|` or `||`). -/// - Exactly one positional `word` argument is allowed for `cd` (no flags, no quoted -/// strings, no second argument). -/// - The apply command is validated in‑query via `#any-of?` to allow `apply_patch` -/// or `applypatch`. -/// - Preceding or trailing commands (e.g., `echo ...;` or `... && echo done`) do not match. -/// -/// Returns `(heredoc_body, Some(path))` when the `cd` variant matches, or -/// `(heredoc_body, None)` for the direct form. Errors are returned if the script -/// cannot be parsed or does not match the allowed patterns. -fn extract_apply_patch_from_bash( - src: &str, -) -> std::result::Result<(String, Option), ExtractHeredocError> { - // This function uses a Tree-sitter query to recognize one of two - // whole-script forms, each expressed as a single top-level statement: - // - // 1. apply_patch <<'EOF'\n...\nEOF - // 2. cd && apply_patch <<'EOF'\n...\nEOF - // - // Key ideas when reading the query: - // - dots (`.`) between named nodes enforces adjacency among named children and - // anchor to the start/end of the expression. - // - we match a single redirected_statement directly under program with leading - // and trailing anchors (`.`). This ensures it is the only top-level statement - // (so prefixes like `echo ...;` or suffixes like `... && echo done` do not match). - // - // Overall, we want to be conservative and only match the intended forms, as other - // forms are likely to be model errors, or incorrectly interpreted by later code. - // - // If you're editing this query, it's helpful to start by creating a debugging binary - // which will let you see the AST of an arbitrary bash script passed in, and optionally - // also run an arbitrary query against the AST. This is useful for understanding - // how tree-sitter parses the script and whether the query syntax is correct. Be sure - // to test both positive and negative cases. - static APPLY_PATCH_QUERY: LazyLock = LazyLock::new(|| { - let language = BASH.into(); - #[expect(clippy::expect_used)] - Query::new( - &language, - r#" - ( - program - . (redirected_statement - body: (command - name: (command_name (word) @apply_name) .) - (#any-of? @apply_name "apply_patch" "applypatch") - redirect: (heredoc_redirect - . (heredoc_start) - . (heredoc_body) @heredoc - . (heredoc_end) - .)) - .) - - ( - program - . (redirected_statement - body: (list - . (command - name: (command_name (word) @cd_name) . - argument: [ - (word) @cd_path - (string (string_content) @cd_path) - (raw_string) @cd_raw_string - ] .) - "&&" - . (command - name: (command_name (word) @apply_name)) - .) - (#eq? @cd_name "cd") - (#any-of? @apply_name "apply_patch" "applypatch") - redirect: (heredoc_redirect - . (heredoc_start) - . (heredoc_body) @heredoc - . (heredoc_end) - .)) - .) - "#, - ) - .expect("valid bash query") - }); - - let lang = BASH.into(); - let mut parser = Parser::new(); - parser - .set_language(&lang) - .map_err(ExtractHeredocError::FailedToLoadBashGrammar)?; - let tree = parser - .parse(src, None) - .ok_or(ExtractHeredocError::FailedToParsePatchIntoAst)?; - - let bytes = src.as_bytes(); - let root = tree.root_node(); - - let mut cursor = QueryCursor::new(); - let mut matches = cursor.matches(&APPLY_PATCH_QUERY, root, bytes); - while let Some(m) = matches.next() { - let mut heredoc_text: Option = None; - let mut cd_path: Option = None; - - for capture in m.captures.iter() { - let name = APPLY_PATCH_QUERY.capture_names()[capture.index as usize]; - match name { - "heredoc" => { - let text = capture - .node - .utf8_text(bytes) - .map_err(ExtractHeredocError::HeredocNotUtf8)? - .trim_end_matches('\n') - .to_string(); - heredoc_text = Some(text); - } - "cd_path" => { - let text = capture - .node - .utf8_text(bytes) - .map_err(ExtractHeredocError::HeredocNotUtf8)? - .to_string(); - cd_path = Some(text); - } - "cd_raw_string" => { - let raw = capture - .node - .utf8_text(bytes) - .map_err(ExtractHeredocError::HeredocNotUtf8)?; - let trimmed = raw - .strip_prefix('\'') - .and_then(|s| s.strip_suffix('\'')) - .unwrap_or(raw); - cd_path = Some(trimmed.to_string()); - } - _ => {} - } - } - - if let Some(heredoc) = heredoc_text { - return Ok((heredoc, cd_path)); - } - } - - Err(ExtractHeredocError::CommandDidNotStartWithApplyPatch) -} - -#[derive(Debug, PartialEq)] -pub enum ExtractHeredocError { - CommandDidNotStartWithApplyPatch, - FailedToLoadBashGrammar(LanguageError), - HeredocNotUtf8(Utf8Error), - FailedToParsePatchIntoAst, - FailedToFindHeredocBody, -} - /// Applies the patch and prints the result to stdout/stderr. pub fn apply_patch( patch: &str, @@ -894,7 +545,6 @@ pub fn print_summary( #[cfg(test)] mod tests { use super::*; - use assert_matches::assert_matches; use pretty_assertions::assert_eq; use std::fs; use std::string::ToString; @@ -905,270 +555,6 @@ mod tests { format!("*** Begin Patch\n{body}\n*** End Patch") } - fn strs_to_strings(strs: &[&str]) -> Vec { - strs.iter().map(ToString::to_string).collect() - } - - // Test helpers to reduce repetition when building bash -lc heredoc scripts - fn args_bash(script: &str) -> Vec { - strs_to_strings(&["bash", "-lc", script]) - } - - fn args_powershell(script: &str) -> Vec { - strs_to_strings(&["powershell.exe", "-Command", script]) - } - - fn args_powershell_no_profile(script: &str) -> Vec { - strs_to_strings(&["powershell.exe", "-NoProfile", "-Command", script]) - } - - fn args_pwsh(script: &str) -> Vec { - strs_to_strings(&["pwsh", "-NoProfile", "-Command", script]) - } - - fn args_cmd(script: &str) -> Vec { - strs_to_strings(&["cmd.exe", "/c", script]) - } - - fn heredoc_script(prefix: &str) -> String { - format!( - "{prefix}apply_patch <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH" - ) - } - - fn heredoc_script_ps(prefix: &str, suffix: &str) -> String { - format!( - "{prefix}apply_patch <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH{suffix}" - ) - } - - fn expected_single_add() -> Vec { - vec![Hunk::AddFile { - path: PathBuf::from("foo"), - contents: "hi\n".to_string(), - }] - } - - fn assert_match_args(args: Vec, expected_workdir: Option<&str>) { - match maybe_parse_apply_patch(&args) { - MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => { - assert_eq!(workdir.as_deref(), expected_workdir); - assert_eq!(hunks, expected_single_add()); - } - result => panic!("expected MaybeApplyPatch::Body got {result:?}"), - } - } - - fn assert_match(script: &str, expected_workdir: Option<&str>) { - let args = args_bash(script); - assert_match_args(args, expected_workdir); - } - - fn assert_not_match(script: &str) { - let args = args_bash(script); - assert_matches!( - maybe_parse_apply_patch(&args), - MaybeApplyPatch::NotApplyPatch - ); - } - - #[test] - fn test_implicit_patch_single_arg_is_error() { - let patch = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch".to_string(); - let args = vec![patch]; - let dir = tempdir().unwrap(); - assert_matches!( - maybe_parse_apply_patch_verified(&args, dir.path()), - MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) - ); - } - - #[test] - fn test_implicit_patch_bash_script_is_error() { - let script = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch"; - let args = args_bash(script); - let dir = tempdir().unwrap(); - assert_matches!( - maybe_parse_apply_patch_verified(&args, dir.path()), - MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) - ); - } - - #[test] - fn test_literal() { - let args = strs_to_strings(&[ - "apply_patch", - r#"*** Begin Patch -*** Add File: foo -+hi -*** End Patch -"#, - ]); - - match maybe_parse_apply_patch(&args) { - MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => { - assert_eq!( - hunks, - vec![Hunk::AddFile { - path: PathBuf::from("foo"), - contents: "hi\n".to_string() - }] - ); - } - result => panic!("expected MaybeApplyPatch::Body got {result:?}"), - } - } - - #[test] - fn test_literal_applypatch() { - let args = strs_to_strings(&[ - "applypatch", - r#"*** Begin Patch -*** Add File: foo -+hi -*** End Patch -"#, - ]); - - match maybe_parse_apply_patch(&args) { - MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => { - assert_eq!( - hunks, - vec![Hunk::AddFile { - path: PathBuf::from("foo"), - contents: "hi\n".to_string() - }] - ); - } - result => panic!("expected MaybeApplyPatch::Body got {result:?}"), - } - } - - #[test] - fn test_heredoc() { - assert_match(&heredoc_script(""), None); - } - - #[test] - fn test_heredoc_non_login_shell() { - let script = heredoc_script(""); - let args = strs_to_strings(&["bash", "-c", &script]); - assert_match_args(args, None); - } - - #[test] - fn test_heredoc_applypatch() { - let args = strs_to_strings(&[ - "bash", - "-lc", - r#"applypatch <<'PATCH' -*** Begin Patch -*** Add File: foo -+hi -*** End Patch -PATCH"#, - ]); - - match maybe_parse_apply_patch(&args) { - MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => { - assert_eq!(workdir, None); - assert_eq!( - hunks, - vec![Hunk::AddFile { - path: PathBuf::from("foo"), - contents: "hi\n".to_string() - }] - ); - } - result => panic!("expected MaybeApplyPatch::Body got {result:?}"), - } - } - - #[test] - fn test_powershell_heredoc() { - let script = heredoc_script(""); - assert_match_args(args_powershell(&script), None); - } - #[test] - fn test_powershell_heredoc_no_profile() { - let script = heredoc_script(""); - assert_match_args(args_powershell_no_profile(&script), None); - } - #[test] - fn test_pwsh_heredoc() { - let script = heredoc_script(""); - assert_match_args(args_pwsh(&script), None); - } - - #[test] - fn test_cmd_heredoc_with_cd() { - let script = heredoc_script("cd foo && "); - assert_match_args(args_cmd(&script), Some("foo")); - } - - #[test] - fn test_heredoc_with_leading_cd() { - assert_match(&heredoc_script("cd foo && "), Some("foo")); - } - - #[test] - fn test_cd_with_semicolon_is_ignored() { - assert_not_match(&heredoc_script("cd foo; ")); - } - - #[test] - fn test_cd_or_apply_patch_is_ignored() { - assert_not_match(&heredoc_script("cd bar || ")); - } - - #[test] - fn test_cd_pipe_apply_patch_is_ignored() { - assert_not_match(&heredoc_script("cd bar | ")); - } - - #[test] - fn test_cd_single_quoted_path_with_spaces() { - assert_match(&heredoc_script("cd 'foo bar' && "), Some("foo bar")); - } - - #[test] - fn test_cd_double_quoted_path_with_spaces() { - assert_match(&heredoc_script("cd \"foo bar\" && "), Some("foo bar")); - } - - #[test] - fn test_echo_and_apply_patch_is_ignored() { - assert_not_match(&heredoc_script("echo foo && ")); - } - - #[test] - fn test_apply_patch_with_arg_is_ignored() { - let script = "apply_patch foo <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH"; - assert_not_match(script); - } - - #[test] - fn test_double_cd_then_apply_patch_is_ignored() { - assert_not_match(&heredoc_script("cd foo && cd bar && ")); - } - - #[test] - fn test_cd_two_args_is_ignored() { - assert_not_match(&heredoc_script("cd foo bar && ")); - } - - #[test] - fn test_cd_then_apply_patch_then_extra_is_ignored() { - let script = heredoc_script_ps("cd bar && ", " && echo done"); - assert_not_match(&script); - } - - #[test] - fn test_echo_then_cd_and_apply_patch_is_ignored() { - // Ensure preceding commands before the `cd && apply_patch <<...` sequence do not match. - assert_not_match(&heredoc_script("echo foo; cd bar && ")); - } - #[test] fn test_add_file_hunk_creates_file_with_contents() { let dir = tempdir().unwrap(); @@ -1657,99 +1043,6 @@ g ); } - #[test] - fn test_apply_patch_should_resolve_absolute_paths_in_cwd() { - let session_dir = tempdir().unwrap(); - let relative_path = "source.txt"; - - // Note that we need this file to exist for the patch to be "verified" - // and parsed correctly. - let session_file_path = session_dir.path().join(relative_path); - fs::write(&session_file_path, "session directory content\n").unwrap(); - - let argv = vec![ - "apply_patch".to_string(), - r#"*** Begin Patch -*** Update File: source.txt -@@ --session directory content -+updated session directory content -*** End Patch"# - .to_string(), - ]; - - let result = maybe_parse_apply_patch_verified(&argv, session_dir.path()); - - // Verify the patch contents - as otherwise we may have pulled contents - // from the wrong file (as we're using relative paths) - assert_eq!( - result, - MaybeApplyPatchVerified::Body(ApplyPatchAction { - changes: HashMap::from([( - session_dir.path().join(relative_path), - ApplyPatchFileChange::Update { - unified_diff: r#"@@ -1 +1 @@ --session directory content -+updated session directory content -"# - .to_string(), - move_path: None, - new_content: "updated session directory content\n".to_string(), - }, - )]), - patch: argv[1].clone(), - cwd: session_dir.path().to_path_buf(), - }) - ); - } - - #[test] - fn test_apply_patch_resolves_move_path_with_effective_cwd() { - let session_dir = tempdir().unwrap(); - let worktree_rel = "alt"; - let worktree_dir = session_dir.path().join(worktree_rel); - fs::create_dir_all(&worktree_dir).unwrap(); - - let source_name = "old.txt"; - let dest_name = "renamed.txt"; - let source_path = worktree_dir.join(source_name); - fs::write(&source_path, "before\n").unwrap(); - - let patch = wrap_patch(&format!( - r#"*** Update File: {source_name} -*** Move to: {dest_name} -@@ --before -+after"# - )); - - let shell_script = format!("cd {worktree_rel} && apply_patch <<'PATCH'\n{patch}\nPATCH"); - let argv = vec!["bash".into(), "-lc".into(), shell_script]; - - let result = maybe_parse_apply_patch_verified(&argv, session_dir.path()); - let action = match result { - MaybeApplyPatchVerified::Body(action) => action, - other => panic!("expected verified body, got {other:?}"), - }; - - assert_eq!(action.cwd, worktree_dir); - - let change = action - .changes() - .get(&worktree_dir.join(source_name)) - .expect("source file change present"); - - match change { - ApplyPatchFileChange::Update { move_path, .. } => { - assert_eq!( - move_path.as_deref(), - Some(worktree_dir.join(dest_name).as_path()) - ); - } - other => panic!("expected update change, got {other:?}"), - } - } - #[test] fn test_apply_patch_fails_on_write_error() { let dir = tempdir().unwrap(); diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 26fecd55c..c298a3511 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -136,7 +136,9 @@ async fn run_command_under_sandbox( if let SandboxType::Windows = sandbox_type { #[cfg(target_os = "windows")] { + use codex_core::features::Feature; use codex_windows_sandbox::run_windows_sandbox_capture; + use codex_windows_sandbox::run_windows_sandbox_capture_elevated; let policy_str = serde_json::to_string(&config.sandbox_policy)?; @@ -145,18 +147,32 @@ async fn run_command_under_sandbox( let env_map = env.clone(); let command_vec = command.clone(); let base_dir = config.codex_home.clone(); + let use_elevated = config.features.enabled(Feature::WindowsSandbox) + && config.features.enabled(Feature::WindowsSandboxElevated); // Preflight audit is invoked elsewhere at the appropriate times. let res = tokio::task::spawn_blocking(move || { - run_windows_sandbox_capture( - policy_str.as_str(), - &sandbox_cwd, - base_dir.as_path(), - command_vec, - &cwd_clone, - env_map, - None, - ) + if use_elevated { + run_windows_sandbox_capture_elevated( + policy_str.as_str(), + &sandbox_cwd, + base_dir.as_path(), + command_vec, + &cwd_clone, + env_map, + None, + ) + } else { + run_windows_sandbox_capture( + policy_str.as_str(), + &sandbox_cwd, + base_dir.as_path(), + command_vec, + &cwd_clone, + env_map, + None, + ) + } }) .await; diff --git a/codex-rs/codex-api/src/endpoint/responses.rs b/codex-rs/codex-api/src/endpoint/responses.rs index d3a314d76..310f7e57b 100644 --- a/codex-rs/codex-api/src/endpoint/responses.rs +++ b/codex-rs/codex-api/src/endpoint/responses.rs @@ -17,6 +17,7 @@ use codex_protocol::protocol::SessionSource; use http::HeaderMap; use serde_json::Value; use std::sync::Arc; +use tracing::instrument; pub struct ResponsesClient { streaming: StreamingClient, @@ -57,6 +58,7 @@ impl ResponsesClient { self.stream(request.body, request.headers).await } + #[instrument(level = "trace", skip_all, err)] pub async fn stream_prompt( &self, model: &str, diff --git a/codex-rs/codex-client/Cargo.toml b/codex-rs/codex-client/Cargo.toml index d1ff23ef3..2eeb45693 100644 --- a/codex-rs/codex-client/Cargo.toml +++ b/codex-rs/codex-client/Cargo.toml @@ -10,6 +10,7 @@ bytes = { workspace = true } eventsource-stream = { workspace = true } futures = { workspace = true } http = { workspace = true } +opentelemetry = { workspace = true } rand = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } serde = { workspace = true, features = ["derive"] } @@ -17,6 +18,11 @@ serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt", "time", "sync"] } tracing = { workspace = true } +tracing-opentelemetry = { workspace = true } [lints] workspace = true + +[dev-dependencies] +opentelemetry_sdk = { workspace = true } +tracing-subscriber = { workspace = true } diff --git a/codex-rs/codex-client/src/default_client.rs b/codex-rs/codex-client/src/default_client.rs index 8a2584638..efb4d5aec 100644 --- a/codex-rs/codex-client/src/default_client.rs +++ b/codex-rs/codex-client/src/default_client.rs @@ -1,4 +1,6 @@ use http::Error as HttpError; +use opentelemetry::global; +use opentelemetry::propagation::Injector; use reqwest::IntoUrl; use reqwest::Method; use reqwest::Response; @@ -9,6 +11,8 @@ use serde::Serialize; use std::collections::HashMap; use std::fmt::Display; use std::time::Duration; +use tracing::Span; +use tracing_opentelemetry::OpenTelemetrySpanExt; #[derive(Clone, Debug)] pub struct CodexHttpClient { @@ -101,7 +105,9 @@ impl CodexRequestBuilder { } pub async fn send(self) -> Result { - match self.builder.send().await { + let headers = trace_headers(); + + match self.builder.headers(headers).send().await { Ok(response) => { let request_ids = Self::extract_request_ids(&response); tracing::debug!( @@ -141,3 +147,79 @@ impl CodexRequestBuilder { .collect() } } + +struct HeaderMapInjector<'a>(&'a mut HeaderMap); + +impl<'a> Injector for HeaderMapInjector<'a> { + fn set(&mut self, key: &str, value: String) { + if let (Ok(name), Ok(val)) = ( + HeaderName::from_bytes(key.as_bytes()), + HeaderValue::from_str(&value), + ) { + self.0.insert(name, val); + } + } +} + +fn trace_headers() -> HeaderMap { + let mut headers = HeaderMap::new(); + global::get_text_map_propagator(|prop| { + prop.inject_context( + &Span::current().context(), + &mut HeaderMapInjector(&mut headers), + ); + }); + headers +} + +#[cfg(test)] +mod tests { + use super::*; + use opentelemetry::propagation::Extractor; + use opentelemetry::propagation::TextMapPropagator; + use opentelemetry::trace::TraceContextExt; + use opentelemetry::trace::TracerProvider; + use opentelemetry_sdk::propagation::TraceContextPropagator; + use opentelemetry_sdk::trace::SdkTracerProvider; + use tracing::trace_span; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + #[test] + fn inject_trace_headers_uses_current_span_context() { + global::set_text_map_propagator(TraceContextPropagator::new()); + + let provider = SdkTracerProvider::builder().build(); + let tracer = provider.tracer("test-tracer"); + let subscriber = + tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer)); + let _guard = subscriber.set_default(); + + let span = trace_span!("client_request"); + let _entered = span.enter(); + let span_context = span.context().span().span_context().clone(); + + let headers = trace_headers(); + + let extractor = HeaderMapExtractor(&headers); + let extracted = TraceContextPropagator::new().extract(&extractor); + let extracted_span = extracted.span(); + let extracted_context = extracted_span.span_context(); + + assert!(extracted_context.is_valid()); + assert_eq!(extracted_context.trace_id(), span_context.trace_id()); + assert_eq!(extracted_context.span_id(), span_context.span_id()); + } + + struct HeaderMapExtractor<'a>(&'a HeaderMap); + + impl<'a> Extractor for HeaderMapExtractor<'a> { + fn get(&self, key: &str) -> Option<&str> { + self.0.get(key).and_then(|value| value.to_str().ok()) + } + + fn keys(&self) -> Vec<&str> { + self.0.keys().map(HeaderName::as_str).collect() + } + } +} diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index e78bc5019..a30636740 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -28,7 +28,7 @@ codex-execpolicy = { workspace = true } codex-file-search = { workspace = true } codex-git = { workspace = true } codex-keyring-store = { workspace = true } -codex-otel = { workspace = true, features = ["otel"] } +codex-otel = { workspace = true } codex-protocol = { workspace = true } codex-rmcp-client = { workspace = true } codex-utils-absolute-path = { workspace = true } @@ -133,6 +133,7 @@ pretty_assertions = { workspace = true } serial_test = { workspace = true } tempfile = { workspace = true } tokio-test = { workspace = true } +tracing-subscriber = { workspace = true } tracing-test = { workspace = true, features = ["no-env-filter"] } walkdir = { workspace = true } wiremock = { workspace = true } diff --git a/codex-rs/core/gpt_5_1_prompt.md b/codex-rs/core/gpt_5_1_prompt.md index 3201ffeb6..a4492c6ac 100644 --- a/codex-rs/core/gpt_5_1_prompt.md +++ b/codex-rs/core/gpt_5_1_prompt.md @@ -319,7 +319,7 @@ For casual greetings, acknowledgements, or other one-off conversational messages When using the shell, you must adhere to the following guidelines: - When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.) -- Read files in chunks with a max chunk size of 250 lines. Do not use python scripts to attempt to output larger chunks of a file. Command line output will be truncated after 10 kilobytes or 256 lines of output, regardless of the command used. +- Do not use python scripts to attempt to output larger chunks of a file. ## apply_patch diff --git a/codex-rs/core/gpt_5_2_prompt.md b/codex-rs/core/gpt_5_2_prompt.md index 99a3f49bb..cfbb22084 100644 --- a/codex-rs/core/gpt_5_2_prompt.md +++ b/codex-rs/core/gpt_5_2_prompt.md @@ -285,7 +285,7 @@ For casual greetings, acknowledgements, or other one-off conversational messages When using the shell, you must adhere to the following guidelines: - When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.) -- Do not use python scripts to attempt to output larger chunks of a file. Command line output will be truncated after 10 kilobytes, regardless of the command used. +- Do not use python scripts to attempt to output larger chunks of a file. - Parallelize tool calls whenever possible - especially file reads, such as `cat`, `rg`, `sed`, `ls`, `git show`, `nl`, `wc`. Use `multi_tool_use.parallel` to parallelize tool calls and only this. ## apply_patch diff --git a/codex-rs/core/prompt.md b/codex-rs/core/prompt.md index e4590c386..d8bebc371 100644 --- a/codex-rs/core/prompt.md +++ b/codex-rs/core/prompt.md @@ -297,7 +297,7 @@ For casual greetings, acknowledgements, or other one-off conversational messages When using the shell, you must adhere to the following guidelines: - When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.) -- Read files in chunks with a max chunk size of 250 lines. Do not use python scripts to attempt to output larger chunks of a file. Command line output will be truncated after 10 kilobytes or 256 lines of output, regardless of the command used. +- Do not use python scripts to attempt to output larger chunks of a file. ## `update_plan` diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index e1863c890..8b4448106 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -1114,6 +1114,18 @@ impl AuthManager { }) } + #[cfg(any(test, feature = "test-support"))] + /// Create an AuthManager with a specific CodexAuth and codex home, for testing only. + pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc { + let cached = CachedAuth { auth: Some(auth) }; + Arc::new(Self { + codex_home, + inner: RwLock::new(cached), + enable_codex_api_key_env: false, + auth_credentials_store_mode: AuthCredentialsStoreMode::File, + }) + } + /// Current cached auth (clone). May be `None` if not logged in or load failed. pub fn auth(&self) -> Option { self.inner.read().ok().and_then(|c| c.auth.clone()) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 9659b1090..8ab56019c 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -18,7 +18,7 @@ use codex_api::common::Reasoning; use codex_api::create_text_param_for_request; use codex_api::error::ApiError; use codex_app_server_protocol::AuthMode; -use codex_otel::otel_event_manager::OtelEventManager; +use codex_otel::otel_manager::OtelManager; use codex_protocol::ConversationId; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::models::ResponseItem; @@ -57,7 +57,7 @@ pub struct ModelClient { config: Arc, auth_manager: Option>, model_family: ModelFamily, - otel_event_manager: OtelEventManager, + otel_manager: OtelManager, provider: ModelProviderInfo, conversation_id: ConversationId, effort: Option, @@ -71,7 +71,7 @@ impl ModelClient { config: Arc, auth_manager: Option>, model_family: ModelFamily, - otel_event_manager: OtelEventManager, + otel_manager: OtelManager, provider: ModelProviderInfo, effort: Option, summary: ReasoningSummaryConfig, @@ -82,7 +82,7 @@ impl ModelClient { config, auth_manager, model_family, - otel_event_manager, + otel_manager, provider, conversation_id, effort, @@ -121,12 +121,12 @@ impl ModelClient { if self.config.show_raw_agent_reasoning { Ok(map_response_stream( api_stream.streaming_mode(), - self.otel_event_manager.clone(), + self.otel_manager.clone(), )) } else { Ok(map_response_stream( api_stream.aggregate(), - self.otel_event_manager.clone(), + self.otel_manager.clone(), )) } } @@ -195,7 +195,7 @@ impl ModelClient { warn!(path, "Streaming from fixture"); let stream = codex_api::stream_from_fixture(path, self.provider.stream_idle_timeout()) .map_err(map_api_error)?; - return Ok(map_response_stream(stream, self.otel_event_manager.clone())); + return Ok(map_response_stream(stream, self.otel_manager.clone())); } let auth_manager = self.auth_manager.clone(); @@ -269,7 +269,7 @@ impl ModelClient { match stream_result { Ok(stream) => { - return Ok(map_response_stream(stream, self.otel_event_manager.clone())); + return Ok(map_response_stream(stream, self.otel_manager.clone())); } Err(ApiError::Transport(TransportError::Http { status, .. })) if status == StatusCode::UNAUTHORIZED => @@ -286,8 +286,8 @@ impl ModelClient { self.provider.clone() } - pub fn get_otel_event_manager(&self) -> OtelEventManager { - self.otel_event_manager.clone() + pub fn get_otel_manager(&self) -> OtelManager { + self.otel_manager.clone() } pub fn get_session_source(&self) -> SessionSource { @@ -371,7 +371,7 @@ impl ModelClient { impl ModelClient { /// Builds request and SSE telemetry for streaming API calls (Chat/Responses). fn build_streaming_telemetry(&self) -> (Arc, Arc) { - let telemetry = Arc::new(ApiTelemetry::new(self.otel_event_manager.clone())); + let telemetry = Arc::new(ApiTelemetry::new(self.otel_manager.clone())); let request_telemetry: Arc = telemetry.clone(); let sse_telemetry: Arc = telemetry; (request_telemetry, sse_telemetry) @@ -379,7 +379,7 @@ impl ModelClient { /// Builds request telemetry for unary API calls (e.g., Compact endpoint). fn build_request_telemetry(&self) -> Arc { - let telemetry = Arc::new(ApiTelemetry::new(self.otel_event_manager.clone())); + let telemetry = Arc::new(ApiTelemetry::new(self.otel_manager.clone())); let request_telemetry: Arc = telemetry; request_telemetry } @@ -396,7 +396,7 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec(api_stream: S, otel_event_manager: OtelEventManager) -> ResponseStream +fn map_response_stream(api_stream: S, otel_manager: OtelManager) -> ResponseStream where S: futures::Stream> + Unpin @@ -404,7 +404,6 @@ where + 'static, { let (tx_event, rx_event) = mpsc::channel::>(1600); - let manager = otel_event_manager; tokio::spawn(async move { let mut logged_error = false; @@ -416,7 +415,7 @@ where token_usage, }) => { if let Some(usage) = &token_usage { - manager.sse_event_completed( + otel_manager.sse_event_completed( usage.input_tokens, usage.output_tokens, Some(usage.cached_input_tokens), @@ -443,7 +442,7 @@ where Err(err) => { let mapped = map_api_error(err); if !logged_error { - manager.see_event_completed_failed(&mapped); + otel_manager.see_event_completed_failed(&mapped); logged_error = true; } if tx_event.send(Err(mapped)).await.is_err() { @@ -497,12 +496,12 @@ fn map_unauthorized_status(status: StatusCode) -> CodexErr { } struct ApiTelemetry { - otel_event_manager: OtelEventManager, + otel_manager: OtelManager, } impl ApiTelemetry { - fn new(otel_event_manager: OtelEventManager) -> Self { - Self { otel_event_manager } + fn new(otel_manager: OtelManager) -> Self { + Self { otel_manager } } } @@ -515,7 +514,7 @@ impl RequestTelemetry for ApiTelemetry { duration: Duration, ) { let error_message = error.map(std::string::ToString::to_string); - self.otel_event_manager.record_api_request( + self.otel_manager.record_api_request( attempt, status.map(|s| s.as_u16()), error_message.as_deref(), @@ -533,6 +532,6 @@ impl SseTelemetry for ApiTelemetry { >, duration: Duration, ) { - self.otel_event_manager.log_sse_event(result, duration); + self.otel_manager.log_sse_event(result, duration); } } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 04603152b..bcfe8e811 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -61,9 +61,13 @@ use tokio::sync::Mutex; use tokio::sync::RwLock; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; +use tracing::Instrument; use tracing::debug; use tracing::error; +use tracing::field; use tracing::info; +use tracing::instrument; +use tracing::trace_span; use tracing::warn; use crate::ModelProviderInfo; @@ -73,6 +77,7 @@ use crate::client_common::Prompt; use crate::client_common::ResponseEvent; use crate::compact::collect_user_messages; use crate::config::Config; +use crate::config::GhostSnapshotConfig; use crate::config::types::ShellEnvironmentPolicy; use crate::context_manager::ContextManager; use crate::environment_context::EnvironmentContext; @@ -102,8 +107,7 @@ use crate::protocol::ReviewDecision; use crate::protocol::SandboxPolicy; use crate::protocol::SessionConfiguredEvent; use crate::protocol::SkillErrorInfo; -use crate::protocol::SkillInfo; -use crate::protocol::SkillLoadOutcomeInfo; +use crate::protocol::SkillMetadata as ProtocolSkillMetadata; use crate::protocol::StreamErrorEvent; use crate::protocol::Submission; use crate::protocol::TokenCountEvent; @@ -116,10 +120,11 @@ use crate::rollout::RolloutRecorderParams; use crate::rollout::map_session_init_error; use crate::shell; use crate::shell_snapshot::ShellSnapshot; +use crate::skills::SkillError; use crate::skills::SkillInjections; -use crate::skills::SkillLoadOutcome; +use crate::skills::SkillMetadata; +use crate::skills::SkillsManager; use crate::skills::build_skill_injections; -use crate::skills::load_skills; use crate::state::ActiveTurn; use crate::state::SessionServices; use crate::state::SessionState; @@ -141,7 +146,7 @@ use crate::user_notification::UserNotification; use crate::util::backoff; use codex_async_utils::OrCancelExt; use codex_execpolicy::Policy as ExecPolicy; -use codex_otel::otel_event_manager::OtelEventManager; +use codex_otel::otel_manager::OtelManager; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseInputItem; @@ -203,6 +208,7 @@ impl Codex { config: Config, auth_manager: Arc, models_manager: Arc, + skills_manager: Arc, conversation_history: InitialHistory, session_source: SessionSource, ) -> CodexResult { @@ -210,7 +216,7 @@ impl Codex { let (tx_event, rx_event) = async_channel::unbounded(); let loaded_skills = if config.features.enabled(Feature::Skills) { - Some(load_skills(&config)) + Some(skills_manager.skills_for_cwd(&config.cwd)) } else { None }; @@ -225,11 +231,9 @@ impl Codex { } } - let skills_outcome = loaded_skills.clone(); - let user_instructions = get_user_instructions( &config, - skills_outcome + loaded_skills .as_ref() .map(|outcome| outcome.skills.as_slice()), ) @@ -275,7 +279,7 @@ impl Codex { tx_event.clone(), conversation_history, session_source_clone, - skills_outcome.clone(), + skills_manager, ) .await .map_err(|e| { @@ -361,6 +365,7 @@ pub(crate) struct TurnContext { pub(crate) sandbox_policy: SandboxPolicy, pub(crate) shell_environment_policy: ShellEnvironmentPolicy, pub(crate) tools_config: ToolsConfig, + pub(crate) ghost_snapshot: GhostSnapshotConfig, pub(crate) final_output_json_schema: Option, pub(crate) codex_linux_sandbox_exe: Option, pub(crate) tool_call_gate: Arc, @@ -479,7 +484,7 @@ impl Session { #[allow(clippy::too_many_arguments)] fn make_turn_context( auth_manager: Option>, - otel_event_manager: &OtelEventManager, + otel_manager: &OtelManager, provider: ModelProviderInfo, session_configuration: &SessionConfiguration, per_turn_config: Config, @@ -487,7 +492,7 @@ impl Session { conversation_id: ConversationId, sub_id: String, ) -> TurnContext { - let otel_event_manager = otel_event_manager.clone().with_model( + let otel_manager = otel_manager.clone().with_model( session_configuration.model.as_str(), model_family.get_model_slug(), ); @@ -497,7 +502,7 @@ impl Session { per_turn_config.clone(), auth_manager, model_family.clone(), - otel_event_manager, + otel_manager, provider, session_configuration.model_reasoning_effort, session_configuration.model_reasoning_summary, @@ -522,6 +527,7 @@ impl Session { sandbox_policy: session_configuration.sandbox_policy.clone(), shell_environment_policy: per_turn_config.shell_environment_policy.clone(), tools_config, + ghost_snapshot: per_turn_config.ghost_snapshot.clone(), final_output_json_schema: None, codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), tool_call_gate: Arc::new(ReadinessFlag::new()), @@ -542,7 +548,7 @@ impl Session { tx_event: Sender, initial_history: InitialHistory, session_source: SessionSource, - skills: Option, + skills_manager: Arc, ) -> anyhow::Result> { debug!( "Configuring session: model={}; provider={:?}", @@ -616,7 +622,7 @@ impl Session { maybe_push_chat_wire_api_deprecation(&config, &mut post_session_configured_events); // todo(aibrahim): why are we passing model here while it can change? - let otel_event_manager = OtelEventManager::new( + let otel_manager = OtelManager::new( conversation_id, session_configuration.model.as_str(), session_configuration.model.as_str(), @@ -625,9 +631,10 @@ impl Session { auth_manager.auth().map(|a| a.mode), config.otel.log_user_prompt, terminal::user_agent(), + session_configuration.session_source.clone(), ); - otel_event_manager.conversation_starts( + otel_manager.conversation_starts( config.model_provider.name.as_str(), config.model_reasoning_effort, config.model_reasoning_summary, @@ -658,10 +665,10 @@ impl Session { user_shell: Arc::new(default_shell), show_raw_agent_reasoning: config.show_raw_agent_reasoning, auth_manager: Arc::clone(&auth_manager), - otel_event_manager, + otel_manager, models_manager: Arc::clone(&models_manager), tool_approvals: Mutex::new(ApprovalStore::default()), - skills: skills.clone(), + skills_manager, }; let sess = Arc::new(Session { @@ -677,8 +684,6 @@ impl Session { // Dispatch the SessionConfiguredEvent first and then report any errors. // If resuming, include converted initial messages in the payload so UIs can render them immediately. let initial_messages = initial_history.get_event_msgs(); - let skill_load_outcome = skill_load_outcome_for_client(skills.as_ref()); - let events = std::iter::once(Event { id: INITIAL_SUBMIT_ID.to_owned(), msg: EventMsg::SessionConfigured(SessionConfiguredEvent { @@ -692,7 +697,6 @@ impl Session { history_log_id, history_entry_count, initial_messages, - skill_load_outcome, rollout_path, }), }) @@ -700,6 +704,14 @@ impl Session { for event in events { sess.send_event_raw(event).await; } + + // Construct sandbox_state before initialize() so it can be sent to each + // MCP server immediately after it becomes ready (avoiding blocking). + let sandbox_state = SandboxState { + sandbox_policy: session_configuration.sandbox_policy.clone(), + codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), + sandbox_cwd: session_configuration.cwd.clone(), + }; sess.services .mcp_connection_manager .write() @@ -710,25 +722,10 @@ impl Session { auth_statuses.clone(), tx_event.clone(), sess.services.mcp_startup_cancellation_token.clone(), + sandbox_state, ) .await; - let sandbox_state = SandboxState { - sandbox_policy: session_configuration.sandbox_policy.clone(), - codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), - sandbox_cwd: session_configuration.cwd.clone(), - }; - if let Err(e) = sess - .services - .mcp_connection_manager - .read() - .await - .notify_sandbox_state_change(&sandbox_state) - .await - { - tracing::error!("Failed to notify sandbox state change: {e}"); - } - // record_initial_history can emit events. We record only after the SessionConfiguredEvent is emitted. sess.record_initial_history(initial_history).await; @@ -794,15 +791,15 @@ impl Session { "resuming session with different model: previous={prev}, current={curr}" ); self.send_event( - &turn_context, - EventMsg::Warning(WarningEvent { - message: format!( - "This session was recorded with model `{prev}` but is resuming with `{curr}`. \ + &turn_context, + EventMsg::Warning(WarningEvent { + message: format!( + "This session was recorded with model `{prev}` but is resuming with `{curr}`. \ Consider switching back to `{prev}` as it may affect Codex performance." - ), - }), - ) - .await; + ), + }), + ) + .await; } } @@ -840,14 +837,34 @@ impl Session { sub_id: String, updates: SessionSettingsUpdate, ) -> Arc { - let session_configuration = { + let (session_configuration, sandbox_policy_changed) = { let mut state = self.state.lock().await; let session_configuration = state.session_configuration.clone().apply(&updates); + let sandbox_policy_changed = + state.session_configuration.sandbox_policy != session_configuration.sandbox_policy; state.session_configuration = session_configuration.clone(); - session_configuration + (session_configuration, sandbox_policy_changed) }; - let per_turn_config = Self::build_per_turn_config(&session_configuration); + + if sandbox_policy_changed { + let sandbox_state = SandboxState { + sandbox_policy: per_turn_config.sandbox_policy.clone(), + codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), + sandbox_cwd: per_turn_config.cwd.clone(), + }; + if let Err(e) = self + .services + .mcp_connection_manager + .read() + .await + .notify_sandbox_state_change(&sandbox_state) + .await + { + warn!("Failed to notify sandbox state change to MCP servers: {e:#}"); + } + } + let model_family = self .services .models_manager @@ -855,7 +872,7 @@ impl Session { .await; let mut turn_context: TurnContext = Self::make_turn_context( Some(Arc::clone(&self.services.auth_manager)), - &self.services.otel_event_manager, + &self.services.otel_manager, session_configuration.provider.clone(), &session_configuration, per_turn_config, @@ -1567,6 +1584,9 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv Op::ListCustomPrompts => { handlers::list_custom_prompts(&sess, sub.id.clone()).await; } + Op::ListSkills { cwds } => { + handlers::list_skills(&sess, sub.id.clone(), cwds).await; + } Op::Undo => { handlers::undo(&sess, sub.id.clone()).await; } @@ -1611,6 +1631,7 @@ mod handlers { use crate::codex::spawn_review_thread; use crate::config::Config; + use crate::features::Feature; use crate::mcp::auth::compute_auth_statuses; use crate::mcp::collect_mcp_snapshot_from_manager; use crate::review_prompts::resolve_review_request; @@ -1624,9 +1645,11 @@ mod handlers { use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ListCustomPromptsResponseEvent; + use codex_protocol::protocol::ListSkillsResponseEvent; use codex_protocol::protocol::Op; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::ReviewRequest; + use codex_protocol::protocol::SkillsListEntry; use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::WarningEvent; @@ -1634,6 +1657,7 @@ mod handlers { use codex_rmcp_client::ElicitationAction; use codex_rmcp_client::ElicitationResponse; use mcp_types::RequestId; + use std::path::PathBuf; use std::sync::Arc; use tracing::info; use tracing::warn; @@ -1681,7 +1705,7 @@ mod handlers { let current_context = sess.new_turn_with_sub_id(sub_id, updates).await; current_context .client - .get_otel_event_manager() + .get_otel_manager() .user_prompt(&items); // Attempt to inject input into current task @@ -1861,6 +1885,43 @@ mod handlers { sess.send_event_raw(event).await; } + pub async fn list_skills(sess: &Session, sub_id: String, cwds: Vec) { + let cwds = if cwds.is_empty() { + let state = sess.state.lock().await; + vec![state.session_configuration.cwd.clone()] + } else { + cwds + }; + let skills = if sess.enabled(Feature::Skills) { + let skills_manager = &sess.services.skills_manager; + cwds.into_iter() + .map(|cwd| { + let outcome = skills_manager.skills_for_cwd(&cwd); + let errors = super::errors_to_info(&outcome.errors); + let skills = super::skills_to_info(&outcome.skills); + SkillsListEntry { + cwd, + skills, + errors, + } + }) + .collect() + } else { + cwds.into_iter() + .map(|cwd| SkillsListEntry { + cwd, + skills: Vec::new(), + errors: Vec::new(), + }) + .collect() + }; + let event = Event { + id: sub_id, + msg: EventMsg::ListSkillsResponse(ListSkillsResponseEvent { skills }), + }; + sess.send_event_raw(event).await; + } + pub async fn undo(sess: &Arc, sub_id: String) { let turn_context = sess .new_turn_with_sub_id(sub_id, SessionSettingsUpdate::default()) @@ -1990,20 +2051,17 @@ async fn spawn_review_thread( per_turn_config.model_reasoning_summary = ReasoningSummaryConfig::Detailed; per_turn_config.features = review_features.clone(); - let otel_event_manager = parent_turn_context - .client - .get_otel_event_manager() - .with_model( - config.review_model.as_str(), - review_model_family.slug.as_str(), - ); + let otel_manager = parent_turn_context.client.get_otel_manager().with_model( + config.review_model.as_str(), + review_model_family.slug.as_str(), + ); let per_turn_config = Arc::new(per_turn_config); let client = ModelClient::new( per_turn_config.clone(), auth_manager, model_family.clone(), - otel_event_manager, + otel_manager, provider, per_turn_config.model_reasoning_effort, per_turn_config.model_reasoning_summary, @@ -2015,6 +2073,7 @@ async fn spawn_review_thread( sub_id: sub_id.to_string(), client, tools_config, + ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), developer_instructions: None, user_instructions: None, base_instructions: Some(base_instructions.clone()), @@ -2046,28 +2105,26 @@ async fn spawn_review_thread( .await; } -fn skill_load_outcome_for_client( - outcome: Option<&SkillLoadOutcome>, -) -> Option { - outcome.map(|outcome| SkillLoadOutcomeInfo { - skills: outcome - .skills - .iter() - .map(|skill| SkillInfo { - name: skill.name.clone(), - description: skill.description.clone(), - path: skill.path.clone(), - }) - .collect(), - errors: outcome - .errors - .iter() - .map(|err| SkillErrorInfo { - path: err.path.clone(), - message: err.message.clone(), - }) - .collect(), - }) +fn skills_to_info(skills: &[SkillMetadata]) -> Vec { + skills + .iter() + .map(|skill| ProtocolSkillMetadata { + name: skill.name.clone(), + description: skill.description.clone(), + path: skill.path.clone(), + scope: skill.scope, + }) + .collect() +} + +fn errors_to_info(errors: &[SkillError]) -> Vec { + errors + .iter() + .map(|err| SkillErrorInfo { + path: err.path.clone(), + message: err.message.clone(), + }) + .collect() } /// Takes a user message as input and runs a loop where, at each turn, the model @@ -2093,15 +2150,35 @@ pub(crate) async fn run_task( if input.is_empty() { return None; } + + let auto_compact_limit = turn_context + .client + .get_model_family() + .auto_compact_token_limit() + .unwrap_or(i64::MAX); + let total_usage_tokens = sess.get_total_token_usage().await; + if total_usage_tokens >= auto_compact_limit { + run_auto_compact(&sess, &turn_context).await; + } let event = EventMsg::TaskStarted(TaskStartedEvent { model_context_window: turn_context.client.get_model_context_window(), }); sess.send_event(&turn_context, event).await; + let skills_outcome = if sess.enabled(Feature::Skills) { + Some( + sess.services + .skills_manager + .skills_for_cwd(&turn_context.cwd), + ) + } else { + None + }; + let SkillInjections { items: skill_items, warnings: skill_warnings, - } = build_skill_injections(&input, sess.services.skills.as_ref()).await; + } = build_skill_injections(&input, skills_outcome.as_ref()).await; for message in skill_warnings { sess.send_event(&turn_context, EventMsg::Warning(WarningEvent { message })) @@ -2165,25 +2242,12 @@ pub(crate) async fn run_task( needs_follow_up, last_agent_message: turn_last_agent_message, } = turn_output; - let limit = turn_context - .client - .get_model_family() - .auto_compact_token_limit() - .unwrap_or(i64::MAX); let total_usage_tokens = sess.get_total_token_usage().await; - let token_limit_reached = total_usage_tokens >= limit; + let token_limit_reached = total_usage_tokens >= auto_compact_limit; // as long as compaction works well in getting us way below the token limit, we shouldn't worry about being in an infinite loop. - if token_limit_reached { - if should_use_remote_compact_task( - sess.as_ref(), - &turn_context.client.get_provider(), - ) { - run_inline_remote_auto_compact_task(sess.clone(), turn_context.clone()) - .await; - } else { - run_inline_auto_compact_task(sess.clone(), turn_context.clone()).await; - } + if token_limit_reached && needs_follow_up { + run_auto_compact(&sess, &turn_context).await; continue; } @@ -2225,6 +2289,22 @@ pub(crate) async fn run_task( last_agent_message } +async fn run_auto_compact(sess: &Arc, turn_context: &Arc) { + if should_use_remote_compact_task(sess.as_ref(), &turn_context.client.get_provider()) { + run_inline_remote_auto_compact_task(Arc::clone(sess), Arc::clone(turn_context)).await; + } else { + run_inline_auto_compact_task(Arc::clone(sess), Arc::clone(turn_context)).await; + } +} + +#[instrument(level = "trace", + skip_all, + fields( + turn_id = %turn_context.sub_id, + model = %turn_context.client.get_model(), + cwd = %turn_context.cwd.display() + ) +)] async fn run_turn( sess: Arc, turn_context: Arc, @@ -2357,6 +2437,13 @@ async fn drain_in_flight( } #[allow(clippy::too_many_arguments)] +#[instrument(level = "trace", + skip_all, + fields( + turn_id = %turn_context.sub_id, + model = %turn_context.client.get_model() + ) +)] async fn try_run_turn( router: Arc, sess: Arc, @@ -2379,6 +2466,7 @@ async fn try_run_turn( .client .clone() .stream(prompt) + .instrument(trace_span!("stream_request")) .or_cancel(&cancellation_token) .await??; @@ -2393,8 +2481,23 @@ async fn try_run_turn( let mut needs_follow_up = false; let mut last_agent_message: Option = None; let mut active_item: Option = None; + let mut should_emit_turn_diff = false; + let receiving_span = trace_span!("receiving_stream"); let outcome: CodexResult = loop { - let event = match stream.next().or_cancel(&cancellation_token).await { + let handle_responses = trace_span!( + parent: &receiving_span, + "handle_responses", + otel.name = field::Empty, + tool_name = field::Empty, + from = field::Empty, + ); + + let event = match stream + .next() + .instrument(trace_span!(parent: &handle_responses, "receiving")) + .or_cancel(&cancellation_token) + .await + { Ok(event) => event, Err(codex_async_utils::CancelErr::Cancelled) => break Err(CodexErr::TurnAborted), }; @@ -2409,6 +2512,10 @@ async fn try_run_turn( } }; + sess.services + .otel_manager + .record_responses(&handle_responses, &event); + match event { ResponseEvent::Created => {} ResponseEvent::OutputItemDone(item) => { @@ -2420,8 +2527,9 @@ async fn try_run_turn( cancellation_token: cancellation_token.child_token(), }; - let output_result = - handle_output_item_done(&mut ctx, item, previously_active_item).await?; + let output_result = handle_output_item_done(&mut ctx, item, previously_active_item) + .instrument(handle_responses) + .await?; if let Some(tool_future) = output_result.tool_future { in_flight.push_back(tool_future); } @@ -2449,14 +2557,7 @@ async fn try_run_turn( } => { sess.update_token_usage_info(&turn_context, token_usage.as_ref()) .await; - let unified_diff = { - let mut tracker = turn_diff_tracker.lock().await; - tracker.get_unified_diff() - }; - if let Ok(Some(unified_diff)) = unified_diff { - let msg = EventMsg::TurnDiff(TurnDiffEvent { unified_diff }); - sess.send_event(&turn_context, msg).await; - } + should_emit_turn_diff = true; break Ok(TurnRunResult { needs_follow_up, @@ -2530,7 +2631,18 @@ async fn try_run_turn( } }; - drain_in_flight(&mut in_flight, sess, turn_context).await?; + drain_in_flight(&mut in_flight, sess.clone(), turn_context.clone()).await?; + + if should_emit_turn_diff { + let unified_diff = { + let mut tracker = turn_diff_tracker.lock().await; + tracker.get_unified_diff() + }; + if let Ok(Some(unified_diff)) = unified_diff { + let msg = EventMsg::TurnDiff(TurnDiffEvent { unified_diff }); + sess.clone().send_event(&turn_context, msg).await; + } + } outcome } @@ -2900,12 +3012,13 @@ mod tests { }) } - fn otel_event_manager( + fn otel_manager( conversation_id: ConversationId, config: &Config, model_family: &ModelFamily, - ) -> OtelEventManager { - OtelEventManager::new( + session_source: SessionSource, + ) -> OtelManager { + OtelManager::new( conversation_id, ModelsManager::get_model_offline(config.model.as_deref()).as_str(), model_family.slug.as_str(), @@ -2914,6 +3027,7 @@ mod tests { Some(AuthMode::ChatGPT), false, "test".to_string(), + session_source, ) } @@ -2953,10 +3067,15 @@ mod tests { session_configuration.model.as_str(), &per_turn_config, ); - let otel_event_manager = - otel_event_manager(conversation_id, config.as_ref(), &model_family); + let otel_manager = otel_manager( + conversation_id, + config.as_ref(), + &model_family, + session_configuration.session_source.clone(), + ); let state = SessionState::new(session_configuration.clone()); + let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone())); let services = SessionServices { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), @@ -2967,15 +3086,15 @@ mod tests { user_shell: Arc::new(default_user_shell()), show_raw_agent_reasoning: config.show_raw_agent_reasoning, auth_manager: auth_manager.clone(), - otel_event_manager: otel_event_manager.clone(), + otel_manager: otel_manager.clone(), models_manager, tool_approvals: Mutex::new(ApprovalStore::default()), - skills: None, + skills_manager, }; let turn_context = Session::make_turn_context( Some(Arc::clone(&auth_manager)), - &otel_event_manager, + &otel_manager, session_configuration.provider.clone(), &session_configuration, per_turn_config, @@ -3039,10 +3158,15 @@ mod tests { session_configuration.model.as_str(), &per_turn_config, ); - let otel_event_manager = - otel_event_manager(conversation_id, config.as_ref(), &model_family); + let otel_manager = otel_manager( + conversation_id, + config.as_ref(), + &model_family, + session_configuration.session_source.clone(), + ); let state = SessionState::new(session_configuration.clone()); + let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone())); let services = SessionServices { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), @@ -3053,15 +3177,15 @@ mod tests { user_shell: Arc::new(default_user_shell()), show_raw_agent_reasoning: config.show_raw_agent_reasoning, auth_manager: Arc::clone(&auth_manager), - otel_event_manager: otel_event_manager.clone(), + otel_manager: otel_manager.clone(), models_manager, tool_approvals: Mutex::new(ApprovalStore::default()), - skills: None, + skills_manager, }; let turn_context = Arc::new(Session::make_turn_context( Some(Arc::clone(&auth_manager)), - &otel_event_manager, + &otel_manager, session_configuration.provider.clone(), &session_configuration, per_turn_config, diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 75b29edde..c7aebbaf9 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -49,6 +49,7 @@ pub(crate) async fn run_codex_conversation_interactive( config, auth_manager, models_manager, + Arc::clone(&parent_session.services.skills_manager), initial_history.unwrap_or(InitialHistory::New), SessionSource::SubAgent(SubAgentSource::Review), ) diff --git a/codex-rs/core/src/command_safety/powershell_parser.ps1 b/codex-rs/core/src/command_safety/powershell_parser.ps1 new file mode 100644 index 000000000..af71cb7f3 --- /dev/null +++ b/codex-rs/core/src/command_safety/powershell_parser.ps1 @@ -0,0 +1,201 @@ +$ErrorActionPreference = 'Stop' + +$payload = $env:CODEX_POWERSHELL_PAYLOAD +if ([string]::IsNullOrEmpty($payload)) { + Write-Output '{"status":"parse_failed"}' + exit 0 +} + +try { + $source = + [System.Text.Encoding]::Unicode.GetString( + [System.Convert]::FromBase64String($payload) + ) +} catch { + Write-Output '{"status":"parse_failed"}' + exit 0 +} + +$tokens = $null +$errors = $null + +$ast = $null +try { + $ast = [System.Management.Automation.Language.Parser]::ParseInput( + $source, + [ref]$tokens, + [ref]$errors + ) +} catch { + Write-Output '{"status":"parse_failed"}' + exit 0 +} + +if ($errors.Count -gt 0) { + Write-Output '{"status":"parse_errors"}' + exit 0 +} + +function Convert-CommandElement { + param($element) + + if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) { + return @($element.Value) + } + + if ($element -is [System.Management.Automation.Language.ExpandableStringExpressionAst]) { + if ($element.NestedExpressions.Count -gt 0) { + return $null + } + return @($element.Value) + } + + if ($element -is [System.Management.Automation.Language.ConstantExpressionAst]) { + return @($element.Value.ToString()) + } + + if ($element -is [System.Management.Automation.Language.CommandParameterAst]) { + if ($element.Argument -eq $null) { + return @('-' + $element.ParameterName) + } + + if ($element.Argument -is [System.Management.Automation.Language.StringConstantExpressionAst]) { + return @('-' + $element.ParameterName, $element.Argument.Value) + } + + if ($element.Argument -is [System.Management.Automation.Language.ConstantExpressionAst]) { + return @('-' + $element.ParameterName, $element.Argument.Value.ToString()) + } + + return $null + } + + return $null +} + +function Convert-PipelineElement { + param($element) + + if ($element -is [System.Management.Automation.Language.CommandAst]) { + if ($element.Redirections.Count -gt 0) { + return $null + } + + if ( + $element.InvocationOperator -ne $null -and + $element.InvocationOperator -ne [System.Management.Automation.Language.TokenKind]::Unknown + ) { + return $null + } + + $parts = @() + foreach ($commandElement in $element.CommandElements) { + $converted = Convert-CommandElement $commandElement + if ($converted -eq $null) { + return $null + } + $parts += $converted + } + return $parts + } + + if ($element -is [System.Management.Automation.Language.CommandExpressionAst]) { + if ($element.Redirections.Count -gt 0) { + return $null + } + + if ($element.Expression -is [System.Management.Automation.Language.ParenExpressionAst]) { + $innerPipeline = $element.Expression.Pipeline + if ($innerPipeline -and $innerPipeline.PipelineElements.Count -eq 1) { + return Convert-PipelineElement $innerPipeline.PipelineElements[0] + } + } + + return $null + } + + return $null +} + +function Add-CommandsFromPipelineAst { + param($pipeline, $commands) + + if ($pipeline.PipelineElements.Count -eq 0) { + return $false + } + + foreach ($element in $pipeline.PipelineElements) { + $words = Convert-PipelineElement $element + if ($words -eq $null -or $words.Count -eq 0) { + return $false + } + $null = $commands.Add($words) + } + + return $true +} + +function Add-CommandsFromPipelineChain { + param($chain, $commands) + + if (-not (Add-CommandsFromPipelineBase $chain.LhsPipelineChain $commands)) { + return $false + } + + if (-not (Add-CommandsFromPipelineAst $chain.RhsPipeline $commands)) { + return $false + } + + return $true +} + +function Add-CommandsFromPipelineBase { + param($pipeline, $commands) + + if ($pipeline -is [System.Management.Automation.Language.PipelineAst]) { + return Add-CommandsFromPipelineAst $pipeline $commands + } + + if ($pipeline -is [System.Management.Automation.Language.PipelineChainAst]) { + return Add-CommandsFromPipelineChain $pipeline $commands + } + + return $false +} + +$commands = [System.Collections.ArrayList]::new() + +foreach ($statement in $ast.EndBlock.Statements) { + if (-not (Add-CommandsFromPipelineBase $statement $commands)) { + $commands = $null + break + } +} + +if ($commands -ne $null) { + $normalized = [System.Collections.ArrayList]::new() + foreach ($cmd in $commands) { + if ($cmd -is [string]) { + $null = $normalized.Add(@($cmd)) + continue + } + + if ($cmd -is [System.Array] -or $cmd -is [System.Collections.IEnumerable]) { + $null = $normalized.Add(@($cmd)) + continue + } + + $normalized = $null + break + } + + $commands = $normalized +} + +$result = if ($commands -eq $null) { + @{ status = 'unsupported' } +} else { + @{ status = 'ok'; commands = $commands } +} + +,$result | ConvertTo-Json -Depth 3 diff --git a/codex-rs/core/src/command_safety/windows_safe_commands.rs b/codex-rs/core/src/command_safety/windows_safe_commands.rs index a1d3b297f..ac479a4d2 100644 --- a/codex-rs/core/src/command_safety/windows_safe_commands.rs +++ b/codex-rs/core/src/command_safety/windows_safe_commands.rs @@ -1,30 +1,38 @@ -use shlex::split as shlex_split; +use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use serde::Deserialize; use std::path::Path; +use std::process::Command; +use std::sync::LazyLock; + +const POWERSHELL_PARSER_SCRIPT: &str = include_str!("powershell_parser.ps1"); /// On Windows, we conservatively allow only clearly read-only PowerShell invocations /// that match a small safelist. Anything else (including direct CMD commands) is unsafe. pub fn is_safe_command_windows(command: &[String]) -> bool { if let Some(commands) = try_parse_powershell_command_sequence(command) { - return commands + commands .iter() - .all(|cmd| is_safe_powershell_command(cmd.as_slice())); + .all(|cmd| is_safe_powershell_command(cmd.as_slice())) + } else { + // Only PowerShell invocations are allowed on Windows for now; anything else is unsafe. + false } - // Only PowerShell invocations are allowed on Windows for now; anything else is unsafe. - false } /// Returns each command sequence if the invocation starts with a PowerShell binary. /// For example, the tokens from `pwsh Get-ChildItem | Measure-Object` become two sequences. fn try_parse_powershell_command_sequence(command: &[String]) -> Option>> { let (exe, rest) = command.split_first()?; - if !is_powershell_executable(exe) { - return None; + if is_powershell_executable(exe) { + parse_powershell_invocation(exe, rest) + } else { + None } - parse_powershell_invocation(rest) } /// Parses a PowerShell invocation into discrete command vectors, rejecting unsafe patterns. -fn parse_powershell_invocation(args: &[String]) -> Option>> { +fn parse_powershell_invocation(executable: &str, args: &[String]) -> Option>> { if args.is_empty() { // Examples rejected here: "pwsh" and "powershell.exe" with no additional arguments. return None; @@ -42,7 +50,7 @@ fn parse_powershell_invocation(args: &[String]) -> Option>> { // Examples rejected here: "pwsh -Command foo bar" and "powershell -c ls extra". return None; } - return parse_powershell_script(script); + return parse_powershell_script(executable, script); } _ if lower.starts_with("-command:") || lower.starts_with("/command:") => { if idx + 1 != args.len() { @@ -51,7 +59,7 @@ fn parse_powershell_invocation(args: &[String]) -> Option>> { return None; } let script = arg.split_once(':')?.1; - return parse_powershell_script(script); + return parse_powershell_script(executable, script); } // Benign, no-arg flags we tolerate. @@ -77,7 +85,8 @@ fn parse_powershell_invocation(args: &[String]) -> Option>> { // This happens if powershell is invoked without -Command, e.g. // ["pwsh", "-NoLogo", "git", "-c", "core.pager=cat", "status"] _ => { - return split_into_commands(args[idx..].to_vec()); + let script = join_arguments_as_script(&args[idx..]); + return parse_powershell_script(executable, &script); } } } @@ -88,46 +97,14 @@ fn parse_powershell_invocation(args: &[String]) -> Option>> { /// Tokenizes an inline PowerShell script and delegates to the command splitter. /// Examples of when this is called: pwsh.exe -Command '