diff --git a/.gitattributes b/.gitattributes index e0e0282b69f8..be42f7cfd68d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,12 @@ /skill-stubs/_shared/*.md text eol=lf /skills/*/SKILL.md text eol=lf /src/cli/bundled-skill-guides.ts text eol=lf +# SVG bytes feed embedded data URLs and asset hashes on every build host. +*.svg text eol=lf +# Compiler input line breaks must not depend on the checkout OS. +*.ts text eol=lf +*.tsx text eol=lf +*.html text eol=lf # Bundled plugin trees are byte-hashed; CRLF checkout would break the pinned hash. /resources/plugins/** text eol=lf # Relay assets are copied verbatim into the bundle and hashed byte-for-byte into @@ -88,3 +94,6 @@ /mobile/web-entry/**/*.otf -text /mobile/web-entry/**/*.woff -text /mobile/web-entry/**/*.woff2 -text + +# Generated ACP schemas are checked byte-for-byte against formatted output. +/src/main/acp/generated/*.generated.ts linguist-generated=true text eol=lf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 322481ae8b35..7fc5200404bd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,6 +16,11 @@ Thanks for contributing to Orca. ## Local Setup +Install Node 24, pnpm, and the Bun version in [`config/bun-version`](../config/bun-version). +`pnpm test` runs Vitest on Bun, with Node workers for runtime contracts such as SQLite, +native PTYs, socket liveness, and V8 memory behavior. `pnpm test:node` runs the same suites +entirely on Node. Builds and dependency installation still use Node and pnpm. + ```bash pnpm install pnpm dev diff --git a/.github/workflows/ci-closed-pr-caches.yml b/.github/workflows/ci-closed-pr-caches.yml index 38cf5803a146..468559ccd4d5 100644 --- a/.github/workflows/ci-closed-pr-caches.yml +++ b/.github/workflows/ci-closed-pr-caches.yml @@ -1,4 +1,4 @@ -name: Clean closed PR caches +name: Clean closed PR work on: pull_request_target: @@ -6,14 +6,81 @@ on: permissions: actions: write + pull-requests: read jobs: clean: runs-on: ubuntu-latest timeout-minutes: 5 steps: + - name: Cancel checks for an unmerged closed PR + if: github.event.pull_request.merged == false + uses: actions/github-script@v8 + with: + script: | + const closed = context.payload.pull_request + const targets = [ + ['pr-checks', 'pr.yml'], + ['node-server', 'node-server-tests.yml'], + ['ssh-windows-hosts', 'ssh-windows-hosts.yml'], + ['ssh-hostile-hosts', 'ssh-hostile-hosts.yml'], + ['mobile', 'mobile.yml'], + ['computer-e2e', 'computer-e2e.yml'] + ] + const stillClosed = async () => { + const { data: current } = await github.rest.pulls.get({ + ...context.repo, pull_number: closed.number + }) + return current.state === 'closed' && current.merged_at === null && + current.closed_at === closed.closed_at + } + if (!Number.isSafeInteger(closed.number) || closed.number <= 0 || + !Number.isFinite(Date.parse(closed.closed_at))) { + throw new Error('Missing closed PR identity') + } + if (!await stillClosed()) return + for (const [prefix, workflow] of targets) { + const group = `${prefix}-${closed.number}` + let data + try { + const response = await github.request( + 'GET /repos/{owner}/{repo}/actions/concurrency_groups/{concurrency_group_name}', { + ...context.repo, concurrency_group_name: group, + headers: { 'X-GitHub-Api-Version': '2026-03-10' } + } + ) + data = response.data + } catch (error) { + if (error.status === 404) continue + throw error + } + if (data.group_name !== group || !Array.isArray(data.group_members)) { + throw new Error(`Unexpected concurrency group: ${group}`) + } + for (const member of data.group_members) { + // Only whole PR runs; release/manual jobs never share this identity. + if (member.job_id !== undefined || !Number.isSafeInteger(member.run_id)) continue + const { data: run } = await github.rest.actions.getWorkflowRun({ + ...context.repo, run_id: member.run_id + }) + if (run.event !== 'pull_request' || run.path !== `.github/workflows/${workflow}` || + run.status === 'completed' || + !Number.isFinite(Date.parse(run.created_at)) || + Date.parse(run.created_at) > Date.parse(closed.closed_at)) continue + if (!await stillClosed()) return + try { + await github.rest.actions.cancelWorkflowRun({ + ...context.repo, run_id: member.run_id + }) + core.info(`Requested cancellation of ${group}: ${member.run_id}`) + } catch (error) { + if (error.status !== 409) throw error + } + } + } # No checkout: this runs trusted default-branch code, including for fork PRs. - uses: actions/github-script@v8 + if: '!cancelled()' with: script: | const ref = `refs/pull/${context.payload.pull_request.number}/merge` diff --git a/.github/workflows/cloud-deploy-relay-production-same-cap-job.yml b/.github/workflows/cloud-deploy-relay-production-same-cap-job.yml index 913b6a24b174..ea21c4185f37 100644 --- a/.github/workflows/cloud-deploy-relay-production-same-cap-job.yml +++ b/.github/workflows/cloud-deploy-relay-production-same-cap-job.yml @@ -15,6 +15,7 @@ on: expected-general-cells: { required: true, type: string } expected-rehome-generation: { required: true, type: string } wave-index: { required: true, type: string } + drain-pace-window-ms: { required: true, type: string } permissions: contents: read @@ -48,14 +49,13 @@ jobs: EXPECTED_GENERAL_CELLS: ${{ inputs.expected-general-cells }} EXPECTED_REHOME_GENERATION: ${{ inputs.expected-rehome-generation }} WAVE_INDEX: ${{ inputs.wave-index }} - # Each drained host re-dials /v1/assign and is re-placed through the director's - # single-slot sticky lane, which clears roughly 8 hosts/s fleet-wide. A 2,100-host - # cell over 2 min (~18/s) overran it on 2026-09-23 and browned out assign for every - # region for ~5 min. 5 min is the cell's maximum window (DRAIN_PACE_WINDOW_MAX_MS); a - # ~2,700-host cell still arrives at ~9/s, so expect a short tail, not zero rejects. - # The transition wait below is the 15-min migration lease plus this window. A cell on - # an older image drains at once. - DRAIN_PACE_WINDOW_MS: '300000' + # The cell spreads its drain sends over this window, so each drained host re-dials + # /v1/assign at hosts / window and is re-placed through the director's drain-return + # lane. The wave validator admits only its closed set, and anything below the 300000 + # default only on US general cells: Asia drains are bound by their targets' accept + # rate. The transition wait below is the 15-min migration lease plus this window. A + # cell on an older image drains at once. + DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }} steps: - name: Require exact reusable-workflow configuration working-directory: . @@ -104,10 +104,11 @@ jobs: # One approved-cell table, in the wave validator the dispatch gate already uses, so # a cell's class and its wave's selector delta cannot drift apart between the two. + # Also re-checks the drain pace against this cell, before anything reads production. - name: Resolve this cell's admission class and wave selector delta run: | CELL_CLASS="$(node dev/scripts/relay-production-same-cap-wave.mjs cell-class \ - --cell-id "${TARGET_CELL_ID}")" + --cell-id "${TARGET_CELL_ID}" --drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}")" ENTRY_ADMISSION="$(jq -er '.entryAdmission' <<< "${CELL_CLASS}")" SELECTOR_WAVE_DELTA="$(jq -er '.selectorWaveDelta' <<< "${CELL_CLASS}")" if test "${DEPLOY_MODE}" = verify; then @@ -537,10 +538,14 @@ jobs: jq -e '.rollIsolated == true' <<< "${ISOLATE_RESULT}" >/dev/null ISOLATE_GENERATION="$(jq -er '.generation' <<< "${ISOLATE_RESULT}")" echo "SELECTOR_GENERATION_AFTER_ISOLATE=${ISOLATE_GENERATION}" >> "${GITHUB_ENV}" - node dev/scripts/prepare-relay-production-capacity-canary.mjs \ + DRAIN_RESULT="$(node dev/scripts/prepare-relay-production-capacity-canary.mjs \ --director-origin "${DIRECTOR_ORIGIN}" --cell-origin "${CELL_ORIGIN}" \ --cell-id "${TARGET_CELL_ID}" --approved-cells same-cap --mode drain \ - --pace-window-ms "${DRAIN_PACE_WINDOW_MS}" + --pace-window-ms "${DRAIN_PACE_WINDOW_MS}")" + echo "${DRAIN_RESULT}" + # What the cell accepted, which is 0 when an older image fell back to an unpaced drain. + echo "drain-applied-pace-window-ms=$(jq -er '.paceWindowMs' <<< "${DRAIN_RESULT}")" \ + >> "${GITHUB_OUTPUT}" # Restart-safe means an empty runtime for a whole pace window after the drain; leases # held by unplaceable hosts are reported, not waited on (c28, 2026-10-01). node dev/scripts/verify-relay-capacity-transition.mjs \ @@ -549,7 +554,9 @@ jobs: --unobserved-bound "${EXPECTED_UNOBSERVED_BOUND}" \ --heartbeat either --admission migration-only --draining required \ --activity restart-safe --expected-image-digests "${PREDECESSOR_IMAGE_DIGEST}" \ - --pace-window-ms "${DRAIN_PACE_WINDOW_MS}" --timeout-ms 1200000 + --pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \ + --timeout-ms "$((900000 + DRAIN_PACE_WINDOW_MS))" + echo "drain-settled-at=$(date -u +%FT%TZ)" >> "${GITHUB_OUTPUT}" - id: capacity-auth if: ${{ inputs.mode != 'verify' }} @@ -822,6 +829,8 @@ jobs: timeout-minutes: 8 env: DRAIN_STARTED_AT: ${{ steps.drain.outputs.drain-started-at }} + DRAIN_SETTLED_AT: ${{ steps.drain.outputs.drain-settled-at }} + DRAIN_APPLIED_PACE_WINDOW_MS: ${{ steps.drain.outputs.drain-applied-pace-window-ms }} APPLY_STARTED_AT: ${{ steps.apply.outputs.apply-started-at }} APPLY_COMPLETED_AT: ${{ steps.apply.outputs.apply-completed-at }} VERIFY_ENDED_AT: ${{ steps.verify-target.outputs.verify-ended-at }} @@ -838,6 +847,10 @@ jobs: --apply-started-at "${APPLY_STARTED_AT}" \ --apply-completed-at "${APPLY_COMPLETED_AT}" \ --verify-ended-at "${VERIFY_ENDED_AT}" \ + --drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \ + --drain-applied-pace-window-ms "${DRAIN_APPLIED_PACE_WINDOW_MS}" \ + --drain-settled-at "${DRAIN_SETTLED_AT}" \ + --target-hosts "${TARGET_HOSTS:-}" \ --summary-file "${GITHUB_STEP_SUMMARY}" \ --output-file "${SHADOW_GATE_DIRECTORY}/${SHADOW_GATE_NAME}" diff --git a/.github/workflows/cloud-deploy-relay-production-same-cap.yml b/.github/workflows/cloud-deploy-relay-production-same-cap.yml index 70ea650b59d7..a4af4f2888d2 100644 --- a/.github/workflows/cloud-deploy-relay-production-same-cap.yml +++ b/.github/workflows/cloud-deploy-relay-production-same-cap.yml @@ -53,6 +53,12 @@ on: description: Exact durable regional-rehome control generation; it must be disabled required: true type: string + drain-pace-window-ms: + description: Window the cell spreads its drain over; below 300000 only for US general cells, named in the confirmation + required: true + default: '300000' + type: choice + options: ['300000', '60000', '30000'] canary-run-id: description: Successful same-code canary in this rehome control generation; reusable across batches required: false @@ -103,11 +109,13 @@ jobs: ROLLBACK_DIGEST: ${{ inputs.rollback-image-digest }} CONFIRMATION: ${{ inputs.confirmation }} CANARY_RUN_ID: ${{ inputs.canary-run-id }} + DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }} run: | CELLS="$(node dev/scripts/relay-production-same-cap-wave.mjs validate \ --mode "${MODE}" --cell-ids "${CELL_IDS}" \ --target-digest "${TARGET_DIGEST}" --rollback-digest "${ROLLBACK_DIGEST}" \ - --confirmation "${CONFIRMATION}" --canary-run-id "${CANARY_RUN_ID}")" + --confirmation "${CONFIRMATION}" --canary-run-id "${CANARY_RUN_ID}" \ + --drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}")" echo "cells=${CELLS}" >> "${GITHUB_OUTPUT}" if [[ "${MODE}" =~ ^(canary-apply|batch-apply)$ ]]; then echo 'job-mode=apply' >> "${GITHUB_OUTPUT}" @@ -133,6 +141,7 @@ jobs: ROLLBACK_DIGEST: ${{ inputs.rollback-image-digest }} SELECTOR_GENERATION: ${{ inputs.expected-selector-generation }} REHOME_GENERATION: ${{ inputs.expected-rehome-generation }} + DRAIN_PACE_WINDOW_MS: ${{ inputs.drain-pace-window-ms }} run: | node dev/scripts/relay-production-same-cap-wave.mjs verify-canary \ --file "${RUNNER_TEMP}/relay-same-cap-canary/authority.json" \ @@ -140,6 +149,7 @@ jobs: --cell-ids "${CELL_IDS}" \ --target-digest "${TARGET_DIGEST}" \ --rollback-digest "${ROLLBACK_DIGEST}" \ + --drain-pace-window-ms "${DRAIN_PACE_WINDOW_MS}" \ --selector-generation "${SELECTOR_GENERATION}" \ --rehome-generation "${REHOME_GENERATION}" @@ -158,6 +168,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '0' secrets: inherit @@ -177,6 +188,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '1' secrets: inherit @@ -196,6 +208,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '2' secrets: inherit @@ -215,6 +228,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '3' secrets: inherit @@ -234,6 +248,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '4' secrets: inherit @@ -253,6 +268,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '5' secrets: inherit @@ -272,6 +288,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '6' secrets: inherit @@ -291,6 +308,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '7' secrets: inherit @@ -310,6 +328,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '8' secrets: inherit @@ -329,6 +348,7 @@ jobs: expected-migration-only-cells: ${{ inputs.expected-migration-only-cells }} expected-general-cells: ${{ inputs.expected-general-cells }} expected-rehome-generation: ${{ inputs.expected-rehome-generation }} + drain-pace-window-ms: ${{ inputs.drain-pace-window-ms }} wave-index: '9' secrets: inherit @@ -343,14 +363,26 @@ jobs: - uses: actions/setup-node@v4 with: { node-version: 24 } + # Its pace checks are what let a batch drain faster than the default; a missing artifact + # seals UNVERIFIED, which only the default accepts. Named from the gate's normalized cell, + # as cell_1 uploaded it, so a padded cell-ids input cannot miss it. + - name: Download the canary cell's shadow health verdict + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: relay-same-cap-shadow-gate-${{ fromJSON(needs.gate.outputs.cells)[0] }}-${{ github.run_id }}.json + path: ${{ runner.temp }}/relay-same-cap-shadow-gate + - name: Seal exact successful canary authority run: | mkdir -p "${RUNNER_TEMP}/relay-same-cap-canary" node dev/scripts/relay-production-same-cap-wave.mjs create-canary \ - --cell-id "${{ inputs.cell-ids }}" \ + --cell-id "${{ fromJSON(needs.gate.outputs.cells)[0] }}" \ --target-digest "${{ inputs.target-image-digest }}" \ --rollback-digest "${{ inputs.rollback-image-digest }}" \ --confirmation "${{ inputs.confirmation }}" \ + --drain-pace-window-ms "${{ inputs.drain-pace-window-ms }}" \ + --shadow-report "${RUNNER_TEMP}/relay-same-cap-shadow-gate/relay-same-cap-shadow-gate-${{ fromJSON(needs.gate.outputs.cells)[0] }}-${GITHUB_RUN_ID}.json" \ --commit-sha "${GITHUB_SHA}" --run-id "${GITHUB_RUN_ID}" \ --selector-generation "${{ inputs.expected-selector-generation }}" \ --rehome-generation "${{ inputs.expected-rehome-generation }}" \ diff --git a/.github/workflows/cloud-operate-relay-asia-admission.yml b/.github/workflows/cloud-operate-relay-asia-admission.yml index 93aec57f11ff..a5bb87d1d985 100644 --- a/.github/workflows/cloud-operate-relay-asia-admission.yml +++ b/.github/workflows/cloud-operate-relay-asia-admission.yml @@ -39,7 +39,7 @@ on: required: false type: string evidence-run-id: - description: Staging evidence run ID for C27, C27 canary run ID for C28/C29; C30-C33 take none and each proves itself by its own canary + description: Staging evidence run ID for C27, C27 canary run ID for C28/C29; C30-C34 take none and each proves itself by its own canary required: false type: string evidence-run-attempt: @@ -156,7 +156,7 @@ jobs: evidence_kind=c27 artifact_name="relay-asia-c27-canary-${EVIDENCE_RUN_ID}-${EVIDENCE_RUN_ATTEMPT}" ;; - production-gce-c30|production-gce-c31) + production-gce-c30|production-gce-c31|production-gce-c34) # No earlier proof binds a later cell's generation; its own canary below rolls it back on failure. canary_cell="${TARGET_CELL_IDS}" ;; @@ -330,7 +330,7 @@ jobs: verify_cells=production-gce-c27,production-gce-c28,production-gce-c29 expected_states='{"production-gce-c27":"general","production-gce-c28":"migration-only","production-gce-c29":"migration-only"}' ;; - production-gce-c30|production-gce-c31|production-gce-c32|production-gce-c33) + production-gce-c30|production-gce-c31|production-gce-c32|production-gce-c33|production-gce-c34) verify_cells="${CANARY_CELL}" expected_states="{\"${CANARY_CELL}\":\"general\"}" ;; diff --git a/.github/workflows/computer-e2e.yml b/.github/workflows/computer-e2e.yml index 325ce6cb61cd..19ea0b8daa83 100644 --- a/.github/workflows/computer-e2e.yml +++ b/.github/workflows/computer-e2e.yml @@ -11,15 +11,12 @@ on: - 'config/scripts/pnpm-cli-invocation.mjs' - 'config/scripts/build-windows-cli-launcher.mjs' - 'config/scripts/build-windows-cli-launcher.test.mjs' - - 'config/scripts/computer-e2e-workflow.test.mjs' - 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs' - 'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs' - 'config/scripts/macos-computer-helper-owner-loss-metrics.mjs' - 'config/scripts/macos-computer-helper-owner-loss-processes.mjs' - 'config/scripts/macos-computer-helper-owner-loss-processes.test.mjs' - 'config/scripts/macos-computer-helper-owner-loss-trial-cleanup.mjs' - - 'config/scripts/computer-use-modifier-safety.test.mjs' - - 'config/scripts/computer-use-skill-guidance.test.mjs' - 'config/scripts/computer-use-smoke.mjs' - 'config/scripts/computer-use-smoke.test.mjs' - 'config/scripts/daemon-boot-smoke.mjs' @@ -88,11 +85,8 @@ jobs: - run: >- pnpm vitest run --config config/vitest.config.ts src/main/ssh/ssh-remote-cli-launcher.test.ts - config/scripts/computer-e2e-workflow.test.mjs config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs config/scripts/macos-computer-helper-owner-loss-processes.test.mjs - config/scripts/computer-use-modifier-safety.test.mjs - config/scripts/computer-use-skill-guidance.test.mjs config/scripts/computer-use-smoke.test.mjs src/main/computer/computer-provider-lifecycle.test.ts src/main/computer/computer-provider-unavailable-message.test.ts @@ -115,7 +109,6 @@ jobs: src/cli/handlers/computer-action-routing.test.ts src/cli/handlers/computer-action-validation.test.ts src/cli/handlers/computer-state-formatting.test.ts - src/cli/specs/computer.test.ts src/cli/index.test.ts src/main/runtime/rpc/dispatcher-computer-errors.test.ts src/main/runtime/rpc/errors.test.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2957c8eaa6ec..a862d0d93958 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -315,6 +315,7 @@ jobs: . != "tests/e2e/ssh-codex-display-artifacts-repro.spec.ts" and . != "tests/e2e/ssh-cold-activation-restore.spec.ts" and . != "tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts" and + . != "tests/e2e/ssh-emptied-worktree-reactivation.spec.ts" and . != "tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts" and . != "tests/e2e/ssh-docker-bulk-open-freeze-repro.spec.ts" and . != "tests/e2e/ssh-docker-half-open-link.spec.ts" and @@ -386,6 +387,7 @@ jobs: contains(inputs.test_files, 'tests/e2e/ssh-codex-display-artifacts-repro.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-cold-activation-restore.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts') || + contains(inputs.test_files, 'tests/e2e/ssh-emptied-worktree-reactivation.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-docker-bulk-open-freeze-repro.spec.ts') || contains(inputs.test_files, 'tests/e2e/ssh-docker-half-open-link.spec.ts') || diff --git a/.github/workflows/fork-verify.yml b/.github/workflows/fork-verify.yml index 4e84b7c1e005..8cabe85deb49 100644 --- a/.github/workflows/fork-verify.yml +++ b/.github/workflows/fork-verify.yml @@ -85,6 +85,11 @@ jobs: - name: Install Electron package binary for tests run: node config/scripts/install-electron-package-binary.mjs + # Upstream's Vitest launcher runs under Bun; unit-tests.yml installs it the same way. + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: config/bun-version + - name: Fork-owned tests env: ORCA_BACKGROUND_LAUNCH: '1' diff --git a/.github/workflows/macos-updater-tests.yml b/.github/workflows/macos-updater-tests.yml index 056a40204b44..b86846fac9d0 100644 --- a/.github/workflows/macos-updater-tests.yml +++ b/.github/workflows/macos-updater-tests.yml @@ -43,7 +43,6 @@ jobs: src/main/updater.mac-install.test.ts src/main/updater.headless-serve-install.test.ts src/main/updater-mac-quit-guard.test.ts - src/main/startup/desktop-startup-ordering.test.ts src/main/startup/main-process-quit-update-veto.test.ts src/main/window/main-window-state-lifecycle.test.ts src/main/window/dashboard-popout-window.test.ts diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ed9a25732bbe..633b8a0166d2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -349,6 +349,10 @@ jobs: if: needs.code_paths.outputs.static_analysis == 'true' run: pnpm run verify:rpc-params-catalog + - name: Verify the generated ACP protocol schema + if: needs.code_paths.outputs.static_analysis == 'true' + run: pnpm run verify:acp-protocol + - name: Verify bundled skill guides if: needs.code_paths.outputs.static_analysis == 'true' run: pnpm run verify:bundled-skill-guides @@ -569,6 +573,35 @@ jobs: pnpm exec vitest run --config config/vitest.config.ts \ src/main/pty/codex-no-daemon-binary-contract.test.ts + # Why both pins and latest: Orca approves its managed-home hook entry with a hash + # Codex computes, so a release that changes it must redden here before users + # meet a review screen. + - name: Verify Codex hook file-entry contract (pinned) + env: + ORCA_CODEX_HOOK_CONTRACT_REQUIRED: '1' + run: | + set -euo pipefail + for install in codex-cli:"$CODEX_CLI_VERSION" codex-cli-no-daemon:"$CODEX_NO_DAEMON_CLI_VERSION"; do + ORCA_CODEX_HOOK_CONTRACT_BINARY="$RUNNER_TEMP/${install%%:*}/node_modules/.bin/codex" \ + ORCA_CODEX_HOOK_CONTRACT_VERSION="${install#*:}" \ + pnpm exec vitest run --config config/vitest.config.ts \ + src/main/codex/codex-hook-file-entry-binary-contract.test.ts + done + + - name: Install latest Codex CLI + run: | + set -euo pipefail + npm install --no-audit --no-fund --prefix "$RUNNER_TEMP/codex-cli-latest" "@openai/codex@latest" + + - name: Verify Codex hook file-entry contract (latest) + env: + ORCA_CODEX_HOOK_CONTRACT_REQUIRED: '1' + run: | + set -euo pipefail + ORCA_CODEX_HOOK_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-latest/node_modules/.bin/codex" \ + pnpm exec vitest run --config config/vitest.config.ts \ + src/main/codex/codex-hook-file-entry-binary-contract.test.ts + # Why the no-daemon install: it is the newest pinned Codex, and its project # lookup decides whether a worktree launch stops at the trust prompt. - name: Verify Codex project-trust contract @@ -1028,6 +1061,7 @@ jobs: src/main/browser/browser-route-webrtc-egress.electron.test.ts src/main/browser/browser-route-h3-egress.electron.test.ts src/main/browser/browser-route-dns-prefetch.electron.test.ts + src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts # Restore independent fixtures during builds; keep native lifecycle probes serial. - uses: ./.github/actions/prepare-linux-package-fixture @@ -1199,6 +1233,7 @@ jobs: src/main/browser/browser-route-webrtc-egress.electron.test.ts src/main/browser/browser-route-h3-egress.electron.test.ts src/main/browser/browser-route-dns-prefetch.electron.test.ts + src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts src/main/providers/pty-repaint-wide-char-buffer.node-pty.test.ts src/shared/child-process/windows-command-line.win32.test.ts @@ -1220,7 +1255,6 @@ jobs: src/main/windows-live-tree-kill.win32.test.ts src/main/wsl/wsl-runner.test.ts src/main/wsl/wsl-guest-environment.test.ts - src/main/wsl/wsl-invocation-boundary.test.ts src/main/wsl/wsl-executable-path.win32.test.ts src/main/wsl/wsl-w1-w3-contract.test.ts src/shared/source-scan/source-tree-scan.test.ts diff --git a/.github/workflows/pullfrog.yml b/.github/workflows/pullfrog.yml index 2fc81c887bba..1520d890920f 100644 --- a/.github/workflows/pullfrog.yml +++ b/.github/workflows/pullfrog.yml @@ -28,6 +28,8 @@ concurrency: jobs: review_scope: + # Paused while we measure CI queue recovery. + if: vars.PULLFROG_PAUSED != 'true' runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index 3073d160d11a..d6282a804a02 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -1212,12 +1212,25 @@ jobs: with: ref: refs/tags/${{ needs.cut.outputs.tag }} + # Compilation can overlap the release gates; signing remains behind release-preflight. + release-javascript: + needs: cut + if: needs.cut.outputs.should_release == 'true' + permissions: + contents: read + uses: ./.github/workflows/release-javascript.yml + with: + ref: refs/tags/${{ needs.cut.outputs.tag }} + secrets: + ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }} + build: needs: - cut - create-release - orcad-template - release-preflight + - release-javascript - relay-windows-process-tree # Why not the implicit success(): a tag without the orcad template skips that job on purpose. if: >- @@ -1226,6 +1239,7 @@ jobs: needs.cut.outputs.should_release == 'true' && needs.create-release.result == 'success' && needs.release-preflight.result == 'success' && + needs.release-javascript.result == 'success' && needs.relay-windows-process-tree.result == 'success' && (needs.orcad-template.result == 'success' || (needs.orcad-template.result == 'skipped' && needs.cut.outputs.ships_orcad_template == 'false')) @@ -1460,26 +1474,30 @@ jobs: } cargo --version - # Why ORCA_POSTHOG_WRITE_KEY here: this is the only build that - # produces a published binary, so this is the only place the secret - # needs to be in scope. The key is a PostHog *project* API key, not - # a server secret — it ships in every official binary's app.asar - # and is therefore extractable from any release. We still keep it - # in GitHub Actions secrets so the literal stays out of the repo - # (and out of fork CI runs / log scrapers / casual greps). - # Why ORCA_BUILD_IDENTITY here (not in env at the job level): the - # value comes from the per-tag classification above and electron-vite - # reads it from `process.env` during `pnpm build:release` only. - # Why ORCA_DIAGNOSTICS_TOKEN_URL here: official builds pin crash - # diagnostic uploads to Orca's endpoint at compile time, matching the - # telemetry gate's "official binary only" behavior. + # Consumers verify the same official build configuration before restoring the bundle. + - name: Download release JavaScript + if: needs.release-javascript.outputs.supported == 'true' + uses: actions/download-artifact@v8 + with: + name: release-javascript + path: .build/release-javascript + - name: Build app - run: pnpm build:release + shell: bash + run: | + if [ "$SHARED_JAVASCRIPT" = true ]; then + node config/scripts/release-javascript-artifact.mjs restore + pnpm run build:release:host + else + pnpm run build:release + fi env: # Why: Vite's web build crossed Node's default old-space ceiling on # the macOS release runner, leaving v1.4.2-rc.8 as an incomplete draft. NODE_OPTIONS: --max-old-space-size=4096 ORCA_BUILD_IDENTITY: ${{ steps.tag-classify.outputs.identity }} + SHARED_JAVASCRIPT: ${{ needs.release-javascript.outputs.supported }} + ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ needs.release-javascript.outputs.source_sha }} ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }} # Fail the release rather than ship a relay that cannot launch outside @@ -2330,6 +2348,7 @@ jobs: - create-release - orcad-template - release-preflight + - release-javascript # release-mac-build.yml downloads the relay addons from this run. - relay-windows-process-tree # Why not the implicit success(): a tag without the orcad template skips that job on purpose. @@ -2339,6 +2358,7 @@ jobs: needs.cut.outputs.should_release == 'true' && needs.create-release.result == 'success' && needs.release-preflight.result == 'success' && + needs.release-javascript.result == 'success' && needs.relay-windows-process-tree.result == 'success' && (needs.orcad-template.result == 'success' || (needs.orcad-template.result == 'skipped' && needs.cut.outputs.ships_orcad_template == 'false')) @@ -2364,6 +2384,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_MAC_BUILD_REF: ${{ github.ref_name }} RELEASE_MAC_BUILD_RELEASE_RUN_ID: ${{ github.run_id }} + RELEASE_MAC_BUILD_JAVASCRIPT_SOURCE_SHA: ${{ needs.release-javascript.outputs.supported == 'true' && needs.release-javascript.outputs.source_sha || '' }} RELEASE_MAC_BUILD_TAG: ${{ needs.cut.outputs.tag }} RELEASE_MAC_BUILD_WORKFLOW: release-mac-build.yml diff --git a/.github/workflows/release-javascript-benchmark.yml b/.github/workflows/release-javascript-benchmark.yml new file mode 100644 index 000000000000..6a5fc4efa31e --- /dev/null +++ b/.github/workflows/release-javascript-benchmark.yml @@ -0,0 +1,120 @@ +name: Release JavaScript comparison + +on: + pull_request: + paths: ['.github/workflows/release-javascript-benchmark.yml'] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-javascript-comparison-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + ORCA_BACKGROUND_LAUNCH: '1' + ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token + ORCA_POSTHOG_WRITE_KEY: ci-build-comparison + NODE_OPTIONS: --max-old-space-size=4096 + +jobs: + bundle: + uses: ./.github/workflows/release-javascript.yml + with: + ref: ${{ github.sha }} + secrets: + ORCA_POSTHOG_WRITE_KEY: ci-build-comparison + + measure: + needs: bundle + name: ${{ matrix.mode }} ${{ matrix.host.platform }} ${{ matrix.host.arch }} + runs-on: ${{ matrix.host.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + max-parallel: 8 + matrix: + mode: [baseline, shared] + host: + - os: ubuntu-latest + platform: linux + arch: x64 + - os: ubuntu-24.04-arm + platform: linux + arch: arm64 + - os: windows-2022 + platform: win32 + arch: x64 + - os: macos-15 + platform: darwin + arch: arm64 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.bundle.outputs.source_sha }} + persist-credentials: false + - uses: ./.github/actions/install-node-dependencies + with: + native-runtime: node + persist-native-cache: 'false' + - uses: ./.github/actions/install-mobile-dependencies + - name: Install Linux provider dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y python3-gi gir1.2-atspi-2.0 at-spi2-core xclip xdotool + - name: Resolve build identity + shell: bash + run: | + RELEASE_TAG="v$(node -p 'require("./package.json").version')" + export RELEASE_TAG + echo "ORCA_BUILD_IDENTITY=$(node config/scripts/release-javascript-artifact.mjs identity)" >> "$GITHUB_ENV" + - name: Download shared JavaScript + if: matrix.mode == 'shared' + uses: actions/download-artifact@v8 + with: + name: release-javascript + path: .build/release-javascript + - name: Measure release build + shell: bash + env: + MEASUREMENT_MODE: ${{ matrix.mode }} + ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ needs.bundle.outputs.source_sha }} + run: node config/scripts/release-javascript-benchmark.mjs "$MEASUREMENT_MODE" + - uses: actions/upload-artifact@v7 + with: + name: release-javascript-measurement-${{ matrix.mode }}-${{ matrix.host.platform }}-${{ matrix.host.arch }} + path: .build/release-javascript-measurements/ + retention-days: 7 + + comparison: + needs: [bundle, measure] + runs-on: ubuntu-slim + permissions: + contents: read + actions: read + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.bundle.outputs.source_sha }} + persist-credentials: false + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + - uses: ./.github/actions/install-node-dependencies + - uses: actions/download-artifact@v8 + with: + pattern: release-javascript-measurement-* + path: .build/release-javascript-measurements + - name: Compare build and transfer timings + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs?per_page=100" > .build/release-javascript-measurements/jobs.json + node config/scripts/release-javascript-benchmark.mjs summary + cat .build/release-javascript-measurements/comparison.md >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v7 + if: always() + with: + name: release-javascript-comparison + path: .build/release-javascript-measurements/comparison.md + retention-days: 30 diff --git a/.github/workflows/release-javascript.yml b/.github/workflows/release-javascript.yml new file mode 100644 index 000000000000..2fc33c8d30c3 --- /dev/null +++ b/.github/workflows/release-javascript.yml @@ -0,0 +1,79 @@ +name: Release JavaScript bundle + +on: + workflow_call: + inputs: + ref: + required: true + type: string + outputs: + supported: + value: ${{ jobs.bundle.outputs.supported }} + source_sha: + value: ${{ jobs.bundle.outputs.source_sha }} + secrets: + ORCA_POSTHOG_WRITE_KEY: + required: true + +permissions: + contents: read + +env: + ORCA_BACKGROUND_LAUNCH: '1' + +jobs: + bundle: + runs-on: ubuntu-latest + timeout-minutes: 20 + outputs: + supported: ${{ steps.source.outputs.supported }} + source_sha: ${{ steps.source.outputs.sha }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref }} + persist-credentials: false + + # Older release refs retain their original per-platform build commands. + - name: Resolve bundle source and support + id: source + shell: bash + run: | + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + node -e 'const p = require("./package.json"); console.log("supported=" + Boolean(p.scripts["build:release:javascript"] && p.scripts["build:release:host"]))' >> "$GITHUB_OUTPUT" + + - uses: ./.github/actions/install-node-dependencies + if: steps.source.outputs.supported == 'true' + with: + native-runtime: node + + - uses: ./.github/actions/install-mobile-dependencies + if: steps.source.outputs.supported == 'true' + + - name: Resolve release build identity + id: identity + if: steps.source.outputs.supported == 'true' + run: | + RELEASE_TAG="v$(node -p 'require("./package.json").version')" + export RELEASE_TAG + echo "value=$(node config/scripts/release-javascript-artifact.mjs identity)" >> "$GITHUB_OUTPUT" + + - name: Build and archive release JavaScript + if: steps.source.outputs.supported == 'true' + env: + NODE_OPTIONS: --max-old-space-size=4096 + ORCA_BUILD_IDENTITY: ${{ steps.identity.outputs.value }} + ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token + ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }} + run: | + pnpm run build:release:javascript + node config/scripts/release-javascript-artifact.mjs pack + + - uses: actions/upload-artifact@v7 + if: steps.source.outputs.supported == 'true' + with: + name: release-javascript + path: .build/release-javascript/ + compression-level: 0 + retention-days: 7 + if-no-files-found: error diff --git a/.github/workflows/release-mac-build.yml b/.github/workflows/release-mac-build.yml index 80694364bae4..22558411c8c1 100644 --- a/.github/workflows/release-mac-build.yml +++ b/.github/workflows/release-mac-build.yml @@ -13,6 +13,11 @@ on: description: release-cut workflow run that requested this build required: true type: string + javascript_source_sha: + description: Commit of the shared JavaScript artifact; empty keeps the legacy build + required: false + default: '' + type: string permissions: # actions: read downloads the orcad template the parent release-cut run built. @@ -143,13 +148,29 @@ jobs: run-id: ${{ inputs.release_run_id }} github-token: ${{ github.token }} + - name: Download release JavaScript from the release run + if: inputs.javascript_source_sha != '' + uses: actions/download-artifact@v8 + with: + name: release-javascript + path: .build/release-javascript + run-id: ${{ inputs.release_run_id }} + github-token: ${{ github.token }} + - name: Build app - run: pnpm build:release + run: | + if [ -n "$ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA" ]; then + node config/scripts/release-javascript-artifact.mjs restore + pnpm run build:release:host + else + pnpm run build:release + fi env: # Why: Vite's web build crossed Node's default old-space ceiling on # the macOS release runner, leaving v1.4.2-rc.8 as an incomplete draft. NODE_OPTIONS: --max-old-space-size=4096 ORCA_BUILD_IDENTITY: ${{ steps.tag-classify.outputs.identity }} + ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ inputs.javascript_source_sha }} ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }} # Fail the release rather than ship a relay that cannot launch outside diff --git a/.github/workflows/release-ref-validation.yml b/.github/workflows/release-ref-validation.yml index 6995f9db1742..665967af0c32 100644 --- a/.github/workflows/release-ref-validation.yml +++ b/.github/workflows/release-ref-validation.yml @@ -35,4 +35,3 @@ jobs: pnpm exec vitest run --config config/vitest.config.ts config/scripts/workflow-ref-reachability.test.mjs config/scripts/workflow-ref-mirror-case-safety.test.mjs - config/scripts/dev-channel-windows-workflow-contract.test.mjs diff --git a/.github/workflows/skill-update-roundtrip.yml b/.github/workflows/skill-update-roundtrip.yml index 96de11012756..813bfffdffbc 100644 --- a/.github/workflows/skill-update-roundtrip.yml +++ b/.github/workflows/skill-update-roundtrip.yml @@ -10,7 +10,6 @@ on: - 'skills/**' - 'resources/skills/**' - 'config/scripts/verify-skill-update-roundtrip.mjs' - - 'config/scripts/skill-update-roundtrip-workflow.test.mjs' - 'src/main/skills/skill-freshness-eligibility.ts' - 'src/shared/skill-freshness.ts' - '.github/workflows/skill-update-roundtrip.yml' diff --git a/.github/workflows/ssh-hostile-hosts.yml b/.github/workflows/ssh-hostile-hosts.yml index c96382dbb40b..4ab295aa26af 100644 --- a/.github/workflows/ssh-hostile-hosts.yml +++ b/.github/workflows/ssh-hostile-hosts.yml @@ -88,18 +88,13 @@ jobs: if-no-files-found: error musl_slot: - needs: glibc_slot + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} runs-on: ubuntu-22.04 timeout-minutes: 25 steps: - uses: actions/checkout@v6 with: persist-credentials: false - # Why chained after the glibc slot: the musl build merges into the same prebuild manifest. - - uses: actions/download-artifact@v8 - with: - name: hostile-hosts-glibc-slot - path: out/orcad-prebuilds - uses: ./.github/actions/restore-pnpm-verification id: pnpm-verification with: @@ -121,12 +116,12 @@ jobs: npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")" pnpm install --frozen-lockfile --ignore-scripts pnpm build:orcad-prebuilds --slot=linux-x64-musl - pnpm build:orcad-prebuilds --require-slots linux-x64-glibc,linux-x64-musl + pnpm build:orcad-prebuilds --require-slots linux-x64-musl pnpm build:orcad-prebuilds --slot=linux-x64-musl --smoke MUSL_SLOT - uses: actions/upload-artifact@v7 with: - name: hostile-hosts-slots + name: hostile-hosts-musl-slot path: out/orcad-prebuilds include-hidden-files: true retention-days: 1 @@ -135,7 +130,7 @@ jobs: # Design D6 rung B: the CentOS 7 cell lands on the glibc 2.17 compat slot, built exactly as the # headless-server compat lane builds it (see linux_glibc217_compat in node-server-tests.yml). glibc217_slot: - needs: musl_slot + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} runs-on: ubuntu-22.04 timeout-minutes: 25 env: @@ -145,11 +140,6 @@ jobs: with: persist-credentials: false - uses: ./.github/actions/install-node-dependencies - # Why chained after musl: the compat build merges into the same prebuild manifest. - - uses: actions/download-artifact@v8 - with: - name: hostile-hosts-slots - path: out/orcad-prebuilds - name: Build and smoke the glibc 2.17 compat slot under the glibc-217 Node run: | compat_node="$(node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --print-runtime)" @@ -167,20 +157,20 @@ jobs: set -eu export PATH="$(dirname "$COMPAT_NODE"):$PATH" node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 - node config/scripts/build-orcad-prebuilds.mjs --require-slots linux-x64-glibc,linux-x64-musl,linux-x64-glibc217 + node config/scripts/build-orcad-prebuilds.mjs --require-slots linux-x64-glibc217 # The image's devtoolset LD_LIBRARY_PATH must not stand in for a host C++ runtime. env -u LD_LIBRARY_PATH node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --smoke GLIBC217_COMPAT_SLOT - uses: actions/upload-artifact@v7 with: - name: hostile-hosts-slots-compat + name: hostile-hosts-glibc217-slot path: out/orcad-prebuilds include-hidden-files: true retention-days: 1 if-no-files-found: error hosts: - needs: glibc217_slot + needs: [glibc_slot, musl_slot, glibc217_slot] runs-on: ubuntu-24.04 timeout-minutes: 60 env: @@ -194,8 +184,12 @@ jobs: native-runtime: node - uses: actions/download-artifact@v8 with: - name: hostile-hosts-slots-compat - path: out/orcad-prebuilds + pattern: hostile-hosts-*-slot + path: ${{ runner.temp }}/hostile-host-prebuild-lanes + - name: Merge verified Linux slots + run: | + node config/scripts/merge-orcad-prebuilds.mjs "$RUNNER_TEMP"/hostile-host-prebuild-lanes/* + pnpm build:orcad-prebuilds --require-slots linux-x64-glibc,linux-x64-musl,linux-x64-glibc217 # The deploy materializes rung A, B and C addons from this template; only the x64 Linux # slots exist here, so it is built for those two, and glibc217 is staged beside its base. - name: Build the orcad template and relay @@ -206,7 +200,7 @@ jobs: env: ORCA_RUN_SSH_HOSTILE_HOSTS: '1' ORCA_SSH_HOSTILE_HOST_CELLS: ${{ github.event.inputs.cells || '' }} - run: pnpm test src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts + run: pnpm test:node src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts # Design D6 on macOS: the runner is the SSH host. A user-level sshd on a loopback port logs in # as the runner user with PATH cut to the toolchain shims and /usr/bin:/bin, so Homebrew's node @@ -245,4 +239,4 @@ jobs: env: ORCA_RUN_SSH_HOSTILE_HOSTS: '1' ORCA_SSH_HOSTILE_HOST_CELLS: ${{ matrix.cell }} - run: pnpm test src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts + run: pnpm test:node src/main/ssh/ssh-relay-hostile-hosts.docker.test.ts diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index fed0c4c31d1c..efb236d0c0e6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -48,6 +48,10 @@ jobs: - name: Install Electron package binary for tests run: node config/scripts/install-electron-package-binary.mjs + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: config/bun-version + - uses: actions/download-artifact@v8 continue-on-error: true with: @@ -61,7 +65,7 @@ jobs: run: | ORCA_SHARD_SOURCE_SHA="$(git rev-parse HEAD)" export ORCA_SHARD_SOURCE_SHA - pnpm exec vitest run --config config/vitest.config.ts \ + node config/scripts/run-vitest.mjs run --config config/vitest.config.ts \ --shard=${{ matrix.shard.index }}/${{ matrix.shard.count }} \ ${{ inputs.runner == 'ubuntu-24.04-arm' && '--maxWorkers=4' || '' }} @@ -93,9 +97,8 @@ jobs: - uses: ./.github/actions/install-node-dependencies with: - native-runtime: node + native-runtime: none node-version: ${{ matrix.node }} - cache-electron-package: 'true' cache-dependency-path: | pnpm-lock.yaml cloud/pnpm-lock.yaml diff --git a/.oxlintrc.json b/.oxlintrc.json index 7a0753c06e27..fec10ce318ec 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -208,6 +208,7 @@ } ], "ignorePatterns": [ + "src/main/acp/generated/acp-protocol.generated.ts", "src/shared/rpc-contract/rpc-params-catalog.generated.ts", "**/node_modules", "**/dist", diff --git a/AGENTS.md b/AGENTS.md index f991769cc1fa..451345c8a0ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,7 +75,7 @@ Orca targets macOS, Linux, and Windows. Keep all platform-dependent behavior beh - **File paths**: Use `path.join` or Electron/Node path utilities — never assume `/` or `\`. - **Windows terminal shells**: `--shell` picks the shell a terminal _is_; `--command` is typed into whatever shell the host spawned, so a shell choice routed through `command` silently becomes a child process. See [`docs/reference/windows-terminal-shell-selection.md`](./docs/reference/windows-terminal-shell-selection.md). - **Windows setup scripts**: the setup/issue-command runner is a `.cmd` batch file unless the script starts with a `#!` line — never derive that from the user's terminal-shell preference, and never launch a `.cmd` runner with a bare `cmd.exe /c` from a Git Bash pane (MSYS rewrites the `/c`). See [`docs/reference/windows-setup-shell.md`](./docs/reference/windows-setup-shell.md). -- **Windows child processes**: start them through `runProcess`/`spawnProcess` in `src/shared/child-process/` — never `child_process` directly. It pins `windowsHide`, refuses `shell: true`, and encodes `.cmd`/`.bat` arguments so neither `CommandLineToArgvW` nor `cmd.exe` mangles them. A ratchet test fails on any new direct import. Recognised npm/pnpm `.cmd` shims are resolved to their real target so the spawn skips `cmd.exe` entirely; see [`docs/reference/windows-cmd-shim-resolution.md`](./docs/reference/windows-cmd-shim-resolution.md) before adding a shim shape or debugging one. +- **Windows child processes**: start them through `runProcess`/`spawnProcess` in `src/shared/child-process/` — never `child_process` directly. It pins `windowsHide`, refuses `shell: true`, and encodes `.cmd`/`.bat` arguments so neither `CommandLineToArgvW` nor `cmd.exe` mangles them. Recognised npm/pnpm `.cmd` shims are resolved to their real target so the spawn skips `cmd.exe` entirely; see [`docs/reference/windows-cmd-shim-resolution.md`](./docs/reference/windows-cmd-shim-resolution.md) before adding a shim shape or debugging one. - **Ripgrep**: Orca bundles `rg` for every platform, WSL, and SSH remotes. Spawn it through `spawnBundledRipgrep` (main) or `resolveRelayRipgrepCommand` (relay), never a bare `'rg'` — Windows resolves a bare name in the spawn cwd before PATH. Don't add git/readdir fallbacks locally; the relay's chain exists only for hosts an upload never reached. - **Windows process enumeration**: read the table through `src/main/windows/windows-process-table.ts`, never by forking `powershell.exe`. See [`docs/reference/windows-process-enumeration.md`](./docs/reference/windows-process-enumeration.md). - **Windows MSYS/Git Bash panes**: their children break away from the per-PTY job unless it is created without `JOB_OBJECT_LIMIT_BREAKAWAY_OK`, and a `conpty.node` built before that fix passes every existing gate. Before changing the per-PTY job or debugging `windows-msys-job.win32.test.ts`, read [`docs/reference/windows-msys-job-breakaway.md`](./docs/reference/windows-msys-job-breakaway.md). diff --git a/FORK.md b/FORK.md index 0ebea4261c66..aa8078bc4231 100644 --- a/FORK.md +++ b/FORK.md @@ -868,6 +868,35 @@ sync hit exactly that (#23062 landed two commits later) and was fixed by merging ### Sync log +**1.4.214 → 1.4.214** (`2b0ce17514..cc6006779f`, 164 upstream commits, eight conflicts) + +- `package.json` still says 1.4.214, so the branch name repeats a fourth time. +- `config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjs` — upstream deleted it + (#25791, "Remove low-value test inventories"). Took the deletion, so the fork's two workflow + entries in it are gone with it; the earlier note about re-adding them no longer applies. +- `DiffCommentPopover.tsx`, `right-panel-comment-composer.tsx` — upstream #25480 swapped their + `