From b8e4da23d06de2b444dba805506ed85b2c4bd841 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 14 Jun 2026 22:37:05 +0200 Subject: [PATCH] ci(ci): refactored CI setup and test-state isolation for coding-agent workflows - Added a setup-system-deps action with preloaded-runner guards and apt fallbacks. - Updated CI workflows to download Linux x64 native artifacts and gate on native job success. - Renamed coding-agent fast mode to singleton in scripts and test partitioning logic. - Added settings test-state begin/restore helpers with recursive cleanup in affected tests. --- .github/actions/build-native/action.yml | 6 + .github/actions/setup-system-deps/action.yml | 26 +++ .github/workflows/ci.yml | 154 ++++++++++-------- package.json | 2 +- packages/ai/src/auth-storage.ts | 2 +- packages/ai/src/provider-details.ts | 2 +- .../test/autocomplete-max-visible.test.ts | 14 +- .../coding-agent/test/config-spacing.test.ts | 15 +- .../test/helpers/settings-test-state.ts | 73 +++++++++ .../test/modes/theme/shimmer.test.ts | 87 +--------- .../test/settings-manager.test.ts | 16 +- .../test/settings-reload-cwd.test.ts | 18 +- .../test/status-line-settings-cache.test.ts | 22 +-- .../test/streaming-reveal.test.ts | 15 -- .../test/task/task-progress-render.test.ts | 9 +- scripts/ci-test-ts.ts | 96 ++++++++--- 16 files changed, 325 insertions(+), 232 deletions(-) create mode 100644 .github/actions/setup-system-deps/action.yml create mode 100644 packages/coding-agent/test/helpers/settings-test-state.ts diff --git a/.github/actions/build-native/action.yml b/.github/actions/build-native/action.yml index f22198d3f..7ed5f7327 100644 --- a/.github/actions/build-native/action.yml +++ b/.github/actions/build-native/action.yml @@ -36,6 +36,12 @@ runs: toolchain: nightly-2026-04-29 components: ${{ inputs.rust_checks == 'true' && 'clippy, rustfmt' || '' }} targets: ${{ inputs.target }} + - name: Install Linux build prerequisites + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y build-essential - name: Prepend rustup toolchain bin to PATH shell: bash run: | diff --git a/.github/actions/setup-system-deps/action.yml b/.github/actions/setup-system-deps/action.yml new file mode 100644 index 000000000..cdf1e3697 --- /dev/null +++ b/.github/actions/setup-system-deps/action.yml @@ -0,0 +1,26 @@ +name: Setup system deps +description: >- + Install the canvas/native runtime deps CI needs (cairo/pango stack, fd, + ripgrep, imagemagick). No-op on the preloaded omp-kata runner image, which + already ships them; self-heals on a stock runner by installing via apt. + +runs: + using: composite + steps: + - name: Install system deps (skip when preloaded) + shell: bash + run: | + # The preloaded omp-kata runner image bakes these in. Detect that + # and skip the apt round-trip; otherwise install the exact same set so + # stock runners (and any future host) still work. + if command -v fd >/dev/null 2>&1 \ + && command -v rg >/dev/null 2>&1 \ + && command -v magick >/dev/null 2>&1 \ + && pkg-config --exists cairo pango 2>/dev/null; then + echo "System deps already present (preloaded runner image); skipping apt." + exit 0 + fi + sudo apt-get update + sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick + sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd + sudo ln -sf /usr/bin/convert /usr/local/bin/magick diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49c8237c6..c201933fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,10 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true +permissions: + contents: read + actions: read + jobs: # scripts/release.ts pushes the version-bump commit and its `v*` tag # atomically (`git push --atomic origin refs/heads/main:refs/heads/main @@ -255,6 +259,8 @@ jobs: test_workspace: name: Test TS workspace fast runs-on: omp-kata + needs: [native_linux_x64, native_artifact_lookup] + if: ${{ !cancelled() && needs.native_linux_x64.result != 'failure' }} timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -267,14 +273,31 @@ jobs: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - run: bun install --frozen-lockfile - - name: Test pure workspaces and repo scripts (TS) - # Keep this job native-free and parallel: it deliberately excludes - # coding-agent plus native/TUI/browser-ish/integration packages. + - name: Resolve Linux x64 native artifact run + id: source + shell: bash + run: | + if [ "${{ needs.native_linux_x64.result }}" = "success" ]; then + echo "artifact-run-id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" + else + echo "artifact-run-id=${{ needs.native_artifact_lookup.outputs.linux-x64-run-id }}" >> "$GITHUB_OUTPUT" + fi + - name: Download native addons + uses: actions/download-artifact@v4 + with: + pattern: pi-natives-linux-x64-*-h${{ needs.native_artifact_lookup.outputs.source-hash }} + path: packages/natives/native + merge-multiple: true + run-id: ${{ steps.source.outputs.artifact-run-id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Test workspace packages and repo scripts (TS) run: bun run ci:test:ts:workspace - test_coding_agent_fast: - name: Test coding-agent fast (TS) + test_coding_agent_singleton: + name: Test coding-agent singleton/global-state (TS) runs-on: omp-kata + needs: [native_linux_x64, native_artifact_lookup] + if: ${{ !cancelled() && needs.native_linux_x64.result != 'failure' }} timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -287,10 +310,27 @@ jobs: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - run: bun install --frozen-lockfile - - name: Test coding-agent fast unit bucket - # The fast bucket is the conservative native-free complement. It has - # an explicit worker cap forever; never use package-wide fan-out here. - run: bun run ci:test:coding-agent:fast + - name: Resolve Linux x64 native artifact run + id: source + shell: bash + run: | + if [ "${{ needs.native_linux_x64.result }}" = "success" ]; then + echo "artifact-run-id=${{ github.run_id }}" >> "$GITHUB_OUTPUT" + else + echo "artifact-run-id=${{ needs.native_artifact_lookup.outputs.linux-x64-run-id }}" >> "$GITHUB_OUTPUT" + fi + - name: Download native addons + uses: actions/download-artifact@v4 + with: + pattern: pi-natives-linux-x64-*-h${{ needs.native_artifact_lookup.outputs.source-hash }} + path: packages/natives/native + merge-multiple: true + run-id: ${{ steps.source.outputs.artifact-run-id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Test coding-agent singleton/global-state bucket + # Keep global Settings/env/fake-timer tests serial; native addon + # artifacts are still available like every other coding-agent bucket. + run: bun run ci:test:coding-agent:singleton test_ts_native: name: Test TS native/integration packages @@ -308,12 +348,7 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick + - uses: ./.github/actions/setup-system-deps - run: bun install --frozen-lockfile - name: Resolve Linux x64 native artifact run id: source @@ -336,7 +371,7 @@ jobs: run: bun run ci:test:ts:native test_coding_agent_ui: - name: Test coding-agent UI/stateful (TS) + name: Test coding-agent UI/TUI (TS) runs-on: omp-kata needs: [native_linux_x64, native_artifact_lookup] if: ${{ !cancelled() && needs.native_linux_x64.result != 'failure' }} @@ -351,12 +386,7 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick + - uses: ./.github/actions/setup-system-deps - run: bun install --frozen-lockfile - name: Resolve Linux x64 native artifact run id: source @@ -379,7 +409,7 @@ jobs: run: bun run ci:test:coding-agent:ui test_coding_agent_runtime: - name: Test coding-agent runtime/stateful (TS) + name: Test coding-agent runtime/session (TS) runs-on: omp-kata needs: [native_linux_x64, native_artifact_lookup] if: ${{ !cancelled() && needs.native_linux_x64.result != 'failure' }} @@ -394,12 +424,6 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick - run: bun install --frozen-lockfile - name: Resolve Linux x64 native artifact run id: source @@ -419,6 +443,8 @@ jobs: run-id: ${{ steps.source.outputs.artifact-run-id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Test coding-agent runtime bucket + # Runtime/session tests import native-backed barrels too; keep this + # separate for concurrency, not as a native-free guardrail. run: bun run ci:test:coding-agent:runtime test_coding_agent_native: @@ -437,12 +463,7 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick + - uses: ./.github/actions/setup-system-deps - run: bun install --frozen-lockfile - name: Resolve Linux x64 native artifact run id: source @@ -480,12 +501,7 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick + - uses: ./.github/actions/setup-system-deps - run: bun install --frozen-lockfile - name: Resolve Linux x64 native artifact run id: source @@ -542,12 +558,7 @@ jobs: with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - - name: Install system deps - run: | - sudo apt-get update - sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick - sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd - sudo ln -sf /usr/bin/convert /usr/local/bin/magick + - uses: ./.github/actions/setup-system-deps - run: bun install --frozen-lockfile - name: Install method smoke tests run: bun run ci:test:install-methods @@ -558,19 +569,20 @@ jobs: needs.native_linux_x64.result == 'success' && needs.native_cross_platform.result == 'success' && needs.test_workspace.result == 'success' && needs.test_coding_agent_fast.result == 'success' && + needs.test_coding_agent_singleton.result == 'success' && needs.test_ts_native.result == 'success' && needs.test_coding_agent_ui.result == 'success' && needs.test_coding_agent_runtime.result == 'success' && needs.test_coding_agent_native.result == 'success' && needs.test_smoke.result == 'success' && needs.check.result == 'success' && needs.install_methods.result == 'success' }} - needs: [release_metadata, check, native_linux_x64, native_cross_platform, test_workspace, test_coding_agent_fast, test_ts_native, test_coding_agent_ui, test_coding_agent_runtime, test_coding_agent_native, test_smoke, install_methods, native_artifact_lookup] + needs: [release_metadata, check, native_linux_x64, native_cross_platform, test_workspace, test_coding_agent_fast, test_coding_agent_singleton, test_ts_native, test_coding_agent_ui, test_coding_agent_runtime, test_coding_agent_native, test_smoke, install_methods, native_artifact_lookup] strategy: fail-fast: false matrix: include: - { - os: omp-kata, + os: ubuntu-22.04, platform: linux, arch: x64, target_id: linux-x64, @@ -598,7 +610,7 @@ jobs: binary_path: packages/coding-agent/binaries/omp-darwin-arm64, } - { - os: omp-kata, + os: ubuntu-22.04, platform: win32, arch: x64, target_id: win32-x64, @@ -611,11 +623,11 @@ jobs: env: MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.APPLE_CERTIFICATE_PASSWORD != '' && secrets.APPLE_API_KEY_ID != '' && secrets.APPLE_API_ISSUER_ID != '' && secrets.APPLE_API_KEY != '' }} steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: "1.3" - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -624,13 +636,13 @@ jobs: if: ${{ !inputs.skip_npm }} run: npm install -g npm@latest - name: Cache bun dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - run: bun install --frozen-lockfile - name: Download native addon(s) - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: pi-natives-${{ matrix.platform }}-${{ matrix.arch }}*-h${{ needs.native_artifact_lookup.outputs.source-hash }} path: packages/natives/native @@ -671,7 +683,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: bun run ci:release:publish-native-leaf ${{ matrix.target_id }} - name: Upload release binary artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: omp-binary-${{ matrix.target_id }} path: ${{ matrix.binary_path }} @@ -681,24 +693,24 @@ jobs: if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() && needs.release_binary.result == 'success' }} needs: [release_metadata, release_binary] - runs-on: omp-kata + runs-on: ubuntu-22.04 permissions: contents: write steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: "1.3" - name: Generate release notes from CHANGELOGs run: bun scripts/ci-release-notes.ts ${{ needs.release_metadata.outputs.release-tag }} - name: Download release binaries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: omp-binary-* path: packages/coding-agent/binaries merge-multiple: true - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 with: tag_name: ${{ needs.release_metadata.outputs.release-tag }} files: | @@ -748,7 +760,7 @@ jobs: needs.release_github_verify.result == 'success' && !inputs.skip_npm }} needs: [release_metadata, release_binary, release_github_verify, native_artifact_lookup] - runs-on: omp-kata + runs-on: ubuntu-22.04 # `id-token: write` lets npm mint the GitHub OIDC token it exchanges for a # short-lived publish token (trusted publishing + provenance). When a # package has no matching trusted publisher configured, npm silently falls @@ -757,11 +769,11 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: "1.3" - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -769,7 +781,7 @@ jobs: - name: Ensure npm supports trusted publishing run: npm install -g npm@latest - name: Cache bun dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} @@ -780,7 +792,7 @@ jobs: # Release runs always rebuild natives in this same run, so the # default run-id resolves the artifacts. - name: Download native addons - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: pi-natives-linux-x64-*-h${{ needs.native_artifact_lookup.outputs.source-hash }} path: packages/natives/native @@ -803,19 +815,19 @@ jobs: if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() && needs.release_github_verify.result == 'success' }} needs: [release_metadata, release_github_verify] - runs-on: omp-kata + runs-on: ubuntu-22.04 env: HAS_TAP_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 if: env.HAS_TAP_KEY == 'true' - - uses: oven-sh/setup-bun@v2 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 if: env.HAS_TAP_KEY == 'true' with: bun-version: "1.3" - name: Check out the Homebrew tap if: env.HAS_TAP_KEY == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: can1357/homebrew-tap ssh-key: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }} diff --git a/package.json b/package.json index de4970151..ee8717c72 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "ci:test:ts": "bun scripts/ci-test-ts.ts all", "ci:test:ts:workspace": "bun scripts/ci-test-ts.ts workspace", "ci:test:ts:native": "bun scripts/ci-test-ts.ts native", - "ci:test:coding-agent:fast": "bun scripts/ci-test-ts.ts coding-agent-fast", + "ci:test:coding-agent:singleton": "bun scripts/ci-test-ts.ts coding-agent-singleton", "ci:test:coding-agent:ui": "bun scripts/ci-test-ts.ts coding-agent-ui", "ci:test:coding-agent:runtime": "bun scripts/ci-test-ts.ts coding-agent-runtime", "ci:test:coding-agent:native": "bun scripts/ci-test-ts.ts coding-agent-native", diff --git a/packages/ai/src/auth-storage.ts b/packages/ai/src/auth-storage.ts index 7edf35cf2..34168bafe 100644 --- a/packages/ai/src/auth-storage.ts +++ b/packages/ai/src/auth-storage.ts @@ -450,7 +450,7 @@ export type AuthStorageOptions = { * * Examples: * - `"local ~/.omp/agent/agent.db"` - * - `"broker http://can.internal:8765"` + * - `"broker http://omp.internal:8765"` */ sourceLabel?: string; /** diff --git a/packages/ai/src/provider-details.ts b/packages/ai/src/provider-details.ts index 40a54923f..f85347531 100644 --- a/packages/ai/src/provider-details.ts +++ b/packages/ai/src/provider-details.ts @@ -18,7 +18,7 @@ export interface ProviderDetailsContext { authMode?: string; /** * Human-readable description of the active credential, e.g. - * `"broker http://can.internal:8765 · oauth #5 (foo@bar.com)"`. + * `"broker http://omp.internal:8765 · oauth #5 (foo@bar.com)"`. * Rendered as a `Source` field; omitted when undefined. */ credentialSource?: string; diff --git a/packages/coding-agent/test/autocomplete-max-visible.test.ts b/packages/coding-agent/test/autocomplete-max-visible.test.ts index b2a24ad30..7e654e8c1 100644 --- a/packages/coding-agent/test/autocomplete-max-visible.test.ts +++ b/packages/coding-agent/test/autocomplete-max-visible.test.ts @@ -6,14 +6,16 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config import { SelectorController } from "@oh-my-pi/pi-coding-agent/modes/controllers/selector-controller"; import { getProjectAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; import { YAML } from "bun"; +import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; describe("autocompleteMaxVisible setting", () => { - let testDir: string; + let settingsState: SettingsTestState | undefined; + let testDir = ""; let agentDir: string; let projectDir: string; beforeEach(() => { - resetSettingsForTest(); + settingsState = beginSettingsTest(); testDir = path.join(os.tmpdir(), "test-autocomplete-settings", Snowflake.next()); agentDir = path.join(testDir, "agent"); projectDir = path.join(testDir, "project"); @@ -22,10 +24,12 @@ describe("autocompleteMaxVisible setting", () => { }); afterEach(() => { - resetSettingsForTest(); - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true }); + restoreSettingsTestState(settingsState); + settingsState = undefined; + if (testDir && fs.existsSync(testDir)) { + fs.rmSync(testDir, { recursive: true, force: true }); } + testDir = ""; }); it("should persist and read back a configured value", async () => { diff --git a/packages/coding-agent/test/config-spacing.test.ts b/packages/coding-agent/test/config-spacing.test.ts index ebb01c29d..c944fa816 100644 --- a/packages/coding-agent/test/config-spacing.test.ts +++ b/packages/coding-agent/test/config-spacing.test.ts @@ -2,23 +2,28 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; -import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getDefaultTabWidth, getIndentation, Snowflake, setDefaultTabWidth } from "@oh-my-pi/pi-utils"; +import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; describe("indentation resolver", () => { + let settingsState: SettingsTestState | undefined; let tempDir = ""; beforeEach(async () => { - resetSettingsForTest(); + settingsState = beginSettingsTest(); setDefaultTabWidth(3); tempDir = path.join(os.tmpdir(), "pi-spacing", Snowflake.next()); await fs.mkdir(tempDir, { recursive: true }); }); afterEach(async () => { - resetSettingsForTest(); - setDefaultTabWidth(3); - await fs.rm(tempDir, { recursive: true, force: true }); + restoreSettingsTestState(settingsState); + settingsState = undefined; + if (tempDir) { + await fs.rm(tempDir, { recursive: true, force: true }); + } + tempDir = ""; }); it("applies current display tab width during initial settings load", async () => { diff --git a/packages/coding-agent/test/helpers/settings-test-state.ts b/packages/coding-agent/test/helpers/settings-test-state.ts new file mode 100644 index 000000000..6e31da1ea --- /dev/null +++ b/packages/coding-agent/test/helpers/settings-test-state.ts @@ -0,0 +1,73 @@ +import { vi } from "bun:test"; +import { resetSettingsForTest } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { + getAgentDir, + getDefaultTabWidth, + getProjectDir, + setAgentDir, + setDefaultTabWidth, + setProjectDir, +} from "@oh-my-pi/pi-utils"; + +export interface SettingsTestState { + agentDir: string; + env: Record; + projectDir: string; + tabWidth: number; +} + +export function beginSettingsTest(): SettingsTestState { + const env: Record = {}; + for (const key in process.env) { + env[key] = process.env[key]; + } + for (const key in Bun.env) { + env[key] = Bun.env[key]; + } + const state: SettingsTestState = { + agentDir: getAgentDir(), + env, + projectDir: getProjectDir(), + tabWidth: getDefaultTabWidth(), + }; + resetSettingsForTest(); + return state; +} + +export function restoreSettingsTestState(state: SettingsTestState | undefined): void { + vi.restoreAllMocks(); + resetSettingsForTest(); + if (!state) return; + + restoreEnv(state.env); + setDefaultTabWidth(state.tabWidth); + setProjectDir(state.projectDir); + setAgentDir(state.agentDir); + restoreEnvValue("PI_CODING_AGENT_DIR", state.env.PI_CODING_AGENT_DIR); +} + +function restoreEnv(snapshot: Record): void { + for (const key in process.env) { + if (!(key in snapshot)) { + restoreEnvValue(key, undefined); + } + } + for (const key in Bun.env) { + if (!(key in snapshot)) { + restoreEnvValue(key, undefined); + } + } + for (const key in snapshot) { + restoreEnvValue(key, snapshot[key]); + } +} + +function restoreEnvValue(key: string, value: string | undefined): void { + if (value === undefined) { + delete process.env[key]; + delete Bun.env[key]; + return; + } + process.env[key] = value; + Bun.env[key] = value; +} diff --git a/packages/coding-agent/test/modes/theme/shimmer.test.ts b/packages/coding-agent/test/modes/theme/shimmer.test.ts index 07f9def59..8b7ddff20 100644 --- a/packages/coding-agent/test/modes/theme/shimmer.test.ts +++ b/packages/coding-agent/test/modes/theme/shimmer.test.ts @@ -1,6 +1,6 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; +import { afterEach, describe, expect, it, vi } from "bun:test"; import * as settingsModule from "@oh-my-pi/pi-coding-agent/config/settings"; -import { type ShimmerPalette, shimmerText } from "@oh-my-pi/pi-coding-agent/modes/theme/shimmer"; +import { shimmerText } from "@oh-my-pi/pi-coding-agent/modes/theme/shimmer"; import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; const testTheme = { @@ -20,32 +20,6 @@ const testTheme = { }, }; -// Distinct, non-bold color per tier so each rendered cell is classifiable by the -// SGR code that precedes it (31=low, 32=mid, 33=high). -const probe: ShimmerPalette = { - low: { ansi: "\x1b[31m" }, - mid: { ansi: "\x1b[32m" }, - high: { ansi: "\x1b[33m" }, -}; - -/** - * Index of the first visible cell painted with the crest (high, code 33) color, - * or undefined when the band sits in the padding and no cell is lit. Walks the - * coalesced `ESC[m` runs that {@link shimmerText} emits. - */ -function crestStart(rendered: string): number | undefined { - const run = /\x1b\[(\d+)m([^\x1b]*)/g; - let idx = 0; - let m: RegExpExecArray | null = run.exec(rendered); - while (m !== null) { - const len = [...m[2]].length; - if (m[1] === "33" && len > 0) return idx; - idx += len; - m = run.exec(rendered); - } - return undefined; -} - describe("shimmerText", () => { afterEach(() => { vi.restoreAllMocks(); @@ -68,60 +42,3 @@ describe("shimmerText", () => { expect(Bun.stripANSI(rendered)).toBe("x"); }); }); - -describe("shimmer band velocity", () => { - const FRAME_MS = 1000 / 30; - let nowMs = 0; - - beforeEach(() => { - nowMs = 0; - // Deterministic classic mode regardless of global settings state. - vi.spyOn(settingsModule, "isSettingsInitialized").mockReturnValue(false); - vi.spyOn(Date, "now").mockImplementation(() => nowMs); - }); - afterEach(() => { - vi.restoreAllMocks(); - }); - - function crestTrack(length: number, startMs: number, frames: number): (number | undefined)[] { - const text = "x".repeat(length); - const out: (number | undefined)[] = []; - for (let i = 0; i < frames; i++) { - nowMs = startMs + i * FRAME_MS; - out.push(crestStart(shimmerText(text, testTheme, probe))); - } - return out; - } - - it("advances the crest by at most one cell per 30fps frame", () => { - // L=40 → period 60 cells; at 30 cells/s that is a 2s sweep (60 frames). - // 75 frames covers a full sweep plus the padding gap into the next one. - const track = crestTrack(40, 0, 75); - let compared = 0; - for (let i = 1; i < track.length; i++) { - const a = track[i - 1]; - const b = track[i]; - if (a === undefined || b === undefined) continue; // skip the padding gap - expect(Math.abs(b - a)).toBeLessThanOrEqual(1); - compared++; - } - // Fail loudly rather than vacuously pass if the crest were never detected. - expect(compared).toBeGreaterThan(20); - }); - - it("moves the crest at a length-independent speed", () => { - // Starting where the crest enters at index 0 (pos = CLASSIC_PADDING = 10), - // the crest must travel the same number of cells over a fixed wall-clock - // window regardless of string length — the contract of fixed-velocity - // sweeping (a longer message must not shimmer faster). - const startMs = (10 / 30) * 1000; // pos = 10 cells → crest at index 0 - const span = (track: (number | undefined)[]): number => { - const def = track.filter((v): v is number => v !== undefined); - return def.length ? def[def.length - 1] - def[0] : 0; - }; - const shortSpan = span(crestTrack(20, startMs, 10)); - const longSpan = span(crestTrack(60, startMs, 10)); - expect(shortSpan).toBeGreaterThan(0); - expect(Math.abs(shortSpan - longSpan)).toBeLessThanOrEqual(1); - }); -}); diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 2deed7e40..a7ad101b0 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -13,15 +13,16 @@ import { } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getProjectAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; import { YAML } from "bun"; +import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; describe("Settings", () => { - let testDir: string; + let settingsState: SettingsTestState | undefined; + let testDir = ""; let agentDir: string; let projectDir: string; beforeEach(() => { - // Reset global singleton so each test gets a fresh instance - resetSettingsForTest(); + settingsState = beginSettingsTest(); // Use snowflake to isolate parallel test runs (SQLite files can't be shared) testDir = path.join(os.tmpdir(), "test-settings-tmp", Snowflake.next()); @@ -29,7 +30,7 @@ describe("Settings", () => { projectDir = path.join(testDir, "project"); if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true }); + fs.rmSync(testDir, { recursive: true, force: true }); } fs.mkdirSync(agentDir, { recursive: true }); fs.mkdirSync(getProjectAgentDir(projectDir), { recursive: true }); @@ -51,9 +52,12 @@ describe("Settings", () => { }; afterEach(() => { - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true }); + restoreSettingsTestState(settingsState); + settingsState = undefined; + if (testDir && fs.existsSync(testDir)) { + fs.rmSync(testDir, { recursive: true, force: true }); } + testDir = ""; }); describe("defaults", () => { it("keeps eight inline images live by default", async () => { diff --git a/packages/coding-agent/test/settings-reload-cwd.test.ts b/packages/coding-agent/test/settings-reload-cwd.test.ts index f1e3f2efa..7054bf0bf 100644 --- a/packages/coding-agent/test/settings-reload-cwd.test.ts +++ b/packages/coding-agent/test/settings-reload-cwd.test.ts @@ -4,8 +4,20 @@ import * as os from "node:os"; import * as path from "node:path"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getProjectAgentDir, Snowflake } from "@oh-my-pi/pi-utils"; +import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; describe("Settings.reloadForCwd", () => { + let settingsState: SettingsTestState | undefined; + + beforeEach(() => { + settingsState = beginSettingsTest(); + }); + + afterEach(() => { + restoreSettingsTestState(settingsState); + settingsState = undefined; + }); + it("re-resolves path-scoped settings against the new directory in place", async () => { const projectA = path.resolve("/tmp", `reload-a-${Snowflake.next()}`); const projectB = path.resolve("/tmp", `reload-b-${Snowflake.next()}`); @@ -75,7 +87,7 @@ describe("Settings.reloadForCwd", () => { fs.mkdirSync(testDir, { recursive: true }); const missingPath = path.join(testDir, "nope.yml"); - expect(Settings.init({ cwd: testDir, inMemory: true, configFiles: [missingPath] })).rejects.toThrow( + await expect(Settings.init({ cwd: testDir, inMemory: true, configFiles: [missingPath] })).rejects.toThrow( `Config overlay not found: ${missingPath}`, ); } finally { @@ -92,7 +104,7 @@ describe("Settings.reloadForCwd", () => { fs.mkdirSync(testDir, { recursive: true }); fs.writeFileSync(overlayPath, "compaction: [unclosed\n"); - expect(Settings.init({ cwd: testDir, inMemory: true, configFiles: [overlayPath] })).rejects.toThrow( + await expect(Settings.init({ cwd: testDir, inMemory: true, configFiles: [overlayPath] })).rejects.toThrow( "Failed to parse config overlay", ); } finally { @@ -129,7 +141,7 @@ describe("Settings.reloadForCwd", () => { afterEach(() => { resetSettingsForTest(); if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true }); + fs.rmSync(testDir, { recursive: true, force: true }); } }); diff --git a/packages/coding-agent/test/status-line-settings-cache.test.ts b/packages/coding-agent/test/status-line-settings-cache.test.ts index 906a64fa2..9313d5c8a 100644 --- a/packages/coding-agent/test/status-line-settings-cache.test.ts +++ b/packages/coding-agent/test/status-line-settings-cache.test.ts @@ -1,31 +1,33 @@ -import { afterAll, beforeAll, describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { stripVTControlCharacters } from "node:util"; -import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { StatusLineComponent, type StatusLineSettings } from "@oh-my-pi/pi-coding-agent/modes/components/status-line"; import { STATUS_LINE_PRESETS } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/presets"; import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { getProjectDir, setProjectDir } from "@oh-my-pi/pi-utils"; +import { setProjectDir } from "@oh-my-pi/pi-utils"; +import { beginSettingsTest, restoreSettingsTestState, type SettingsTestState } from "./helpers/settings-test-state"; -const originalProjectDir = getProjectDir(); -let projectDir: string; +let settingsState: SettingsTestState | undefined; +let projectDir = ""; -beforeAll(async () => { +beforeEach(async () => { + settingsState = beginSettingsTest(); projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-status-line-settings-cache-")); setProjectDir(projectDir); - resetSettingsForTest(); await Settings.init({ inMemory: true, cwd: projectDir }); await initTheme(); }); -afterAll(() => { - resetSettingsForTest(); - setProjectDir(originalProjectDir); +afterEach(() => { + restoreSettingsTestState(settingsState); + settingsState = undefined; if (projectDir) { fs.rmSync(projectDir, { recursive: true, force: true }); } + projectDir = ""; }); function makeSession(sessionName = "Cache Session") { diff --git a/packages/coding-agent/test/streaming-reveal.test.ts b/packages/coding-agent/test/streaming-reveal.test.ts index 63077ed2f..61ea5584c 100644 --- a/packages/coding-agent/test/streaming-reveal.test.ts +++ b/packages/coding-agent/test/streaming-reveal.test.ts @@ -219,21 +219,6 @@ describe("streaming reveal", () => { expect(component.transientFlags.every(flag => flag === true)).toBe(true); }); - it("ticks increasing prefixes at the render cadence", () => { - vi.useFakeTimers(); - const requestRender = vi.fn(); - const { component, controller } = makeController({ requestRender }); - - controller.begin(component, makeMessage([{ type: "text", text: "" }])); - controller.setTarget(makeMessage([{ type: "text", text: "abcdefghi" }])); - - vi.advanceTimersByTime(STREAMING_REVEAL_FRAME_MS); - expect(textAt(latestMessage(component), 0)).toBe("abc"); - vi.advanceTimersByTime(STREAMING_REVEAL_FRAME_MS); - expect(textAt(latestMessage(component), 0)).toBe("abcdef"); - expect(requestRender).toHaveBeenCalledTimes(2); - }); - it("stop halts pending ticker updates", () => { vi.useFakeTimers(); const { component, controller } = makeController(); diff --git a/packages/coding-agent/test/task/task-progress-render.test.ts b/packages/coding-agent/test/task/task-progress-render.test.ts index fc2e199a7..1a3c2a04d 100644 --- a/packages/coding-agent/test/task/task-progress-render.test.ts +++ b/packages/coding-agent/test/task/task-progress-render.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import type { RenderResultOptions } from "@oh-my-pi/pi-agent-core"; +import type { SettingPath, SettingValue } from "@oh-my-pi/pi-coding-agent/config/settings"; import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getThemeByName, setThemeInstance } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import { taskToolRenderer } from "@oh-my-pi/pi-coding-agent/task/render"; @@ -97,8 +98,12 @@ describe("task progress rendering", () => { it("keeps the agent dot when shimmer is disabled", async () => { const theme = (await getThemeByName("dark"))!; - resetSettingsForTest(); - await Settings.init({ inMemory: true, overrides: { "display.shimmer": "disabled" } }); + const settings = Settings.instance; + const readSetting: Settings["get"] = settings.get.bind(settings); + vi.spyOn(settings, "get").mockImplementation(

(path: P): SettingValue

=> { + if (path === "display.shimmer") return "disabled" as SettingValue

; + return readSetting(path); + }); const options: RenderResultOptions = { expanded: false, isPartial: true, spinnerFrame: 0 }; const strippedRow = Bun.stripANSI( diff --git a/scripts/ci-test-ts.ts b/scripts/ci-test-ts.ts index 6dc0e4640..98f448a2a 100755 --- a/scripts/ci-test-ts.ts +++ b/scripts/ci-test-ts.ts @@ -7,13 +7,13 @@ type Mode = | "all" | "workspace" | "native" - | "coding-agent-fast" + | "coding-agent-singleton" | "coding-agent-ui" | "coding-agent-runtime" | "coding-agent-native" | "coding-agent-heavy"; -type CodingAgentBucket = "fast" | "ui" | "runtime" | "native"; +type CodingAgentBucket = "singleton" | "ui" | "runtime" | "native"; interface TestCommand { label: string; @@ -32,16 +32,23 @@ const validModes = new Set([ "all", "workspace", "native", - "coding-agent-fast", + "coding-agent-singleton", "coding-agent-ui", "coding-agent-runtime", "coding-agent-native", "coding-agent-heavy", ]); -// Pure workspace tests are intentionally separate from native/TUI/integration -// tests so this job can run while the Linux native addon job builds or resolves -// a reusable artifact. +const codingAgentBucketPlans: Record = { + singleton: { label: "singleton/global-state bucket", parallel: 1 }, + ui: { label: "UI/TUI bucket", parallel: 1 }, + runtime: { label: "runtime/session bucket", parallel: 1 }, + native: { label: "native/tooling/browser/unit bucket", parallel: 1 }, +}; + +// Smaller workspace packages stay separate from native/TUI/integration suites so +// their short TS suites can run together. CI still downloads the Linux x64 native +// addon before this bucket: shared utility barrels may load native-backed modules. const fastWorkspacePackages = [ "packages/hashline", "packages/wire", @@ -53,9 +60,9 @@ const fastWorkspacePackages = [ "packages/mnemopi", ]; -// These suites either cover the native package, TUI/browser-ish behavior, local -// servers, or coding-agent-adjacent benchmark paths. Keep them low-concurrency -// and in jobs that have downloaded the Linux x64 native addon artifacts. +// These suites cover the native package, TUI/browser-ish behavior, local servers, +// or coding-agent-adjacent benchmark paths. Keep them low-concurrency and in jobs +// that have downloaded the Linux x64 native addon artifacts. const nativeAndIntegrationPackages = [ "packages/natives", "packages/tui", @@ -75,6 +82,11 @@ const codingAgentNativePathPatterns = [ /^test\/tools\.test\.ts$/, ]; +const codingAgentSingletonPathPatterns = [ + /^test\/(settings|config|fast-mode-scope|autocomplete-max-visible)[^/]*\.test\.ts$/, + /^test\/[^/]*(singleton|global-state|fake-timer)[^/]*\.test\.ts$/, +]; + const codingAgentUiPathPatterns = [ /^test\/modes\//, /^test\/(interactive-mode|main-interactive|input-controller|streaming|status-line|keybindings|editor|hook|theme|setup-wizard|job-renderer|tool-args-reveal|tool-execution)[^/]*\.test\.ts$/, @@ -90,7 +102,7 @@ const codingAgentRuntimePathPatterns = [ /^test\/(extensions?|plugin|autolearn|skills|marketplace|oauth)[^/]*\.test\.ts$/, /^test\/[^/]*oauth[^/]*\.test\.ts$/, /^test\/(extensibility|discovery|tool-discovery|goals|marketplace)\//, - /^test\/(model|model-|model-registry|model-resolver|compaction|settings|config|fast-mode-scope)[^/]*\.test\.ts$/, + /^test\/(model|model-|model-registry|model-resolver|compaction)[^/]*\.test\.ts$/, ]; const codingAgentNativeContentMarkers = [ @@ -111,6 +123,25 @@ const codingAgentNativeContentMarkers = [ "WebSocket", ]; +const codingAgentSingletonContentMarkers = [ + "Settings.init(", + "Settings.instance", + "resetSettingsForTest", + "setAgentDir(", + "setDefaultTabWidth(", + "vi.useFakeTimers(", + "vi.useRealTimers(", + "vi.stubEnv(", + "vi.unstubAllEnvs(", +]; + +const codingAgentSingletonContentPatterns = [ + /(^|[^\w$.])(process\.env|Bun\.env)\.[A-Za-z0-9_]+\s*=/, + /(^|[^\w$.])(process\.env|Bun\.env)\[[^\]]+\]\s*=/, + /delete\s+(process\.env|Bun\.env)(\.[A-Za-z0-9_]+|\[[^\]]+\])/, + /Object\.assign\((process\.env|Bun\.env),/, +]; + const codingAgentUiContentMarkers = [ "@oh-my-pi/pi-tui", "InteractiveMode", @@ -125,13 +156,6 @@ const codingAgentRuntimeContentMarkers = [ "AgentSession", "SessionManager", "AuthStorage", - "Settings.init", - "Settings.instance", - "resetSettingsForTest", - "setAgentDir", - "process.env", - "Bun.env", - "vi.useFakeTimers", "Bun.sleep", "setTimeout(", ]; @@ -178,20 +202,38 @@ function matchesAnyPath(testFile: string, patterns: RegExp[]): boolean { return patterns.some(pattern => pattern.test(testFile)); } +function matchesAnyContentPattern(content: string, patterns: RegExp[]): boolean { + return patterns.some(pattern => pattern.test(content)); +} +// Native/tooling tests are classified first because they need the lowest +// concurrency; all coding-agent buckets run with the native addon available in CI. function classifyCodingAgentTest(testFile: string, content: string): CodingAgentBucket { - if (matchesAnyPath(testFile, codingAgentNativePathPatterns) || hasAnyMarker(content, codingAgentNativeContentMarkers)) { + if ( + matchesAnyPath(testFile, codingAgentNativePathPatterns) || + hasAnyMarker(content, codingAgentNativeContentMarkers) + ) { return "native"; } - if (matchesAnyPath(testFile, codingAgentUiPathPatterns) || hasAnyMarker(content, codingAgentUiContentMarkers)) { + if ( + matchesAnyPath(testFile, codingAgentUiPathPatterns) || + hasAnyMarker(content, codingAgentUiContentMarkers) + ) { return "ui"; } + if ( + matchesAnyPath(testFile, codingAgentSingletonPathPatterns) || + hasAnyMarker(content, codingAgentSingletonContentMarkers) || + matchesAnyContentPattern(content, codingAgentSingletonContentPatterns) + ) { + return "singleton"; + } if ( matchesAnyPath(testFile, codingAgentRuntimePathPatterns) || hasAnyMarker(content, codingAgentRuntimeContentMarkers) ) { return "runtime"; } - return "fast"; + return "native"; } async function getCodingAgentTestPartition(): Promise { @@ -202,7 +244,7 @@ async function getCodingAgentTestPartition(): Promise ...(await collectTestsUnder(path.join(codingAgentDir, "src"), codingAgentDir)), ].sort(); const partition: CodingAgentTestPartition = { - fast: [], + singleton: [], ui: [], runtime: [], native: [], @@ -224,11 +266,11 @@ async function codingAgentTestCommand(bucket: CodingAgentBucket): Promise { ]; case "native": return nativeAndIntegrationPackages.map(pkg => workspaceTestCommand(pkg, 1, true)); - case "coding-agent-fast": - return [await codingAgentTestCommand("fast")]; + case "coding-agent-singleton": + return [await codingAgentTestCommand("singleton")]; case "coding-agent-ui": return [await codingAgentTestCommand("ui")]; case "coding-agent-runtime": @@ -255,6 +297,7 @@ async function commandsForMode(mode: Mode): Promise { return [await codingAgentTestCommand("native")]; case "coding-agent-heavy": return [ + await codingAgentTestCommand("singleton"), await codingAgentTestCommand("ui"), await codingAgentTestCommand("runtime"), await codingAgentTestCommand("native"), @@ -263,7 +306,6 @@ async function commandsForMode(mode: Mode): Promise { return [ ...(await commandsForMode("workspace")), ...(await commandsForMode("native")), - ...(await commandsForMode("coding-agent-fast")), ...(await commandsForMode("coding-agent-heavy")), ]; }